Skip to main content

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.clear();
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    callee_lr_is_heuristic: bool,
561
562    caller_ctx: C,
563    caller_validity: HashSet<&'static str>,
564
565    cfi_rules_start_address: Option<u64>,
566
567    module: &'a MinidumpModule,
568    stack_memory: UnifiedMemory<'a, 'a>,
569}
570
571impl<'a, C> CfiStackWalker<'a, C>
572where
573    C: CpuContext + Clone,
574{
575    fn from_ctx_and_args<P, R>(
576        ctx: &'a C,
577        args: &'a GetCallerFrameArgs<'a, P>,
578        callee_forwarded_regs: R,
579    ) -> Option<Self>
580    where
581        R: Fn(&MinidumpContextValidity) -> HashSet<&'static str>,
582    {
583        let module = args
584            .modules
585            .module_at_address(args.callee_frame.instruction)?;
586        let grand_callee = args.grand_callee_frame;
587        Some(Self {
588            instruction: args.callee_frame.instruction,
589            has_grand_callee: grand_callee.is_some(),
590            grand_callee_parameter_size: grand_callee.and_then(|f| f.parameter_size).unwrap_or(0),
591
592            callee_ctx: ctx,
593            callee_validity: args.valid(),
594            callee_lr_is_heuristic: false,
595
596            // Default to forwarding all callee-saved regs verbatim.
597            // The CFI evaluator may clear or overwrite these values.
598            // The stack pointer and instruction pointer are not included.
599            caller_ctx: ctx.clone(),
600            caller_validity: callee_forwarded_regs(args.valid()),
601
602            cfi_rules_start_address: None,
603
604            module,
605            stack_memory: args.stack_memory,
606        })
607    }
608}
609
610impl<'a, C> FrameWalker for CfiStackWalker<'a, C>
611where
612    C: CpuContext,
613    C::Register: TryFrom<u64>,
614    u64: TryFrom<C::Register>,
615    C::Register: TryFromCtx<'a, Endian, [u8], Error = scroll::Error> + SizeWith<Endian>,
616{
617    fn get_instruction(&self) -> u64 {
618        self.instruction
619    }
620    fn has_grand_callee(&self) -> bool {
621        self.has_grand_callee
622    }
623    fn get_grand_callee_parameter_size(&self) -> u32 {
624        self.grand_callee_parameter_size
625    }
626    fn get_register_at_address(&self, address: u64) -> Option<u64> {
627        let result: Option<C::Register> = self.stack_memory.get_memory_at_address(address);
628        result.and_then(|val| u64::try_from(val).ok())
629    }
630    fn get_callee_register(&self, name: &str) -> Option<u64> {
631        if self.callee_lr_is_heuristic
632            && matches!(name, "lr" | "x30")
633            && self.cfi_rules_start_address != Some(self.instruction)
634        {
635            return None;
636        }
637        self.callee_ctx
638            .get_register(name, self.callee_validity)
639            .and_then(|val| u64::try_from(val).ok())
640    }
641    fn set_caller_register(&mut self, name: &str, val: u64) -> Option<()> {
642        let memoized = self.caller_ctx.memoize_register(name)?;
643        let val = C::Register::try_from(val).ok()?;
644        self.caller_validity.insert(memoized);
645        self.caller_ctx.set_register(name, val)
646    }
647    fn clear_caller_register(&mut self, name: &str) {
648        self.caller_validity.remove(name);
649    }
650    fn set_cfa(&mut self, val: u64) -> Option<()> {
651        // NOTE: some things have alluded to architectures where this isn't
652        // how the CFA should be handled, but we apparently don't support them yet?
653        let stack_pointer_reg = self.caller_ctx.stack_pointer_register_name();
654        let val = C::Register::try_from(val).ok()?;
655        self.caller_validity.insert(stack_pointer_reg);
656        self.caller_ctx.set_register(stack_pointer_reg, val)
657    }
658    fn set_ra(&mut self, val: u64) -> Option<()> {
659        let instruction_pointer_reg = self.caller_ctx.instruction_pointer_register_name();
660        let val = C::Register::try_from(val).ok()?;
661        self.caller_validity.insert(instruction_pointer_reg);
662        self.caller_ctx.set_register(instruction_pointer_reg, val)
663    }
664    fn set_cfi_rules_start_address(&mut self, addr: Option<u64>) {
665        self.cfi_rules_start_address = addr;
666    }
667}
668
669#[tracing::instrument(name = "unwind_frame", level = "trace", skip_all, fields(idx = _frame_idx, fname = args.callee_frame.function_name.as_deref().unwrap_or("")))]
670async fn get_caller_frame<P>(
671    _frame_idx: usize,
672    args: &GetCallerFrameArgs<'_, P>,
673) -> Option<StackFrame>
674where
675    P: SymbolProvider + Sync,
676{
677    match args.callee_frame.context.raw {
678        /*
679        MinidumpRawContext::PPC(ctx) => ctx.get_caller_frame(stack_memory),
680        MinidumpRawContext::PPC64(ctx) => ctx.get_caller_frame(stack_memory),
681        MinidumpRawContext::SPARC(ctx) => ctx.get_caller_frame(stack_memory),
682         */
683        MinidumpRawContext::Arm(ref ctx) => arm::get_caller_frame(ctx, args).await,
684        MinidumpRawContext::Arm64(ref ctx) => arm64::get_caller_frame(ctx, args).await,
685        MinidumpRawContext::OldArm64(ref ctx) => arm64_old::get_caller_frame(ctx, args).await,
686        MinidumpRawContext::Amd64(ref ctx) => amd64::get_caller_frame(ctx, args).await,
687        MinidumpRawContext::X86(ref ctx) => x86::get_caller_frame(ctx, args).await,
688        MinidumpRawContext::Mips(ref ctx) => mips::get_caller_frame(ctx, args).await,
689        _ => None,
690    }
691}
692
693async fn fill_source_line_info<P>(
694    frame: &mut StackFrame,
695    modules: &MinidumpModuleList,
696    symbol_provider: &P,
697) where
698    P: SymbolProvider + Sync,
699{
700    // Find the module whose address range covers this frame's instruction.
701    if let Some(module) = modules.module_at_address(frame.instruction) {
702        // FIXME: this shouldn't need to clone, we should be able to use
703        // the same lifetime as the module list that's passed in.
704        frame.module = Some(module.clone());
705
706        // This is best effort, so ignore any errors.
707        let _ = symbol_provider.fill_symbol(module, frame).await;
708
709        // If we got any inlines, reverse them! The symbol format makes it simplest to
710        // emit inlines from the shallowest callee to the deepest one ("inner to outer"),
711        // but we want inlines to be in the same order as the stackwalk itself, which means
712        // we want the deepest frame first (the callee-est frame).
713        frame.inlines.reverse();
714    }
715}
716
717/// An optional callback when walking frames.
718///
719/// One may convert from other types to this callback type:
720/// `FnMut(frame_idx: usize, frame: &StackFrame)` types can be converted to a
721/// callback, and `()` can be converted to no callback (do nothing).
722pub enum OnWalkedFrame<'a> {
723    None,
724    #[allow(clippy::type_complexity)]
725    Some(Box<dyn FnMut(usize, &StackFrame) + Send + 'a>),
726}
727
728impl From<()> for OnWalkedFrame<'_> {
729    fn from(_: ()) -> Self {
730        Self::None
731    }
732}
733
734impl<'a, F: FnMut(usize, &StackFrame) + Send + 'a> From<F> for OnWalkedFrame<'a> {
735    fn from(f: F) -> Self {
736        Self::Some(Box::new(f))
737    }
738}
739
740#[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("")))]
741pub async fn walk_stack<P>(
742    _thread_idx: usize,
743    on_walked_frame: impl Into<OnWalkedFrame<'_>>,
744    stack: &mut CallStack,
745    stack_memory: Option<UnifiedMemory<'_, '_>>,
746    modules: &MinidumpModuleList,
747    system_info: &SystemInfo,
748    symbol_provider: &P,
749) where
750    P: SymbolProvider + Sync,
751{
752    trace!(
753        "starting stack unwind of thread {} {}",
754        stack.thread_id,
755        stack.thread_name.as_deref().unwrap_or(""),
756    );
757
758    // All the unwinder code down below in `get_caller_frame` requires a valid `stack_memory`,
759    // where _valid_ means that we can actually read something from it. A call to `memory_range` will validate that,
760    // as it will reject empty stack memory or one with an overflowing `size`.
761    let stack_memory =
762        stack_memory.and_then(|stack_memory| stack_memory.memory_range().map(|_| stack_memory));
763
764    // Begin with the context frame, and keep getting callers until there are no more.
765    let mut has_new_frame = !stack.frames.is_empty();
766    let mut on_walked_frame = on_walked_frame.into();
767    while has_new_frame {
768        // Symbolicate the new frame
769        let frame_idx = stack.frames.len() - 1;
770        let frame = stack.frames.last_mut().unwrap();
771
772        fill_source_line_info(frame, modules, symbol_provider).await;
773
774        // Report the frame as walked and symbolicated
775        if let OnWalkedFrame::Some(on_walked_frame) = &mut on_walked_frame {
776            on_walked_frame(frame_idx, frame);
777        }
778
779        let Some(stack_memory) = stack_memory else {
780            break;
781        };
782
783        // Walk the new frame
784        let callee_frame = &stack.frames.last().unwrap();
785        let grand_callee_frame = stack
786            .frames
787            .len()
788            .checked_sub(2)
789            .and_then(|idx| stack.frames.get(idx));
790        match callee_frame.function_name.as_ref() {
791            Some(name) => trace!("unwinding {}", name),
792            None => trace!("unwinding 0x{:016x}", callee_frame.instruction),
793        }
794        let new_frame = get_caller_frame(
795            frame_idx,
796            &GetCallerFrameArgs {
797                callee_frame,
798                grand_callee_frame,
799                stack_memory,
800                modules,
801                system_info,
802                symbol_provider,
803            },
804        )
805        .await;
806
807        // Check if we're done
808        if let Some(new_frame) = new_frame {
809            stack.frames.push(new_frame);
810        } else {
811            has_new_frame = false;
812        }
813    }
814    trace!(
815        "finished stack unwind of thread {} {}\n",
816        stack.thread_id,
817        stack.thread_name.as_deref().unwrap_or(""),
818    );
819}
820
821/// Checks if we can dismiss the validity of an instruction based on our symbols,
822/// to refine the quality of each unwinder's instruction_seems_valid implementation.
823async fn instruction_seems_valid_by_symbols<P>(
824    instruction: u64,
825    modules: &MinidumpModuleList,
826    symbol_provider: &P,
827) -> bool
828where
829    P: SymbolProvider + Sync,
830{
831    // Our input is a candidate return address, but we *really* want to validate the address
832    // of the call instruction *before* the return address. In theory this symbol-based
833    // analysis shouldn't *care* whether we're looking at the call or the instruction
834    // after it, but there is one corner case where the return address can be invalid
835    // but the instruction before it isn't: noreturn.
836    //
837    // If the *callee* is noreturn, then the caller has no obligation to have any instructions
838    // after the call! So e.g. on x86 if you CALL a noreturn function, the return address
839    // that's implicitly pushed *could* be one-past-the-end of the "function".
840    //
841    // This has been observed in practice with `+[NSThread exit]`!
842    //
843    // We don't otherwise need the instruction pointer to be terribly precise, so
844    // subtracting 1 from the address should be sufficient to handle this corner case.
845    let instruction = instruction.saturating_sub(1);
846
847    // NULL pointer is definitely not valid
848    if instruction == 0 {
849        return false;
850    }
851
852    if let Some(module) = modules.module_at_address(instruction) {
853        // Create a dummy frame symbolizing implementation to feed into
854        // our symbol provider with the address we're interested in. If
855        // it tries to set a non-empty function name, then we can reasonably
856        // assume the instruction address is valid.
857        //use crate::FrameSymbolizer;
858
859        struct DummyFrame {
860            instruction: u64,
861            has_name: bool,
862        }
863        impl FrameSymbolizer for DummyFrame {
864            fn get_instruction(&self) -> u64 {
865                self.instruction
866            }
867            fn set_function(&mut self, name: &str, _base: u64, _parameter_size: u32) {
868                self.has_name = !name.is_empty();
869            }
870            fn set_source_file(&mut self, _file: &str, _line: u32, _base: u64) {
871                // Do nothing
872            }
873        }
874
875        let mut frame = DummyFrame {
876            instruction,
877            has_name: false,
878        };
879
880        if symbol_provider
881            .fill_symbol(module, &mut frame)
882            .await
883            .is_ok()
884        {
885            frame.has_name
886        } else {
887            // If the symbol provider returns an Error, this means that we
888            // didn't have any symbols for the *module*. Just assume the
889            // instruction is valid in this case so that scanning works
890            // when we have no symbols.
891            true
892        }
893    } else {
894        // We couldn't even map this address to a module. Reject the pointer
895        // so that we have *some* way to distinguish "normal" pointers
896        // from instruction address.
897        //
898        // FIXME: this will reject any pointer into JITed code which otherwise
899        // isn't part of a normal well-defined module. We can potentially use
900        // MemoryInfoListStream (windows) and /proc/self/maps (linux) to refine
901        // this analysis and allow scans to walk through JITed code.
902        false
903    }
904}
905
906#[cfg(test)]
907mod amd64_unittest;
908#[cfg(test)]
909mod arm64_unittest;
910#[cfg(test)]
911mod arm_unittest;
912#[cfg(test)]
913mod x86_unittest;