1use std::collections::HashSet;
12
13use idakit::prelude::*;
14
15const SHOW_PROTOS: usize = 12;
16const FRAME_BUDGET: usize = 3000;
17const MAX_NAMES: usize = 64;
18
19fn one_line(table: &TypeTable, id: TypeId) -> String {
24 match &table.get(id).shape {
25 TypeShape::Void => "void".to_owned(),
26 TypeShape::Bool => "bool".to_owned(),
27 TypeShape::Int { bytes, signed } => {
28 format!(
29 "{}int{}",
30 if *signed { "" } else { "u" },
31 u32::from(*bytes) * 8
32 )
33 }
34 TypeShape::Float { bytes } => format!("float{}", u32::from(*bytes) * 8),
35 TypeShape::Ptr(inner) => format!("{} *", one_line(table, *inner)),
36 TypeShape::Array { elem, len } => format!("{}[{len}]", one_line(table, *elem)),
37 TypeShape::Struct { name, .. } => {
38 format!("struct {}", name.as_deref().unwrap_or("<anon>"))
39 }
40 TypeShape::Union { name, .. } => format!("union {}", name.as_deref().unwrap_or("<anon>")),
41 TypeShape::Enum { name, .. } => format!("enum {}", name.as_deref().unwrap_or("<anon>")),
42 TypeShape::Function {
43 ret,
44 params,
45 varargs,
46 } => {
47 let ps: Vec<String> = params.iter().map(|p| one_line(table, *p)).collect();
48 let tail = if *varargs { ", ..." } else { "" };
49 format!("{} ({}{})", one_line(table, *ret), ps.join(", "), tail)
50 }
51 TypeShape::Typedef { name, .. } | TypeShape::Opaque(name) => name.clone(),
52 TypeShape::Unknown => "<unknown>".to_owned(),
53 }
54}
55
56fn referenced_name(shape: &TypeShape) -> Option<&str> {
62 match shape {
63 TypeShape::Opaque(name) => Some(name),
64 _ => shape.tag_name(),
65 }
66}
67
68fn param_count(image: &Type) -> usize {
70 match image.shape() {
71 TypeShape::Function { params, .. } => params.len(),
72 _ => 0,
73 }
74}
75
76fn layout(table: &TypeTable, id: TypeId, indent: usize, seen: &mut HashSet<TypeId>) {
81 const MAX_DEPTH: usize = 3;
82 let (TypeShape::Struct { members, .. } | TypeShape::Union { members, .. }) =
83 &table.get(id).shape
84 else {
85 return;
86 };
87 let pad = " ".repeat(indent);
88 for m in members {
89 println!(
90 "{pad}+{:#06x} {:<28} {}",
91 m.bit_offset / 8,
92 m.name,
93 one_line(table, m.ty)
94 );
95 if indent < MAX_DEPTH && seen.insert(m.ty) {
96 layout(table, m.ty, indent + 1, seen);
97 }
98 }
99}
100
101fn print_layout(image: &Type, name: &str) {
106 let (table, root) = (image.types(), image.root());
107 println!("\n-- layout: {name} --");
108 match image.size() {
109 Some(s) => println!(" {} ({s:#x} bytes)", one_line(table, root)),
110 None => println!(" {} (no stored size)", one_line(table, root)),
111 }
112 if image.members().is_none_or(<[TypeMember]>::is_empty) {
113 println!(" forward-declared -- the database stores the name but no field layout");
114 return;
115 }
116 let mut seen = HashSet::from([root]);
117 layout(table, root, 1, &mut seen);
118}
119
120fn soff(offset: i64) -> String {
122 if offset < 0 {
123 format!("-{:#x}", offset.unsigned_abs())
124 } else {
125 format!("{offset:#x}")
126 }
127}
128
129fn print_frame(frame: &StackFrame, ea: Address) {
131 println!(
132 "\n== stack frame: {ea:#x} ({} bytes, {} slots) ==",
133 frame.size(),
134 frame.len()
135 );
136 for v in frame.slots() {
137 let ty = v
138 .ty()
139 .map_or_else(|| "-".to_owned(), |id| one_line(frame.types(), id));
140 let label = match v.kind() {
141 StackSlotKind::Variable { name, .. } if !name.is_empty() => name.clone(),
142 StackSlotKind::Variable { .. } => "<unnamed>".to_owned(),
143 StackSlotKind::ReturnAddress => "<return address>".to_owned(),
144 StackSlotKind::SavedRegisters => "<saved registers>".to_owned(),
145 };
146 println!(" {:>8} {label}", soff(v.offset()));
147 if ty != "-" {
148 println!(" {ty}");
149 }
150 }
151}
152
153fn main() -> Result<(), Box<dyn std::error::Error>> {
154 let mut argv = std::env::args().skip(1);
155 let db = argv.next().expect("usage: types <db.i64> [TypeName]");
156 let arg_type = argv.next();
157
158 Ida::run(move |ida| -> Result<(), Error> {
159 ida.call(move |idb| -> Result<(), Error> {
160 idb.open(&db).call()?;
161
162 let mut total = 0usize;
165 let mut typed = 0usize;
166 let mut shown: Vec<(Address, String, Type)> = Vec::new();
167 let mut names: Vec<String> = Vec::new();
168 let mut best_frame: Option<(Address, StackFrame)> = None;
169 let mut best_vars = 0usize;
170 let mut frames_tried = 0usize;
171
172 for f in idb.functions() {
173 total += 1;
174
175 if let Some(image) = f.prototype_type()? {
176 typed += 1;
177 for (_, t) in image.types().iter() {
178 if let Some(n) = referenced_name(&t.shape)
179 && names.len() < MAX_NAMES
180 && !names.iter().any(|x| x == n)
181 {
182 names.push(n.to_owned());
183 }
184 }
185 if shown.len() < SHOW_PROTOS {
186 shown.push((f.address(), f.name().as_str().to_owned(), image));
187 }
188 }
189
190 if frames_tried < FRAME_BUDGET
191 && let Some(frame) = idb.frame(f.address())?
192 {
193 frames_tried += 1;
194 let locals = frame.slots().iter().filter(|v| !v.is_special());
197 let sane = locals.clone().all(|v| v.offset().unsigned_abs() < 0x1_0000);
198 let n = locals.count();
199 if sane && (best_frame.is_none() || n > best_vars) {
200 best_vars = n;
201 best_frame = Some((f.address(), frame));
202 }
203 }
204 }
205
206 println!("== function prototypes ==");
207 println!("{typed} of {total} functions carry a stored prototype.\n");
208 for (ea, sym, image) in &shown {
209 println!(" {ea:#x} {}", one_line(image.types(), image.root()));
210 let short: String = sym.chars().take(64).collect();
211 if !short.is_empty() {
212 println!(" {short}");
213 }
214 }
215 if let Some((ea, _, image)) = shown.iter().max_by_key(|(_, _, im)| param_count(im))
216 && let TypeShape::Function {
217 ret,
218 params,
219 varargs,
220 } = image.shape()
221 && !params.is_empty()
222 {
223 println!("\n every parameter is a resolved TypeId -- {ea:#x}:");
224 println!(" ret {}", one_line(image.types(), *ret));
225 for (i, p) in params.iter().enumerate() {
226 println!(" arg{i} {}", one_line(image.types(), *p));
227 }
228 if *varargs {
229 println!(" ...");
230 }
231 }
232
233 println!("\n== referenced named types ==");
236 if let Some(name) = &arg_type {
237 match idb.type_named(name) {
238 Ok(image) => print_layout(&image, name),
239 Err(e) => println!(" type_named({name:?}): {e}"),
240 }
241 } else {
242 let mut bodies: Vec<(String, Type)> = Vec::new();
243 let mut forward: Vec<String> = Vec::new();
244 let mut not_local = 0usize;
245 for name in &names {
246 match idb.type_named(name) {
247 Ok(image) if image.members().is_some_and(|m| !m.is_empty()) => {
248 bodies.push((name.clone(), image));
249 }
250 Ok(_) => forward.push(name.clone()),
251 Err(Error::TypeNotFound { .. }) => not_local += 1,
252 Err(e) => println!(" type_named({name:?}): {e}"),
253 }
254 }
255 println!(
256 "{} referenced: {} with a full body, {} forward-declared, {} not a local type.",
257 names.len(),
258 bodies.len(),
259 forward.len(),
260 not_local
261 );
262 if let Some((name, image)) = bodies
263 .iter()
264 .max_by_key(|(_, im)| im.members().map_or(0, <[_]>::len))
265 {
266 print_layout(image, name);
267 } else {
268 for n in forward.iter().take(6) {
269 println!(" forward-decl: {n}");
270 }
271 }
272 }
273
274 match &best_frame {
275 Some((ea, frame)) => print_frame(frame, *ea),
276 None => println!("\n(no function has a stack frame)"),
277 }
278
279 idb.close(false);
280 println!("\nTYPES OK");
281 Ok(())
282 })?
283 })??;
284
285 Ok(())
286}