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;
}
}
#[derive(Debug, Clone, Default)]
#[repr(C)]
pub struct LinkMap {
l_addr: ElfAddr,
l_name: usize,
l_ld: usize,
l_next: usize,
l_prev: usize,
}
#[derive(Debug, Clone, Default)]
#[allow(non_camel_case_types, unused)]
#[repr(C)]
enum RState {
#[default]
RT_CONSISTENT,
RT_ADD,
RT_DELETE,
}
#[derive(Debug, Clone, Default)]
#[repr(C)]
pub struct RDebug {
r_version: libc::c_int,
r_map: usize,
r_brk: ElfAddr,
r_state: RState,
r_ldbase: ElfAddr,
}
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(any(target_arch = "arm", all(target_os = "android", target_arch = "x86")))]
{
at_phdr = 3;
at_phnum = 5;
}
#[cfg(not(any(target_arch = "arm", all(target_os = "android", target_arch = "x86"))))]
{
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,
);
};
let mut base = phdr & !0xfff;
let mut dyn_addr = 0;
for ph in program_headers {
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;
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;
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];
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;
}
}
let debug_entry_data = PtraceDumper::copy_from_process(
blamed_thread,
r_debug as *mut libc::c_void,
std::mem::size_of::<RDebug>(),
)?;
let (head, body, _tail) = unsafe { debug_entry_data.align_to::<RDebug>() };
assert!(head.is_empty(), "Data was not aligned");
let debug_entry = &body[0];
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>(),
)?;
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() {
let mut linkmap = MemoryArrayWriter::<MDRawLinkMap>::alloc_array(buffer, dso_vec.len())?;
linkmap_rva = linkmap.location().rva;
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,
)?;
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)?;
}
}
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)
}