mirror of https://github.com/encounter/objdiff.git
Compare commits
4 Commits
9051f48731
...
7f7d4dce73
Author | SHA1 | Date |
---|---|---|
LagoLunatic | 7f7d4dce73 | |
LagoLunatic | d8ead1fcd4 | |
LagoLunatic | bd7ccbe38e | |
LagoLunatic | 6c65347e85 |
File diff suppressed because it is too large
Load Diff
|
@ -73,7 +73,6 @@ ignore = [
|
||||||
#{ id = "RUSTSEC-0000-0000", reason = "you can specify a reason the advisory is ignored" },
|
#{ id = "RUSTSEC-0000-0000", reason = "you can specify a reason the advisory is ignored" },
|
||||||
#"a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish
|
#"a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish
|
||||||
#{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" },
|
#{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" },
|
||||||
{ id = "RUSTSEC-2024-0384", reason = "Unmaintained indirect dependency" },
|
|
||||||
]
|
]
|
||||||
# If this is true, then cargo deny will use the git executable to fetch advisory database.
|
# If this is true, then cargo deny will use the git executable to fetch advisory database.
|
||||||
# If this is false, then it uses a built-in git library.
|
# If this is false, then it uses a built-in git library.
|
||||||
|
@ -98,7 +97,7 @@ allow = [
|
||||||
"BSL-1.0",
|
"BSL-1.0",
|
||||||
"CC0-1.0",
|
"CC0-1.0",
|
||||||
"MPL-2.0",
|
"MPL-2.0",
|
||||||
"Unicode-3.0",
|
"Unicode-DFS-2016",
|
||||||
"Zlib",
|
"Zlib",
|
||||||
"0BSD",
|
"0BSD",
|
||||||
"OFL-1.1",
|
"OFL-1.1",
|
||||||
|
|
|
@ -20,7 +20,7 @@ enable-ansi-support = "0.2"
|
||||||
memmap2 = "0.9"
|
memmap2 = "0.9"
|
||||||
objdiff-core = { path = "../objdiff-core", features = ["all"] }
|
objdiff-core = { path = "../objdiff-core", features = ["all"] }
|
||||||
prost = "0.13"
|
prost = "0.13"
|
||||||
ratatui = "0.29"
|
ratatui = "0.28"
|
||||||
rayon = "1.10"
|
rayon = "1.10"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
|
|
|
@ -30,6 +30,180 @@ fn is_rel_abs_arg(arg: &Argument) -> bool {
|
||||||
|
|
||||||
fn is_offset_arg(arg: &Argument) -> bool { matches!(arg, Argument::Offset(_)) }
|
fn is_offset_arg(arg: &Argument) -> bool { matches!(arg, Argument::Offset(_)) }
|
||||||
|
|
||||||
|
fn guess_data_type_from_load_store_inst_op(inst_op: Opcode) -> Option<DataType> {
|
||||||
|
match inst_op {
|
||||||
|
Opcode::Lbz | Opcode::Lbzu | Opcode::Lbzux | Opcode::Lbzx => Some(DataType::Int8),
|
||||||
|
Opcode::Lhz | Opcode::Lhzu | Opcode::Lhzux | Opcode::Lhzx => Some(DataType::Int16),
|
||||||
|
Opcode::Lha | Opcode::Lhau | Opcode::Lhaux | Opcode::Lhax => Some(DataType::Int16),
|
||||||
|
Opcode::Lwz | Opcode::Lwzu | Opcode::Lwzux | Opcode::Lwzx => Some(DataType::Int32),
|
||||||
|
Opcode::Lfs | Opcode::Lfsu | Opcode::Lfsux | Opcode::Lfsx => Some(DataType::Float),
|
||||||
|
Opcode::Lfd | Opcode::Lfdu | Opcode::Lfdux | Opcode::Lfdx => Some(DataType::Double),
|
||||||
|
|
||||||
|
Opcode::Stb | Opcode::Stbu | Opcode::Stbux | Opcode::Stbx => Some(DataType::Int8),
|
||||||
|
Opcode::Sth | Opcode::Sthu | Opcode::Sthux | Opcode::Sthx => Some(DataType::Int16),
|
||||||
|
Opcode::Stw | Opcode::Stwu | Opcode::Stwux | Opcode::Stwx => Some(DataType::Int32),
|
||||||
|
Opcode::Stfs | Opcode::Stfsu | Opcode::Stfsux | Opcode::Stfsx => Some(DataType::Float),
|
||||||
|
Opcode::Stfd | Opcode::Stfdu | Opcode::Stfdux | Opcode::Stfdx => Some(DataType::Double),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Given an instruction, determine if it could accessing data at the address in a register.
|
||||||
|
// If so, return the offset added to the register's address, the register containing that address,
|
||||||
|
// and (optionally) which destination register the address is being copied into.
|
||||||
|
fn get_offset_and_addr_gpr_for_possible_pool_reference(
|
||||||
|
opcode: Opcode,
|
||||||
|
simplified: &ParsedIns,
|
||||||
|
) -> Option<(i16, GPR, Option<GPR>)> {
|
||||||
|
let args = &simplified.args;
|
||||||
|
if guess_data_type_from_load_store_inst_op(opcode).is_some() {
|
||||||
|
match (args[1], args[2]) {
|
||||||
|
(Argument::Offset(offset), Argument::GPR(addr_src_gpr)) => {
|
||||||
|
// e.g. lwz. Immediate offset.
|
||||||
|
Some((offset.0, addr_src_gpr, None))
|
||||||
|
}
|
||||||
|
(Argument::GPR(addr_src_gpr), Argument::GPR(_offset_gpr)) => {
|
||||||
|
// e.g. lwzx. The offset is in a register and was likely calculated from an index.
|
||||||
|
// Treat the offset as being 0 in this case to show the first element of the array.
|
||||||
|
// It may be possible to show all elements by figuring out the stride of the array
|
||||||
|
// from the calculations performed on the index before it's put into offset_gpr, but
|
||||||
|
// this would be much more complicated, so it's not currently done.
|
||||||
|
Some((0, addr_src_gpr, None))
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If it's not a load/store instruction, there's two more possibilities we need to handle.
|
||||||
|
// 1. It could be a reference to @stringBase.
|
||||||
|
// 2. It could be moving the relocation address plus an offset into a different register to
|
||||||
|
// load from later.
|
||||||
|
// If either of these match, we also want to return the destination register that the
|
||||||
|
// address is being copied into so that we can detect any future references to that new
|
||||||
|
// register as well.
|
||||||
|
match (opcode, args[0], args[1], args[2]) {
|
||||||
|
(
|
||||||
|
Opcode::Addi,
|
||||||
|
Argument::GPR(addr_dst_gpr),
|
||||||
|
Argument::GPR(addr_src_gpr),
|
||||||
|
Argument::Simm(simm),
|
||||||
|
) => Some((simm.0, addr_src_gpr, Some(addr_dst_gpr))),
|
||||||
|
(
|
||||||
|
Opcode::Or,
|
||||||
|
Argument::GPR(addr_dst_gpr),
|
||||||
|
Argument::GPR(addr_src_gpr),
|
||||||
|
Argument::None,
|
||||||
|
) => Some((0, addr_src_gpr, Some(addr_dst_gpr))), // `mr` or `mr.`
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We create a fake relocation for an instruction, vaguely simulating what the actual relocation
|
||||||
|
// might have looked like if it wasn't pooled. This is so minimal changes are needed to display
|
||||||
|
// pooled accesses vs non-pooled accesses. We set the relocation type to R_PPC_NONE to indicate that
|
||||||
|
// there isn't really a relocation here, as copying the pool relocation's type wouldn't make sense.
|
||||||
|
// Also, if this instruction is accessing the middle of a symbol instead of the start, we add an
|
||||||
|
// addend to indicate that.
|
||||||
|
fn make_fake_pool_reloc(
|
||||||
|
offset: i16,
|
||||||
|
cur_addr: u32,
|
||||||
|
pool_reloc: &ObjReloc,
|
||||||
|
sections: &[ObjSection],
|
||||||
|
) -> Option<ObjReloc> {
|
||||||
|
let offset_from_pool = pool_reloc.addend + offset as i64;
|
||||||
|
let target_address = pool_reloc.target.address.checked_add_signed(offset_from_pool)?;
|
||||||
|
let orig_section_index = pool_reloc.target.orig_section_index?;
|
||||||
|
let section = sections.iter().find(|s| s.orig_index == orig_section_index)?;
|
||||||
|
let target_symbol = section
|
||||||
|
.symbols
|
||||||
|
.iter()
|
||||||
|
.find(|s| s.size > 0 && (s.address..s.address + s.size).contains(&target_address))?;
|
||||||
|
let addend = (target_address - target_symbol.address) as i64;
|
||||||
|
Some(ObjReloc {
|
||||||
|
flags: RelocationFlags::Elf { r_type: elf::R_PPC_NONE },
|
||||||
|
address: cur_addr as u64,
|
||||||
|
target: target_symbol.clone(),
|
||||||
|
addend,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Searches through all instructions in a function, determining which registers have the addresses
|
||||||
|
// of pooled data relocations in them, finding which instructions load data from those addresses,
|
||||||
|
// and constructing a mapping of the address of that instruction to a "fake pool relocation" that
|
||||||
|
// simulates what that instruction's relocation would look like if data hadn't been pooled.
|
||||||
|
// Limitations: This method currently only goes through the instructions in a function in linear
|
||||||
|
// order, from start to finish. It does *not* follow any branches. This means that it could have
|
||||||
|
// false positives or false negatives in determining which relocation is currently loaded in which
|
||||||
|
// register at any given point in the function, as control flow is not respected.
|
||||||
|
// There are currently no known examples of this method producing inaccurate results in reality, but
|
||||||
|
// if examples are found, it may be possible to update this method to also follow all branches so
|
||||||
|
// that it produces more accurate results.
|
||||||
|
fn generate_fake_pool_reloc_for_addr_mapping(
|
||||||
|
address: u64,
|
||||||
|
code: &[u8],
|
||||||
|
relocations: &[ObjReloc],
|
||||||
|
sections: &[ObjSection],
|
||||||
|
) -> HashMap<u32, ObjReloc> {
|
||||||
|
let mut active_pool_relocs = HashMap::new();
|
||||||
|
let mut pool_reloc_for_addr = HashMap::new();
|
||||||
|
for (cur_addr, ins) in InsIter::new(code, address as u32) {
|
||||||
|
let simplified = ins.simplified();
|
||||||
|
let reloc = relocations.iter().find(|r| (r.address as u32 & !3) == cur_addr);
|
||||||
|
|
||||||
|
if let Some(reloc) = reloc {
|
||||||
|
// This instruction has a real relocation, so it may be a pool load we want to keep
|
||||||
|
// track of.
|
||||||
|
let args = &simplified.args;
|
||||||
|
match (ins.op, args[0], args[1], args[2]) {
|
||||||
|
(
|
||||||
|
Opcode::Addi,
|
||||||
|
Argument::GPR(addr_dst_gpr),
|
||||||
|
Argument::GPR(_addr_src_gpr),
|
||||||
|
Argument::Simm(_simm),
|
||||||
|
) => {
|
||||||
|
active_pool_relocs.insert(addr_dst_gpr.0, reloc.clone()); // `lis` + `addi`
|
||||||
|
}
|
||||||
|
(
|
||||||
|
Opcode::Ori,
|
||||||
|
Argument::GPR(addr_dst_gpr),
|
||||||
|
Argument::GPR(_addr_src_gpr),
|
||||||
|
Argument::Uimm(_uimm),
|
||||||
|
) => {
|
||||||
|
active_pool_relocs.insert(addr_dst_gpr.0, reloc.clone()); // `lis` + `ori`
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
} else if let Some((offset, addr_src_gpr, addr_dst_gpr)) =
|
||||||
|
get_offset_and_addr_gpr_for_possible_pool_reference(ins.op, &simplified)
|
||||||
|
{
|
||||||
|
// This instruction doesn't have a real relocation, so it may be a reference to one of
|
||||||
|
// the already-loaded pools.
|
||||||
|
if let Some(pool_reloc) = active_pool_relocs.get(&addr_src_gpr.0) {
|
||||||
|
if let Some(fake_pool_reloc) =
|
||||||
|
make_fake_pool_reloc(offset, cur_addr, pool_reloc, sections)
|
||||||
|
{
|
||||||
|
pool_reloc_for_addr.insert(cur_addr, fake_pool_reloc);
|
||||||
|
}
|
||||||
|
if let Some(addr_dst_gpr) = addr_dst_gpr {
|
||||||
|
// If the address of the pool relocation got copied into another register, we
|
||||||
|
// need to keep track of it in that register too as future instructions may
|
||||||
|
// reference the symbol indirectly via this new register, instead of the
|
||||||
|
// register the symbol's address was originally loaded into.
|
||||||
|
// For example, the start of the function might `lis` + `addi` the start of the
|
||||||
|
// ...data pool into r25, and then later the start of a loop will `addi` r25
|
||||||
|
// with the offset within the .data section of an array variable into r21.
|
||||||
|
// Then the body of the loop will `lwzx` one of the array elements from r21.
|
||||||
|
let mut new_reloc = pool_reloc.clone();
|
||||||
|
new_reloc.addend += offset as i64;
|
||||||
|
active_pool_relocs.insert(addr_dst_gpr.0, new_reloc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pool_reloc_for_addr
|
||||||
|
}
|
||||||
|
|
||||||
pub struct ObjArchPpc {
|
pub struct ObjArchPpc {
|
||||||
/// Exception info
|
/// Exception info
|
||||||
pub extab: Option<BTreeMap<usize, ExceptionInfo>>,
|
pub extab: Option<BTreeMap<usize, ExceptionInfo>>,
|
||||||
|
@ -378,177 +552,3 @@ fn make_symbol_ref(symbol: &Symbol) -> Result<ExtabSymbolRef> {
|
||||||
let demangled_name = cwdemangle::demangle(&name, &cwdemangle::DemangleOptions::default());
|
let demangled_name = cwdemangle::demangle(&name, &cwdemangle::DemangleOptions::default());
|
||||||
Ok(ExtabSymbolRef { original_index: symbol.index().0, name, demangled_name })
|
Ok(ExtabSymbolRef { original_index: symbol.index().0, name, demangled_name })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn guess_data_type_from_load_store_inst_op(inst_op: Opcode) -> Option<DataType> {
|
|
||||||
match inst_op {
|
|
||||||
Opcode::Lbz | Opcode::Lbzu | Opcode::Lbzux | Opcode::Lbzx => Some(DataType::Int8),
|
|
||||||
Opcode::Lhz | Opcode::Lhzu | Opcode::Lhzux | Opcode::Lhzx => Some(DataType::Int16),
|
|
||||||
Opcode::Lha | Opcode::Lhau | Opcode::Lhaux | Opcode::Lhax => Some(DataType::Int16),
|
|
||||||
Opcode::Lwz | Opcode::Lwzu | Opcode::Lwzux | Opcode::Lwzx => Some(DataType::Int32),
|
|
||||||
Opcode::Lfs | Opcode::Lfsu | Opcode::Lfsux | Opcode::Lfsx => Some(DataType::Float),
|
|
||||||
Opcode::Lfd | Opcode::Lfdu | Opcode::Lfdux | Opcode::Lfdx => Some(DataType::Double),
|
|
||||||
|
|
||||||
Opcode::Stb | Opcode::Stbu | Opcode::Stbux | Opcode::Stbx => Some(DataType::Int8),
|
|
||||||
Opcode::Sth | Opcode::Sthu | Opcode::Sthux | Opcode::Sthx => Some(DataType::Int16),
|
|
||||||
Opcode::Stw | Opcode::Stwu | Opcode::Stwux | Opcode::Stwx => Some(DataType::Int32),
|
|
||||||
Opcode::Stfs | Opcode::Stfsu | Opcode::Stfsux | Opcode::Stfsx => Some(DataType::Float),
|
|
||||||
Opcode::Stfd | Opcode::Stfdu | Opcode::Stfdux | Opcode::Stfdx => Some(DataType::Double),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Given an instruction, determine if it could accessing data at the address in a register.
|
|
||||||
// If so, return the offset added to the register's address, the register containing that address,
|
|
||||||
// and (optionally) which destination register the address is being copied into.
|
|
||||||
fn get_offset_and_addr_gpr_for_possible_pool_reference(
|
|
||||||
opcode: Opcode,
|
|
||||||
simplified: &ParsedIns,
|
|
||||||
) -> Option<(i16, GPR, Option<GPR>)> {
|
|
||||||
let args = &simplified.args;
|
|
||||||
if guess_data_type_from_load_store_inst_op(opcode).is_some() {
|
|
||||||
match (args[1], args[2]) {
|
|
||||||
(Argument::Offset(offset), Argument::GPR(addr_src_gpr)) => {
|
|
||||||
// e.g. lwz. Immediate offset.
|
|
||||||
Some((offset.0, addr_src_gpr, None))
|
|
||||||
}
|
|
||||||
(Argument::GPR(addr_src_gpr), Argument::GPR(_offset_gpr)) => {
|
|
||||||
// e.g. lwzx. The offset is in a register and was likely calculated from an index.
|
|
||||||
// Treat the offset as being 0 in this case to show the first element of the array.
|
|
||||||
// It may be possible to show all elements by figuring out the stride of the array
|
|
||||||
// from the calculations performed on the index before it's put into offset_gpr, but
|
|
||||||
// this would be much more complicated, so it's not currently done.
|
|
||||||
Some((0, addr_src_gpr, None))
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// If it's not a load/store instruction, there's two more possibilities we need to handle.
|
|
||||||
// 1. It could be a reference to @stringBase.
|
|
||||||
// 2. It could be moving the relocation address plus an offset into a different register to
|
|
||||||
// load from later.
|
|
||||||
// If either of these match, we also want to return the destination register that the
|
|
||||||
// address is being copied into so that we can detect any future references to that new
|
|
||||||
// register as well.
|
|
||||||
match (opcode, args[0], args[1], args[2]) {
|
|
||||||
(
|
|
||||||
Opcode::Addi,
|
|
||||||
Argument::GPR(addr_dst_gpr),
|
|
||||||
Argument::GPR(addr_src_gpr),
|
|
||||||
Argument::Simm(simm),
|
|
||||||
) => Some((simm.0, addr_src_gpr, Some(addr_dst_gpr))),
|
|
||||||
(
|
|
||||||
Opcode::Or,
|
|
||||||
Argument::GPR(addr_dst_gpr),
|
|
||||||
Argument::GPR(addr_src_gpr),
|
|
||||||
Argument::None,
|
|
||||||
) => Some((0, addr_src_gpr, Some(addr_dst_gpr))), // `mr` or `mr.`
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We create a fake relocation for an instruction, vaguely simulating what the actual relocation
|
|
||||||
// might have looked like if it wasn't pooled. This is so minimal changes are needed to display
|
|
||||||
// pooled accesses vs non-pooled accesses. We set the relocation type to R_PPC_NONE to indicate that
|
|
||||||
// there isn't really a relocation here, as copying the pool relocation's type wouldn't make sense.
|
|
||||||
// Also, if this instruction is accessing the middle of a symbol instead of the start, we add an
|
|
||||||
// addend to indicate that.
|
|
||||||
fn make_fake_pool_reloc(
|
|
||||||
offset: i16,
|
|
||||||
cur_addr: u32,
|
|
||||||
pool_reloc: &ObjReloc,
|
|
||||||
sections: &[ObjSection],
|
|
||||||
) -> Option<ObjReloc> {
|
|
||||||
let offset_from_pool = pool_reloc.addend + offset as i64;
|
|
||||||
let target_address = pool_reloc.target.address.checked_add_signed(offset_from_pool)?;
|
|
||||||
let orig_section_index = pool_reloc.target.orig_section_index?;
|
|
||||||
let section = sections.iter().find(|s| s.orig_index == orig_section_index)?;
|
|
||||||
let target_symbol = section
|
|
||||||
.symbols
|
|
||||||
.iter()
|
|
||||||
.find(|s| s.size > 0 && (s.address..s.address + s.size).contains(&target_address))?;
|
|
||||||
let addend = (target_address - target_symbol.address) as i64;
|
|
||||||
Some(ObjReloc {
|
|
||||||
flags: RelocationFlags::Elf { r_type: elf::R_PPC_NONE },
|
|
||||||
address: cur_addr as u64,
|
|
||||||
target: target_symbol.clone(),
|
|
||||||
addend,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Searches through all instructions in a function, determining which registers have the addresses
|
|
||||||
// of pooled data relocations in them, finding which instructions load data from those addresses,
|
|
||||||
// and constructing a mapping of the address of that instruction to a "fake pool relocation" that
|
|
||||||
// simulates what that instruction's relocation would look like if data hadn't been pooled.
|
|
||||||
// Limitations: This method currently only goes through the instructions in a function in linear
|
|
||||||
// order, from start to finish. It does *not* follow any branches. This means that it could have
|
|
||||||
// false positives or false negatives in determining which relocation is currently loaded in which
|
|
||||||
// register at any given point in the function, as control flow is not respected.
|
|
||||||
// There are currently no known examples of this method producing inaccurate results in reality, but
|
|
||||||
// if examples are found, it may be possible to update this method to also follow all branches so
|
|
||||||
// that it produces more accurate results.
|
|
||||||
fn generate_fake_pool_reloc_for_addr_mapping(
|
|
||||||
address: u64,
|
|
||||||
code: &[u8],
|
|
||||||
relocations: &[ObjReloc],
|
|
||||||
sections: &[ObjSection],
|
|
||||||
) -> HashMap<u32, ObjReloc> {
|
|
||||||
let mut active_pool_relocs = HashMap::new();
|
|
||||||
let mut pool_reloc_for_addr = HashMap::new();
|
|
||||||
for (cur_addr, ins) in InsIter::new(code, address as u32) {
|
|
||||||
let simplified = ins.simplified();
|
|
||||||
let reloc = relocations.iter().find(|r| (r.address as u32 & !3) == cur_addr);
|
|
||||||
|
|
||||||
if let Some(reloc) = reloc {
|
|
||||||
// This instruction has a real relocation, so it may be a pool load we want to keep
|
|
||||||
// track of.
|
|
||||||
let args = &simplified.args;
|
|
||||||
match (ins.op, args[0], args[1], args[2]) {
|
|
||||||
(
|
|
||||||
Opcode::Addi,
|
|
||||||
Argument::GPR(addr_dst_gpr),
|
|
||||||
Argument::GPR(_addr_src_gpr),
|
|
||||||
Argument::Simm(_simm),
|
|
||||||
) => {
|
|
||||||
active_pool_relocs.insert(addr_dst_gpr.0, reloc.clone()); // `lis` + `addi`
|
|
||||||
}
|
|
||||||
(
|
|
||||||
Opcode::Ori,
|
|
||||||
Argument::GPR(addr_dst_gpr),
|
|
||||||
Argument::GPR(_addr_src_gpr),
|
|
||||||
Argument::Uimm(_uimm),
|
|
||||||
) => {
|
|
||||||
active_pool_relocs.insert(addr_dst_gpr.0, reloc.clone()); // `lis` + `ori`
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
} else if let Some((offset, addr_src_gpr, addr_dst_gpr)) =
|
|
||||||
get_offset_and_addr_gpr_for_possible_pool_reference(ins.op, &simplified)
|
|
||||||
{
|
|
||||||
// This instruction doesn't have a real relocation, so it may be a reference to one of
|
|
||||||
// the already-loaded pools.
|
|
||||||
if let Some(pool_reloc) = active_pool_relocs.get(&addr_src_gpr.0) {
|
|
||||||
if let Some(fake_pool_reloc) =
|
|
||||||
make_fake_pool_reloc(offset, cur_addr, pool_reloc, sections)
|
|
||||||
{
|
|
||||||
pool_reloc_for_addr.insert(cur_addr, fake_pool_reloc);
|
|
||||||
}
|
|
||||||
if let Some(addr_dst_gpr) = addr_dst_gpr {
|
|
||||||
// If the address of the pool relocation got copied into another register, we
|
|
||||||
// need to keep track of it in that register too as future instructions may
|
|
||||||
// reference the symbol indirectly via this new register, instead of the
|
|
||||||
// register the symbol's address was originally loaded into.
|
|
||||||
// For example, the start of the function might `lis` + `addi` the start of the
|
|
||||||
// ...data pool into r25, and then later the start of a loop will `addi` r25
|
|
||||||
// with the offset within the .data section of an array variable into r21.
|
|
||||||
// Then the body of the loop will `lwzx` one of the array elements from r21.
|
|
||||||
let mut new_reloc = pool_reloc.clone();
|
|
||||||
new_reloc.addend += offset as i64;
|
|
||||||
active_pool_relocs.insert(addr_dst_gpr.0, new_reloc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pool_reloc_for_addr
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
#![allow(clippy::needless_lifetimes)] // Generated serde code
|
|
||||||
use crate::{
|
use crate::{
|
||||||
diff::{
|
diff::{
|
||||||
ObjDataDiff, ObjDataDiffKind, ObjDiff, ObjInsArgDiff, ObjInsBranchFrom, ObjInsBranchTo,
|
ObjDataDiff, ObjDataDiffKind, ObjDiff, ObjInsArgDiff, ObjInsBranchFrom, ObjInsBranchTo,
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
#![allow(clippy::needless_lifetimes)] // Generated serde code
|
|
||||||
use std::ops::AddAssign;
|
use std::ops::AddAssign;
|
||||||
|
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
|
@ -174,7 +173,8 @@ impl Report {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
fn is_sub_category(id: &str, parent: &str, sep: char) -> bool {
|
fn is_sub_category(id: &str, parent: &str, sep: char) -> bool {
|
||||||
id.starts_with(parent) && id.get(parent.len()..).is_some_and(|s| s.starts_with(sep))
|
id.starts_with(parent)
|
||||||
|
&& id.get(parent.len()..).map_or(false, |s| s.starts_with(sep))
|
||||||
}
|
}
|
||||||
let mut sub_categories = self
|
let mut sub_categories = self
|
||||||
.categories
|
.categories
|
||||||
|
|
|
@ -65,7 +65,10 @@ fn to_obj_symbol(
|
||||||
flags = ObjSymbolFlagSet(flags.0 | ObjSymbolFlags::Hidden);
|
flags = ObjSymbolFlagSet(flags.0 | ObjSymbolFlags::Hidden);
|
||||||
}
|
}
|
||||||
#[cfg(feature = "ppc")]
|
#[cfg(feature = "ppc")]
|
||||||
if arch.ppc().and_then(|a| a.extab.as_ref()).is_some_and(|e| e.contains_key(&symbol.index().0))
|
if arch
|
||||||
|
.ppc()
|
||||||
|
.and_then(|a| a.extab.as_ref())
|
||||||
|
.map_or(false, |e| e.contains_key(&symbol.index().0))
|
||||||
{
|
{
|
||||||
flags = ObjSymbolFlagSet(flags.0 | ObjSymbolFlags::HasExtra);
|
flags = ObjSymbolFlagSet(flags.0 | ObjSymbolFlags::HasExtra);
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,7 @@ wsl = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
bytes = "1.9"
|
bytes = "1.7"
|
||||||
cfg-if = "1.0"
|
cfg-if = "1.0"
|
||||||
const_format = "0.2"
|
const_format = "0.2"
|
||||||
cwdemangle = "1.0"
|
cwdemangle = "1.0"
|
||||||
|
@ -42,7 +42,7 @@ notify = { git = "https://github.com/notify-rs/notify", rev = "128bf6230c03d39db
|
||||||
objdiff-core = { path = "../objdiff-core", features = ["all"] }
|
objdiff-core = { path = "../objdiff-core", features = ["all"] }
|
||||||
open = "5.3"
|
open = "5.3"
|
||||||
png = "0.17"
|
png = "0.17"
|
||||||
pollster = "0.4"
|
pollster = "0.3"
|
||||||
regex = "1.11"
|
regex = "1.11"
|
||||||
rfd = { version = "0.15" } #, default-features = false, features = ['xdg-portal']
|
rfd = { version = "0.15" } #, default-features = false, features = ['xdg-portal']
|
||||||
rlwinmdec = "1.0"
|
rlwinmdec = "1.0"
|
||||||
|
@ -51,7 +51,7 @@ serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
shell-escape = "0.1"
|
shell-escape = "0.1"
|
||||||
strum = { version = "0.26", features = ["derive"] }
|
strum = { version = "0.26", features = ["derive"] }
|
||||||
tempfile = "3.14"
|
tempfile = "3.13"
|
||||||
time = { version = "0.3", features = ["formatting", "local-offset"] }
|
time = { version = "0.3", features = ["formatting", "local-offset"] }
|
||||||
|
|
||||||
# Keep version in sync with egui
|
# Keep version in sync with egui
|
||||||
|
|
|
@ -152,7 +152,8 @@ fn start_job(
|
||||||
let context = JobContext { status: status.clone(), egui: ctx.clone() };
|
let context = JobContext { status: status.clone(), egui: ctx.clone() };
|
||||||
let context_inner = JobContext { status: status.clone(), egui: ctx.clone() };
|
let context_inner = JobContext { status: status.clone(), egui: ctx.clone() };
|
||||||
let (tx, rx) = std::sync::mpsc::channel();
|
let (tx, rx) = std::sync::mpsc::channel();
|
||||||
let handle = std::thread::spawn(move || match run(context_inner, rx) {
|
let handle = std::thread::spawn(move || {
|
||||||
|
return match run(context_inner, rx) {
|
||||||
Ok(state) => state,
|
Ok(state) => state,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
if let Ok(mut w) = status.write() {
|
if let Ok(mut w) = status.write() {
|
||||||
|
@ -160,6 +161,7 @@ fn start_job(
|
||||||
}
|
}
|
||||||
JobResult::None
|
JobResult::None
|
||||||
}
|
}
|
||||||
|
};
|
||||||
});
|
});
|
||||||
let id = JOB_ID.fetch_add(1, Ordering::Relaxed);
|
let id = JOB_ID.fetch_add(1, Ordering::Relaxed);
|
||||||
log::info!("Started job {}", id);
|
log::info!("Started job {}", id);
|
||||||
|
|
|
@ -213,8 +213,8 @@ pub fn data_diff_ui(
|
||||||
let right_ctx = SectionDiffContext::new(result.second_obj.as_ref(), section_name);
|
let right_ctx = SectionDiffContext::new(result.second_obj.as_ref(), section_name);
|
||||||
|
|
||||||
// If both sides are missing a symbol, switch to symbol diff view
|
// If both sides are missing a symbol, switch to symbol diff view
|
||||||
if !right_ctx.is_some_and(|ctx| ctx.has_section())
|
if !right_ctx.map_or(false, |ctx| ctx.has_section())
|
||||||
&& !left_ctx.is_some_and(|ctx| ctx.has_section())
|
&& !left_ctx.map_or(false, |ctx| ctx.has_section())
|
||||||
{
|
{
|
||||||
return Some(DiffViewAction::Navigate(DiffViewNavigation::symbol_diff()));
|
return Some(DiffViewAction::Navigate(DiffViewNavigation::symbol_diff()));
|
||||||
}
|
}
|
||||||
|
|
|
@ -101,7 +101,7 @@ pub fn extab_diff_ui(
|
||||||
let right_diff_symbol = right_ctx.and_then(|ctx| {
|
let right_diff_symbol = right_ctx.and_then(|ctx| {
|
||||||
ctx.symbol_ref.and_then(|symbol_ref| ctx.diff.symbol_diff(symbol_ref).target_symbol)
|
ctx.symbol_ref.and_then(|symbol_ref| ctx.diff.symbol_diff(symbol_ref).target_symbol)
|
||||||
});
|
});
|
||||||
if left_diff_symbol.is_some() && right_ctx.is_some_and(|ctx| !ctx.has_symbol()) {
|
if left_diff_symbol.is_some() && right_ctx.map_or(false, |ctx| !ctx.has_symbol()) {
|
||||||
let (right_section, right_symbol) =
|
let (right_section, right_symbol) =
|
||||||
right_ctx.unwrap().obj.section_symbol(left_diff_symbol.unwrap());
|
right_ctx.unwrap().obj.section_symbol(left_diff_symbol.unwrap());
|
||||||
let symbol_ref = SymbolRefByName::new(right_symbol, right_section);
|
let symbol_ref = SymbolRefByName::new(right_symbol, right_section);
|
||||||
|
@ -111,7 +111,7 @@ pub fn extab_diff_ui(
|
||||||
left_symbol: state.symbol_state.left_symbol.clone(),
|
left_symbol: state.symbol_state.left_symbol.clone(),
|
||||||
right_symbol: Some(symbol_ref),
|
right_symbol: Some(symbol_ref),
|
||||||
}));
|
}));
|
||||||
} else if right_diff_symbol.is_some() && left_ctx.is_some_and(|ctx| !ctx.has_symbol()) {
|
} else if right_diff_symbol.is_some() && left_ctx.map_or(false, |ctx| !ctx.has_symbol()) {
|
||||||
let (left_section, left_symbol) =
|
let (left_section, left_symbol) =
|
||||||
left_ctx.unwrap().obj.section_symbol(right_diff_symbol.unwrap());
|
left_ctx.unwrap().obj.section_symbol(right_diff_symbol.unwrap());
|
||||||
let symbol_ref = SymbolRefByName::new(left_symbol, left_section);
|
let symbol_ref = SymbolRefByName::new(left_symbol, left_section);
|
||||||
|
@ -124,8 +124,8 @@ pub fn extab_diff_ui(
|
||||||
}
|
}
|
||||||
|
|
||||||
// If both sides are missing a symbol, switch to symbol diff view
|
// If both sides are missing a symbol, switch to symbol diff view
|
||||||
if right_ctx.is_some_and(|ctx| !ctx.has_symbol())
|
if right_ctx.map_or(false, |ctx| !ctx.has_symbol())
|
||||||
&& left_ctx.is_some_and(|ctx| !ctx.has_symbol())
|
&& left_ctx.map_or(false, |ctx| !ctx.has_symbol())
|
||||||
{
|
{
|
||||||
return Some(DiffViewAction::Navigate(DiffViewNavigation::symbol_diff()));
|
return Some(DiffViewAction::Navigate(DiffViewNavigation::symbol_diff()));
|
||||||
}
|
}
|
||||||
|
@ -144,7 +144,7 @@ pub fn extab_diff_ui(
|
||||||
.add_enabled(
|
.add_enabled(
|
||||||
!state.scratch_running
|
!state.scratch_running
|
||||||
&& state.scratch_available
|
&& state.scratch_available
|
||||||
&& left_ctx.is_some_and(|ctx| ctx.has_symbol()),
|
&& left_ctx.map_or(false, |ctx| ctx.has_symbol()),
|
||||||
egui::Button::new("📲 decomp.me"),
|
egui::Button::new("📲 decomp.me"),
|
||||||
)
|
)
|
||||||
.on_hover_text_at_pointer("Create a new scratch on decomp.me (beta)")
|
.on_hover_text_at_pointer("Create a new scratch on decomp.me (beta)")
|
||||||
|
|
|
@ -648,7 +648,7 @@ pub fn function_diff_ui(
|
||||||
let right_diff_symbol = right_ctx.and_then(|ctx| {
|
let right_diff_symbol = right_ctx.and_then(|ctx| {
|
||||||
ctx.symbol_ref.and_then(|symbol_ref| ctx.diff.symbol_diff(symbol_ref).target_symbol)
|
ctx.symbol_ref.and_then(|symbol_ref| ctx.diff.symbol_diff(symbol_ref).target_symbol)
|
||||||
});
|
});
|
||||||
if left_diff_symbol.is_some() && right_ctx.is_some_and(|ctx| !ctx.has_symbol()) {
|
if left_diff_symbol.is_some() && right_ctx.map_or(false, |ctx| !ctx.has_symbol()) {
|
||||||
let (right_section, right_symbol) =
|
let (right_section, right_symbol) =
|
||||||
right_ctx.unwrap().obj.section_symbol(left_diff_symbol.unwrap());
|
right_ctx.unwrap().obj.section_symbol(left_diff_symbol.unwrap());
|
||||||
let symbol_ref = SymbolRefByName::new(right_symbol, right_section);
|
let symbol_ref = SymbolRefByName::new(right_symbol, right_section);
|
||||||
|
@ -658,7 +658,7 @@ pub fn function_diff_ui(
|
||||||
left_symbol: state.symbol_state.left_symbol.clone(),
|
left_symbol: state.symbol_state.left_symbol.clone(),
|
||||||
right_symbol: Some(symbol_ref),
|
right_symbol: Some(symbol_ref),
|
||||||
}));
|
}));
|
||||||
} else if right_diff_symbol.is_some() && left_ctx.is_some_and(|ctx| !ctx.has_symbol()) {
|
} else if right_diff_symbol.is_some() && left_ctx.map_or(false, |ctx| !ctx.has_symbol()) {
|
||||||
let (left_section, left_symbol) =
|
let (left_section, left_symbol) =
|
||||||
left_ctx.unwrap().obj.section_symbol(right_diff_symbol.unwrap());
|
left_ctx.unwrap().obj.section_symbol(right_diff_symbol.unwrap());
|
||||||
let symbol_ref = SymbolRefByName::new(left_symbol, left_section);
|
let symbol_ref = SymbolRefByName::new(left_symbol, left_section);
|
||||||
|
@ -671,8 +671,8 @@ pub fn function_diff_ui(
|
||||||
}
|
}
|
||||||
|
|
||||||
// If both sides are missing a symbol, switch to symbol diff view
|
// If both sides are missing a symbol, switch to symbol diff view
|
||||||
if right_ctx.is_some_and(|ctx| !ctx.has_symbol())
|
if right_ctx.map_or(false, |ctx| !ctx.has_symbol())
|
||||||
&& left_ctx.is_some_and(|ctx| !ctx.has_symbol())
|
&& left_ctx.map_or(false, |ctx| !ctx.has_symbol())
|
||||||
{
|
{
|
||||||
return Some(DiffViewAction::Navigate(DiffViewNavigation::symbol_diff()));
|
return Some(DiffViewAction::Navigate(DiffViewNavigation::symbol_diff()));
|
||||||
}
|
}
|
||||||
|
@ -691,7 +691,7 @@ pub fn function_diff_ui(
|
||||||
.add_enabled(
|
.add_enabled(
|
||||||
!state.scratch_running
|
!state.scratch_running
|
||||||
&& state.scratch_available
|
&& state.scratch_available
|
||||||
&& left_ctx.is_some_and(|ctx| ctx.has_symbol()),
|
&& left_ctx.map_or(false, |ctx| ctx.has_symbol()),
|
||||||
egui::Button::new("📲 decomp.me"),
|
egui::Button::new("📲 decomp.me"),
|
||||||
)
|
)
|
||||||
.on_hover_text_at_pointer("Create a new scratch on decomp.me (beta)")
|
.on_hover_text_at_pointer("Create a new scratch on decomp.me (beta)")
|
||||||
|
@ -715,7 +715,7 @@ pub fn function_diff_ui(
|
||||||
.font(appearance.code_font.clone())
|
.font(appearance.code_font.clone())
|
||||||
.color(appearance.highlight_color),
|
.color(appearance.highlight_color),
|
||||||
);
|
);
|
||||||
if right_ctx.is_some_and(|m| m.has_symbol())
|
if right_ctx.map_or(false, |m| m.has_symbol())
|
||||||
&& ui
|
&& ui
|
||||||
.button("Change target")
|
.button("Change target")
|
||||||
.on_hover_text_at_pointer("Choose a different symbol to use as the target")
|
.on_hover_text_at_pointer("Choose a different symbol to use as the target")
|
||||||
|
@ -785,7 +785,7 @@ pub fn function_diff_ui(
|
||||||
.color(match_color_for_symbol(match_percent, appearance)),
|
.color(match_color_for_symbol(match_percent, appearance)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if left_ctx.is_some_and(|m| m.has_symbol()) {
|
if left_ctx.map_or(false, |m| m.has_symbol()) {
|
||||||
ui.separator();
|
ui.separator();
|
||||||
if ui
|
if ui
|
||||||
.button("Change base")
|
.button("Change base")
|
||||||
|
|
Loading…
Reference in New Issue