Skip to content
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
1 change: 1 addition & 0 deletions AGENT.md
584 changes: 170 additions & 414 deletions ARCHITECTURE.md

Large diffs are not rendered by default.

58 changes: 58 additions & 0 deletions ARCHITECTURE/arch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Arch — CPU architecture layer

## Goal

Own everything CPU-specific: the Unicorn `Uc` instance, register access, stack
primitives, disassembler/assembler, CPU models, and per-arch calling
conventions. This is the bottom layer — everything else reads `ql.arch`; arch
depends only on Unicorn/Capstone/Keystone. Mature released infrastructure;
maturity-based status.

## Status

`done` — all architectures exercised by the CI suite; CPU model selection
covered by `tests/test_cpu_models.py`.

## Code Structure

| File | Role |
| ---- | ---- |
| `qiling/arch/arch.py` | Abstract base `QlArch`: owns `uc`, `regs`, stack push/pop, save/restore, disassembler |
| `qiling/arch/x86.py` | `QlArchIntel` base + `QlArchA8086`/`QlArchX86`/`QlArchX8664`, GDT/MSR wiring |
| `qiling/arch/arm.py`, `arm64.py` | ARM/AArch64, thumb handling, coprocessor access |
| `qiling/arch/cortex_m.py` | Cortex-M on top of ARM: NVIC-style interrupt entry/exit for MCU mode |
| `qiling/arch/mips.py`, `riscv.py`, `riscv64.py`, `ppc.py` | Remaining architectures |
| `qiling/arch/register.py` | `QlRegisterManager` — attribute-style register read/write |
| `qiling/arch/models.py` | CPU model enums (`X86_CPU_MODEL` … `RISCV64_CPU_MODEL`) |
| `qiling/arch/msr.py`, `cpr.py`, `cpr64.py` | x86 MSRs, ARM/ARM64 coprocessor registers |
| `qiling/arch/utils.py` | `QlArchUtils`: disassembly output for verbose/trace modes |
| `qiling/cc/__init__.py` + `intel.py`, `arm.py`, `mips.py`, `ppc.py`, `riscv.py` | Calling conventions (arg/retval marshalling) consumed by `os/fcall.py` |

## Key Types and Entry Points

- `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`).
- `qiling/arch/register.py:11` - `QlRegisterManager` - `ql.arch.regs.rax`-style access, backed by per-arch `*_const.py` tables.
- `qiling/arch/x86.py:22,53,79,111` - `QlArchIntel` / `QlArchA8086` / `QlArchX86` / `QlArchX8664`.
- `qiling/arch/cortex_m.py:67` - `QlArchCORTEX_M(QlArchARM)` - plus `QlInterruptContext` (`:25`) for exception entry/exit in MCU mode.
- `qiling/arch/models.py` - CPU model enums selected via the `cputype` kwarg (resolved in `select_arch`, `qiling/utils.py:376`).
- `qiling/cc/__init__.py:9` - `QlCC` - abstract calling convention; `QlCommonBaseCC` (`:104`); e.g. `cc/intel.py` defines `cdecl`/`stdcall`/`ms64`/`macosx64`.

## Interactions

- Instantiated first by [core.md](core.md) (`qiling/core.py:154`); `Qiling.uc` proxies `arch.uc` (`qiling/core.py:479`).
- [loader.md](loader.md) and the OS layers use `arch.regs` and stack primitives to set up entry state.
- `qiling/cc/` is consumed by `QlFunctionCall` in [os-base.md](os-base.md) for API argument marshalling.
- [debugger.md](debugger.md) reads/writes registers through this layer.

## How to Test

```sh
cd tests && python3 test_cpu_models.py # pass = unittest "OK", exit code 0
```

- Broader arch coverage comes for free from `test_shellcode.py` (5 archs) and the per-OS suites.

## Open Gaps / Roadmap

- PowerPC and RISC-V have fewer OS-level tests than x86/ARM/MIPS (no dedicated POSIX suite beyond `tests/test_riscv.py`).
- Thumb state handling has a known fixup in `Qiling.emu_start` (`qiling/core.py:743`) rather than in the arch layer itself.
48 changes: 48 additions & 0 deletions ARCHITECTURE/cli.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# CLI — qltool and qltui

## Goal

Give users a no-code way to run emulations: `qltool run` executes a binary
against a rootfs, `qltool code` runs shellcode (hex/asm/bin), `qltool
examples` prints usage samples, and `qltool qltui` launches an interactive
TUI that gathers the same options. Mature released infrastructure;
maturity-based status.

