Skip to content

Commit

Permalink
Add ObjectMap
Browse files Browse the repository at this point in the history
Maps from addresses to symbol names and object file names
using Mach-O STAB entries.
  • Loading branch information
philipc committed Oct 24, 2020
1 parent 28d1cef commit 6213eaf
Show file tree
Hide file tree
Showing 8 changed files with 237 additions and 13 deletions.
4 changes: 4 additions & 0 deletions Cargo.toml
Expand Up @@ -75,3 +75,7 @@ required-features = ["read_core"]
[[example]]
name = "ar"
required-features = ["read_core", "archive"]

[[example]]
name = "objectmap"
required-features = ["read_core"]
67 changes: 67 additions & 0 deletions examples/objectmap.rs
@@ -0,0 +1,67 @@
use object::Object;
use std::{env, fs, process};

fn main() {
let mut args = env::args().skip(1);
if args.len() == 0 {
eprintln!(
"Usage: {} <file> [address] ...",
env::args().next().unwrap()
);
process::exit(1);
}

let file_path = args.next().unwrap();
let file = match fs::File::open(&file_path) {
Ok(file) => file,
Err(err) => {
println!("Failed to open file '{}': {}", file_path, err,);
process::exit(1);
}
};
let file = match unsafe { memmap::Mmap::map(&file) } {
Ok(mmap) => mmap,
Err(err) => {
println!("Failed to map file '{}': {}", file_path, err,);
process::exit(1);
}
};
let file = match object::File::parse(&*file) {
Ok(file) => file,
Err(err) => {
println!("Failed to parse file '{}': {}", file_path, err);
process::exit(1);
}
};

let map = file.object_map();

if args.len() == 0 {
for symbol in map.symbols() {
print_symbol(symbol, &map);
}
} else {
for arg in args {
let mut arg = &arg[..];
if arg.starts_with("0x") {
arg = &arg[2..];
}
let address = u64::from_str_radix(arg, 16).expect("Failed to parse address");
if let Some(symbol) = map.get(address) {
print_symbol(symbol, &map);
} else {
println!("{:} not found", address);
}
}
}
}

fn print_symbol(symbol: &object::ObjectMapEntry<'_>, map: &object::ObjectMap<'_>) {
println!(
"{:x} {:x} {} {}",
symbol.address(),
symbol.size(),
String::from_utf8_lossy(symbol.name()),
String::from_utf8_lossy(symbol.object(map)),
);
}
10 changes: 7 additions & 3 deletions src/read/any.rs
Expand Up @@ -12,9 +12,9 @@ use crate::read::pe;
use crate::read::wasm;
use crate::read::{
self, Architecture, BinaryFormat, ComdatKind, CompressedData, Error, FileFlags, Object,
ObjectComdat, ObjectSection, ObjectSegment, ObjectSymbol, ObjectSymbolTable, Relocation,
Result, SectionFlags, SectionIndex, SectionKind, SymbolFlags, SymbolIndex, SymbolKind,
SymbolMap, SymbolMapName, SymbolScope, SymbolSection,
ObjectComdat, ObjectMap, ObjectSection, ObjectSegment, ObjectSymbol, ObjectSymbolTable,
Relocation, Result, SectionFlags, SectionIndex, SectionKind, SymbolFlags, SymbolIndex,
SymbolKind, SymbolMap, SymbolMapName, SymbolScope, SymbolSection,
};

/// Evaluate an expression on the contents of a file format enum.
Expand Down Expand Up @@ -331,6 +331,10 @@ where
with_inner!(self.inner, FileInternal, |x| x.symbol_map())
}

fn object_map(&self) -> ObjectMap<'data> {
with_inner!(self.inner, FileInternal, |x| x.object_map())
}

