1use std::borrow::Cow;
4use std::fmt::{self, Debug, Display, Formatter};
5use std::hash::{Hash, Hasher};
6use std::os::raw::c_void;
7use std::path::PathBuf;
8use std::time::SystemTime;
9
10use smallvec::SmallVec;
11use symbolic_demangle::demangle;
12
13use crate::backtrace::{Frame, Trace, TraceImpl};
14use crate::{MAX_DEPTH, MAX_THREAD_NAME};
15
16#[cfg(feature = "perfmaps")]
17fn resolve_in_perfmap(ip: usize) -> Option<Symbol> {
18 use crate::perfmap::get_resolver;
19
20 if let Some(perf_map_resolver) = get_resolver().as_ref() {
21 if let Some(symbol) = perf_map_resolver.find(ip as _) {
22 return Some(Symbol::from(symbol));
23 }
24 }
25
26 None
27}
28
29#[cfg(not(feature = "perfmaps"))]
30fn resolve_in_perfmap(_ip: usize) -> Option<Symbol> {
31 None
32}
33
34#[derive(Clone)]
35pub struct UnresolvedFrames {
36 pub frames: SmallVec<[<TraceImpl as Trace>::Frame; MAX_DEPTH]>,
37 pub thread_name: [u8; MAX_THREAD_NAME],
38 pub thread_name_length: usize,
39 pub thread_id: u64,
40 pub sample_timestamp: SystemTime,
41}
42
43impl Default for UnresolvedFrames {
44 fn default() -> Self {
45 let frames = SmallVec::with_capacity(MAX_DEPTH);
46 Self {
47 frames,
48 thread_name: [0; MAX_THREAD_NAME],
49 thread_name_length: 0,
50 thread_id: 0,
51 sample_timestamp: SystemTime::now(),
52 }
53 }
54}
55
56impl Debug for UnresolvedFrames {
57 fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
58 self.frames.fmt(f)
59 }
60}
61
62impl UnresolvedFrames {
63 pub fn new(
64 frames: SmallVec<[<TraceImpl as Trace>::Frame; MAX_DEPTH]>,
65 tn: &[u8],
66 thread_id: u64,
67 sample_timestamp: SystemTime,
68 ) -> Self {
69 let thread_name_length = tn.len();
70 let mut thread_name = [0; MAX_THREAD_NAME];
71 thread_name[0..thread_name_length].clone_from_slice(tn);
72
73 Self {
74 frames,
75 thread_name,
76 thread_name_length,
77 thread_id,
78 sample_timestamp,
79 }
80 }
81}
82
83impl PartialEq for UnresolvedFrames {
84 fn eq(&self, other: &Self) -> bool {
85 let (frames1, frames2) = (&self.frames, &other.frames);
86 if self.thread_id != other.thread_id || frames1.len() != frames2.len() {
87 false
88 } else {
89 Iterator::zip(frames1.iter(), frames2.iter())
90 .all(|(s1, s2)| s1.symbol_address() == s2.symbol_address())
91 }
92 }
93}
94
95impl Eq for UnresolvedFrames {}
96
97impl Hash for UnresolvedFrames {
98 fn hash<H: Hasher>(&self, state: &mut H) {
99 self.frames
100 .iter()
101 .for_each(|frame| frame.symbol_address().hash(state));
102 self.thread_id.hash(state);
103 }
104}
105
106#[derive(Debug, Clone)]
109pub struct Symbol {
110 pub name: Option<Vec<u8>>,
112
113 pub addr: Option<*mut c_void>,
115
116 pub lineno: Option<u32>,
118
119 pub filename: Option<PathBuf>,
121}
122
123impl Symbol {
124 pub fn raw_name(&self) -> &[u8] {
125 self.name.as_deref().unwrap_or(b"Unknown")
126 }
127
128 pub fn name(&self) -> String {
129 demangle(&String::from_utf8_lossy(self.raw_name())).into_owned()
130 }
131
132 pub fn sys_name(&self) -> Cow<str> {
133 String::from_utf8_lossy(self.raw_name())
134 }
135
136 pub fn filename(&self) -> Cow<str> {
137 self.filename
138 .as_ref()
139 .map(|name| name.as_os_str().to_string_lossy())
140 .unwrap_or_else(|| Cow::Borrowed("Unknown"))
141 }
142
143 pub fn lineno(&self) -> u32 {
144 self.lineno.unwrap_or(0)
145 }
146}
147
148unsafe impl Send for Symbol {}
149
150impl<T> From<&T> for Symbol
151where
152 T: crate::backtrace::Symbol,
153{
154 fn from(symbol: &T) -> Self {
155 Symbol {
156 name: symbol.name(),
157 addr: symbol.addr(),
158 lineno: symbol.lineno(),
159 filename: symbol.filename(),
160 }
161 }
162}
163
164impl Display for Symbol {
165 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
166 f.write_str(&self.name())
167 }
168}
169
170impl PartialEq for Symbol {
171 fn eq(&self, other: &Self) -> bool {
172 self.raw_name() == other.raw_name()
173 }
174}
175
176impl Hash for Symbol {
177 fn hash<H: Hasher>(&self, state: &mut H) {
178 self.raw_name().hash(state)
179 }
180}
181
182#[derive(Clone, PartialEq, Hash)]
185pub struct Frames {
186 pub frames: Vec<Vec<Symbol>>,
187 pub thread_name: String,
188 pub thread_id: u64,
189 pub sample_timestamp: SystemTime,
190}
191
192impl Frames {
193 pub fn thread_name_or_id(&self) -> String {
195 if !self.thread_name.is_empty() {
196 self.thread_name.clone()
197 } else {
198 format!("{:?}", self.thread_id)
199 }
200 }
201}
202
203impl From<UnresolvedFrames> for Frames {
204 fn from(frames: UnresolvedFrames) -> Self {
205 let mut fs = Vec::new();
206
207 let mut frame_iter = frames.frames.iter();
208
209 while let Some(frame) = frame_iter.next() {
210 let mut symbols: Vec<Symbol> = Vec::new();
211
212 if let Some(perfmap_symbol) = resolve_in_perfmap(frame.ip() as usize) {
213 symbols.push(perfmap_symbol);
214 } else {
215 frame.resolve_symbol(|symbol| {
216 let symbol = Symbol::from(symbol);
217 symbols.push(symbol);
218 });
219 }
220
221 if symbols.iter().any(|symbol| {
222 matches!(
224 &*symbol.name(),
225 "perf_signal_handler" | "_perf_signal_handler"
226 )
227 }) {
228 frame_iter.next();
230 continue;
231 }
232
233 if !symbols.is_empty() {
234 fs.push(symbols);
235 }
236 }
237
238 Self {
239 frames: fs,
240 thread_name: String::from_utf8_lossy(&frames.thread_name[0..frames.thread_name_length])
241 .into_owned(),
242 thread_id: frames.thread_id,
243 sample_timestamp: frames.sample_timestamp,
244 }
245 }
246}
247
248impl Eq for Frames {}
249
250impl Debug for Frames {
251 fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
252 for frame in self.frames.iter() {
253 write!(f, "FRAME: ")?;
254 for symbol in frame.iter() {
255 write!(f, "{} -> ", symbol)?;
256 }
257 }
258 write!(f, "THREAD: ")?;
259 if !self.thread_name.is_empty() {
260 write!(f, "{}", self.thread_name)
261 } else {
262 write!(f, "ThreadId({})", self.thread_id)
263 }
264 }
265}
266
267#[cfg(test)]
268mod tests {
269 use super::*;
270
271 #[test]
272 fn demangle_rust() {
273 let symbol = Symbol {
274 name: Some(b"_ZN3foo3barE".to_vec()),
275 addr: None,
276 lineno: None,
277 filename: None,
278 };
279
280 assert_eq!(&symbol.name(), "foo::bar")
281 }
282
283 #[test]
284 fn demangle_cpp() {
285 let name =
286 b"_ZNK3MapI10StringName3RefI8GDScriptE10ComparatorIS0_E16DefaultAllocatorE3hasERKS0_"
287 .to_vec();
288
289 let symbol = Symbol {
290 name: Some(name),
291 addr: None,
292 lineno: None,
293 filename: None,
294 };
295
296 assert_eq!(
297 &symbol.name(),
298 "Map<StringName, Ref<GDScript>, Comparator<StringName>, DefaultAllocator>::has(StringName const&) const"
299 )
300 }
301}