Skip to content

Commit

Permalink
correct reset emulator to restore default memory mapping when switchi…
Browse files Browse the repository at this point in the history
…ng architecture (+ minor type hint)
  • Loading branch information
hugsy committed Mar 20, 2024
1 parent ad42631 commit 17b6d7e
Show file tree
Hide file tree
Showing 9 changed files with 131 additions and 113 deletions.
21 changes: 19 additions & 2 deletions cemu/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,36 @@

import pathlib
import sys
import os

import cemu.const
import cemu.core
import cemu.log


def setup_remote_debug(port: int = cemu.const.DEBUG_DEBUGPY_PORT):
assert cemu.const.DEBUG
import debugpy

debugpy.listen(port)
cemu.log.dbg("Waiting for debugger attach")
debugpy.wait_for_client()
cemu.log.dbg("Client connected, resuming session")


def main():
if bool(os.getenv("DEBUG", False)) or "--debug" in sys.argv:
cemu.const.DEBUG = True

if cemu.const.DEBUG:
cemu.log.register_sink(print)
cemu.log.dbg("Starting in Debug Mode")

if len(sys.argv) >= 2 and sys.argv[1] == "cli":
cemu.core.CemuCli(sys.argv[2:])
if "--attach" in sys.argv:
setup_remote_debug()

if "--cli" in sys.argv:
cemu.core.CemuCli(sys.argv)
return

cemu.core.CemuGui(sys.argv)
Expand Down
9 changes: 1 addition & 8 deletions cemu/cli/repl.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,14 @@
import cemu.const
import cemu.core
import cemu.memory
from cemu.emulator import EmulatorState
from cemu.emulator import MEMORY_MAP_DEFAULT_LAYOUT, EmulatorState
from cemu.log import dbg, error, info, ok, warn
from cemu.utils import hexdump

bindings = KeyBindings()

TEXT_EDITOR = os.getenv("EDITOR") or "nano -c"

MEMORY_MAP_DEFAULT_LAYOUT: list[cemu.memory.MemorySection] = [
cemu.memory.MemorySection(".text", 0x00004000, 0x1000, "READ|EXEC"),
cemu.memory.MemorySection(".data", 0x00005000, 0x1000, "READ|WRITE"),
cemu.memory.MemorySection(".stack", 0x00006000, 0x4000, "READ|WRITE"),
cemu.memory.MemorySection(".misc", 0x0000A000, 0x1000, "READ|WRITE|EXEC"),
]


@bindings.add("c-c")
def _(event):
Expand Down
9 changes: 8 additions & 1 deletion cemu/const.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import pathlib

DEBUG = True
DEBUG = False

PROGNAME = "cemu"
AUTHOR = "hugsy"
Expand Down Expand Up @@ -30,6 +30,8 @@
CONFIG_FILEPATH = HOME / ".cemu.ini"
DEFAULT_STYLE_PATH = STYLE_PATH / "default.qss"

DEBUG_DEBUGPY_PORT = 5678

LOG_INSERT_TIMESTAMP = False
LOG_DEFAULT_TIMESTAMP_FORMAT = "%Y/%m/%d - %H:%M:%S"

Expand All @@ -49,3 +51,8 @@

DEFAULT_FONT: str = "Courier"
DEFAULT_FONT_SIZE: int = 10

MEMORY_MAX_SECTION_SIZE: int = 4294967296 # 4GB
MEMORY_TEXT_SECTION_NAME: str = ".text"
MEMORY_STACK_SECTION_NAME: str = ".stack"
MEMORY_DATA_SECTION_NAME: str = ".data"
11 changes: 5 additions & 6 deletions cemu/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
import sys
from typing import TYPE_CHECKING, Union

import cemu.ui.main

if TYPE_CHECKING:
import cemu.ui.main
from cemu.emulator import Emulator

import cemu.arch
import cemu.cli.repl
Expand Down Expand Up @@ -44,7 +46,7 @@ def architecture(self, new_arch: cemu.arch.Architecture):
return

@property
def emulator(self) -> cemu.emulator.Emulator:
def emulator(self) -> "Emulator":
return self.__emulator

@property
Expand Down Expand Up @@ -88,15 +90,13 @@ def CemuGui(args: list[str]) -> None:
from PyQt6.QtGui import QIcon
from PyQt6.QtWidgets import QApplication

from cemu.ui.main import CEmuWindow

cemu.log.dbg("Creating GUI context")
context = GlobalGuiContext()

