use core::fmt::{self, Write};
use core::mem::{size_of, transmute};
use core::slice::from_raw_parts;
use libc::c_char;
extern "C" {
#[allow(improper_ctypes)]
fn dl_iterate_phdr(
f: extern "C" fn(info: &dl_phdr_info, size: usize, data: &mut DsoPrinter<'_, '_>) -> i32,
data: &mut DsoPrinter<'_, '_>,
) -> i32;
}
const PT_LOAD: u32 = 1;
const PT_NOTE: u32 = 4;
#[allow(non_camel_case_types)]
#[repr(C)]
struct dl_phdr_info {
addr: *const u8,
name: *const c_char,
phdr: *const Elf_Phdr,
phnum: u16,
adds: u64,
subs: u64,
tls_modid: usize,
tls_data: *const u8,
}
impl dl_phdr_info {
fn program_headers(&self) -> PhdrIter<'_> {
PhdrIter {
phdrs: self.phdr_slice(),
base: self.addr,
}
}
fn phdr_slice(&self) -> &[Elf_Phdr] {
unsafe { from_raw_parts(self.phdr, self.phnum as usize) }
}
}
struct PhdrIter<'a> {
phdrs: &'a [Elf_Phdr],
base: *const u8,
}
impl<'a> Iterator for PhdrIter<'a> {
type Item = Phdr<'a>;
fn next(&mut self) -> Option<Self::Item> {
self.phdrs.split_first().map(|(phdr, new_phdrs)| {
self.phdrs = new_phdrs;
Phdr {
phdr,
base: self.base,
}
})
}
}
#[allow(non_camel_case_types)]
#[derive(Clone, Debug)]
#[repr(C)]
struct Elf_Phdr {
p_type: u32,
p_flags: u32,
p_offset: u64,
p_vaddr: u64,
p_paddr: u64,
p_filesz: u64,
p_memsz: u64,
p_align: u64,
}
struct Phdr<'a> {
phdr: &'a Elf_Phdr,
base: *const u8,
}
impl<'a> Phdr<'a> {
fn notes(&self) -> NoteIter<'a> {
unsafe {
NoteIter::new(
self.base.add(self.phdr.p_offset as usize),
self.phdr.p_memsz as usize,
)
}
}
}
const NT_GNU_BUILD_ID: u32 = 3;
#[allow(non_camel_case_types)]
#[repr(C)]
struct Elf_Nhdr {
n_namesz: u32,
n_descsz: u32,
n_type: u32,
}
struct Note<'a> {
name: &'a [u8],
desc: &'a [u8],
tipe: u32,
}
struct NoteIter<'a> {
base: &'a [u8],
error: bool,
}
impl<'a> NoteIter<'a> {
unsafe fn new(base: *const u8, size: usize) -> Self {
NoteIter {
base: from_raw_parts(base, size),
error: false,
}
}
}
fn align_to(x: usize, to: usize) -> usize {
(x + to - 1) & (!to + 1)
}
fn take_bytes_align4<'a>(num: usize, bytes: &mut &'a [u8]) -> Option<&'a [u8]> {
if bytes.len() < align_to(num, 4) {
return None;
}
let (out, bytes_new) = bytes.split_at(num);
*bytes = &bytes_new[align_to(num, 4) - num..];
Some(out)
}
fn take_nhdr<'a>(bytes: &mut &'a [u8]) -> Option<&'a Elf_Nhdr> {
if size_of::<Elf_Nhdr>() > bytes.len() {
return None;
}
let out = unsafe { transmute::<*const u8, &'a Elf_Nhdr>(bytes.as_ptr()) };
*bytes = &bytes[size_of::<Elf_Nhdr>()..];
Some(out)
}
impl<'a> Iterator for NoteIter<'a> {
type Item = Note<'a>;
fn next(&mut self) -> Option<Self::Item> {
if self.base.len() == 0 || self.error {
return None;
}
let nhdr = take_nhdr(&mut self.base)?;
let name = take_bytes_align4(nhdr.n_namesz as usize, &mut self.base)?;
let desc = take_bytes_align4(nhdr.n_descsz as usize, &mut self.base)?;
Some(Note {
name: name,
desc: desc,
tipe: nhdr.n_type,
})
}
}
struct Perm(u32);
const PERM_X: u32 = 0b00000001;
const PERM_W: u32 = 0b00000010;
const PERM_R: u32 = 0b00000100;
impl core::fmt::Display for Perm {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let v = self.0;
if v & PERM_R != 0 {
f.write_char('r')?
}
if v & PERM_W != 0 {
f.write_char('w')?
}
if v & PERM_X != 0 {
f.write_char('x')?
}
Ok(())
}
}
struct Segment {
addr: usize,
size: usize,
mod_rel_addr: usize,
flags: Perm,
}
struct SegmentIter<'a> {
phdrs: &'a [Elf_Phdr],
base: usize,
}
impl Iterator for SegmentIter<'_> {
type Item = Segment;
fn next(&mut self) -> Option<Self::Item> {
self.phdrs.split_first().and_then(|(phdr, new_phdrs)| {
self.phdrs = new_phdrs;
if phdr.p_type != PT_LOAD {
self.next()
} else {
Some(Segment {
addr: phdr.p_vaddr as usize + self.base,
size: phdr.p_memsz as usize,
mod_rel_addr: phdr.p_vaddr as usize,
flags: Perm(phdr.p_flags),
})
}
})
}
}
struct Dso<'a> {
name: &'a str,
build_id: &'a [u8],
base: usize,
phdrs: &'a [Elf_Phdr],
}
impl Dso<'_> {
fn segments(&self) -> SegmentIter<'_> {
SegmentIter {
phdrs: self.phdrs.as_ref(),
base: self.base,
}
}
}
struct HexSlice<'a> {
bytes: &'a [u8],
}
impl fmt::Display for HexSlice<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for byte in self.bytes {
write!(f, "{:02x}", byte)?;
}
Ok(())
}
}
fn get_build_id<'a>(info: &'a dl_phdr_info) -> Option<&'a [u8]> {
for phdr in info.program_headers() {
if phdr.phdr.p_type == PT_NOTE {
for note in phdr.notes() {
if note.tipe == NT_GNU_BUILD_ID && (note.name == b"GNU\0" || note.name == b"GNU") {
return Some(note.desc);
}
}
}
}
None
}
enum Error {
NameError(core::str::Utf8Error),
BuildIDError,
}
fn for_each_dso(mut visitor: &mut DsoPrinter<'_, '_>) {
extern "C" fn callback(
info: &dl_phdr_info,
_size: usize,
visitor: &mut DsoPrinter<'_, '_>,
) -> i32 {
let name_len = unsafe { libc::strlen(info.name) };
let name_slice: &[u8] =
unsafe { core::slice::from_raw_parts(info.name as *const u8, name_len) };
let name = match core::str::from_utf8(name_slice) {
Ok(name) => name,
Err(err) => {
return visitor.error(Error::NameError(err)) as i32;
}
};
let build_id = match get_build_id(info) {
Some(build_id) => build_id,
None => {
return visitor.error(Error::BuildIDError) as i32;
}
};
visitor.dso(Dso {
name: name,
build_id: build_id,
phdrs: info.phdr_slice(),
base: info.addr as usize,
}) as i32
}
unsafe { dl_iterate_phdr(callback, &mut visitor) };
}
struct DsoPrinter<'a, 'b> {
writer: &'a mut core::fmt::Formatter<'b>,
module_count: usize,
error: core::fmt::Result,
}
impl DsoPrinter<'_, '_> {
fn dso(&mut self, dso: Dso<'_>) -> bool {
let mut write = || {
write!(
self.writer,
"{{{{{{module:{:#x}:{}:elf:{}}}}}}}\n",
self.module_count,
dso.name,
HexSlice {
bytes: dso.build_id.as_ref()
}
)?;
for seg in dso.segments() {
write!(
self.writer,
"{{{{{{mmap:{:#x}:{:#x}:load:{:#x}:{}:{:#x}}}}}}}\n",
seg.addr, seg.size, self.module_count, seg.flags, seg.mod_rel_addr
)?;
}
self.module_count += 1;
Ok(())
};
match write() {
Ok(()) => false,
Err(err) => {
self.error = Err(err);
true
}
}
}
fn error(&mut self, _error: Error) -> bool {
false
}
}
pub fn print_dso_context(out: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
out.write_str("{{{reset}}}\n")?;
let mut visitor = DsoPrinter {
writer: out,
module_count: 0,
error: Ok(()),
};
for_each_dso(&mut visitor);
visitor.error
}