Skip to content

Commit

Permalink
Add dwarf dump cmd
Browse files Browse the repository at this point in the history
  • Loading branch information
encounter committed Feb 15, 2023
1 parent 36e609e commit f1b4afa
Show file tree
Hide file tree
Showing 5 changed files with 314 additions and 184 deletions.
297 changes: 297 additions & 0 deletions src/cmd/dwarf.rs
Original file line number Diff line number Diff line change
@@ -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<PathBuf>,
}

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"!<arch>\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: Write>(
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::<String>::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::<u32, Vec<u32>>::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::<Vec<TagKind>>());
}
_ => {
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(())
}
1 change: 1 addition & 0 deletions src/cmd/mod.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
2 changes: 2 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand All @@ -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),
Expand Down
16 changes: 14 additions & 2 deletions src/util/dwarf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -844,7 +845,7 @@ pub fn fund_type_string(ft: FundType) -> Result<&'static str> {
})
}

fn process_offset(block: &[u8]) -> Result<u32> {
pub fn process_offset(block: &[u8]) -> Result<u32> {
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()?))
Expand All @@ -861,6 +862,17 @@ pub fn process_address(block: &[u8]) -> Result<u32> {
}
}

pub fn process_variable_location(block: &[u8]) -> Result<String> {
// 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<UserDefinedType> {
match tag.kind {
TagKind::ArrayType => {
Expand Down
Loading

0 comments on commit f1b4afa

Please sign in to comment.