Skip to content

Commit 828766b

Browse files
committed
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.
1 parent 21c386d commit 828766b

File tree

8 files changed

+165
-16
lines changed

8 files changed

+165
-16
lines changed

Cargo.lock

Lines changed: 7 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ name = "decomp-toolkit"
33
description = "GameCube/Wii decompilation project tools."
44
authors = ["Luke Street <[email protected]>"]
55
license = "MIT OR Apache-2.0"
6-
version = "0.2.1"
6+
version = "0.2.2"
77
edition = "2021"
88
publish = false
99
build = "build.rs"
@@ -22,6 +22,7 @@ strip = "debuginfo"
2222

2323
[dependencies]
2424
anyhow = "1.0.64"
25+
ar = { git = "https://github.com/bjorn3/rust-ar.git", branch = "do_not_remove_cg_clif_ranlib" }
2526
argh = "0.1.8"
2627
base16ct = "0.1.1"
2728
cwdemangle = "0.1.3"

src/cmd/ar.rs

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
use std::{
2+
collections::{btree_map::Entry, BTreeMap},
3+
fs::File,
4+
io::{BufRead, BufReader, BufWriter, Write},
5+
path::PathBuf,
6+
};
7+
8+
use anyhow::{Context, Error, Result};
9+
use argh::FromArgs;
10+
use object::{Object, ObjectSymbol};
11+
12+
#[derive(FromArgs, PartialEq, Debug)]
13+
/// Commands for processing static libraries.
14+
#[argh(subcommand, name = "ar")]
15+
pub struct Args {
16+
#[argh(subcommand)]
17+
command: SubCommand,
18+
}
19+
20+
#[derive(FromArgs, PartialEq, Debug)]
21+
#[argh(subcommand)]
22+
enum SubCommand {
23+
Create(CreateArgs),
24+
}
25+
26+
#[derive(FromArgs, PartialEq, Eq, Debug)]
27+
/// Creates a static library.
28+
#[argh(subcommand, name = "create")]
29+
pub struct CreateArgs {
30+
#[argh(positional)]
31+
/// output file
32+
out: PathBuf,
33+
#[argh(positional)]
34+
/// input files
35+
files: Vec<PathBuf>,
36+
}
37+
38+
pub fn run(args: Args) -> Result<()> {
39+
match args.command {
40+
SubCommand::Create(c_args) => create(c_args),
41+
}
42+
}
43+
44+
fn create(args: CreateArgs) -> Result<()> {
45+
// Process response files (starting with '@')
46+
let mut files = Vec::with_capacity(args.files.len());
47+
for path in args.files {
48+
let path_str = path.to_str().ok_or_else(|| {
49+
Error::msg(format!("'{}' is not valid UTF-8", path.to_string_lossy()))
50+
})?;
51+
match path_str.strip_prefix('@') {
52+
Some(rsp_file) => {
53+
let reader = BufReader::new(
54+
File::open(rsp_file)
55+
.with_context(|| format!("Failed to open file '{}'", rsp_file))?,
56+
);
57+
for result in reader.lines() {
58+
let line = result?;
59+
if !line.is_empty() {
60+
files.push(PathBuf::from(line));
61+
}
62+
}
63+
}
64+
None => {
65+
files.push(path);
66+
}
67+
}
68+
}
69+
70+
// Build identifiers & symbol table
71+
let mut identifiers = Vec::with_capacity(files.len());
72+
let mut symbol_table = BTreeMap::new();
73+
for path in &files {
74+
let file_name = path.file_name().ok_or_else(|| {
75+
Error::msg(format!("'{}' is not a file path", path.to_string_lossy()))
76+
})?;
77+
let file_name = file_name.to_str().ok_or_else(|| {
78+
Error::msg(format!("'{}' is not valid UTF-8", file_name.to_string_lossy()))
79+
})?;
80+
let identifier = file_name.as_bytes().to_vec();
81+
identifiers.push(identifier.clone());
82+
83+
let entries = match symbol_table.entry(identifier) {
84+
Entry::Vacant(e) => e.insert(Vec::new()),
85+
Entry::Occupied(_) => {
86+
return Err(Error::msg(format!("Duplicate file name '{file_name}'")))
87+
}
88+
};
89+
let object_file = File::open(path)
90+
.with_context(|| format!("Failed to open object file '{}'", path.to_string_lossy()))?;
91+
let map = unsafe { memmap2::MmapOptions::new().map(&object_file) }
92+
.with_context(|| format!("Failed to mmap object file: '{}'", path.to_string_lossy()))?;
93+
let obj = object::File::parse(map.as_ref())?;
94+
for symbol in obj.symbols() {
95+
if symbol.is_global() {
96+
entries.push(symbol.name_bytes()?.to_vec());
97+
}
98+
}
99+
}
100+
101+
// Write archive
102+
let out = BufWriter::new(File::create(&args.out)?);
103+
let mut builder =
104+
ar::GnuBuilder::new(out, identifiers, ar::GnuSymbolTableFormat::Size32, symbol_table)?;
105+
for path in files {
106+
builder.append_path(path)?;
107+
}
108+
builder.into_inner()?.flush()?;
109+
Ok(())
110+
}

