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
13pub 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
73pub 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}