Skip to content

Fix crash on invalid-UTF-8/truncated profiler output during demangling - #466

Open
JakkuSakura wants to merge 1 commit into
flamegraph-rs:mainfrom
JakkuSakura:fix/demangle-invalid-utf8
Open

Fix crash on invalid-UTF-8/truncated profiler output during demangling#466
JakkuSakura wants to merge 1 commit into
flamegraph-rs:mainfrom
JakkuSakura:fix/demangle-invalid-utf8

Conversation

@JakkuSakura

Copy link
Copy Markdown
Contributor

Summary

Profiler output (perf script, dtrace, xctrace) is not guaranteed to be valid UTF-8 or well-formed as a whole - e.g. a garbled symbol name (as reported in #403, on Debian/perf, specifically with libssl-linked binaries), or output truncated mid multi-byte sequence/mid-tag by an interrupted profiler run. Demangling assumed the whole buffer was valid UTF-8 and crashed hard on the first bad byte instead of degrading gracefully.

This is a platform-agnostic problem, so it gets one fix used identically on every platform rather than a per-platform workaround:

  • demangle_bytes is a byte-level reimplementation of rustc_demangle's own line-scanning algorithm. Rust's mangling grammar (_ZN.../_R... plus [A-Za-z0-9_.$]*) is pure ASCII, so demangling never actually needs the whole buffer to be UTF-8 - only the matched candidate substrings do, and those are always ASCII by construction. Every other byte passes through completely unmodified, wherever it occurs, with no crash and no data loss (unlike a lossy re-encode, which would corrupt any legitimate non-UTF-8 bytes elsewhere in the buffer too). The scan is a single linear pass over the input (gating on the cheap _ byte before the short prefix comparison), matching the algorithmic complexity of rustc_demangle's own str::find-based scan rather than a naive O(n*m) substring search - verified at ~45ms for an 81MB buffer densely packed with underscores.
  • This single function is used unconditionally on every platform: on Linux/dtrace it demangles the raw profiler text directly, and on macOS's xctrace path it demangles each XML attribute value in place (replacing that path's separate from_utf8_lossy-based demangling).
  • xctrace's XML tokenizer can additionally hard-error on truncated/malformed input (e.g. a tag cut off mid-write), which is the same underlying problem surfacing as a different symptom. Fixed the same way: treat that as an early end of stream and keep whatever was parsed before the truncation, instead of failing the whole run.

Fixes #403.

Test plan

  • cargo test --lib passes on aarch64-apple-darwin and cargo check/clippy pass on x86_64-unknown-linux-gnu (cross-checked; no Linux perf environment available for a full end-to-end run)
  • New unit tests: clean symbol still demangles correctly, invalid UTF-8 adjacent to a match round-trips byte-for-byte with no corruption, macOS XML truncation is tolerated, existing array-type/semicolon test still passes
  • Manually confirmed the exact before/after: unmodified rustc_demangle::demangle_stream errors (stream did not contain valid UTF-8) on the same input demangle_bytes succeeds on
  • cargo fmt/clippy clean on both targets

🤖 Generated with Claude Code

Profiler output (perf script, dtrace, xctrace) is not guaranteed to be
valid UTF-8 or well-formed as a whole - e.g. a garbled symbol name
(reported in flamegraph-rs#403, on Debian/perf, specifically with libssl-linked
binaries), or output truncated mid multi-byte sequence/mid-tag by an
interrupted profiler. Demangling assumed the whole buffer was valid
UTF-8 and crashed hard on the first bad byte instead of degrading
gracefully. This is a platform-agnostic problem, so it gets one fix
used identically everywhere rather than a per-platform workaround:

- demangle_bytes is a byte-level reimplementation of rustc_demangle's
  own line-scanning algorithm. Rust's mangling grammar (_ZN.../_R...
  plus [A-Za-z0-9_.$]*) is pure ASCII, so demangling never actually
  needs the whole buffer to be UTF-8 - only the matched candidate
  substrings do, and those are always ASCII by construction. Every
  other byte passes through completely unmodified, wherever it occurs,
  with no crash and no data loss (unlike a lossy re-encode, which
  would corrupt any legitimate non-UTF-8 bytes elsewhere in the buffer
  too). The scan is a single linear pass over the input (gating on the
  cheap '_' byte before the short prefix comparison), matching the
  algorithmic complexity of rustc_demangle's own str::find-based scan
  rather than a naive O(n*m) substring search - verified at ~45ms for
  an 81MB buffer densely packed with underscores.

- This single function is used unconditionally on every platform: on
  Linux/dtrace it demangles the raw profiler text directly, and on
  macOS's xctrace path it demangles each XML attribute value in place
  (replacing that path's separate from_utf8_lossy-based demangling).

- xctrace's XML tokenizer can additionally hard-error on truncated/
  malformed input (e.g. a tag cut off mid-write), which is the same
  underlying problem surfacing as a different symptom. Fixed the same
  way: treat that as an early end of stream and keep whatever was
  parsed before the truncation, instead of failing the whole run.

Fixes flamegraph-rs#403.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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.

Error: invalid utf-8 sequence

1 participant