Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/toolkit/crashreporter/minidump-analyzer/src/   (Firefox Browser Version 153.0.1©)  Datei vom 27.6.2026 mit Größe 21 kB image not shown  

Quelle  lib.rs

  Sprache: Rust
 

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */


use anyhow::Context;
use futures_executor::{block_on, ThreadPool};
use minidump::{
    system_info::Cpu, Minidump, MinidumpException, MinidumpMemoryList, MinidumpMiscInfo,
    MinidumpModule, MinidumpModuleList, MinidumpSystemInfo, MinidumpThread, MinidumpThreadList,
    MinidumpUnloadedModule, MinidumpUnloadedModuleList, Module, UnifiedMemoryList,
};
use minidump_unwind::{
    symbols::debuginfo::DebugInfoSymbolProvider, symbols::SymbolProvider, walk_stack, CallStack,
    CallStackInfo, SystemInfo,
};
use std::borrow::Cow;
use std::fmt::Debug;
use std::fs::File;
use std::io::Read;
use std::path::{Path, PathBuf};
use std::sync::Arc;

use serde_json::{json, Value as JsonValue};

#[cfg(windows)]
mod windows;

/// Analyze a minidump file to augment a corresponding .extra file with stack trace information.
#[derive(Debug)]
pub struct MinidumpAnalyzer<'a> {
    all_stacks: bool,
    minidump: &'a Path,
    extras: Option<&'a Path>,
}

impl<'a> MinidumpAnalyzer<'a> {
    /// Create a new MinidumpAnalyzer for the given minidump file.
    pub fn new(minidump: &'a Path) -> Self {
        MinidumpAnalyzer {
            all_stacks: false,
            minidump,
            extras: None,
        }
    }

    /// Generate all stacks, rather than just those of the crashing thread.
    pub fn all_threads(mut self, value: bool) -> Self {
        self.all_stacks = value;
        self
    }

    /// Set the extras file explicitly, rather than deriving from the minidump path.
    pub fn extras_file(mut self, path: &'a Path) -> Self {
        self.extras = Some(path);
        self
    }

