Skip to content

feat: add JITDump support for Bun/JSC JIT symbol resolution - #96

Open
zz85 wants to merge 3 commits into
mainfrom
bun
Open

feat: add JITDump support for Bun/JSC JIT symbol resolution#96
zz85 wants to merge 3 commits into
mainfrom
bun

Conversation

@zz85

@zz85 zz85 commented Apr 19, 2026

Copy link
Copy Markdown
Owner

Given that I'm using bunjs more often than nodejs makes sense to have this supported...

Add a JITDump binary format parser that resolves JIT-compiled JavaScript function names from runtimes like Bun (JavaScriptCore), Java HotSpot, and LuaJIT. Without this, JIT code in anonymous mmap'd pages shows as [unknown].

Implementation:

  • New jitdump.rs: zero-dependency parser with BTreeMap for O(log n) address range lookups, incremental reload for streaming modes, tolerates truncated files. Handles JIT_CODE_LOAD, JIT_CODE_MOVE, JIT_CODE_DEBUG_INFO, and JIT_CODE_CLOSE records. Discovers both standard (jit-.dump) and JSC (jit---) file naming conventions.
  • trace_handler.rs: JIT tables per PID, override [unknown] symbols after V8 SFI resolution (V8 takes priority for Node.js, JITDump for non-V8).
  • event_loop.rs: auto-load JITDump on new PID detection, reload at end of each collection window to catch symbols written during profiling.
  • spawn.rs: auto-detect bun/bunx and inject BUN_JSC_useJITDump=1.
  • profile-bee.rs: warn when --pid targets Bun without JITDump file.
  • symbolize.rs: JITDump override in offline re-symbolization path.
  • E2E tests: bun_callstack.js fixture + 2 tests verifying sample collection and JS function name resolution via JITDump.

Summary by CodeRabbit

  • New Features
    • Added JITDump parsing and per-process JIT symbol resolution to enhance profiling output for Bun/JavaScriptCore.
    • Added automatic Bun JITDump detection, including guidance to restart Bun with BUN_JSC_useJITDump=1 when symbol support is missing.
    • Added streaming/incremental JITDump reloading so newly written symbols appear in symbolized stacks.
  • Improvements
    • Improved display of non-V8 user symbols, including Zig demangling and clone-suffix cleanup.
  • Tests
    • Added Bun + JITDump end-to-end coverage and fixtures to verify resolved JIT function names.

@coderabbitai

coderabbitai Bot commented Apr 19, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR adds JITDump parsing and per-PID symbol resolution, injects Bun JITDump enablement into spawned processes, adds Bun-specific runtime warnings, and extends unwind handling for Zig binaries. It also adds Bun end-to-end coverage.

Changes

Runtime JITDump integration

Layer / File(s) Summary
JITDump parser and lookup
profile-bee/src/jitdump.rs, profile-bee/src/lib.rs
The new jitdump module defines JitSymbol and JitSymbolTable, parses JITDump records, reloads tables incrementally, locates JITDump files by PID, and formats resolved symbols.
Bun runtime detection and warnings
profile-bee/src/spawn.rs, profile-bee/bin/profile-bee.rs
The spawn helper detects Bun programs and injects BUN_JSC_useJITDump=1, the CLI warning path checks Bun JITDump availability alongside Node.js perf-map support, and the jitdump module is exported from the library.
Per-PID JIT symbol resolution
profile-bee/src/event_loop.rs, profile-bee/src/symbolize.rs, profile-bee/src/trace_handler.rs
The event loop loads and reloads per-PID JITDump tables, symbolize.rs preloads tables for mapped PIDs and uses them for unknown user frames, and TraceHandler stores per-PID JIT tables and applies them after V8 overrides.
Zig unwind override
profile-bee/src/dwarf_unwind.rs
The DWARF unwind code detects Zig binaries from ELF symbols, overrides the Zig _start unwind entry to stop unwinding, and adds tests for detecting Rust and Bun binaries.
Bun end-to-end coverage
tests/fixtures/src/bun_callstack.js, tests/run_e2e.sh
The Bun fixture builds a nested JIT-compiled call stack, and the E2E runner profiles it and checks the resulting collapse output for expected JSC symbol names.

Sequence Diagram(s)

sequenceDiagram
  participant Spawn
  participant EventLoop
  participant Jitdump
  participant TraceHandler
  participant Symbolizer

  Spawn->>Spawn: detect Bun and inject BUN_JSC_useJITDump=1
  EventLoop->>Jitdump: find_jitdump_for_pid(tgid)
  EventLoop->>Jitdump: load_from_file(path)
  EventLoop->>TraceHandler: register_jit_table(tgid, table)
  Symbolizer->>Jitdump: find_jitdump_for_pid(pid)
  Symbolizer->>Jitdump: load_from_file(path)
  Symbolizer->>Jitdump: resolve(addr)
  TraceHandler->>Jitdump: format_jit_symbol(sym)
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~90+ minutes

Possibly related PRs

  • zz85/profile-bee#13: It touches profile-bee/src/trace_handler.rs, which is extended here with per-PID JITDump table storage and frame resolution.
  • zz85/profile-bee#20: It also changes profile-bee/src/dwarf_unwind.rs, and this PR adds Zig-specific unwind behavior on top of that path.
  • zz85/profile-bee#95: It modifies the same stack-symbolization flow in trace_handler.rs that this PR extends with JITDump fallback handling.

Poem

🐰 I hopped through Bun and found the spark,
JIT dumps lit symbols in the dark.
Unknown frames blinked, then names came true,
With zig and Bun paths routed through.
My whiskers twitch, my buffer sings—
profile hops on brighter wings.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: adding JITDump-based JIT symbol resolution, especially for Bun/JSC.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch bun

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
profile-bee/src/trace_handler.rs (1)

