summaryrefslogtreecommitdiffstats
path: root/tools/profiler/rust-helper
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--tools/profiler/rust-helper/Cargo.toml30
-rw-r--r--tools/profiler/rust-helper/src/compact_symbol_table.rs40
-rw-r--r--tools/profiler/rust-helper/src/elf.rs100
-rw-r--r--tools/profiler/rust-helper/src/lib.rs111
4 files changed, 281 insertions, 0 deletions
diff --git a/tools/profiler/rust-helper/Cargo.toml b/tools/profiler/rust-helper/Cargo.toml
new file mode 100644
index 0000000000..28f8276ee1
--- /dev/null
+++ b/tools/profiler/rust-helper/Cargo.toml
@@ -0,0 +1,30 @@
+[package]
+name = "profiler_helper"
+version = "0.1.0"
+authors = ["Markus Stange <mstange@themasta.com>"]
+
+[dependencies]
+memmap = "0.7"
+rustc-demangle = "0.1"
+uuid = "0.8"
+
+[dependencies.object]
+version = "0.16"
+optional = true
+default-features = false
+features = ["std", "read"]
+
+[dependencies.goblin]
+optional = true
+# The version and features of goblin need to match what's in object's Cargo.toml,
+# because we really want object's goblin and not another instance of goblin.
+version = "0.1"
+features = ["endian_fd", "elf32", "elf64", "mach32", "mach64", "pe32", "pe64", "archive"]
+default-features = false
+
+[dependencies.thin-vec]
+version = "0.2.1"
+features = ["gecko-ffi"]
+
+[features]
+parse_elf = ["object", "goblin"]
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,
+ }
+}