X64 Call Tracer is a tool for tracing function calls in x64 Windows executables.
Its goal is simple: capture what calls what (and optionally when functions return), even when debug symbols are missing — useful for reverse engineering, analyzing game engines, or understanding large unfamiliar binaries.
Knowing the exact call trace of a binary (which code paths call which addresses / functions) makes it far easier to locate functions and understand program structure without symbols.
Compiler optimizations, aggressive inlining, self-modifying code, and dynamic code generation can break complete tracing — this is a practical tool, not a magic bullet.
Existing tools (Callgrind/Valgrind, DynamoRIO, Intel PIN, Frida) can provide similar functionality, but are often heavyweight, fragile with protected launchers, or slow for complex binaries.
X64 Call Tracer focuses narrowly on function-call tracing to stay smaller and easier to attach to running processes.
-
function_tracer.py(main):- Spawns a new process or attaches to a running one (by PID or name).
- Injects a helper DLL (
calltrace.dll) into the target.
The DLL handles trace buffering, memory allocation, and saving traces to disk. - Parses the executable’s
.pdatasection to discover functions. - Instruments functions:
- Injects a trace point at the beginning of each function.
- Replaces direct
callsites with jumps to trampolines that record the call, invoke the original target, and record completion. - Optional: a “return-address modification” mode that captures when functions return.
⚠️ This is experimental and may break some functions, so it’s disabled by default.
- Once injection is complete, the Python process exits; the target process continues and writes traces to disk during execution.
-
parse_trace.py:- Converts raw
.tracefiles into a human-readable or analyzable format.
- Converts raw
# Capture trace
python function_tracer.py {name of exeutable to attach to or spawn, or pid}
# View trace
python parse_trace.py --file output\{executable}.trace --modules "output\{executable}_modules.json" --map "output\{executable}_map.jsonpip install pefile keystone-engine capstone>=6.0.0a4 pdbparse- Not fully transparent — instrumentation tries to be as transparent as posible but
self-modifying code, JITs, or anti-tamper checks may detect or break under it. - Performance penalty — tracing every call is expensive.
Use exclusions to remove hot functions or utility calls. - Incomplete coverage — inlining, tail calls, or dynamic code can escape detection.
- Launchers / anti-cheat — some executables verify integrity and may reject injection.
- Platform — designed for x64 Windows
- Data in code section - some libraries put data in the .code section, this may lead to data beeing interpreted as code.
- Start with a simple short trace to verify setup.
- Run, reproduce behavior, inspect the generated trace.
- If the target crashes after instrumentation:
- Look at c:\dbg\debug_output.txt and other output files for exception info to find the issue.
- Add crashing functions to
function_exclusions. - Re-run and iterate.
- Exclude hot-loop functions to reduce slowdown.
- Keep return-address mode disabled unless you need exact returns.
-
function_exclusions— a list of functions (addresses/names) that should not be traced.
Useful for avoiding crashes or excessive slowdown. -
Trace output — binary
.tracefiles written tooutput/.
Parse them usingparse_trace.pyfor inspection or graph generation.
- Add feature to create a map of all calls.
- Build tool that analyses a captured trace and builds a call graph.
- Build tool that uses the call graph to supplement the call map with info about dynamic calls.
Inspired by dynamic instrumentation frameworks such as:
- Valgrind / Callgrind
- DynamoRIO
- Intel PIN
- Frida
Each has its strengths and trade-offs.
X64 Call Tracer narrows the problem space to stay lightweight and more flexible for attaching to arbitrary processes.