app = QApplication(args)
app.setStyleSheet(cemu.const.DEFAULT_STYLE_PATH.open().read())
app.setWindowIcon(QIcon(str(cemu.const.ICON_PATH.absolute())))
context.root = CEmuWindow(app)
context.root = cemu.ui.main.CEmuWindow(app)
sys.exit(app.exec())


Expand Down Expand Up @@ -124,4 +124,3 @@ def CemuCli(argv: list[str]) -> None:

instance = cemu.cli.repl.CEmuRepl(args)
instance.run_forever()
return
76 changes: 48 additions & 28 deletions cemu/emulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,46 @@

import cemu.const
import cemu.core
from cemu.exceptions import CemuEmulatorMissingRequiredSection
import cemu.os
from cemu.ui.utils import popup
import cemu.utils

from cemu.log import dbg, error, info, warn

from cemu.const import (
MEMORY_TEXT_SECTION_NAME,
MEMORY_DATA_SECTION_NAME,
MEMORY_STACK_SECTION_NAME,
)

from .arch import is_x86, is_x86_32, x86
from .memory import MemorySection


@unique
class EmulatorState(IntEnum):
# fmt: off
STARTING = 0 # CEmu is starting
NOT_RUNNING = 1 # CEmu is started, but no emulation context is initialized
IDLE = 2 # The VM is running but stopped: used for stepping mode
RUNNING = 3 # The VM is running
TEARDOWN = 5 # Emulation is finishing
FINISHED = 6 # The VM has reached the end of the execution
# fmt: on
INVALID = 0
"""An invalid state, ideally should never be here"""
STARTING = 1
"""CEmu is starting"""
NOT_RUNNING = 2
"""CEmu is started, but no emulation context is initialized"""
IDLE = 3
"""The VM is running but stopped: used for stepping mode"""
RUNNING = 4
"""The VM is running"""
TEARDOWN = 5
"""Emulation is finishing"""
FINISHED = 6
"""The VM has reached the end of the execution"""


MEMORY_MAP_DEFAULT_LAYOUT: list[MemorySection] = [
MemorySection(MEMORY_TEXT_SECTION_NAME, 0x00004000, 0x1000, "READ|EXEC"),
MemorySection(MEMORY_DATA_SECTION_NAME, 0x00005000, 0x1000, "READ|WRITE"),
MemorySection(MEMORY_STACK_SECTION_NAME, 0x00006000, 0x4000, "READ|WRITE"),
]


class EmulationRegisters(collections.UserDict):
Expand Down Expand Up @@ -83,7 +105,7 @@ def reset(self):
self.vm = None
self.code = b""
self.codelines = ""
self.sections = []
self.sections = MEMORY_MAP_DEFAULT_LAYOUT[:]
self.registers = EmulationRegisters(
{name: 0 for name in cemu.core.context.architecture.registers}
)
Expand Down Expand Up @@ -158,10 +180,10 @@ def setup(self) -> None:
)

if not self.__populate_memory():
raise Exception("populate_memory() failed")
raise RuntimeError("populate_memory() failed")

if not self.__populate_vm_registers():
raise Exception("populate_registers() failed")
raise RuntimeError("populate_registers() failed")

if not self.__populate_text_section():
raise Exception("populate_text_section() failed")
Expand All @@ -176,7 +198,7 @@ def __populate_memory(self) -> bool:
error("VM is not initalized")
return False

if len(self.sections) < 0:
if len(self.sections) < 1:
error("No section declared")
return False

