use core::ops::Range;
use scroll::{Pread, Uleb128};
use crate::error;
use core::fmt::{self, Debug};
use crate::mach::load_command;
use crate::alloc::vec::Vec;
use crate::alloc::string::String;
type Flag = u64;
pub const EXPORT_SYMBOL_FLAGS_KIND_MASK : Flag = 0x03;
pub const EXPORT_SYMBOL_FLAGS_KIND_REGULAR : Flag = 0x00;
pub const EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE : Flag = 0x02;
pub const EXPORT_SYMBOL_FLAGS_KIND_THREAD_LOCAL : Flag = 0x01;
pub const EXPORT_SYMBOL_FLAGS_WEAK_DEFINITION : Flag = 0x04;
pub const EXPORT_SYMBOL_FLAGS_REEXPORT : Flag = 0x08;
pub const EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER : Flag = 0x10;
#[derive(Debug)]
pub enum SymbolKind {
Regular,
Absolute,
ThreadLocal,
UnknownSymbolKind(Flag),
}
impl SymbolKind {
pub fn new(kind: Flag) -> SymbolKind {
match kind & EXPORT_SYMBOL_FLAGS_KIND_MASK {
0x00 => SymbolKind::Regular,
0x01 => SymbolKind::ThreadLocal,
0x02 => SymbolKind::Absolute,
_ => SymbolKind::UnknownSymbolKind(kind),
}
}
pub fn to_str(&self) -> &'static str {
match self {
SymbolKind::Regular => "Regular",
SymbolKind::Absolute => "Absolute",
SymbolKind::ThreadLocal => "Thread_LOCAL",
SymbolKind::UnknownSymbolKind(_k) => "Unknown",
}
}
}
#[derive(Debug)]
pub enum ExportInfo<'a> {
Regular {
address: u64,
flags: Flag,
},
Reexport {
lib: &'a str,
lib_symbol_name: Option<&'a str>,
flags: Flag,
},
Stub {
stub_offset: scroll::Uleb128,
resolver_offset: scroll::Uleb128,
flags: Flag,
},
}
impl<'a> ExportInfo<'a> {
pub fn parse(bytes: &'a [u8], libs: &[&'a str], flags: Flag, mut offset: usize) -> error::Result<ExportInfo<'a>> {
use self::ExportInfo::*;
let regular = |offset| -> error::Result<ExportInfo> {
let address = bytes.pread::<Uleb128>(offset)?;
Ok(Regular {
address: address.into(),
flags
})
};
let reexport = |mut offset| -> error::Result<ExportInfo<'a>> {
let lib_ordinal: u64 = {
let tmp = bytes.pread::<Uleb128>(offset)?;
offset += tmp.size();
tmp.into()
};
let lib_symbol_name = bytes.pread::<&str>(offset)?;
let lib = libs[lib_ordinal as usize];
let lib_symbol_name = if lib_symbol_name == "" { None } else { Some (lib_symbol_name)};
Ok(Reexport {
lib,
lib_symbol_name,
flags
})
};
match SymbolKind::new(flags) {
SymbolKind::Regular => {
if flags & EXPORT_SYMBOL_FLAGS_REEXPORT != 0 {
reexport(offset)
} else if flags & EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER != 0 {
let stub_offset = bytes.pread::<Uleb128>(offset)?;
offset += stub_offset.size();
let resolver_offset = bytes.pread::<Uleb128>(offset)?;
Ok(Stub {
stub_offset,
resolver_offset,
flags
})
} else {
regular(offset)
}
},
SymbolKind::ThreadLocal | SymbolKind::Absolute => {
if flags & EXPORT_SYMBOL_FLAGS_REEXPORT != 0 {
reexport(offset)
} else {
regular(offset)
}
},
SymbolKind::UnknownSymbolKind(_kind) => {
regular(offset)
}
}
}
}
#[derive(Debug)]
pub struct Export<'a> {
pub name: String,
pub info: ExportInfo<'a>,
pub size: usize,
pub offset: u64,
}
impl<'a> Export<'a> {
pub fn new(name: String, info: ExportInfo<'a>) -> Export<'a> {
let offset = match info {
ExportInfo::Regular { address, .. } => address,
_ => 0x0,
};
Export { name, info, size: 0, offset }
}
}
pub struct ExportTrie<'a> {
data: &'a [u8],
location: Range<usize>,
}
impl<'a> ExportTrie<'a> {
#[inline]
fn walk_nodes(&self, libs: &[&'a str], branches: Vec<(String, usize)>, acc: &mut Vec<Export<'a>>) -> error::Result<()> {
for (symbol, next_node) in branches {
self.walk_trie(libs, symbol, next_node, acc)?;
}
Ok(())
}
fn walk_branches(&self, nbranches: usize, current_symbol: String, mut offset: usize) -> error::Result<Vec<(String, usize)>> {
let mut branches = Vec::with_capacity(nbranches);
for _i in 0..nbranches {
let offset = &mut offset;
let string = self.data.pread::<&str>(*offset)?;
let mut key = current_symbol.clone();
key.push_str(string);
*offset = *offset + string.len() + 1;
let next_node = Uleb128::read(&self.data, offset)? as usize + self.location.start;
branches.push((key, next_node));
}
Ok(branches)
}
fn walk_trie(&self, libs: &[&'a str], current_symbol: String, start: usize, exports: &mut Vec<Export<'a>>) -> error::Result<()> {
if start < self.location.end {
let mut offset = start;
let terminal_size = Uleb128::read(&self.data, &mut offset)?;
if terminal_size == 0 {
let nbranches = Uleb128::read(&self.data, &mut offset)? as usize;
let branches = self.walk_branches(nbranches, current_symbol, offset)?;
self.walk_nodes(libs, branches, exports)
} else {
let pos = offset;
let children_start = &mut (pos + terminal_size as usize);
let nchildren = Uleb128::read(&self.data, children_start)? as usize;
let flags = Uleb128::read(&self.data, &mut offset)?;
let info = ExportInfo::parse(&self.data, libs, flags, offset)?;
let export = Export::new(current_symbol.clone(), info);
exports.push(export);
if nchildren == 0 {
Ok(())
} else {
let branches = self.walk_branches(nchildren, current_symbol, *children_start)?;
self.walk_nodes(libs, branches, exports)
}
}
} else { Ok(()) }
}
pub fn exports(&self, libs: &[&'a str]) -> error::Result<Vec<Export<'a>>> {
let offset = self.location.start;
let current_symbol = String::new();
let mut exports = Vec::new();
self.walk_trie(libs, current_symbol, offset, &mut exports)?;
Ok(exports)
}
pub fn new(bytes: &'a [u8], command: &load_command::DyldInfoCommand) -> Self {
let start = command.export_off as usize;
let end = (command.export_size + command.export_off) as usize;
ExportTrie {
data: bytes,
location: start..end,
}
}
}
impl<'a> Debug for ExportTrie<'a> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.debug_struct("ExportTrie")
.field("data", &"<... redacted ...>")
.field("location", &format_args!("{:#x}..{:#x}", self.location.start, self.location.end))
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn export_trie () {
const EXPORTS: [u8; 64] = [0x00,0x01,0x5f,0x00,0x05,0x00,0x02,0x5f,0x6d,0x68,0x5f,0x65,0x78,0x65,0x63,0x75,0x74,0x65,0x5f,0x68,0x65,0x61,0x64,0x65,0x72,0x00,0x1f,0x6d,0x61,0x00,0x23,0x02,0x00,0x00,0x00,0x00,0x02,0x78,0x69,0x6d,0x75,0x6d,0x00,0x30,0x69,0x6e,0x00,0x35,0x03,0x00,0xc0,0x1e,0x00,0x03,0x00,0xd0,0x1e,0x00,0x00,0x00,0x00,0x00,0x00,0x00];
let exports = &EXPORTS[..];
let libs = vec!["/usr/lib/libderp.so", "/usr/lib/libthuglife.so"];
let mut command = load_command::DyldInfoCommand::default();
command.export_size = exports.len() as u32;
let trie = ExportTrie::new(&exports, &command);
println!("trie: {:#?}", &trie);
let exports = trie.exports(&libs).unwrap();
println!("len: {} exports: {:#?}", exports.len(), &exports);
assert_eq!(exports.len() as usize, 3usize)
}
}