fix(tracer): fix C/C++ dynamic tracer compile/link/crash bugs#2050
Conversation
trace_c_cpp() never produced edges for either language: - The C case linked trace_support.c twice: src_files was globbed via `ls *.c` *after* trace_support.c had already been written into the same directory, so the glob captured it, and it was then appended a second time explicitly, producing duplicate-symbol link errors. Snapshot src_files before writing trace_support.c. - extract_name()/extract_file() weren't marked no_instrument_function, so calling them from inside __cyg_profile_func_enter (itself instrumented) triggered infinite instrumentation recursion and a stack overflow. - The C++ case failed to link: g++ compiles .c input as C++, mangling trace_support.c's __cyg_profile_func_enter/_exit definitions so they no longer matched the compiler's auto-inserted unmangled calls. Wrapped the hook declarations/definitions in extern "C". - Even after the above, g++ compiling trace_support.c itself as C++ under -finstrument-functions crashed at runtime — the file is tracer scaffolding, not code under test, and never needs instrumenting. Compile it as a separate, uninstrumented object and link it against the instrumented fixture sources. With these fixed, both c and cpp now produce correct name-level call edges end-to-end. Doing so exposed a separate, structural bug in extract_file() (dladdr() can only resolve the compiled binary's own filename, not the originating source file, since everything links into one executable) that keeps same-file recall at 0% regardless of call graph correctness — filed as #2049 and reflected in SAME_FILE_THRESHOLDS so the newly-working tracer doesn't fail CI on a metric it structurally cannot pass yet. Refs #1914 Impact: 1 functions changed, 1 affected
Greptile SummaryFixes three compounding bugs that prevented
Confidence Score: 4/5All three root-cause fixes are correct and independently verified; the only open question is the trace_support.c compilation language, which is purely cosmetic. The src_files snapshot fix, extern C guards, and separate uninstrumented compile step each address a distinct, independently verifiable root cause. The threshold drop to 0.0 is appropriately conservative given the documented dladdr() limitation. The only minor concern is compiling trace_support.c with $compiler (g++ for C++ fixtures) rather than forcing C-mode with -x c, making the ifdef cplusplus guards load-bearing rather than belt-and-suspenders. native-tracer.sh (trace_support.c compilation step at line 326) Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[trace_c_cpp called\ncompiler, ext] --> B{compiler available?}
B -- No --> Z[empty_result]
B -- Yes --> C[Copy fixture sources to TMP_DIR]
C --> D[Snapshot src_files = ls *.ext\nBEFORE writing trace_support.c]
D --> E[Write trace_support.c heredoc\nwith extern C guards + no_instrument attrs]
E --> F[Compile trace_support.c\nwithout -finstrument-functions\nto trace_support.o]
F -- fails --> Z
F -- ok --> G{compiler == gcc or cc?}
G -- Yes --> H[gcc -finstrument-functions -rdynamic -ldl src_files trace_support.o -o traced]
G -- No --> I[g++ -finstrument-functions -rdynamic src_files trace_support.o -o traced -ldl -lstdc++]
H -- fails --> Z
I -- fails --> Z
H -- ok --> J[./traced produces JSON edges]
I -- ok --> J
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
A[trace_c_cpp called\ncompiler, ext] --> B{compiler available?}
B -- No --> Z[empty_result]
B -- Yes --> C[Copy fixture sources to TMP_DIR]
C --> D[Snapshot src_files = ls *.ext\nBEFORE writing trace_support.c]
D --> E[Write trace_support.c heredoc\nwith extern C guards + no_instrument attrs]
E --> F[Compile trace_support.c\nwithout -finstrument-functions\nto trace_support.o]
F -- fails --> Z
F -- ok --> G{compiler == gcc or cc?}
G -- Yes --> H[gcc -finstrument-functions -rdynamic -ldl src_files trace_support.o -o traced]
G -- No --> I[g++ -finstrument-functions -rdynamic src_files trace_support.o -o traced -ldl -lstdc++]
H -- fails --> Z
I -- fails --> Z
H -- ok --> J[./traced produces JSON edges]
I -- ok --> J
Reviews (1): Last reviewed commit: "fix(tracer): fix C/C++ dynamic tracer co..." | Re-trigger Greptile |
| # generated bookkeeping crashes at runtime (see #1914). Compile it as a | ||
| # separate, uninstrumented object and link it against the instrumented | ||
| # fixture sources. | ||
| if ! $compiler -c trace_support.c -o trace_support.o 2>/dev/null; then |
There was a problem hiding this comment.
Since
trace_support.c is always pure C scaffolding, compiling it with $compiler (which is g++ for the C++ fixture) makes g++ treat the .c file as C++, requiring the #ifdef __cplusplus guards inside the heredoc to keep the hook symbols unmangled. Forcing C compilation with -x c (or always using gcc) removes that dependency and keeps the support file's compilation language-agnostic, regardless of which compiler is selected for the fixture.
| if ! $compiler -c trace_support.c -o trace_support.o 2>/dev/null; then | |
| if ! $compiler -x c -c trace_support.c -o trace_support.o 2>/dev/null; then |
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Codegraph Impact Analysis1 functions changed → 1 callers affected across 1 files
|
Summary
trace_c_cpp()intests/benchmarks/resolution/tracer/native-tracer.sh(dispatches thecandcppfixtures) never produced any dynamic-call edges. Fixed three compounding bugs:trace_support.clink (C case).src_fileswas computed vials *.caftertrace_support.chad already been written into the same temp directory, so the glob captured it — and it was then appended a second time explicitly in the compiler invocation, producingduplicate symbollink errors. Fixed by snapshottingsrc_filesbefore writingtrace_support.c.extern "C"(C++ case). g++ compiles.cinput as C++ (unlike gcc, which treats it as C), sotrace_support.c's__cyg_profile_func_enter/_exitdefinitions got C++ name-mangled and no longer matched the compiler's auto-inserted unmangled calls, producingsymbol(s) not foundlink errors. Fixed by wrapping the hook declarations/definitions inextern "C".extract_name()/extract_file()weren't markedno_instrument_function, so calling them from inside__cyg_profile_func_enter(itself instrumented) triggered infinite instrumentation recursion → stack overflow → SIGSEGV for the C case.trace_support.citself with-finstrument-functionsunder g++'s C++ frontend crashed at runtime for the C++ case (verified with a trivialint main(){}— same crash).trace_support.cis tracer scaffolding, not code under test, and never needs instrumenting regardless of language. Fixed by compiling it as a separate, uninstrumented object file and linking it against the instrumented fixture sources.With all of the above,
trace_c_cpp()now produces correct name-level call edges end-to-end for both C and C++ (verified manually and viatracer-validation.test.ts).Follow-up filed
Fixing the crashes above exposed a separate, structural bug:
extract_file()resolves the source file viadladdr(), which can only report the compiled binary's own path — since every fixture file links into one executable,source_file/target_fileare identical ("traced") for every edge, sotracer-validation.test.ts's same-file recall check forc/cppcan never pass regardless of call-graph correctness. This needs a different instrumentation strategy (matching the source-level sed injection Rust/Swift/C#/Java already use, or per-file shared-library compilation, or debug-info-based address resolution) — filed as #2049, andSAME_FILE_THRESHOLDS.c/.cppdropped from0.5to0.0(with a comment pointing at #2049) so the newly-working tracer doesn't fail CI on a metric it structurally cannot pass yet.Refs #1914 (compile/link/crash bugs are fully fixed; file-attribution correctness is tracked separately in #2049)
Test plan
bash -n native-tracer.sh— syntax cleanbash native-tracer.sh tests/benchmarks/resolution/fixtures/c cand... cpp cppwith compiler stderr unmasked at each step of the bisection — confirmed each of the three bugs individually, then confirmed the combined fix produces correct edges for both languagesnpx vitest run tests/benchmarks/resolution/tracer/tracer-validation.test.ts— 42/42 passed (previously 2 would now fail with the tracer working but before the threshold adjustment — verified that regression and fixed it)npx vitest run tests/benchmarks/resolution/tracer/— 47/47 passednpx vitest run tests/benchmarks/resolution/— 317/317 passednpx biome checkon the touched test file — cleancodegraph diff-impact --staged -T— 1 function changed (trace_c_cpp), 1 transitive caller, isolated to test infraStacked on #1913 (base branch
fix/issue-1913-tracer-sed-injection-uses-bsd-incompatible) — only the tracer script + threshold diff is this issue's change.