Skip to main content

libdd_crashtracker/crash_info/
error_data.rs

1// Copyright 2024-Present Datadog, Inc. https://www.datadoghq.com/
2// SPDX-License-Identifier: Apache-2.0
3use super::stacktrace::StackTrace;
4#[cfg(unix)]
5use blazesym::helper::ElfResolver;
6use schemars::JsonSchema;
7use serde::{Deserialize, Serialize};
8#[cfg(unix)]
9use std::collections::HashMap;
10#[cfg(unix)]
11use std::path::PathBuf;
12#[cfg(unix)]
13use std::rc::Rc;
14
15#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
16pub struct ErrorData {
17    pub is_crash: bool,
18    pub kind: ErrorKind,
19    #[serde(default, skip_serializing_if = "Option::is_none")]
20    pub message: Option<String>,
21    pub source_type: SourceType,
22    pub stack: StackTrace,
23    #[serde(default, skip_serializing_if = "Vec::is_empty")]
24    pub threads: Vec<ThreadData>,
25}
26
27#[cfg(unix)]
28pub struct CachedElfResolvers<'a> {
29    symbolizer: &'a mut blazesym::symbolize::Symbolizer,
30    elf_resolvers: HashMap<PathBuf, Rc<ElfResolver>>,
31}
32
33#[cfg(unix)]
34impl<'a> CachedElfResolvers<'a> {
35    pub fn new(symbolizer: &'a mut blazesym::symbolize::Symbolizer) -> Self {
36        Self {
37            symbolizer,
38            elf_resolvers: HashMap::new(),
39        }
40    }
41
42    pub fn get_or_insert(&mut self, file_path: &PathBuf) -> anyhow::Result<Rc<ElfResolver>> {
43        use anyhow::Context;
44        let entry = self.elf_resolvers.entry(file_path.clone());
45
46        match entry {
47            std::collections::hash_map::Entry::Occupied(o) => Ok(o.get().clone()),
48            std::collections::hash_map::Entry::Vacant(v) => {
49                let resolver = ElfResolver::open(file_path).with_context(|| {
50                    format!(
51                        "ElfResolver::open failed for '{}'",
52                        file_path.to_string_lossy()
53                    )
54                });
55
56                match resolver {
57                    Ok(resolver) => {
58                        let resolver = Rc::new(resolver);
59                        // even if the symbolizer failed at registering the elf resolver, we still
60                        // cache it to avoid trying to open it again
61                        let _ = self
62                            .symbolizer
63                            .register_elf_resolver(file_path.as_path(), Rc::clone(&resolver));
64                        v.insert(Rc::clone(&resolver));
65                        Ok(resolver)
66                    }
67                    Err(e) => Err(e),
68                }
69            }
70        }
71    }
72}
73
74#[cfg(unix)]
75impl ErrorData {
76    pub fn normalize_ips(&mut self, pid: u32) -> anyhow::Result<()> {
77        let mut symbolizer = blazesym::symbolize::Symbolizer::new();
78        let mut elf_resolvers = CachedElfResolvers::new(&mut symbolizer);
79        let normalizer = blazesym::normalize::Normalizer::builder()
80            .enable_vma_caching(true)
81            .enable_build_ids(true)
82            .enable_build_id_caching(true)
83            .build();
84        self.normalize_ips_impl(pid, &normalizer, &mut elf_resolvers)
85    }
86
87    pub(crate) fn normalize_ips_impl(
88        &mut self,
89        pid: u32,
90        normalizer: &blazesym::normalize::Normalizer,
91        elf_resolvers: &mut CachedElfResolvers,
92    ) -> anyhow::Result<()> {
93        let mut errors = 0;
94        let pid = pid.into();
95        self.stack
96            .normalize_ips(normalizer, pid, elf_resolvers)
97            .unwrap_or_else(|_| errors += 1);
98
99        for thread in &mut self.threads {
100            thread
101                .stack
102                .normalize_ips(normalizer, pid, elf_resolvers)
103                .unwrap_or_else(|_| errors += 1);
104        }
105        anyhow::ensure!(
106            errors == 0,
107            "Failed to normalize ips, see frame comments for details"
108        );
109        Ok(())
110    }
111
112    pub(crate) fn create_symbolizer_source<'a>(
113        pid: u32,
114    ) -> blazesym::symbolize::source::Source<'a> {
115        let mut process = blazesym::symbolize::source::Process::new(pid.into());
116        // https://github.com/libbpf/blazesym/issues/518
117        process.map_files = false;
118        blazesym::symbolize::source::Source::Process(process)
119    }
120
121    pub(crate) fn create_normalizer() -> blazesym::normalize::Normalizer {
122        blazesym::normalize::Normalizer::builder()
123            .enable_vma_caching(true)
124            .enable_build_ids(true)
125            .enable_build_id_caching(true)
126            .build()
127    }
128
129    pub fn resolve_names(&mut self, pid: u32) -> anyhow::Result<()> {
130        let src = Self::create_symbolizer_source(pid);
131        let symbolizer = blazesym::symbolize::Symbolizer::new();
132        self.resolve_names_impl(&symbolizer, &src)
133    }
134
135    pub(crate) fn resolve_names_impl(
136        &mut self,
137        symbolizer: &blazesym::symbolize::Symbolizer,
138        src: &blazesym::symbolize::source::Source,
139    ) -> anyhow::Result<()> {
140        let mut errors = 0;
141        self.stack
142            .resolve_names(src, symbolizer)
143            .unwrap_or_else(|_| errors += 1);
144
145        for thread in &mut self.threads {
146            thread
147                .stack
148                .resolve_names(src, symbolizer)
149                .unwrap_or_else(|_| errors += 1);
150        }
151        anyhow::ensure!(
152            errors == 0,
153            "Failed to resolve names, see frame comments for details"
154        );
155        Ok(())
156    }
157}
158
159impl ErrorData {
160    pub fn demangle_names(&mut self) -> anyhow::Result<()> {
161        let mut errors = 0;
162        self.stack.demangle_names().unwrap_or_else(|_| errors += 1);
163        for thread in &mut self.threads {
164            thread
165                .stack
166                .demangle_names()
167                .unwrap_or_else(|_| errors += 1);
168        }
169        anyhow::ensure!(
170            errors == 0,
171            "Failed to demangle names, see frame comments for details"
172        );
173        Ok(())
174    }
175}
176
177#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
178pub enum SourceType {
179    Crashtracking,
180}
181
182#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
183#[repr(C)]
184pub enum ErrorKind {
185    Panic,
186    UnhandledException,
187    UnixSignal,
188}
189
190#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
191pub struct ThreadData {
192    pub crashed: bool,
193    pub name: String,
194    pub stack: StackTrace,
195    #[serde(default, skip_serializing_if = "Option::is_none")]
196    pub state: Option<String>,
197}
198
199#[cfg(test)]
200impl super::test_utils::TestInstance for ErrorData {
201    fn test_instance(seed: u64) -> Self {
202        Self {
203            is_crash: true,
204            kind: ErrorKind::UnixSignal,
205            message: None,
206            source_type: SourceType::Crashtracking,
207            stack: StackTrace::test_instance(seed),
208            threads: vec![],
209        }
210    }
211}