Skip to content

Commit 63e76b3

Browse files
committed
Add support for demangling C++ frames' symbols
This adds a new optional (but on-by-default) dependency on the `cpp_demangle` crate. When this feature is enabled, if demangling a frame's symbol as a Rust symbol fails, then we attempt to demangle it as a C++ symbol.
1 parent 4477f21 commit 63e76b3

File tree

9 files changed

+223
-19
lines changed

9 files changed

+223
-19
lines changed

.travis.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ script:
1818
- cargo test --no-default-features --features 'serialize-serde'
1919
- cargo test --no-default-features --features 'serialize-rustc'
2020
- cargo test --no-default-features --features 'serialize-rustc serialize-serde'
21+
- cd ./cpp_smoke_test && cargo test
2122
- cargo clean && cargo build
2223
- rustdoc --test README.md -L target/debug/deps -L target/debug
2324
- cargo doc --no-deps

Cargo.toml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,15 @@ build = "build.rs"
1616
[dependencies]
1717
libc = "0.2"
1818
cfg-if = "0.1"
19-
rustc-demangle = "0.1"
19+
rustc-demangle = "0.1.4"
2020

2121
# Optionally enable the ability to serialize a `Backtrace`
2222
serde = { version = "0.8", optional = true }
2323
rustc-serialize = { version = "0.3", optional = true }
2424

25+
# Optionally demangle C++ frames' symbols in backtraces.
26+
cpp_demangle = { version = "0.2", optional = true }
27+
2528
[target.'cfg(windows)'.dependencies]
2629
dbghelp-sys = { version = "0.2", optional = true }
2730
kernel32-sys = { version = "0.2", optional = true }
@@ -42,7 +45,7 @@ serde_codegen = { version = "0.8", optional = true }
4245
# Note that not all features are available on all platforms, so even though a
4346
# feature is enabled some other feature may be used instead.
4447
[features]
45-
default = ["libunwind", "libbacktrace", "coresymbolication", "dladdr", "dbghelp"]
48+
default = ["libunwind", "libbacktrace", "coresymbolication", "dladdr", "dbghelp", "cpp_demangle"]
4649

4750
#=======================================
4851
# Methods of acquiring a backtrace

cpp_smoke_test/Cargo.toml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[package]
2+
name = "cpp_smoke_test"
3+
version = "0.1.0"
4+
authors = ["Nick Fitzgerald <[email protected]>"]
5+
build = "build.rs"
6+
7+
[build-dependencies]
8+
gcc = "0.3.43"
9+
10+
[dependencies]
11+
"backtrace" = { path = ".." }

cpp_smoke_test/build.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
extern crate gcc;
2+
3+
fn main() {
4+
compile_cpp();
5+
}
6+
7+
fn compile_cpp() {
8+
println!("cargo:rerun-if-changed=cpp/trampoline.cpp");
9+
10+
gcc::Config::new()
11+
.cpp(true)
12+
.debug(true)
13+
.opt_level(0)
14+
.file("cpp/trampoline.cpp")
15+
.compile("libcpptrampoline.a");
16+
}

cpp_smoke_test/cpp/trampoline.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#include <stdio.h>
2+
3+
namespace space {
4+
template <typename FuncT>
5+
void templated_trampoline(FuncT func) {
6+
func();
7+
}
8+
}
9+
10+
typedef void (*FuncPtr)();
11+
12+
extern "C" void cpp_trampoline(FuncPtr func) {
13+
space::templated_trampoline(func);
14+
}

cpp_smoke_test/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#[test]
2+
fn it_works() {
3+
}

