Compare commits

...

6 Commits

Author SHA1 Message Date
LagoLunatic 9051f48731 Move big functions to bottom ppc.rs 2024-12-02 01:22:55 -05:00
LagoLunatic cdf3fd1780 Tooltip data display: Format floats and doubles better
Floats and doubles will now always be displayed with a decimal point and one digit after it, even if they are whole numbers. Floats will also have the f suffix. This is so you can tell the data type just by glancing at the value.
2024-12-02 01:22:55 -05:00
LagoLunatic 70b460649c PPC: Display data values on hover for pools as well 2024-12-02 01:22:55 -05:00
LagoLunatic 774676ec8a Update .gitignore 2024-12-02 01:22:55 -05:00
LagoLunatic 135d21b76f Fix missing dependency feature for objdiff-gui 2024-12-02 01:22:55 -05:00
Luke Street 7aa878b48e Update all dependencies & clippy fixes 2024-12-01 22:22:35 -07:00
20 changed files with 968 additions and 476 deletions

2
.gitignore vendored
View File

@ -18,4 +18,4 @@ android.keystore
*.frag *.frag
*.vert *.vert
*.metal *.metal
.vscode/launch.json .vscode/

1099
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -73,6 +73,7 @@ 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.
@ -97,7 +98,7 @@ allow = [
"BSL-1.0", "BSL-1.0",
"CC0-1.0", "CC0-1.0",
"MPL-2.0", "MPL-2.0",
"Unicode-DFS-2016", "Unicode-3.0",
"Zlib", "Zlib",
"0BSD", "0BSD",
"OFL-1.1", "OFL-1.1",

View File

@ -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.28" ratatui = "0.29"
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"

View File

@ -113,6 +113,7 @@ impl ObjArch for ObjArchArm {
relocations: &[ObjReloc], relocations: &[ObjReloc],
line_info: &BTreeMap<u64, u32>, line_info: &BTreeMap<u64, u32>,
config: &DiffObjConfig, config: &DiffObjConfig,
_sections: &[ObjSection],
) -> Result<ProcessCodeResult> { ) -> Result<ProcessCodeResult> {
let start_addr = address as u32; let start_addr = address as u32;
let end_addr = start_addr + code.len() as u32; let end_addr = start_addr + code.len() as u32;
@ -216,6 +217,7 @@ impl ObjArch for ObjArchArm {
mnemonic: Cow::Borrowed(parsed_ins.mnemonic), mnemonic: Cow::Borrowed(parsed_ins.mnemonic),
args, args,
reloc, reloc,
fake_pool_reloc: None,
branch_dest, branch_dest,
line, line,
formatted: parsed_ins.display(display_options).to_string(), formatted: parsed_ins.display(display_options).to_string(),

View File

@ -29,6 +29,7 @@ impl ObjArch for ObjArchArm64 {
relocations: &[ObjReloc], relocations: &[ObjReloc],
line_info: &BTreeMap<u64, u32>, line_info: &BTreeMap<u64, u32>,
config: &DiffObjConfig, config: &DiffObjConfig,
_sections: &[ObjSection],
) -> Result<ProcessCodeResult> { ) -> Result<ProcessCodeResult> {
let start_address = address; let start_address = address;
let end_address = address + code.len() as u64; let end_address = address + code.len() as u64;
@ -59,6 +60,7 @@ impl ObjArch for ObjArchArm64 {
mnemonic: Cow::Borrowed("<invalid>"), mnemonic: Cow::Borrowed("<invalid>"),
args: vec![], args: vec![],
reloc: None, reloc: None,
fake_pool_reloc: None,
branch_dest: None, branch_dest: None,
line: None, line: None,
formatted: "".to_string(), formatted: "".to_string(),
@ -121,6 +123,7 @@ impl ObjArch for ObjArchArm64 {
mnemonic: Cow::Borrowed(mnemonic), mnemonic: Cow::Borrowed(mnemonic),
args, args,
reloc, reloc,
fake_pool_reloc: None,
branch_dest, branch_dest,
line, line,
formatted: ins.to_string(), formatted: ins.to_string(),

View File

@ -87,6 +87,7 @@ impl ObjArch for ObjArchMips {
relocations: &[ObjReloc], relocations: &[ObjReloc],
line_info: &BTreeMap<u64, u32>, line_info: &BTreeMap<u64, u32>,
config: &DiffObjConfig, config: &DiffObjConfig,
_sections: &[ObjSection],
) -> Result<ProcessCodeResult> { ) -> Result<ProcessCodeResult> {
let _guard = RABBITIZER_MUTEX.lock().map_err(|e| anyhow!("Failed to lock mutex: {e}"))?; let _guard = RABBITIZER_MUTEX.lock().map_err(|e| anyhow!("Failed to lock mutex: {e}"))?;
configure_rabbitizer(match config.mips_abi { configure_rabbitizer(match config.mips_abi {
@ -205,6 +206,7 @@ impl ObjArch for ObjArchMips {
mnemonic: Cow::Borrowed(mnemonic), mnemonic: Cow::Borrowed(mnemonic),
args, args,
reloc: reloc.cloned(), reloc: reloc.cloned(),
fake_pool_reloc: None,
branch_dest, branch_dest,
line, line,
formatted, formatted,

View File

@ -36,13 +36,22 @@ pub enum DataType {
impl DataType { impl DataType {
pub fn display_bytes<Endian: ByteOrder>(&self, bytes: &[u8]) -> Option<String> { pub fn display_bytes<Endian: ByteOrder>(&self, bytes: &[u8]) -> Option<String> {
// TODO: Attempt to interpret large symbols as arrays of a smaller type, if self.required_len().is_some_and(|l| bytes.len() < l) {
// fallback to intrepreting it as bytes. log::warn!("Failed to display a symbol value for a symbol whose size is too small for instruction referencing it.");
// https://github.com/encounter/objdiff/issues/124
if self.required_len().is_some_and(|l| bytes.len() != l) {
log::warn!("Failed to display a symbol value for a symbol whose size doesn't match the instruction referencing it.");
return None; return None;
} }
let mut bytes = bytes;
if self.required_len().is_some_and(|l| bytes.len() > l) {
// If the symbol's size is larger a single instance of this data type, we take just the
// bytes necessary for one of them in order to display the first element of the array.
bytes = &bytes[0..self.required_len().unwrap()];
// TODO: Attempt to interpret large symbols as arrays of a smaller type and show all
// elements of the array instead. https://github.com/encounter/objdiff/issues/124
// However, note that the stride of an array can not always be determined just by the
// data type guessed by the single instruction accessing it. There can also be arrays of
// structs that contain multiple elements of different types, so if other elements after
// the first one were to be displayed in this manner, they may be inaccurate.
}
match self { match self {
DataType::Int8 => { DataType::Int8 => {
@ -86,10 +95,10 @@ impl DataType {
} }
} }
DataType::Float => { DataType::Float => {
format!("Float: {}", Endian::read_f32(bytes)) format!("Float: {:?}f", Endian::read_f32(bytes))
} }
DataType::Double => { DataType::Double => {
format!("Double: {}", Endian::read_f64(bytes)) format!("Double: {:?}", Endian::read_f64(bytes))
} }
DataType::Bytes => { DataType::Bytes => {
format!("Bytes: {:#?}", bytes) format!("Bytes: {:#?}", bytes)
@ -117,6 +126,7 @@ impl DataType {
} }
pub trait ObjArch: Send + Sync { pub trait ObjArch: Send + Sync {
#[expect(clippy::too_many_arguments)]
fn process_code( fn process_code(
&self, &self,
address: u64, address: u64,
@ -125,6 +135,7 @@ pub trait ObjArch: Send + Sync {
relocations: &[ObjReloc], relocations: &[ObjReloc],
line_info: &BTreeMap<u64, u32>, line_info: &BTreeMap<u64, u32>,
config: &DiffObjConfig, config: &DiffObjConfig,
sections: &[ObjSection],
) -> Result<ProcessCodeResult>; ) -> Result<ProcessCodeResult>;
fn implcit_addend( fn implcit_addend(

View File

@ -1,4 +1,7 @@
use std::{borrow::Cow, collections::BTreeMap}; use std::{
borrow::Cow,
collections::{BTreeMap, HashMap},
};
use anyhow::{bail, ensure, Result}; use anyhow::{bail, ensure, Result};
use byteorder::BigEndian; use byteorder::BigEndian;
@ -7,7 +10,7 @@ use object::{
elf, File, Object, ObjectSection, ObjectSymbol, Relocation, RelocationFlags, RelocationTarget, elf, File, Object, ObjectSection, ObjectSymbol, Relocation, RelocationFlags, RelocationTarget,
Symbol, SymbolKind, Symbol, SymbolKind,
}; };
use ppc750cl::{Argument, InsIter, Opcode, GPR}; use ppc750cl::{Argument, InsIter, Opcode, ParsedIns, GPR};
use crate::{ use crate::{
arch::{DataType, ObjArch, ProcessCodeResult}, arch::{DataType, ObjArch, ProcessCodeResult},
@ -45,10 +48,13 @@ impl ObjArch for ObjArchPpc {
relocations: &[ObjReloc], relocations: &[ObjReloc],
line_info: &BTreeMap<u64, u32>, line_info: &BTreeMap<u64, u32>,
config: &DiffObjConfig, config: &DiffObjConfig,
sections: &[ObjSection],
) -> Result<ProcessCodeResult> { ) -> Result<ProcessCodeResult> {
let ins_count = code.len() / 4; let ins_count = code.len() / 4;
let mut ops = Vec::<u16>::with_capacity(ins_count); let mut ops = Vec::<u16>::with_capacity(ins_count);
let mut insts = Vec::<ObjIns>::with_capacity(ins_count); let mut insts = Vec::<ObjIns>::with_capacity(ins_count);
let fake_pool_reloc_for_addr =
generate_fake_pool_reloc_for_addr_mapping(address, code, relocations, sections);
for (cur_addr, mut ins) in InsIter::new(code, address as u32) { for (cur_addr, mut ins) in InsIter::new(code, address as u32) {
let reloc = relocations.iter().find(|r| (r.address as u32 & !3) == cur_addr); let reloc = relocations.iter().find(|r| (r.address as u32 & !3) == cur_addr);
if let Some(reloc) = reloc { if let Some(reloc) = reloc {
@ -146,6 +152,7 @@ impl ObjArch for ObjArchPpc {
mnemonic: Cow::Borrowed(simplified.mnemonic), mnemonic: Cow::Borrowed(simplified.mnemonic),
args, args,
reloc: reloc.cloned(), reloc: reloc.cloned(),
fake_pool_reloc: fake_pool_reloc_for_addr.get(&cur_addr).cloned(),
op: ins.op as u16, op: ins.op as u16,
branch_dest, branch_dest,
line, line,
@ -173,6 +180,7 @@ impl ObjArch for ObjArchPpc {
fn display_reloc(&self, flags: RelocationFlags) -> Cow<'static, str> { fn display_reloc(&self, flags: RelocationFlags) -> Cow<'static, str> {
match flags { match flags {
RelocationFlags::Elf { r_type } => match r_type { RelocationFlags::Elf { r_type } => match r_type {
elf::R_PPC_NONE => Cow::Borrowed("R_PPC_NONE"), // We use this for fake pool relocs
elf::R_PPC_ADDR16_LO => Cow::Borrowed("R_PPC_ADDR16_LO"), elf::R_PPC_ADDR16_LO => Cow::Borrowed("R_PPC_ADDR16_LO"),
elf::R_PPC_ADDR16_HI => Cow::Borrowed("R_PPC_ADDR16_HI"), elf::R_PPC_ADDR16_HI => Cow::Borrowed("R_PPC_ADDR16_HI"),
elf::R_PPC_ADDR16_HA => Cow::Borrowed("R_PPC_ADDR16_HA"), elf::R_PPC_ADDR16_HA => Cow::Borrowed("R_PPC_ADDR16_HA"),
@ -188,27 +196,16 @@ impl ObjArch for ObjArchPpc {
} }
fn guess_data_type(&self, instruction: &ObjIns) -> Option<super::DataType> { fn guess_data_type(&self, instruction: &ObjIns) -> Option<super::DataType> {
// Always shows the first string of the table. Not ideal, but it's really hard to find if instruction
// the actual string being referenced. .reloc
if instruction.reloc.as_ref().is_some_and(|r| r.target.name.starts_with("@stringBase")) { .as_ref()
.or(instruction.fake_pool_reloc.as_ref())
.is_some_and(|r| r.target.name.starts_with("@stringBase"))
{
return Some(DataType::String); return Some(DataType::String);
} }
match Opcode::from(instruction.op as u8) { guess_data_type_from_load_store_inst_op(Opcode::from(instruction.op as u8))
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,
}
} }
fn display_data_type(&self, ty: DataType, bytes: &[u8]) -> Option<String> { fn display_data_type(&self, ty: DataType, bytes: &[u8]) -> Option<String> {
@ -381,3 +378,177 @@ 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
}

View File

@ -34,6 +34,7 @@ impl ObjArch for ObjArchX86 {
relocations: &[ObjReloc], relocations: &[ObjReloc],
line_info: &BTreeMap<u64, u32>, line_info: &BTreeMap<u64, u32>,
config: &DiffObjConfig, config: &DiffObjConfig,
_sections: &[ObjSection],
) -> Result<ProcessCodeResult> { ) -> Result<ProcessCodeResult> {
let mut result = ProcessCodeResult { ops: Vec::new(), insts: Vec::new() }; let mut result = ProcessCodeResult { ops: Vec::new(), insts: Vec::new() };
let mut decoder = Decoder::with_ip(self.bits, code, address, DecoderOptions::NONE); let mut decoder = Decoder::with_ip(self.bits, code, address, DecoderOptions::NONE);
@ -54,6 +55,7 @@ impl ObjArch for ObjArchX86 {
mnemonic: Cow::Borrowed("<invalid>"), mnemonic: Cow::Borrowed("<invalid>"),
args: vec![], args: vec![],
reloc: None, reloc: None,
fake_pool_reloc: None,
branch_dest: None, branch_dest: None,
line: None, line: None,
formatted: String::new(), formatted: String::new(),
@ -79,6 +81,7 @@ impl ObjArch for ObjArchX86 {
mnemonic: Cow::Borrowed("<invalid>"), mnemonic: Cow::Borrowed("<invalid>"),
args: vec![], args: vec![],
reloc: reloc.cloned(), reloc: reloc.cloned(),
fake_pool_reloc: None,
branch_dest: None, branch_dest: None,
line, line,
formatted: String::new(), formatted: String::new(),

View File

@ -1,3 +1,4 @@
#![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,

View File

@ -1,3 +1,4 @@
#![allow(clippy::needless_lifetimes)] // Generated serde code
use std::ops::AddAssign; use std::ops::AddAssign;
use anyhow::{bail, Result}; use anyhow::{bail, Result};
@ -173,8 +174,7 @@ 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.starts_with(parent) && id.get(parent.len()..).is_some_and(|s| s.starts_with(sep))
&& id.get(parent.len()..).map_or(false, |s| s.starts_with(sep))
} }
let mut sub_categories = self let mut sub_categories = self
.categories .categories

View File

@ -28,6 +28,7 @@ pub fn process_code_symbol(
&section.relocations, &section.relocations,
&section.line_info, &section.line_info,
config, config,
&obj.sections,
) )
} }

View File

@ -106,6 +106,7 @@ pub struct ObjIns {
pub mnemonic: Cow<'static, str>, pub mnemonic: Cow<'static, str>,
pub args: Vec<ObjInsArg>, pub args: Vec<ObjInsArg>,
pub reloc: Option<ObjReloc>, pub reloc: Option<ObjReloc>,
pub fake_pool_reloc: Option<ObjReloc>,
pub branch_dest: Option<u64>, pub branch_dest: Option<u64>,
/// Line number /// Line number
pub line: Option<u32>, pub line: Option<u32>,

View File

@ -65,10 +65,7 @@ fn to_obj_symbol(
flags = ObjSymbolFlagSet(flags.0 | ObjSymbolFlags::Hidden); flags = ObjSymbolFlagSet(flags.0 | ObjSymbolFlags::Hidden);
} }
#[cfg(feature = "ppc")] #[cfg(feature = "ppc")]
if arch if arch.ppc().and_then(|a| a.extab.as_ref()).is_some_and(|e| e.contains_key(&symbol.index().0))
.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);
} }

View File

@ -25,7 +25,7 @@ wsl = []
[dependencies] [dependencies]
anyhow = "1.0" anyhow = "1.0"
bytes = "1.7" bytes = "1.9"
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.3" pollster = "0.4"
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.13" tempfile = "3.14"
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
@ -95,7 +95,7 @@ exec = "0.3"
# native: # native:
[target.'cfg(not(target_arch = "wasm32"))'.dependencies] [target.'cfg(not(target_arch = "wasm32"))'.dependencies]
tracing-subscriber = "0.3" tracing-subscriber = { version = "0.3", features = ["env-filter"] }
# web: # web:
[target.'cfg(target_arch = "wasm32")'.dependencies] [target.'cfg(target_arch = "wasm32")'.dependencies]

View File

@ -152,16 +152,14 @@ 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 || { let handle = std::thread::spawn(move || match run(context_inner, rx) {
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() { w.error = Some(e);
w.error = Some(e);
}
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);

View File

@ -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.map_or(false, |ctx| ctx.has_section()) if !right_ctx.is_some_and(|ctx| ctx.has_section())
&& !left_ctx.map_or(false, |ctx| ctx.has_section()) && !left_ctx.is_some_and(|ctx| ctx.has_section())
{ {
return Some(DiffViewAction::Navigate(DiffViewNavigation::symbol_diff())); return Some(DiffViewAction::Navigate(DiffViewNavigation::symbol_diff()));
} }

View File

@ -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.map_or(false, |ctx| !ctx.has_symbol()) { if left_diff_symbol.is_some() && right_ctx.is_some_and(|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.map_or(false, |ctx| !ctx.has_symbol()) { } else if right_diff_symbol.is_some() && left_ctx.is_some_and(|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.map_or(false, |ctx| !ctx.has_symbol()) if right_ctx.is_some_and(|ctx| !ctx.has_symbol())
&& left_ctx.map_or(false, |ctx| !ctx.has_symbol()) && left_ctx.is_some_and(|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.map_or(false, |ctx| ctx.has_symbol()), && left_ctx.is_some_and(|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)")

View File

@ -1,4 +1,4 @@
use std::default::Default; use std::{cmp::Ordering, default::Default};
use egui::{text::LayoutJob, Id, Label, Response, RichText, Sense, Widget}; use egui::{text::LayoutJob, Id, Label, Response, RichText, Sense, Widget};
use egui_extras::TableRow; use egui_extras::TableRow;
@ -118,9 +118,17 @@ fn ins_hover_ui(
} }
} }
if let Some(reloc) = &ins.reloc { if let Some(reloc) = ins.reloc.as_ref().or(ins.fake_pool_reloc.as_ref()) {
ui.label(format!("Relocation type: {}", obj.arch.display_reloc(reloc.flags))); ui.label(format!("Relocation type: {}", obj.arch.display_reloc(reloc.flags)));
ui.colored_label(appearance.highlight_color, format!("Name: {}", reloc.target.name)); let addend_str = match reloc.addend.cmp(&0i64) {
Ordering::Greater => format!("+{:x}", reloc.addend),
Ordering::Less => format!("-{:x}", -reloc.addend),
_ => "".to_string(),
};
ui.colored_label(
appearance.highlight_color,
format!("Name: {}{}", reloc.target.name, addend_str),
);
if let Some(orig_section_index) = reloc.target.orig_section_index { if let Some(orig_section_index) = reloc.target.orig_section_index {
if let Some(section) = if let Some(section) =
obj.sections.iter().find(|s| s.orig_index == orig_section_index) obj.sections.iter().find(|s| s.orig_index == orig_section_index)
@ -132,18 +140,18 @@ fn ins_hover_ui(
} }
ui.colored_label( ui.colored_label(
appearance.highlight_color, appearance.highlight_color,
format!("Address: {:x}", reloc.target.address), format!("Address: {:x}{}", reloc.target.address, addend_str),
); );
ui.colored_label( ui.colored_label(
appearance.highlight_color, appearance.highlight_color,
format!("Size: {:x}", reloc.target.size), format!("Size: {:x}", reloc.target.size),
); );
if let Some(s) = obj if reloc.addend >= 0 && reloc.target.bytes.len() > reloc.addend as usize {
.arch if let Some(s) = obj.arch.guess_data_type(ins).and_then(|ty| {
.guess_data_type(ins) obj.arch.display_data_type(ty, &reloc.target.bytes[reloc.addend as usize..])
.and_then(|ty| obj.arch.display_data_type(ty, &reloc.target.bytes)) }) {
{ ui.colored_label(appearance.highlight_color, s);
ui.colored_label(appearance.highlight_color, s); }
} }
} else { } else {
ui.colored_label(appearance.highlight_color, "Extern".to_string()); ui.colored_label(appearance.highlight_color, "Extern".to_string());
@ -640,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.map_or(false, |ctx| !ctx.has_symbol()) { if left_diff_symbol.is_some() && right_ctx.is_some_and(|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);
@ -650,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.map_or(false, |ctx| !ctx.has_symbol()) { } else if right_diff_symbol.is_some() && left_ctx.is_some_and(|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);
@ -663,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.map_or(false, |ctx| !ctx.has_symbol()) if right_ctx.is_some_and(|ctx| !ctx.has_symbol())
&& left_ctx.map_or(false, |ctx| !ctx.has_symbol()) && left_ctx.is_some_and(|ctx| !ctx.has_symbol())
{ {
return Some(DiffViewAction::Navigate(DiffViewNavigation::symbol_diff())); return Some(DiffViewAction::Navigate(DiffViewNavigation::symbol_diff()));
} }
@ -683,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.map_or(false, |ctx| ctx.has_symbol()), && left_ctx.is_some_and(|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)")
@ -707,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.map_or(false, |m| m.has_symbol()) if right_ctx.is_some_and(|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")
@ -777,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.map_or(false, |m| m.has_symbol()) { if left_ctx.is_some_and(|m| m.has_symbol()) {
ui.separator(); ui.separator();
if ui if ui
.button("Change base") .button("Change base")