minidump_unwind/lib.rs
1// Copyright 2015 Ted Mielczarek. See the COPYRIGHT
2// file at the top-level directory of this distribution.
3
4//! Unwind stack frames for a thread.
5
6#[cfg(all(doctest, feature = "http"))]
7doc_comment::doctest!("../README.md");
8
9mod amd64;
10mod arm;
11mod arm64;
12mod arm64_old;
13mod mips;
14pub mod symbols;
15pub mod system_info;
16mod x86;
17
18use minidump::*;
19use minidump_common::utils::basename;
20use scroll::ctx::{SizeWith, TryFromCtx};
21use std::borrow::Cow;
22use std::collections::{BTreeMap, BTreeSet, HashSet};
23use std::convert::TryFrom;
24use std::io::{self, Write};
25use tracing::trace;
26
27pub use crate::symbols::*;
28pub use crate::system_info::*;
29
30#[derive(Clone, Copy)]
31struct GetCallerFrameArgs<'a, P> {
32 callee_frame: &'a StackFrame,
33 grand_callee_frame: Option<&'a StackFrame>,
34 stack_memory: UnifiedMemory<'a, 'a>,
35 modules: &'a MinidumpModuleList,
36 system_info: &'a SystemInfo,
37 symbol_provider: &'a P,
38}
39
40impl<P> GetCallerFrameArgs<'_, P> {
41 fn valid(&self) -> &MinidumpContextValidity {
42 &self.callee_frame.context.valid
43 }
44}
45
46mod impl_prelude {
47 pub(crate) use super::{
48 CfiStackWalker, FrameTrust, GetCallerFrameArgs, StackFrame, SymbolProvider,
49 };
50}
51
52/// Indicates how well the instruction pointer derived during
53/// stack walking is trusted. Since the stack walker can resort to
54/// stack scanning, it can wind up with dubious frames.
55#[derive(Copy, Clone, Debug, PartialEq, Eq)]
56pub enum FrameTrust {
57 /// Unknown
58 None,
59 /// Scanned the stack, found this.
60 Scan,
61 /// Found while scanning stack using call frame info.
62 CfiScan,
63 /// Derived from frame pointer.
64 FramePointer,
65 /// Derived from call frame info.
66 CallFrameInfo,
67 /// Explicitly provided by some external stack walker.
68 PreWalked,
69 /// Given as instruction pointer in a context.
70 Context,
71}
72
73impl FrameTrust {
74 /// Return a string describing how a stack frame was found
75 /// by the stackwalker.
76 pub fn description(&self) -> &'static str {
77 match *self {
78 FrameTrust::Context => "given as instruction pointer in context",
79 FrameTrust::PreWalked => "recovered by external stack walker",
80 FrameTrust::CallFrameInfo => "call frame info",
81 FrameTrust::CfiScan => "call frame info with scanning",
82 FrameTrust::FramePointer => "previous frame's frame pointer",
83 FrameTrust::Scan => "stack scanning",
84 FrameTrust::None => "unknown",
85 }
86 }
87
88 pub fn as_str(&self) -> &'static str {
89 match *self {
90 FrameTrust::Context => "context",
91 FrameTrust::PreWalked => "prewalked",
92 FrameTrust::CallFrameInfo => "cfi",
93 FrameTrust::CfiScan => "cfi_scan",
94 FrameTrust::FramePointer => "frame_pointer",
95 FrameTrust::Scan => "scan",
96 FrameTrust::None => "non",
97 }
98 }
99}
100
101/// The calling convention of a function.
102#[derive(Debug, Clone)]
103pub enum CallingConvention {
104 Cdecl,
105 WindowsThisCall,
106 OtherThisCall,
107}
108
109/// Arguments for this function
110#[derive(Debug, Clone)]
111pub struct FunctionArgs {
112 /// What we assumed the calling convention was.
113 pub calling_convention: CallingConvention,
114
115 /// The actual arguments.
116 pub args: Vec<FunctionArg>,
117}
118
119/// A function argument.
120#[derive(Debug, Clone)]
121pub struct FunctionArg {
122 /// The name of the argument (usually actually just the type).
123 pub name: String,
124 /// The value of the argument.
125 pub value: Option<u64>,
126}
127
128/// A stack frame for an inlined function.
129///
130/// See [`StackFrame::inlines`][] for more details.
131#[derive(Debug, Clone)]
132pub struct InlineFrame {
133 /// The name of the function
134 pub function_name: String,
135 /// The file name of the stack frame
136 pub source_file_name: Option<String>,
137 /// The line number of the stack frame
138 pub source_line: Option<u32>,
139}
140
141/// A single stack frame produced from unwinding a thread's stack.
142#[derive(Debug, Clone)]
143pub struct StackFrame {
144 /// The program counter location as an absolute virtual address.
145 ///
146 /// - For the innermost called frame in a stack, this will be an exact
147 /// program counter or instruction pointer value.
148 ///
149 /// - For all other frames, this address is within the instruction that
150 /// caused execution to branch to this frame's callee (although it may
151 /// not point to the exact beginning of that instruction). This ensures
152 /// that, when we look up the source code location for this frame, we
153 /// get the source location of the call, not of the point at which
154 /// control will resume when the call returns, which may be on the next
155 /// line. (If the compiler knows the callee never returns, it may even
156 /// place the call instruction at the very end of the caller's machine
157 /// code, such that the "return address" (which will never be used)
158 /// immediately after the call instruction is in an entirely different
159 /// function, perhaps even from a different source file.)
160 ///
161 /// On some architectures, the return address as saved on the stack or in
162 /// a register is fine for looking up the point of the call. On others, it
163 /// requires adjustment.
164 pub instruction: u64,
165
166 /// The instruction address (program counter) that execution of this function
167 /// would resume at, if the callee returns.
168 ///
169 /// This is exactly **the return address of the of the callee**. We use this
170 /// nonstandard terminology because just calling this "return address"
171 /// would be ambiguous and too easy to mix up.
172 ///
173 /// **Note:** you should strongly prefer using [`StackFrame::instruction`][], which should
174 /// be the address of the instruction before this one which called the callee.
175 /// That is the instruction that this function was logically "executing" when the
176 /// program's state was captured, and therefore what people expect from
177 /// backtraces.
178 ///
179 /// This is more than a matter of user expections: **there are situations
180 /// where this value is nonsensical but the [`StackFrame::instruction`][] is valid.**
181 ///
182 /// Specifically, if the callee is "noreturn" then *this function should
183 /// never resume execution*. The compiler has no obligation to emit any
184 /// instructions after such a CALL, but CALL still implicitly pushes the
185 /// instruction after itself to the stack. Such a return address may
186 /// therefore be outside the "bounds" of this function!!!
187 ///
188 /// Yes, compilers *can* just immediately jump into the callee for
189 /// noreturn calls, but it's genuinely very helpful for them to emit a
190 /// CALL because it keeps the stack reasonable for backtraces and
191 /// debuggers, which are more interested in [`StackFrame::instruction`][] anyway!
192 ///
193 /// (If this is the top frame of the call stack, then `resume_address`
194 /// and `instruction` are exactly equal and should reflect the actual
195 /// program counter of this thread.)
196 pub resume_address: u64,
197
198 /// The module in which the instruction resides.
199 pub module: Option<MinidumpModule>,
200
201 /// Any unloaded modules which overlap with this address.
202 ///
203 /// This is currently only populated if `module` is None.
204 ///
205 /// Since unloaded modules may overlap, there may be more than
206 /// one module. Since a module may be unloaded and reloaded at
207 /// multiple positions, we keep track of all the offsets that
208 /// apply. BTrees are used to produce a more stable output.
209 ///
210 /// So this is a `BTreeMap<module_name, Set<offsets>>`.
211 pub unloaded_modules: BTreeMap<String, BTreeSet<u64>>,
212
213 /// The function name, may be omitted if debug symbols are not available.
214 pub function_name: Option<String>,
215
216 /// The start address of the function, may be omitted if debug symbols
217 /// are not available.
218 pub function_base: Option<u64>,
219
220 /// The size, in bytes, of the arguments pushed on the stack for this function.
221 /// WIN STACK unwinding needs this value to work; it's otherwise uninteresting.
222 pub parameter_size: Option<u32>,
223
224 /// The source file name, may be omitted if debug symbols are not available.
225 pub source_file_name: Option<String>,
226
227 /// The (1-based) source line number, may be omitted if debug symbols are
228 /// not available.
229 pub source_line: Option<u32>,
230
231 /// The start address of the source line, may be omitted if debug symbols
232 /// are not available.
233 pub source_line_base: Option<u64>,
234
235 /// Any inline frames that cover the frame address, ordered "inside to outside",
236 /// or "deepest callee to shallowest callee". This is the same order that StackFrames
237 /// appear in.
238 ///
239 /// These frames are "fake" in that they don't actually exist at runtime, and are only
240 /// known because the compiler added debuginfo saying they exist.
241 ///
242 /// As a result, many properties of these frames either don't exist or are
243 /// in some sense "inherited" from the parent real frame. For instance they
244 /// have the same instruction/module by definiton.
245 ///
246 /// If you were to print frames you would want to do something like:
247 ///
248 /// ```ignore
249 /// let mut frame_num = 0;
250 /// for frame in &thread.frames {
251 /// // Inlines come first
252 /// for inline in &frame.inlines {
253 /// print_inline(frame_num, frame, inline);
254 /// frame_num += 1;
255 /// }
256 /// print_frame(frame_num, frame);
257 /// frame_num += 1;
258 /// }
259 /// ```
260 pub inlines: Vec<InlineFrame>,
261
262 /// Amount of trust the stack walker has in the instruction pointer
263 /// of this frame.
264 pub trust: FrameTrust,
265
266 /// The CPU context containing register state for this frame.
267 pub context: MinidumpContext,
268
269 /// Any function args we recovered.
270 pub arguments: Option<FunctionArgs>,
271}
272
273impl StackFrame {
274 /// Create a `StackFrame` from a `MinidumpContext`.
275 pub fn from_context(context: MinidumpContext, trust: FrameTrust) -> StackFrame {
276 StackFrame {
277 instruction: context.get_instruction_pointer(),
278 // Initialized the same as `instruction`, but left unmodified during stack walking.
279 resume_address: context.get_instruction_pointer(),
280 module: None,
281 unloaded_modules: BTreeMap::new(),
282 function_name: None,
283 function_base: None,
284 parameter_size: None,
285 source_file_name: None,
286 source_line: None,
287 source_line_base: None,
288 inlines: Vec::new(),
289 arguments: None,
290 trust,
291 context,
292 }
293 }
294}
295
296impl FrameSymbolizer for StackFrame {
297 fn get_instruction(&self) -> u64 {
298 self.instruction
299 }
300 fn set_function(&mut self, name: &str, base: u64, parameter_size: u32) {
301 self.function_name = Some(String::from(name));
302 self.function_base = Some(base);
303 self.parameter_size = Some(parameter_size);
304 }
305 fn set_source_file(&mut self, file: &str, line: u32, base: u64) {
306 self.source_file_name = Some(String::from(file));
307 self.source_line = Some(line);
308 self.source_line_base = Some(base);
309 }
310 /// This function can be called multiple times, for the inlines that cover the
311 /// address at various levels of inlining. The call order is from outside to
312 /// inside.
313 fn add_inline_frame(&mut self, name: &str, file: Option<&str>, line: Option<u32>) {
314 self.inlines.push(InlineFrame {
315 function_name: name.to_string(),
316 source_file_name: file.map(ToString::to_string),
317 source_line: line,
318 })
319 }
320}
321
322/// Information about the results of unwinding a thread's stack.
323#[derive(Debug, Clone, PartialEq, Eq)]
324pub enum CallStackInfo {
325 /// Everything went great.
326 Ok,
327 /// No `MinidumpContext` was provided, couldn't do anything.
328 MissingContext,
329 /// No stack memory was provided, couldn't unwind past the top frame.
330 MissingMemory,
331 /// The CPU type is unsupported.
332 UnsupportedCpu,
333 /// This thread wrote the minidump, it was skipped.
334 DumpThreadSkipped,
335}
336
337/// A stack of `StackFrame`s produced as a result of unwinding a thread.
338#[derive(Debug, Clone)]
339pub struct CallStack {
340 /// The stack frames.
341 /// By convention, the stack frame at index 0 is the innermost callee frame,
342 /// and the frame at the highest index in a call stack is the outermost
343 /// caller.
344 pub frames: Vec<StackFrame>,
345 /// Information about this `CallStack`.
346 pub info: CallStackInfo,
347 /// The identifier of the thread.
348 pub thread_id: u32,
349 /// The name of the thread, if known.
350 pub thread_name: Option<String>,
351 /// The GetLastError() value stored in the TEB.
352 pub last_error_value: Option<CrashReason>,
353}
354
355impl CallStack {
356 /// Construct a CallStack that just has the unsymbolicated context frame.
357 ///
358 /// This is the desired input for the stack walker.
359 pub fn with_context(context: MinidumpContext) -> Self {
360 Self {
361 frames: vec![StackFrame::from_context(context, FrameTrust::Context)],
362 info: CallStackInfo::Ok,
363 thread_id: 0,
364 thread_name: None,
365 last_error_value: None,
366 }
367 }
368
369 /// Create a `CallStack` with `info` and no frames.
370 pub fn with_info(id: u32, info: CallStackInfo) -> CallStack {
371 CallStack {
372 info,
373 frames: vec![],
374 thread_id: id,
375 thread_name: None,
376 last_error_value: None,
377 }
378 }
379
380 /// Write a human-readable description of the call stack to `f`.
381 ///
382 /// This is very verbose, it implements the output format used by
383 /// minidump_stackwalk.
384 pub fn print<T: Write>(&self, f: &mut T) -> io::Result<()> {
385 fn print_registers<T: Write>(f: &mut T, ctx: &MinidumpContext) -> io::Result<()> {
386 let registers: Cow<HashSet<&str>> = match ctx.valid {
387 MinidumpContextValidity::All => {
388 let gpr = ctx.general_purpose_registers();
389 let set: HashSet<&str> = gpr.iter().cloned().collect();
390 Cow::Owned(set)
391 }
392 MinidumpContextValidity::Some(ref which) => Cow::Borrowed(which),
393 };
394
395 // Iterate over registers in a known order.
396 let mut output = String::new();
397 for reg in ctx.general_purpose_registers() {
398 if registers.contains(reg) {
399 let reg_val = ctx.format_register(reg);
400 let next = format!(" {reg: >6} = {reg_val}");
401 if output.chars().count() + next.chars().count() > 80 {
402 // Flush the buffer.
403 writeln!(f, " {output}")?;
404 output.truncate(0);
405 }
406 output.push_str(&next);
407 }
408 }
409 if !output.is_empty() {
410 writeln!(f, " {output}")?;
411 }
412 Ok(())
413 }
414
415 if self.frames.is_empty() {
416 writeln!(f, "<no frames>")?;
417 }
418 let mut frame_count = 0;
419 for frame in &self.frames {
420 // First print out inlines
421 for inline in &frame.inlines {
422 // Frame number
423 let frame_idx = frame_count;
424 frame_count += 1;
425 write!(f, "{frame_idx:2} ")?;
426
427 // Module name
428 if let Some(ref module) = frame.module {
429 write!(f, "{}", basename(&module.code_file()))?;
430 }
431
432 // Function name
433 write!(f, "!{}", inline.function_name)?;
434
435 // Source file and line
436 if let (Some(source_file), Some(source_line)) =
437 (&inline.source_file_name, &inline.source_line)
438 {
439 write!(f, " [{} : {}]", basename(source_file), source_line,)?;
440 }
441 writeln!(f)?;
442 // A fake `trust`
443 writeln!(f, " Found by: inlining")?;
444 }
445
446 // Now print out the "real frame"
447 let frame_idx = frame_count;
448 frame_count += 1;
449 let addr = frame.instruction;
450
451 // Frame number
452 write!(f, "{frame_idx:2} ")?;
453 if let Some(module) = &frame.module {
454 // Module name
455 write!(f, "{}", basename(&module.code_file()))?;
456
457 if let (Some(func_name), Some(func_base)) =
458 (&frame.function_name, &frame.function_base)
459 {
460 // Function name
461 write!(f, "!{func_name}")?;
462
463 if let (Some(src_file), Some(src_line), Some(src_base)) = (
464 &frame.source_file_name,
465 &frame.source_line,
466 &frame.source_line_base,
467 ) {
468 // Source file, line, and offset
469 write!(
470 f,
471 " [{} : {} + {:#x}]",
472 basename(src_file),
473 src_line,
474 addr - src_base
475 )?;
476 } else {
477 // We didn't have source info, so just give a byte offset from the func
478 write!(f, " + {:#x}", addr - func_base)?;
479 }
480 } else {
481 // We didn't have a function name, so just give a byte offset from the module
482 write!(f, " + {:#x}", addr - module.base_address())?;
483 }
484 } else {
485 // We didn't even find a module, so just print the raw address
486 write!(f, "{addr:#x}")?;
487
488 // List off overlapping unloaded modules.
489
490 // First we need to collect them up by name so that we can print
491 // all the overlaps from one module together and dedupe them.
492 // (!!! was that code deleted?)
493 for (name, offsets) in &frame.unloaded_modules {
494 write!(f, " (unloaded {name}@")?;
495 let mut first = true;
496 for offset in offsets {
497 if first {
498 write!(f, "{offset:#x}")?;
499 } else {
500 // `|` is our separator for multiple entries
501 write!(f, "|{offset:#x}")?;
502 }
503 first = false;
504 }
505 write!(f, ")")?;
506 }
507 }
508
509 // Print the valid registers
510 writeln!(f)?;
511 print_registers(f, &frame.context)?;
512
513 // And the trust we have of this result
514 writeln!(f, " Found by: {}", frame.trust.description())?;
515
516 // Now print out recovered args
517 if let Some(args) = &frame.arguments {
518 use MinidumpRawContext::*;
519 let pointer_width = match &frame.context.raw {
520 X86(_) | Ppc(_) | Sparc(_) | Arm(_) | Mips(_) => 4,
521 Ppc64(_) | Amd64(_) | Arm64(_) | OldArm64(_) => 8,
522 };
523
524 let cc_summary = match args.calling_convention {
525 CallingConvention::Cdecl => "cdecl [static function]",
526 CallingConvention::WindowsThisCall => "windows thiscall [C++ member function]",
527 CallingConvention::OtherThisCall => {
528 "non-windows thiscall [C++ member function]"
529 }
530 };
531
532 writeln!(f, " Arguments (assuming {cc_summary})")?;
533 for (idx, arg) in args.args.iter().enumerate() {
534 if let Some(val) = arg.value {
535 if pointer_width == 4 {
536 writeln!(f, " arg {} ({}) = 0x{:08x}", idx, arg.name, val)?;
537 } else {
538 writeln!(f, " arg {} ({}) = 0x{:016x}", idx, arg.name, val)?;
539 }
540 } else {
541 writeln!(f, " arg {} ({}) = <unknown>", idx, arg.name)?;
542 }
543 }
544 // Add an extra new-line between frames when there's function arguments to make
545 // it more readable.
546 writeln!(f)?;
547 }
548 }
549 Ok(())
550 }
551}
552
553struct CfiStackWalker<'a, C: CpuContext> {
554 instruction: u64,
555 has_grand_callee: bool,
556 grand_callee_parameter_size: u32,
557
558 callee_ctx: &'a C,
559 callee_validity: &'a MinidumpContextValidity,
560
561 caller_ctx: C,
562 caller_validity: HashSet<&'static str>,
563
564 module: &'a MinidumpModule,
565 stack_memory: UnifiedMemory<'a, 'a>,
566}
567
568impl<'a, C> CfiStackWalker<'a, C>
569where
570 C: CpuContext + Clone,
571{
572 fn from_ctx_and_args<P, R>(
573 ctx: &'a C,
574 args: &'a GetCallerFrameArgs<'a, P>,
575 callee_forwarded_regs: R,
576 ) -> Option<Self>
577 where
578 R: Fn(&MinidumpContextValidity) -> HashSet<&'static str>,
579 {
580 let module = args
581 .modules
582 .module_at_address(args.callee_frame.instruction)?;
583 let grand_callee = args.grand_callee_frame;
584 Some(Self {
585 instruction: args.callee_frame.instruction,
586 has_grand_callee: grand_callee.is_some(),
587 grand_callee_parameter_size: grand_callee.and_then(|f| f.parameter_size).unwrap_or(0),
588
589 callee_ctx: ctx,
590 callee_validity: args.valid(),
591
592 // Default to forwarding all callee-saved regs verbatim.
593 // The CFI evaluator may clear or overwrite these values.
594 // The stack pointer and instruction pointer are not included.
595 caller_ctx: ctx.clone(),
596 caller_validity: callee_forwarded_regs(args.valid()),
597
598 module,
599 stack_memory: args.stack_memory,
600 })
601 }
602}
603
604impl<'a, C> FrameWalker for CfiStackWalker<'a, C>
605where
606 C: CpuContext,
607 C::Register: TryFrom<u64>,
608 u64: TryFrom<C::Register>,
609 C::Register: TryFromCtx<'a, Endian, [u8], Error = scroll::Error> + SizeWith<Endian>,
610{
611 fn get_instruction(&self) -> u64 {
612 self.instruction
613 }
614 fn has_grand_callee(&self) -> bool {
615 self.has_grand_callee
616 }
617 fn get_grand_callee_parameter_size(&self) -> u32 {
618 self.grand_callee_parameter_size
619 }
620 fn get_register_at_address(&self, address: u64) -> Option<u64> {
621 let result: Option<C::Register> = self.stack_memory.get_memory_at_address(address);
622 result.and_then(|val| u64::try_from(val).ok())
623 }
624 fn get_callee_register(&self, name: &str) -> Option<u64> {
625 self.callee_ctx
626 .get_register(name, self.callee_validity)
627 .and_then(|val| u64::try_from(val).ok())
628 }
629 fn set_caller_register(&mut self, name: &str, val: u64) -> Option<()> {
630 let memoized = self.caller_ctx.memoize_register(name)?;
631 let val = C::Register::try_from(val).ok()?;
632 self.caller_validity.insert(memoized);
633 self.caller_ctx.set_register(name, val)
634 }
635 fn clear_caller_register(&mut self, name: &str) {
636 self.caller_validity.remove(name);
637 }
638 fn set_cfa(&mut self, val: u64) -> Option<()> {
639 // NOTE: some things have alluded to architectures where this isn't
640 // how the CFA should be handled, but we apparently don't support them yet?
641 let stack_pointer_reg = self.caller_ctx.stack_pointer_register_name();
642 let val = C::Register::try_from(val).ok()?;
643 self.caller_validity.insert(stack_pointer_reg);
644 self.caller_ctx.set_register(stack_pointer_reg, val)
645 }
646 fn set_ra(&mut self, val: u64) -> Option<()> {
647 let instruction_pointer_reg = self.caller_ctx.instruction_pointer_register_name();
648 let val = C::Register::try_from(val).ok()?;
649 self.caller_validity.insert(instruction_pointer_reg);
650 self.caller_ctx.set_register(instruction_pointer_reg, val)
651 }
652}
653
654#[tracing::instrument(name = "unwind_frame", level = "trace", skip_all, fields(idx = _frame_idx, fname = args.callee_frame.function_name.as_deref().unwrap_or("")))]
655async fn get_caller_frame<P>(
656 _frame_idx: usize,
657 args: &GetCallerFrameArgs<'_, P>,
658) -> Option<StackFrame>
659where
660 P: SymbolProvider + Sync,
661{
662 match args.callee_frame.context.raw {
663 /*
664 MinidumpRawContext::PPC(ctx) => ctx.get_caller_frame(stack_memory),
665 MinidumpRawContext::PPC64(ctx) => ctx.get_caller_frame(stack_memory),
666 MinidumpRawContext::SPARC(ctx) => ctx.get_caller_frame(stack_memory),
667 */
668 MinidumpRawContext::Arm(ref ctx) => arm::get_caller_frame(ctx, args).await,
669 MinidumpRawContext::Arm64(ref ctx) => arm64::get_caller_frame(ctx, args).await,
670 MinidumpRawContext::OldArm64(ref ctx) => arm64_old::get_caller_frame(ctx, args).await,
671 MinidumpRawContext::Amd64(ref ctx) => amd64::get_caller_frame(ctx, args).await,
672 MinidumpRawContext::X86(ref ctx) => x86::get_caller_frame(ctx, args).await,
673 MinidumpRawContext::Mips(ref ctx) => mips::get_caller_frame(ctx, args).await,
674 _ => None,
675 }
676}
677
678async fn fill_source_line_info<P>(
679 frame: &mut StackFrame,
680 modules: &MinidumpModuleList,
681 symbol_provider: &P,
682) where
683 P: SymbolProvider + Sync,
684{
685 // Find the module whose address range covers this frame's instruction.
686 if let Some(module) = modules.module_at_address(frame.instruction) {
687 // FIXME: this shouldn't need to clone, we should be able to use
688 // the same lifetime as the module list that's passed in.
689 frame.module = Some(module.clone());
690
691 // This is best effort, so ignore any errors.
692 let _ = symbol_provider.fill_symbol(module, frame).await;
693
694 // If we got any inlines, reverse them! The symbol format makes it simplest to
695 // emit inlines from the shallowest callee to the deepest one ("inner to outer"),
696 // but we want inlines to be in the same order as the stackwalk itself, which means
697 // we want the deepest frame first (the callee-est frame).
698 frame.inlines.reverse();
699 }
700}
701
702/// An optional callback when walking frames.
703///
704/// One may convert from other types to this callback type:
705/// `FnMut(frame_idx: usize, frame: &StackFrame)` types can be converted to a
706/// callback, and `()` can be converted to no callback (do nothing).
707pub enum OnWalkedFrame<'a> {
708 None,
709 #[allow(clippy::type_complexity)]
710 Some(Box<dyn FnMut(usize, &StackFrame) + Send + 'a>),
711}
712
713impl From<()> for OnWalkedFrame<'_> {
714 fn from(_: ()) -> Self {
715 Self::None
716 }
717}
718
719impl<'a, F: FnMut(usize, &StackFrame) + Send + 'a> From<F> for OnWalkedFrame<'a> {
720 fn from(f: F) -> Self {
721 Self::Some(Box::new(f))
722 }
723}
724
725#[tracing::instrument(name = "unwind_thread", level = "trace", skip_all, fields(idx = _thread_idx, tid = stack.thread_id, tname = stack.thread_name.as_deref().unwrap_or("")))]
726pub async fn walk_stack<P>(
727 _thread_idx: usize,
728 on_walked_frame: impl Into<OnWalkedFrame<'_>>,
729 stack: &mut CallStack,
730 stack_memory: Option<UnifiedMemory<'_, '_>>,
731 modules: &MinidumpModuleList,
732 system_info: &SystemInfo,
733 symbol_provider: &P,
734) where
735 P: SymbolProvider + Sync,
736{
737 trace!(
738 "starting stack unwind of thread {} {}",
739 stack.thread_id,
740 stack.thread_name.as_deref().unwrap_or(""),
741 );
742
743 // All the unwinder code down below in `get_caller_frame` requires a valid `stack_memory`,
744 // where _valid_ means that we can actually read something from it. A call to `memory_range` will validate that,
745 // as it will reject empty stack memory or one with an overflowing `size`.
746 let stack_memory =
747 stack_memory.and_then(|stack_memory| stack_memory.memory_range().map(|_| stack_memory));
748
749 // Begin with the context frame, and keep getting callers until there are no more.
750 let mut has_new_frame = !stack.frames.is_empty();
751 let mut on_walked_frame = on_walked_frame.into();
752 while has_new_frame {
753 // Symbolicate the new frame
754 let frame_idx = stack.frames.len() - 1;
755 let frame = stack.frames.last_mut().unwrap();
756
757 fill_source_line_info(frame, modules, symbol_provider).await;
758
759 // Report the frame as walked and symbolicated
760 if let OnWalkedFrame::Some(on_walked_frame) = &mut on_walked_frame {
761 on_walked_frame(frame_idx, frame);
762 }
763
764 let Some(stack_memory) = stack_memory else {
765 break;
766 };
767
768 // Walk the new frame
769 let callee_frame = &stack.frames.last().unwrap();
770 let grand_callee_frame = stack
771 .frames
772 .len()
773 .checked_sub(2)
774 .and_then(|idx| stack.frames.get(idx));
775 match callee_frame.function_name.as_ref() {
776 Some(name) => trace!("unwinding {}", name),
777 None => trace!("unwinding 0x{:016x}", callee_frame.instruction),
778 }
779 let new_frame = get_caller_frame(
780 frame_idx,
781 &GetCallerFrameArgs {
782 callee_frame,
783 grand_callee_frame,
784 stack_memory,
785 modules,
786 system_info,
787 symbol_provider,
788 },
789 )
790 .await;
791
792 // Check if we're done
793 if let Some(new_frame) = new_frame {
794 stack.frames.push(new_frame);
795 } else {
796 has_new_frame = false;
797 }
798 }
799 trace!(
800 "finished stack unwind of thread {} {}\n",
801 stack.thread_id,
802 stack.thread_name.as_deref().unwrap_or(""),
803 );
804}
805
806/// Checks if we can dismiss the validity of an instruction based on our symbols,
807/// to refine the quality of each unwinder's instruction_seems_valid implementation.
808async fn instruction_seems_valid_by_symbols<P>(
809 instruction: u64,
810 modules: &MinidumpModuleList,
811 symbol_provider: &P,
812) -> bool
813where
814 P: SymbolProvider + Sync,
815{
816 // Our input is a candidate return address, but we *really* want to validate the address
817 // of the call instruction *before* the return address. In theory this symbol-based
818 // analysis shouldn't *care* whether we're looking at the call or the instruction
819 // after it, but there is one corner case where the return address can be invalid
820 // but the instruction before it isn't: noreturn.
821 //
822 // If the *callee* is noreturn, then the caller has no obligation to have any instructions
823 // after the call! So e.g. on x86 if you CALL a noreturn function, the return address
824 // that's implicitly pushed *could* be one-past-the-end of the "function".
825 //
826 // This has been observed in practice with `+[NSThread exit]`!
827 //
828 // We don't otherwise need the instruction pointer to be terribly precise, so
829 // subtracting 1 from the address should be sufficient to handle this corner case.
830 let instruction = instruction.saturating_sub(1);
831
832 // NULL pointer is definitely not valid
833 if instruction == 0 {
834 return false;
835 }
836
837 if let Some(module) = modules.module_at_address(instruction) {
838 // Create a dummy frame symbolizing implementation to feed into
839 // our symbol provider with the address we're interested in. If
840 // it tries to set a non-empty function name, then we can reasonably
841 // assume the instruction address is valid.
842 //use crate::FrameSymbolizer;
843
844 struct DummyFrame {
845 instruction: u64,
846 has_name: bool,
847 }
848 impl FrameSymbolizer for DummyFrame {
849 fn get_instruction(&self) -> u64 {
850 self.instruction
851 }
852 fn set_function(&mut self, name: &str, _base: u64, _parameter_size: u32) {
853 self.has_name = !name.is_empty();
854 }
855 fn set_source_file(&mut self, _file: &str, _line: u32, _base: u64) {
856 // Do nothing
857 }
858 }
859
860 let mut frame = DummyFrame {
861 instruction,
862 has_name: false,
863 };
864
865 if symbol_provider
866 .fill_symbol(module, &mut frame)
867 .await
868 .is_ok()
869 {
870 frame.has_name
871 } else {
872 // If the symbol provider returns an Error, this means that we
873 // didn't have any symbols for the *module*. Just assume the
874 // instruction is valid in this case so that scanning works
875 // when we have no symbols.
876 true
877 }
878 } else {
879 // We couldn't even map this address to a module. Reject the pointer
880 // so that we have *some* way to distinguish "normal" pointers
881 // from instruction address.
882 //
883 // FIXME: this will reject any pointer into JITed code which otherwise
884 // isn't part of a normal well-defined module. We can potentially use
885 // MemoryInfoListStream (windows) and /proc/self/maps (linux) to refine
886 // this analysis and allow scans to walk through JITed code.
887 false
888 }
889}
890
891#[cfg(test)]
892mod amd64_unittest;
893#[cfg(test)]
894mod arm64_unittest;
895#[cfg(test)]
896mod arm_unittest;
897#[cfg(test)]
898mod x86_unittest;