use std::io::{Read, Seek, SeekFrom};
use std::mem::size_of;
use elf::dynamic::{Dyn, DT_DEBUG};
use elf::program_header::{ProgramHeader, PT_DYNAMIC, PT_PHDR, SIZEOF_PHDR};
use eyre::{eyre, Result};
use itertools::Itertools;
use libc::PATH_MAX;
use log::{debug, warn};
use scroll::Pread;
use crate::cli::memfault_core_handler::elf;
use crate::cli::memfault_core_handler::memory_range::MemoryRange;
use crate::cli::memfault_core_handler::procfs::read_proc_mem;
use crate::cli::memfault_core_handler::r_debug::{LinkMap, RDebug, RDebugIter};
use crate::cli::memfault_core_handler::ElfPtrSize;
use super::elf_utils::read_program_headers;
pub fn find_dynamic_linker_ranges<P: Read + Seek>(
proc_mem_stream: &mut P,
phdr_vaddr: ElfPtrSize,
phdr_num: ElfPtrSize,
memory_maps: &[MemoryRange],
output: &mut Vec<MemoryRange>,
) -> Result<()> {
debug!(
"Detecting dynamic linker ranges from vaddr 0x{:x}",
phdr_vaddr
);
let phdr = read_main_executable_phdr(proc_mem_stream, phdr_vaddr, phdr_num)?;
let main_reloc_addr = calc_relocation_addr(phdr_vaddr, phdr);
output.push(MemoryRange::from_start_and_size(
main_reloc_addr + phdr.p_vaddr,
phdr.p_memsz,
));
let main_program_headers =
read_main_exec_program_headers(proc_mem_stream, &phdr, main_reloc_addr)?;
let dynamic_ph = find_dynamic_program_header(&main_program_headers)?;
output.push(MemoryRange::from_start_and_size(
main_reloc_addr + dynamic_ph.p_vaddr,
dynamic_ph.p_memsz,
));
let r_debug_addr = find_r_debug_addr(proc_mem_stream, main_reloc_addr, dynamic_ph)?;
output.push(MemoryRange::from_start_and_size(
r_debug_addr,
size_of::<RDebug>() as ElfPtrSize,
));
let mut name_vaddrs: Vec<ElfPtrSize> = vec![];
RDebugIter::new(proc_mem_stream, r_debug_addr)?.for_each(|(vaddr, link_map)| {
output.push(MemoryRange::from_start_and_size(
vaddr,
size_of::<LinkMap>() as ElfPtrSize,
));
name_vaddrs.push(link_map.l_name);
});
name_vaddrs.into_iter().for_each(|name_vaddr| {
output.push(find_c_string_region(
proc_mem_stream,
memory_maps,
name_vaddr,
));
});
Ok(())
}
fn find_c_string_region<P: Read + Seek>(
proc_mem_stream: &mut P,
memory_maps: &[MemoryRange],
c_string_vaddr: ElfPtrSize,
) -> MemoryRange {
let read_size = memory_maps
.iter()
.find(|r| r.contains(c_string_vaddr))
.map_or(PATH_MAX as ElfPtrSize, |r| r.end - c_string_vaddr)
.min(PATH_MAX as ElfPtrSize);
read_proc_mem(proc_mem_stream, c_string_vaddr, read_size)
.map(|data| {
data.iter().find_position(|b| **b == 0).map_or_else(
|| MemoryRange::from_start_and_size(c_string_vaddr, read_size),
|(idx, _)| {
let string_size = (idx + 1).min(read_size as usize);
MemoryRange::from_start_and_size(c_string_vaddr, string_size as ElfPtrSize)
},
)
})
.unwrap_or_else(|e| {
warn!("Failed to read C-string at 0x{:x}: {}", c_string_vaddr, e);
MemoryRange::from_start_and_size(c_string_vaddr, read_size)
})
}
fn find_r_debug_addr<P: Read + Seek>(
proc_mem_stream: &mut P,
main_reloc_addr: ElfPtrSize,
dynamic_ph: &ProgramHeader,
) -> Result<ElfPtrSize> {
let dyn_data = read_proc_mem(
proc_mem_stream,
main_reloc_addr + dynamic_ph.p_vaddr,
dynamic_ph.p_memsz,
)
.map_err(|e| eyre!("Failed to read dynamic segment: {}", e))?;
let mut dyn_iter = DynIter::new(dyn_data);
match find_dt_debug(&mut dyn_iter) {
Some(addr) => Ok(addr),
None => Err(eyre!("Missing DT_DEBUG entry")),
}
}
fn find_dt_debug(dyn_iter: &mut impl Iterator<Item = Dyn>) -> Option<ElfPtrSize> {
dyn_iter
.find(|dyn_entry| dyn_entry.d_tag == DT_DEBUG as ElfPtrSize)
.map(|dyn_entry| dyn_entry.d_val)
}
struct DynIter {
data: Vec<u8>,
offset: usize,
}
impl DynIter {
fn new(data: Vec<u8>) -> Self {
Self { data, offset: 0 }
}
}
impl Iterator for DynIter {
type Item = Dyn;
fn next(&mut self) -> Option<Self::Item> {
self.data.gread::<Dyn>(&mut self.offset).ok()
}
}
fn find_dynamic_program_header(program_headers: &[ProgramHeader]) -> Result<&ProgramHeader> {
match program_headers.iter().find(|ph| ph.p_type == PT_DYNAMIC) {
Some(ph) => Ok(ph),
None => Err(eyre!("No PT_DYNAMIC found")),
}
}
fn read_main_exec_program_headers<P: Read + Seek>(
proc_mem_stream: &mut P,
phdr: &ProgramHeader,
main_reloc_addr: ElfPtrSize,
) -> Result<Vec<ProgramHeader>> {
proc_mem_stream.seek(SeekFrom::Start((main_reloc_addr + phdr.p_vaddr) as _))?;
let count = phdr.p_memsz / (SIZEOF_PHDR as ElfPtrSize);
read_program_headers(proc_mem_stream, count as usize)
}
fn read_main_executable_phdr<P: Read + Seek>(
proc_mem_stream: &mut P,
phdr_vaddr: ElfPtrSize,
phdr_num: ElfPtrSize,
) -> Result<ProgramHeader> {
proc_mem_stream.seek(SeekFrom::Start(phdr_vaddr as _))?;
read_program_headers(proc_mem_stream, phdr_num as usize)?
.into_iter()
.find(|ph| ph.p_type == PT_PHDR)
.ok_or_else(|| eyre!("Main executable PT_PHDR not found"))
}
fn calc_relocation_addr(phdr_vaddr: ElfPtrSize, phdr: ProgramHeader) -> ElfPtrSize {
phdr_vaddr - phdr.p_vaddr
}
#[cfg(test)]
mod test {
use std::io::Cursor;
use std::path::PathBuf;
use insta::assert_debug_snapshot;
use rstest::rstest;
use scroll::{IOwrite, Pwrite};
use crate::cli::memfault_core_handler::procfs::ProcMaps;
use crate::cli::memfault_core_handler::test_utils::{FakeProcMaps, FakeProcMem};
use super::*;
#[test]
fn test_phdr_not_first_header() {
let pdyn_header = build_test_program_header(PT_DYNAMIC);
let phdr_header = build_test_program_header(PT_PHDR);
let mut phdr_bytes = [0; SIZEOF_PHDR * 2];
phdr_bytes.pwrite::<ProgramHeader>(pdyn_header, 0).unwrap();
phdr_bytes
.pwrite::<ProgramHeader>(phdr_header, SIZEOF_PHDR)
.unwrap();
let mut proc_mem_stream = Cursor::new(phdr_bytes);
let actual_phdr = read_main_executable_phdr(&mut proc_mem_stream, 0, 2).unwrap();
assert_eq!(actual_phdr, phdr_header);
}
#[test]
fn test_find_dynamic_linker_ranges() {
let input_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("src/cli/memfault_core_handler/fixtures/elf-core-runtime-ld-paths.elf");
let mut proc_mem_stream = FakeProcMem::new_from_path(&input_path).unwrap();
let phdr_vaddr = 0x5587ae8bd000 + 0x40;
let phdr_num = 0x02d8 / SIZEOF_PHDR as ElfPtrSize;
let mut fake_proc_maps = FakeProcMaps::new_from_path(&input_path).unwrap();
let memory_maps = fake_proc_maps.get_process_maps().unwrap();
let memory_maps_ranges: Vec<MemoryRange> =
memory_maps.iter().map(MemoryRange::from).collect();
let mut output = vec![];
find_dynamic_linker_ranges(
&mut proc_mem_stream,
phdr_vaddr,
phdr_num,
&memory_maps_ranges,
&mut output,
)
.unwrap();
assert_debug_snapshot!(output);
}
#[rstest]
#[case(
b"1hello\0brave\0new\0world!!\0",
vec![MemoryRange::from_start_and_size(0, 25)],
1,
MemoryRange::from_start_and_size(1, 6), // hello\0 -> 6 bytes
)]
#[case(
b"1hello",
vec![MemoryRange::from_start_and_size(0, 6)],
1,
MemoryRange::from_start_and_size(1, 5),
)]
#[case(
b"1hello\0brave\0new\0world!!\0",
vec![MemoryRange::new(0, 4)],
1,
MemoryRange::new(1, 4),
)]
#[case(
b"1hello\0",
vec![],
1,
MemoryRange::from_start_and_size(1, PATH_MAX as ElfPtrSize),
)]
#[case(
&[b'A'; PATH_MAX as usize + 1],
vec![MemoryRange::from_start_and_size(0, PATH_MAX as ElfPtrSize + 1)],
0,
MemoryRange::from_start_and_size(0, PATH_MAX as ElfPtrSize),
)]
fn test_find_c_string_region(
#[case] proc_mem: &[u8],
#[case] mmap_regions: Vec<MemoryRange>,
#[case] c_string_vaddr: ElfPtrSize,
#[case] expected: MemoryRange,
) {
let mut proc_mem_stream = Cursor::new(proc_mem);
assert_eq!(
find_c_string_region(&mut proc_mem_stream, &mmap_regions, c_string_vaddr),
expected
);
}
#[rstest]
#[case(vec![], vec![])]
#[case(
vec![1, 2, 3, 4],
vec![
Dyn { d_tag: 1, d_val: 2 },
Dyn { d_tag: 3, d_val: 4 }
],
)]
#[case(
vec![1, 2, 3],
vec![
Dyn { d_tag: 1, d_val: 2 },
],
)]
fn test_dyn_iter(#[case] input: Vec<ElfPtrSize>, #[case] expected: Vec<Dyn>) {
let data = make_dyn_fixture(input);
assert_eq!(DynIter::new(data).collect::<Vec<_>>(), expected);
}
fn make_dyn_fixture(values: Vec<ElfPtrSize>) -> Vec<u8> {
let mut cursor = Cursor::new(vec![]);
for value in values {
cursor.iowrite::<ElfPtrSize>(value).unwrap();
}
cursor.into_inner()
}
fn build_test_program_header(p_type: u32) -> ProgramHeader {
ProgramHeader {
p_type,
p_flags: 1,
p_offset: 2,
p_vaddr: 3,
p_paddr: 4,
p_filesz: 5,
p_memsz: 6,
p_align: 0,
}
}
}