mirror of
https://github.com/encounter/objdiff.git
synced 2026-07-10 12:18:36 -07:00
Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 02daae417c | |||
| 8b1b4a9a79 | |||
| 13f1267eac | |||
| 3cebee6766 | |||
| ab0538e9a4 | |||
| b643265809 | |||
| 7a65e07e9b | |||
| 3c4092b965 | |||
| 0f599bea2b | |||
| 9927fc35b6 | |||
| 16fb6a491f | |||
| 2541aef71a | |||
| 7ad1f95bf7 | |||
| 66c879a95d | |||
| d53c358d2f | |||
| f24c017545 | |||
| 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 | |||
| 26a4cc79cf | |||
| 5c96c2ee51 | |||
| d162fe847e |
@@ -62,7 +62,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
checks:
|
||||
- advisories
|
||||
# - advisories
|
||||
- bans licenses sources
|
||||
# Prevent new advisories from failing CI
|
||||
continue-on-error: ${{ matrix.checks == 'advisories' }}
|
||||
|
||||
Generated
+17
-17
@@ -598,9 +598,9 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.11.0"
|
||||
version = "1.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3"
|
||||
checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
|
||||
|
||||
[[package]]
|
||||
name = "calloop"
|
||||
@@ -1000,9 +1000,9 @@ checksum = "c2e06f9bce634a3c898eb1e5cb949ff63133cbb218af93cc9b38b31d6f3ea285"
|
||||
|
||||
[[package]]
|
||||
name = "cwextab"
|
||||
version = "1.1.6"
|
||||
version = "1.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d21b94648643c61e1b5cef71e8d3a9f89350397ac2b0fe42890edbc94757b18"
|
||||
checksum = "e23dadbc8945746c361e3967336075be7281a5455c06f5c5a17fddca4e7d2175"
|
||||
dependencies = [
|
||||
"thiserror 2.0.17",
|
||||
]
|
||||
@@ -3157,9 +3157,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "num-conv"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
||||
checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050"
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
@@ -3491,7 +3491,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "objdiff-cli"
|
||||
version = "3.4.2"
|
||||
version = "3.7.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argp",
|
||||
@@ -3514,7 +3514,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "objdiff-core"
|
||||
version = "3.4.2"
|
||||
version = "3.7.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"arm-attr",
|
||||
@@ -3570,7 +3570,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "objdiff-gui"
|
||||
version = "3.4.2"
|
||||
version = "3.7.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argp",
|
||||
@@ -3608,7 +3608,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "objdiff-wasm"
|
||||
version = "3.4.2"
|
||||
version = "3.7.2"
|
||||
dependencies = [
|
||||
"log",
|
||||
"objdiff-core",
|
||||
@@ -5327,9 +5327,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.44"
|
||||
version = "0.3.47"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d"
|
||||
checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c"
|
||||
dependencies = [
|
||||
"deranged",
|
||||
"itoa",
|
||||
@@ -5337,22 +5337,22 @@ dependencies = [
|
||||
"num-conv",
|
||||
"num_threads",
|
||||
"powerfmt",
|
||||
"serde",
|
||||
"serde_core",
|
||||
"time-core",
|
||||
"time-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time-core"
|
||||
version = "0.1.6"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b"
|
||||
checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca"
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.24"
|
||||
version = "0.2.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3"
|
||||
checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215"
|
||||
dependencies = [
|
||||
"num-conv",
|
||||
"time-core",
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ default-members = [
|
||||
resolver = "3"
|
||||
|
||||
[workspace.package]
|
||||
version = "3.4.2"
|
||||
version = "3.7.2"
|
||||
authors = ["Luke Street <luke@street.dev>"]
|
||||
edition = "2024"
|
||||
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-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-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 false, then it uses a built-in git library.
|
||||
|
||||
+48
-23
@@ -19,6 +19,7 @@ use crossterm::{
|
||||
},
|
||||
};
|
||||
use objdiff_core::{
|
||||
bindings::diff::DiffResult,
|
||||
build::{
|
||||
BuildConfig, BuildStatus,
|
||||
watcher::{Watcher, create_watcher},
|
||||
@@ -28,7 +29,7 @@ use objdiff_core::{
|
||||
build_globset,
|
||||
path::{check_path_buf, platform_path, platform_path_serde_option},
|
||||
},
|
||||
diff::{DiffObjConfig, MappingConfig, ObjectDiff},
|
||||
diff::{self, DiffObjConfig, DiffSide, MappingConfig, ObjectDiff},
|
||||
jobs::{
|
||||
Job, JobQueue, JobResult,
|
||||
objdiff::{ObjDiffConfig, start_build},
|
||||
@@ -40,7 +41,10 @@ use typed_path::{Utf8PlatformPath, Utf8PlatformPathBuf};
|
||||
|
||||
use crate::{
|
||||
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},
|
||||
};
|
||||
|
||||
@@ -60,21 +64,18 @@ pub struct Args {
|
||||
#[argp(option, short = 'u')]
|
||||
/// Unit name within project
|
||||
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)]
|
||||
/// Function symbol to diff
|
||||
symbol: Option<String>,
|
||||
#[argp(option, short = 'c')]
|
||||
/// Configuration property (key=value)
|
||||
config: Vec<String>,
|
||||
#[argp(option, short = 'm')]
|
||||
/// Symbol mapping (target=base)
|
||||
mapping: Vec<String>,
|
||||
#[argp(option)]
|
||||
/// Left symbol name for selection
|
||||
selecting_left: Option<String>,
|
||||
#[argp(option)]
|
||||
/// Right symbol name for selection
|
||||
selecting_right: Option<String>,
|
||||
}
|
||||
|
||||
pub fn run(args: Args) -> Result<()> {
|
||||
@@ -167,7 +168,41 @@ pub fn run(args: Args) -> Result<()> {
|
||||
_ => 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().zip(result.left.as_ref());
|
||||
let right = base.as_ref().zip(result.right.as_ref());
|
||||
let diff_result = DiffResult::new(left, right, &diff_config)?;
|
||||
write_output(&diff_result, Some(output), output_format)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn build_config_from_args(
|
||||
@@ -183,17 +218,7 @@ fn build_config_from_args(
|
||||
apply_project_options(&mut diff_config, options)?;
|
||||
}
|
||||
apply_config_args(&mut diff_config, &args.config)?;
|
||||
let mut mapping_config = MappingConfig {
|
||||
mappings: Default::default(),
|
||||
selecting_left: args.selecting_left.clone(),
|
||||
selecting_right: args.selecting_right.clone(),
|
||||
};
|
||||
for mapping in &args.mapping {
|
||||
let (target, base) =
|
||||
mapping.split_once('=').context("--mapping expects \"target=base\"")?;
|
||||
mapping_config.mappings.insert(target.to_string(), base.to_string());
|
||||
}
|
||||
Ok((diff_config, mapping_config))
|
||||
Ok((diff_config, MappingConfig::default()))
|
||||
}
|
||||
|
||||
pub struct AppState {
|
||||
|
||||
@@ -539,6 +539,7 @@ impl FunctionDiffUi {
|
||||
DiffText::Opcode(mnemonic, _op) => format!("{mnemonic} "),
|
||||
DiffText::Argument(arg) => arg.to_string(),
|
||||
DiffText::BranchDest(addr) => format!("{addr:x}"),
|
||||
DiffText::BranchArrow(_) => " ~> ".to_string(),
|
||||
DiffText::Symbol(sym) => {
|
||||
sym.demangled_name.as_ref().unwrap_or(&sym.name).clone()
|
||||
}
|
||||
|
||||
@@ -117,6 +117,51 @@
|
||||
"name": "Combine text sections",
|
||||
"description": "Combines all text sections into one."
|
||||
},
|
||||
{
|
||||
"id": "preferredStringEncoding",
|
||||
"type": "choice",
|
||||
"default": "auto",
|
||||
"name": "Preferred string encoding",
|
||||
"description": "Which encoding to use when diffing string literals used in functions.",
|
||||
"items": [
|
||||
{
|
||||
"value": "auto",
|
||||
"name": "Auto"
|
||||
},
|
||||
{
|
||||
"value": "ascii",
|
||||
"name": "ASCII"
|
||||
},
|
||||
{
|
||||
"value": "utf_8",
|
||||
"name": "UTF-8"
|
||||
},
|
||||
{
|
||||
"value": "shift_jis",
|
||||
"name": "Shift JIS"
|
||||
},
|
||||
{
|
||||
"value": "windows_1252",
|
||||
"name": "Windows-1252"
|
||||
},
|
||||
{
|
||||
"value": "euc_jp",
|
||||
"name": "EUC-JP"
|
||||
},
|
||||
{
|
||||
"value": "big5",
|
||||
"name": "Big5"
|
||||
},
|
||||
{
|
||||
"value": "utf_16be",
|
||||
"name": "UTF-16BE"
|
||||
},
|
||||
{
|
||||
"value": "utf_16le",
|
||||
"name": "UTF-16LE"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "arm.archVersion",
|
||||
"type": "choice",
|
||||
@@ -338,6 +383,7 @@
|
||||
"id": "general",
|
||||
"name": "General",
|
||||
"properties": [
|
||||
"preferredStringEncoding",
|
||||
"functionRelocDiffs",
|
||||
"demangler",
|
||||
"showSymbolSizes",
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
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;
|
||||
DiffSymbolFlags flags = 5;
|
||||
DiffSymbolKind kind = 10;
|
||||
// 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;
|
||||
}
|
||||
|
||||
enum DiffSymbolKind {
|
||||
SYMBOL_UNKNOWN = 0;
|
||||
SYMBOL_FUNCTION = 1;
|
||||
SYMBOL_OBJECT = 2;
|
||||
SYMBOL_SECTION = 3;
|
||||
}
|
||||
|
||||
// Symbol flags
|
||||
message DiffSymbolFlags {
|
||||
bool global = 1;
|
||||
bool local = 2;
|
||||
bool weak = 3;
|
||||
bool common = 4;
|
||||
bool hidden = 5;
|
||||
bool ignored = 6;
|
||||
bool size_inferred = 7;
|
||||
}
|
||||
|
||||
// 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.
@@ -162,7 +162,7 @@ impl ArchArm {
|
||||
}
|
||||
|
||||
impl Arch for ArchArm {
|
||||
fn post_init(&mut self, sections: &[Section], symbols: &[Symbol]) {
|
||||
fn post_init(&mut self, sections: &[Section], symbols: &[Symbol], _symbol_indices: &[usize]) {
|
||||
self.disasm_modes = Self::get_mapping_symbols(sections, symbols);
|
||||
}
|
||||
|
||||
@@ -224,7 +224,9 @@ impl Arch for ArchArm {
|
||||
}
|
||||
|
||||
// Check how many bytes we can/should read
|
||||
let num_code_bytes = if data.len() >= 4 {
|
||||
let bytes_until_next_mapping =
|
||||
next_mapping.map(|m| (m.address - address) as usize).unwrap_or(usize::MAX);
|
||||
let num_code_bytes = if data.len() >= 4 && bytes_until_next_mapping >= 4 {
|
||||
if mode == unarm::ParseMode::Data && address & 3 != 0 {
|
||||
// 32-bit .word value should be aligned on a 4-byte boundary, otherwise use .hword
|
||||
2
|
||||
@@ -345,7 +347,10 @@ impl Arch for ArchArm {
|
||||
let address = address as usize;
|
||||
let addend = match r_type {
|
||||
// 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 addend = self.endianness.read_i32_bytes(data);
|
||||
let imm24 = addend & 0xffffff;
|
||||
@@ -363,6 +368,22 @@ impl Arch for ArchArm {
|
||||
(imm22 << 1) << 9 >> 9
|
||||
}
|
||||
|
||||
// Thumb unconditional branch (B, 11-bit offset)
|
||||
elf::R_ARM_THM_PC11 => {
|
||||
let data = section_data[address..address + 2].try_into()?;
|
||||
let insn = self.endianness.read_u16_bytes(data) as i32;
|
||||
let imm11 = insn & 0x7ff;
|
||||
(imm11 << 1) << 20 >> 20
|
||||
}
|
||||
|
||||
// Thumb conditional branch (B<cond>, 8-bit offset)
|
||||
elf::R_ARM_THM_PC9 => {
|
||||
let data = section_data[address..address + 2].try_into()?;
|
||||
let insn = self.endianness.read_u16_bytes(data) as i32;
|
||||
let imm8 = insn & 0xff;
|
||||
(imm8 << 1) << 23 >> 23
|
||||
}
|
||||
|
||||
// Data
|
||||
elf::R_ARM_ABS32 => {
|
||||
let data = section_data[address..address + 4].try_into()?;
|
||||
@@ -396,6 +417,8 @@ impl Arch for ArchArm {
|
||||
elf::R_ARM_PC24 => Some("R_ARM_PC24"),
|
||||
elf::R_ARM_XPC25 => Some("R_ARM_XPC25"),
|
||||
elf::R_ARM_CALL => Some("R_ARM_CALL"),
|
||||
elf::R_ARM_THM_PC11 => Some("R_ARM_THM_PC11"),
|
||||
elf::R_ARM_THM_PC9 => Some("R_ARM_THM_PC9"),
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
@@ -415,6 +438,8 @@ impl Arch for ArchArm {
|
||||
elf::R_ARM_PC24 => 4,
|
||||
elf::R_ARM_XPC25 => 4,
|
||||
elf::R_ARM_CALL => 4,
|
||||
elf::R_ARM_THM_PC11 => 2,
|
||||
elf::R_ARM_THM_PC9 => 2,
|
||||
_ => 1,
|
||||
},
|
||||
_ => 1,
|
||||
@@ -541,7 +566,9 @@ impl unarm::FormatIns for ArgsFormatter<'_> {
|
||||
| RelocationFlags::Elf(elf::R_ARM_THM_PC22)
|
||||
| RelocationFlags::Elf(elf::R_ARM_PC24)
|
||||
| RelocationFlags::Elf(elf::R_ARM_XPC25)
|
||||
| RelocationFlags::Elf(elf::R_ARM_CALL) => {
|
||||
| RelocationFlags::Elf(elf::R_ARM_CALL)
|
||||
| RelocationFlags::Elf(elf::R_ARM_THM_PC11)
|
||||
| RelocationFlags::Elf(elf::R_ARM_THM_PC9) => {
|
||||
return self.write(InstructionPart::reloc());
|
||||
}
|
||||
_ => {}
|
||||
|
||||
+137
-38
@@ -7,17 +7,15 @@ use alloc::{
|
||||
};
|
||||
use core::{
|
||||
any::Any,
|
||||
ffi::CStr,
|
||||
fmt::{self, Debug},
|
||||
};
|
||||
|
||||
use anyhow::{Result, bail};
|
||||
use encoding_rs::SHIFT_JIS;
|
||||
use object::Endian as _;
|
||||
|
||||
use crate::{
|
||||
diff::{
|
||||
DiffObjConfig, DiffSide,
|
||||
ConfigEnum, DiffObjConfig, DiffSide, PreferredStringEncoding,
|
||||
display::{ContextItem, HoverItem, InstructionPart},
|
||||
},
|
||||
obj::{
|
||||
@@ -44,6 +42,43 @@ pub mod x86;
|
||||
pub const OPCODE_INVALID: u16 = u16::MAX;
|
||||
pub const OPCODE_DATA: u16 = u16::MAX - 1;
|
||||
|
||||
const SUPPORTED_ENCODINGS_WITH_NULL_TERM: [(&encoding_rs::Encoding, &str); 5] = [
|
||||
(encoding_rs::UTF_8, "UTF-8"),
|
||||
(encoding_rs::SHIFT_JIS, "Shift JIS"),
|
||||
(encoding_rs::WINDOWS_1252, "Windows-1252"),
|
||||
(encoding_rs::EUC_JP, "EUC-JP"),
|
||||
(encoding_rs::BIG5, "Big5"),
|
||||
];
|
||||
const SUPPORTED_ENCODINGS_NO_NULL_TERM: [(&encoding_rs::Encoding, &str); 2] =
|
||||
[(encoding_rs::UTF_16BE, "UTF-16BE"), (encoding_rs::UTF_16LE, "UTF-16LE")];
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq)]
|
||||
pub struct LiteralInfo {
|
||||
pub literal: String,
|
||||
pub label_override: Option<String>,
|
||||
pub copy_string: Option<String>,
|
||||
pub hidden: bool, // Only used when the user hasn't set a preferred string encoding
|
||||
pub is_string: bool,
|
||||
}
|
||||
|
||||
impl LiteralInfo {
|
||||
pub fn hidden(&self, diff_config: Option<&DiffObjConfig>) -> bool {
|
||||
let Some(diff_config) = diff_config else {
|
||||
return self.hidden;
|
||||
};
|
||||
if !self.is_string {
|
||||
return self.hidden;
|
||||
}
|
||||
if diff_config.preferred_string_encoding == PreferredStringEncoding::Auto {
|
||||
return self.hidden;
|
||||
}
|
||||
let Some(ref label) = self.label_override else {
|
||||
return self.hidden;
|
||||
};
|
||||
*label != diff_config.preferred_string_encoding.name()
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the type of data associated with an instruction
|
||||
#[derive(PartialEq)]
|
||||
pub enum DataType {
|
||||
@@ -75,18 +110,14 @@ impl fmt::Display for DataType {
|
||||
impl DataType {
|
||||
pub fn display_labels(&self, endian: object::Endianness, bytes: &[u8]) -> Vec<String> {
|
||||
let mut strs = Vec::new();
|
||||
for (literal, label_override) in self.display_literals(endian, bytes) {
|
||||
let label = label_override.unwrap_or_else(|| self.to_string());
|
||||
strs.push(format!("{label}: {literal}"))
|
||||
for lit_info in self.display_literals(endian, bytes) {
|
||||
let label = lit_info.label_override.unwrap_or_else(|| self.to_string());
|
||||
strs.push(format!("{}: {:?}", label, lit_info.literal))
|
||||
}
|
||||
strs
|
||||
}
|
||||
|
||||
pub fn display_literals(
|
||||
&self,
|
||||
endian: object::Endianness,
|
||||
bytes: &[u8],
|
||||
) -> Vec<(String, Option<String>)> {
|
||||
pub fn display_literals(&self, endian: object::Endianness, bytes: &[u8]) -> Vec<LiteralInfo> {
|
||||
let mut strs = Vec::new();
|
||||
if self.required_len().is_some_and(|l| bytes.len() < l) {
|
||||
log::warn!(
|
||||
@@ -110,73 +141,122 @@ impl DataType {
|
||||
match self {
|
||||
DataType::Int8 => {
|
||||
let i = i8::from_ne_bytes(bytes.try_into().unwrap());
|
||||
strs.push((format!("{i:#x}"), None));
|
||||
strs.push(LiteralInfo { literal: format!("{i:#x}"), ..Default::default() });
|
||||
|
||||
if i < 0 {
|
||||
strs.push((format!("{:#x}", ReallySigned(i)), None));
|
||||
strs.push(LiteralInfo {
|
||||
literal: format!("{:#x}", ReallySigned(i)),
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
}
|
||||
DataType::Int16 => {
|
||||
let i = endian.read_i16_bytes(bytes.try_into().unwrap());
|
||||
strs.push((format!("{i:#x}"), None));
|
||||
strs.push(LiteralInfo { literal: format!("{i:#x}"), ..Default::default() });
|
||||
|
||||
if i < 0 {
|
||||
strs.push((format!("{:#x}", ReallySigned(i)), None));
|
||||
strs.push(LiteralInfo {
|
||||
literal: format!("{:#x}", ReallySigned(i)),
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
}
|
||||
DataType::Int32 => {
|
||||
let i = endian.read_i32_bytes(bytes.try_into().unwrap());
|
||||
strs.push((format!("{i:#x}"), None));
|
||||
strs.push(LiteralInfo { literal: format!("{i:#x}"), ..Default::default() });
|
||||
|
||||
if i < 0 {
|
||||
strs.push((format!("{:#x}", ReallySigned(i)), None));
|
||||
strs.push(LiteralInfo {
|
||||
literal: format!("{:#x}", ReallySigned(i)),
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
}
|
||||
DataType::Int64 => {
|
||||
let i = endian.read_i64_bytes(bytes.try_into().unwrap());
|
||||
strs.push((format!("{i:#x}"), None));
|
||||
strs.push(LiteralInfo { literal: format!("{i:#x}"), ..Default::default() });
|
||||
|
||||
if i < 0 {
|
||||
strs.push((format!("{:#x}", ReallySigned(i)), None));
|
||||
strs.push(LiteralInfo {
|
||||
literal: format!("{:#x}", ReallySigned(i)),
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
}
|
||||
DataType::Float => {
|
||||
let bytes: [u8; 4] = bytes.try_into().unwrap();
|
||||
strs.push((
|
||||
format!("{:?}f", match endian {
|
||||
strs.push(LiteralInfo {
|
||||
literal: format!("{:?}f", match endian {
|
||||
object::Endianness::Little => f32::from_le_bytes(bytes),
|
||||
object::Endianness::Big => f32::from_be_bytes(bytes),
|
||||
}),
|
||||
None,
|
||||
));
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
DataType::Double => {
|
||||
let bytes: [u8; 8] = bytes.try_into().unwrap();
|
||||
strs.push((
|
||||
format!("{:?}", match endian {
|
||||
strs.push(LiteralInfo {
|
||||
literal: format!("{:?}", match endian {
|
||||
object::Endianness::Little => f64::from_le_bytes(bytes),
|
||||
object::Endianness::Big => f64::from_be_bytes(bytes),
|
||||
}),
|
||||
None,
|
||||
));
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
DataType::Bytes => {
|
||||
strs.push((format!("{bytes:#?}"), None));
|
||||
strs.push(LiteralInfo { literal: format!("{bytes:#?}"), ..Default::default() });
|
||||
}
|
||||
DataType::String => {
|
||||
if let Ok(cstr) = CStr::from_bytes_until_nul(bytes) {
|
||||
strs.push((format!("{cstr:?}"), None));
|
||||
}
|
||||
if let Some(nul_idx) = bytes.iter().position(|&c| c == b'\0') {
|
||||
let (cow, _, had_errors) = SHIFT_JIS.decode(&bytes[..nul_idx]);
|
||||
if !had_errors {
|
||||
let str = format!("{cow:?}");
|
||||
// Only add the Shift JIS string if it's different from the ASCII string.
|
||||
if !strs.iter().any(|x| x.0 == str) {
|
||||
strs.push((str, Some("Shift JIS".into())));
|
||||
let str_bytes = &bytes[..nul_idx];
|
||||
// Special case to display (ASCII) as the label for ASCII-only strings.
|
||||
let (cow, _, had_errors) = encoding_rs::UTF_8.decode(str_bytes);
|
||||
if !had_errors && cow.is_ascii() {
|
||||
let string = format!("{cow}");
|
||||
let copy_string = escape_special_ascii_characters(&string);
|
||||
strs.push(LiteralInfo {
|
||||
literal: string,
|
||||
label_override: Some("ASCII".into()),
|
||||
copy_string: Some(copy_string),
|
||||
hidden: false,
|
||||
is_string: true,
|
||||
});
|
||||
}
|
||||
for (encoding, encoding_name) in SUPPORTED_ENCODINGS_WITH_NULL_TERM {
|
||||
let (cow, _, had_errors) = encoding.decode(str_bytes);
|
||||
if !had_errors {
|
||||
let string = format!("{cow}");
|
||||
let copy_string = escape_special_ascii_characters(&string);
|
||||
// Avoid showing ASCII-only strings more than once if the encoding is ASCII-compatible.
|
||||
let hidden = encoding.is_ascii_compatible() && cow.is_ascii();
|
||||
strs.push(LiteralInfo {
|
||||
literal: string,
|
||||
label_override: Some(encoding_name.into()),
|
||||
copy_string: Some(copy_string),
|
||||
hidden,
|
||||
is_string: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (encoding, encoding_name) in SUPPORTED_ENCODINGS_NO_NULL_TERM {
|
||||
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(LiteralInfo {
|
||||
literal: trimmed.to_string(),
|
||||
label_override: Some(encoding_name.into()),
|
||||
copy_string: Some(copy_string),
|
||||
hidden: false,
|
||||
is_string: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -311,7 +391,8 @@ impl dyn Arch {
|
||||
|
||||
pub trait Arch: Any + Debug + Send + Sync {
|
||||
/// Finishes arch-specific initialization that must be done after sections have been combined.
|
||||
fn post_init(&mut self, _sections: &[Section], _symbols: &[Symbol]) {}
|
||||
fn post_init(&mut self, _sections: &[Section], _symbols: &[Symbol], _symbol_indices: &[usize]) {
|
||||
}
|
||||
|
||||
/// Generate a list of instructions references (offset, size, opcode) from the given code.
|
||||
///
|
||||
@@ -489,3 +570,21 @@ pub struct RelocationOverride {
|
||||
pub target: RelocationOverrideTarget,
|
||||
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
|
||||
}
|
||||
|
||||
@@ -513,6 +513,8 @@ pub fn ppc_data_flow_analysis(
|
||||
)
|
||||
}
|
||||
|
||||
// Note: This function only supports MWCC ASCII strings.
|
||||
// Other encodings and other compilers are currently not supported.
|
||||
fn get_string_data(obj: &Object, symbol_index: usize, offset: Simm) -> Option<&str> {
|
||||
if let Some(sym) = obj.symbols.get(symbol_index)
|
||||
&& sym.name.starts_with("@stringBase")
|
||||
|
||||
@@ -238,9 +238,9 @@ impl Arch for ArchPpc {
|
||||
addend: 0,
|
||||
})),
|
||||
|(_, reloc)| match reloc.target() {
|
||||
object::RelocationTarget::Symbol(index) => Ok(Some(RelocationOverride {
|
||||
object::RelocationTarget::Symbol(_) => Ok(Some(RelocationOverride {
|
||||
target: RelocationOverrideTarget::Keep,
|
||||
addend: index.0 as u16 as i16 as i64,
|
||||
addend: 0,
|
||||
})),
|
||||
target => Err(anyhow!("Unsupported IMAGE_REL_PPC_PAIR target {target:?}")),
|
||||
},
|
||||
@@ -342,7 +342,10 @@ impl Arch for ArchPpc {
|
||||
elf::R_PPC_UADDR32 => 4,
|
||||
_ => 1,
|
||||
},
|
||||
_ => 1,
|
||||
RelocationFlags::Coff(r_type) => match r_type {
|
||||
pe::IMAGE_REL_PPC_ADDR32 => 4,
|
||||
_ => 1,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -355,8 +358,13 @@ impl Arch for ArchPpc {
|
||||
}
|
||||
|
||||
fn guess_data_type(&self, resolved: ResolvedInstructionRef, bytes: &[u8]) -> Option<DataType> {
|
||||
if resolved.relocation.is_some_and(|r| r.symbol.name.starts_with("@stringBase")) {
|
||||
// Pooled string.
|
||||
if resolved.relocation.is_some_and(|r| {
|
||||
r.symbol.name.starts_with("@stringBase")
|
||||
|| r.symbol.name.starts_with("@wstringBase")
|
||||
|| r.symbol.name.starts_with("$SG")
|
||||
|| r.symbol.demangled_name == Some("`string'".to_string())
|
||||
}) {
|
||||
// Compiler-generated symbol name for a string or a pool of strings.
|
||||
return Some(DataType::String);
|
||||
}
|
||||
let opcode = powerpc::Opcode::from(resolved.ins_ref.opcode);
|
||||
@@ -440,9 +448,13 @@ impl Arch for ArchPpc {
|
||||
let simplified = ins.simplified().to_string();
|
||||
let show_orig = orig != simplified;
|
||||
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 {
|
||||
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
|
||||
}
|
||||
@@ -463,12 +475,27 @@ impl Arch for ArchPpc {
|
||||
}
|
||||
Ok(next_address.saturating_sub(symbol.address))
|
||||
}
|
||||
|
||||
fn post_init(&mut self, _sections: &[Section], _symbols: &[Symbol], symbol_indices: &[usize]) {
|
||||
// Change the indices used as keys from the original symbol indices to the new symbol array indices
|
||||
self.extab = Self::convert_extab_map_indices(self, symbol_indices);
|
||||
}
|
||||
}
|
||||
|
||||
impl ArchPpc {
|
||||
pub fn extab_for_symbol(&self, symbol_index: usize) -> Option<&ExceptionInfo> {
|
||||
self.extab.as_ref()?.get(&symbol_index)
|
||||
}
|
||||
|
||||
pub fn convert_extab_map_indices(
|
||||
&self,
|
||||
symbol_indices: &[usize],
|
||||
) -> Option<BTreeMap<usize, ExceptionInfo>> {
|
||||
let new_map: BTreeMap<usize, ExceptionInfo> =
|
||||
self.extab.as_ref()?.iter().map(|e| (symbol_indices[*e.0 + 1], e.1.clone())).collect();
|
||||
|
||||
Some(new_map)
|
||||
}
|
||||
}
|
||||
|
||||
fn zero_reloc(code: u32, reloc: &Relocation) -> u32 {
|
||||
|
||||
@@ -68,7 +68,12 @@ impl ArchX86 {
|
||||
_ => None,
|
||||
},
|
||||
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),
|
||||
_ => None,
|
||||
},
|
||||
@@ -272,7 +277,15 @@ impl Arch for ArchX86 {
|
||||
object::RelocationFlags::Coff {
|
||||
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 =
|
||||
section.data()?[address as usize..address as usize + 4].try_into()?;
|
||||
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"),
|
||||
_ => 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 {
|
||||
RelocationFlags::Coff(typ) => match typ {
|
||||
|
||||
@@ -0,0 +1,351 @@
|
||||
#![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];
|
||||
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),
|
||||
kind: DiffSymbolKind::from(symbol.kind) as i32,
|
||||
// Diff information
|
||||
target_symbol: symbol_diff.target_symbol.map(|i| i as u32),
|
||||
match_percent: symbol_diff.match_percent,
|
||||
instructions,
|
||||
data_diff,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<obj::SymbolKind> for DiffSymbolKind {
|
||||
fn from(value: obj::SymbolKind) -> Self {
|
||||
match value {
|
||||
obj::SymbolKind::Unknown => DiffSymbolKind::SymbolUnknown,
|
||||
obj::SymbolKind::Function => DiffSymbolKind::SymbolFunction,
|
||||
obj::SymbolKind::Object => DiffSymbolKind::SymbolObject,
|
||||
obj::SymbolKind::Section => DiffSymbolKind::SymbolSection,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn symbol_flags(flags: &obj::SymbolFlagSet) -> Option<DiffSymbolFlags> {
|
||||
Some(DiffSymbolFlags {
|
||||
global: flags.contains(SymbolFlag::Global),
|
||||
local: flags.contains(SymbolFlag::Local),
|
||||
weak: flags.contains(SymbolFlag::Weak),
|
||||
common: flags.contains(SymbolFlag::Common),
|
||||
hidden: flags.contains(SymbolFlag::Hidden),
|
||||
ignored: flags.contains(SymbolFlag::Ignored),
|
||||
size_inferred: flags.contains(SymbolFlag::SizeInferred),
|
||||
})
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
@@ -9,8 +9,8 @@ use anyhow::{Context, Result, anyhow, ensure};
|
||||
|
||||
use super::{
|
||||
DiffObjConfig, FunctionRelocDiffs, InstructionArgDiffIndex, InstructionBranchFrom,
|
||||
InstructionBranchTo, InstructionDiffKind, InstructionDiffRow, SymbolDiff,
|
||||
display::display_ins_data_literals,
|
||||
InstructionBranchTo, InstructionDiffKind, InstructionDiffRow, PreferredStringEncoding,
|
||||
SymbolDiff, display::display_ins_data_literals,
|
||||
};
|
||||
use crate::obj::{
|
||||
InstructionArg, InstructionArgValue, InstructionRef, Object, ResolvedInstructionRef,
|
||||
@@ -296,6 +296,26 @@ pub(crate) fn section_name_eq(
|
||||
})
|
||||
}
|
||||
|
||||
fn ins_data_literals_eq(
|
||||
left_obj: &Object,
|
||||
right_obj: &Object,
|
||||
left_ins: ResolvedInstructionRef,
|
||||
right_ins: ResolvedInstructionRef,
|
||||
diff_config: &DiffObjConfig,
|
||||
) -> bool {
|
||||
let mut left_literals = display_ins_data_literals(left_obj, left_ins);
|
||||
let mut right_literals = display_ins_data_literals(right_obj, right_ins);
|
||||
if left_literals == right_literals {
|
||||
return true;
|
||||
}
|
||||
if diff_config.preferred_string_encoding == PreferredStringEncoding::Auto {
|
||||
return left_literals == right_literals;
|
||||
}
|
||||
left_literals.retain(|lit_info| !lit_info.hidden(Some(diff_config)));
|
||||
right_literals.retain(|lit_info| !lit_info.hidden(Some(diff_config)));
|
||||
left_literals == right_literals
|
||||
}
|
||||
|
||||
fn reloc_eq(
|
||||
left_obj: &Object,
|
||||
right_obj: &Object,
|
||||
@@ -330,8 +350,7 @@ fn reloc_eq(
|
||||
&& (diff_config.function_reloc_diffs == FunctionRelocDiffs::NameAddress
|
||||
|| left_reloc.symbol.kind != SymbolKind::Object
|
||||
|| right_reloc.symbol.size == 0 // Likely a pool symbol like ...data, don't treat this as a diff
|
||||
|| display_ins_data_literals(left_obj, left_ins)
|
||||
== display_ins_data_literals(right_obj, right_ins))
|
||||
|| ins_data_literals_eq(left_obj, right_obj, left_ins, right_ins, diff_config))
|
||||
}
|
||||
(Some(_), None) | (None, Some(_)) | (None, None) => symbol_name_addend_matches,
|
||||
}
|
||||
|
||||
@@ -35,18 +35,13 @@ pub fn diff_bss_symbol(
|
||||
))
|
||||
}
|
||||
|
||||
pub fn symbol_name_matches(left_name: &str, right_name: &str) -> bool {
|
||||
// Match Metrowerks symbol$1234 against symbol$2345
|
||||
// and GCC symbol.1234 against symbol.2345
|
||||
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 {
|
||||
pub fn symbol_name_matches(left: &Symbol, right: &Symbol) -> bool {
|
||||
if let Some(left_name) = &left.normalized_name
|
||||
&& let Some(right_name) = &right.normalized_name
|
||||
{
|
||||
left_name == right_name
|
||||
} else {
|
||||
left.name == right.name
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +55,7 @@ fn reloc_eq(
|
||||
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;
|
||||
match (left.symbol.section, right.symbol.section) {
|
||||
(Some(sl), Some(sr)) => {
|
||||
@@ -588,6 +583,7 @@ fn symbols_matching_section(
|
||||
s.section == Some(section_idx)
|
||||
&& s.kind != SymbolKind::Section
|
||||
&& s.size > 0
|
||||
&& !s.flags.contains(SymbolFlag::Hidden)
|
||||
&& !s.flags.contains(SymbolFlag::Ignored)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ use itertools::Itertools;
|
||||
use regex::Regex;
|
||||
|
||||
use crate::{
|
||||
arch::LiteralInfo,
|
||||
diff::{
|
||||
DataDiffKind, DataDiffRow, DiffObjConfig, InstructionDiffKind, InstructionDiffRow,
|
||||
ObjectDiff, SymbolDiff, data::resolve_relocation,
|
||||
@@ -37,6 +38,8 @@ pub enum DiffText<'a> {
|
||||
Argument(InstructionArgValue<'a>),
|
||||
/// Branch destination
|
||||
BranchDest(u64),
|
||||
/// Branch source/destination arrow, scrolls to a specific row when clicked
|
||||
BranchArrow(u32),
|
||||
/// Symbol name
|
||||
Symbol(&'a Symbol),
|
||||
/// Relocation addend
|
||||
@@ -185,7 +188,13 @@ pub fn display_row(
|
||||
pad_to: 5,
|
||||
})?;
|
||||
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 {
|
||||
cb(DiffTextSegment::spacing(4))?;
|
||||
}
|
||||
@@ -290,9 +299,8 @@ pub fn display_row(
|
||||
}
|
||||
})?;
|
||||
// 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))?;
|
||||
let resolved = resolved.relocation.unwrap();
|
||||
let diff_index = ins_row.arg_diff.get(arg_idx).copied().unwrap_or_default();
|
||||
let color =
|
||||
diff_index.get().map_or(DiffTextColor::Bright, |i| DiffTextColor::Rotating(i as u8));
|
||||
@@ -307,7 +315,11 @@ pub fn display_row(
|
||||
cb(DiffTextSegment::basic(">", base_color))?;
|
||||
}
|
||||
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)?;
|
||||
Ok(())
|
||||
@@ -354,7 +366,7 @@ impl From<&DiffText<'_>> for HighlightKind {
|
||||
}
|
||||
|
||||
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 },
|
||||
Separator,
|
||||
}
|
||||
@@ -386,9 +398,9 @@ pub fn symbol_context(obj: &Object, symbol_index: usize) -> Vec<ContextItem> {
|
||||
return 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 {
|
||||
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()
|
||||
&& let Some(address) = symbol.virtual_address
|
||||
@@ -396,6 +408,7 @@ pub fn symbol_context(obj: &Object, symbol_index: usize) -> Vec<ContextItem> {
|
||||
out.push(ContextItem::Copy {
|
||||
value: format!("{address:x}"),
|
||||
label: Some("virtual address".to_string()),
|
||||
copy_string: None,
|
||||
});
|
||||
}
|
||||
out.append(&mut obj.arch.symbol_context(obj, symbol_index));
|
||||
@@ -482,15 +495,21 @@ pub fn relocation_context(
|
||||
obj: &Object,
|
||||
reloc: ResolvedRelocation,
|
||||
ins: Option<ResolvedInstructionRef>,
|
||||
diff_config: Option<&DiffObjConfig>,
|
||||
) -> Vec<ContextItem> {
|
||||
let mut out = Vec::new();
|
||||
out.append(&mut symbol_context(obj, reloc.relocation.target_symbol));
|
||||
if let Some(ins) = ins {
|
||||
let literals = display_ins_data_literals(obj, ins);
|
||||
let mut literals = display_ins_data_literals(obj, ins);
|
||||
literals.retain(|lit_info| !lit_info.hidden(diff_config));
|
||||
if !literals.is_empty() {
|
||||
out.push(ContextItem::Separator);
|
||||
for (literal, label_override) in literals {
|
||||
out.push(ContextItem::Copy { value: literal, label: label_override });
|
||||
for lit_info in literals {
|
||||
out.push(ContextItem::Copy {
|
||||
value: lit_info.literal,
|
||||
label: lit_info.label_override,
|
||||
copy_string: lit_info.copy_string,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -543,7 +562,7 @@ pub fn data_row_context(obj: &Object, diff_row: &DataDiffRow) -> Vec<ContextItem
|
||||
prev_reloc = Some(reloc);
|
||||
|
||||
let reloc = resolve_relocation(&obj.symbols, reloc);
|
||||
out.append(&mut relocation_context(obj, reloc, None));
|
||||
out.append(&mut relocation_context(obj, reloc, None, None));
|
||||
}
|
||||
out
|
||||
}
|
||||
@@ -580,30 +599,44 @@ pub fn instruction_context(
|
||||
obj: &Object,
|
||||
resolved: ResolvedInstructionRef,
|
||||
ins: &ParsedInstruction,
|
||||
diff_config: &DiffObjConfig,
|
||||
) -> Vec<ContextItem> {
|
||||
let mut out = Vec::new();
|
||||
let mut hex_string = String::new();
|
||||
for byte in resolved.code {
|
||||
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));
|
||||
if let Some(virtual_address) = resolved.symbol.virtual_address {
|
||||
let offset = resolved.ins_ref.address - resolved.symbol.address;
|
||||
out.push(ContextItem::Copy {
|
||||
value: format!("{:x}", virtual_address + offset),
|
||||
label: Some("virtual address".to_string()),
|
||||
copy_string: None,
|
||||
});
|
||||
}
|
||||
for arg in &ins.args {
|
||||
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 {
|
||||
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) => {
|
||||
out.push(ContextItem::Copy { value: v.to_string(), label: None });
|
||||
out.push(ContextItem::Copy {
|
||||
value: v.to_string(),
|
||||
label: None,
|
||||
copy_string: None,
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@@ -611,7 +644,7 @@ pub fn instruction_context(
|
||||
}
|
||||
if let Some(reloc) = resolved.relocation {
|
||||
out.push(ContextItem::Separator);
|
||||
out.append(&mut relocation_context(obj, reloc, Some(resolved)));
|
||||
out.append(&mut relocation_context(obj, reloc, Some(resolved), Some(diff_config)));
|
||||
}
|
||||
out
|
||||
}
|
||||
@@ -620,6 +653,7 @@ pub fn instruction_hover(
|
||||
obj: &Object,
|
||||
resolved: ResolvedInstructionRef,
|
||||
ins: &ParsedInstruction,
|
||||
diff_config: &DiffObjConfig,
|
||||
) -> Vec<HoverItem> {
|
||||
let mut out = Vec::new();
|
||||
out.push(HoverItem::Text {
|
||||
@@ -662,13 +696,14 @@ pub fn instruction_hover(
|
||||
out.append(&mut relocation_hover(obj, reloc, None));
|
||||
let bytes = obj.symbol_data(reloc.relocation.target_symbol).unwrap_or(&[]);
|
||||
if let Some(ty) = obj.arch.guess_data_type(resolved, bytes) {
|
||||
let literals = display_ins_data_literals(obj, resolved);
|
||||
let mut literals = display_ins_data_literals(obj, resolved);
|
||||
literals.retain(|lit_info| !lit_info.hidden(Some(diff_config)));
|
||||
if !literals.is_empty() {
|
||||
out.push(HoverItem::Separator);
|
||||
for (literal, label_override) in literals {
|
||||
for lit_info in literals {
|
||||
out.push(HoverItem::Text {
|
||||
label: label_override.unwrap_or_else(|| ty.to_string()),
|
||||
value: literal,
|
||||
label: lit_info.label_override.unwrap_or_else(|| ty.to_string()),
|
||||
value: format!("{:?}", lit_info.literal),
|
||||
color: HoverItemColor::Normal,
|
||||
});
|
||||
}
|
||||
@@ -787,19 +822,15 @@ pub fn display_sections(
|
||||
}
|
||||
let section_diff = &diff.sections[section_idx];
|
||||
let reverse_fn_order = section.kind == SectionKind::Code && reverse_fn_order;
|
||||
symbols.sort_by(|a, b| {
|
||||
let a = &obj.symbols[a.symbol];
|
||||
let b = &obj.symbols[b.symbol];
|
||||
section_symbol_sort(a, b)
|
||||
.then_with(|| {
|
||||
if reverse_fn_order {
|
||||
b.address.cmp(&a.address)
|
||||
} else {
|
||||
a.address.cmp(&b.address)
|
||||
}
|
||||
})
|
||||
.then_with(|| a.size.cmp(&b.size))
|
||||
});
|
||||
if reverse_fn_order {
|
||||
symbols.sort_by(|a, b| {
|
||||
let a = &obj.symbols[a.symbol];
|
||||
let b = &obj.symbols[b.symbol];
|
||||
section_symbol_sort(a, b)
|
||||
.then_with(|| b.address.cmp(&a.address))
|
||||
.then_with(|| a.size.cmp(&b.size))
|
||||
});
|
||||
}
|
||||
sections.push(SectionDisplay {
|
||||
id: section.id.clone(),
|
||||
name: if section.flags.contains(SectionFlag::Combined) {
|
||||
@@ -859,7 +890,7 @@ pub fn display_ins_data_labels(obj: &Object, resolved: ResolvedInstructionRef) -
|
||||
pub fn display_ins_data_literals(
|
||||
obj: &Object,
|
||||
resolved: ResolvedInstructionRef,
|
||||
) -> Vec<(String, Option<String>)> {
|
||||
) -> Vec<LiteralInfo> {
|
||||
let Some(reloc) = resolved.relocation else {
|
||||
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(
|
||||
obj: Option<&Object>,
|
||||
in_obj: &Object,
|
||||
@@ -711,7 +699,7 @@ fn find_symbol(
|
||||
|
||||
// Match compiler-generated symbols against each other (e.g. @251 -> @60)
|
||||
// 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)
|
||||
{
|
||||
let mut closest_match_symbol_idx = None;
|
||||
@@ -723,7 +711,7 @@ fn find_symbol(
|
||||
if obj.sections[section_index].name != section_name {
|
||||
continue;
|
||||
}
|
||||
if !is_symbol_compiler_generated(symbol) {
|
||||
if !symbol.flags.contains(SymbolFlag::CompilerGenerated) {
|
||||
continue;
|
||||
}
|
||||
match section_kind {
|
||||
@@ -759,15 +747,9 @@ fn find_symbol(
|
||||
return closest_match_symbol_idx;
|
||||
}
|
||||
|
||||
// Try to find an exact name match
|
||||
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);
|
||||
}
|
||||
|
||||
// Try to find a symbol with a matching name
|
||||
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(obj, symbol).is_some_and(|(name, _)| name == section_name)
|
||||
}) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user