Automatic, compiler-based function tracing for C/C++ — add enter/exit, parameters, and return values at build time, with zero changes to your source. A Windows + WSL proof of concept using Clang/LLVM's -finstrument-functions, a custom LLVM pass, and a libclang descriptor generator, built up in four comparable levels.
ENTER main main.cpp:47
ENTER Factorial mathutil.cpp:3
n = 5
ENTER Factorial mathutil.cpp:3
n = 4
... (recursion)
return = 24
return = 120
ENTER ConfigureSpi device.cpp:11
cfg = SpiConfig { clock = Spi0 (1), clockHz = 8000000, enabled = true }
EXIT ConfigureSpi
EXIT main
Ordinary code like int Factorial(int n) — no macros, no manual Enter()/Exit()
calls — is traced automatically because the build injects the instrumentation.
Adding tracing to a large C/C++ codebase by hand (macros in every function) is invasive and never complete. Clang/LLVM can do it for you at compile time. This project proves that out and compares the practical options, from a one-flag approach to a full LLVM pass that recovers parameter names, values, enum names, and struct fields — none of which require touching application source.
It's a learning/bring-up reference aimed at an eventual embedded LLVM/Clang toolchain, not an always-on production tracer.
Prerequisites: Clang/LLVM (winget install LLVM.LLVM), CMake ≥ 3.20, Ninja.
The LLVM pass (levels 2 / 1+2) needs LLVM dev libraries, so it builds in WSL
(sudo apt install clang-14 llvm-14-dev cmake ninja-build) — which also mirrors
a real Linux-hosted toolchain.
Build everything and regenerate all sample outputs in one shot:
.\build_all.ps1 # all levels: Windows builds + WSL pass builds
.\build_all.ps1 -SkipWsl # Windows-only levels (0, 1, 2-signatures)Or the minimal starting point — a self-contained 3-file level-1 example:
cd examples\hello_trace
cmake -S . -B build-trace -G Ninja -DCMAKE_CXX_COMPILER="C:/Program Files/LLVM/bin/clang++.exe" -DENABLE_FUNCTION_TRACE=ON
cmake --build build-trace
.\build-trace\hello.exeFour levels of capability, each with checked-in sample output in samples/:
| Level | Technique | Adds |
|---|---|---|
| none | ordinary build | app output only |
| 1 | -finstrument-functions + runtime |
enter/exit, name, file:line, depth, timestamp, thread id |
| 2 | custom LLVM pass (-fpass-plugin) |
parameter names, values, types, return values, enum names, struct fields |
| 1+2 | both combined | -finstrument enter/exit skeleton enriched with the pass's argument data |
The compiler injects __cyg_profile_func_enter/exit (level 1) or, for level 2,
our LLVM pass rewrites the IR to emit typed logging calls — reading parameter
names from debug info and argument values directly from the IR, still with no
source edits. A libclang tool generates an enum/struct descriptor table so
clockId prints as Spi0 (1) and structs print field-by-field.
Two recursion hazards are handled so tracing doesn't eat itself: the runtime is
compiled without instrumentation and marked no_instrument_function, and a
thread-local reentrancy guard stops the output path (whose inlined CRT printf
is itself instrumented on Windows) from recursing into the hooks.
Full write-ups:
docs/APPROACHES.md— compare/contrast of all four levels and the technique tradeoffs (-finstrument-functionsvs LLVM pass vs Clang AST).docs/INTEGRATION.md— step-by-step guide to applying 1 / 2 / 1+2 to another project, with a troubleshooting table.
FunctionTraceDemo/
build_all.ps1 # one-shot: builds every level (Windows + WSL), regenerates samples
src/ # tricky-flow demo app (recursion, virtual dispatch, callbacks, enum + struct params)
trace/ # level-1 runtime: __cyg_profile_func_enter/exit + DbgHelp (Windows)
pass/ # level-2 LLVM pass + its runtime + build script (WSL)
tools/trace_meta_gen/ # libclang tool: emits the enum/struct descriptor table
generated/ # trace_descriptors.cpp (checked-in generated artifact)
examples/hello_trace/ # minimal 3-file level-1 starter to copy into a new project
docs/ # APPROACHES.md, INTEGRATION.md
samples/ # checked-in output for every level
This is a personal tool, published as-is — issues and pull requests aren't accepted (PRs auto-close). Fork it and make it your own. 🐾
MIT — see LICENSE. © 2026 RelentlessOldMan.