## Status

`done` — covered by `tests/test_qltool.py`, which shells out to `qltool` for
run/code subcommands including gdb attach and coverage output.

## Code Structure

| File | Role |
| ---- | ---- |
| `qltool` | Executable argparse CLI; builds kwargs and drives `Qiling` |
| `qltui.py` | questionary/pyfx/termcolor TUI; collects options, returned to qltool |

## Key Types and Entry Points

- `qltool:189` - `run()` - argparse setup with subcommands `run`, `code`, `examples`, `qltui`; enum-mapping actions translate `--arch/--os/--endian/--verbose` strings to `QL_ARCH`/`QL_OS` enums (`qltool:59-75`).
- `qltool:129` - `handle_run(options)` - builds `{'argv': [file]+args, 'rootfs': ...}`.
- `qltool:78` - `handle_code(options)` - reads hex/asm/bin shellcode, assembling asm via `qiling.arch.utils.assembler` (`qltool:104`).
- `qltool:276` - `ql = Qiling(**ql_args)` - the single construction point; then optional Qdb (`:279`), gdbserver (`:285`), coverage-wrapped `ql.run()` (`:306-310`), JSON report (`:312`), exit with `ql.os.exit_code` (`:321`).

## Interactions

- Thin client of [core.md](core.md): constructs `Qiling` and calls `run()`.
- Attaches [debugger.md](debugger.md) via `--gdb host:port` / `--qdb [--rr]`.
- Uses [extensions.md](extensions.md) for `--coverage-file` (drcov) and `--json` report output.
- `qltool examples` mirrors scripts documented in `examples/README.md`.

## How to Test

```sh
cd tests && python3 test_qltool.py # pass = unittest "OK", exit 0
```

- Manual smoke test: `./qltool run -f examples/rootfs/x8664_linux/bin/x8664_hello --rootfs examples/rootfs/x8664_linux` — pass = prints `Hello, World!`.

## Open Gaps / Roadmap

- `qltui.py` (TUI) has no automated tests — interactive only.
- `qltool` predates subcommand-style config files; complex setups (fs mappers, custom hooks) still require the Python API.
61 changes: 61 additions & 0 deletions ARCHITECTURE/core.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Core — the Qiling facade and plumbing

## Goal

Own the public API and object lifecycle of an emulation: the `Qiling` class
composes arch, loader, memory, OS, and (for bare-metal) hardware components,
and exposes hooks, memory/register access, patching, and save/restore to
users. No roadmap milestone applies — this is mature released infrastructure;
status is maturity-based per the control center.

## Status

`done` — exercised by the whole test suite; the How to Test command below
boots shellcode end-to-end through `Qiling.__init__` → `run()` → `emu_start`.

## Code Structure

| File | Role |
| ---- | ---- |
| `qiling/core.py` | `Qiling` class: constructor/composition root, `run`, `emu_start/stop`, `save/restore`, `patch`, properties |
| `qiling/core_hooks.py` | `QlCoreHooks` mixin: wraps Unicorn hooks, dispatches to `Hook` lists |
| `qiling/core_hooks_types.py` | `Hook`, `HookAddr`, `HookIntr`, `HookRet` records |
| `qiling/core_struct.py` | `QlCoreStructs` mixin: endian/bit-width-aware pack/unpack helpers |
| `qiling/utils.py` | Name→class resolution: `select_arch/loader/os/component/debugger`, binary format sniffing, profile loading |
| `qiling/const.py` | Enums: `QL_ARCH`, `QL_OS`, `QL_VERBOSE`, `QL_INTERCEPT`, `QL_STATE`, groupings `QL_OS_POSIX`/`QL_OS_BAREMETAL` |
| `qiling/exception.py` | `QlErrorBase` and ~20 subclasses (`QlErrorArch`, `QlSyscallError`, …) |
| `qiling/host.py` | `QlHost`: describes the *hosting* platform (for pass-through features) |
| `qiling/log.py` | Logger setup, colored/plain formatters, regex filtering behind `Qiling.filter` |
| `qiling/profiles/*.ql` | Default per-OS config (memory layout, kernel uid/gid/pid) merged with user overrides |

## Key Types and Entry Points

