use std::env;
use std::process::exit;
use pelite::FileMap;
use pelite::pe32::{Pe, PeFile, Ptr};
use pelite::pe32::image::{Rva, Va};
use pelite::pe32::msvc::*;
struct VTable<'a> {
col: &'a RTTICompleteObjectLocator,
vtable: &'a [Va],
rva: Rva,
}
struct Type<'a> {
type_ptr: Ptr<TypeDescriptor>,
class_ptr: Ptr<RTTIClassHierarchyDescriptor>,
ty_name: &'a str,
class_desc: &'a RTTIClassHierarchyDescriptor,
vtables: Vec<VTable<'a>>,
}
fn main() {
let mut args = env::args_os();
if args.len() != 2 {
println!("Dumps MSVC RTTI from windows PE binaries.");
exit(0);
}
let path = args.nth(1).unwrap();
let file_map = FileMap::open(&path).expect("msrtti: failed to open the given file");
let file = PeFile::from_bytes(&file_map).expect("msrtti: the file isn't a PE32 binary");
let text = file.section_headers().iter().find(|sec| &sec.Name == b".text\0\0\0").expect("msrtti: no `.text` section found");
let rdata = file.section_headers().iter().find(|sec| &sec.Name == b".rdata\0\0").expect("msrtti: no `.rdata` section found");
let base_relocs = file.base_relocs().expect("msrtti: no base relocations found");
let mut vrefs = Vec::new();
base_relocs.for_each(|rva, _| {
if rva < rdata.VirtualAddress || rva >= (rdata.VirtualAddress + rdata.VirtualSize) {
return;
}
let target_va = file.derva_copy(rva).expect(&format!("msrtti: corrupt reloc at {:08X}", rva));
let target_rva = file.va_to_rva(target_va).expect(&format!("msrtti: corrupt xref at {:08X}", rva));
if target_rva < text.VirtualAddress || target_rva >= (text.VirtualAddress + text.VirtualSize) {
return;
}
vrefs.push(rva);
});
vrefs.sort();
let mut xrefs = Vec::new();
base_relocs.for_each(|rva, _| {
let target_va = file.derva_copy(rva).expect(&format!("msrtti: corrupt reloc at {:08X}", rva));
let target_rva = file.va_to_rva(target_va).expect(&format!("msrtti: corrupt xref at {:08X}", rva));
if let Ok(idx) = vrefs.binary_search(&target_rva) {
xrefs.push(idx);
}
});
xrefs.sort();
xrefs.dedup();
let mut types = Vec::new();
for &xref in &xrefs {
let _ = vtable(file, &mut types, xref, &vrefs);
}
types.sort_by_key(|ty| ty.ty_name);
for ty in &mut types {
ty.vtables.sort_by_key(|vtable| vtable.col.offset);
}
for ty in &types {
let _ = print(file, ty);
for vtable in &ty.vtables {
let _ = print_vtable(file, ty, vtable);
}
let _ = print_class(file, ty);
println!();
}
}
fn vtable<'a>(file: PeFile<'a>, types: &mut Vec<Type<'a>>, xref: usize, vrefs: &[Rva]) -> pelite::Result<()> {
let col_ptr: Ptr<RTTICompleteObjectLocator> = Ptr::from(*file.derva::<Va>(vrefs[xref] - 4)?);
let col = file.deref(col_ptr)?;
let index = if let Some(index) = types.iter_mut().position(|ty| ty.type_ptr == col.type_descriptor && ty.class_ptr == col.class_descriptor) { index }
else {
let ty_name = file.deref_c_str(col.type_descriptor.offset(8))?.to_str()?;
let class_desc = file.deref(col.class_descriptor)?;
types.push(Type { type_ptr: col.type_descriptor, class_ptr: col.class_descriptor, ty_name, class_desc, vtables: Vec::new() });
types.len() - 1
};
let ty = &mut types[index];
let mut size = 1;
while xref + size < vrefs.len() && vrefs[xref + size - 1] + 4 == vrefs[xref + size] {
size += 1;
}
let vtable = file.derva_slice(vrefs[xref], size)?;
ty.vtables.push(VTable { col, vtable, rva: vrefs[xref] });
Ok(())
}
fn print<'a>(_file: PeFile<'a>, ty: &Type<'a>) -> pelite::Result<()> {
let kind = match ty.class_desc.attributes & 3 { 0 => " (SI)", 1 => " (MI)", 2 => " (VI)", 3 => " (MI VI)", _ => unreachable!() };
println!("class {}{}", ty.ty_name, kind);
Ok(())
}
fn print_vtable<'a>(file: PeFile<'a>, ty: &Type<'a>, vtable: &VTable<'a>) -> pelite::Result<()> {
let base_classes = file.deref_slice(ty.class_desc.base_class_array, ty.class_desc.num_base_classes as usize)?;
for &base_class_ptr in base_classes {
let base_class = file.deref(base_class_ptr)?;
if base_class.pmd.mdisp == vtable.col.offset as i32 {
let base_ty_name = file.deref_c_str(base_class.type_descriptor.offset(8))?.to_str()?;
println!("{:#010X}: ??_7{}6B@ {{for `{}'}} ({} methods)", vtable.rva, &ty.ty_name[4..], base_ty_name, vtable.vtable.len());
return Ok(())
}
}
println!("{:#010X}: ??_7{}6B@ {{for `?'}} ({} methods)", vtable.rva, &ty.ty_name[4..], vtable.vtable.len());
Ok(())
}
fn print_class<'a>(file: PeFile<'a>, ty: &Type<'a>) -> pelite::Result<()> {
let base_classes = file.deref_slice(ty.class_desc.base_class_array, ty.class_desc.num_base_classes as usize)?;
let mut margin = [false; 32];
let mut stack = [0u32; 32];
stack[0] = ty.class_desc.num_base_classes;
let mut depth = 0;
let mut s = String::new();
for &base_class_ptr in base_classes {
let base_class = file.deref(base_class_ptr)?;
if base_class.pmd.pdisp != -1 { s += "****: "; }
else { s += &format!("{:04X}: ", base_class.pmd.mdisp); }
let is_last = base_class.num_contained_bases + 1 == stack[depth];
if depth > 0 {
for &is_last in &margin[1..depth] {
s += if is_last { " " } else { "| " };
}
let prefix = if is_last { "`-- " } else { "+-- " };
s += prefix;
}
let base_ty_name = file.deref_c_str(base_class.type_descriptor.offset(8))?.to_str()?;
s += base_ty_name; s += "\n";
stack[depth] -= base_class.num_contained_bases + 1;
if base_class.num_contained_bases != 0 {
margin[depth] = is_last;
depth += 1;
stack[depth] = base_class.num_contained_bases;
}
else {
while depth > 0 && stack[depth] == 0 {
depth -= 1;
}
}
}
print!("{}", s);
Ok(())
}