diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:02:58 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:02:58 +0000 |
commit | 698f8c2f01ea549d77d7dc3338a12e04c11057b9 (patch) | |
tree | 173a775858bd501c378080a10dca74132f05bc50 /library/backtrace/src/print | |
parent | Initial commit. (diff) | |
download | rustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.tar.xz rustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.zip |
Adding upstream version 1.64.0+dfsg1.upstream/1.64.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'library/backtrace/src/print')
-rw-r--r-- | library/backtrace/src/print/fuchsia.rs | 436 |
1 files changed, 436 insertions, 0 deletions
diff --git a/library/backtrace/src/print/fuchsia.rs b/library/backtrace/src/print/fuchsia.rs new file mode 100644 index 000000000..959253acb --- /dev/null +++ b/library/backtrace/src/print/fuchsia.rs @@ -0,0 +1,436 @@ +use core::fmt::{self, Write}; +use core::mem::{size_of, transmute}; +use core::slice::from_raw_parts; +use libc::c_char; + +extern "C" { + // dl_iterate_phdr takes a callback that will receive a dl_phdr_info pointer + // for every DSO that has been linked into the process. dl_iterate_phdr also + // ensures that the dynamic linker is locked from start to finish of the + // iteration. If the callback returns a non-zero value the iteration is + // terminated early. 'data' will be passed as the third argument to the + // callback on each call. 'size' gives the size of the dl_phdr_info. + #[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; +} + +// We need to parse out the build ID and some basic program header data +// which means that we need a bit of stuff from the ELF spec as well. + +const PT_LOAD: u32 = 1; +const PT_NOTE: u32 = 4; + +// Now we have to replicate, bit for bit, the structure of the dl_phdr_info +// type used by fuchsia's current dynamic linker. Chromium also has this ABI +// boundary as well as crashpad. Eventully we'd like to move these cases to +// use elf-search but we'd need to provide that in the SDK and that has not +// yet been done. Thus we (and they) are stuck having to use this method +// which incurs a tight coupling with the fuchsia libc. + +#[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, + } + } + // We have no way of knowing of checking if e_phoff and e_phnum are valid. + // libc should ensure this for us however so it's safe to form a slice here. + 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, + } + }) + } +} + +// Elf_Phdr represents a 64-bit ELF program header in the endianness of the target +// architecture. +#[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, +} + +// Phdr represents a valid ELF program header and its contents. +struct Phdr<'a> { + phdr: &'a Elf_Phdr, + base: *const u8, +} + +impl<'a> Phdr<'a> { + // We have no way of checking if p_addr or p_memsz are valid. Fuchsia's libc + // parses the notes first however so by virtue of being here these headers + // must be valid. NoteIter does not require the underlying data to be valid + // but it does require the bounds to be valid. We trust that libc has ensured + // that this is the case for us here. + fn notes(&self) -> NoteIter<'a> { + unsafe { + NoteIter::new( + self.base.add(self.phdr.p_offset as usize), + self.phdr.p_memsz as usize, + ) + } + } +} + +// The note type for build IDs. +const NT_GNU_BUILD_ID: u32 = 3; + +// Elf_Nhdr represents an ELF note header in the endianness of the target. +#[allow(non_camel_case_types)] +#[repr(C)] +struct Elf_Nhdr { + n_namesz: u32, + n_descsz: u32, + n_type: u32, +} + +// Note represents an ELF note (header + contents). The name is left as a u8 +// slice because it is not always null terminated and rust makes it easy enough +// to check that the bytes match eitherway. +struct Note<'a> { + name: &'a [u8], + desc: &'a [u8], + tipe: u32, +} + +// NoteIter lets you safely iterate over a note segment. It terminates as soon +// as an error occurs or there are no more notes. If you iterate over invalid +// data it will function as though no notes were found. +struct NoteIter<'a> { + base: &'a [u8], + error: bool, +} + +impl<'a> NoteIter<'a> { + // It is an invariant of function that the pointer and size given denote a + // valid range of bytes that can all be read. The contents of these bytes + // can be anything but the range must be valid for this to be safe. + unsafe fn new(base: *const u8, size: usize) -> Self { + NoteIter { + base: from_raw_parts(base, size), + error: false, + } + } +} + +// align_to aligns 'x' to 'to'-byte alignment assuming 'to' is a power of 2. +// This follows a standard pattern in C/C++ ELF parsing code where +// (x + to - 1) & -to is used. Rust does not let you negate usize so I use +// 2's-complement conversion to recreate that. +fn align_to(x: usize, to: usize) -> usize { + (x + to - 1) & (!to + 1) +} + +// take_bytes_align4 consumes num bytes from the slice (if present) and +// additionally ensures that the final slice is properlly aligned. If an +// either the number of bytes requested is too large or the slice can't be +// realigned afterwards due to not enough remaining bytes existing, None is +// returned and the slice is not modified. +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) +} + +// This function has no real invariants the caller must uphold other than +// perhaps that 'bytes' should be aligned for performance (and on some +// architectures correctness). The values in the Elf_Nhdr fields might +// be nonsense but this function ensures no such thing. +fn take_nhdr<'a>(bytes: &mut &'a [u8]) -> Option<&'a Elf_Nhdr> { + if size_of::<Elf_Nhdr>() > bytes.len() { + return None; + } + // This is safe as long as there is enough space and we just confirmed that + // in the if statement above so this should not be unsafe. + let out = unsafe { transmute::<*const u8, &'a Elf_Nhdr>(bytes.as_ptr()) }; + // Note that sice_of::<Elf_Nhdr>() is always 4-byte aligned. + *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> { + // Check if we've reached the end. + if self.base.len() == 0 || self.error { + return None; + } + // We transmute out an nhdr but we carefully consider the resulting + // struct. We don't trust the namesz or descsz and we make no unsafe + // decisions based on the type. So even if we get out complete garbage + // we should still be safe. + 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); + +/// Indicates that a segment is executable. +const PERM_X: u32 = 0b00000001; +/// Indicates that a segment is writable. +const PERM_W: u32 = 0b00000010; +/// Indicates that a segment is readable. +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(()) + } +} + +/// Represents an ELF segment at runtime. +struct Segment { + /// Gives the runtime virtual address of this segment's contents. + addr: usize, + /// Gives the memory size of this segment's contents. + size: usize, + /// Gives the module virtual address of this segment with the ELF file. + mod_rel_addr: usize, + /// Gives the permissions found in the ELF file. These permissions are not + /// necessarily the permissions present at runtime however. + flags: Perm, +} + +/// Lets one iterate over Segments from a DSO. +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), + }) + } + }) + } +} + +/// Represents an ELF DSO (Dynamic Shared Object). This type references +/// the data stored in the actual DSO rather than making its own copy. +struct Dso<'a> { + /// The dynamic linker always gives us a name, even if the name is empty. + /// In the case of the main executable this name will be empty. In the case + /// of a shared object it will be the soname (see DT_SONAME). + name: &'a str, + /// On Fuchsia virtually all binaries have build IDs but this is not a strict + /// requirement. There's no way to match up DSO information with a real ELF + /// file afterwards if there is no build_id so we require that every DSO + /// have one here. DSO's without a build_id are ignored. + build_id: &'a [u8], + + base: usize, + phdrs: &'a [Elf_Phdr], +} + +impl Dso<'_> { + /// Returns an iterator over Segments in this 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 +} + +/// These errors encode issues that arise while parsing information about +/// each DSO. +enum Error { + /// NameError means that an error occurred while converting a C style string + /// into a rust string. + NameError(core::str::Utf8Error), + /// BuildIDError means that we didn't find a build ID. This could either be + /// because the DSO had no build ID or because the segment containing the + /// build ID was malformed. + BuildIDError, +} + +/// Calls either 'dso' or 'error' for each DSO linked into the process by the +/// dynamic linker. +/// +/// # Arguments +/// +/// * `visitor` - A DsoPrinter that will have one of eats methods called foreach DSO. +fn for_each_dso(mut visitor: &mut DsoPrinter<'_, '_>) { + extern "C" fn callback( + info: &dl_phdr_info, + _size: usize, + visitor: &mut DsoPrinter<'_, '_>, + ) -> i32 { + // dl_iterate_phdr ensures that info.name will point to a valid + // location. + 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 + } +} + +/// This function prints the Fuchsia symbolizer markup for all information contained in a DSO. +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 +} |