- `qiling/core.py:35` - `Qiling(QlCoreHooks, QlCoreStructs)` - the facade; `__init__` composes components in fixed order: arch (`:154`) → mixins (`:157`) → logger → profile (`:178`) → loader (`:183`) → memory (`:188`) → OS (`:189`) → hw if bare-metal (`:191`) → `loader.run()` (`:195`).
- `qiling/core.py:561` - `Qiling.run(begin, end, timeout, count)` - attaches debugger, applies patches, writes exit trap, delegates to `os.run()`.
- `qiling/core.py:743` - `Qiling.emu_start(begin, end, timeout, count)` - thin wrapper over `uc.emu_start`; manages thumb bit, `QL_STATE`, exception re-raise.
- `qiling/core.py:609` / `:658` - `save()` / `restore()` - snapshot regs/mem/fd/os per-component.
- `qiling/core_hooks.py:150` - `QlCoreHooks` - hook registration API: `hook_code` (`:400`), `hook_block` (`:422`), `hook_address` (`:550`), `hook_intno` (`:575`), `hook_mem_read/write` (`:592`/`:610`), `hook_insn` (`:646`), `hook_del` (`:686`).
- `qiling/utils.py:278` - `ql_guess_emu_env(path)` - sniffs arch/OS/endian from pathname, ELF, Mach-O, or PE headers when not given.
- `qiling/utils.py:297,376,409,323,332` - `select_loader/arch/os/component/debugger` - dynamic-import factories; core never imports concrete subclasses.
- `qiling/utils.py:419` - `profile_setup(ostype, user_config)` - YAML for MCU, else ConfigParser over `qiling/profiles/<os>.ql` + user overrides.

## Interactions

- Instantiates every other subsystem: [arch.md](arch.md), [loader.md](loader.md), [os-base.md](os-base.md) (memory + OS), [hw.md](hw.md) (bare-metal only).
- Lazily instantiates [debugger.md](debugger.md) inside `run()` via `select_debugger`.
- [extensions.md](extensions.md) and [cli.md](cli.md) consume only this public API.
- Hook dispatch protocol (`QL_HOOK_BLOCK`, `qiling/const.py:77`) is honored by the OS layers when they intercept syscalls/APIs.

## How to Test

```sh
cd tests && python3 test_shellcode.py # pass = unittest "OK", exit code 0
```

- Exercises `Qiling(code=...)` construction and `run()` across x86/x86-64/ARM/ARM64/MIPS shellcode for Linux and Windows.

## Open Gaps / Roadmap

