Skip to main content

macho_cpp/
typeinfo.rs

1use std::collections::BTreeMap;
2
3use super::types::{
4    CppBaseClass, CppConfidence, CppEvidence, CppEvidenceKind, CppTypeInfoKind, CppTypeInfoNode,
5};
6use crate::Result;
7use crate::model::addr::Va;
8use crate::model::macho_file::MachoFile;
9use crate::model::symbol::SymbolTable;
10use macho_dyld::resolve::fixups::{collect_resolved_targets, resolve_pointer_target};
11use macho_dyld::resolve::{ResolutionContext, ResolvedTarget};
12
13/// Performs build_typeinfo_index.
14pub fn build_typeinfo_index(macho: &MachoFile<'_>) -> Result<BTreeMap<String, CppTypeInfoNode>> {
15    let symtab = macho.ext::<SymbolTable<'_>>()?;
16    let symbols = symtab.symbols();
17    let resolver = PointerResolver::new(macho);
18    let mut va_to_symbol = BTreeMap::new();
19    for symbol in symbols {
20        if symbol.is_defined() && symbol.value != 0 {
21            va_to_symbol.insert(symbol.value, symbol.name.to_string());
22        }
23    }
24
25    let mut out = BTreeMap::new();
26    for symbol in symbols.iter().filter(|symbol| {
27        symbol.is_defined() && symbol.value != 0 && is_typeinfo_symbol(symbol.name)
28    }) {
29        let class_name = typeinfo_class_name(symbol.name);
30        let raw_kind = resolver.resolve_pointer(Va(symbol.value)).ok();
31        let kind = raw_kind
32            .as_ref()
33            .and_then(|target| match target {
34                ResolvedTarget::Address(va) => va_to_symbol.get(&va.0).map(String::as_str),
35                ResolvedTarget::Import { name, .. } => Some(name.as_str()),
36                _ => None,
37            })
38            .map(classify_typeinfo_kind)
39            .unwrap_or(CppTypeInfoKind::Unknown);
40
41        let bases = match kind {
42            CppTypeInfoKind::SingleInheritance => {
43                parse_single_base(&resolver, &va_to_symbol, Va(symbol.value), macho.is_64bit())
44                    .into_iter()
45                    .collect()
46            }
47            CppTypeInfoKind::VirtualMultipleInheritance => {
48                parse_vmi_bases(&resolver, &va_to_symbol, Va(symbol.value), macho.is_64bit())
49            }
50            _ => Vec::new(),
51        };
52
53        out.insert(
54            class_name.clone(),
55            CppTypeInfoNode {
56                name: class_name,
57                mangled_name: symbol.name.to_string(),
58                address: symbol.value,
59                kind,
60                bases,
61                evidence: vec![CppEvidence {
62                    kind: CppEvidenceKind::TypeInfo,
63                    confidence: CppConfidence::High,
64                    detail: symbol.name.to_string(),
65                }],
66            },
67        );
68    }
69
70    Ok(out)
71}
72
73/// Build a C++ typeinfo index from one borrowed thin Mach-O byte source.
74///
75/// The source is not copied and may be a byte slice, vector, or caller-owned
76/// read-only memory map. Universal binaries are rejected so the caller can
77/// explicitly select an architecture.
78pub fn build_typeinfo_index_from_source<S>(source: &S) -> Result<BTreeMap<String, CppTypeInfoNode>>
79where
80    S: AsRef<[u8]> + ?Sized,
81{
82    let macho = crate::parse_source(source)?;
83    build_typeinfo_index(&macho)
84}
85
86fn is_typeinfo_symbol(name: &str) -> bool {
87    name.starts_with("__ZTI") || name.starts_with("_ZTI")
88}
89
90fn typeinfo_class_name(name: &str) -> String {
91    macho_demangle::demangle_symbol(name)
92        .and_then(|text| text.strip_prefix("typeinfo for ").map(str::to_string))
93        .unwrap_or_else(|| name.to_string())
94}
95
96fn classify_typeinfo_kind(symbol_name: &str) -> CppTypeInfoKind {
97    if symbol_name.contains("__si_class_type_info") {
98        CppTypeInfoKind::SingleInheritance
99    } else if symbol_name.contains("__vmi_class_type_info") {
100        CppTypeInfoKind::VirtualMultipleInheritance
101    } else if symbol_name.contains("__class_type_info") {
102        CppTypeInfoKind::Class
103    } else {
104        CppTypeInfoKind::Unknown
105    }
106}
107
108fn parse_single_base(
109    resolver: &PointerResolver<'_>,
110    va_to_symbol: &BTreeMap<u64, String>,
111    typeinfo_va: Va,
112    is_64bit: bool,
113) -> Option<CppBaseClass> {
114    let ptr_size = if is_64bit { 8 } else { 4 } as u64;
115    let base_field = Va(typeinfo_va.0 + ptr_size * 2);
116    let target = resolver.resolve_pointer(base_field).ok()?;
117    let (name, flags) = resolve_base_name(target, va_to_symbol)?;
118    Some(CppBaseClass {
119        name,
120        offset: None,
121        flags,
122        is_virtual: false,
123        is_public: true,
124        evidence: vec![CppEvidence {
125            kind: CppEvidenceKind::TypeInfo,
126            confidence: CppConfidence::High,
127            detail: "single inheritance RTTI".to_string(),
128        }],
129    })
130}
131
132fn parse_vmi_bases(
133    resolver: &PointerResolver<'_>,
134    va_to_symbol: &BTreeMap<u64, String>,
135    typeinfo_va: Va,
136    is_64bit: bool,
137) -> Vec<CppBaseClass> {
138    let ptr_size = if is_64bit { 8 } else { 4 } as u64;
139    let count_offset = Va(typeinfo_va.0 + ptr_size * 2 + 4);
140    let base_count = resolver.read_u32(count_offset).unwrap_or(0) as usize;
141    let mut bases = Vec::new();
142    let mut cursor = typeinfo_va.0 + ptr_size * 2 + 8;
143    for _ in 0..base_count {
144        let target = match resolver.resolve_pointer(Va(cursor)) {
145            Ok(target) => target,
146            Err(_) => break,
147        };
148        let flags_offset = Va(cursor + ptr_size);
149        let offset_flags = if is_64bit {
150            resolver.read_u64(flags_offset).unwrap_or(0)
151        } else {
152            resolver.read_u32(flags_offset).unwrap_or(0) as u64
153        };
154        if let Some((name, _)) = resolve_base_name(target, va_to_symbol) {
155            let signed_offset = (offset_flags as i64) >> 8;
156            let offset = (signed_offset != 0).then_some(signed_offset);
157            bases.push(CppBaseClass {
158                name,
159                offset,
160                flags: offset_flags,
161                is_virtual: offset_flags & 0x1 != 0,
162                is_public: offset_flags & 0x2 != 0,
163                evidence: vec![CppEvidence {
164                    kind: CppEvidenceKind::TypeInfo,
165                    confidence: CppConfidence::High,
166                    detail: "vmi RTTI base entry".to_string(),
167                }],
168            });
169        }
170        cursor += ptr_size * 2;
171    }
172    bases
173}
174
175fn resolve_base_name(
176    target: ResolvedTarget,
177    va_to_symbol: &BTreeMap<u64, String>,
178) -> Option<(String, u64)> {
179    match target {
180        ResolvedTarget::Address(va) => {
181            let symbol_name = va_to_symbol.get(&va.0)?;
182            Some((typeinfo_class_name(symbol_name), 0))
183        }
184        ResolvedTarget::Import { name, .. } => Some((typeinfo_class_name(&name), 0)),
185        _ => None,
186    }
187}
188
189struct PointerResolver<'a> {
190    ctx: ResolutionContext<'a, 'a>,
191    fixups: BTreeMap<u64, ResolvedTarget>,
192}
193
194impl<'a> PointerResolver<'a> {
195    fn new(macho: &'a MachoFile<'a>) -> Self {
196        Self {
197            ctx: ResolutionContext::new(macho),
198            fixups: collect_resolved_targets(macho),
199        }
200    }
201
202    fn resolve_pointer(&self, va: Va) -> Result<ResolvedTarget> {
203        Ok(resolve_pointer_target(&self.ctx, &self.fixups, va)?)
204    }
205
206    fn read_u32(&self, va: Va) -> Result<u32> {
207        let bytes = self.ctx.macho().read_bytes_at_va(va, 4)?;
208        let arr: [u8; 4] = bytes.try_into().map_err(|_| {
209            crate::error::Error::format("read_bytes_at_va returned unexpected length (want 4)")
210        })?;
211        Ok(self.ctx.macho().endian().read_u32(arr))
212    }
213
214    fn read_u64(&self, va: Va) -> Result<u64> {
215        let bytes = self.ctx.macho().read_bytes_at_va(va, 8)?;
216        let arr: [u8; 8] = bytes.try_into().map_err(|_| {
217            crate::error::Error::format("read_bytes_at_va returned unexpected length (want 8)")
218        })?;
219        Ok(self.ctx.macho().endian().read_u64(arr))
220    }
221}