cpp_smoke_test/tests/smoke.rs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
extern crate cpp_smoke_test;
2+
extern crate backtrace;
3+
4+
use std::sync::atomic::{ATOMIC_BOOL_INIT, AtomicBool, Ordering};
5+
6+
extern "C" {
7+
fn cpp_trampoline(func: extern "C" fn()) -> ();
8+
}
9+
10+
#[test]
11+
#[cfg(not(target_os = "windows"))]
12+
fn smoke_test_cpp() {
13+
static RAN_ASSERTS: AtomicBool = ATOMIC_BOOL_INIT;
14+
15+
extern "C" fn assert_cpp_frames() {
16+
let mut physical_frames = Vec::new();
17+
backtrace::trace(|cx| {
18+
physical_frames.push(cx.ip());
19+
20+
// We only want to capture this closure's frame, assert_cpp_frames,
21+
// space::templated_trampoline, and cpp_trampoline. Those are
22+
// logical frames, which might be inlined into fewer physical
23+
// frames, so we may end up with extra logical frames after
24+
// resolving these.
25+
physical_frames.len() < 4
26+
});
27+
28+
let names: Vec<_> = physical_frames.into_iter()
29+
.flat_map(|ip| {
30+
let mut logical_frame_names = vec![];
31+
32+
backtrace::resolve(ip, |sym| {
33+
let sym_name = sym.name().expect("Should have a symbol name");
34+
let demangled = sym_name.to_string();
35+
logical_frame_names.push(demangled);
36+
});
37+
38+
assert!(!logical_frame_names.is_empty(),
39+
"Should have resolved at least one symbol for the physical frame");
40+
41+
logical_frame_names
42+
})
43+
// Skip the backtrace::trace closure and assert_cpp_frames, and then
44+
// take the two C++ frame names.
45+
.skip_while(|name| !name.contains("trampoline"))
46+
.take(2)
47+
.collect();
48+
49+
println!("actual names = {:#?}", names);
50+
51+
let expected = [
52+
"void space::templated_trampoline<void (*)()>(void (*)())",
53+
"cpp_trampoline",
54+
];
55+
println!("expected names = {:#?}", expected);
56+
57+
assert_eq!(names.len(), expected.len());
58+
for (actual, expected) in names.iter().zip(expected.iter()) {
59+
assert_eq!(actual, expected);
60+
}
61+
62+
RAN_ASSERTS.store(true, Ordering::SeqCst);
63+
}
64+
65+
assert!(!RAN_ASSERTS.load(Ordering::SeqCst));
66+
unsafe {
67+
cpp_trampoline(assert_cpp_frames);
68+
}
69+
assert!(RAN_ASSERTS.load(Ordering::SeqCst));
70+
}

src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,9 @@ extern crate cfg_if;
8585

8686
extern crate rustc_demangle;
8787

88+
#[cfg(feature = "cpp_demangle")]
89+
extern crate cpp_demangle;
90+
8891
#[allow(dead_code)] // not used everywhere
8992
#[cfg(unix)]
9093
#[macro_use]

src/symbolize/mod.rs

Lines changed: 100 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
use std::fmt;
2+
#[cfg(not(feature = "cpp_demangle"))]
3+
use std::marker::PhantomData;
24
use std::os::raw::c_void;
35
use std::path::Path;
46
use std::str;
5-
use rustc_demangle::{demangle, Demangle};
7+
use rustc_demangle::{try_demangle, Demangle};
68

79
/// Resolve an address to a symbol, passing the symbol to the specified
810
/// closure.
@@ -108,26 +110,76 @@ impl fmt::Debug for Symbol {
108110
}
109111
}
110112

113+
114+
cfg_if! {
115+
if #[cfg(feature = "cpp_demangle")] {
116+
// Maybe a parsed C++ symbol, if parsing the mangled symbol as Rust
117+
// failed.
118+
struct OptionCppSymbol<'a>(Option<::cpp_demangle::BorrowedSymbol<'a>>);
119+
120+
impl<'a> OptionCppSymbol<'a> {
121+
fn parse(input: &'a [u8]) -> OptionCppSymbol<'a> {
122+
OptionCppSymbol(::cpp_demangle::BorrowedSymbol::new(input).ok())
123+
}
124+
125+
fn none() -> OptionCppSymbol<'a> {
126+
OptionCppSymbol(None)
127+
}
128+
}
129+
} else {
130+
// Make sure to keep this zero-sized, so that the `cpp_demangle` feature
131+
// has no cost when disabled.
132+
struct OptionCppSymbol<'a>(PhantomData<&'a ()>);
133+
134+
impl<'a> OptionCppSymbol<'a> {
135+
fn parse(_: &'a [u8]) -> OptionCppSymbol<'a> {
136+
OptionCppSymbol(PhantomData)
137+
}
138+
139+
fn none() -> OptionCppSymbol<'a> {
140+
OptionCppSymbol(PhantomData)
141+
}
142+
}
143+
}
144+
}
145+
111146
/// A wrapper around a symbol name to provide ergonomic accessors to the
112147
/// demangled name, the raw bytes, the raw string, etc.
148+
// Allow dead code for when the `cpp_demangle` feature is not enabled.
149+
#[allow(dead_code)]
113150
pub struct SymbolName<'a> {
114151
bytes: &'a [u8],
115152
demangled: Option<Demangle<'a>>,
153+
cpp_demangled: OptionCppSymbol<'a>,
116154
}
117155