    /// Return the effective extras file to read.
    pub fn get_extras_file(&self) -> Cow<'a, Path> {
        self.extras
            .map(Cow::Borrowed)
            .unwrap_or_else(|| Cow::Owned(extra_path_from_minidump(&self.minidump)))
    }

    /// Analyze the thread(s) and put stacks in the associated .extra file.
    pub fn analyze(self) -> anyhow::Result<()> {
        let extra_file = self.get_extras_file();
        let extra_file = extra_file.as_ref();

        log::info!("minidump file path: {}"self.minidump.display());
        log::info!("extra file path: {}", extra_file.display());

        let mut extra_json = parse_extra_file(extra_file)?;

        self.analyze_json(&mut extra_json)?;

        std::fs::write(extra_file, extra_json.to_string())
            .context("while writing modified extra file")?;

        Ok(())
    }

    /// Analyze the thread(s) and alter the given extra JSON data.
    pub fn analyze_json(self, extra_json: &mut JsonValue) -> anyhow::Result<()> {
        let minidump = Minidump::read_path(&self.minidump).context("while reading minidump")?;

        let proc = processor::Processor::new(&minidump)?;

        let (crashing_thread_idx, call_stacks) = proc.get_call_stacks(self.all_stacks)?;
        let crash_type = proc.crash_reason();
        let crash_address = proc.crash_address();
        let used_modules = get_used_modules(&call_stacks, proc.main_module());

        // Set the `StackTraces` field in the extra JSON data.
        let mut stack_traces = json!({
            "error": call_stack_error(&call_stacks),
            "crash_type": crash_type,
            "crash_address": format!("{crash_address:#x}"),
            "crash_thread": crashing_thread_idx,
            "main_module": proc.main_module().and_then(|m| module_index(&used_modules, m)),
            "modules": used_modules.iter().map(|module| {
                let code_file = module.code_file();
                let code_file_path: &std::path::Path = code_file.as_ref().as_ref();

                fn file_name_str(p: &std::path::Path) -> Option<Cow<'_, str>> {
                    p.file_name().map(|s| s.to_string_lossy())
                }

                json!({
                    "base_address": format!("{:#x}", module.base_address()),
                    "end_address": format!("{:#x}", module.base_address() + module.size()),
                    "filename": file_name_str(code_file_path),
                    "code_id": module.code_identifier().as_ref().map(|id| id.as_str()),
                    // `debug_file` may be a file path with additional components; we just want the
                    // final component. See bug 1931237.
                    "debug_file": module.debug_file().as_deref().and_then(|s| file_name_str(Path::new(s))),
                    "debug_id": module.debug_identifier().map(|debug| debug.breakpad().to_string()),
                    "version": module.version().as_deref()
                })
            }).collect::<Vec<_>>(),
            "unloaded_modules": proc.unloaded_modules().map(|module| {
                let code_file = module.code_file();
                let code_file_path: &std::path::Path = code_file.as_ref().as_ref();
                json!({
                    "base_address": format!("{:#x}", module.base_address()),
                    "end_address": format!("{:#x}", module.base_address() + module.size()),
                    "filename": code_file_path.file_name().map(|s| s.to_string_lossy()),
                    "code_id": module.code_identifier().as_ref().map(|id| id.as_str()),
                })
            }).collect::<Vec<_>>(),
            "threads": call_stacks.iter().map(|call_stack| call_stack_to_json(call_stack, &used_modules)).collect::<Vec<_>>()
        });

        // StackTraces should not have null values (upstream processing expects the values to be
        // omitted).
        remove_nulls(&mut stack_traces);
        extra_json["StackTraces"] = serde_json::to_string(&stack_traces).unwrap().into();

        let module_signature_info = proc.module_signature_info();
        if !module_signature_info.is_null() {
            // ModuleSignatureInfo is sent as a crash annotation so must be string. This differs from
            // StackTraces which isn't actually sent (it's just read and removed by the crash
            // reporter client).
            extra_json["ModuleSignatureInfo"] = serde_json::to_string(&module_signature_info)
                .unwrap()
                .into();
        }

        Ok(())
    }
}

fn extra_path_from_minidump(minidump: &Path) -> PathBuf {
    let mut ret = minidump.to_owned();
    ret.set_extension("extra");
    ret
}

/// Parse the extra file (JSON).
fn parse_extra_file(path: &Path) -> anyhow::Result<JsonValue> {
    let mut extra_file_content = String::new();

    File::open(path)
        .context("while opening extra file")?
        .read_to_string(&mut extra_file_content)
        .context("while reading extra file")?;

    serde_json::from_str(&extra_file_content).context("while parsing extra file JSON")
}

/// Get a list of modules used in call stacks, ordered by base address.
fn get_used_modules<'a>(
    call_stacks: impl IntoIterator<Item = &'a CallStack>,
    main_module: Option<&'a MinidumpModule>,
) -> Vec<&'a MinidumpModule> {
    let mut v = call_stacks
        .into_iter()
        .flat_map(|call_stack| call_stack.frames.iter())
        .filter_map(|frame| frame.module.as_ref())
        // Always include the main module.
        .chain(main_module)
        .collect::<Vec<_>>();
    v.sort_by_key(|m| m.base_address());
    v.dedup_by_key(|m| m.base_address());
    v
}

mod processor {
    use super::*;

