diff --git a/Cargo.toml b/Cargo.toml index 237afe6a..7f5f8966 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,22 +1,33 @@ [package] -name = "minidump_writer_linux" +name = "minidump-writer" version = "0.1.0" authors = ["Martin Sirringhaus"] -edition = "2018" +edition = "2021" license = "MIT" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] -tempfile = "3.1.0" -nix = "0.15" -libc = "0.2.74" -memoffset = "0.5.1" byteorder = "1.3.2" -memmap2 = "0.2.2" -goblin = "0.1.2" +cfg-if = "1.0" +goblin = "0.5" +libc = "0.2.74" +memmap2 = "0.5" +memoffset = "0.6" +minidump-common = "0.10" +nix = "0.23" +scroll = "0.11" +tempfile = "3.1.0" thiserror = "1.0.21" +[dependencies.crash-context] +#git = "https://github.com/EmbarkStudios/crash-handling" +#branch = "impl" +path = "../crash-handling/crash-context" +features = ["fill-minidump"] + [dev-dependencies] -minidump = {git = "https://github.com/luser/rust-minidump", rev="9652b3b" } -minidump-common = {git = "https://github.com/luser/rust-minidump", rev="9652b3b" } +debugid = "0.7.3" +minidump = "0.10" + +[patch.crates-io] +minidump = { git = "https://github.com/EmbarkStudios/rust-minidump", branch = "master" } +minidump-common = { git = "https://github.com/EmbarkStudios/rust-minidump", branch = "master" } diff --git a/src/bin/test.rs b/src/bin/test.rs index 9899e8f9..bbfe2fe8 100644 --- a/src/bin/test.rs +++ b/src/bin/test.rs @@ -1,10 +1,11 @@ // This binary shouldn't be under /src, but under /tests, but that is // currently not possible (https://github.com/rust-lang/cargo/issues/4356) -use minidump_writer_linux::linux_ptrace_dumper::{LinuxPtraceDumper, AT_SYSINFO_EHDR}; -use minidump_writer_linux::{linux_ptrace_dumper, LINUX_GATE_LIBRARY_NAME}; +use minidump_writer::{ + ptrace_dumper::{PtraceDumper, AT_SYSINFO_EHDR}, + LINUX_GATE_LIBRARY_NAME, +}; use nix::sys::mman::{mmap, MapFlags, ProtFlags}; use nix::unistd::getppid; -use std::convert::TryInto; use std::env; use std::error; use std::result; @@ -24,13 +25,13 @@ macro_rules! test { fn test_setup() -> Result<()> { let ppid = getppid(); - linux_ptrace_dumper::LinuxPtraceDumper::new(ppid.as_raw())?; + PtraceDumper::new(ppid.as_raw())?; Ok(()) } fn test_thread_list() -> Result<()> { let ppid = getppid(); - let dumper = linux_ptrace_dumper::LinuxPtraceDumper::new(ppid.as_raw())?; + let dumper = PtraceDumper::new(ppid.as_raw())?; test!(!dumper.threads.is_empty(), "No threads")?; test!( dumper @@ -46,9 +47,9 @@ fn test_thread_list() -> Result<()> { fn test_copy_from_process(stack_var: usize, heap_var: usize) -> Result<()> { let ppid = getppid().as_raw(); - let mut dumper = linux_ptrace_dumper::LinuxPtraceDumper::new(ppid)?; + let mut dumper = PtraceDumper::new(ppid)?; dumper.suspend_threads()?; - let stack_res = LinuxPtraceDumper::copy_from_process(ppid, stack_var as *mut libc::c_void, 1)?; + let stack_res = PtraceDumper::copy_from_process(ppid, stack_var as *mut libc::c_void, 1)?; let expected_stack: libc::c_long = 0x11223344; test!( @@ -56,7 +57,7 @@ fn test_copy_from_process(stack_var: usize, heap_var: usize) -> Result<()> { "stack var not correct" )?; - let heap_res = LinuxPtraceDumper::copy_from_process(ppid, heap_var as *mut libc::c_void, 1)?; + let heap_res = PtraceDumper::copy_from_process(ppid, heap_var as *mut libc::c_void, 1)?; let expected_heap: libc::c_long = 0x55667788; test!( heap_res == expected_heap.to_ne_bytes(), @@ -68,7 +69,7 @@ fn test_copy_from_process(stack_var: usize, heap_var: usize) -> Result<()> { fn test_find_mappings(addr1: usize, addr2: usize) -> Result<()> { let ppid = getppid(); - let dumper = linux_ptrace_dumper::LinuxPtraceDumper::new(ppid.as_raw())?; + let dumper = PtraceDumper::new(ppid.as_raw())?; dumper .find_mapping(addr1) .ok_or("No mapping for addr1 found")?; @@ -85,7 +86,7 @@ fn test_file_id() -> Result<()> { let ppid = getppid().as_raw(); let exe_link = format!("/proc/{}/exe", ppid); let exe_name = std::fs::read_link(&exe_link)?.into_os_string(); - let mut dumper = linux_ptrace_dumper::LinuxPtraceDumper::new(getppid().as_raw())?; + let mut dumper = PtraceDumper::new(getppid().as_raw())?; let mut found_exe = None; for (idx, mapping) in dumper.mappings.iter().enumerate() { if mapping.name.as_ref().map(|x| x.into()).as_ref() == Some(&exe_name) { @@ -102,7 +103,7 @@ fn test_file_id() -> Result<()> { fn test_merged_mappings(path: String, mapped_mem: usize, mem_size: usize) -> Result<()> { // Now check that LinuxPtraceDumper interpreted the mappings properly. - let dumper = linux_ptrace_dumper::LinuxPtraceDumper::new(getppid().as_raw())?; + let dumper = PtraceDumper::new(getppid().as_raw())?; let mut mapping_count = 0; for map in &dumper.mappings { if map.name == Some(path.clone()) { @@ -120,13 +121,13 @@ fn test_merged_mappings(path: String, mapped_mem: usize, mem_size: usize) -> Res fn test_linux_gate_mapping_id() -> Result<()> { let ppid = getppid().as_raw(); - let mut dumper = linux_ptrace_dumper::LinuxPtraceDumper::new(ppid)?; + let mut dumper = PtraceDumper::new(ppid)?; let mut found_linux_gate = false; for mut mapping in dumper.mappings.clone() { if mapping.name.as_deref() == Some(LINUX_GATE_LIBRARY_NAME) { found_linux_gate = true; dumper.suspend_threads()?; - let id = LinuxPtraceDumper::elf_identifier_for_mapping(&mut mapping, ppid)?; + let id = PtraceDumper::elf_identifier_for_mapping(&mut mapping, ppid)?; test!(!id.is_empty(), "id-vec is empty")?; test!(id.iter().any(|&x| x > 0), "all id elements are 0")?; dumper.resume_threads()?; @@ -139,7 +140,7 @@ fn test_linux_gate_mapping_id() -> Result<()> { fn test_mappings_include_linux_gate() -> Result<()> { let ppid = getppid().as_raw(); - let dumper = linux_ptrace_dumper::LinuxPtraceDumper::new(ppid)?; + let dumper = PtraceDumper::new(ppid)?; let linux_gate_loc = dumper.auxv[&AT_SYSINFO_EHDR]; test!(linux_gate_loc != 0, "linux_gate_loc == 0")?; let mut found_linux_gate = false; diff --git a/src/crash_context/crash_context_aarch64.rs b/src/crash_context/crash_context_aarch64.rs deleted file mode 100644 index e6fb3f9e..00000000 --- a/src/crash_context/crash_context_aarch64.rs +++ /dev/null @@ -1,11 +0,0 @@ -use super::CrashContext; - -impl CrashContext { - pub fn get_instruction_pointer(&self) -> usize { - self.context.uc_mcontext.sp as usize - } - - pub fn get_stack_pointer(&self) -> usize { - self.context.uc_mcontext.pc as usize - } -} diff --git a/src/crash_context/crash_context_x86.rs b/src/crash_context/crash_context_x86.rs deleted file mode 100644 index 181deeae..00000000 --- a/src/crash_context/crash_context_x86.rs +++ /dev/null @@ -1,50 +0,0 @@ -use super::CrashContext; -use crate::minidump_cpu::imp::*; -use crate::minidump_cpu::RawContextCPU; -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.context.uc_mcontext.gregs[REG_EIP as usize] as usize - } - - pub fn get_stack_pointer(&self) -> usize { - self.context.uc_mcontext.gregs[REG_ESP as usize] as usize - } - - pub fn fill_cpu_context(&self, out: &mut RawContextCPU) { - out.context_flags = MD_CONTEXT_X86_FULL | MD_CONTEXT_X86_FLOATING_POINT; - - out.gs = self.context.uc_mcontext.gregs[REG_GS as usize] as u32; - out.fs = self.context.uc_mcontext.gregs[REG_FS as usize] as u32; - out.es = self.context.uc_mcontext.gregs[REG_ES as usize] as u32; - out.ds = self.context.uc_mcontext.gregs[REG_DS as usize] as u32; - - out.edi = self.context.uc_mcontext.gregs[REG_EDI as usize] as u32; - out.esi = self.context.uc_mcontext.gregs[REG_ESI as usize] as u32; - out.ebx = self.context.uc_mcontext.gregs[REG_EBX as usize] as u32; - out.edx = self.context.uc_mcontext.gregs[REG_EDX as usize] as u32; - out.ecx = self.context.uc_mcontext.gregs[REG_ECX as usize] as u32; - out.eax = self.context.uc_mcontext.gregs[REG_EAX as usize] as u32; - - out.ebp = self.context.uc_mcontext.gregs[REG_EBP as usize] as u32; - out.eip = self.context.uc_mcontext.gregs[REG_EIP as usize] as u32; - out.cs = self.context.uc_mcontext.gregs[REG_CS as usize] as u32; - out.eflags = self.context.uc_mcontext.gregs[REG_EFL as usize] as u32; - out.esp = self.context.uc_mcontext.gregs[REG_UESP as usize] as u32; - out.ss = self.context.uc_mcontext.gregs[REG_SS as usize] as u32; - - out.float_save.control_word = self.float_state.cw; - out.float_save.status_word = self.float_state.sw; - out.float_save.tag_word = self.float_state.tag; - out.float_save.error_offset = self.float_state.ipoff; - out.float_save.error_selector = self.float_state.cssel; - out.float_save.data_offset = self.float_state.dataoff; - out.float_save.data_selector = self.float_state.datasel; - - // 8 registers * 10 bytes per register. - // my_memcpy(out->float_save.register_area, fp->_st, 10 * 8); - } -} diff --git a/src/crash_context/crash_context_x86_64.rs b/src/crash_context/crash_context_x86_64.rs deleted file mode 100644 index dd82d29a..00000000 --- a/src/crash_context/crash_context_x86_64.rs +++ /dev/null @@ -1,69 +0,0 @@ -use super::CrashContext; -use crate::minidump_cpu::imp::*; -use crate::minidump_cpu::RawContextCPU; -use crate::thread_info::to_u128; -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, -}; - -impl CrashContext { - pub fn get_instruction_pointer(&self) -> usize { - self.context.uc_mcontext.gregs[REG_RIP as usize] as usize - } - - pub fn get_stack_pointer(&self) -> usize { - self.context.uc_mcontext.gregs[REG_RSP as usize] as usize - } - - pub fn fill_cpu_context(&self, out: &mut RawContextCPU) { - out.context_flags = MD_CONTEXT_AMD64_FULL; - out.cs = (self.context.uc_mcontext.gregs[REG_CSGSFS as usize] & 0xffff) as u16; - - out.fs = ((self.context.uc_mcontext.gregs[REG_CSGSFS as usize] >> 32) & 0xffff) as u16; - out.gs = ((self.context.uc_mcontext.gregs[REG_CSGSFS as usize] >> 16) & 0xffff) as u16; - - out.eflags = self.context.uc_mcontext.gregs[REG_EFL as usize] as u32; - - out.rax = self.context.uc_mcontext.gregs[REG_RAX as usize] as u64; - out.rcx = self.context.uc_mcontext.gregs[REG_RCX as usize] as u64; - out.rdx = self.context.uc_mcontext.gregs[REG_RDX as usize] as u64; - out.rbx = self.context.uc_mcontext.gregs[REG_RBX as usize] as u64; - - out.rsp = self.context.uc_mcontext.gregs[REG_RSP as usize] as u64; - out.rbp = self.context.uc_mcontext.gregs[REG_RBP as usize] as u64; - out.rsi = self.context.uc_mcontext.gregs[REG_RSI as usize] as u64; - out.rdi = self.context.uc_mcontext.gregs[REG_RDI as usize] as u64; - out.r8 = self.context.uc_mcontext.gregs[REG_R8 as usize] as u64; - out.r9 = self.context.uc_mcontext.gregs[REG_R9 as usize] as u64; - out.r10 = self.context.uc_mcontext.gregs[REG_R10 as usize] as u64; - out.r11 = self.context.uc_mcontext.gregs[REG_R11 as usize] as u64; - out.r12 = self.context.uc_mcontext.gregs[REG_R12 as usize] as u64; - out.r13 = self.context.uc_mcontext.gregs[REG_R13 as usize] as u64; - out.r14 = self.context.uc_mcontext.gregs[REG_R14 as usize] as u64; - out.r15 = self.context.uc_mcontext.gregs[REG_R15 as usize] as u64; - - out.rip = self.context.uc_mcontext.gregs[REG_RIP as usize] as u64; - - out.flt_save.control_word = self.float_state.cwd; - out.flt_save.status_word = self.float_state.swd; - out.flt_save.tag_word = self.float_state.ftw as u8; - out.flt_save.error_opcode = self.float_state.fop; - out.flt_save.error_offset = self.float_state.rip as u32; - out.flt_save.data_offset = self.float_state.rdp as u32; - out.flt_save.error_selector = 0; // We don't have this. - out.flt_save.data_selector = 0; // We don't have this. - out.flt_save.mx_csr = self.float_state.mxcsr; - out.flt_save.mx_csr_mask = self.float_state.mxcr_mask; - - let data = to_u128(&self.float_state.st_space); - for idx in 0..data.len() { - out.flt_save.float_registers[idx] = data[idx]; - } - - let data = to_u128(&self.float_state.xmm_space); - for idx in 0..data.len() { - out.flt_save.xmm_registers[idx] = data[idx]; - } - } -} diff --git a/src/crash_context/mod.rs b/src/crash_context/mod.rs deleted file mode 100644 index 2be8fb15..00000000 --- a/src/crash_context/mod.rs +++ /dev/null @@ -1,65 +0,0 @@ -use libc; - -// 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. - -#[cfg(target_arch = "x86_64")] -#[path = "crash_context_x86_64.rs"] -pub mod imp; -#[cfg(target_arch = "x86")] -#[path = "crash_context_x86.rs"] -pub mod imp; -// Deactivated for now, as ucontext_t is missing from libc -// #[cfg(target_arch = "arm")] -// #[path = "crash_context_arm.rs"] -// pub mod imp; -#[cfg(target_arch = "arm")] -use crate::minidump_cpu::RawContextCPU; -#[cfg(target_arch = "arm")] -impl CrashContext { - pub fn get_instruction_pointer(&self) -> usize { - 0 - } - - pub fn get_stack_pointer(&self) -> usize { - 0 - } - - pub fn fill_cpu_context(&self, _: &mut RawContextCPU) {} -} - -#[cfg(target_arch = "aarch64")] -#[path = "crash_context_aarch64.rs"] -pub mod imp; -#[cfg(target_arch = "mips")] -#[path = "crash_context_mips.rs"] -pub mod imp; - -#[cfg(target_arch = "aarch64")] -pub type fpstate_t = libc::fpsimd_context; // Currently not part of libc! This will produce an error. -#[cfg(not(any( - target_arch = "aarch64", - target_arch = "mips", - target_arch = "arm-eabi" -)))] -#[cfg(target_arch = "x86")] -#[allow(non_camel_case_types)] -pub type fpstate_t = libc::_libc_fpstate; -#[cfg(target_arch = "x86_64")] -#[allow(non_camel_case_types)] -pub type fpstate_t = libc::user_fpregs_struct; - -#[repr(C)] -#[derive(Clone)] -pub struct CrashContext { - pub siginfo: libc::siginfo_t, - pub tid: libc::pid_t, // the crashing thread. - #[cfg(not(target_arch = "arm"))] - pub context: libc::ucontext_t, - // #ifdef this out because FP state is not part of user ABI for Linux ARM. - // In case of MIPS Linux FP state is already part of ucontext_t so - // 'float_state' is not required. - #[cfg(not(any(target_arch = "mips", target_arch = "arm")))] - pub float_state: fpstate_t, -} diff --git a/src/dumper_cpu_info/cpu_info_x86_mips.rs b/src/dumper_cpu_info/cpu_info_x86_mips.rs deleted file mode 100644 index c37fa94f..00000000 --- a/src/dumper_cpu_info/cpu_info_x86_mips.rs +++ /dev/null @@ -1,123 +0,0 @@ -use crate::errors::CpuInfoError; -use crate::minidump_format::*; -use std::convert::TryInto; -use std::io::{BufRead, BufReader}; -use std::path; - -type Result = std::result::Result; - -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 - if cfg!(target_arch = "mips") { - sys_info.processor_architecture = MDCPUArchitecture::Mips as u16; - } else if cfg!(target_arch = "mips64") { - sys_info.processor_architecture = MDCPUArchitecture::Mips64 as u16; - } else if cfg!(target_arch = "x86") { - sys_info.processor_architecture = MDCPUArchitecture::X86 as u16; - } else { - sys_info.processor_architecture = MDCPUArchitecture::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: + ':' - // Note that: - // - empty lines happen. - // - can contain spaces. - // - some fields have an empty - if line.trim().is_empty() { - continue; - } - - let split: Vec<_> = line.split(':').map(|x| x.trim()).collect(); - let field = split[0]; - let value = split.get(1); // Option, might be missing - - 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 Some(val) = value { - if let Ok(v) = val.parse() { - entry.value = v; - entry.found = true; - } else { - continue; - } - } else { - continue; - } - } - - // special case for vendor_id - if field == vendor_id_name && value.is_some() && !value.unwrap().is_empty() { - vendor_id = value.unwrap().to_string(); - } - } - } - // 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 mut slice = vendor_id.as_bytes(); - for id_part in sys_info.cpu.vendor_id.iter_mut() { - let (int_bytes, rest) = slice.split_at(std::mem::size_of::()); - slice = rest; - *id_part = match int_bytes.try_into() { - Ok(x) => u32::from_ne_bytes(x), - Err(_) => { - continue; - } - }; - } - } - - Ok(()) -} diff --git a/src/dumper_cpu_info/mod.rs b/src/dumper_cpu_info/mod.rs deleted file mode 100644 index 248ae4c2..00000000 --- a/src/dumper_cpu_info/mod.rs +++ /dev/null @@ -1,45 +0,0 @@ -#[cfg(any( - target_arch = "x86_64", - target_arch = "x86", - target_arch = "mips", - target_arch = "mips64" -))] -#[path = "cpu_info_x86_mips.rs"] -pub mod imp; -#[cfg(any(target_arch = "arm", target_arch = "aarch64"))] -#[path = "cpu_info_arm.rs"] -pub mod imp; - -pub use imp::write_cpu_information; - -use crate::errors::MemoryWriterError; -use crate::minidump_format::{MDOSPlatform, MDRawSystemInfo}; -use crate::sections::write_string_to_location; -use nix::sys::utsname::uname; -use std::io::Cursor; - -type Result = std::result::Result; - -pub fn write_os_information( - buffer: &mut Cursor>, - sys_info: &mut MDRawSystemInfo, -) -> Result<()> { - let info = uname(); - if cfg!(target_os = "android") { - sys_info.platform_id = MDOSPlatform::Android as u32; - } else { - sys_info.platform_id = MDOSPlatform::Linux as u32; - } - let merged = vec![ - info.sysname(), - info.release(), - info.version(), - info.machine(), - ] - .join(" "); - - let location = write_string_to_location(buffer, &merged)?; - sys_info.csd_version_rva = location.rva; - - Ok(()) -} diff --git a/src/lib.rs b/src/lib.rs index 1a774cd8..3bcb07e1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,17 +1,91 @@ -#[cfg(target_os = "android")] -mod android; -pub mod app_memory; -mod auxv_reader; -pub mod crash_context; -mod dso_debug; -mod dumper_cpu_info; -pub mod errors; -pub mod linux_ptrace_dumper; -pub mod maps_reader; +// BEGIN - Embark standard lints v6 for Rust 1.55+ +// do not change or add/remove here, but one can add exceptions after this section +// for more info see: +#![deny(unsafe_code)] +#![warn( + clippy::all, + clippy::await_holding_lock, + clippy::char_lit_as_u8, + clippy::checked_conversions, + clippy::dbg_macro, + clippy::debug_assert_with_mut_call, + clippy::doc_markdown, + clippy::empty_enum, + clippy::enum_glob_use, + clippy::exit, + clippy::expl_impl_clone_on_copy, + clippy::explicit_deref_methods, + clippy::explicit_into_iter_loop, + clippy::fallible_impl_from, + clippy::filter_map_next, + clippy::flat_map_option, + clippy::float_cmp_const, + clippy::fn_params_excessive_bools, + clippy::from_iter_instead_of_collect, + clippy::if_let_mutex, + clippy::implicit_clone, + clippy::imprecise_flops, + clippy::inefficient_to_string, + clippy::invalid_upcast_comparisons, + clippy::large_digit_groups, + clippy::large_stack_arrays, + clippy::large_types_passed_by_value, + clippy::let_unit_value, + clippy::linkedlist, + clippy::lossy_float_literal, + clippy::macro_use_imports, + clippy::manual_ok_or, + clippy::map_err_ignore, + clippy::map_flatten, + clippy::map_unwrap_or, + clippy::match_on_vec_items, + clippy::match_same_arms, + clippy::match_wild_err_arm, + clippy::match_wildcard_for_single_variants, + clippy::mem_forget, + clippy::mismatched_target_os, + clippy::missing_enforced_import_renames, + clippy::mut_mut, + clippy::mutex_integer, + clippy::needless_borrow, + clippy::needless_continue, + clippy::needless_for_each, + clippy::option_option, + clippy::path_buf_push_overwrite, + clippy::ptr_as_ptr, + clippy::rc_mutex, + clippy::ref_option_ref, + clippy::rest_pat_in_fully_bound_structs, + clippy::same_functions_in_if_condition, + clippy::semicolon_if_nothing_returned, + clippy::single_match_else, + clippy::string_add_assign, + clippy::string_add, + clippy::string_lit_as_bytes, + clippy::string_to_string, + clippy::todo, + clippy::trait_duplication_in_bounds, + clippy::unimplemented, + clippy::unnested_or_patterns, + clippy::unused_self, + clippy::useless_transmute, + clippy::verbose_file_reads, + clippy::zero_sized_map_values, + future_incompatible, + nonstandard_style, + rust_2018_idioms +)] +// END - Embark standard lints v6 for Rust 1.55+ +// crate-specific exceptions: +#![allow(unsafe_code)] + +cfg_if::cfg_if! { + if #[cfg(any(target_os = "linux", target_os = "android"))] { + mod linux; + + pub use linux::*; + } +} + pub mod minidump_cpu; pub mod minidump_format; -pub mod minidump_writer; -mod sections; -pub mod thread_info; - -pub use maps_reader::LINUX_GATE_LIBRARY_NAME; diff --git a/src/linux.rs b/src/linux.rs new file mode 100644 index 00000000..8f8c649b --- /dev/null +++ b/src/linux.rs @@ -0,0 +1,14 @@ +#[cfg(target_os = "android")] +mod android; +pub mod app_memory; +mod auxv_reader; +mod dso_debug; +mod dumper_cpu_info; +pub mod errors; +pub mod maps_reader; +pub mod minidump_writer; +pub mod ptrace_dumper; +mod sections; +pub mod thread_info; + +pub use maps_reader::LINUX_GATE_LIBRARY_NAME; diff --git a/src/android.rs b/src/linux/android.rs similarity index 99% rename from src/android.rs rename to src/linux/android.rs index b605b6ce..917b4ea1 100644 --- a/src/android.rs +++ b/src/linux/android.rs @@ -15,7 +15,6 @@ use goblin::elf::header::header64 as elf_header; use goblin::elf::program_header::program_header32::ProgramHeader; #[cfg(target_pointer_width = "64")] use goblin::elf::program_header::program_header64::ProgramHeader; -use std::convert::TryInto; use std::ffi::c_void; type Result = std::result::Result; diff --git a/src/app_memory.rs b/src/linux/app_memory.rs similarity index 100% rename from src/app_memory.rs rename to src/linux/app_memory.rs diff --git a/src/auxv_reader.rs b/src/linux/auxv_reader.rs similarity index 100% rename from src/auxv_reader.rs rename to src/linux/auxv_reader.rs diff --git a/src/crash_context/crash_context_arm.rs b/src/linux/crash_context/arm.rs similarity index 100% rename from src/crash_context/crash_context_arm.rs rename to src/linux/crash_context/arm.rs diff --git a/src/crash_context/crash_context_mips.rs b/src/linux/crash_context/mips.rs similarity index 100% rename from src/crash_context/crash_context_mips.rs rename to src/linux/crash_context/mips.rs diff --git a/src/dso_debug.rs b/src/linux/dso_debug.rs similarity index 91% rename from src/dso_debug.rs rename to src/linux/dso_debug.rs index 781c34ab..d842a926 100644 --- a/src/dso_debug.rs +++ b/src/linux/dso_debug.rs @@ -1,11 +1,13 @@ -use crate::auxv_reader::AuxvType; -use crate::errors::SectionDsoDebugError; -use crate::linux_ptrace_dumper::LinuxPtraceDumper; -use crate::minidump_format::*; -use crate::sections::{write_string_to_location, MemoryArrayWriter, MemoryWriter}; -use libc; +use crate::{ + linux::{ + auxv_reader::AuxvType, + errors::SectionDsoDebugError, + ptrace_dumper::PtraceDumper, + sections::{write_string_to_location, Buffer, MemoryArrayWriter, MemoryWriter}, + }, + minidump_format::*, +}; use std::collections::HashMap; -use std::io::Cursor; type Result = std::result::Result; @@ -71,7 +73,7 @@ pub struct RDebug { } pub fn write_dso_debug_stream( - buffer: &mut Cursor>, + buffer: &mut Buffer, blamed_thread: i32, auxv: &HashMap, ) -> Result { @@ -95,7 +97,7 @@ pub fn write_dso_debug_stream( .get(&at_phdr) .ok_or(SectionDsoDebugError::CouldNotFind("AT_PHDR in auxv"))? as usize; - let ph = LinuxPtraceDumper::copy_from_process( + let ph = PtraceDumper::copy_from_process( blamed_thread, phdr as *mut libc::c_void, SIZEOF_PHDR * phnum_max, @@ -145,7 +147,7 @@ pub fn write_dso_debug_stream( // DSOs loaded into the program. If this information is indeed available, // dump it to a MD_LINUX_DSO_DEBUG stream. loop { - let dyn_data = LinuxPtraceDumper::copy_from_process( + let dyn_data = PtraceDumper::copy_from_process( blamed_thread, (dyn_addr as usize + dynamic_length) as *mut libc::c_void, dyn_size, @@ -178,7 +180,7 @@ pub fn write_dso_debug_stream( // See for a more detailed discussion of the how the dynamic // loader communicates with debuggers. - let debug_entry_data = LinuxPtraceDumper::copy_from_process( + let debug_entry_data = PtraceDumper::copy_from_process( blamed_thread, r_debug as *mut libc::c_void, std::mem::size_of::(), @@ -193,7 +195,7 @@ pub fn write_dso_debug_stream( let mut dso_vec = Vec::new(); let mut curr_map = debug_entry.r_map; while curr_map != 0 { - let link_map_data = LinuxPtraceDumper::copy_from_process( + let link_map_data = PtraceDumper::copy_from_process( blamed_thread, curr_map as *mut libc::c_void, std::mem::size_of::(), @@ -209,7 +211,7 @@ pub fn write_dso_debug_stream( } let mut linkmap_rva = u32::MAX; - if dso_vec.len() > 0 { + 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::::alloc_array(buffer, dso_vec.len())?; @@ -219,7 +221,7 @@ pub fn write_dso_debug_stream( for (idx, map) in dso_vec.iter().enumerate() { let mut filename = String::new(); if map.l_name > 0 { - let filename_data = LinuxPtraceDumper::copy_from_process( + let filename_data = PtraceDumper::copy_from_process( blamed_thread, map.l_name as *mut libc::c_void, 256, @@ -258,12 +260,12 @@ pub fn write_dso_debug_stream( }; dirent.location.data_size += dynamic_length as u32; - let dso_debug_data = LinuxPtraceDumper::copy_from_process( + let dso_debug_data = PtraceDumper::copy_from_process( blamed_thread, dyn_addr as *mut libc::c_void, dynamic_length, )?; - MemoryArrayWriter::::alloc_from_array(buffer, &dso_debug_data)?; + MemoryArrayWriter::write_bytes(buffer, &dso_debug_data); Ok(dirent) } diff --git a/src/linux/dumper_cpu_info.rs b/src/linux/dumper_cpu_info.rs new file mode 100644 index 00000000..bbe22e7c --- /dev/null +++ b/src/linux/dumper_cpu_info.rs @@ -0,0 +1,45 @@ +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 info = uname(); + let vers = format!( + "{} {} {} {}", + info.sysname(), + info.release(), + info.version(), + info.machine() + ); + + ( + if cfg!(target_os = "android") { + PlatformId::Android + } else { + PlatformId::Linux + }, + vers, + ) +} diff --git a/src/dumper_cpu_info/cpu_info_arm.rs b/src/linux/dumper_cpu_info/arm.rs similarity index 76% rename from src/dumper_cpu_info/cpu_info_arm.rs rename to src/linux/dumper_cpu_info/arm.rs index ab7da7ae..67568516 100644 --- a/src/dumper_cpu_info/cpu_info_arm.rs +++ b/src/linux/dumper_cpu_info/arm.rs @@ -86,47 +86,36 @@ pub fn write_cpu_information(sys_info: &mut MDRawSystemInfo) -> Result<()> { // The ELF hwcaps are listed in the "Features" entry as textual tags. // This table is used to rebuild them. - let cpu_features_entries; #[cfg(target_arch = "arm")] - { - cpu_features_entries = [ - CpuFeaturesEntry::new("swp", MDCPUInformationARMElfHwCaps::Swp as u32), - CpuFeaturesEntry::new("half", MDCPUInformationARMElfHwCaps::Half as u32), - CpuFeaturesEntry::new("thumb", MDCPUInformationARMElfHwCaps::Thumb as u32), - CpuFeaturesEntry::new("bit26", MDCPUInformationARMElfHwCaps::Bit26 as u32), - CpuFeaturesEntry::new("fastmult", MDCPUInformationARMElfHwCaps::FastMult as u32), - CpuFeaturesEntry::new("fpa", MDCPUInformationARMElfHwCaps::Fpa as u32), - CpuFeaturesEntry::new("vfp", MDCPUInformationARMElfHwCaps::Vfp as u32), - CpuFeaturesEntry::new("edsp", MDCPUInformationARMElfHwCaps::Edsp as u32), - CpuFeaturesEntry::new("java", MDCPUInformationARMElfHwCaps::Java as u32), - CpuFeaturesEntry::new("iwmmxt", MDCPUInformationARMElfHwCaps::Iwmmxt as u32), - CpuFeaturesEntry::new("crunch", MDCPUInformationARMElfHwCaps::Crunch as u32), - CpuFeaturesEntry::new("thumbee", MDCPUInformationARMElfHwCaps::Thumbee as u32), - CpuFeaturesEntry::new("neon", MDCPUInformationARMElfHwCaps::Neon as u32), - CpuFeaturesEntry::new("vfpv3", MDCPUInformationARMElfHwCaps::Vfpv3 as u32), - CpuFeaturesEntry::new("vfpv3d16", MDCPUInformationARMElfHwCaps::Vfpv3d16 as u32), - CpuFeaturesEntry::new("tls", MDCPUInformationARMElfHwCaps::Tls as u32), - CpuFeaturesEntry::new("vfpv4", MDCPUInformationARMElfHwCaps::Vfpv4 as u32), - CpuFeaturesEntry::new("idiva", MDCPUInformationARMElfHwCaps::Idiva as u32), - CpuFeaturesEntry::new("idivt", MDCPUInformationARMElfHwCaps::Idivt as u32), - CpuFeaturesEntry::new( - "idiv", - MDCPUInformationARMElfHwCaps::Idiva as u32 - | MDCPUInformationARMElfHwCaps::Idivt as u32, - ), - ]; - } - #[cfg(target_arch = "aarch64")] - { - // No hwcaps on aarch64. - cpu_features_entries = []; - } + let cpu_features_entries = [ + CpuFeaturesEntry::new("swp", MDCPUInformationARMElfHwCaps::HWCAP_SWP), + CpuFeaturesEntry::new("half", MDCPUInformationARMElfHwCaps::HWCAP_HALF), + CpuFeaturesEntry::new("thumb", MDCPUInformationARMElfHwCaps::HWCAP_THUMB), + CpuFeaturesEntry::new("bit26", MDCPUInformationARMElfHwCaps::HWCAP_26BIT), + CpuFeaturesEntry::new("fastmult", MDCPUInformationARMElfHwCaps::HWCAP_FAST_MULT), + CpuFeaturesEntry::new("fpa", MDCPUInformationARMElfHwCaps::HWCAP_FPA), + CpuFeaturesEntry::new("vfp", MDCPUInformationARMElfHwCaps::HWCAP_VFP), + CpuFeaturesEntry::new("edsp", MDCPUInformationARMElfHwCaps::HWCAP_EDSP), + CpuFeaturesEntry::new("java", MDCPUInformationARMElfHwCaps::HWCAP_JAVA), + CpuFeaturesEntry::new("iwmmxt", MDCPUInformationARMElfHwCaps::HWCAP_IWMMXT), + CpuFeaturesEntry::new("crunch", MDCPUInformationARMElfHwCaps::HWCAP_CRUNCH), + CpuFeaturesEntry::new("thumbee", MDCPUInformationARMElfHwCaps::HWCAP_THUMBEE), + CpuFeaturesEntry::new("neon", MDCPUInformationARMElfHwCaps::HWCAP_NEON), + CpuFeaturesEntry::new("vfpv3", MDCPUInformationARMElfHwCaps::HWCAP_VFPv3), + CpuFeaturesEntry::new("vfpv3d16", MDCPUInformationARMElfHwCaps::HWCAP_VFPv3D16), + CpuFeaturesEntry::new("tls", MDCPUInformationARMElfHwCaps::HWCAP_TLS), + CpuFeaturesEntry::new("vfpv4", MDCPUInformationARMElfHwCaps::HWCAP_VFPv4), + CpuFeaturesEntry::new("idiva", MDCPUInformationARMElfHwCaps::HWCAP_IDIVA), + CpuFeaturesEntry::new("idivt", MDCPUInformationARMElfHwCaps::HWCAP_IDIVT), + CpuFeaturesEntry::new("idiv", HWCAP_IDIV), + ]; // processor_architecture should always be set, do this first if cfg!(target_arch = "aarch64") { - sys_info.processor_architecture = MDCPUArchitecture::Arm64Old as u16; + sys_info.processor_architecture = + MDCPUArchitecture::PROCESSOR_ARCHITECTURE_ARM64_OLD as u16; } else { - sys_info.processor_architecture = MDCPUArchitecture::Arm as u16; + sys_info.processor_architecture = MDCPUArchitecture::PROCESSOR_ARCHITECTURE_ARM as u16; } // /proc/cpuinfo is not readable under various sandboxed environments @@ -139,8 +128,9 @@ pub fn write_cpu_information(sys_info: &mut MDRawSystemInfo) -> Result<()> { sys_info.number_of_processors = 0; sys_info.processor_level = 1; // There is no ARMv1 sys_info.processor_revision = 42; - sys_info.cpu.cpuid = 0; - sys_info.cpu.elf_hwcaps = 0; + + //sys_info.cpu.cpuid = 0; + //sys_info.cpu.elf_hwcaps = 0; // Counting the number of CPUs involves parsing two sysfs files, // because the content of /proc/cpuinfo will only mirror the number @@ -174,6 +164,8 @@ pub fn write_cpu_information(sys_info: &mut MDRawSystemInfo) -> Result<()> { } }; + let mut cpuid = 0; + for line in BufReader::new(cpuinfo_file).lines() { let line = line?; // Expected format: + ':' @@ -211,7 +203,7 @@ pub fn write_cpu_information(sys_info: &mut MDRawSystemInfo) -> Result<()> { }; result &= (1 << entry.bit_length) - 1; result <<= entry.bit_lshift; - sys_info.cpu.cpuid |= result as u32; + cpuid |= result as u32; } if cfg!(target_arch = "arm") { @@ -246,20 +238,50 @@ pub fn write_cpu_information(sys_info: &mut MDRawSystemInfo) -> Result<()> { } } - // Rebuild the ELF hwcaps from the 'Features' field. - if field == "Features" { - if let Some(val) = value { - // Parse each space-separated tag. - for tag in val.split_whitespace() { - for entry in &cpu_features_entries { - if entry.tag == tag { - sys_info.cpu.elf_hwcaps |= entry.hwcaps; - break; + let elf_hwcaps = { + let mut elf_hwcaps = 0; + #[cfg(target_arch = "arm")] + { + // Rebuild the ELF hwcaps from the 'Features' field. + if field == "Features" { + if let Some(val) = value { + // Parse each space-separated tag. + for tag in val.split_whitespace() { + for entry in &cpu_features_entries { + if entry.tag == tag { + elf_hwcaps |= entry.hwcaps; + break; + } + } } } } } - } + + elf_hwcaps + }; + + // 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, + // } + use scroll::Pwrite; + 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::(), + scroll::Endian::Little, + ) + .expect("impossible"); } Ok(()) } diff --git a/src/linux/dumper_cpu_info/x86_mips.rs b/src/linux/dumper_cpu_info/x86_mips.rs new file mode 100644 index 00000000..05e7fc95 --- /dev/null +++ b/src/linux/dumper_cpu_info/x86_mips.rs @@ -0,0 +1,110 @@ +use crate::errors::CpuInfoError; +use crate::minidump_format::*; +use std::io::{BufRead, BufReader}; +use std::path; + +type Result = std::result::Result; + +pub fn write_cpu_information(sys_info: &mut MDRawSystemInfo) -> Result<()> { + // 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 processor = None; + // x86/_64 specific + let mut vendor_id = None; + let mut model = None; + let mut stepping = None; + let mut family = None; + // + + for line in BufReader::new(cpuinfo_file).lines() { + let line = line?; + // Expected format: + ':' + // Note that: + // - empty lines happen. + // - can contain spaces. + // - some fields have an empty + 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 entry = match field { + "processor" => &mut processor, + "model" => &mut model, + "stepping" => &mut stepping, + "cpu family" => &mut family, + "vendor_id" => { + if vendor_id.is_none() && !value.is_empty() { + vendor_id = Some(value.to_owned()); + } + continue; + } + _ => continue, + }; + + if entry.is_some() && field != "processor" { + continue; + } + + if let Ok(v) = value.parse::() { + *entry = Some(v); + } + } + + // This holds the highest processor id which start from 0 so add 1 to get the actual count + // This field is only a u8 which means it will not work great in high (artificially or otherwise) + // contexts + sys_info.number_of_processors = std::cmp::max( + (processor.ok_or(CpuInfoError::NotAllProcEntriesFound)? + 1) as u8, + u8::MAX, + ); + + #[cfg(any(target_arch = "x86_64", target_arch = "x86"))] + { + sys_info.processor_level = family.ok_or(CpuInfoError::NotAllProcEntriesFound)? as u16; + sys_info.processor_revision = (model.ok_or(CpuInfoError::NotAllProcEntriesFound)? << 8 + | stepping.ok_or(CpuInfoError::NotAllProcEntriesFound)?) + as u16; + + if let Some(vendor_id) = vendor_id { + let mut slice = vendor_id.as_bytes(); + + // SAFETY: CPU_INFORMATION is a block of bytes, which is actually + // a union, including the X86 information that we actually want to + // set + let cpu_info: &mut MDCPUInformation = + unsafe { &mut *sys_info.cpu.data.as_mut_ptr().cast() }; + + for id_part in cpu_info.vendor_id.iter_mut() { + let (int_bytes, rest) = slice.split_at(std::mem::size_of::()); + slice = rest; + *id_part = match int_bytes.try_into() { + Ok(x) => u32::from_ne_bytes(x), + Err(_) => { + continue; + } + }; + } + } + } + + Ok(()) +} diff --git a/src/errors.rs b/src/linux/errors.rs similarity index 97% rename from src/errors.rs rename to src/linux/errors.rs index 4a540916..2ce25037 100644 --- a/src/errors.rs +++ b/src/linux/errors.rs @@ -1,6 +1,4 @@ -use crate::maps_reader::MappingInfo; -use crate::thread_info::Pid; -use goblin; +use crate::{maps_reader::MappingInfo, thread_info::Pid}; use thiserror::Error; #[derive(Debug, Error)] @@ -11,6 +9,7 @@ pub enum InitError { NoAuxvEntryFound(Pid), #[error("crash thread does not reference principal mapping")] PrincipalMappingNotReferenced, + #[cfg(target_os = "android")] #[error("Failed Android specific late init")] AndroidLateInitError(#[from] AndroidError), } @@ -78,6 +77,7 @@ pub enum ThreadInfoError { InvalidProcStatusFile(Pid, String), } +#[cfg(target_os = "android")] #[derive(Debug, Error)] pub enum AndroidError { #[error("Failed to copy memory from process")] @@ -126,6 +126,8 @@ pub enum MemoryWriterError { IOError(#[from] std::io::Error), #[error("Failed integer conversion")] TryFromIntError(#[from] std::num::TryFromIntError), + #[error("Failed to write to buffer")] + Scroll(#[from] scroll::Error), } #[derive(Debug, Error)] diff --git a/src/maps_reader.rs b/src/linux/maps_reader.rs similarity index 93% rename from src/maps_reader.rs rename to src/linux/maps_reader.rs index ec0b4543..f31bca3f 100644 --- a/src/maps_reader.rs +++ b/src/linux/maps_reader.rs @@ -1,13 +1,8 @@ -use crate::auxv_reader::AuxvType; -use crate::errors::MapsReaderError; -use crate::thread_info::Pid; +use crate::linux::{auxv_reader::AuxvType, errors::MapsReaderError, thread_info::Pid}; use byteorder::{NativeEndian, ReadBytesExt}; use goblin::elf; use memmap2::{Mmap, MmapOptions}; -use std::convert::TryInto; -use std::fs::File; -use std::mem::size_of; -use std::path::PathBuf; +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)"; @@ -58,6 +53,7 @@ pub enum MappingInfoParsingResult { Success(MappingInfo), } +#[inline] fn is_mapping_a_path(pathname: Option<&str>) -> bool { match pathname { Some(x) => x.contains('/'), @@ -185,7 +181,7 @@ impl MappingInfo { } pub fn get_mmap(name: &Option, offset: usize) -> Result { - if !MappingInfo::is_mapped_file_safe_to_open(&name) { + if !MappingInfo::is_mapped_file_safe_to_open(name) { return Err(MapsReaderError::NotSafeToOpenMapping( name.clone().unwrap_or_default(), )); @@ -301,7 +297,6 @@ impl MappingInfo { pub fn get_mapping_effective_name_and_path(&self) -> Result<(String, String)> { let mut file_path = self.name.clone().unwrap_or_default(); - let file_name; // 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 @@ -309,15 +304,13 @@ impl MappingInfo { // filesystem name of the module. // Just use the filesystem name if no SONAME is present. - let file_name = match self.elf_file_so_name() { - Ok(name) => name, - Err(_) => { - // file_path := /path/to/libname.so - // file_name := libname.so - let split: Vec<_> = file_path.rsplitn(2, '/').collect(); - file_name = split.first().unwrap().to_string(); - return Ok((file_path, file_name)); - } + let file_name = if let Ok(name) = self.elf_file_so_name() { + name + } else { + // file_path := /path/to/libname.so + // file_name := libname.so + let file_name = file_path.rsplit('/').next().unwrap().to_owned(); + return Ok((file_path, file_name)); }; if self.executable && self.offset != 0 { @@ -424,10 +417,11 @@ mod tests { 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()) { - Ok(MappingInfoParsingResult::Success(map)) => mappings.push(map), - Ok(MappingInfoParsingResult::SkipLine) => continue, - Err(_) => assert!(false), + match MappingInfo::parse_from_line(line, linux_gate_loc, mappings.last_mut()) + .expect("unexpected parse failure") + { + MappingInfoParsingResult::Success(map) => mappings.push(map), + MappingInfoParsingResult::SkipLine => continue, } } assert_eq!(mappings.len(), 23); @@ -440,10 +434,11 @@ mod tests { 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()) { - Ok(MappingInfoParsingResult::Success(map)) => mappings.push(map), - Ok(MappingInfoParsingResult::SkipLine) => continue, - Err(_) => assert!(false), + match MappingInfo::parse_from_line(line, linux_gate_loc, mappings.last_mut()) + .expect("unexpected parse failure") + { + MappingInfoParsingResult::Success(map) => mappings.push(map), + MappingInfoParsingResult::SkipLine => continue, } } @@ -581,10 +576,11 @@ mod tests { let linux_gate_loc = 0x7ffe091bf000; let mut mappings: Vec = Vec::new(); for line in lines { - match MappingInfo::parse_from_line(&line, linux_gate_loc, mappings.last_mut()) { - Ok(MappingInfoParsingResult::Success(map)) => mappings.push(map), - Ok(MappingInfoParsingResult::SkipLine) => continue, - Err(_) => assert!(false), + match MappingInfo::parse_from_line(line, linux_gate_loc, mappings.last_mut()) + .expect("unexpected parse failure") + { + MappingInfoParsingResult::Success(map) => mappings.push(map), + MappingInfoParsingResult::SkipLine => continue, } } assert_eq!(mappings.len(), 1); @@ -606,7 +602,7 @@ mod tests { let linux_gate_loc = 0x7ffe091bf000; let mut mappings: Vec = Vec::new(); for line in lines { - match MappingInfo::parse_from_line(&line, linux_gate_loc, mappings.last_mut()) { + match MappingInfo::parse_from_line(line, linux_gate_loc, mappings.last_mut()) { Ok(MappingInfoParsingResult::Success(map)) => mappings.push(map), Ok(MappingInfoParsingResult::SkipLine) => continue, Err(x) => panic!("{:?}", x), @@ -640,10 +636,11 @@ mod tests { let linux_gate_loc = 0x7ffe091bf000; let mut mappings: Vec = Vec::new(); for line in lines { - match MappingInfo::parse_from_line(&line, linux_gate_loc, mappings.last_mut()) { - Ok(MappingInfoParsingResult::Success(map)) => mappings.push(map), - Ok(MappingInfoParsingResult::SkipLine) => continue, - Err(_) => assert!(false), + match MappingInfo::parse_from_line(line, linux_gate_loc, mappings.last_mut()) + .expect("unexpected parse failure") + { + MappingInfoParsingResult::Success(map) => mappings.push(map), + MappingInfoParsingResult::SkipLine => continue, } } assert_eq!(mappings.len(), 4); diff --git a/src/minidump_writer.rs b/src/linux/minidump_writer.rs similarity index 84% rename from src/minidump_writer.rs rename to src/linux/minidump_writer.rs index 644843d9..86bbeaba 100644 --- a/src/minidump_writer.rs +++ b/src/linux/minidump_writer.rs @@ -1,15 +1,19 @@ -use crate::app_memory::AppMemoryList; -use crate::crash_context::CrashContext; -use crate::dso_debug; -use crate::errors::{FileWriterError, InitError, MemoryWriterError, WriterError}; -use crate::linux_ptrace_dumper::LinuxPtraceDumper; -use crate::maps_reader::{MappingInfo, MappingList}; -use crate::minidump_format::*; -use crate::sections::*; -use crate::thread_info::Pid; -use std::io::{Cursor, Read, Seek, SeekFrom, Write}; - -pub type DumpBuf = Cursor>; +use crate::{ + linux::{ + app_memory::AppMemoryList, + dso_debug, + errors::{FileWriterError, InitError, MemoryWriterError, WriterError}, + maps_reader::{MappingInfo, MappingList}, + ptrace_dumper::PtraceDumper, + sections::*, + thread_info::Pid, + }, + minidump_format::*, +}; +use crash_context::{CpuContext, CrashContext}; +use std::io::{Seek, SeekFrom, Write}; + +pub type DumpBuf = Buffer; #[derive(Debug)] pub struct DirSection<'a, W> @@ -33,17 +37,18 @@ where index_length: u32, destination: &'a mut W, ) -> std::result::Result { - let dir_section = + let section = MemoryArrayWriter::::alloc_array(buffer, index_length as usize)?; Ok(DirSection { curr_idx: 0, - section: dir_section, + section, destination_start_offset: destination.seek(SeekFrom::Current(0))?, destination, last_position_written_to_file: 0, }) } + #[inline] fn position(&self) -> u32 { self.section.position } @@ -62,16 +67,15 @@ where let idx_pos = self.section.location_of_index(self.curr_idx); self.curr_idx += 1; - self.destination.seek(std::io::SeekFrom::Start( + self.destination.seek(SeekFrom::Start( self.destination_start_offset + idx_pos.rva as u64, ))?; let start = idx_pos.rva as usize; let end = (idx_pos.rva + idx_pos.data_size) as usize; - self.destination.write_all(&buffer.get_ref()[start..end])?; + self.destination.write_all(&buffer[start..end])?; // Reset file-position - self.destination - .seek(std::io::SeekFrom::Start(curr_file_pos))?; + self.destination.seek(SeekFrom::Start(curr_file_pos))?; Ok(()) } @@ -89,7 +93,7 @@ where } let start_pos = self.last_position_written_to_file as usize; - self.destination.write_all(&buffer.get_ref()[start_pos..])?; + self.destination.write_all(&buffer[start_pos..])?; self.last_position_written_to_file = buffer.position(); Ok(()) } @@ -165,8 +169,6 @@ impl MinidumpWriter { self } - // Has to be deactivated for ARM for now, as libc doesn't include ucontext_t for ARM yet - #[cfg(not(target_arch = "arm"))] pub fn set_crash_context(&mut self, crash_context: CrashContext) -> &mut Self { self.crash_context = Some(crash_context); self @@ -185,7 +187,7 @@ impl MinidumpWriter { /// 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> { - let mut dumper = LinuxPtraceDumper::new(self.process_id)?; + let mut dumper = PtraceDumper::new(self.process_id)?; dumper.suspend_threads()?; dumper.late_init()?; @@ -199,17 +201,17 @@ impl MinidumpWriter { } } - let mut buffer = Cursor::new(Vec::new()); + let mut buffer = Buffer::with_capacity(4 * 1024); 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_inner()) + Ok(buffer.into()) } - fn crash_thread_references_principal_mapping(&self, dumper: &LinuxPtraceDumper) -> bool { + fn crash_thread_references_principal_mapping(&self, dumper: &PtraceDumper) -> bool { if self.crash_context.is_none() || self.principal_mapping.is_none() { return false; } @@ -227,12 +229,8 @@ impl MinidumpWriter { .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(); + let pc = self.crash_context.as_ref().unwrap().instruction_pointer(); + let stack_pointer = self.crash_context.as_ref().unwrap().stack_pointer(); if pc >= low_addr && pc < high_addr { return true; @@ -244,7 +242,7 @@ impl MinidumpWriter { return false; } }; - let stack_copy = match LinuxPtraceDumper::copy_from_process( + let stack_copy = match PtraceDumper::copy_from_process( self.blamed_thread, stack_ptr as *mut libc::c_void, stack_len, @@ -265,7 +263,7 @@ impl MinidumpWriter { fn generate_dump( &mut self, buffer: &mut DumpBuf, - dumper: &mut LinuxPtraceDumper, + dumper: &mut PtraceDumper, destination: &mut (impl Write + Seek), ) -> Result<()> { // A minidump file contains a number of tagged streams. This is the number @@ -280,7 +278,6 @@ impl MinidumpWriter { 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. */ @@ -295,28 +292,22 @@ impl MinidumpWriter { // 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 + let dirent = thread_list_stream::write(self, buffer, dumper)?; 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))?; - let _ = app_memory::write(self, buffer)?; - // Write section to file + app_memory::write(self, buffer)?; 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") { @@ -326,7 +317,6 @@ impl MinidumpWriter { }, 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)) @@ -337,7 +327,6 @@ impl MinidumpWriter { }, Err(_) => Default::default(), }; - // Write section to file dir_section.write_to_file(buffer, Some(dirent))?; let dirent = match self @@ -350,7 +339,6 @@ impl MinidumpWriter { }, 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)) @@ -361,7 +349,6 @@ impl MinidumpWriter { }, 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)) @@ -372,7 +359,6 @@ impl MinidumpWriter { }, 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)) { @@ -382,7 +368,6 @@ impl MinidumpWriter { }, 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)) { @@ -392,16 +377,13 @@ impl MinidumpWriter { }, 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.blamed_thread, &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, @@ -409,16 +391,15 @@ impl MinidumpWriter { Ok(()) } + #[allow(clippy::unused_self)] fn write_file( &self, buffer: &mut DumpBuf, filename: &str, ) -> std::result::Result { - let mut file = std::fs::File::open(std::path::PathBuf::from(filename))?; - let mut content = Vec::new(); - file.read_to_end(&mut content)?; + let content = std::fs::read(filename)?; - let section = MemoryArrayWriter::::alloc_from_array(buffer, &content)?; + let section = MemoryArrayWriter::write_bytes(buffer, &content); Ok(section.location()) } } diff --git a/src/linux_ptrace_dumper.rs b/src/linux/ptrace_dumper.rs similarity index 84% rename from src/linux_ptrace_dumper.rs rename to src/linux/ptrace_dumper.rs index f1e249b0..3f192ed5 100644 --- a/src/linux_ptrace_dumper.rs +++ b/src/linux/ptrace_dumper.rs @@ -1,21 +1,25 @@ // use libc::c_void; #[cfg(target_os = "android")] -use crate::android::late_process_mappings; -use crate::auxv_reader::{AuxvType, ProcfsAuxvIter}; -use crate::errors::{DumperError, InitError, ThreadInfoError}; -use crate::maps_reader::{MappingInfo, MappingInfoParsingResult, DELETED_SUFFIX}; -use crate::minidump_format::MDGUID; -use crate::thread_info::{Pid, ThreadInfo}; -use crate::LINUX_GATE_LIBRARY_NAME; +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; use nix::sys::{ptrace, wait}; -use std::collections::HashMap; -use std::convert::TryInto; -use std::ffi::c_void; -use std::io::{BufRead, BufReader}; -use std::path; -use std::result::Result; +use std::{ + collections::HashMap, + ffi::c_void, + io::{BufRead, BufReader}, + path, + result::Result, +}; #[derive(Debug, Clone)] pub struct Thread { @@ -24,7 +28,7 @@ pub struct Thread { } #[derive(Debug)] -pub struct LinuxPtraceDumper { +pub struct PtraceDumper { pub pid: Pid, threads_suspended: bool, pub threads: Vec, @@ -37,18 +41,18 @@ pub const AT_SYSINFO_EHDR: u32 = 33; #[cfg(target_pointer_width = "64")] pub const AT_SYSINFO_EHDR: u64 = 33; -impl Drop for LinuxPtraceDumper { +impl Drop for PtraceDumper { fn drop(&mut self) { // Always try to resume all threads (e.g. in case of error) let _ = self.resume_threads(); } } -impl LinuxPtraceDumper { +impl PtraceDumper { /// Constructs a dumper for extracting information of a given process /// with a process ID of |pid|. pub fn new(pid: Pid) -> Result { - let mut dumper = LinuxPtraceDumper { + let mut dumper = Self { pid, threads_suspended: false, threads: Vec::new(), @@ -67,6 +71,7 @@ impl LinuxPtraceDumper { Ok(()) } + #[cfg_attr(not(target_os = "android"), allow(clippy::unused_self))] pub fn late_init(&mut self) -> Result<(), InitError> { #[cfg(target_os = "android")] { @@ -107,8 +112,8 @@ impl LinuxPtraceDumper { loop { match wait::waitpid(pid, Some(wait::WaitPidFlag::__WALL)) { Ok(_) => break, - Err(e @ nix::Error::Sys(Errno::EINTR)) => { - ptrace::detach(pid).map_err(|e| DetachErr(child, e))?; + Err(e @ nix::Error::EINTR) => { + ptrace::detach(pid, None).map_err(|e| DetachErr(child, e))?; return Err(DumperError::WaitPidError(child, e)); } Err(_) => continue, @@ -138,7 +143,7 @@ impl LinuxPtraceDumper { skip_thread = true; } if skip_thread { - ptrace::detach(pid).map_err(|e| DetachErr(child, e))?; + ptrace::detach(pid, None).map_err(|e| DetachErr(child, e))?; return Err(DumperError::DetachSkippedThread(child)); } } @@ -149,7 +154,7 @@ impl LinuxPtraceDumper { pub fn resume_thread(child: Pid) -> Result<(), DumperError> { use DumperError::PtraceDetachError as DetachErr; let pid = nix::unistd::Pid::from_raw(child); - ptrace::detach(pid).map_err(|e| DetachErr(child, e))?; + ptrace::detach(pid, None).map_err(|e| DetachErr(child, e))?; Ok(()) } @@ -209,7 +214,7 @@ impl LinuxPtraceDumper { .ok(); (tid, name) }) - .for_each(|(tid, name)| self.threads.push(Thread { tid, name })) + .for_each(|(tid, name)| self.threads.push(Thread { tid, name })); } Ok(()) } @@ -267,8 +272,7 @@ impl LinuxPtraceDumper { 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) => continue, - Err(_) => continue, + Ok(MappingInfoParsingResult::SkipLine) | Err(_) => continue, } } @@ -347,12 +351,12 @@ impl LinuxPtraceDumper { let defaced; #[cfg(target_pointer_width = "64")] { - defaced = 0x0defaced0defacedusize.to_ne_bytes() + defaced = 0x0defaced0defacedusize.to_ne_bytes(); } #[cfg(target_pointer_width = "32")] { - defaced = 0x0defacedusize.to_ne_bytes() - }; + defaced = 0x0defacedusize.to_ne_bytes(); + } // the bitfield length is 2^test_bits long. let test_bits = 11; // byte length of the corresponding array. @@ -439,28 +443,24 @@ impl LinuxPtraceDumper { Ok(()) } - // Find the mapping which the given memory address falls in. + /// Find the mapping which the given memory address falls in. + #[inline] pub fn find_mapping(&self, address: usize) -> Option<&MappingInfo> { - for map in &self.mappings { - if address >= map.start_address && address - map.start_address < map.size { - return Some(&map); - } - } - None + 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. + /// Find the mapping which the given memory address falls in. + /// + /// Uses the unadjusted mapping address range from the kernel, rather than + /// the biased range. + #[inline] pub fn find_mapping_no_bias(&self, address: usize) -> Option<&MappingInfo> { - for map in &self.mappings { - if address >= map.system_mapping_info.start_address + self.mappings.iter().find(|map| { + address >= map.system_mapping_info.start_address && address < map.system_mapping_info.end_address - { - return Some(&map); - } - } - None + }) } fn parse_build_id<'data>( @@ -486,41 +486,40 @@ impl LinuxPtraceDumper { pub fn elf_file_identifier_from_mapped_file(mem_slice: &[u8]) -> Result, DumperError> { let elf_obj = elf::Elf::parse(mem_slice)?; - match Self::parse_build_id(&elf_obj, mem_slice) { + if let Some(build_id) = Self::parse_build_id(&elf_obj, mem_slice) { // Look for a build id note first. - Some(build_id) => Ok(build_id.to_vec()), + Ok(build_id.to_vec()) + } else { // Fall back on hashing the first page of the text section. - None => { - // 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 { - if 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::()]; - let mut offset = 0; - while offset < max_len { - for idx in 0..std::mem::size_of::() { - if offset + idx >= text_section.len() { - break; - } - result[idx] ^= text_section[offset + idx]; - } - offset += std::mem::size_of::(); + // 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 & elf::section_header::SHF_ALLOC as u64 != 0 + && section.sh_flags & elf::section_header::SHF_EXECINSTR as u64 != 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::()]; + let mut offset = 0; + while offset < max_len { + for idx in 0..std::mem::size_of::() { + if offset + idx >= text_section.len() { + break; } - return Ok(result); + result[idx] ^= text_section[offset + idx]; } + offset += std::mem::size_of::(); } + return Ok(result); } - Err(DumperError::NoBuildIDFound) } + Err(DumperError::NoBuildIDFound) } } @@ -557,7 +556,7 @@ impl LinuxPtraceDumper { } } let new_name = MappingInfo::handle_deleted_file_in_mapping( - &mapping.name.as_ref().unwrap_or(&String::new()), + mapping.name.as_deref().unwrap_or(""), pid, )?; diff --git a/src/linux/sections.rs b/src/linux/sections.rs new file mode 100644 index 00000000..b7850ac0 --- /dev/null +++ b/src/linux/sections.rs @@ -0,0 +1,276 @@ +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::{ + errors::{self, MemoryWriterError}, + linux::{ + minidump_writer::{self, DumpBuf, MinidumpWriter}, + ptrace_dumper::PtraceDumper, + }, + minidump_format::*, +}; +use scroll::ctx::{SizeWith, TryIntoCtx}; + +type WriteResult = std::result::Result; + +macro_rules! size { + ($t:ty) => { + <$t>::size_with(&scroll::Endian::Little) + }; +} + +pub struct Buffer { + inner: Vec, +} + +impl Buffer { + pub fn with_capacity(cap: usize) -> Self { + Self { + inner: Vec::with_capacity(cap), + } + } + + #[inline] + pub fn position(&self) -> u64 { + self.inner.len() as u64 + } + + #[inline] + #[must_use] + fn reserve(&mut self, len: usize) -> usize { + let mark = self.inner.len(); + self.inner.resize(self.inner.len() + len, 0); + mark + } + + #[inline] + fn write(&mut self, val: N) -> Result + where + N: TryIntoCtx + SizeWith, + E: From, + { + self.write_at(self.inner.len(), val) + } + + fn write_at(&mut self, offset: usize, val: N) -> Result + where + N: TryIntoCtx + SizeWith, + E: From, + { + let to_write = size!(N); + let remainder = self.inner.len() - offset; + if remainder < to_write { + self.inner + .resize(self.inner.len() + to_write - remainder, 0); + } + + let dst = &mut self.inner[offset..offset + to_write]; + val.try_into_ctx(dst, scroll::Endian::Little) + } + + #[inline] + pub fn write_all(&mut self, buffer: &[u8]) { + self.inner.extend_from_slice(buffer); + } +} + +impl From for Vec { + fn from(b: Buffer) -> Self { + b.inner + } +} + +impl std::ops::Deref for Buffer { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +#[derive(Debug, PartialEq)] +pub struct MemoryWriter { + pub position: MDRVA, + pub size: usize, + phantom: std::marker::PhantomData, +} + +impl MemoryWriter +where + T: TryIntoCtx + SizeWith, +{ + /// Create a slot for a type T in the buffer, we can fill right now with real values. + pub fn alloc_with_val(buffer: &mut Buffer, val: T) -> WriteResult { + // Mark the position as we may overwrite later + let position = buffer.position(); + let size = buffer.write(val)?; + + Ok(Self { + position: position as u32, + size, + phantom: std::marker::PhantomData, + }) + } + + /// Create a slot for a type T in the buffer, we can fill later with real values. + pub fn alloc(buffer: &mut Buffer) -> WriteResult { + let size = size!(T); + let position = buffer.reserve(size) as u32; + + Ok(Self { + position: position as u32, + size, + phantom: std::marker::PhantomData, + }) + } + + /// Write actual values in the buffer-slot we got during `alloc()` + #[inline] + pub fn set_value(&mut self, buffer: &mut Buffer, val: T) -> WriteResult<()> { + Ok(buffer.write_at(self.position as usize, val).map(|_sz| ())?) + } + + #[inline] + pub fn location(&self) -> MDLocationDescriptor { + MDLocationDescriptor { + data_size: size!(T) as u32, + rva: self.position, + } + } +} + +#[derive(Debug, PartialEq)] +pub struct MemoryArrayWriter { + pub position: MDRVA, + array_size: usize, + phantom: std::marker::PhantomData, +} + +impl MemoryArrayWriter { + #[inline] + pub fn write_bytes(buffer: &mut Buffer, slice: &[u8]) -> Self { + let position = buffer.position(); + buffer.write_all(slice); + + Self { + position: position as u32, + array_size: slice.len(), + phantom: std::marker::PhantomData, + } + } +} + +impl MemoryArrayWriter +where + T: TryIntoCtx + SizeWith + Copy, +{ + pub fn alloc_from_array(buffer: &mut Buffer, array: &[T]) -> WriteResult { + let array_size = array.len(); + let position = buffer.reserve(array_size * size!(T)); + + for (idx, val) in array.iter().enumerate() { + buffer.write_at(position + idx * size!(T), *val)?; + } + + Ok(Self { + position: position as u32, + array_size, + phantom: std::marker::PhantomData, + }) + } +} + +impl MemoryArrayWriter +where + T: TryIntoCtx + SizeWith, +{ + /// Create a slot for a type T in the buffer, we can fill in the values in one go. + pub fn alloc_from_iter( + buffer: &mut Buffer, + iter: impl IntoIterator, + ) -> WriteResult + where + I: std::iter::ExactSizeIterator, + { + let iter = iter.into_iter(); + let array_size = iter.len(); + let size = size!(T); + let position = buffer.reserve(array_size * size); + + for (idx, val) in iter.enumerate() { + buffer.write_at(position + idx * size, val)?; + } + + Ok(Self { + position: position as u32, + array_size, + phantom: std::marker::PhantomData, + }) + } + + /// Create a slot for a type T in the buffer, we can fill later with real values. + /// This function fills it with `Default::default()`, which is less performant than + /// using uninitialized memory, but safe. + pub fn alloc_array(buffer: &mut Buffer, array_size: usize) -> WriteResult { + let position = buffer.reserve(array_size * size!(T)); + + Ok(Self { + position: position as u32, + array_size, + phantom: std::marker::PhantomData, + }) + } + + /// Write actual values in the buffer-slot we got during `alloc()` + #[inline] + pub fn set_value_at(&mut self, buffer: &mut Buffer, val: T, index: usize) -> WriteResult<()> { + Ok(buffer + .write_at(self.position as usize + size!(T) * index, val) + .map(|_sz| ())?) + } + + #[inline] + pub fn location(&self) -> MDLocationDescriptor { + MDLocationDescriptor { + data_size: (self.array_size * size!(T)) as u32, + rva: self.position, + } + } + + #[inline] + pub fn location_of_index(&self, idx: usize) -> MDLocationDescriptor { + MDLocationDescriptor { + data_size: size!(T) as u32, + rva: self.position + (size!(T) * idx) as u32, + } + } +} + +pub fn write_string_to_location( + buffer: &mut Buffer, + text: &str, +) -> WriteResult { + let letters: Vec = text.encode_utf16().collect(); + + // First write size of the string (x letters in u16, times the size of u16) + let text_header = MemoryWriter::::alloc_with_val( + buffer, + (letters.len() * std::mem::size_of::()).try_into()?, + )?; + + // Then write utf-16 letters after that + let mut text_section = MemoryArrayWriter::::alloc_array(buffer, letters.len())?; + for (index, letter) in letters.iter().enumerate() { + text_section.set_value_at(buffer, *letter, index)?; + } + + let mut location = text_header.location(); + location.data_size += text_section.location().data_size; + + Ok(location) +} diff --git a/src/linux/sections/app_memory.rs b/src/linux/sections/app_memory.rs new file mode 100644 index 00000000..824e0cf5 --- /dev/null +++ b/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, +) -> std::result::Result<(), crate::linux::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/src/sections/exception_stream.rs b/src/linux/sections/exception_stream.rs similarity index 88% rename from src/sections/exception_stream.rs rename to src/linux/sections/exception_stream.rs index 65e14119..4cddc3e8 100644 --- a/src/sections/exception_stream.rs +++ b/src/linux/sections/exception_stream.rs @@ -1,9 +1,5 @@ -use crate::errors::SectionExceptionStreamError; -use crate::minidump_format::*; -use crate::minidump_writer::{CrashingThreadContext, DumpBuf, MinidumpWriter}; -use crate::sections::MemoryWriter; - -type Result = std::result::Result; +use super::minidump_writer::CrashingThreadContext; +use super::*; #[allow(non_camel_case_types, unused)] #[repr(u32)] @@ -45,7 +41,10 @@ enum MDExceptionCodeLinux { dump requested. */ } -pub fn write(config: &mut MinidumpWriter, buffer: &mut DumpBuf) -> Result { +pub fn write( + config: &mut MinidumpWriter, + buffer: &mut DumpBuf, +) -> std::result::Result { let exception = if let Some(context) = &config.crash_context { let sig_addr; #[cfg(target_arch = "arm")] @@ -67,11 +66,11 @@ pub fn write(config: &mut MinidumpWriter, buffer: &mut DumpBuf) -> Result Result ctx, - CrashingThreadContext::CrashContext(ctx) => ctx, + CrashingThreadContext::CrashContextPlusAddress((ctx, _)) + | CrashingThreadContext::CrashContext(ctx) => ctx, CrashingThreadContext::None => MDLocationDescriptor { data_size: 0, rva: 0, diff --git a/src/sections/mappings.rs b/src/linux/sections/mappings.rs similarity index 79% rename from src/sections/mappings.rs rename to src/linux/sections/mappings.rs index d95741d0..6fb875fc 100644 --- a/src/sections/mappings.rs +++ b/src/linux/sections/mappings.rs @@ -1,11 +1,7 @@ -use crate::errors::SectionMappingsError; -use crate::linux_ptrace_dumper::LinuxPtraceDumper; -use crate::maps_reader::MappingInfo; -use crate::minidump_format::*; -use crate::minidump_writer::{DumpBuf, MinidumpWriter}; -use crate::sections::{write_string_to_location, MemoryArrayWriter, MemoryWriter}; +use super::*; +use crate::linux::maps_reader::MappingInfo; -type Result = std::result::Result; +type Result = std::result::Result; /// Write information about the mappings in effect. Because we are using the /// minidump format, the information about the mappings is pretty limited. @@ -14,7 +10,7 @@ type Result = std::result::Result; pub fn write( config: &mut MinidumpWriter, buffer: &mut DumpBuf, - dumper: &mut LinuxPtraceDumper, + dumper: &mut PtraceDumper, ) -> Result { let mut modules = Vec::new(); @@ -47,7 +43,7 @@ pub fn write( 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) + modules.push(module); } let list_header = MemoryWriter::::alloc_with_val(buffer, modules.len() as u32)?; @@ -58,7 +54,7 @@ pub fn write( }; if !modules.is_empty() { - let mapping_list = MemoryArrayWriter::::alloc_from_array(buffer, &modules)?; + let mapping_list = MemoryArrayWriter::::alloc_from_iter(buffer, modules)?; dirent.location.data_size += mapping_list.location().data_size; } @@ -70,12 +66,11 @@ fn fill_raw_module( mapping: &MappingInfo, identifier: &[u8], ) -> Result { - let cv_record: MDLocationDescriptor; - if identifier.is_empty() { + let cv_record = if identifier.is_empty() { // Just zeroes - cv_record = Default::default(); + Default::default() } else { - let cv_signature = MD_CVINFOELF_SIGNATURE; + let cv_signature = minidump_common::format::CvSignature::Elf as u32; let array_size = std::mem::size_of_val(&cv_signature) + identifier.len(); let mut sig_section = MemoryArrayWriter::::alloc_array(buffer, array_size)?; @@ -87,12 +82,12 @@ fn fill_raw_module( { sig_section.set_value_at(buffer, *val, index)?; } - cv_record = sig_section.location(); - } + sig_section.location() + }; let (file_path, _) = mapping .get_mapping_effective_name_and_path() - .map_err(|e| SectionMappingsError::GetEffectivePathError(mapping.clone(), e))?; + .map_err(|e| errors::SectionMappingsError::GetEffectivePathError(mapping.clone(), e))?; let name_header = write_string_to_location(buffer, &file_path)?; Ok(MDRawModule { diff --git a/src/sections/memory_list_stream.rs b/src/linux/sections/memory_list_stream.rs similarity index 69% rename from src/sections/memory_list_stream.rs rename to src/linux/sections/memory_list_stream.rs index 4f090831..572049ea 100644 --- a/src/sections/memory_list_stream.rs +++ b/src/linux/sections/memory_list_stream.rs @@ -1,9 +1,6 @@ -use crate::errors::SectionMemListError; -use crate::minidump_format::*; -use crate::minidump_writer::{DumpBuf, MinidumpWriter}; -use crate::sections::{MemoryArrayWriter, MemoryWriter}; +use super::*; -type Result = std::result::Result; +type Result = std::result::Result; pub fn write(config: &mut MinidumpWriter, buffer: &mut DumpBuf) -> Result { let list_header = diff --git a/src/linux/sections/systeminfo_stream.rs b/src/linux/sections/systeminfo_stream.rs new file mode 100644 index 00000000..5fc68eff --- /dev/null +++ b/src/linux/sections/systeminfo_stream.rs @@ -0,0 +1,25 @@ +use super::*; +use crate::linux::dumper_cpu_info::{os_information, write_cpu_information}; + +type Result = std::result::Result; + +pub fn write(buffer: &mut DumpBuf) -> Result { + let mut info_section = MemoryWriter::::alloc(buffer)?; + let dirent = MDRawDirectory { + stream_type: MDStreamType::SystemInfoStream as u32, + location: info_section.location(), + }; + + let (platform_id, os_version) = os_information(); + let os_version_loc = write_string_to_location(buffer, &os_version)?; + + // SAFETY: POD + let mut info = unsafe { std::mem::zeroed::() }; + info.platform_id = platform_id as u32; + info.csd_version_rva = os_version_loc.rva; + + write_cpu_information(&mut info)?; + + info_section.set_value(buffer, info)?; + Ok(dirent) +} diff --git a/src/sections/thread_list_stream.rs b/src/linux/sections/thread_list_stream.rs similarity index 91% rename from src/sections/thread_list_stream.rs rename to src/linux/sections/thread_list_stream.rs index d3672d99..3d6ac0ef 100644 --- a/src/sections/thread_list_stream.rs +++ b/src/linux/sections/thread_list_stream.rs @@ -1,13 +1,8 @@ -use crate::errors::SectionThreadListError; -use crate::linux_ptrace_dumper::LinuxPtraceDumper; -use crate::minidump_cpu::RawContextCPU; -use crate::minidump_format::*; -use crate::minidump_writer::{CrashingThreadContext, DumpBuf, MinidumpWriter}; -use crate::sections::{MemoryArrayWriter, MemoryWriter}; -use std::convert::TryInto; -use std::io::Write; +use super::*; +use crate::{minidump_cpu::RawContextCPU, minidump_writer::CrashingThreadContext}; +use crash_context::CpuContext; -type Result = std::result::Result; +type Result = std::result::Result; // The following kLimit* constants are for when minidump_size_limit_ is set // and the minidump size might exceed it. @@ -34,7 +29,7 @@ enum MaxStackLen { pub fn write( config: &mut MinidumpWriter, buffer: &mut DumpBuf, - dumper: &LinuxPtraceDumper, + dumper: &PtraceDumper, ) -> Result { let num_threads = dumper.threads.len(); // Memory looks like this: @@ -68,7 +63,12 @@ pub fn write( for (idx, item) in dumper.threads.clone().iter().enumerate() { let mut thread = MDRawThread { thread_id: item.tid.try_into()?, - ..Default::default() + 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 @@ -77,8 +77,8 @@ pub fn write( // 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() as usize; - let stack_pointer = crash_context.get_stack_pointer() as usize; + let instruction_ptr = crash_context.instruction_pointer(); + let stack_pointer = crash_context.stack_pointer(); fill_thread_stack( config, buffer, @@ -116,7 +116,7 @@ pub fn write( ip_memory_d.memory.data_size = (end_of_range - ip_memory_d.start_of_memory_range) as u32; - let memory_copy = LinuxPtraceDumper::copy_from_process( + 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, @@ -178,7 +178,7 @@ pub fn write( fn fill_thread_stack( config: &mut MinidumpWriter, buffer: &mut DumpBuf, - dumper: &LinuxPtraceDumper, + dumper: &PtraceDumper, thread: &mut MDRawThread, instruction_ptr: usize, stack_ptr: usize, @@ -203,7 +203,7 @@ fn fill_thread_stack( } } - let mut stack_bytes = LinuxPtraceDumper::copy_from_process( + let mut stack_bytes = PtraceDumper::copy_from_process( thread.thread_id.try_into()?, stack as *mut libc::c_void, stack_len, @@ -232,7 +232,7 @@ fn fill_thread_stack( data_size: stack_bytes.len() as u32, rva: buffer.position() as u32, }; - buffer.write_all(&stack_bytes)?; + 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.clone()); diff --git a/src/sections/thread_names_stream.rs b/src/linux/sections/thread_names_stream.rs similarity index 65% rename from src/sections/thread_names_stream.rs rename to src/linux/sections/thread_names_stream.rs index decb1674..2e97f3cf 100644 --- a/src/sections/thread_names_stream.rs +++ b/src/linux/sections/thread_names_stream.rs @@ -1,14 +1,8 @@ -use crate::errors::SectionThreadNamesError; -use crate::linux_ptrace_dumper::LinuxPtraceDumper; -use crate::minidump_format::*; -use crate::minidump_writer::DumpBuf; -use crate::sections::write_string_to_location; -use crate::sections::{MemoryArrayWriter, MemoryWriter}; -use std::convert::TryInto; +use super::*; -type Result = std::result::Result; +type Result = std::result::Result; -pub fn write(buffer: &mut DumpBuf, dumper: &LinuxPtraceDumper) -> Result { +pub fn write(buffer: &mut DumpBuf, dumper: &PtraceDumper) -> Result { // Only count threads that have a name let num_threads = dumper.threads.iter().filter(|t| t.name.is_some()).count(); // Memory looks like this: @@ -26,7 +20,7 @@ pub fn write(buffer: &mut DumpBuf, dumper: &LinuxPtraceDumper) -> Result = std::result::Result; pub type Pid = i32; -#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] -#[path = "thread_info_x86.rs"] -mod imp; -#[cfg(target_arch = "arm")] -#[path = "thread_info_arm.rs"] -mod imp; -#[cfg(target_arch = "aarch64")] -#[path = "thread_info_aarch64.rs"] -mod imp; -#[cfg(target_arch = "mips")] -#[path = "thread_info_mips.rs"] -mod imp; - -#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] -pub type ThreadInfo = imp::ThreadInfoX86; -#[cfg(target_arch = "arm")] -pub type ThreadInfo = imp::ThreadInfoArm; -#[cfg(target_arch = "aarch64")] -pub type ThreadInfo = imp::ThreadInfoAarch64; -#[cfg(target_arch = "mips")] -pub type ThreadInfo = imp::ThreadInfoMips; +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)] enum NT_Elf { NT_NONE = 0, NT_PRSTATUS = 1, - NT_PRFPREG = 2, + NT_PRFPREGSET = 2, //NT_PRPSINFO = 3, //NT_TASKSTRUCT = 4, //NT_AUXV = 6, } -pub fn to_u128(slice: &[u32]) -> Vec { - let mut res = Vec::new(); - for chunk in slice.chunks_exact(4) { - let value = u128::from_ne_bytes( - chunk - .iter() - .map(|x| x.to_ne_bytes().to_vec()) - .flatten() - .collect::>() - .as_slice() - .try_into() // Make slice into fixed size array - .unwrap(), // Which has to work as we know the numbers work out - ); - res.push(value) - } - res +#[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 { @@ -78,25 +69,26 @@ trait CommonThreadInfo { tgid = l .get(6..) .ok_or_else(|| ThreadInfoError::InvalidProcStatusFile(tid, l.clone()))? - .parse::()? + .parse::()?; } "PPid:\t" => { ppid = l .get(6..) .ok_or_else(|| ThreadInfoError::InvalidProcStatusFile(tid, l.clone()))? - .parse::()? + .parse::()?; } _ => continue, } } if ppid == -1 || tgid == -1 { - return Err(ThreadInfoError::InvalidPid( + Err(ThreadInfoError::InvalidPid( format!("/proc/{}/status", tid), ppid, tgid, - )); + )) + } else { + Ok((ppid, tgid)) } - Ok((ppid, tgid)) } /// SLIGHTLY MODIFIED COPY FROM CRATE nix @@ -109,13 +101,13 @@ trait CommonThreadInfo { flag: Option, pid: nix::unistd::Pid, ) -> Result { - let mut data = std::mem::MaybeUninit::uninit(); + 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() as *const _ as *const libc::c_void, + data.as_mut_ptr(), ) }; Errno::result(res)?; @@ -132,9 +124,9 @@ trait CommonThreadInfo { flag: Option, pid: nix::unistd::Pid, ) -> Result { - let mut data = std::mem::MaybeUninit::uninit(); + let mut data = std::mem::MaybeUninit::::uninit(); let io = libc::iovec { - iov_base: data.as_mut_ptr() as *mut libc::c_void, + iov_base: data.as_mut_ptr().cast(), iov_len: std::mem::size_of::(), }; let res = unsafe { @@ -142,7 +134,7 @@ trait CommonThreadInfo { request as ptrace::RequestType, libc::pid_t::from(pid), flag.unwrap_or(NT_Elf::NT_NONE), - &io as *const _ as *const libc::c_void, + (&io as *const libc::iovec).cast::(), ) }; Errno::result(res)?; @@ -166,13 +158,14 @@ trait CommonThreadInfo { ) }; match Errno::result(ret) { - Ok(..) | Err(nix::Error::Sys(Errno::UnknownErrno)) => Ok(ret), + Ok(..) | Err(nix::Error::UnknownErrno) => Ok(ret), err @ Err(..) => err, } } } impl ThreadInfo { - pub fn create(pid: Pid, tid: Pid) -> std::result::Result { - Self::create_impl(pid, tid) + pub fn create(_pid: Pid, tid: Pid) -> std::result::Result { + let (ppid, tgid) = Self::get_ppid_and_tgid(tid)?; + Self::create_impl(tid, ppid, tgid) } } diff --git a/src/linux/thread_info/aarch64.rs b/src/linux/thread_info/aarch64.rs new file mode 100644 index 00000000..878ed7cb --- /dev/null +++ b/src/linux/thread_info/aarch64.rs @@ -0,0 +1,129 @@ +use super::{CommonThreadInfo, Pid}; +use crate::errors::ThreadInfoError; +use crate::minidump_cpu::RawContextCPU; +use nix::sys::ptrace; + +pub const MD_FLOATINGSAVEAREA_ARM64_FPR_COUNT: usize = 32; +pub const MD_CONTEXT_ARM64_GPR_COUNT: usize = 33; + +/* Indices into iregs for registers with a dedicated or conventional + * purpose. + */ +pub enum MDARM64RegisterNumbers { + Fp = 29, + Lr = 30, + Sp = 31, + Pc = 32, +} + +/// 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 = std::result::Result; + +#[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 { + Self::ptrace_get_data_via_io::( + ptrace::Request::PTRACE_GETREGSET, + Some(super::NT_Elf::NT_PRFPREGSET), + nix::unistd::Pid::from_raw(pid), + ) + .or_else(|_err| { + // TODO: nix restricts PTRACE_GETFPREGS to arm android for some reason + let mut data = std::mem::MaybeUninit::::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() }) + }) + } + + fn getregs(pid: Pid) -> Result { + Self::ptrace_get_data_via_io::( + ptrace::Request::PTRACE_GETREGSET, + Some(super::NT_Elf::NT_PRSTATUS), + nix::unistd::Pid::from_raw(pid), + ) + .or_else(|_err| { + // TODO: nix restricts PTRACE_GETREGS to arm android for some reason + let mut data = std::mem::MaybeUninit::::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() }) + }) + } + + pub fn fill_cpu_context(&self, out: &mut RawContextCPU) { + out.context_flags = + minidump_common::format::ContextFlagsArm64Old::CONTEXT_ARM64_FULL_OLD.bits() as u64; + + /// This is the number of general purpose registers _not_ counting + /// the stack pointer + const GP_REG_COUNT: usize = 31; + /// The number of floating point registers in the floating point save area + const FP_REG_COUNT: usize = 32; + + out.cpsr = self.regs.pstate as u32; + out.iregs[..GP_REG_COUNT].copy_from_slice(&self.regs.regs[..GP_REG_COUNT]); + out.iregs[MDARM64RegisterNumbers::Sp as usize] = 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.float_save.fpsr = self.fpregs.fpsr; + out.float_save.fpcr = self.fpregs.fpcr; + out.float_save.regs[..FP_REG_COUNT].copy_from_slice(&self.fpregs.vregs[..FP_REG_COUNT]); + } + + pub fn create_impl(tid: Pid, ppid: Pid, tgid: Pid) -> Result { + let regs = Self::getregs(tid)?; + let fpregs = Self::getfpregs(tid)?; + + let stack_pointer = regs.regs[13] as usize; + + Ok(Self { + stack_pointer, + tgid, + ppid, + regs, + fpregs, + }) + } +} diff --git a/src/thread_info/thread_info_arm.rs b/src/linux/thread_info/arm.rs similarity index 95% rename from src/thread_info/thread_info_arm.rs rename to src/linux/thread_info/arm.rs index a66670ce..4ac955df 100644 --- a/src/thread_info/thread_info_arm.rs +++ b/src/linux/thread_info/arm.rs @@ -97,14 +97,13 @@ impl ThreadInfoArm { } } - pub fn create_impl(_pid: Pid, tid: Pid) -> Result { - let (ppid, tgid) = Self::get_ppid_and_tgid(tid)?; + pub fn create_impl(tid: Pid, ppid: Pid, tgid: Pid) -> Result { let regs = Self::getregs(tid)?; let fpregs = Self::getfpregs(tid)?; let stack_pointer = regs.uregs[13] as usize; - Ok(ThreadInfoArm { + Ok(Self { stack_pointer, tgid, ppid, diff --git a/src/thread_info/thread_info_mips.rs b/src/linux/thread_info/mips.rs similarity index 100% rename from src/thread_info/thread_info_mips.rs rename to src/linux/thread_info/mips.rs diff --git a/src/thread_info/thread_info_x86.rs b/src/linux/thread_info/x86.rs similarity index 57% rename from src/thread_info/thread_info_x86.rs rename to src/linux/thread_info/x86.rs index 6dbf6c2d..eab420b7 100644 --- a/src/thread_info/thread_info_x86.rs +++ b/src/linux/thread_info/x86.rs @@ -1,17 +1,12 @@ use super::{CommonThreadInfo, NT_Elf, Pid}; use crate::errors::ThreadInfoError; -use crate::minidump_cpu::imp::*; use crate::minidump_cpu::RawContextCPU; #[cfg(target_arch = "x86_64")] -use crate::thread_info::to_u128; +use crate::thread_info::copy_u32_registers; use core::mem::size_of_val; -use libc; -use libc::user; +use libc::{self, user}; use memoffset; -use nix::sys::ptrace; -use nix::unistd; -#[cfg(target_arch = "x86")] -use std::convert::TryInto; +use nix::{sys::ptrace, unistd}; type Result = std::result::Result; @@ -47,7 +42,7 @@ impl ThreadInfoX86 { fn getfpregset(pid: Pid) -> Result { Self::ptrace_get_data_via_io::( ptrace::Request::PTRACE_GETREGSET, - Some(NT_Elf::NT_PRFPREG), + Some(NT_Elf::NT_PRFPREGSET), nix::unistd::Pid::from_raw(pid), ) } @@ -80,8 +75,7 @@ impl ThreadInfoX86 { ) } - pub fn create_impl(_pid: Pid, tid: Pid) -> Result { - let (ppid, tgid) = Self::get_ppid_and_tgid(tid)?; + pub fn create_impl(tid: Pid, ppid: Pid, tgid: Pid) -> Result { 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")] @@ -102,18 +96,18 @@ impl ThreadInfoX86 { let debug_offset = memoffset::offset_of!(user, u_debugreg); let elem_offset = size_of_val(&dregs[0]); - for idx in 0..NUM_DEBUG_REGISTERS { + 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")] { - dregs[idx] = chunk as u64; // libc / ptrace is very messy wrt int types used... + *dreg = chunk as u64; // libc / ptrace is very messy wrt int types used... } #[cfg(target_arch = "x86")] { - dregs[idx] = chunk as i32; // libc / ptrace is very messy wrt int types used... + *dreg = chunk as i32; // libc / ptrace is very messy wrt int types used... } } @@ -122,7 +116,7 @@ impl ThreadInfoX86 { #[cfg(target_arch = "x86")] let stack_pointer = regs.esp as usize; - Ok(ThreadInfoX86 { + Ok(Self { stack_pointer, tgid, ppid, @@ -146,17 +140,20 @@ impl ThreadInfoX86 { #[cfg(target_arch = "x86_64")] pub fn fill_cpu_context(&self, out: &mut RawContextCPU) { - out.context_flags = MD_CONTEXT_AMD64_FULL | MD_CONTEXT_AMD64_SEGMENTS; + out.context_flags = crate::minidump_format::format::ContextFlagsAmd64::CONTEXT_AMD64_FULL + .bits() + | crate::minidump_format::format::ContextFlagsAmd64::CONTEXT_AMD64_SEGMENTS.bits(); - out.cs = self.regs.cs as u16; // TODO: This is u64, do we loose information by doing this? + // TODO: These are all u64, do we lose information by doing this? + out.cs = self.regs.cs as u16; - 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.ds = self.regs.ds as u16; + out.es = self.regs.es as u16; + out.fs = self.regs.fs as u16; + out.gs = self.regs.gs as u16; - 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.ss = self.regs.ss as u16; + out.eflags = self.regs.eflags as u32; out.dr0 = self.dregs[0]; out.dr1 = self.dregs[1]; @@ -188,33 +185,35 @@ impl ThreadInfoX86 { out.rip = self.regs.rip; - out.flt_save.control_word = self.fpregs.cwd; - out.flt_save.status_word = self.fpregs.swd; - out.flt_save.tag_word = self.fpregs.ftw as u8; // TODO: This is u16, do we loose information by doing this? - out.flt_save.error_opcode = self.fpregs.fop; - out.flt_save.error_offset = self.fpregs.rip as u32; // TODO: This is u64, do we loose information by doing this? - out.flt_save.error_selector = 0; // We don't have this. - out.flt_save.data_offset = self.fpregs.rdp as u32; // TODO: This is u64, do we loose information by doing this? - out.flt_save.data_selector = 0; // We don't have this. - out.flt_save.mx_csr = self.fpregs.mxcsr; - out.flt_save.mx_csr_mask = self.fpregs.mxcr_mask; - - let data = to_u128(&self.fpregs.st_space); - for idx in 0..data.len() { - out.flt_save.float_registers[idx] = data[idx]; - } - - let data = to_u128(&self.fpregs.xmm_space); - for idx in 0..data.len() { - out.flt_save.xmm_registers[idx] = data[idx]; + { + 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() + }; + + copy_u32_registers(&mut float_save.float_registers, &fs.st_space); + copy_u32_registers(&mut float_save.xmm_registers, &fs.xmm_space); + + use scroll::Pwrite; + out.float_save + .pwrite_with(float_save, 0, scroll::Endian::Little) + .expect("this is impossible"); } - // my_memcpy(&out.flt_save.float_registers, &self.fpregs.st_space, 8 * 16); - // my_memcpy(&out.flt_save.xmm_registers, &self.fpregs.xmm_space, 16 * 16); } #[cfg(target_arch = "x86")] pub fn fill_cpu_context(&self, out: &mut RawContextCPU) { - out.context_flags = MD_CONTEXT_X86_ALL; + out.context_flags = crate::minidump_format::format::ContextFlagsX86::CONTEXT_X86_ALL.bits(); out.dr0 = self.dregs[0] as u32; out.dr3 = self.dregs[3] as u32; @@ -252,74 +251,54 @@ impl ThreadInfoX86 { out.float_save.data_offset = self.fpregs.foo as u32; out.float_save.data_selector = self.fpregs.fos as u32; - // 8 registers * 10 bytes per register. - // my_memcpy(out->float_save.register_area, fpregs.st_space, 10 * 8); - out.float_save.register_area = self - .fpregs - .st_space - .iter() - .map(|x| x.to_ne_bytes().to_vec()) - .flatten() - .take(MD_FLOATINGSAVEAREA_X86_REGISTERAREA_SIZE) - .collect::>() - .as_slice() - .try_into() // Make slice into fixed size array - .unwrap(); // Which has to work as we know the numbers work out - - // This matches the Intel fpsave format. - let mut idx = 0; - for val in &(self.fpregs.cwd as u16).to_ne_bytes() { - out.extended_registers[idx] = *val; - idx += 1; - } - for val in &(self.fpregs.swd as u16).to_ne_bytes() { - out.extended_registers[idx] = *val; - idx += 1; - } - for val in &(self.fpregs.twd as u16).to_ne_bytes() { - out.extended_registers[idx] = *val; - idx += 1; - } - for val in &(self.fpxregs.fop as u16).to_ne_bytes() { - out.extended_registers[idx] = *val; - idx += 1; - } - for val in &(self.fpxregs.fip as u32).to_ne_bytes() { - out.extended_registers[idx] = *val; - idx += 1; - } - for val in &(self.fpxregs.fcs as u16).to_ne_bytes() { - out.extended_registers[idx] = *val; - idx += 1; - } - for val in &(self.fpregs.foo as u32).to_ne_bytes() { - out.extended_registers[idx] = *val; - idx += 1; - } - for val in &(self.fpregs.fos as u16).to_ne_bytes() { - out.extended_registers[idx] = *val; - idx += 1; - } - for val in &(self.fpxregs.mxcsr as u32).to_ne_bytes() { - out.extended_registers[idx] = *val; - idx += 1; + use scroll::Pwrite; + { + 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::(); + if offset >= ra.len() { + break; + } + + ra.pwrite_with(block, offset, scroll::Endian::Little) + .expect("checked"); + } } - // my_memcpy(out->extended_registers + 32, &fpxregs.st_space, 128); - idx = 32; - for val in &self.fpxregs.st_space { - for byte in &val.to_ne_bytes() { - out.extended_registers[idx] = *byte; - idx += 1; + #[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() + }; } - } - // my_memcpy(out->extended_registers + 160, &fpxregs.xmm_space, 128); - idx = 160; - for val in &self.fpxregs.xmm_space { - for byte in &val.to_ne_bytes() { - out.extended_registers[idx] = *byte; - idx += 1; + // 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); } } } diff --git a/src/minidump_cpu.rs b/src/minidump_cpu.rs new file mode 100644 index 00000000..51884ebd --- /dev/null +++ b/src/minidump_cpu.rs @@ -0,0 +1,20 @@ +cfg_if::cfg_if! { + if #[cfg(target_arch = "x86_64")] { + pub type RawContextCPU = minidump_common::format::CONTEXT_AMD64; + pub type FloatStateCPU = minidump_common::format::XMM_SAVE_AREA32; + } else if #[cfg(target_arch = "x86")] { + pub type RawContextCPU = minidump_common::format::CONTEXT_X86; + pub type FloatStateCPU = minidump_common::format::FLOATING_SAVE_AREA_X86; + } else if #[cfg(target_arch = "arm")] { + pub mod arm; + pub use arm as imp; + pub type RawContextCPU = arm::MDRawContextARM; + } else if #[cfg(target_arch = "aarch64")] { + pub type RawContextCPU = minidump_common::format::CONTEXT_ARM64_OLD; + pub type FloatStateCPU = minidump_common::format::FLOATING_SAVE_AREA_ARM64_OLD; + } else if #[cfg(target_arch = "mips")] { + compile_error!("flesh me out"); + } else { + compile_error!("unsupported target architecture"); + } +} diff --git a/src/minidump_cpu/minidump_cpu_arm.rs b/src/minidump_cpu/arm.rs similarity index 100% rename from src/minidump_cpu/minidump_cpu_arm.rs rename to src/minidump_cpu/arm.rs diff --git a/src/minidump_cpu/minidump_cpu_aarch64.rs b/src/minidump_cpu/minidump_cpu_aarch64.rs deleted file mode 100644 index c35e2e3c..00000000 --- a/src/minidump_cpu/minidump_cpu_aarch64.rs +++ /dev/null @@ -1,12 +0,0 @@ -pub const MD_FLOATINGSAVEAREA_ARM64_FPR_COUNT: usize = 32; -pub const MD_CONTEXT_ARM64_GPR_COUNT: usize = 33; - -/* Indices into iregs for registers with a dedicated or conventional - * purpose. - */ -pub enum MDARM64RegisterNumbers { - MD_CONTEXT_ARM64_REG_FP = 29, - MD_CONTEXT_ARM64_REG_LR = 30, - MD_CONTEXT_ARM64_REG_SP = 31, - MD_CONTEXT_ARM64_REG_PC = 32, -} diff --git a/src/minidump_cpu/minidump_cpu_amd64.rs b/src/minidump_cpu/minidump_cpu_amd64.rs deleted file mode 100644 index 3924d792..00000000 --- a/src/minidump_cpu/minidump_cpu_amd64.rs +++ /dev/null @@ -1,179 +0,0 @@ -#[repr(C)] -pub struct MDXmmSaveArea32AMD64 { - pub control_word: u16, - pub status_word: u16, - pub tag_word: u8, - pub reserved1: u8, - pub error_opcode: u16, - pub error_offset: u32, - pub error_selector: u16, - pub reserved2: u16, - pub data_offset: u32, - pub data_selector: u16, - pub reserved3: u16, - pub mx_csr: u32, - pub mx_csr_mask: u32, - pub float_registers: [u128; 8], - pub xmm_registers: [u128; 16], - pub reserved4: [u8; 96], -} - -// The std library doesn't provide "Default" for all -// array-lengths. Only up to 32. So we have to implement -// our own default, because of `reserved4: [u8; 96]` -impl Default for MDXmmSaveArea32AMD64 { - #[inline] - fn default() -> Self { - MDXmmSaveArea32AMD64 { - control_word: 0, - status_word: 0, - tag_word: 0, - reserved1: 0, - error_opcode: 0, - error_offset: 0, - error_selector: 0, - reserved2: 0, - data_offset: 0, - data_selector: 0, - reserved3: 0, - mx_csr: 0, - mx_csr_mask: 0, - float_registers: [0; 8], - xmm_registers: [0; 16], - reserved4: [0; 96], - } - } -} - -const MD_CONTEXT_AMD64_VR_COUNT: usize = 26; - -#[repr(C)] -#[derive(Default)] -pub struct MDRawContextAMD64 { - /* - * Register parameter home addresses. - */ - pub p1_home: u64, - pub p2_home: u64, - pub p3_home: u64, - pub p4_home: u64, - pub p5_home: u64, - pub p6_home: u64, - - /* The next field determines the layout of the structure, and which parts - * of it are populated */ - pub context_flags: u32, - pub mx_csr: u32, - - /* The next register is included with MD_CONTEXT_AMD64_CONTROL */ - pub cs: u16, - - /* The next 4 registers are included with MD_CONTEXT_AMD64_SEGMENTS */ - pub ds: u16, - pub es: u16, - pub fs: u16, - pub gs: u16, - - /* The next 2 registers are included with MD_CONTEXT_AMD64_CONTROL */ - pub ss: u16, - pub eflags: u32, - - /* The next 6 registers are included with MD_CONTEXT_AMD64_DEBUG_REGISTERS */ - pub dr0: u64, - pub dr1: u64, - pub dr2: u64, - pub dr3: u64, - pub dr6: u64, - pub dr7: u64, - - /* The next 4 registers are included with MD_CONTEXT_AMD64_INTEGER */ - pub rax: u64, - pub rcx: u64, - pub rdx: u64, - pub rbx: u64, - - /* The next register is included with MD_CONTEXT_AMD64_CONTROL */ - pub rsp: u64, - - /* The next 11 registers are included with MD_CONTEXT_AMD64_INTEGER */ - pub rbp: u64, - pub rsi: u64, - pub rdi: u64, - pub r8: u64, - pub r9: u64, - pub r10: u64, - pub r11: u64, - pub r12: u64, - pub r13: u64, - pub r14: u64, - pub r15: u64, - - /* The next register is included with MD_CONTEXT_AMD64_CONTROL */ - pub rip: u64, - - /* The next set of registers are included with - * MD_CONTEXT_AMD64_FLOATING_POINT - */ - pub flt_save: MDXmmSaveArea32AMD64, - // union { - // MDXmmSaveArea32AMD64 flt_save; - // struct { - // uint128_struct header[2]; - // uint128_struct legacy[8]; - // uint128_struct xmm0; - // uint128_struct xmm1; - // uint128_struct xmm2; - // uint128_struct xmm3; - // uint128_struct xmm4; - // uint128_struct xmm5; - // uint128_struct xmm6; - // uint128_struct xmm7; - // uint128_struct xmm8; - // uint128_struct xmm9; - // uint128_struct xmm10; - // uint128_struct xmm11; - // uint128_struct xmm12; - // uint128_struct xmm13; - // uint128_struct xmm14; - // uint128_struct xmm15; - // } sse_registers; - // }; - pub vector_register: [u128; MD_CONTEXT_AMD64_VR_COUNT], - pub vector_control: u64, - - /* The next 5 registers are included with MD_CONTEXT_AMD64_DEBUG_REGISTERS */ - pub debug_control: u64, - pub last_branch_to_rip: u64, - pub last_branch_from_rip: u64, - pub last_exception_to_rip: u64, - pub last_exception_from_rip: u64, -} - -/* For (MDRawContextAMD64).context_flags. These values indicate the type of -* context stored in the structure. The high 24 bits identify the CPU, the -* low 8 bits identify the type of context saved. */ -pub const MD_CONTEXT_AMD64: u32 = 0x00100000; /* CONTEXT_AMD64 */ -pub const MD_CONTEXT_AMD64_CONTROL: u32 = MD_CONTEXT_AMD64 | 0x00000001; -/* CONTEXT_CONTROL */ -pub const MD_CONTEXT_AMD64_INTEGER: u32 = MD_CONTEXT_AMD64 | 0x00000002; -/* CONTEXT_INTEGER */ -pub const MD_CONTEXT_AMD64_SEGMENTS: u32 = MD_CONTEXT_AMD64 | 0x00000004; -/* CONTEXT_SEGMENTS */ -pub const MD_CONTEXT_AMD64_FLOATING_POINT: u32 = MD_CONTEXT_AMD64 | 0x00000008; -/* CONTEXT_FLOATING_POINT */ -pub const MD_CONTEXT_AMD64_DEBUG_REGISTERS: u32 = MD_CONTEXT_AMD64 | 0x00000010; -/* CONTEXT_DEBUG_REGISTERS */ -pub const MD_CONTEXT_AMD64_XSTATE: u32 = MD_CONTEXT_AMD64 | 0x00000040; -/* CONTEXT_XSTATE */ - -/* WinNT.h refers to CONTEXT_MMX_REGISTERS but doesn't appear to define it -* I think it really means CONTEXT_FLOATING_POINT. -*/ - -pub const MD_CONTEXT_AMD64_FULL: u32 = - MD_CONTEXT_AMD64_CONTROL | MD_CONTEXT_AMD64_INTEGER | MD_CONTEXT_AMD64_FLOATING_POINT; -/* CONTEXT_FULL */ - -pub const MD_CONTEXT_AMD64_ALL: u32 = - MD_CONTEXT_AMD64_FULL | MD_CONTEXT_AMD64_SEGMENTS | MD_CONTEXT_AMD64_DEBUG_REGISTERS; -/* CONTEXT_ALL */ diff --git a/src/minidump_cpu/minidump_cpu_mips.rs b/src/minidump_cpu/minidump_cpu_mips.rs deleted file mode 100644 index 8b137891..00000000 --- a/src/minidump_cpu/minidump_cpu_mips.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/minidump_cpu/minidump_cpu_ppc.rs b/src/minidump_cpu/minidump_cpu_ppc.rs deleted file mode 100644 index e69de29b..00000000 diff --git a/src/minidump_cpu/minidump_cpu_ppc64.rs b/src/minidump_cpu/minidump_cpu_ppc64.rs deleted file mode 100644 index e69de29b..00000000 diff --git a/src/minidump_cpu/minidump_cpu_sparc.rs b/src/minidump_cpu/minidump_cpu_sparc.rs deleted file mode 100644 index e69de29b..00000000 diff --git a/src/minidump_cpu/minidump_cpu_x86.rs b/src/minidump_cpu/minidump_cpu_x86.rs deleted file mode 100644 index b0b073ff..00000000 --- a/src/minidump_cpu/minidump_cpu_x86.rs +++ /dev/null @@ -1,151 +0,0 @@ -pub const MD_FLOATINGSAVEAREA_X86_REGISTERAREA_SIZE: usize = 80; - -#[repr(C)] -#[derive(Debug, PartialEq)] -pub struct MDFloatingSaveAreaX86 { - pub control_word: u32, - pub status_word: u32, - pub tag_word: u32, - pub error_offset: u32, - pub error_selector: u32, - pub data_offset: u32, - pub data_selector: u32, - - /* register_area contains eight 80-bit (x87 "long double") quantities for - * floating-point registers %st0 (%mm0) through %st7 (%mm7). */ - pub register_area: [u8; MD_FLOATINGSAVEAREA_X86_REGISTERAREA_SIZE], - pub cr0_npx_state: u32, -} - -// The std library doesn't provide "Default" for all -// array-lengths. Only up to 32. So we have to implement -// our own default, because of `reserved4: [u8; 96]` -impl Default for MDFloatingSaveAreaX86 { - #[inline] - fn default() -> Self { - MDFloatingSaveAreaX86 { - control_word: 0, - status_word: 0, - tag_word: 0, - error_offset: 0, - error_selector: 0, - data_offset: 0, - data_selector: 0, - register_area: [0; MD_FLOATINGSAVEAREA_X86_REGISTERAREA_SIZE], - cr0_npx_state: 0, - } - } -} - -const MD_CONTEXT_X86_EXTENDED_REGISTERS_SIZE: usize = 512; -/* MAXIMUM_SUPPORTED_EXTENSION */ - -#[repr(C)] -#[derive(Debug, PartialEq)] -pub struct MDRawContextX86 { - /* The next field determines the layout of the structure, and which parts - * of it are populated */ - pub context_flags: u32, - - /* The next 6 registers are included with MD_CONTEXT_X86_DEBUG_REGISTERS */ - pub dr0: u32, - pub dr1: u32, - pub dr2: u32, - pub dr3: u32, - pub dr6: u32, - pub dr7: u32, - - /* The next field is included with MD_CONTEXT_X86_FLOATING_POINT */ - pub float_save: MDFloatingSaveAreaX86, - - /* The next 4 registers are included with MD_CONTEXT_X86_SEGMENTS */ - pub gs: u32, - pub fs: u32, - pub es: u32, - pub ds: u32, - /* The next 6 registers are included with MD_CONTEXT_X86_INTEGER */ - pub edi: u32, - pub esi: u32, - pub ebx: u32, - pub edx: u32, - pub ecx: u32, - pub eax: u32, - - /* The next 6 registers are included with MD_CONTEXT_X86_CONTROL */ - pub ebp: u32, - pub eip: u32, - pub cs: u32, /* WinNT.h says "must be sanitized" */ - pub eflags: u32, /* WinNT.h says "must be sanitized" */ - pub esp: u32, - pub ss: u32, - - /* The next field is included with MD_CONTEXT_X86_EXTENDED_REGISTERS. - * It contains vector (MMX/SSE) registers. It it laid out in the - * format used by the fxsave and fsrstor instructions, so it includes - * a copy of the x87 floating-point registers as well. See FXSAVE in - * "Intel Architecture Software Developer's Manual, Volume 2." */ - pub extended_registers: [u8; MD_CONTEXT_X86_EXTENDED_REGISTERS_SIZE], -} - -impl Default for MDRawContextX86 { - #[inline] - fn default() -> Self { - MDRawContextX86 { - context_flags: 0, - dr0: 0, - dr1: 0, - dr2: 0, - dr3: 0, - dr6: 0, - dr7: 0, - float_save: Default::default(), - gs: 0, - fs: 0, - es: 0, - ds: 0, - edi: 0, - esi: 0, - ebx: 0, - edx: 0, - ecx: 0, - eax: 0, - ebp: 0, - eip: 0, - cs: 0, - eflags: 0, - esp: 0, - ss: 0, - extended_registers: [0; MD_CONTEXT_X86_EXTENDED_REGISTERS_SIZE], - } - } -} - -/* For (MDRawContextX86).context_flags. These values indicate the type of - * context stored in the structure. The high 24 bits identify the CPU, the - * low 8 bits identify the type of context saved. */ -pub const MD_CONTEXT_X86: u32 = 0x00010000; -/* CONTEXT_i386, CONTEXT_i486: identifies CPU */ -pub const MD_CONTEXT_X86_CONTROL: u32 = MD_CONTEXT_X86 | 0x00000001; -/* CONTEXT_CONTROL */ -pub const MD_CONTEXT_X86_INTEGER: u32 = MD_CONTEXT_X86 | 0x00000002; -/* CONTEXT_INTEGER */ -pub const MD_CONTEXT_X86_SEGMENTS: u32 = MD_CONTEXT_X86 | 0x00000004; -/* CONTEXT_SEGMENTS */ -pub const MD_CONTEXT_X86_FLOATING_POINT: u32 = MD_CONTEXT_X86 | 0x00000008; -/* CONTEXT_FLOATING_POINT */ -pub const MD_CONTEXT_X86_DEBUG_REGISTERS: u32 = MD_CONTEXT_X86 | 0x00000010; -/* CONTEXT_DEBUG_REGISTERS */ -pub const MD_CONTEXT_X86_EXTENDED_REGISTERS: u32 = MD_CONTEXT_X86 | 0x00000020; -/* CONTEXT_EXTENDED_REGISTERS */ -pub const MD_CONTEXT_X86_XSTATE: u32 = MD_CONTEXT_X86 | 0x00000040; -/* CONTEXT_XSTATE */ - -pub const MD_CONTEXT_X86_FULL: u32 = - MD_CONTEXT_X86_CONTROL | MD_CONTEXT_X86_INTEGER | MD_CONTEXT_X86_SEGMENTS; -/* CONTEXT_FULL */ - -pub const MD_CONTEXT_X86_ALL: u32 = MD_CONTEXT_X86_FULL - | MD_CONTEXT_X86_FLOATING_POINT - | MD_CONTEXT_X86_DEBUG_REGISTERS - | MD_CONTEXT_X86_EXTENDED_REGISTERS; -/* CONTEXT_ALL */ diff --git a/src/minidump_cpu/mod.rs b/src/minidump_cpu/mod.rs deleted file mode 100644 index ebce0ad5..00000000 --- a/src/minidump_cpu/mod.rs +++ /dev/null @@ -1,26 +0,0 @@ -#[cfg(target_arch = "x86_64")] -#[path = "minidump_cpu_amd64.rs"] -pub mod imp; -#[cfg(target_arch = "x86")] -#[path = "minidump_cpu_x86.rs"] -pub mod imp; -#[cfg(target_arch = "arm")] -#[path = "minidump_cpu_arm.rs"] -pub mod imp; -#[cfg(target_arch = "aarch64")] -#[path = "minidump_cpu_aarch64.rs"] -pub mod imp; -#[cfg(target_arch = "mips")] -#[path = "minidump_cpu_mips.rs"] -pub mod imp; - -#[cfg(target_arch = "x86_64")] -pub type RawContextCPU = imp::MDRawContextAMD64; -#[cfg(target_arch = "x86")] -pub type RawContextCPU = imp::MDRawContextX86; -#[cfg(target_arch = "arm")] -pub type RawContextCPU = imp::MDRawContextARM; -#[cfg(target_arch = "aarch64")] -pub type RawContextCPU = imp::MDRawContextX86; -#[cfg(target_arch = "mips")] -pub type RawContextCPU = i32; diff --git a/src/minidump_format.rs b/src/minidump_format.rs index cd6910c8..8ffa40fc 100644 --- a/src/minidump_format.rs +++ b/src/minidump_format.rs @@ -1,437 +1,40 @@ -#[repr(C)] -#[derive(Debug, Default, PartialEq)] -pub struct MDGUID { - data1: u32, - data2: u16, - data3: u16, - data4: [u8; 8], -} - -#[repr(C)] -#[derive(Debug, Default, PartialEq, Clone, Copy)] -pub struct MDVSFixedFileInfo { - pub signature: u32, - pub struct_version: u32, - pub file_version_hi: u32, - pub file_version_lo: u32, - pub product_version_hi: u32, - pub product_version_lo: u32, - pub file_flags_mask: u32, /* Identifies valid bits in fileFlags */ - pub file_flags: u32, - pub file_os: u32, - pub file_type: u32, - pub file_subtype: u32, - pub file_date_hi: u32, - pub file_date_lo: u32, -} +pub use minidump_common::format::{ + self, ArmElfHwCaps as MDCPUInformationARMElfHwCaps, PlatformId, + ProcessorArchitecture as MDCPUArchitecture, GUID, MINIDUMP_DIRECTORY as MDRawDirectory, + MINIDUMP_EXCEPTION as MDException, MINIDUMP_EXCEPTION_STREAM as MDRawExceptionStream, + MINIDUMP_HEADER as MDRawHeader, MINIDUMP_LOCATION_DESCRIPTOR as MDLocationDescriptor, + MINIDUMP_MEMORY_DESCRIPTOR as MDMemoryDescriptor, MINIDUMP_MODULE as MDRawModule, + MINIDUMP_SIGNATURE as MD_HEADER_SIGNATURE, MINIDUMP_STREAM_TYPE as MDStreamType, + MINIDUMP_SYSTEM_INFO as MDRawSystemInfo, MINIDUMP_THREAD as MDRawThread, + MINIDUMP_THREAD_NAME as MDRawThreadName, MINIDUMP_VERSION as MD_HEADER_VERSION, + VS_FIXEDFILEINFO as MDVSFixedFileInfo, +}; /* An MDRVA is an offset into the minidump file. The beginning of the * MDRawHeader is at offset 0. */ pub type MDRVA = u32; -#[repr(C)] -#[derive(Debug, Default, Clone, Copy, PartialEq)] -pub struct MDLocationDescriptor { - pub data_size: u32, - pub rva: MDRVA, -} - -#[repr(C)] -#[derive(Debug, Default, Clone, PartialEq)] -pub struct MDMemoryDescriptor { - /* The base address of the memory range on the host that produced the - * minidump. */ - pub start_of_memory_range: u64, - pub memory: MDLocationDescriptor, -} - -#[repr(C)] -#[derive(Debug, Default, PartialEq)] -pub struct MDRawHeader { - pub signature: u32, - pub version: u32, - pub stream_count: u32, - pub stream_directory_rva: MDRVA, /* A |stream_count|-sized array of - * MDRawDirectory structures. */ - pub checksum: u32, /* Can be 0. In fact, that's all that's - * been found in minidump files. */ - pub time_date_stamp: u32, /* time_t */ - pub flags: u64, -} - -/* For (MDRawHeader).signature and (MDRawHeader).version. Note that only the - * low 16 bits of (MDRawHeader).version are MD_HEADER_VERSION. Per the - * documentation, the high 16 bits are implementation-specific. */ -pub const MD_HEADER_SIGNATURE: u32 = 0x504d444d; /* 'PMDM' */ -/* MINIDUMP_SIGNATURE */ -pub const MD_HEADER_VERSION: u32 = 0x0000a793; /* 42899 */ -/* MINIDUMP_VERSION */ - -/// The name of a thread, found in the ThreadNamesStream. -#[repr(C, packed)] -#[derive(Clone, Copy, Debug, Default, PartialEq)] -pub struct MDRawThreadName { - /// The id of the thread. - pub thread_id: u32, - /// Where the name of the thread is stored (yes, the legendary RVA64 is real!!). - pub thread_name_rva: u64, -} - -#[repr(C)] -#[derive(Debug, Default, PartialEq)] -pub struct MDRawThread { - pub thread_id: u32, - pub suspend_count: u32, - pub priority_class: u32, - pub priority: u32, - pub teb: u64, /* Thread environment block */ - pub stack: MDMemoryDescriptor, - pub thread_context: MDLocationDescriptor, /* MDRawContext[CPU] */ -} - pub type MDRawThreadList = Vec; -/* The inclusion of a 64-bit type in MINIDUMP_MODULE forces the struct to - * be tail-padded out to a multiple of 64 bits under some ABIs (such as PPC). - * This doesn't occur on systems that don't tail-pad in this manner. Define - * this macro to be the usable size of the MDRawModule struct, and use it in - * place of sizeof(MDRawModule). */ -// pub const MD_MODULE_SIZE: usize = 108; -// NOTE: We use "packed" here instead, to size_of::() == 108 -// "packed" should be safe here, as we don't address reserved{0,1} at all -// and padding should happen only at the tail -#[repr(C, packed)] -#[derive(Clone, Copy, Debug, Default, PartialEq)] -pub struct MDRawModule { - pub base_of_image: u64, - pub size_of_image: u32, - pub checksum: u32, /* 0 if unknown */ - pub time_date_stamp: u32, /* time_t */ - pub module_name_rva: MDRVA, /* MDString, pathname or filename */ - pub version_info: MDVSFixedFileInfo, - - /* The next field stores a CodeView record and is populated when a module's - * debug information resides in a PDB file. It identifies the PDB file. */ - pub cv_record: MDLocationDescriptor, - - /* The next field is populated when a module's debug information resides - * in a DBG file. It identifies the DBG file. This field is effectively - * obsolete with modules built by recent toolchains. */ - pub misc_record: MDLocationDescriptor, - - /* Alignment problem: reserved0 and reserved1 are defined by the platform - * SDK as 64-bit quantities. However, that results in a structure whose - * alignment is unpredictable on different CPUs and ABIs. If the ABI - * specifies full alignment of 64-bit quantities in structures (as ppc - * does), there will be padding between miscRecord and reserved0. If - * 64-bit quantities can be aligned on 32-bit boundaries (as on x86), - * this padding will not exist. (Note that the structure up to this point - * contains 1 64-bit member followed by 21 32-bit members.) - * As a workaround, reserved0 and reserved1 are instead defined here as - * four 32-bit quantities. This should be harmless, as there are - * currently no known uses for these fields. */ - pub reserved0: [u32; 2], - pub reserved1: [u32; 2], -} - -#[repr(C)] -#[derive(Debug, Default, PartialEq, Clone)] -pub struct MDRawDirectory { - pub stream_type: u32, - pub location: MDLocationDescriptor, -} - -#[repr(C)] -#[derive(Debug, Default, PartialEq)] -pub struct MDException { - pub exception_code: u32, /* Windows: MDExceptionCodeWin, - * Mac OS X: MDExceptionMac, - * Linux: MDExceptionCodeLinux. */ - pub exception_flags: u32, /* Windows: 1 if noncontinuable, - Mac OS X: MDExceptionCodeMac. */ - pub exception_record: u64, /* Address (in the minidump-producing host's - * memory) of another MDException, for - * nested exceptions. */ - pub exception_address: u64, /* The address that caused the exception. - * Mac OS X: exception subcode (which is - * typically the address). */ - pub number_parameters: u32, /* Number of valid elements in - * exception_information. */ - pub __align: u32, - pub exception_information: [u64; 15], -} - -#[repr(C)] -#[derive(Debug, Default, PartialEq)] -pub struct MDRawExceptionStream { - pub thread_id: u32, /* Thread in which the exception - * occurred. Corresponds to - * (MDRawThread).thread_id. */ - pub __align: u32, - pub exception_record: MDException, - pub thread_context: MDLocationDescriptor, /* MDRawContext[CPU] */ -} - -#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] -#[repr(C)] -#[derive(Debug, Default, PartialEq)] -pub struct MDCPUInformation { - pub vendor_id: [u32; 3], /* cpuid 0: ebx, edx, ecx */ - pub version_information: u32, /* cpuid 1: eax */ - pub feature_information: u32, /* cpuid 1: edx */ - pub amd_extended_cpu_features: u32, /* cpuid 0x80000001, ebx */ -} - -#[cfg(any(target_arch = "arm", target_arch = "aarch64"))] -#[repr(C)] -#[derive(Debug, Default, PartialEq)] -pub struct MDCPUInformation { - pub cpuid: u32, - pub elf_hwcaps: u32, /* linux specific, 0 otherwise */ - _padding: [u32; 4], -} - -#[cfg(target_arch = "mips")] -#[repr(C)] -#[derive(Debug, Default, PartialEq)] -pub struct MDCPUInformation { - pub cpuid: [u64; 2], - _padding: [u32; 2], -} - -/* For (MDCPUInformation).arm_cpu_info.elf_hwcaps. - * This matches the Linux kernel definitions from */ -#[repr(u32)] -pub enum MDCPUInformationARMElfHwCaps { - Swp = 1 << 0, - Half = 1 << 1, - Thumb = 1 << 2, - Bit26 = 1 << 3, - FastMult = 1 << 4, - Fpa = 1 << 5, - Vfp = 1 << 6, - Edsp = 1 << 7, - Java = 1 << 8, - Iwmmxt = 1 << 9, - Crunch = 1 << 10, - Thumbee = 1 << 11, - Neon = 1 << 12, - Vfpv3 = 1 << 13, - Vfpv3d16 = 1 << 14, - Tls = 1 << 15, - Vfpv4 = 1 << 16, - Idiva = 1 << 17, - Idivt = 1 << 18, -} - -#[repr(C)] -#[derive(Debug, Default, PartialEq)] -pub struct MDRawSystemInfo { - /* The next 3 fields and numberOfProcessors are from the SYSTEM_INFO - * structure as returned by GetSystemInfo */ - pub processor_architecture: u16, - pub processor_level: u16, /* x86: 5 = 586, 6 = 686, ... */ - /* ARM: 6 = ARMv6, 7 = ARMv7 ... */ - pub processor_revision: u16, /* x86: 0xMMSS, where MM=model, - * SS=stepping */ - /* ARM: 0 */ - pub number_of_processors: u8, - pub product_type: u8, /* Windows: VER_NT_* from WinNT.h */ - - /* The next 5 fields are from the OSVERSIONINFO structure as returned - * by GetVersionEx */ - pub major_version: u32, - pub minor_version: u32, - pub build_number: u32, - pub platform_id: u32, - pub csd_version_rva: MDRVA, /* MDString further identifying the - * host OS. - * Windows: name of the installed OS - * service pack. - * Mac OS X: the Apple OS build number - * (sw_vers -buildVersion). - * Linux: uname -srvmo */ - - pub suite_mask: u16, /* Windows: VER_SUITE_* from WinNT.h */ - pub reserved2: u16, - - pub cpu: MDCPUInformation, -} - -#[cfg(target_pointer_width = "64")] -#[derive(Debug, Default, PartialEq)] -pub struct MDRawLinkMap { - pub addr: u64, - pub name: MDRVA, - pub ld: u64, -} - -#[cfg(target_pointer_width = "64")] -#[derive(Debug, Default, PartialEq)] -pub struct MDRawDebug { - pub version: u32, - pub map: MDRVA, /* array of MDRawLinkMap64 */ - pub dso_count: u32, - pub brk: u64, - pub ldbase: u64, - pub dynamic: u64, -} - -#[cfg(target_pointer_width = "32")] -#[derive(Debug, Default, PartialEq)] -pub struct MDRawLinkMap { - pub addr: u32, - pub name: MDRVA, - pub ld: u32, -} - -#[cfg(target_pointer_width = "32")] -#[derive(Debug, Default, PartialEq)] -pub struct MDRawDebug { - pub version: u32, - pub map: MDRVA, /* array of MDRawLinkMap32 */ - pub dso_count: u32, - pub brk: u32, - pub ldbase: u32, - pub dynamic: u32, -} - -/* For (MDRawSystemInfo).processor_architecture: */ -#[repr(u16)] -pub enum MDCPUArchitecture { - X86 = 0, /* PROCESSOR_ARCHITECTURE_INTEL */ - Mips = 1, /* PROCESSOR_ARCHITECTURE_MIPS */ - Alpha = 2, /* PROCESSOR_ARCHITECTURE_ALPHA */ - Ppc = 3, /* PROCESSOR_ARCHITECTURE_PPC */ - Shx = 4, /* PROCESSOR_ARCHITECTURE_SHX - * (Super-H) */ - Arm = 5, /* PROCESSOR_ARCHITECTURE_ARM */ - Ia64 = 6, /* PROCESSOR_ARCHITECTURE_IA64 */ - Alpha64 = 7, /* PROCESSOR_ARCHITECTURE_ALPHA64 */ - Msil = 8, /* PROCESSOR_ARCHITECTURE_MSIL - * (Microsoft Intermediate Language) */ - Amd64 = 9, /* PROCESSOR_ARCHITECTURE_AMD64 */ - X86Win64 = 10, - /* PROCESSOR_ARCHITECTURE_IA32_ON_WIN64 (WoW64) */ - Arm64 = 12, /* PROCESSOR_ARCHITECTURE_ARM64 */ - Sparc = 0x8001, /* Breakpad-defined value for SPARC */ - Ppc64 = 0x8002, /* Breakpad-defined value for PPC64 */ - Arm64Old = 0x8003, /* Breakpad-defined value for ARM64 */ - Mips64 = 0x8004, /* Breakpad-defined value for MIPS64 */ - Unknown = 0xffff, /* PROCESSOR_ARCHITECTURE_UNKNOWN */ -} - -/* For (MDRawSystemInfo).platform_id: */ -#[repr(u32)] -pub enum MDOSPlatform { - Win32s = 0, /* VER_PLATFORM_WIN32s (Windows 3.1) */ - Win32Windows = 1, /* VER_PLATFORM_WIN32_WINDOWS (Windows 95-98-Me) */ - Win32Nt = 2, /* VER_PLATFORM_WIN32_NT (Windows NT, 2000+) */ - Win32Ce = 3, /* VER_PLATFORM_WIN32_CE, VER_PLATFORM_WIN32_HH - * (Windows CE, Windows Mobile, "Handheld") */ - /* The following values are Breakpad-defined. */ - Unix = 0x8000, /* Generic Unix-ish */ - MacOsX = 0x8101, /* Mac OS X/Darwin */ - Ios = 0x8102, /* iOS */ - Linux = 0x8201, /* Linux */ - Solaris = 0x8202, /* Solaris */ - Android = 0x8203, /* Android */ - Ps3 = 0x8204, /* PS3 */ - Nacl = 0x8205, /* Native Client (NaCl) */ - Fuchsia = 0x8206, /* Fuchsia */ -} - -/* - * Modern ELF toolchains insert a "build id" into the ELF headers that - * usually contains a hash of some ELF headers + sections to uniquely - * identify a binary. - * - * https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/6/html/Developer_Guide/compiling-build-id.html - * https://sourceware.org/binutils/docs-2.26/ld/Options.html#index-g_t_002d_002dbuild_002did-292 - */ -pub const MD_CVINFOELF_SIGNATURE: u32 = 0x4270454c; /* cvSignature = 'BpEL' */ -/* Signature is followed by the bytes of the - * build id from GNU_BUILD_ID ELF note. - * This is variable-length, but usually 20 bytes - * as the binutils ld default is a SHA-1 hash. */ - -/* For (MDRawHeader).flags: */ -pub enum MDType { - /* MD_NORMAL is the standard type of minidump. It includes full - * streams for the thread list, module list, exception, system info, - * and miscellaneous info. A memory list stream is also present, - * pointing to the same stack memory contained in the thread list, - * as well as a 256-byte region around the instruction address that - * was executing when the exception occurred. Stack memory is from - * 4 bytes below a thread's stack pointer up to the top of the - * memory region encompassing the stack. */ - Normal = 0x00000000, - WithDataSegs = 0x00000001, - WithFullMemory = 0x00000002, - WithHandleData = 0x00000004, - FilterMemory = 0x00000008, - ScanMemory = 0x00000010, - WithUnloadedModules = 0x00000020, - WithIndirectlyReferencedMemory = 0x00000040, - FilterModulePaths = 0x00000080, - WithProcessThreadData = 0x00000100, - WithPrivateReadWriteMemory = 0x00000200, - WithoutOptionalData = 0x00000400, - WithFullMemoryInfo = 0x00000800, - WithThreadInfo = 0x00001000, - WithCodeSegs = 0x00002000, - WithoutAuxilliarySegs = 0x00004000, - WithFullAuxilliaryState = 0x00008000, - WithPrivateWriteCopyMemory = 0x00010000, - IgnoreInaccessibleMemory = 0x00020000, - WithTokenInformation = 0x00040000, -} - -/* For (MDRawDirectory).stream_type */ -#[repr(u32)] -pub enum MDStreamType { - UnusedStream = 0, - ReservedStream0 = 1, - ReservedStream1 = 2, - ThreadListStream = 3, /* MDRawThreadList */ - ModuleListStream = 4, /* MDRawModuleList */ - MemoryListStream = 5, /* MDRawMemoryList */ - ExceptionStream = 6, /* MDRawExceptionStream */ - SystemInfoStream = 7, /* MDRawSystemInfo */ - ThreadExListStream = 8, - Memory64ListStream = 9, - CommentStreamA = 10, - CommentStreamW = 11, - HandleDataStream = 12, - FunctionTableStream = 13, - UnloadedModuleListStream = 14, - MiscInfoStream = 15, /* MDRawMiscInfo */ - MemoryInfoListStream = 16, /* MDRawMemoryInfoList */ - ThreadInfoListStream = 17, - HandleOperationListStream = 18, - TokenStream = 19, - JavascriptDataStream = 20, - SystemMemoryInfoStream = 21, - ProcessVmCountersStream = 22, - IptTraceStream = 23, - ThreadNamesStream = 24, - LastReservedStream = 0x0000ffff, - - /* Breakpad extension types. 0x4767 = "Gg" */ - BreakpadInfoStream = 0x47670001, /* MDRawBreakpadInfo */ - AssertionInfoStream = 0x47670002, /* MDRawAssertionInfo */ - /* These are additional minidump stream values which are specific to - * the linux breakpad implementation. */ - LinuxCpuInfo = 0x47670003, /* /proc/cpuinfo */ - LinuxProcStatus = 0x47670004, /* /proc/$x/status */ - LinuxLsbRelease = 0x47670005, /* /etc/lsb-release */ - LinuxCmdLine = 0x47670006, /* /proc/$x/cmdline */ - LinuxEnviron = 0x47670007, /* /proc/$x/environ */ - LinuxAuxv = 0x47670008, /* /proc/$x/auxv */ - LinuxMaps = 0x47670009, /* /proc/$x/maps */ - LinuxDsoDebug = 0x4767000A, /* MDRawDebug{32,64} */ - - /* Crashpad extension types. 0x4350 = "CP" - * See Crashpad's minidump/minidump_extensions.h. */ - CrashpadInfoStream = 0x43500001, /* MDRawCrashpadInfo */ +cfg_if::cfg_if! { + if #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] { + pub use format::X86CpuInfo as MDCPUInformation; + } else if #[cfg(any(target_arch = "arm", target_arch = "aarch64"))] { + pub use format::ARMCpuInfo as MDCPUInformation; + } else if #[cfg(target_arch = "mips")] { + pub struct MDCPUInformation { + pub cpuid: [u64; 2], + _padding: [u32; 2], + } + } +} + +cfg_if::cfg_if! { + if #[cfg(target_pointer_width = "64")] { + pub use format::LINK_MAP_64 as MDRawLinkMap; + pub use format::DSO_DEBUG_64 as MDRawDebug; + } else if #[cfg(target_pointer_width = "32")] { + pub use format::LINK_MAP_32 as MDRawLinkMap; + pub use format::DSO_DEBUG_32 as MDRawDebug; + } } diff --git a/src/sections/app_memory.rs b/src/sections/app_memory.rs deleted file mode 100644 index cbc28ba2..00000000 --- a/src/sections/app_memory.rs +++ /dev/null @@ -1,26 +0,0 @@ -use crate::errors::SectionAppMemoryError; -use crate::linux_ptrace_dumper::LinuxPtraceDumper; -use crate::minidump_format::*; -use crate::minidump_writer::{DumpBuf, MinidumpWriter}; -use crate::sections::MemoryArrayWriter; - -type Result = std::result::Result; - -/// Write application-provided memory regions. -pub fn write(config: &mut MinidumpWriter, buffer: &mut DumpBuf) -> Result<()> { - for app_memory in &config.app_memory { - let data_copy = LinuxPtraceDumper::copy_from_process( - config.blamed_thread, - app_memory.ptr as *mut libc::c_void, - app_memory.length, - )?; - - let section = MemoryArrayWriter::::alloc_from_array(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/src/sections/mod.rs b/src/sections/mod.rs deleted file mode 100644 index 086e7e23..00000000 --- a/src/sections/mod.rs +++ /dev/null @@ -1,194 +0,0 @@ -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::errors::MemoryWriterError; -use crate::minidump_format::*; -use std::convert::TryInto; -use std::io::{Cursor, Write}; - -type Result = std::result::Result; - -#[derive(Debug, PartialEq)] -pub struct MemoryWriter { - pub position: MDRVA, - pub size: usize, - phantom: std::marker::PhantomData, -} - -impl MemoryWriter -where - T: Default + Sized, -{ - /// Create a slot for a type T in the buffer, we can fill right now with real values. - pub fn alloc_with_val(buffer: &mut Cursor>, val: T) -> Result { - // Get position of this value (e.g. before we add ourselves there) - let position = buffer.position(); - let size = std::mem::size_of::(); - let bytes = unsafe { std::slice::from_raw_parts(&val as *const T as *const u8, size) }; - buffer.write_all(bytes)?; - - Ok(MemoryWriter { - position: position as u32, - size, - phantom: std::marker::PhantomData:: {}, - }) - } - - /// Create a slot for a type T in the buffer, we can fill later with real values. - /// This function fills it with `Default::default()`, which is less performant than - /// using uninitialized memory, but safe. - pub fn alloc(buffer: &mut Cursor>) -> Result { - // Filling out the buffer with default-values - let val: T = Default::default(); - Self::alloc_with_val(buffer, val) - } - - /// Write actual values in the buffer-slot we got during `alloc()` - pub fn set_value(&mut self, buffer: &mut Cursor>, val: T) -> Result<()> { - // Save whereever the current cursor stands in the buffer - let curr_pos = buffer.position(); - - // Write the actual value we want at our position that - // was determined by `alloc()` into the buffer - buffer.set_position(self.position as u64); - let bytes = unsafe { - std::slice::from_raw_parts(&val as *const T as *const u8, std::mem::size_of::()) - }; - let res = buffer.write_all(bytes); - - // Resetting whereever we were before updating this - // regardless of the write-result - buffer.set_position(curr_pos); - - res?; - Ok(()) - } - - pub fn location(&self) -> MDLocationDescriptor { - MDLocationDescriptor { - data_size: std::mem::size_of::() as u32, - rva: self.position, - } - } -} - -#[derive(Debug, PartialEq)] -pub struct MemoryArrayWriter { - pub position: MDRVA, - array_size: usize, - phantom: std::marker::PhantomData, -} - -impl MemoryArrayWriter -where - T: Default + Sized, -{ - /// Create a slot for a type T in the buffer, we can fill in the values in one go. - pub fn alloc_from_array(buffer: &mut Cursor>, array: &[T]) -> Result { - // Get position of this value (e.g. before we add ourselves there) - let position = buffer.position(); - for val in array { - let bytes = unsafe { - std::slice::from_raw_parts(val as *const T as *const u8, std::mem::size_of::()) - }; - buffer.write_all(bytes)?; - } - - Ok(MemoryArrayWriter { - position: position as u32, - array_size: array.len(), - phantom: std::marker::PhantomData:: {}, - }) - } - - /// Create a slot for a type T in the buffer, we can fill later with real values. - /// This function fills it with `Default::default()`, which is less performant than - /// using uninitialized memory, but safe. - pub fn alloc_array(buffer: &mut Cursor>, array_size: usize) -> Result { - // Get position of this value (e.g. before we add ourselves there) - let position = buffer.position(); - for _ in 0..array_size { - // Filling out the buffer with default-values - let val: T = Default::default(); - let bytes = unsafe { - std::slice::from_raw_parts(&val as *const T as *const u8, std::mem::size_of::()) - }; - buffer.write_all(bytes)?; - } - - Ok(MemoryArrayWriter { - position: position as u32, - array_size, - phantom: std::marker::PhantomData:: {}, - }) - } - - /// Write actual values in the buffer-slot we got during `alloc()` - pub fn set_value_at( - &mut self, - buffer: &mut Cursor>, - val: T, - index: usize, - ) -> Result<()> { - // Save whereever the current cursor stands in the buffer - let curr_pos = buffer.position(); - - // Write the actual value we want at our position that - // was determined by `alloc()` into the buffer - buffer.set_position(self.position as u64 + (std::mem::size_of::() * index) as u64); - let bytes = unsafe { - std::slice::from_raw_parts(&val as *const T as *const u8, std::mem::size_of::()) - }; - let res = buffer.write_all(bytes); - - // Resetting whereever we were before updating this - // regardless of the write-result - buffer.set_position(curr_pos); - - res?; - Ok(()) - } - - pub fn location(&self) -> MDLocationDescriptor { - MDLocationDescriptor { - data_size: (self.array_size * std::mem::size_of::()) as u32, - rva: self.position, - } - } - - pub fn location_of_index(&self, idx: usize) -> MDLocationDescriptor { - MDLocationDescriptor { - data_size: std::mem::size_of::() as u32, - rva: self.position + (std::mem::size_of::() * idx) as u32, - } - } -} - -pub fn write_string_to_location( - buffer: &mut Cursor>, - text: &str, -) -> Result { - let letters: Vec = text.encode_utf16().collect(); - - // First write size of the string (x letters in u16, times the size of u16) - let text_header = MemoryWriter::::alloc_with_val( - buffer, - (letters.len() * std::mem::size_of::()).try_into()?, - )?; - - // Then write utf-16 letters after that - let mut text_section = MemoryArrayWriter::::alloc_array(buffer, letters.len())?; - for (index, letter) in letters.iter().enumerate() { - text_section.set_value_at(buffer, *letter, index)?; - } - - let mut location = text_header.location(); - location.data_size += text_section.location().data_size; - - Ok(location) -} diff --git a/src/sections/systeminfo_stream.rs b/src/sections/systeminfo_stream.rs deleted file mode 100644 index dabd7793..00000000 --- a/src/sections/systeminfo_stream.rs +++ /dev/null @@ -1,21 +0,0 @@ -use crate::dumper_cpu_info::{write_cpu_information, write_os_information}; -use crate::errors::SectionSystemInfoError; -use crate::minidump_format::*; -use crate::minidump_writer::DumpBuf; -use crate::sections::MemoryWriter; - -type Result = std::result::Result; - -pub fn write(buffer: &mut DumpBuf) -> Result { - let mut info_section = MemoryWriter::::alloc(buffer)?; - let dirent = MDRawDirectory { - stream_type: MDStreamType::SystemInfoStream as u32, - location: info_section.location(), - }; - let mut info: MDRawSystemInfo = Default::default(); - write_cpu_information(&mut info)?; - write_os_information(buffer, &mut info)?; - - info_section.set_value(buffer, info)?; - Ok(dirent) -} diff --git a/src/thread_info/thread_info_aarch64.rs b/src/thread_info/thread_info_aarch64.rs deleted file mode 100644 index cca218f4..00000000 --- a/src/thread_info/thread_info_aarch64.rs +++ /dev/null @@ -1,48 +0,0 @@ -use super::Pid; -use crate::errors::ThreadInfoError; -use crate::minidump_cpu::imp::{MDARM64RegisterNumbers, MD_FLOATINGSAVEAREA_ARM64_FPR_COUNT}; -use libc; - -type Result = std::result::Result; - -#[cfg(target_arch = "aarch64")] -#[derive(Debug)] -pub struct ThreadInfoAarch64 { - pub stack_pointer: libc::c_ulonglong, - pub tgid: Pid, // thread group id - pub ppid: Pid, // parent process - pub regs: libc::user_regs_struct, - pub fpregs: libc::user_fpsimd_struct, -} - -impl ThreadInfoAarch64 { - pub fn get_instruction_pointer(&self) -> libc::c_ulonglong { - self.regs.pc - } - - pub fn fill_cpu_context(&self, out: &mut RawContextCPU) { - // out->context_flags = MD_CONTEXT_ARM64_FULL_OLD; - out.cpsr = self.regs.pstate as u32; - for idx in 0..MDARM64RegisterNumbers::MD_CONTEXT_ARM64_REG_SP { - out.iregs[idx] = self.regs.regs[idx]; - } - out.iregs[MDARM64RegisterNumbers::MD_CONTEXT_ARM64_REG_SP] = self.regs.sp; - out.iregs[MDARM64RegisterNumbers::MD_CONTEXT_ARM64_REG_PC] = self.regs.pc; - out.float_save.fpsr = self.fpregs.fpsr; - out.float_save.fpcr = self.fpregs.fpcr; - - // my_memcpy(&out->float_save.regs, &fpregs.vregs, - // MD_FLOATINGSAVEAREA_ARM64_FPR_COUNT * 16); - out.float_save.regs = self - .fpregs - .vregs - .iter() - .map(|x| x.to_ne_bytes().to_vec()) - .flatten() - .take(MD_FLOATINGSAVEAREA_ARM64_FPR_COUNT * 16) - .collect::>() - .as_slice() - .try_into() // Make slice into fixed size array - .unwrap(); // Which has to work as we know the numbers work out - } -} diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 3775bcc3..bb262601 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -86,7 +86,7 @@ pub fn start_child_and_return(command: &str) -> Child { .arg("--bin") .arg("test") .arg("--") - .arg(format!("{}", command)) + .arg(command) .stdout(Stdio::piped()) .spawn() .expect("failed to execute child"); diff --git a/tests/minidump_writer.rs b/tests/minidump_writer.rs index 95c26fb3..4f4d907b 100644 --- a/tests/minidump_writer.rs +++ b/tests/minidump_writer.rs @@ -1,22 +1,21 @@ +use crash_context::CrashContext; use minidump::*; use minidump_common::format::{GUID, MINIDUMP_STREAM_TYPE::*}; -use minidump_writer_linux::app_memory::AppMemory; -#[cfg(not(any(target_arch = "mips", target_arch = "arm")))] -use minidump_writer_linux::crash_context::fpstate_t; -use minidump_writer_linux::crash_context::CrashContext; -use minidump_writer_linux::errors::*; -use minidump_writer_linux::linux_ptrace_dumper::LinuxPtraceDumper; -use minidump_writer_linux::maps_reader::{MappingEntry, MappingInfo, SystemMappingInfo}; -use minidump_writer_linux::minidump_writer::MinidumpWriter; -use minidump_writer_linux::thread_info::Pid; -use nix::errno::Errno; -use nix::sys::signal::Signal; -use std::collections::HashSet; -use std::convert::TryInto; -use std::io::{BufRead, BufReader}; -use std::os::unix::process::ExitStatusExt; -use std::process::{Command, Stdio}; -use std::str::FromStr; +use minidump_writer::{ + app_memory::AppMemory, + errors::*, + maps_reader::{MappingEntry, MappingInfo, SystemMappingInfo}, + minidump_writer::MinidumpWriter, + ptrace_dumper::PtraceDumper, + thread_info::Pid, +}; +use nix::{errno::Errno, sys::signal::Signal}; +use std::{ + collections::HashSet, + io::{BufRead, BufReader}, + os::unix::process::ExitStatusExt, + process::{Command, Stdio}, +}; mod common; use common::*; @@ -28,18 +27,21 @@ enum Context { } #[cfg(not(any(target_arch = "mips", target_arch = "arm")))] -fn get_ucontext() -> Result { - let mut context = std::mem::MaybeUninit::::uninit(); - let res = unsafe { libc::getcontext(context.as_mut_ptr()) }; - Errno::result(res)?; - unsafe { Ok(context.assume_init()) } +fn get_ucontext() -> Result { + let mut context = std::mem::MaybeUninit::uninit(); + unsafe { + let res = crash_context::crash_context_getcontext(context.as_mut_ptr()); + Errno::result(res)?; + + Ok(context.assume_init()) + } } #[cfg(not(any(target_arch = "mips", target_arch = "arm")))] fn get_crash_context(tid: Pid) -> CrashContext { - let siginfo: libc::siginfo_t = unsafe { std::mem::zeroed() }; + let siginfo = unsafe { std::mem::zeroed() }; let context = get_ucontext().expect("Failed to get ucontext"); - let float_state: fpstate_t = unsafe { std::mem::zeroed() }; + let float_state = unsafe { std::mem::zeroed() }; CrashContext { siginfo, tid, @@ -84,6 +86,7 @@ fn test_write_dump_helper(context: Context) { fn test_write_dump() { test_write_dump_helper(Context::Without) } + #[cfg(not(any(target_arch = "mips", target_arch = "arm")))] #[test] fn test_write_dump_with_context() { @@ -105,8 +108,16 @@ fn test_write_and_read_dump_from_parent_helper(context: Context) { .read_line(&mut buf) .expect("Couldn't read address provided by child"); let mut output = buf.split_whitespace(); - let mmap_addr = usize::from_str(output.next().unwrap()).expect("unable to parse mmap_addr"); - let memory_size = usize::from_str(output.next().unwrap()).expect("unable to parse memory_size"); + let mmap_addr: usize = output + .next() + .unwrap() + .parse() + .expect("unable to parse mmap_addr"); + let memory_size: usize = output + .next() + .unwrap() + .parse() + .expect("unable to parse memory_size"); // Add information about the mapped memory. let mapping = MappingInfo { start_address: mmap_addr, @@ -159,7 +170,7 @@ fn test_write_and_read_dump_from_parent_helper(context: Context) { assert_eq!(module.code_file(), "a fake mapping"); assert_eq!( module.debug_identifier(), - Some("33221100554477668899AABBCCDDEEFF0".into()) + Some("33221100554477668899AABBCCDDEEFF0".parse().unwrap()) ); let _: MinidumpException = dump.get_stream().expect("Couldn't find MinidumpException"); @@ -189,10 +200,12 @@ fn test_write_and_read_dump_from_parent_helper(context: Context) { .get_raw_stream(LinuxDsoDebug) .expect("Couldn't find LinuxDsoDebug"); } + #[test] fn test_write_and_read_dump_from_parent() { test_write_and_read_dump_from_parent_helper(Context::Without) } + #[cfg(not(any(target_arch = "mips", target_arch = "arm")))] #[test] fn test_write_and_read_dump_from_parent_with_context() { @@ -216,7 +229,11 @@ fn test_write_with_additional_memory_helper(context: Context) { let mut output = buf.split_whitespace(); let memory_addr = usize::from_str_radix(output.next().unwrap().trim_start_matches("0x"), 16) .expect("unable to parse mmap_addr"); - let memory_size = usize::from_str(output.next().unwrap()).expect("unable to parse memory_size"); + let memory_size: usize = output + .next() + .unwrap() + .parse() + .expect("unable to parse memory_size"); let app_memory = AppMemory { ptr: memory_addr, @@ -260,10 +277,12 @@ fn test_write_with_additional_memory_helper(context: Context) { // Verify memory contents. assert_eq!(region.bytes, values); } + #[test] fn test_write_with_additional_memory() { test_write_with_additional_memory_helper(Context::Without) } + #[cfg(not(any(target_arch = "mips", target_arch = "arm")))] #[test] fn test_write_with_additional_memory_with_context() { @@ -429,7 +448,7 @@ fn test_with_deleted_binary() { let pid = child.id() as i32; - let build_id = LinuxPtraceDumper::elf_file_identifier_from_mapped_file(&mem_slice) + let build_id = PtraceDumper::elf_file_identifier_from_mapped_file(&mem_slice) .expect("Failed to get build_id"); let guid = GUID { @@ -492,7 +511,7 @@ fn test_with_deleted_binary() { assert_eq!(main_module.code_file(), binary_copy.to_string_lossy()); assert_eq!( main_module.debug_identifier(), - Some(std::borrow::Cow::from(filtered.as_str())) + Some(filtered.parse().unwrap()) ); } @@ -536,10 +555,12 @@ fn test_skip_if_requested_helper(context: Context) { assert!(res.is_err()); } + #[test] fn test_skip_if_requested() { test_skip_if_requested_helper(Context::Without) } + #[cfg(not(any(target_arch = "mips", target_arch = "arm")))] #[test] fn test_skip_if_requested_with_context() { @@ -594,16 +615,15 @@ fn test_sanitized_stacks_helper(context: Context) { let start = mem.rva as usize; let end = (mem.rva + mem.data_size) as usize; let slice = &dump_array.as_slice()[start..end]; - assert!(slice - .windows(defaced.len()) - .position(|window| window == defaced) - .is_some()); + assert!(slice.windows(defaced.len()).any(|window| window == defaced)); } } + #[test] fn test_sanitized_stacks() { test_sanitized_stacks_helper(Context::Without) } + #[cfg(not(any(target_arch = "mips", target_arch = "arm")))] #[test] fn test_sanitized_stacks_with_context() { @@ -630,7 +650,11 @@ fn test_write_early_abort_helper(context: Context) { let _ = usize::from_str_radix(output.next().unwrap().trim_start_matches("0x"), 16) .expect("unable to parse mmap_addr"); let memory_addr = 0; - let memory_size = usize::from_str(output.next().unwrap()).expect("unable to parse memory_size"); + let memory_size: usize = output + .next() + .unwrap() + .parse() + .expect("unable to parse memory_size"); let app_memory = AppMemory { ptr: memory_addr, @@ -666,10 +690,12 @@ fn test_write_early_abort_helper(context: Context) { // Should be missing: assert!(dump.get_stream::().is_err()); } + #[test] fn test_write_early_abort() { test_write_early_abort_helper(Context::Without) } + #[cfg(not(any(target_arch = "mips", target_arch = "arm")))] #[test] fn test_write_early_abort_with_context() { @@ -723,10 +749,12 @@ fn test_named_threads_helper(context: Context) { } assert_eq!(expected, names); } + #[test] fn test_named_threads() { test_named_threads_helper(Context::Without) } + #[cfg(not(any(target_arch = "mips", target_arch = "arm")))] #[test] fn test_named_threads_with_context() { diff --git a/tests/ptrace_dumper.rs b/tests/ptrace_dumper.rs index c2e8fccf..8d6fb648 100644 --- a/tests/ptrace_dumper.rs +++ b/tests/ptrace_dumper.rs @@ -1,7 +1,6 @@ -use minidump_writer_linux::linux_ptrace_dumper; +use minidump_writer::ptrace_dumper; use nix::sys::mman::{mmap, MapFlags, ProtFlags}; use nix::sys::signal::Signal; -use std::convert::TryInto; use std::io::{BufRead, BufReader}; use std::mem::size_of; use std::os::unix::io::AsRawFd; @@ -26,8 +25,7 @@ fn test_thread_list_from_parent() { let num_of_threads = 5; let mut child = start_child_and_wait_for_threads(num_of_threads); let pid = child.id() as i32; - let mut dumper = - linux_ptrace_dumper::LinuxPtraceDumper::new(pid).expect("Couldn't init dumper"); + let mut dumper = ptrace_dumper::PtraceDumper::new(pid).expect("Couldn't init dumper"); assert_eq!(dumper.threads.len(), num_of_threads); dumper.suspend_threads().expect("Could not suspend threads"); @@ -196,8 +194,7 @@ fn test_sanitize_stack_copy() { let heap_addr = usize::from_str_radix(output.next().unwrap().trim_start_matches("0x"), 16) .expect("unable to parse mmap_addr"); - let mut dumper = - linux_ptrace_dumper::LinuxPtraceDumper::new(pid).expect("Couldn't init dumper"); + let mut dumper = ptrace_dumper::PtraceDumper::new(pid).expect("Couldn't init dumper"); assert_eq!(dumper.threads.len(), num_of_threads); dumper.suspend_threads().expect("Could not suspend threads"); let thread_info = dumper