gdb_command/
stacktrace.rs1use regex::Regex;
3use std::hash::{Hash, Hasher};
4use std::path::Path;
5
6use crate::error;
7use crate::mappings::{MappedFiles, MappedFilesExt};
8
9#[derive(Clone, Debug, Default)]
11pub struct StacktraceEntry {
12 pub address: u64,
14 pub function: String,
16 pub module: String,
18 pub offset: u64,
20 pub debug: DebugInfo,
22}
23
24#[derive(Clone, Debug, PartialEq, Eq, Default)]
26pub struct DebugInfo {
27 pub file: String,
29 pub line: u64,
31 pub column: u64,
33}
34
35impl PartialEq for StacktraceEntry {
36 fn eq(&self, other: &Self) -> bool {
37 if !self.debug.file.is_empty() && !other.debug.file.is_empty() {
38 return self.debug == other.debug;
39 }
40 if !self.module.is_empty()
41 && !other.module.is_empty()
42 && self.offset != 0
43 && other.offset != 0
44 {
45 return self.module == other.module && self.offset == other.offset;
46 }
47
48 self.address == other.address
49 }
50}
51
52impl Eq for StacktraceEntry {}
53
54impl Hash for StacktraceEntry {
55 fn hash<H: Hasher>(&self, state: &mut H) {
56 if !self.debug.file.is_empty() {
57 self.debug.file.hash(state);
58 self.debug.line.hash(state);
59 self.debug.column.hash(state);
60 return;
61 }
62 if !self.module.is_empty() && self.offset != 0 {
63 self.module.hash(state);
64 self.offset.hash(state);
65 return;
66 }
67
68 self.address.hash(state);
69 }
70}
71
72impl StacktraceEntry {
73 pub fn new<T: AsRef<str>>(entry: T) -> error::Result<StacktraceEntry> {
79 let mut stentry = StacktraceEntry::default();
80
81 let re =
84 Regex::new(r"^ *#[0-9]+ *(?:0x([0-9a-f]+) +in)? *(.+) +at +(.+):(\d+):(\d+)").unwrap();
85 if let Some(caps) = re.captures(entry.as_ref()) {
86 if let Some(address) = caps.get(1) {
88 stentry.address = u64::from_str_radix(address.as_str(), 16)?;
89 }
90 stentry.function = caps.get(2).unwrap().as_str().trim().to_string();
92 stentry.debug.file = caps.get(3).unwrap().as_str().trim().to_string();
94 stentry.debug.line = caps.get(4).unwrap().as_str().parse::<u64>()?;
96 stentry.debug.column = caps.get(5).unwrap().as_str().parse::<u64>()?;
98
99 return Ok(stentry);
100 }
101
102 let re = Regex::new(r"^ *#[0-9]+ *(?:0x([0-9a-f]+) +in)? *(.+) +at +(.+):(\d+)").unwrap();
104 if let Some(caps) = re.captures(entry.as_ref()) {
105 if let Some(address) = caps.get(1) {
107 stentry.address = u64::from_str_radix(address.as_str(), 16)?;
108 }
109 stentry.function = caps.get(2).unwrap().as_str().trim().to_string();
111 stentry.debug.file = caps.get(3).unwrap().as_str().trim().to_string();
113 stentry.debug.line = caps.get(4).unwrap().as_str().parse::<u64>()?;
115
116 return Ok(stentry);
117 }
118
119 let re = Regex::new(r"^ *#[0-9]+ *(?:0x([0-9a-f]+) +in)? *(.+) +at +(.+)").unwrap();
121 if let Some(caps) = re.captures(entry.as_ref()) {
122 if let Some(address) = caps.get(1) {
124 stentry.address = u64::from_str_radix(address.as_str(), 16)?;
125 }
126 stentry.function = caps.get(2).unwrap().as_str().trim().to_string();
128 stentry.debug.file = caps.get(3).unwrap().as_str().trim().to_string();
130
131 return Ok(stentry);
132 }
133
134 let re = Regex::new(r"^ *#[0-9]+ *(?:0x([0-9a-f]+) +in)? *(.+) +from +(.+)").unwrap();
136 if let Some(caps) = re.captures(entry.as_ref()) {
137 if let Some(address) = caps.get(1) {
139 stentry.address = u64::from_str_radix(address.as_str(), 16)?;
140 }
141 stentry.function = caps.get(2).unwrap().as_str().trim().to_string();
143 stentry.module = caps.get(3).unwrap().as_str().trim().to_string();
145
146 return Ok(stentry);
147 }
148
149 let re = Regex::new(r"^ *#[0-9]+ *(?:0x([0-9a-f]+) +in)? *(.+)").unwrap();
151 if let Some(caps) = re.captures(entry.as_ref()) {
152 if let Some(address) = caps.get(1) {
154 stentry.address = u64::from_str_radix(address.as_str(), 16)?;
155 }
156 stentry.function = caps.get(2).unwrap().as_str().trim().to_string();
158
159 return Ok(stentry);
160 }
161
162 Err(error::Error::StacktraceParse(format!(
163 "Couldn't parse stack trace entry: {}",
164 entry.as_ref()
165 )))
166 }
167
168 pub fn strip_prefix<T: AsRef<str>>(&mut self, prefix: T) {
174 if let Ok(stripped) = Path::new(&self.debug.file).strip_prefix(prefix.as_ref()) {
175 self.debug.file = stripped.display().to_string();
176 }
177 if let Ok(stripped) = Path::new(&self.module).strip_prefix(prefix.as_ref()) {
178 self.module = stripped.display().to_string();
179 }
180 }
181}
182
183pub type Stacktrace = Vec<StacktraceEntry>;
185
186pub trait StacktraceExt {
187 fn from_gdb<T: AsRef<str>>(trace: T) -> error::Result<Stacktrace>;
197
198 fn compute_module_offsets(&mut self, mappings: &MappedFiles);
205
206 fn strip_prefix<T: AsRef<str>>(&mut self, prefix: T);
212}
213
214impl StacktraceExt for Stacktrace {
215 fn from_gdb<T: AsRef<str>>(trace: T) -> error::Result<Stacktrace> {
216 trace
217 .as_ref()
218 .lines()
219 .map(|s| s.trim().to_string())
220 .filter(|trace| !trace.is_empty())
221 .map(StacktraceEntry::new)
222 .collect()
223 }
224
225 fn compute_module_offsets(&mut self, mappings: &MappedFiles) {
226 self.iter_mut().for_each(|x| {
227 if let Some(y) = mappings.find(x.address) {
228 x.offset = x.address - y.start + y.offset;
229 if !y.name.is_empty() {
230 x.module = y.name;
231 }
232 }
233 });
234 }
235
236 fn strip_prefix<T: AsRef<str>>(&mut self, prefix: T) {
237 *self = std::mem::take(self)
238 .into_iter()
239 .map(|mut e| {
240 e.strip_prefix(prefix.as_ref());
241 e
242 })
243 .collect();
244 }
245}