summaryrefslogtreecommitdiffstats
path: root/third_party/rust/minidump-writer/src/linux
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /third_party/rust/minidump-writer/src/linux
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/minidump-writer/src/linux')
-rw-r--r--third_party/rust/minidump-writer/src/linux/android.rs146
-rw-r--r--third_party/rust/minidump-writer/src/linux/app_memory.rs9
-rw-r--r--third_party/rust/minidump-writer/src/linux/auxv_reader.rs115
-rw-r--r--third_party/rust/minidump-writer/src/linux/crash_context.rs19
-rw-r--r--third_party/rust/minidump-writer/src/linux/crash_context/aarch64.rs34
-rw-r--r--third_party/rust/minidump-writer/src/linux/crash_context/arm.rs47
-rw-r--r--third_party/rust/minidump-writer/src/linux/crash_context/x86.rs62
-rw-r--r--third_party/rust/minidump-writer/src/linux/crash_context/x86_64.rs78
-rw-r--r--third_party/rust/minidump-writer/src/linux/dso_debug.rs273
-rw-r--r--third_party/rust/minidump-writer/src/linux/dumper_cpu_info.rs72
-rw-r--r--third_party/rust/minidump-writer/src/linux/dumper_cpu_info/arm.rs404
-rw-r--r--third_party/rust/minidump-writer/src/linux/dumper_cpu_info/x86_mips.rs115
-rw-r--r--third_party/rust/minidump-writer/src/linux/errors.rs225
-rw-r--r--third_party/rust/minidump-writer/src/linux/maps_reader.rs658
-rw-r--r--third_party/rust/minidump-writer/src/linux/minidump_writer.rs341
-rw-r--r--third_party/rust/minidump-writer/src/linux/ptrace_dumper.rs594
-rw-r--r--third_party/rust/minidump-writer/src/linux/sections.rs18
-rw-r--r--third_party/rust/minidump-writer/src/linux/sections/app_memory.rs23
-rw-r--r--third_party/rust/minidump-writer/src/linux/sections/exception_stream.rs50
-rw-r--r--third_party/rust/minidump-writer/src/linux/sections/mappings.rs98
-rw-r--r--third_party/rust/minidump-writer/src/linux/sections/memory_list_stream.rs21
-rw-r--r--third_party/rust/minidump-writer/src/linux/sections/systeminfo_stream.rs23
-rw-r--r--third_party/rust/minidump-writer/src/linux/sections/thread_list_stream.rs238
-rw-r--r--third_party/rust/minidump-writer/src/linux/sections/thread_names_stream.rs33
-rw-r--r--third_party/rust/minidump-writer/src/linux/thread_info.rs169
-rw-r--r--third_party/rust/minidump-writer/src/linux/thread_info/aarch64.rs120
-rw-r--r--third_party/rust/minidump-writer/src/linux/thread_info/arm.rs107
-rw-r--r--third_party/rust/minidump-writer/src/linux/thread_info/mips.rs56
-rw-r--r--third_party/rust/minidump-writer/src/linux/thread_info/x86.rs301
29 files changed, 4449 insertions, 0 deletions
diff --git a/third_party/rust/minidump-writer/src/linux/android.rs b/third_party/rust/minidump-writer/src/linux/android.rs
new file mode 100644
index 0000000000..0c8195cc0f
--- /dev/null
+++ b/third_party/rust/minidump-writer/src/linux/android.rs
@@ -0,0 +1,146 @@
+use crate::errors::AndroidError;
+use crate::maps_reader::MappingInfo;
+use crate::ptrace_dumper::PtraceDumper;
+use crate::thread_info::Pid;
+use goblin::elf;
+#[cfg(target_pointer_width = "32")]
+use goblin::elf::dynamic::dyn32::{Dyn, SIZEOF_DYN};
+#[cfg(target_pointer_width = "64")]
+use goblin::elf::dynamic::dyn64::{Dyn, SIZEOF_DYN};
+#[cfg(target_pointer_width = "32")]
+use goblin::elf::header::header32 as elf_header;
+#[cfg(target_pointer_width = "64")]
+use goblin::elf::header::header64 as elf_header;
+#[cfg(target_pointer_width = "32")]
+use goblin::elf::program_header::program_header32::ProgramHeader;
+#[cfg(target_pointer_width = "64")]
+use goblin::elf::program_header::program_header64::ProgramHeader;
+use std::ffi::c_void;
+
+type Result<T> = std::result::Result<T, AndroidError>;
+
+// From /usr/include/elf.h of the android SDK
+// #define DT_ANDROID_REL (DT_LOOS + 2)
+// #define DT_ANDROID_RELSZ (DT_LOOS + 3)
+// #define DT_ANDROID_RELA (DT_LOOS + 4)
+// #define DT_ANDROID_RELASZ (DT_LOOS + 5)
+#[cfg(target_pointer_width = "64")]
+const DT_ANDROID_REL: u64 = elf::dynamic::DT_LOOS + 2;
+#[cfg(target_pointer_width = "64")]
+const DT_ANDROID_RELA: u64 = elf::dynamic::DT_LOOS + 4;
+#[cfg(target_pointer_width = "32")]
+const DT_ANDROID_REL: u32 = (elf::dynamic::DT_LOOS + 2) as u32;
+#[cfg(target_pointer_width = "32")]
+const DT_ANDROID_RELA: u32 = (elf::dynamic::DT_LOOS + 4) as u32;
+
+struct DynVaddresses {
+ min_vaddr: usize,
+ dyn_vaddr: usize,
+ dyn_count: usize,
+}
+
+fn has_android_packed_relocations(pid: Pid, load_bias: usize, vaddrs: DynVaddresses) -> Result<()> {
+ let dyn_addr = load_bias + vaddrs.dyn_vaddr;
+ for idx in 0..vaddrs.dyn_count {
+ let addr = (dyn_addr + SIZEOF_DYN * idx) as *mut c_void;
+ let dyn_data = PtraceDumper::copy_from_process(pid, addr, SIZEOF_DYN)?;
+ // TODO: Couldn't find a nice way to use goblin for that, to avoid the unsafe-block
+ let dyn_obj: Dyn;
+ unsafe {
+ dyn_obj = std::mem::transmute::<[u8; SIZEOF_DYN], Dyn>(dyn_data.as_slice().try_into()?);
+ }
+
+ if dyn_obj.d_tag == DT_ANDROID_REL || dyn_obj.d_tag == DT_ANDROID_RELA {
+ return Ok(());
+ }
+ }
+ Err(AndroidError::NoRelFound)
+}
+
+fn get_effective_load_bias(pid: Pid, ehdr: &elf_header::Header, address: usize) -> usize {
+ let ph = parse_loaded_elf_program_headers(pid, ehdr, address);
+ // If |min_vaddr| is non-zero and we find Android packed relocation tags,
+ // return the effective load bias.
+
+ if ph.min_vaddr != 0 {
+ let load_bias = address - ph.min_vaddr;
+ if has_android_packed_relocations(pid, load_bias, ph).is_ok() {
+ return load_bias;
+ }
+ }
+ // Either |min_vaddr| is zero, or it is non-zero but we did not find the
+ // expected Android packed relocations tags.
+ address
+}
+
+fn parse_loaded_elf_program_headers(
+ pid: Pid,
+ ehdr: &elf_header::Header,
+ address: usize,
+) -> DynVaddresses {
+ let phdr_addr = address + ehdr.e_phoff as usize;
+ let mut min_vaddr = usize::MAX;
+ let mut dyn_vaddr = 0;
+ let mut dyn_count = 0;
+
+ let phdr_opt = PtraceDumper::copy_from_process(
+ pid,
+ phdr_addr as *mut c_void,
+ elf_header::SIZEOF_EHDR * ehdr.e_phnum as usize,
+ );
+ if let Ok(ph_data) = phdr_opt {
+ // TODO: The original C code doesn't have error-handling here at all.
+ // We silently ignore "not parsable" for now, but might bubble it up.
+ // TODO2: `from_bytes` might panic, `parse()` would return a Result<>, so maybe better
+ // to switch to that at some point.
+ for phdr in ProgramHeader::from_bytes(&ph_data, ehdr.e_phnum as usize) {
+ let p_vaddr = phdr.p_vaddr as usize;
+ if phdr.p_type == elf::program_header::PT_LOAD && p_vaddr < min_vaddr {
+ min_vaddr = p_vaddr;
+ }
+
+ if phdr.p_type == elf::program_header::PT_DYNAMIC {
+ dyn_vaddr = p_vaddr;
+ dyn_count = phdr.p_memsz as usize / SIZEOF_DYN;
+ }
+ }
+ }
+
+ DynVaddresses {
+ min_vaddr,
+ dyn_vaddr,
+ dyn_count,
+ }
+}
+
+pub fn late_process_mappings(pid: Pid, mappings: &mut [MappingInfo]) -> Result<()> {
+ // Only consider exec mappings that indicate a file path was mapped, and
+ // where the ELF header indicates a mapped shared library.
+ for mut map in mappings
+ .iter_mut()
+ .filter(|m| m.executable && m.name.as_ref().map_or(false, |n| n.starts_with("/")))
+ {
+ let ehdr_opt = PtraceDumper::copy_from_process(
+ pid,
+ map.start_address as *mut c_void,
+ elf_header::SIZEOF_EHDR,
+ )
+ .ok()
+ .and_then(|x| elf_header::Header::parse(&x).ok());
+
+ if let Some(ehdr) = ehdr_opt {
+ if ehdr.e_type == elf_header::ET_DYN {
+ // Compute the effective load bias for this mapped library, and update
+ // the mapping to hold that rather than |start_addr|, at the same time
+ // adjusting |size| to account for the change in |start_addr|. Where
+ // the library does not contain Android packed relocations,
+ // GetEffectiveLoadBias() returns |start_addr| and the mapping entry
+ // is not changed.
+ let load_bias = get_effective_load_bias(pid, &ehdr, map.start_address);
+ map.size += map.start_address - load_bias;
+ map.start_address = load_bias;
+ }
+ }
+ }
+ Ok(())
+}
diff --git a/third_party/rust/minidump-writer/src/linux/app_memory.rs b/third_party/rust/minidump-writer/src/linux/app_memory.rs
new file mode 100644
index 0000000000..80e77d5bb0
--- /dev/null
+++ b/third_party/rust/minidump-writer/src/linux/app_memory.rs
@@ -0,0 +1,9 @@
+// These entries store a list of memory regions that the client wants included
+// in the minidump.
+#[derive(Debug, Default, PartialEq, Eq)]
+pub struct AppMemory {
+ pub ptr: usize,
+ pub length: usize,
+}
+
+pub type AppMemoryList = Vec<AppMemory>;
diff --git a/third_party/rust/minidump-writer/src/linux/auxv_reader.rs b/third_party/rust/minidump-writer/src/linux/auxv_reader.rs
new file mode 100644
index 0000000000..1fcaff9658
--- /dev/null
+++ b/third_party/rust/minidump-writer/src/linux/auxv_reader.rs
@@ -0,0 +1,115 @@
+// This file is heavily based on https://bitbucket.org/marshallpierce/rust-auxv
+// Thus I'm keeping the original MIT-license copyright here:
+// Copyright 2017 Marshall Pierce
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+// The above copyright notice and this permission notice shall be in…substantial portions of the Software.
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+use crate::errors::AuxvReaderError;
+use byteorder::{NativeEndian, ReadBytesExt};
+use std::fs::File;
+use std::io::{BufReader, Read};
+
+pub type Result<T> = std::result::Result<T, AuxvReaderError>;
+
+/// The type used in auxv keys and values.
+#[cfg(target_pointer_width = "32")]
+pub type AuxvType = u32;
+/// The type used in auxv keys and values.
+#[cfg(target_pointer_width = "64")]
+pub type AuxvType = u64;
+
+/// An auxv key-value pair.
+#[derive(Debug, PartialEq, Eq)]
+pub struct AuxvPair {
+ pub key: AuxvType,
+ pub value: AuxvType,
+}
+
+/// An iterator across auxv pairs froom procfs.
+pub struct ProcfsAuxvIter {
+ pair_size: usize,
+ buf: Vec<u8>,
+ input: BufReader<File>,
+ keep_going: bool,
+}
+
+impl ProcfsAuxvIter {
+ pub fn new(input: BufReader<File>) -> Self {
+ let pair_size = 2 * std::mem::size_of::<AuxvType>();
+ let buf: Vec<u8> = Vec::with_capacity(pair_size);
+
+ Self {
+ pair_size,
+ buf,
+ input,
+ keep_going: true,
+ }
+ }
+}
+
+impl Iterator for ProcfsAuxvIter {
+ type Item = Result<AuxvPair>;
+ fn next(&mut self) -> Option<Self::Item> {
+ if !self.keep_going {
+ return None;
+ }
+ // assume something will fail
+ self.keep_going = false;
+
+ self.buf = vec![0; self.pair_size];
+
+ let mut read_bytes: usize = 0;
+ while read_bytes < self.pair_size {
+ // read exactly buf's len of bytes.
+ match self.input.read(&mut self.buf[read_bytes..]) {
+ Ok(n) => {
+ if n == 0 {
+ // should not hit EOF before AT_NULL
+ return Some(Err(AuxvReaderError::InvalidFormat));
+ }
+
+ read_bytes += n;
+ }
+ Err(x) => return Some(Err(x.into())),
+ }
+ }
+
+ let mut reader = &self.buf[..];
+ let aux_key = match read_long(&mut reader) {
+ Ok(x) => x,
+ Err(x) => return Some(Err(x.into())),
+ };
+ let aux_val = match read_long(&mut reader) {
+ Ok(x) => x,
+ Err(x) => return Some(Err(x.into())),
+ };
+
+ let at_null;
+ #[cfg(target_arch = "arm")]
+ {
+ at_null = 0;
+ }
+ #[cfg(not(target_arch = "arm"))]
+ {
+ at_null = libc::AT_NULL;
+ }
+
+ if aux_key == at_null {
+ return None;
+ }
+
+ self.keep_going = true;
+ Some(Ok(AuxvPair {
+ key: aux_key,
+ value: aux_val,
+ }))
+ }
+}
+
+fn read_long(reader: &mut dyn Read) -> std::io::Result<AuxvType> {
+ match std::mem::size_of::<AuxvType>() {
+ 4 => reader.read_u32::<NativeEndian>().map(|u| u as AuxvType),
+ 8 => reader.read_u64::<NativeEndian>().map(|u| u as AuxvType),
+ x => panic!("Unexpected type width: {}", x),
+ }
+}
diff --git a/third_party/rust/minidump-writer/src/linux/crash_context.rs b/third_party/rust/minidump-writer/src/linux/crash_context.rs
new file mode 100644
index 0000000000..f7a554d110
--- /dev/null
+++ b/third_party/rust/minidump-writer/src/linux/crash_context.rs
@@ -0,0 +1,19 @@
+//! Minidump defines register structures which are different from the raw
+//! structures which we get from the kernel. These are platform specific
+//! functions to juggle the `ucontext_t` and user structures into minidump format.
+
+pub struct CrashContext {
+ pub inner: crash_context::CrashContext,
+}
+
+cfg_if::cfg_if! {
+ if #[cfg(target_arch = "x86_64")] {
+ mod x86_64;
+ } else if #[cfg(target_arch = "x86")] {
+ mod x86;
+ } else if #[cfg(target_arch = "aarch64")] {
+ mod aarch64;
+ } else if #[cfg(target_arch = "arm")] {
+ mod arm;
+ }
+}
diff --git a/third_party/rust/minidump-writer/src/linux/crash_context/aarch64.rs b/third_party/rust/minidump-writer/src/linux/crash_context/aarch64.rs
new file mode 100644
index 0000000000..c53c37720c
--- /dev/null
+++ b/third_party/rust/minidump-writer/src/linux/crash_context/aarch64.rs
@@ -0,0 +1,34 @@
+use super::CrashContext;
+use crate::{
+ minidump_cpu::{RawContextCPU, FP_REG_COUNT, GP_REG_COUNT},
+ minidump_format::format,
+};
+
+impl CrashContext {
+ pub fn get_instruction_pointer(&self) -> usize {
+ self.inner.context.uc_mcontext.pc as usize
+ }
+
+ pub fn get_stack_pointer(&self) -> usize {
+ self.inner.context.uc_mcontext.sp as usize
+ }
+
+ pub fn fill_cpu_context(&self, out: &mut RawContextCPU) {
+ out.context_flags = format::ContextFlagsArm64Old::CONTEXT_ARM64_OLD_FULL.bits() as u64;
+
+ {
+ let gregs = &self.inner.context.uc_mcontext;
+ out.cpsr = gregs.pstate as u32;
+ out.iregs[..GP_REG_COUNT].copy_from_slice(&gregs.regs[..GP_REG_COUNT]);
+ out.sp = gregs.sp;
+ out.pc = gregs.pc;
+ }
+
+ {
+ let fs = &self.inner.float_state;
+ out.fpsr = fs.fpsr;
+ out.fpcr = fs.fpcr;
+ out.float_regs[..FP_REG_COUNT].copy_from_slice(&fs.vregs[..FP_REG_COUNT]);
+ }
+ }
+}
diff --git a/third_party/rust/minidump-writer/src/linux/crash_context/arm.rs b/third_party/rust/minidump-writer/src/linux/crash_context/arm.rs
new file mode 100644
index 0000000000..e4e40216e2
--- /dev/null
+++ b/third_party/rust/minidump-writer/src/linux/crash_context/arm.rs
@@ -0,0 +1,47 @@
+use super::CrashContext;
+use crate::minidump_cpu::RawContextCPU;
+
+impl CrashContext {
+ pub fn get_instruction_pointer(&self) -> usize {
+ self.inner.context.uc_mcontext.arm_pc as usize
+ }
+
+ pub fn get_stack_pointer(&self) -> usize {
+ self.inner.context.uc_mcontext.arm_sp as usize
+ }
+
+ pub fn fill_cpu_context(&self, out: &mut RawContextCPU) {
+ out.context_flags =
+ crate::minidump_format::format::ContextFlagsArm::CONTEXT_ARM_FULL.bits();
+
+ {
+ let iregs = &mut out.iregs;
+ let gregs = &self.inner.context.uc_mcontext;
+ iregs[0] = gregs.arm_r0;
+ iregs[1] = gregs.arm_r1;
+ iregs[2] = gregs.arm_r2;
+ iregs[3] = gregs.arm_r3;
+ iregs[4] = gregs.arm_r4;
+ iregs[5] = gregs.arm_r5;
+ iregs[6] = gregs.arm_r6;
+ iregs[7] = gregs.arm_r7;
+ iregs[8] = gregs.arm_r8;
+ iregs[9] = gregs.arm_r9;
+ iregs[10] = gregs.arm_r10;
+
+ iregs[11] = gregs.arm_fp;
+ iregs[12] = gregs.arm_ip;
+ iregs[13] = gregs.arm_sp;
+ iregs[14] = gregs.arm_lr;
+ iregs[15] = gregs.arm_pc;
+
+ out.cpsr = gregs.arm_cpsr;
+ }
+
+ // TODO: this todo has been in breakpad for years....
+ // TODO: fix this after fixing ExceptionHandler
+ //out.float_save.fpscr = 0;
+ //out.float_save.regs = [0; MD_FLOATINGSAVEAREA_ARM_FPR_COUNT];
+ //out.float_save.extra = [0; MD_FLOATINGSAVEAREA_ARM_FPEXTRA_COUNT];
+ }
+}
diff --git a/third_party/rust/minidump-writer/src/linux/crash_context/x86.rs b/third_party/rust/minidump-writer/src/linux/crash_context/x86.rs
new file mode 100644
index 0000000000..7f092716bd
--- /dev/null
+++ b/third_party/rust/minidump-writer/src/linux/crash_context/x86.rs
@@ -0,0 +1,62 @@
+use super::CrashContext;
+use crate::{minidump_cpu::RawContextCPU, minidump_format::format::ContextFlagsX86};
+use libc::{
+ REG_CS, REG_DS, REG_EAX, REG_EBP, REG_EBX, REG_ECX, REG_EDI, REG_EDX, REG_EFL, REG_EIP, REG_ES,
+ REG_ESI, REG_ESP, REG_FS, REG_GS, REG_SS, REG_UESP,
+};
+impl CrashContext {
+ pub fn get_instruction_pointer(&self) -> usize {
+ self.inner.context.uc_mcontext.gregs[REG_EIP as usize] as usize
+ }
+
+ pub fn get_stack_pointer(&self) -> usize {
+ self.inner.context.uc_mcontext.gregs[REG_ESP as usize] as usize
+ }
+
+ pub fn fill_cpu_context(&self, out: &mut RawContextCPU) {
+ out.context_flags = ContextFlagsX86::CONTEXT_X86_FULL.bits()
+ | ContextFlagsX86::CONTEXT_X86_FLOATING_POINT.bits();
+
+ {
+ let gregs = &self.inner.context.uc_mcontext.gregs;
+ out.gs = gregs[REG_GS as usize] as u32;
+ out.fs = gregs[REG_FS as usize] as u32;
+ out.es = gregs[REG_ES as usize] as u32;
+ out.ds = gregs[REG_DS as usize] as u32;
+
+ out.edi = gregs[REG_EDI as usize] as u32;
+ out.esi = gregs[REG_ESI as usize] as u32;
+ out.ebx = gregs[REG_EBX as usize] as u32;
+ out.edx = gregs[REG_EDX as usize] as u32;
+ out.ecx = gregs[REG_ECX as usize] as u32;
+ out.eax = gregs[REG_EAX as usize] as u32;
+
+ out.ebp = gregs[REG_EBP as usize] as u32;
+ out.eip = gregs[REG_EIP as usize] as u32;
+ out.cs = gregs[REG_CS as usize] as u32;
+ out.eflags = gregs[REG_EFL as usize] as u32;
+ out.esp = gregs[REG_UESP as usize] as u32;
+ out.ss = gregs[REG_SS as usize] as u32;
+ }
+
+ {
+ let fs = &self.inner.float_state;
+ let mut out = &mut out.float_save;
+ out.control_word = fs.cw;
+ out.status_word = fs.sw;
+ out.tag_word = fs.tag;
+ out.error_offset = fs.ipoff;
+ out.error_selector = fs.cssel;
+ out.data_offset = fs.dataoff;
+ out.data_selector = fs.datasel;
+
+ debug_assert_eq!(fs._st.len() * std::mem::size_of::<libc::_libc_fpreg>(), 80);
+ out.register_area.copy_from_slice(unsafe {
+ std::slice::from_raw_parts(
+ fs._st.as_ptr().cast(),
+ fs._st.len() * std::mem::size_of::<libc::_libc_fpreg>(),
+ )
+ });
+ }
+ }
+}
diff --git a/third_party/rust/minidump-writer/src/linux/crash_context/x86_64.rs b/third_party/rust/minidump-writer/src/linux/crash_context/x86_64.rs
new file mode 100644
index 0000000000..e559692679
--- /dev/null
+++ b/third_party/rust/minidump-writer/src/linux/crash_context/x86_64.rs
@@ -0,0 +1,78 @@
+use super::CrashContext;
+use crate::{
+ minidump_cpu::RawContextCPU, minidump_format::format, thread_info::copy_u32_registers,
+};
+use libc::{
+ REG_CSGSFS, REG_EFL, REG_R10, REG_R11, REG_R12, REG_R13, REG_R14, REG_R15, REG_R8, REG_R9,
+ REG_RAX, REG_RBP, REG_RBX, REG_RCX, REG_RDI, REG_RDX, REG_RIP, REG_RSI, REG_RSP,
+};
+use scroll::Pwrite;
+
+impl CrashContext {
+ pub fn get_instruction_pointer(&self) -> usize {
+ self.inner.context.uc_mcontext.gregs[REG_RIP as usize] as usize
+ }
+
+ pub fn get_stack_pointer(&self) -> usize {
+ self.inner.context.uc_mcontext.gregs[REG_RSP as usize] as usize
+ }
+
+ pub fn fill_cpu_context(&self, out: &mut RawContextCPU) {
+ out.context_flags = format::ContextFlagsAmd64::CONTEXT_AMD64_FULL.bits();
+
+ {
+ let gregs = &self.inner.context.uc_mcontext.gregs;
+ out.cs = (gregs[REG_CSGSFS as usize] & 0xffff) as u16;
+
+ out.fs = ((gregs[REG_CSGSFS as usize] >> 32) & 0xffff) as u16;
+ out.gs = ((gregs[REG_CSGSFS as usize] >> 16) & 0xffff) as u16;
+
+ out.eflags = gregs[REG_EFL as usize] as u32;
+
+ out.rax = gregs[REG_RAX as usize] as u64;
+ out.rcx = gregs[REG_RCX as usize] as u64;
+ out.rdx = gregs[REG_RDX as usize] as u64;
+ out.rbx = gregs[REG_RBX as usize] as u64;
+
+ out.rsp = gregs[REG_RSP as usize] as u64;
+ out.rbp = gregs[REG_RBP as usize] as u64;
+ out.rsi = gregs[REG_RSI as usize] as u64;
+ out.rdi = gregs[REG_RDI as usize] as u64;
+ out.r8 = gregs[REG_R8 as usize] as u64;
+ out.r9 = gregs[REG_R9 as usize] as u64;
+ out.r10 = gregs[REG_R10 as usize] as u64;
+ out.r11 = gregs[REG_R11 as usize] as u64;
+ out.r12 = gregs[REG_R12 as usize] as u64;
+ out.r13 = gregs[REG_R13 as usize] as u64;
+ out.r14 = gregs[REG_R14 as usize] as u64;
+ out.r15 = gregs[REG_R15 as usize] as u64;
+
+ out.rip = gregs[REG_RIP as usize] as u64;
+ }
+
+ {
+ let fs = &self.inner.float_state;
+
+ let mut float_save = format::XMM_SAVE_AREA32 {
+ control_word: fs.cwd,
+ status_word: fs.swd,
+ tag_word: fs.ftw as u8,
+ error_opcode: fs.fop,
+ error_offset: fs.rip as u32,
+ data_offset: fs.rdp as u32,
+ error_selector: 0, // We don't have this.
+ data_selector: 0, // We don't have this.
+ mx_csr: fs.mxcsr,
+ mx_csr_mask: fs.mxcr_mask,
+ ..Default::default()
+ };
+
+ copy_u32_registers(&mut float_save.float_registers, &fs.st_space);
+ copy_u32_registers(&mut float_save.xmm_registers, &fs.xmm_space);
+
+ out.float_save
+ .pwrite_with(float_save, 0, scroll::Endian::Little)
+ .expect("this is impossible");
+ }
+ }
+}
diff --git a/third_party/rust/minidump-writer/src/linux/dso_debug.rs b/third_party/rust/minidump-writer/src/linux/dso_debug.rs
new file mode 100644
index 0000000000..b77f241392
--- /dev/null
+++ b/third_party/rust/minidump-writer/src/linux/dso_debug.rs
@@ -0,0 +1,273 @@
+use crate::{
+ linux::{auxv_reader::AuxvType, errors::SectionDsoDebugError, ptrace_dumper::PtraceDumper},
+ mem_writer::{write_string_to_location, Buffer, MemoryArrayWriter, MemoryWriter},
+ minidump_format::*,
+};
+use std::collections::HashMap;
+
+type Result<T> = std::result::Result<T, SectionDsoDebugError>;
+
+cfg_if::cfg_if! {
+ if #[cfg(target_pointer_width = "32")] {
+ use goblin::elf::program_header::program_header32::SIZEOF_PHDR;
+ } else if #[cfg(target_pointer_width = "64")] {
+ use goblin::elf::program_header::program_header64::SIZEOF_PHDR;
+ }
+}
+
+cfg_if::cfg_if! {
+ if #[cfg(all(target_pointer_width = "64", target_arch = "arm"))] {
+ type ElfAddr = u64;
+ } else if #[cfg(all(target_pointer_width = "64", not(target_arch = "arm")))] {
+ type ElfAddr = libc::Elf64_Addr;
+ } else if #[cfg(all(target_pointer_width = "32", target_arch = "arm"))] {
+ type ElfAddr = u32;
+ } else if #[cfg(all(target_pointer_width = "32", not(target_arch = "arm")))] {
+ type ElfAddr = libc::Elf32_Addr;
+ }
+}
+
+// COPY from <link.h>
+#[derive(Debug, Clone, Default)]
+#[repr(C)]
+pub struct LinkMap {
+ /* These first few members are part of the protocol with the debugger.
+ This is the same format used in SVR4. */
+ l_addr: ElfAddr, /* Difference between the address in the ELF
+ file and the addresses in memory. */
+ l_name: usize, /* Absolute file name object was found in. WAS: `char*` */
+ l_ld: usize, /* Dynamic section of the shared object. WAS: `ElfW(Dyn) *` */
+ l_next: usize, /* Chain of loaded objects. WAS: `struct link_map *` */
+ l_prev: usize, /* Chain of loaded objects. WAS: `struct link_map *` */
+}
+
+// COPY from <link.h>
+/// This state value describes the mapping change taking place when
+/// the `r_brk' address is called.
+#[derive(Debug, Clone, Default)]
+#[allow(non_camel_case_types, unused)]
+#[repr(C)]
+enum RState {
+ /// Mapping change is complete.
+ #[default]
+ RT_CONSISTENT,
+ /// Beginning to add a new object.
+ RT_ADD,
+ /// Beginning to remove an object mapping.
+ RT_DELETE,
+}
+
+// COPY from <link.h>
+#[derive(Debug, Clone, Default)]
+#[repr(C)]
+pub struct RDebug {
+ r_version: libc::c_int, /* Version number for this protocol. */
+ r_map: usize, /* Head of the chain of loaded objects. WAS: `struct link_map *` */
+
+ /* This is the address of a function internal to the run-time linker,
+ that will always be called when the linker begins to map in a
+ library or unmap it, and again when the mapping change is complete.
+ The debugger can set a breakpoint at this address if it wants to
+ notice shared object mapping changes. */
+ r_brk: ElfAddr,
+ r_state: RState,
+ r_ldbase: ElfAddr, /* Base address the linker is loaded at. */
+}
+
+pub fn write_dso_debug_stream(
+ buffer: &mut Buffer,
+ blamed_thread: i32,
+ auxv: &HashMap<AuxvType, AuxvType>,
+) -> Result<MDRawDirectory> {
+ let at_phnum;
+ let at_phdr;
+ #[cfg(target_arch = "arm")]
+ {
+ at_phdr = 3;
+ at_phnum = 5;
+ }
+ #[cfg(not(target_arch = "arm"))]
+ {
+ at_phdr = libc::AT_PHDR;
+ at_phnum = libc::AT_PHNUM;
+ }
+ let phnum_max = *auxv
+ .get(&at_phnum)
+ .ok_or(SectionDsoDebugError::CouldNotFind("AT_PHNUM in auxv"))?
+ as usize;
+ let phdr = *auxv
+ .get(&at_phdr)
+ .ok_or(SectionDsoDebugError::CouldNotFind("AT_PHDR in auxv"))? as usize;
+
+ let ph = PtraceDumper::copy_from_process(
+ blamed_thread,
+ phdr as *mut libc::c_void,
+ SIZEOF_PHDR * phnum_max,
+ )?;
+ let program_headers;
+ #[cfg(target_pointer_width = "64")]
+ {
+ program_headers = goblin::elf::program_header::program_header64::ProgramHeader::from_bytes(
+ &ph, phnum_max,
+ );
+ }
+ #[cfg(target_pointer_width = "32")]
+ {
+ program_headers = goblin::elf::program_header::program_header32::ProgramHeader::from_bytes(
+ &ph, phnum_max,
+ );
+ };
+
+ // Assume the program base is at the beginning of the same page as the PHDR
+ let mut base = phdr & !0xfff;
+ let mut dyn_addr = 0;
+ // Search for the program PT_DYNAMIC segment
+ for ph in program_headers {
+ // Adjust base address with the virtual address of the PT_LOAD segment
+ // corresponding to offset 0
+ if ph.p_type == goblin::elf::program_header::PT_LOAD && ph.p_offset == 0 {
+ base -= ph.p_vaddr as usize;
+ }
+ if ph.p_type == goblin::elf::program_header::PT_DYNAMIC {
+ dyn_addr = ph.p_vaddr;
+ }
+ }
+
+ if dyn_addr == 0 {
+ return Err(SectionDsoDebugError::CouldNotFind(
+ "dyn_addr in program headers",
+ ));
+ }
+
+ dyn_addr += base as ElfAddr;
+
+ let dyn_size = std::mem::size_of::<goblin::elf::Dyn>();
+ let mut r_debug = 0usize;
+ let mut dynamic_length = 0usize;
+
+ // The dynamic linker makes information available that helps gdb find all
+ // DSOs loaded into the program. If this information is indeed available,
+ // dump it to a MD_LINUX_DSO_DEBUG stream.
+ loop {
+ let dyn_data = PtraceDumper::copy_from_process(
+ blamed_thread,
+ (dyn_addr as usize + dynamic_length) as *mut libc::c_void,
+ dyn_size,
+ )?;
+ dynamic_length += dyn_size;
+
+ // goblin::elf::Dyn doesn't have padding bytes
+ let (head, body, _tail) = unsafe { dyn_data.align_to::<goblin::elf::Dyn>() };
+ assert!(head.is_empty(), "Data was not aligned");
+ let dyn_struct = &body[0];
+
+ // #ifdef __mips__
+ // const int32_t debug_tag = DT_MIPS_RLD_MAP;
+ // #else
+ // const int32_t debug_tag = DT_DEBUG;
+ // #endif
+ let debug_tag = goblin::elf::dynamic::DT_DEBUG;
+ if dyn_struct.d_tag == debug_tag {
+ r_debug = dyn_struct.d_val as usize;
+ } else if dyn_struct.d_tag == goblin::elf::dynamic::DT_NULL {
+ break;
+ }
+ }
+
+ // The "r_map" field of that r_debug struct contains a linked list of all
+ // loaded DSOs.
+ // Our list of DSOs potentially is different from the ones in the crashing
+ // process. So, we have to be careful to never dereference pointers
+ // directly. Instead, we use CopyFromProcess() everywhere.
+ // See <link.h> for a more detailed discussion of the how the dynamic
+ // loader communicates with debuggers.
+
+ let debug_entry_data = PtraceDumper::copy_from_process(
+ blamed_thread,
+ r_debug as *mut libc::c_void,
+ std::mem::size_of::<RDebug>(),
+ )?;
+
+ // goblin::elf::Dyn doesn't have padding bytes
+ let (head, body, _tail) = unsafe { debug_entry_data.align_to::<RDebug>() };
+ assert!(head.is_empty(), "Data was not aligned");
+ let debug_entry = &body[0];
+
+ // Count the number of loaded DSOs
+ let mut dso_vec = Vec::new();
+ let mut curr_map = debug_entry.r_map;
+ while curr_map != 0 {
+ let link_map_data = PtraceDumper::copy_from_process(
+ blamed_thread,
+ curr_map as *mut libc::c_void,
+ std::mem::size_of::<LinkMap>(),
+ )?;
+
+ // LinkMap is repr(C) and doesn't have padding bytes, so this should be safe
+ let (head, body, _tail) = unsafe { link_map_data.align_to::<LinkMap>() };
+ assert!(head.is_empty(), "Data was not aligned");
+ let map = &body[0];
+
+ curr_map = map.l_next;
+ dso_vec.push(map.clone());
+ }
+
+ let mut linkmap_rva = u32::MAX;
+ if !dso_vec.is_empty() {
+ // If we have at least one DSO, create an array of MDRawLinkMap
+ // entries in the minidump file.
+ let mut linkmap = MemoryArrayWriter::<MDRawLinkMap>::alloc_array(buffer, dso_vec.len())?;
+ linkmap_rva = linkmap.location().rva;
+
+ // Iterate over DSOs and write their information to mini dump
+ for (idx, map) in dso_vec.iter().enumerate() {
+ let mut filename = String::new();
+ if map.l_name > 0 {
+ let filename_data = PtraceDumper::copy_from_process(
+ blamed_thread,
+ map.l_name as *mut libc::c_void,
+ 256,
+ )?;
+
+ // C - string is NULL-terminated
+ if let Some(name) = filename_data.splitn(2, |x| *x == b'\0').next() {
+ filename = String::from_utf8(name.to_vec())?;
+ }
+ }
+ let location = write_string_to_location(buffer, &filename)?;
+ let entry = MDRawLinkMap {
+ addr: map.l_addr,
+ name: location.rva,
+ ld: map.l_ld as ElfAddr,
+ };
+
+ linkmap.set_value_at(buffer, entry, idx)?;
+ }
+ }
+
+ // Write MD_LINUX_DSO_DEBUG record
+ let debug = MDRawDebug {
+ version: debug_entry.r_version as u32,
+ map: linkmap_rva,
+ dso_count: dso_vec.len() as u32,
+ brk: debug_entry.r_brk,
+ ldbase: debug_entry.r_ldbase,
+ dynamic: dyn_addr,
+ };
+ let debug_loc = MemoryWriter::<MDRawDebug>::alloc_with_val(buffer, debug)?;
+
+ let mut dirent = MDRawDirectory {
+ stream_type: MDStreamType::LinuxDsoDebug as u32,
+ location: debug_loc.location(),
+ };
+
+ dirent.location.data_size += dynamic_length as u32;
+ let dso_debug_data = PtraceDumper::copy_from_process(
+ blamed_thread,
+ dyn_addr as *mut libc::c_void,
+ dynamic_length,
+ )?;
+ MemoryArrayWriter::write_bytes(buffer, &dso_debug_data);
+
+ Ok(dirent)
+}
diff --git a/third_party/rust/minidump-writer/src/linux/dumper_cpu_info.rs b/third_party/rust/minidump-writer/src/linux/dumper_cpu_info.rs
new file mode 100644
index 0000000000..72da20fe30
--- /dev/null
+++ b/third_party/rust/minidump-writer/src/linux/dumper_cpu_info.rs
@@ -0,0 +1,72 @@
+cfg_if::cfg_if! {
+ if #[cfg(any(
+ target_arch = "x86_64",
+ target_arch = "x86",
+ target_arch = "mips",
+ target_arch = "mips64"
+ ))]
+ {
+ pub mod x86_mips;
+ pub use x86_mips as imp;
+ } else if #[cfg(any(
+ target_arch = "arm",
+ target_arch = "aarch64",
+ ))]
+ {
+ pub mod arm;
+ pub use arm as imp;
+ }
+}
+
+pub use imp::write_cpu_information;
+
+use crate::minidump_format::PlatformId;
+use nix::sys::utsname::uname;
+
+/// Retrieves the [`MDOSPlatform`] and synthesized version information
+pub fn os_information() -> (PlatformId, String) {
+ let platform_id = if cfg!(target_os = "android") {
+ PlatformId::Android
+ } else {
+ PlatformId::Linux
+ };
+
+ // This is quite unfortunate, but the primary reason that uname could fail
+ // would be if it failed to fill out the nodename (hostname) field, even
+ // though we don't care about that particular field at all
+ let info = uname().map_or_else(
+ |_e| {
+ let os = if platform_id == PlatformId::Linux {
+ "Linux"
+ } else {
+ "Android"
+ };
+
+ let machine = if cfg!(target_arch = "x86_64") {
+ "x86_64"
+ } else if cfg!(target_arch = "x86") {
+ "x86"
+ } else if cfg!(target_arch = "aarch64") {
+ "aarch64"
+ } else if cfg!(target_arch = "arm") {
+ "arm"
+ } else {
+ "<unknown>"
+ };
+
+ // TODO: Fallback to other sources of information, eg /etc/os-release
+ format!("{os} <unknown> <unknown> {machine}")
+ },
+ |info| {
+ format!(
+ "{} {} {} {}",
+ info.sysname().to_str().unwrap_or("<unknown>"),
+ info.release().to_str().unwrap_or("<unknown>"),
+ info.version().to_str().unwrap_or("<unknown>"),
+ info.machine().to_str().unwrap_or("<unknown>"),
+ )
+ },
+ );
+
+ (platform_id, info)
+}
diff --git a/third_party/rust/minidump-writer/src/linux/dumper_cpu_info/arm.rs b/third_party/rust/minidump-writer/src/linux/dumper_cpu_info/arm.rs
new file mode 100644
index 0000000000..886920f560
--- /dev/null
+++ b/third_party/rust/minidump-writer/src/linux/dumper_cpu_info/arm.rs
@@ -0,0 +1,404 @@
+use crate::{errors::CpuInfoError, minidump_format::*};
+use scroll::Pwrite;
+use std::{
+ collections::HashSet,
+ fs::File,
+ io::{BufRead, BufReader, Read},
+ path,
+};
+
+type Result<T> = std::result::Result<T, CpuInfoError>;
+
+pub fn parse_cpus_from_sysfile(file: &mut File) -> Result<HashSet<u32>> {
+ let mut res = HashSet::new();
+ let mut content = String::new();
+ file.read_to_string(&mut content)?;
+ // Expected format: comma-separated list of items, where each
+ // item can be a decimal integer, or two decimal integers separated
+ // by a dash.
+ // E.g.:
+ // 0
+ // 0,1,2,3
+ // 0-3
+ // 1,10-23
+ for items in content.split(',') {
+ let items = items.trim();
+ if items.is_empty() {
+ continue;
+ }
+ let cores: std::result::Result<Vec<_>, _> =
+ items.split('-').map(|x| x.parse::<u32>()).collect();
+ let cores = cores?;
+ match cores.as_slice() {
+ [x] => {
+ res.insert(*x);
+ }
+ [x, y] => {
+ for core in *x..=*y {
+ res.insert(core);
+ }
+ }
+ _ => {
+ return Err(CpuInfoError::UnparsableCores(format!("{:?}", cores)));
+ }
+ }
+ }
+ Ok(res)
+}
+
+struct CpuInfoEntry {
+ field: &'static str,
+ format: char,
+ bit_lshift: u8,
+ bit_length: u8,
+}
+
+impl CpuInfoEntry {
+ fn new(field: &'static str, format: char, bit_lshift: u8, bit_length: u8) -> Self {
+ CpuInfoEntry {
+ field,
+ format,
+ bit_lshift,
+ bit_length,
+ }
+ }
+}
+
+/// Retrieves the hardware capabilities from the 'Features' field.
+#[cfg(target_arch = "arm")]
+fn parse_features(val: &str) -> u32 {
+ struct CpuFeaturesEntry {
+ tag: &'static str,
+ hwcaps: u32,
+ }
+
+ impl CpuFeaturesEntry {
+ fn new(tag: &'static str, hwcaps: u32) -> Self {
+ CpuFeaturesEntry { tag, hwcaps }
+ }
+ }
+
+ // The ELF hwcaps are listed in the "Features" entry as textual tags.
+ // This table is used to rebuild them.
+ let cpu_features_entries = [
+ CpuFeaturesEntry::new("swp", MDCPUInformationARMElfHwCaps::HWCAP_SWP.bits()),
+ CpuFeaturesEntry::new("half", MDCPUInformationARMElfHwCaps::HWCAP_HALF.bits()),
+ CpuFeaturesEntry::new("thumb", MDCPUInformationARMElfHwCaps::HWCAP_THUMB.bits()),
+ CpuFeaturesEntry::new("bit26", MDCPUInformationARMElfHwCaps::HWCAP_26BIT.bits()),
+ CpuFeaturesEntry::new(
+ "fastmult",
+ MDCPUInformationARMElfHwCaps::HWCAP_FAST_MULT.bits(),
+ ),
+ CpuFeaturesEntry::new("fpa", MDCPUInformationARMElfHwCaps::HWCAP_FPA.bits()),
+ CpuFeaturesEntry::new("vfp", MDCPUInformationARMElfHwCaps::HWCAP_VFP.bits()),
+ CpuFeaturesEntry::new("edsp", MDCPUInformationARMElfHwCaps::HWCAP_EDSP.bits()),
+ CpuFeaturesEntry::new("java", MDCPUInformationARMElfHwCaps::HWCAP_JAVA.bits()),
+ CpuFeaturesEntry::new("iwmmxt", MDCPUInformationARMElfHwCaps::HWCAP_IWMMXT.bits()),
+ CpuFeaturesEntry::new("crunch", MDCPUInformationARMElfHwCaps::HWCAP_CRUNCH.bits()),
+ CpuFeaturesEntry::new(
+ "thumbee",
+ MDCPUInformationARMElfHwCaps::HWCAP_THUMBEE.bits(),
+ ),
+ CpuFeaturesEntry::new("neon", MDCPUInformationARMElfHwCaps::HWCAP_NEON.bits()),
+ CpuFeaturesEntry::new("vfpv3", MDCPUInformationARMElfHwCaps::HWCAP_VFPv3.bits()),
+ CpuFeaturesEntry::new(
+ "vfpv3d16",
+ MDCPUInformationARMElfHwCaps::HWCAP_VFPv3D16.bits(),
+ ),
+ CpuFeaturesEntry::new("tls", MDCPUInformationARMElfHwCaps::HWCAP_TLS.bits()),
+ CpuFeaturesEntry::new("vfpv4", MDCPUInformationARMElfHwCaps::HWCAP_VFPv4.bits()),
+ CpuFeaturesEntry::new("idiva", MDCPUInformationARMElfHwCaps::HWCAP_IDIVA.bits()),
+ CpuFeaturesEntry::new("idivt", MDCPUInformationARMElfHwCaps::HWCAP_IDIVT.bits()),
+ CpuFeaturesEntry::new("idiv", MDCPUInformationARMElfHwCaps::HWCAP_IDIV.bits()),
+ ];
+
+ let mut ehwc = 0;
+ // Parse each space-separated tag.
+ for tag in val.split_whitespace() {
+ for entry in &cpu_features_entries {
+ if entry.tag == tag {
+ ehwc |= entry.hwcaps;
+ break;
+ }
+ }
+ }
+
+ ehwc
+}
+
+/// Stub for aarch64, always 0
+#[cfg(target_arch = "aarch64")]
+fn parse_features(_val: &str) -> u32 {
+ 0
+}
+
+pub fn write_cpu_information(sys_info: &mut MDRawSystemInfo) -> Result<()> {
+ // The CPUID value is broken up in several entries in /proc/cpuinfo.
+ // This table is used to rebuild it from the entries.
+ let cpu_id_entries = [
+ CpuInfoEntry::new("CPU implementer", 'x', 24, 8),
+ CpuInfoEntry::new("CPU variant", 'x', 20, 4),
+ CpuInfoEntry::new("CPU part", 'x', 4, 12),
+ CpuInfoEntry::new("CPU revision", 'd', 0, 4),
+ ];
+
+ // processor_architecture should always be set, do this first
+ if cfg!(target_arch = "aarch64") {
+ sys_info.processor_architecture =
+ MDCPUArchitecture::PROCESSOR_ARCHITECTURE_ARM64_OLD as u16;
+ } else {
+ sys_info.processor_architecture = MDCPUArchitecture::PROCESSOR_ARCHITECTURE_ARM as u16;
+ }
+
+ // /proc/cpuinfo is not readable under various sandboxed environments
+ // (e.g. Android services with the android:isolatedProcess attribute)
+ // prepare for this by setting default values now, which will be
+ // returned when this happens.
+ //
+ // Note: Bogus values are used to distinguish between failures (to
+ // read /sys and /proc files) and really badly configured kernels.
+ sys_info.number_of_processors = 0;
+ sys_info.processor_level = 1; // There is no ARMv1
+ sys_info.processor_revision = 42;
+
+ // Counting the number of CPUs involves parsing two sysfs files,
+ // because the content of /proc/cpuinfo will only mirror the number
+ // of 'online' cores, and thus will vary with time.
+ // See http://www.kernel.org/doc/Documentation/cputopology.txt
+ if let Ok(mut present_file) = File::open("/sys/devices/system/cpu/present") {
+ // Ignore unparsable content
+ let cpus_present = parse_cpus_from_sysfile(&mut present_file).unwrap_or_default();
+
+ if let Ok(mut possible_file) = File::open("/sys/devices/system/cpu/possible") {
+ // Ignore unparsable content
+ let cpus_possible = parse_cpus_from_sysfile(&mut possible_file).unwrap_or_default();
+ let intersection = cpus_present.intersection(&cpus_possible).count();
+ let cpu_count = std::cmp::min(255, intersection) as u8;
+ sys_info.number_of_processors = cpu_count;
+ }
+ }
+
+ // Parse /proc/cpuinfo to reconstruct the CPUID value, as well
+ // as the ELF hwcaps field. For the latter, it would be easier to
+ // read /proc/self/auxv but unfortunately, this file is not always
+ // readable from regular Android applications on later versions
+ // (>= 4.1) of the Android platform.
+
+ let cpuinfo_file = match File::open(path::PathBuf::from("/proc/cpuinfo")) {
+ Ok(x) => x,
+ Err(_) => {
+ // Do not return Error here to allow the minidump generation
+ // to happen properly.
+ return Ok(());
+ }
+ };
+
+ let mut cpuid = 0;
+ let mut elf_hwcaps = 0;
+
+ for line in BufReader::new(cpuinfo_file).lines() {
+ let line = line?;
+ // Expected format: <field-name> <space>+ ':' <space> <value>
+ // Note that:
+ // - empty lines happen.
+ // - <field-name> can contain spaces.
+ // - some fields have an empty <value>
+ if line.trim().is_empty() {
+ continue;
+ }
+
+ let (field, value) = if let Some(ind) = line.find(':') {
+ (&line[..ind], Some(&line[ind + 1..]))
+ } else {
+ (line.as_str(), None)
+ };
+
+ if let Some(val) = value {
+ for entry in &cpu_id_entries {
+ if field != entry.field {
+ continue;
+ }
+
+ let rr = if val.starts_with("0x") || entry.format == 'x' {
+ usize::from_str_radix(val.trim_start_matches("0x"), 16)
+ } else {
+ val.parse()
+ };
+
+ if let Ok(mut result) = rr {
+ result &= (1 << entry.bit_length) - 1;
+ result <<= entry.bit_lshift;
+ cpuid |= result as u32;
+ }
+ }
+ }
+
+ if cfg!(target_arch = "arm") {
+ // Get the architecture version from the "Processor" field.
+ // Note that it is also available in the "CPU architecture" field,
+ // however, some existing kernels are misconfigured and will report
+ // invalid values here (e.g. 6, while the CPU is ARMv7-A based).
+ // The "Processor" field doesn't have this issue.
+ if field == "Processor" {
+ // Expected format: <text> (v<level><endian>)
+ // Where <text> is some text like "ARMv7 Processor rev 2"
+ // and <level> is a decimal corresponding to the ARM
+ // architecture number. <endian> is either 'l' or 'b'
+ // and corresponds to the endianess, it is ignored here.
+ if let Some(val) = value.and_then(|v| v.split_whitespace().last()) {
+ // val is now something like "(v7l)"
+ sys_info.processor_level = val[2..val.len() - 2].parse::<u16>().unwrap_or(5);
+ }
+ }
+ } else {
+ // aarch64
+ // The aarch64 architecture does not provide the architecture level
+ // in the Processor field, so we instead check the "CPU architecture"
+ // field.
+ if field == "CPU architecture" {
+ sys_info.processor_level = match value.and_then(|v| v.parse::<u16>().ok()) {
+ Some(v) => v,
+ None => {
+ continue;
+ }
+ };
+ }
+ }
+
+ // Rebuild the ELF hwcaps from the 'Features' field.
+ if field == "Features" {
+ if let Some(val) = value {
+ elf_hwcaps = parse_features(val);
+ }
+ }
+ }
+
+ // The sys_info.cpu field is just a byte array, but in arm's case it is
+ // actually
+ // minidump_common::format::ARMCpuInfo {
+ // pub cpuid: u32,
+ // pub elf_hwcaps: u32,
+ // }
+ sys_info
+ .cpu
+ .data
+ .pwrite_with(cpuid, 0, scroll::Endian::Little)
+ .expect("impossible");
+ sys_info
+ .cpu
+ .data
+ .pwrite_with(
+ elf_hwcaps,
+ std::mem::size_of::<u32>(),
+ scroll::Endian::Little,
+ )
+ .expect("impossible");
+
+ Ok(())
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ // In tests we can have access to std
+ extern crate std;
+ use std::io::Write;
+
+ fn new_file(content: &str) -> File {
+ let mut file = tempfile::Builder::new()
+ .prefix("cpu_sets")
+ .tempfile()
+ .unwrap();
+ write!(file, "{}", content).unwrap();
+ std::fs::File::open(file).unwrap()
+ }
+
+ #[test]
+ fn test_empty_count() {
+ let mut file = new_file("");
+ let set = parse_cpus_from_sysfile(&mut file).expect("Failed to parse empty file");
+ assert_eq!(set.len(), 0);
+ }
+
+ #[test]
+ fn test_one_cpu() {
+ let mut file = new_file("10");
+ let set = parse_cpus_from_sysfile(&mut file).expect("Failed to file");
+ assert_eq!(set, [10,].iter().copied().collect());
+ }
+
+ #[test]
+ fn test_one_cpu_newline() {
+ let mut file = new_file("10\n");
+ let set = parse_cpus_from_sysfile(&mut file).expect("Failed to file");
+ assert_eq!(set, [10,].iter().copied().collect());
+ }
+
+ #[test]
+ fn test_two_cpus() {
+ let mut file = new_file("1,10\n");
+ let set = parse_cpus_from_sysfile(&mut file).expect("Failed to file");
+ assert_eq!(set, [1, 10].iter().copied().collect());
+ }
+
+ #[test]
+ fn test_two_cpus_with_range() {
+ let mut file = new_file("1-2\n");
+ let set = parse_cpus_from_sysfile(&mut file).expect("Failed to file");
+ assert_eq!(set, [1, 2].iter().copied().collect());
+ }
+
+ #[test]
+ fn test_ten_cpus_with_range() {
+ let mut file = new_file("9-18\n");
+ let set = parse_cpus_from_sysfile(&mut file).expect("Failed to file");
+ assert_eq!(set, (9..=18).collect());
+ }
+
+ #[test]
+ fn test_multiple_items() {
+ let mut file = new_file("0, 2-4, 128\n");
+ let set = parse_cpus_from_sysfile(&mut file).expect("Failed to file");
+ assert_eq!(set, [0, 2, 3, 4, 128].iter().copied().collect());
+ }
+
+ #[test]
+ fn test_intersects_with() {
+ let mut file1 = new_file("9-19\n");
+ let mut set1 = parse_cpus_from_sysfile(&mut file1).expect("Failed to file");
+ assert_eq!(set1, (9..=19).collect());
+
+ let mut file2 = new_file("16-24\n");
+ let set2 = parse_cpus_from_sysfile(&mut file2).expect("Failed to file");
+ assert_eq!(set2, (16..=24).collect());
+
+ set1 = set1.intersection(&set2).copied().collect();
+ assert_eq!(set1, (16..=19).collect());
+ }
+
+ #[test]
+ fn test_intersects_with_discontinuous() {
+ let mut file1 = new_file("0, 2-4, 7, 10\n");
+ let mut set1 = parse_cpus_from_sysfile(&mut file1).expect("Failed to file");
+ assert_eq!(set1, [0, 2, 3, 4, 7, 10].iter().copied().collect());
+
+ let mut file2 = new_file("0-2, 5, 8-10\n");
+ let set2 = parse_cpus_from_sysfile(&mut file2).expect("Failed to file");
+ assert_eq!(set2, [0, 1, 2, 5, 8, 9, 10].iter().copied().collect());
+
+ set1 = set1.intersection(&set2).copied().collect();
+ assert_eq!(set1, [0, 2, 10].iter().copied().collect());
+ }
+
+ #[test]
+ fn test_bad_input() {
+ let mut file = new_file("abc\n");
+ let _set = parse_cpus_from_sysfile(&mut file).expect_err("Did not fail to parse");
+ }
+
+ #[test]
+ fn test_bad_input_range() {
+ let mut file = new_file("1-abc\n");
+ let _set = parse_cpus_from_sysfile(&mut file).expect_err("Did not fail to parse");
+ }
+}
diff --git a/third_party/rust/minidump-writer/src/linux/dumper_cpu_info/x86_mips.rs b/third_party/rust/minidump-writer/src/linux/dumper_cpu_info/x86_mips.rs
new file mode 100644
index 0000000000..131df34f94
--- /dev/null
+++ b/third_party/rust/minidump-writer/src/linux/dumper_cpu_info/x86_mips.rs
@@ -0,0 +1,115 @@
+use crate::errors::CpuInfoError;
+use crate::minidump_format::*;
+use std::io::{BufRead, BufReader};
+use std::path;
+
+type Result<T> = std::result::Result<T, CpuInfoError>;
+
+struct CpuInfoEntry {
+ info_name: &'static str,
+ value: i32,
+ found: bool,
+}
+
+impl CpuInfoEntry {
+ fn new(info_name: &'static str, value: i32, found: bool) -> Self {
+ CpuInfoEntry {
+ info_name,
+ value,
+ found,
+ }
+ }
+}
+
+pub fn write_cpu_information(sys_info: &mut MDRawSystemInfo) -> Result<()> {
+ let vendor_id_name = "vendor_id";
+ let mut cpu_info_table = [
+ CpuInfoEntry::new("processor", -1, false),
+ #[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
+ CpuInfoEntry::new("model", 0, false),
+ #[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
+ CpuInfoEntry::new("stepping", 0, false),
+ #[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
+ CpuInfoEntry::new("cpu family", 0, false),
+ ];
+
+ // processor_architecture should always be set, do this first
+ sys_info.processor_architecture = if cfg!(target_arch = "mips") {
+ MDCPUArchitecture::PROCESSOR_ARCHITECTURE_MIPS
+ } else if cfg!(target_arch = "mips64") {
+ MDCPUArchitecture::PROCESSOR_ARCHITECTURE_MIPS64
+ } else if cfg!(target_arch = "x86") {
+ MDCPUArchitecture::PROCESSOR_ARCHITECTURE_INTEL
+ } else {
+ MDCPUArchitecture::PROCESSOR_ARCHITECTURE_AMD64
+ } as u16;
+
+ let cpuinfo_file = std::fs::File::open(path::PathBuf::from("/proc/cpuinfo"))?;
+
+ let mut vendor_id = String::new();
+ for line in BufReader::new(cpuinfo_file).lines() {
+ let line = line?;
+ // Expected format: <field-name> <space>+ ':' <space> <value>
+ // Note that:
+ // - empty lines happen.
+ // - <field-name> can contain spaces.
+ // - some fields have an empty <value>
+ if line.trim().is_empty() {
+ continue;
+ }
+
+ let mut liter = line.split(':').map(|x| x.trim());
+ let field = liter.next().unwrap(); // guaranteed to have at least one item
+ let value = if let Some(val) = liter.next() {
+ val
+ } else {
+ continue;
+ };
+
+ let mut is_first_entry = true;
+ for mut entry in cpu_info_table.iter_mut() {
+ if !is_first_entry && entry.found {
+ // except for the 'processor' field, ignore repeated values.
+ continue;
+ }
+ is_first_entry = false;
+ if field == entry.info_name {
+ if let Ok(v) = value.parse() {
+ entry.value = v;
+ entry.found = true;
+ } else {
+ continue;
+ }
+ }
+
+ // special case for vendor_id
+ if field == vendor_id_name && !value.is_empty() {
+ vendor_id = value.to_owned();
+ }
+ }
+ }
+ // make sure we got everything we wanted
+ if !cpu_info_table.iter().all(|x| x.found) {
+ return Err(CpuInfoError::NotAllProcEntriesFound);
+ }
+ // cpu_info_table[0] holds the last cpu id listed in /proc/cpuinfo,
+ // assuming this is the highest id, change it to the number of CPUs
+ // by adding one.
+ cpu_info_table[0].value += 1;
+
+ sys_info.number_of_processors = cpu_info_table[0].value as u8; // TODO: might not work on special machines with LOTS of CPUs
+ #[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
+ {
+ sys_info.processor_level = cpu_info_table[3].value as u16;
+ sys_info.processor_revision =
+ (cpu_info_table[1].value << 8 | cpu_info_table[2].value) as u16;
+ }
+ if !vendor_id.is_empty() {
+ let vendor_id = vendor_id.as_bytes();
+ // The vendor_id is the first 12 (3 * size_of::<u32>()) bytes
+ let vendor_len = std::cmp::min(3 * std::mem::size_of::<u32>(), vendor_id.len());
+ sys_info.cpu.data[..vendor_len].copy_from_slice(&vendor_id[..vendor_len]);
+ }
+
+ Ok(())
+}
diff --git a/third_party/rust/minidump-writer/src/linux/errors.rs b/third_party/rust/minidump-writer/src/linux/errors.rs
new file mode 100644
index 0000000000..e423389f44
--- /dev/null
+++ b/third_party/rust/minidump-writer/src/linux/errors.rs
@@ -0,0 +1,225 @@
+use crate::dir_section::FileWriterError;
+use crate::maps_reader::MappingInfo;
+use crate::mem_writer::MemoryWriterError;
+use crate::thread_info::Pid;
+use goblin;
+use thiserror::Error;
+
+#[derive(Debug, Error)]
+pub enum InitError {
+ #[error("IO error for file {0}")]
+ IOError(String, #[source] std::io::Error),
+ #[error("No auxv entry found for PID {0}")]
+ NoAuxvEntryFound(Pid),
+ #[error("crash thread does not reference principal mapping")]
+ PrincipalMappingNotReferenced,
+ #[error("Failed Android specific late init")]
+ AndroidLateInitError(#[from] AndroidError),
+}
+
+#[derive(Error, Debug)]
+pub enum MapsReaderError {
+ // parse_from_line()
+ #[error("Map entry malformed: No {0} found")]
+ MapEntryMalformed(&'static str),
+ #[error("Couldn't parse address")]
+ UnparsableInteger(#[from] std::num::ParseIntError),
+ #[error("Linux gate location doesn't fit in the required integer type")]
+ LinuxGateNotConvertable(#[from] std::num::TryFromIntError),
+
+ // get_mmap()
+ #[error("Not safe to open mapping {0}")]
+ NotSafeToOpenMapping(String),
+ #[error("IO Error")]
+ FileError(#[from] std::io::Error),
+ #[error("Mmapped file empty or not an ELF file")]
+ MmapSanityCheckFailed,
+ #[error("Symlink does not match ({0} vs. {1}")]
+ SymlinkError(std::path::PathBuf, std::path::PathBuf),
+
+ // handle_deleted_file_in_mapping()
+ #[error("Couldn't parse as ELF file")]
+ ELFParsingFailed(#[from] goblin::error::Error),
+ #[error("No soname found (filename: {0}")]
+ NoSoName(String),
+}
+
+#[derive(Debug, Error)]
+pub enum AuxvReaderError {
+ #[error("Invalid auxv format (should not hit EOF before AT_NULL)")]
+ InvalidFormat,
+ #[error("IO Error")]
+ IOError(#[from] std::io::Error),
+}
+
+#[derive(Debug, Error)]
+pub enum CpuInfoError {
+ #[error("IO error for file /proc/cpuinfo")]
+ IOError(#[from] std::io::Error),
+ #[error("Not all entries of /proc/cpuinfo found!")]
+ NotAllProcEntriesFound,
+ #[error("Couldn't parse core from file")]
+ UnparsableInteger(#[from] std::num::ParseIntError),
+ #[error("Couldn't parse cores: {0}")]
+ UnparsableCores(String),
+}
+
+#[derive(Error, Debug)]
+pub enum ThreadInfoError {
+ #[error("Index out of bounds: Got {0}, only have {1}")]
+ IndexOutOfBounds(usize, usize),
+ #[error("Either ppid ({1}) or tgid ({2}) not found in {0}")]
+ InvalidPid(String, Pid, Pid),
+ #[error("IO error")]
+ IOError(#[from] std::io::Error),
+ #[error("Couldn't parse address")]
+ UnparsableInteger(#[from] std::num::ParseIntError),
+ #[error("nix::ptrace() error")]
+ PtraceError(#[from] nix::Error),
+ #[error("Invalid line in /proc/{0}/status: {1}")]
+ InvalidProcStatusFile(Pid, String),
+}
+
+#[derive(Debug, Error)]
+pub enum AndroidError {
+ #[error("Failed to copy memory from process")]
+ CopyFromProcessError(#[from] DumperError),
+ #[error("Failed slice conversion")]
+ TryFromSliceError(#[from] std::array::TryFromSliceError),
+ #[error("No Android rel found")]
+ NoRelFound,
+}
+
+#[derive(Debug, Error)]
+pub enum DumperError {
+ #[error("Failed to get PAGE_SIZE from system")]
+ SysConfError(#[from] nix::Error),
+ #[error("wait::waitpid(Pid={0}) failed")]
+ WaitPidError(Pid, #[source] nix::Error),
+ #[error("nix::ptrace::attach(Pid={0}) failed")]
+ PtraceAttachError(Pid, #[source] nix::Error),
+ #[error("nix::ptrace::detach(Pid={0}) failed")]
+ PtraceDetachError(Pid, #[source] nix::Error),
+ #[error("Copy from process {0} failed (source {1}, offset: {2}, length: {3})")]
+ CopyFromProcessError(Pid, usize, usize, usize, #[source] nix::Error),
+ #[error("Skipped thread {0} due to it being part of the seccomp sandbox's trusted code")]
+ DetachSkippedThread(Pid),
+ #[error("No threads left to suspend out of {0}")]
+ SuspendNoThreadsLeft(usize),
+ #[error("No mapping for stack pointer found")]
+ NoStackPointerMapping,
+ #[error("Failed slice conversion")]
+ TryFromSliceError(#[from] std::array::TryFromSliceError),
+ #[error("Couldn't parse as ELF file")]
+ ELFParsingFailed(#[from] goblin::error::Error),
+ #[error("No build-id found")]
+ NoBuildIDFound,
+ #[error("Not safe to open mapping: {0}")]
+ NotSafeToOpenMapping(String),
+ #[error("Failed integer conversion")]
+ TryFromIntError(#[from] std::num::TryFromIntError),
+ #[error("Maps reader error")]
+ MapsReaderError(#[from] MapsReaderError),
+}
+
+#[derive(Debug, Error)]
+pub enum SectionAppMemoryError {
+ #[error("Failed to copy memory from process")]
+ CopyFromProcessError(#[from] DumperError),
+ #[error("Failed to write to memory")]
+ MemoryWriterError(#[from] MemoryWriterError),
+}
+
+#[derive(Debug, Error)]
+pub enum SectionExceptionStreamError {
+ #[error("Failed to write to memory")]
+ MemoryWriterError(#[from] MemoryWriterError),
+}
+
+#[derive(Debug, Error)]
+pub enum SectionMappingsError {
+ #[error("Failed to write to memory")]
+ MemoryWriterError(#[from] MemoryWriterError),
+ #[error("Failed to get effective path of mapping ({0:?})")]
+ GetEffectivePathError(MappingInfo, #[source] MapsReaderError),
+}
+
+#[derive(Debug, Error)]
+pub enum SectionMemListError {
+ #[error("Failed to write to memory")]
+ MemoryWriterError(#[from] MemoryWriterError),
+}
+
+#[derive(Debug, Error)]
+pub enum SectionSystemInfoError {
+ #[error("Failed to write to memory")]
+ MemoryWriterError(#[from] MemoryWriterError),
+ #[error("Failed to get CPU Info")]
+ CpuInfoError(#[from] CpuInfoError),
+}
+
+#[derive(Debug, Error)]
+pub enum SectionThreadListError {
+ #[error("Failed to write to memory")]
+ MemoryWriterError(#[from] MemoryWriterError),
+ #[error("Failed integer conversion")]
+ TryFromIntError(#[from] std::num::TryFromIntError),
+ #[error("Failed to copy memory from process")]
+ CopyFromProcessError(#[from] DumperError),
+ #[error("Failed to get thread info")]
+ ThreadInfoError(#[from] ThreadInfoError),
+ #[error("Failed to write to memory buffer")]
+ IOError(#[from] std::io::Error),
+}
+
+#[derive(Debug, Error)]
+pub enum SectionThreadNamesError {
+ #[error("Failed integer conversion")]
+ TryFromIntError(#[from] std::num::TryFromIntError),
+ #[error("Failed to write to memory")]
+ MemoryWriterError(#[from] MemoryWriterError),
+ #[error("Failed to write to memory buffer")]
+ IOError(#[from] std::io::Error),
+}
+
+#[derive(Debug, Error)]
+pub enum SectionDsoDebugError {
+ #[error("Failed to write to memory")]
+ MemoryWriterError(#[from] MemoryWriterError),
+ #[error("Could not find: {0}")]
+ CouldNotFind(&'static str),
+ #[error("Failed to copy memory from process")]
+ CopyFromProcessError(#[from] DumperError),
+ #[error("Failed to copy memory from process")]
+ FromUTF8Error(#[from] std::string::FromUtf8Error),
+}
+
+#[derive(Debug, Error)]
+pub enum WriterError {
+ #[error("Error during init phase")]
+ InitError(#[from] InitError),
+ #[error(transparent)]
+ DumperError(#[from] DumperError),
+ #[error("Failed when writing section AppMemory")]
+ SectionAppMemoryError(#[from] SectionAppMemoryError),
+ #[error("Failed when writing section ExceptionStream")]
+ SectionExceptionStreamError(#[from] SectionExceptionStreamError),
+ #[error("Failed when writing section MappingsError")]
+ SectionMappingsError(#[from] SectionMappingsError),
+ #[error("Failed when writing section MemList")]
+ SectionMemListError(#[from] SectionMemListError),
+ #[error("Failed when writing section SystemInfo")]
+ SectionSystemInfoError(#[from] SectionSystemInfoError),
+ #[error("Failed when writing section ThreadList")]
+ SectionThreadListError(#[from] SectionThreadListError),
+ #[error("Failed when writing section ThreadNameList")]
+ SectionThreadNamesError(#[from] SectionThreadNamesError),
+ #[error("Failed when writing section DsoDebug")]
+ SectionDsoDebugError(#[from] SectionDsoDebugError),
+ #[error("Failed to write to memory")]
+ MemoryWriterError(#[from] MemoryWriterError),
+ #[error("Failed to write to file")]
+ FileWriterError(#[from] FileWriterError),
+ #[error("Failed to get current timestamp when writing header of minidump")]
+ SystemTimeError(#[from] std::time::SystemTimeError),
+}
diff --git a/third_party/rust/minidump-writer/src/linux/maps_reader.rs b/third_party/rust/minidump-writer/src/linux/maps_reader.rs
new file mode 100644
index 0000000000..8050387aa7
--- /dev/null
+++ b/third_party/rust/minidump-writer/src/linux/maps_reader.rs
@@ -0,0 +1,658 @@
+use crate::auxv_reader::AuxvType;
+use crate::errors::MapsReaderError;
+use crate::thread_info::Pid;
+use byteorder::{NativeEndian, ReadBytesExt};
+use goblin::elf;
+use memmap2::{Mmap, MmapOptions};
+use std::{fs::File, mem::size_of, path::PathBuf};
+
+pub const LINUX_GATE_LIBRARY_NAME: &str = "linux-gate.so";
+pub const DELETED_SUFFIX: &str = " (deleted)";
+pub const RESERVED_FLAGS: &str = "---p";
+
+type Result<T> = std::result::Result<T, MapsReaderError>;
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub struct SystemMappingInfo {
+ pub start_address: usize,
+ pub end_address: usize,
+}
+
+// One of these is produced for each mapping in the process (i.e. line in
+// /proc/$x/maps).
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub struct MappingInfo {
+ // On Android, relocation packing can mean that the reported start
+ // address of the mapping must be adjusted by a bias in order to
+ // compensate for the compression of the relocation section. The
+ // following two members hold (after LateInit) the adjusted mapping
+ // range. See crbug.com/606972 for more information.
+ pub start_address: usize,
+ pub size: usize,
+ // When Android relocation packing causes |start_addr| and |size| to
+ // be modified with a load bias, we need to remember the unbiased
+ // address range. The following structure holds the original mapping
+ // address range as reported by the operating system.
+ pub system_mapping_info: SystemMappingInfo,
+ pub offset: usize, // offset into the backed file.
+ pub executable: bool, // true if the mapping has the execute bit set.
+ pub name: Option<String>,
+ // pub elf_obj: Option<elf::Elf>,
+}
+
+#[derive(Debug)]
+pub struct MappingEntry {
+ pub mapping: MappingInfo,
+ pub identifier: Vec<u8>,
+}
+
+// A list of <MappingInfo, GUID>
+pub type MappingList = Vec<MappingEntry>;
+
+#[derive(Debug)]
+pub enum MappingInfoParsingResult {
+ SkipLine,
+ Success(MappingInfo),
+}
+
+fn is_mapping_a_path(pathname: Option<&str>) -> bool {
+ match pathname {
+ Some(x) => x.contains('/'),
+ None => false,
+ }
+}
+
+impl MappingInfo {
+ pub fn parse_from_line(
+ line: &str,
+ linux_gate_loc: AuxvType,
+ last_mapping: Option<&mut MappingInfo>,
+ ) -> Result<MappingInfoParsingResult> {
+ let mut last_whitespace = false;
+
+ // There is no `line.splitn_whitespace(6)`, so we have to do it somewhat manually
+ // Split at the first whitespace, trim of the rest.
+ let mut splits = line
+ .trim()
+ .splitn(6, |c: char| {
+ if c.is_whitespace() {
+ if last_whitespace {
+ return false;
+ }
+ last_whitespace = true;
+ true
+ } else {
+ last_whitespace = false;
+ false
+ }
+ })
+ .map(str::trim);
+
+ let address = splits
+ .next()
+ .ok_or(MapsReaderError::MapEntryMalformed("address"))?;
+ let perms = splits
+ .next()
+ .ok_or(MapsReaderError::MapEntryMalformed("permissions"))?;
+ let mut offset = usize::from_str_radix(
+ splits
+ .next()
+ .ok_or(MapsReaderError::MapEntryMalformed("offset"))?,
+ 16,
+ )?;
+ let _dev = splits
+ .next()
+ .ok_or(MapsReaderError::MapEntryMalformed("dev"))?;
+ let _inode = splits
+ .next()
+ .ok_or(MapsReaderError::MapEntryMalformed("inode"))?;
+ let mut pathname = splits.next(); // Optional
+
+ // Due to our ugly `splitn_whitespace()` hack from above, we might have
+ // only trailing whitespaces as the name, so we it might still be "Some()"
+ if let Some(x) = pathname {
+ if x.is_empty() {
+ pathname = None;
+ }
+ }
+
+ let mut addresses = address.split('-');
+ let start_address = usize::from_str_radix(addresses.next().unwrap(), 16)?;
+ let end_address = usize::from_str_radix(addresses.next().unwrap(), 16)?;
+
+ let executable = perms.contains('x');
+
+ // Only copy name if the name is a valid path name, or if
+ // it's the VDSO image.
+ let is_path = is_mapping_a_path(pathname);
+
+ if !is_path && linux_gate_loc != 0 && start_address == linux_gate_loc.try_into()? {
+ pathname = Some(LINUX_GATE_LIBRARY_NAME);
+ offset = 0;
+ }
+
+ match (pathname, last_mapping) {
+ (Some(_name), Some(module)) => {
+ // Merge adjacent mappings into one module, assuming they're a single
+ // library mapped by the dynamic linker.
+ if (start_address == module.start_address + module.size)
+ && (pathname == module.name.as_deref())
+ {
+ module.system_mapping_info.end_address = end_address;
+ module.size = end_address - module.start_address;
+ module.executable |= executable;
+ return Ok(MappingInfoParsingResult::SkipLine);
+ }
+ }
+ (None, Some(module)) => {
+ // Also merge mappings that result from address ranges that the
+ // linker reserved but which a loaded library did not use. These
+ // appear as an anonymous private mapping with no access flags set
+ // and which directly follow an executable mapping.
+ let module_end_address = module.start_address + module.size;
+ if (start_address == module_end_address)
+ && module.executable
+ && is_mapping_a_path(module.name.as_deref())
+ && (offset == 0 || offset == module_end_address)
+ && perms == RESERVED_FLAGS
+ {
+ module.size = end_address - module.start_address;
+ return Ok(MappingInfoParsingResult::SkipLine);
+ }
+ }
+ _ => (),
+ }
+
+ let name = pathname.map(ToOwned::to_owned);
+
+ let info = MappingInfo {
+ start_address,
+ size: end_address - start_address,
+ system_mapping_info: SystemMappingInfo {
+ start_address,
+ end_address,
+ },
+ offset,
+ executable,
+ name,
+ // elf_obj,
+ };
+
+ Ok(MappingInfoParsingResult::Success(info))
+ }
+
+ pub fn get_mmap(name: &Option<String>, offset: usize) -> Result<Mmap> {
+ if !MappingInfo::is_mapped_file_safe_to_open(name) {
+ return Err(MapsReaderError::NotSafeToOpenMapping(
+ name.clone().unwrap_or_default(),
+ ));
+ }
+
+ // Not doing this as root_prefix is always "" at the moment
+ // if (!dumper.GetMappingAbsolutePath(mapping, filename))
+ let filename = name.clone().unwrap_or_default();
+ let mapped_file = unsafe {
+ MmapOptions::new()
+ .offset(offset.try_into()?) // try_into() to work for both 32 and 64 bit
+ .map(&File::open(filename)?)?
+ };
+
+ if mapped_file.is_empty() || mapped_file.len() < elf::header::SELFMAG {
+ return Err(MapsReaderError::MmapSanityCheckFailed);
+ }
+ Ok(mapped_file)
+ }
+
+ pub fn handle_deleted_file_in_mapping(path: &str, pid: Pid) -> Result<String> {
+ // Check for ' (deleted)' in |path|.
+ // |path| has to be at least as long as "/x (deleted)".
+ if !path.ends_with(DELETED_SUFFIX) {
+ return Ok(path.to_string());
+ }
+
+ // Check |path| against the /proc/pid/exe 'symlink'.
+ let exe_link = format!("/proc/{}/exe", pid);
+ let link_path = std::fs::read_link(&exe_link)?;
+
+ // This is a no-op for now (until we want to support root_prefix for chroot-envs)
+ // if (!GetMappingAbsolutePath(new_mapping, new_path))
+ // return false;
+
+ if link_path != PathBuf::from(path) {
+ return Err(MapsReaderError::SymlinkError(
+ PathBuf::from(path),
+ link_path,
+ ));
+ }
+
+ // Check to see if someone actually named their executable 'foo (deleted)'.
+
+ // This makes currently no sense, as exe_link == new_path
+ // if let (Some(exe_stat), Some(new_path_stat)) = (nix::stat::stat(exe_link), nix::stat::stat(new_path)) {
+ // if exe_stat.st_dev == new_path_stat.st_dev && exe_stat.st_ino == new_path_stat.st_ino {
+ // return Err("".into());
+ // }
+ // }
+ Ok(exe_link)
+ }
+
+ pub fn stack_has_pointer_to_mapping(&self, stack_copy: &[u8], sp_offset: usize) -> bool {
+ // Loop over all stack words that would have been on the stack in
+ // the target process (i.e. are word aligned, and at addresses >=
+ // the stack pointer). Regardless of the alignment of |stack_copy|,
+ // the memory starting at |stack_copy| + |offset| represents an
+ // aligned word in the target process.
+ let low_addr = self.system_mapping_info.start_address;
+ let high_addr = self.system_mapping_info.end_address;
+ let mut offset = (sp_offset + size_of::<usize>() - 1) & !(size_of::<usize>() - 1);
+ while offset <= stack_copy.len() - size_of::<usize>() {
+ let addr = match std::mem::size_of::<usize>() {
+ 4 => stack_copy[offset..]
+ .as_ref()
+ .read_u32::<NativeEndian>()
+ .map(|u| u as usize),
+ 8 => stack_copy[offset..]
+ .as_ref()
+ .read_u64::<NativeEndian>()
+ .map(|u| u as usize),
+ x => panic!("Unexpected type width: {}", x),
+ };
+ if let Ok(addr) = addr {
+ if low_addr <= addr && addr <= high_addr {
+ return true;
+ }
+ offset += size_of::<usize>();
+ } else {
+ break;
+ }
+ }
+ false
+ }
+
+ pub fn is_mapped_file_safe_to_open(name: &Option<String>) -> bool {
+ // It is unsafe to attempt to open a mapped file that lives under /dev,
+ // because the semantics of the open may be driver-specific so we'd risk
+ // hanging the crash dumper. And a file in /dev/ almost certainly has no
+ // ELF file identifier anyways.
+ if let Some(name) = name {
+ if name.starts_with("/dev/") {
+ return false;
+ }
+ }
+ true
+ }
+
+ fn elf_file_so_name(&self) -> Result<String> {
+ // Find the shared object name (SONAME) by examining the ELF information
+ // for |mapping|. If the SONAME is found copy it into the passed buffer
+ // |soname| and return true. The size of the buffer is |soname_size|.
+ let mapped_file = MappingInfo::get_mmap(&self.name, self.offset)?;
+
+ let elf_obj = elf::Elf::parse(&mapped_file)?;
+
+ let soname = elf_obj.soname.ok_or_else(|| {
+ MapsReaderError::NoSoName(self.name.clone().unwrap_or_else(|| "None".to_string()))
+ })?;
+ Ok(soname.to_string())
+ }
+
+ pub fn get_mapping_effective_name_and_path(&self) -> Result<(String, String)> {
+ let mut file_path = self.name.clone().unwrap_or_default();
+
+ // Tools such as minidump_stackwalk use the name of the module to look up
+ // symbols produced by dump_syms. dump_syms will prefer to use a module's
+ // DT_SONAME as the module name, if one exists, and will fall back to the
+ // filesystem name of the module.
+
+ // Just use the filesystem name if no SONAME is present.
+ let file_name = if let Ok(name) = self.elf_file_so_name() {
+ name
+ } else {
+ // file_path := /path/to/libname.so
+ // file_name := libname.so
+ // SAFETY: The unwrap is safe as rsplit always returns at least one item
+ let file_name = file_path.rsplit('/').next().unwrap().to_owned();
+ return Ok((file_path, file_name));
+ };
+
+ if self.executable && self.offset != 0 {
+ // If an executable is mapped from a non-zero offset, this is likely because
+ // the executable was loaded directly from inside an archive file (e.g., an
+ // apk on Android).
+ // In this case, we append the file_name to the mapped archive path:
+ // file_name := libname.so
+ // file_path := /path/to/ARCHIVE.APK/libname.so
+ file_path = format!("{}/{}", file_path, file_name);
+ } else {
+ // Otherwise, replace the basename with the SONAME.
+ let split: Vec<_> = file_path.rsplitn(2, '/').collect();
+ if split.len() == 2 {
+ // NOTE: rsplitn reverses the order, so the remainder is the last item
+ file_path = format!("{}/{}", split[1], file_name);
+ } else {
+ file_path = file_name.clone();
+ }
+ }
+
+ Ok((file_path, file_name))
+ }
+
+ pub fn is_contained_in(&self, user_mapping_list: &MappingList) -> bool {
+ for user in user_mapping_list {
+ // Ignore any mappings that are wholly contained within
+ // mappings in the mapping_info_ list.
+ if self.start_address >= user.mapping.start_address
+ && (self.start_address + self.size)
+ <= (user.mapping.start_address + user.mapping.size)
+ {
+ return true;
+ }
+ }
+ false
+ }
+
+ pub fn is_interesting(&self) -> bool {
+ // only want modules with filenames.
+ self.name.is_some() &&
+ // Only want to include one mapping per shared lib.
+ // Avoid filtering executable mappings.
+ (self.offset == 0 || self.executable) &&
+ // big enough to get a signature for.
+ self.size >= 4096
+ }
+
+ pub fn contains_address(&self, address: usize) -> bool {
+ self.system_mapping_info.start_address <= address
+ && address < self.system_mapping_info.end_address
+ }
+}
+
+#[cfg(test)]
+#[cfg(target_pointer_width = "64")] // All addresses are 64 bit and I'm currently too lazy to adjust it to work for both
+mod tests {
+ use super::*;
+
+ fn get_lines_and_loc() -> (Vec<&'static str>, u64) {
+ (vec![
+"5597483fc000-5597483fe000 r--p 00000000 00:31 4750073 /usr/bin/cat",
+"5597483fe000-559748402000 r-xp 00002000 00:31 4750073 /usr/bin/cat",
+"559748402000-559748404000 r--p 00006000 00:31 4750073 /usr/bin/cat",
+"559748404000-559748405000 r--p 00007000 00:31 4750073 /usr/bin/cat",
+"559748405000-559748406000 rw-p 00008000 00:31 4750073 /usr/bin/cat",
+"559749b0e000-559749b2f000 rw-p 00000000 00:00 0 [heap]",
+"7efd968d3000-7efd968f5000 rw-p 00000000 00:00 0",
+"7efd968f5000-7efd9694a000 r--p 00000000 00:31 5004638 /usr/lib/locale/en_US.utf8/LC_CTYPE",
+"7efd9694a000-7efd96bc2000 r--p 00000000 00:31 5004373 /usr/lib/locale/en_US.utf8/LC_COLLATE",
+"7efd96bc2000-7efd96bc4000 rw-p 00000000 00:00 0",
+"7efd96bc4000-7efd96bea000 r--p 00000000 00:31 4996104 /lib64/libc-2.32.so",
+"7efd96bea000-7efd96d39000 r-xp 00026000 00:31 4996104 /lib64/libc-2.32.so",
+"7efd96d39000-7efd96d85000 r--p 00175000 00:31 4996104 /lib64/libc-2.32.so",
+"7efd96d85000-7efd96d86000 ---p 001c1000 00:31 4996104 /lib64/libc-2.32.so",
+"7efd96d86000-7efd96d89000 r--p 001c1000 00:31 4996104 /lib64/libc-2.32.so",
+"7efd96d89000-7efd96d8c000 rw-p 001c4000 00:31 4996104 /lib64/libc-2.32.so",
+"7efd96d8c000-7efd96d92000 ---p 00000000 00:00 0",
+"7efd96da0000-7efd96da1000 r--p 00000000 00:31 5004379 /usr/lib/locale/en_US.utf8/LC_NUMERIC",
+"7efd96da1000-7efd96da2000 r--p 00000000 00:31 5004382 /usr/lib/locale/en_US.utf8/LC_TIME",
+"7efd96da2000-7efd96da3000 r--p 00000000 00:31 5004377 /usr/lib/locale/en_US.utf8/LC_MONETARY",
+"7efd96da3000-7efd96da4000 r--p 00000000 00:31 5004376 /usr/lib/locale/en_US.utf8/LC_MESSAGES/SYS_LC_MESSAGES",
+"7efd96da4000-7efd96da5000 r--p 00000000 00:31 5004380 /usr/lib/locale/en_US.utf8/LC_PAPER",
+"7efd96da5000-7efd96da6000 r--p 00000000 00:31 5004378 /usr/lib/locale/en_US.utf8/LC_NAME",
+"7efd96da6000-7efd96da7000 r--p 00000000 00:31 5004372 /usr/lib/locale/en_US.utf8/LC_ADDRESS",
+"7efd96da7000-7efd96da8000 r--p 00000000 00:31 5004381 /usr/lib/locale/en_US.utf8/LC_TELEPHONE",
+"7efd96da8000-7efd96da9000 r--p 00000000 00:31 5004375 /usr/lib/locale/en_US.utf8/LC_MEASUREMENT",
+"7efd96da9000-7efd96db0000 r--s 00000000 00:31 5004639 /usr/lib64/gconv/gconv-modules.cache",
+"7efd96db0000-7efd96db1000 r--p 00000000 00:31 5004374 /usr/lib/locale/en_US.utf8/LC_IDENTIFICATION",
+"7efd96db1000-7efd96db2000 r--p 00000000 00:31 4996100 /lib64/ld-2.32.so",
+"7efd96db2000-7efd96dd3000 r-xp 00001000 00:31 4996100 /lib64/ld-2.32.so",
+"7efd96dd3000-7efd96ddc000 r--p 00022000 00:31 4996100 /lib64/ld-2.32.so",
+"7efd96ddc000-7efd96ddd000 r--p 0002a000 00:31 4996100 /lib64/ld-2.32.so",
+"7efd96ddd000-7efd96ddf000 rw-p 0002b000 00:31 4996100 /lib64/ld-2.32.so",
+"7ffc6dfda000-7ffc6dffb000 rw-p 00000000 00:00 0 [stack]",
+"7ffc6e0f3000-7ffc6e0f7000 r--p 00000000 00:00 0 [vvar]",
+"7ffc6e0f7000-7ffc6e0f9000 r-xp 00000000 00:00 0 [vdso]",
+"ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall]"
+ ], 0x7ffc6e0f7000)
+ }
+
+ fn get_all_mappings() -> Vec<MappingInfo> {
+ let mut mappings: Vec<MappingInfo> = Vec::new();
+ let (lines, linux_gate_loc) = get_lines_and_loc();
+ // Only /usr/bin/cat and [heap]
+ for line in lines {
+ match MappingInfo::parse_from_line(line, linux_gate_loc, mappings.last_mut())
+ .expect("failed to read mapping info")
+ {
+ MappingInfoParsingResult::Success(map) => mappings.push(map),
+ MappingInfoParsingResult::SkipLine => continue,
+ }
+ }
+ assert_eq!(mappings.len(), 23);
+ mappings
+ }
+
+ #[test]
+ fn test_merged() {
+ let mut mappings: Vec<MappingInfo> = Vec::new();
+ let (lines, linux_gate_loc) = get_lines_and_loc();
+ // Only /usr/bin/cat and [heap]
+ for line in lines[0..=6].iter() {
+ match MappingInfo::parse_from_line(line, linux_gate_loc, mappings.last_mut())
+ .expect("failed to read mapping info")
+ {
+ MappingInfoParsingResult::Success(map) => mappings.push(map),
+ MappingInfoParsingResult::SkipLine => continue,
+ }
+ }
+
+ assert_eq!(mappings.len(), 3);
+ let cat_map = MappingInfo {
+ start_address: 0x5597483fc000,
+ size: 40960,
+ system_mapping_info: SystemMappingInfo {
+ start_address: 0x5597483fc000,
+ end_address: 0x559748406000,
+ },
+ offset: 0,
+ executable: true,
+ name: Some("/usr/bin/cat".to_string()),
+ };
+
+ assert_eq!(mappings[0], cat_map);
+
+ let heap_map = MappingInfo {
+ start_address: 0x559749b0e000,
+ size: 135168,
+ system_mapping_info: SystemMappingInfo {
+ start_address: 0x559749b0e000,
+ end_address: 0x559749b2f000,
+ },
+ offset: 0,
+ executable: false,
+ name: Some("[heap]".to_string()),
+ };
+
+ assert_eq!(mappings[1], heap_map);
+
+ let empty_map = MappingInfo {
+ start_address: 0x7efd968d3000,
+ size: 139264,
+ system_mapping_info: SystemMappingInfo {
+ start_address: 0x7efd968d3000,
+ end_address: 0x7efd968f5000,
+ },
+ offset: 0,
+ executable: false,
+ name: None,
+ };
+
+ assert_eq!(mappings[2], empty_map);
+ }
+
+ #[test]
+ fn test_linux_gate_parsing() {
+ let mappings = get_all_mappings();
+
+ let gate_map = MappingInfo {
+ start_address: 0x7ffc6e0f7000,
+ size: 8192,
+ system_mapping_info: SystemMappingInfo {
+ start_address: 0x7ffc6e0f7000,
+ end_address: 0x7ffc6e0f9000,
+ },
+ offset: 0,
+ executable: true,
+ name: Some("linux-gate.so".to_string()),
+ };
+
+ assert_eq!(mappings[21], gate_map);
+ }
+
+ #[test]
+ fn test_reading_all() {
+ let mappings = get_all_mappings();
+
+ let found_items = vec![
+ Some("/usr/bin/cat".to_string()),
+ Some("[heap]".to_string()),
+ None,
+ Some("/usr/lib/locale/en_US.utf8/LC_CTYPE".to_string()),
+ Some("/usr/lib/locale/en_US.utf8/LC_COLLATE".to_string()),
+ None,
+ Some("/lib64/libc-2.32.so".to_string()),
+ // The original shows a None here, but this is an address ranges that the
+ // linker reserved but which a loaded library did not use. These
+ // appear as an anonymous private mapping with no access flags set
+ // and which directly follow an executable mapping.
+ Some("/usr/lib/locale/en_US.utf8/LC_NUMERIC".to_string()),
+ Some("/usr/lib/locale/en_US.utf8/LC_TIME".to_string()),
+ Some("/usr/lib/locale/en_US.utf8/LC_MONETARY".to_string()),
+ Some("/usr/lib/locale/en_US.utf8/LC_MESSAGES/SYS_LC_MESSAGES".to_string()),
+ Some("/usr/lib/locale/en_US.utf8/LC_PAPER".to_string()),
+ Some("/usr/lib/locale/en_US.utf8/LC_NAME".to_string()),
+ Some("/usr/lib/locale/en_US.utf8/LC_ADDRESS".to_string()),
+ Some("/usr/lib/locale/en_US.utf8/LC_TELEPHONE".to_string()),
+ Some("/usr/lib/locale/en_US.utf8/LC_MEASUREMENT".to_string()),
+ Some("/usr/lib64/gconv/gconv-modules.cache".to_string()),
+ Some("/usr/lib/locale/en_US.utf8/LC_IDENTIFICATION".to_string()),
+ Some("/lib64/ld-2.32.so".to_string()),
+ Some("[stack]".to_string()),
+ Some("[vvar]".to_string()),
+ // This is rewritten from [vdso] to linux-gate.so
+ Some("linux-gate.so".to_string()),
+ Some("[vsyscall]".to_string()),
+ ];
+
+ assert_eq!(
+ mappings.iter().map(|x| x.name.clone()).collect::<Vec<_>>(),
+ found_items
+ );
+ }
+
+ #[test]
+ fn test_merged_reserved_mappings() {
+ let mappings = get_all_mappings();
+
+ let gate_map = MappingInfo {
+ start_address: 0x7efd96bc4000,
+ size: 1892352, // Merged the anonymous area after in this mapping, so its bigger..
+ system_mapping_info: SystemMappingInfo {
+ start_address: 0x7efd96bc4000,
+ end_address: 0x7efd96d8c000, // ..but this is not visible here
+ },
+ offset: 0,
+ executable: true,
+ name: Some("/lib64/libc-2.32.so".to_string()),
+ };
+
+ assert_eq!(mappings[6], gate_map);
+ }
+
+ #[test]
+ fn test_get_mapping_effective_name() {
+ let lines = vec![
+"7f0b97b6f000-7f0b97b70000 r--p 00000000 00:3e 27136458 /home/martin/Documents/mozilla/devel/mozilla-central/obj/widget/gtk/mozgtk/gtk3/libmozgtk.so",
+"7f0b97b70000-7f0b97b71000 r-xp 00000000 00:3e 27136458 /home/martin/Documents/mozilla/devel/mozilla-central/obj/widget/gtk/mozgtk/gtk3/libmozgtk.so",
+"7f0b97b71000-7f0b97b73000 r--p 00000000 00:3e 27136458 /home/martin/Documents/mozilla/devel/mozilla-central/obj/widget/gtk/mozgtk/gtk3/libmozgtk.so",
+"7f0b97b73000-7f0b97b74000 rw-p 00001000 00:3e 27136458 /home/martin/Documents/mozilla/devel/mozilla-central/obj/widget/gtk/mozgtk/gtk3/libmozgtk.so",
+ ];
+ let linux_gate_loc = 0x7ffe091bf000;
+ let mut mappings: Vec<MappingInfo> = Vec::new();
+ for line in lines {
+ match MappingInfo::parse_from_line(line, linux_gate_loc, mappings.last_mut())
+ .expect("failed to read mapping info")
+ {
+ MappingInfoParsingResult::Success(map) => mappings.push(map),
+ MappingInfoParsingResult::SkipLine => continue,
+ }
+ }
+ assert_eq!(mappings.len(), 1);
+
+ let (file_path, file_name) = mappings[0]
+ .get_mapping_effective_name_and_path()
+ .expect("Couldn't get effective name for mapping");
+ assert_eq!(file_name, "libmozgtk.so");
+ assert_eq!(file_path, "/home/martin/Documents/mozilla/devel/mozilla-central/obj/widget/gtk/mozgtk/gtk3/libmozgtk.so");
+ }
+
+ #[test]
+ fn test_whitespaces_in_maps() {
+ let lines = vec![
+" 7f0b97b6f000-7f0b97b70000 r--p 00000000 00:3e 27136458 libmozgtk.so",
+"7f0b97b70000-7f0b97b71000 r-xp 00000000 00:3e 27136458 libmozgtk.so ",
+"7f0b97b71000-7f0b97b73000 r--p 00000000 00:3e 27136458\t\t\tlibmozgtk.so",
+ ];
+ let linux_gate_loc = 0x7ffe091bf000;
+ let mut mappings: Vec<MappingInfo> = Vec::new();
+ for line in lines {
+ match MappingInfo::parse_from_line(line, linux_gate_loc, mappings.last_mut())
+ .expect("failed to read mapping info")
+ {
+ MappingInfoParsingResult::Success(map) => mappings.push(map),
+ MappingInfoParsingResult::SkipLine => continue,
+ }
+ }
+ assert_eq!(mappings.len(), 1);
+
+ let expected_map = MappingInfo {
+ start_address: 0x7f0b97b6f000,
+ size: 16384,
+ system_mapping_info: SystemMappingInfo {
+ start_address: 0x7f0b97b6f000,
+ end_address: 0x7f0b97b73000,
+ },
+ offset: 0,
+ executable: true,
+ name: Some("libmozgtk.so".to_string()),
+ };
+
+ assert_eq!(expected_map, mappings[0]);
+ }
+
+ #[test]
+ fn test_whitespaces_in_name() {
+ let lines = vec![
+"10000000-20000000 r--p 00000000 00:3e 27136458 libmoz gtk.so",
+"20000000-30000000 r--p 00000000 00:3e 27136458 libmozgtk.so (deleted)",
+"30000000-40000000 r--p 00000000 00:3e 27136458 \"libmoz gtk.so (deleted)\"",
+"30000000-40000000 r--p 00000000 00:3e 27136458 ",
+ ];
+ let linux_gate_loc = 0x7ffe091bf000;
+ let mut mappings: Vec<MappingInfo> = Vec::new();
+ for line in lines {
+ match MappingInfo::parse_from_line(line, linux_gate_loc, mappings.last_mut())
+ .expect("failed to read mapping info")
+ {
+ MappingInfoParsingResult::Success(map) => mappings.push(map),
+ MappingInfoParsingResult::SkipLine => continue,
+ }
+ }
+ assert_eq!(mappings.len(), 4);
+ assert_eq!(mappings[0].name, Some("libmoz gtk.so".to_string()));
+ assert_eq!(mappings[1].name, Some("libmozgtk.so (deleted)".to_string()));
+ assert_eq!(
+ mappings[2].name,
+ Some("\"libmoz gtk.so (deleted)\"".to_string())
+ );
+ assert_eq!(mappings[3].name, None);
+ }
+}
diff --git a/third_party/rust/minidump-writer/src/linux/minidump_writer.rs b/third_party/rust/minidump-writer/src/linux/minidump_writer.rs
new file mode 100644
index 0000000000..606a85907b
--- /dev/null
+++ b/third_party/rust/minidump-writer/src/linux/minidump_writer.rs
@@ -0,0 +1,341 @@
+use crate::{
+ dir_section::{DirSection, DumpBuf},
+ linux::{
+ app_memory::AppMemoryList,
+ crash_context::CrashContext,
+ dso_debug,
+ errors::{InitError, WriterError},
+ maps_reader::{MappingInfo, MappingList},
+ ptrace_dumper::PtraceDumper,
+ sections::*,
+ thread_info::Pid,
+ },
+ mem_writer::{Buffer, MemoryArrayWriter, MemoryWriter, MemoryWriterError},
+ minidump_format::*,
+};
+use std::io::{Seek, Write};
+
+pub enum CrashingThreadContext {
+ None,
+ CrashContext(MDLocationDescriptor),
+ CrashContextPlusAddress((MDLocationDescriptor, usize)),
+}
+
+pub struct MinidumpWriter {
+ pub process_id: Pid,
+ pub blamed_thread: Pid,
+ pub minidump_size_limit: Option<u64>,
+ pub skip_stacks_if_mapping_unreferenced: bool,
+ pub principal_mapping_address: Option<usize>,
+ pub user_mapping_list: MappingList,
+ pub app_memory: AppMemoryList,
+ pub memory_blocks: Vec<MDMemoryDescriptor>,
+ pub principal_mapping: Option<MappingInfo>,
+ pub sanitize_stack: bool,
+ pub crash_context: Option<CrashContext>,
+ pub crashing_thread_context: CrashingThreadContext,
+}
+
+// This doesn't work yet:
+// https://github.com/rust-lang/rust/issues/43408
+// fn write<T: Sized, P: AsRef<Path>>(path: P, value: T) -> Result<()> {
+// let mut file = std::fs::File::open(path)?;
+// let bytes: [u8; size_of::<T>()] = unsafe { transmute(value) };
+// file.write_all(&bytes)?;
+// Ok(())
+// }
+
+type Result<T> = std::result::Result<T, WriterError>;
+
+impl MinidumpWriter {
+ pub fn new(process: Pid, blamed_thread: Pid) -> Self {
+ Self {
+ process_id: process,
+ blamed_thread,
+ minidump_size_limit: None,
+ skip_stacks_if_mapping_unreferenced: false,
+ principal_mapping_address: None,
+ user_mapping_list: MappingList::new(),
+ app_memory: AppMemoryList::new(),
+ memory_blocks: Vec::new(),
+ principal_mapping: None,
+ sanitize_stack: false,
+ crash_context: None,
+ crashing_thread_context: CrashingThreadContext::None,
+ }
+ }
+
+ pub fn set_minidump_size_limit(&mut self, limit: u64) -> &mut Self {
+ self.minidump_size_limit = Some(limit);
+ self
+ }
+
+ pub fn set_user_mapping_list(&mut self, user_mapping_list: MappingList) -> &mut Self {
+ self.user_mapping_list = user_mapping_list;
+ self
+ }
+
+ pub fn set_principal_mapping_address(&mut self, principal_mapping_address: usize) -> &mut Self {
+ self.principal_mapping_address = Some(principal_mapping_address);
+ self
+ }
+
+ pub fn set_app_memory(&mut self, app_memory: AppMemoryList) -> &mut Self {
+ self.app_memory = app_memory;
+ self
+ }
+
+ pub fn set_crash_context(&mut self, crash_context: CrashContext) -> &mut Self {
+ self.crash_context = Some(crash_context);
+ self
+ }
+
+ pub fn skip_stacks_if_mapping_unreferenced(&mut self) -> &mut Self {
+ self.skip_stacks_if_mapping_unreferenced = true; // Off by default
+ self
+ }
+
+ pub fn sanitize_stack(&mut self) -> &mut Self {
+ self.sanitize_stack = true; // Off by default
+ self
+ }
+
+ /// Generates a minidump and writes to the destination provided. Returns the in-memory
+ /// version of the minidump as well.
+ pub fn dump(&mut self, destination: &mut (impl Write + Seek)) -> Result<Vec<u8>> {
+ let mut dumper = PtraceDumper::new(self.process_id)?;
+ dumper.suspend_threads()?;
+ dumper.late_init()?;
+
+ if self.skip_stacks_if_mapping_unreferenced {
+ if let Some(address) = self.principal_mapping_address {
+ self.principal_mapping = dumper.find_mapping_no_bias(address).cloned();
+ }
+
+ if !self.crash_thread_references_principal_mapping(&dumper) {
+ return Err(InitError::PrincipalMappingNotReferenced.into());
+ }
+ }
+
+ let mut buffer = Buffer::with_capacity(0);
+ self.generate_dump(&mut buffer, &mut dumper, destination)?;
+
+ // dumper would resume threads in drop() automatically,
+ // but in case there is an error, we want to catch it
+ dumper.resume_threads()?;
+
+ Ok(buffer.into())
+ }
+
+ fn crash_thread_references_principal_mapping(&self, dumper: &PtraceDumper) -> bool {
+ if self.crash_context.is_none() || self.principal_mapping.is_none() {
+ return false;
+ }
+
+ let low_addr = self
+ .principal_mapping
+ .as_ref()
+ .unwrap()
+ .system_mapping_info
+ .start_address;
+ let high_addr = self
+ .principal_mapping
+ .as_ref()
+ .unwrap()
+ .system_mapping_info
+ .end_address;
+
+ let pc = self
+ .crash_context
+ .as_ref()
+ .unwrap()
+ .get_instruction_pointer();
+ let stack_pointer = self.crash_context.as_ref().unwrap().get_stack_pointer();
+
+ if pc >= low_addr && pc < high_addr {
+ return true;
+ }
+
+ let (stack_ptr, stack_len) = match dumper.get_stack_info(stack_pointer) {
+ Ok(x) => x,
+ Err(_) => {
+ return false;
+ }
+ };
+ let stack_copy = match PtraceDumper::copy_from_process(
+ self.blamed_thread,
+ stack_ptr as *mut libc::c_void,
+ stack_len,
+ ) {
+ Ok(x) => x,
+ Err(_) => {
+ return false;
+ }
+ };
+
+ let sp_offset = stack_pointer - stack_ptr;
+ self.principal_mapping
+ .as_ref()
+ .unwrap()
+ .stack_has_pointer_to_mapping(&stack_copy, sp_offset)
+ }
+
+ fn generate_dump(
+ &mut self,
+ buffer: &mut DumpBuf,
+ dumper: &mut PtraceDumper,
+ destination: &mut (impl Write + Seek),
+ ) -> Result<()> {
+ // A minidump file contains a number of tagged streams. This is the number
+ // of stream which we write.
+ let num_writers = 14u32;
+
+ let mut header_section = MemoryWriter::<MDRawHeader>::alloc(buffer)?;
+
+ let mut dir_section = DirSection::new(buffer, num_writers, destination)?;
+
+ let header = MDRawHeader {
+ signature: MD_HEADER_SIGNATURE,
+ version: MD_HEADER_VERSION,
+ stream_count: num_writers,
+ // header.get()->stream_directory_rva = dir.position();
+ stream_directory_rva: dir_section.position(),
+ checksum: 0, /* Can be 0. In fact, that's all that's
+ * been found in minidump files. */
+ time_date_stamp: std::time::SystemTime::now()
+ .duration_since(std::time::UNIX_EPOCH)?
+ .as_secs() as u32, // TODO: This is not Y2038 safe, but thats how its currently defined as
+ flags: 0,
+ };
+ header_section.set_value(buffer, header)?;
+
+ // Ensure the header gets flushed. If we crash somewhere below,
+ // we should have a mostly-intact dump
+ dir_section.write_to_file(buffer, None)?;
+
+ let dirent = thread_list_stream::write(self, buffer, dumper)?;
+ // Write section to file
+ dir_section.write_to_file(buffer, Some(dirent))?;
+
+ let dirent = mappings::write(self, buffer, dumper)?;
+ // Write section to file
+ dir_section.write_to_file(buffer, Some(dirent))?;
+
+ app_memory::write(self, buffer)?;
+ // Write section to file
+ dir_section.write_to_file(buffer, None)?;
+
+ let dirent = memory_list_stream::write(self, buffer)?;
+ // Write section to file
+ dir_section.write_to_file(buffer, Some(dirent))?;
+
+ let dirent = exception_stream::write(self, buffer)?;
+ // Write section to file
+ dir_section.write_to_file(buffer, Some(dirent))?;
+
+ let dirent = systeminfo_stream::write(buffer)?;
+ // Write section to file
+ dir_section.write_to_file(buffer, Some(dirent))?;
+
+ let dirent = match self.write_file(buffer, "/proc/cpuinfo") {
+ Ok(location) => MDRawDirectory {
+ stream_type: MDStreamType::LinuxCpuInfo as u32,
+ location,
+ },
+ Err(_) => Default::default(),
+ };
+ // Write section to file
+ dir_section.write_to_file(buffer, Some(dirent))?;
+
+ let dirent = match self.write_file(buffer, &format!("/proc/{}/status", self.blamed_thread))
+ {
+ Ok(location) => MDRawDirectory {
+ stream_type: MDStreamType::LinuxProcStatus as u32,
+ location,
+ },
+ Err(_) => Default::default(),
+ };
+ // Write section to file
+ dir_section.write_to_file(buffer, Some(dirent))?;
+
+ let dirent = match self
+ .write_file(buffer, "/etc/lsb-release")
+ .or_else(|_| self.write_file(buffer, "/etc/os-release"))
+ {
+ Ok(location) => MDRawDirectory {
+ stream_type: MDStreamType::LinuxLsbRelease as u32,
+ location,
+ },
+ Err(_) => Default::default(),
+ };
+ // Write section to file
+ dir_section.write_to_file(buffer, Some(dirent))?;
+
+ let dirent = match self.write_file(buffer, &format!("/proc/{}/cmdline", self.blamed_thread))
+ {
+ Ok(location) => MDRawDirectory {
+ stream_type: MDStreamType::LinuxCmdLine as u32,
+ location,
+ },
+ Err(_) => Default::default(),
+ };
+ // Write section to file
+ dir_section.write_to_file(buffer, Some(dirent))?;
+
+ let dirent = match self.write_file(buffer, &format!("/proc/{}/environ", self.blamed_thread))
+ {
+ Ok(location) => MDRawDirectory {
+ stream_type: MDStreamType::LinuxEnviron as u32,
+ location,
+ },
+ Err(_) => Default::default(),
+ };
+ // Write section to file
+ dir_section.write_to_file(buffer, Some(dirent))?;
+
+ let dirent = match self.write_file(buffer, &format!("/proc/{}/auxv", self.blamed_thread)) {
+ Ok(location) => MDRawDirectory {
+ stream_type: MDStreamType::LinuxAuxv as u32,
+ location,
+ },
+ Err(_) => Default::default(),
+ };
+ // Write section to file
+ dir_section.write_to_file(buffer, Some(dirent))?;
+
+ let dirent = match self.write_file(buffer, &format!("/proc/{}/maps", self.blamed_thread)) {
+ Ok(location) => MDRawDirectory {
+ stream_type: MDStreamType::LinuxMaps as u32,
+ location,
+ },
+ Err(_) => Default::default(),
+ };
+ // Write section to file
+ dir_section.write_to_file(buffer, Some(dirent))?;
+
+ let dirent = dso_debug::write_dso_debug_stream(buffer, self.process_id, &dumper.auxv)
+ .unwrap_or_default();
+ // Write section to file
+ dir_section.write_to_file(buffer, Some(dirent))?;
+
+ let dirent = thread_names_stream::write(buffer, dumper)?;
+ // Write section to file
+ dir_section.write_to_file(buffer, Some(dirent))?;
+
+ // If you add more directory entries, don't forget to update kNumWriters,
+ // above.
+ Ok(())
+ }
+
+ #[allow(clippy::unused_self)]
+ fn write_file(
+ &self,
+ buffer: &mut DumpBuf,
+ filename: &str,
+ ) -> std::result::Result<MDLocationDescriptor, MemoryWriterError> {
+ let content = std::fs::read(filename)?;
+
+ let section = MemoryArrayWriter::write_bytes(buffer, &content);
+ Ok(section.location())
+ }
+}
diff --git a/third_party/rust/minidump-writer/src/linux/ptrace_dumper.rs b/third_party/rust/minidump-writer/src/linux/ptrace_dumper.rs
new file mode 100644
index 0000000000..2380afb09f
--- /dev/null
+++ b/third_party/rust/minidump-writer/src/linux/ptrace_dumper.rs
@@ -0,0 +1,594 @@
+#[cfg(target_os = "android")]
+use crate::linux::android::late_process_mappings;
+use crate::{
+ linux::{
+ auxv_reader::{AuxvType, ProcfsAuxvIter},
+ errors::{DumperError, InitError, ThreadInfoError},
+ maps_reader::{MappingInfo, MappingInfoParsingResult, DELETED_SUFFIX},
+ thread_info::{Pid, ThreadInfo},
+ LINUX_GATE_LIBRARY_NAME,
+ },
+ minidump_format::GUID,
+};
+use goblin::elf;
+use nix::{
+ errno::Errno,
+ sys::{ptrace, wait},
+};
+use std::{
+ collections::HashMap,
+ ffi::c_void,
+ io::{BufRead, BufReader},
+ path,
+ result::Result,
+};
+
+#[derive(Debug, Clone)]
+pub struct Thread {
+ pub tid: Pid,
+ pub name: Option<String>,
+}
+
+#[derive(Debug)]
+pub struct PtraceDumper {
+ pub pid: Pid,
+ threads_suspended: bool,
+ pub threads: Vec<Thread>,
+ pub auxv: HashMap<AuxvType, AuxvType>,
+ pub mappings: Vec<MappingInfo>,
+}
+
+#[cfg(target_pointer_width = "32")]
+pub const AT_SYSINFO_EHDR: u32 = 33;
+#[cfg(target_pointer_width = "64")]
+pub const AT_SYSINFO_EHDR: u64 = 33;
+
+impl Drop for PtraceDumper {
+ fn drop(&mut self) {
+ // Always try to resume all threads (e.g. in case of error)
+ let _ = self.resume_threads();
+ }
+}
+
+/// PTRACE_DETACH the given pid.
+///
+/// This handles special errno cases (ESRCH) which we won't consider errors.
+fn ptrace_detach(child: Pid) -> Result<(), DumperError> {
+ let pid = nix::unistd::Pid::from_raw(child);
+ ptrace::detach(pid, None).or_else(|e| {
+ // errno is set to ESRCH if the pid no longer exists, but we don't want to error in that
+ // case.
+ if e == nix::Error::ESRCH {
+ Ok(())
+ } else {
+ Err(DumperError::PtraceDetachError(child, e))
+ }
+ })
+}
+
+impl PtraceDumper {
+ /// Constructs a dumper for extracting information of a given process
+ /// with a process ID of |pid|.
+ pub fn new(pid: Pid) -> Result<Self, InitError> {
+ let mut dumper = PtraceDumper {
+ pid,
+ threads_suspended: false,
+ threads: Vec::new(),
+ auxv: HashMap::new(),
+ mappings: Vec::new(),
+ };
+ dumper.init()?;
+ Ok(dumper)
+ }
+
+ // TODO: late_init for chromeos and android
+ pub fn init(&mut self) -> Result<(), InitError> {
+ self.read_auxv()?;
+ self.enumerate_threads()?;
+ self.enumerate_mappings()?;
+ Ok(())
+ }
+
+ #[cfg_attr(not(target_os = "android"), allow(clippy::unused_self))]
+ pub fn late_init(&mut self) -> Result<(), InitError> {
+ #[cfg(target_os = "android")]
+ {
+ late_process_mappings(self.pid, &mut self.mappings)?;
+ }
+ Ok(())
+ }
+
+ /// Copies content of |length| bytes from a given process |child|,
+ /// starting from |src|, into |dest|. This method uses ptrace to extract
+ /// the content from the target process. Always returns true.
+ pub fn copy_from_process(
+ child: Pid,
+ src: *mut c_void,
+ num_of_bytes: usize,
+ ) -> Result<Vec<u8>, DumperError> {
+ use DumperError::CopyFromProcessError as CFPE;
+ let pid = nix::unistd::Pid::from_raw(child);
+ let mut res = Vec::new();
+ let mut idx = 0usize;
+ while idx < num_of_bytes {
+ let word = ptrace::read(pid, (src as usize + idx) as *mut c_void)
+ .map_err(|e| CFPE(child, src as usize, idx, num_of_bytes, e))?;
+ res.append(&mut word.to_ne_bytes().to_vec());
+ idx += std::mem::size_of::<libc::c_long>();
+ }
+ Ok(res)
+ }
+
+ /// Suspends a thread by attaching to it.
+ pub fn suspend_thread(child: Pid) -> Result<(), DumperError> {
+ use DumperError::PtraceAttachError as AttachErr;
+
+ let pid = nix::unistd::Pid::from_raw(child);
+ // This may fail if the thread has just died or debugged.
+ ptrace::attach(pid).map_err(|e| AttachErr(child, e))?;
+ loop {
+ match wait::waitpid(pid, Some(wait::WaitPidFlag::__WALL)) {
+ Ok(_) => break,
+ Err(_e @ Errno::EINTR) => continue,
+ Err(e) => {
+ ptrace_detach(child)?;
+ return Err(DumperError::WaitPidError(child, e));
+ }
+ }
+ }
+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+ {
+ // On x86, the stack pointer is NULL or -1, when executing trusted code in
+ // the seccomp sandbox. Not only does this cause difficulties down the line
+ // when trying to dump the thread's stack, it also results in the minidumps
+ // containing information about the trusted threads. This information is
+ // generally completely meaningless and just pollutes the minidumps.
+ // We thus test the stack pointer and exclude any threads that are part of
+ // the seccomp sandbox's trusted code.
+ let skip_thread;
+ let regs = ptrace::getregs(pid);
+ if let Ok(regs) = regs {
+ #[cfg(target_arch = "x86_64")]
+ {
+ skip_thread = regs.rsp == 0;
+ }
+ #[cfg(target_arch = "x86")]
+ {
+ skip_thread = regs.esp == 0;
+ }
+ } else {
+ skip_thread = true;
+ }
+ if skip_thread {
+ ptrace_detach(child)?;
+ return Err(DumperError::DetachSkippedThread(child));
+ }
+ }
+ Ok(())
+ }
+
+ /// Resumes a thread by detaching from it.
+ pub fn resume_thread(child: Pid) -> Result<(), DumperError> {
+ ptrace_detach(child)
+ }
+
+ pub fn suspend_threads(&mut self) -> Result<(), DumperError> {
+ let threads_count = self.threads.len();
+ // Iterate over all threads and try to suspend them.
+ // If the thread either disappeared before we could attach to it, or if
+ // it was part of the seccomp sandbox's trusted code, it is OK to
+ // silently drop it from the minidump.
+ self.threads.retain(|x| Self::suspend_thread(x.tid).is_ok());
+
+ if self.threads.is_empty() {
+ Err(DumperError::SuspendNoThreadsLeft(threads_count))
+ } else {
+ self.threads_suspended = true;
+ Ok(())
+ }
+ }
+
+ pub fn resume_threads(&mut self) -> Result<(), DumperError> {
+ let mut result = Ok(());
+ if self.threads_suspended {
+ for thread in &self.threads {
+ match Self::resume_thread(thread.tid) {
+ Ok(_) => {}
+ x => {
+ result = x;
+ }
+ }
+ }
+ }
+ self.threads_suspended = false;
+ result
+ }
+
+ /// Parse /proc/$pid/task to list all the threads of the process identified by
+ /// pid.
+ fn enumerate_threads(&mut self) -> Result<(), InitError> {
+ let pid = self.pid;
+ let filename = format!("/proc/{}/task", pid);
+ let task_path = path::PathBuf::from(&filename);
+ if task_path.is_dir() {
+ std::fs::read_dir(task_path)
+ .map_err(|e| InitError::IOError(filename, e))?
+ .filter_map(|entry| entry.ok()) // Filter out bad entries
+ .filter_map(|entry| {
+ entry
+ .file_name() // Parse name to Pid, filter out those that are unparsable
+ .to_str()
+ .and_then(|name| name.parse::<Pid>().ok())
+ })
+ .map(|tid| {
+ // Read the thread-name (if there is any)
+ let name = std::fs::read_to_string(format!("/proc/{}/task/{}/comm", pid, tid))
+ // NOTE: This is a bit wasteful as it does two allocations in order to trim, but leaving it for now
+ .map(|s| s.trim_end().to_string())
+ .ok();
+ (tid, name)
+ })
+ .for_each(|(tid, name)| self.threads.push(Thread { tid, name }));
+ }
+ Ok(())
+ }
+
+ fn read_auxv(&mut self) -> Result<(), InitError> {
+ let filename = format!("/proc/{}/auxv", self.pid);
+ let auxv_path = path::PathBuf::from(&filename);
+ let auxv_file =
+ std::fs::File::open(auxv_path).map_err(|e| InitError::IOError(filename, e))?;
+ let input = BufReader::new(auxv_file);
+ let reader = ProcfsAuxvIter::new(input);
+ self.auxv = reader
+ .filter_map(Result::ok)
+ .map(|x| (x.key, x.value))
+ .collect();
+
+ if self.auxv.is_empty() {
+ Err(InitError::NoAuxvEntryFound(self.pid))
+ } else {
+ Ok(())
+ }
+ }
+
+ fn enumerate_mappings(&mut self) -> Result<(), InitError> {
+ // linux_gate_loc is the beginning of the kernel's mapping of
+ // linux-gate.so in the process. It doesn't actually show up in the
+ // maps list as a filename, but it can be found using the AT_SYSINFO_EHDR
+ // aux vector entry, which gives the information necessary to special
+ // case its entry when creating the list of mappings.
+ // See http://www.trilithium.com/johan/2005/08/linux-gate/ for more
+ // information.
+ let linux_gate_loc = *self.auxv.get(&AT_SYSINFO_EHDR).unwrap_or(&0);
+ // Although the initial executable is usually the first mapping, it's not
+ // guaranteed (see http://crosbug.com/25355); therefore, try to use the
+ // actual entry point to find the mapping.
+ let at_entry;
+ #[cfg(target_arch = "arm")]
+ {
+ at_entry = 9;
+ }
+ #[cfg(not(target_arch = "arm"))]
+ {
+ at_entry = libc::AT_ENTRY;
+ }
+
+ let entry_point_loc = *self.auxv.get(&at_entry).unwrap_or(&0);
+ let filename = format!("/proc/{}/maps", self.pid);
+ let errmap = |e| InitError::IOError(filename.clone(), e);
+ let maps_path = path::PathBuf::from(&filename);
+ let maps_file = std::fs::File::open(maps_path).map_err(errmap)?;
+
+ for line in BufReader::new(maps_file).lines() {
+ // /proc/<pid>/maps looks like this
+ // 7fe34a863000-7fe34a864000 rw-p 00009000 00:31 4746408 /usr/lib64/libogg.so.0.8.4
+ let line = line.map_err(errmap)?;
+ match MappingInfo::parse_from_line(&line, linux_gate_loc, self.mappings.last_mut()) {
+ Ok(MappingInfoParsingResult::Success(map)) => self.mappings.push(map),
+ Ok(MappingInfoParsingResult::SkipLine) | Err(_) => continue,
+ }
+ }
+
+ if entry_point_loc != 0 {
+ let mut swap_idx = None;
+ for (idx, module) in self.mappings.iter().enumerate() {
+ // If this module contains the entry-point, and it's not already the first
+ // one, then we need to make it be first. This is because the minidump
+ // format assumes the first module is the one that corresponds to the main
+ // executable (as codified in
+ // processor/minidump.cc:MinidumpModuleList::GetMainModule()).
+ if entry_point_loc >= module.start_address.try_into().unwrap()
+ && entry_point_loc < (module.start_address + module.size).try_into().unwrap()
+ {
+ swap_idx = Some(idx);
+ break;
+ }
+ }
+ if let Some(idx) = swap_idx {
+ self.mappings.swap(0, idx);
+ }
+ }
+ Ok(())
+ }
+
+ /// Read thread info from /proc/$pid/status.
+ /// Fill out the |tgid|, |ppid| and |pid| members of |info|. If unavailable,
+ /// these members are set to -1. Returns true if all three members are
+ /// available.
+ pub fn get_thread_info_by_index(&self, index: usize) -> Result<ThreadInfo, ThreadInfoError> {
+ if index > self.threads.len() {
+ return Err(ThreadInfoError::IndexOutOfBounds(index, self.threads.len()));
+ }
+
+ ThreadInfo::create(self.pid, self.threads[index].tid)
+ }
+
+ // Get information about the stack, given the stack pointer. We don't try to
+ // walk the stack since we might not have all the information needed to do
+ // unwind. So we just grab, up to, 32k of stack.
+ pub fn get_stack_info(&self, int_stack_pointer: usize) -> Result<(usize, usize), DumperError> {
+ // Move the stack pointer to the bottom of the page that it's in.
+ // NOTE: original code uses getpagesize(), which a) isn't there in Rust and
+ // b) shouldn't be used, as its not portable (see man getpagesize)
+ let page_size = nix::unistd::sysconf(nix::unistd::SysconfVar::PAGE_SIZE)?
+ .expect("page size apparently unlimited: doesn't make sense.");
+ let stack_pointer = int_stack_pointer & !(page_size as usize - 1);
+
+ // The number of bytes of stack which we try to capture.
+ let stack_to_capture = 32 * 1024;
+
+ let mapping = self
+ .find_mapping(stack_pointer)
+ .ok_or(DumperError::NoStackPointerMapping)?;
+ let offset = stack_pointer - mapping.start_address;
+ let distance_to_end = mapping.size - offset;
+ let stack_len = std::cmp::min(distance_to_end, stack_to_capture);
+
+ Ok((stack_pointer, stack_len))
+ }
+
+ pub fn sanitize_stack_copy(
+ &self,
+ stack_copy: &mut [u8],
+ stack_pointer: usize,
+ sp_offset: usize,
+ ) -> Result<(), DumperError> {
+ // We optimize the search for containing mappings in three ways:
+ // 1) We expect that pointers into the stack mapping will be common, so
+ // we cache that address range.
+ // 2) The last referenced mapping is a reasonable predictor for the next
+ // referenced mapping, so we test that first.
+ // 3) We precompute a bitfield based upon bits 32:32-n of the start and
+ // stop addresses, and use that to short circuit any values that can
+ // not be pointers. (n=11)
+ let defaced;
+ #[cfg(target_pointer_width = "64")]
+ {
+ defaced = 0x0defaced0defacedusize.to_ne_bytes();
+ }
+ #[cfg(target_pointer_width = "32")]
+ {
+ defaced = 0x0defacedusize.to_ne_bytes();
+ };
+ // the bitfield length is 2^test_bits long.
+ let test_bits = 11;
+ // byte length of the corresponding array.
+ let array_size = 1 << (test_bits - 3);
+ let array_mask = array_size - 1;
+ // The amount to right shift pointers by. This captures the top bits
+ // on 32 bit architectures. On 64 bit architectures this would be
+ // uninformative so we take the same range of bits.
+ let shift = 32 - 11;
+ // let MappingInfo* last_hit_mapping = nullptr;
+ // let MappingInfo* hit_mapping = nullptr;
+ let stack_mapping = self.find_mapping_no_bias(stack_pointer);
+ let mut last_hit_mapping: Option<&MappingInfo> = None;
+ // The magnitude below which integers are considered to be to be
+ // 'small', and not constitute a PII risk. These are included to
+ // avoid eliding useful register values.
+ let small_int_magnitude: isize = 4096;
+
+ let mut could_hit_mapping = vec![0; array_size];
+ // Initialize the bitfield such that if the (pointer >> shift)'th
+ // bit, modulo the bitfield size, is not set then there does not
+ // exist a mapping in mappings that would contain that pointer.
+ for mapping in &self.mappings {
+ if !mapping.executable {
+ continue;
+ }
+ // For each mapping, work out the (unmodulo'ed) range of bits to
+ // set.
+ let mut start = mapping.start_address;
+ let mut end = start + mapping.size;
+ start >>= shift;
+ end >>= shift;
+ for bit in start..=end {
+ // Set each bit in the range, applying the modulus.
+ could_hit_mapping[(bit >> 3) & array_mask] |= 1 << (bit & 7);
+ }
+ }
+
+ // Zero memory that is below the current stack pointer.
+ let offset =
+ (sp_offset + std::mem::size_of::<usize>() - 1) & !(std::mem::size_of::<usize>() - 1);
+ for x in &mut stack_copy[0..offset] {
+ *x = 0;
+ }
+ let mut chunks = stack_copy[offset..].chunks_exact_mut(std::mem::size_of::<usize>());
+
+ // Apply sanitization to each complete pointer-aligned word in the
+ // stack.
+ for sp in &mut chunks {
+ let addr = usize::from_ne_bytes(sp.to_vec().as_slice().try_into()?);
+ let addr_signed = isize::from_ne_bytes(sp.to_vec().as_slice().try_into()?);
+
+ if addr <= small_int_magnitude as usize && addr_signed >= -small_int_magnitude {
+ continue;
+ }
+
+ if let Some(stack_map) = stack_mapping {
+ if stack_map.contains_address(addr) {
+ continue;
+ }
+ }
+ if let Some(last_hit) = last_hit_mapping {
+ if last_hit.contains_address(addr) {
+ continue;
+ }
+ }
+
+ let test = addr >> shift;
+ if could_hit_mapping[(test >> 3) & array_mask] & (1 << (test & 7)) != 0 {
+ if let Some(hit_mapping) = self.find_mapping_no_bias(addr) {
+ if hit_mapping.executable {
+ last_hit_mapping = Some(hit_mapping);
+ continue;
+ }
+ }
+ }
+ sp.copy_from_slice(&defaced);
+ }
+ // Zero any partial word at the top of the stack, if alignment is
+ // such that that is required.
+ for sp in chunks.into_remainder() {
+ *sp = 0;
+ }
+ Ok(())
+ }
+
+ // Find the mapping which the given memory address falls in.
+ pub fn find_mapping(&self, address: usize) -> Option<&MappingInfo> {
+ self.mappings
+ .iter()
+ .find(|map| address >= map.start_address && address - map.start_address < map.size)
+ }
+
+ // Find the mapping which the given memory address falls in. Uses the
+ // unadjusted mapping address range from the kernel, rather than the
+ // biased range.
+ pub fn find_mapping_no_bias(&self, address: usize) -> Option<&MappingInfo> {
+ self.mappings.iter().find(|map| {
+ address >= map.system_mapping_info.start_address
+ && address < map.system_mapping_info.end_address
+ })
+ }
+
+ fn parse_build_id<'data>(
+ elf_obj: &elf::Elf<'data>,
+ mem_slice: &'data [u8],
+ ) -> Option<&'data [u8]> {
+ if let Some(mut notes) = elf_obj.iter_note_headers(mem_slice) {
+ while let Some(Ok(note)) = notes.next() {
+ if (note.name == "GNU") && (note.n_type == elf::note::NT_GNU_BUILD_ID) {
+ return Some(note.desc);
+ }
+ }
+ }
+ if let Some(mut notes) = elf_obj.iter_note_sections(mem_slice, Some(".note.gnu.build-id")) {
+ while let Some(Ok(note)) = notes.next() {
+ if (note.name == "GNU") && (note.n_type == elf::note::NT_GNU_BUILD_ID) {
+ return Some(note.desc);
+ }
+ }
+ }
+ None
+ }
+
+ pub fn elf_file_identifier_from_mapped_file(mem_slice: &[u8]) -> Result<Vec<u8>, DumperError> {
+ let elf_obj = elf::Elf::parse(mem_slice)?;
+
+ if let Some(build_id) = Self::parse_build_id(&elf_obj, mem_slice) {
+ // Look for a build id note first.
+ Ok(build_id.to_vec())
+ } else {
+ // Fall back on hashing the first page of the text section.
+
+ // Attempt to locate the .text section of an ELF binary and generate
+ // a simple hash by XORing the first page worth of bytes into |result|.
+ for section in elf_obj.section_headers {
+ if section.sh_type != elf::section_header::SHT_PROGBITS {
+ continue;
+ }
+ if section.sh_flags & u64::from(elf::section_header::SHF_ALLOC) != 0
+ && section.sh_flags & u64::from(elf::section_header::SHF_EXECINSTR) != 0
+ {
+ let text_section =
+ &mem_slice[section.sh_offset as usize..][..section.sh_size as usize];
+ // Only provide mem::size_of(MDGUID) bytes to keep identifiers produced by this
+ // function backwards-compatible.
+ let max_len = std::cmp::min(text_section.len(), 4096);
+ let mut result = vec![0u8; std::mem::size_of::<GUID>()];
+ let mut offset = 0;
+ while offset < max_len {
+ for idx in 0..std::mem::size_of::<GUID>() {
+ if offset + idx >= text_section.len() {
+ break;
+ }
+ result[idx] ^= text_section[offset + idx];
+ }
+ offset += std::mem::size_of::<GUID>();
+ }
+ return Ok(result);
+ }
+ }
+ Err(DumperError::NoBuildIDFound)
+ }
+ }
+
+ pub fn elf_identifier_for_mapping_index(&mut self, idx: usize) -> Result<Vec<u8>, DumperError> {
+ assert!(idx < self.mappings.len());
+
+ Self::elf_identifier_for_mapping(&mut self.mappings[idx], self.pid)
+ }
+
+ pub fn elf_identifier_for_mapping(
+ mapping: &mut MappingInfo,
+ pid: Pid,
+ ) -> Result<Vec<u8>, DumperError> {
+ if !MappingInfo::is_mapped_file_safe_to_open(&mapping.name) {
+ return Err(DumperError::NotSafeToOpenMapping(
+ mapping.name.clone().unwrap_or_default(),
+ ));
+ }
+
+ // Special-case linux-gate because it's not a real file.
+ if mapping.name.as_deref() == Some(LINUX_GATE_LIBRARY_NAME) {
+ if pid == std::process::id().try_into()? {
+ let mem_slice = unsafe {
+ std::slice::from_raw_parts(mapping.start_address as *const u8, mapping.size)
+ };
+ return Self::elf_file_identifier_from_mapped_file(mem_slice);
+ } else {
+ let mem_slice = Self::copy_from_process(
+ pid,
+ mapping.start_address as *mut libc::c_void,
+ mapping.size,
+ )?;
+ return Self::elf_file_identifier_from_mapped_file(&mem_slice);
+ }
+ }
+ let new_name = MappingInfo::handle_deleted_file_in_mapping(
+ mapping.name.as_deref().unwrap_or_default(),
+ pid,
+ )?;
+
+ let mem_slice = MappingInfo::get_mmap(&Some(new_name.clone()), mapping.offset)?;
+ let build_id = Self::elf_file_identifier_from_mapped_file(&mem_slice)?;
+
+ // This means we switched from "/my/binary" to "/proc/1234/exe", because /my/binary
+ // was deleted and thus has a "/my/binary (deleted)" entry. We found the mapping anyway
+ // so we remove the "(deleted)".
+ if let Some(old_name) = &mapping.name {
+ if &new_name != old_name {
+ mapping.name = Some(
+ old_name
+ .trim_end_matches(DELETED_SUFFIX)
+ .trim_end()
+ .to_string(),
+ );
+ }
+ }
+ Ok(build_id)
+ }
+}
diff --git a/third_party/rust/minidump-writer/src/linux/sections.rs b/third_party/rust/minidump-writer/src/linux/sections.rs
new file mode 100644
index 0000000000..c7c4172c37
--- /dev/null
+++ b/third_party/rust/minidump-writer/src/linux/sections.rs
@@ -0,0 +1,18 @@
+pub mod app_memory;
+pub mod exception_stream;
+pub mod mappings;
+pub mod memory_list_stream;
+pub mod systeminfo_stream;
+pub mod thread_list_stream;
+pub mod thread_names_stream;
+
+use crate::{
+ dir_section::DumpBuf,
+ errors::{self},
+ linux::{
+ minidump_writer::{self, MinidumpWriter},
+ ptrace_dumper::PtraceDumper,
+ },
+ mem_writer::*,
+ minidump_format::*,
+};
diff --git a/third_party/rust/minidump-writer/src/linux/sections/app_memory.rs b/third_party/rust/minidump-writer/src/linux/sections/app_memory.rs
new file mode 100644
index 0000000000..6d4a2e908f
--- /dev/null
+++ b/third_party/rust/minidump-writer/src/linux/sections/app_memory.rs
@@ -0,0 +1,23 @@
+use super::*;
+
+/// Write application-provided memory regions.
+pub fn write(
+ config: &mut MinidumpWriter,
+ buffer: &mut DumpBuf,
+) -> Result<(), errors::SectionAppMemoryError> {
+ for app_memory in &config.app_memory {
+ let data_copy = PtraceDumper::copy_from_process(
+ config.blamed_thread,
+ app_memory.ptr as *mut libc::c_void,
+ app_memory.length,
+ )?;
+
+ let section = MemoryArrayWriter::write_bytes(buffer, &data_copy);
+ let desc = MDMemoryDescriptor {
+ start_of_memory_range: app_memory.ptr as u64,
+ memory: section.location(),
+ };
+ config.memory_blocks.push(desc);
+ }
+ Ok(())
+}
diff --git a/third_party/rust/minidump-writer/src/linux/sections/exception_stream.rs b/third_party/rust/minidump-writer/src/linux/sections/exception_stream.rs
new file mode 100644
index 0000000000..f7edda8d4c
--- /dev/null
+++ b/third_party/rust/minidump-writer/src/linux/sections/exception_stream.rs
@@ -0,0 +1,50 @@
+use super::minidump_writer::CrashingThreadContext;
+use super::*;
+use minidump_common::errors::ExceptionCodeLinux;
+
+pub fn write(
+ config: &mut MinidumpWriter,
+ buffer: &mut DumpBuf,
+) -> Result<MDRawDirectory, errors::SectionExceptionStreamError> {
+ let exception = if let Some(context) = &config.crash_context {
+ MDException {
+ exception_code: context.inner.siginfo.ssi_signo,
+ exception_flags: context.inner.siginfo.ssi_code as u32,
+ exception_address: context.inner.siginfo.ssi_addr,
+ ..Default::default()
+ }
+ } else {
+ let addr = match &config.crashing_thread_context {
+ CrashingThreadContext::CrashContextPlusAddress((_, addr)) => *addr,
+ _ => 0,
+ };
+ MDException {
+ exception_code: ExceptionCodeLinux::DUMP_REQUESTED as u32,
+ exception_address: addr as u64,
+ ..Default::default()
+ }
+ };
+
+ let thread_context = match config.crashing_thread_context {
+ CrashingThreadContext::CrashContextPlusAddress((ctx, _))
+ | CrashingThreadContext::CrashContext(ctx) => ctx,
+ CrashingThreadContext::None => MDLocationDescriptor {
+ data_size: 0,
+ rva: 0,
+ },
+ };
+
+ let stream = MDRawExceptionStream {
+ thread_id: config.blamed_thread as u32,
+ exception_record: exception,
+ __align: 0,
+ thread_context,
+ };
+ let exc = MemoryWriter::alloc_with_val(buffer, stream)?;
+ let dirent = MDRawDirectory {
+ stream_type: MDStreamType::ExceptionStream as u32,
+ location: exc.location(),
+ };
+
+ Ok(dirent)
+}
diff --git a/third_party/rust/minidump-writer/src/linux/sections/mappings.rs b/third_party/rust/minidump-writer/src/linux/sections/mappings.rs
new file mode 100644
index 0000000000..7eb002d036
--- /dev/null
+++ b/third_party/rust/minidump-writer/src/linux/sections/mappings.rs
@@ -0,0 +1,98 @@
+use super::*;
+use crate::linux::maps_reader::MappingInfo;
+
+/// Write information about the mappings in effect. Because we are using the
+/// minidump format, the information about the mappings is pretty limited.
+/// Because of this, we also include the full, unparsed, /proc/$x/maps file in
+/// another stream in the file.
+pub fn write(
+ config: &mut MinidumpWriter,
+ buffer: &mut DumpBuf,
+ dumper: &mut PtraceDumper,
+) -> Result<MDRawDirectory, errors::SectionMappingsError> {
+ let mut modules = Vec::new();
+
+ // First write all the mappings from the dumper
+ for map_idx in 0..dumper.mappings.len() {
+ // If the mapping is uninteresting, or if
+ // there is caller-provided information about this mapping
+ // in the user_mapping_list list, skip it
+
+ if !dumper.mappings[map_idx].is_interesting()
+ || dumper.mappings[map_idx].is_contained_in(&config.user_mapping_list)
+ {
+ continue;
+ }
+ // Note: elf_identifier_for_mapping_index() can manipulate the |mapping.name|.
+ let identifier = dumper
+ .elf_identifier_for_mapping_index(map_idx)
+ .unwrap_or_default();
+
+ // If the identifier is all 0, its an uninteresting mapping (bmc#1676109)
+ if identifier.is_empty() || identifier.iter().all(|&x| x == 0) {
+ continue;
+ }
+
+ let module = fill_raw_module(buffer, &dumper.mappings[map_idx], &identifier)?;
+ modules.push(module);
+ }
+
+ // Next write all the mappings provided by the caller
+ for user in &config.user_mapping_list {
+ // GUID was provided by caller.
+ let module = fill_raw_module(buffer, &user.mapping, &user.identifier)?;
+ modules.push(module);
+ }
+
+ let list_header = MemoryWriter::<u32>::alloc_with_val(buffer, modules.len() as u32)?;
+
+ let mut dirent = MDRawDirectory {
+ stream_type: MDStreamType::ModuleListStream as u32,
+ location: list_header.location(),
+ };
+
+ if !modules.is_empty() {
+ let mapping_list = MemoryArrayWriter::<MDRawModule>::alloc_from_iter(buffer, modules)?;
+ dirent.location.data_size += mapping_list.location().data_size;
+ }
+
+ Ok(dirent)
+}
+
+fn fill_raw_module(
+ buffer: &mut DumpBuf,
+ mapping: &MappingInfo,
+ identifier: &[u8],
+) -> Result<MDRawModule, errors::SectionMappingsError> {
+ let cv_record = if identifier.is_empty() {
+ // Just zeroes
+ Default::default()
+ } else {
+ let cv_signature = crate::minidump_format::format::CvSignature::Elf as u32;
+ let array_size = std::mem::size_of_val(&cv_signature) + identifier.len();
+
+ let mut sig_section = MemoryArrayWriter::<u8>::alloc_array(buffer, array_size)?;
+ for (index, val) in cv_signature
+ .to_ne_bytes()
+ .iter()
+ .chain(identifier.iter())
+ .enumerate()
+ {
+ sig_section.set_value_at(buffer, *val, index)?;
+ }
+ sig_section.location()
+ };
+
+ let (file_path, _) = mapping
+ .get_mapping_effective_name_and_path()
+ .map_err(|e| errors::SectionMappingsError::GetEffectivePathError(mapping.clone(), e))?;
+ let name_header = write_string_to_location(buffer, &file_path)?;
+
+ Ok(MDRawModule {
+ base_of_image: mapping.start_address as u64,
+ size_of_image: mapping.size as u32,
+ cv_record,
+ module_name_rva: name_header.rva,
+ ..Default::default()
+ })
+}
diff --git a/third_party/rust/minidump-writer/src/linux/sections/memory_list_stream.rs b/third_party/rust/minidump-writer/src/linux/sections/memory_list_stream.rs
new file mode 100644
index 0000000000..7f49779204
--- /dev/null
+++ b/third_party/rust/minidump-writer/src/linux/sections/memory_list_stream.rs
@@ -0,0 +1,21 @@
+use super::*;
+
+pub fn write(
+ config: &mut MinidumpWriter,
+ buffer: &mut DumpBuf,
+) -> Result<MDRawDirectory, errors::SectionMemListError> {
+ let list_header =
+ MemoryWriter::<u32>::alloc_with_val(buffer, config.memory_blocks.len() as u32)?;
+
+ let mut dirent = MDRawDirectory {
+ stream_type: MDStreamType::MemoryListStream as u32,
+ location: list_header.location(),
+ };
+
+ let block_list =
+ MemoryArrayWriter::<MDMemoryDescriptor>::alloc_from_array(buffer, &config.memory_blocks)?;
+
+ dirent.location.data_size += block_list.location().data_size;
+
+ Ok(dirent)
+}
diff --git a/third_party/rust/minidump-writer/src/linux/sections/systeminfo_stream.rs b/third_party/rust/minidump-writer/src/linux/sections/systeminfo_stream.rs
new file mode 100644
index 0000000000..a298c00d15
--- /dev/null
+++ b/third_party/rust/minidump-writer/src/linux/sections/systeminfo_stream.rs
@@ -0,0 +1,23 @@
+use super::*;
+use crate::linux::dumper_cpu_info as dci;
+
+pub fn write(buffer: &mut DumpBuf) -> Result<MDRawDirectory, errors::SectionSystemInfoError> {
+ let mut info_section = MemoryWriter::<MDRawSystemInfo>::alloc(buffer)?;
+ let dirent = MDRawDirectory {
+ stream_type: MDStreamType::SystemInfoStream as u32,
+ location: info_section.location(),
+ };
+
+ let (platform_id, os_version) = dci::os_information();
+ let os_version_loc = write_string_to_location(buffer, &os_version)?;
+
+ // SAFETY: POD
+ let mut info = unsafe { std::mem::zeroed::<MDRawSystemInfo>() };
+ info.platform_id = platform_id as u32;
+ info.csd_version_rva = os_version_loc.rva;
+
+ dci::write_cpu_information(&mut info)?;
+
+ info_section.set_value(buffer, info)?;
+ Ok(dirent)
+}
diff --git a/third_party/rust/minidump-writer/src/linux/sections/thread_list_stream.rs b/third_party/rust/minidump-writer/src/linux/sections/thread_list_stream.rs
new file mode 100644
index 0000000000..11e625d0b6
--- /dev/null
+++ b/third_party/rust/minidump-writer/src/linux/sections/thread_list_stream.rs
@@ -0,0 +1,238 @@
+use super::*;
+use crate::{minidump_cpu::RawContextCPU, minidump_writer::CrashingThreadContext};
+
+// The following kLimit* constants are for when minidump_size_limit_ is set
+// and the minidump size might exceed it.
+//
+// Estimate for how big each thread's stack will be (in bytes).
+const LIMIT_AVERAGE_THREAD_STACK_LENGTH: usize = 8 * 1024;
+// Number of threads whose stack size we don't want to limit. These base
+// threads will simply be the first N threads returned by the dumper (although
+// the crashing thread will never be limited). Threads beyond this count are
+// the extra threads.
+const LIMIT_BASE_THREAD_COUNT: usize = 20;
+// Maximum stack size to dump for any extra thread (in bytes).
+const LIMIT_MAX_EXTRA_THREAD_STACK_LEN: usize = 2 * 1024;
+// Make sure this number of additional bytes can fit in the minidump
+// (exclude the stack data).
+const LIMIT_MINIDUMP_FUDGE_FACTOR: u64 = 64 * 1024;
+
+#[derive(Debug, Clone, Copy)]
+enum MaxStackLen {
+ None,
+ Len(usize),
+}
+
+pub fn write(
+ config: &mut MinidumpWriter,
+ buffer: &mut DumpBuf,
+ dumper: &PtraceDumper,
+) -> Result<MDRawDirectory, errors::SectionThreadListError> {
+ let num_threads = dumper.threads.len();
+ // Memory looks like this:
+ // <num_threads><thread_1><thread_2>...
+
+ let list_header = MemoryWriter::<u32>::alloc_with_val(buffer, num_threads as u32)?;
+
+ let mut dirent = MDRawDirectory {
+ stream_type: MDStreamType::ThreadListStream as u32,
+ location: list_header.location(),
+ };
+
+ let mut thread_list = MemoryArrayWriter::<MDRawThread>::alloc_array(buffer, num_threads)?;
+ dirent.location.data_size += thread_list.location().data_size;
+ // If there's a minidump size limit, check if it might be exceeded. Since
+ // most of the space is filled with stack data, just check against that.
+ // If this expects to exceed the limit, set extra_thread_stack_len such
+ // that any thread beyond the first kLimitBaseThreadCount threads will
+ // have only kLimitMaxExtraThreadStackLen bytes dumped.
+ let mut extra_thread_stack_len = MaxStackLen::None; // default to no maximum
+ if let Some(minidump_size_limit) = config.minidump_size_limit {
+ let estimated_total_stack_size = (num_threads * LIMIT_AVERAGE_THREAD_STACK_LENGTH) as u64;
+ let curr_pos = buffer.position();
+ let estimated_minidump_size =
+ curr_pos + estimated_total_stack_size + LIMIT_MINIDUMP_FUDGE_FACTOR;
+ if estimated_minidump_size > minidump_size_limit {
+ extra_thread_stack_len = MaxStackLen::Len(LIMIT_MAX_EXTRA_THREAD_STACK_LEN);
+ }
+ }
+
+ for (idx, item) in dumper.threads.clone().iter().enumerate() {
+ let mut thread = MDRawThread {
+ thread_id: item.tid.try_into()?,
+ suspend_count: 0,
+ priority_class: 0,
+ priority: 0,
+ teb: 0,
+ stack: MDMemoryDescriptor::default(),
+ thread_context: MDLocationDescriptor::default(),
+ };
+
+ // We have a different source of information for the crashing thread. If
+ // we used the actual state of the thread we would find it running in the
+ // signal handler with the alternative stack, which would be deeply
+ // unhelpful.
+ if config.crash_context.is_some() && thread.thread_id == config.blamed_thread as u32 {
+ let crash_context = config.crash_context.as_ref().unwrap();
+ let instruction_ptr = crash_context.get_instruction_pointer();
+ let stack_pointer = crash_context.get_stack_pointer();
+ fill_thread_stack(
+ config,
+ buffer,
+ dumper,
+ &mut thread,
+ instruction_ptr,
+ stack_pointer,
+ MaxStackLen::None,
+ )?;
+ // Copy 256 bytes around crashing instruction pointer to minidump.
+ let ip_memory_size: usize = 256;
+ // Bound it to the upper and lower bounds of the memory map
+ // it's contained within. If it's not in mapped memory,
+ // don't bother trying to write it.
+ for mapping in &dumper.mappings {
+ if instruction_ptr < mapping.start_address
+ || instruction_ptr >= mapping.start_address + mapping.size
+ {
+ continue;
+ }
+ // Try to get 128 bytes before and after the IP, but
+ // settle for whatever's available.
+ let mut ip_memory_d = MDMemoryDescriptor {
+ start_of_memory_range: std::cmp::max(
+ mapping.start_address,
+ instruction_ptr - ip_memory_size / 2,
+ ) as u64,
+ ..Default::default()
+ };
+
+ let end_of_range = std::cmp::min(
+ mapping.start_address + mapping.size,
+ instruction_ptr + ip_memory_size / 2,
+ ) as u64;
+ ip_memory_d.memory.data_size =
+ (end_of_range - ip_memory_d.start_of_memory_range) as u32;
+
+ let memory_copy = PtraceDumper::copy_from_process(
+ thread.thread_id as i32,
+ ip_memory_d.start_of_memory_range as *mut libc::c_void,
+ ip_memory_d.memory.data_size as usize,
+ )?;
+
+ let mem_section = MemoryArrayWriter::alloc_from_array(buffer, &memory_copy)?;
+ ip_memory_d.memory = mem_section.location();
+ config.memory_blocks.push(ip_memory_d);
+
+ break;
+ }
+ // let cpu = MemoryWriter::alloc(buffer, &memory_copy)?;
+ let mut cpu: RawContextCPU = Default::default();
+ let crash_context = config.crash_context.as_ref().unwrap();
+ crash_context.fill_cpu_context(&mut cpu);
+ let cpu_section = MemoryWriter::alloc_with_val(buffer, cpu)?;
+ thread.thread_context = cpu_section.location();
+
+ config.crashing_thread_context =
+ CrashingThreadContext::CrashContext(cpu_section.location());
+ } else {
+ let info = dumper.get_thread_info_by_index(idx)?;
+ let max_stack_len =
+ if config.minidump_size_limit.is_some() && idx >= LIMIT_BASE_THREAD_COUNT {
+ extra_thread_stack_len
+ } else {
+ MaxStackLen::None // default to no maximum for this thread
+ };
+ let instruction_ptr = info.get_instruction_pointer();
+ fill_thread_stack(
+ config,
+ buffer,
+ dumper,
+ &mut thread,
+ instruction_ptr,
+ info.stack_pointer,
+ max_stack_len,
+ )?;
+
+ let mut cpu = RawContextCPU::default();
+ info.fill_cpu_context(&mut cpu);
+ let cpu_section = MemoryWriter::<RawContextCPU>::alloc_with_val(buffer, cpu)?;
+ thread.thread_context = cpu_section.location();
+ if item.tid == config.blamed_thread {
+ // This is the crashing thread of a live process, but
+ // no context was provided, so set the crash address
+ // while the instruction pointer is already here.
+ config.crashing_thread_context = CrashingThreadContext::CrashContextPlusAddress((
+ cpu_section.location(),
+ instruction_ptr,
+ ));
+ }
+ }
+ thread_list.set_value_at(buffer, thread, idx)?;
+ }
+ Ok(dirent)
+}
+
+fn fill_thread_stack(
+ config: &mut MinidumpWriter,
+ buffer: &mut DumpBuf,
+ dumper: &PtraceDumper,
+ thread: &mut MDRawThread,
+ instruction_ptr: usize,
+ stack_ptr: usize,
+ max_stack_len: MaxStackLen,
+) -> Result<(), errors::SectionThreadListError> {
+ thread.stack.start_of_memory_range = stack_ptr.try_into()?;
+ thread.stack.memory.data_size = 0;
+ thread.stack.memory.rva = buffer.position() as u32;
+
+ if let Ok((mut stack, mut stack_len)) = dumper.get_stack_info(stack_ptr) {
+ if let MaxStackLen::Len(max_stack_len) = max_stack_len {
+ if stack_len > max_stack_len {
+ stack_len = max_stack_len;
+
+ // Skip empty chunks of length max_stack_len.
+ // Meaning != 0
+ if stack_len > 0 {
+ while stack + stack_len < stack_ptr {
+ stack += stack_len;
+ }
+ }
+ }
+ }
+
+ let mut stack_bytes = PtraceDumper::copy_from_process(
+ thread.thread_id.try_into()?,
+ stack as *mut libc::c_void,
+ stack_len,
+ )?;
+ let stack_pointer_offset = stack_ptr - stack;
+ if config.skip_stacks_if_mapping_unreferenced {
+ if let Some(principal_mapping) = &config.principal_mapping {
+ let low_addr = principal_mapping.system_mapping_info.start_address;
+ let high_addr = principal_mapping.system_mapping_info.end_address;
+ if (instruction_ptr < low_addr || instruction_ptr > high_addr)
+ && !principal_mapping
+ .stack_has_pointer_to_mapping(&stack_bytes, stack_pointer_offset)
+ {
+ return Ok(());
+ }
+ } else {
+ return Ok(());
+ }
+ }
+
+ if config.sanitize_stack {
+ dumper.sanitize_stack_copy(&mut stack_bytes, stack_ptr, stack_pointer_offset)?;
+ }
+
+ let stack_location = MDLocationDescriptor {
+ data_size: stack_bytes.len() as u32,
+ rva: buffer.position() as u32,
+ };
+ buffer.write_all(&stack_bytes);
+ thread.stack.start_of_memory_range = stack as u64;
+ thread.stack.memory = stack_location;
+ config.memory_blocks.push(thread.stack);
+ }
+ Ok(())
+}
diff --git a/third_party/rust/minidump-writer/src/linux/sections/thread_names_stream.rs b/third_party/rust/minidump-writer/src/linux/sections/thread_names_stream.rs
new file mode 100644
index 0000000000..bd8682b28a
--- /dev/null
+++ b/third_party/rust/minidump-writer/src/linux/sections/thread_names_stream.rs
@@ -0,0 +1,33 @@
+use super::*;
+
+pub fn write(
+ buffer: &mut DumpBuf,
+ dumper: &PtraceDumper,
+) -> Result<MDRawDirectory, errors::SectionThreadNamesError> {
+ // Only count threads that have a name
+ let num_threads = dumper.threads.iter().filter(|t| t.name.is_some()).count();
+ // Memory looks like this:
+ // <num_threads><thread_1><thread_2>...
+
+ let list_header = MemoryWriter::<u32>::alloc_with_val(buffer, num_threads as u32)?;
+
+ let mut dirent = MDRawDirectory {
+ stream_type: MDStreamType::ThreadNamesStream as u32,
+ location: list_header.location(),
+ };
+
+ let mut thread_list = MemoryArrayWriter::<MDRawThreadName>::alloc_array(buffer, num_threads)?;
+ dirent.location.data_size += thread_list.location().data_size;
+
+ for (idx, item) in dumper.threads.iter().enumerate() {
+ if let Some(name) = &item.name {
+ let pos = write_string_to_location(buffer, name)?;
+ let thread = MDRawThreadName {
+ thread_id: item.tid.try_into()?,
+ thread_name_rva: pos.rva.into(),
+ };
+ thread_list.set_value_at(buffer, thread, idx)?;
+ }
+ }
+ Ok(dirent)
+}
diff --git a/third_party/rust/minidump-writer/src/linux/thread_info.rs b/third_party/rust/minidump-writer/src/linux/thread_info.rs
new file mode 100644
index 0000000000..2b653438e1
--- /dev/null
+++ b/third_party/rust/minidump-writer/src/linux/thread_info.rs
@@ -0,0 +1,169 @@
+use crate::errors::ThreadInfoError;
+use nix::{errno::Errno, sys::ptrace, unistd};
+use std::{
+ io::{self, BufRead},
+ path,
+};
+
+type Result<T> = std::result::Result<T, ThreadInfoError>;
+
+pub type Pid = i32;
+
+cfg_if::cfg_if! {
+ if #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] {
+ mod x86;
+ pub type ThreadInfo = x86::ThreadInfoX86;
+ } else if #[cfg(target_arch = "arm")] {
+ mod arm;
+ pub type ThreadInfo = arm::ThreadInfoArm;
+ } else if #[cfg(target_arch = "aarch64")] {
+ mod aarch64;
+ pub type ThreadInfo = aarch64::ThreadInfoAarch64;
+ } else if #[cfg(target_arch = "mips")] {
+ mod mips;
+ pub type ThreadInfo = mips::ThreadInfoMips;
+ }
+}
+
+#[derive(Debug)]
+#[allow(non_camel_case_types, dead_code)]
+enum NT_Elf {
+ NT_NONE = 0,
+ NT_PRSTATUS = 1,
+ NT_PRFPREGSET = 2,
+ //NT_PRPSINFO = 3,
+ //NT_TASKSTRUCT = 4,
+ //NT_AUXV = 6,
+}
+
+#[inline]
+pub fn to_u128(slice: &[u32]) -> &[u128] {
+ unsafe { std::slice::from_raw_parts(slice.as_ptr().cast(), slice.len().saturating_div(4)) }
+}
+
+#[inline]
+pub fn copy_registers(dst: &mut [u128], src: &[u128]) {
+ let to_copy = std::cmp::min(dst.len(), src.len());
+ dst[..to_copy].copy_from_slice(&src[..to_copy]);
+}
+
+#[inline]
+pub fn copy_u32_registers(dst: &mut [u128], src: &[u32]) {
+ copy_registers(dst, to_u128(src));
+}
+
+trait CommonThreadInfo {
+ fn get_ppid_and_tgid(tid: Pid) -> Result<(Pid, Pid)> {
+ let mut ppid = -1;
+ let mut tgid = -1;
+
+ let status_path = path::PathBuf::from(format!("/proc/{}/status", tid));
+ let status_file = std::fs::File::open(status_path)?;
+ for line in io::BufReader::new(status_file).lines() {
+ let l = line?;
+ let start = l
+ .get(0..6)
+ .ok_or_else(|| ThreadInfoError::InvalidProcStatusFile(tid, l.clone()))?;
+ match start {
+ "Tgid:\t" => {
+ tgid = l
+ .get(6..)
+ .ok_or_else(|| ThreadInfoError::InvalidProcStatusFile(tid, l.clone()))?
+ .parse::<Pid>()?;
+ }
+ "PPid:\t" => {
+ ppid = l
+ .get(6..)
+ .ok_or_else(|| ThreadInfoError::InvalidProcStatusFile(tid, l.clone()))?
+ .parse::<Pid>()?;
+ }
+ _ => continue,
+ }
+ }
+ if ppid == -1 || tgid == -1 {
+ return Err(ThreadInfoError::InvalidPid(
+ format!("/proc/{}/status", tid),
+ ppid,
+ tgid,
+ ));
+ }
+ Ok((ppid, tgid))
+ }
+
+ /// SLIGHTLY MODIFIED COPY FROM CRATE nix
+ /// Function for ptrace requests that return values from the data field.
+ /// Some ptrace get requests populate structs or larger elements than `c_long`
+ /// and therefore use the data field to return values. This function handles these
+ /// requests.
+ fn ptrace_get_data<T>(
+ request: ptrace::Request,
+ flag: Option<NT_Elf>,
+ pid: nix::unistd::Pid,
+ ) -> Result<T> {
+ let mut data = std::mem::MaybeUninit::uninit();
+ let res = unsafe {
+ libc::ptrace(
+ request as ptrace::RequestType,
+ libc::pid_t::from(pid),
+ flag.unwrap_or(NT_Elf::NT_NONE),
+ data.as_mut_ptr(),
+ )
+ };
+ Errno::result(res)?;
+ Ok(unsafe { data.assume_init() })
+ }
+
+ /// SLIGHTLY MODIFIED COPY FROM CRATE nix
+ /// Function for ptrace requests that return values from the data field.
+ /// Some ptrace get requests populate structs or larger elements than `c_long`
+ /// and therefore use the data field to return values. This function handles these
+ /// requests.
+ fn ptrace_get_data_via_io<T>(
+ request: ptrace::Request,
+ flag: Option<NT_Elf>,
+ pid: nix::unistd::Pid,
+ ) -> Result<T> {
+ let mut data = std::mem::MaybeUninit::<T>::uninit();
+ let io = libc::iovec {
+ iov_base: data.as_mut_ptr().cast(),
+ iov_len: std::mem::size_of::<T>(),
+ };
+ let res = unsafe {
+ libc::ptrace(
+ request as ptrace::RequestType,
+ libc::pid_t::from(pid),
+ flag.unwrap_or(NT_Elf::NT_NONE),
+ &io as *const _,
+ )
+ };
+ Errno::result(res)?;
+ Ok(unsafe { data.assume_init() })
+ }
+
+ /// COPY FROM CRATE nix BECAUSE ITS NOT PUBLIC
+ fn ptrace_peek(
+ request: ptrace::Request,
+ pid: unistd::Pid,
+ addr: ptrace::AddressType,
+ data: *mut libc::c_void,
+ ) -> nix::Result<libc::c_long> {
+ let ret = unsafe {
+ Errno::clear();
+ libc::ptrace(
+ request as ptrace::RequestType,
+ libc::pid_t::from(pid),
+ addr,
+ data,
+ )
+ };
+ match Errno::result(ret) {
+ Ok(..) | Err(Errno::UnknownErrno) => Ok(ret),
+ err @ Err(..) => err,
+ }
+ }
+}
+impl ThreadInfo {
+ pub fn create(pid: Pid, tid: Pid) -> std::result::Result<Self, ThreadInfoError> {
+ Self::create_impl(pid, tid)
+ }
+}
diff --git a/third_party/rust/minidump-writer/src/linux/thread_info/aarch64.rs b/third_party/rust/minidump-writer/src/linux/thread_info/aarch64.rs
new file mode 100644
index 0000000000..4c69b54dd4
--- /dev/null
+++ b/third_party/rust/minidump-writer/src/linux/thread_info/aarch64.rs
@@ -0,0 +1,120 @@
+use super::{CommonThreadInfo, Pid};
+use crate::{
+ errors::ThreadInfoError,
+ minidump_cpu::{RawContextCPU, FP_REG_COUNT, GP_REG_COUNT},
+};
+#[cfg(not(target_os = "android"))]
+use nix::sys::ptrace;
+
+/// https://github.com/rust-lang/libc/pull/2719
+#[derive(Debug)]
+#[allow(non_camel_case_types)]
+pub struct user_fpsimd_struct {
+ pub vregs: [u128; 32],
+ pub fpsr: u32,
+ pub fpcr: u32,
+}
+
+type Result<T> = std::result::Result<T, ThreadInfoError>;
+
+#[cfg(target_arch = "aarch64")]
+#[derive(Debug)]
+pub struct ThreadInfoAarch64 {
+ pub stack_pointer: usize,
+ pub tgid: Pid, // thread group id
+ pub ppid: Pid, // parent process
+ pub regs: libc::user_regs_struct,
+ pub fpregs: user_fpsimd_struct,
+}
+
+impl CommonThreadInfo for ThreadInfoAarch64 {}
+
+impl ThreadInfoAarch64 {
+ pub fn get_instruction_pointer(&self) -> usize {
+ self.regs.pc as usize
+ }
+
+ // nix currently doesn't support PTRACE_GETFPREGS, so we have to do it ourselves
+ fn getfpregs(pid: Pid) -> Result<user_fpsimd_struct> {
+ cfg_if::cfg_if! {
+ if #[cfg(target_os = "android")] {
+ // TODO: nix restricts PTRACE_GETFPREGS to arm android for some reason
+ let mut data = std::mem::MaybeUninit::<user_fpsimd_struct>::uninit();
+ let res = unsafe {
+ libc::ptrace(
+ 14,
+ libc::pid_t::from(pid),
+ super::NT_Elf::NT_NONE,
+ data.as_mut_ptr(),
+ )
+ };
+ nix::errno::Errno::result(res)?;
+ Ok(unsafe { data.assume_init() })
+ } else {
+ Self::ptrace_get_data_via_io::<user_fpsimd_struct>(
+ ptrace::Request::PTRACE_GETREGSET,
+ Some(super::NT_Elf::NT_PRFPREGSET),
+ nix::unistd::Pid::from_raw(pid),
+ )
+ }
+ }
+ }
+
+ fn getregs(pid: Pid) -> Result<libc::user_regs_struct> {
+ cfg_if::cfg_if! {
+ if #[cfg(target_os = "android")] {
+ // TODO: nix restricts PTRACE_GETREGS to arm android for some reason
+ let mut data = std::mem::MaybeUninit::<libc::user_regs_struct>::uninit();
+ let res = unsafe {
+ libc::ptrace(
+ 12,
+ libc::pid_t::from(pid),
+ super::NT_Elf::NT_NONE,
+ data.as_mut_ptr(),
+ )
+ };
+ nix::errno::Errno::result(res)?;
+ Ok(unsafe { data.assume_init() })
+ } else {
+ Self::ptrace_get_data_via_io::<libc::user_regs_struct>(
+ ptrace::Request::PTRACE_GETREGSET,
+ Some(super::NT_Elf::NT_PRSTATUS),
+ nix::unistd::Pid::from_raw(pid),
+ )
+ }
+ }
+ }
+
+ pub fn fill_cpu_context(&self, out: &mut RawContextCPU) {
+ out.context_flags =
+ minidump_common::format::ContextFlagsArm64Old::CONTEXT_ARM64_OLD_FULL.bits() as u64;
+
+ out.cpsr = self.regs.pstate as u32;
+ out.iregs[..GP_REG_COUNT].copy_from_slice(&self.regs.regs[..GP_REG_COUNT]);
+ out.sp = self.regs.sp;
+ // Note that in breakpad this was the last member of the iregs field
+ // which was 33 in length, but in rust-minidump it is its own separate
+ // field instead
+ out.pc = self.regs.pc;
+
+ out.fpsr = self.fpregs.fpsr;
+ out.fpcr = self.fpregs.fpcr;
+ out.float_regs[..FP_REG_COUNT].copy_from_slice(&self.fpregs.vregs[..FP_REG_COUNT]);
+ }
+
+ pub fn create_impl(_pid: Pid, tid: Pid) -> Result<Self> {
+ let (ppid, tgid) = Self::get_ppid_and_tgid(tid)?;
+ let regs = Self::getregs(tid)?;
+ let fpregs = Self::getfpregs(tid)?;
+
+ let stack_pointer = regs.sp as usize;
+
+ Ok(Self {
+ stack_pointer,
+ tgid,
+ ppid,
+ regs,
+ fpregs,
+ })
+ }
+}
diff --git a/third_party/rust/minidump-writer/src/linux/thread_info/arm.rs b/third_party/rust/minidump-writer/src/linux/thread_info/arm.rs
new file mode 100644
index 0000000000..6359f9b8f5
--- /dev/null
+++ b/third_party/rust/minidump-writer/src/linux/thread_info/arm.rs
@@ -0,0 +1,107 @@
+use super::{CommonThreadInfo, Pid};
+use crate::{errors::ThreadInfoError, minidump_cpu::RawContextCPU};
+use nix::sys::ptrace;
+
+type Result<T> = std::result::Result<T, ThreadInfoError>;
+// These are not (yet) part of the libc-crate
+// #[repr(C)]
+// #[derive(Debug, Eq, Hash, PartialEq, Copy, Clone, Default)]
+// pub struct fp_reg {
+// // TODO: No bitfields at the moment, just the next best integer-type
+// sign1: u8,
+// unused: u16,
+// sign2: u8,
+// exponent: u16,
+// j: u8,
+// mantissa1: u32,
+// mantissa2: u32,
+// // unsigned int sign1:1;
+// // unsigned int unused:15;
+// // unsigned int sign2:1;
+// // unsigned int exponent:14;
+// // unsigned int j:1;
+// // unsigned int mantissa1:31;
+// // unsigned int mantissa0:32;
+// }
+
+#[repr(C)]
+#[derive(Debug, Eq, Hash, PartialEq, Copy, Clone, Default)]
+pub struct user_fpregs {
+ // fpregs: [fp_reg; 8],
+ fpregs: [u32; 8 * 3], // Fields not used, so shortening the struct to 3 x u32
+ fpsr: u32,
+ fpcr: u32,
+ ftype: [u8; 8],
+ init_flag: u32,
+}
+
+#[repr(C)]
+#[derive(Debug, Eq, Hash, PartialEq, Copy, Clone, Default)]
+pub struct user_regs {
+ uregs: [u32; 18],
+}
+
+#[derive(Debug)]
+pub struct ThreadInfoArm {
+ pub stack_pointer: usize,
+ pub tgid: Pid, // thread group id
+ pub ppid: Pid, // parent process
+ pub regs: user_regs,
+ pub fpregs: user_fpregs,
+}
+
+impl CommonThreadInfo for ThreadInfoArm {}
+
+impl ThreadInfoArm {
+ // nix currently doesn't support PTRACE_GETFPREGS, so we have to do it ourselves
+ fn getfpregs(pid: Pid) -> Result<user_fpregs> {
+ Self::ptrace_get_data::<user_fpregs>(
+ ptrace::Request::PTRACE_GETFPREGS,
+ None,
+ nix::unistd::Pid::from_raw(pid),
+ )
+ }
+
+ // nix currently doesn't support PTRACE_GETFPREGS, so we have to do it ourselves
+ fn getregs(pid: Pid) -> Result<user_regs> {
+ Self::ptrace_get_data::<user_regs>(
+ ptrace::Request::PTRACE_GETFPREGS,
+ None,
+ nix::unistd::Pid::from_raw(pid),
+ )
+ }
+
+ pub fn get_instruction_pointer(&self) -> usize {
+ self.regs.uregs[15] as usize
+ }
+
+ pub fn fill_cpu_context(&self, out: &mut RawContextCPU) {
+ out.context_flags =
+ crate::minidump_format::format::ContextFlagsArm::CONTEXT_ARM_FULL.bits();
+
+ out.iregs.copy_from_slice(&self.regs.uregs[..16]);
+ // No CPSR register in ThreadInfo(it's not accessible via ptrace)
+ out.cpsr = 0;
+
+ #[cfg(not(target_os = "android"))]
+ {
+ out.float_save.fpscr = self.fpregs.fpsr as u64 | ((self.fpregs.fpcr as u64) << 32);
+ }
+ }
+
+ pub fn create_impl(_pid: Pid, tid: Pid) -> Result<Self> {
+ let (ppid, tgid) = Self::get_ppid_and_tgid(tid)?;
+ let regs = Self::getregs(tid)?;
+ let fpregs = Self::getfpregs(tid)?;
+
+ let stack_pointer = regs.uregs[13] as usize;
+
+ Ok(ThreadInfoArm {
+ stack_pointer,
+ tgid,
+ ppid,
+ regs,
+ fpregs,
+ })
+ }
+}
diff --git a/third_party/rust/minidump-writer/src/linux/thread_info/mips.rs b/third_party/rust/minidump-writer/src/linux/thread_info/mips.rs
new file mode 100644
index 0000000000..ff263f5f7b
--- /dev/null
+++ b/third_party/rust/minidump-writer/src/linux/thread_info/mips.rs
@@ -0,0 +1,56 @@
+use super::Pid;
+use crate::errors::ThreadInfoError;
+use libc;
+
+type Result<T> = std::result::Result<T, ThreadInfoError>;
+
+#[derive(Debug)]
+pub struct ThreadInfoMips {
+ pub stack_pointer: libc::c_ulonglong,
+ pub tgid: Pid, // thread group id
+ pub ppid: Pid, // parent process
+ // Use the structure defined in <sys/ucontext.h>
+ pub mcontext: libc::mcontext_t,
+}
+
+impl ThreadInfoMips {
+ pub fn get_instruction_pointer(&self) -> libc::c_ulonglong {
+ self.mcontext.pc
+ }
+
+ pub fn fill_cpu_context(&self, out: &mut RawContextCPU) {
+ // #if _MIPS_SIM == _ABI64
+ // out->context_flags = MD_CONTEXT_MIPS64_FULL;
+ // #elif _MIPS_SIM == _ABIO32
+ // out->context_flags = MD_CONTEXT_MIPS_FULL;
+ for idx in 0..MD_CONTEXT_MIPS_GPR_COUNT {
+ out.iregs[idx] = self.mcontext.gregs[idx];
+ }
+
+ out.mdhi = self.mcontext.mdhi;
+ out.mdlo = self.mcontext.mdlo;
+ out.dsp_control = self.mcontext.dsp;
+
+ out.hi[0] = self.mcontext.hi1;
+ out.lo[0] = self.mcontext.lo1;
+ out.hi[1] = self.mcontext.hi2;
+ out.lo[1] = self.mcontext.lo2;
+ out.hi[2] = self.mcontext.hi3;
+ out.lo[2] = self.mcontext.lo3;
+
+ out.epc = self.mcontext.pc;
+ out.badvaddr = 0; // Not stored in mcontext
+ out.status = 0; // Not stored in mcontext
+ out.cause = 0; // Not stored in mcontext
+
+ for idx in 0..MD_FLOATINGSAVEAREA_MIPS_FPR_COUNT {
+ out.float_save.regs[idx] = self.mcontext.fpregs.fp_r.fp_fregs[idx]._fp_fregs;
+ }
+
+ out.float_save.fpcsr = mcontext.fpc_csr;
+
+ // #if _MIPS_SIM == _ABIO32
+ // out.float_save.fir = self.mcontext.fpc_eir;
+ // #endif
+ }
+}
diff --git a/third_party/rust/minidump-writer/src/linux/thread_info/x86.rs b/third_party/rust/minidump-writer/src/linux/thread_info/x86.rs
new file mode 100644
index 0000000000..9ede4f44b6
--- /dev/null
+++ b/third_party/rust/minidump-writer/src/linux/thread_info/x86.rs
@@ -0,0 +1,301 @@
+use super::{CommonThreadInfo, NT_Elf, Pid};
+use crate::{errors::ThreadInfoError, minidump_cpu::RawContextCPU, minidump_format::format};
+use core::mem::size_of_val;
+use libc::user;
+use nix::{sys::ptrace, unistd};
+use scroll::Pwrite;
+
+type Result<T> = std::result::Result<T, ThreadInfoError>;
+
+const NUM_DEBUG_REGISTERS: usize = 8;
+
+pub struct ThreadInfoX86 {
+ pub stack_pointer: usize,
+ pub tgid: Pid, // thread group id
+ pub ppid: Pid, // parent process
+ pub regs: libc::user_regs_struct,
+ pub fpregs: libc::user_fpregs_struct,
+ #[cfg(target_arch = "x86_64")]
+ pub dregs: [libc::c_ulonglong; NUM_DEBUG_REGISTERS],
+ #[cfg(target_arch = "x86")]
+ pub dregs: [libc::c_int; NUM_DEBUG_REGISTERS],
+ #[cfg(target_arch = "x86")]
+ pub fpxregs: libc::user_fpxregs_struct,
+}
+
+impl CommonThreadInfo for ThreadInfoX86 {}
+
+impl ThreadInfoX86 {
+ // nix currently doesn't support PTRACE_GETREGSET, so we have to do it ourselves
+ fn getregset(pid: Pid) -> Result<libc::user_regs_struct> {
+ Self::ptrace_get_data_via_io::<libc::user_regs_struct>(
+ ptrace::Request::PTRACE_GETREGSET,
+ Some(NT_Elf::NT_PRSTATUS),
+ nix::unistd::Pid::from_raw(pid),
+ )
+ }
+
+ // nix currently doesn't support PTRACE_GETREGSET, so we have to do it ourselves
+ fn getfpregset(pid: Pid) -> Result<libc::user_fpregs_struct> {
+ Self::ptrace_get_data_via_io::<libc::user_fpregs_struct>(
+ ptrace::Request::PTRACE_GETREGSET,
+ Some(NT_Elf::NT_PRFPREGSET),
+ nix::unistd::Pid::from_raw(pid),
+ )
+ }
+
+ // nix currently doesn't support PTRACE_GETFPREGS, so we have to do it ourselves
+ fn getfpregs(pid: Pid) -> Result<libc::user_fpregs_struct> {
+ Self::ptrace_get_data::<libc::user_fpregs_struct>(
+ ptrace::Request::PTRACE_GETFPREGS,
+ None,
+ nix::unistd::Pid::from_raw(pid),
+ )
+ }
+
+ // nix currently doesn't support PTRACE_GETFPXREGS, so we have to do it ourselves
+ #[cfg(target_arch = "x86")]
+ fn getfpxregs(pid: Pid) -> Result<libc::user_fpxregs_struct> {
+ Self::ptrace_get_data::<libc::user_fpxregs_struct>(
+ ptrace::Request::PTRACE_GETFPXREGS,
+ None,
+ nix::unistd::Pid::from_raw(pid),
+ )
+ }
+
+ fn peek_user(pid: Pid, addr: ptrace::AddressType) -> nix::Result<libc::c_long> {
+ Self::ptrace_peek(
+ ptrace::Request::PTRACE_PEEKUSER,
+ nix::unistd::Pid::from_raw(pid),
+ addr,
+ std::ptr::null_mut(),
+ )
+ }
+
+ pub fn create_impl(_pid: Pid, tid: Pid) -> Result<Self> {
+ let (ppid, tgid) = Self::get_ppid_and_tgid(tid)?;
+ let regs = Self::getregset(tid).or_else(|_| ptrace::getregs(unistd::Pid::from_raw(tid)))?;
+ let fpregs = Self::getfpregset(tid).or_else(|_| Self::getfpregs(tid))?;
+ #[cfg(target_arch = "x86")]
+ let fpxregs: libc::user_fpxregs_struct;
+ #[cfg(target_arch = "x86")]
+ {
+ if cfg!(target_feature = "fxsr") {
+ fpxregs = Self::getfpxregs(tid)?;
+ } else {
+ fpxregs = unsafe { std::mem::zeroed() };
+ }
+ }
+
+ #[cfg(target_arch = "x86_64")]
+ let mut dregs: [libc::c_ulonglong; NUM_DEBUG_REGISTERS] = [0; NUM_DEBUG_REGISTERS];
+ #[cfg(target_arch = "x86")]
+ let mut dregs: [libc::c_int; NUM_DEBUG_REGISTERS] = [0; NUM_DEBUG_REGISTERS];
+
+ let debug_offset = memoffset::offset_of!(user, u_debugreg);
+ let elem_offset = size_of_val(&dregs[0]);
+ for (idx, dreg) in dregs.iter_mut().enumerate() {
+ let chunk = Self::peek_user(
+ tid,
+ (debug_offset + idx * elem_offset) as ptrace::AddressType,
+ )?;
+ #[cfg(target_arch = "x86_64")]
+ {
+ *dreg = chunk as u64; // libc / ptrace is very messy wrt int types used...
+ }
+ #[cfg(target_arch = "x86")]
+ {
+ *dreg = chunk as i32; // libc / ptrace is very messy wrt int types used...
+ }
+ }
+
+ #[cfg(target_arch = "x86_64")]
+ let stack_pointer = regs.rsp as usize;
+ #[cfg(target_arch = "x86")]
+ let stack_pointer = regs.esp as usize;
+
+ Ok(Self {
+ stack_pointer,
+ tgid,
+ ppid,
+ regs,
+ fpregs,
+ dregs,
+ #[cfg(target_arch = "x86")]
+ fpxregs,
+ })
+ }
+
+ #[cfg(target_arch = "x86_64")]
+ pub fn get_instruction_pointer(&self) -> usize {
+ self.regs.rip as usize
+ }
+
+ #[cfg(target_arch = "x86")]
+ pub fn get_instruction_pointer(&self) -> usize {
+ self.regs.eip as usize
+ }
+
+ #[cfg(target_arch = "x86_64")]
+ pub fn fill_cpu_context(&self, out: &mut RawContextCPU) {
+ use format::ContextFlagsAmd64;
+
+ out.context_flags = ContextFlagsAmd64::CONTEXT_AMD64_FULL.bits()
+ | ContextFlagsAmd64::CONTEXT_AMD64_SEGMENTS.bits();
+
+ out.cs = self.regs.cs as u16; // TODO: This is u64, do we loose information by doing this?
+
+ out.ds = self.regs.ds as u16; // TODO: This is u64, do we loose information by doing this?
+ out.es = self.regs.es as u16; // TODO: This is u64, do we loose information by doing this?
+ out.fs = self.regs.fs as u16; // TODO: This is u64, do we loose information by doing this?
+ out.gs = self.regs.gs as u16; // TODO: This is u64, do we loose information by doing this?
+
+ out.ss = self.regs.ss as u16; // TODO: This is u64, do we loose information by doing this?
+ out.eflags = self.regs.eflags as u32; // TODO: This is u64, do we loose information by doing this?
+
+ out.dr0 = self.dregs[0];
+ out.dr1 = self.dregs[1];
+ out.dr2 = self.dregs[2];
+ out.dr3 = self.dregs[3];
+ // 4 and 5 deliberatly omitted because they aren't included in the minidump
+ // format.
+ out.dr6 = self.dregs[6];
+ out.dr7 = self.dregs[7];
+
+ out.rax = self.regs.rax;
+ out.rcx = self.regs.rcx;
+ out.rdx = self.regs.rdx;
+ out.rbx = self.regs.rbx;
+
+ out.rsp = self.regs.rsp;
+
+ out.rbp = self.regs.rbp;
+ out.rsi = self.regs.rsi;
+ out.rdi = self.regs.rdi;
+ out.r8 = self.regs.r8;
+ out.r9 = self.regs.r9;
+ out.r10 = self.regs.r10;
+ out.r11 = self.regs.r11;
+ out.r12 = self.regs.r12;
+ out.r13 = self.regs.r13;
+ out.r14 = self.regs.r14;
+ out.r15 = self.regs.r15;
+
+ out.rip = self.regs.rip;
+
+ {
+ let fs = &self.fpregs;
+ let mut float_save = crate::minidump_cpu::FloatStateCPU {
+ control_word: fs.cwd,
+ status_word: fs.swd,
+ tag_word: fs.ftw as u8,
+ error_opcode: fs.fop,
+ error_offset: fs.rip as u32,
+ data_offset: fs.rdp as u32,
+ error_selector: 0, // We don't have this.
+ data_selector: 0, // We don't have this.
+ mx_csr: fs.mxcsr,
+ mx_csr_mask: fs.mxcr_mask,
+ ..Default::default()
+ };
+
+ super::copy_u32_registers(&mut float_save.float_registers, &fs.st_space);
+ super::copy_u32_registers(&mut float_save.xmm_registers, &fs.xmm_space);
+
+ out.float_save
+ .pwrite_with(float_save, 0, scroll::Endian::Little)
+ .expect("this is impossible");
+ }
+ }
+
+ #[cfg(target_arch = "x86")]
+ pub fn fill_cpu_context(&self, out: &mut RawContextCPU) {
+ out.context_flags = format::ContextFlagsX86::CONTEXT_X86_ALL.bits();
+
+ out.dr0 = self.dregs[0] as u32;
+ out.dr3 = self.dregs[3] as u32;
+ out.dr1 = self.dregs[1] as u32;
+ out.dr2 = self.dregs[2] as u32;
+ // 4 and 5 deliberatly omitted because they aren't included in the minidump
+ // format.
+ out.dr6 = self.dregs[6] as u32;
+ out.dr7 = self.dregs[7] as u32;
+
+ out.gs = self.regs.xgs as u32;
+ out.fs = self.regs.xfs as u32;
+ out.es = self.regs.xes as u32;
+ out.ds = self.regs.xds as u32;
+
+ out.edi = self.regs.edi as u32;
+ out.esi = self.regs.esi as u32;
+ out.ebx = self.regs.ebx as u32;
+ out.edx = self.regs.edx as u32;
+ out.ecx = self.regs.ecx as u32;
+ out.eax = self.regs.eax as u32;
+
+ out.ebp = self.regs.ebp as u32;
+ out.eip = self.regs.eip as u32;
+ out.cs = self.regs.xcs as u32;
+ out.eflags = self.regs.eflags as u32;
+ out.esp = self.regs.esp as u32;
+ out.ss = self.regs.xss as u32;
+
+ out.float_save.control_word = self.fpregs.cwd as u32;
+ out.float_save.status_word = self.fpregs.swd as u32;
+ out.float_save.tag_word = self.fpregs.twd as u32;
+ out.float_save.error_offset = self.fpregs.fip as u32;
+ out.float_save.error_selector = self.fpregs.fcs as u32;
+ out.float_save.data_offset = self.fpregs.foo as u32;
+ out.float_save.data_selector = self.fpregs.fos as u32;
+
+ {
+ let ra = &mut out.float_save.register_area;
+ // 8 registers * 10 bytes per register.
+ for (idx, block) in self.fpregs.st_space.iter().enumerate() {
+ let offset = idx * std::mem::size_of::<u32>();
+ if offset >= ra.len() {
+ break;
+ }
+
+ ra.pwrite_with(block, offset, scroll::Endian::Little)
+ .expect("this is impossible");
+ }
+ }
+
+ #[allow(unused_assignments)]
+ {
+ let mut offset = 0;
+ macro_rules! write_er {
+ ($reg:expr) => {
+ offset += out
+ .extended_registers
+ .pwrite_with($reg, offset, scroll::Endian::Little)
+ .unwrap()
+ };
+ }
+
+ // This matches the Intel fpsave format.
+ write_er!(self.fpregs.cwd as u16);
+ write_er!(self.fpregs.swd as u16);
+ write_er!(self.fpregs.twd as u16);
+ write_er!(self.fpxregs.fop);
+ write_er!(self.fpxregs.fip);
+ write_er!(self.fpxregs.fcs);
+ write_er!(self.fpregs.foo);
+ write_er!(self.fpregs.fos);
+ write_er!(self.fpxregs.mxcsr);
+
+ offset = 32;
+
+ for val in &self.fpxregs.st_space {
+ write_er!(val);
+ }
+
+ debug_assert_eq!(offset, 160);
+
+ for val in &self.fpxregs.xmm_space {
+ write_er!(val);
+ }
+ }
+ }
+}