mirror of
https://github.com/encounter/objdiff.git
synced 2026-07-10 12:18:36 -07:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a015971c20 | |||
| e67d5998b3 | |||
| 91bc23edfc | |||
| c9c3b32376 | |||
| 0dc123b064 | |||
| 1e62d4664c |
Generated
+15
-8
@@ -722,7 +722,7 @@ checksum = "fe6d2e5af09e8c8ad56c969f2157a3d4238cebc7c55f0a517728c38f7b200f81"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"termcolor",
|
||||
"unicode-width 0.1.14",
|
||||
"unicode-width 0.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3437,7 +3437,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "objdiff-cli"
|
||||
version = "3.0.0-beta.12"
|
||||
version = "3.0.0-beta.13"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argp",
|
||||
@@ -3460,7 +3460,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "objdiff-core"
|
||||
version = "3.0.0-beta.12"
|
||||
version = "3.0.0-beta.13"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"arm-attr",
|
||||
@@ -3505,6 +3505,7 @@ dependencies = [
|
||||
"syn",
|
||||
"tempfile",
|
||||
"time",
|
||||
"typed-arena",
|
||||
"typed-path",
|
||||
"unarm",
|
||||
"winapi",
|
||||
@@ -3514,7 +3515,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "objdiff-gui"
|
||||
version = "3.0.0-beta.12"
|
||||
version = "3.0.0-beta.13"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cfg-if",
|
||||
@@ -3550,7 +3551,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "objdiff-wasm"
|
||||
version = "3.0.0-beta.12"
|
||||
version = "3.0.0-beta.13"
|
||||
dependencies = [
|
||||
"log",
|
||||
"objdiff-core",
|
||||
@@ -4014,7 +4015,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac6c3320f9abac597dcbc668774ef006702672474aad53c6d596b62e487b40b1"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"itertools 0.13.0",
|
||||
"itertools 0.14.0",
|
||||
"log",
|
||||
"multimap",
|
||||
"once_cell",
|
||||
@@ -4034,7 +4035,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9120690fafc389a67ba3803df527d0ec9cbbc9cc45e4cc20b332996dfb672425"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools 0.13.0",
|
||||
"itertools 0.14.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
@@ -5620,6 +5621,12 @@ dependencies = [
|
||||
"rustc-hash 2.1.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typed-arena"
|
||||
version = "2.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a"
|
||||
|
||||
[[package]]
|
||||
name = "typed-path"
|
||||
version = "0.11.0"
|
||||
@@ -6257,7 +6264,7 @@ version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
||||
dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ strip = "debuginfo"
|
||||
codegen-units = 1
|
||||
|
||||
[workspace.package]
|
||||
version = "3.0.0-beta.12"
|
||||
version = "3.0.0-beta.13"
|
||||
authors = ["Luke Street <luke@street.dev>"]
|
||||
edition = "2024"
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
@@ -248,13 +248,12 @@ fn report_object(
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if let Some(existing_functions) = &mut existing_functions {
|
||||
if (symbol.flags.contains(SymbolFlag::Global)
|
||||
if let Some(existing_functions) = &mut existing_functions
|
||||
&& (symbol.flags.contains(SymbolFlag::Global)
|
||||
|| symbol.flags.contains(SymbolFlag::Weak))
|
||||
&& !existing_functions.insert(symbol.name.clone())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
&& !existing_functions.insert(symbol.name.clone())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let match_percent = symbol_diff.match_percent.unwrap_or_else(|| {
|
||||
// Support cases where we don't have a target object,
|
||||
|
||||
@@ -170,32 +170,31 @@ impl UiView for FunctionDiffUi {
|
||||
|
||||
let mut prev_text = None;
|
||||
let mut prev_margin_text = None;
|
||||
if self.three_way {
|
||||
if let Some((obj, symbol_idx, symbol_diff)) =
|
||||
if self.three_way
|
||||
&& let Some((obj, symbol_idx, symbol_diff)) =
|
||||
get_symbol(state.prev_obj.as_ref(), self.prev_sym)
|
||||
{
|
||||
let mut text = Text::default();
|
||||
let rect = content_chunks[4].inner(Margin::new(0, 1));
|
||||
self.print_sym(
|
||||
&mut text,
|
||||
obj,
|
||||
symbol_idx,
|
||||
symbol_diff,
|
||||
&state.diff_obj_config,
|
||||
rect,
|
||||
&self.right_highlight,
|
||||
result,
|
||||
true,
|
||||
);
|
||||
max_width = max_width.max(text.width());
|
||||
prev_text = Some(text);
|
||||
{
|
||||
let mut text = Text::default();
|
||||
let rect = content_chunks[4].inner(Margin::new(0, 1));
|
||||
self.print_sym(
|
||||
&mut text,
|
||||
obj,
|
||||
symbol_idx,
|
||||
symbol_diff,
|
||||
&state.diff_obj_config,
|
||||
rect,
|
||||
&self.right_highlight,
|
||||
result,
|
||||
true,
|
||||
);
|
||||
max_width = max_width.max(text.width());
|
||||
prev_text = Some(text);
|
||||
|
||||
// Render margin
|
||||
let mut text = Text::default();
|
||||
let rect = content_chunks[3].inner(Margin::new(1, 1));
|
||||
self.print_margin(&mut text, symbol_diff, rect);
|
||||
prev_margin_text = Some(text);
|
||||
}
|
||||
// Render margin
|
||||
let mut text = Text::default();
|
||||
let rect = content_chunks[3].inner(Margin::new(1, 1));
|
||||
self.print_margin(&mut text, symbol_diff, rect);
|
||||
prev_margin_text = Some(text);
|
||||
}
|
||||
|
||||
let max_scroll_x =
|
||||
@@ -561,10 +560,12 @@ impl FunctionDiffUi {
|
||||
let len = label_text.len();
|
||||
let highlighted =
|
||||
highlight_kind != HighlightKind::None && *highlight == highlight_kind;
|
||||
if let Some((cx, cy)) = result.click_xy {
|
||||
if cx >= sx && cx < sx + len as u16 && cy == sy {
|
||||
new_highlight = Some(highlight_kind);
|
||||
}
|
||||
if let Some((cx, cy)) = result.click_xy
|
||||
&& cx >= sx
|
||||
&& cx < sx + len as u16
|
||||
&& cy == sy
|
||||
{
|
||||
new_highlight = Some(highlight_kind);
|
||||
}
|
||||
let mut style = Style::new().fg(match segment.color {
|
||||
DiffTextColor::Normal => Color::Gray,
|
||||
|
||||
@@ -62,7 +62,10 @@ config = [
|
||||
"dep:semver",
|
||||
"dep:typed-path",
|
||||
]
|
||||
dwarf = ["dep:gimli"]
|
||||
dwarf = [
|
||||
"dep:gimli",
|
||||
"dep:typed-arena",
|
||||
]
|
||||
serde = [
|
||||
"dep:pbjson",
|
||||
"dep:pbjson-build",
|
||||
@@ -78,6 +81,7 @@ std = [
|
||||
"prost?/std",
|
||||
"serde?/std",
|
||||
"similar?/std",
|
||||
"typed-arena?/std",
|
||||
"typed-path?/std",
|
||||
"dep:filetime",
|
||||
"dep:memmap2",
|
||||
@@ -143,6 +147,7 @@ serde_json = { version = "1.0", default-features = false, features = ["alloc"],
|
||||
|
||||
# dwarf
|
||||
gimli = { version = "0.32", default-features = false, features = ["read"], optional = true }
|
||||
typed-arena = { version = "2.0", default-features = false, optional = true }
|
||||
|
||||
# ppc
|
||||
cwdemangle = { version = "1.0", optional = true }
|
||||
|
||||
+282
-266
File diff suppressed because it is too large
Load Diff
@@ -15,7 +15,7 @@ use crate::{
|
||||
diff::{DiffObjConfig, MipsAbi, MipsInstrCategory, display::InstructionPart},
|
||||
obj::{
|
||||
InstructionArg, InstructionArgValue, InstructionRef, Relocation, RelocationFlags,
|
||||
ResolvedInstructionRef, ResolvedRelocation, SymbolFlag, SymbolFlagSet,
|
||||
ResolvedInstructionRef, ResolvedRelocation, Section, Symbol, SymbolFlag, SymbolFlagSet,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -140,6 +140,14 @@ impl ArchMips {
|
||||
})
|
||||
}
|
||||
|
||||
fn default_instruction_flags(&self) -> rabbitizer::InstructionFlags {
|
||||
match self.isa_extension {
|
||||
Some(extension) => rabbitizer::InstructionFlags::new_extension(extension),
|
||||
None => rabbitizer::InstructionFlags::new(IsaVersion::MIPS_III),
|
||||
}
|
||||
.with_abi(self.abi)
|
||||
}
|
||||
|
||||
fn instruction_flags(&self, diff_config: &DiffObjConfig) -> rabbitizer::InstructionFlags {
|
||||
let isa_extension = match diff_config.mips_instr_category {
|
||||
MipsInstrCategory::Auto => self.isa_extension,
|
||||
@@ -151,7 +159,7 @@ impl ArchMips {
|
||||
};
|
||||
match isa_extension {
|
||||
Some(extension) => rabbitizer::InstructionFlags::new_extension(extension),
|
||||
None => rabbitizer::InstructionFlags::new_isa(IsaVersion::MIPS_III, None),
|
||||
None => rabbitizer::InstructionFlags::new(IsaVersion::MIPS_III),
|
||||
}
|
||||
.with_abi(match diff_config.mips_abi {
|
||||
MipsAbi::Auto => self.abi,
|
||||
@@ -234,17 +242,16 @@ impl Arch for ArchMips {
|
||||
object::RelocationFlags::Elf { r_type } => {
|
||||
if relocation.has_implicit_addend() {
|
||||
// Check for paired R_MIPS_HI16 and R_MIPS_LO16 relocations.
|
||||
if let elf::R_MIPS_HI16 | elf::R_MIPS_LO16 = r_type {
|
||||
if let Some(addend) = self
|
||||
if let elf::R_MIPS_HI16 | elf::R_MIPS_LO16 = r_type
|
||||
&& let Some(addend) = self
|
||||
.paired_relocations
|
||||
.get(section.index().0)
|
||||
.and_then(|m| m.get(&address).copied())
|
||||
{
|
||||
return Ok(Some(RelocationOverride {
|
||||
target: RelocationOverrideTarget::Keep,
|
||||
addend,
|
||||
}));
|
||||
}
|
||||
{
|
||||
return Ok(Some(RelocationOverride {
|
||||
target: RelocationOverrideTarget::Keep,
|
||||
addend,
|
||||
}));
|
||||
}
|
||||
|
||||
let data = section.data()?;
|
||||
@@ -331,6 +338,36 @@ impl Arch for ArchMips {
|
||||
}
|
||||
flags
|
||||
}
|
||||
|
||||
fn infer_function_size(
|
||||
&self,
|
||||
symbol: &Symbol,
|
||||
section: &Section,
|
||||
next_address: u64,
|
||||
) -> Result<u64> {
|
||||
// Trim any trailing 4-byte zeroes from the end (nops)
|
||||
let mut new_address = next_address;
|
||||
while new_address >= symbol.address + 4
|
||||
&& let Some(data) = section.data_range(new_address - 4, 4)
|
||||
&& data == [0u8; 4]
|
||||
{
|
||||
new_address -= 4;
|
||||
}
|
||||
// Check if the last instruction has a delay slot, if so, include the delay slot nop
|
||||
if new_address + 4 <= next_address
|
||||
&& new_address >= symbol.address + 4
|
||||
&& let Some(data) = section.data_range(new_address - 4, 4)
|
||||
&& let instruction = rabbitizer::Instruction::new(
|
||||
self.endianness.read_u32_bytes(data.try_into().unwrap()),
|
||||
Vram::new((new_address - 4) as u32),
|
||||
self.default_instruction_flags(),
|
||||
)
|
||||
&& instruction.opcode().has_delay_slot()
|
||||
{
|
||||
new_address += 4;
|
||||
}
|
||||
Ok(new_address.saturating_sub(symbol.address))
|
||||
}
|
||||
}
|
||||
|
||||
fn push_args(
|
||||
|
||||
@@ -215,10 +215,10 @@ impl dyn Arch {
|
||||
|
||||
// Remove any branch destinations that are outside the function range
|
||||
for ins in result.iter_mut() {
|
||||
if let Some(branch_dest) = ins.branch_dest {
|
||||
if branch_dest < function_start || branch_dest >= function_end {
|
||||
ins.branch_dest = None;
|
||||
}
|
||||
if let Some(branch_dest) = ins.branch_dest
|
||||
&& (branch_dest < function_start || branch_dest >= function_end)
|
||||
{
|
||||
ins.branch_dest = None;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -406,6 +406,15 @@ pub trait Arch: Send + Sync + Debug {
|
||||
) -> Vec<ContextItem> {
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
fn infer_function_size(
|
||||
&self,
|
||||
symbol: &Symbol,
|
||||
_section: &Section,
|
||||
next_address: u64,
|
||||
) -> Result<u64> {
|
||||
Ok(next_address.saturating_sub(symbol.address))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_arch(object: &object::File) -> Result<Box<dyn Arch>> {
|
||||
|
||||
@@ -514,14 +514,14 @@ pub fn ppc_data_flow_analysis(
|
||||
}
|
||||
|
||||
fn get_string_data(obj: &Object, symbol_index: usize, offset: Simm) -> Option<&str> {
|
||||
if let Some(sym) = obj.symbols.get(symbol_index) {
|
||||
if sym.name.starts_with("@stringBase") && offset.0 != 0 {
|
||||
if let Some(data) = obj.symbol_data(symbol_index) {
|
||||
let bytes = &data[offset.0 as usize..];
|
||||
if let Ok(Ok(str)) = CStr::from_bytes_until_nul(bytes).map(|x| x.to_str()) {
|
||||
return Some(str);
|
||||
}
|
||||
}
|
||||
if let Some(sym) = obj.symbols.get(symbol_index)
|
||||
&& sym.name.starts_with("@stringBase")
|
||||
&& offset.0 != 0
|
||||
&& let Some(data) = obj.symbol_data(symbol_index)
|
||||
{
|
||||
let bytes = &data[offset.0 as usize..];
|
||||
if let Ok(Ok(str)) = CStr::from_bytes_until_nul(bytes).map(|x| x.to_str()) {
|
||||
return Some(str);
|
||||
}
|
||||
}
|
||||
None
|
||||
@@ -577,19 +577,17 @@ fn generate_flow_analysis_result(
|
||||
let registers = register_state_at.get(index as usize).unwrap_or(&default_register_state);
|
||||
if let (powerpc::Opcode::Addi, Argument::GPR(rel), Argument::Simm(offset)) =
|
||||
(ins.op, args[1], args[2])
|
||||
&& let RegisterContent::Symbol(sym_index) = registers[rel]
|
||||
&& let Some(str) = get_string_data(obj, sym_index, offset)
|
||||
{
|
||||
if let RegisterContent::Symbol(sym_index) = registers[rel] {
|
||||
if let Some(str) = get_string_data(obj, sym_index, offset) {
|
||||
// Show the string constant in the analysis result
|
||||
let formatted = format!("\"{str}\"");
|
||||
analysis_result.set_argument_value_at_address(
|
||||
ins_address,
|
||||
2,
|
||||
FlowAnalysisValue::Text(clamp_text_length(formatted, 20)),
|
||||
);
|
||||
// Don't continue, we want to show the stringbase value as well
|
||||
}
|
||||
}
|
||||
// Show the string constant in the analysis result
|
||||
let formatted = format!("\"{str}\"");
|
||||
analysis_result.set_argument_value_at_address(
|
||||
ins_address,
|
||||
2,
|
||||
FlowAnalysisValue::Text(clamp_text_length(formatted, 20)),
|
||||
);
|
||||
// Don't continue, we want to show the stringbase value as well
|
||||
}
|
||||
|
||||
let is_store = is_store_instruction(ins.op);
|
||||
|
||||
@@ -20,7 +20,7 @@ use crate::{
|
||||
},
|
||||
obj::{
|
||||
FlowAnalysisResult, InstructionRef, Object, Relocation, RelocationFlags,
|
||||
ResolvedInstructionRef, ResolvedRelocation, Symbol, SymbolFlag, SymbolFlagSet,
|
||||
ResolvedInstructionRef, ResolvedRelocation, Section, Symbol, SymbolFlag, SymbolFlagSet,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -66,7 +66,9 @@ impl ArchPpc {
|
||||
if file.is_64() {
|
||||
powerpc::Extension::Ppc64 | powerpc::Extension::AltiVec
|
||||
} else {
|
||||
powerpc::Extension::AltiVec.into()
|
||||
// Gekko/Broadway objects often use the EF_PPC_EMB flag,
|
||||
// but ProDG in particular does not emit it.
|
||||
powerpc::Extensions::gekko_broadway()
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -455,6 +457,22 @@ impl Arch for ArchPpc {
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
fn infer_function_size(
|
||||
&self,
|
||||
symbol: &Symbol,
|
||||
section: &Section,
|
||||
mut next_address: u64,
|
||||
) -> Result<u64> {
|
||||
// Trim any trailing 4-byte zeroes from the end (padding)
|
||||
while next_address >= symbol.address + 4
|
||||
&& let Some(data) = section.data_range(next_address - 4, 4)
|
||||
&& data == [0u8; 4]
|
||||
{
|
||||
next_address -= 4;
|
||||
}
|
||||
Ok(next_address.saturating_sub(symbol.address))
|
||||
}
|
||||
}
|
||||
|
||||
impl ArchPpc {
|
||||
@@ -850,44 +868,43 @@ fn generate_fake_pool_relocations_for_function(
|
||||
break;
|
||||
}
|
||||
}
|
||||
if let Some(branch_dest) = branch_dest {
|
||||
if branch_dest >= func_address as u32
|
||||
&& (branch_dest - func_address as u32) < code.len() as u32
|
||||
{
|
||||
let dest_offset_into_func = branch_dest - func_address as u32;
|
||||
let dest_code_slice = &code[dest_offset_into_func as usize..];
|
||||
match ins.op {
|
||||
Opcode::Bc => {
|
||||
// Conditional branch.
|
||||
// Add the branch destination to the queue to do later.
|
||||
if let Some(branch_dest) = branch_dest
|
||||
&& branch_dest >= func_address as u32
|
||||
&& (branch_dest - func_address as u32) < code.len() as u32
|
||||
{
|
||||
let dest_offset_into_func = branch_dest - func_address as u32;
|
||||
let dest_code_slice = &code[dest_offset_into_func as usize..];
|
||||
match ins.op {
|
||||
Opcode::Bc => {
|
||||
// Conditional branch.
|
||||
// Add the branch destination to the queue to do later.
|
||||
ins_iters_with_gpr_state.push((
|
||||
InsIter::new(dest_code_slice, branch_dest, extensions),
|
||||
gpr_pool_relocs.clone(),
|
||||
));
|
||||
// Then continue on with the current iterator.
|
||||
}
|
||||
Opcode::B => {
|
||||
if simplified.mnemonic != "bl" {
|
||||
// Unconditional branch.
|
||||
// Add the branch destination to the queue.
|
||||
ins_iters_with_gpr_state.push((
|
||||
InsIter::new(dest_code_slice, branch_dest, extensions),
|
||||
gpr_pool_relocs.clone(),
|
||||
));
|
||||
// Then continue on with the current iterator.
|
||||
// Break out of the current iterator so we can do the newly added one.
|
||||
break;
|
||||
}
|
||||
Opcode::B => {
|
||||
if simplified.mnemonic != "bl" {
|
||||
// Unconditional branch.
|
||||
// Add the branch destination to the queue.
|
||||
ins_iters_with_gpr_state.push((
|
||||
InsIter::new(dest_code_slice, branch_dest, extensions),
|
||||
gpr_pool_relocs.clone(),
|
||||
));
|
||||
// Break out of the current iterator so we can do the newly added one.
|
||||
break;
|
||||
}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
if let Opcode::Bcctr = ins.op {
|
||||
if simplified.mnemonic == "bctr" {
|
||||
// Unconditional branch to count register.
|
||||
// Likely a jump table.
|
||||
gpr_state_at_bctr.insert(cur_addr, gpr_pool_relocs.clone());
|
||||
}
|
||||
if let Opcode::Bcctr = ins.op
|
||||
&& simplified.mnemonic == "bctr"
|
||||
{
|
||||
// Unconditional branch to count register.
|
||||
// Likely a jump table.
|
||||
gpr_state_at_bctr.insert(cur_addr, gpr_pool_relocs.clone());
|
||||
}
|
||||
|
||||
// Then handle keeping track of which GPR contains which pool relocation.
|
||||
|
||||
@@ -11,7 +11,7 @@ use object::{Endian as _, Object as _, ObjectSection as _, elf, pe};
|
||||
use crate::{
|
||||
arch::{Arch, RelocationOverride, RelocationOverrideTarget},
|
||||
diff::{DiffObjConfig, X86Formatter, display::InstructionPart},
|
||||
obj::{InstructionRef, Relocation, RelocationFlags, ResolvedInstructionRef},
|
||||
obj::{InstructionRef, Relocation, RelocationFlags, ResolvedInstructionRef, Section, Symbol},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -303,6 +303,52 @@ impl Arch for ArchX86 {
|
||||
fn data_reloc_size(&self, flags: RelocationFlags) -> usize {
|
||||
self.reloc_size(flags).unwrap_or(1)
|
||||
}
|
||||
|
||||
fn infer_function_size(
|
||||
&self,
|
||||
symbol: &Symbol,
|
||||
section: &Section,
|
||||
next_address: u64,
|
||||
) -> Result<u64> {
|
||||
let Ok(size) = (next_address - symbol.address).try_into() else {
|
||||
return Ok(next_address.saturating_sub(symbol.address));
|
||||
};
|
||||
let Some(code) = section.data_range(symbol.address, size) else {
|
||||
return Ok(0);
|
||||
};
|
||||
// Decode instructions to find the last non-NOP instruction
|
||||
let mut decoder = self.decoder(code, symbol.address);
|
||||
let mut instruction = Instruction::default();
|
||||
let mut new_address = 0;
|
||||
let mut reloc_iter = section.relocations.iter().peekable();
|
||||
'outer: while decoder.can_decode() {
|
||||
let address = decoder.ip();
|
||||
while let Some(reloc) = reloc_iter.peek() {
|
||||
match reloc.address.cmp(&address) {
|
||||
Ordering::Less => {
|
||||
reloc_iter.next();
|
||||
}
|
||||
Ordering::Equal => {
|
||||
// If the instruction starts at a relocation, it's inline data
|
||||
let reloc_size = self.reloc_size(reloc.flags).with_context(|| {
|
||||
format!("Unsupported inline x86 relocation {:?}", reloc.flags)
|
||||
})?;
|
||||
if decoder.set_position(decoder.position() + reloc_size).is_ok() {
|
||||
new_address = address + reloc_size as u64;
|
||||
decoder.set_ip(new_address);
|
||||
continue 'outer;
|
||||
}
|
||||
}
|
||||
Ordering::Greater => break,
|
||||
}
|
||||
}
|
||||
decoder.decode_out(&mut instruction);
|
||||
if instruction.mnemonic() != iced_x86::Mnemonic::Nop {
|
||||
new_address = instruction.next_ip();
|
||||
}
|
||||
}
|
||||
Ok(new_address.saturating_sub(symbol.address))
|
||||
}
|
||||
}
|
||||
|
||||
struct InstructionFormatterOutput<'a> {
|
||||
|
||||
@@ -385,13 +385,13 @@ pub fn symbol_context(obj: &Object, symbol_index: usize) -> Vec<ContextItem> {
|
||||
if let Some(name) = &symbol.demangled_name {
|
||||
out.push(ContextItem::Copy { value: name.clone(), label: None });
|
||||
}
|
||||
if symbol.section.is_some() {
|
||||
if let Some(address) = symbol.virtual_address {
|
||||
out.push(ContextItem::Copy {
|
||||
value: format!("{address:x}"),
|
||||
label: Some("virtual address".to_string()),
|
||||
});
|
||||
}
|
||||
if symbol.section.is_some()
|
||||
&& let Some(address) = symbol.virtual_address
|
||||
{
|
||||
out.push(ContextItem::Copy {
|
||||
value: format!("{address:x}"),
|
||||
label: Some("virtual address".to_string()),
|
||||
});
|
||||
}
|
||||
out.append(&mut obj.arch.symbol_context(obj, symbol_index));
|
||||
out
|
||||
|
||||
@@ -467,15 +467,15 @@ fn apply_symbol_mappings(
|
||||
) -> Result<()> {
|
||||
// If we're selecting a symbol to use as a comparison, mark it as used
|
||||
// This ensures that we don't match it to another symbol at any point
|
||||
if let Some(left_name) = &mapping_config.selecting_left {
|
||||
if let Some(left_symbol) = left.symbol_by_name(left_name) {
|
||||
left_used.insert(left_symbol);
|
||||
}
|
||||
if let Some(left_name) = &mapping_config.selecting_left
|
||||
&& let Some(left_symbol) = left.symbol_by_name(left_name)
|
||||
{
|
||||
left_used.insert(left_symbol);
|
||||
}
|
||||
if let Some(right_name) = &mapping_config.selecting_right {
|
||||
if let Some(right_symbol) = right.symbol_by_name(right_name) {
|
||||
right_used.insert(right_symbol);
|
||||
}
|
||||
if let Some(right_name) = &mapping_config.selecting_right
|
||||
&& let Some(right_symbol) = right.symbol_by_name(right_name)
|
||||
{
|
||||
right_used.insert(right_symbol);
|
||||
}
|
||||
|
||||
// Apply manual symbol mappings
|
||||
@@ -639,17 +639,16 @@ fn find_symbol(
|
||||
// If they are at the same address in the same section
|
||||
if in_symbol.name.starts_with('@')
|
||||
&& matches!(section_kind, SectionKind::Data | SectionKind::Bss)
|
||||
{
|
||||
if let Some((symbol_idx, _)) = unmatched_symbols(obj, used).find(|(_, symbol)| {
|
||||
&& let Some((symbol_idx, _)) = unmatched_symbols(obj, used).find(|(_, symbol)| {
|
||||
let Some(section_index) = symbol.section else {
|
||||
return false;
|
||||
};
|
||||
symbol.name.starts_with('@')
|
||||
&& symbol.address == in_symbol.address
|
||||
&& obj.sections[section_index].name == section_name
|
||||
}) {
|
||||
return Some(symbol_idx);
|
||||
}
|
||||
})
|
||||
{
|
||||
return Some(symbol_idx);
|
||||
}
|
||||
// Match Metrowerks symbol$1234 against symbol$2345
|
||||
if let Some((prefix, suffix)) = in_symbol.name.split_once('$') {
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
use alloc::{borrow::Cow, vec::Vec};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use object::{Object as _, ObjectSection as _};
|
||||
use typed_arena::Arena;
|
||||
|
||||
use crate::obj::{Section, SectionKind};
|
||||
|
||||
/// Parse line information from DWARF 2+ sections.
|
||||
pub(crate) fn parse_line_info_dwarf2(
|
||||
obj_file: &object::File,
|
||||
sections: &mut [Section],
|
||||
) -> Result<()> {
|
||||
let arena_data = Arena::new();
|
||||
let arena_relocations = Arena::new();
|
||||
let endian = match obj_file.endianness() {
|
||||
object::Endianness::Little => gimli::RunTimeEndian::Little,
|
||||
object::Endianness::Big => gimli::RunTimeEndian::Big,
|
||||
};
|
||||
let dwarf = gimli::Dwarf::load(|id: gimli::SectionId| -> Result<_> {
|
||||
load_file_section(id, obj_file, endian, &arena_data, &arena_relocations)
|
||||
})
|
||||
.context("loading DWARF sections")?;
|
||||
|
||||
let mut iter = dwarf.units();
|
||||
if let Some(header) = iter.next().map_err(|e| gimli_error(e, "iterating over DWARF units"))? {
|
||||
let unit = dwarf.unit(header).map_err(|e| gimli_error(e, "loading DWARF unit"))?;
|
||||
if let Some(program) = unit.line_program.clone() {
|
||||
let mut text_sections = sections.iter_mut().filter(|s| s.kind == SectionKind::Code);
|
||||
let mut lines = text_sections.next().map(|section| &mut section.line_info);
|
||||
|
||||
let mut rows = program.rows();
|
||||
while let Some((_header, row)) =
|
||||
rows.next_row().map_err(|e| gimli_error(e, "loading program row"))?
|
||||
{
|
||||
if let (Some(line), Some(lines)) = (row.line(), &mut lines) {
|
||||
lines.insert(row.address(), line.get() as u32);
|
||||
}
|
||||
if row.end_sequence() {
|
||||
// The next row is the start of a new sequence, which means we must
|
||||
// advance to the next .text section.
|
||||
lines = text_sections.next().map(|section| &mut section.line_info);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if iter.next().map_err(|e| gimli_error(e, "checking for next unit"))?.is_some() {
|
||||
log::warn!("Multiple units found in DWARF data, only processing the first");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct RelocationMap(object::read::RelocationMap);
|
||||
|
||||
impl RelocationMap {
|
||||
fn add(&mut self, file: &object::File, section: &object::Section) {
|
||||
for (offset, relocation) in section.relocations() {
|
||||
if let Err(e) = self.0.add(file, offset, relocation) {
|
||||
log::error!(
|
||||
"Relocation error for section {} at offset 0x{:08x}: {}",
|
||||
section.name().unwrap(),
|
||||
offset,
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl gimli::read::Relocate for &'_ RelocationMap {
|
||||
fn relocate_address(&self, offset: usize, value: u64) -> gimli::Result<u64> {
|
||||
Ok(self.0.relocate(offset as u64, value))
|
||||
}
|
||||
|
||||
fn relocate_offset(&self, offset: usize, value: usize) -> gimli::Result<usize> {
|
||||
<usize as gimli::ReaderOffset>::from_u64(self.0.relocate(offset as u64, value as u64))
|
||||
}
|
||||
}
|
||||
|
||||
type Relocate<'a, R> = gimli::RelocateReader<R, &'a RelocationMap>;
|
||||
|
||||
fn load_file_section<'input, 'arena, Endian: gimli::Endianity>(
|
||||
id: gimli::SectionId,
|
||||
file: &object::File<'input>,
|
||||
endian: Endian,
|
||||
arena_data: &'arena Arena<Cow<'input, [u8]>>,
|
||||
arena_relocations: &'arena Arena<RelocationMap>,
|
||||
) -> Result<Relocate<'arena, gimli::EndianSlice<'arena, Endian>>> {
|
||||
let mut relocations = RelocationMap::default();
|
||||
let data = match file.section_by_name(id.name()) {
|
||||
Some(ref section) => {
|
||||
relocations.add(file, section);
|
||||
section.uncompressed_data()?
|
||||
}
|
||||
// Use a non-zero capacity so that `ReaderOffsetId`s are unique.
|
||||
None => Cow::Owned(Vec::with_capacity(1)),
|
||||
};
|
||||
let data_ref = arena_data.alloc(data);
|
||||
let section = gimli::EndianSlice::new(data_ref, endian);
|
||||
let relocations = arena_relocations.alloc(relocations);
|
||||
Ok(Relocate::new(section, relocations))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn gimli_error(e: gimli::Error, context: &str) -> anyhow::Error {
|
||||
anyhow::anyhow!("gimli error {context}: {e:?}")
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
#[cfg(feature = "dwarf")]
|
||||
mod dwarf2;
|
||||
pub mod read;
|
||||
pub mod split_meta;
|
||||
|
||||
|
||||
@@ -122,7 +122,7 @@ fn map_symbols(
|
||||
}
|
||||
|
||||
// Infer symbol sizes for 0-size symbols
|
||||
infer_symbol_sizes(&mut symbols, sections);
|
||||
infer_symbol_sizes(arch, &mut symbols, sections)?;
|
||||
|
||||
Ok((symbols, symbol_indices))
|
||||
}
|
||||
@@ -136,7 +136,7 @@ fn is_local_label(symbol: &Symbol) -> bool {
|
||||
&& LABEL_PREFIXES.iter().any(|p| symbol.name.starts_with(p))
|
||||
}
|
||||
|
||||
fn infer_symbol_sizes(symbols: &mut [Symbol], sections: &[Section]) {
|
||||
fn infer_symbol_sizes(arch: &dyn Arch, symbols: &mut [Symbol], sections: &[Section]) -> Result<()> {
|
||||
// Create a sorted list of symbol indices by section
|
||||
let mut symbols_with_section = Vec::<usize>::with_capacity(symbols.len());
|
||||
for (i, symbol) in symbols.iter().enumerate() {
|
||||
@@ -206,18 +206,13 @@ fn infer_symbol_sizes(symbols: &mut [Symbol], sections: &[Section]) {
|
||||
iter_idx += 1;
|
||||
};
|
||||
let section = §ions[section_idx];
|
||||
let mut next_address =
|
||||
let next_address =
|
||||
next_symbol.map(|s| s.address).unwrap_or_else(|| section.address + section.size);
|
||||
if section.kind == SectionKind::Code {
|
||||
// For functions, trim any trailing 4-byte zeroes from the end (padding, nops)
|
||||
while next_address > symbol.address + 4
|
||||
&& let Some(data) = section.data_range(next_address - 4, 4)
|
||||
&& data == [0u8; 4]
|
||||
{
|
||||
next_address -= 4;
|
||||
}
|
||||
}
|
||||
let new_size = next_address.saturating_sub(symbol.address);
|
||||
let new_size = if section.kind == SectionKind::Code {
|
||||
arch.infer_function_size(symbol, section, next_address)?
|
||||
} else {
|
||||
next_address.saturating_sub(symbol.address)
|
||||
};
|
||||
if new_size > 0 {
|
||||
let symbol = &mut symbols[symbol_idx];
|
||||
symbol.size = new_size;
|
||||
@@ -234,6 +229,7 @@ fn infer_symbol_sizes(symbols: &mut [Symbol], sections: &[Section]) {
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn map_sections(
|
||||
@@ -554,12 +550,11 @@ fn perform_data_flow_analysis(obj: &mut Object, config: &DiffObjConfig) -> Resul
|
||||
}
|
||||
|
||||
// Optional full data flow analysis
|
||||
if config.analyze_data_flow {
|
||||
if let Some(flow_result) =
|
||||
if config.analyze_data_flow
|
||||
&& let Some(flow_result) =
|
||||
obj.arch.data_flow_analysis(obj, symbol, code, §ion.relocations)
|
||||
{
|
||||
generated_flow_results.push((symbol.clone(), flow_result));
|
||||
}
|
||||
{
|
||||
generated_flow_results.push((symbol.clone(), flow_result));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -582,6 +577,28 @@ fn parse_line_info(
|
||||
obj_data: &[u8],
|
||||
) -> Result<()> {
|
||||
// DWARF 1.1
|
||||
if let Err(e) = parse_line_info_dwarf1(obj_file, sections) {
|
||||
log::warn!("Failed to parse DWARF 1.1 line info: {e}");
|
||||
}
|
||||
|
||||
// DWARF 2+
|
||||
#[cfg(feature = "dwarf")]
|
||||
if let Err(e) = super::dwarf2::parse_line_info_dwarf2(obj_file, sections) {
|
||||
log::warn!("Failed to parse DWARF 2+ line info: {e}");
|
||||
}
|
||||
|
||||
// COFF
|
||||
if let object::File::Coff(coff) = obj_file
|
||||
&& let Err(e) = parse_line_info_coff(coff, sections, section_indices, obj_data)
|
||||
{
|
||||
log::warn!("Failed to parse COFF line info: {e}");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Parse .line section from DWARF 1.1 format.
|
||||
fn parse_line_info_dwarf1(obj_file: &object::File, sections: &mut [Section]) -> Result<()> {
|
||||
if let Some(section) = obj_file.section_by_name(".line") {
|
||||
let data = section.uncompressed_data()?;
|
||||
let mut reader: &[u8] = data.as_ref();
|
||||
@@ -609,55 +626,6 @@ fn parse_line_info(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DWARF 2+
|
||||
#[cfg(feature = "dwarf")]
|
||||
{
|
||||
fn gimli_error(e: gimli::Error) -> anyhow::Error { anyhow::anyhow!("DWARF error: {e:?}") }
|
||||
let dwarf_cow = gimli::DwarfSections::load(|id| {
|
||||
Ok::<_, gimli::Error>(
|
||||
obj_file
|
||||
.section_by_name(id.name())
|
||||
.and_then(|section| section.uncompressed_data().ok())
|
||||
.unwrap_or(alloc::borrow::Cow::Borrowed(&[][..])),
|
||||
)
|
||||
})
|
||||
.map_err(gimli_error)?;
|
||||
let endian = match obj_file.endianness() {
|
||||
object::Endianness::Little => gimli::RunTimeEndian::Little,
|
||||
object::Endianness::Big => gimli::RunTimeEndian::Big,
|
||||
};
|
||||
let dwarf = dwarf_cow.borrow(|section| gimli::EndianSlice::new(section, endian));
|
||||
let mut iter = dwarf.units();
|
||||
if let Some(header) = iter.next().map_err(gimli_error)? {
|
||||
let unit = dwarf.unit(header).map_err(gimli_error)?;
|
||||
if let Some(program) = unit.line_program.clone() {
|
||||
let mut text_sections = sections.iter_mut().filter(|s| s.kind == SectionKind::Code);
|
||||
let mut lines = text_sections.next().map(|section| &mut section.line_info);
|
||||
|
||||
let mut rows = program.rows();
|
||||
while let Some((_header, row)) = rows.next_row().map_err(gimli_error)? {
|
||||
if let (Some(line), Some(lines)) = (row.line(), &mut lines) {
|
||||
lines.insert(row.address(), line.get() as u32);
|
||||
}
|
||||
if row.end_sequence() {
|
||||
// The next row is the start of a new sequence, which means we must
|
||||
// advance to the next .text section.
|
||||
lines = text_sections.next().map(|section| &mut section.line_info);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if iter.next().map_err(gimli_error)?.is_some() {
|
||||
log::warn!("Multiple units found in DWARF data, only processing the first");
|
||||
}
|
||||
}
|
||||
|
||||
// COFF
|
||||
if let object::File::Coff(coff) = obj_file {
|
||||
parse_line_info_coff(coff, sections, section_indices, obj_data)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ pub fn align_data_to_4<W: std::io::Write + ?Sized>(
|
||||
len: usize,
|
||||
) -> std::io::Result<()> {
|
||||
const ALIGN_BYTES: &[u8] = &[0; 4];
|
||||
if len % 4 != 0 {
|
||||
if !len.is_multiple_of(4) {
|
||||
writer.write_all(&ALIGN_BYTES[..4 - len % 4])?;
|
||||
}
|
||||
Ok(())
|
||||
|
||||
@@ -507,116 +507,4 @@ expression: diff.instruction_rows
|
||||
),
|
||||
arg_diff: [],
|
||||
},
|
||||
InstructionDiffRow {
|
||||
ins_ref: Some(
|
||||
InstructionRef {
|
||||
address: 88,
|
||||
size: 1,
|
||||
opcode: 465,
|
||||
branch_dest: None,
|
||||
},
|
||||
),
|
||||
kind: None,
|
||||
branch_from: None,
|
||||
branch_to: None,
|
||||
arg_diff: [],
|
||||
},
|
||||
InstructionDiffRow {
|
||||
ins_ref: Some(
|
||||
InstructionRef {
|
||||
address: 89,
|
||||
size: 1,
|
||||
opcode: 465,
|
||||
branch_dest: None,
|
||||
},
|
||||
),
|
||||
kind: None,
|
||||
branch_from: None,
|
||||
branch_to: None,
|
||||
arg_diff: [],
|
||||
},
|
||||
InstructionDiffRow {
|
||||
ins_ref: Some(
|
||||
InstructionRef {
|
||||
address: 90,
|
||||
size: 1,
|
||||
opcode: 465,
|
||||
branch_dest: None,
|
||||
},
|
||||
),
|
||||
kind: None,
|
||||
branch_from: None,
|
||||
branch_to: None,
|
||||
arg_diff: [],
|
||||
},
|
||||
InstructionDiffRow {
|
||||
ins_ref: Some(
|
||||
InstructionRef {
|
||||
address: 91,
|
||||
size: 1,
|
||||
opcode: 465,
|
||||
branch_dest: None,
|
||||
},
|
||||
),
|
||||
kind: None,
|
||||
branch_from: None,
|
||||
branch_to: None,
|
||||
arg_diff: [],
|
||||
},
|
||||
InstructionDiffRow {
|
||||
ins_ref: Some(
|
||||
InstructionRef {
|
||||
address: 92,
|
||||
size: 1,
|
||||
opcode: 465,
|
||||
branch_dest: None,
|
||||
},
|
||||
),
|
||||
kind: None,
|
||||
branch_from: None,
|
||||
branch_to: None,
|
||||
arg_diff: [],
|
||||
},
|
||||
InstructionDiffRow {
|
||||
ins_ref: Some(
|
||||
InstructionRef {
|
||||
address: 93,
|
||||
size: 1,
|
||||
opcode: 465,
|
||||
branch_dest: None,
|
||||
},
|
||||
),
|
||||
kind: None,
|
||||
branch_from: None,
|
||||
branch_to: None,
|
||||
arg_diff: [],
|
||||
},
|
||||
InstructionDiffRow {
|
||||
ins_ref: Some(
|
||||
InstructionRef {
|
||||
address: 94,
|
||||
size: 1,
|
||||
opcode: 465,
|
||||
branch_dest: None,
|
||||
},
|
||||
),
|
||||
kind: None,
|
||||
branch_from: None,
|
||||
branch_to: None,
|
||||
arg_diff: [],
|
||||
},
|
||||
InstructionDiffRow {
|
||||
ins_ref: Some(
|
||||
InstructionRef {
|
||||
address: 95,
|
||||
size: 1,
|
||||
opcode: 465,
|
||||
branch_dest: None,
|
||||
},
|
||||
),
|
||||
kind: None,
|
||||
branch_from: None,
|
||||
branch_to: None,
|
||||
arg_diff: [],
|
||||
},
|
||||
]
|
||||
|
||||
@@ -29,11 +29,3 @@ expression: output
|
||||
[(Address(76), Normal, 5), (Spacing(4), Normal, 0), (Opcode(".dword", 65534), Normal, 10), (BranchDest(41), Normal, 0), (Basic(" ~>"), Rotating(6), 0), (Eol, Normal, 0)]
|
||||
[(Address(80), Normal, 5), (Spacing(4), Normal, 0), (Opcode(".dword", 65534), Normal, 10), (BranchDest(47), Normal, 0), (Basic(" ~>"), Rotating(7), 0), (Eol, Normal, 0)]
|
||||
[(Address(84), Normal, 5), (Spacing(4), Normal, 0), (Opcode(".dword", 65534), Normal, 10), (BranchDest(53), Normal, 0), (Basic(" ~>"), Rotating(8), 0), (Eol, Normal, 0)]
|
||||
[(Address(88), Normal, 5), (Spacing(4), Normal, 0), (Opcode("nop", 465), Normal, 10), (Eol, Normal, 0)]
|
||||
[(Address(89), Normal, 5), (Spacing(4), Normal, 0), (Opcode("nop", 465), Normal, 10), (Eol, Normal, 0)]
|
||||
[(Address(90), Normal, 5), (Spacing(4), Normal, 0), (Opcode("nop", 465), Normal, 10), (Eol, Normal, 0)]
|
||||
[(Address(91), Normal, 5), (Spacing(4), Normal, 0), (Opcode("nop", 465), Normal, 10), (Eol, Normal, 0)]
|
||||
[(Address(92), Normal, 5), (Spacing(4), Normal, 0), (Opcode("nop", 465), Normal, 10), (Eol, Normal, 0)]
|
||||
[(Address(93), Normal, 5), (Spacing(4), Normal, 0), (Opcode("nop", 465), Normal, 10), (Eol, Normal, 0)]
|
||||
[(Address(94), Normal, 5), (Spacing(4), Normal, 0), (Opcode("nop", 465), Normal, 10), (Eol, Normal, 0)]
|
||||
[(Address(95), Normal, 5), (Spacing(4), Normal, 0), (Opcode("nop", 465), Normal, 10), (Eol, Normal, 0)]
|
||||
|
||||
@@ -63,7 +63,7 @@ Object {
|
||||
"int __cdecl test(int)",
|
||||
),
|
||||
address: 0,
|
||||
size: 96,
|
||||
size: 88,
|
||||
kind: Function,
|
||||
section: Some(
|
||||
1,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user