367-373: ⚠️ Potential issue | 🟡 Minor

Caching concern: JITDump overrides won't reflect later reloads.

get_exp_stacked_frames caches the symbolized result by (tgid, ktrace_id, utrace_id) for reuse. In streaming/TUI mode, event_loop::reload_jitdump_tables picks up new symbols between windows, but any stack that hit the cache before those symbols were loaded will keep serving [unknown] from the cached vector — JITDump-based overrides only apply on the first symbolization for a given stack ID. For long-lived processes that JIT functions progressively, this can leave stale [unknown] frames in the flamegraph despite the reload.

Consider invalidating (or at least refreshing the JIT-override-dependent entries of) the cache when reload_jitdump_tables adds new symbols for a PID, or performing the JIT override on the cached result at lookup time rather than at insertion.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@profile-bee/src/trace_handler.rs` around lines 367 - 373, The cached
symbolized stacks returned by get_exp_stacked_frames are becoming stale when
reload_jitdump_tables adds new JIT symbols; update the cache logic so JIT-dump
overrides are not permanently baked into cached entries—either invalidate or
version cache entries when reload_jitdump_tables updates symbols for a PID or
apply the JIT override at lookup time. Concretely: add a JIT-generation/version
or last-jit-update marker per tgid that reload_jitdump_tables increments,
include that marker in the cache key or store it with entries and check it in
get_exp_stacked_frames before returning self.cache.get(tgid, ktrace_id,
utrace_id), and if out-of-date re-run the JIT-override step (or
refresh/invalidate the entry) so newly loaded JIT symbols are applied. Ensure
references to self.cache.get, get_exp_stacked_frames and reload_jitdump_tables
are the touch points for this change.
🧹 Nitpick comments (6)
profile-bee/src/event_loop.rs (2)

544-550: reload_jitdump_tables() runs on every drain_events call, including the collect_unsymbolized path.

In raw-capture mode (collect_unsymbolized), symbolization is deliberately skipped for speed, but this unconditional tail call still does filesystem I/O and parsing per PID per window. Consider skipping the reload when symbolize == false, since the raw output path never reads the JIT tables anyway.

♻️ Proposed refactor
-        // Reload JITDump symbol tables at the end of the collection window.
-        // JIT runtimes (Bun, Java, etc.) write symbols incrementally as code
-        // is compiled — the initial load on first PID sight may have found an
-        // empty or nonexistent file. This final pass picks up all symbols
-        // written during the profiling window.
-        self.reload_jitdump_tables();
+        // Reload JITDump symbol tables at the end of the collection window,
+        // but only when we intend to symbolize — raw-capture mode doesn't
+        // consult these tables.
+        if symbolize {
+            self.reload_jitdump_tables();
+        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@profile-bee/src/event_loop.rs` around lines 544 - 550, The call to
reload_jitdump_tables() in drain_events causes unnecessary I/O during the
collect_unsymbolized/raw-capture path; update drain_events to skip calling
reload_jitdump_tables() when symbolization is disabled (check the symbolization
flag used elsewhere, e.g. symbolize or the collect_unsymbolized code path) so
that reload_jitdump_tables() only runs when symbolization is enabled; locate the
drain_events implementation and add a conditional guard around the
reload_jitdump_tables() tail call (or early-return for the raw path) so JIT
parsing is avoided when symbolize == false or when collect_unsymbolized is
active.

342-368: self.known_tgids.clone() per drain cycle is unnecessary.

reload_jitdump_tables is called at the tail of every drain_events (so once per collection window / TUI refresh). Cloning the whole HashSet<u32> each time is avoidable — collect into a small Vec<u32> once via .iter().copied().collect() if you need to release the borrow on self.known_tgids, or restructure to avoid the borrow entirely.

Also, on every tick this function does a stat/open per known PID (via find_jitdump_for_pid, which does read_dir("/tmp") for the JSC pattern path). For a system-wide profile with many PIDs this is O(N × dir_size) syscalls per window — probably fine in practice, but worth noting if profiling dense workloads.

