use std::collections::HashSet;
use idakit::prelude::*;
const SHOW_PROTOS: usize = 12;
const FRAME_BUDGET: usize = 3000;
const MAX_NAMES: usize = 64;
fn one_line(table: &TypeTable, id: TypeId) -> String {
match &table.get(id).shape {
TypeShape::Void => "void".to_owned(),
TypeShape::Bool => "bool".to_owned(),
TypeShape::Int { bytes, signed } => {
format!(
"{}int{}",
if *signed { "" } else { "u" },
u32::from(*bytes) * 8
)
}
TypeShape::Float { bytes } => format!("float{}", u32::from(*bytes) * 8),
TypeShape::Ptr(inner) => format!("{} *", one_line(table, *inner)),
TypeShape::Array { elem, len } => format!("{}[{len}]", one_line(table, *elem)),
TypeShape::Struct { name, .. } => {
format!("struct {}", name.as_deref().unwrap_or("<anon>"))
}
TypeShape::Union { name, .. } => format!("union {}", name.as_deref().unwrap_or("<anon>")),
TypeShape::Enum { name, .. } => format!("enum {}", name.as_deref().unwrap_or("<anon>")),
TypeShape::Function {
ret,
params,
varargs,
} => {
let ps: Vec<String> = params.iter().map(|p| one_line(table, *p)).collect();
let tail = if *varargs { ", ..." } else { "" };
format!("{} ({}{})", one_line(table, *ret), ps.join(", "), tail)
}
TypeShape::Typedef { name, .. } | TypeShape::Opaque(name) => name.clone(),
TypeShape::Unknown => "<unknown>".to_owned(),
}
}
fn referenced_name(shape: &TypeShape) -> Option<&str> {
match shape {
TypeShape::Opaque(name) => Some(name),
_ => shape.tag_name(),
}
}
fn param_count(image: &Type) -> usize {
match image.shape() {
TypeShape::Function { params, .. } => params.len(),
_ => 0,
}
}
fn layout(table: &TypeTable, id: TypeId, indent: usize, seen: &mut HashSet<TypeId>) {
const MAX_DEPTH: usize = 3;
let (TypeShape::Struct { members, .. } | TypeShape::Union { members, .. }) =
&table.get(id).shape
else {
return;
};
let pad = " ".repeat(indent);
for m in members {
println!(
"{pad}+{:#06x} {:<28} {}",
m.bit_offset / 8,
m.name,
one_line(table, m.ty)
);
if indent < MAX_DEPTH && seen.insert(m.ty) {
layout(table, m.ty, indent + 1, seen);
}
}
}
fn print_layout(image: &Type, name: &str) {
let (table, root) = (image.types(), image.root());
println!("\n-- layout: {name} --");
match image.size() {
Some(s) => println!(" {} ({s:#x} bytes)", one_line(table, root)),
None => println!(" {} (no stored size)", one_line(table, root)),
}
if image.members().is_none_or(<[TypeMember]>::is_empty) {
println!(" forward-declared -- the database stores the name but no field layout");
return;
}
let mut seen = HashSet::from([root]);
layout(table, root, 1, &mut seen);
}
fn soff(offset: i64) -> String {
if offset < 0 {
format!("-{:#x}", offset.unsigned_abs())
} else {
format!("{offset:#x}")
}
}
fn print_frame(frame: &StackFrame, ea: Address) {
println!(
"\n== stack frame: {ea:#x} ({} bytes, {} slots) ==",
frame.size(),
frame.len()
);
for v in frame.slots() {
let ty = v
.ty()
.map_or_else(|| "-".to_owned(), |id| one_line(frame.types(), id));
let label = match v.kind() {
StackSlotKind::Variable { name, .. } if !name.is_empty() => name.clone(),
StackSlotKind::Variable { .. } => "<unnamed>".to_owned(),
StackSlotKind::ReturnAddress => "<return address>".to_owned(),
StackSlotKind::SavedRegisters => "<saved registers>".to_owned(),
};
println!(" {:>8} {label}", soff(v.offset()));
if ty != "-" {
println!(" {ty}");
}
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut argv = std::env::args().skip(1);
let db = argv.next().expect("usage: types <db.i64> [TypeName]");
let arg_type = argv.next();
Ida::run(move |ida| -> Result<(), Error> {
ida.call(move |idb| -> Result<(), Error> {
idb.open(&db).call()?;
let mut total = 0usize;
let mut typed = 0usize;
let mut shown: Vec<(Address, String, Type)> = Vec::new();
let mut names: Vec<String> = Vec::new();
let mut best_frame: Option<(Address, StackFrame)> = None;
let mut best_vars = 0usize;
let mut frames_tried = 0usize;
for f in idb.functions() {
total += 1;
if let Some(image) = f.prototype_type()? {
typed += 1;
for (_, t) in image.types().iter() {
if let Some(n) = referenced_name(&t.shape)
&& names.len() < MAX_NAMES
&& !names.iter().any(|x| x == n)
{
names.push(n.to_owned());
}
}
if shown.len() < SHOW_PROTOS {
shown.push((f.address(), f.name().as_str().to_owned(), image));
}
}
if frames_tried < FRAME_BUDGET
&& let Some(frame) = idb.frame(f.address())?
{
frames_tried += 1;
let locals = frame.slots().iter().filter(|v| !v.is_special());
let sane = locals.clone().all(|v| v.offset().unsigned_abs() < 0x1_0000);
let n = locals.count();
if sane && (best_frame.is_none() || n > best_vars) {
best_vars = n;
best_frame = Some((f.address(), frame));
}
}
}
println!("== function prototypes ==");
println!("{typed} of {total} functions carry a stored prototype.\n");
for (ea, sym, image) in &shown {
println!(" {ea:#x} {}", one_line(image.types(), image.root()));
let short: String = sym.chars().take(64).collect();
if !short.is_empty() {
println!(" {short}");
}
}
if let Some((ea, _, image)) = shown.iter().max_by_key(|(_, _, im)| param_count(im))
&& let TypeShape::Function {
ret,
params,
varargs,
} = image.shape()
&& !params.is_empty()
{
println!("\n every parameter is a resolved TypeId -- {ea:#x}:");
println!(" ret {}", one_line(image.types(), *ret));
for (i, p) in params.iter().enumerate() {
println!(" arg{i} {}", one_line(image.types(), *p));
}
if *varargs {
println!(" ...");
}
}
println!("\n== referenced named types ==");
if let Some(name) = &arg_type {
match idb.type_named(name) {
Ok(image) => print_layout(&image, name),
Err(e) => println!(" type_named({name:?}): {e}"),
}
} else {
let mut bodies: Vec<(String, Type)> = Vec::new();
let mut forward: Vec<String> = Vec::new();
let mut not_local = 0usize;
for name in &names {
match idb.type_named(name) {
Ok(image) if image.members().is_some_and(|m| !m.is_empty()) => {
bodies.push((name.clone(), image));
}
Ok(_) => forward.push(name.clone()),
Err(Error::TypeNotFound { .. }) => not_local += 1,
Err(e) => println!(" type_named({name:?}): {e}"),
}
}
println!(
"{} referenced: {} with a full body, {} forward-declared, {} not a local type.",
names.len(),
bodies.len(),
forward.len(),
not_local
);
if let Some((name, image)) = bodies
.iter()
.max_by_key(|(_, im)| im.members().map_or(0, <[_]>::len))
{
print_layout(image, name);
} else {
for n in forward.iter().take(6) {
println!(" forward-decl: {n}");
}
}
}
match &best_frame {
Some((ea, frame)) => print_frame(frame, *ea),
None => println!("\n(no function has a stack frame)"),
}
idb.close(false);
println!("\nTYPES OK");
Ok(())
})?
})??;
Ok(())
}