fn has_debug_symbols(&self) -> bool {
with_inner!(self.inner, FileInternal, |x| x.has_debug_symbols())
}
Expand Down
2 changes: 1 addition & 1 deletion src/read/coff/symbol.rs
Expand Up @@ -112,7 +112,7 @@ impl<'data> SymbolTable<'data> {
impl pe::ImageSymbol {
/// Parse a COFF symbol name.
///
/// `strings` must be the string table used for symbols names.
/// `strings` must be the string table used for symbol names.
pub fn name<'data>(&'data self, strings: StringTable<'data>) -> Result<&'data [u8]> {
if self.name[0] == 0 {
// If the name starts with 0 then the last 4 bytes are a string table offset.
Expand Down
8 changes: 6 additions & 2 deletions src/read/macho/file.rs
Expand Up @@ -3,8 +3,8 @@ use core::fmt::Debug;
use core::{mem, str};

use crate::read::{
self, Architecture, ComdatKind, Error, FileFlags, Object, ObjectComdat, ObjectSection,
ReadError, Result, SectionIndex, SymbolIndex,
self, Architecture, ComdatKind, Error, FileFlags, Object, ObjectComdat, ObjectMap,
ObjectSection, ReadError, Result, SectionIndex, SymbolIndex,
};
use crate::{endian, macho, BigEndian, Bytes, Endian, Endianness, Pod};

Expand Down Expand Up @@ -199,6 +199,10 @@ where
None
}

fn object_map(&'file self) -> ObjectMap<'data> {
self.symbols.object_map(self.endian)
}

fn has_debug_symbols(&self) -> bool {
self.section_by_name(".debug_info").is_some()
}
Expand Down
60 changes: 57 additions & 3 deletions src/read/macho/symbol.rs
Expand Up @@ -7,8 +7,9 @@ use crate::macho;
use crate::pod::Pod;
use crate::read::util::StringTable;
use crate::read::{
self, ObjectSymbol, ObjectSymbolTable, ReadError, Result, SectionIndex, SectionKind,
SymbolFlags, SymbolIndex, SymbolKind, SymbolMap, SymbolMapEntry, SymbolScope, SymbolSection,
self, ObjectMap, ObjectMapEntry, ObjectSymbol, ObjectSymbolTable, ReadError, Result,
SectionIndex, SectionKind, SymbolFlags, SymbolIndex, SymbolKind, SymbolMap, SymbolMapEntry,
SymbolScope, SymbolSection,
};

use super::{MachHeader, MachOFile};
Expand Down Expand Up @@ -73,7 +74,7 @@ impl<'data, Mach: MachHeader> SymbolTable<'data, Mach> {
&self,
f: F,
) -> SymbolMap<Entry> {
let mut symbols = Vec::with_capacity(self.symbols.len());
let mut symbols = Vec::new();
for nlist in self.symbols {
if !nlist.is_definition() {
continue;
Expand All @@ -84,6 +85,59 @@ impl<'data, Mach: MachHeader> SymbolTable<'data, Mach> {
}
SymbolMap::new(symbols)
}

/// Construct a map from addresses to symbol names and object file names.
pub fn object_map(&self, endian: Mach::Endian) -> ObjectMap<'data> {
let mut symbols = Vec::new();
let mut objects = Vec::new();
let mut object = None;
let mut current_function = None;
// Each module starts with one or two N_SO symbols (path, or directory + filename)
// and one N_OSO symbol. The module is terminated by an empty N_SO symbol.
for nlist in self.symbols {
let n_type = nlist.n_type();
if n_type & macho::N_STAB == 0 {
continue;
}
// TODO: includes variables too (N_GSYM, N_STSYM). These may need to get their
// address from regular symbols though.
match n_type {
macho::N_SO => {
object = None;
}
macho::N_OSO => {
object = None;
if let Ok(name) = nlist.name(endian, self.strings) {
if !name.is_empty() {
object = Some(objects.len());
objects.push(name);
}
}
}
macho::N_FUN => {
if let Ok(name) = nlist.name(endian, self.strings) {
if !name.is_empty() {
current_function = Some((name, nlist.n_value(endian).into()))
} else if let Some((name, address)) = current_function.take() {
if let Some(object) = object {
symbols.push(ObjectMapEntry {
address,
size: nlist.n_value(endian).into(),
name,
object,
});
}
}
}
}
_ => {}
}
}
ObjectMap {
symbols: SymbolMap::new(symbols),
objects,
}
}
}

/// An iterator over the symbols of a `MachOFile32`.
Expand Down
82 changes: 81 additions & 1 deletion src/read/mod.rs
Expand Up @@ -155,7 +155,7 @@ pub trait SymbolMapEntry {
}

/// A map from addresses to symbols.
#[derive(Debug)]
#[derive(Debug, Default)]
pub struct SymbolMap<T: SymbolMapEntry> {
symbols: Vec<T>,
}
Expand Down Expand Up @@ -221,6 +221,86 @@ impl<'data> SymbolMapEntry for SymbolMapName<'data> {
}
}

/// A map from addresses to symbol names and object files.
///
/// This is derived from STAB entries in Mach-O files.
#[derive(Debug, Default, Clone)]
pub struct ObjectMap<'data> {
symbols: SymbolMap<ObjectMapEntry<'data>>,
objects: Vec<&'data [u8]>,
}

impl<'data> ObjectMap<'data> {
/// Get the entry containing the given address.
pub fn get(&self, address: u64) -> Option<&ObjectMapEntry<'data>> {
self.symbols
.get(address)
.filter(|entry| entry.size == 0 || address.wrapping_sub(entry.address) < entry.size)
}

/// Get all symbols in the map.
#[inline]
pub fn symbols(&self) -> &[ObjectMapEntry<'data>] {
self.symbols.symbols()
}

/// Get all objects in the map.
#[inline]
pub fn objects(&self) -> &[&'data [u8]] {
&self.objects
}
}

/// A `ObjectMap` entry.
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ObjectMapEntry<'data> {
address: u64,
size: u64,
name: &'data [u8],
object: usize,
}

