When to Use / When Not to Use
- Use: Zig artifacts (no C++ RTTI/exception tables, panic function chain characteristics,
_start → mainstartup form), need to recover error handling paths, C ABI boundaries, behavior after comptime expansion - Use: distinguish Zig-side code in mixed Zig/C artifacts (no RTTI side + Zig symbol patterns)
- Don't use: C/C++ artifacts (go to [[re-cpp-abi]]; having RTTI/exception tables means it is not a Zig-only artifact)
- Don't use: only need function logic (use decompilation skill directly)
Tool Preparation
readelf / llvm-nm (symbol and section analysis)
- For installation and verification see [[re-cpp-abi]] Tool Preparation
- Purpose:
readelf -Scheck exception table sections;readelf -s/llvm-nmcheck symbols and visibility (Zig business functions are mostly LOCAL symbols)
llvm-objdump / objdump (disassembly)
- For installation and verification see [[re-cpp-abi]] Tool Preparation
- Purpose: locate panic call sites, error code comparisons for catch/orelse (
cmpw+ branches)
Ghidra / IDA (decompilation base)
- For installation and verification see [[re-ghidra]] / [[re-ida]]
- Zig artifacts have no type information (when there is no DWARF); combine with behavior analysis (see steps 3/4)
strings (string pool/error names)
- Bundled with the system; verification:
strings --version - Purpose:
@errorNamelocate error name strings, panic messages, format strings
zig compiler (optional, for comparison compilation)
- Official tarball /
brew install zig/ Windows official installer; verification:zig version - Purpose: compile comparison artifacts with the same version, verify panic chain/error union layout (versions differ greatly, see [[layout]])
Operation Steps
Execute in order, archive each step's artifacts (path + sha256, see [[re-triage]]).
-
Artifact Identification:
readelf -s sample | grep -iE 'panicking|panicExtra|defaultPanic|zig' | head readelf -S sample | grep gcc_except_table # 应无输出(Zig 产物常带 .eh_frame,不能作判别) readelf -s sample | grep __gxx_personality_v0 # 应无匹配 readelf -s sample | grep -wE '_start|main' # 启动形态- Zig characteristics: panic function chain (
debug.panicExtra/debug.panickingetc., version-dependent),stdsymbol patterns (std.debug.printetc.),no C++ RTTI/exception tables(compare with the RTTI/exception-intensive characteristics of [[re-cpp-abi]]) - Combined criteria: no
.gcc_except_table+ no__gxx_personality_v0+ no_ZTV*(RTTI vtable) → no C++ exception mechanism;.eh_framehaving both is not sufficient as a sole criterion - Mixed compilation with C: Zig symbols coexist with C symbols (see step 5 boundaries)
- Zig characteristics: panic function chain (
-
Symbol Visibility and Startup Path:
readelf -s sample | grep -E 'GLOBAL|LOCAL' | grep -cE 'FUNC' readelf -s sample | grep -wE '_start|main' | head- Startup path:
_start(GLOBAL) → runtime initialization (std.start) →main(LOCAL); Zig'smainis an ordinary function, the entry is wrapped via std.start (exit handling is in the wrapper layer) - Symbol visibility: Zig by default only exports
_startand functions explicitly marked withexport; business functions are LOCAL symbols (symbol tables still present in debug/ReleaseSafe, can be stripped in ReleaseFast)——nmbeing visible does not mean exported; the hook/injection surface is calculated by the export table - comptime expansion output: compile-time computations have been inlined/expanded—there is no corresponding source structure; analyze by behavior (see pitfall 1)
- Startup path:
-
panic/error handling paths:
readelf -s sample | grep -iE 'panic' | head # panic 链符号(版本相关命名)- panic chain:
@panic/assertion failure → panic function (print + abort)—locating the panic call site finds input validation/invariants; the panic handler itself is "print + exit", and the call site is the actual business validation - error union:
!Ttype; its layout is divided into two kinds depending on payload size (small payload: 8-byte slot, error code in the high bits; large payload: error code first, payload inlined according to alignment—[[layout]] has a measured table); the call site checks theorelse/catchbranch (compiled as an error code comparison + branch) - Analysis: error paths are a reverse-engineering focus (validation logic, failure branches)—the error code comparison point is the branch condition, and error names can be recovered via the
@errorNamestring pool - Error name recovery:
@errorName(e)'s string is in__zig_tag_name_*symbols/string pool—stringserror names correspond directly to code paths, making them first-hand clues to error semantics
- panic chain:
-
Locating constants and strings (entry point for behavior analysis):
strings -n 5 sample | head -30 # 格式串/错误名/panic 消息 readelf -S sample | grep -E 'rodata|data' # 常量区std.debug.print's format string is in the read-only data section, and cross-references can lead back to the call site (error output path)- Constants evaluated at comptime are inlined directly as immediates, with no constant table—to find "magic numbers", trace the parameters back from the call site
-
comptime and generic expansion:
- Constants/inline functions computed at comptime leave no runtime trace; generic instantiation produces duplicate code (specialized by call-site parameters)
- Recovery strategy: analyze by behavior (where a constant appears → which call argument it traces back to), not by source mapping
std.debug.printand other std functions are heavily inlined (Release mode),readelf -smay retain only startup and the panic chain
-
C ABI boundary:
@extern/@cImport: Zig calls the C library (the import table is clearly inspectable—readelf -d's NEEDED and imported symbols)export fn: the Zig side exports to C/host calls (GLOBAL symbols, visible in the export table)- Mixed artifact: distinguish by symbol origin (Zig symbols vs C symbols—linker grouping/section ownership); the boundary is the logic entry point (the main Zig logic is inside the boundary)
- Calling convention:only on
extern/exportor an explicitcallconv(.c)boundary is it the C ABI—the compiler's own definition is "cis an alias for this target's C calling convention;functions markedexternorexportobtain this convention by default", whereasordinary Zigfnuses the Zig default convention(original definition: used when it is neitherexportnorinline;makes no guarantees about stack alignment, registers, etc., and can only be used within the same Zig compilation unit). Socallconv(.c)isan explicit requirement for the C convention, not the default for ordinary functions - Pure Zig internal functions cannot unconditionally be given the C ABI: Register parameters, aggregate type argument passing, and return value rules may all differ from the target platform's C ABI; when recovering prototypes, infer from the actual usage near the call site; SysV on x86-64 only applies to
.c's mapping on that target, and does not represent Zig's internal convention itself - The parameter layout at C library call sites is read according to the ABI (unlike C++ thiscall in [[re-cpp-abi]], there is no hidden parameter/vtable indirection layer)——this item only applies to confirmed C ABI boundaries
-
stripped/ReleaseFast fallback:
strings -n 6 sample | grep -iE 'panic|error' | head # panic 消息/错误名(@errorName 字符串池)- Under ReleaseFast, both the symbol table and the panic chain may be stripped; recover by behavioral characteristics (error code comparison patterns, string pools), see [[re-triage]] for the initial triage fallback
Cross-domain union
- [[re-binary-core]] gateway: this skill's ownership (selection tree "Zig artifacts" branch)
- [[re-cpp-abi]]: boundary distinction (no RTTI/exceptions → not C++)
- [[re-imports]]: C library boundaries (NEEDED/imported symbols) and export table
- [[re-analyze/analysis-contract]]: symbol table passed according to the data contract
- [[re-triage]]: initial triage fallback
Common pitfalls and traps
- Symbol bloat caused by comptime expansion: Symptom——artifact symbols do not correspond to source code; Cause——compile-time expansion/inlining/generic specialization; Countermeasure——analyze by behavior rather than mapping to source
- Panic paths are misleading: Symptom——a large amount of panic handling code is mistaken for the main logic; Cause——error paths and normal paths are intertwined; Countermeasure——first separate out the panic call sites (validation), then analyze the normal paths
- Error union layout version differences: Symptom——error code reads are misaligned; Cause——error union layout changes across versions (small payload 8-byte slot/high bits of the error code, large payload inlined by alignment); Countermeasure——confirm the layout according to the target version ([[layout]] measured table), the error code is always u16
- Difficult to distinguish boundaries when mixed-compiled with C: Symptom——Zig/C symbols are mixed; Cause——mixed compilation; Countermeasure——group by symbol origin and section ownership, and enter the Zig logic at the boundary
- No exception table ≠ no protection: Symptom——misjudging that there is no error handling; Cause——Zig error handling goes through error union rather than the exception table; Countermeasure——check the error union call sites (
catch/orelsebranch's error code comparison) - Business functions are LOCAL symbols: Symptom——
nmthere are a great many functions in the list, butnm -g(global) only has_start; Cause——Zig does not export business symbols by default; Countermeasure——hook/injection is counted by the export table; analysis can still locate them by LOCAL symbols - Panic chain naming changes across versions: Symptom——searching for symbols by
std.debug.panicfinds nothing; Cause——0.14+ refactored todebug.panicExtra/defaultPanicchain (older versionsstd.debug.panicnamed directly); Countermeasure——locate by the panic call site (@paniccompiled artifact: print+abort sequence), without relying on the specific symbol name