Compare commits

...

3 Commits

Author SHA1 Message Date
Luke Street 1614997108 ci: Fix linux-i686 artifact name (previously linux-x86) 2024-01-13 22:02:52 -07:00
Luke Street 968f50ebed Add `auto_force_active` option (disable to support `-code_merging`)
Fixes #13
2024-01-13 21:54:10 -07:00
Benjamin Moir 5a3256b2b6
Infer anonymous unions from type layout (#26)
* Infer anonymous unions from type layout

* Add comments to signify inferred types

* Improve union detection

* Fix some output weirdness

* Handle some more anonymous union edge cases

* Change union offset validation

* Skip anonymous unions with less than 2 members.
2024-01-13 21:42:39 -07:00
8 changed files with 172 additions and 11 deletions

View File

@ -88,7 +88,7 @@ jobs:
build: zigbuild
- platform: ubuntu-latest
target: i686-unknown-linux-musl
name: linux-x86
name: linux-i686
build: zigbuild
- platform: ubuntu-latest
target: aarch64-unknown-linux-musl

2
Cargo.lock generated
View File

@ -295,7 +295,7 @@ dependencies = [
[[package]]
name = "decomp-toolkit"
version = "0.7.0"
version = "0.7.1"
dependencies = [
"anyhow",
"ar",

View File

@ -3,7 +3,7 @@ name = "decomp-toolkit"
description = "Yet another GameCube/Wii decompilation toolkit."
authors = ["Luke Street <luke@street.dev>"]
license = "MIT OR Apache-2.0"
version = "0.7.0"
version = "0.7.1"
edition = "2021"
publish = false
repository = "https://github.com/encounter/decomp-toolkit"

View File

@ -225,9 +225,12 @@ pub struct ProjectConfig {
/// and instead assumes that all symbols are known.
#[serde(default, skip_serializing_if = "is_default")]
pub symbols_known: bool,
/// Fills gaps between symbols with
/// Fills gaps between symbols to avoid linker realignment.
#[serde(default = "bool_true", skip_serializing_if = "is_true")]
pub fill_gaps: bool,
/// Marks all emitted symbols as "force active" to prevent the linker from removing them.
#[serde(default = "bool_true", skip_serializing_if = "is_true")]
pub auto_force_active: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
@ -859,7 +862,7 @@ fn split_write_obj(
entry,
};
for (unit, split_obj) in module.obj.link_order.iter().zip(&split_objs) {
let out_obj = write_elf(split_obj)?;
let out_obj = write_elf(split_obj, config.auto_force_active)?;
let out_path = obj_dir.join(obj_path_for_unit(&unit.name));
out_config.units.push(OutputUnit {
object: out_path.clone(),
@ -1763,6 +1766,7 @@ fn config(args: ConfigArgs) -> Result<()> {
common_start: None,
symbols_known: false,
fill_gaps: true,
auto_force_active: true,
};
let mut modules = Vec::<(u32, ModuleConfig)>::new();

View File

@ -185,7 +185,7 @@ fn split(args: SplitArgs) -> Result<()> {
let split_objs = split_obj(&obj)?;
for (unit, split_obj) in obj.link_order.iter().zip(&split_objs) {
let out_obj = write_elf(split_obj)?;
let out_obj = write_elf(split_obj, false)?;
match file_map.entry(unit.name.clone()) {
hash_map::Entry::Vacant(e) => e.insert(out_obj),
hash_map::Entry::Occupied(_) => bail!("Duplicate file {}", unit.name),

View File

@ -579,7 +579,7 @@ fn merge(args: MergeArgs) -> Result<()> {
// Write ELF
log::info!("Writing {}", args.out_file.display());
fs::write(&args.out_file, write_elf(&obj)?)?;
fs::write(&args.out_file, write_elf(&obj, false)?)?;
Ok(())
}

View File

@ -1407,6 +1407,114 @@ fn subroutine_block_string(
Ok(out)
}
#[derive(Debug, Clone)]
struct AnonUnion {
offset: u32,
member_index: usize,
member_count: usize,
}
#[derive(Debug, Clone)]
struct AnonUnionGroup {
member_index: usize,
member_count: usize,
}
fn get_anon_unions(info: &DwarfInfo, members: &[StructureMember]) -> Result<Vec<AnonUnion>> {
let mut unions = Vec::<AnonUnion>::new();
let mut offset = u32::MAX;
'member: for (prev, member) in members.iter().skip(1).enumerate() {
if let Some(bit) = &member.bit {
if bit.bit_offset != 0 {
continue;
}
}
if member.offset <= members[prev].offset && member.offset != offset {
offset = member.offset;
for (i, member) in members.iter().enumerate() {
if member.offset == offset {
for anon in &unions {
if anon.member_index == i {
continue 'member;
}
}
unions.push(AnonUnion { offset, member_index: i, member_count: 0 });
break;
}
}
}
}
for anon in &mut unions {
for (i, member) in members.iter().skip(anon.member_index).enumerate() {
if let Some(bit) = &member.bit {
if bit.bit_offset != 0 {
continue;
}
}
if member.offset == anon.offset {
anon.member_count = i;
}
}
let mut max_offset = 0;
for member in members.iter().skip(anon.member_index).take(anon.member_count + 1) {
if let Some(bit) = &member.bit {
if bit.bit_offset != 0 {
continue;
}
}
let size =
if let Some(size) = member.byte_size { size } else { member.kind.size(info)? };
max_offset = max(max_offset, member.offset + size);
}
for member in members.iter().skip(anon.member_index + anon.member_count) {
if let Some(bit) = &member.bit {
if bit.bit_offset != 0 {
continue;
}
}
if member.offset >= max_offset || member.offset < anon.offset {
break;
}
anon.member_count += 1;
}
}
Ok(unions)
}
fn get_anon_union_groups(members: &[StructureMember], unions: &[AnonUnion]) -> Vec<AnonUnionGroup> {
let mut groups = Vec::new();
for anon in unions {
for (i, member) in
members.iter().skip(anon.member_index).take(anon.member_count).enumerate()
{
if let Some(bit) = &member.bit {
if bit.bit_offset != 0 {
continue;
}
}
if member.offset == anon.offset {
let mut group =
AnonUnionGroup { member_index: anon.member_index + i, member_count: 1 };
for member in
members.iter().skip(anon.member_index).take(anon.member_count).skip(i + 1)
{
if member.offset == anon.offset {
break;
}
group.member_count += 1;
}
if group.member_count > 1 {
groups.push(group);
}
}
}
}
groups
}
pub fn struct_def_string(
info: &DwarfInfo,
typedefs: &TypedefMap,
@ -1453,7 +1561,12 @@ pub fn struct_def_string(
StructureKind::Struct => Visibility::Public,
StructureKind::Class => Visibility::Private,
};
for member in &t.members {
let mut indent = 4;
let unions = get_anon_unions(info, &t.members)?;
let groups = get_anon_union_groups(&t.members, &unions);
let mut in_union = 0;
let mut in_group = 0;
for (i, member) in t.members.iter().enumerate() {
if vis != member.visibility {
vis = member.visibility;
match member.visibility {
@ -1462,6 +1575,40 @@ pub fn struct_def_string(
Visibility::Public => out.push_str("public:\n"),
}
}
for anon in &groups {
if i == anon.member_index + anon.member_count {
indent -= 4;
out.push_str(&indent_all_by(indent, "};\n"));
in_group -= 1;
}
}
for anon in &unions {
if anon.member_count < 2 {
continue;
}
if i == anon.member_index + anon.member_count {
indent -= 4;
out.push_str(&indent_all_by(indent, "};\n"));
in_union -= 1;
}
}
for anon in &unions {
if anon.member_count < 2 {
continue;
}
if i == anon.member_index {
out.push_str(&indent_all_by(indent, "union { // inferred\n"));
indent += 4;
in_union += 1;
}
}
for anon in &groups {
if i == anon.member_index {
out.push_str(&indent_all_by(indent, "struct { // inferred\n"));
indent += 4;
in_group += 1;
}
}
let mut var_out = String::new();
let ts = type_string(info, typedefs, &member.kind, true)?;
write!(var_out, "{} {}{}", ts.prefix, member.name, ts.suffix)?;
@ -1470,7 +1617,17 @@ pub fn struct_def_string(
}
let size = if let Some(size) = member.byte_size { size } else { member.kind.size(info)? };
writeln!(var_out, "; // offset {:#X}, size {:#X}", member.offset, size)?;
out.push_str(&indent_all_by(4, var_out));
out.push_str(&indent_all_by(indent, var_out));
}
while in_group > 0 {
indent -= 4;
out.push_str(&indent_all_by(indent, "};\n"));
in_group -= 1;
}
while in_union > 0 {
indent -= 4;
out.push_str(&indent_all_by(indent, "};\n"));
in_union -= 1;
}
out.push('}');
Ok(out)

View File

@ -346,7 +346,7 @@ where P: AsRef<Path> {
Ok(obj)
}
pub fn write_elf(obj: &ObjInfo) -> Result<Vec<u8>> {
pub fn write_elf(obj: &ObjInfo, force_active: bool) -> Result<Vec<u8>> {
let mut out_data = Vec::new();
let mut writer = object::write::elf::Writer::new(Endianness::Big, false, &mut out_data);
@ -540,7 +540,7 @@ pub fn write_elf(obj: &ObjInfo) -> Result<Vec<u8>> {
out_symbols.push(OutSymbol { index, sym });
symbol_map[symbol_index] = Some(index.0);
if let Some(comment_data) = &mut comment_data {
CommentSym::from(symbol, true).to_writer_static(comment_data, Endian::Big)?;
CommentSym::from(symbol, force_active).to_writer_static(comment_data, Endian::Big)?;
}
}