Skip to content

Commit ede6e06

Browse files
author
Ariel Ben-Yehuda
committed
allow getting task dump backtraces in a programmatic format
Fixes #6309.
1 parent 772e0ca commit ede6e06

File tree

3 files changed

+155
-1
lines changed

3 files changed

+155
-1
lines changed

spellcheck.dic

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
450ms
3131
50ms
3232
8MB
33+
ABI
3334
adaptor
3435
adaptors
3536
Adaptors
@@ -44,7 +45,9 @@ awaitable
4445
backend
4546
backpressure
4647
backtrace
48+
BacktraceFrame
4749
backtraces
50+
BacktraceSymbol
4851
backtracing
4952
binded
5053
bitfield
@@ -75,7 +78,9 @@ datagrams
7578
deallocate
7679
deallocated
7780
Deallocates
81+
debuginfo
7882
decrementing
83+
demangled
7984
dequeued
8085
deregister
8186
deregistered
@@ -133,6 +138,7 @@ implementers
133138
implementor
134139
implementors
135140
incrementing
141+
inlining
136142
interoperate
137143
invariants
138144
Invariants

tokio/src/runtime/dump.rs

Lines changed: 145 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
//! See [Handle::dump][crate::runtime::Handle::dump].
44
55
use crate::task::Id;
6-
use std::fmt;
6+
use std::{fmt, path::Path};
77

88
/// A snapshot of a runtime's state.
99
///
@@ -30,14 +30,158 @@ pub struct Task {
3030
trace: Trace,
3131
}
3232