impl<'data> ObjectMapEntry<'data> {
/// Get the symbol address.
#[inline]
pub fn address(&self) -> u64 {
self.address
}

/// Get the symbol size.
///
/// This may be 0 if the size is unknown.
#[inline]
pub fn size(&self) -> u64 {
self.size
}

/// Get the symbol name.
#[inline]
pub fn name(&self) -> &'data [u8] {
self.name
}

/// Get the index of the object file name.
#[inline]
pub fn object_index(&self) -> usize {
self.object
}

/// Get the object file name.
#[inline]
pub fn object(&self, map: &ObjectMap<'data>) -> &'data [u8] {
map.objects[self.object]
}
}

impl<'data> SymbolMapEntry for ObjectMapEntry<'data> {
#[inline]
fn address(&self) -> u64 {
self.address
}
}

/// The target referenced by a relocation.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum RelocationTarget {
Expand Down
17 changes: 14 additions & 3 deletions src/read/traits.rs
Expand Up @@ -2,9 +2,9 @@ use alloc::borrow::Cow;
use alloc::vec::Vec;

use crate::read::{
self, Architecture, ComdatKind, CompressedData, FileFlags, Relocation, Result, SectionFlags,
SectionIndex, SectionKind, SymbolFlags, SymbolIndex, SymbolKind, SymbolMap, SymbolMapName,
SymbolScope, SymbolSection,
self, Architecture, ComdatKind, CompressedData, FileFlags, ObjectMap, Relocation, Result,
SectionFlags, SectionIndex, SectionKind, SymbolFlags, SymbolIndex, SymbolKind, SymbolMap,
SymbolMapName, SymbolScope, SymbolSection,
};
use crate::Endianness;

Expand Down Expand Up @@ -110,9 +110,13 @@ pub trait Object<'data: 'file, 'file>: read::private::Sealed {
/// Get an iterator over the debugging symbols in the file.
///
/// This may skip over symbols that are malformed or unsupported.
///
/// For Mach-O files, this does not include STAB entries.
fn symbols(&'file self) -> Self::SymbolIterator;

/// Get the dynamic linking symbol table, if any.
///
/// Only ELF has a separate dynamic linking symbol table.
fn dynamic_symbol_table(&'file self) -> Option<Self::SymbolTable>;

/// Get an iterator over the dynamic linking symbols in the file.
Expand All @@ -139,6 +143,13 @@ pub trait Object<'data: 'file, 'file>: read::private::Sealed {
SymbolMap::new(symbols)
}

/// Construct a map from addresses to symbol names and object file names.
///
/// This is derived from Mach-O STAB entries.
fn object_map(&'file self) -> ObjectMap<'data> {
ObjectMap::default()
}

/// Return true if the file contains debug information sections, false if not.
fn has_debug_symbols(&self) -> bool;

Expand Down

0 comments on commit 6213eaf

Please sign in to comment.