libdd_crashtracker/crash_info/
error_data.rs1use 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 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 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}