mirror of
https://github.com/encounter/objdiff.git
synced 2026-07-10 12:18:36 -07:00
Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3b8603ab22 | |||
| 86b7d6b229 | |||
| 3f82781808 | |||
| 926dd06ce9 | |||
| de70b6fca0 | |||
| 79542a4725 | |||
| 0ebd2ae459 | |||
| 08ebea8bd2 | |||
| 46b8890159 | |||
| 8356fe670d | |||
| 7834185223 | |||
| 216dd70d5b | |||
| 976e629f08 | |||
| 67b28b7da1 | |||
| c0b9bfae21 | |||
| f3f4b0482a | |||
| 278683f103 | |||
| 6ad60de32a | |||
| 90c93164ab | |||
| c02eb31dbb | |||
| 86d92866aa | |||
| 51c3af2bbe | |||
| d0b8b449d9 | |||
| 32f5f202f7 | |||
| 481dbc185a |
Generated
+4
-4
@@ -3491,7 +3491,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "objdiff-cli"
|
name = "objdiff-cli"
|
||||||
version = "3.4.3"
|
version = "3.6.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"argp",
|
"argp",
|
||||||
@@ -3514,7 +3514,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "objdiff-core"
|
name = "objdiff-core"
|
||||||
version = "3.4.3"
|
version = "3.6.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"arm-attr",
|
"arm-attr",
|
||||||
@@ -3570,7 +3570,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "objdiff-gui"
|
name = "objdiff-gui"
|
||||||
version = "3.4.3"
|
version = "3.6.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"argp",
|
"argp",
|
||||||
@@ -3608,7 +3608,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "objdiff-wasm"
|
name = "objdiff-wasm"
|
||||||
version = "3.4.3"
|
version = "3.6.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"objdiff-core",
|
"objdiff-core",
|
||||||
|
|||||||
+1
-1
@@ -14,7 +14,7 @@ default-members = [
|
|||||||
resolver = "3"
|
resolver = "3"
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "3.4.3"
|
version = "3.6.0"
|
||||||
authors = ["Luke Street <luke@street.dev>"]
|
authors = ["Luke Street <luke@street.dev>"]
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
|
|||||||
@@ -76,6 +76,8 @@ ignore = [
|
|||||||
{ id = "RUSTSEC-2024-0436", reason = "Unmaintained paste crate is an indirect dependency" },
|
{ id = "RUSTSEC-2024-0436", reason = "Unmaintained paste crate is an indirect dependency" },
|
||||||
{ id = "RUSTSEC-2025-0052", reason = "Unmaintained async-std crate is an indirect dependency" },
|
{ id = "RUSTSEC-2025-0052", reason = "Unmaintained async-std crate is an indirect dependency" },
|
||||||
{ id = "RUSTSEC-2025-0119", reason = "Unmaintained number_prefix crate is an indirect dependency" },
|
{ id = "RUSTSEC-2025-0119", reason = "Unmaintained number_prefix crate is an indirect dependency" },
|
||||||
|
{ id = "RUSTSEC-2025-0134", reason = "Unmaintained rustls-pemfile crate is an indirect dependency" },
|
||||||
|
{ id = "RUSTSEC-2026-0002", reason = "Unsound advisory in lru 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.
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ use crossterm::{
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
use objdiff_core::{
|
use objdiff_core::{
|
||||||
|
bindings::diff::DiffResult,
|
||||||
build::{
|
build::{
|
||||||
BuildConfig, BuildStatus,
|
BuildConfig, BuildStatus,
|
||||||
watcher::{Watcher, create_watcher},
|
watcher::{Watcher, create_watcher},
|
||||||
@@ -28,7 +29,7 @@ use objdiff_core::{
|
|||||||
build_globset,
|
build_globset,
|
||||||
path::{check_path_buf, platform_path, platform_path_serde_option},
|
path::{check_path_buf, platform_path, platform_path_serde_option},
|
||||||
},
|
},
|
||||||
diff::{DiffObjConfig, MappingConfig, ObjectDiff},
|
diff::{self, DiffObjConfig, DiffSide, MappingConfig, ObjectDiff},
|
||||||
jobs::{
|
jobs::{
|
||||||
Job, JobQueue, JobResult,
|
Job, JobQueue, JobResult,
|
||||||
objdiff::{ObjDiffConfig, start_build},
|
objdiff::{ObjDiffConfig, start_build},
|
||||||
@@ -40,7 +41,10 @@ use typed_path::{Utf8PlatformPath, Utf8PlatformPathBuf};
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
cmd::apply_config_args,
|
cmd::apply_config_args,
|
||||||
util::term::crossterm_panic_handler,
|
util::{
|
||||||
|
output::{OutputFormat, write_output},
|
||||||
|
term::crossterm_panic_handler,
|
||||||
|
},
|
||||||
views::{EventControlFlow, EventResult, UiView, function_diff::FunctionDiffUi},
|
views::{EventControlFlow, EventResult, UiView, function_diff::FunctionDiffUi},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -60,6 +64,12 @@ pub struct Args {
|
|||||||
#[argp(option, short = 'u')]
|
#[argp(option, short = 'u')]
|
||||||
/// Unit name within project
|
/// Unit name within project
|
||||||
unit: Option<String>,
|
unit: Option<String>,
|
||||||
|
#[argp(option, short = 'o', from_str_fn(platform_path))]
|
||||||
|
/// Output file (one-shot mode) ("-" for stdout)
|
||||||
|
output: Option<Utf8PlatformPathBuf>,
|
||||||
|
#[argp(option)]
|
||||||
|
/// Output format (json, json-pretty, proto) (default: json)
|
||||||
|
format: Option<String>,
|
||||||
#[argp(positional)]
|
#[argp(positional)]
|
||||||
/// Function symbol to diff
|
/// Function symbol to diff
|
||||||
symbol: Option<String>,
|
symbol: Option<String>,
|
||||||
@@ -158,7 +168,41 @@ pub fn run(args: Args) -> Result<()> {
|
|||||||
_ => bail!("Either target and base or project and unit must be specified"),
|
_ => bail!("Either target and base or project and unit must be specified"),
|
||||||
};
|
};
|
||||||
|
|
||||||
run_interactive(args, target_path, base_path, project_config, unit_options)
|
if let Some(output) = &args.output {
|
||||||
|
run_oneshot(&args, output, target_path.as_deref(), base_path.as_deref(), unit_options)
|
||||||
|
} else {
|
||||||
|
run_interactive(args, target_path, base_path, project_config, unit_options)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_oneshot(
|
||||||
|
args: &Args,
|
||||||
|
output: &Utf8PlatformPath,
|
||||||
|
target_path: Option<&Utf8PlatformPath>,
|
||||||
|
base_path: Option<&Utf8PlatformPath>,
|
||||||
|
unit_options: Option<ProjectOptions>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let output_format = OutputFormat::from_option(args.format.as_deref())?;
|
||||||
|
let (diff_config, mapping_config) = build_config_from_args(args, None, unit_options.as_ref())?;
|
||||||
|
let target = target_path
|
||||||
|
.map(|p| {
|
||||||
|
obj::read::read(p.as_ref(), &diff_config, DiffSide::Target)
|
||||||
|
.with_context(|| format!("Loading {p}"))
|
||||||
|
})
|
||||||
|
.transpose()?;
|
||||||
|
let base = base_path
|
||||||
|
.map(|p| {
|
||||||
|
obj::read::read(p.as_ref(), &diff_config, DiffSide::Base)
|
||||||
|
.with_context(|| format!("Loading {p}"))
|
||||||
|
})
|
||||||
|
.transpose()?;
|
||||||
|
let result =
|
||||||
|
diff::diff_objs(target.as_ref(), base.as_ref(), None, &diff_config, &mapping_config)?;
|
||||||
|
let left = target.as_ref().and_then(|o| result.left.as_ref().map(|d| (o, d)));
|
||||||
|
let right = base.as_ref().and_then(|o| result.right.as_ref().map(|d| (o, d)));
|
||||||
|
let diff_result = DiffResult::new(left, right, &diff_config)?;
|
||||||
|
write_output(&diff_result, Some(output), output_format)?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_config_from_args(
|
fn build_config_from_args(
|
||||||
|
|||||||
@@ -539,6 +539,7 @@ impl FunctionDiffUi {
|
|||||||
DiffText::Opcode(mnemonic, _op) => format!("{mnemonic} "),
|
DiffText::Opcode(mnemonic, _op) => format!("{mnemonic} "),
|
||||||
DiffText::Argument(arg) => arg.to_string(),
|
DiffText::Argument(arg) => arg.to_string(),
|
||||||
DiffText::BranchDest(addr) => format!("{addr:x}"),
|
DiffText::BranchDest(addr) => format!("{addr:x}"),
|
||||||
|
DiffText::BranchArrow(_) => " ~> ".to_string(),
|
||||||
DiffText::Symbol(sym) => {
|
DiffText::Symbol(sym) => {
|
||||||
sym.demangled_name.as_ref().unwrap_or(&sym.name).clone()
|
sym.demangled_name.as_ref().unwrap_or(&sym.name).clone()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,162 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package objdiff.diff;
|
||||||
|
|
||||||
|
// Top-level diff result containing left and right object diffs
|
||||||
|
message DiffResult {
|
||||||
|
optional DiffObject left = 1;
|
||||||
|
optional DiffObject right = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Diff information for a single object file
|
||||||
|
message DiffObject {
|
||||||
|
repeated DiffSection sections = 1;
|
||||||
|
repeated DiffSymbol symbols = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Diff information for a section
|
||||||
|
message DiffSection {
|
||||||
|
string name = 1;
|
||||||
|
DiffSectionKind kind = 2;
|
||||||
|
uint64 size = 3;
|
||||||
|
uint64 address = 4;
|
||||||
|
optional float match_percent = 5;
|
||||||
|
repeated DiffDataSegment data_diff = 6;
|
||||||
|
repeated DiffDataRelocation reloc_diff = 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum DiffSectionKind {
|
||||||
|
SECTION_UNKNOWN = 0;
|
||||||
|
SECTION_CODE = 1;
|
||||||
|
SECTION_DATA = 2;
|
||||||
|
SECTION_BSS = 3;
|
||||||
|
SECTION_COMMON = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Symbol with diff information
|
||||||
|
message DiffSymbol {
|
||||||
|
string name = 1;
|
||||||
|
optional string demangled_name = 2;
|
||||||
|
uint64 address = 3;
|
||||||
|
uint64 size = 4;
|
||||||
|
uint32 flags = 5;
|
||||||
|
// The symbol index in the _other_ object that this symbol was diffed against
|
||||||
|
optional uint32 target_symbol = 6;
|
||||||
|
optional float match_percent = 7;
|
||||||
|
// Instruction diff rows (for code symbols)
|
||||||
|
repeated DiffInstructionRow instructions = 8;
|
||||||
|
// Data diff (for data symbols)
|
||||||
|
repeated DiffDataSegment data_diff = 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Symbol visibility flags (bitmask)
|
||||||
|
enum DiffSymbolFlag {
|
||||||
|
SYMBOL_NONE = 0;
|
||||||
|
SYMBOL_GLOBAL = 1;
|
||||||
|
SYMBOL_LOCAL = 2;
|
||||||
|
SYMBOL_WEAK = 4;
|
||||||
|
SYMBOL_COMMON = 8;
|
||||||
|
SYMBOL_HIDDEN = 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A single instruction diff row
|
||||||
|
message DiffInstructionRow {
|
||||||
|
DiffKind diff_kind = 1;
|
||||||
|
optional DiffInstruction instruction = 2;
|
||||||
|
repeated DiffInstructionArgDiff arg_diff = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parsed instruction information
|
||||||
|
message DiffInstruction {
|
||||||
|
uint64 address = 1;
|
||||||
|
uint32 size = 2;
|
||||||
|
// Formatted instruction string
|
||||||
|
string formatted = 3;
|
||||||
|
// Formatted instruction parts
|
||||||
|
repeated DiffInstructionPart parts = 4;
|
||||||
|
// Relocation information (if present)
|
||||||
|
optional DiffRelocation relocation = 5;
|
||||||
|
// Branch destination address
|
||||||
|
optional uint64 branch_dest = 6;
|
||||||
|
// Source line number (if present)
|
||||||
|
optional uint32 line_number = 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
// An instruction part
|
||||||
|
message DiffInstructionPart {
|
||||||
|
oneof part {
|
||||||
|
// Basic text (whitespace, punctuation, etc.)
|
||||||
|
string basic = 1;
|
||||||
|
// Opcode/mnemonic
|
||||||
|
DiffOpcode opcode = 2;
|
||||||
|
// Argument
|
||||||
|
DiffInstructionArg arg = 3;
|
||||||
|
// Separator between arguments (comma)
|
||||||
|
bool separator = 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message DiffOpcode {
|
||||||
|
// Mnemonic string
|
||||||
|
string mnemonic = 1;
|
||||||
|
// Base opcode ID
|
||||||
|
uint32 opcode = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// An instruction argument
|
||||||
|
message DiffInstructionArg {
|
||||||
|
oneof arg {
|
||||||
|
// Signed immediate value
|
||||||
|
sint64 signed = 1;
|
||||||
|
// Unsigned immediate value
|
||||||
|
uint64 unsigned = 2;
|
||||||
|
// Opaque string value (register names, etc.)
|
||||||
|
string opaque = 3;
|
||||||
|
// Relocation
|
||||||
|
bool reloc = 4;
|
||||||
|
// Branch destination address
|
||||||
|
uint64 branch_dest = 5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Relocation information
|
||||||
|
message DiffRelocation {
|
||||||
|
uint32 type = 1;
|
||||||
|
string type_name = 2;
|
||||||
|
uint32 target_symbol = 3;
|
||||||
|
int64 addend = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Argument diff information
|
||||||
|
message DiffInstructionArgDiff {
|
||||||
|
// If set, this argument differs from the other side.
|
||||||
|
// The value is a rotating index for coloring.
|
||||||
|
optional uint32 diff_index = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Diff kind for instructions and data
|
||||||
|
enum DiffKind {
|
||||||
|
DIFF_NONE = 0;
|
||||||
|
DIFF_REPLACE = 1;
|
||||||
|
DIFF_DELETE = 2;
|
||||||
|
DIFF_INSERT = 3;
|
||||||
|
DIFF_OP_MISMATCH = 4;
|
||||||
|
DIFF_ARG_MISMATCH = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Data diff segment
|
||||||
|
message DiffDataSegment {
|
||||||
|
DiffKind kind = 1;
|
||||||
|
bytes data = 2;
|
||||||
|
// Size may be larger than data length for BSS
|
||||||
|
uint64 size = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Data relocation diff
|
||||||
|
message DiffDataRelocation {
|
||||||
|
DiffRelocation relocation = 1;
|
||||||
|
DiffKind kind = 2;
|
||||||
|
// Address range this relocation covers
|
||||||
|
uint64 start = 3;
|
||||||
|
uint64 end = 4;
|
||||||
|
}
|
||||||
Binary file not shown.
@@ -345,7 +345,10 @@ impl Arch for ArchArm {
|
|||||||
let address = address as usize;
|
let address = address as usize;
|
||||||
let addend = match r_type {
|
let addend = match r_type {
|
||||||
// ARM calls
|
// ARM calls
|
||||||
elf::R_ARM_PC24 | elf::R_ARM_XPC25 | elf::R_ARM_CALL => {
|
elf::R_ARM_PC24
|
||||||
|
| elf::R_ARM_XPC25
|
||||||
|
| elf::R_ARM_CALL
|
||||||
|
| elf::R_ARM_JUMP24 => {
|
||||||
let data = section_data[address..address + 4].try_into()?;
|
let data = section_data[address..address + 4].try_into()?;
|
||||||
let addend = self.endianness.read_i32_bytes(data);
|
let addend = self.endianness.read_i32_bytes(data);
|
||||||
let imm24 = addend & 0xffffff;
|
let imm24 = addend & 0xffffff;
|
||||||
|
|||||||
@@ -7,12 +7,10 @@ use alloc::{
|
|||||||
};
|
};
|
||||||
use core::{
|
use core::{
|
||||||
any::Any,
|
any::Any,
|
||||||
ffi::CStr,
|
|
||||||
fmt::{self, Debug},
|
fmt::{self, Debug},
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::{Result, bail};
|
use anyhow::{Result, bail};
|
||||||
use encoding_rs::SHIFT_JIS;
|
|
||||||
use object::Endian as _;
|
use object::Endian as _;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -44,6 +42,16 @@ pub mod x86;
|
|||||||
pub const OPCODE_INVALID: u16 = u16::MAX;
|
pub const OPCODE_INVALID: u16 = u16::MAX;
|
||||||
pub const OPCODE_DATA: u16 = u16::MAX - 1;
|
pub const OPCODE_DATA: u16 = u16::MAX - 1;
|
||||||
|
|
||||||
|
const SUPPORTED_ENCODINGS: [(&encoding_rs::Encoding, &str); 7] = [
|
||||||
|
(encoding_rs::UTF_8, "UTF-8"),
|
||||||
|
(encoding_rs::SHIFT_JIS, "Shift JIS"),
|
||||||
|
(encoding_rs::UTF_16BE, "UTF-16BE"),
|
||||||
|
(encoding_rs::UTF_16LE, "UTF-16LE"),
|
||||||
|
(encoding_rs::WINDOWS_1252, "Windows-1252"),
|
||||||
|
(encoding_rs::EUC_JP, "EUC-JP"),
|
||||||
|
(encoding_rs::BIG5, "Big5"),
|
||||||
|
];
|
||||||
|
|
||||||
/// Represents the type of data associated with an instruction
|
/// Represents the type of data associated with an instruction
|
||||||
#[derive(PartialEq)]
|
#[derive(PartialEq)]
|
||||||
pub enum DataType {
|
pub enum DataType {
|
||||||
@@ -75,9 +83,9 @@ impl fmt::Display for DataType {
|
|||||||
impl DataType {
|
impl DataType {
|
||||||
pub fn display_labels(&self, endian: object::Endianness, bytes: &[u8]) -> Vec<String> {
|
pub fn display_labels(&self, endian: object::Endianness, bytes: &[u8]) -> Vec<String> {
|
||||||
let mut strs = Vec::new();
|
let mut strs = Vec::new();
|
||||||
for (literal, label_override) in self.display_literals(endian, bytes) {
|
for (literal, label_override, _) in self.display_literals(endian, bytes) {
|
||||||
let label = label_override.unwrap_or_else(|| self.to_string());
|
let label = label_override.unwrap_or_else(|| self.to_string());
|
||||||
strs.push(format!("{label}: {literal}"))
|
strs.push(format!("{label}: {literal:?}"))
|
||||||
}
|
}
|
||||||
strs
|
strs
|
||||||
}
|
}
|
||||||
@@ -86,7 +94,7 @@ impl DataType {
|
|||||||
&self,
|
&self,
|
||||||
endian: object::Endianness,
|
endian: object::Endianness,
|
||||||
bytes: &[u8],
|
bytes: &[u8],
|
||||||
) -> Vec<(String, Option<String>)> {
|
) -> Vec<(String, Option<String>, Option<String>)> {
|
||||||
let mut strs = Vec::new();
|
let mut strs = Vec::new();
|
||||||
if self.required_len().is_some_and(|l| bytes.len() < l) {
|
if self.required_len().is_some_and(|l| bytes.len() < l) {
|
||||||
log::warn!(
|
log::warn!(
|
||||||
@@ -110,34 +118,34 @@ impl DataType {
|
|||||||
match self {
|
match self {
|
||||||
DataType::Int8 => {
|
DataType::Int8 => {
|
||||||
let i = i8::from_ne_bytes(bytes.try_into().unwrap());
|
let i = i8::from_ne_bytes(bytes.try_into().unwrap());
|
||||||
strs.push((format!("{i:#x}"), None));
|
strs.push((format!("{i:#x}"), None, None));
|
||||||
|
|
||||||
if i < 0 {
|
if i < 0 {
|
||||||
strs.push((format!("{:#x}", ReallySigned(i)), None));
|
strs.push((format!("{:#x}", ReallySigned(i)), None, None));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DataType::Int16 => {
|
DataType::Int16 => {
|
||||||
let i = endian.read_i16_bytes(bytes.try_into().unwrap());
|
let i = endian.read_i16_bytes(bytes.try_into().unwrap());
|
||||||
strs.push((format!("{i:#x}"), None));
|
strs.push((format!("{i:#x}"), None, None));
|
||||||
|
|
||||||
if i < 0 {
|
if i < 0 {
|
||||||
strs.push((format!("{:#x}", ReallySigned(i)), None));
|
strs.push((format!("{:#x}", ReallySigned(i)), None, None));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DataType::Int32 => {
|
DataType::Int32 => {
|
||||||
let i = endian.read_i32_bytes(bytes.try_into().unwrap());
|
let i = endian.read_i32_bytes(bytes.try_into().unwrap());
|
||||||
strs.push((format!("{i:#x}"), None));
|
strs.push((format!("{i:#x}"), None, None));
|
||||||
|
|
||||||
if i < 0 {
|
if i < 0 {
|
||||||
strs.push((format!("{:#x}", ReallySigned(i)), None));
|
strs.push((format!("{:#x}", ReallySigned(i)), None, None));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DataType::Int64 => {
|
DataType::Int64 => {
|
||||||
let i = endian.read_i64_bytes(bytes.try_into().unwrap());
|
let i = endian.read_i64_bytes(bytes.try_into().unwrap());
|
||||||
strs.push((format!("{i:#x}"), None));
|
strs.push((format!("{i:#x}"), None, None));
|
||||||
|
|
||||||
if i < 0 {
|
if i < 0 {
|
||||||
strs.push((format!("{:#x}", ReallySigned(i)), None));
|
strs.push((format!("{:#x}", ReallySigned(i)), None, None));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DataType::Float => {
|
DataType::Float => {
|
||||||
@@ -148,6 +156,7 @@ impl DataType {
|
|||||||
object::Endianness::Big => f32::from_be_bytes(bytes),
|
object::Endianness::Big => f32::from_be_bytes(bytes),
|
||||||
}),
|
}),
|
||||||
None,
|
None,
|
||||||
|
None,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
DataType::Double => {
|
DataType::Double => {
|
||||||
@@ -158,23 +167,43 @@ impl DataType {
|
|||||||
object::Endianness::Big => f64::from_be_bytes(bytes),
|
object::Endianness::Big => f64::from_be_bytes(bytes),
|
||||||
}),
|
}),
|
||||||
None,
|
None,
|
||||||
|
None,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
DataType::Bytes => {
|
DataType::Bytes => {
|
||||||
strs.push((format!("{bytes:#?}"), None));
|
strs.push((format!("{bytes:#?}"), None, None));
|
||||||
}
|
}
|
||||||
DataType::String => {
|
DataType::String => {
|
||||||
if let Ok(cstr) = CStr::from_bytes_until_nul(bytes) {
|
// Special case to display (ASCII) as the label for ASCII-only strings.
|
||||||
strs.push((format!("{cstr:?}"), None));
|
let mut is_ascii = false;
|
||||||
|
if bytes.is_ascii()
|
||||||
|
&& let Ok(str) = str::from_utf8(bytes)
|
||||||
|
{
|
||||||
|
let trimmed = str.trim_end_matches('\0');
|
||||||
|
if !trimmed.is_empty() {
|
||||||
|
let copy_string = escape_special_ascii_characters(trimmed);
|
||||||
|
strs.push((trimmed.to_string(), Some("ASCII".into()), Some(copy_string)));
|
||||||
|
is_ascii = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if let Some(nul_idx) = bytes.iter().position(|&c| c == b'\0') {
|
|
||||||
let (cow, _, had_errors) = SHIFT_JIS.decode(&bytes[..nul_idx]);
|
for (encoding, encoding_name) in SUPPORTED_ENCODINGS {
|
||||||
if !had_errors {
|
// Avoid showing ASCII-only strings more than once if the encoding is ASCII-compatible.
|
||||||
let str = format!("{cow:?}");
|
if is_ascii && encoding.is_ascii_compatible() {
|
||||||
// Only add the Shift JIS string if it's different from the ASCII string.
|
continue;
|
||||||
if !strs.iter().any(|x| x.0 == str) {
|
}
|
||||||
strs.push((str, Some("Shift JIS".into())));
|
let (cow, _, had_errors) = encoding.decode(bytes);
|
||||||
}
|
if had_errors {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let trimmed = cow.trim_end_matches('\0');
|
||||||
|
if !trimmed.is_empty() {
|
||||||
|
let copy_string = escape_special_ascii_characters(trimmed);
|
||||||
|
strs.push((
|
||||||
|
trimmed.to_string(),
|
||||||
|
Some(encoding_name.into()),
|
||||||
|
Some(copy_string),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -489,3 +518,21 @@ pub struct RelocationOverride {
|
|||||||
pub target: RelocationOverrideTarget,
|
pub target: RelocationOverrideTarget,
|
||||||
pub addend: i64,
|
pub addend: i64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Escape ASCII characters such as \n or \t, but not Unicode characters such as \u{3000}.
|
||||||
|
/// Suitable for copying to clipboard.
|
||||||
|
fn escape_special_ascii_characters(value: &str) -> String {
|
||||||
|
let mut escaped = String::new();
|
||||||
|
escaped.push('"');
|
||||||
|
for c in value.chars() {
|
||||||
|
if c.is_ascii() {
|
||||||
|
for e in c.escape_default() {
|
||||||
|
escaped.push(e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
escaped.push(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
escaped.push('"');
|
||||||
|
escaped
|
||||||
|
}
|
||||||
|
|||||||
@@ -342,7 +342,10 @@ impl Arch for ArchPpc {
|
|||||||
elf::R_PPC_UADDR32 => 4,
|
elf::R_PPC_UADDR32 => 4,
|
||||||
_ => 1,
|
_ => 1,
|
||||||
},
|
},
|
||||||
_ => 1,
|
RelocationFlags::Coff(r_type) => match r_type {
|
||||||
|
pe::IMAGE_REL_PPC_ADDR32 => 4,
|
||||||
|
_ => 1,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -355,7 +358,9 @@ impl Arch for ArchPpc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn guess_data_type(&self, resolved: ResolvedInstructionRef, bytes: &[u8]) -> Option<DataType> {
|
fn guess_data_type(&self, resolved: ResolvedInstructionRef, bytes: &[u8]) -> Option<DataType> {
|
||||||
if resolved.relocation.is_some_and(|r| r.symbol.name.starts_with("@stringBase")) {
|
if resolved.relocation.is_some_and(|r| {
|
||||||
|
r.symbol.name.starts_with("@stringBase") || r.symbol.name.starts_with("$SG")
|
||||||
|
}) {
|
||||||
// Pooled string.
|
// Pooled string.
|
||||||
return Some(DataType::String);
|
return Some(DataType::String);
|
||||||
}
|
}
|
||||||
@@ -440,9 +445,13 @@ impl Arch for ArchPpc {
|
|||||||
let simplified = ins.simplified().to_string();
|
let simplified = ins.simplified().to_string();
|
||||||
let show_orig = orig != simplified;
|
let show_orig = orig != simplified;
|
||||||
let mut out = Vec::with_capacity(2);
|
let mut out = Vec::with_capacity(2);
|
||||||
out.push(ContextItem::Copy { value: simplified, label: None });
|
out.push(ContextItem::Copy { value: simplified, label: None, copy_string: None });
|
||||||
if show_orig {
|
if show_orig {
|
||||||
out.push(ContextItem::Copy { value: orig, label: Some("original".to_string()) });
|
out.push(ContextItem::Copy {
|
||||||
|
value: orig,
|
||||||
|
label: Some("original".to_string()),
|
||||||
|
copy_string: None,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
out
|
out
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,7 +68,12 @@ impl ArchX86 {
|
|||||||
_ => None,
|
_ => None,
|
||||||
},
|
},
|
||||||
RelocationFlags::Elf(typ) => match typ {
|
RelocationFlags::Elf(typ) => match typ {
|
||||||
elf::R_386_32 | elf::R_386_PC32 => Some(4),
|
elf::R_386_32
|
||||||
|
| elf::R_386_PC32
|
||||||
|
| elf::R_386_GOT32
|
||||||
|
| elf::R_386_PLT32
|
||||||
|
| elf::R_386_GOTOFF
|
||||||
|
| elf::R_386_GOTPC => Some(4),
|
||||||
elf::R_386_16 => Some(2),
|
elf::R_386_16 => Some(2),
|
||||||
_ => None,
|
_ => None,
|
||||||
},
|
},
|
||||||
@@ -272,7 +277,15 @@ impl Arch for ArchX86 {
|
|||||||
object::RelocationFlags::Coff {
|
object::RelocationFlags::Coff {
|
||||||
typ: pe::IMAGE_REL_I386_DIR32 | pe::IMAGE_REL_I386_REL32,
|
typ: pe::IMAGE_REL_I386_DIR32 | pe::IMAGE_REL_I386_REL32,
|
||||||
}
|
}
|
||||||
| object::RelocationFlags::Elf { r_type: elf::R_386_32 | elf::R_386_PC32 } => {
|
| object::RelocationFlags::Elf {
|
||||||
|
r_type:
|
||||||
|
elf::R_386_32
|
||||||
|
| elf::R_386_PC32
|
||||||
|
| elf::R_386_GOT32
|
||||||
|
| elf::R_386_PLT32
|
||||||
|
| elf::R_386_GOTOFF
|
||||||
|
| elf::R_386_GOTPC,
|
||||||
|
} => {
|
||||||
let data =
|
let data =
|
||||||
section.data()?[address as usize..address as usize + 4].try_into()?;
|
section.data()?[address as usize..address as usize + 4].try_into()?;
|
||||||
self.endianness.read_i32_bytes(data) as i64
|
self.endianness.read_i32_bytes(data) as i64
|
||||||
@@ -308,7 +321,16 @@ impl Arch for ArchX86 {
|
|||||||
pe::IMAGE_REL_I386_REL32 => Some("IMAGE_REL_I386_REL32"),
|
pe::IMAGE_REL_I386_REL32 => Some("IMAGE_REL_I386_REL32"),
|
||||||
_ => None,
|
_ => None,
|
||||||
},
|
},
|
||||||
_ => None,
|
RelocationFlags::Elf(typ) => match typ {
|
||||||
|
elf::R_386_32 => Some("R_386_32"),
|
||||||
|
elf::R_386_PC32 => Some("R_386_PC32"),
|
||||||
|
elf::R_386_GOT32 => Some("R_386_GOT32"),
|
||||||
|
elf::R_386_PLT32 => Some("R_386_PLT32"),
|
||||||
|
elf::R_386_GOTOFF => Some("R_386_GOTOFF"),
|
||||||
|
elf::R_386_GOTPC => Some("R_386_GOTPC"),
|
||||||
|
elf::R_386_16 => Some("R_386_16"),
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Architecture::X86_64 => match flags {
|
Architecture::X86_64 => match flags {
|
||||||
RelocationFlags::Coff(typ) => match typ {
|
RelocationFlags::Coff(typ) => match typ {
|
||||||
|
|||||||
@@ -0,0 +1,350 @@
|
|||||||
|
#![allow(clippy::needless_lifetimes)] // Generated serde code
|
||||||
|
|
||||||
|
use alloc::{
|
||||||
|
string::{String, ToString},
|
||||||
|
vec,
|
||||||
|
vec::Vec,
|
||||||
|
};
|
||||||
|
use core::fmt::Write;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
diff::{self, DiffObjConfig, display::InstructionPart},
|
||||||
|
obj::{self, Object, SymbolFlag},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Protobuf diff types
|
||||||
|
include!(concat!(env!("OUT_DIR"), "/objdiff.diff.rs"));
|
||||||
|
#[cfg(feature = "serde")]
|
||||||
|
include!(concat!(env!("OUT_DIR"), "/objdiff.diff.serde.rs"));
|
||||||
|
|
||||||
|
impl DiffResult {
|
||||||
|
pub fn new(
|
||||||
|
left: Option<(&Object, &diff::ObjectDiff)>,
|
||||||
|
right: Option<(&Object, &diff::ObjectDiff)>,
|
||||||
|
diff_config: &DiffObjConfig,
|
||||||
|
) -> Result<Self> {
|
||||||
|
Ok(Self {
|
||||||
|
left: left.map(|(obj, diff)| DiffObject::new(obj, diff, diff_config)).transpose()?,
|
||||||
|
right: right.map(|(obj, diff)| DiffObject::new(obj, diff, diff_config)).transpose()?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DiffObject {
|
||||||
|
pub fn new(obj: &Object, diff: &diff::ObjectDiff, diff_config: &DiffObjConfig) -> Result<Self> {
|
||||||
|
let mut sections = Vec::with_capacity(obj.sections.len());
|
||||||
|
for (section_idx, section) in obj.sections.iter().enumerate() {
|
||||||
|
let section_diff = &diff.sections[section_idx];
|
||||||
|
sections.push(DiffSection::new(obj, section, section_diff));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut symbols = Vec::with_capacity(obj.symbols.len());
|
||||||
|
for (symbol_idx, symbol) in obj.symbols.iter().enumerate() {
|
||||||
|
let symbol_diff = &diff.symbols[symbol_idx];
|
||||||
|
if symbol.size == 0 || symbol.flags.contains(SymbolFlag::Ignored) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
symbols.push(DiffSymbol::new(obj, symbol_idx, symbol, symbol_diff, diff_config)?);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self { sections, symbols })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DiffSection {
|
||||||
|
pub fn new(obj: &Object, section: &obj::Section, section_diff: &diff::SectionDiff) -> Self {
|
||||||
|
Self {
|
||||||
|
name: section.name.clone(),
|
||||||
|
kind: DiffSectionKind::from(section.kind) as i32,
|
||||||
|
size: section.size,
|
||||||
|
address: section.address,
|
||||||
|
match_percent: section_diff.match_percent,
|
||||||
|
data_diff: section_diff.data_diff.iter().map(DiffDataSegment::from).collect(),
|
||||||
|
reloc_diff: section_diff
|
||||||
|
.reloc_diff
|
||||||
|
.iter()
|
||||||
|
.map(|r| DiffDataRelocation::new(obj, r))
|
||||||
|
.collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<obj::SectionKind> for DiffSectionKind {
|
||||||
|
fn from(value: obj::SectionKind) -> Self {
|
||||||
|
match value {
|
||||||
|
obj::SectionKind::Unknown => DiffSectionKind::SectionUnknown,
|
||||||
|
obj::SectionKind::Code => DiffSectionKind::SectionCode,
|
||||||
|
obj::SectionKind::Data => DiffSectionKind::SectionData,
|
||||||
|
obj::SectionKind::Bss => DiffSectionKind::SectionBss,
|
||||||
|
obj::SectionKind::Common => DiffSectionKind::SectionCommon,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DiffSymbol {
|
||||||
|
pub fn new(
|
||||||
|
obj: &Object,
|
||||||
|
symbol_idx: usize,
|
||||||
|
symbol: &obj::Symbol,
|
||||||
|
symbol_diff: &diff::SymbolDiff,
|
||||||
|
diff_config: &DiffObjConfig,
|
||||||
|
) -> Result<Self> {
|
||||||
|
// Convert instruction rows
|
||||||
|
let instructions = symbol_diff
|
||||||
|
.instruction_rows
|
||||||
|
.iter()
|
||||||
|
.map(|row| DiffInstructionRow::new(obj, symbol_idx, row, diff_config))
|
||||||
|
.collect::<Result<Vec<_>>>()?;
|
||||||
|
|
||||||
|
// Convert data diff - flatten DataDiffRow segments into a single list
|
||||||
|
let data_diff: Vec<DiffDataSegment> = symbol_diff
|
||||||
|
.data_rows
|
||||||
|
.iter()
|
||||||
|
.flat_map(|row| row.segments.iter().map(DiffDataSegment::from))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
// Symbol metadata
|
||||||
|
name: symbol.name.clone(),
|
||||||
|
demangled_name: symbol.demangled_name.clone(),
|
||||||
|
address: symbol.address,
|
||||||
|
size: symbol.size,
|
||||||
|
flags: symbol_flags(&symbol.flags),
|
||||||
|
// Diff information
|
||||||
|
target_symbol: symbol_diff.target_symbol.map(|i| i as u32),
|
||||||
|
match_percent: symbol_diff.match_percent,
|
||||||
|
instructions,
|
||||||
|
data_diff,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn symbol_flags(flags: &obj::SymbolFlagSet) -> u32 {
|
||||||
|
let mut result = 0u32;
|
||||||
|
if flags.contains(SymbolFlag::Global) {
|
||||||
|
result |= DiffSymbolFlag::SymbolGlobal as u32;
|
||||||
|
}
|
||||||
|
if flags.contains(SymbolFlag::Local) {
|
||||||
|
result |= DiffSymbolFlag::SymbolLocal as u32;
|
||||||
|
}
|
||||||
|
if flags.contains(SymbolFlag::Weak) {
|
||||||
|
result |= DiffSymbolFlag::SymbolWeak as u32;
|
||||||
|
}
|
||||||
|
if flags.contains(SymbolFlag::Common) {
|
||||||
|
result |= DiffSymbolFlag::SymbolCommon as u32;
|
||||||
|
}
|
||||||
|
if flags.contains(SymbolFlag::Hidden) {
|
||||||
|
result |= DiffSymbolFlag::SymbolHidden as u32;
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DiffInstructionRow {
|
||||||
|
pub fn new(
|
||||||
|
obj: &Object,
|
||||||
|
symbol_idx: usize,
|
||||||
|
row: &diff::InstructionDiffRow,
|
||||||
|
diff_config: &DiffObjConfig,
|
||||||
|
) -> Result<Self> {
|
||||||
|
let instruction = if let Some(ins_ref) = row.ins_ref {
|
||||||
|
let resolved = obj.resolve_instruction_ref(symbol_idx, ins_ref);
|
||||||
|
resolved.map(|r| DiffInstruction::new(obj, r, diff_config)).transpose()?
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let arg_diff =
|
||||||
|
row.arg_diff.iter().map(|d| DiffInstructionArgDiff { diff_index: d.get() }).collect();
|
||||||
|
|
||||||
|
Ok(Self { diff_kind: DiffKind::from(row.kind) as i32, instruction, arg_diff })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DiffInstruction {
|
||||||
|
pub fn new(
|
||||||
|
obj: &Object,
|
||||||
|
resolved: obj::ResolvedInstructionRef,
|
||||||
|
diff_config: &DiffObjConfig,
|
||||||
|
) -> Result<Self> {
|
||||||
|
let mut formatted = String::new();
|
||||||
|
let mut parts = vec![];
|
||||||
|
let separator = diff_config.separator();
|
||||||
|
|
||||||
|
// Use the arch's display_instruction to get formatted parts
|
||||||
|
obj.arch.display_instruction(resolved, diff_config, &mut |part| {
|
||||||
|
write_instruction_part(&mut formatted, &part, separator, resolved.relocation);
|
||||||
|
parts
|
||||||
|
.push(DiffInstructionPart { part: Some(diff_instruction_part::Part::from(&part)) });
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let relocation = resolved.relocation.map(|r| DiffRelocation::new(obj, r));
|
||||||
|
|
||||||
|
let line_number = resolved
|
||||||
|
.section
|
||||||
|
.line_info
|
||||||
|
.range(..=resolved.ins_ref.address)
|
||||||
|
.last()
|
||||||
|
.map(|(_, &line)| line);
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
address: resolved.ins_ref.address,
|
||||||
|
size: resolved.ins_ref.size as u32,
|
||||||
|
formatted,
|
||||||
|
parts,
|
||||||
|
relocation,
|
||||||
|
branch_dest: resolved.ins_ref.branch_dest,
|
||||||
|
line_number,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_instruction_part(
|
||||||
|
out: &mut String,
|
||||||
|
part: &InstructionPart,
|
||||||
|
separator: &str,
|
||||||
|
reloc: Option<obj::ResolvedRelocation>,
|
||||||
|
) {
|
||||||
|
match part {
|
||||||
|
InstructionPart::Basic(s) => out.push_str(s),
|
||||||
|
InstructionPart::Opcode(s, _) => {
|
||||||
|
out.push_str(s);
|
||||||
|
out.push(' ');
|
||||||
|
}
|
||||||
|
InstructionPart::Arg(arg) => match arg {
|
||||||
|
obj::InstructionArg::Value(v) => {
|
||||||
|
let _ = write!(out, "{}", v);
|
||||||
|
}
|
||||||
|
obj::InstructionArg::Reloc => {
|
||||||
|
if let Some(resolved) = reloc {
|
||||||
|
out.push_str(&resolved.symbol.name);
|
||||||
|
if resolved.relocation.addend != 0 {
|
||||||
|
if resolved.relocation.addend < 0 {
|
||||||
|
let _ = write!(out, "-{:#x}", -resolved.relocation.addend);
|
||||||
|
} else {
|
||||||
|
let _ = write!(out, "+{:#x}", resolved.relocation.addend);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
obj::InstructionArg::BranchDest(dest) => {
|
||||||
|
let _ = write!(out, "{:#x}", dest);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
InstructionPart::Separator => out.push_str(separator),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl diff_instruction_part::Part {
|
||||||
|
fn from(part: &InstructionPart) -> Self {
|
||||||
|
match part {
|
||||||
|
InstructionPart::Basic(s) => diff_instruction_part::Part::Basic(s.to_string()),
|
||||||
|
InstructionPart::Opcode(mnemonic, opcode) => {
|
||||||
|
diff_instruction_part::Part::Opcode(DiffOpcode {
|
||||||
|
mnemonic: mnemonic.to_string(),
|
||||||
|
opcode: *opcode as u32,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
InstructionPart::Arg(arg) => {
|
||||||
|
diff_instruction_part::Part::Arg(DiffInstructionArg::from(arg))
|
||||||
|
}
|
||||||
|
InstructionPart::Separator => diff_instruction_part::Part::Separator(true),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&obj::InstructionArg<'_>> for DiffInstructionArg {
|
||||||
|
fn from(arg: &obj::InstructionArg) -> Self {
|
||||||
|
let arg = match arg {
|
||||||
|
obj::InstructionArg::Value(v) => match v {
|
||||||
|
obj::InstructionArgValue::Signed(v) => diff_instruction_arg::Arg::Signed(*v),
|
||||||
|
obj::InstructionArgValue::Unsigned(v) => diff_instruction_arg::Arg::Unsigned(*v),
|
||||||
|
obj::InstructionArgValue::Opaque(v) => {
|
||||||
|
diff_instruction_arg::Arg::Opaque(v.to_string())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
obj::InstructionArg::Reloc => diff_instruction_arg::Arg::Reloc(true),
|
||||||
|
obj::InstructionArg::BranchDest(dest) => diff_instruction_arg::Arg::BranchDest(*dest),
|
||||||
|
};
|
||||||
|
DiffInstructionArg { arg: Some(arg) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DiffRelocation {
|
||||||
|
pub fn new(obj: &Object, resolved: obj::ResolvedRelocation) -> Self {
|
||||||
|
let type_val = relocation_type(resolved.relocation.flags);
|
||||||
|
let type_name = obj
|
||||||
|
.arch
|
||||||
|
.reloc_name(resolved.relocation.flags)
|
||||||
|
.map(|s| s.to_string())
|
||||||
|
.unwrap_or_default();
|
||||||
|
Self {
|
||||||
|
r#type: type_val,
|
||||||
|
type_name,
|
||||||
|
target_symbol: resolved.relocation.target_symbol as u32,
|
||||||
|
addend: resolved.relocation.addend,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn relocation_type(flags: obj::RelocationFlags) -> u32 {
|
||||||
|
match flags {
|
||||||
|
obj::RelocationFlags::Elf(r_type) => r_type,
|
||||||
|
obj::RelocationFlags::Coff(typ) => typ as u32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<diff::InstructionDiffKind> for DiffKind {
|
||||||
|
fn from(value: diff::InstructionDiffKind) -> Self {
|
||||||
|
match value {
|
||||||
|
diff::InstructionDiffKind::None => DiffKind::DiffNone,
|
||||||
|
diff::InstructionDiffKind::OpMismatch => DiffKind::DiffOpMismatch,
|
||||||
|
diff::InstructionDiffKind::ArgMismatch => DiffKind::DiffArgMismatch,
|
||||||
|
diff::InstructionDiffKind::Replace => DiffKind::DiffReplace,
|
||||||
|
diff::InstructionDiffKind::Delete => DiffKind::DiffDelete,
|
||||||
|
diff::InstructionDiffKind::Insert => DiffKind::DiffInsert,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<diff::DataDiffKind> for DiffKind {
|
||||||
|
fn from(value: diff::DataDiffKind) -> Self {
|
||||||
|
match value {
|
||||||
|
diff::DataDiffKind::None => DiffKind::DiffNone,
|
||||||
|
diff::DataDiffKind::Replace => DiffKind::DiffReplace,
|
||||||
|
diff::DataDiffKind::Delete => DiffKind::DiffDelete,
|
||||||
|
diff::DataDiffKind::Insert => DiffKind::DiffInsert,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&diff::DataDiff> for DiffDataSegment {
|
||||||
|
fn from(value: &diff::DataDiff) -> Self {
|
||||||
|
Self {
|
||||||
|
kind: DiffKind::from(value.kind) as i32,
|
||||||
|
data: value.data.clone(),
|
||||||
|
size: value.size as u64,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DiffDataRelocation {
|
||||||
|
pub fn new(obj: &Object, value: &diff::DataRelocationDiff) -> Self {
|
||||||
|
let type_val = relocation_type(value.reloc.flags);
|
||||||
|
let type_name =
|
||||||
|
obj.arch.reloc_name(value.reloc.flags).map(|s| s.to_string()).unwrap_or_default();
|
||||||
|
Self {
|
||||||
|
relocation: Some(DiffRelocation {
|
||||||
|
r#type: type_val,
|
||||||
|
type_name,
|
||||||
|
target_symbol: value.reloc.target_symbol as u32,
|
||||||
|
addend: value.reloc.addend,
|
||||||
|
}),
|
||||||
|
kind: DiffKind::from(value.kind) as i32,
|
||||||
|
start: value.range.start,
|
||||||
|
end: value.range.end,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1 +1,3 @@
|
|||||||
|
#[cfg(feature = "any-arch")]
|
||||||
|
pub mod diff;
|
||||||
pub mod report;
|
pub mod report;
|
||||||
|
|||||||
@@ -35,18 +35,13 @@ pub fn diff_bss_symbol(
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn symbol_name_matches(left_name: &str, right_name: &str) -> bool {
|
pub fn symbol_name_matches(left: &Symbol, right: &Symbol) -> bool {
|
||||||
// Match Metrowerks symbol$1234 against symbol$2345
|
if let Some(left_name) = &left.normalized_name
|
||||||
// and GCC symbol.1234 against symbol.2345
|
&& let Some(right_name) = &right.normalized_name
|
||||||
if let Some((prefix, suffix)) = left_name.split_once(['$', '.']) {
|
{
|
||||||
if !suffix.chars().all(char::is_numeric) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
right_name
|
|
||||||
.split_once(['$', '.'])
|
|
||||||
.is_some_and(|(p, s)| p == prefix && s.chars().all(char::is_numeric))
|
|
||||||
} else {
|
|
||||||
left_name == right_name
|
left_name == right_name
|
||||||
|
} else {
|
||||||
|
left.name == right.name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,7 +55,7 @@ fn reloc_eq(
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let symbol_name_addend_matches = symbol_name_matches(&left.symbol.name, &right.symbol.name)
|
let symbol_name_addend_matches = symbol_name_matches(left.symbol, right.symbol)
|
||||||
&& left.relocation.addend == right.relocation.addend;
|
&& left.relocation.addend == right.relocation.addend;
|
||||||
match (left.symbol.section, right.symbol.section) {
|
match (left.symbol.section, right.symbol.section) {
|
||||||
(Some(sl), Some(sr)) => {
|
(Some(sl), Some(sr)) => {
|
||||||
@@ -588,6 +583,7 @@ fn symbols_matching_section(
|
|||||||
s.section == Some(section_idx)
|
s.section == Some(section_idx)
|
||||||
&& s.kind != SymbolKind::Section
|
&& s.kind != SymbolKind::Section
|
||||||
&& s.size > 0
|
&& s.size > 0
|
||||||
|
&& !s.flags.contains(SymbolFlag::Hidden)
|
||||||
&& !s.flags.contains(SymbolFlag::Ignored)
|
&& !s.flags.contains(SymbolFlag::Ignored)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ pub enum DiffText<'a> {
|
|||||||
Argument(InstructionArgValue<'a>),
|
Argument(InstructionArgValue<'a>),
|
||||||
/// Branch destination
|
/// Branch destination
|
||||||
BranchDest(u64),
|
BranchDest(u64),
|
||||||
|
/// Branch source/destination arrow, scrolls to a specific row when clicked
|
||||||
|
BranchArrow(u32),
|
||||||
/// Symbol name
|
/// Symbol name
|
||||||
Symbol(&'a Symbol),
|
Symbol(&'a Symbol),
|
||||||
/// Relocation addend
|
/// Relocation addend
|
||||||
@@ -185,7 +187,13 @@ pub fn display_row(
|
|||||||
pad_to: 5,
|
pad_to: 5,
|
||||||
})?;
|
})?;
|
||||||
if let Some(branch) = &ins_row.branch_from {
|
if let Some(branch) = &ins_row.branch_from {
|
||||||
cb(DiffTextSegment::basic(" ~> ", DiffTextColor::Rotating(branch.branch_idx as u8)))?;
|
// Pick the first branch source if there are multiple.
|
||||||
|
let ins_idx = branch.ins_idx[0];
|
||||||
|
cb(DiffTextSegment {
|
||||||
|
text: DiffText::BranchArrow(ins_idx),
|
||||||
|
color: DiffTextColor::Rotating(branch.branch_idx as u8),
|
||||||
|
pad_to: 0,
|
||||||
|
})?;
|
||||||
} else {
|
} else {
|
||||||
cb(DiffTextSegment::spacing(4))?;
|
cb(DiffTextSegment::spacing(4))?;
|
||||||
}
|
}
|
||||||
@@ -290,9 +298,8 @@ pub fn display_row(
|
|||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
// Fallback for relocation that wasn't displayed
|
// Fallback for relocation that wasn't displayed
|
||||||
if resolved.relocation.is_some() && !displayed_relocation {
|
if !displayed_relocation && let Some(resolved) = resolved.relocation {
|
||||||
cb(DiffTextSegment::basic(" <", base_color))?;
|
cb(DiffTextSegment::basic(" <", base_color))?;
|
||||||
let resolved = resolved.relocation.unwrap();
|
|
||||||
let diff_index = ins_row.arg_diff.get(arg_idx).copied().unwrap_or_default();
|
let diff_index = ins_row.arg_diff.get(arg_idx).copied().unwrap_or_default();
|
||||||
let color =
|
let color =
|
||||||
diff_index.get().map_or(DiffTextColor::Bright, |i| DiffTextColor::Rotating(i as u8));
|
diff_index.get().map_or(DiffTextColor::Bright, |i| DiffTextColor::Rotating(i as u8));
|
||||||
@@ -307,7 +314,11 @@ pub fn display_row(
|
|||||||
cb(DiffTextSegment::basic(">", base_color))?;
|
cb(DiffTextSegment::basic(">", base_color))?;
|
||||||
}
|
}
|
||||||
if let Some(branch) = &ins_row.branch_to {
|
if let Some(branch) = &ins_row.branch_to {
|
||||||
cb(DiffTextSegment::basic(" ~>", DiffTextColor::Rotating(branch.branch_idx as u8)))?;
|
cb(DiffTextSegment {
|
||||||
|
text: DiffText::BranchArrow(branch.ins_idx),
|
||||||
|
color: DiffTextColor::Rotating(branch.branch_idx as u8),
|
||||||
|
pad_to: 0,
|
||||||
|
})?;
|
||||||
}
|
}
|
||||||
cb(EOL_SEGMENT)?;
|
cb(EOL_SEGMENT)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -354,7 +365,7 @@ impl From<&DiffText<'_>> for HighlightKind {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub enum ContextItem {
|
pub enum ContextItem {
|
||||||
Copy { value: String, label: Option<String> },
|
Copy { value: String, label: Option<String>, copy_string: Option<String> },
|
||||||
Navigate { label: String, symbol_index: usize, kind: SymbolNavigationKind },
|
Navigate { label: String, symbol_index: usize, kind: SymbolNavigationKind },
|
||||||
Separator,
|
Separator,
|
||||||
}
|
}
|
||||||
@@ -386,9 +397,9 @@ pub fn symbol_context(obj: &Object, symbol_index: usize) -> Vec<ContextItem> {
|
|||||||
return Vec::new();
|
return Vec::new();
|
||||||
};
|
};
|
||||||
let mut out = Vec::new();
|
let mut out = Vec::new();
|
||||||
out.push(ContextItem::Copy { value: symbol.name.clone(), label: None });
|
out.push(ContextItem::Copy { value: symbol.name.clone(), label: None, copy_string: None });
|
||||||
if let Some(name) = &symbol.demangled_name {
|
if let Some(name) = &symbol.demangled_name {
|
||||||
out.push(ContextItem::Copy { value: name.clone(), label: None });
|
out.push(ContextItem::Copy { value: name.clone(), label: None, copy_string: None });
|
||||||
}
|
}
|
||||||
if symbol.section.is_some()
|
if symbol.section.is_some()
|
||||||
&& let Some(address) = symbol.virtual_address
|
&& let Some(address) = symbol.virtual_address
|
||||||
@@ -396,6 +407,7 @@ pub fn symbol_context(obj: &Object, symbol_index: usize) -> Vec<ContextItem> {
|
|||||||
out.push(ContextItem::Copy {
|
out.push(ContextItem::Copy {
|
||||||
value: format!("{address:x}"),
|
value: format!("{address:x}"),
|
||||||
label: Some("virtual address".to_string()),
|
label: Some("virtual address".to_string()),
|
||||||
|
copy_string: None,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
out.append(&mut obj.arch.symbol_context(obj, symbol_index));
|
out.append(&mut obj.arch.symbol_context(obj, symbol_index));
|
||||||
@@ -489,8 +501,8 @@ pub fn relocation_context(
|
|||||||
let literals = display_ins_data_literals(obj, ins);
|
let literals = display_ins_data_literals(obj, ins);
|
||||||
if !literals.is_empty() {
|
if !literals.is_empty() {
|
||||||
out.push(ContextItem::Separator);
|
out.push(ContextItem::Separator);
|
||||||
for (literal, label_override) in literals {
|
for (literal, label_override, copy_string) in literals {
|
||||||
out.push(ContextItem::Copy { value: literal, label: label_override });
|
out.push(ContextItem::Copy { value: literal, label: label_override, copy_string });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -586,24 +598,37 @@ pub fn instruction_context(
|
|||||||
for byte in resolved.code {
|
for byte in resolved.code {
|
||||||
hex_string.push_str(&format!("{byte:02x}"));
|
hex_string.push_str(&format!("{byte:02x}"));
|
||||||
}
|
}
|
||||||
out.push(ContextItem::Copy { value: hex_string, label: Some("instruction bytes".to_string()) });
|
out.push(ContextItem::Copy {
|
||||||
|
value: hex_string,
|
||||||
|
label: Some("instruction bytes".to_string()),
|
||||||
|
copy_string: None,
|
||||||
|
});
|
||||||
out.append(&mut obj.arch.instruction_context(obj, resolved));
|
out.append(&mut obj.arch.instruction_context(obj, resolved));
|
||||||
if let Some(virtual_address) = resolved.symbol.virtual_address {
|
if let Some(virtual_address) = resolved.symbol.virtual_address {
|
||||||
let offset = resolved.ins_ref.address - resolved.symbol.address;
|
let offset = resolved.ins_ref.address - resolved.symbol.address;
|
||||||
out.push(ContextItem::Copy {
|
out.push(ContextItem::Copy {
|
||||||
value: format!("{:x}", virtual_address + offset),
|
value: format!("{:x}", virtual_address + offset),
|
||||||
label: Some("virtual address".to_string()),
|
label: Some("virtual address".to_string()),
|
||||||
|
copy_string: None,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
for arg in &ins.args {
|
for arg in &ins.args {
|
||||||
if let InstructionArg::Value(arg) = arg {
|
if let InstructionArg::Value(arg) = arg {
|
||||||
out.push(ContextItem::Copy { value: arg.to_string(), label: None });
|
out.push(ContextItem::Copy { value: arg.to_string(), label: None, copy_string: None });
|
||||||
match arg {
|
match arg {
|
||||||
InstructionArgValue::Signed(v) => {
|
InstructionArgValue::Signed(v) => {
|
||||||
out.push(ContextItem::Copy { value: v.to_string(), label: None });
|
out.push(ContextItem::Copy {
|
||||||
|
value: v.to_string(),
|
||||||
|
label: None,
|
||||||
|
copy_string: None,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
InstructionArgValue::Unsigned(v) => {
|
InstructionArgValue::Unsigned(v) => {
|
||||||
out.push(ContextItem::Copy { value: v.to_string(), label: None });
|
out.push(ContextItem::Copy {
|
||||||
|
value: v.to_string(),
|
||||||
|
label: None,
|
||||||
|
copy_string: None,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
@@ -665,10 +690,10 @@ pub fn instruction_hover(
|
|||||||
let literals = display_ins_data_literals(obj, resolved);
|
let literals = display_ins_data_literals(obj, resolved);
|
||||||
if !literals.is_empty() {
|
if !literals.is_empty() {
|
||||||
out.push(HoverItem::Separator);
|
out.push(HoverItem::Separator);
|
||||||
for (literal, label_override) in literals {
|
for (literal, label_override, _) in literals {
|
||||||
out.push(HoverItem::Text {
|
out.push(HoverItem::Text {
|
||||||
label: label_override.unwrap_or_else(|| ty.to_string()),
|
label: label_override.unwrap_or_else(|| ty.to_string()),
|
||||||
value: literal,
|
value: format!("{literal:?}"),
|
||||||
color: HoverItemColor::Normal,
|
color: HoverItemColor::Normal,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -787,19 +812,15 @@ pub fn display_sections(
|
|||||||
}
|
}
|
||||||
let section_diff = &diff.sections[section_idx];
|
let section_diff = &diff.sections[section_idx];
|
||||||
let reverse_fn_order = section.kind == SectionKind::Code && reverse_fn_order;
|
let reverse_fn_order = section.kind == SectionKind::Code && reverse_fn_order;
|
||||||
symbols.sort_by(|a, b| {
|
if reverse_fn_order {
|
||||||
let a = &obj.symbols[a.symbol];
|
symbols.sort_by(|a, b| {
|
||||||
let b = &obj.symbols[b.symbol];
|
let a = &obj.symbols[a.symbol];
|
||||||
section_symbol_sort(a, b)
|
let b = &obj.symbols[b.symbol];
|
||||||
.then_with(|| {
|
section_symbol_sort(a, b)
|
||||||
if reverse_fn_order {
|
.then_with(|| b.address.cmp(&a.address))
|
||||||
b.address.cmp(&a.address)
|
.then_with(|| a.size.cmp(&b.size))
|
||||||
} else {
|
});
|
||||||
a.address.cmp(&b.address)
|
}
|
||||||
}
|
|
||||||
})
|
|
||||||
.then_with(|| a.size.cmp(&b.size))
|
|
||||||
});
|
|
||||||
sections.push(SectionDisplay {
|
sections.push(SectionDisplay {
|
||||||
id: section.id.clone(),
|
id: section.id.clone(),
|
||||||
name: if section.flags.contains(SectionFlag::Combined) {
|
name: if section.flags.contains(SectionFlag::Combined) {
|
||||||
@@ -859,7 +880,7 @@ pub fn display_ins_data_labels(obj: &Object, resolved: ResolvedInstructionRef) -
|
|||||||
pub fn display_ins_data_literals(
|
pub fn display_ins_data_literals(
|
||||||
obj: &Object,
|
obj: &Object,
|
||||||
resolved: ResolvedInstructionRef,
|
resolved: ResolvedInstructionRef,
|
||||||
) -> Vec<(String, Option<String>)> {
|
) -> Vec<(String, Option<String>, Option<String>)> {
|
||||||
let Some(reloc) = resolved.relocation else {
|
let Some(reloc) = resolved.relocation else {
|
||||||
return Vec::new();
|
return Vec::new();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -686,18 +686,6 @@ fn symbol_section_kind(obj: &Object, symbol: &Symbol) -> SectionKind {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if a symbol is a compiler-generated like @1234 or _$E1234.
|
|
||||||
fn is_symbol_compiler_generated(symbol: &Symbol) -> bool {
|
|
||||||
if symbol.name.starts_with('@') && symbol.name[1..].chars().all(char::is_numeric) {
|
|
||||||
// Exclude @stringBase0, @GUARD@, etc.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if symbol.name.starts_with("_$E") && symbol.name[3..].chars().all(char::is_numeric) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
fn find_symbol(
|
fn find_symbol(
|
||||||
obj: Option<&Object>,
|
obj: Option<&Object>,
|
||||||
in_obj: &Object,
|
in_obj: &Object,
|
||||||
@@ -711,7 +699,7 @@ fn find_symbol(
|
|||||||
|
|
||||||
// Match compiler-generated symbols against each other (e.g. @251 -> @60)
|
// Match compiler-generated symbols against each other (e.g. @251 -> @60)
|
||||||
// If they are in the same section and have the same value
|
// If they are in the same section and have the same value
|
||||||
if is_symbol_compiler_generated(in_symbol)
|
if in_symbol.flags.contains(SymbolFlag::CompilerGenerated)
|
||||||
&& matches!(section_kind, SectionKind::Code | SectionKind::Data | SectionKind::Bss)
|
&& matches!(section_kind, SectionKind::Code | SectionKind::Data | SectionKind::Bss)
|
||||||
{
|
{
|
||||||
let mut closest_match_symbol_idx = None;
|
let mut closest_match_symbol_idx = None;
|
||||||
@@ -723,7 +711,7 @@ fn find_symbol(
|
|||||||
if obj.sections[section_index].name != section_name {
|
if obj.sections[section_index].name != section_name {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if !is_symbol_compiler_generated(symbol) {
|
if !symbol.flags.contains(SymbolFlag::CompilerGenerated) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
match section_kind {
|
match section_kind {
|
||||||
@@ -759,15 +747,9 @@ fn find_symbol(
|
|||||||
return closest_match_symbol_idx;
|
return closest_match_symbol_idx;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to find an exact name match
|
// Try to find a symbol with a matching name
|
||||||
if let Some((symbol_idx, _)) = unmatched_symbols(obj, used).find(|(_, symbol)| {
|
|
||||||
symbol.name == in_symbol.name && symbol_section_kind(obj, symbol) == section_kind
|
|
||||||
}) {
|
|
||||||
return Some(symbol_idx);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some((symbol_idx, _)) = unmatched_symbols(obj, used).find(|&(_, symbol)| {
|
if let Some((symbol_idx, _)) = unmatched_symbols(obj, used).find(|&(_, symbol)| {
|
||||||
symbol_name_matches(&in_symbol.name, &symbol.name)
|
symbol_name_matches(in_symbol, symbol)
|
||||||
&& symbol_section_kind(obj, symbol) == section_kind
|
&& symbol_section_kind(obj, symbol) == section_kind
|
||||||
&& symbol_section(obj, symbol).is_some_and(|(name, _)| name == section_name)
|
&& symbol_section(obj, symbol).is_some_and(|(name, _)| name == section_name)
|
||||||
}) {
|
}) {
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ pub enum SectionKind {
|
|||||||
|
|
||||||
flags! {
|
flags! {
|
||||||
#[derive(Hash)]
|
#[derive(Hash)]
|
||||||
pub enum SymbolFlag: u8 {
|
pub enum SymbolFlag: u16 {
|
||||||
Global,
|
Global,
|
||||||
Local,
|
Local,
|
||||||
Weak,
|
Weak,
|
||||||
@@ -50,6 +50,8 @@ flags! {
|
|||||||
SizeInferred,
|
SizeInferred,
|
||||||
/// Symbol should be ignored by any diffing
|
/// Symbol should be ignored by any diffing
|
||||||
Ignored,
|
Ignored,
|
||||||
|
/// Symbol name is compiler-generated; compare by value instead of name
|
||||||
|
CompilerGenerated,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -264,6 +266,7 @@ pub trait FlowAnalysisResult: core::fmt::Debug + Send {
|
|||||||
pub struct Symbol {
|
pub struct Symbol {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub demangled_name: Option<String>,
|
pub demangled_name: Option<String>,
|
||||||
|
pub normalized_name: Option<String>,
|
||||||
pub address: u64,
|
pub address: u64,
|
||||||
pub size: u64,
|
pub size: u64,
|
||||||
pub kind: SymbolKind,
|
pub kind: SymbolKind,
|
||||||
@@ -403,6 +406,7 @@ pub struct ResolvedInstructionRef<'obj> {
|
|||||||
static DUMMY_SYMBOL: Symbol = Symbol {
|
static DUMMY_SYMBOL: Symbol = Symbol {
|
||||||
name: String::new(),
|
name: String::new(),
|
||||||
demangled_name: None,
|
demangled_name: None,
|
||||||
|
normalized_name: None,
|
||||||
address: 0,
|
address: 0,
|
||||||
size: 0,
|
size: 0,
|
||||||
kind: SymbolKind::Unknown,
|
kind: SymbolKind::Unknown,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ use alloc::{
|
|||||||
collections::BTreeMap,
|
collections::BTreeMap,
|
||||||
format,
|
format,
|
||||||
string::{String, ToString},
|
string::{String, ToString},
|
||||||
|
vec,
|
||||||
vec::Vec,
|
vec::Vec,
|
||||||
};
|
};
|
||||||
use core::{cmp::Ordering, num::NonZeroU64};
|
use core::{cmp::Ordering, num::NonZeroU64};
|
||||||
@@ -35,6 +36,46 @@ fn map_section_kind(section: &object::Section) -> SectionKind {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check if a symbol's name is partially compiler-generated, and if so normalize it for pairing.
|
||||||
|
/// e.g. symbol$1234 and symbol$2345 will both be replaced with symbol$0000 internally.
|
||||||
|
fn get_normalized_symbol_name(name: &str) -> Option<String> {
|
||||||
|
const DUMMY_UNIQUE_ID: &str = "0000";
|
||||||
|
if let Some((prefix, suffix)) = name.split_once("@class$")
|
||||||
|
&& let Some(idx) = suffix.chars().position(|c| !c.is_numeric())
|
||||||
|
&& idx > 0
|
||||||
|
{
|
||||||
|
// Match Metrowerks anonymous class symbol names, ignoring the unique ID.
|
||||||
|
// e.g. __dt__Q29dCamera_c23@class$3665d_camera_cppFv
|
||||||
|
// and: __dt__Q29dCamera_c23@class$1727d_camera_cppFv
|
||||||
|
let suffix = &suffix[idx..];
|
||||||
|
Some(format!("{prefix}@class${DUMMY_UNIQUE_ID}{suffix}"))
|
||||||
|
} else if let Some((prefix, suffix)) = name.split_once('$')
|
||||||
|
&& suffix.chars().all(char::is_numeric)
|
||||||
|
{
|
||||||
|
// Match Metrowerks symbol$1234 against symbol$2345
|
||||||
|
Some(format!("{prefix}${DUMMY_UNIQUE_ID}"))
|
||||||
|
} else if let Some((prefix, suffix)) = name.split_once('.')
|
||||||
|
&& suffix.chars().all(char::is_numeric)
|
||||||
|
{
|
||||||
|
// Match GCC symbol.1234 against symbol.2345
|
||||||
|
Some(format!("{prefix}.{DUMMY_UNIQUE_ID}"))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if a symbol's name is entirely compiler-generated, such as @1234 or _$E1234.
|
||||||
|
/// This enables pairing these symbols up by their value instead of their name.
|
||||||
|
fn is_symbol_name_compiler_generated(name: &str) -> bool {
|
||||||
|
if name.starts_with('@') && name[1..].chars().all(char::is_numeric) {
|
||||||
|
// Exclude @stringBase0, @GUARD@, etc.
|
||||||
|
return true;
|
||||||
|
} else if name.starts_with("_$E") && name[3..].chars().all(char::is_numeric) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
fn map_symbol(
|
fn map_symbol(
|
||||||
arch: &dyn Arch,
|
arch: &dyn Arch,
|
||||||
file: &object::File,
|
file: &object::File,
|
||||||
@@ -97,10 +138,15 @@ fn map_symbol(
|
|||||||
.and_then(|m| m.virtual_addresses.as_ref())
|
.and_then(|m| m.virtual_addresses.as_ref())
|
||||||
.and_then(|v| v.get(symbol.index().0).cloned());
|
.and_then(|v| v.get(symbol.index().0).cloned());
|
||||||
let section = symbol.section_index().and_then(|i| section_indices.get(i.0).copied());
|
let section = symbol.section_index().and_then(|i| section_indices.get(i.0).copied());
|
||||||
|
let normalized_name = get_normalized_symbol_name(&name);
|
||||||
|
if is_symbol_name_compiler_generated(&name) {
|
||||||
|
flags |= SymbolFlag::CompilerGenerated;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(Symbol {
|
Ok(Symbol {
|
||||||
name,
|
name,
|
||||||
demangled_name,
|
demangled_name,
|
||||||
|
normalized_name,
|
||||||
address,
|
address,
|
||||||
size,
|
size,
|
||||||
kind,
|
kind,
|
||||||
@@ -119,13 +165,38 @@ fn map_symbols(
|
|||||||
split_meta: Option<&SplitMeta>,
|
split_meta: Option<&SplitMeta>,
|
||||||
config: &DiffObjConfig,
|
config: &DiffObjConfig,
|
||||||
) -> Result<(Vec<Symbol>, Vec<usize>)> {
|
) -> Result<(Vec<Symbol>, Vec<usize>)> {
|
||||||
let symbol_count = obj_file.symbols().count();
|
// symbols() is not guaranteed to be sorted by address.
|
||||||
let mut symbols = Vec::<Symbol>::with_capacity(symbol_count + obj_file.sections().count());
|
// We sort it here to fix pairing bugs with diff algorithms that assume the symbols are ordered.
|
||||||
let mut symbol_indices = Vec::<usize>::with_capacity(symbol_count + 1);
|
// Sorting everything here once is less expensive than sorting subsets later in expensive loops.
|
||||||
for obj_symbol in obj_file.symbols() {
|
let mut max_index = 0;
|
||||||
if symbol_indices.len() <= obj_symbol.index().0 {
|
let mut obj_symbols = obj_file
|
||||||
symbol_indices.resize(obj_symbol.index().0 + 1, usize::MAX);
|
.symbols()
|
||||||
}
|
.filter(|s| s.kind() != object::SymbolKind::File)
|
||||||
|
.inspect(|sym| max_index = max_index.max(sym.index().0))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
obj_symbols.sort_by(|a, b| {
|
||||||
|
// Sort symbols by section index, placing absolute symbols last
|
||||||
|
a.section_index()
|
||||||
|
.map_or(usize::MAX, |s| s.0)
|
||||||
|
.cmp(&b.section_index().map_or(usize::MAX, |s| s.0))
|
||||||
|
.then_with(|| {
|
||||||
|
// Sort section symbols first in a section
|
||||||
|
if a.kind() == object::SymbolKind::Section {
|
||||||
|
Ordering::Less
|
||||||
|
} else if b.kind() == object::SymbolKind::Section {
|
||||||
|
Ordering::Greater
|
||||||
|
} else {
|
||||||
|
Ordering::Equal
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// Sort by address within section
|
||||||
|
.then_with(|| a.address().cmp(&b.address()))
|
||||||
|
// If there are multiple symbols with the same address, smaller symbol first
|
||||||
|
.then_with(|| a.size().cmp(&b.size()))
|
||||||
|
});
|
||||||
|
let mut symbols = Vec::<Symbol>::with_capacity(obj_symbols.len() + obj_file.sections().count());
|
||||||
|
let mut symbol_indices = vec![usize::MAX; max_index + 1];
|
||||||
|
for obj_symbol in obj_symbols {
|
||||||
let symbol = map_symbol(arch, obj_file, &obj_symbol, section_indices, split_meta, config)?;
|
let symbol = map_symbol(arch, obj_file, &obj_symbol, section_indices, split_meta, config)?;
|
||||||
symbol_indices[obj_symbol.index().0] = symbols.len();
|
symbol_indices[obj_symbol.index().0] = symbols.len();
|
||||||
symbols.push(symbol);
|
symbols.push(symbol);
|
||||||
@@ -172,6 +243,7 @@ fn add_section_symbols(sections: &[Section], symbols: &mut Vec<Symbol>) {
|
|||||||
symbols.push(Symbol {
|
symbols.push(Symbol {
|
||||||
name,
|
name,
|
||||||
demangled_name: None,
|
demangled_name: None,
|
||||||
|
normalized_name: None,
|
||||||
address: 0,
|
address: 0,
|
||||||
size,
|
size,
|
||||||
kind: SymbolKind::Section,
|
kind: SymbolKind::Section,
|
||||||
@@ -193,40 +265,18 @@ fn is_local_label(symbol: &Symbol) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn infer_symbol_sizes(arch: &dyn Arch, symbols: &mut [Symbol], sections: &[Section]) -> Result<()> {
|
fn infer_symbol_sizes(arch: &dyn Arch, symbols: &mut [Symbol], sections: &[Section]) -> Result<()> {
|
||||||
// Create a sorted list of symbol indices by section
|
// Above, we've sorted the symbols by section and then by address.
|
||||||
let mut symbols_with_section = Vec::<usize>::with_capacity(symbols.len());
|
|
||||||
for (i, symbol) in symbols.iter().enumerate() {
|
|
||||||
if symbol.section.is_some() {
|
|
||||||
symbols_with_section.push(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
symbols_with_section.sort_by(|a, b| {
|
|
||||||
let a = &symbols[*a];
|
|
||||||
let b = &symbols[*b];
|
|
||||||
a.section
|
|
||||||
.unwrap_or(usize::MAX)
|
|
||||||
.cmp(&b.section.unwrap_or(usize::MAX))
|
|
||||||
.then_with(|| {
|
|
||||||
// Sort section symbols first
|
|
||||||
if a.kind == SymbolKind::Section {
|
|
||||||
Ordering::Less
|
|
||||||
} else if b.kind == SymbolKind::Section {
|
|
||||||
Ordering::Greater
|
|
||||||
} else {
|
|
||||||
Ordering::Equal
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then_with(|| a.address.cmp(&b.address))
|
|
||||||
.then_with(|| a.size.cmp(&b.size))
|
|
||||||
});
|
|
||||||
|
|
||||||
// Set symbol sizes based on the next symbol's address
|
// Set symbol sizes based on the next symbol's address
|
||||||
let mut iter_idx = 0;
|
let mut iter_idx = 0;
|
||||||
let mut last_end = (0, 0);
|
let mut last_end = (0, 0);
|
||||||
while iter_idx < symbols_with_section.len() {
|
while iter_idx < symbols.len() {
|
||||||
let symbol_idx = symbols_with_section[iter_idx];
|
let symbol_idx = iter_idx;
|
||||||
let symbol = &symbols[symbol_idx];
|
let symbol = &symbols[symbol_idx];
|
||||||
let section_idx = symbol.section.unwrap();
|
let Some(section_idx) = symbol.section else {
|
||||||
|
// Start of absolute symbols
|
||||||
|
break;
|
||||||
|
};
|
||||||
iter_idx += 1;
|
iter_idx += 1;
|
||||||
if symbol.size != 0 {
|
if symbol.size != 0 {
|
||||||
if symbol.kind != SymbolKind::Section {
|
if symbol.kind != SymbolKind::Section {
|
||||||
@@ -239,10 +289,9 @@ fn infer_symbol_sizes(arch: &dyn Arch, symbols: &mut [Symbol], sections: &[Secti
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let next_symbol = loop {
|
let next_symbol = loop {
|
||||||
if iter_idx >= symbols_with_section.len() {
|
let Some(next_symbol) = symbols.get(iter_idx) else {
|
||||||
break None;
|
break None;
|
||||||
}
|
};
|
||||||
let next_symbol = &symbols[symbols_with_section[iter_idx]];
|
|
||||||
if next_symbol.section != Some(section_idx) {
|
if next_symbol.section != Some(section_idx) {
|
||||||
break None;
|
break None;
|
||||||
}
|
}
|
||||||
@@ -298,9 +347,11 @@ fn map_sections(
|
|||||||
split_meta: Option<&SplitMeta>,
|
split_meta: Option<&SplitMeta>,
|
||||||
) -> Result<(Vec<Section>, Vec<usize>)> {
|
) -> Result<(Vec<Section>, Vec<usize>)> {
|
||||||
let mut section_names = BTreeMap::<String, usize>::new();
|
let mut section_names = BTreeMap::<String, usize>::new();
|
||||||
let section_count = obj_file.sections().count();
|
let mut max_index = 0;
|
||||||
|
let section_count =
|
||||||
|
obj_file.sections().inspect(|s| max_index = max_index.max(s.index().0)).count();
|
||||||
let mut result = Vec::<Section>::with_capacity(section_count);
|
let mut result = Vec::<Section>::with_capacity(section_count);
|
||||||
let mut section_indices = Vec::<usize>::with_capacity(section_count + 1);
|
let mut section_indices = vec![usize::MAX; max_index + 1];
|
||||||
for section in obj_file.sections() {
|
for section in obj_file.sections() {
|
||||||
let name = section.name().context("Failed to process section name")?;
|
let name = section.name().context("Failed to process section name")?;
|
||||||
let kind = map_section_kind(§ion);
|
let kind = map_section_kind(§ion);
|
||||||
@@ -325,9 +376,6 @@ fn map_sections(
|
|||||||
let id = format!("{name}-{unique_id}");
|
let id = format!("{name}-{unique_id}");
|
||||||
*unique_id += 1;
|
*unique_id += 1;
|
||||||
|
|
||||||
if section_indices.len() <= section.index().0 {
|
|
||||||
section_indices.resize(section.index().0 + 1, usize::MAX);
|
|
||||||
}
|
|
||||||
section_indices[section.index().0] = result.len();
|
section_indices[section.index().0] = result.len();
|
||||||
result.push(Section {
|
result.push(Section {
|
||||||
id,
|
id,
|
||||||
@@ -824,19 +872,16 @@ fn combine_sections(
|
|||||||
config: &DiffObjConfig,
|
config: &DiffObjConfig,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let mut data_sections = BTreeMap::<String, Vec<usize>>::new();
|
let mut data_sections = BTreeMap::<String, Vec<usize>>::new();
|
||||||
let mut text_sections = Vec::<usize>::new();
|
let mut text_sections = BTreeMap::<String, Vec<usize>>::new();
|
||||||
for (i, section) in sections.iter().enumerate() {
|
for (i, section) in sections.iter().enumerate() {
|
||||||
|
let base_name =
|
||||||
|
if let Some(i) = section.name.rfind('$') { §ion.name[..i] } else { §ion.name };
|
||||||
match section.kind {
|
match section.kind {
|
||||||
SectionKind::Data | SectionKind::Bss => {
|
SectionKind::Data | SectionKind::Bss => {
|
||||||
let base_name = if let Some(i) = section.name.rfind('$') {
|
|
||||||
§ion.name[..i]
|
|
||||||
} else {
|
|
||||||
§ion.name
|
|
||||||
};
|
|
||||||
data_sections.entry(base_name.to_string()).or_default().push(i);
|
data_sections.entry(base_name.to_string()).or_default().push(i);
|
||||||
}
|
}
|
||||||
SectionKind::Code => {
|
SectionKind::Code => {
|
||||||
text_sections.push(i);
|
text_sections.entry(base_name.to_string()).or_default().push(i);
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
@@ -847,7 +892,9 @@ fn combine_sections(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if config.combine_text_sections {
|
if config.combine_text_sections {
|
||||||
do_combine_sections(sections, symbols, &mut text_sections, ".text".to_string())?;
|
for (combined_name, mut section_indices) in text_sections {
|
||||||
|
do_combine_sections(sections, symbols, &mut section_indices, combined_name)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -114,6 +114,7 @@ expression: "(sections, symbols)"
|
|||||||
Symbol {
|
Symbol {
|
||||||
name: ".data",
|
name: ".data",
|
||||||
demangled_name: None,
|
demangled_name: None,
|
||||||
|
normalized_name: None,
|
||||||
address: 0,
|
address: 0,
|
||||||
size: 0,
|
size: 0,
|
||||||
kind: Section,
|
kind: Section,
|
||||||
@@ -127,6 +128,7 @@ expression: "(sections, symbols)"
|
|||||||
Symbol {
|
Symbol {
|
||||||
name: "symbol",
|
name: "symbol",
|
||||||
demangled_name: None,
|
demangled_name: None,
|
||||||
|
normalized_name: None,
|
||||||
address: 4,
|
address: 4,
|
||||||
size: 4,
|
size: 4,
|
||||||
kind: Object,
|
kind: Object,
|
||||||
@@ -140,6 +142,7 @@ expression: "(sections, symbols)"
|
|||||||
Symbol {
|
Symbol {
|
||||||
name: "function",
|
name: "function",
|
||||||
demangled_name: None,
|
demangled_name: None,
|
||||||
|
normalized_name: None,
|
||||||
address: 0,
|
address: 0,
|
||||||
size: 8,
|
size: 8,
|
||||||
kind: Function,
|
kind: Function,
|
||||||
@@ -153,6 +156,7 @@ expression: "(sections, symbols)"
|
|||||||
Symbol {
|
Symbol {
|
||||||
name: ".data",
|
name: ".data",
|
||||||
demangled_name: None,
|
demangled_name: None,
|
||||||
|
normalized_name: None,
|
||||||
address: 0,
|
address: 0,
|
||||||
size: 0,
|
size: 0,
|
||||||
kind: Unknown,
|
kind: Unknown,
|
||||||
|
|||||||
@@ -4,4 +4,4 @@ expression: output
|
|||||||
---
|
---
|
||||||
[(Line(90), Dim, 5), (Address(0), Dim, 5), (Spacing(4), Normal, 0), (Opcode("ldr", 32792), Normal, 10), (Argument(Opaque("r12")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("pc")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(0)), Normal, 0), (Basic("]"), Normal, 0), (Basic(" (->"), Normal, 0), (BranchDest(8), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
|
[(Line(90), Dim, 5), (Address(0), Dim, 5), (Spacing(4), Normal, 0), (Opcode("ldr", 32792), Normal, 10), (Argument(Opaque("r12")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("pc")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(0)), Normal, 0), (Basic("]"), Normal, 0), (Basic(" (->"), Normal, 0), (BranchDest(8), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
|
||||||
[(Line(90), Dim, 5), (Address(4), Dim, 5), (Spacing(4), Normal, 0), (Opcode("bx", 32777), Normal, 10), (Argument(Opaque("r12")), Normal, 0), (Eol, Normal, 0)]
|
[(Line(90), Dim, 5), (Address(4), Dim, 5), (Spacing(4), Normal, 0), (Opcode("bx", 32777), Normal, 10), (Argument(Opaque("r12")), Normal, 0), (Eol, Normal, 0)]
|
||||||
[(Line(90), Dim, 5), (Address(8), Dim, 5), (Spacing(4), Normal, 0), (Opcode(".word", 65534), Normal, 10), (Symbol(Symbol { name: "esEnemyDraw", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Eol, Normal, 0)]
|
[(Line(90), Dim, 5), (Address(8), Dim, 5), (Spacing(4), Normal, 0), (Opcode(".word", 65534), Normal, 10), (Symbol(Symbol { name: "esEnemyDraw", demangled_name: None, normalized_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Eol, Normal, 0)]
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user