Skip to content

Commit fcd1ac4

Browse files
committed
Use debugger rendez-vous to obtain memory mappings
1 parent c35af12 commit fcd1ac4

File tree

10 files changed

+495
-78
lines changed

10 files changed

+495
-78
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ thiserror = "2.0"
2828
libc = "0.2"
2929
goblin = "0.9.2"
3030
memmap2 = "0.9"
31+
plain = { version = "0.2.3", default-features = false }
3132

3233
[target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies]
3334
nix = { version = "0.29", default-features = false, features = [

src/bin/test.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ mod linux {
1717
sys::mman::{mmap_anonymous, MapFlags, ProtFlags},
1818
unistd::getppid,
1919
},
20+
std::time::Duration,
2021
};
2122

2223
macro_rules! test {
@@ -143,11 +144,11 @@ mod linux {
143144
fn test_find_mappings(addr1: usize, addr2: usize) -> Result<()> {
144145
let ppid = getppid();
145146

146-
let dumper = fail_on_soft_error!(
147-
soft_errors,
148-
MinidumpWriterConfig::new(ppid.as_raw(), ppid.as_raw())
149-
.build_for_testing(&mut soft_errors)?
150-
);
147+
let mut soft_errors = ErrorList::default();
148+
let mut config = MinidumpWriterConfig::new(ppid.as_raw(), ppid.as_raw());
149+
config.stop_timeout(Duration::from_secs(5));
150+
let dumper = config.build_for_testing(&mut soft_errors)?;
151+
151152
dumper
152153
.find_mapping(addr1)
153154
.ok_or("No mapping for addr1 found")?;

src/lib.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
// Because of the nature of this crate, there are lots of times we cast aliased types to `u64`
2+
// Often, on 64-bit platforms, it's already that, so Clippy gets upset at the u64-to-u64
3+
// conversion.
4+
#![allow(clippy::useless_conversion)]
5+
16
cfg_if::cfg_if! {
27
if #[cfg(any(target_os = "linux", target_os = "android"))] {
38
mod linux;
@@ -28,5 +33,6 @@ failspot::failspot_name! {
2833
ThreadName,
2934
SuspendThreads,
3035
CpuInfoFileOpen,
36+
EnumerateMappingsFromProc,
3137
}
3238
}

src/linux/maps_reader.rs

Lines changed: 274 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
11
use {
2-
super::{auxv::AuxvType, module_reader::ModuleReaderError, serializers::*},
2+
super::{
3+
auxv::AuxvType, mem_reader::MemReader, module_reader::ModuleReaderError, serializers::*,
4+
Pid,
5+
},
36
crate::serializers::*,
47
byteorder::{NativeEndian, ReadBytesExt},
5-
goblin::elf,
8+
elf::{
9+
dynamic::{Dyn, DT_DEBUG},
10+
header::Header as ElfHeader,
11+
program_header::{ProgramHeader, PF_R, PF_W, PF_X, PT_DYNAMIC, PT_LOAD, PT_PHDR},
12+
},
613
memmap2::{Mmap, MmapOptions},
14+
plain::Plain,
715
procfs_core::process::{MMPermissions, MMapPath, MemoryMaps},
816
std::{
917
ffi::{OsStr, OsString},
@@ -14,6 +22,11 @@ use {
1422
},
1523
};
1624

25+
#[cfg(not(target_pointer_width = "64"))]
26+
use goblin::elf32 as elf;
27+
#[cfg(target_pointer_width = "64")]
28+
use goblin::elf64 as elf;
29+
1730
pub const LINUX_GATE_LIBRARY_NAME: &str = "linux-gate.so";
1831
pub const DELETED_SUFFIX: &[u8] = b" (deleted)";
1932

@@ -98,6 +111,68 @@ pub enum MapsReaderError {
98111
SymlinkError(std::path::PathBuf, std::path::PathBuf),
99112
}
100113

114+
#[allow(non_camel_case_types)]
115+
#[repr(C)]
116+
#[derive(Debug)]
117+
struct r_debug {
118+
r_version: std::ffi::c_int,
119+
r_map: usize,
120+
r_brk: usize,
121+
r_state: u8,
122+
r_ldbase: usize,
123+
}
124+
125+
#[allow(non_camel_case_types)]
126+
#[repr(C)]
127+
#[derive(Debug)]
128+
struct link_map {
129+
l_addr: usize,
130+
l_name: usize,
131+
l_ld: usize,
132+
l_next: usize,
133+
l_prev: usize,
134+
}
135+
136+
#[derive(Debug, thiserror::Error, serde::Serialize)]
137+
pub enum FromDebuggerRendezvousError {
138+
#[error("failed to read program header table")]
139+
#[serde(serialize_with = "serialize_io_error")]
140+
ReadProgramHeaderTableFailed(#[source] std::io::Error),
141+
#[error("program header table missing self-pointer")]
142+
ProgramHeaderTableNoSelf,
143+
#[error("program header table missing dynamic section")]
144+
ProgramHeaderTableNoDynamic,
145+
#[error("failed to read ELF header")]
146+
#[serde(serialize_with = "serialize_io_error")]
147+
ReadElfHeaderFailed(#[source] std::io::Error),
148+
#[error("ELF header had invalid identifer bytes: `{0:?}`")]
149+
InvalidElfSignature([u8; 4]),
150+
#[error("unexpected size for dynamic section `{0}`")]
151+
InvalidDynamicSectionSize(usize),
152+
#[error("dynamic section missing NULL entry")]
153+
DynamicSectionMissingTerminator,
154+
#[error("failed to read dynamic section")]
155+
#[serde(serialize_with = "serialize_io_error")]
156+
ReadDynamicSectionFailed(#[source] std::io::Error),
157+
#[error("failed to find DT_DEBUG entry in dynamic section")]
158+
MissingDebugEntry,
159+
#[error("failed to read debugger rendezvous address")]
160+
#[serde(serialize_with = "serialize_io_error")]
161+
ReadDebuggerRendezvousAddressFailed(#[source] std::io::Error),
162+
#[error("unexpected debugger rendezvous version '{0}'")]
163+
UnexpectedDebuggerRendezvousVersion(i32),
164+
#[error("an error occurred iterating the link_map linked list")]
165+
IterateLinkMapFailed(#[source] Box<FromDebuggerRendezvousError>),
166+
#[error("failed reading link_map entry")]
167+
#[serde(serialize_with = "serialize_io_error")]
168+
ReadLinkMapEntryFailed(#[source] std::io::Error),
169+
#[error("invalid link entry")]
170+
InvalidLinkEntry,
171+
#[error("failed reading module name")]
172+
#[serde(serialize_with = "serialize_io_error")]
173+
ReadModuleNameFailed(#[source] std::io::Error),
174+
}
175+
101176
fn is_mapping_a_path(pathname: Option<&OsStr>) -> bool {
102177
match pathname {
103178
Some(x) => x.as_bytes().contains(&b'/'),
@@ -130,6 +205,187 @@ impl MappingInfo {
130205
self.start_address + self.size
131206
}
132207

208+
pub fn from_debugger_rendezvous(
209+
pid: Pid,
210+
program_header_table_address: usize,
211+
program_header_count: usize,
212+
) -> std::result::Result<Vec<Self>, FromDebuggerRendezvousError> {
213+
use FromDebuggerRendezvousError as E;
214+
215+
let mut memory_reader = MemReader::new(pid);
216+
217+
let program_headers: Vec<ProgramHeader> = memory_reader
218+
.read_pod_vec(program_header_table_address, program_header_count)
219+
.map_err(E::ReadProgramHeaderTableFailed)?;
220+
221+
let program_header_table_virtual_address = program_headers
222+
.iter()
223+
.find_map(|hdr| (hdr.p_type == PT_PHDR).then_some(hdr.p_vaddr))
224+
.map(|a| usize::try_from(a).unwrap())
225+
.ok_or(E::ProgramHeaderTableNoSelf)?;
226+
227+
let (dynamic_segment_virtual_address, dynamic_segment_size) = program_headers
228+
.iter()
229+
.find_map(|hdr| (hdr.p_type == PT_DYNAMIC).then_some((hdr.p_vaddr, hdr.p_memsz)))
230+
.map(|(a, b)| (usize::try_from(a).unwrap(), usize::try_from(b).unwrap()))
231+
.ok_or(E::ProgramHeaderTableNoDynamic)?;
232+
233+
let elf_header_address =
234+
program_header_table_address - program_header_table_virtual_address;
235+
let dynamic_segment_address = elf_header_address + dynamic_segment_virtual_address;
236+
237+
// Let's make sure we can locate and parse the ELF header to ensure we didn't somehow read garbage.
238+
let elf_header: ElfHeader = memory_reader
239+
.read_pod(elf_header_address)
240+
.map_err(E::ReadElfHeaderFailed)?;
241+
242+
validate_elf_signature(&elf_header)?;
243+
244+
// Check that the dynamic section size is a multiple of the size of a dynamic entry
245+
if dynamic_segment_size % std::mem::size_of::<Dyn>() != 0 {
246+
return Err(E::InvalidDynamicSectionSize(dynamic_segment_size));
247+
}
248+
249+
let dynamic_section: Vec<Dyn> = memory_reader
250+
.read_pod_vec(
251+
dynamic_segment_address,
252+
dynamic_segment_size / std::mem::size_of::<Dyn>(),
253+
)
254+
.map_err(E::ReadDynamicSectionFailed)?;
255+
256+
if dynamic_section.last() != Some(&Dyn::default()) {
257+
return Err(E::DynamicSectionMissingTerminator);
258+
}
259+
260+
let debugger_rendezvous_address = dynamic_section
261+
.iter()
262+
.find_map(|d| (u64::from(d.d_tag) == DT_DEBUG).then_some(d.d_val))
263+
.map(|a| usize::try_from(a).unwrap())
264+
.ok_or(E::MissingDebugEntry)?;
265+
266+
let debugger_rendezvous: r_debug = memory_reader
267+
.read_pod(debugger_rendezvous_address)
268+
.map_err(E::ReadDebuggerRendezvousAddressFailed)?;
269+
270+
if debugger_rendezvous.r_version != 1 {
271+
return Err(E::UnexpectedDebuggerRendezvousVersion(
272+
debugger_rendezvous.r_version,
273+
));
274+
}
275+
276+
Self::read_link_map(&mut memory_reader, debugger_rendezvous.r_map)
277+
.map_err(|e| E::IterateLinkMapFailed(Box::new(e)))
278+
}
279+
280+
fn read_link_map(
281+
memory_reader: &mut MemReader,
282+
link_map_address: usize,
283+
) -> std::result::Result<Vec<MappingInfo>, FromDebuggerRendezvousError> {
284+
use FromDebuggerRendezvousError as E;
285+
286+
let mut link: link_map = memory_reader
287+
.read_pod(link_map_address)
288+
.map_err(E::ReadLinkMapEntryFailed)?;
289+
290+
if link.l_prev != 0 {
291+
return Err(E::InvalidLinkEntry);
292+
}
293+
294+
let mut result: Vec<MappingInfo> = Vec::new();
295+
296+
loop {
297+
let name = {
298+
let mut buf = Vec::new();
299+
memory_reader
300+
.read_until(link.l_name, 0, &mut buf)
301+
.map_err(E::ReadModuleNameFailed)?;
302+
buf.pop();
303+
OsString::from(String::from_utf8(buf).unwrap())
304+
};
305+
306+
let module_elf_header_address = link.l_addr;
307+
let module_elf_header: ElfHeader = memory_reader
308+
.read_pod(module_elf_header_address)
309+
.map_err(E::ReadElfHeaderFailed)?;
310+
validate_elf_signature(&module_elf_header)?;
311+
312+
let module_program_header_virtual_address =
313+
usize::try_from(module_elf_header.e_phoff).unwrap();
314+
let module_program_header_address =
315+
module_elf_header_address + module_program_header_virtual_address;
316+
let module_program_header_len = usize::from(module_elf_header.e_phnum);
317+
let module_program_headers: Vec<ProgramHeader> = memory_reader
318+
.read_pod_vec(module_program_header_address, module_program_header_len)
319+
.map_err(E::ReadProgramHeaderTableFailed)?;
320+
321+
for module_program_header in module_program_headers
322+
.iter()
323+
.filter(|x| x.p_type == PT_LOAD)
324+
{
325+
let segment_virtual_address =
326+
usize::try_from(module_program_header.p_vaddr).unwrap();
327+
let segment_address = module_elf_header_address + segment_virtual_address;
328+
let segment_len = usize::try_from(module_program_header.p_memsz).unwrap();
329+
let segment_end_address = segment_address + segment_len;
330+
let segment_offset = usize::try_from(module_program_header.p_offset).unwrap();
331+
332+
// Round each of these down or up to the nearest page
333+
let segment_address = segment_address / 4096 * 4096;
334+
let segment_offset = segment_offset / 4096 * 4096;
335+
let segment_end_address = segment_end_address.div_ceil(4096) * 4096;
336+
let segment_len = segment_end_address - segment_address;
337+
338+
let mut permissions = MMPermissions::empty();
339+
if module_program_header.p_flags & PF_R != 0 {
340+
permissions.insert(MMPermissions::READ);
341+
}
342+
if module_program_header.p_flags & PF_W != 0 {
343+
permissions.insert(MMPermissions::WRITE);
344+
}
345+
if module_program_header.p_flags & PF_X != 0 {
346+
permissions.insert(MMPermissions::EXECUTE);
347+
}
348+
349+
if let Some(prev_mapping) = result.last_mut() {
350+
if prev_mapping.end_address() == segment_address
351+
&& prev_mapping.name.is_some()
352+
&& prev_mapping.name.as_deref() == Some(&name)
353+
{
354+
prev_mapping.system_mapping_info.end_address = segment_end_address;
355+
prev_mapping.size = segment_end_address - prev_mapping.start_address;
356+
prev_mapping.permissions |= permissions;
357+
continue;
358+
}
359+
}
360+
361+
let mapping_info = MappingInfo {
362+
start_address: segment_address,
363+
size: segment_len,
364+
// When Android relocation packing causes |start_addr| and |size| to
365+
// be modified with a load bias, we need to remember the unbiased
366+
// address range. The following structure holds the original mapping
367+
// address range as reported by the operating system.
368+
system_mapping_info: SystemMappingInfo {
369+
start_address: segment_address,
370+
end_address: segment_end_address,
371+
},
372+
offset: segment_offset,
373+
permissions,
374+
name: Some(name.clone()),
375+
};
376+
result.push(mapping_info);
377+
}
378+
379+
if link.l_next == 0 {
380+
break;
381+
}
382+
link = memory_reader
383+
.read_pod(link.l_next)
384+
.map_err(E::ReadLinkMapEntryFailed)?;
385+
}
386+
Ok(result)
387+
}
388+
133389
pub fn aggregate(
134390
memory_maps: MemoryMaps,
135391
linux_gate_loc: Option<AuxvType>,
@@ -248,7 +504,7 @@ impl MappingInfo {
248504
.map(&File::open(filename)?)?
249505
};
250506

251-
if mapped_file.is_empty() || mapped_file.len() < elf::header::SELFMAG {
507+
if mapped_file.is_empty() || mapped_file.len() < goblin::elf::header::SELFMAG {
252508
return Err(MapsReaderError::MmapSanityCheckFailed);
253509
}
254510
Ok(mapped_file)
@@ -489,6 +745,21 @@ impl PartialEq<(u32, u32, u32, u32)> for SoVersion {
489745
}
490746
}
491747

748+
unsafe impl Plain for link_map {}
749+
unsafe impl Plain for r_debug {}
750+
751+
fn validate_elf_signature(
752+
elf_header: &ElfHeader,
753+
) -> std::result::Result<(), FromDebuggerRendezvousError> {
754+
if elf_header.e_ident[0..4] == [0x7f, 0x45, 0x4c, 0x46] {
755+
Ok(())
756+
} else {
757+
Err(FromDebuggerRendezvousError::InvalidElfSignature(
758+
elf_header.e_ident[0..4].try_into().unwrap(),
759+
))
760+
}
761+
}
762+
492763
#[cfg(test)]
493764
#[cfg(target_pointer_width = "64")] // All addresses are 64 bit and I'm currently too lazy to adjust it to work for both
494765
mod tests {

0 commit comments

Comments
 (0)