Skip to content

Commit fbcb440

Browse files
authored
[crashtracker] Demangle function names (#1031)
1 parent d669f9f commit fbcb440

File tree

16 files changed

+735
-8
lines changed

16 files changed

+735
-8
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bin_tests/src/bin/crashtracker_bin_test.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ mod unix {
7272
crashtracker::default_signals(),
7373
TEST_COLLECTOR_TIMEOUT_MS,
7474
Some("".to_string()),
75+
true,
7576
)?;
7677

7778
let metadata = Metadata {

bin_tests/tests/crashtracker_bin_test.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,7 @@ fn assert_telemetry_message(crash_telemetry: &[u8], crash_typ: &str) {
321321

322322
let base_expected_tags: std::collections::HashSet<&str> =
323323
std::collections::HashSet::from_iter([
324-
"data_schema_version:1.3",
324+
"data_schema_version:1.4",
325325
"incomplete:false",
326326
"is_crash:true",
327327
"profiler_collecting_sample:1",

datadog-crashtracker-ffi/src/collector/datatypes.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,12 @@ impl<'a> TryFrom<ReceiverConfig<'a>> for datadog_crashtracker::CrashtrackerRecei
5757
pub struct Config<'a> {
5858
pub additional_files: Slice<'a, CharSlice<'a>>,
5959
pub create_alt_stack: bool,
60-
pub use_alt_stack: bool,
60+
pub demangle_names: bool,
6161
/// The endpoint to send the crash report to (can be a file://).
6262
/// If None, the crashtracker will infer the agent host from env variables.
6363
pub endpoint: Option<&'a Endpoint>,
64+
/// Optional filename for a unix domain socket if the receiver is used asynchonously
65+
pub optional_unix_socket_filename: CharSlice<'a>,
6466
pub resolve_frames: StacktraceCollection,
6567
/// The set of signals we should be registered for.
6668
/// If empty, use the default set.
@@ -69,8 +71,7 @@ pub struct Config<'a> {
6971
/// This is given as a uint32_t, but the actual timeout needs to fit inside of an i32 (max
7072
/// 2^31-1). This is a limitation of the various interfaces used to guarantee the timeout.
7173
pub timeout_ms: u32,
72-
/// Optional filename for a unix domain socket if the receiver is used asynchonously
73-
pub optional_unix_socket_filename: CharSlice<'a>,
74+
pub use_alt_stack: bool,
7475
}
7576

7677
impl<'a> TryFrom<Config<'a>> for datadog_crashtracker::CrashtrackerConfiguration {
@@ -90,6 +91,7 @@ impl<'a> TryFrom<Config<'a>> for datadog_crashtracker::CrashtrackerConfiguration
9091
let signals = value.signals.iter().copied().collect();
9192
let timeout_ms = value.timeout_ms;
9293
let unix_socket_path = value.optional_unix_socket_filename.try_to_string_option()?;
94+
let demangle_names = value.demangle_names;
9395
Self::new(
9496
additional_files,
9597
create_alt_stack,
@@ -99,6 +101,7 @@ impl<'a> TryFrom<Config<'a>> for datadog_crashtracker::CrashtrackerConfiguration
99101
signals,
100102
timeout_ms,
101103
unix_socket_path,
104+
demangle_names,
102105
)
103106
}
104107
}

datadog-crashtracker-ffi/src/crash_info/api.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,20 @@ use ddcommon::Endpoint;
66
use ddcommon_ffi::{wrap_with_void_ffi_result, Handle, ToInner, VoidResult};
77
use function_name::named;
88

9+
/// # Safety
10+
/// The `crash_info` can be null, but if non-null it must point to a Builder made by this module,
11+
/// which has not previously been dropped.
12+
#[no_mangle]
13+
#[must_use]
14+
#[named]
15+
pub unsafe extern "C" fn ddog_crasht_CrashInfo_demangle_names(
16+
mut crash_info: *mut Handle<CrashInfo>,
17+
) -> VoidResult {
18+
wrap_with_void_ffi_result!({
19+
crash_info.to_inner_mut()?.demangle_names()?;
20+
})
21+
}
22+
923
/// # Safety
1024
/// The `builder` can be null, but if non-null it must point to a Frame
1125
/// made by this module, which has not previously been dropped.

datadog-crashtracker/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ rand = "0.8.5"
4747
schemars = "0.8.21"
4848
serde = {version = "1.0", features = ["derive"]}
4949
serde_json = {version = "1.0"}
50+
symbolic-demangle = { version = "12.8.0", default-features = false, features = ["rust", "cpp", "msvc"] }
51+
symbolic-common = { version = "12.8.0", default-features = false }
5052
tokio = { version = "1.23", features = ["rt", "macros", "io-std", "io-util"] }
5153
uuid = { version = "1.4.1", features = ["v4", "serde"] }
5254

datadog-crashtracker/src/collector/api.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ fn test_crash() -> anyhow::Result<()> {
139139
default_signals(),
140140
timeout_ms,
141141
None,
142+
true,
142143
)?;
143144
let metadata = Metadata::new(
144145
"libname".to_string(),
@@ -197,6 +198,7 @@ fn test_altstack_paradox() -> anyhow::Result<()> {
197198
default_signals(),
198199
timeout_ms,
199200
None,
201+
true,
200202
);
201203

202204
// This is slightly over-tuned to the language of the error message, but it'd require some
@@ -267,6 +269,7 @@ fn test_altstack_use_create() -> anyhow::Result<()> {
267269
signals,
268270
timeout_ms,
269271
None,
272+
true,
270273
)?;
271274
let metadata = Metadata::new(
272275
"libname".to_string(),
@@ -392,6 +395,7 @@ fn test_altstack_use_nocreate() -> anyhow::Result<()> {
392395
signals,
393396
timeout_ms,
394397
None,
398+
true,
395399
)?;
396400
let metadata = Metadata::new(
397401
"libname".to_string(),
@@ -521,6 +525,7 @@ fn test_altstack_nouse() -> anyhow::Result<()> {
521525
signals,
522526
timeout_ms,
523527
None,
528+
true,
524529
)?;
525530
let metadata = Metadata::new(
526531
"libname".to_string(),
@@ -685,6 +690,7 @@ fn test_waitall_nohang() -> anyhow::Result<()> {
685690
signals,
686691
timeout_ms,
687692
None,
693+
true,
688694
)?;
689695

690696
let metadata = Metadata::new(

datadog-crashtracker/src/crash_info/error_data.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,24 @@ impl ErrorData {
6868
}
6969
}
7070

71+
impl ErrorData {
72+
pub fn demangle_names(&mut self) -> anyhow::Result<()> {
73+
let mut errors = 0;
74+
self.stack.demangle_names().unwrap_or_else(|_| errors += 1);
75+
for thread in &mut self.threads {
76+
thread
77+
.stack
78+
.demangle_names()
79+
.unwrap_or_else(|_| errors += 1);
80+
}
81+
anyhow::ensure!(
82+
errors == 0,
83+
"Failed to demangle names, see frame comments for details"
84+
);
85+
Ok(())
86+
}
87+
}
88+
7189
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
7290
pub enum SourceType {
7391
Crashtracking,

datadog-crashtracker/src/crash_info/mod.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,11 @@ pub struct CrashInfo {
6262

6363
impl CrashInfo {
6464
pub fn current_schema_version() -> String {
65-
"1.3".to_string()
65+
"1.4".to_string()
66+
}
67+
68+
pub fn demangle_names(&mut self) -> anyhow::Result<()> {
69+
self.error.demangle_names()
6670
}
6771
}
6872

@@ -131,7 +135,7 @@ mod tests {
131135
fn test_schema_matches_rfc() {
132136
let rfc_schema_filename = concat!(
133137
env!("CARGO_MANIFEST_DIR"),
134-
"/../docs/RFCs/artifacts/0008-crashtracker-schema.json"
138+
"/../docs/RFCs/artifacts/0009-crashtracker-schema.json"
135139
);
136140
let rfc_schema_json = fs::read_to_string(rfc_schema_filename).expect("File to exist");
137141
let rfc_schema: RootSchema = serde_json::from_str(&rfc_schema_json).expect("Valid json");

datadog-crashtracker/src/crash_info/stacktrace.rs

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ use blazesym::{
1010
};
1111
use schemars::JsonSchema;
1212
use serde::{Deserialize, Serialize};
13+
use symbolic_common::Name;
14+
use symbolic_demangle::{Demangle, DemangleOptions};
1315

1416
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1517
pub struct StackTrace {
@@ -69,6 +71,18 @@ impl StackTrace {
6971
self.incomplete = incomplete;
7072
Ok(())
7173
}
74+
75+
pub fn demangle_names(&mut self) -> anyhow::Result<()> {
76+
let mut errors = 0;
77+
for frame in &mut self.frames {
78+
frame.demangle_name().unwrap_or_else(|e| {
79+
frame.comments.push(e.to_string());
80+
errors += 1;
81+
});
82+
}
83+
anyhow::ensure!(errors == 0);
84+
Ok(())
85+
}
7286
}
7387

7488
#[cfg(unix)]
@@ -137,6 +151,8 @@ pub struct StackFrame {
137151
pub function: Option<String>,
138152
#[serde(default, skip_serializing_if = "Option::is_none")]
139153
pub line: Option<u32>,
154+
#[serde(default, skip_serializing_if = "Option::is_none")]
155+
pub mangled_name: Option<String>,
140156

141157
// Additional Info
142158
#[serde(default, skip_serializing_if = "Vec::is_empty")]
@@ -198,6 +214,23 @@ impl StackFrame {
198214
}
199215
}
200216

217+
impl StackFrame {
218+
pub fn demangle_name(&mut self) -> anyhow::Result<()> {
219+
if let Some(name) = self.function.take() {
220+
match Name::from(&name).demangle(DemangleOptions::name_only()) {
221+
Some(demangled) if demangled != name => {
222+
self.mangled_name = Some(name);
223+
self.function = Some(demangled.to_string());
224+
}
225+
_ => {
226+
self.function = Some(name);
227+
}
228+
}
229+
}
230+
Ok(())
231+
}
232+
}
233+
201234
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
202235
#[allow(clippy::upper_case_acronyms)]
203236
#[repr(C)]
@@ -253,6 +286,7 @@ impl super::test_utils::TestInstance for StackFrame {
253286
let column = Some(2 * seed as u32);
254287
let file = Some(format!("banana{seed}.rs"));
255288
let function = Some(format!("Bar::baz{seed}"));
289+
let mangled_name = Some(format!("_ZN3Bar3baz{seed}E"));
256290
let line = Some((2 * seed + 1) as u32);
257291

258292
let comments = vec![format!("This is a comment on frame {seed}")];
@@ -269,8 +303,70 @@ impl super::test_utils::TestInstance for StackFrame {
269303
column,
270304
file,
271305
function,
306+
mangled_name,
272307
line,
273308
comments,
274309
}
275310
}
276311
}
312+
313+
#[cfg(test)]
314+
mod tests {
315+
use super::*;
316+
317+
#[test]
318+
fn test_demangle_rust() {
319+
let mut frame = StackFrame::new();
320+
frame.function = Some("_ZN3std2rt10lang_start17h7a87e81ecc4a9d6cE".to_string());
321+
frame.demangle_name().unwrap();
322+
assert_eq!(frame.function, Some("std::rt::lang_start".to_string()));
323+
assert_eq!(
324+
frame.mangled_name,
325+
Some("_ZN3std2rt10lang_start17h7a87e81ecc4a9d6cE".to_string())
326+
);
327+
}
328+
329+
#[test]
330+
fn test_demangle_cpp() {
331+
let mut frame = StackFrame::new();
332+
frame.function = Some("_ZN3Foo3barEv".to_string());
333+
frame.demangle_name().unwrap();
334+
assert_eq!(frame.function, Some("Foo::bar".to_string()));
335+
assert_eq!(frame.mangled_name, Some("_ZN3Foo3barEv".to_string()));
336+
}
337+
338+
#[test]
339+
fn test_demangle_msvc() {
340+
let mut frame = StackFrame::new();
341+
frame.function = Some("?bar@Foo@@QEAAXXZ".to_string());
342+
frame.demangle_name().unwrap();
343+
assert_eq!(frame.function, Some("Foo::bar".to_string()));
344+
assert_eq!(frame.mangled_name, Some("?bar@Foo@@QEAAXXZ".to_string()));
345+
}
346+
347+
#[test]
348+
fn test_demangle_unmangled() {
349+
let mut frame = StackFrame::new();
350+
frame.function = Some("main".to_string());
351+
frame.demangle_name().unwrap();
352+
assert_eq!(frame.function, Some("main".to_string()));
353+
assert_eq!(frame.mangled_name, None);
354+
}
355+
356+
#[test]
357+
fn test_demangle_empty() {
358+
let mut frame = StackFrame::new();
359+
frame.demangle_name().unwrap();
360+
assert_eq!(frame.function, None);
361+
assert_eq!(frame.mangled_name, None);
362+
}
363+
364+
#[test]
365+
fn test_demangle_invalid() {
366+
let mut frame = StackFrame::new();
367+
frame.function = Some("invalid_mangled_name".to_string());
368+
frame.demangle_name().unwrap();
369+
assert_eq!(frame.function, Some("invalid_mangled_name".to_string()));
370+
assert_eq!(frame.mangled_name, None);
371+
}
372+
}

0 commit comments

Comments
 (0)