Skip to content

Commit 4dea00c

Browse files
authored
make it easy to resolve symbols by frame (#526)
Hello, Resolving backtrace (at least for the first time) is very slow, but this is not something that can be solved (I guess). However, not all frames are equally useful, and at least in the code base that I'm working on, we simply ignore bunch of them. We have code something like this: ```rust let mut bt = Backtrace::new_unresolved(); // ... bt.resolve(); let bt: Vec<String> = bt.frames() .iter() .flat_map(BacktraceFrame::symbols) .take_while(is_not_async_stuff) .map(format_symbols) .collect(); ``` For us, it takes around 700~1500ms to resolve whole backtrace. With this PR we're able to write like this: ```rust let bt: Vec<String> = bt.frames_mut() .iter_mut() .flat_map(BacktraceFrame::symbols_resolved) .take_while(is_not_async_stuff) .map(format_symbols) .collect(); ``` And it takes around 25ms to resolve the frames that we actually need. I'm aware of low level utils `backtrace::trace` and `backtrace::resolve_frame`, but this would basically mean that I would basically need to reimplement Backtrace, including Serialize and Deserialize capability, which is too much.
1 parent da34864 commit 4dea00c

File tree

1 file changed

+53
-43
lines changed

1 file changed

+53
-43
lines changed

src/capture.rs

+53-43
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,6 @@ use serde::{Deserialize, Serialize};
2626
pub struct Backtrace {
2727
// Frames here are listed from top-to-bottom of the stack
2828
frames: Vec<BacktraceFrame>,
29-
// The index we believe is the actual start of the backtrace, omitting
30-
// frames like `Backtrace::new` and `backtrace::trace`.
31-
actual_start_index: usize,
3229
}
3330

3431
fn _assert_send_sync() {
@@ -86,6 +83,27 @@ impl Frame {
8683
} => module_base_address.map(|addr| addr as *mut c_void),
8784
}
8885
}
86+
87+
/// Resolve all addresses in the frame to their symbolic names.
88+
fn resolve_symbols(&self) -> Vec<BacktraceSymbol> {
89+
let mut symbols = Vec::new();
90+
let sym = |symbol: &Symbol| {
91+
symbols.push(BacktraceSymbol {
92+
name: symbol.name().map(|m| m.as_bytes().to_vec()),
93+
addr: symbol.addr().map(|a| a as usize),
94+
filename: symbol.filename().map(|m| m.to_owned()),
95+
lineno: symbol.lineno(),
96+
colno: symbol.colno(),
97+
});
98+
};
99+
match *self {
100+
Frame::Raw(ref f) => resolve_frame(f, sym),
101+
Frame::Deserialized { ip, .. } => {
102+
resolve(ip as *mut c_void, sym);
103+
}
104+
}
105+
symbols
106+
}
89107
}
90108

