v0.9.2 — Symbolication for Reports #3
jamesgober
announced in
Announcements
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
mod-alloc v0.9.2 — Symbolication for Reports
Date: 2026-05-14
Compare:
v0.9.1...v0.9.2Headline
Tier 2 backtraces are now human-readable. With
--features symbolicate(default OFF), the per-call-site reportresolves each raw return address against the running binary's
own debug info, returning function names and (on Linux/macOS)
source file + line. The allocation hot path is untouched; this is
a pure report-generation upgrade.
What's new
Public API additions
ModAlloc::symbolicated_report() -> Vec<SymbolicatedCallSite>drains the call-site table and resolves each frame. Cached
per-address across calls. Allocates — call from outside the
allocator hook only.
SymbolicatedCallSitecarriescount,total_bytes, anda
Vec<SymbolicatedFrame>.SymbolicatedFramecarriesaddress,function: Option<String>,file: Option<PathBuf>,line: Option<u32>, andinlined: boolto flag expansions from a single physicalreturn address.
Resolution backends
addr2line+object(DWARF)addr2line+object(DWARF)pdbAll backends share the same return type. Names are demangled via
rustc-demangle. Self-binary path is discovered viastd::env::current_exe.Storage architecture
Box::leaks the bytesso
addr2line::Context(which borrows from the parsedobject::File, which borrows from the bytes) can sit in aOnceLock<Option<UnixSymbolicator>>with'staticlifetime.Memory cost: roughly the binary's on-disk size, one-time.
public symbol once, builds a sorted
Vec<(rva, name)>index,then drops the PDB. Per-resolve work is a binary search.
Sidesteps PDB's non-
Sendinternal types and self-referentialborrows entirely.
symbolicate::reportkeyed byu64. Cache miss runs the platform symbolicator once;subsequent calls reuse the cached
Vec<SymbolicatedFrame>.Mutex-protected
HashMap.Dependency policy
The zero-runtime-deps promise applies to the allocation hot path
and the default build. Symbolication is an explicitly out-of-band
report-generation activity, so the
symbolicatefeature is theapproved exception. New optional deps:
addr2line0.21object0.32addr2linebackendrustc-demangle0.1pdb0.8uuid=1.10.0All are pure-Rust, MSRV 1.75-compatible. No FFI. Documented in
.dev/DIRECTIVES.mdsection 2.2.The
uuidpin is load-bearing:pdb 0.8pullsuuidintransitively, and the latest
uuid 1.xrequires Rust 1.85. Thepinned 1.10.0 supports MSRV 1.63.
Test additions
tests/symbolicate_self.rs— symbolicates the test binary'sown allocations and looks for a known function name in the
resolved frames. Downgrades to a shape-only check when the
test binary was built without debug info.
tests/symbolicate_concurrent.rs— 8 threads callsymbolicated_report()simultaneously after a primer callflushes the main thread's arena. Asserts no deadlock and
consistent row counts across reports.
src/symbolicate/self_binary.rsunit tests cover pathresolution and caching.
Example
examples/symbolicate.rs— installsModAlloc, exercises afew
#[inline(never)]call paths, prints the top 10 sitessorted by total bytes with resolved function names plus any
inlined-frame expansions.
CI
.github/workflows/ci.yml:cargo build --features symbolicate --verboseandcargo test --features symbolicate --verbose, run onubuntu / macos / windows with the FP flag set.
Design notes
Why
Box::leakon Unix instead of a self-referential structaddr2line::Context<R>holds references intoobject::File,which holds references into the binary bytes. Storing all three
in a single struct requires self-referential lifetimes that don't
work without
ouroboros/yokeor unsafePinmachinery. Fora profiler that opens its own binary once per process, leaking
the bytes is the simplest correct approach and incurs a one-time
megabyte-scale allocation rather than perpetual transitive crate
deps.
Why a sorted snapshot on Windows instead of holding the PDB
pdb::PDBand its derivedAddressMap/SymbolTable/DebugInformationborrow from each other through trait objectsthat aren't auto-marked
Send. Storing them together in a statichits
cannot be sent between threadserrors. The clean fix is todo the parse once at first call, copy out the data we need
(
Vec<(u32, String)>of(rva, demangled_name)), drop the PDB,and binary-search on every resolve. PDB's source-line decoding
and
S_INLINESITEexpansion are deferred to a later release; thecurrent shipping output is function name plus the raw address.
Why
current_exeinstead of platform-specific syscallsstd::env::current_exealready wrapsreadlink /proc/self/exeon Linux,
_NSGetExecutablePathon macOS, andGetModuleFileNameWon Windows. Reimplementing these adds codeto maintain without any practical benefit; using
stddirectlykeeps the symbolicator module short.
Address-to-RVA approximation on Windows
The captured addresses are absolute virtual addresses. PDB
resolves via RVA (relative virtual address). Without the
module's load base we approximate by masking
addressto 32bits and binary-searching the sorted RVA index. For non-ASLR
builds this is exact; for ASLR builds the result is usable for
relative comparison inside the same process run but addresses
across runs will differ.
Migration
Default builds are unchanged. The
countersandbacktracesfeatures behave identically. No code edits required for callers
on those paths.
Opting in to
symbolicate:backtracesis implied bysymbolicate; activatingsymbolicatealone is sufficient.Verification
Full matrix run on Windows host (x86_64):
cargo build(default features)cargo build --no-default-featurescargo build --features counterscargo build --features backtracescargo build --features symbolicatecargo build --features dhat-compatcargo build --all-featurescargo +1.75 build --all-features(MSRV)cargo fmt --all -- --checkcargo clippy --all-targets -- -D warningscargo clippy --all-targets --all-features -- -D warningscargo doc --no-depscargo doc --all-features --no-depscargo test --all-features: 60/60 pass (34 unit + 2 newv0.9.2 integration + 4 v0.9.1 integration + 4 v0.9.0
integration + 5 smoke + 11 doctests)
CI matrix: ubuntu-latest, macos-latest, windows-latest plus the
ASAN nightly Linux job. New steps for the
symbolicatefeature.Limitations
resolution from PDB is deferred. Inlined-frame expansion from
S_INLINESITEis deferred.masking; exact for non-ASLR builds, approximate but
intra-run-consistent for ASLR builds.
stdis compiledwithout frame pointers, the walker captures only the immediate
caller of
GlobalAlloc::allocinsidestd's allocationinfrastructure. Documented in v0.9.1; symbolication exposes
the limitation more visibly (the resolved frame is often a CRT
helper like
__dyn_tls_init_callbackrather than the user'sfunction). For meaningful traces, build with
cargo +nightly -Z build-std=stdandRUSTFLAGS="-C force-frame-pointers=yes".via
rustc-demangle. Addingcpp_demangleis a follow-up ifthere's demand.
fsysintegration deferred. The ROADMAP's mention offsysfor reading the binary was not taken:fsys's publicAPI does not currently support generic-path reads (precedent
set by
mod-tempdir's v0.9.3 audit).std::fs::readis useddirectly.
Follow-ups
module.line_program()plumbing. Material work butachievable.
S_INLINESITErecords inside per-module symbol streams.
symbolicatefeature if a realuser needs it.
separate milestone, unaffected by symbolication work.
Release ceremony
Standard pattern:
git tag -a v0.9.2 -m "Release v0.9.2 - symbolication for reports"git push origin maingit push origin v0.9.2cargo publish --dry-run --all-featurescargo publishhttps://crates.io/crates/mod-alloc/0.9.2is live.GitHub release title:
v0.9.2 — Symbolication for Reports.Tag as pre-release;
1.0.0waits forv0.9.4(dev-benchintegration) to ship and bake.
Full Changelog: v0.9.1...v0.9.2
This discussion was created from the release v0.9.2 — Symbolication for Reports.
All reactions