From 5a3256b2b6b138d5c7021d7fbf9b4b49b0b093c2 Mon Sep 17 00:00:00 2001 From: Benjamin Moir Date: Sun, 14 Jan 2024 14:42:39 +1000 Subject: [PATCH] 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. --- src/util/dwarf.rs | 161 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 159 insertions(+), 2 deletions(-) diff --git a/src/util/dwarf.rs b/src/util/dwarf.rs index 185e255..7bf9ce1 100644 --- a/src/util/dwarf.rs +++ b/src/util/dwarf.rs @@ -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> { + let mut unions = Vec::::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 { + 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)