name: decompiling
description: Use when reverse-engineering binaries, executables, shared libraries, GPU shaders, or any compiled artifact. Use when the user wants to understand what compiled code does, extract algorithms from binaries, or analyze shader behavior. Trigger keywords: decompile, disassemble, reverse engineer, binary analysis, Hopper, Ghidra, IDA, Mach-O, ELF, dylib, metallib, shader, objdump, hex dump, assembly, vtable, AIR, LLVM IR. Also triggers on /decompile command. DO NOT trigger on source code review or debugging of code the user wrote.
Decompilation
Two domains: ARM64 macOS binaries and Metal GPU shaders. Read the section matching your target. Detailed reference tables are in references/.
When to Use
- Reverse-engineering ARM64 Mach-O binaries (vtable tracing, float decoding, struct mapping)
- Reverse-engineering Metal GPU shaders from metallib/AIR bitcode
- Extracting algorithms from stripped binaries with no source
- Translating shader IR to pseudocode
When NOT to Use
- Source code review or debugging code the user wrote
- Debugging compilation errors in the user's own shaders
- General LLVM IR questions unrelated to reverse engineering
ARM64 Binary Decompilation
Core Workflow
- Find function names in string constants (
strings -o binary)
- Trace to vtables via Itanium ABI (typeinfo name → typeinfo struct → vtable → method pointers)
- Disassemble methods (
llvm-objdump -d --start-address=0x... --stop-address=0x...)
- Decode the math (float constants from literal pools and MOV/MOVK pairs)
Toolchain
lipo -thin arm64 -output /tmp/binary_arm64 /path/to/binary
llvm-objdump -d --start-address=0xADDRESS --stop-address=0xEND /tmp/binary_arm64
strings -o /tmp/binary_arm64 > strings.txt
| Tool | Use For | Cost |
|---|
| llvm-objdump | Targeted disassembly | Free/Xcode |
| Ghidra 12.0 | Decompiler, xrefs, structs | Free |
| lldb | Dynamic validation | Free/Xcode |
| Hopper | Quick interactive browsing | ~$99 |
| GhidraMCP | Claude Code ↔ Ghidra bridge | Free |
Ghidra headless import (expect 1-4 hours for 100MB+):
$GHIDRA_HOME/support/analyzeHeadless /tmp/project BinaryName \
-import /tmp/binary_arm64 -processor AARCH64:LE:64:v8A \
-analysisTimeoutPerFile 7200 -max-cpu 8
Disable Aggressive Instruction Finder and Decompiler Parameter ID on first pass.
String-Anchored Function Discovery
Stripped C++ binaries retain string constants for logging/registration/dispatch.
- Extract strings → find target names → get byte offsets
- Find xrefs to string address (Ghidra: References → Show References To; or search for ADRP+ADD pairs)
- At the xref, a function pointer is passed alongside the string → that's the implementation
Itanium ABI Vtable Tracing
typeinfo name: "24cr_split_tone_function" (mangled, in __cstring)
typeinfo struct: [vtable_ptr] [name_ptr] [base_class_ptr...]
vtable: [...] [typeinfo_ptr] [offset] [vfunc0] [vfunc1] [vfunc2] ...
- Find typeinfo name string in
__cstring
- Search binary for name string's address → typeinfo struct (name ptr at offset +8)
- Search for typeinfo struct's address → vtable (typeinfo ptr at slot[-1])
- Read method pointers: slot[3] = Evaluate, slot[4] = EvaluateInverse (verify by disassembly)
Critical: External bind fixups appear as small values (< 0x1000) in static analysis. These are dyld placeholders — ignore them. Internal pointers (name, base class, typeinfo in vtable) show correct addresses.
Float Constant Decoding
Three patterns — see references/arm64-reference.md for decode scripts and common constants.
| Pattern | Example | Decode Method |
|---|
| Literal pool load | adrp x8, #page; ldr s0, [x8] | Read 4/8 bytes as IEEE 754 |
| MOV/MOVK pairs | mov x8, #lo; movk x8, #hi... | Assemble 64-bit → double |
| FMOV immediate | fmov s0, #1.0 | Assembler shows value |
Dynamic Validation
lldb -n "Target App"
(lldb) b -a 0xADDRESS
(lldb) register read s0 s1 s2 s3
(lldb) memory read -s4 -ff -c16 $x0
Static for understanding math; dynamic for confirming which functions execute and getting concrete values.
GhidraMCP Integration
Works well via MCP: decompiled output queries, xref chains, batch renaming, float constants.
Does NOT work: initial large-binary analysis, visual graph navigation, complex type inference.
Common Mistakes (ARM64)
| Mistake | Fix |
|---|
| Treating small pointer values as real | Values < 0x1000 are bind fixup placeholders |
| Skipping literal pool decode | Every ldr s/d from adrp+add is a constant — decode it |
| Wrong vtable slot for Evaluate | Slot indices depend on hierarchy — verify by disassembly |
| Assuming RTTI exists | Check for __cxxrt1 strings; if absent, use constructor trace |
| Ignoring ADRP page alignment | ADRP zeros low 12 bits of PC before adding |
| Frida for float registers on ARM64e | Frida Interceptor can't read D0-D7; use lldb |
| Full Ghidra analysis on 100MB+ | Selective analysis on target address ranges instead |
Metal Shader Decompilation
Core Workflow
- Find the metallib (standalone in Resources/ or embedded — search for
MTLB magic)
- Disassemble with
metal-objdump -d shaders.metallib > output.ll
- Read LLVM IR — follow SSA chains, decode hex floats, map struct fields
- Translate to pseudocode — name registers, recognize patterns, annotate constants
Toolchain
DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer xcodebuild -downloadComponent MetalToolchain
/Volumes/MetalToolchainCryptex/Metal.xctoolchain/usr/bin/metal-objdump -d /path/to/shaders.metallib > output.ll
IMPORTANT: metal-objdump works on .metallib files only, NOT raw .air blobs.
Finding metallib files
find /path/to/App.app -name "*.metallib"
grep -oba "MTLB" /path/to/binary | head -5
Reading LLVM IR
Registers are SSA (%1234 or %name), each assigned once. Follow the chain backwards from output.
Key patterns — full table in references/metal-reference.md:
| IR Pattern | Meaning |
|---|
fadd/fsub/fmul/fdiv float | Arithmetic |
air.fast_fma.f32(a, b, c) | a*b + c |
air.fast_exp2/log2.f32(x) | 2^x / log2 |
fcmp olt/ogt + select | Conditional |
air.sample_texture_2d.v4f32() | Tex sample |
getelementptr %struct, i32 N | Uniform[N] |
phi float [%a, %bb1], [%b, %bb2] | Merge |
Power functions: pow(x, n) = exp2(log2(x) * n)
Hex float decoding
import struct
def decode_hex_float(h): return struct.unpack('d', struct.pack('Q', int(h, 16)))[0]
Translation Strategy
- List entry points:
grep "^0x.*-- " output.ll
- Map struct fields from type definitions at function top
- Trace backwards from output through SSA chain, naming registers
- Recognize patterns: sorting networks, trapezoidal windows, rational formulas, Hermite splines, soft-light blends
Common Mistakes (Metal)
| Mistake | Fix |
|---|
| metal-objdump on raw .air blobs | Only works on .metallib files |
| Missing Xcode (Command Line Tools) | Full Xcode required for Metal Toolchain |
| Ignoring PHI nodes | They're merge points — trace both predecessor blocks |
| Not decoding hex floats | Every constant is hex; decode to find recognizable values |
| Treating inlined functions as separate | GPU compiler aggressively inlines; look for repeated patterns |
| Skipping sorting network | RGB→max/mid/min sort is fundamental to color algorithms |