gdb_command/
stacktrace.rs

1//! The `Stacktrace` struct represents gathered stacktrace.
2use regex::Regex;
3use std::hash::{Hash, Hasher};
4use std::path::Path;
5
6use crate::error;
7use crate::mappings::{MappedFiles, MappedFilesExt};
8
9/// `StacktraceEntry` struct represents the information about one line of the stack trace.
10#[derive(Clone, Debug, Default)]
11pub struct StacktraceEntry {
12    /// Function address
13    pub address: u64,
14    /// Function name
15    pub function: String,
16    /// Module name
17    pub module: String,
18    /// Offset in module
19    pub offset: u64,
20    /// Debug information
21    pub debug: DebugInfo,
22}
23
24/// `FrameDebug` struct represents the debug information of one frame in stack trace.
25#[derive(Clone, Debug, PartialEq, Eq, Default)]
26pub struct DebugInfo {
27    /// Source file.
28    pub file: String,
29    /// Source line.
30    pub line: u64,
31    /// Source column.
32    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    /// Returns 'StacktraceEntry' struct
74    ///
75    /// # Arguments
76    ///
77    /// * 'entry' - one line of stacktrace from gdb
78    pub fn new<T: AsRef<str>>(entry: T) -> error::Result<StacktraceEntry> {
79        let mut stentry = StacktraceEntry::default();
80
81        // NOTE: the order of applying regexps is important.
82        // 1. GDB source+line+column
83        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            // Get address (optional).
87            if let Some(address) = caps.get(1) {
88                stentry.address = u64::from_str_radix(address.as_str(), 16)?;
89            }
90            // Get function name.
91            stentry.function = caps.get(2).unwrap().as_str().trim().to_string();
92            // Get source file.
93            stentry.debug.file = caps.get(3).unwrap().as_str().trim().to_string();
94            // Get source line.
95            stentry.debug.line = caps.get(4).unwrap().as_str().parse::<u64>()?;
96            // Get source column.
97            stentry.debug.column = caps.get(5).unwrap().as_str().parse::<u64>()?;
98
99            return Ok(stentry);
100        }
101
102        // 2. GDB source+line
103        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            // Get address (optional).
106            if let Some(address) = caps.get(1) {
107                stentry.address = u64::from_str_radix(address.as_str(), 16)?;
108            }
109            // Get function name.
110            stentry.function = caps.get(2).unwrap().as_str().trim().to_string();
111            // Get source file.
112            stentry.debug.file = caps.get(3).unwrap().as_str().trim().to_string();
113            // Get source line.
114            stentry.debug.line = caps.get(4).unwrap().as_str().parse::<u64>()?;
115
116            return Ok(stentry);
117        }
118
119        // 3. GDB source
120        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            // Get address (optional).
123            if let Some(address) = caps.get(1) {
124                stentry.address = u64::from_str_radix(address.as_str(), 16)?;
125            }
126            // Get function name.
127            stentry.function = caps.get(2).unwrap().as_str().trim().to_string();
128            // Get source file.
129            stentry.debug.file = caps.get(3).unwrap().as_str().trim().to_string();
130
131            return Ok(stentry);
132        }
133
134        // 4. GDB from library (address is optional)
135        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            // Get address (optional).
138            if let Some(address) = caps.get(1) {
139                stentry.address = u64::from_str_radix(address.as_str(), 16)?;
140            }
141            // Get function name.
142            stentry.function = caps.get(2).unwrap().as_str().trim().to_string();
143            // Get module name.
144            stentry.module = caps.get(3).unwrap().as_str().trim().to_string();
145
146            return Ok(stentry);
147        }
148
149        // 5. GDB no source (address is optional)
150        let re = Regex::new(r"^ *#[0-9]+ *(?:0x([0-9a-f]+) +in)? *(.+)").unwrap();
151        if let Some(caps) = re.captures(entry.as_ref()) {
152            // Get address (optional).
153            if let Some(address) = caps.get(1) {
154                stentry.address = u64::from_str_radix(address.as_str(), 16)?;
155            }
156            // Get function name.
157            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    /// Strip prefix from source file path and module
169    ///
170    /// # Arguments
171    ///
172    /// * 'prefix' - path prefix
173    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
183/// Represents the information about stack trace
184pub type Stacktrace = Vec<StacktraceEntry>;
185
186pub trait StacktraceExt {
187    /// Get stack trace as a string and converts it into 'Stacktrace'
188    ///
189    /// # Arguments
190    ///
191    /// * 'trace' - stack trace from gdb
192    ///
193    /// # Return value
194    ///
195    /// The return value is a 'Stacktrace' struct
196    fn from_gdb<T: AsRef<str>>(trace: T) -> error::Result<Stacktrace>;
197
198    /// Compute module offsets for stack trace entries based on mapped files.
199    /// Gdb doesn't print module and offset in stack trace.
200    ///
201    /// # Arguments
202    ///
203    /// * 'mappings' - information about mapped files
204    fn compute_module_offsets(&mut self, mappings: &MappedFiles);
205
206    /// Strip prefix from source file path for all StacktraceEntry's
207    ///
208    /// # Arguments
209    ///
210    /// * 'prefix' - path prefix
211    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}