Skip to content

Latest commit

 

History

History
129 lines (102 loc) · 6.56 KB

File metadata and controls

129 lines (102 loc) · 6.56 KB

mwemu — Modular Architecture (design)

Status: draft / proposal. This documents the target layering for splitting libmwemu into focused, reusable crates, and the order to get there without hitting circular dependencies.

Motivation

libmwemu has grown into a monolith that mixes several independent concerns:

  • a binary loader/parser (PE32/PE64, ELF, Mach-O),
  • the CPU engines (x86/x64 and AArch64 instruction emulation),
  • OS API emulation (Windows / Linux / macOS userland + syscalls + structures),
  • the memory model (maps), registers, config, serialization,
  • and the orchestration that ties it all into the Emu object.

Splitting these makes each piece reusable on its own (e.g. the PE parser for any tool, not just mwemu), shrinks the compile/test surface per crate, clarifies ownership, and lets the engines/OS layers evolve independently.

Target layering

                         mwemu (CLI)
                             │
                        libmwemu  ── thin orchestrator: the Emu that wires
                             │        a Cpu + an Os + memory, plus the run
        ┌──────────┬─────────┼───────────┬──────────┐   loop, serialization,
   mwemu-x86  mwemu-aarch64  mwemu-win32  mwemu-pe   …   and the public API.
        └──────────┴─────────┼───────────┴──────────┘
                        mwemu-core  ── shared contract: memory (Maps) + Memory
                                       trait, register banks, Config, errors,
                                       shared structures, and the Cpu/Os traits.

The one rule that makes this work: every crate depends downward on mwemu-core. Nothing depends on libmwemu except the CLI. That breaks the otherwise-circular Emu ↔ engine ↔ Emu relationship.

Crates

Crate Responsibility Depends on
mwemu-core Memory model + Memory trait, registers, Config, error types, shared structures, the Cpu / Os traits. The shared contract.
mwemu-pe Pure PE32/PE64 parsing → data structs (headers, sections, import/export/reloc/resource tables). No emulator state. (nothing / core for a memory-writer trait if needed)
mwemu-x86 x86/x64 decode + execute. Implements Cpu. mwemu-core
mwemu-aarch64 AArch64 decode + execute. Implements Cpu. mwemu-core
mwemu-win32 Windows API/syscall emulation + loading. Implements Os. mwemu-core, mwemu-pe
(later) mwemu-linux / mwemu-macos Linux / macOS OS layers. Implement Os. mwemu-core, mwemu-pe(elf/macho)
libmwemu The Emu orchestrator: core state + Box<dyn Cpu> + Box<dyn Os>, the run loop, serialization, the public API consumed by pymwemu/cmwemu/mwemu-mcp. all of the above
mwemu Command-line front end. libmwemu

mwemu-core: the contract

This is the hard part — deciding the boundary. Candidates to live here:

  • Memory: the Maps model + a Memory trait (read/write_*, is_mapped, get_mem_by_addr_mut, …) so engines and loaders touch memory through an interface, not the concrete Emu.
  • Registers: the x86 Regs64 and RegsAarch64 banks (or a shared trait).
  • Config, error types (MwemuError).
  • Shared Windows structures — open question (see below).
  • Traits:
    • trait Cpudecode, step, register access, current PC/SP. Implemented by mwemu-x86 and mwemu-aarch64.
    • trait Os — syscall/API dispatch, process/PEB/TEB setup. Implemented by mwemu-win32 (and later linux/macos).

The exact trait signatures are the key design work; they must be expressive enough for libmwemu to drive any engine/OS without depending on the concrete implementation.

mwemu-pe: split "parse" from "bind"

Today the PE layer mixes two concerns in the same structs:

  1. Parsing (headers, sections, the import/export/reloc/resource tables as data) — already free of Emu. → goes to mwemu-pe.
  2. Binding into the emulator (iat_binding, apply_relocations, delay_load_binding, get_dependencies) — takes &mut Emu, writes to emu.maps, resolves APIs via winapi. → stays in libmwemu/win32, now consuming mwemu-pe's parsed output + a Memory writer.

So mwemu-pe is not a copy-paste of the existing files; the binding/reloc methods must be peeled off the PE structs first. The payoff is a PE parser any project can use standalone.

Engines & OS layers

  • mwemu-x86 / mwemu-aarch64 are the bulk of today's libmwemu. They implement Cpu against mwemu-core's memory + register types.
  • mwemu-win32 implements Os; it owns the syscall table, API stubs, PEB/TEB, and the PE→memory binding (using mwemu-pe).

Phased plan

  1. Design (this doc) — agree the mwemu-core boundary and the Cpu/Os trait shapes.
  2. Extract mwemu-core — the shared types + traits. Biggest enabler; until it exists, any satellite extraction hits the circular-dep wall.
  3. mwemu-pe — do the parse/bind split. Good pilot of the pattern.
  4. mwemu-x86 behind the Cpu trait — proves the trait is expressive.
  5. mwemu-aarch64 — second Cpu impl.
  6. mwemu-win32 (then linux/macos) behind the Os trait.
  7. libmwemu becomes the thin orchestrator.

Each phase publishes to crates.io and the ones above re-pin (the version-lockstep cost we already know — keep = "0.x" caret ranges).

Risks & open questions

  • Hot-loop dispatch cost. Box<dyn Cpu> in the per-instruction step loop adds dynamic dispatch. Consider enum dispatch (enum Cpu { X86(..), Aarch64(..) }) or generics/monomorphization instead of trait objects in the hot path.
  • Where do Windows structures live? They're used by both mwemu-win32 and parts of loading. Options: in mwemu-core, in mwemu-pe, or a dedicated mwemu-structures crate. Decide before phase 1.
  • Serialization spans all state. Save/restore touches core + engine + OS; needs a cross-crate strategy (likely traits in core, impls per crate).
  • Trait granularity. Too fine = churn; too coarse = leaks implementation. Iterate on Cpu/Os against one real engine (phase 3) before committing.
  • Keep the dependency rule strict. The moment a satellite imports libmwemu, the cycle returns.

Out of scope (for now)

mwemu-linux / mwemu-macos OS layers and any LIEF-backed loader work — revisit after the Windows/x86 path proves the layering.