diff options
Diffstat (limited to 'tools/profiler/rust-helper/src')
-rw-r--r-- | tools/profiler/rust-helper/src/compact_symbol_table.rs | 40 | ||||
-rw-r--r-- | tools/profiler/rust-helper/src/elf.rs | 100 | ||||
-rw-r--r-- | tools/profiler/rust-helper/src/lib.rs | 111 |
3 files changed, 251 insertions, 0 deletions
diff --git a/tools/profiler/rust-helper/src/compact_symbol_table.rs b/tools/profiler/rust-helper/src/compact_symbol_table.rs new file mode 100644 index 0000000000..12c4ca081b --- /dev/null +++ b/tools/profiler/rust-helper/src/compact_symbol_table.rs @@ -0,0 +1,40 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use std::collections::HashMap; +use thin_vec::ThinVec; + +#[repr(C)] +pub struct CompactSymbolTable { + pub addr: ThinVec<u32>, + pub index: ThinVec<u32>, + pub buffer: ThinVec<u8>, +} + +impl CompactSymbolTable { + pub fn new() -> Self { + Self { + addr: ThinVec::new(), + index: ThinVec::new(), + buffer: ThinVec::new(), + } + } + + pub fn from_map(map: HashMap<u32, &str>) -> Self { + let mut table = Self::new(); + let mut entries: Vec<_> = map.into_iter().collect(); + entries.sort_by_key(|&(addr, _)| addr); + for (addr, name) in entries { + table.addr.push(addr); + table.index.push(table.buffer.len() as u32); + table.add_name(name); + } + table.index.push(table.buffer.len() as u32); + table + } + + fn add_name(&mut self, name: &str) { + self.buffer.extend_from_slice(name.as_bytes()); + } +} diff --git a/tools/profiler/rust-helper/src/elf.rs b/tools/profiler/rust-helper/src/elf.rs new file mode 100644 index 0000000000..168f5f8837 --- /dev/null +++ b/tools/profiler/rust-helper/src/elf.rs @@ -0,0 +1,100 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use compact_symbol_table::CompactSymbolTable; +use goblin::elf; +use object::read::{ElfFile, Object}; +use object::SymbolKind; +use std::cmp; +use std::collections::HashMap; +use uuid::Uuid; + +const UUID_SIZE: usize = 16; +const PAGE_SIZE: usize = 4096; + +fn get_symbol_map<'a, 'b, T>(object_file: &'b T) -> HashMap<u32, &'a str> +where + T: Object<'a, 'b>, +{ + object_file + .dynamic_symbols() + .chain(object_file.symbols()) + .filter(|(_, symbol)| symbol.kind() == SymbolKind::Text) + .filter_map(|(_, symbol)| symbol.name().map(|name| (symbol.address() as u32, name))) + .collect() +} + +pub fn get_compact_symbol_table( + buffer: &[u8], + breakpad_id: Option<&str>, +) -> Option<CompactSymbolTable> { + let elf_file = ElfFile::parse(buffer).ok()?; + let elf_id = get_elf_id(&elf_file, buffer)?; + if !breakpad_id.map_or(true, |id| id == format!("{:X}0", elf_id.to_simple_ref())) { + return None; + } + return Some(CompactSymbolTable::from_map(get_symbol_map(&elf_file))); +} + +fn create_elf_id(identifier: &[u8], little_endian: bool) -> Uuid { + // Make sure that we have exactly UUID_SIZE bytes available + let mut data = [0 as u8; UUID_SIZE]; + let len = cmp::min(identifier.len(), UUID_SIZE); + data[0..len].copy_from_slice(&identifier[0..len]); + + if little_endian { + // The file ELF file targets a little endian architecture. Convert to + // network byte order (big endian) to match the Breakpad processor's + // expectations. For big endian object files, this is not needed. + data[0..4].reverse(); // uuid field 1 + data[4..6].reverse(); // uuid field 2 + data[6..8].reverse(); // uuid field 3 + } + + Uuid::from_bytes(data) +} + +/// Tries to obtain the object identifier of an ELF object. +/// +/// As opposed to Mach-O, ELF does not specify a unique ID for object files in +/// its header. Compilers and linkers usually add either `SHT_NOTE` sections or +/// `PT_NOTE` program header elements for this purpose. If one of these notes +/// is present, ElfFile's build_id() method will find it. +/// +/// If neither of the above are present, this function will hash the first page +/// of the `.text` section (program code). This matches what the Breakpad +/// processor does. +/// +/// If all of the above fails, this function will return `None`. +pub fn get_elf_id(elf_file: &ElfFile, data: &[u8]) -> Option<Uuid> { + if let Some(identifier) = elf_file.build_id() { + return Some(create_elf_id(identifier, elf_file.elf().little_endian)); + } + + // We were not able to locate the build ID, so fall back to hashing the + // first page of the ".text" (program code) section. This algorithm XORs + // 16-byte chunks directly into a UUID buffer. + if let Some(section_data) = find_text_section(elf_file.elf(), data) { + let mut hash = [0; UUID_SIZE]; + for i in 0..cmp::min(section_data.len(), PAGE_SIZE) { + hash[i % UUID_SIZE] ^= section_data[i]; + } + + return Some(create_elf_id(&hash, elf_file.elf().little_endian)); + } + + None +} + +/// Returns a reference to the data of the the .text section in an ELF binary. +fn find_text_section<'elf, 'data>(elf: &'elf elf::Elf, data: &'data [u8]) -> Option<&'data [u8]> { + elf.section_headers.iter().find_map(|header| { + match (header.sh_type, elf.shdr_strtab.get(header.sh_name)) { + (elf::section_header::SHT_PROGBITS, Some(Ok(".text"))) => { + Some(&data[header.sh_offset as usize..][..header.sh_size as usize]) + } + _ => None, + } + }) +} diff --git a/tools/profiler/rust-helper/src/lib.rs b/tools/profiler/rust-helper/src/lib.rs new file mode 100644 index 0000000000..a22e631852 --- /dev/null +++ b/tools/profiler/rust-helper/src/lib.rs @@ -0,0 +1,111 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +extern crate memmap; +extern crate rustc_demangle; +extern crate thin_vec; +extern crate uuid; + +#[cfg(feature = "parse_elf")] +extern crate goblin; +#[cfg(feature = "parse_elf")] +extern crate object; + +mod compact_symbol_table; + +#[cfg(feature = "parse_elf")] +mod elf; + +#[cfg(feature = "parse_elf")] +use memmap::MmapOptions; +#[cfg(feature = "parse_elf")] +use std::fs::File; + +use compact_symbol_table::CompactSymbolTable; +use rustc_demangle::try_demangle; +use std::ffi::CStr; +use std::mem; +use std::os::raw::c_char; +use std::ptr; + +#[cfg(feature = "parse_elf")] +pub fn get_compact_symbol_table_from_file( + debug_path: &str, + breakpad_id: Option<&str>, +) -> Option<CompactSymbolTable> { + let file = File::open(debug_path).ok()?; + let buffer = unsafe { MmapOptions::new().map(&file).ok()? }; + elf::get_compact_symbol_table(&buffer, breakpad_id) +} + +#[cfg(not(feature = "parse_elf"))] +pub fn get_compact_symbol_table_from_file( + _debug_path: &str, + _breakpad_id: Option<&str>, +) -> Option<CompactSymbolTable> { + None +} + +#[no_mangle] +pub extern "C" fn profiler_get_symbol_table( + debug_path: *const c_char, + breakpad_id: *const c_char, + symbol_table: &mut CompactSymbolTable, +) -> bool { + let debug_path = unsafe { CStr::from_ptr(debug_path).to_string_lossy() }; + let breakpad_id = if breakpad_id.is_null() { + None + } else { + match unsafe { CStr::from_ptr(breakpad_id).to_str() } { + Ok(s) => Some(s), + Err(_) => return false, + } + }; + + match get_compact_symbol_table_from_file(&debug_path, breakpad_id.map(|id| id.as_ref())) { + Some(mut st) => { + std::mem::swap(symbol_table, &mut st); + true + } + None => false, + } +} + +#[no_mangle] +pub extern "C" fn profiler_demangle_rust( + mangled: *const c_char, + buffer: *mut c_char, + buffer_len: usize, +) -> bool { + assert!(!mangled.is_null()); + assert!(!buffer.is_null()); + + if buffer_len == 0 { + return false; + } + + let buffer: *mut u8 = unsafe { mem::transmute(buffer) }; + let mangled = match unsafe { CStr::from_ptr(mangled).to_str() } { + Ok(s) => s, + Err(_) => return false, + }; + + match try_demangle(mangled) { + Ok(demangled) => { + let mut demangled = format!("{:#}", demangled); + if !demangled.is_ascii() { + return false; + } + demangled.truncate(buffer_len - 1); + + let bytes = demangled.as_bytes(); + unsafe { + ptr::copy(bytes.as_ptr(), buffer, bytes.len()); + ptr::write(buffer.offset(bytes.len() as isize), 0); + } + true + } + Err(_) => false, + } +} |