    pub struct Processor<'a> {
        runtime: ThreadPool,
        // We create a Context abstraction to easily spawn tokio tasks (which must be 'static).
        context: Arc<Ctx<'a>>,
    }

    struct Ctx<'a> {
        module_list: MinidumpModuleList,
        unloaded_module_list: MinidumpUnloadedModuleList,
        memory_list: UnifiedMemoryList<'a>,
        thread_list: MinidumpThreadList<'a>,
        system_info: MinidumpSystemInfo,
        processor_system_info: SystemInfo,
        exception: MinidumpException<'a>,
        misc_info: Option<MinidumpMiscInfo>,
        symbol_provider: BoxedSymbolProvider,
    }

    /// Concurrently execute the given futures, returning a Vec of the results.
    async fn concurrently<'a, I, Fut, R>(runtime: &R, iter: I) -> Vec<Fut::Output>
    where
        R: futures_util::task::Spawn,
        I: IntoIterator<Item = Fut>,
        Fut: std::future::Future + Send + 'a,
        // It's possible, though very obtuse, to support `'a` on the Output. We don't need it
        // though, so we keep it `'static` to simplify things.
        Fut::Output: Send + 'static,
    {
        use futures_util::{
            future::{join_all, BoxFuture, FutureExt},
            task::SpawnExt,
        };

        join_all(iter.into_iter().map(|f| {
            let fut: BoxFuture<'a, Fut::Output> = f.boxed();
            // Safety: It is safe to transmute to a static lifetime because we await the output of
            // the future while the `'a` lifetime is guaranteed to be valid (before exit from this
            // function).
            let fut: BoxFuture<'static, Fut::Output> = unsafe { std::mem::transmute(fut) };
            runtime.spawn_with_handle(fut).expect("spawn failed")
        }))
        .await
    }

    impl<'a> Processor<'a> {
        pub fn new<T>(minidump: &'a Minidump<T>) -> anyhow::Result<Self>
        where
            T: std::ops::Deref<Target = [u8]>,
        {
            let system_info = minidump.get_stream::<MinidumpSystemInfo>()?;
            let misc_info = minidump.get_stream::<MinidumpMiscInfo>().ok();
            let module_list = minidump
                .get_stream::<MinidumpModuleList>()
                .unwrap_or_default();
            let unloaded_module_list = minidump
                .get_stream::<MinidumpUnloadedModuleList>()
                .unwrap_or_default();
            let memory_list = minidump
                .get_stream::<MinidumpMemoryList>()
                .unwrap_or_default();
            let thread_list = minidump.get_stream::<MinidumpThreadList>()?;
            let exception = minidump.get_stream::<MinidumpException>()?;

            // TODO Something like SystemInfo::current() to get the active system's info?
            let processor_system_info = SystemInfo {
                os: system_info.os,
                os_version: None,
                os_build: None,
                cpu: system_info.cpu,
                cpu_info: None,
                cpu_microcode_version: None,
                cpu_count: 1,
            };

            let symbol_provider = BoxedSymbolProvider(match system_info.cpu {
                // DebugInfoSymbolProvider only supports x86_64 and Arm64 right now
                Cpu::X86_64 | Cpu::Arm64 => Box::new(block_on(DebugInfoSymbolProvider::new(
                    &system_info,
                    &module_list,
                ))),
                _ => Box::new(breakpad_symbols::Symbolizer::new(
                    breakpad_symbols::SimpleSymbolSupplier::new(vec![]),
                )),
            });

            Ok(Processor {
                runtime: ThreadPool::new()?,
                context: Arc::new(Ctx {
                    module_list,
                    unloaded_module_list,
                    memory_list: UnifiedMemoryList::Memory(memory_list),
                    thread_list,
                    system_info,
                    processor_system_info,
                    exception,
                    misc_info,
                    symbol_provider,
                }),
            })
        }

        /// Get the minidump system info.
        pub fn system_info(&self) -> &MinidumpSystemInfo {
            &self.context.system_info
        }

        /// Get the crash reason string.
        pub fn crash_reason(&self) -> String {
            self.context
                .exception
                .get_crash_reason(self.system_info().os, self.system_info().cpu)
                .to_string()
        }

        /// Get the crash address.
        pub fn crash_address(&self) -> u64 {
            self.context
                .exception
                .get_crash_address(self.system_info().os, self.system_info().cpu)
        }

        /// Get call stacks for the given threads.
        ///
        /// Call stacks will be concurrently calculated.
        fn thread_call_stacks<'b>(
            &self,
            threads: impl IntoIterator<Item = &'b MinidumpThread<'b>>,
        ) -> anyhow::Result<Vec<CallStack>> {
            Ok(block_on(concurrently(
                &self.runtime,
                threads
                    .into_iter()
                    .map(|thread| self.context.thread_call_stack(thread)),
            ))
            .into_iter()
            .collect())
        }

        /// Get the call stacks to process.
        ///
        /// The first value in the returned tuple is the crashing thread index. The second value is the
        /// set of call stacks to process.
        pub fn get_call_stacks(&self, all_stacks: bool) -> anyhow::Result<(usize, Vec<CallStack>)> {
            // Derive additional arguments used in stack walking.
            let crashing_thread = self
                .context
                .thread_list
                .get_thread(self.context.exception.get_crashing_thread_id())
                .ok_or(anyhow::anyhow!(
                    "exception thread id missing in thread list"
                ))?;

            Ok(if all_stacks {
                (
                    self.context
                        .thread_list
                        .threads
                        .iter()
                        .position(|t| t.raw.thread_id == crashing_thread.raw.thread_id)
                        .expect("get_thread() returned a thread that doesn't exist"),
                    self.thread_call_stacks(&self.context.thread_list.threads)?,
                )
            } else {
                (0self.thread_call_stacks([crashing_thread])?)
            })
        }

        /// Get all modules, ordered by address.
        #[cfg(windows)]
        pub fn ordered_modules(&self) -> impl Iterator<Item = &MinidumpModule> {
            self.context.module_list.by_addr()
        }

        /// Get all unloaded modules, ordered by address.
        pub fn unloaded_modules(&self) -> impl Iterator<Item = &MinidumpUnloadedModule> {
            self.context.unloaded_module_list.by_addr()
        }

        /// Get the index of the main module.
        ///
        /// Returns `None` when no main module exists (only when there are modules).
        pub fn main_module(&self) -> Option<&MinidumpModule> {
            self.context.module_list.main_module()
        }

        /// Get the json representation of module signature information.
        #[cfg(windows)]
        pub fn module_signature_info(&self) -> JsonValue {
            // JSON with structure { <binary_org_name>: [<code_file filename>...], ... }
            let mut ret = json!({});
            for module in self
                .ordered_modules()
                .map(|m| m as &dyn Module)
                .chain(self.unloaded_modules().map(|m| m as &dyn Module))
            {
                let code_file = module.code_file();
                let code_file_path: &std::path::Path = code_file.as_ref().as_ref();
                if let Some(org_name) = windows::binary_org_name(code_file_path) {
                    let entry = &mut ret[org_name];

                    if entry.is_null() {
                        *entry = json!([]);
                    }
                    entry.as_array_mut().unwrap().push(
                        code_file_path
                            .file_name()
                            .map(|s| s.to_string_lossy())
                            .into(),
                    );
                } else {
                    log::warn!("couldn't get binary org name for {code_file}");
                }
            }

            ret
        }

        /// Get the json representation of module signature information.
        ///
        /// This is currently unimplemented and returns null.
        #[cfg(unix)]
        pub fn module_signature_info(&self) -> JsonValue {
            JsonValue::Null
        }
    }

    impl Ctx<'_> {
        /// Compute the call stack for a single thread.
        pub async fn thread_call_stack(&self, thread: &MinidumpThread<'_>) -> CallStack {
            let context = if thread.raw.thread_id == self.exception.get_crashing_thread_id() {
                self.exception
                    .context(&self.system_info, self.misc_info.as_ref())
            } else {
                thread.context(&self.system_info, self.misc_info.as_ref())
            }
            .map(|c| c.into_owned());

            let mut stack_memory = thread.stack_memory(&self.memory_list);

            let Some(mut call_stack) = context.map(CallStack::with_context) else {
                return CallStack::with_info(thread.raw.thread_id, CallStackInfo::MissingContext);
            };

            // Always choose the memory region that is referenced by the context,
            // as the exception context may refer to a different memory region than
            // the thread context, which in turn would fail to stack walk.
            if let Some(stack_ptr) = call_stack
                .frames
                .first()
                .map(|frame| frame.context.get_stack_pointer())
            {
                let contains_stack_ptr = stack_memory
                    .as_ref()
                    .and_then(|memory| memory.get_memory_at_address::<u64>(stack_ptr))
                    .is_some();
                if !contains_stack_ptr {
                    stack_memory = self
                        .memory_list
                        .memory_at_address(stack_ptr)
                        .or(stack_memory);
                }
            }

            walk_stack(
                0,
                (),
                &mut call_stack,
                stack_memory,
                &self.module_list,
                &self.processor_system_info,
                &self.symbol_provider,
            )
            .await;

            call_stack
        }
    }
}

