Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ project structure and build system that uses decomp-toolkit under the hood.
- [dol apply](#dol-apply)
- [dol config](#dol-config)
- [dwarf dump](#dwarf-dump)
- [dwarf types](#dwarf-types)
- [elf disasm](#elf-disasm)
- [elf fixup](#elf-fixup)
- [elf2dol](#elf2dol)
Expand Down Expand Up @@ -301,6 +302,14 @@ $ dtk dwarf dump input.elf
$ dtk dwarf dump input.elf --include-erased
```

### dwarf types

Dumps DWARF 1.1 information from an ELF file in JSON format. (Does **not** support DWARF 2+)

```shell
$ dtk dwarf types input.elf -o types.json
```

### elf disasm

Disassemble an unstripped CodeWarrior ELF file. Attempts to automatically split objects and rebuild relocations
Expand Down
130 changes: 104 additions & 26 deletions src/cmd/dwarf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@ use typed_path::Utf8NativePathBuf;
use crate::{
util::{
dwarf::{
AttributeKind, MemberFunctionMap, TagKind, TypedefMap, parse_producer,
preprocess_cu_tag, print::tag_type_string, process_compile_unit, process_cu_tag,
process_overlay_branch, read_debug_section, should_skip_tag,
AttributeKind, DwarfInfo, MemberFunctionMap, TagKind, TypedefMap,
layout::LayoutCollector, parse_producer, preprocess_cu_tag, print::tag_type_string,
process_compile_unit, process_cu_tag, process_overlay_branch, read_debug_section,
should_skip_tag,
},
file::buf_writer,
path::native_path,
Expand All @@ -42,6 +43,7 @@ pub struct Args {
#[argp(subcommand)]
enum SubCommand {
Dump(DumpArgs),
Types(TypesArgs),
}

#[derive(FromArgs, PartialEq, Eq, Debug)]
Expand All @@ -63,12 +65,80 @@ pub struct DumpArgs {
include_erased: bool,
}

#[derive(FromArgs, PartialEq, Eq, Debug)]
/// Exports struct, union and enum layouts from DWARF 1.1 info as JSON.
/// Types are deduplicated across compile units; definitions that disagree
/// are reported under "conflicts" instead.
#[argp(subcommand, name = "types")]
pub struct TypesArgs {
#[argp(positional, from_str_fn(native_path))]
/// Input object. (ELF or archive)
in_file: Utf8NativePathBuf,
#[argp(option, short = 'o', from_str_fn(native_path))]
/// Output JSON file. (Default: stdout)
out: Option<Utf8NativePathBuf>,
}

pub fn run(args: Args) -> Result<()> {
match args.command {
SubCommand::Dump(c_args) => dump(c_args),
SubCommand::Types(c_args) => types(c_args),
}
}

fn types(args: TypesArgs) -> Result<()> {
let mut collector = LayoutCollector::default();
let mut file = open_file(&args.in_file, true)?;
let buf = file.map()?;
if buf.starts_with(b"!<arch>\n") {
let mut archive = ar::Archive::new(buf);
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_exact(&mut data)?;
let obj_file = object::read::File::parse(&*data)?;
let Some(debug_section) = obj_file.section_by_name(".debug") else {
log::warn!("Object '{}' missing .debug section", name);
continue;
};
let mut info = read_object_dwarf(&obj_file, debug_section, false)?;
collector.add_info(&mut info).with_context(|| format!("Processing '{name}'"))?;
}
} else {
let obj_file = object::read::File::parse(buf)?;
let debug_section = obj_file
.section_by_name(".debug")
.ok_or_else(|| anyhow!("Failed to locate .debug section"))?;
let mut info = read_object_dwarf(&obj_file, debug_section, false)?;
collector.add_info(&mut info)?;
}
if collector.errors > 0 {
log::warn!("{} tags or types could not be processed", collector.errors);
}
let layouts = collector.finish()?;
log::info!(
"{} structs, {} unions, {} enums, {} conflicts",
layouts.structs.len(),
layouts.unions.len(),
layouts.enums.len(),
layouts.conflicts.len()
);
if let Some(out_path) = &args.out {
let mut w = buf_writer(out_path)?;
serde_json::to_writer_pretty(&mut w, &layouts)?;
w.flush()?;
} else {
let mut w = stdout().lock();
serde_json::to_writer_pretty(&mut w, &layouts)?;
writeln!(w)?;
}
Ok(())
}

fn dump(args: DumpArgs) -> Result<()> {
// Load syntect
let theme_set: ThemeSet =
Expand Down Expand Up @@ -147,29 +217,7 @@ fn dump_debug_section<W>(
where
W: Write + ?Sized,
{
let mut data = debug_section.uncompressed_data()?.into_owned();

// Apply relocations to data
for (addr, reloc) in debug_section.relocations() {
match reloc.flags() {
RelocationFlags::Elf { r_type: elf::R_PPC_ADDR32 | 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());
}
RelocationFlags::Elf { r_type: elf::R_PPC_NONE } => {}
_ => bail!("Unhandled .debug relocation type {:?}", reloc.kind()),
}
}

let mut reader = Cursor::new(&*data);
let mut info =
read_debug_section(&mut reader, obj_file.endianness().into(), args.include_erased)?;
let mut info = read_object_dwarf(obj_file, debug_section, args.include_erased)?;

for (&addr, tag) in &info.tags {
log::debug!("{}: {:?}", addr, tag);
Expand Down Expand Up @@ -328,6 +376,36 @@ where
Ok(())
}

/// Reads the `.debug` section of an object, applying its relocations.
fn read_object_dwarf(
obj_file: &object::File<'_>,
debug_section: Section,
include_erased: bool,
) -> Result<DwarfInfo> {
let mut data = debug_section.uncompressed_data()?.into_owned();

// Apply relocations to data
for (addr, reloc) in debug_section.relocations() {
match reloc.flags() {
RelocationFlags::Elf { r_type: elf::R_PPC_ADDR32 | 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());
}
RelocationFlags::Elf { r_type: elf::R_PPC_NONE } => {}
_ => bail!("Unhandled .debug relocation type {:?}", reloc.kind()),
}
}

let mut reader = Cursor::new(&*data);
read_debug_section(&mut reader, obj_file.endianness().into(), include_erased)
}

struct HighlightWriter<'a> {
line: String,
highlighter: Highlighter<'a>,
Expand Down
Loading