33+
/// A backtrace symbol. This is similar to [backtrace::BacktraceSymbol],
34+
/// but is a separate struct to avoid public dependency issues.
35+
///
36+
/// This struct is guaranteed to be pure data and operations involving
37+
/// it will not call platform functions that take an unpredictable amount
38+
/// of time to finish.
39+
#[derive(Clone, Debug)]
40+
pub struct BacktraceSymbol {
41+
name: Option<Vec<u8>>,
42+
name_demangled: Option<String>,
43+
addr: Option<*mut std::ffi::c_void>,
44+
filename: Option<std::path::PathBuf>,
45+
lineno: Option<u32>,
46+
colno: Option<u32>,
47+
}
48+
49+
impl BacktraceSymbol {
50+
pub(crate) fn from_backtrace_symbol(sym: &backtrace::BacktraceSymbol) -> Self {
51+
let name = sym.name();
52+
Self {
53+
name: name.as_ref().map(|name| name.as_bytes().into()),
54+
name_demangled: name.map(|name| format!("{}", name)),
55+
addr: sym.addr(),
56+
filename: sym.filename().map(From::from),
57+
lineno: sym.lineno(),
58+
colno: sym.colno(),
59+
}
60+
}
61+
62+
/// Return the raw name of the symbol.
63+
pub fn name_raw(&self) -> Option<&[u8]> {
64+
self.name.as_deref()
65+
}
66+
67+
/// Return the demangled name of the symbol.
68+
pub fn name_demangled(&self) -> Option<&str> {
69+
self.name_demangled.as_deref()
70+
}
71+
72+
/// Returns the starting address of this symbol.
73+
pub fn addr(&self) -> Option<*mut std::ffi::c_void> {
74+
self.addr
75+
}
76+
77+
/// Returns the file name where this function was defined. If debuginfo
78+
/// is missing, this is likely to return None.
79+
pub fn filename(&self) -> Option<&Path> {
80+
self.filename.as_deref()
81+
}
82+
83+
/// Returns the line number for where this symbol is currently executing If debuginfo
84+
/// is missing, this is likely to return None.
85+
pub fn lineno(&self) -> Option<u32> {
86+
self.lineno
87+
}
88+
89+
/// Returns the column number for where this symbol is currently executing If debuginfo
90+
/// is missing, this is likely to return None.
91+
pub fn colno(&self) -> Option<u32> {
92+
self.colno
93+
}
94+
}
95+
96+
/// A backtrace frame. This is similar to [backtrace::BacktraceFrame],
97+
/// but is a separate struct to avoid public dependency issues.
98+
///
99+
/// This struct is guaranteed to be pure data and operations involving
100+
/// it will not call platform functions that take an unpredictable amount
101+
/// of time to finish.
102+
#[derive(Clone, Debug)]
103+
pub struct BacktraceFrame {
104+
ip: *mut std::ffi::c_void,
105+
symbol_address: *mut std::ffi::c_void,
106+
symbols: Vec<BacktraceSymbol>,
107+
}
108+
109+
impl BacktraceFrame {
110+
pub(crate) fn from_resolved_backtrace_frame(frame: &backtrace::BacktraceFrame) -> Self {
111+
Self {
112+
ip: frame.ip(),
113+
symbol_address: frame.symbol_address(),
114+
symbols: frame
115+
.symbols()
116+
.iter()
117+
.map(BacktraceSymbol::from_backtrace_symbol)
118+
.collect(),
119+
}
120+
}
121+
122+
/// Return the instruction pointer of this frame.
123+
///
124+
/// See the ABI docs for your platform for the exact meaning.
125+
pub fn ip(&self) -> *mut std::ffi::c_void {
126+
self.ip
127+
}
128+
129+
/// Returns the starting symbol address of the frame of this function.
130+
pub fn symbol_address(&self) -> *mut std::ffi::c_void {
131+
self.symbol_address
132+
}
133+
134+
/// Return an iterator over the symbols of this backtrace frame.
135+
///
136+
/// Due to inlining, it is possible for there to be multiple [BacktraceSymbol] items relating
137+
/// to a single frame. The first symbol listed is the "innermost function",
138+
/// whereas the last symbol is the outermost (last caller).
139+
pub fn symbols(&self) -> impl Iterator<Item = &BacktraceSymbol> {
140+
self.symbols.iter()
141+
}
142+
}
143+
33144
/// An execution trace of a task's last poll.
34145
///
146+
/// <div class="warning">
147+
/// Resolving a backtrace, either via the [`Display`][std::fmt::Display] impl or via
148+
/// [`resolve_backtraces`][Trace::resolve_backtraces], parses debuginfo, which is
149+
/// possibly a CPU-expensive operation that can take a platform-specific but
150+
/// long time to run - often over 100 milliseconds, especially if the current
151+
/// process's binary is big. In some cases, the platform might internally cache some of the
152+
/// debuginfo, so successive calls to `resolve_backtraces` might be faster than
153+
/// the first call, but all guarantees are platform-dependent.
154+
///
155+
/// To avoid blocking the runtime, it is recommended
156+
/// that you resolve backtraces inside of a [spawn_blocking()][crate::task::spawn_blocking]
157+
/// and to have some concurrency-limiting mechanism to avoid unexpected performance impact.
158+
/// </div>
159+
///
35160
/// See [Handle::dump][crate::runtime::Handle::dump].
36161
#[derive(Debug)]
37162
pub struct Trace {
38163
inner: super::task::trace::Trace,
39164
}
40165

166+
impl Trace {
167+
/// Resolve and return a list of backtraces that are involved in polls in this task.
168+
pub fn resolve_backtraces(&self) -> Vec<Vec<BacktraceFrame>> {
169+
self.inner
170+
.backtraces()
171+
.iter()
172+
.map(|backtrace| {
173+
let mut backtrace = backtrace::Backtrace::from(backtrace.clone());
174+
backtrace.resolve();
175+
backtrace
176+
.frames()
177+
.iter()
178+
.map(BacktraceFrame::from_resolved_backtrace_frame)
179+
.collect()
180+
})
181+
.collect()
182+
}
183+
}
184+
41185
impl Dump {
42186
pub(crate) fn new(tasks: Vec<Task>) -> Self {
43187
Self {

tokio/src/runtime/task/trace/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,10 @@ impl Trace {
138138
pub(crate) fn root<F>(future: F) -> Root<F> {
139139
Root { future }
140140
}
141+
142+
pub(crate) fn backtraces(&self) -> &[Backtrace] {
143+
&self.backtraces
144+
}
141145
}
142146

143147
/// If this is a sub-invocation of [`Trace::capture`], capture a backtrace.

0 commit comments

Comments
 (0)