ACPI AML decompiler w/ CFG recovery and structured pseudocode
29

Configure Feed

Select the types of activity you want to include in your feed.

18 1 0

Clone this repository

https://git.vm.fail/vm.fail/tobi https://git.vm.fail/did:plc:rn27uq67uvrvcfkbawl4szb5
ssh://git@knot.vm.fail:2222/vm.fail/tobi ssh://git@knot.vm.fail:2222/did:plc:rn27uq67uvrvcfkbawl4szb5

For self-hosted knots, clone URLs may differ based on your setup.


README.md

tobi#

ACPI AML decompiler that reads raw AML blobs and DSDT/SSDT tables, skips validated ACPI headers, and builds its own IR while doing conservative CFG recovery over package bounded control operations, then emits pseudocode, opcode disassembly, JSON IR, raw table metadata/Graphviz CFG view

Unsupported bytes stay in the tree as explicit IR nodes with offsets anyway and malformed input goes through diagnostics rather than blowing up whereas the parser keeps bounded reader state as well so hostile or truncated input just fails as data instead of turning into out of bounds reads

Build#

cmake -S . -B build -DTOBI_WERROR=ON -DTOBI_FZ=ON
cmake --build build
ctest --test-dir build --output-on-failure

The recommended validation path before landing parser changes is the sanitised configuration. ASan/UBSan are enabled to surface memory safety violations and UB during parser execution. LSan is intentionally disabled in the default environment because the test harness executes under ptrace and causes LeakSanitizer to abort before it can report meaningful allocation diagnostics:

cmake -S . -B build-san -DCMAKE_C_COMPILER=clang -DTOBI_SAN=ON -DTOBI_WERROR=ON -DTOBI_FZ=ON
cmake --build build-san
ctest --test-dir build-san --output-on-failure
./build-san/fz

For a bounded libFuzzer pass with the checked-in seed corpus:

cmake -S . -B build-libfz -DCMAKE_C_COMPILER=clang -DTOBI_LIBFUZZER=ON -DTOBI_WERROR=ON -DTOBI_FZ=OFF
cmake --build build-libfz --target fz_llvm
rm -rf /tmp/tobi-fuzz-corpus
mkdir -p /tmp/tobi-fuzz-corpus
cp fuzz/seed/*.aml /tmp/tobi-fuzz-corpus/
./build-libfz/fz_llvm -runs=256 /tmp/tobi-fuzz-corpus

Usage#

tobi [opts] <file> [file...]
--dis       print opcode disassembly
--json      print parsed IR as json
--dot       print recovered CF as Graphviz DOT
--raw       print input/table metadata and AML offsets
--strict    treat malformed/unsupported constructs as hard errors
--plain     disable coloured diagnostics
--help      print usage
--version   print version

Default mode prints pseudocode:

./build/tobi sam/m0.aml
method MTH0(args=1, serialized=false, sync=0) {
    Local0 = 0x2a;
    return Local0;
}

Opcode view:

./build/tobi --dis sam/m0.aml

JSON IR:

./build/tobi --json sam/m0.aml

JSON contains the aggregate parse metadata in meta, per-input metadata in inputs, source-aware diagnostics in diagnostics, the discovered namespace, and the deterministic IR tree. Multi-input parses keep each file/table as a named IR block and diagnostics include source plus input_index when the input name is known.

CFG view:

./build/tobi --dot sam/m1.aml

Visualise any user supplied AML blob or ACPI table with Graphviz:

./build/tobi --dot path/to/input.aml > input.dot
dot -Tsvg input.dot -o input.svg
firefox input.svg

For one command:

./build/tobi --dot path/to/input.aml 2>/dev/null | dot -Tsvg -o input.svg

Multiple AML inputs share one namespace pre pass before parsing:

./build/tobi dsdt.aml ssdt1.aml ssdt2.aml

--dis and --raw still report each input independently because those modes are byte stream oriented and dont need the aggregate IR

Input#

Accepted inputs:

raw AML bytecode
DSDT tables with a 36 byte ACPI header
SSDT tables with a 36 byte ACPI header

For tables it checks the signature and then validates the length at offset 4 before touching the AML body and verifies the ACPI checksum across the declared table length. Bad lengths are errors (obviously) and checksum mismatches are warnings by default and hard errors under --strict

Every IR node stores:

kind
source byte offset
source byte length where known
owned name/path/string payloads
raw opcode for unknown or opcode expression nodes
deterministic child array

Currently covered AML surface:

name/scope/device/method/external/alias objects
opregion, field, indexfield, bankfield, mutex, event, processor-style objects
integer/string/buffer/package values and common arithmetic/logical/conversion expressions
method calls resolved through a namespace pre pass, including forward and multi-file calls
IfOp, ElseOp, WhileOp, ReturnOp, BreakOp, ContinueOp
resource-template item summaries inside buffer objects

Unsupported or ambiguous opcodes are preserved as explicit unknown IR nodes with byte offsets and diagnostics. Pseudocode is intentionally conservative: it is an inspection format, not recompilable ASL.

Control flow#

AML IfOp, ElseOp, and WhileOp carry package bounded bodies so tobi reconstructs those directly while preserving the original lexical nesting. It doesnt invent higher level source constructs from control bytes whose semantics cant be established with confidence

Any ambiguous branch residue is kept as explicit fallback labels or annotated comments tied to the originating byte offset instead. Break and Continue are only emitted when the surrounding lexical context makes them unambiguously valid loop control operations. Outside loops they remain explicit IR nodes with diagnostics rather than being rewritten into incorrect high level control flow

Samples#

m0   14 0c 4d 54 48 30 01 70 0a 2a 60 a4 60
m1   14 15 49 46 45 30 01 a0 06 68 70 0a 01 60 a1 05 70 0a 02 60 a4 60
m2   14 20 57 4c 4f 30 01 70 0a 03 60 a2 13 60 a0 06 68 70 0a 01 61 a1 05 70 0a 02 61 74 60 01 60 a4 61
dev0 10 31 5c 5f 53 42 5f 5b 82 29 44 45 56 30 5b 80 52 45 47 30 01 0a 10 0a 04 5b 81 0b 52 45 47 30 00 46 4c 44 30 08 14 0b 52 44 4d 30 00 a4 46 4c 44