Merge pull request #1642 from xwings/dev

Add AGENT documentation.
This commit is contained in:
xwings
2026-07-02 09:57:41 +08:00
committed by GitHub
15 changed files with 800 additions and 422 deletions
Symlink
+1
View File
@@ -0,0 +1 @@
ARCHITECTURE.md
+177 -421
View File
@@ -1,427 +1,183 @@
# Qiling Framework Architecture
# Qiling Framework — Architecture
Qiling is a cross-platform, multi-architecture binary emulation framework built on top of the [Unicorn](https://www.unicorn-engine.org/) CPU emulation engine. It adds OS-level abstractions (syscalls, file systems, loaders) on top of raw CPU emulation, enabling full binary execution without native hardware.
This is the control center for agent-readable architecture docs. Cross-cutting
facts live here; each subsystem is documented once in `ARCHITECTURE/<module>.md`
(see the [Index](#index)).
## High-Level Overview
## Mission
Qiling is an advanced binary emulation framework: it emulates and sandboxes
code in an isolated environment across multiple platforms and architectures.
Built on top of Unicorn Engine, it adds what raw CPU emulation lacks —
operating-system context (syscalls, APIs, filesystems, registries), executable
format loading, and dynamic linking (`README.md:13`).
Supported combinations are defined authoritatively in code:
- Architectures — `QL_ARCH` (`qiling/const.py:15`): 8086, x86, x86-64, ARM,
ARM64, Cortex-M, MIPS, RISC-V 32/64, PowerPC.
- Operating systems — `QL_OS` (`qiling/const.py:28`): Linux, FreeBSD, macOS,
Windows, UEFI, DOS, QNX, MCU (bare-metal), BLOB.
- Formats: ELF, PE, Mach-O, COM/MBR, Intel HEX / raw firmware. Kernel-module
emulation for Windows `.sys`, Linux `.ko`, macOS `.kext`.
Headline capabilities: fine-grained instrumentation hooks (instruction, basic
block, memory access, interrupt, syscall/API), VM state save/restore, dynamic
hot patching, cross-architecture debugging (GDB server and the built-in Qdb
with reverse debugging), and fuzzing integration (AFL++/unicornafl).
## Target Environment
- **Shape**: pure-Python library (`from qiling import Qiling`), plus the
`qltool` CLI and `qltui.py` TUI. Also shipped as a Docker image
(`Dockerfile`) and PyPI package.
- **Python**: 3.8+ (`pyproject.toml:30`). Packaging via Poetry; version 1.4.8
(`pyproject.toml:4`), status Beta, license GPLv2.
- **Core dependencies**: `unicorn ==2.1.3` (hard-pinned CPU emulator),
`capstone` (disassembly), `keystone-engine` (assembly), `pefile`,
`pyelftools`, `python-registry`, `gevent` (multithread emulation),
`pyyaml` (MCU profiles). Extras: `fuzz` → unicornafl/fuzzercorn,
`RE` → r2libr.
- **Hosts**: Linux, Windows, macOS. Some test suites are host-gated (Windows
PE tests need real system DLLs collected on Windows; see
[os-windows.md](ARCHITECTURE/os-windows.md)).
- **Fixtures**: `examples/rootfs/` is a git submodule
(https://github.com/qilingframework/rootfs.git) holding target binaries for
tests and examples.
## Workspace Layout
| Path | Holds |
| ---- | ----- |
| `qiling/` | The framework package (see Index for per-subsystem docs) |
| `qiling/profiles/` | Default per-OS config profiles (`linux.ql`, `windows.ql`, …) |
| `examples/` | Curated demo scripts, `fuzzing/`, `mcu/`, `shellcodes/`, `extensions/`, `scripts/` (DLL collectors), and the `rootfs/` submodule |
| `jexamples/` | Legacy example set (not covered by CI) |
| `tests/` | CI test suite — standalone `unittest` files run from `tests/` (`tests/test_onlinux.sh`, `tests/test_pe.bat`, `tests/test_macho.sh`) |
| `docs/` | Stubs and assets; real documentation lives at https://docs.qiling.io |
| `qltool` | CLI entry point (see [cli.md](ARCHITECTURE/cli.md)) |
| `qltui.py` | Terminal UI invoked via `qltool qltui` |
| `Dockerfile` | Multi-stage Poetry wheel build on `python:3-slim` |
| `pyproject.toml` / `poetry.lock` | Packaging and dependency pins |
| `.github/workflows/` | CI (`build-ci.yml`: Windows + Ubuntu × Python 3.9/3.11), Docker publish, PyPI publish, Gitee mirror sync |
## Boot / Entry Flow
From CLI to emulated instructions (details live in the module docs):
1. `qltool` parses args and builds kwargs; `run` and `code` subcommands end in
`Qiling(**ql_args)` (`qltool:276`) — see [cli.md](ARCHITECTURE/cli.md).
2. `Qiling.__init__` (`qiling/core.py:35`) is the composition root. Order:
guess arch/OS from the binary if not given → instantiate arch → init
struct/hook mixins → logger → profile → loader → memory manager → OS layer
→ hardware manager (bare-metal only) → `loader.run()` maps the target into
emulated memory (`qiling/core.py:154-195`). All name→class resolution is in
`qiling/utils.py` — see [core.md](ARCHITECTURE/core.md).
3. `Qiling.run()` (`qiling/core.py:561`) attaches the debugger if configured,
applies queued patches, writes the exit trap, then delegates to
`os.run()` — see [debugger.md](ARCHITECTURE/debugger.md) and the OS docs.
4. The OS layer drives `Qiling.emu_start` (`qiling/core.py:743`), the thin
wrapper over Unicorn's `uc.emu_start`. Syscalls/APIs raised by the emulated
code are dispatched back into the OS layer
([os-posix.md](ARCHITECTURE/os-posix.md),
[os-windows.md](ARCHITECTURE/os-windows.md),
[os-baremetal.md](ARCHITECTURE/os-baremetal.md)).
## Roadmap
Maturity-based — Qiling is a released project in maintenance/beta (v1.4.8).
There are no in-repo milestones; module Status is `done` when its test suite
proves it. Feature requests and the forward-looking wishlist are tracked in
GitHub issue [#333](https://github.com/qilingframework/qiling/issues/333)
(the `TODO` file is a pointer to it). Known cross-cutting gaps:
- `ChangeLog` stops at 1.4.6 while `pyproject.toml` says 1.4.8.
- macOS CI job is commented out in `.github/workflows/build-ci.yml`.
- `jexamples/` is legacy and unexercised by CI.
## Coding Discipline
Behavioral guidelines to reduce common LLM coding mistakes. Merge with
project-specific instructions as needed.
**Tradeoff:** These guidelines bias toward caution over speed. For
trivial tasks, use judgment.
### 1. Think Before Coding
**Don't assume. Don't hide confusion. Surface tradeoffs.**
Before implementing:
- State your assumptions explicitly. If uncertain, ask.
- If multiple interpretations exist, present them - don't pick silently.
- If a simpler approach exists, say so. Push back when warranted.
- If something is unclear, stop. Name what's confusing. Ask.
### 2. Simplicity First
**Minimum code that solves the problem. Nothing speculative.**
- No features beyond what was asked.
- No abstractions for single-use code.
- No "flexibility" or "configurability" that wasn't requested.
- No error handling for impossible scenarios.
- If you write 200 lines and it could be 50, rewrite it.
Ask yourself: "Would a senior engineer say this is overcomplicated?" If
yes, simplify.
### 3. Surgical Changes
**Touch only what you must. Clean up only your own mess.**
When editing existing code:
- Don't "improve" adjacent code, comments, or formatting.
- Don't refactor things that aren't broken.
- Match existing style, even if you'd do it differently.
- If you notice unrelated dead code, mention it - don't delete it.
When your changes create orphans:
- Remove imports/variables/functions that YOUR changes made unused.
- Don't remove pre-existing dead code unless asked.
The test: Every changed line should trace directly to the user's request.
### 4. Goal-Driven Execution
**Define success criteria. Loop until verified.**
Transform tasks into verifiable goals:
- "Add validation" → "Write tests for invalid inputs, then make them pass"
- "Fix the bug" → "Write a test that reproduces it, then make it pass"
- "Refactor X" → "Ensure tests pass before and after"
For multi-step tasks, state a brief plan:
```
┌──────────────────────────────────────────────────┐
│ User Script / qltool │
├──────────────────────────────────────────────────┤
│ Qiling Core (core.py) │
│ hooks · state snapshots · patches │
├────────────┬─────────────┬───────────────────────┤
│ OS Layer │ Loader │ Memory Manager │
│ (QlOs) │ (QlLoader) │ (QlMemoryManager) │
│ syscalls │ ELF/PE/ │ map · read · write │
│ APIs │ MachO/etc │ MMIO callbacks │
├────────────┴─────────────┴───────────────────────┤
│ Architecture Layer (QlArch) │
│ registers · disassembly · calling conventions │
├──────────────────────────────────────────────────┤
│ Unicorn Engine (CPU) │
│ instruction-level emulation │
└──────────────────────────────────────────────────┘
1. [Step] → verify: [check]
2. [Step] → verify: [check]
3. [Step] → verify: [check]
```
## Directory Structure
```
qiling/
├── qiling/ # Core framework package
│ ├── core.py # Qiling class — main entry point and orchestrator
│ ├── core_hooks.py # Hook system (code, memory, interrupt, address hooks)
│ ├── core_hooks_types.py # Hook type definitions and dispatch
│ ├── core_struct.py # Endian-aware struct packing utilities
│ ├── const.py # Enumerations: QL_ARCH, QL_OS, QL_ENDIAN, etc.
│ ├── exception.py # Custom exception hierarchy
│ ├── utils.py # Component selection (select_arch, select_os, etc.)
│ ├── host.py # Host platform interface
│ ├── log.py # Logging configuration
│ │
│ ├── arch/ # Architecture implementations
│ │ ├── arch.py # QlArch — abstract base class
│ │ ├── x86.py # x86 / x86-64 / 8086
│ │ ├── arm.py # ARMv7 (32-bit)
│ │ ├── arm64.py # ARMv8 (64-bit)
│ │ ├── mips.py # MIPS32
│ │ ├── riscv.py # RISC-V 32-bit
│ │ ├── riscv64.py # RISC-V 64-bit
│ │ ├── ppc.py # PowerPC 32-bit
│ │ ├── cortex_m.py # ARM Cortex-M (MCU)
│ │ ├── register.py # Register management
│ │ └── models.py # CPU model definitions
│ │
│ ├── os/ # Operating system implementations
│ │ ├── os.py # QlOs — abstract base class
│ │ ├── memory.py # QlMemoryManager
│ │ ├── fcall.py # Function call interface (read params, set return)
│ │ ├── mapper.py # Syscall/API mapping
│ │ ├── path.py # Virtual filesystem path resolution
│ │ ├── filestruct.py # File descriptor abstraction
│ │ ├── thread.py # Threading primitives
│ │ ├── posix/ # POSIX shared layer (syscall handlers)
│ │ ├── linux/ # Linux-specific OS
│ │ ├── freebsd/ # FreeBSD-specific OS
│ │ ├── macos/ # macOS-specific OS
│ │ ├── qnx/ # QNX RTOS
│ │ ├── windows/ # Windows (Win32/Win64 API emulation)
│ │ ├── uefi/ # UEFI firmware services
│ │ ├── dos/ # DOS (8086 interrupts)
│ │ ├── mcu/ # Bare-metal microcontroller
│ │ └── blob/ # Raw binary blob execution
│ │
│ ├── loader/ # Binary format loaders
│ │ ├── loader.py # QlLoader — abstract base class
│ │ ├── elf.py # ELF (Linux, FreeBSD, QNX)
│ │ ├── pe.py # PE (Windows)
│ │ ├── pe_uefi.py # PE for UEFI
│ │ ├── macho.py # Mach-O (macOS)
│ │ ├── dos.py # DOS COM/EXE
│ │ ├── mcu.py # MCU firmware images
│ │ └── blob.py # Raw binary blobs
│ │
│ ├── cc/ # Calling conventions
│ │ ├── intel.py # cdecl, stdcall, ms64
│ │ ├── arm.py # aarch32, aarch64
│ │ ├── mips.py # MIPS o32
│ │ ├── riscv.py # RISC-V ABI
│ │ └── ppc.py # PowerPC ABI
│ │
│ ├── hw/ # Hardware peripheral emulation (MCU)
│ │ ├── peripheral.py # Base peripheral class
│ │ ├── hw.py # Hardware manager
│ │ ├── gpio/ # GPIO pins and interrupts
│ │ ├── timer/ # Timers, PWM, counters
│ │ ├── char/ # UART serial
│ │ ├── spi/ # SPI bus
│ │ ├── i2c/ # I2C bus
│ │ ├── net/ # Network interfaces
│ │ ├── analog/ # ADC/DAC
│ │ ├── intc/ # Interrupt controllers
│ │ ├── flash/ # Flash memory
│ │ ├── dma/ # DMA controllers
│ │ └── ... # Power, SD, misc peripherals
│ │
│ ├── debugger/ # Debugger subsystem
│ │ ├── gdb/ # GDB remote protocol server
│ │ └── qdb/ # Qiling native debugger (with reverse debugging)
│ │
│ ├── extensions/ # Optional extensions
│ │ ├── multitask.py # gevent-based multithreading
│ │ ├── trace.py # Instruction tracing
│ │ ├── coverage/ # Code coverage collection
│ │ ├── sanitizers/ # Memory sanitizers
│ │ ├── afl/ # AFL fuzzer integration
│ │ ├── r2/ # Radare2 integration
│ │ └── idaplugin/ # IDA Pro plugin
│ │
│ └── profiles/ # Default OS configuration files (.ql)
│ ├── linux.ql # Stack/heap addresses, kernel params
│ ├── windows.ql
│ ├── macos.ql
│ └── ...
│
├── qltool # CLI tool for running binaries
├── qltui.py # TUI interface
├── examples/ # Usage examples and sample scripts
├── tests/ # Test suite
└── docs/ # Documentation
```
## Core Components
### `Qiling` (core.py)
The central class. Every emulation session creates one `Qiling` instance that owns and wires together all other components:
```python
ql = Qiling(
argv=["/path/to/binary", "arg1"], # binary + arguments
rootfs="/path/to/rootfs", # virtual filesystem root
ostype=QL_OS.LINUX, # target OS (auto-detected if omitted)
archtype=QL_ARCH.X8664, # target arch (auto-detected if omitted)
)
```
`Qiling` inherits from `QlCoreHooks` (hook management) and `QlCoreStructs` (endian-aware packing). Key properties:
| Property | Type | Description |
|----------|------|-------------|
| `ql.arch` | `QlArch` | CPU architecture — registers, disassembly |
| `ql.os` | `QlOs` | Operating system — syscalls, I/O, APIs |
| `ql.loader` | `QlLoader` | Binary loader — parses and maps the executable |
| `ql.mem` | `QlMemoryManager` | Memory — map, read, write, MMIO |
| `ql.uc` | `unicorn.Uc` | Underlying Unicorn engine instance |
### Architecture Layer (`qiling/arch/`)
`QlArch` is the abstract base. Each architecture subclass configures:
- **Unicorn engine** mode and architecture constants
- **Register** access via `ql.arch.regs` (read/write by name)
- **Disassembler** (Capstone) and **assembler** (Keystone)
- **Stack operations** — push, pop, pointer-width-aware
- **Endianness** and **bit width** (16/32/64)
Supported: x86, x86-64, 8086, ARM, ARM64, MIPS, RISC-V (32/64), PowerPC, Cortex-M.
### OS Layer (`qiling/os/`)
`QlOs` is the abstract base. Each OS subclass provides:
- **Syscall/interrupt dispatch** — routes CPU interrupts to handler functions
- **I/O streams** — `stdin`, `stdout`, `stderr` (interceptable)
- **Virtual filesystem** — path mapping through `rootfs`
- **Function call interface** (`ql.os.fcall`) — read params, set return values
- **API interception** — `set_api()` for hooking library functions
**POSIX subsystem** (`os/posix/`): Shared syscall implementation for Linux, FreeBSD, macOS, and QNX. Individual syscall handlers live under `os/posix/syscall/`.
**Windows** (`os/windows/`): Emulates Win32/Win64 API by hooking DLL imports. Includes registry, thread, handle, and fiber support.
**UEFI** (`os/uefi/`): Emulates UEFI Boot Services, Runtime Services, and SMM. Uses a GUID database and protocol framework.
### Loader Layer (`qiling/loader/`)
`QlLoader` is the abstract base. Loaders parse a binary format, map segments into memory, resolve symbols, load dependencies, and set initial CPU state (PC, SP).
| Loader | Format | Used By |
|--------|--------|---------|
| `QlLoaderELF` | ELF | Linux, FreeBSD, QNX |
| `QlLoaderPE` | PE/COFF | Windows, UEFI |
| `QlLoaderMacho` | Mach-O | macOS |
| `QlLoaderDOS` | COM/EXE | DOS |
| `QlLoaderMCU` | Firmware | Cortex-M MCU |
| `QlLoaderBlob` | Raw bytes | Shellcode / blob |
### Memory Manager (`qiling/os/memory.py`)
Wraps Unicorn's memory model with higher-level operations:
- `map(addr, size, perms)` / `unmap(addr, size)` — region management
- `read(addr, size)` / `write(addr, data)` — data access
- `read_ptr(addr)` / `write_ptr(addr, val)` — pointer-width-aware access
- `read_cstring(addr)` — null-terminated string read
- MMIO callback support for memory-mapped peripherals
### Calling Conventions (`qiling/cc/`)
Each architecture has calling convention classes that abstract argument passing and return values. The `QlOs.fcall` interface uses these to provide a uniform way to read function parameters regardless of platform.
## Execution Flow
### 1. Initialization (`Qiling.__init__`)
```
Qiling(argv, rootfs)
│
├─ Detect arch/OS from binary headers (ql_guess_emu_env)
│ ELF magic → parse e_machine, OSABI
│ PE magic → parse Machine, Subsystem
│ MachO magic → parse CPU type
│
├─ Create QlArch (select_arch) → initializes Unicorn engine
├─ Create QlLoader (select_loader)
├─ Create QlMemoryManager
├─ Create QlOs (select_os)
│
└─ loader.run()
├─ Parse binary format (headers, segments, sections)
├─ Map segments into memory
├─ Load shared libraries / DLLs
├─ Setup stack, heap, TLS, auxiliary vectors
└─ Set initial PC (entry point) and SP
```
### 2. Execution (`ql.run()`)
```
ql.run(begin, end, timeout, count)
│
├─ Apply binary patches (ql.patch)
├─ Write exit trap (guard address)
│
└─ os.run()
└─ uc.emu_start(entry_point, exit_point)
│
├─ Unicorn executes instructions
│
├─ Hooks fire on:
│ ├─ Every instruction (hook_code)
│ ├─ Basic blocks (hook_block)
│ ├─ Memory access (hook_mem_read/write)
│ ├─ Interrupts (hook_intno) → syscall dispatch
│ ├─ Specific addresses (hook_address)
│ └─ Specific instructions (hook_insn)
│
└─ Stops when PC reaches exit point, timeout,
ql.emu_stop(), or unhandled exception
```
### 3. Syscall Handling
When the emulated binary issues a syscall (via `int 0x80`, `syscall`, `svc`, etc.):
```
CPU interrupt/instruction
→ Unicorn interrupt hook
→ QlOs syscall dispatcher
→ Look up handler by syscall number
→ Handler reads args via calling convention
→ Emulates syscall behavior
→ Sets return value in registers
```
## Component Selection
Components are selected dynamically at runtime based on `QL_ARCH` and `QL_OS` enums. The `qiling/utils.py` module provides:
- `select_arch(archtype)` → architecture class
- `select_os(ostype)` → OS class
- `select_loader(ostype)` → loader class
- `select_debugger(options)` → debugger class
This makes it possible to support diverse platform combinations from a unified codebase.
## Hook System
The hook system (`core_hooks.py`) wraps Unicorn's callback mechanism:
| Hook Type | Trigger |
|-----------|---------|
| `hook_code` | Every instruction (optionally within address range) |
| `hook_block` | Every basic block entry |
| `hook_address` | Specific address reached |
| `hook_intno` | CPU interrupt/exception |
| `hook_insn` | Specific instruction type (e.g., `syscall`) |
| `hook_mem_read` | Memory read |
| `hook_mem_write` | Memory write |
| `hook_mem_invalid` | Invalid memory access |
Hooks can be scoped to address ranges and return `QL_HOOK_BLOCK` to suppress further hooks in the chain.
## Key Extension Points
- **Custom syscall handlers** — replace or extend any syscall
- **API hooking** — `ql.os.set_api(name, callback)` to intercept library calls
- **Binary patching** — `ql.patch(offset, data)` for runtime patching
- **State snapshots** — `ql.save()` / `ql.restore()` for checkpointing
- **Debugger attachment** — GDB remote protocol or native QDB debugger
- **Coverage/tracing** — `extensions/coverage/` and `extensions/trace.py`
- **Fuzzing** — AFL integration via `extensions/afl/`
- **Hardware peripherals** — register custom MCU peripherals in `hw/`
## Dependencies
| Package | Role |
|---------|------|
| `unicorn` (2.1.3) | CPU emulation engine |
| `capstone` | Disassembly |
| `keystone-engine` | Assembly |
| `pyelftools` | ELF parsing |
| `pefile` | PE parsing |
| `python-registry` | Windows registry emulation |
| `gevent` | Cooperative multithreading |
| `pyyaml` | Configuration parsing |
Optional: `unicornafl` / `fuzzercorn` (fuzzing), `r2libr` (Radare2 integration).
## Supported Platforms
**Architectures:** x86, x86-64, 8086, ARM, ARM64, MIPS, RISC-V (32/64), PowerPC, Cortex-M
**Operating Systems:** Linux, FreeBSD, macOS, Windows, UEFI, DOS, QNX, MCU (bare-metal), Blob
## Improvement: Hybrid Kernel Architecture
> Detailed implementation plan and task tracking: [TODO.md](TODO.md)
### The Problem
Qiling reimplements Linux kernel behavior syscall-by-syscall in Python. This works
for simple operations (file I/O, memory management, stat) but fundamentally cannot
scale to the full kernel surface:
- **Networking**: No epoll. Sockets are proxied to host sockets with no isolation.
No real TCP state machine, no multicast, no raw/netlink sockets.
- **Multithreading**: Gevent greenlets are cooperative and single-threaded. No
preemption, no real concurrency. Futex is a gevent Event. Programs using pthreads,
mutexes, or condition variables don't behave correctly.
- **Signals**: `signal()`, `sigaction()`, `kill()` are mostly stubbed. No delivery,
no `EINTR`, no `SA_RESTART`.
- **Long tail**: capabilities, cgroups, namespaces, io_uring, seccomp, eBPF — the
kernel API surface is vast and growing.
### The Solution
A **hybrid architecture** that keeps Unicorn for CPU emulation and Qiling for
instrumentation, but offloads complex kernel subsystems to a real Linux kernel via
a **kernel proxy** helper process. Simple syscalls stay emulated in Python.
```
Syscall interrupt
→ load_syscall() [UNCHANGED — existing dispatch in posix.py]
→ check posix_syscall_hooks[CALL]
→ proxy hook registered? → forward to kernel proxy
→ no proxy hook? → existing Python handler [UNCHANGED]
```
The user explicitly chooses which missing syscalls to forward. Nothing is automatic —
by default Qiling behaves exactly as today. The integration uses the **existing
`set_syscall()` CALL hook mechanism** (`posix.py:128-143`), so `load_syscall()` and
all existing dispatch code remain completely unchanged.
```python
proxy = KernelProxy(ql)
proxy.forward_syscall("epoll_create", returns_fd=True)
proxy.forward_syscall("epoll_ctl")
proxy.forward_syscall("epoll_wait")
ql.run()
```
Under the hood, `forward_syscall()` registers a CALL hook that serializes the
arguments and sends them to a helper process (the kernel proxy) which executes
the real syscall and returns the result. For syscalls that return FDs, the result
is wrapped in a `ql_proxy_fd` object and stored in Qiling's FD table. Since the
FD table is already polymorphic (`ql_socket`, `ql_file`, `ql_pipe`), existing
handlers like `ql_syscall_read` and `ql_syscall_close` dispatch through the proxy
FD's `.read()`/`.close()` methods automatically — no changes needed.
### Phases
| Phase | Scope | Risk | Goal |
|-------|-------|------|------|
| 0 | Proof of concept | Low | User manually forwards specific syscalls — zero existing code changed |
| 1 | Networking foundation | Low-Med | Specific hooks for socket syscalls, `ql_proxy_fd`, TCP works |
| 2 | Complete networking | Medium | epoll, poll/select, network namespaces |
| 3 | Real threading | **High** | One Unicorn per thread, shared memory, real futex |
| 4 | Signals | Medium | Real signal delivery, EINTR, handler execution |
| 5 | Integration | Low | API polish, fallback, platform support, benchmarks |
Phase 0 gives users explicit control — they identify which missing syscalls to
forward and the proxy handles them. Phases 1-2 add pointer-aware forwarding for
networking with pre-built forwarders so users don't have to wire up each syscall.
Phase 3 (threading) is the highest-risk change and is deferred until networking is
stable. Each phase preserves backward compatibility — hybrid mode is opt-in, default
behavior is unchanged.
### Alternatives Considered
- **Run a real kernel in Unicorn**: Unicorn doesn't emulate hardware (interrupt
controllers, MMU page tables, timers). Would require rebuilding QEMU system mode.
- **ptrace-based execution**: Run natively, intercept syscalls. Fast, but no
cross-architecture support and limited instruction-level hooks.
- **User-Mode Linux (UML)**: Run the kernel as a userspace process. x86-only,
somewhat unmaintained, complex syscall bridge.
- **Auto-forward all unimplemented syscalls**: Forward every missing syscall
automatically. Convenient but unpredictable — hard to debug, may forward syscalls
that shouldn't be (security, state leaks). Explicit user control is safer.
The hybrid approach was chosen because it preserves Qiling's core value
(instrumentation + cross-arch emulation) while getting real kernel behavior where
it matters most — without modifying the existing dispatch path.
## Testing
Tests live in `tests/` and are organized by platform: `test_elf.py`, `test_pe.py`, `test_macho.py`, `test_dos.py`, `test_mcu.py`, etc. They use binaries from `examples/rootfs/` as test fixtures.
Strong success criteria let you loop independently. Weak criteria ("make
it work") require constant clarification.
---
**These guidelines are working if:** fewer unnecessary changes in diffs,
fewer rewrites due to overcomplication, and clarifying questions come
before implementation rather than after mistakes.
## Index
- [core.md](ARCHITECTURE/core.md) — the `Qiling` facade, hook engine, component selection, profiles, logging, exceptions
- [arch.md](ARCHITECTURE/arch.md) — CPU layer: registers, Unicorn instance, disassembler, calling conventions
- [loader.md](ARCHITECTURE/loader.md) — binary format loaders: ELF, PE, PE/UEFI, Mach-O, DOS, MCU firmware, raw blobs
- [os-base.md](ARCHITECTURE/os-base.md) — shared OS layer: `QlOs`, memory manager/heap, fcall marshalling, fs mapper, path virtualization
- [os-posix.md](ARCHITECTURE/os-posix.md) — POSIX syscall emulation: Linux, FreeBSD, macOS, QNX
- [os-windows.md](ARCHITECTURE/os-windows.md) — Windows API emulation, UEFI boot/runtime/SMM services, DOS interrupts
- [os-baremetal.md](ARCHITECTURE/os-baremetal.md) — MCU and raw-blob execution modes, cooperative multitasking
- [hw.md](ARCHITECTURE/hw.md) — MMIO peripheral emulation for bare-metal targets, board/chip definitions
- [debugger.md](ARCHITECTURE/debugger.md) — GDB remote-serial server and the built-in Qdb debugger
- [extensions.md](ARCHITECTURE/extensions.md) — AFL fuzzing, coverage/tracing, sanitizers, r2/IDA integration, pipes, reports
- [cli.md](ARCHITECTURE/cli.md) — `qltool` CLI and `qltui.py` TUI
+58
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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.
+58
View File
@@ -0,0 +1,58 @@
# Loader — binary format loaders
## Goal
Map the target binary into emulated memory and prepare execution state: parse
the format, load segments/sections and dependencies (ld.so, DLLs, dylibs),
build stack/argv/auxv or OS structures (PEB/TEB, UEFI protocols), and set
entry/exit points. One loader per format, selected by OS type. Mature released
infrastructure; maturity-based status.
## Status
`done` — every format loader is exercised by its OS test suite; the How to
Test command proves the ELF path end-to-end.
## Code Structure
| File | Role |
| ---- | ---- |
| `qiling/loader/loader.py` | Base `QlLoader` + `Image` record; abstract `run()`, save/restore |
| `qiling/loader/elf.py` | ELF for Linux/FreeBSD/QNX: segments, interpreter (ld.so), stack, auxv |
| `qiling/loader/pe.py` | Windows PE: DLL resolution + `QlPeCache`, PEB/TEB/LDR via `Process` |
| `qiling/loader/pe_uefi.py` | UEFI DXE/SMM module loading, protocol installation |
| `qiling/loader/macho.py` | Mach-O (with its own `macho_parser/` package) |
| `qiling/loader/dos.py` | DOS COM/EXE/MBR |
| `qiling/loader/mcu.py` | MCU firmware: Intel HEX (`IhexParser`), ELF, or raw bin per YAML profile |
| `qiling/loader/blob.py` | Raw blobs loaded at a fixed address (e.g. u-boot) |
## Key Types and Entry Points
- `qiling/loader/loader.py:21` - `QlLoader` - base; `Image` NamedTuple (`:15`), abstract `run()` (`:62`), `skip_exit_check` (`:27`).
- `qiling/loader/elf.py:68` - `QlLoaderELF` - loads binary + interpreter, builds stack/auxv (`AUXV` enum `:32`), sets `entry_point`/`elf_entry`.
- `qiling/loader/pe.py:666` - `QlLoaderPE(QlLoader, Process)` - `Process` (`:69`) builds PEB/TEB/LDR; `QlPeCache` (`:41`) caches parsed DLLs behind the `libcache` kwarg (wired in `select_loader`, `qiling/utils.py:300`).
- `qiling/loader/pe_uefi.py:26` - `QlLoaderPE_UEFI` - loads DXE/SMM modules and installs protocols into the UEFI context.
- `qiling/loader/macho.py:70` - `QlLoaderMACHO`.
- `qiling/loader/mcu.py:57` - `QlLoaderMCU` - with `IhexParser` (`:15`).
- OS→loader mapping: `select_loader` (`qiling/utils.py:297`) — LINUX/FREEBSD/QNX→elf, MACOS→macho, WINDOWS→pe, UEFI→pe_uefi, DOS→dos, MCU→mcu, BLOB→blob.
## Interactions
- Instantiated by [core.md](core.md) (`qiling/core.py:183`); `loader.run()` fires at the end of `Qiling.__init__` (`qiling/core.py:195`).
- Uses `ql.mem` ([os-base.md](os-base.md)) for mapping and `ql.arch` ([arch.md](arch.md)) for initial register/stack state.
- The PE loader builds Windows process structures with [os-windows.md](os-windows.md) components (heap, handles); the UEFI loader populates the context in `qiling/os/uefi/`.
- IAT entries recorded here drive address-based API hooking in [os-windows.md](os-windows.md).
- The MCU loader reads the YAML profile owned by [core.md](core.md) and memory maps defined via [hw.md](hw.md).
## How to Test
```sh
cd tests && python3 -m unittest test_elf.ELFTest.test_elf_linux_x8664 # pass = "OK", exit 0
```
- Full loader coverage: `test_elf.py`, `test_pe.py` (Windows host), `test_macho.py` (macOS host), `test_dos.py`, `test_mcu.py`, `test_blob.py`, `test_uefi.py`.
## Open Gaps / Roadmap
- PE and Mach-O loading depend on host-collected libraries (`examples/scripts/dllscollector.bat` / `dylibcollector.sh`); not runnable from a Linux checkout alone.
- `QlPeCache` cache invalidation is manual (delete cache files) — no versioning.
+52
View File
@@ -0,0 +1,52 @@
# OS bare-metal — MCU firmware and raw blobs
## Goal
Execute code with no operating system. MCU mode runs microcontroller firmware
(STM32, GD32V, NXP, …) with peripheral emulation and interrupt scheduling on
a cooperative multitasking wrapper around Unicorn; BLOB mode runs raw binaries
(e.g. u-boot) at a fixed load address with no OS services at all. Mature
released infrastructure; maturity-based status.
## Status
`done` — MCU covered by `tests/test_mcu.py` (STM32F1/F4, GD32VF1 firmware),
BLOB by `tests/test_blob.py` (u-boot) and `tests/test_edl.py`.
## Code Structure
| File | Role |
| ---- | ---- |
| `qiling/os/mcu/mcu.py` | `QlOsMcu` run loop + `MCUTask` driving Unicorn and peripheral steps |
| `qiling/os/blob/blob.py` | `QlOsBlob`: minimal run loop for raw binaries |
| `qiling/extensions/multitask.py` | `UnicornTask` / `MultiTaskUnicorn`: cooperative multitasking over Unicorn |
## Key Types and Entry Points
- `qiling/os/mcu/mcu.py:37` - `QlOsMcu(QlOs)` - run loop; steps hardware between execution chunks and delivers interrupts.
- `qiling/os/mcu/mcu.py:17` - `MCUTask(UnicornTask)` - the firmware execution task.
- `qiling/extensions/multitask.py:26` / `:152` - `UnicornTask` / `MultiTaskUnicorn(Uc)` - task-switching Unicorn subclass MCU mode runs on.
- `qiling/os/blob/blob.py:12` - `QlOsBlob(QlOs)` - runs `entry_point` → `exit_point` with no OS services.
- MCU selection: `QL_OS.MCU` is the only member of `QL_OS_BAREMETAL` (`qiling/const.py:74`); `ql.baremetal` (`qiling/core.py:357`) gates hardware-manager creation.
## Interactions
- Both subclass [os-base.md](os-base.md) `QlOs`.
- MCU mode drives [hw.md](hw.md): `QlHwManager.step()` is called between execution slices; board/chip memory maps come from `qiling/extensions/mcu/` passed as `env=`.
- Interrupt entry/exit uses `QlInterruptContext` in [arch.md](arch.md) (`qiling/arch/cortex_m.py:25`).
- Firmware images are loaded by `QlLoaderMCU` / `QlLoaderBLOB` ([loader.md](loader.md)); MCU profiles are YAML (`profile_setup`, [core.md](core.md)).
- Fuzzing MCU firmware (e.g. `examples/fuzzing/stm32f429/`) combines this mode with [extensions.md](extensions.md) AFL support.
## How to Test
```sh
cd tests && python3 test_blob.py # pass = unittest "OK", exit 0
```
- MCU (also proves [hw.md](hw.md)): `cd tests && python3 test_mcu.py`.
- Qualcomm EDL loader: `cd tests && python3 test_edl.py`.
## Open Gaps / Roadmap
- Supported chip families are those with board definitions in `qiling/extensions/mcu/` (STM32F1/F4, GD32VF1, NXP, Atmel, BES); new chips need new peripheral maps.
- BLOB mode provides no services by design — targets needing hardware must use MCU mode instead.
+60
View File
@@ -0,0 +1,60 @@
# OS base — shared OS-layer services
## Goal
Own the OS-agnostic services every OS personality builds on: the abstract
`QlOs` class, the emulated memory manager and heap, function-call argument
marshalling (via calling conventions), filesystem virtualization (rootfs
confinement + host mapping), file descriptor objects, green threads, and
API-call statistics. Mature released infrastructure; maturity-based status.
## Status
`done` — path virtualization and struct helpers have dedicated unit tests;
everything else is exercised by every OS suite.
## Code Structure
| File | Role |
| ---- | ---- |
| `qiling/os/os.py` | Abstract `QlOs`: utils/stats/path/fs-mapper composition, `set_api`, user-defined API dict, abstract `run()` |
| `qiling/os/memory.py` | `QlMemoryManager` (map/unmap/search, `ql.mem`) and `QlMemoryHeap` |
| `qiling/os/fcall.py` | `QlFunctionCall`: read args / write retval using `qiling/cc/` conventions |
| `qiling/os/mapper.py` | `QlFsMapper`/`QlFsMappedObject`: redirect emulated paths to host files/objects |
| `qiling/os/path.py` | `QlOsPath`: rootfs confinement and virtual→host path conversion |
| `qiling/os/filestruct.py`, `disk.py` | File-descriptor and raw-disk objects |
| `qiling/os/thread.py` | `QlThread(Greenlet)` base for multithread emulation |
| `qiling/os/stats.py` | `QlOsStats`: API/syscall call statistics |
| `qiling/os/struct.py`, `utils.py`, `const.py` | ctypes-based struct helpers, OS utils, shared constants |
## Key Types and Entry Points
- `qiling/os/os.py:24` - `QlOs` - composes utils/stats/path/fs-mapper; `user_defined_api` keyed by `QL_INTERCEPT`; abstract `run()` (`:239`).
- `qiling/os/os.py:224` - `QlOs.set_api(target, handler, intercept)` - user override of an emulated API (address- or name-based).
- `qiling/os/memory.py:23` - `QlMemoryManager` - `ql.mem`; instantiated by core *before* the OS (`qiling/core.py:188`).
- `qiling/os/memory.py:658` - `QlMemoryHeap` - heap used by Windows/UEFI APIs and the sanitizers extension.
- `qiling/os/fcall.py:21` - `QlFunctionCall` - argument/return marshalling on top of `qiling/cc/`.
- `qiling/os/mapper.py:64` - `QlFsMapper` - behind `ql.add_fs_mapper` (`qiling/core.py:701`).
- `qiling/os/path.py:14` - `QlOsPath` - rootfs-confined path resolution.
- `qiling/os/thread.py:11` - `QlThread(Greenlet)` - gevent-based thread base.
## Interactions
- Instantiated by [core.md](core.md): memory at `qiling/core.py:188`, the concrete OS at `:189` (via `select_os`).
- Uses [arch.md](arch.md) calling conventions (`qiling/cc/`) inside `QlFunctionCall`.
- Subclassed by [os-posix.md](os-posix.md) (`QlOsPosix`), [os-windows.md](os-windows.md) (`QlOsWindows`, `QlOsUefi`, `QlOsDos`), and [os-baremetal.md](os-baremetal.md) (`QlOsMcu`, `QlOsBlob`).
- [loader.md](loader.md) maps segments through `QlMemoryManager`.
- The heap is wrapped by `QlSanitizedMemoryHeap` in [extensions.md](extensions.md).
## How to Test
```sh
cd tests && python3 test_pathutils.py && python3 test_struct.py # pass = both print "OK", exit 0
```
- `test_pathutils.py` proves rootfs path virtualization (incl. case-insensitive Windows semantics); `test_struct.py` proves the struct helpers.
## Open Gaps / Roadmap
- Multithread emulation (gevent) is opt-in via `multithread=True` and less battle-tested than single-threaded mode (see `tests/test_elf_multithread.py`).
- `QlOs.run()` contract is loose: each personality implements its own loop; no shared scheduler outside POSIX threads and MCU multitask.
+58
View File
@@ -0,0 +1,58 @@
# OS POSIX — Linux, FreeBSD, macOS, QNX
## Goal
Emulate POSIX operating systems by intercepting syscalls: a shared
`QlOsPosix` layer owns the per-arch syscall ABI, number→name mapping, and the
file-descriptor table; per-OS subclasses add their personality (Linux futex
and procfs, macOS mach ports, QNX message passing). Mature released
infrastructure; maturity-based status.
## Status
`done` — Linux is the flagship target with the largest suite; FreeBSD/QNX
covered by dedicated tests; macOS tests are host-gated (see Open Gaps).
## Code Structure
| File | Role |
| ---- | ---- |
| `qiling/os/posix/posix.py` | `QlOsPosix`: syscall ABI per arch, dispatcher, `set_syscall`, fd table |
| `qiling/os/posix/syscall/` | Syscall implementations, one module per family (unistd, mman, socket, fcntl, signal, ioctl, …) |
| `qiling/os/posix/shm.py`, `msq.py` | SysV IPC (shared memory, message queues) |
| `qiling/os/linux/linux.py` | `QlOsLinux`: syscall hook wiring and the dynamic-ELF run loop |
| `qiling/os/linux/` | `futex.py`, `thread.py` (green threads), `procfs.py`, `map_syscall.py`/`syscall_nums.py`, `kernel_api/` (for .ko emulation) |
| `qiling/os/freebsd/freebsd.py` | `QlOsFreebsd` |
| `qiling/os/macos/macos.py` | `QlOsMacos` + mach ports/tasks/kext support |
| `qiling/os/qnx/qnx.py` | `QlOsQnx` + QNX message passing (`message.py`) |
## Key Types and Entry Points
- `qiling/os/posix/posix.py:48` - `QlOsPosix(QlOs)` - per-arch syscall ABI tables (`:69`), fd table, SysV IPC.
- `qiling/os/posix/posix.py:170` - `load_syscall()` - the dispatcher: syscall id → name (via per-OS `map_syscall`) → handler in `os/posix/syscall/*` or user override.
- `qiling/os/posix/posix.py:132` - `set_syscall(target, handler, intercept)` - user syscall hijack (by number or name, CALL/ENTER/EXIT).
- `qiling/os/posix/posix.py:100` - `__get_syscall_mapper(archtype)` - loads `qiling/os/<os>/map_syscall.py` dynamically.
- `qiling/os/linux/linux.py:26` - `QlOsLinux(QlOsPosix)` - `hook_syscall` (`:137`) registered on interrupt/insn hooks.
- `qiling/os/linux/linux.py:148` - `QlOsLinux.run()` - shellcode single-shot, gevent thread manager when `multithread=True`, or the common path: run ld.so to `elf_entry`, apply lib patches, then run to exit.
## Interactions
- Subclasses [os-base.md](os-base.md) `QlOs`; fd objects come from `qiling/os/filestruct.py`.
- Syscall entry is an arch hook: interrupt/insn hooks from [core.md](core.md) (`QlCoreHooks`) fire `hook_syscall`, using [arch.md](arch.md) registers per the syscall ABI.
- Runs binaries prepared by [loader.md](loader.md) (`QlLoaderELF`, `QlLoaderMACHO`).
- Path/fs access goes through `QlOsPath`/`QlFsMapper` in [os-base.md](os-base.md).
- Fuzzing harnesses in [extensions.md](extensions.md) commonly hijack fd 0 with `pipe.py` and syscalls via `set_syscall`.
## How to Test
```sh
cd tests && python3 test_posix.py # pass = unittest "OK", exit 0
```
- Full POSIX coverage in CI: `tests/test_onlinux.sh` (adds `test_elf.py` targets, `test_elf_multithread.py`, `test_elf_ko.py`, `test_qnx.py`, `test_android.py`).
## Open Gaps / Roadmap
- macOS emulation tests (`test_macho.py`, `test_macho_kext.py`) need a macOS host with collected dylibs; the macOS CI job is commented out in `.github/workflows/build-ci.yml`.
- `test_elf_ko.py` requires a one-time `unzip -P infected m0hamed_rootkit.ko.zip` in `examples/rootfs/x86_linux/kernel/`.
- Syscall coverage is demand-driven — unimplemented syscalls raise/log and get added as targets need them.
+59
View File
@@ -0,0 +1,59 @@
# OS Windows family — Windows, UEFI, DOS
## Goal
Emulate API-call-driven (rather than syscall-driven) environments. Windows:
Win32/NT API emulation with handles, registry, and PE process structures.
UEFI: boot/runtime/SMM services and protocol database for DXE/SMM modules.
DOS: BIOS/DOS interrupt services for COM/EXE/MBR targets. Mature released
infrastructure; maturity-based status.
## Status
`done` — Windows PE suite runs in CI on a Windows host; UEFI and DOS suites
run on Linux. Windows tests are platform-gated locally (need collected DLLs).
## Code Structure
| File | Role |
| ---- | ---- |
| `qiling/os/windows/windows.py` | `QlOsWindows`: component setup (handles, registry, clipboard, fiber), run loop |
| `qiling/os/windows/dlls/` | Win32 API implementations per DLL (kernel32/, ntdll.py, msvcrt.py, advapi32.py, …) |
| `qiling/os/windows/fncc.py`, `api.py` | Decorators declaring API signatures/calling conventions |
| `qiling/os/windows/handle.py`, `registry.py`, `clipboard.py`, `fiber.py`, `thread.py`, `structs.py` | Windows kernel-object emulation |
| `qiling/os/uefi/uefi.py` | `QlOsUefi` run loop |
| `qiling/os/uefi/bs.py`, `rt.py`, `ds.py`, `smm.py`, `protocols/` | Boot/runtime/DXE/SMM services and protocol implementations |
| `qiling/os/uefi/context.py` | UEFI execution contexts populated by the loader |
| `qiling/os/dos/dos.py` | `QlOsDos` run loop |
| `qiling/os/dos/interrupts/` | INT 10h/13h/15h/16h/21h… service handlers |
## Key Types and Entry Points
- `qiling/os/windows/windows.py:33` - `QlOsWindows(QlOs)` - `__setup_components` (`:156`) builds handle manager/registry/clipboard/fiber; `run()` (`:201`).
- `qiling/os/windows/fncc.py` - `@winsdkapi` decorator - declares an API's calling convention and typed params; implementations live in `dlls/`.
- API dispatch is address-based: IAT addresses recorded by the PE loader are hooked and marshalled through `QlFunctionCall` — user overrides via `QlOs.set_api` (`qiling/os/os.py:224`).
- `qiling/os/uefi/uefi.py:22` - `QlOsUefi(QlOs)` - executes DXE/SMM modules; services in `bs.py`/`rt.py`/`smm.py` are installed as callable tables.
- `qiling/os/dos/dos.py:33` - `QlOsDos(QlOs)` - dispatches BIOS/DOS interrupts from `interrupts/`.
## Interactions
- All three subclass [os-base.md](os-base.md) `QlOs` and allocate from `QlMemoryHeap`.
- [loader.md](loader.md): `QlLoaderPE` builds PEB/TEB and records IAT hook addresses; `QlLoaderPE_UEFI` installs protocols into `os/uefi/context.py`; `QlLoaderDOS` sets real-mode state.
- Argument marshalling uses `QlFunctionCall` + `qiling/cc/intel.py` conventions ([arch.md](arch.md)).
- The registry emulation reads hive files from the rootfs via [os-base.md](os-base.md) path services.
- `examples/uefi_sanitized_heap.py` pairs UEFI with the heap sanitizer from [extensions.md](extensions.md).
## How to Test
```sh
cd tests && python3 test_uefi.py # pass = unittest "OK", exit 0 (Linux-runnable)
```
- Windows host only: `cd tests && cmd.exe /C test_pe.bat` after running `examples/scripts/dllscollector.bat` (CI does this in `.github/workflows/build-ci.yml`).
- DOS: `cd tests && python3 test_dos.py` (uses scripted keyboard input).
## Open Gaps / Roadmap
- Windows tests require a Windows host: system DLLs/registry hives are not redistributable and must be collected locally.
- Win32 API surface is demand-driven; unimplemented APIs raise `QlErrorSyscallNotFound`-style errors and are added as samples need them.
- SMM emulation covers common protocols only (see `qiling/os/uefi/smm.py`).
Symlink
+1
View File
@@ -0,0 +1 @@
ARCHITECTURE.md