def-use
207 functions read an unwritten local
Across the four C and C++ lanes, 207 of 11,166 emitted functions read a local before writing it. The Rust lanes are worse and are reported separately.
❯ glaurung kickoff /bin/ls
Rust runs the expensive analysis. Python keeps it scriptable. Analysts, command-line tools, and optional LLM workflows share the same SQLite project, with provenance on every name and type.
0x1190: 0f50c0 movmskps eax, xmm0
0x1193: 83e001 and eax, 0x1
0x1196: c3 ret fn sign_bit_of_binary32 {
ret = 0;
ret = (ret | (var0 < 0));
ret = (ret | ((var1 < 0) << 1));
ret = (ret | ((var2 < 0) << 2));
ret = (ret | ((var3 < 0) << 3));
ret = (ret & 1);
return (unsigned long)((unsigned int)(ret));
} original C
int32_t sign_bit_of_binary32(float value) {
return (int32_t)((fp174_float_bits(value) & FP174_SIGN_MASK) >> 31);
}
clang turned the sign-bit test into movmskps, which extracts four sign bits
at once. The decompiler follows the three instructions and masks the result to one bit.
The recovered code looks different. The test only passes if it returns the same value.
Tests
Text similarity cannot tell you whether recovered C still works. The fixture corpus calls both versions with the same inputs.
Each recovered function is compiled into a shared object. A subprocess loads it beside the original, calls both with seeded inputs, and compares the return value and every mutable buffer. One mismatch fails the lane.
213
fixture programs, from sorting and hashing to SHA-256 and Duff's device
tests/decompiler_fixtures/
3,449
function-lanes across 838 compiler and optimization cells
baseline.json
83.9%
behave the same as the original (2,895 of 3,449)
baseline.json @ 356be614
119
of 213 fixtures pass every lane they have
baseline.json
Counted from tests/decompiler_fixtures/baseline.json at commit 356be614, 2026-08-27. Toolchain pinned in the file: gcc 11.4.0, clang 14.0.0, glibc 2.35. By lane,
over pass-or-fail verdicts: clang -O0 89.6%, gcc -O0 89.6%, gcc -O2 88.2%, clang -O2 86.6%.
rustc is the weak lane at 30.9% (-O0) and 72.1% (-O2).
def-use
Across the four C and C++ lanes, 207 of 11,166 emitted functions read a local before writing it. The Rust lanes are worse and are reported separately.
closure
All 2,253 closure checks pass across three render styles. None of 751 placeholder checks found an invented name.
symbols
Eighty-seven functions pass with debug information and fail without it. Eight go the other way.
Project files
A .glaurung file stores names, comments, types, prototypes, cross-references, and stack variables in SQLite. Each record says where it came from.
manual an analyst typed it always wins dwarf read from debug info stdlib known library signature flirt FLIRT pattern match propagated carried along a proven edge auto inferred by analysis borrowed taken from a sibling binary
An agent writes proposed names at auto, below debug information and below
anything an analyst typed. Re-running analysis cannot replace a manual name.
Security write-ups use lifted pseudo-C as a lead. They cite disassembly, emulation, or a live reproduction as evidence.
Architecture
Rust owns parsing, disassembly, lifting, and analysis. PyO3 puts the same engine behind Python, the CLI, and the optional LLM tools.
Twenty-two top-level modules include triage, formats, disasm,
analysis, ir, decompile, symbols,
demangle, flirt, strings, similarity,
entropy, and optional modules behind feature gates.
Agents can query bounded analysis tools, propose function names, review vulnerability
leads, and help recover source. Their changes enter the project at auto
provenance. Manual work still wins.
The native module returns types built by the Rust core: Binary,
Function, ControlFlowGraph, Instruction, DataType. A test generates type stubs from the built extension and diffs them against the
checked-in files.
import glaurung as g
art = g.triage.analyze_path("samples/containers/hello-cpp-g++-O0")
v = art.verdicts[0]
print(f"{v.format} {v.arch} {v.bits}-bit confidence={v.confidence:.2f}")
print(f"size={art.size_bytes} entropy={art.entropy.overall:.2f}")
print("ascii strings:", art.strings.ascii_count)
# ELF x86_64 64-bit confidence=0.92
# size=44104 entropy=4.82
# ascii strings: 298 $ uv run glaurung triage samples/containers/hello-cpp-g++-O0
path: samples/containers/hello-cpp-g++-O0
size: 44104 bytes (43.1 KiB)
verdicts: 1
format=ELF arch=x86_64 64-bit endianness=Little confidence=0.92
symbols: imports=41 exports=2 libs=3 flags: nx,aslr,relro,pie
strings: ascii=298 utf8=2 u16le=0 u16be=0
languages: (none)
scripts: (none)
entropy: overall=4.82 Quick start
You need uv, Rust 1.88 or newer, a C compiler, and a linker. The first run builds the Rust extension and caches it.
$ uvx --from git+https://github.com/mjbommar/glaurung.git \
glaurung kickoff /bin/ls