|
| 1 | +--- |
| 2 | +eatmycode_version: "1.1.0" |
| 3 | +--- |
| 4 | + |
1 | 5 | # Arch — CPU architecture layer |
2 | 6 |
|
3 | 7 | ## Goal |
4 | 8 |
|
5 | | -Own everything CPU-specific: the Unicorn `Uc` instance, register access, stack |
6 | | -primitives, disassembler/assembler, CPU models, and per-arch calling |
7 | | -conventions. This is the bottom layer — everything else reads `ql.arch`; arch |
8 | | -depends only on Unicorn/Capstone/Keystone. Mature released infrastructure; |
9 | | -maturity-based status. |
| 9 | +Own everything CPU-specific: the Unicorn `Uc` instance, register access, |
| 10 | +stack primitives, disassembler/assembler, CPU models, and per-arch calling |
| 11 | +conventions. This is the bottom layer: every other module reads `ql.arch`; |
| 12 | +arch depends only on Unicorn/Capstone/Keystone (with the documented |
| 13 | +exceptions below). No roadmap milestone applies; maturity-based status. |
10 | 14 |
|
11 | 15 | ## Status |
12 | 16 |
|
13 | | -`done` — all architectures exercised by the CI suite; CPU model selection |
14 | | -covered by `tests/test_cpu_models.py`. |
| 17 | +`done` — all ten architectures are exercised by the CI suites; CPU model |
| 18 | +selection is covered by `tests/test_cpu_models.py` (observed: |
| 19 | +`Ran 7 tests … OK`). |
15 | 20 |
|
16 | 21 | ## Code Structure |
17 | 22 |
|
18 | 23 | | File | Role | |
19 | 24 | | ---- | ---- | |
20 | | -| `qiling/arch/arch.py` | Abstract base `QlArch`: owns `uc`, `regs`, stack push/pop, save/restore, disassembler | |
21 | | -| `qiling/arch/x86.py` | `QlArchIntel` base + `QlArchA8086`/`QlArchX86`/`QlArchX8664`, GDT/MSR wiring | |
22 | | -| `qiling/arch/arm.py`, `arm64.py` | ARM/AArch64, thumb handling, coprocessor access | |
23 | | -| `qiling/arch/cortex_m.py` | Cortex-M on top of ARM: NVIC-style interrupt entry/exit for MCU mode | |
24 | | -| `qiling/arch/mips.py`, `riscv.py`, `riscv64.py`, `ppc.py` | Remaining architectures | |
25 | | -| `qiling/arch/register.py` | `QlRegisterManager` — attribute-style register read/write | |
| 25 | +| `qiling/arch/arch.py` | Abstract base `QlArch`: owns `uc`, `regs`, stack push/pop, save/restore, disassembler/assembler | |
| 26 | +| `qiling/arch/x86.py`, `x86_utils.py`, `x86_const.py`, `msr.py` | `QlArchIntel` base + `QlArchA8086`/`QlArchX86`/`QlArchX8664`; GDT/segment setup; MSRs | |
| 27 | +| `qiling/arch/arm.py`, `arm_utils.py`, `arm_const.py`, `cpr.py` | ARM: thumb handling, coprocessor registers | |
| 28 | +| `qiling/arch/arm64.py`, `arm64_const.py`, `cpr64.py` | AArch64 | |
| 29 | +| `qiling/arch/cortex_m.py`, `cortex_m_const.py` | Cortex-M on top of ARM: `QlInterruptContext`, NVIC-style exception entry/exit for MCU mode; uses `MultiTaskUnicorn` | |
| 30 | +| `qiling/arch/mips.py`, `riscv.py`, `riscv64.py`, `ppc.py` (+ `*_const.py`) | Remaining architectures | |
| 31 | +| `qiling/arch/register.py` | `QlRegisterManager`: attribute-style register read/write | |
26 | 32 | | `qiling/arch/models.py` | CPU model enums (`X86_CPU_MODEL` … `RISCV64_CPU_MODEL`) | |
27 | | -| `qiling/arch/msr.py`, `cpr.py`, `cpr64.py` | x86 MSRs, ARM/ARM64 coprocessor registers | |
28 | | -| `qiling/arch/utils.py` | `QlArchUtils`: disassembly output for verbose/trace modes | |
29 | | -| `qiling/cc/__init__.py` + `intel.py`, `arm.py`, `mips.py`, `ppc.py`, `riscv.py` | Calling conventions (arg/retval marshalling) consumed by `qiling/os/fcall.py` | |
| 33 | +| `qiling/arch/utils.py` | `QlArchUtils` (disassembly output for verbose modes) and the `assembler()` factory | |
| 34 | +| `qiling/cc/__init__.py`, `intel.py`, `arm.py`, `mips.py`, `ppc.py`, `riscv.py` | Calling conventions (argument/return marshalling) consumed by `qiling/os/fcall.py` | |
| 35 | + |
| 36 | +## Language and Conventions |
| 37 | + |
| 38 | +Python; root rules apply. Local patterns: |
| 39 | + |
| 40 | +- One class per architecture named `QlArch<ENUMNAME>` so `select_arch` |
| 41 | + can derive it (`qiling/utils.py:376-406`). |
| 42 | +- Register tables live in `*_const.py` as name→Unicorn-constant maps and are |
| 43 | + handed to `QlRegisterManager` (`qiling/arch/register.py:16`). |
| 44 | +- Calling conventions are small classes named after the ABI (`cdecl`, |
| 45 | + `stdcall`, `ms64`, `amd64`, `macosx64`, `aarch64`, `aarch32`, `mipso32`; |
| 46 | + `qiling/cc/intel.py:61-95`, `qiling/cc/arm.py:35-40`, |
| 47 | + `qiling/cc/mips.py:9`), all deriving from `QlCommonBaseCC` |
| 48 | + (`qiling/cc/__init__.py:110`). |
| 49 | +- `TODO.md:638-644` records that GDT/segment validation in |
| 50 | + `qiling/arch/x86_utils.py` uses `assert`; treat that as observed, not a |
| 51 | + convention to copy. |
| 52 | + |
| 53 | +## Design and Invariants |
| 54 | + |
| 55 | +- `QlArch` creates the `Uc` lazily as a cached property (`qiling/arch/arch.py:34`) |
| 56 | + and exposes `regs` (`:42`), `stack_push/stack_pop` (`:52`/`:66`), |
| 57 | + `save/restore` via `UcContext` (`:108`/`:112`), `disassembler` (`:117`), |
| 58 | + and `assembler` (`:125`). Everything above arch must go through these. |
| 59 | +- `ql.uc` is a proxy to `arch.uc` (`qiling/core.py:479`); there is exactly |
| 60 | + one Unicorn instance per `Qiling` (the multi-Unicorn threading idea in |
| 61 | + `TODO.md:462-488` is a proposal only). |
| 62 | +- **Layering exceptions**: `qiling/arch/cortex_m.py:22` imports |
| 63 | + `MultiTaskUnicorn` from `qiling/extensions/multitask.py`; |
| 64 | + `qiling/arch/utils.py:94` lazily imports the r2 extension for symbol |
| 65 | + names; `qiling/arch/x86_utils.py:10` imports `QlMemoryManager` from OS |
| 66 | + base for the GDT manager's constructor annotation. Do not add further |
| 67 | + upward imports; see [os-baremetal.md](os-baremetal.md) for the multitask |
| 68 | + contract. |
| 69 | +- CPU models are selected by the `cputype` kwarg and validated by |
| 70 | + `select_arch`; a model belongs to exactly one enum in |
| 71 | + `qiling/arch/models.py`. |
| 72 | +- Endianness and thumb are constructor inputs for ARM/MIPS only |
| 73 | + (`qiling/utils.py:379-386`). |
30 | 74 |
|
31 | 75 | ## Key Types and Entry Points |
32 | 76 |
|
33 | | -- `qiling/arch/arch.py:22` - `QlArch(ABC)` - cached properties `uc` (`:34`), `regs` (`:42`), `stack_push/stack_pop` (`:52`/`:66`), `save/restore` via UcContext (`:108`/`:112`), `disassembler` (`:117`). |
34 | | -- `qiling/arch/register.py:11` - `QlRegisterManager` - `ql.arch.regs.rax`-style access, backed by per-arch `*_const.py` tables. |
35 | | -- `qiling/arch/x86.py:22,53,79,111` - `QlArchIntel` / `QlArchA8086` / `QlArchX86` / `QlArchX8664`. |
36 | | -- `qiling/arch/cortex_m.py:67` - `QlArchCORTEX_M(QlArchARM)` - plus `QlInterruptContext` (`:25`) for exception entry/exit in MCU mode. |
37 | | -- `qiling/arch/models.py` - CPU model enums selected via the `cputype` kwarg (resolved in `select_arch`, `qiling/utils.py:376`). |
38 | | -- `qiling/cc/__init__.py:9` - `QlCC` - abstract calling convention; `QlCommonBaseCC` (`:110`); e.g. `qiling/cc/intel.py` defines `cdecl`/`stdcall`/`ms64`/`macosx64`. |
| 77 | +- `qiling/arch/arch.py:22` - `QlArch(ABC)` - base class; see properties |
| 78 | + above. |
| 79 | +- `qiling/arch/register.py:11` - `QlRegisterManager` - `ql.arch.regs.rax` |
| 80 | + style access (`__getattr__` `:35`, `__setattr__` `:44`), plus |
| 81 | + `read/write` by name or Unicorn id (`:53`/`:62`). |
| 82 | +- `qiling/arch/x86.py:22,53,79,111` - `QlArchIntel` / `QlArchA8086` / |
| 83 | + `QlArchX86` / `QlArchX8664`. |
| 84 | +- `qiling/arch/cortex_m.py:67` - `QlArchCORTEX_M(QlArchARM)` - |
| 85 | + `interrupt_handler` (`:146`) consults `ql.hw.nvic` and enters the handler |
| 86 | + inside `QlInterruptContext` (`:25`). |
| 87 | +- `qiling/arch/utils.py:106` - `assembler(arch, endianness, is_thumb)` - |
| 88 | + Keystone factory used by `qltool code --format asm`. |
| 89 | +- `qiling/cc/__init__.py:9` - `QlCC` - abstract calling convention |
| 90 | + (`getRawParam`, `setReturnValue`, …); `QlCommonBaseCC` (`:110`). |
| 91 | +- `qiling/utils.py:376` - `select_arch(archtype, cputype, endian, thumb)` - |
| 92 | + the only construction path (`qiling/core.py:154`). |
39 | 93 |
|
40 | 94 | ## Interactions |
41 | 95 |
|
42 | | -- Instantiated first by [core.md](core.md) (`qiling/core.py:154`); `Qiling.uc` proxies `arch.uc` (`qiling/core.py:479`). |
43 | | -- [loader.md](loader.md) and the OS layers use `arch.regs` and stack primitives to set up entry state. |
44 | | -- `qiling/cc/` is consumed by `QlFunctionCall` in [os-base.md](os-base.md) for API argument marshalling. |
45 | | -- [debugger.md](debugger.md) reads/writes registers through this layer. |
| 96 | +- Constructed first by [core.md](core.md); `QlCoreStructs`/`QlCoreHooks` |
| 97 | + are initialized from `arch.endian`/`arch.bits`/`arch.uc` |
| 98 | + (`qiling/core.py:157-158`). |
| 99 | +- [loader.md](loader.md) and the OS layers set initial register/stack state |
| 100 | + through `arch.regs` and the stack primitives. |
| 101 | +- `qiling/cc/` is consumed by `QlFunctionCall` ([os-base.md](os-base.md)) |
| 102 | + and by the Windows fcall selector (`qiling/os/windows/windows.py:41-65`). |
| 103 | +- POSIX syscall ABIs are a separate table in |
| 104 | + `qiling/os/posix/syscall/abi/` ([os-posix.md](os-posix.md)), not here. |
| 105 | +- [debugger.md](debugger.md) reads/writes registers through this layer and |
| 106 | + ships per-arch GDB target XML. |
| 107 | +- [hw.md](hw.md) NVIC peripherals call `arch.interrupt_handler` |
| 108 | + (`qiling/hw/intc/cm_nvic.py:55`, `:127`). |
46 | 109 |
|
47 | 110 | ## How to Test |
48 | 111 |
|
49 | 112 | ```sh |
50 | | -cd tests && python3 test_cpu_models.py # pass = unittest "OK", exit code 0 |
| 113 | +cd tests && python3 test_cpu_models.py # pass = "Ran 7 tests … OK", exit 0 |
51 | 114 | ``` |
52 | 115 |
|
53 | | -- Broader arch coverage comes for free from `test_shellcode.py` (5 archs) and the per-OS suites. |
| 116 | +- Broader coverage comes from `tests/test_shellcode.py` (five archs) and the |
| 117 | + per-OS suites; RISC-V has `tests/test_riscv.py` (`Ran 4 tests … OK`). |
| 118 | +- There is no unit test for `qiling/cc/`; it is proven through Windows API |
| 119 | + calls (Windows host) and UEFI (`tests/test_uefi.py`). |
| 120 | + |
| 121 | +## Review and Refactor Guide |
| 122 | + |
| 123 | +- **New architecture**: add `qiling/arch/<name>.py` with `QlArch<ENUM>`, |
| 124 | + a `*_const.py` register table, a `qiling/cc/` convention, a |
| 125 | + `qiling/os/posix/syscall/abi/` ABI, a GDB XML directory, and the enum in |
| 126 | + `qiling/const.py:15`; then extend `select_arch`. |
| 127 | +- **Register changes** affect `qiling/debugger/gdb/xml/<arch>/` and |
| 128 | + `qiling/debugger/qdb/arch/`; run `tests/test_debugger.py` and |
| 129 | + `tests/test_qdb.py`. |
| 130 | +- **Do not** put OS-specific state (segment selectors for Windows, TLS) in |
| 131 | + arch classes; the OS layer owns that (`qiling/os/windows/windows.py:132`). |
| 132 | +- Improvement candidate (proposal): move the thumb-bit fixup out of |
| 133 | + `Qiling.emu_start` (`qiling/core.py:759`) into `QlArchARM` once Unicorn |
| 134 | + reflects thumb mode in `cpsr` at init. Success check: |
| 135 | + `tests/test_shellcode.py::test_linux_arm_thumb` and `tests/test_qdb.py` |
| 136 | + stay green. |
54 | 137 |
|
55 | 138 | ## Open Gaps / Roadmap |
56 | 139 |
|
57 | | -- PowerPC and RISC-V have fewer OS-level tests than x86/ARM/MIPS (no dedicated POSIX suite beyond `tests/test_riscv.py`). |
58 | | -- Thumb state handling has a known fixup in `Qiling.emu_start` (`qiling/core.py:743`) rather than in the arch layer itself. |
| 140 | +- PowerPC has no OS-level test suite beyond CPU model selection; RISC-V has |
| 141 | + four Linux tests. |
| 142 | +- 8086 GDB stop replies use the wrong register names (FIXME at |
| 143 | + `qiling/debugger/gdb/gdb.py:242`). |
| 144 | +- `TODO.md:655-657` notes the 32-bit GDT data segment is built with |
| 145 | + `QL_X86_A_PRIV_0` (`qiling/arch/x86_utils.py:167`); the 64-bit manager |
| 146 | + already uses ring 3 (`:200`, `:215`). |
0 commit comments