118156
impl<'a> SymbolName<'a> {
119157
/// Creates a new symbol name from the raw underlying bytes.
120158
pub fn new(bytes: &'a [u8]) -> SymbolName<'a> {
121-
let demangled = str::from_utf8(bytes).ok().map(demangle);
159+
let str_bytes = str::from_utf8(bytes).ok();
160+
let demangled = str_bytes.and_then(|s| try_demangle(s).ok());
161+
162+
let cpp = if demangled.is_none() {
163+
OptionCppSymbol::parse(bytes)
164+
} else {
165+
OptionCppSymbol::none()
166+
};
167+
122168
SymbolName {
123169
bytes: bytes,
124170
demangled: demangled,
171+
cpp_demangled: cpp,
125172
}
126173
}
127174

128-
/// Returns the raw symbol name as `&str` if the symbols is valid utf-8.
175+
/// Returns the raw symbol name as a `str` if the symbols is valid utf-8.
129176
pub fn as_str(&self) -> Option<&'a str> {
130-
self.demangled.as_ref().map(|s| s.as_str())
177+
self.demangled
178+
.as_ref()
179+
.map(|s| s.as_str())
180+
.or_else(|| {
181+
str::from_utf8(self.bytes).ok()
182+
})
131183
}
132184

133185
/// Returns the raw symbol name as a list of bytes
@@ -136,22 +188,54 @@ impl<'a> SymbolName<'a> {
136188
}
137189
}
138190

139-
impl<'a> fmt::Display for SymbolName<'a> {
140-
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
141-
if let Some(ref s) = self.demangled {
142-
s.fmt(f)
143-
} else {
144-
String::from_utf8_lossy(self.bytes).fmt(f)
191+
cfg_if! {
192+
if #[cfg(feature = "cpp_demangle")] {
193+
impl<'a> fmt::Display for SymbolName<'a> {
194+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
195+
if let Some(ref s) = self.demangled {
196+
s.fmt(f)
197+
} else if let Some(ref cpp) = self.cpp_demangled.0 {
198+
cpp.fmt(f)
199+
} else {
200+
String::from_utf8_lossy(self.bytes).fmt(f)
201+
}
202+
}
203+
}
204+
} else {
205+
impl<'a> fmt::Display for SymbolName<'a> {
206+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
207+
if let Some(ref s) = self.demangled {
208+
s.fmt(f)
209+
} else {
210+
String::from_utf8_lossy(self.bytes).fmt(f)
211+
}
212+
}
145213
}
146214
}
147215
}
148216

149-
impl<'a> fmt::Debug for SymbolName<'a> {
150-
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
151-
if let Some(ref s) = self.demangled {
152-
s.fmt(f)
153-
} else {
154-
String::from_utf8_lossy(self.bytes).fmt(f)
217+
cfg_if! {
218+
if #[cfg(feature = "cpp_demangle")] {
219+
impl<'a> fmt::Debug for SymbolName<'a> {
220+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
221+
if let Some(ref s) = self.demangled {
222+
s.fmt(f)
223+
} else if let Some(ref cpp) = self.cpp_demangled.0 {
224+
fmt::Display::fmt(cpp, f)
225+
} else {
226+
String::from_utf8_lossy(self.bytes).fmt(f)
227+
}
228+
}
229+
}
230+
} else {
231+
impl<'a> fmt::Debug for SymbolName<'a> {
232+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
233+
if let Some(ref s) = self.demangled {
234+
s.fmt(f)
235+
} else {
236+
String::from_utf8_lossy(self.bytes).fmt(f)
237+
}
238+
}
155239
}
156240
}
157241
}
@@ -185,4 +269,3 @@ cfg_if! {
185269
use self::noop::Symbol as SymbolImp;
186270
}
187271
}
188-

0 commit comments

Comments
 (0)