Skip to main content

object/read/macho/
exports_trie.rs

1use crate::macho::{EXPORT_SYMBOL_FLAGS_REEXPORT, EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER};
2use crate::read::{Bytes, ReadError, Result};
3use alloc::boxed::Box;
4use alloc::vec::Vec;
5use core::convert::TryInto;
6
7// The exports trie is a serialized trie with the following structure:
8//
9// struct Node {
10//     terminal_size: uleb128, // Size of export_data
11//     export_data: ExportData, // Absent if terminal_size == 0
12//     children_count: u8,
13//     edges: [(&[u8] edge_str, uleb128 child_offset); children_count],
14//     children: [Node; children_count],
15// }
16//
17// Note that child_offset is relative to the start of the trie data, not the
18// current node.
19//
20// ExportData is a union of the following variants:
21//
22// struct ExportDataRegular {
23//     flags: uleb128,
24//     address: uleb128,
25// }
26//
27// struct ExportDataReexport {
28//     flags: uleb128,
29//     dylib_ordinal: uleb128,
30//     import_name: &'data [u8],
31// }
32//
33// struct ExportDataStubAndResolver {
34//     flags: uleb128,
35//     stub_address: uleb128,
36//     resolver_address: uleb128,
37// }
38//
39// ExportData is only present if the current node corresponds to an exported
40// symbol. Otherwise it is just an internal node in the prefix trie.
41
42/// Iterator over the exports trie.
43#[derive(Debug)]
44pub struct ExportsTrieIterator<'data> {
45    node_iter: NodeIterator<'data>,
46}
47
48impl<'data> ExportsTrieIterator<'data> {
49    pub(super) fn new(data: &'data [u8]) -> Self {
50        ExportsTrieIterator {
51            node_iter: NodeIterator::new(data),
52        }
53    }
54
55    /// Returns the next exported symbol, if any.
56    // All the heavy lifting is done by NodeIterator. This just skips over the internal nodes
57    // with no terminal data.
58    pub fn next(&mut self) -> Result<Option<ExportSymbol<'data>>> {
59        for node in &mut self.node_iter {
60            if let Some(export_symbol) = node? {
61                return Ok(Some(export_symbol));
62            }
63        }
64        Ok(None)
65    }
66}
67
68impl<'data> Iterator for ExportsTrieIterator<'data> {
69    type Item = Result<ExportSymbol<'data>>;
70
71    fn next(&mut self) -> Option<Self::Item> {
72        self.next().transpose()
73    }
74}
75
76/// Exported symbol information.
77#[derive(Debug)]
78pub struct ExportSymbol<'data> {
79    name: Box<[u8]>,
80    flags: u8,
81    data: ExportData<'data>,
82}
83
84impl<'data> ExportSymbol<'data> {
85    /// The name of the exported symbol.
86    pub fn name(&self) -> &[u8] {
87        &self.name
88    }
89
90    /// The flags for the exported symbol.
91    pub fn flags(&self) -> u8 {
92        self.flags
93    }
94
95    /// The terminal data for the exported symbol.
96    pub fn data(&self) -> &ExportData<'data> {
97        &self.data
98    }
99}
100
101#[derive(Debug)]
102struct Frame<'data> {
103    data: Bytes<'data>,
104    children_remaining: u8,
105    name_buf_len: usize,
106}
107
108#[derive(Debug)]
109struct NodeIterator<'data> {
110    data: &'data [u8],
111    offset: usize,
112    stack: Vec<Frame<'data>>,
113    // Accumulates the prefix edge strings as we traverse the trie.
114    name_buf: Vec<u8>,
115}
116
117// Implements a DFS pre-order traversal of the exports trie.
118impl<'data> NodeIterator<'data> {
119    pub(super) fn new(data: &'data [u8]) -> Self {
120        NodeIterator {
121            data,
122            offset: 0,
123            stack: Vec::new(),
124            name_buf: Vec::new(),
125        }
126    }
127
128    fn push_node(&mut self) -> Result<Option<ExportSymbol<'data>>> {
129        let mut data = Bytes(
130            self.data
131                .get(self.offset..)
132                .read_error("Invalid exports trie offset")?,
133        );
134        let terminal_size = data
135            .read_uleb128()
136            .read_error("Invalid exports trie terminal size")?;
137        let export_data = if terminal_size == 0 {
138            None
139        } else {
140            let (flags, export_data) = ExportData::parse(
141                data.read_bytes(terminal_size as usize)
142                    .read_error("Exports trie terminal size exceeds bounds")?,
143            )?;
144            Some(ExportSymbol {
145                name: self.name_buf.clone().into_boxed_slice(),
146                flags,
147                data: export_data,
148            })
149        };
150        let children_count = *data
151            .read::<u8>()
152            .read_error("Invalid exports trie children count")?;
153        self.stack.push(Frame {
154            data,
155            children_remaining: children_count,
156            name_buf_len: self.name_buf.len(),
157        });
158        Ok(export_data)
159    }
160
161    // Returns:
162    // - `Ok(Some(Some(ExportSymbol)))` if we have terminal data at the current node.
163    // - `Ok(Some(None))` if we don't have terminal data at the current node.
164    // - `Ok(None)` if we've reached the end of the trie.
165    fn next(&mut self) -> Result<Option<Option<ExportSymbol<'data>>>> {
166        let Some(frame) = self.stack.last_mut() else {
167            // The stack is empty at the beginning and end of our traversal. We
168            // use self.offset to distinguish the two cases.
169            if self.offset == 0 {
170                return Ok(Some(self.push_node()?));
171            }
172            return Ok(None);
173        };
174        self.name_buf.truncate(frame.name_buf_len);
175        if frame.children_remaining == 0 {
176            self.stack.pop();
177            return self.next();
178        }
179        let edge_str = frame
180            .data
181            .read_string()
182            .read_error("Invalid exports trie edge string")?;
183        let child_offset = frame
184            .data
185            .read_uleb128()
186            .read_error("Invalid exports trie child offset")?;
187        frame.children_remaining -= 1;
188        self.name_buf.extend(edge_str);
189        self.offset = child_offset as usize;
190        Ok(Some(self.push_node()?))
191    }
192}
193
194impl<'data> Iterator for NodeIterator<'data> {
195    type Item = Result<Option<ExportSymbol<'data>>>;
196
197    fn next(&mut self) -> Option<Self::Item> {
198        self.next().transpose()
199    }
200}
201
202/// Terminal data for an exports trie node.
203#[derive(Debug)]
204pub enum ExportData<'data> {
205    /// A regular export.
206    Regular {
207        /// The address of the export.
208        address: u64,
209    },
210    /// A re-exported symbol.
211    Reexport {
212        /// The ordinal of the dylib to re-export from.
213        dylib_ordinal: u64,
214        /// The name of the symbol to re-export.
215        import_name: &'data [u8],
216    },
217    /// A stub-and-resolver symbol.
218    StubAndResolver {
219        /// The address of the stub.
220        stub_address: u64,
221        /// The address of the resolver.
222        resolver_address: u64,
223    },
224}
225
226impl<'data> ExportData<'data> {
227    pub(super) fn parse(mut data: Bytes<'data>) -> Result<(u8, Self)> {
228        let flags = data
229            .read_uleb128()
230            .read_error("Invalid exports trie flags")?;
231        let flags: u8 = flags
232            .try_into()
233            .map_err(|_| ())
234            .read_error("Exports trie flags too large")?;
235        if flags & EXPORT_SYMBOL_FLAGS_REEXPORT != 0 {
236            let dylib_ordinal = data
237                .read_uleb128()
238                .read_error("Invalid exports trie dylib ordinal")?;
239            let import_name = data
240                .read_string()
241                .read_error("Invalid exports trie import name")?;
242            return Ok((
243                flags,
244                ExportData::Reexport {
245                    dylib_ordinal,
246                    import_name,
247                },
248            ));
249        }
250        if flags & EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER != 0 {
251            let stub_address = data
252                .read_uleb128()
253                .read_error("Invalid exports trie stub address")?;
254            let resolver_address = data
255                .read_uleb128()
256                .read_error("Invalid exports trie resolver address")?;
257            return Ok((
258                flags,
259                ExportData::StubAndResolver {
260                    stub_address,
261                    resolver_address,
262                },
263            ));
264        }
265        let address = data
266            .read_uleb128()
267            .read_error("Invalid exports trie address")?;
268        Ok((flags, ExportData::Regular { address }))
269    }
270}