struct BoxedSymbolProvider(Box<dyn SymbolProvider + Send + Sync>);

#[async_trait::async_trait]
impl SymbolProvider for BoxedSymbolProvider {
    async fn fill_symbol(
        &self,
        module: &(dyn Module + Sync),
        frame: &mut (dyn minidump_unwind::FrameSymbolizer + Send),
    ) -> Result<(), minidump_unwind::FillSymbolError> {
        // Initialize the function name to a dummy value to allow stack scanning to work when the
        // DebugInfoSymbolProvider is used. It will be overwritten by `fill_symbol` if necessary,
        // however either way we don't care about symbols at all here. This works around
        // `minidump-unwind`'s behavior of only doing stack scanning if fill_symbol (1) fails or
        // (2) succeeds and sets a function name. By always setting a function name, we can ensure
        // that unwinding will always attempt to scan the stack if necessary.
        //
        // TODO: Remove this workaround once minidump-unwind is updated to a version which includes
        // https://github.com/rust-minidump/rust-minidump/pull/1117.
        frame.set_function("<unknown>"00);
        self.0.fill_symbol(module, frame).await
    }

    async fn walk_frame(
        &self,
        module: &(dyn Module + Sync),
        walker: &mut (dyn minidump_unwind::FrameWalker + Send),
    ) -> Option<()> {
        self.0.walk_frame(module, walker).await
    }

