Skip to content

Commit 73decea

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 73decea

File tree

6 files changed

+217
-19
lines changed

6 files changed

+217
-19
lines changed

Cargo.toml

Lines changed: 9 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 }
@@ -32,6 +35,7 @@ backtrace-sys = { path = "backtrace-sys", version = "0.1.3", optional = true }
3235

3336
[build-dependencies]
3437
serde_codegen = { version = "0.8", optional = true }
38+
gcc = { version = "0.3.43", optional = true }
3539

3640
# Each feature controls the two phases of finding a backtrace: getting a
3741
# backtrace and then resolving instruction pointers to symbols. The default
@@ -42,7 +46,7 @@ serde_codegen = { version = "0.8", optional = true }
4246
# Note that not all features are available on all platforms, so even though a
4347
# feature is enabled some other feature may be used instead.
4448
[features]
45-
default = ["libunwind", "libbacktrace", "coresymbolication", "dladdr", "dbghelp"]
49+
default = ["libunwind", "libbacktrace", "coresymbolication", "dladdr", "dbghelp", "demangle_cpp"]
4650

4751
#=======================================
4852
# Methods of acquiring a backtrace
@@ -76,9 +80,12 @@ default = ["libunwind", "libbacktrace", "coresymbolication", "dladdr", "dbghelp"
7680
# enough on OSX.
7781
# - coresymbolication: this feature uses the undocumented core symbolication
7882
# framework on OS X to symbolize.
83+
# - demangle_cpp: this feature enables demangling C++ symbols from C++
84+
# frames.
7985
libbacktrace = ["backtrace-sys"]
8086
dladdr = []
8187
coresymbolication = []
88+
demangle_cpp = ["cpp_demangle", "gcc"]
8289

8390
#=======================================
8491
# Methods of serialization

build.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ fn main() {
2525

2626
expand_serde(out_dir);
2727
println!("cargo:rerun-if-changed=src/capture.rs");
28+
29+
compile_cpp();
30+
println!("cargo:rerun-if-changed=cpp/trampoline.cpp");
2831
}
2932

3033
#[cfg(not(feature = "serialize-serde"))]
@@ -49,3 +52,23 @@ fn expand_serde(out_dir: &Path) {
4952
serde_codegen::expand(&out_dir.join("tmp.rs"), &dst).unwrap();
5053
}).unwrap().join().unwrap();
5154
}
55+
56+
// TODO: how to only do this for tests? I tried to do:
57+
//
58+
// > #[cfg(all(test, feature = "demangle_cpp"))]
59+
//
60+
// but it did not work...
61+
#[cfg(feature = "demangle_cpp")]
62+
fn compile_cpp() {
63+
extern crate gcc;
64+
65+
gcc::Config::new()
66+
.cpp(true)
67+
.debug(true)
68+
.opt_level(0)
69+
.file("cpp/trampoline.cpp")
70+
.compile("libcpptrampoline.a");
71+
}
72+
73+
#[cfg(not(feature = "demangle_cpp"))]
74+
fn compile_cpp() {}

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+
}

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-

tests/smoke.rs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,3 +165,71 @@ fn is_serde() {
165165
is_serialize::<backtrace::Backtrace>();
166166
is_deserialize::<backtrace::Backtrace>();
167167
}
168+
169+
#[test]
170+
#[cfg(all(not(target_os = "windows"), feature = "demangle_cpp"))]
171+
fn smoke_test_cpp() {
172+
use std::sync::atomic::{ATOMIC_BOOL_INIT, AtomicBool, Ordering};
173+
174+
extern "C" {
175+
fn cpp_trampoline(func: extern "C" fn()) -> ();
176+
}
177+
178+
static RAN_ASSERTS: AtomicBool = ATOMIC_BOOL_INIT;
179+
180+
extern "C" fn assert_cpp_frames() {
181+
let mut physical_frames = Vec::new();
182+
backtrace::trace(|cx| {
183+
physical_frames.push(cx.ip());
184+
185+
// We only want to capture this closure's frame, assert_cpp_frames,
186+
// space::templated_trampoline, and cpp_trampoline. Those are
187+
// logical frames, which might be inlined into fewer physical
188+
// frames, so we may end up with extra logical frames after
189+
// resolving these.
190+
physical_frames.len() < 4
191+
});
192+
193+
let names: Vec<_> = physical_frames.into_iter()
194+
.flat_map(|ip| {
195+
let mut logical_frame_names = vec![];
196+
197+
backtrace::resolve(ip, |sym| {
198+
let sym_name = sym.name().expect("Should have a symbol name");
199+
let demangled = sym_name.to_string();
200+
logical_frame_names.push(demangled);
201+
});
202+
203+
assert!(!logical_frame_names.is_empty(),
204+
"Should have resolved at least one symbol for the physical frame");
205+
206+
logical_frame_names
207+
})
208+
// Skip the backtrace::trace closure and assert_cpp_frames, and then
209+
// take the two C++ frame names.
210+
.skip_while(|name| !name.contains("trampoline"))
211+
.take(2)
212+
.collect();
213+
214+
println!("actual names = {:#?}", names);
215+
216+
let expected = [
217+
"void space::templated_trampoline<void (*)()>(void (*)())",
218+
"cpp_trampoline",
219+
];
220+
println!("expected names = {:#?}", expected);
221+
222+
assert_eq!(names.len(), expected.len());
223+
for (actual, expected) in names.iter().zip(expected.iter()) {
224+
assert_eq!(actual, expected);
225+
}
226+
227+
RAN_ASSERTS.store(true, Ordering::SeqCst);
228+
}
229+
230+
assert!(!RAN_ASSERTS.load(Ordering::SeqCst));
231+
unsafe {
232+
cpp_trampoline(assert_cpp_frames);
233+
}
234+
assert!(RAN_ASSERTS.load(Ordering::SeqCst));
235+
}

0 commit comments

Comments
 (0)