1#![doc(html_root_url = "https://sfackler.github.io/rstack/doc")]
20#![warn(missing_docs)]
21
22use cfg_if::cfg_if;
23use libc::{
24 c_void, pid_t, ptrace, waitpid, ESRCH, PTRACE_ATTACH, PTRACE_CONT, PTRACE_DETACH,
25 PTRACE_INTERRUPT, PTRACE_SEIZE, SIGSTOP, WIFSTOPPED, WSTOPSIG, __WALL,
26};
27use log::debug;
28use std::borrow::Borrow;
29use std::cmp::Ordering;
30use std::collections::BTreeSet;
31use std::error;
32use std::fmt;
33use std::fs::{self, File};
34use std::io::{self, Read};
35use std::ptr;
36use std::result;
37
38cfg_if! {
39 if #[cfg(feature = "dw")] {
40 #[path = "imp/dw.rs"]
41 mod imp;
42 } else if #[cfg(feature = "unwind")] {
43 #[path = "imp/unwind.rs"]
44 mod imp;
45 } else {
46 compile_error!("You must select an unwinding implementation");
47 }
48}
49
50pub type Result<T> = result::Result<T, Error>;
52
53#[derive(Debug)]
54enum ErrorInner {
55 Io(io::Error),
56 Unwind(imp::Error),
57}
58
59#[derive(Debug)]
61pub struct Error(ErrorInner);
62
63impl fmt::Display for Error {
64 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
65 match self.0 {
66 ErrorInner::Io(ref e) => fmt::Display::fmt(e, fmt),
67 ErrorInner::Unwind(ref e) => fmt::Display::fmt(e, fmt),
68 }
69 }
70}
71
72impl error::Error for Error {
73 fn cause(&self) -> Option<&dyn error::Error> {
74 match self.0 {
75 ErrorInner::Io(ref e) => Some(e),
76 ErrorInner::Unwind(ref e) => Some(e),
77 }
78 }
79}
80
81#[derive(Debug, Clone)]
83pub struct Process {
84 id: u32,
85 threads: Vec<Thread>,
86}
87
88impl Process {
89 pub fn id(&self) -> u32 {
91 self.id
92 }
93
94 pub fn threads(&self) -> &[Thread] {
96 &self.threads
97 }
98}
99
100#[derive(Debug, Clone)]
102pub struct Thread {
103 id: u32,
104 name: Option<String>,
105 frames: Vec<Frame>,
106}
107
108impl Thread {
109 #[inline]
111 pub fn id(&self) -> u32 {
112 self.id
113 }
114
115 #[inline]
117 pub fn name(&self) -> Option<&str> {
118 self.name.as_ref().map(|s| &**s)
119 }
120
121 #[inline]
123 pub fn frames(&self) -> &[Frame] {
124 &self.frames
125 }
126}
127
128#[derive(Debug, Clone)]
130pub struct Frame {
131 ip: u64,
132 is_signal: bool,
133 symbol: Option<Symbol>,
134}
135
136impl Frame {
137 #[inline]
139 pub fn ip(&self) -> u64 {
140 self.ip
141 }
142
143 #[inline]
145 pub fn is_signal(&self) -> bool {
146 self.is_signal
147 }
148
149 #[inline]
151 pub fn symbol(&self) -> Option<&Symbol> {
152 self.symbol.as_ref()
153 }
154}
155
156#[derive(Debug, Clone)]
158pub struct Symbol {
159 name: String,
160 offset: u64,
161 address: u64,
162 size: u64,
163}
164
165impl Symbol {
166 #[inline]
168 pub fn name(&self) -> &str {
169 &self.name
170 }
171
172 #[inline]
174 pub fn offset(&self) -> u64 {
175 self.offset
176 }
177
178 #[inline]
180 pub fn address(&self) -> u64 {
181 self.address
182 }
183
184 #[inline]
186 pub fn size(&self) -> u64 {
187 self.size
188 }
189}
190
191pub fn trace(pid: u32) -> Result<Process> {
193 TraceOptions::new()
194 .thread_names(true)
195 .symbols(true)
196 .trace(pid)
197}
198
199#[derive(Debug, Clone)]
201pub struct TraceOptions {
202 snapshot: bool,
203 thread_names: bool,
204 symbols: bool,
205 ptrace_attach: bool,
206}
207
208impl Default for TraceOptions {
209 fn default() -> TraceOptions {
210 TraceOptions {
211 snapshot: false,
212 thread_names: false,
213 symbols: false,
214 ptrace_attach: true,
215 }
216 }
217}
218
219impl TraceOptions {
220 pub fn new() -> TraceOptions {
222 TraceOptions::default()
223 }
224
225 pub fn snapshot(&mut self, snapshot: bool) -> &mut TraceOptions {
232 self.snapshot = snapshot;
233 self
234 }
235
236 pub fn thread_names(&mut self, thread_names: bool) -> &mut TraceOptions {
240 self.thread_names = thread_names;
241 self
242 }
243
244 pub fn symbols(&mut self, symbols: bool) -> &mut TraceOptions {
248 self.symbols = symbols;
249 self
250 }
251
252 pub fn ptrace_attach(&mut self, ptrace_attach: bool) -> &mut TraceOptions {
259 self.ptrace_attach = ptrace_attach;
260 self
261 }
262
263 pub fn trace(&self, pid: u32) -> Result<Process> {
265 let mut state = imp::State::new(pid).map_err(|e| Error(ErrorInner::Unwind(e)))?;
266
267 let threads = if self.snapshot {
268 self.trace_snapshot(pid, &mut state)?
269 } else {
270 self.trace_rolling(pid, &mut state)?
271 };
272
273 Ok(Process { id: pid, threads })
274 }
275
276 fn trace_snapshot(&self, pid: u32, state: &mut imp::State) -> Result<Vec<Thread>> {
277 let threads = snapshot_threads(pid, self.ptrace_attach)?
278 .iter()
279 .map(|t| t.info(pid, state, self))
280 .collect();
281
282 Ok(threads)
283 }
284
285 fn trace_rolling(&self, pid: u32, state: &mut imp::State) -> Result<Vec<Thread>> {
286 let mut threads = vec![];
287
288 each_thread(pid, |tid| {
289 let thread = if self.ptrace_attach {
290 TracedThread::attach(tid)
291 } else {
292 TracedThread::traced(tid)
293 };
294 let thread = match thread {
295 Ok(thread) => thread,
296 Err(ref e) if e.raw_os_error() == Some(ESRCH) => {
297 debug!("error attaching to thread {}: {}", tid, e);
298 return Ok(());
299 }
300 Err(e) => return Err(Error(ErrorInner::Io(e))),
301 };
302
303 let trace = thread.info(pid, state, self);
304 threads.push(trace);
305 Ok(())
306 })?;
307
308 Ok(threads)
309 }
310}
311
312fn snapshot_threads(pid: u32, ptrace_attach: bool) -> Result<BTreeSet<TracedThread>> {
313 let mut threads = BTreeSet::new();
314
315 for _ in 0..5 {
318 let prev = threads.len();
319 add_threads(&mut threads, pid, ptrace_attach)?;
320 if prev == threads.len() {
321 break;
322 }
323 }
324
325 Ok(threads)
326}
327
328fn add_threads(threads: &mut BTreeSet<TracedThread>, pid: u32, ptrace_attach: bool) -> Result<()> {
329 each_thread(pid, |tid| {
330 if !threads.contains(&tid) {
331 let thread = if ptrace_attach {
332 TracedThread::attach(pid)
333 } else {
334 TracedThread::traced(pid)
335 };
336 let thread = match thread {
337 Ok(thread) => thread,
338 Err(e) => {
340 if e.raw_os_error() == Some(ESRCH) {
341 debug!("error attaching to thread {}: {}", pid, e);
342 return Ok(());
343 } else {
344 return Err(Error(ErrorInner::Io(e)));
345 }
346 }
347 };
348 threads.insert(thread);
349 }
350
351 Ok(())
352 })
353}
354
355fn each_thread<F>(pid: u32, mut f: F) -> Result<()>
356where
357 F: FnMut(u32) -> Result<()>,
358{
359 let dir = format!("/proc/{}/task", pid);
360 for entry in fs::read_dir(dir).map_err(|e| Error(ErrorInner::Io(e)))? {
361 let entry = entry.map_err(|e| Error(ErrorInner::Io(e)))?;
362
363 if let Some(tid) = entry
364 .file_name()
365 .to_str()
366 .and_then(|s| s.parse::<u32>().ok())
367 {
368 f(tid)?;
369 }
370 }
371 Ok(())
372}
373
374struct TracedThread {
375 id: u32,
376 should_detach: bool,
379}
380
381impl Drop for TracedThread {
382 fn drop(&mut self) {
383 if self.should_detach {
384 unsafe {
385 ptrace(
386 PTRACE_DETACH,
387 self.id as pid_t,
388 ptr::null_mut::<c_void>(),
389 ptr::null_mut::<c_void>(),
390 );
391 }
392 }
393 }
394}
395
396impl PartialOrd for TracedThread {
398 fn partial_cmp(&self, other: &TracedThread) -> Option<Ordering> {
399 Some(self.cmp(other))
400 }
401}
402
403impl Ord for TracedThread {
404 fn cmp(&self, other: &TracedThread) -> Ordering {
405 self.id.cmp(&other.id)
406 }
407}
408
409impl PartialEq for TracedThread {
410 fn eq(&self, other: &TracedThread) -> bool {
411 self.id == other.id
412 }
413}
414
415impl Eq for TracedThread {}
416
417impl Borrow<u32> for TracedThread {
418 fn borrow(&self) -> &u32 {
419 &self.id
420 }
421}
422
423impl TracedThread {
424 fn attach(pid: u32) -> io::Result<TracedThread> {
425 unsafe {
426 let ret = ptrace(
427 PTRACE_SEIZE,
428 pid as pid_t,
429 ptr::null_mut::<c_void>(),
430 ptr::null_mut::<c_void>(),
431 );
432 if ret != 0 {
433 let e = io::Error::last_os_error();
434 if e.raw_os_error() == Some(ESRCH as i32) {
436 return TracedThread::new_fallback(pid);
437 }
438
439 return Err(e);
440 }
441
442 let thread = TracedThread {
443 id: pid,
444 should_detach: true,
445 };
446
447 let ret = ptrace(
448 PTRACE_INTERRUPT,
449 pid as pid_t,
450 ptr::null_mut::<c_void>(),
451 ptr::null_mut::<c_void>(),
452 );
453 if ret != 0 {
454 return Err(io::Error::last_os_error());
455 }
456
457 let mut status = 0;
458 while waitpid(pid as pid_t, &mut status, __WALL) < 0 {
459 let e = io::Error::last_os_error();
460 if e.kind() != io::ErrorKind::Interrupted {
461 return Err(e);
462 }
463 }
464
465 if !WIFSTOPPED(status) {
466 return Err(io::Error::new(
467 io::ErrorKind::Other,
468 format!("unexpected wait status {}", status),
469 ));
470 }
471
472 Ok(thread)
473 }
474 }
475
476 fn traced(pid: u32) -> io::Result<TracedThread> {
479 Ok(TracedThread {
480 id: pid,
481 should_detach: false,
482 })
483 }
484
485 fn new_fallback(pid: u32) -> io::Result<TracedThread> {
486 unsafe {
487 let ret = ptrace(
488 PTRACE_ATTACH,
489 pid as pid_t,
490 ptr::null_mut::<c_void>(),
491 ptr::null_mut::<c_void>(),
492 );
493 if ret != 0 {
494 return Err(io::Error::last_os_error());
495 }
496
497 let thread = TracedThread {
498 id: pid,
499 should_detach: true,
500 };
501
502 let mut status = 0;
503 loop {
504 let ret = waitpid(pid as pid_t, &mut status, __WALL);
505 if ret < 0 {
506 let e = io::Error::last_os_error();
507 if e.kind() != io::ErrorKind::Interrupted {
508 return Err(e);
509 }
510
511 continue;
512 }
513
514 if !WIFSTOPPED(status) {
515 return Err(io::Error::new(
516 io::ErrorKind::Other,
517 format!("unexpected wait status {}", status),
518 ));
519 }
520
521 let sig = WSTOPSIG(status);
522 if sig == SIGSTOP {
523 return Ok(thread);
524 }
525
526 let ret = ptrace(
527 PTRACE_CONT,
528 pid as pid_t,
529 ptr::null_mut::<c_void>(),
530 sig as *const c_void,
531 );
532 if ret != 0 {
533 return Err(io::Error::last_os_error());
534 }
535 }
536 }
537 }
538
539 fn info(&self, pid: u32, state: &mut imp::State, options: &TraceOptions) -> Thread {
540 let name = if options.thread_names {
541 self.name(pid)
542 } else {
543 None
544 };
545
546 let frames = self.dump(state, options);
547
548 Thread {
549 id: self.id,
550 name,
551 frames,
552 }
553 }
554
555 fn dump(&self, state: &mut imp::State, options: &TraceOptions) -> Vec<Frame> {
556 let mut frames = vec![];
557
558 if let Err(e) = self.dump_inner(state, options, &mut frames) {
559 debug!("error tracing thread {}: {}", self.id, e);
560 }
561
562 frames
563 }
564
565 fn name(&self, pid: u32) -> Option<String> {
566 let path = format!("/proc/{}/task/{}/comm", pid, self.id);
567 let mut name = vec![];
568 match File::open(path).and_then(|mut f| f.read_to_end(&mut name)) {
569 Ok(_) => Some(String::from_utf8_lossy(&name).trim().to_string()),
570 Err(e) => {
571 debug!("error getting name for thread {}: {}", self.id, e);
572 None
573 }
574 }
575 }
576}