♻️ Proposed refactor
-    pub fn reload_jitdump_tables(&mut self) {
-        for &tgid in &self.known_tgids.clone() {
+    pub fn reload_jitdump_tables(&mut self) {
+        let tgids: Vec<u32> = self.known_tgids.iter().copied().collect();
+        for tgid in tgids {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@profile-bee/src/event_loop.rs` around lines 342 - 368, The current
reload_jitdump_tables clones self.known_tgids every call which is unnecessary
and expensive; change the iteration to first collect the PIDs into a small
Vec<u32> (e.g. self.known_tgids.iter().copied().collect()) so you release the
borrow on self before calling jitter lookups, then iterate that Vec and call
jitdump::find_jitdump_for_pid, trace_handler.jit_table_mut(tgid) and
try_load_jitdump_for_pid(tgid) as before; this removes the per-cycle HashSet
clone while preserving behavior and prevents holding a borrow across calls that
access the filesystem.
profile-bee/src/jitdump.rs (2)

108-117: reload_from_file doesn't re-validate on file rotation.

If a JIT runtime rotates or truncates /tmp/jit-<pid>.dump between reads (e.g., process exited and a reused PID wrote a fresh file), seeking to the old last_read_offset will land mid-record of the new file and produce garbage before tripping the truncation fallback. Not a bug today given the eBPF lifecycle hooks invalidate caches on exec/exit, but worth guarding — e.g., checking file.metadata().len() < self.last_read_offset and resetting, or comparing the header magic bytes at offset 0 before seeking.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@profile-bee/src/jitdump.rs` around lines 108 - 117, In reload_from_file,
guard against rotated/truncated files by validating the file before seeking:
open the file, check file.metadata().len() and if it's less than
self.last_read_offset set self.last_read_offset = 0 (or otherwise reset state),
and/or read and verify the header magic bytes at offset 0 match the expected JIT
dump signature before doing reader.seek(SeekFrom::Start(self.last_read_offset));
then proceed to call self.read_records(&mut reader) and update symbols as
before; make sure to reference and update the fields last_read_offset and
symbols and use the existing read_records method when implementing the fallback
reset.

342-365: Minor: format!("jit-") is unnecessary and PID substring match can over-accept.

Two small points on find_jitdump_for_pid:

  1. Line 351: let prefix = format!("jit-"); allocates via format! with no interpolation — "jit-" can be used directly (clippy useless_format).
  2. The JSC pattern match name.contains(&format!("-{}-", pid_str)) is a substring test on the whole filename. For small PIDs this can incidentally match the <tid> or <random> segment rather than the intended <pid> segment (e.g. pid=5 matching jit-5-<otherpid>-abc or jit-123-456-5abc variants). Splitting on - and checking the PID is at position 2 would be more precise.
♻️ Proposed refactor
-    // JSC/Bun convention: /tmp/jit-<tid>-<pid>-<random>
-    // Scan /tmp/ for files matching this pattern.
-    let prefix = format!("jit-");
-    let pid_str = pid.to_string();
-    if let Ok(entries) = std::fs::read_dir("/tmp") {
-        for entry in entries.flatten() {
-            let name = entry.file_name();
-            let name = name.to_string_lossy();
-            // Match pattern: jit-<digits>-<pid>-<alphanum>
-            if name.starts_with(&prefix) && name.contains(&format!("-{}-", pid_str)) {
-                return Some(entry.path());
-            }
-        }
-    }
+    // JSC/Bun convention: /tmp/jit-<tid>-<pid>-<random>
+    let pid_str = pid.to_string();
+    if let Ok(entries) = std::fs::read_dir("/tmp") {
+        for entry in entries.flatten() {
+            let name = entry.file_name();
+            let name = name.to_string_lossy();
+            // Match pattern: jit-<tid>-<pid>-<random>
+            let parts: Vec<&str> = name.split('-').collect();
+            if parts.len() >= 4 && parts[0] == "jit" && parts[2] == pid_str {
+                return Some(entry.path());
+            }
+        }
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@profile-bee/src/jitdump.rs` around lines 342 - 365, In find_jitdump_for_pid,
avoid the useless allocation from format!("jit-") by using the literal "jit-"
and make the JSC/Bun filename match precise by splitting the filename on '-' and
verifying the PID is in the expected segment: check that the split parts length
is >= 3 and parts[2] == pid_str (instead of name.contains(&format!("-{}-",
pid_str))). Keep the existing starts_with("jit-") check (use the literal) and
return entry.path() when the split PID matches.
profile-bee/bin/profile-bee.rs (1)

612-617: Three near-duplicate warning blocks — consider extracting.

The spawn.is_none() { if let Some(target_pid) = pid { warn_nodejs_without_perf_map(target_pid); warn_bun_without_jitdump(target_pid); } } pattern is now repeated verbatim across the batch path, run_combined_mode, and run_tui_mode. A tiny helper (e.g. warn_unsupported_jit_runtimes(target_pid) wrapping both calls) would make future additions (Deno, Java, …) a one-liner.

Also applies to: 1664-1670, 1773-1779

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@profile-bee/bin/profile-bee.rs` around lines 612 - 617, Extract the
duplicated block that checks spawn.is_none() and, when pid is Some, calls
warn_nodejs_without_perf_map and warn_bun_without_jitdump into a small helper
function (e.g. warn_unsupported_jit_runtimes(target_pid: i32)) and replace the
three verbatim sites (the batch path, run_combined_mode, and run_tui_mode) with
a single call to that helper; specifically, create the helper that accepts the
target_pid and invokes warn_nodejs_without_perf_map(target_pid) and
warn_bun_without_jitdump(target_pid), then in each location replace the if
spawn.is_none() { if let Some(target_pid) = pid { ... } } block with the same
pattern that calls the new helper to keep the spawn/pid check but centralize the
warnings.
tests/run_e2e.sh (1)

611-664: Optional: test_bun_samples_collected largely duplicates the sample check inside test_bun_jitdump_callstack.

Both tests gate on bun/fixture presence, run run_bun_profiler, and assert count_samples > 0. The only extra behavior in test_bun_jitdump_callstack is the assert_stack_contains check. This mirrors the Node.js split (test_nodejs_samples_collected + test_nodejs_callstack), so it's consistent — but be aware each test spins up a fresh profile run (~1s + overhead), effectively doubling Bun test runtime for a check already covered by the callstack test. Consider collapsing into a single test if CI time becomes a concern.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/run_e2e.sh` around lines 611 - 664, The two tests
test_bun_samples_collected and test_bun_jitdump_callstack duplicate the same
setup and sample-count check; to reduce redundant runs collapse them by removing
test_bun_samples_collected and moving its basic sample existence check into
test_bun_jitdump_callstack before the assert_stack_contains call (use the
existing run_bun_profiler and count_samples logic), or alternatively have
test_bun_jitdump_callstack call test_bun_samples_collected to reuse its check;
update references to run_bun_profiler, count_samples, and assert_stack_contains
accordingly so only one profiler run is performed per Bun fixture.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@profile-bee/src/jitdump.rs`:
- Around line 689-692: test_find_jitdump_nonexistent currently scans the real
/tmp and can false-fail if a stale jit file exists; update the test to use an
isolated temporary directory instead of the global /tmp by either (a) adding a
helper that searches a provided directory (e.g.,
find_jitdump_for_pid_in_dir(pid: u32, dir: &Path) or make find_jitdump_for_pid
accept a Path argument) and calling that helper from the test with a tempdir
created via tempfile::tempdir(), or (b) change the test to use a randomized high
PID that is extremely unlikely to exist; prefer option (a) so change the
test_find_jitdump_nonexistent to create a tempfile::TempDir, call the
new/modified search function (referencing find_jitdump_for_pid or new
find_jitdump_for_pid_in_dir) against that temp dir, and assert is_none().
- Around line 199-247: The bug is a key mismatch between parse_debug_info and
parse_code_load: change both sides to key pending_debug by code_addr (u64)
instead of using code_index; specifically, in parse_code_load replace the lookup
self.pending_debug.remove(&code_index) with
self.pending_debug.remove(&code_addr) and ensure parse_debug_info inserts into
pending_debug using the debug record's code_addr as the map key; also update the
parse_debug_info doc comment to state that code_addr is the correlation key (not
a "proxy for code_index") so future readers aren't confused.

In `@tests/run_e2e.sh`:
- Around line 661-663: The current regex passed to assert_stack_contains
("hot\|processData\|handleRequest\|serverLoop") is too loose and can match
unrelated native symbols; tighten it by anchoring to exact JS function names
from bun_callstack.js (use word-boundaries like
\bprocessData\b,\bhandleRequest\b,\bserverLoop\b) or replace the check with an
assertion that matches JITDump's collapse-line format (e.g. the resolved
collapse line pattern produced by JITDump for bun_callstack.js) so the test only
passes when symbolization actually resolved JS function names; update the call
to assert_stack_contains to use the new anchored regex or format-specific
pattern.

---

Outside diff comments:
In `@profile-bee/src/trace_handler.rs`:
- Around line 367-373: The cached symbolized stacks returned by
get_exp_stacked_frames are becoming stale when reload_jitdump_tables adds new
JIT symbols; update the cache logic so JIT-dump overrides are not permanently
baked into cached entries—either invalidate or version cache entries when
reload_jitdump_tables updates symbols for a PID or apply the JIT override at
lookup time. Concretely: add a JIT-generation/version or last-jit-update marker
per tgid that reload_jitdump_tables increments, include that marker in the cache
key or store it with entries and check it in get_exp_stacked_frames before
returning self.cache.get(tgid, ktrace_id, utrace_id), and if out-of-date re-run
the JIT-override step (or refresh/invalidate the entry) so newly loaded JIT
symbols are applied. Ensure references to self.cache.get, get_exp_stacked_frames
and reload_jitdump_tables are the touch points for this change.

---

Nitpick comments:
In `@profile-bee/bin/profile-bee.rs`:
- Around line 612-617: Extract the duplicated block that checks spawn.is_none()
and, when pid is Some, calls warn_nodejs_without_perf_map and
warn_bun_without_jitdump into a small helper function (e.g.
warn_unsupported_jit_runtimes(target_pid: i32)) and replace the three verbatim
sites (the batch path, run_combined_mode, and run_tui_mode) with a single call
to that helper; specifically, create the helper that accepts the target_pid and
invokes warn_nodejs_without_perf_map(target_pid) and
warn_bun_without_jitdump(target_pid), then in each location replace the if
spawn.is_none() { if let Some(target_pid) = pid { ... } } block with the same
pattern that calls the new helper to keep the spawn/pid check but centralize the
warnings.

In `@profile-bee/src/event_loop.rs`:
- Around line 544-550: The call to reload_jitdump_tables() in drain_events
causes unnecessary I/O during the collect_unsymbolized/raw-capture path; update
drain_events to skip calling reload_jitdump_tables() when symbolization is
disabled (check the symbolization flag used elsewhere, e.g. symbolize or the
collect_unsymbolized code path) so that reload_jitdump_tables() only runs when
symbolization is enabled; locate the drain_events implementation and add a
conditional guard around the reload_jitdump_tables() tail call (or early-return
for the raw path) so JIT parsing is avoided when symbolize == false or when
collect_unsymbolized is active.
- Around line 342-368: The current reload_jitdump_tables clones self.known_tgids
every call which is unnecessary and expensive; change the iteration to first
collect the PIDs into a small Vec<u32> (e.g.
self.known_tgids.iter().copied().collect()) so you release the borrow on self
before calling jitter lookups, then iterate that Vec and call
jitdump::find_jitdump_for_pid, trace_handler.jit_table_mut(tgid) and
try_load_jitdump_for_pid(tgid) as before; this removes the per-cycle HashSet
clone while preserving behavior and prevents holding a borrow across calls that
access the filesystem.

In `@profile-bee/src/jitdump.rs`:
- Around line 108-117: In reload_from_file, guard against rotated/truncated
files by validating the file before seeking: open the file, check
file.metadata().len() and if it's less than self.last_read_offset set
self.last_read_offset = 0 (or otherwise reset state), and/or read and verify the
header magic bytes at offset 0 match the expected JIT dump signature before
doing reader.seek(SeekFrom::Start(self.last_read_offset)); then proceed to call
self.read_records(&mut reader) and update symbols as before; make sure to
reference and update the fields last_read_offset and symbols and use the
existing read_records method when implementing the fallback reset.
- Around line 342-365: In find_jitdump_for_pid, avoid the useless allocation
from format!("jit-") by using the literal "jit-" and make the JSC/Bun filename
match precise by splitting the filename on '-' and verifying the PID is in the
expected segment: check that the split parts length is >= 3 and parts[2] ==
pid_str (instead of name.contains(&format!("-{}-", pid_str))). Keep the existing
starts_with("jit-") check (use the literal) and return entry.path() when the
split PID matches.

In `@tests/run_e2e.sh`:
- Around line 611-664: The two tests test_bun_samples_collected and
test_bun_jitdump_callstack duplicate the same setup and sample-count check; to
reduce redundant runs collapse them by removing test_bun_samples_collected and
moving its basic sample existence check into test_bun_jitdump_callstack before
the assert_stack_contains call (use the existing run_bun_profiler and
count_samples logic), or alternatively have test_bun_jitdump_callstack call
test_bun_samples_collected to reuse its check; update references to
run_bun_profiler, count_samples, and assert_stack_contains accordingly so only
one profiler run is performed per Bun fixture.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6edf4292-b853-4eae-9fdb-3dc02aead58e

📥 Commits

Reviewing files that changed from the base of the PR and between 7fcb39e and fb1682b.

📒 Files selected for processing (9)
  • profile-bee/bin/profile-bee.rs
  • profile-bee/src/event_loop.rs
  • profile-bee/src/jitdump.rs
  • profile-bee/src/lib.rs
  • profile-bee/src/spawn.rs
  • profile-bee/src/symbolize.rs
  • profile-bee/src/trace_handler.rs
  • tests/fixtures/src/bun_callstack.js
  • tests/run_e2e.sh

Comment thread profile-bee/src/jitdump.rs
Comment thread profile-bee/src/jitdump.rs
Comment thread tests/run_e2e.sh Outdated
zz85 added 3 commits April 19, 2026 05:25
Add a JITDump binary format parser that resolves JIT-compiled JavaScript
function names from runtimes like Bun (JavaScriptCore), Java HotSpot, and
LuaJIT. Without this, JIT code in anonymous mmap'd pages shows as [unknown].

Implementation:
- New jitdump.rs: zero-dependency parser with BTreeMap for O(log n) address
  range lookups, incremental reload for streaming modes, tolerates truncated
  files. Handles JIT_CODE_LOAD, JIT_CODE_MOVE, JIT_CODE_DEBUG_INFO, and
  JIT_CODE_CLOSE records. Discovers both standard (jit-<pid>.dump) and JSC
  (jit-<tid>-<pid>-<random>) file naming conventions.
- trace_handler.rs: JIT tables per PID, override [unknown] symbols after
  V8 SFI resolution (V8 takes priority for Node.js, JITDump for non-V8).
- event_loop.rs: auto-load JITDump on new PID detection, reload at end of
  each collection window to catch symbols written during profiling.
- spawn.rs: auto-detect bun/bunx and inject BUN_JSC_useJITDump=1.
- profile-bee.rs: warn when --pid targets Bun without JITDump file.
- symbolize.rs: JITDump override in offline re-symbolization path.
- E2E tests: bun_callstack.js fixture + 2 tests verifying sample collection
  and JS function name resolution via JITDump.
Bug fixes:
- Fix key mismatch between parse_debug_info and parse_code_load: both
  now correctly correlate via code_addr (was using code_index in lookup
  which would never match the code_addr key from debug_info).
- Fix cache staleness: reload_jitdump_tables now invalidates the symbol
  cache (but not the JIT table itself) when new symbols are loaded, so
  previously-cached [unknown] frames get re-resolved. Added targeted
  invalidate_symbol_cache_for_pid() that doesn't remove V8/JIT readers.
- Fix find_jitdump_for_pid JSC filename parsing: use precise splitn('-')
  and check parts[2] == pid_str instead of loose contains() match.
- Guard reload_from_file against truncated/rotated files: if file size <
  last_read_offset, reset and re-parse from scratch.

Test improvements:
- Refactor find_jitdump_for_pid into find_jitdump_for_pid_in_dir(pid, dir)
  for testability; test uses isolated temp dirs instead of scanning /tmp.
- Added tests for both standard and JSC file naming conventions.
- Tighten E2E regex to require JSC JIT tier prefix (JSC-FTL/JSC-DFG)
  before function names, preventing false matches on native symbols.
- Collapse duplicate test_bun_samples_collected into test_bun_jitdump_callstack.

Code quality:
- Extract duplicated warn block into warn_unsupported_jit_runtimes() helper.
- Skip reload_jitdump_tables() when symbolize=false (raw-capture path).
- Replace known_tgids.clone() with .iter().copied().collect::<Vec<_>>().
- Detect Zig binaries and apply stop-unwind override at _start to fix
  bogus DWARF frames from older Zig compilers
- Add Zig symbol demangling (strip __anon_, __struct_, __hex suffixes)
- Strip GCC/LLVM clone suffixes (.cold, .constprop, .isra, .llvm.*)
- Strip JSC JIT code version hash from JITDump symbols (#DdCypB)
- Warn when Bun process is detected without JITDump file during
  system-wide profiling

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@profile-bee/src/dwarf_unwind.rs`:
- Around line 1038-1065: The Bun test in test_detect_zig_start_address_bun
currently only verifies that detect_zig_start_address does not crash because it
allows None to pass; either change the assertion to require a successful Zig
start address detection for Bun (and validate the returned value), or rename the
test to explicitly describe that it is only a smoke test for a real Zig binary.
- Around line 81-82: The `_start` handling in dwarf_unwind.rs currently drops
the symbol size and only applies the override when `entry.pc == _start`, so
update the `_start` capture path to preserve both address and size and treat the
symbol as a range instead of a single PC. In the logic around `start_addr` and
the unwind-row override, use the recorded `_start` span to override any PCs that
fall within the whole `_start` range, and also allow the override to trigger for
stripped binaries that only provide an address with size 0.

In `@profile-bee/src/jitdump.rs`:
- Around line 249-251: The JITDump record parsing in the name/filename handling
path is allocating a buffer directly from the untrusted payload length, which
can trigger huge allocations before validation. Update the parsing logic in the
relevant reader flow around the record-name handling (including the equivalent
code path at the later matching site) to read the NUL-terminated string into a
capped, fixed-size buffer and then stream-skip any remaining bytes instead of
sizing a Vec from payload_size. Keep the fix localized to the record parsing
functions that currently use remaining and name_and_code so the allocation is no
longer driven by attacker-controlled sizes.
- Around line 214-217: The unknown-record branch in jitdump parsing should
handle truncation the same way as the known-record paths do. Update the record
processing logic around the unknown-type fallback in jitdump.rs so that when
skip_bytes(reader, payload_size) returns UnexpectedEof, you preserve
last_read_offset = record_start and break out of the loop instead of treating it
as a hard error; keep the behavior for other I/O errors unchanged. Locate the
fix in the record-dispatch code that handles unknown record types and the same
truncation handling used by the existing known-record parsing branches.
- Line 69: The JIT symbol range check in the `jitdump.rs` lookup logic can
overflow when computing the end of a symbol range with `code_addr + code_size`.
Update the filter in the JIT symbol matching path to avoid addition overflow by
comparing against a subtraction-based bound instead, using the existing
`sym.code_addr`, `sym.code_size`, and `addr` values in the same lookup function.
- Around line 281-293: The JIT_CODE_MOVE parser in parse_code_move is reading
the record with the wrong fixed layout, so it treats the leading pid/tid/vma
fields as old_code_addr and mis-parses real move records. Update parse_code_move
to consume the full perf jitdump JIT_CODE_MOVE header in the correct order
before reading old_code_addr, new_code_addr, new_code_size, and code_index, and
adjust the FIXED_SIZE check accordingly. Also update the code_move_record test
helper to emit the same full 48-byte payload so the parser and tests match the
jitdump format.

In `@profile-bee/src/trace_handler.rs`:
- Around line 388-394: The register_jit_table() path in trace_handler.rs should
also invalidate any cached stack entries for the same PID, since a fresh JIT
table can arrive after “[unknown]” stacks have already been cached. Update
register_jit_table() to clear the symbol cache for the given tgid before or when
inserting into self.jit_tables, so subsequent lookups can resolve with the new
JIT symbols. Use the existing cache and JitSymbolTable-related symbols in
TraceHandler to keep the fix localized.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2ab38fcb-1b30-4c62-809b-62917cf8c206

📥 Commits

Reviewing files that changed from the base of the PR and between fb1682b and e7b5f9c.

📒 Files selected for processing (10)
  • profile-bee/bin/profile-bee.rs
  • profile-bee/src/dwarf_unwind.rs
  • profile-bee/src/event_loop.rs
  • profile-bee/src/jitdump.rs
  • profile-bee/src/lib.rs
  • profile-bee/src/spawn.rs
  • profile-bee/src/symbolize.rs
  • profile-bee/src/trace_handler.rs
  • tests/fixtures/src/bun_callstack.js
  • tests/run_e2e.sh
✅ Files skipped from review due to trivial changes (1)
  • profile-bee/src/lib.rs
🚧 Files skipped from review as they are similar to previous changes (5)
  • tests/fixtures/src/bun_callstack.js
  • profile-bee/src/spawn.rs
  • profile-bee/src/symbolize.rs
  • profile-bee/src/event_loop.rs
  • profile-bee/bin/profile-bee.rs

Comment on lines +81 to +82
if name == "_start" && symbol.size() > 0 {
start_addr = Some(symbol.address());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Carry _start size through and override the whole range.

The helper rejects zero-sized _start symbols and then discards non-zero sizes, while the override only handles entry.pc == _start. PCs inside _start can still use the bogus CFI rows, and stripped binaries with an address but size 0 get no override at all.

Suggested direction
-fn detect_zig_start_address(obj: &object::File) -> Option<u64> {
+fn detect_zig_start_address(obj: &object::File) -> Option<(u64, u64)> {
@@
-        if name == "_start" && symbol.size() > 0 {
-            start_addr = Some(symbol.address());
+        if name == "_start" {
+            start_addr = Some((symbol.address(), symbol.size()));
@@
-            if start_addr.is_none() && name == "_start" && symbol.size() > 0 {
-                start_addr = Some(symbol.address());
+            if start_addr.is_none() && name == "_start" {
+                start_addr = Some((symbol.address(), symbol.size()));
@@
-    if let Some(start_pc) = zig_start_override {
-        let start_pc_relative = (start_pc - base_vaddr) as u32;
+    if let Some((start_pc, start_size)) = zig_start_override {
+        let Some(start_pc_relative) = start_pc
+            .checked_sub(base_vaddr)
+            .and_then(|pc| u32::try_from(pc).ok())
+        else {
+            return Ok((entries, build_id));
+        };
+        let end_pc_relative = start_pc
+            .checked_add(start_size)
+            .and_then(|end| end.checked_sub(base_vaddr))
+            .and_then(|end| u32::try_from(end).ok());
         for entry in entries.iter_mut() {
-            if entry.pc == start_pc_relative {
+            let in_start = if start_size == 0 {
+                entry.pc == start_pc_relative
+            } else {
+                end_pc_relative
+                    .map(|end| entry.pc >= start_pc_relative && entry.pc < end)
+                    .unwrap_or(false)
+            };
+            if in_start {
                 entry.cfa_type = CFA_REG_EXPRESSION;
@@
-                break;
             }
         }
     }

Also applies to: 97-98, 535-549

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@profile-bee/src/dwarf_unwind.rs` around lines 81 - 82, The `_start` handling
in dwarf_unwind.rs currently drops the symbol size and only applies the override
when `entry.pc == _start`, so update the `_start` capture path to preserve both
address and size and treat the symbol as a range instead of a single PC. In the
logic around `start_addr` and the unwind-row override, use the recorded `_start`
span to override any PCs that fall within the whole `_start` range, and also
allow the override to trigger for stripped binaries that only provide an address
with size 0.

Comment on lines +1038 to +1065
#[test]
fn test_detect_zig_start_address_bun() {
// Bun is a Zig-compiled binary — if installed, verify detection
let candidates = [
std::path::PathBuf::from("/usr/local/bin/bun"),
std::env::var("HOME")
.map(|h| std::path::PathBuf::from(h).join(".bun/bin/bun"))
.unwrap_or_default(),
];
let path = match candidates.iter().find(|p| p.exists()) {
Some(p) => p.clone(),
None => return, // Bun not installed, skip
};
let data = match std::fs::read(&path) {
Ok(d) => d,
Err(_) => return,
};
let obj = match object::File::parse(&*data) {
Ok(o) => o,
Err(_) => return,
};
let result = detect_zig_start_address(&obj);
// Bun is compiled with Zig so it should have __zig* symbols
// and _start. However, Bun is stripped so _start may not have size > 0.
// We just verify the function doesn't crash on a real Zig binary.
if result.is_some() {
assert!(result.unwrap() > 0);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Make the Bun test assert the detection contract or rename it.

As written, this passes when Bun is present but detect_zig_start_address returns None, so it only checks “does not crash” rather than Bun/Zig detection. If detection is expected for Bun, assert Some; otherwise rename the test to reflect the weaker coverage.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@profile-bee/src/dwarf_unwind.rs` around lines 1038 - 1065, The Bun test in
test_detect_zig_start_address_bun currently only verifies that
detect_zig_start_address does not crash because it allows None to pass; either
change the assertion to require a successful Zig start address detection for Bun
(and validate the returned value), or rename the test to explicitly describe
that it is only a smoke test for a real Zig binary.

.range(..=addr)
.next_back()
.map(|(_, sym)| sym)
.filter(|sym| addr < sym.code_addr + sym.code_size)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Avoid wrapping the JIT range end calculation.

Line 69 can overflow on malformed input when code_addr + code_size exceeds u64::MAX, causing a debug panic or wrapped release-mode lookup. Compare via subtraction instead.

Proposed fix
-            .filter(|sym| addr < sym.code_addr + sym.code_size)
+            .filter(|sym| addr >= sym.code_addr && addr - sym.code_addr < sym.code_size)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
.filter(|sym| addr < sym.code_addr + sym.code_size)
.filter(|sym| addr >= sym.code_addr && addr - sym.code_addr < sym.code_size)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@profile-bee/src/jitdump.rs` at line 69, The JIT symbol range check in the
`jitdump.rs` lookup logic can overflow when computing the end of a symbol range
with `code_addr + code_size`. Update the filter in the JIT symbol matching path
to avoid addition overflow by comparing against a subtraction-based bound
instead, using the existing `sym.code_addr`, `sym.code_size`, and `addr` values
in the same lookup function.

Comment on lines +214 to +217
_ => {
// Unknown record type — skip
skip_bytes(reader, payload_size)?;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Treat truncated unknown records like truncated known records.

The parser tolerates partial known records, but a truncated unknown record makes skip_bytes return an error and can discard an otherwise usable table. Keep last_read_offset = record_start and break on UnexpectedEof.

Proposed fix
                 _ => {
                     // Unknown record type — skip
-                    skip_bytes(reader, payload_size)?;
+                    if let Err(e) = skip_bytes(reader, payload_size) {
+                        self.last_read_offset = record_start;
+                        if e.kind() != io::ErrorKind::UnexpectedEof {
+                            tracing::debug!("JITDump unknown record skip error: {}", e);
+                        }
+                        break;
+                    }
                 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
_ => {
// Unknown record type — skip
skip_bytes(reader, payload_size)?;
}
_ => {
// Unknown record type — skip
if let Err(e) = skip_bytes(reader, payload_size) {
self.last_read_offset = record_start;
if e.kind() != io::ErrorKind::UnexpectedEof {
tracing::debug!("JITDump unknown record skip error: {}", e);
}
break;
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@profile-bee/src/jitdump.rs` around lines 214 - 217, The unknown-record branch
in jitdump parsing should handle truncation the same way as the known-record
paths do. Update the record processing logic around the unknown-type fallback in
jitdump.rs so that when skip_bytes(reader, payload_size) returns UnexpectedEof,
you preserve last_read_offset = record_start and break out of the loop instead
of treating it as a hard error; keep the behavior for other I/O errors
unchanged. Locate the fix in the record-dispatch code that handles unknown
record types and the same truncation handling used by the existing known-record
parsing branches.

Comment on lines +249 to +251
let remaining = payload_size - FIXED_SIZE;
let mut name_and_code = vec![0u8; remaining as usize];
reader.read_exact(&mut name_and_code)?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Do not allocate record-sized buffers from untrusted payload lengths.

total_size comes from the JITDump file; a malformed /tmp/jit-* file can advertise a huge payload and force multi-GB allocations here before read_exact fails. Read the NUL-terminated name/filename into a capped buffer and stream-skip the remaining payload instead.

Also applies to: 341-343

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@profile-bee/src/jitdump.rs` around lines 249 - 251, The JITDump record
parsing in the name/filename handling path is allocating a buffer directly from
the untrusted payload length, which can trigger huge allocations before
validation. Update the parsing logic in the relevant reader flow around the
record-name handling (including the equivalent code path at the later matching
site) to read the NUL-terminated string into a capped, fixed-size buffer and
then stream-skip any remaining bytes instead of sizing a Vec from payload_size.
Keep the fix localized to the record parsing functions that currently use
remaining and name_and_code so the allocation is no longer driven by
attacker-controlled sizes.

Comment on lines +281 to +293
fn parse_code_move<R: Read>(&mut self, reader: &mut R, payload_size: u64) -> io::Result<()> {
const FIXED_SIZE: u64 = 32;
if payload_size < FIXED_SIZE {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"JIT_CODE_MOVE too small",
));
}

let old_code_addr = read_u64(reader)?;
let new_code_addr = read_u64(reader)?;
let new_code_size = read_u64(reader)?;
let _code_index = read_u64(reader)?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Parse JIT_CODE_MOVE with the full record layout.

Line 290 currently reads the leading pid/tid bytes as old_code_addr. The Linux perf jitdump definition includes pid, tid, and vma before old_code_addr, so real MOVE records will not relocate symbols correctly. (codebrowser.dev)

Proposed fix
-        const FIXED_SIZE: u64 = 32;
+        const FIXED_SIZE: u64 = 48;
         if payload_size < FIXED_SIZE {
             return Err(io::Error::new(
                 io::ErrorKind::InvalidData,
                 "JIT_CODE_MOVE too small",
             ));
         }
 
+        let _pid = read_u32(reader)?;
+        let _tid = read_u32(reader)?;
+        let _vma = read_u64(reader)?;
         let old_code_addr = read_u64(reader)?;
         let new_code_addr = read_u64(reader)?;
         let new_code_size = read_u64(reader)?;
         let _code_index = read_u64(reader)?;

Also update code_move_record in the tests to emit the same 48-byte fixed payload.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fn parse_code_move<R: Read>(&mut self, reader: &mut R, payload_size: u64) -> io::Result<()> {
const FIXED_SIZE: u64 = 32;
if payload_size < FIXED_SIZE {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"JIT_CODE_MOVE too small",
));
}
let old_code_addr = read_u64(reader)?;
let new_code_addr = read_u64(reader)?;
let new_code_size = read_u64(reader)?;
let _code_index = read_u64(reader)?;
fn parse_code_move<R: Read>(&mut self, reader: &mut R, payload_size: u64) -> io::Result<()> {
const FIXED_SIZE: u64 = 48;
if payload_size < FIXED_SIZE {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"JIT_CODE_MOVE too small",
));
}
let _pid = read_u32(reader)?;
let _tid = read_u32(reader)?;
let _vma = read_u64(reader)?;
let old_code_addr = read_u64(reader)?;
let new_code_addr = read_u64(reader)?;
let new_code_size = read_u64(reader)?;
let _code_index = read_u64(reader)?;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@profile-bee/src/jitdump.rs` around lines 281 - 293, The JIT_CODE_MOVE parser
in parse_code_move is reading the record with the wrong fixed layout, so it
treats the leading pid/tid/vma fields as old_code_addr and mis-parses real move
records. Update parse_code_move to consume the full perf jitdump JIT_CODE_MOVE
header in the correct order before reading old_code_addr, new_code_addr,
new_code_size, and code_index, and adjust the FIXED_SIZE check accordingly. Also
update the code_move_record test helper to emit the same full 48-byte payload so
the parser and tests match the jitdump format.

Comment on lines +388 to +394
pub fn register_jit_table(&mut self, tgid: u32, table: JitSymbolTable) {
tracing::info!(
"registered JITDump symbol table for pid {} ({} symbols)",
tgid,
table.len(),
);
self.jit_tables.insert(tgid, table);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Invalidate cached unknown stacks when registering a fresh JIT table.

reload_jitdump_tables() invalidates after incremental reloads, but its fresh-load branch calls register_jit_table() after stacks may already be cached as [unknown]. Clear this PID’s symbol cache when inserting the table so later output can use the new JIT symbols.

Proposed fix
         );
         self.jit_tables.insert(tgid, table);
+        self.cache.invalidate_pid(tgid);
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
pub fn register_jit_table(&mut self, tgid: u32, table: JitSymbolTable) {
tracing::info!(
"registered JITDump symbol table for pid {} ({} symbols)",
tgid,
table.len(),
);
self.jit_tables.insert(tgid, table);
pub fn register_jit_table(&mut self, tgid: u32, table: JitSymbolTable) {
tracing::info!(
"registered JITDump symbol table for pid {} ({} symbols)",
tgid,
table.len(),
);
self.jit_tables.insert(tgid, table);
self.cache.invalidate_pid(tgid);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@profile-bee/src/trace_handler.rs` around lines 388 - 394, The
register_jit_table() path in trace_handler.rs should also invalidate any cached
stack entries for the same PID, since a fresh JIT table can arrive after
“[unknown]” stacks have already been cached. Update register_jit_table() to
clear the symbol cache for the given tgid before or when inserting into
self.jit_tables, so subsequent lookups can resolve with the new JIT symbols. Use
the existing cache and JitSymbolTable-related symbols in TraceHandler to keep
the fix localized.

@zz85 zz85 assigned zz85 and Copilot and unassigned zz85 Aug 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants