14 KiB
Function-Level Analysis
Deep-dive reference for decompiling, disassembling, and tracing individual functions. Load this when the triage or the user identifies a specific function to investigate, when you need to understand pseudocode output, or when tracing call paths between functions.
When to Go Deep
Load this reference when:
- The user asks to decompile or disassemble a specific function.
- A triage heuristic points to a suspicious function (high risk_score, multiple callers, unusual address).
- An
unknownfrom the triage mentions an unresolved indirect call or obfuscated region that needs manual investigation. - You need to trace the call path from one function to another.
- You need to understand which functions call or are called by a specific function.
Do NOT load this for broad surveys (use triage) or format-level questions (use binary-formats.md).
Prerequisites
Before starting function-level analysis, confirm:
# The project exists and has been analyzed
binary project status <proj> --json
# state should be READY (or STALE — you can still query, but note staleness)
Decompilation
Basic Decompilation
binary decompile --project <proj> function:<name-or-address> --json
The response contains:
data.pseudocode: Reconstructed C-like pseudocode.data.address_map: Maps source line numbers to canonical addresses. Use this to correlate pseudocode lines with disassembly addresses.data.diagnostics[]: Any limitations the decompiler encountered (indirect calls, switch tables that couldn't be fully recovered, type ambiguities).
Important: The output is reconstructed pseudocode, not original source. Variables are auto-named (local_10, param_1, etc.). Types are inferred by the decompiler and may not match the original source types. Control flow is reconstructed and may differ from the original layout.
Selector Resolution
Function selectors work by name or address:
# By name (exact or substring match)
binary decompile --project <proj> function:main --json
binary decompile --project <proj> function:CreateProcessW --json
# By address
binary decompile --project <proj> function:0x401000 --json
Ambiguous selectors (exit code 8): If a name matches multiple functions
(e.g., main exists in both the binary and an imported library, or a substring
matches multiple entries), the CLI returns a list of candidates:
{
"success": false,
"data": {
"candidates": [
{"address": "0x401000", "name": "main"},
{"address": "0x402000", "name": "WinMain"}
]
}
}
Resolve by using the full name or address from the candidates list.
Understanding Pseudocode Output
Variable Naming
| Prefix | Meaning |
|---|---|
param_1, param_2, ... |
Function parameters (calling convention dependent) |
local_10, local_18, ... |
Stack local variables (hex offset from stack frame base) |
uVar1, iVar2, ... |
Temporary variables — the decompiler created these |
DAT_<addr>, PTR_<addr> |
Global data references at that address |
FUN_<addr> |
Indirect function call target (unresolved) |
Common Patterns and What They Mean
Memory allocation:
local_10 = FUN_00401200(8); // malloc(8) or new(8)
Look at the surrounding code to determine if the allocation is checked for NULL.
String operations:
FUN_00401400(local_20, "some_string"); // strcpy or similar
FUN_00401450(local_20, local_10, 0x100); // strncpy(dst, src, 256)
Unchecked string copies → possible buffer overflow. Note the buffer size.
API calls:
uVar1 = FUN_00401800(local_30, 0, 0, 0, 0, 0, 0);
This is likely a call through the IAT (Import Address Table). The address
FUN_00401800 is a thunk. Use binary callees to resolve:
binary callees --project <proj> function:<name> --json
Indirect calls:
(*(code *)local_10)(param_1); // Call through function pointer
The target is resolved at runtime. Use binary xrefs on the address where the
function pointer is stored to find possible assignments. Note this as an
unknown if the target cannot be determined.
Loop patterns:
local_c = 0;
while (local_c < local_10) { ... local_c = local_c + 1; }
Standard for (i = 0; i < n; i++) loop.
Decompilation Timeouts and Partial Results
If decompilation times out (exit code 12), the response may still contain
partial pseudocode. The partial: true flag and diagnostics explain what was
incomplete.
Do NOT present partial pseudocode as complete. Include a caveat: "The decompiler produced partial pseudocode before timing out. The following analysis is based on incomplete results."
Large Functions
For functions with hundreds of basic blocks, decompilation may be slow. Use
--timeout to set a longer limit:
binary decompile --project <proj> function:<name> --timeout 600 --json
Disassembly
Disassembly provides the raw instruction stream — more granular than pseudocode but lower-level.
Disassembling a Function
binary disassemble --project <proj> function:<name> --json
Each instruction in data.instructions[] contains:
mnemonic: The instruction (e.g., "mov", "call", "jmp", "xor")operands: The operands (e.g., "eax, dword ptr [ebp-0x8]")bytes: Raw bytes in hex (e.g., "8b 45 f8")address: Canonical address object
Disassembling an Address Range
binary disassemble --project <proj> 0x401000..0x401200 --json
Use this when:
- You want to see the code before/after a function boundary.
- A function boundary is uncertain and you want to check adjacent code.
- You're investigating a specific address from xrefs or strings output.
Partial Disassembly
If the address range spans both mapped and unmapped regions, the response has
partial: true and a diagnostic about the unmapped gap. The data.instructions
array contains only the mapped portion. This is expected behavior for ranges
that cross segment boundaries.
When to Use Disassembly vs Decompilation
| Use Disassembly When | Use Decompilation When |
|---|---|
| You need exact instruction sequence (e.g., for exploit analysis) | You want to understand high-level logic |
| The decompiler produced incomplete/confusing output | The function is straightforward and you want readable code |
| You're looking for specific instructions (syscall, int 0x80, cpuid, rdtsc) | You're analyzing control flow and branching |
| You need to verify the decompiler's interpretation | You're presenting findings to someone who doesn't read assembly |
| The function is small (< 20 instructions) | The function is large (> 50 instructions) |
Cross-References (XRefs)
Cross-references show which code references a specific entity and which entities the target references.
binary xrefs --project <proj> function:<name> --json
binary xrefs --project <proj> 0x402080 --json
Each reference entry:
from,to: Address objects. Direction depends on context.kind: CALL, JUMP, READ, WRITE, DATA, IMPORT, EXPORT, INDIRECT, or UNKNOWN.confidence: Backend confidence in this reference.
Interpreting XRef Kinds
| Kind | Meaning | Example |
|---|---|---|
| CALL | The source calls the target | call sub_401000 |
| JUMP | The source jumps to the target | jmp loc_401100 (tail call or branch) |
| READ | The source reads data from the target address | mov eax, [0x403000] |
| WRITE | The source writes data to the target address | mov [0x403000], eax |
| DATA | The target's address appears as a data value | Function pointer in a vtable or array |
| INDIRECT | The reference is through a pointer or register | call [eax+0x10] |
| IMPORT | Reference to an imported symbol | call [__imp_CreateFileW] |
| EXPORT | Reference from an exported symbol | A function is exported |
Key questions xrefs answers:
- "Who calls this function?" → Filter for
kind: CALLwheretomatches your function. - "What data does this function read?" → Filter for
kind: READwherefromis within your function. - "Is this function's address stored anywhere?" → Filter for
kind: DATAwheretomatches your function (potential callback or function pointer table entry).
Callers and Callees
Focused versions of xrefs that return only CALL relationships.
Finding Callers
binary callers --project <proj> function:<name> --json
Returns data.callers[] — functions that call the target.
Use callers to answer:
- "What code paths reach this function?"
- "Is this function reachable from
main?" (usebinary trace) - "Is this function ever called?" (empty callers → dead code or callback only)
Finding Callees
binary callees --project <proj> function:<name> --json
Returns data.callees[] — functions called by the target.
Use callees to answer:
- "What system APIs does this function invoke?"
- "What helper functions does it use?"
- "Is this function a leaf (no callees) or a dispatcher (many callees)?"
Tip: Resolve Import Thunks
If a callee has name_source: "IMPORTED", it's an import thunk — the actual
API is the symbol name. For example, kernel32.dll_CreateFileW means the
function calls CreateFileW.
Call Graph
Build a bounded graph of call relationships:
binary callgraph --project <proj> function:<name> --depth 3 --json
The response contains:
data.graph.nodes[]: Functions in the graph (each with name, address)data.graph.edges[]: Call relationships (from → to)
Depth control:
- Default depth: 3 (callers + callees up to 3 hops)
- Maximum depth: 10
- Depth 0 or negative → exit code 2 (INVALID_ARGS)
When to Use Call Graph
| Scenario | Action |
|---|---|
| "What's the call tree under main?" | callgraph with main as root |
| "What functions lead to this suspicious API?" | callgraph with the suspicious function as root, then check callers |
| "How deep is the call chain here?" | callgraph with increasing depth until you hit leaves |
| "Map the attack surface" | callgraph from entry points and exports |
Breadth limits: If a function calls hundreds of others (e.g., a large
switch-based dispatcher), the graph may be truncated. Check diagnostics for
truncation warnings. The depth parameter is a hard limit — nodes at
depth + 1 are not included.
Trace (Path Finding)
Find call paths between two entities:
binary trace --project <proj> --from function:main --to function:CreateFileW --json
Returns data.paths[] — ordered sequences of functions from source to target.
Path Finding Parameters
| Flag | Default | Purpose |
|---|---|---|
--depth |
5 | Maximum path length (number of hops) |
--from |
(required) | Source entity |
--to |
(required) | Target entity |
Interpreting Trace Results
- Multiple paths: There are multiple call chains from source to target. Present the shortest and most interesting paths.
- No paths (empty array): There is no call chain within the depth limit. The functions may still be related through data flow or indirect calls.
- Depth limit reached: Paths exist but exceed the depth limit. Increase
--depthif needed (be aware of output limits).
Use trace to answer:
- "Can main() reach this suspicious function?"
- "What's the shortest call path to the network API?"
- "Is there a code path from the entry point to the decryption routine?"
Bytes (Raw Memory Read)
Read raw bytes at a specific address:
binary bytes --project <proj> 0x401000 64 --json
Returns:
data.hex: Hex string (2 * length chars)data.base64: Base64-encoded bytesdata.address: Canonical addressdata.length: Actual bytes returned (may be less than requested at segment boundaries)
Use bytes to:
- Inspect data referenced by xrefs (constants, strings, jump tables).
- Verify instruction encoding by reading bytes at a disassembly address.
- Extract embedded data referenced by pseudocode (e.g.,
DAT_00403000).
Analysis Flow for a Suspicious Function
When triage flags a function for investigation, follow this flow:
-
Decompile to understand the high-level logic:
binary decompile --project <proj> function:<name> --json -
Check callees to see what APIs and helpers it uses:
binary callees --project <proj> function:<name> --json -
Check callers to understand the context — who invokes this function:
binary callers --project <proj> function:<name> --json -
Check cross-references for data access patterns (writes to global state, reads from configuration areas):
binary xrefs --project <proj> function:<name> --json -
Trace from entry point to see if the function is reachable:
binary trace --project <proj> --from function:entry --to function:<name> --json -
Synthesize your findings. What does this function do? What APIs does it use? Is it reachable from normal program flow? Does it read or write sensitive data?
Output Limits
Function analysis commands enforce limits:
| Limit | Default | Maximum | What Happens |
|---|---|---|---|
| Decompile timeout | 300s | 3600s | partial: true with whatever was completed |
| Disassemble result count | 100 | 1000 | Paginated |
| Callgraph depth | 3 | 10 | Depth > 10 is invalid (exit code 2) |
| Callgraph node count | bounded | disclosed | Truncation diagnostic |
| Trace path count | bounded | bounded | Truncation diagnostic |
| Trace depth | 5 | (implied by depth flag) | Paths beyond depth are not returned |
Always check diagnostics for truncation or limit warnings.
Presenting Function Analysis Results
When presenting your findings to the user, follow this structure:
## Function: <name> at <address>
### Purpose (Agent Assessment)
<1-2 sentence synthesis of what this function does>
### Pseudocode Summary
<key logic, not the full pseudocode>
### APIs Called
- <api1> (from <module>)
- <api2> (from <module>)
### Called By
- <caller1> at <address>
- <caller2> at <address>
### Agent Notes
- <anything notable: suspicious patterns, anti-analysis, bugs, etc.>
- <confidence gaps: what we can't determine from static analysis alone>