Skip to content

Commit 7f9ec03

Browse files
authored
[crashtracker] Take relative address and build id for remote symbolication (#473)
1 parent cd598b9 commit 7f9ec03

File tree

5 files changed

+161
-10
lines changed

5 files changed

+161
-10
lines changed

crashtracker/src/crash_info.rs

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@ use crate::stacktrace::StackFrame;
55
use crate::telemetry::TelemetryCrashUploader;
66
use crate::CrashtrackerConfiguration;
77
use anyhow::Context;
8-
#[cfg(unix)]
9-
use blazesym::symbolize::{Process, Source, Symbolizer};
108
use chrono::{DateTime, Utc};
119
use ddcommon::tag::Tag;
1210
use serde::{Deserialize, Serialize};
@@ -102,8 +100,19 @@ impl Default for CrashInfo {
102100

103101
#[cfg(unix)]
104102
impl CrashInfo {
105-
pub fn resolve_names(&mut self, src: &Source) -> anyhow::Result<()> {
106-
let symbolizer = Symbolizer::new();
103+
pub fn normalize_ips(&mut self, pid: u32) -> anyhow::Result<()> {
104+
let normalizer = blazesym::normalize::Normalizer::new();
105+
let pid = pid.into();
106+
self.stacktrace.iter_mut().for_each(|frame| {
107+
frame
108+
.normalize_ip(&normalizer, pid)
109+
.unwrap_or_else(|err| eprintln!("Error resolving name {err}"))
110+
});
111+
Ok(())
112+
}
113+
114+
pub fn resolve_names(&mut self, src: &blazesym::symbolize::Source) -> anyhow::Result<()> {
115+
let symbolizer = blazesym::symbolize::Symbolizer::new();
107116
for frame in &mut self.stacktrace {
108117
// Resolving names is best effort, just print the error and continue
109118
frame
@@ -114,10 +123,10 @@ impl CrashInfo {
114123
}
115124

116125
pub fn resolve_names_from_process(&mut self, pid: u32) -> anyhow::Result<()> {
117-
let mut process = Process::new(pid.into());
126+
let mut process = blazesym::symbolize::Process::new(pid.into());
118127
// https://github.com/libbpf/blazesym/issues/518
119128
process.map_files = false;
120-
let src = Source::Process(process);
129+
let src = blazesym::symbolize::Source::Process(process);
121130
self.resolve_names(&src)
122131
}
123132
}

crashtracker/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,4 +69,4 @@ pub use crash_handler::{update_config, update_metadata};
6969
pub use crash_info::*;
7070
#[cfg(unix)]
7171
pub use receiver::{receiver_entry_point_stdin, reciever_entry_point_unix_socket};
72-
pub use stacktrace::{StackFrame, StackFrameNames};
72+
pub use stacktrace::{NormalizedAddress, NormalizedAddressMeta, StackFrame, StackFrameNames};

crashtracker/src/stacktrace.rs

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// SPDX-License-Identifier: Apache-2.0
33

44
use serde::{Deserialize, Serialize};
5+
use std::path::PathBuf;
56

67
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
78
pub struct StackFrameNames {
@@ -33,16 +34,54 @@ pub struct StackFrame {
3334
pub names: Option<Vec<StackFrameNames>>,
3435
#[serde(skip_serializing_if = "Option::is_none")]
3536
#[serde(default)]
37+
pub normalized_ip: Option<NormalizedAddress>,
38+
#[serde(skip_serializing_if = "Option::is_none")]
39+
#[serde(default)]
3640
pub sp: Option<String>,
3741
#[serde(skip_serializing_if = "Option::is_none")]
3842
#[serde(default)]
3943
pub symbol_address: Option<String>,
4044
}
4145

46+
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
47+
pub enum NormalizedAddressMeta {
48+
Apk(PathBuf),
49+
Elf {
50+
path: PathBuf,
51+
build_id: Option<Vec<u8>>,
52+
},
53+
Unknown,
54+
Unexpected(String),
55+
}
56+
57+
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
58+
pub struct NormalizedAddress {
59+
pub file_offset: u64,
60+
pub meta: NormalizedAddressMeta,
61+
}
62+
4263
#[cfg(unix)]
4364
mod unix {
4465
use super::*;
45-
use blazesym::symbolize::{Input, Source, Sym, Symbolized, Symbolizer};
66+
use blazesym::{
67+
normalize::{Normalizer, UserMeta},
68+
symbolize::{Input, Source, Sym, Symbolized, Symbolizer},
69+
Pid,
70+
};
71+
72+
impl From<&UserMeta> for NormalizedAddressMeta {
73+
fn from(value: &UserMeta) -> Self {
74+
match value {
75+
UserMeta::Apk(a) => Self::Apk(a.path.clone()),
76+
UserMeta::Elf(e) => Self::Elf {
77+
path: e.path.clone(),
78+
build_id: e.build_id.clone(),
79+
},
80+
UserMeta::Unknown(_) => Self::Unknown,
81+
_ => Self::Unexpected(format!("{value:?}")),
82+
}
83+
}
84+
}
4685

4786
impl From<Sym<'_>> for StackFrameNames {
4887
fn from(value: Sym) -> Self {
@@ -58,6 +97,19 @@ mod unix {
5897
}
5998

6099
impl StackFrame {
100+
pub fn normalize_ip(&mut self, normalizer: &Normalizer, pid: Pid) -> anyhow::Result<()> {
101+
if let Some(ip) = &self.ip {
102+
let ip = ip.trim_start_matches("0x");
103+
let ip = u64::from_str_radix(ip, 16)?;
104+
let normed = normalizer.normalize_user_addrs(pid, &[ip])?;
105+
anyhow::ensure!(normed.outputs.len() == 1);
106+
let (file_offset, meta_idx) = normed.outputs[0];
107+
let meta = (&normed.meta[meta_idx]).into();
108+
self.normalized_ip = Some(NormalizedAddress { file_offset, meta });
109+
}
110+
Ok(())
111+
}
112+
61113
pub fn resolve_names(
62114
&mut self,
63115
src: &Source,

profiling-ffi/src/crashtracker/crash_info.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,26 @@ pub unsafe extern "C" fn ddog_crashinfo_drop(crashinfo: *mut CrashInfo) {
3131
}
3232
}
3333

34+
/// Best effort attempt to normalize all `ip` on the stacktrace.
35+
/// `pid` must be the pid of the currently active process where the ips came from.
36+
///
37+
/// # Safety
38+
/// `crashinfo` must be a valid pointer to a `CrashInfo` object.
39+
#[cfg(unix)]
40+
#[no_mangle]
41+
#[must_use]
42+
pub unsafe extern "C" fn ddog_crashinfo_normalize_ips(
43+
crashinfo: *mut CrashInfo,
44+
pid: u32,
45+
) -> CrashtrackerResult {
46+
(|| {
47+
let crashinfo = crashinfo_ptr_to_inner(crashinfo)?;
48+
crashinfo.normalize_ips(pid)
49+
})()
50+
.context("ddog_crashinfo_normalize_ips failed")
51+
.into()
52+
}
53+
3454
/// Adds a "counter" variable, with the given value. Useful for determining if
3555
/// "interesting" operations were occurring when the crash did.
3656
///

profiling-ffi/src/crashtracker/datatypes.rs

Lines changed: 72 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
use crate::exporter::{self, ProfilingEndpoint};
55
pub use datadog_crashtracker::{ProfilingOpTypes, StacktraceCollection};
66
use ddcommon::tag::Tag;
7-
use ddcommon_ffi::slice::{AsBytes, CharSlice};
7+
use ddcommon_ffi::slice::{AsBytes, ByteSlice, CharSlice};
88
use ddcommon_ffi::{Error, Slice, StringWrapper};
99
use std::ops::Not;
1010
use std::time::Duration;
@@ -263,6 +263,73 @@ impl From<anyhow::Result<()>> for CrashtrackerResult {
263263
}
264264
}
265265

266+
#[repr(C)]
267+
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
268+
pub enum NormalizedAddressTypes {
269+
// Make None 0 so that default construction gives none
270+
None = 0,
271+
Elf,
272+
}
273+
274+
#[repr(C)]
275+
pub struct NormalizedAddress<'a> {
276+
file_offset: u64,
277+
build_id: ByteSlice<'a>,
278+
path: CharSlice<'a>,
279+
typ: NormalizedAddressTypes,
280+
}
281+
282+
impl<'a> TryFrom<NormalizedAddress<'a>> for Option<datadog_crashtracker::NormalizedAddress> {
283+
type Error = anyhow::Error;
284+
285+
fn try_from(value: NormalizedAddress<'a>) -> Result<Self, Self::Error> {
286+
Self::try_from(&value)
287+
}
288+
}
289+
290+
impl<'a> TryFrom<&NormalizedAddress<'a>> for Option<datadog_crashtracker::NormalizedAddress> {
291+
type Error = anyhow::Error;
292+
293+
fn try_from(value: &NormalizedAddress<'a>) -> Result<Self, Self::Error> {
294+
if value.typ == NormalizedAddressTypes::None {
295+
Ok(None)
296+
} else {
297+
Ok(Some(value.try_into()?))
298+
}
299+
}
300+
}
301+
302+
impl<'a> TryFrom<NormalizedAddress<'a>> for datadog_crashtracker::NormalizedAddress {
303+
type Error = anyhow::Error;
304+
305+
fn try_from(value: NormalizedAddress<'a>) -> Result<Self, Self::Error> {
306+
Self::try_from(&value)
307+
}
308+
}
309+
310+
impl<'a> TryFrom<&NormalizedAddress<'a>> for datadog_crashtracker::NormalizedAddress {
311+
type Error = anyhow::Error;
312+
313+
fn try_from(value: &NormalizedAddress<'a>) -> Result<Self, Self::Error> {
314+
match &value.typ {
315+
NormalizedAddressTypes::Elf => {
316+
let build_id = if value.build_id.is_empty() {
317+
None
318+
} else {
319+
Some(Vec::from(value.build_id.as_bytes()))
320+
};
321+
let path = value.path.try_to_utf8()?.into();
322+
let meta = datadog_crashtracker::NormalizedAddressMeta::Elf { build_id, path };
323+
Ok(Self {
324+
file_offset: value.file_offset,
325+
meta,
326+
})
327+
}
328+
_ => anyhow::bail!("Unsupported normalized address type {:?}", value.typ),
329+
}
330+
}
331+
}
332+
266333
#[repr(C)]
267334
pub struct StackFrameNames<'a> {
268335
colno: ddcommon_ffi::Option<u32>,
@@ -298,9 +365,11 @@ impl<'a> TryFrom<&StackFrameNames<'a>> for datadog_crashtracker::StackFrameNames
298365

299366
#[repr(C)]
300367
pub struct StackFrame<'a> {
368+
build_id: CharSlice<'a>,
301369
ip: usize,
302370
module_base_address: usize,
303371
names: Slice<'a, StackFrameNames<'a>>,
372+
normalized_ip: NormalizedAddress<'a>,
304373
sp: usize,
305374
symbol_address: usize,
306375
}
@@ -316,7 +385,6 @@ impl<'a> TryFrom<&StackFrame<'a>> for datadog_crashtracker::StackFrame {
316385
Some(format!("{v:#X}"))
317386
}
318387
}
319-
320388
let ip = to_hex(value.ip);
321389
let module_base_address = to_hex(value.module_base_address);
322390
let names = if value.names.is_empty() {
@@ -328,12 +396,14 @@ impl<'a> TryFrom<&StackFrame<'a>> for datadog_crashtracker::StackFrame {
328396
}
329397
Some(vec)
330398
};
399+
let normalized_ip = (&value.normalized_ip).try_into()?;
331400
let sp = to_hex(value.sp);
332401
let symbol_address = to_hex(value.symbol_address);
333402
Ok(Self {
334403
ip,
335404
module_base_address,
336405
names,
406+
normalized_ip,
337407
sp,
338408
symbol_address,
339409
})

0 commit comments

Comments
 (0)