diff --git a/src/cmd/dwarf.rs b/src/cmd/dwarf.rs new file mode 100644 index 0000000..32b22b2 --- /dev/null +++ b/src/cmd/dwarf.rs @@ -0,0 +1,297 @@ +use std::{ + collections::{btree_map, BTreeMap}, + fs::File, + io::{stdout, BufWriter, Cursor, Read, Write}, + path::PathBuf, +}; + +use anyhow::{anyhow, bail, Result}; +use argh::FromArgs; +use object::{elf, Object, ObjectSection, ObjectSymbol, RelocationKind, RelocationTarget, Section}; + +use crate::util::{ + dwarf::{ + process_address, process_offset, process_type, process_variable_location, + read_debug_section, type_string, ud_type, ud_type_def, ud_type_string, AttributeKind, + TagKind, TypeKind, + }, + file::map_file, +}; + +#[derive(FromArgs, PartialEq, Debug)] +/// process DWARF 1.1 information +#[argh(subcommand, name = "dwarf")] +pub struct Args { + #[argh(subcommand)] + command: SubCommand, +} + +#[derive(FromArgs, PartialEq, Debug)] +#[argh(subcommand)] +enum SubCommand { + Dump(DumpArgs), +} + +#[derive(FromArgs, PartialEq, Eq, Debug)] +/// dumps DWARF 1.1 info from an object or archive +#[argh(subcommand, name = "dump")] +pub struct DumpArgs { + #[argh(positional)] + /// input object (ELF or archive) + in_file: PathBuf, + #[argh(option, short = 'o')] + /// output file (or directory, for archive) + out: Option, +} + +pub fn run(args: Args) -> Result<()> { + match args.command { + SubCommand::Dump(c_args) => dump(c_args), + } +} + +fn dump(args: DumpArgs) -> Result<()> { + let mmap = map_file(&args.in_file)?; + if mmap.starts_with(b"!\n") { + let mut archive = ar::Archive::new(&*mmap); + while let Some(result) = archive.next_entry() { + let mut e = match result { + Ok(e) => e, + Err(e) => bail!("Failed to read archive entry: {:?}", e), + }; + let name = String::from_utf8_lossy(e.header().identifier()).to_string(); + let mut data = vec![0u8; e.header().size() as usize]; + e.read(&mut data)?; + let obj_file = object::read::File::parse(&*data)?; + let debug_section = match obj_file.section_by_name(".debug") { + Some(section) => { + log::info!("Processing '{}'", name); + section + }, + None => { + log::warn!("Object '{}' missing .debug section", name); + continue; + } + }; + if let Some(out_path) = &args.out { + // TODO make a basename method + let name = name.trim_start_matches("D:").replace('\\', "/"); + let name = name.rsplit_once('/').map(|(a, b)| b).unwrap_or(&name); + let file_path = out_path.join(format!("{}.txt", name)); + let mut file = BufWriter::new(File::create(file_path)?); + dump_debug_section(&mut file, &obj_file, debug_section)?; + file.flush()?; + } else { + println!("\nFile {}:", name); + dump_debug_section(&mut stdout(), &obj_file, debug_section)?; + } + } + } else { + let obj_file = object::read::File::parse(&*mmap)?; + let debug_section = obj_file + .section_by_name(".debug") + .ok_or_else(|| anyhow!("Failed to locate .debug section"))?; + if let Some(out_path) = &args.out { + let mut file = BufWriter::new(File::create(out_path)?); + dump_debug_section(&mut file, &obj_file, debug_section)?; + file.flush()?; + } else { + dump_debug_section(&mut stdout(), &obj_file, debug_section)?; + } + } + Ok(()) +} + +fn dump_debug_section( + w: &mut W, + obj_file: &object::File<'_>, + debug_section: Section, +) -> Result<()> { + let mut data = debug_section.uncompressed_data()?.into_owned(); + + // Apply relocations to data + for (addr, reloc) in debug_section.relocations() { + match reloc.kind() { + RelocationKind::Absolute | RelocationKind::Elf(elf::R_PPC_UADDR32) => { + let target = match reloc.target() { + RelocationTarget::Symbol(symbol_idx) => { + let symbol = obj_file.symbol_by_index(symbol_idx)?; + (symbol.address() as i64 + reloc.addend()) as u32 + } + _ => bail!("Invalid .debug relocation target"), + }; + data[addr as usize..addr as usize + 4].copy_from_slice(&target.to_be_bytes()); + } + RelocationKind::Elf(elf::R_PPC_NONE) => {} + _ => bail!("Unhandled .debug relocation type {:?}", reloc.kind()), + } + } + + let mut reader = Cursor::new(&*data); + let tags = read_debug_section(&mut reader)?; + + for (&addr, tag) in &tags { + log::debug!("{}: {:?}", addr, tag); + } + + let mut units = Vec::::new(); + if let Some((_, mut tag)) = tags.first_key_value() { + loop { + match tag.kind { + TagKind::CompileUnit => { + let unit = tag + .string_attribute(AttributeKind::Name) + .ok_or_else(|| anyhow!("CompileUnit without name {:?}", tag))?; + if units.contains(unit) { + log::warn!("Duplicate unit '{}'", unit); + } else { + units.push(unit.clone()); + } + + let children = tag.children(&tags); + let mut typedefs = BTreeMap::>::new(); + for child in children { + match child.kind { + TagKind::GlobalSubroutine | TagKind::Subroutine => { + let _is_prototyped = + child.string_attribute(AttributeKind::Prototyped).is_some(); + if let (Some(_hi), Some(_lo)) = ( + child.address_attribute(AttributeKind::HighPc), + child.address_attribute(AttributeKind::LowPc), + ) {} + let name = child + .string_attribute(AttributeKind::Name) + .ok_or_else(|| anyhow!("Subroutine without name"))?; + let udt = ud_type(&tags, child)?; + let ts = ud_type_string(&tags, &typedefs, &udt)?; + writeln!(w, "{} {}{} {{", ts.prefix, name, ts.suffix)?; + for tag in child.children(&tags) { + match tag.kind { + TagKind::LocalVariable => {} + _ => continue, + } + let address = if let Some(location) = + tag.block_attribute(AttributeKind::Location) + { + process_variable_location(location)? + } else { + "[unknown]".to_string() + }; + let type_attr = tag.type_attribute().ok_or_else(|| { + anyhow!("LocalVariable without type attr") + })?; + let var_type = process_type(type_attr)?; + let ts = type_string(&tags, &typedefs, &var_type)?; + let name = tag + .string_attribute(AttributeKind::Name) + .ok_or_else(|| anyhow!("LocalVariable without name"))?; + writeln!( + w, + "\t{} {}{}; // {}", + ts.prefix, name, ts.suffix, address + )?; + } + writeln!(w, "}}")?; + } + TagKind::Typedef => { + let name = child + .string_attribute(AttributeKind::Name) + .ok_or_else(|| anyhow!("Typedef without name"))?; + let attr = child + .type_attribute() + .ok_or_else(|| anyhow!("Typedef without type attribute"))?; + let t = process_type(attr)?; + let ts = type_string(&tags, &typedefs, &t)?; + writeln!(w, "typedef {} {}{};", ts.prefix, name, ts.suffix)?; + + // TODO fundamental typedefs? + if let Some(ud_type_ref) = + child.reference_attribute(AttributeKind::UserDefType) + { + match typedefs.entry(ud_type_ref) { + btree_map::Entry::Vacant(e) => { + e.insert(vec![child.key]); + } + btree_map::Entry::Occupied(e) => { + e.into_mut().push(child.key); + } + } + } + } + TagKind::GlobalVariable | TagKind::LocalVariable => { + let name = child + .string_attribute(AttributeKind::Name) + .ok_or_else(|| anyhow!("Variable without name"))?; + let address = if let Some(location) = + child.block_attribute(AttributeKind::Location) + { + Some(process_address(location)?) + } else { + None + }; + if let Some(type_attr) = child.type_attribute() { + let var_type = process_type(type_attr)?; + // log::info!("{:?}", var_type); + // if let TypeKind::UserDefined(key) = var_type.kind { + // let ud_tag = tags + // .get(&key) + // .ok_or_else(|| anyhow!("Invalid UD type ref"))?; + // let ud_type = ud_type(&tags, ud_tag)?; + // log::info!("{:?}", ud_type); + // } + let ts = type_string(&tags, &typedefs, &var_type)?; + let st = if child.kind == TagKind::LocalVariable { + "static " + } else { + "" + }; + let address_str = match address { + Some(addr) => format!(" : {:#010X}", addr), + None => String::new(), + }; + let size = var_type.size(&tags)?; + writeln!( + w, + "{}{} {}{}{}; // size: {:#X}", + st, ts.prefix, name, ts.suffix, address_str, size, + )?; + } + } + TagKind::StructureType + | TagKind::ArrayType + | TagKind::EnumerationType + | TagKind::UnionType + | TagKind::ClassType + | TagKind::SubroutineType => { + let udt = ud_type(&tags, child)?; + if child.string_attribute(AttributeKind::Name).is_some() { + writeln!(w, "{}", ud_type_def(&tags, &typedefs, &udt)?)?; + } else { + // log::warn!("No name for tag: {:?}", child); + } + } + _ => { + log::warn!("Unhandled CompileUnit child {:?}", child.kind); + } + } + } + // println!("Children: {:?}", children.iter().map(|c| c.kind).collect::>()); + } + _ => { + log::warn!("Expected CompileUnit, got {:?}", tag.kind); + break; + } + } + if let Some(next) = tag.next_sibling(&tags) { + tag = next; + } else { + break; + } + } + } + // log::info!("Link order:"); + // for x in units { + // log::info!("{}", x); + // } + Ok(()) +} diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs index cd0b7cf..40fe441 100644 --- a/src/cmd/mod.rs +++ b/src/cmd/mod.rs @@ -1,6 +1,7 @@ pub mod ar; pub mod demangle; pub mod dol; +pub mod dwarf; pub mod elf; pub mod elf2dol; pub mod map; diff --git a/src/main.rs b/src/main.rs index df30f8a..280618d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,6 +19,7 @@ enum SubCommand { Ar(cmd::ar::Args), Demangle(cmd::demangle::Args), Dol(cmd::dol::Args), + Dwarf(cmd::dwarf::Args), Elf(cmd::elf::Args), Elf2Dol(cmd::elf2dol::Args), Map(cmd::map::Args), @@ -36,6 +37,7 @@ fn main() { SubCommand::Ar(c_args) => cmd::ar::run(c_args), SubCommand::Demangle(c_args) => cmd::demangle::run(c_args), SubCommand::Dol(c_args) => cmd::dol::run(c_args), + SubCommand::Dwarf(c_args) => cmd::dwarf::run(c_args), SubCommand::Elf(c_args) => cmd::elf::run(c_args), SubCommand::Elf2Dol(c_args) => cmd::elf2dol::run(c_args), SubCommand::Map(c_args) => cmd::map::run(c_args), diff --git a/src/util/dwarf.rs b/src/util/dwarf.rs index 3b9f716..cadaefd 100644 --- a/src/util/dwarf.rs +++ b/src/util/dwarf.rs @@ -777,13 +777,14 @@ pub fn struct_def_string( Some(name) => format!("struct {} {{\n", name), None => "struct {\n".to_string(), }; + writeln!(out, "\t// total size: {:#X}", t.byte_size)?; for member in &t.members { let ts = type_string(tags, typedefs, &member.kind)?; write!(out, "\t{} {}{}", ts.prefix, member.name, ts.suffix)?; if let Some(bit) = &member.bit { write!(out, " : {}", bit.bit_size)?; } - write!(out, ";\n")?; + write!(out, "; // offset {:#X}, size {:#X}\n", member.offset, member.kind.size(tags)?)?; } write!(out, "}}")?; Ok(out) @@ -844,7 +845,7 @@ pub fn fund_type_string(ft: FundType) -> Result<&'static str> { }) } -fn process_offset(block: &[u8]) -> Result { +pub fn process_offset(block: &[u8]) -> Result { if block.len() == 6 && block[0] == LocationOp::Const as u8 && block[5] == LocationOp::Add as u8 { Ok(u32::from_be_bytes(block[1..5].try_into()?)) @@ -861,6 +862,17 @@ pub fn process_address(block: &[u8]) -> Result { } } +pub fn process_variable_location(block: &[u8]) -> Result { + // TODO: float regs + if block.len() == 5 && block[0] == LocationOp::Register as u8 { + Ok(format!("r{}", u32::from_be_bytes(block[1..].try_into()?))) + } else if block.len() == 11 && block[0] == LocationOp::BaseRegister as u8 && block[5] == LocationOp::Const as u8 && block[10] == LocationOp::Add as u8 { + Ok(format!("r{}+{:#X}", u32::from_be_bytes(block[1..5].try_into()?), u32::from_be_bytes(block[6..10].try_into()?))) + } else { + Err(anyhow!("Unhandled location data {:?}, expected variable loc", block)) + } +} + pub fn ud_type(tags: &TagMap, tag: &Tag) -> Result { match tag.kind { TagKind::ArrayType => { diff --git a/src/util/elf.rs b/src/util/elf.rs index ba3873b..fe2d508 100644 --- a/src/util/elf.rs +++ b/src/util/elf.rs @@ -44,8 +44,6 @@ enum BoundaryState { FilesEnded, } -const ENABLE_DWARF: bool = false; - pub fn process_elf>(path: P) -> Result { let mmap = map_file(path)?; let obj_file = object::read::File::parse(&*mmap)?; @@ -60,14 +58,6 @@ pub fn process_elf>(path: P) -> Result { kind => bail!("Unexpected ELF type: {kind:?}"), }; - if ENABLE_DWARF { - if let Some(debug_section) = obj_file.section_by_name(".debug") { - if debug_section.size() > 0 { - load_debug_section(&obj_file, debug_section)?; - } - } - } - let mut obj_name = String::new(); let mut stack_address: Option = None; let mut stack_end: Option = None; @@ -719,175 +709,3 @@ fn to_obj_reloc( let reloc_data = ObjReloc { kind: reloc_kind, address, target_symbol, addend }; Ok(reloc_data) } - -fn load_debug_section(obj_file: &object::File<'_>, debug_section: Section) -> Result<()> { - let mut data = debug_section.uncompressed_data()?.into_owned(); - - // Apply relocations to data - for (addr, reloc) in debug_section.relocations() { - match reloc.kind() { - RelocationKind::Absolute | RelocationKind::Elf(elf::R_PPC_UADDR32) => { - let target = match reloc.target() { - RelocationTarget::Symbol(symbol_idx) => { - let symbol = obj_file.symbol_by_index(symbol_idx)?; - (symbol.address() as i64 + reloc.addend()) as u32 - } - // RelocationTarget::Section(section_idx) => { - // let section = obj_file.section_by_index(section_idx)?; - // (section.address() as i64 + reloc.addend()) as u32 - // } - // RelocationTarget::Absolute => reloc.addend() as u32, - _ => bail!("Invalid .debug relocation target"), - }; - data[addr as usize..addr as usize + 4].copy_from_slice(&target.to_be_bytes()); - } - RelocationKind::Elf(elf::R_PPC_NONE) => {} - _ => bail!("Unhandled .debug relocation type {:?}", reloc.kind()), - } - } - - let mut reader = Cursor::new(&*data); - let tags = read_debug_section(&mut reader)?; - - // let mut w = BufWriter::new(File::create("dwarfdump2.txt")?); - // for (&addr, tag) in &tags { - // writeln!(w, "{}: {:?}", addr, tag)?; - // } - // w.flush()?; - - let mut units = Vec::::new(); - if let Some((_, mut tag)) = tags.first_key_value() { - loop { - match tag.kind { - TagKind::CompileUnit => { - let unit = tag - .string_attribute(AttributeKind::Name) - .ok_or_else(|| anyhow!("CompileUnit without name {:?}", tag))?; - if units.contains(unit) { - log::warn!("Duplicate unit '{}'", unit); - } else { - units.push(unit.clone()); - } - - let children = tag.children(&tags); - let mut typedefs = BTreeMap::>::new(); - for child in children { - match child.kind { - TagKind::GlobalSubroutine | TagKind::Subroutine => { - let _is_prototyped = - child.string_attribute(AttributeKind::Prototyped).is_some(); - if let (Some(_hi), Some(_lo)) = ( - child.address_attribute(AttributeKind::HighPc), - child.address_attribute(AttributeKind::LowPc), - ) {} - let name = child - .string_attribute(AttributeKind::Name) - .ok_or_else(|| anyhow!("Subroutine without name"))?; - let udt = ud_type(&tags, child)?; - let ts = ud_type_string(&tags, &typedefs, &udt)?; - // log::info!("{} {}{};", ts.prefix, name, ts.suffix); - } - TagKind::Typedef => { - let name = child - .string_attribute(AttributeKind::Name) - .ok_or_else(|| anyhow!("Typedef without name"))?; - let attr = child - .type_attribute() - .ok_or_else(|| anyhow!("Typedef without type attribute"))?; - let t = process_type(attr)?; - let ts = type_string(&tags, &typedefs, &t)?; - // log::info!("typedef {} {}{};", ts.prefix, name, ts.suffix); - - // TODO fundamental typedefs? - if let Some(ud_type_ref) = - child.reference_attribute(AttributeKind::UserDefType) - { - match typedefs.entry(ud_type_ref) { - Entry::Vacant(e) => { - e.insert(vec![child.key]); - } - Entry::Occupied(e) => { - e.into_mut().push(child.key); - } - } - } - } - TagKind::GlobalVariable | TagKind::LocalVariable => { - let name = child - .string_attribute(AttributeKind::Name) - .ok_or_else(|| anyhow!("Variable without name"))?; - let address = if let Some(location) = - child.block_attribute(AttributeKind::Location) - { - Some(process_address(location)?) - } else { - None - }; - if let Some(type_attr) = child.type_attribute() { - let var_type = process_type(type_attr)?; - // log::info!("{:?}", var_type); - if let TypeKind::UserDefined(key) = var_type.kind { - let ud_tag = tags - .get(&key) - .ok_or_else(|| anyhow!("Invalid UD type ref"))?; - let ud_type = ud_type(&tags, ud_tag)?; - // log::info!("{:?}", ud_type); - } - let ts = type_string(&tags, &typedefs, &var_type)?; - let st = if child.kind == TagKind::LocalVariable { - "static " - } else { - "" - }; - let address_str = match address { - Some(addr) => format!(" : {:#010X}", addr), - None => String::new(), - }; - let size = var_type.size(&tags)?; - log::info!( - "{}{} {}{}{}; // size: {:#X}", - st, - ts.prefix, - name, - ts.suffix, - address_str, - size, - ); - } - } - TagKind::StructureType - | TagKind::ArrayType - | TagKind::EnumerationType - | TagKind::UnionType - | TagKind::ClassType - | TagKind::SubroutineType => { - let udt = ud_type(&tags, child)?; - if child.string_attribute(AttributeKind::Name).is_some() { - // log::info!("{}", ud_type_def(&tags, &typedefs, &udt)?); - } - } - _ => { - log::warn!("Unhandled CompileUnit child {:?}", child.kind); - } - } - } - // println!("Children: {:?}", children.iter().map(|c| c.kind).collect::>()); - } - _ => { - log::warn!("Expected CompileUnit, got {:?}", tag.kind); - break; - } - } - if let Some(next) = tag.next_sibling(&tags) { - tag = next; - } else { - break; - } - } - } - // log::info!("Link order:"); - // for x in units { - // log::info!("{}", x); - // } - Ok(()) -}