mirror of
https://github.com/encounter/objdiff.git
synced 2026-07-10 12:18:36 -07:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bd3ed0d5ad | |||
| e638d0b17a | |||
| f58616b6dd | |||
| e9762e24c2 | |||
| dab79d96a1 | |||
| a57e5db983 | |||
| d0afd3b83e | |||
| a367af612b | |||
| 22052ea10b | |||
| f7c3501eae | |||
| 07ef93f16a | |||
| 8e8ab6bef8 | |||
| e865f3d598 | |||
| 2b13e9886a | |||
| 1750af736a | |||
| 731b604c24 | |||
| 2d643eb071 |
+4
-5
@@ -1,5 +1,4 @@
|
||||
[target.x86_64-pc-windows-msvc]
|
||||
linker = "rust-lld"
|
||||
|
||||
[target.aarch64-pc-windows-msvc]
|
||||
linker = "rust-lld"
|
||||
# statically link the C runtime so the executable does not depend on
|
||||
# that shared/dynamic library.
|
||||
[target.'cfg(all(target_env = "msvc", target_os = "windows"))']
|
||||
rustflags = ["-C", "target-feature=+crt-static"]
|
||||
|
||||
Generated
+4
-4
@@ -3373,7 +3373,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "objdiff-cli"
|
||||
version = "3.0.0-beta.7"
|
||||
version = "3.0.0-beta.10"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argp",
|
||||
@@ -3396,7 +3396,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "objdiff-core"
|
||||
version = "3.0.0-beta.7"
|
||||
version = "3.0.0-beta.10"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"arm-attr",
|
||||
@@ -3450,7 +3450,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "objdiff-gui"
|
||||
version = "3.0.0-beta.7"
|
||||
version = "3.0.0-beta.10"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cfg-if",
|
||||
@@ -3486,7 +3486,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "objdiff-wasm"
|
||||
version = "3.0.0-beta.7"
|
||||
version = "3.0.0-beta.10"
|
||||
dependencies = [
|
||||
"log",
|
||||
"objdiff-core",
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ strip = "debuginfo"
|
||||
codegen-units = 1
|
||||
|
||||
[workspace.package]
|
||||
version = "3.0.0-beta.7"
|
||||
version = "3.0.0-beta.10"
|
||||
authors = ["Luke Street <luke@street.dev>"]
|
||||
edition = "2024"
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
@@ -21,7 +21,7 @@ Supports:
|
||||
- MIPS (N64, PS1, PS2, PSP)
|
||||
- PowerPC (GameCube, Wii)
|
||||
- SuperH (Saturn, Dreamcast)
|
||||
- x86 (COFF only)
|
||||
- x86, x86_64 (PC)
|
||||
|
||||
See [Usage](#usage) for more information.
|
||||
|
||||
|
||||
@@ -175,6 +175,10 @@
|
||||
"type": "boolean",
|
||||
"description": "If true, objdiff will run the build command with the context file as an argument to generate it.",
|
||||
"default": false
|
||||
},
|
||||
"preset_id": {
|
||||
"type": "number",
|
||||
"description": "The decomp.me preset ID to use for the scratch.\nCompiler and flags in the config will take precedence over the preset, but the preset is useful for organizational purposes."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
||||
@@ -245,12 +245,14 @@ fn report_object(
|
||||
for (symbol, symbol_diff) in obj.symbols.iter().zip(&obj_diff.symbols) {
|
||||
if symbol.section != Some(section_idx)
|
||||
|| symbol.size == 0
|
||||
|| symbol.flags.contains(SymbolFlag::Hidden | SymbolFlag::Ignored)
|
||||
|| symbol.flags.contains(SymbolFlag::Hidden)
|
||||
|| symbol.flags.contains(SymbolFlag::Ignored)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if let Some(existing_functions) = &mut existing_functions {
|
||||
if symbol.flags.contains(SymbolFlag::Global | SymbolFlag::Weak)
|
||||
if (symbol.flags.contains(SymbolFlag::Global)
|
||||
|| symbol.flags.contains(SymbolFlag::Weak))
|
||||
&& !existing_functions.insert(symbol.name.clone())
|
||||
{
|
||||
continue;
|
||||
|
||||
@@ -450,11 +450,11 @@ impl UiView for FunctionDiffUi {
|
||||
|
||||
fn reload(&mut self, state: &AppState) -> Result<()> {
|
||||
let left_sym =
|
||||
state.left_obj.as_ref().and_then(|(o, _)| find_function(o, &self.symbol_name));
|
||||
state.left_obj.as_ref().and_then(|(o, _)| o.symbol_by_name(&self.symbol_name));
|
||||
let right_sym =
|
||||
state.right_obj.as_ref().and_then(|(o, _)| find_function(o, &self.symbol_name));
|
||||
state.right_obj.as_ref().and_then(|(o, _)| o.symbol_by_name(&self.symbol_name));
|
||||
let prev_sym =
|
||||
state.prev_obj.as_ref().and_then(|(o, _)| find_function(o, &self.symbol_name));
|
||||
state.prev_obj.as_ref().and_then(|(o, _)| o.symbol_by_name(&self.symbol_name));
|
||||
self.num_rows = match (
|
||||
get_symbol(state.left_obj.as_ref(), left_sym),
|
||||
get_symbol(state.right_obj.as_ref(), right_sym),
|
||||
@@ -570,6 +570,7 @@ impl FunctionDiffUi {
|
||||
DiffTextColor::Normal => Color::Gray,
|
||||
DiffTextColor::Dim => Color::DarkGray,
|
||||
DiffTextColor::Bright => Color::White,
|
||||
DiffTextColor::DataFlow => Color::LightCyan,
|
||||
DiffTextColor::Replace => Color::Cyan,
|
||||
DiffTextColor::Delete => Color::Red,
|
||||
DiffTextColor::Insert => Color::Green,
|
||||
@@ -650,12 +651,3 @@ fn get_symbol(
|
||||
let sym = sym?;
|
||||
Some((obj, sym, &diff.symbols[sym]))
|
||||
}
|
||||
|
||||
fn find_function(obj: &Object, name: &str) -> Option<usize> {
|
||||
for (symbol_idx, symbol) in obj.symbols.iter().enumerate() {
|
||||
if symbol.name == name {
|
||||
return Some(symbol_idx);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ any-arch = [
|
||||
"dep:regex",
|
||||
"dep:similar",
|
||||
"dep:syn",
|
||||
"dep:encoding_rs"
|
||||
]
|
||||
bindings = [
|
||||
"dep:prost",
|
||||
@@ -171,10 +172,10 @@ notify-debouncer-full = { version = "0.5.0", optional = true }
|
||||
shell-escape = { version = "0.1", optional = true }
|
||||
tempfile = { version = "3.19", optional = true }
|
||||
time = { version = "0.3", optional = true }
|
||||
encoding_rs = "0.8.35"
|
||||
encoding_rs = { version = "0.8.35", optional = true }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
winapi = { version = "0.3", optional = true }
|
||||
winapi = { version = "0.3", optional = true, features = ["winbase"] }
|
||||
|
||||
# For Linux static binaries, use rustls
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
|
||||
@@ -25,6 +25,20 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "analyzeDataFlow",
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"name": "(Experimental) Perform data flow analysis",
|
||||
"description": "Use data flow analysis to display known information about register contents where possible"
|
||||
},
|
||||
{
|
||||
"id": "showDataFlow",
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"name": "Show data flow",
|
||||
"description": "Show data flow analysis results in place of register name where present"
|
||||
},
|
||||
{
|
||||
"id": "spaceBetweenArgs",
|
||||
"type": "boolean",
|
||||
@@ -264,7 +278,8 @@
|
||||
"id": "ppc",
|
||||
"name": "PowerPC",
|
||||
"properties": [
|
||||
"ppc.calculatePoolRelocations"
|
||||
"ppc.calculatePoolRelocations",
|
||||
"analyzeDataFlow"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -225,7 +225,7 @@ impl Arch for ArchArm {
|
||||
|
||||
let mut address = start_addr;
|
||||
while address < end_addr {
|
||||
while let Some(next) = next_mapping.take_if(|x| address >= x.address) {
|
||||
while let Some(next) = next_mapping.filter(|x| address >= x.address) {
|
||||
// Change mapping
|
||||
mode = next.mapping;
|
||||
next_mapping = mappings_iter.next();
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
use alloc::{borrow::Cow, boxed::Box, format, string::String, vec::Vec};
|
||||
use core::{ffi::CStr, fmt, fmt::Debug};
|
||||
use core::{
|
||||
ffi::CStr,
|
||||
fmt::{self, Debug},
|
||||
};
|
||||
|
||||
use anyhow::{Result, bail};
|
||||
use encoding_rs::SHIFT_JIS;
|
||||
@@ -11,8 +14,9 @@ use crate::{
|
||||
display::{ContextItem, HoverItem, InstructionPart},
|
||||
},
|
||||
obj::{
|
||||
InstructionArg, InstructionRef, Object, ParsedInstruction, Relocation, RelocationFlags,
|
||||
ResolvedInstructionRef, ResolvedSymbol, Section, Symbol, SymbolFlagSet, SymbolKind,
|
||||
FlowAnalysisResult, InstructionArg, InstructionRef, Object, ParsedInstruction, Relocation,
|
||||
RelocationFlags, ResolvedInstructionRef, ResolvedSymbol, Section, Symbol, SymbolFlagSet,
|
||||
SymbolKind,
|
||||
},
|
||||
util::ReallySigned,
|
||||
};
|
||||
@@ -31,6 +35,7 @@ pub mod superh;
|
||||
pub mod x86;
|
||||
|
||||
/// Represents the type of data associated with an instruction
|
||||
#[derive(PartialEq)]
|
||||
pub enum DataType {
|
||||
Int8,
|
||||
Int16,
|
||||
@@ -335,6 +340,17 @@ pub trait Arch: Send + Sync + Debug {
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
// Perform detailed data flow analysis
|
||||
fn data_flow_analysis(
|
||||
&self,
|
||||
_obj: &Object,
|
||||
_symbol: &Symbol,
|
||||
_code: &[u8],
|
||||
_relocations: &[Relocation],
|
||||
) -> Option<Box<dyn FlowAnalysisResult>> {
|
||||
None
|
||||
}
|
||||
|
||||
fn implcit_addend(
|
||||
&self,
|
||||
file: &object::File<'_>,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,5 @@
|
||||
use alloc::{
|
||||
boxed::Box,
|
||||
collections::{BTreeMap, BTreeSet},
|
||||
string::{String, ToString},
|
||||
vec,
|
||||
@@ -18,11 +19,13 @@ use crate::{
|
||||
display::{ContextItem, HoverItem, HoverItemColor, InstructionPart, SymbolNavigationKind},
|
||||
},
|
||||
obj::{
|
||||
InstructionRef, Object, Relocation, RelocationFlags, ResolvedInstructionRef,
|
||||
ResolvedRelocation, Symbol, SymbolFlag, SymbolFlagSet,
|
||||
FlowAnalysisResult, InstructionRef, Object, Relocation, RelocationFlags,
|
||||
ResolvedInstructionRef, ResolvedRelocation, Symbol, SymbolFlag, SymbolFlagSet,
|
||||
},
|
||||
};
|
||||
|
||||
mod flow_analysis;
|
||||
|
||||
// Relative relocation, can be Simm, Offset or BranchDest
|
||||
fn is_relative_arg(arg: &ppc750cl::Argument) -> bool {
|
||||
matches!(
|
||||
@@ -157,6 +160,7 @@ impl Arch for ArchPpc {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Could be replaced by data_flow_analysis once that feature stabilizes
|
||||
fn generate_pooled_relocations(
|
||||
&self,
|
||||
address: u64,
|
||||
@@ -167,6 +171,16 @@ impl Arch for ArchPpc {
|
||||
generate_fake_pool_relocations_for_function(address, code, relocations, symbols)
|
||||
}
|
||||
|
||||
fn data_flow_analysis(
|
||||
&self,
|
||||
obj: &Object,
|
||||
symbol: &Symbol,
|
||||
code: &[u8],
|
||||
relocations: &[Relocation],
|
||||
) -> Option<Box<dyn FlowAnalysisResult>> {
|
||||
Some(flow_analysis::ppc_data_flow_analysis(obj, symbol, code, relocations))
|
||||
}
|
||||
|
||||
fn implcit_addend(
|
||||
&self,
|
||||
_file: &object::File<'_>,
|
||||
@@ -225,7 +239,7 @@ impl Arch for ArchPpc {
|
||||
return Some(DataType::String);
|
||||
}
|
||||
let opcode = ppc750cl::Opcode::from(resolved.ins_ref.opcode as u8);
|
||||
if let Some(ty) = guess_data_type_from_load_store_inst_op(opcode) {
|
||||
if let Some(ty) = flow_analysis::guess_data_type_from_load_store_inst_op(opcode) {
|
||||
// Numeric type.
|
||||
return Some(ty);
|
||||
}
|
||||
@@ -501,25 +515,6 @@ fn make_symbol_ref(symbol: &object::Symbol) -> Result<ExtabSymbolRef> {
|
||||
Ok(ExtabSymbolRef { original_index: symbol.index().0 - 1, name, demangled_name })
|
||||
}
|
||||
|
||||
fn guess_data_type_from_load_store_inst_op(inst_op: ppc750cl::Opcode) -> Option<DataType> {
|
||||
use ppc750cl::Opcode;
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct PoolReference {
|
||||
addr_src_gpr: ppc750cl::GPR,
|
||||
@@ -536,7 +531,7 @@ fn get_pool_reference_for_inst(
|
||||
) -> Option<PoolReference> {
|
||||
use ppc750cl::{Argument, Opcode};
|
||||
let args = &simplified.args;
|
||||
if guess_data_type_from_load_store_inst_op(opcode).is_some() {
|
||||
if flow_analysis::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.
|
||||
@@ -668,7 +663,7 @@ fn make_fake_pool_reloc(
|
||||
// and returns a Vec of "fake pool relocations" that simulate what a relocation for that instruction
|
||||
// would look like if data hadn't been pooled.
|
||||
// This method tries to follow the function's proper control flow. It keeps track of a queue of
|
||||
// states it hasn't traversed yet, where each state holds an instruction address and a HashMap of
|
||||
// states it hasn't traversed yet, where each state holds an instruction address and a map of
|
||||
// which registers hold which pool relocations at that point.
|
||||
// When a conditional or unconditional branch is encountered, the destination of the branch is added
|
||||
// to the queue. Conditional branches will traverse both the path where the branch is taken and the
|
||||
@@ -6,7 +6,7 @@ use iced_x86::{
|
||||
Decoder, DecoderOptions, DecoratorKind, FormatterOutput, FormatterTextKind, GasFormatter,
|
||||
Instruction, IntelFormatter, MasmFormatter, NasmFormatter, NumberKind, OpKind, Register,
|
||||
};
|
||||
use object::{Endian as _, Object as _, ObjectSection as _, pe};
|
||||
use object::{Endian as _, Object as _, ObjectSection as _, elf, pe};
|
||||
|
||||
use crate::{
|
||||
arch::Arch,
|
||||
@@ -67,7 +67,11 @@ impl ArchX86 {
|
||||
pe::IMAGE_REL_I386_DIR32 | pe::IMAGE_REL_I386_REL32 => Some(4),
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
RelocationFlags::Elf(typ) => match typ {
|
||||
elf::R_386_32 | elf::R_386_PC32 => Some(4),
|
||||
elf::R_386_16 => Some(2),
|
||||
_ => None,
|
||||
},
|
||||
},
|
||||
Architecture::X86_64 => match flags {
|
||||
RelocationFlags::Coff(typ) => match typ {
|
||||
@@ -75,7 +79,11 @@ impl ArchX86 {
|
||||
pe::IMAGE_REL_AMD64_ADDR64 => Some(8),
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
RelocationFlags::Elf(typ) => match typ {
|
||||
elf::R_X86_64_PC32 => Some(4),
|
||||
elf::R_X86_64_64 => Some(8),
|
||||
_ => None,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -227,7 +235,8 @@ impl Arch for ArchX86 {
|
||||
) -> Result<i64> {
|
||||
match self.arch {
|
||||
Architecture::X86 => match flags {
|
||||
RelocationFlags::Coff(pe::IMAGE_REL_I386_DIR32 | pe::IMAGE_REL_I386_REL32) => {
|
||||
RelocationFlags::Coff(pe::IMAGE_REL_I386_DIR32 | pe::IMAGE_REL_I386_REL32)
|
||||
| RelocationFlags::Elf(elf::R_386_32 | elf::R_386_PC32) => {
|
||||
let data =
|
||||
section.data()?[address as usize..address as usize + 4].try_into()?;
|
||||
Ok(self.endianness.read_i32_bytes(data) as i64)
|
||||
@@ -235,12 +244,14 @@ impl Arch for ArchX86 {
|
||||
flags => bail!("Unsupported x86 implicit relocation {flags:?}"),
|
||||
},
|
||||
Architecture::X86_64 => match flags {
|
||||
RelocationFlags::Coff(pe::IMAGE_REL_AMD64_ADDR32NB | pe::IMAGE_REL_AMD64_REL32) => {
|
||||
RelocationFlags::Coff(pe::IMAGE_REL_AMD64_ADDR32NB | pe::IMAGE_REL_AMD64_REL32)
|
||||
| RelocationFlags::Elf(elf::R_X86_64_32 | elf::R_X86_64_PC32) => {
|
||||
let data =
|
||||
section.data()?[address as usize..address as usize + 4].try_into()?;
|
||||
Ok(self.endianness.read_i32_bytes(data) as i64)
|
||||
}
|
||||
RelocationFlags::Coff(pe::IMAGE_REL_AMD64_ADDR64) => {
|
||||
RelocationFlags::Coff(pe::IMAGE_REL_AMD64_ADDR64)
|
||||
| RelocationFlags::Elf(elf::R_X86_64_64) => {
|
||||
let data =
|
||||
section.data()?[address as usize..address as usize + 8].try_into()?;
|
||||
Ok(self.endianness.read_i64_bytes(data))
|
||||
|
||||
@@ -325,12 +325,11 @@ fn reloc_eq(
|
||||
|| display_ins_data_literals(left_obj, left_ins)
|
||||
== display_ins_data_literals(right_obj, right_ins))
|
||||
}
|
||||
(Some(_), None) => false,
|
||||
(None, Some(_)) => {
|
||||
// Match if possibly stripped weak symbol
|
||||
symbol_name_addend_matches && right_reloc.symbol.flags.contains(SymbolFlag::Weak)
|
||||
}
|
||||
(None, None) => symbol_name_addend_matches,
|
||||
(Some(_), None) | (None, None) => symbol_name_addend_matches,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -53,12 +53,11 @@ fn reloc_eq(
|
||||
section_name_eq(left_obj, right_obj, sl, sr)
|
||||
&& (symbol_name_addend_matches || address_eq(left, right))
|
||||
}
|
||||
(Some(_), None) => false,
|
||||
(None, Some(_)) => {
|
||||
// Match if possibly stripped weak symbol
|
||||
symbol_name_addend_matches && right.symbol.flags.contains(SymbolFlag::Weak)
|
||||
}
|
||||
(None, None) => symbol_name_addend_matches,
|
||||
(Some(_), None) | (None, None) => symbol_name_addend_matches,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,8 +14,9 @@ use regex::Regex;
|
||||
use crate::{
|
||||
diff::{DiffObjConfig, InstructionDiffKind, InstructionDiffRow, ObjectDiff, SymbolDiff},
|
||||
obj::{
|
||||
InstructionArg, InstructionArgValue, Object, ParsedInstruction, ResolvedInstructionRef,
|
||||
ResolvedRelocation, SectionFlag, SectionKind, Symbol, SymbolFlag, SymbolKind,
|
||||
FlowAnalysisValue, InstructionArg, InstructionArgValue, Object, ParsedInstruction,
|
||||
ResolvedInstructionRef, ResolvedRelocation, SectionFlag, SectionKind, Symbol, SymbolFlag,
|
||||
SymbolKind,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -47,11 +48,12 @@ pub enum DiffText<'a> {
|
||||
pub enum DiffTextColor {
|
||||
#[default]
|
||||
Normal, // Grey
|
||||
Dim, // Dark grey
|
||||
Bright, // White
|
||||
Replace, // Blue
|
||||
Delete, // Red
|
||||
Insert, // Green
|
||||
Dim, // Dark grey
|
||||
Bright, // White
|
||||
DataFlow, // Light blue
|
||||
Replace, // Blue
|
||||
Delete, // Red
|
||||
Insert, // Green
|
||||
Rotating(u8),
|
||||
}
|
||||
|
||||
@@ -77,7 +79,7 @@ impl<'a> DiffTextSegment<'a> {
|
||||
const EOL_SEGMENT: DiffTextSegment<'static> =
|
||||
DiffTextSegment { text: DiffText::Eol, color: DiffTextColor::Normal, pad_to: 0 };
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub enum HighlightKind {
|
||||
#[default]
|
||||
None,
|
||||
@@ -186,6 +188,11 @@ pub fn display_row(
|
||||
}
|
||||
let mut arg_idx = 0;
|
||||
let mut displayed_relocation = false;
|
||||
let analysis_result = if diff_config.show_data_flow {
|
||||
obj.flow_analysis_results.get(&resolved.symbol.address)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
obj.arch.display_instruction(resolved, diff_config, &mut |part| match part {
|
||||
InstructionPart::Basic(text) => {
|
||||
if text.chars().all(|c| c == ' ') {
|
||||
@@ -208,15 +215,30 @@ pub fn display_row(
|
||||
if arg == InstructionArg::Reloc {
|
||||
displayed_relocation = true;
|
||||
}
|
||||
match (arg, resolved.ins_ref.branch_dest) {
|
||||
(InstructionArg::Value(value), _) => cb(DiffTextSegment {
|
||||
text: DiffText::Argument(value),
|
||||
color: diff_index
|
||||
let data_flow_value =
|
||||
analysis_result.and_then(|result|
|
||||
result.as_ref().get_argument_value_at_address(
|
||||
ins_ref.address, (arg_idx - 1) as u8));
|
||||
match (arg, data_flow_value, resolved.ins_ref.branch_dest) {
|
||||
// If we have a flow analysis result, always use that over anything else.
|
||||
(InstructionArg::Value(_) | InstructionArg::Reloc, Some(FlowAnalysisValue::Text(text)), _) => {
|
||||
cb(DiffTextSegment {
|
||||
text: DiffText::Argument(InstructionArgValue::Opaque(Cow::Borrowed(text))),
|
||||
color: DiffTextColor::DataFlow,
|
||||
pad_to: 0,
|
||||
})
|
||||
},
|
||||
(InstructionArg::Value(value), None, _) => {
|
||||
let color = diff_index
|
||||
.get()
|
||||
.map_or(base_color, |i| DiffTextColor::Rotating(i as u8)),
|
||||
pad_to: 0,
|
||||
}),
|
||||
(InstructionArg::Reloc, None) => {
|
||||
.map_or(base_color, |i| DiffTextColor::Rotating(i as u8));
|
||||
cb(DiffTextSegment {
|
||||
text: DiffText::Argument(value),
|
||||
color,
|
||||
pad_to: 0,
|
||||
})
|
||||
},
|
||||
(InstructionArg::Reloc, _, None) => {
|
||||
let resolved = resolved.relocation.unwrap();
|
||||
let color = diff_index
|
||||
.get()
|
||||
@@ -235,9 +257,9 @@ pub fn display_row(
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
(InstructionArg::BranchDest(dest), _) |
|
||||
(InstructionArg::BranchDest(dest), _, _) |
|
||||
// If the relocation was resolved to a branch destination, emit that instead.
|
||||
(InstructionArg::Reloc, Some(dest)) => {
|
||||
(InstructionArg::Reloc, _, Some(dest)) => {
|
||||
if let Some(addr) = dest.checked_sub(resolved.symbol.address) {
|
||||
cb(DiffTextSegment {
|
||||
text: DiffText::BranchDest(addr),
|
||||
@@ -288,6 +310,18 @@ pub fn display_row(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl PartialEq<HighlightKind> for HighlightKind {
|
||||
fn eq(&self, other: &HighlightKind) -> bool {
|
||||
match (self, other) {
|
||||
(HighlightKind::Opcode(a), HighlightKind::Opcode(b)) => a == b,
|
||||
(HighlightKind::Argument(a), HighlightKind::Argument(b)) => a.loose_eq(b),
|
||||
(HighlightKind::Symbol(a), HighlightKind::Symbol(b)) => a == b,
|
||||
(HighlightKind::Address(a), HighlightKind::Address(b)) => a == b,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<DiffText<'_>> for HighlightKind {
|
||||
fn eq(&self, other: &DiffText) -> bool {
|
||||
match (self, other) {
|
||||
@@ -604,7 +638,9 @@ fn symbol_matches_filter(
|
||||
return false;
|
||||
}
|
||||
if !show_hidden_symbols
|
||||
&& (symbol.size == 0 || symbol.flags.contains(SymbolFlag::Hidden | SymbolFlag::Ignored))
|
||||
&& (symbol.size == 0
|
||||
|| symbol.flags.contains(SymbolFlag::Hidden)
|
||||
|| symbol.flags.contains(SymbolFlag::Ignored))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -341,11 +341,25 @@ pub fn diff_objs(
|
||||
if let (Some((right_obj, right_out)), Some((left_obj, left_out))) =
|
||||
(right.as_mut(), left.as_mut())
|
||||
{
|
||||
if let Some(right_name) = &mapping_config.selecting_left {
|
||||
generate_mapping_symbols(right_obj, right_name, left_obj, left_out, diff_config)?;
|
||||
if let Some(right_name) = mapping_config.selecting_left.as_deref() {
|
||||
generate_mapping_symbols(
|
||||
left_obj,
|
||||
left_out,
|
||||
right_obj,
|
||||
right_out,
|
||||
MappingSymbol::Right(right_name),
|
||||
diff_config,
|
||||
)?;
|
||||
}
|
||||
if let Some(left_name) = &mapping_config.selecting_right {
|
||||
generate_mapping_symbols(left_obj, left_name, right_obj, right_out, diff_config)?;
|
||||
if let Some(left_name) = mapping_config.selecting_right.as_deref() {
|
||||
generate_mapping_symbols(
|
||||
left_obj,
|
||||
left_out,
|
||||
right_obj,
|
||||
right_out,
|
||||
MappingSymbol::Left(left_name),
|
||||
diff_config,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -356,17 +370,28 @@ pub fn diff_objs(
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum MappingSymbol<'a> {
|
||||
Left(&'a str),
|
||||
Right(&'a str),
|
||||
}
|
||||
|
||||
/// When we're selecting a symbol to use as a comparison, we'll create comparisons for all
|
||||
/// symbols in the other object that match the selected symbol's section and kind. This allows
|
||||
/// us to display match percentages for all symbols in the other object that could be selected.
|
||||
fn generate_mapping_symbols(
|
||||
base_obj: &Object,
|
||||
base_name: &str,
|
||||
target_obj: &Object,
|
||||
target_out: &mut ObjectDiff,
|
||||
left_obj: &Object,
|
||||
left_out: &mut ObjectDiff,
|
||||
right_obj: &Object,
|
||||
right_out: &mut ObjectDiff,
|
||||
mapping_symbol: MappingSymbol,
|
||||
config: &DiffObjConfig,
|
||||
) -> Result<()> {
|
||||
let Some(base_symbol_ref) = symbol_ref_by_name(base_obj, base_name) else {
|
||||
let (base_obj, base_name, target_obj) = match mapping_symbol {
|
||||
MappingSymbol::Left(name) => (left_obj, name, right_obj),
|
||||
MappingSymbol::Right(name) => (right_obj, name, left_obj),
|
||||
};
|
||||
let Some(base_symbol_ref) = base_obj.symbol_by_name(base_name) else {
|
||||
return Ok(());
|
||||
};
|
||||
let base_section_kind = symbol_section_kind(base_obj, &base_obj.symbols[base_symbol_ref]);
|
||||
@@ -377,32 +402,30 @@ fn generate_mapping_symbols(
|
||||
{
|
||||
continue;
|
||||
}
|
||||
match base_section_kind {
|
||||
let (left_symbol_idx, right_symbol_idx) = match mapping_symbol {
|
||||
MappingSymbol::Left(_) => (base_symbol_ref, target_symbol_index),
|
||||
MappingSymbol::Right(_) => (target_symbol_index, base_symbol_ref),
|
||||
};
|
||||
let (left_diff, right_diff) = match base_section_kind {
|
||||
SectionKind::Code => {
|
||||
let (left_diff, _right_diff) =
|
||||
diff_code(target_obj, base_obj, target_symbol_index, base_symbol_ref, config)?;
|
||||
target_out.mapping_symbols.push(MappingSymbolDiff {
|
||||
symbol_index: target_symbol_index,
|
||||
symbol_diff: left_diff,
|
||||
});
|
||||
diff_code(left_obj, right_obj, left_symbol_idx, right_symbol_idx, config)
|
||||
}
|
||||
SectionKind::Data => {
|
||||
let (left_diff, _right_diff) =
|
||||
diff_data_symbol(target_obj, base_obj, target_symbol_index, base_symbol_ref)?;
|
||||
target_out.mapping_symbols.push(MappingSymbolDiff {
|
||||
symbol_index: target_symbol_index,
|
||||
symbol_diff: left_diff,
|
||||
});
|
||||
diff_data_symbol(left_obj, right_obj, left_symbol_idx, right_symbol_idx)
|
||||
}
|
||||
SectionKind::Bss | SectionKind::Common => {
|
||||
let (left_diff, _right_diff) =
|
||||
diff_bss_symbol(target_obj, base_obj, target_symbol_index, base_symbol_ref)?;
|
||||
target_out.mapping_symbols.push(MappingSymbolDiff {
|
||||
symbol_index: target_symbol_index,
|
||||
symbol_diff: left_diff,
|
||||
});
|
||||
diff_bss_symbol(left_obj, right_obj, left_symbol_idx, right_symbol_idx)
|
||||
}
|
||||
SectionKind::Unknown => {}
|
||||
SectionKind::Unknown => continue,
|
||||
}?;
|
||||
match mapping_symbol {
|
||||
MappingSymbol::Left(_) => right_out.mapping_symbols.push(MappingSymbolDiff {
|
||||
symbol_index: right_symbol_idx,
|
||||
symbol_diff: right_diff,
|
||||
}),
|
||||
MappingSymbol::Right(_) => left_out
|
||||
.mapping_symbols
|
||||
.push(MappingSymbolDiff { symbol_index: left_symbol_idx, symbol_diff: left_diff }),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
@@ -434,10 +457,6 @@ pub struct MappingConfig {
|
||||
pub selecting_right: Option<String>,
|
||||
}
|
||||
|
||||
fn symbol_ref_by_name(obj: &Object, name: &str) -> Option<usize> {
|
||||
obj.symbols.iter().position(|s| s.name == name)
|
||||
}
|
||||
|
||||
fn apply_symbol_mappings(
|
||||
left: &Object,
|
||||
right: &Object,
|
||||
@@ -449,25 +468,25 @@ fn apply_symbol_mappings(
|
||||
// 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) = symbol_ref_by_name(left, left_name) {
|
||||
if 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) = symbol_ref_by_name(right, right_name) {
|
||||
if let Some(right_symbol) = right.symbol_by_name(right_name) {
|
||||
right_used.insert(right_symbol);
|
||||
}
|
||||
}
|
||||
|
||||
// Apply manual symbol mappings
|
||||
for (left_name, right_name) in &mapping_config.mappings {
|
||||
let Some(left_symbol_index) = symbol_ref_by_name(left, left_name) else {
|
||||
let Some(left_symbol_index) = left.symbol_by_name(left_name) else {
|
||||
continue;
|
||||
};
|
||||
if left_used.contains(&left_symbol_index) {
|
||||
continue;
|
||||
}
|
||||
let Some(right_symbol_index) = symbol_ref_by_name(right, right_name) else {
|
||||
let Some(right_symbol_index) = right.symbol_by_name(right_name) else {
|
||||
continue;
|
||||
};
|
||||
if right_used.contains(&right_symbol_index) {
|
||||
|
||||
@@ -118,7 +118,7 @@ impl Section {
|
||||
Err(i) => self
|
||||
.relocations
|
||||
.get(i)
|
||||
.take_if(|r| r.address < ins_ref.address + ins_ref.size as u64),
|
||||
.filter(|r| r.address < ins_ref.address + ins_ref.size as u64),
|
||||
}
|
||||
.and_then(|relocation| {
|
||||
let symbol = obj.symbols.get(relocation.target_symbol)?;
|
||||
@@ -233,6 +233,19 @@ pub enum SymbolKind {
|
||||
Section,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum FlowAnalysisValue {
|
||||
Text(String),
|
||||
}
|
||||
|
||||
pub trait FlowAnalysisResult: core::fmt::Debug + Send {
|
||||
fn get_argument_value_at_address(
|
||||
&self,
|
||||
address: u64,
|
||||
argument: u8,
|
||||
) -> Option<&FlowAnalysisValue>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash, Default)]
|
||||
pub struct Symbol {
|
||||
pub name: String,
|
||||
@@ -260,6 +273,7 @@ pub struct Object {
|
||||
pub path: Option<std::path::PathBuf>,
|
||||
#[cfg(feature = "std")]
|
||||
pub timestamp: Option<filetime::FileTime>,
|
||||
pub flow_analysis_results: BTreeMap<u64, Box<dyn FlowAnalysisResult>>,
|
||||
}
|
||||
|
||||
impl Default for Object {
|
||||
@@ -274,6 +288,7 @@ impl Default for Object {
|
||||
path: None,
|
||||
#[cfg(feature = "std")]
|
||||
timestamp: None,
|
||||
flow_analysis_results: BTreeMap::<u64, Box<dyn FlowAnalysisResult>>::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -308,6 +323,12 @@ impl Object {
|
||||
let offset = symbol.address.checked_sub(section.address)?;
|
||||
section.data.get(offset as usize..offset as usize + symbol.size as usize)
|
||||
}
|
||||
|
||||
pub fn symbol_by_name(&self, name: &str) -> Option<usize> {
|
||||
self.symbols.iter().position(|symbol| symbol.section.is_some() && symbol.name == name)
|
||||
}
|
||||
|
||||
pub fn has_flow_analysis_result(&self) -> bool { !self.flow_analysis_results.is_empty() }
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
|
||||
@@ -121,6 +121,15 @@ fn map_symbols(
|
||||
Ok((symbols, symbol_indices))
|
||||
}
|
||||
|
||||
/// When inferring a symbol's size, we ignore symbols that start with specific prefixes. They are
|
||||
/// usually emitted as branch targets and do not represent the start of a function or object.
|
||||
fn is_local_label(symbol: &Symbol) -> bool {
|
||||
const LABEL_PREFIXES: &[&str] = &[".L", "LAB_"];
|
||||
symbol.size == 0
|
||||
&& symbol.flags.contains(SymbolFlag::Local)
|
||||
&& LABEL_PREFIXES.iter().any(|p| symbol.name.starts_with(p))
|
||||
}
|
||||
|
||||
fn infer_symbol_sizes(symbols: &mut [Symbol], sections: &[Section]) {
|
||||
// Create a sorted list of symbol indices by section
|
||||
let mut symbols_with_section = Vec::<usize>::with_capacity(symbols.len());
|
||||
@@ -167,27 +176,28 @@ fn infer_symbol_sizes(symbols: &mut [Symbol], sections: &[Section]) {
|
||||
if last_end.0 == section_idx && last_end.1 > symbol.address {
|
||||
continue;
|
||||
}
|
||||
let next_symbol = match symbol.kind {
|
||||
// For function/object symbols, find the next function/object symbol (in other words:
|
||||
// skip over labels)
|
||||
SymbolKind::Function | SymbolKind::Object => loop {
|
||||
if iter_idx >= symbols_with_section.len() {
|
||||
break None;
|
||||
let next_symbol = loop {
|
||||
if iter_idx >= symbols_with_section.len() {
|
||||
break None;
|
||||
}
|
||||
let next_symbol = &symbols[symbols_with_section[iter_idx]];
|
||||
if next_symbol.section != Some(section_idx) {
|
||||
break None;
|
||||
}
|
||||
if match symbol.kind {
|
||||
SymbolKind::Function | SymbolKind::Object => {
|
||||
// For function/object symbols, find the next function/object
|
||||
matches!(next_symbol.kind, SymbolKind::Function | SymbolKind::Object)
|
||||
}
|
||||
let next_symbol = &symbols[symbols_with_section[iter_idx]];
|
||||
if next_symbol.section != Some(section_idx) {
|
||||
break None;
|
||||
SymbolKind::Unknown | SymbolKind::Section => {
|
||||
// For labels (or anything else), stop at any symbol
|
||||
true
|
||||
}
|
||||
if let SymbolKind::Function | SymbolKind::Object = next_symbol.kind {
|
||||
break Some(next_symbol);
|
||||
}
|
||||
iter_idx += 1;
|
||||
},
|
||||
// For labels (or anything else), simply use the next symbol's address
|
||||
SymbolKind::Unknown | SymbolKind::Section => symbols_with_section
|
||||
.get(iter_idx)
|
||||
.map(|&i| &symbols[i])
|
||||
.take_if(|s| s.section == Some(section_idx)),
|
||||
} && !is_local_label(next_symbol)
|
||||
{
|
||||
break Some(next_symbol);
|
||||
}
|
||||
iter_idx += 1;
|
||||
};
|
||||
let next_address = next_symbol.map(|s| s.address).unwrap_or_else(|| {
|
||||
let section = §ions[section_idx];
|
||||
@@ -341,7 +351,7 @@ fn map_section_relocations(
|
||||
let idx = if let Some(section_symbol) = obj_file
|
||||
.symbol_by_index(idx)
|
||||
.ok()
|
||||
.take_if(|s| s.kind() == object::SymbolKind::Section)
|
||||
.filter(|s| s.kind() == object::SymbolKind::Section)
|
||||
{
|
||||
let section_index =
|
||||
section_symbol.section_index().context("Section symbol without section")?;
|
||||
@@ -422,17 +432,18 @@ fn map_relocations(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn calculate_pooled_relocations(
|
||||
arch: &dyn Arch,
|
||||
sections: &mut [Section],
|
||||
symbols: &[Symbol],
|
||||
) -> Result<()> {
|
||||
for (section_index, section) in sections.iter_mut().enumerate() {
|
||||
fn perform_data_flow_analysis(obj: &mut Object, config: &DiffObjConfig) -> Result<()> {
|
||||
// If neither of these settings are on, no flow analysis to perform
|
||||
if !config.analyze_data_flow && !config.ppc_calculate_pool_relocations {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut generated_relocations = Vec::<(usize, Vec<Relocation>)>::new();
|
||||
for (section_index, section) in obj.sections.iter().enumerate() {
|
||||
if section.kind != SectionKind::Code {
|
||||
continue;
|
||||
}
|
||||
let mut fake_pool_relocs = Vec::new();
|
||||
for symbol in symbols {
|
||||
for symbol in obj.symbols.iter() {
|
||||
if symbol.section != Some(section_index) {
|
||||
continue;
|
||||
}
|
||||
@@ -447,14 +458,32 @@ fn calculate_pooled_relocations(
|
||||
symbol.address + symbol.size
|
||||
)
|
||||
})?;
|
||||
fake_pool_relocs.append(&mut arch.generate_pooled_relocations(
|
||||
symbol.address,
|
||||
code,
|
||||
§ion.relocations,
|
||||
symbols,
|
||||
));
|
||||
|
||||
// Optional pooled relocation computation
|
||||
// Long view: This could be replaced by the full data flow analysis
|
||||
// once that feature has stabilized.
|
||||
if config.ppc_calculate_pool_relocations {
|
||||
let relocations = obj.arch.generate_pooled_relocations(
|
||||
symbol.address,
|
||||
code,
|
||||
§ion.relocations,
|
||||
&obj.symbols,
|
||||
);
|
||||
generated_relocations.push((section_index, relocations));
|
||||
}
|
||||
|
||||
// Optional full data flow analysis
|
||||
if config.analyze_data_flow {
|
||||
obj.arch.data_flow_analysis(obj, symbol, code, §ion.relocations).and_then(
|
||||
|flow_result| obj.flow_analysis_results.insert(symbol.address, flow_result),
|
||||
);
|
||||
}
|
||||
}
|
||||
section.relocations.append(&mut fake_pool_relocs);
|
||||
}
|
||||
for (section_index, mut relocations) in generated_relocations {
|
||||
obj.sections[section_index].relocations.append(&mut relocations);
|
||||
}
|
||||
for section in obj.sections.iter_mut() {
|
||||
section.relocations.sort_by_key(|r| r.address);
|
||||
}
|
||||
Ok(())
|
||||
@@ -855,15 +884,12 @@ pub fn parse(data: &[u8], config: &DiffObjConfig) -> Result<Object> {
|
||||
let (mut symbols, symbol_indices) =
|
||||
map_symbols(arch.as_ref(), &obj_file, §ions, §ion_indices, split_meta.as_ref())?;
|
||||
map_relocations(arch.as_ref(), &obj_file, &mut sections, §ion_indices, &symbol_indices)?;
|
||||
if config.ppc_calculate_pool_relocations {
|
||||
calculate_pooled_relocations(arch.as_ref(), &mut sections, &symbols)?;
|
||||
}
|
||||
parse_line_info(&obj_file, &mut sections, §ion_indices, data)?;
|
||||
if config.combine_data_sections || config.combine_text_sections {
|
||||
combine_sections(&mut sections, &mut symbols, config)?;
|
||||
}
|
||||
arch.post_init(§ions, &symbols);
|
||||
Ok(Object {
|
||||
let mut obj = Object {
|
||||
arch,
|
||||
endianness: obj_file.endianness(),
|
||||
symbols,
|
||||
@@ -873,7 +899,14 @@ pub fn parse(data: &[u8], config: &DiffObjConfig) -> Result<Object> {
|
||||
path: None,
|
||||
#[cfg(feature = "std")]
|
||||
timestamp: None,
|
||||
})
|
||||
flow_analysis_results: Default::default(),
|
||||
};
|
||||
|
||||
// Need to construct the obj first so that we have a convinient package to
|
||||
// pass to flow analysis. Then the flow analysis will mutate obj adding
|
||||
// additional data to it.
|
||||
perform_data_flow_analysis(&mut obj, config)?;
|
||||
Ok(obj)
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user