src/cmd/elf.rs

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,15 @@ use std::{
33
fs,
44
fs::File,
55
io::{BufWriter, Write},
6+
path::PathBuf,
67
};
78

89
use anyhow::{Context, Error, Result};
910
use argh::FromArgs;
1011
use object::{
1112
write::{SectionId, SymbolId},
1213
Object, ObjectSection, ObjectSymbol, RelocationKind, RelocationTarget, SectionFlags,
13-
SectionIndex, SectionKind, SymbolFlags, SymbolKind, SymbolSection,
14+
SectionIndex, SectionKind, SymbolFlags, SymbolKind, SymbolScope, SymbolSection,
1415
};
1516

1617
use crate::util::{asm::write_asm, elf::process_elf};
@@ -36,10 +37,10 @@ enum SubCommand {
3637
pub struct DisasmArgs {
3738
#[argh(positional)]
3839
/// input file
39-
elf_file: String,
40+
elf_file: PathBuf,
4041
#[argh(positional)]
4142
/// output directory
42-
out_dir: String,
43+
out_dir: PathBuf,
4344
}
4445

4546
#[derive(FromArgs, PartialEq, Eq, Debug)]
@@ -48,10 +49,10 @@ pub struct DisasmArgs {
4849
pub struct FixupArgs {
4950
#[argh(positional)]
5051
/// input file
51-
in_file: String,
52+
in_file: PathBuf,
5253
#[argh(positional)]
5354
/// output file
54-
out_file: String,
55+
out_file: PathBuf,
5556
}
5657

5758
pub fn run(args: Args) -> Result<()> {
@@ -83,18 +84,45 @@ fn file_name_from_unit(str: &str) -> String {
8384
format!("{}.o", str.strip_prefix('/').unwrap_or(&str))
8485
}
8586

87+
const ASM_SUFFIX: &[u8] = " (asm)".as_bytes();
88+
8689
fn fixup(args: FixupArgs) -> Result<()> {
8790
let in_buf = fs::read(&args.in_file).context("Failed to open input file")?;
8891
let in_file = object::read::File::parse(&*in_buf).context("Failed to parse input ELF")?;
8992
let mut out_file =
9093
object::write::Object::new(in_file.format(), in_file.architecture(), in_file.endianness());
9194

9295
// Write file symbol(s) first
96+
let mut file_symbol_found = false;
9397
for symbol in in_file.symbols() {
9498
if symbol.kind() != SymbolKind::File {
9599
continue;
96100
}
97-
out_file.add_symbol(to_write_symbol(&symbol, &[])?);
101+
let mut out_symbol = to_write_symbol(&symbol, &[])?;
102+
out_symbol.name.append(&mut ASM_SUFFIX.to_vec());
103+
out_file.add_symbol(out_symbol);
104+
file_symbol_found = true;
105+
break;
106+
}
107+
if !file_symbol_found {
108+
let file_name = args.in_file.file_name().ok_or_else(|| {
109+
Error::msg(format!("'{}' is not a file path", args.in_file.to_string_lossy()))
110+
})?;
111+
let file_name = file_name.to_str().ok_or_else(|| {
112+
Error::msg(format!("'{}' is not valid UTF-8", file_name.to_string_lossy()))
113+
})?;
114+
let mut name_bytes = file_name.as_bytes().to_vec();
115+
name_bytes.append(&mut ASM_SUFFIX.to_vec());
116+
out_file.add_symbol(object::write::Symbol {
117+
name: name_bytes,
118+
value: 0,
119+
size: 0,
120+
kind: SymbolKind::File,
121+
scope: SymbolScope::Compilation,
122+
weak: false,
123+
section: object::write::SymbolSection::Absolute,
124+
flags: SymbolFlags::None,
125+
});
98126
}
99127

100128
// Write section symbols & sections

src/cmd/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
pub(crate) mod ar;
12
pub(crate) mod demangle;
23
pub(crate) mod elf;
34
pub(crate) mod elf2dol;

src/main.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ struct TopLevel {
1616
#[derive(FromArgs, PartialEq, Debug)]
1717
#[argh(subcommand)]
1818
enum SubCommand {
19+
Ar(cmd::ar::Args),
1920
Demangle(cmd::demangle::Args),
2021
Elf(cmd::elf::Args),
2122
Elf2Dol(cmd::elf2dol::Args),
@@ -29,6 +30,7 @@ fn main() {
2930

3031
let args: TopLevel = argh_version::from_env();
3132
let result = match args.command {
33+
SubCommand::Ar(c_args) => cmd::ar::run(c_args),
3234
SubCommand::Demangle(c_args) => cmd::demangle::run(c_args),
3335
SubCommand::Elf(c_args) => cmd::elf::run(c_args),
3436
SubCommand::Elf2Dol(c_args) => cmd::elf2dol::run(c_args),

src/util/asm.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
use std::{
22
cmp::{min, Ordering},
33
collections::{btree_map, hash_map::Entry, BTreeMap, HashMap},
4-
fmt::Display,
54
fs,
65
fs::{DirBuilder, File},
76
io::{BufWriter, Write},
@@ -29,7 +28,7 @@ struct SymbolEntry {
2928
kind: SymbolEntryKind,
3029
}
3130

32-
pub fn write_asm<P: AsRef<Path> + Display>(path: P, obj: &ObjInfo) -> Result<()> {
31+
pub fn write_asm<P: AsRef<Path>>(path: P, obj: &ObjInfo) -> Result<()> {
3332
let mut file_map = HashMap::<String, BufWriter<File>>::new();
3433

3534
let asm_dir = path.as_ref().join("asm");

src/util/elf.rs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::{collections::BTreeMap, fmt::Display, fs::File, path::Path};
1+
use std::{collections::BTreeMap, fs::File, path::Path};
22

33
use anyhow::{Context, Error, Result};
44
use cwdemangle::demangle;
@@ -19,11 +19,13 @@ use crate::util::obj::{
1919
ObjSymbolFlagSet, ObjSymbolFlags, ObjSymbolKind,
2020
};
2121

22-
pub fn process_elf<P: AsRef<Path> + Display>(path: P) -> Result<ObjInfo> {
23-
let elf_file =
24-
File::open(&path).with_context(|| format!("Failed to open ELF file '{path}'"))?;
25-
let map = unsafe { MmapOptions::new().map(&elf_file) }
26-
.with_context(|| format!("Failed to mmap ELF file: '{path}'"))?;
22+
pub fn process_elf<P: AsRef<Path>>(path: P) -> Result<ObjInfo> {
23+
let elf_file = File::open(&path).with_context(|| {
24+
format!("Failed to open ELF file '{}'", path.as_ref().to_string_lossy())
25+
})?;
26+
let map = unsafe { MmapOptions::new().map(&elf_file) }.with_context(|| {
27+
format!("Failed to mmap ELF file: '{}'", path.as_ref().to_string_lossy())
28+
})?;
2729
let obj_file = object::read::File::parse(&*map)?;
2830
let architecture = match obj_file.architecture() {
2931
Architecture::PowerPc => ObjArchitecture::PowerPc,

0 commit comments

Comments
 (0)