diff options
Diffstat (limited to 'third_party/rust/minidump-writer/src/linux/dso_debug.rs')
-rw-r--r-- | third_party/rust/minidump-writer/src/linux/dso_debug.rs | 273 |
1 files changed, 273 insertions, 0 deletions
diff --git a/third_party/rust/minidump-writer/src/linux/dso_debug.rs b/third_party/rust/minidump-writer/src/linux/dso_debug.rs new file mode 100644 index 0000000000..b77f241392 --- /dev/null +++ b/third_party/rust/minidump-writer/src/linux/dso_debug.rs @@ -0,0 +1,273 @@ +use crate::{ + linux::{auxv_reader::AuxvType, errors::SectionDsoDebugError, ptrace_dumper::PtraceDumper}, + mem_writer::{write_string_to_location, Buffer, MemoryArrayWriter, MemoryWriter}, + minidump_format::*, +}; +use std::collections::HashMap; + +type Result<T> = std::result::Result<T, SectionDsoDebugError>; + +cfg_if::cfg_if! { + if #[cfg(target_pointer_width = "32")] { + use goblin::elf::program_header::program_header32::SIZEOF_PHDR; + } else if #[cfg(target_pointer_width = "64")] { + use goblin::elf::program_header::program_header64::SIZEOF_PHDR; + } +} + +cfg_if::cfg_if! { + if #[cfg(all(target_pointer_width = "64", target_arch = "arm"))] { + type ElfAddr = u64; + } else if #[cfg(all(target_pointer_width = "64", not(target_arch = "arm")))] { + type ElfAddr = libc::Elf64_Addr; + } else if #[cfg(all(target_pointer_width = "32", target_arch = "arm"))] { + type ElfAddr = u32; + } else if #[cfg(all(target_pointer_width = "32", not(target_arch = "arm")))] { + type ElfAddr = libc::Elf32_Addr; + } +} + +// COPY from <link.h> +#[derive(Debug, Clone, Default)] +#[repr(C)] +pub struct LinkMap { + /* These first few members are part of the protocol with the debugger. + This is the same format used in SVR4. */ + l_addr: ElfAddr, /* Difference between the address in the ELF + file and the addresses in memory. */ + l_name: usize, /* Absolute file name object was found in. WAS: `char*` */ + l_ld: usize, /* Dynamic section of the shared object. WAS: `ElfW(Dyn) *` */ + l_next: usize, /* Chain of loaded objects. WAS: `struct link_map *` */ + l_prev: usize, /* Chain of loaded objects. WAS: `struct link_map *` */ +} + +// COPY from <link.h> +/// This state value describes the mapping change taking place when +/// the `r_brk' address is called. +#[derive(Debug, Clone, Default)] +#[allow(non_camel_case_types, unused)] +#[repr(C)] +enum RState { + /// Mapping change is complete. + #[default] + RT_CONSISTENT, + /// Beginning to add a new object. + RT_ADD, + /// Beginning to remove an object mapping. + RT_DELETE, +} + +// COPY from <link.h> +#[derive(Debug, Clone, Default)] +#[repr(C)] +pub struct RDebug { + r_version: libc::c_int, /* Version number for this protocol. */ + r_map: usize, /* Head of the chain of loaded objects. WAS: `struct link_map *` */ + + /* This is the address of a function internal to the run-time linker, + that will always be called when the linker begins to map in a + library or unmap it, and again when the mapping change is complete. + The debugger can set a breakpoint at this address if it wants to + notice shared object mapping changes. */ + r_brk: ElfAddr, + r_state: RState, + r_ldbase: ElfAddr, /* Base address the linker is loaded at. */ +} + +pub fn write_dso_debug_stream( + buffer: &mut Buffer, + blamed_thread: i32, + auxv: &HashMap<AuxvType, AuxvType>, +) -> Result<MDRawDirectory> { + let at_phnum; + let at_phdr; + #[cfg(target_arch = "arm")] + { + at_phdr = 3; + at_phnum = 5; + } + #[cfg(not(target_arch = "arm"))] + { + at_phdr = libc::AT_PHDR; + at_phnum = libc::AT_PHNUM; + } + let phnum_max = *auxv + .get(&at_phnum) + .ok_or(SectionDsoDebugError::CouldNotFind("AT_PHNUM in auxv"))? + as usize; + let phdr = *auxv + .get(&at_phdr) + .ok_or(SectionDsoDebugError::CouldNotFind("AT_PHDR in auxv"))? as usize; + + let ph = PtraceDumper::copy_from_process( + blamed_thread, + phdr as *mut libc::c_void, + SIZEOF_PHDR * phnum_max, + )?; + let program_headers; + #[cfg(target_pointer_width = "64")] + { + program_headers = goblin::elf::program_header::program_header64::ProgramHeader::from_bytes( + &ph, phnum_max, + ); + } + #[cfg(target_pointer_width = "32")] + { + program_headers = goblin::elf::program_header::program_header32::ProgramHeader::from_bytes( + &ph, phnum_max, + ); + }; + + // Assume the program base is at the beginning of the same page as the PHDR + let mut base = phdr & !0xfff; + let mut dyn_addr = 0; + // Search for the program PT_DYNAMIC segment + for ph in program_headers { + // Adjust base address with the virtual address of the PT_LOAD segment + // corresponding to offset 0 + if ph.p_type == goblin::elf::program_header::PT_LOAD && ph.p_offset == 0 { + base -= ph.p_vaddr as usize; + } + if ph.p_type == goblin::elf::program_header::PT_DYNAMIC { + dyn_addr = ph.p_vaddr; + } + } + + if dyn_addr == 0 { + return Err(SectionDsoDebugError::CouldNotFind( + "dyn_addr in program headers", + )); + } + + dyn_addr += base as ElfAddr; + + let dyn_size = std::mem::size_of::<goblin::elf::Dyn>(); + let mut r_debug = 0usize; + let mut dynamic_length = 0usize; + + // The dynamic linker makes information available that helps gdb find all + // DSOs loaded into the program. If this information is indeed available, + // dump it to a MD_LINUX_DSO_DEBUG stream. + loop { + let dyn_data = PtraceDumper::copy_from_process( + blamed_thread, + (dyn_addr as usize + dynamic_length) as *mut libc::c_void, + dyn_size, + )?; + dynamic_length += dyn_size; + + // goblin::elf::Dyn doesn't have padding bytes + let (head, body, _tail) = unsafe { dyn_data.align_to::<goblin::elf::Dyn>() }; + assert!(head.is_empty(), "Data was not aligned"); + let dyn_struct = &body[0]; + + // #ifdef __mips__ + // const int32_t debug_tag = DT_MIPS_RLD_MAP; + // #else + // const int32_t debug_tag = DT_DEBUG; + // #endif + let debug_tag = goblin::elf::dynamic::DT_DEBUG; + if dyn_struct.d_tag == debug_tag { + r_debug = dyn_struct.d_val as usize; + } else if dyn_struct.d_tag == goblin::elf::dynamic::DT_NULL { + break; + } + } + + // The "r_map" field of that r_debug struct contains a linked list of all + // loaded DSOs. + // Our list of DSOs potentially is different from the ones in the crashing + // process. So, we have to be careful to never dereference pointers + // directly. Instead, we use CopyFromProcess() everywhere. + // See <link.h> for a more detailed discussion of the how the dynamic + // loader communicates with debuggers. + + let debug_entry_data = PtraceDumper::copy_from_process( + blamed_thread, + r_debug as *mut libc::c_void, + std::mem::size_of::<RDebug>(), + )?; + + // goblin::elf::Dyn doesn't have padding bytes + let (head, body, _tail) = unsafe { debug_entry_data.align_to::<RDebug>() }; + assert!(head.is_empty(), "Data was not aligned"); + let debug_entry = &body[0]; + + // Count the number of loaded DSOs + let mut dso_vec = Vec::new(); + let mut curr_map = debug_entry.r_map; + while curr_map != 0 { + let link_map_data = PtraceDumper::copy_from_process( + blamed_thread, + curr_map as *mut libc::c_void, + std::mem::size_of::<LinkMap>(), + )?; + + // LinkMap is repr(C) and doesn't have padding bytes, so this should be safe + let (head, body, _tail) = unsafe { link_map_data.align_to::<LinkMap>() }; + assert!(head.is_empty(), "Data was not aligned"); + let map = &body[0]; + + curr_map = map.l_next; + dso_vec.push(map.clone()); + } + + let mut linkmap_rva = u32::MAX; + if !dso_vec.is_empty() { + // If we have at least one DSO, create an array of MDRawLinkMap + // entries in the minidump file. + let mut linkmap = MemoryArrayWriter::<MDRawLinkMap>::alloc_array(buffer, dso_vec.len())?; + linkmap_rva = linkmap.location().rva; + + // Iterate over DSOs and write their information to mini dump + for (idx, map) in dso_vec.iter().enumerate() { + let mut filename = String::new(); + if map.l_name > 0 { + let filename_data = PtraceDumper::copy_from_process( + blamed_thread, + map.l_name as *mut libc::c_void, + 256, + )?; + + // C - string is NULL-terminated + if let Some(name) = filename_data.splitn(2, |x| *x == b'\0').next() { + filename = String::from_utf8(name.to_vec())?; + } + } + let location = write_string_to_location(buffer, &filename)?; + let entry = MDRawLinkMap { + addr: map.l_addr, + name: location.rva, + ld: map.l_ld as ElfAddr, + }; + + linkmap.set_value_at(buffer, entry, idx)?; + } + } + + // Write MD_LINUX_DSO_DEBUG record + let debug = MDRawDebug { + version: debug_entry.r_version as u32, + map: linkmap_rva, + dso_count: dso_vec.len() as u32, + brk: debug_entry.r_brk, + ldbase: debug_entry.r_ldbase, + dynamic: dyn_addr, + }; + let debug_loc = MemoryWriter::<MDRawDebug>::alloc_with_val(buffer, debug)?; + + let mut dirent = MDRawDirectory { + stream_type: MDStreamType::LinuxDsoDebug as u32, + location: debug_loc.location(), + }; + + dirent.location.data_size += dynamic_length as u32; + let dso_debug_data = PtraceDumper::copy_from_process( + blamed_thread, + dyn_addr as *mut libc::c_void, + dynamic_length, + )?; + MemoryArrayWriter::write_bytes(buffer, &dso_debug_data); + + Ok(dirent) +} |