use crate::macho;
use crate::read::{Bytes, Error, ReadError, Result};
use alloc::boxed::Box;
use alloc::vec::Vec;
#[derive(Debug)]
pub struct ExportsTrieIterator<'data> {
node_iter: NodeIterator<'data>,
}
impl<'data> ExportsTrieIterator<'data> {
pub(super) fn new(data: &'data [u8]) -> Self {
ExportsTrieIterator {
node_iter: NodeIterator::new(data),
}
}
pub fn next(&mut self) -> Result<Option<ExportSymbol<'data>>> {
for node in &mut self.node_iter {
if let Some(export_symbol) = node? {
return Ok(Some(export_symbol));
}
}
Ok(None)
}
}
impl<'data> Iterator for ExportsTrieIterator<'data> {
type Item = Result<ExportSymbol<'data>>;
fn next(&mut self) -> Option<Self::Item> {
self.next().transpose()
}
}
#[derive(Debug)]
pub struct ExportSymbol<'data> {
name: Box<[u8]>,
flags: macho::ExportSymbolFlags,
data: ExportData<'data>,
}
impl<'data> ExportSymbol<'data> {
pub fn name(&self) -> &[u8] {
&self.name
}
pub fn into_name(self) -> Vec<u8> {
self.name.into_vec()
}
pub fn flags(&self) -> macho::ExportSymbolFlags {
self.flags
}
pub fn data(&self) -> &ExportData<'data> {
&self.data
}
}
#[derive(Debug)]
struct Frame<'data> {
data: Bytes<'data>,
offset: u64,
children_remaining: u8,
name_buf_len: usize,
}
#[derive(Debug)]
struct NodeIterator<'data> {
data: &'data [u8],
first: bool,
stack: Vec<Frame<'data>>,
name_buf: Vec<u8>,
}
impl<'data> NodeIterator<'data> {
pub(super) fn new(data: &'data [u8]) -> Self {
NodeIterator {
data,
first: true,
stack: Vec::new(),
name_buf: Vec::new(),
}
}
fn push_node(&mut self, offset: u64) -> Result<Option<ExportSymbol<'data>>> {
let mut data = Bytes(
self.data
.get(offset as usize..)
.read_error("Invalid exports trie offset")?,
);
let terminal_size = data
.read_uleb128()
.read_error("Invalid exports trie terminal size")?;
let export_data = if terminal_size == 0 {
None
} else {
let (flags, export_data) = ExportData::parse(
data.read_bytes(terminal_size as usize)
.read_error("Exports trie terminal size exceeds bounds")?,
)?;
Some(ExportSymbol {
name: self.name_buf.clone().into_boxed_slice(),
flags,
data: export_data,
})
};
let children_count = *data
.read::<u8>()
.read_error("Invalid exports trie children count")?;
self.stack.push(Frame {
data,
offset,
children_remaining: children_count,
name_buf_len: self.name_buf.len(),
});
Ok(export_data)
}
fn next(&mut self) -> Result<Option<Option<ExportSymbol<'data>>>> {
if self.first {
self.first = false;
return Ok(Some(self.push_node(0)?));
}
loop {
let Some(frame) = self.stack.last_mut() else {
return Ok(None);
};
self.name_buf.truncate(frame.name_buf_len);
if frame.children_remaining == 0 {
self.stack.pop();
continue;
}
let edge_str = frame
.data
.read_string()
.read_error("Invalid exports trie edge string")?;
let child_offset = frame
.data
.read_uleb128()
.read_error("Invalid exports trie child offset")?;
frame.children_remaining -= 1;
self.name_buf.extend(edge_str);
if self.stack.iter().any(|frame| frame.offset == child_offset) {
return Err(Error("Invalid exports trie child offset"));
}
return Ok(Some(self.push_node(child_offset)?));
}
}
}
impl<'data> Iterator for NodeIterator<'data> {
type Item = Result<Option<ExportSymbol<'data>>>;
fn next(&mut self) -> Option<Self::Item> {
self.next().transpose()
}
}
#[derive(Debug)]
pub enum ExportData<'data> {
Regular {
address: u64,
},
Reexport {
dylib_ordinal: u64,
import_name: &'data [u8],
},
StubAndResolver {
stub_address: u64,
resolver_address: u64,
},
}
impl<'data> ExportData<'data> {
pub(super) fn parse(mut data: Bytes<'data>) -> Result<(macho::ExportSymbolFlags, Self)> {
let flags = data
.read_uleb128()
.map(macho::ExportSymbolFlags)
.read_error("Invalid exports trie flags")?;
if flags.has_unknown_bits() {
return Err(Error("Exports trie flags too large"));
}
if flags.contains(macho::EXPORT_SYMBOL_FLAGS_REEXPORT) {
let dylib_ordinal = data
.read_uleb128()
.read_error("Invalid exports trie dylib ordinal")?;
let import_name = data
.read_string()
.read_error("Invalid exports trie import name")?;
return Ok((
flags,
ExportData::Reexport {
dylib_ordinal,
import_name,
},
));
}
if flags.contains(macho::EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER) {
let stub_address = data
.read_uleb128()
.read_error("Invalid exports trie stub address")?;
let resolver_address = data
.read_uleb128()
.read_error("Invalid exports trie resolver address")?;
return Ok((
flags,
ExportData::StubAndResolver {
stub_address,
resolver_address,
},
));
}
let address = data
.read_uleb128()
.read_error("Invalid exports trie address")?;
Ok((flags, ExportData::Regular { address }))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn single_export() {
let data = [
0x00, 0x01, b'a', 0x00, 0x05, 0x02, 0x00, 0x10, 0x00, ];
let mut exports = ExportsTrieIterator::new(&data);
let symbol = exports.next().unwrap().unwrap();
assert_eq!(symbol.name(), b"a");
assert_eq!(symbol.flags(), macho::ExportSymbolFlags(0));
let ExportData::Regular { address: 0x10 } = symbol.data() else {
panic!();
};
assert!(exports.next().unwrap().is_none());
}
#[test]
fn root_with_no_children() {
let data = [
0x00, 0x00, ];
let mut exports = ExportsTrieIterator::new(&data);
assert!(exports.next().unwrap().is_none());
}
#[test]
fn cycle_to_root() {
let data = [
0x00, 0x01, b'a', 0x00, 0x00, ];
let mut exports = ExportsTrieIterator::new(&data);
assert!(exports.next().is_err());
}
}