Expand Down Expand Up @@ -213,7 +235,7 @@ def __populate_vm_registers(self) -> bool:
# Set the initial IP if unspecified
#
if self.registers[arch.pc] == 0:
section = self.find_section(".text")
section = self.find_section(cemu.const.MEMORY_TEXT_SECTION_NAME)
self.registers[arch.pc] = section.address
warn(
f"No value specified for PC register, setting to {self.registers[arch.pc]:#x}"
Expand All @@ -223,7 +245,7 @@ def __populate_vm_registers(self) -> bool:
# Set the initial SP if unspecified, in the middle of the stack section
#
if self.registers[arch.sp] == 0:
section = self.find_section(".stack")
section = self.find_section(MEMORY_STACK_SECTION_NAME)
self.registers[arch.sp] = section.address + (section.size // 2)
warn(
f"No value specified for SP register, setting to {self.registers[arch.sp]:#x}"
Expand All @@ -235,7 +257,7 @@ def __populate_vm_registers(self) -> bool:
if is_x86_32(arch):
# create fake selectors
## required
text = self.find_section(".text")
text = self.find_section(MEMORY_TEXT_SECTION_NAME)
self.registers["CS"] = int(
x86.X86_32.SegmentDescriptor(
text.address >> 8,
Expand All @@ -246,7 +268,7 @@ def __populate_vm_registers(self) -> bool:
)
)

data = self.find_section(".data")
data = self.find_section(MEMORY_DATA_SECTION_NAME)
self.registers["DS"] = int(
x86.X86_32.SegmentDescriptor(
data.address >> 8,
Expand All @@ -257,7 +279,7 @@ def __populate_vm_registers(self) -> bool:
)
)

stack = self.find_section(".stack")
stack = self.find_section(MEMORY_STACK_SECTION_NAME)
self.registers["SS"] = int(
x86.X86_32.SegmentDescriptor(
stack.address >> 8,
Expand All @@ -275,7 +297,6 @@ def __populate_vm_registers(self) -> bool:
self.registers["ES"] = 0

for regname, regvalue in self.registers.items():
# TODO figure out segmentation on unicorn
if regname in x86.X86_32.selector_registers:
continue

Expand Down Expand Up @@ -332,16 +353,15 @@ def __populate_text_section(self) -> bool:
if not self.vm:
return False

try:
text_section = self.find_section(".text")
except KeyError:
#
# Try to get the 1st executable section. Let the exception propagage if it fails
#
matches = [
section for section in self.sections if section.permission.executable
]
text_section = matches[0]
for secname in (
MEMORY_TEXT_SECTION_NAME,
MEMORY_DATA_SECTION_NAME,
MEMORY_STACK_SECTION_NAME,
):
try:
text_section = self.find_section(secname)
except KeyError:
raise CemuEmulatorMissingRequiredSection(secname)

info(f"Using text section {text_section}")

Expand Down
2 changes: 2 additions & 0 deletions cemu/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
class CemuEmulatorMissingRequiredSection(Exception):
pass
15 changes: 11 additions & 4 deletions cemu/memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

import unicorn

from cemu.const import MEMORY_MAX_SECTION_SIZE

MemoryLayoutEntryType = tuple[str, int, int, str, Optional[pathlib.Path]]

MEMORY_FIELD_SEPARATOR = "|"
Expand Down Expand Up @@ -180,22 +182,27 @@ def __init__(
name: str,
addr: int,
size: int,
perm: str,
perm: str | MemoryPermission,
data_file: Optional[pathlib.Path] = None,
):
if addr < 0 or addr >= 2**64:
if not (0 <= addr < 2**64):
raise ValueError("address")

if len(name.strip()) == 0:
raise ValueError("name")

if size < 0:
if not (0 < size < MEMORY_MAX_SECTION_SIZE):
raise ValueError("size")

self.name = name.strip().lower()
self.address = addr
self.size = size
self.permission = MemoryPermission.from_string(perm)
if isinstance(perm, str):
self.permission = MemoryPermission.from_string(perm)
elif isinstance(perm, MemoryPermission):
self.permission = perm
else:
raise TypeError("permission")
self.file_source = data_file if data_file and data_file.is_file() else None
return

Expand Down
19 changes: 11 additions & 8 deletions cemu/ui/highlighter.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
from pygments.lexers import get_lexer_by_name
from PyQt6.QtGui import QColor, QFont, QSyntaxHighlighter, QTextCharFormat

import cemu.log


class QFormatter(Formatter):
def __init__(self, *args, **kwargs):
Expand All @@ -26,10 +28,10 @@ def __init__(self, *args, **kwargs):
self.styles[str(token)] = qtf
return

def hex2QColor(self, c):
red = int(c[0:2], 16)
green = int(c[2:4], 16)
blue = int(c[4:6], 16)
def hex2QColor(self, color: str):
red = int(color[0:2], 16)
green = int(color[2:4], 16)
blue = int(color[4:6], 16)
return QColor(red, green, blue)

def format(self, tokensource, outfile):
Expand All @@ -56,13 +58,14 @@ def __init__(self, parent, mode):

def highlightBlock(self, text):
cb = self.currentBlock()
p = cb.position()
pos = cb.position()
text = self.document().toPlainText() + "\n"
highlight(text, self.lexer, self.formatter)
for i in range(len(text)):
try:
self.setFormat(i, 1, self.formatter.data[p + i])
except IndexError:
pass
self.setFormat(i, 1, self.formatter.data[pos + i])
except IndexError as ie:
cemu.log.dbg(f"setFormat() failed, reason: {ie}")
break
self.tstamp = time.time()
return
Loading

0 comments on commit 17b6d7e

Please sign in to comment.