    async fn get_file_path(
        &self,
        module: &(dyn Module + Sync),
        file_kind: minidump_unwind::FileKind,
    ) -> Result<PathBuf, minidump_unwind::FileError> {
        self.0.get_file_path(module, file_kind).await
    }

    fn stats(&self) -> std::collections::HashMap<String, minidump_unwind::SymbolStats> {
        self.0.stats()
    }

    fn pending_stats(&self) -> minidump_unwind::PendingSymbolStats {
        self.0.pending_stats()
    }
}

/// Get the index of `needle` in `modules`.
fn module_index(modules: &[&MinidumpModule], needle: &MinidumpModule) -> Option<usize> {
    modules
        .iter()
        .position(|o| o.base_address() == needle.base_address())
}

/// Convert a call stack to json (in a form appropriate for the extra json file).
fn call_stack_to_json(call_stack: &CallStack, modules: &[&MinidumpModule]) -> JsonValue {
    json!({
        "frames": call_stack.frames.iter().map(|frame| {
            json!({
                "ip": format!("{:#x}", frame.instruction),
                "module_index": frame.module.as_ref().and_then(|m| module_index(modules, m)),
                "trust": frame.trust.as_str(),
            })
        }).collect::<Vec<_>>()
    })
}

fn call_stack_error(stacks: &[CallStack]) -> Option<String> {
    for (_i, s) in stacks.iter().enumerate() {
        match s.info {
            CallStackInfo::Ok | CallStackInfo::DumpThreadSkipped => (),
            CallStackInfo::UnsupportedCpu => {
                // If the CPU is unsupported, it ought to be the same error for every thread.
                return Some("unsupported cpu".into());
            }
            // We ignore these errors as they are permissible wrt the overall status.
            CallStackInfo::MissingContext | CallStackInfo::MissingMemory => (),
        }
    }
    None
}

/// Remove all object entries which have null values.
fn remove_nulls(value: &mut JsonValue) {
    match value {
        JsonValue::Array(vals) => {
            for v in vals {
                remove_nulls(v);
            }
        }
        JsonValue::Object(kvs) => {
            kvs.retain(|_, v| !v.is_null());
            for v in kvs.values_mut() {
                remove_nulls(v);
            }
        }
        _ => (),
    }
}

Messung V0.5 in Prozent
C=94 H=94 G=93

¤ Dauer der Verarbeitung: 0.9 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.