Skip to content

Commit e56d4cd

Browse files
marti4dJake-Shadle
authored andcommitted
Use debugger rendez-vous to obtain memory mappings
1 parent 1d658cf commit e56d4cd

File tree

9 files changed

+483
-69
lines changed

9 files changed

+483
-69
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/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,6 @@ failspot::failspot_name! {
2828
ThreadName,
2929
SuspendThreads,
3030
CpuInfoFileOpen,
31+
EnumerateMappingsFromProc,
3132
}
3233
}

src/linux/errors.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,10 +114,10 @@ pub enum AndroidError {
114114
}
115115

116116
#[derive(Debug, Error, serde::Serialize)]
117-
#[error("Copy from process {child} failed (source {src}, offset: {offset}, length: {length})")]
117+
#[error("Copy from process {child} failed (address {address}, offset: {offset}, length: {length})")]
118118
pub struct CopyFromProcessError {
119119
pub child: Pid,
120-
pub src: usize,
120+
pub address: usize,
121121
pub offset: usize,
122122
pub length: usize,
123123
#[serde(serialize_with = "serialize_nix_error")]

src/linux/maps_reader.rs

Lines changed: 273 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,19 @@
1+
#[cfg(not(target_pointer_width = "64"))]
2+
use goblin::elf32 as elf;
3+
#[cfg(target_pointer_width = "64")]
4+
use goblin::elf64 as elf;
5+
16
use {
2-
crate::{auxv::AuxvType, errors::MapsReaderError},
7+
super::mem_reader::MemReader,
8+
crate::{auxv::AuxvType, errors::MapsReaderError, linux::Pid, serializers::*},
39
byteorder::{NativeEndian, ReadBytesExt},
4-
goblin::elf,
10+
elf::{
11+
dynamic::{Dyn, DT_DEBUG},
12+
header::Header as ElfHeader,
13+
program_header::{ProgramHeader, PF_R, PF_W, PF_X, PT_DYNAMIC, PT_LOAD, PT_PHDR},
14+
},
515
memmap2::{Mmap, MmapOptions},
16+
plain::Plain,
617
procfs_core::process::{MMPermissions, MMapPath, MemoryMaps},
718
std::{
819
ffi::{OsStr, OsString},
@@ -61,6 +72,72 @@ pub enum MappingInfoParsingResult {
6172
Success(MappingInfo),
6273
}
6374

75+
#[allow(non_camel_case_types)]
76+
#[repr(C)]
77+
#[derive(Debug)]
78+
struct r_debug {
79+
r_version: std::ffi::c_int,
80+
r_map: usize,
81+
r_brk: usize,
82+
r_state: u8,
83+
r_ldbase: usize,
84+
}
85+
86+
unsafe impl Plain for r_debug {}
87+
88+
#[allow(non_camel_case_types)]
89+
#[repr(C)]
90+
#[derive(Debug)]
91+
struct link_map {
92+
l_addr: usize,
93+
l_name: usize,
94+
l_ld: usize,
95+
l_next: usize,
96+
l_prev: usize,
97+
}
98+
99+
unsafe impl Plain for link_map {}
100+
101+
#[derive(Debug, thiserror::Error, serde::Serialize)]
102+
pub enum FromDebuggerRendezvousError {
103+
#[error("failed to read program header table")]
104+
#[serde(serialize_with = "serialize_io_error")]
105+
ReadProgramHeaderTableFailed(#[source] std::io::Error),
106+
#[error("program header table missing self-pointer")]
107+
ProgramHeaderTableNoSelf,
108+
#[error("program header table missing dynamic section")]
109+
ProgramHeaderTableNoDynamic,
110+
#[error("failed to read ELF header")]
111+
#[serde(serialize_with = "serialize_io_error")]
112+
ReadElfHeaderFailed(#[source] std::io::Error),
113+
#[error("ELF header had invalid identifer bytes: `{0:?}`")]
114+
InvalidElfSignature([u8; 4]),
115+
#[error("unexpected size for dynamic section `{0}`")]
116+
InvalidDynamicSectionSize(usize),
117+
#[error("dynamic section missing NULL entry")]
118+
DynamicSectionMissingTerminator,
119+
#[error("failed to read dynamic section")]
120+
#[serde(serialize_with = "serialize_io_error")]
121+
ReadDynamicSectionFailed(#[source] std::io::Error),
122+
#[error("failed to find DT_DEBUG entry in dynamic section")]
123+
MissingDebugEntry,
124+
#[error("failed to read debugger rendezvous address")]
125+
#[serde(serialize_with = "serialize_io_error")]
126+
ReadDebuggerRendezvousAddressFailed(#[source] std::io::Error),
127+
#[error("unexpected debugger rendezvous version '{0}'")]
128+
UnexpectedDebuggerRendezvousVersion(i32),
129+
#[error("an error occurred iterating the link_map linked list")]
130+
IterateLinkMapFailed(#[source] Box<FromDebuggerRendezvousError>),
131+
#[error("failed reading link_map entry")]
132+
#[serde(serialize_with = "serialize_io_error")]
133+
ReadLinkMapEntryFailed(#[source] std::io::Error),
134+
#[error("invalid link entry")]
135+
InvalidLinkEntry,
136+
#[error("failed reading module name")]
137+
#[serde(serialize_with = "serialize_io_error")]
138+
ReadModuleNameFailed(#[source] std::io::Error),
139+
}
140+
64141
fn is_mapping_a_path(pathname: Option<&OsStr>) -> bool {
65142
match pathname {
66143
Some(x) => x.as_bytes().contains(&b'/'),
@@ -93,6 +170,187 @@ impl MappingInfo {
93170
self.start_address + self.size
94171
}
95172

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

214-
if mapped_file.is_empty() || mapped_file.len() < elf::header::SELFMAG {
472+
if mapped_file.is_empty() || mapped_file.len() < goblin::elf::header::SELFMAG {
215473
return Err(MapsReaderError::MmapSanityCheckFailed);
216474
}
217475
Ok(mapped_file)
@@ -452,6 +710,18 @@ impl PartialEq<(u32, u32, u32, u32)> for SoVersion {
452710
}
453711
}
454712

713+
fn validate_elf_signature(
714+
elf_header: &ElfHeader,
715+
) -> std::result::Result<(), FromDebuggerRendezvousError> {
716+
if elf_header.e_ident[0..4] == [0x7f, 0x45, 0x4c, 0x46] {
717+
Ok(())
718+
} else {
719+
Err(FromDebuggerRendezvousError::InvalidElfSignature(
720+
elf_header.e_ident[0..4].try_into().unwrap(),
721+
))
722+
}
723+
}
724+
455725
#[cfg(test)]
456726
#[cfg(target_pointer_width = "64")] // All addresses are 64 bit and I'm currently too lazy to adjust it to work for both
457727
mod tests {

0 commit comments

Comments
 (0)