Version 0.2.2

- Add `ar create` command for static libraries
- Update `elf fixup` to add an "(asm)" suffix
  to object file symbols, for use with progress
  tracking.
This commit is contained in:
Luke Street 2022-12-14 19:56:55 -05:00
parent 21c386d1a6
commit 828766b22b
8 changed files with 165 additions and 16 deletions

8
Cargo.lock generated
View File

@ -28,6 +28,11 @@ version = "1.0.66"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6" checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6"
[[package]]
name = "ar"
version = "0.8.0"
source = "git+https://github.com/bjorn3/rust-ar.git?branch=do_not_remove_cg_clif_ranlib#de9ab0e56bf3a208381d342aa5b60f9ff2891648"
[[package]] [[package]]
name = "argh" name = "argh"
version = "0.1.9" version = "0.1.9"
@ -149,9 +154,10 @@ dependencies = [
[[package]] [[package]]
name = "decomp-toolkit" name = "decomp-toolkit"
version = "0.2.0" version = "0.2.2"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"ar",
"argh", "argh",
"base16ct", "base16ct",
"cwdemangle", "cwdemangle",

View File

@ -3,7 +3,7 @@ name = "decomp-toolkit"
description = "GameCube/Wii decompilation project tools." description = "GameCube/Wii decompilation project tools."
authors = ["Luke Street <luke@street.dev>"] authors = ["Luke Street <luke@street.dev>"]
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
version = "0.2.1" version = "0.2.2"
edition = "2021" edition = "2021"
publish = false publish = false
build = "build.rs" build = "build.rs"
@ -22,6 +22,7 @@ strip = "debuginfo"
[dependencies] [dependencies]
anyhow = "1.0.64" anyhow = "1.0.64"
ar = { git = "https://github.com/bjorn3/rust-ar.git", branch = "do_not_remove_cg_clif_ranlib" }
argh = "0.1.8" argh = "0.1.8"
base16ct = "0.1.1" base16ct = "0.1.1"
cwdemangle = "0.1.3" cwdemangle = "0.1.3"

110
src/cmd/ar.rs Normal file
View File

@ -0,0 +1,110 @@
use std::{
collections::{btree_map::Entry, BTreeMap},
fs::File,
io::{BufRead, BufReader, BufWriter, Write},
path::PathBuf,
};
use anyhow::{Context, Error, Result};
use argh::FromArgs;
use object::{Object, ObjectSymbol};
#[derive(FromArgs, PartialEq, Debug)]
/// Commands for processing static libraries.
#[argh(subcommand, name = "ar")]
pub struct Args {
#[argh(subcommand)]
command: SubCommand,
}
#[derive(FromArgs, PartialEq, Debug)]
#[argh(subcommand)]
enum SubCommand {
Create(CreateArgs),
}
#[derive(FromArgs, PartialEq, Eq, Debug)]
/// Creates a static library.
#[argh(subcommand, name = "create")]
pub struct CreateArgs {
#[argh(positional)]
/// output file
out: PathBuf,
#[argh(positional)]
/// input files
files: Vec<PathBuf>,
}
pub fn run(args: Args) -> Result<()> {
match args.command {
SubCommand::Create(c_args) => create(c_args),
}
}
fn create(args: CreateArgs) -> Result<()> {
// Process response files (starting with '@')
let mut files = Vec::with_capacity(args.files.len());
for path in args.files {
let path_str = path.to_str().ok_or_else(|| {
Error::msg(format!("'{}' is not valid UTF-8", path.to_string_lossy()))
})?;
match path_str.strip_prefix('@') {
Some(rsp_file) => {
let reader = BufReader::new(
File::open(rsp_file)
.with_context(|| format!("Failed to open file '{}'", rsp_file))?,
);
for result in reader.lines() {
let line = result?;
if !line.is_empty() {
files.push(PathBuf::from(line));
}
}
}
None => {
files.push(path);
}
}
}
// Build identifiers & symbol table
let mut identifiers = Vec::with_capacity(files.len());
let mut symbol_table = BTreeMap::new();
for path in &files {
let file_name = path.file_name().ok_or_else(|| {
Error::msg(format!("'{}' is not a file path", path.to_string_lossy()))
})?;
let file_name = file_name.to_str().ok_or_else(|| {
Error::msg(format!("'{}' is not valid UTF-8", file_name.to_string_lossy()))
})?;
let identifier = file_name.as_bytes().to_vec();
identifiers.push(identifier.clone());
let entries = match symbol_table.entry(identifier) {
Entry::Vacant(e) => e.insert(Vec::new()),
Entry::Occupied(_) => {
return Err(Error::msg(format!("Duplicate file name '{file_name}'")))
}
};
let object_file = File::open(path)
.with_context(|| format!("Failed to open object file '{}'", path.to_string_lossy()))?;
let map = unsafe { memmap2::MmapOptions::new().map(&object_file) }
.with_context(|| format!("Failed to mmap object file: '{}'", path.to_string_lossy()))?;
let obj = object::File::parse(map.as_ref())?;
for symbol in obj.symbols() {
if symbol.is_global() {
entries.push(symbol.name_bytes()?.to_vec());
}
}
}
// Write archive
let out = BufWriter::new(File::create(&args.out)?);
let mut builder =
ar::GnuBuilder::new(out, identifiers, ar::GnuSymbolTableFormat::Size32, symbol_table)?;
for path in files {
builder.append_path(path)?;
}
builder.into_inner()?.flush()?;
Ok(())
}

View File

@ -3,6 +3,7 @@ use std::{
fs, fs,
fs::File, fs::File,
io::{BufWriter, Write}, io::{BufWriter, Write},
path::PathBuf,
}; };
use anyhow::{Context, Error, Result}; use anyhow::{Context, Error, Result};
@ -10,7 +11,7 @@ use argh::FromArgs;
use object::{ use object::{
write::{SectionId, SymbolId}, write::{SectionId, SymbolId},
Object, ObjectSection, ObjectSymbol, RelocationKind, RelocationTarget, SectionFlags, Object, ObjectSection, ObjectSymbol, RelocationKind, RelocationTarget, SectionFlags,
SectionIndex, SectionKind, SymbolFlags, SymbolKind, SymbolSection, SectionIndex, SectionKind, SymbolFlags, SymbolKind, SymbolScope, SymbolSection,
}; };
use crate::util::{asm::write_asm, elf::process_elf}; use crate::util::{asm::write_asm, elf::process_elf};
@ -36,10 +37,10 @@ enum SubCommand {
pub struct DisasmArgs { pub struct DisasmArgs {
#[argh(positional)] #[argh(positional)]
/// input file /// input file
elf_file: String, elf_file: PathBuf,
#[argh(positional)] #[argh(positional)]
/// output directory /// output directory
out_dir: String, out_dir: PathBuf,
} }
#[derive(FromArgs, PartialEq, Eq, Debug)] #[derive(FromArgs, PartialEq, Eq, Debug)]
@ -48,10 +49,10 @@ pub struct DisasmArgs {
pub struct FixupArgs { pub struct FixupArgs {
#[argh(positional)] #[argh(positional)]
/// input file /// input file
in_file: String, in_file: PathBuf,
#[argh(positional)] #[argh(positional)]
/// output file /// output file
out_file: String, out_file: PathBuf,
} }
pub fn run(args: Args) -> Result<()> { pub fn run(args: Args) -> Result<()> {
@ -83,6 +84,8 @@ fn file_name_from_unit(str: &str) -> String {
format!("{}.o", str.strip_prefix('/').unwrap_or(&str)) format!("{}.o", str.strip_prefix('/').unwrap_or(&str))
} }
const ASM_SUFFIX: &[u8] = " (asm)".as_bytes();
fn fixup(args: FixupArgs) -> Result<()> { fn fixup(args: FixupArgs) -> Result<()> {
let in_buf = fs::read(&args.in_file).context("Failed to open input file")?; let in_buf = fs::read(&args.in_file).context("Failed to open input file")?;
let in_file = object::read::File::parse(&*in_buf).context("Failed to parse input ELF")?; let in_file = object::read::File::parse(&*in_buf).context("Failed to parse input ELF")?;
@ -90,11 +93,36 @@ fn fixup(args: FixupArgs) -> Result<()> {
object::write::Object::new(in_file.format(), in_file.architecture(), in_file.endianness()); object::write::Object::new(in_file.format(), in_file.architecture(), in_file.endianness());
// Write file symbol(s) first // Write file symbol(s) first
let mut file_symbol_found = false;
for symbol in in_file.symbols() { for symbol in in_file.symbols() {
if symbol.kind() != SymbolKind::File { if symbol.kind() != SymbolKind::File {
continue; continue;
} }
out_file.add_symbol(to_write_symbol(&symbol, &[])?); let mut out_symbol = to_write_symbol(&symbol, &[])?;
out_symbol.name.append(&mut ASM_SUFFIX.to_vec());
out_file.add_symbol(out_symbol);
file_symbol_found = true;
break;
}
if !file_symbol_found {
let file_name = args.in_file.file_name().ok_or_else(|| {
Error::msg(format!("'{}' is not a file path", args.in_file.to_string_lossy()))
})?;
let file_name = file_name.to_str().ok_or_else(|| {
Error::msg(format!("'{}' is not valid UTF-8", file_name.to_string_lossy()))
})?;
let mut name_bytes = file_name.as_bytes().to_vec();
name_bytes.append(&mut ASM_SUFFIX.to_vec());
out_file.add_symbol(object::write::Symbol {
name: name_bytes,
value: 0,
size: 0,
kind: SymbolKind::File,
scope: SymbolScope::Compilation,
weak: false,
section: object::write::SymbolSection::Absolute,
flags: SymbolFlags::None,
});
} }
// Write section symbols & sections // Write section symbols & sections

View File

@ -1,3 +1,4 @@
pub(crate) mod ar;
pub(crate) mod demangle; pub(crate) mod demangle;
pub(crate) mod elf; pub(crate) mod elf;
pub(crate) mod elf2dol; pub(crate) mod elf2dol;

View File

@ -16,6 +16,7 @@ struct TopLevel {
#[derive(FromArgs, PartialEq, Debug)] #[derive(FromArgs, PartialEq, Debug)]
#[argh(subcommand)] #[argh(subcommand)]
enum SubCommand { enum SubCommand {
Ar(cmd::ar::Args),
Demangle(cmd::demangle::Args), Demangle(cmd::demangle::Args),
Elf(cmd::elf::Args), Elf(cmd::elf::Args),
Elf2Dol(cmd::elf2dol::Args), Elf2Dol(cmd::elf2dol::Args),
@ -29,6 +30,7 @@ fn main() {
let args: TopLevel = argh_version::from_env(); let args: TopLevel = argh_version::from_env();
let result = match args.command { let result = match args.command {
SubCommand::Ar(c_args) => cmd::ar::run(c_args),
SubCommand::Demangle(c_args) => cmd::demangle::run(c_args), SubCommand::Demangle(c_args) => cmd::demangle::run(c_args),
SubCommand::Elf(c_args) => cmd::elf::run(c_args), SubCommand::Elf(c_args) => cmd::elf::run(c_args),
SubCommand::Elf2Dol(c_args) => cmd::elf2dol::run(c_args), SubCommand::Elf2Dol(c_args) => cmd::elf2dol::run(c_args),

View File

@ -1,7 +1,6 @@
use std::{ use std::{
cmp::{min, Ordering}, cmp::{min, Ordering},
collections::{btree_map, hash_map::Entry, BTreeMap, HashMap}, collections::{btree_map, hash_map::Entry, BTreeMap, HashMap},
fmt::Display,
fs, fs,
fs::{DirBuilder, File}, fs::{DirBuilder, File},
io::{BufWriter, Write}, io::{BufWriter, Write},
@ -29,7 +28,7 @@ struct SymbolEntry {
kind: SymbolEntryKind, kind: SymbolEntryKind,
} }
pub fn write_asm<P: AsRef<Path> + Display>(path: P, obj: &ObjInfo) -> Result<()> { pub fn write_asm<P: AsRef<Path>>(path: P, obj: &ObjInfo) -> Result<()> {
let mut file_map = HashMap::<String, BufWriter<File>>::new(); let mut file_map = HashMap::<String, BufWriter<File>>::new();
let asm_dir = path.as_ref().join("asm"); let asm_dir = path.as_ref().join("asm");

View File

@ -1,4 +1,4 @@
use std::{collections::BTreeMap, fmt::Display, fs::File, path::Path}; use std::{collections::BTreeMap, fs::File, path::Path};
use anyhow::{Context, Error, Result}; use anyhow::{Context, Error, Result};
use cwdemangle::demangle; use cwdemangle::demangle;
@ -19,11 +19,13 @@ use crate::util::obj::{
ObjSymbolFlagSet, ObjSymbolFlags, ObjSymbolKind, ObjSymbolFlagSet, ObjSymbolFlags, ObjSymbolKind,
}; };
pub fn process_elf<P: AsRef<Path> + Display>(path: P) -> Result<ObjInfo> { pub fn process_elf<P: AsRef<Path>>(path: P) -> Result<ObjInfo> {
let elf_file = let elf_file = File::open(&path).with_context(|| {
File::open(&path).with_context(|| format!("Failed to open ELF file '{path}'"))?; format!("Failed to open ELF file '{}'", path.as_ref().to_string_lossy())
let map = unsafe { MmapOptions::new().map(&elf_file) } })?;
.with_context(|| format!("Failed to mmap ELF file: '{path}'"))?; let map = unsafe { MmapOptions::new().map(&elf_file) }.with_context(|| {
format!("Failed to mmap ELF file: '{}'", path.as_ref().to_string_lossy())
})?;
let obj_file = object::read::File::parse(&*map)?; let obj_file = object::read::File::parse(&*map)?;
let architecture = match obj_file.architecture() { let architecture = match obj_file.architecture() {
Architecture::PowerPc => ObjArchitecture::PowerPc, Architecture::PowerPc => ObjArchitecture::PowerPc,