- `ChangeLog` lags the released version (1.4.6 vs 1.4.8 in `pyproject.toml`).
- `unicorn` is hard-pinned to 2.1.3; upgrading Unicorn is a project-wide event.
- Feature wishlist lives in GitHub issue [#333](https://github.com/qilingframework/qiling/issues/333).
52 changes: 52 additions & 0 deletions ARCHITECTURE/debugger.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Debugger — GDB server and Qdb

## Goal

Let users debug emulated targets: a GDB remote-serial-protocol server so any
GDB/IDA/lldb front end can attach cross-architecture, and Qdb — a built-in
interactive CLI debugger with stepping, branch prediction, and record/replay
reverse debugging. Mature released infrastructure; maturity-based status.

## Status

`done` — Qdb covered by `tests/test_qdb.py`; the GDB server by
`tests/test_debugger.py` (spawns a real client session).

## Code Structure

| File | Role |
| ---- | ---- |
| `qiling/debugger/debugger.py` | Base `QlDebugger` |
| `qiling/debugger/gdb/gdb.py` | `QlGdb`: GDB remote-serial-protocol server |
| `qiling/debugger/gdb/xmlregs.py`, `gdb/xml/` | Target-description XML per arch for modern GDB clients |
| `qiling/debugger/qdb/qdb.py` | `QlQdb`: interactive Cmd-based debugger |
| `qiling/debugger/qdb/arch/` | Per-arch Qdb support (arm, intel, mips) |
| `qiling/debugger/qdb/branch_predictor/` | Predicts branch targets for step/next |
| `qiling/debugger/qdb/render/` | Register/stack/disasm view rendering |

## Key Types and Entry Points

- `qiling/debugger/debugger.py:13` - `QlDebugger` - base; `run()` starts the session.
- `qiling/debugger/gdb/gdb.py:68` - `QlGdb(QlDebugger)` - listens on ip:port, translates RSP packets to Qiling hook/mem/reg operations.
- `qiling/debugger/qdb/qdb.py:59` - `QlQdb(Cmd, QlDebugger)` - CLI loop; `rr` mode enables record/replay reverse debugging.
- Activation: set `ql.debugger = True | "gdb" | "gdb:0.0.0.0:9999" | "qdb" | "qdb:rr"` (`qiling/core.py:437`); instantiated lazily in `Qiling.run` via `select_debugger` (`qiling/utils.py:332`).
- `qltool` flags: `--gdb` and `--qdb` (see [cli.md](cli.md)).

## Interactions

- Instantiated by [core.md](core.md) at `Qiling.run` time, not construction.
- Reads/writes state exclusively through public APIs: registers via [arch.md](arch.md), memory via [os-base.md](os-base.md), breakpoints via `hook_address` ([core.md](core.md)).
- The IDA plugin in [extensions.md](extensions.md) offers an alternative front end over the same public API.

## How to Test

```sh
cd tests && python3 test_qdb.py # pass = unittest "OK", exit 0
```

- GDB server: `cd tests && python3 test_debugger.py` — starts `QlGdb` and drives a scripted client.

## Open Gaps / Roadmap

- Qdb per-arch support covers arm/cortex-m/mips/intel; RISC-V and PPC lack Qdb arch modules (`qiling/debugger/qdb/arch/`).
- Record/replay (`qdb:rr`) stores full state per step — memory-heavy on long runs.
61 changes: 61 additions & 0 deletions ARCHITECTURE/extensions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Extensions — optional tooling on top of the core

## Goal

House everything that builds on the public `Qiling` API without being required
by it: fuzzing (AFL), code-coverage and execution-trace writers, a heap
sanitizer, radare2 and IDA Pro integration, fake stdio pipes, and JSON run
reports. Mature released infrastructure; maturity-based status.

## Status

`done` — history/coverage tracker tested in CI; AFL and r2 integrations need
optional extras (`fuzz`, `RE`); the IDA plugin needs IDA Pro (untested in CI).

## Code Structure

| File | Role |
| ---- | ---- |
| `qiling/extensions/afl/` | `ql_afl_fuzz` (unicornafl bridge) + `QlFuzzer` harness base class |
| `qiling/extensions/coverage/` | `collect_coverage` context manager; writers in `formats/` (drcov, drcov_exact, ezcov, history) |
| `qiling/extensions/tracing/` | Tenet-style execution trace writers (`formats/`) |
| `qiling/extensions/trace.py` | Disassembly tracing: full trace or ring-buffer history |
| `qiling/extensions/sanitizers/heap.py` | Canary-based heap sanitizer (UAF/OOB detection) |
| `qiling/extensions/r2/r2.py` | radare2 (r2libr) integration: sections/symbols/functions/xrefs of the loaded target |
| `qiling/extensions/idaplugin/qilingida.py` | IDA Pro plugin driving Qiling emulation from IDA |
| `qiling/extensions/pipe.py` | Fake stdio streams for hijacking emulated I/O (fuzzing staple) |
| `qiling/extensions/report/report.py` | `generate_report(ql)`: JSON summary of a run |
| `qiling/extensions/multitask.py` | Cooperative-multitask Unicorn wrapper (documented in [os-baremetal.md](os-baremetal.md)) |
| `qiling/extensions/mcu/` | Board/chip definitions (documented in [hw.md](hw.md)) |
| `qiling/extensions/winsdkapi.py` | Windows API signature decorator glue |

## Key Types and Entry Points

- `qiling/extensions/afl/afl.py:21` / `:87` - `ql_afl_fuzz` / `ql_afl_fuzz_custom` - hand control to AFL++ via unicornafl; harness base `QlFuzzer` (`qiling/extensions/afl/qlfuzzer.py:14`).
- `qiling/extensions/coverage/utils.py:48` - `collect_coverage(ql, name, file)` - context manager writing e.g. drcov files (used by `qltool --coverage-file`).
- `qiling/extensions/trace.py:145` / `:180` - `enable_full_trace` / `enable_history_trace` - disassembly tracing via `hook_code`.
- `qiling/extensions/sanitizers/heap.py:16` - `QlSanitizedMemoryHeap` - drop-in replacement for `ql.os.heap` with canaries and free-list checks.
- `qiling/extensions/r2/r2.py:135` - `R2(ql)` - rzpipe-backed analysis of the loaded image.
- `qiling/extensions/pipe.py` - `SimpleInStream`/`SimpleOutStream` (`:62`/`:69`) - assigned to `ql.os.stdin`/`stdout`.
- `qiling/extensions/report/report.py:56` - `generate_report(ql)` - JSON report (used by `qltool --json`).

## Interactions

- Everything here consumes only the public API of [core.md](core.md) (hooks, mem, regs) and [os-base.md](os-base.md) (heap, stdio, syscall/API overrides).
- The heap sanitizer wraps `QlMemoryHeap` from [os-base.md](os-base.md) (demo: `examples/uefi_sanitized_heap.py` with [os-windows.md](os-windows.md) UEFI).
- Fuzzing harnesses live in `examples/fuzzing/` and pair AFL with `pipe.py` and `set_syscall` from [os-posix.md](os-posix.md).
- `qltool` wires in coverage and report generation ([cli.md](cli.md)).

## How to Test

```sh
cd tests && python3 test_history.py # pass = unittest "OK", exit 0
```

- r2 integration (needs `pip install qiling[RE]`): `cd tests && python3 test_r2.py`.
- AFL (needs `pip install qiling[fuzz]` + AFL++): run a harness from `examples/fuzzing/linux_x8664/`.

## Open Gaps / Roadmap

- `afl/` and `r2/` depend on optional extras not installed by default; the IDA plugin cannot be CI-tested (requires an IDA Pro license).
- Two overlapping trace mechanisms exist (`trace.py` and `tracing/`); no unified interface.
53 changes: 53 additions & 0 deletions ARCHITECTURE/hw.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# HW — peripheral emulation for bare-metal targets

## Goal

Emulate memory-mapped peripherals (GPIO, UART/char, SPI, I2C, timers, DMA,
interrupt controllers, flash, …) so MCU firmware runs against realistic
hardware. Peripherals are instantiated from a YAML profile naming which
peripheral class sits at which MMIO base address. Only active in bare-metal
(MCU) mode. Mature released infrastructure; maturity-based status.

## Status

`done` — exercised by `tests/test_mcu.py` against STM32F1/F4 and GD32VF1
firmware images (UART echo, freertos, blink, crc, dma_clock, i2c/spi/lcd).

## Code Structure

| File | Role |
| ---- | ---- |
| `qiling/hw/hw.py` | `QlHwManager`: creates peripherals from profile, maps MMIO, steps them each tick |
| `qiling/hw/peripheral.py` | Base `QlPeripheral` (+ `QlPeripheralUtils`) |
| `qiling/hw/analog/ char/ dma/ flash/ gpio/ i2c/ intc/ math/ mem/ misc/ net/ power/ sd/ spi/ timer/` | One directory per peripheral class, chip-specific implementations inside |
| `qiling/hw/const/` | Register layout constants per chip family |
| `qiling/hw/connectivity.py`, `external_device/` | External-interface plumbing (e.g. connecting a fake device to a bus) |
| `qiling/extensions/mcu/` | Board/chip definitions (stm32f4xx, gd32vf1, nxp, atmel, bes): memory maps naming peripheral class + base address, passed as `env=` to `Qiling` |

## Key Types and Entry Points

- `qiling/hw/hw.py:14` - `QlHwManager` - available as `ql.hw`; created by core only when `ql.baremetal` (`qiling/core.py:191`).
- `qiling/hw/hw.py:23` - `QlHwManager.create(label, struct, base)` - instantiates a peripheral from the profile entry and maps its MMIO region.
- `qiling/hw/hw.py:82` - `QlHwManager.step()` - advances every peripheral one tick; called from the MCU run loop.
- `qiling/hw/peripheral.py:132` - `QlPeripheral(QlPeripheralUtils)` - base class: a ctypes register struct + read/write handlers on the MMIO region.
- `qiling/extensions/mcu/stm32f4xx/stm32f407.py` (and siblings) - chip `env` dicts consumed at `Qiling(..., env=...)` construction.

## Interactions

- Created by [core.md](core.md) for bare-metal targets; driven by the MCU run loop in [os-baremetal.md](os-baremetal.md) (`QlHwManager.step()` between execution slices).
- Interrupt controller peripherals (`intc/`) raise exceptions delivered through `QlInterruptContext` in [arch.md](arch.md).
- MMIO regions are mapped through `QlMemoryManager` ([os-base.md](os-base.md)).
- Profile parsing (YAML) is in `profile_setup` ([core.md](core.md), `qiling/utils.py:419`).

## How to Test

```sh
cd tests && python3 test_mcu.py # pass = unittest "OK", exit 0
```

- Exercises GPIO, UART, EXTI, I2C, SPI, DMA, CRC, RTC, watchdog peripherals on real firmware from `examples/rootfs/mcu/`.

## Open Gaps / Roadmap

- Peripheral fidelity is demand-driven: registers behave as observed firmware needs, not per full datasheets.
- Chip coverage limited to families under `qiling/extensions/mcu/`; adding a chip means writing its memory map + any missing peripheral classes.
Loading
Loading