91109
/// Captured version of a symbol in a backtrace.
@@ -172,23 +190,22 @@ impl Backtrace {
172190

173191
fn create(ip: usize) -> Backtrace {
174192
let mut frames = Vec::new();
175-
let mut actual_start_index = None;
176193
trace(|frame| {
177194
frames.push(BacktraceFrame {
178195
frame: Frame::Raw(frame.clone()),
179196
symbols: None,
180197
});
181198

182-
if frame.symbol_address() as usize == ip && actual_start_index.is_none() {
183-
actual_start_index = Some(frames.len());
199+
// clear inner frames, and start with call site.
200+
if frame.symbol_address() as usize == ip {
201+
frames.clear();
184202
}
203+
185204
true
186205
});
206+
frames.shrink_to_fit();
187207

188-
Backtrace {
189-
frames,
190-
actual_start_index: actual_start_index.unwrap_or(0),
191-
}
208+
Backtrace { frames }
192209
}
193210

194211
/// Returns the frames from when this backtrace was captured.
@@ -202,7 +219,7 @@ impl Backtrace {
202219
/// This function requires the `std` feature of the `backtrace` crate to be
203220
/// enabled, and the `std` feature is enabled by default.
204221
pub fn frames(&self) -> &[BacktraceFrame] {
205-
&self.frames[self.actual_start_index..]
222+
self.frames.as_slice()
206223
}
207224

208225
/// If this backtrace was created from `new_unresolved` then this function
@@ -216,48 +233,28 @@ impl Backtrace {
216233
/// This function requires the `std` feature of the `backtrace` crate to be
217234
/// enabled, and the `std` feature is enabled by default.
218235
pub fn resolve(&mut self) {
219-
for frame in self.frames.iter_mut().filter(|f| f.symbols.is_none()) {
220-
let mut symbols = Vec::new();
221-
{
222-
let sym = |symbol: &Symbol| {
223-
symbols.push(BacktraceSymbol {
224-
name: symbol.name().map(|m| m.as_bytes().to_vec()),
225-
addr: symbol.addr().map(|a| a as usize),
226-
filename: symbol.filename().map(|m| m.to_owned()),
227-
lineno: symbol.lineno(),
228-
colno: symbol.colno(),
229-
});
230-
};
231-
match frame.frame {
232-
Frame::Raw(ref f) => resolve_frame(f, sym),
233-
Frame::Deserialized { ip, .. } => {
234-
resolve(ip as *mut c_void, sym);
235-
}
236-
}
237-
}
238-
frame.symbols = Some(symbols);
239-
}
236+
self.frames.iter_mut().for_each(BacktraceFrame::resolve);
240237
}
241238
}
242239

243240
impl From<Vec<BacktraceFrame>> for Backtrace {
244241
fn from(frames: Vec<BacktraceFrame>) -> Self {
245-
Backtrace {
246-
frames,
247-
actual_start_index: 0,
248-
}
242+
Backtrace { frames }
249243
}
250244
}
251245

252246
impl From<crate::Frame> for BacktraceFrame {
253-
fn from(frame: crate::Frame) -> BacktraceFrame {
247+
fn from(frame: crate::Frame) -> Self {
254248
BacktraceFrame {
255249
frame: Frame::Raw(frame),
256250
symbols: None,
257251
}
258252
}
259253
}
260254

255+
// we don't want implementing `impl From<Backtrace> for Vec<BacktraceFrame>` on purpose,
256+
// because "... additional directions for Vec<T> can weaken type inference ..."
257+
// more information on https://github.com/rust-lang/backtrace-rs/pull/526
261258
impl Into<Vec<BacktraceFrame>> for Backtrace {
262259
fn into(self) -> Vec<BacktraceFrame> {
263260
self.frames
@@ -312,6 +309,20 @@ impl BacktraceFrame {
312309
pub fn symbols(&self) -> &[BacktraceSymbol] {
313310
self.symbols.as_ref().map(|s| &s[..]).unwrap_or(&[])
314311
}
312+
313+
/// Resolve all addresses in this frame to their symbolic names.
314+
///
315+
/// If this frame has been previously resolved, this function does nothing.
316+
///
317+
/// # Required features
318+
///
319+
/// This function requires the `std` feature of the `backtrace` crate to be
320+
/// enabled, and the `std` feature is enabled by default.
321+
pub fn resolve(&mut self) {
322+
if self.symbols.is_none() {
323+
self.symbols = Some(self.frame.resolve_symbols());
324+
}
325+
}
315326
}
316327

317328
impl BacktraceSymbol {
@@ -368,11 +379,10 @@ impl BacktraceSymbol {
368379

369380
impl fmt::Debug for Backtrace {
370381
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
371-
let full = fmt.alternate();
372-
let (frames, style) = if full {
373-
(&self.frames[..], PrintFmt::Full)
382+
let style = if fmt.alternate() {
383+
PrintFmt::Full
374384
} else {
375-
(&self.frames[self.actual_start_index..], PrintFmt::Short)
385+
PrintFmt::Short
376386
};
377387

378388
// When printing paths we try to strip the cwd if it exists, otherwise
@@ -383,7 +393,7 @@ impl fmt::Debug for Backtrace {
383393
let mut print_path =
384394
move |fmt: &mut fmt::Formatter<'_>, path: crate::BytesOrWideString<'_>| {
385395
let path = path.into_path_buf();
386-
if !full {
396+
if style == PrintFmt::Full {
387397
if let Ok(cwd) = &cwd {
388398
if let Ok(suffix) = path.strip_prefix(cwd) {
389399
return fmt::Display::fmt(&suffix.display(), fmt);
@@ -395,7 +405,7 @@ impl fmt::Debug for Backtrace {
395405

396406
let mut f = BacktraceFmt::new(fmt, style, &mut print_path);
397407
f.add_context()?;
398-
for frame in frames {
408+
for frame in &self.frames {
399409
f.frame().backtrace_frame(frame)?;
400410
}
401411
f.finish()?;

0 commit comments

Comments
 (0)