From 46fda2eb83f35a042f46aab1e4fdffdb7b1976d9 Mon Sep 17 00:00:00 2001 From: Simon Chopin Date: Mon, 14 Sep 2026 17:38:12 +0200 Subject: [PATCH] linux: dso_debug: use width-agnostic ELF types in its APIs The goblin crate exposes a set of types that can represent headers of both 32-bit and 64-bit ELF objects, and that's what module_reader uses. In order to facilitate interaction between those two Rust module, let's use that rather than the hardcoded native variants in our pub(crate) APIs. The module still uses the native versions at parsing time, so it's not portable, but it will make the module list work easier. --- src/linux/dso_debug.rs | 85 +++++++++++++++++++----------------------- 1 file changed, 39 insertions(+), 46 deletions(-) diff --git a/src/linux/dso_debug.rs b/src/linux/dso_debug.rs index 72aeea34..2a597480 100644 --- a/src/linux/dso_debug.rs +++ b/src/linux/dso_debug.rs @@ -23,28 +23,20 @@ use { type Result = std::result::Result; type RendezvousResult = std::result::Result; +// Native types for internal use, while we expose the width-agnostic variants +// for easier interaction with other code. #[cfg(not(target_pointer_width = "64"))] -use goblin::elf32 as elf; +use goblin::elf32 as native_elf; #[cfg(target_pointer_width = "64")] -use goblin::elf64 as elf; +use goblin::elf64 as native_elf; -use elf::{ - dynamic::Dyn, +use goblin::elf::{ + dynamic::{DT_DEBUG, DT_NULL, Dyn}, program_header::{PT_DYNAMIC, PT_PHDR, ProgramHeader}, }; -use goblin::elf::dynamic::{DT_DEBUG, DT_NULL}; - -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; - } -} + +/// Size of one dynamic section entry *in the target*. +const SIZEOF_DYN: usize = native_elf::dynamic::SIZEOF_DYN; #[derive(Debug, thiserror::Error, serde::Serialize)] pub enum SectionDsoDebugError { @@ -100,8 +92,14 @@ impl MainExecutable { program_header_count: usize, ) -> RendezvousResult { let program_headers: Vec = memory_reader - .read_pod_vec(program_header_table_address, program_header_count) - .map_err(RendezvousError::ReadProgramHeaderTableFailed)?; + .read_pod_vec::( + program_header_table_address, + program_header_count, + ) + .map_err(RendezvousError::ReadProgramHeaderTableFailed)? + .into_iter() + .map(Into::into) + .collect(); let load_bias = Self::compute_bias(&program_headers, program_header_table_address)?; Ok(Self { @@ -151,13 +149,16 @@ impl MainExecutable { .ok_or(RendezvousError::ProgramHeaderTableNoDynamic)?; // A partial trailing entry means we are not looking at a dynamic section. - if !size.is_multiple_of(std::mem::size_of::()) { + if !size.is_multiple_of(SIZEOF_DYN) { return Err(RendezvousError::InvalidDynamicSectionSize(size)); } let entries: Vec = memory_reader - .read_pod_vec(address, size / std::mem::size_of::()) - .map_err(RendezvousError::ReadDynamicSectionFailed)?; + .read_pod_vec::(address, size / SIZEOF_DYN) + .map_err(RendezvousError::ReadDynamicSectionFailed)? + .into_iter() + .map(Into::into) + .collect(); if entries.last() != Some(&Dyn::default()) { return Err(RendezvousError::DynamicSectionMissingTerminator); @@ -167,18 +168,12 @@ impl MainExecutable { } } -// Helper to isolate conversion noise -#[allow(clippy::useless_conversion)] -fn dtag(entry: &Dyn) -> u64 { - u64::from(entry.d_tag) -} - /// Find the DT_DEBUG debugger rendezvous within a dynamic section, /// typically gotten from `MainExecutable::dynamic_section`. pub(crate) fn find_rendezvous_address(dynamic_section: &[Dyn]) -> RendezvousResult { dynamic_section .iter() - .find_map(|entry| (dtag(entry) == DT_DEBUG).then_some(entry.d_val)) + .find_map(|entry| (entry.d_tag == DT_DEBUG).then_some(entry.d_val)) .map(elf_addr_to_usize) .ok_or(RendezvousError::MissingDebugEntry) } @@ -188,15 +183,16 @@ pub(crate) fn find_rendezvous_address(dynamic_section: &[Dyn]) -> RendezvousResu fn dynamic_section_len(dynamic_section: &[Dyn]) -> usize { let entries = dynamic_section .iter() - .position(|entry| dtag(entry) == DT_NULL) + .position(|entry| entry.d_tag == DT_NULL) .map_or(dynamic_section.len(), |terminator| terminator + 1); - entries * std::mem::size_of::() + entries * SIZEOF_DYN } -/// Convert an address read from ELF data into a native usize, panic if the target is too narrow to hold it. -pub(crate) fn elf_addr_to_usize(addr: ElfAddr) -> usize { - usize::try_from(addr).expect("Pointer size mismatch between dumper and target") +/// Convert an address read from ELF data into a native usize, panic if the target is too narrow to +/// hold it. Assumes the ELF data comes from width-agnostic code, hence u64. +pub(crate) fn elf_addr_to_usize(addr: impl Into) -> usize { + usize::try_from(addr.into()).expect("Pointer size mismatch between dumper and target") } /// Information for a single dynamically loaded module. @@ -215,7 +211,7 @@ pub(crate) fn elf_addr_to_usize(addr: ElfAddr) -> usize { pub struct LinkMap { /// Difference between the addresses in the ELF file and the addresses in /// memory. - pub(crate) l_addr: ElfAddr, + pub(crate) l_addr: usize, /// Address of the absolute file name the object was found in. /// WAS: `char *` pub(crate) l_name: usize, @@ -253,13 +249,13 @@ pub struct RDebug { /// 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. - pub(crate) r_brk: ElfAddr, + pub(crate) r_brk: usize, /// Which mapping change is taking place when `r_brk` is called: 0 /// (`RT_CONSISTENT`, the change is complete), 1 (`RT_ADD`, beginning to add /// a new object) or 2 (`RT_DELETE`, beginning to remove an object mapping). pub(crate) r_state: libc::c_int, /// Base address the linker is loaded at. - pub(crate) r_ldbase: ElfAddr, + pub(crate) r_ldbase: usize, } // Safety: both are `repr(C)` aggregates of plain integers, so every bit pattern @@ -324,9 +320,9 @@ pub fn write_dso_debug_stream( } let location = write_string_to_location(buffer, &filename)?; let entry = MDRawLinkMap { - addr: map.l_addr, + addr: map.l_addr as _, name: location.rva, - ld: map.l_ld as ElfAddr, + ld: map.l_ld as _, }; linkmap.set_value_at(buffer, entry, idx)?; @@ -338,9 +334,9 @@ pub fn write_dso_debug_stream( 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 as ElfAddr, + brk: debug_entry.r_brk as _, + ldbase: debug_entry.r_ldbase as _, + dynamic: dyn_addr as _, }; let debug_loc = MemoryWriter::::alloc_with_val(buffer, debug)?; @@ -360,12 +356,9 @@ pub fn write_dso_debug_stream( } #[cfg(test)] -// Every address below is 64 bit, and the header fields they go into are only -// that wide on a 64-bit target. -#[cfg(target_pointer_width = "64")] mod tests { use super::*; - use elf::program_header::PT_LOAD; + use goblin::elf::program_header::PT_LOAD; /// Create a fake PT_PHDR with the given vaddr. fn program_header_table_at(virtual_address: u64) -> ProgramHeader {