Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix incorrect reset of the memory layout when switching architecture #91

Merged
merged 4 commits into from
Mar 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,12 @@ jobs:
run: |
sudo apt update
sudo apt upgrade -y
sudo apt install -y build-essential python3-dev python3-pip python3-wheel python3-setuptools
sudo apt install -y build-essential libegl1 libgl1-mesa-glx python3-dev python3-pip python3-wheel python3-setuptools

- name: "Install Pre-requisite (macOS)"
if: matrix.os == 'macos-latest'
run: |
env

- name: "Install Pre-requisite (Windows)"
if: matrix.os == 'windows-latest'
Expand All @@ -64,8 +69,7 @@ jobs:
python --version
python -m pip --version
python -m pip install --upgrade pip setuptools wheel
python -m pip install --user --upgrade -r tests/requirements.txt
python -m pip install --user --upgrade .
python -m pip install --user --upgrade .[all]

- name: "Post build Cemu (Windows)"
if: matrix.os == 'windows-latest'
Expand Down
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
77 changes: 49 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,17 +353,17 @@ 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:
self.find_section(secname)
except KeyError:
raise CemuEmulatorMissingRequiredSection(secname)

text_section = self.find_section(MEMORY_TEXT_SECTION_NAME)
info(f"Using text section {text_section}")

if not self.__generate_text_bytecode():
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
Loading
Loading