mirror of
https://github.com/encounter/objdiff.git
synced 2026-07-10 12:18:36 -07:00
Compare commits
34 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7834185223 | |||
| 216dd70d5b | |||
| 976e629f08 | |||
| 67b28b7da1 | |||
| c0b9bfae21 | |||
| f3f4b0482a | |||
| 278683f103 | |||
| 6ad60de32a | |||
| 90c93164ab | |||
| c02eb31dbb | |||
| 86d92866aa | |||
| 51c3af2bbe | |||
| d0b8b449d9 | |||
| 32f5f202f7 | |||
| 481dbc185a | |||
| 26a4cc79cf | |||
| 5c96c2ee51 | |||
| d162fe847e | |||
| 2a24eb5aec | |||
| f7c291bd55 | |||
| 03a578c1bb | |||
| d09ef8e207 | |||
| 63552a58ae | |||
| 827f4a42bd | |||
| b2dcecc5d8 | |||
| 67b237eab6 | |||
| 66da80ff69 | |||
| 0a85f540f2 | |||
| d79b5b1233 | |||
| 8f5519cb6a | |||
| 19ec08be9a | |||
| 2ad0898efa | |||
| 03f2bcb8b1 | |||
| 781071761a |
Generated
+752
-728
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -14,7 +14,7 @@ default-members = [
|
||||
resolver = "3"
|
||||
|
||||
[workspace.package]
|
||||
version = "3.3.0"
|
||||
version = "3.5.1"
|
||||
authors = ["Luke Street <luke@street.dev>"]
|
||||
edition = "2024"
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
@@ -75,6 +75,8 @@ ignore = [
|
||||
#{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" },
|
||||
{ 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" },
|
||||
]
|
||||
# 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.
|
||||
|
||||
@@ -16,19 +16,19 @@ publish = false
|
||||
anyhow = "1.0"
|
||||
argp = "0.4"
|
||||
crossterm = "0.29"
|
||||
enable-ansi-support = "0.2"
|
||||
enable-ansi-support = "0.3"
|
||||
memmap2 = "0.9"
|
||||
objdiff-core = { path = "../objdiff-core", features = ["all"] }
|
||||
prost = "0.14"
|
||||
ratatui = "0.29"
|
||||
rayon = "1.10"
|
||||
rayon = "1.11"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
supports-color = "3.0"
|
||||
time = { version = "0.3", features = ["formatting", "local-offset"] }
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
typed-path = "0.11"
|
||||
typed-path = "0.12"
|
||||
|
||||
[target.'cfg(target_env = "musl")'.dependencies]
|
||||
mimalloc = "0.1"
|
||||
|
||||
@@ -66,15 +66,6 @@ pub struct Args {
|
||||
#[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<()> {
|
||||
@@ -183,17 +174,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 {
|
||||
|
||||
@@ -378,7 +378,6 @@ impl UiView for FunctionDiffUi {
|
||||
}
|
||||
// Reload
|
||||
KeyCode::Char('r') => {
|
||||
result.redraw = true;
|
||||
return EventControlFlow::Reload;
|
||||
}
|
||||
// Scroll right
|
||||
@@ -400,7 +399,6 @@ impl UiView for FunctionDiffUi {
|
||||
FunctionRelocDiffs::DataValue => FunctionRelocDiffs::All,
|
||||
FunctionRelocDiffs::All => FunctionRelocDiffs::None,
|
||||
};
|
||||
result.redraw = true;
|
||||
return EventControlFlow::Reload;
|
||||
}
|
||||
// Toggle three-way diff
|
||||
@@ -541,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()
|
||||
}
|
||||
|
||||
@@ -135,10 +135,10 @@ num-traits = { version = "0.2", default-features = false, optional = true }
|
||||
object = { version = "0.37", default-features = false, features = ["read_core", "elf", "coff"] }
|
||||
pbjson = { version = "0.8", default-features = false, optional = true }
|
||||
prost = { version = "0.14", default-features = false, features = ["derive"], optional = true }
|
||||
regex = { version = "1.11", default-features = false, features = [], optional = true }
|
||||
regex = { version = "1.12", default-features = false, features = [], optional = true }
|
||||
serde = { version = "1.0", default-features = false, features = ["derive"], optional = true }
|
||||
similar = { git = "https://github.com/encounter/similar.git", branch = "no_std", default-features = false, features = ["hashbrown"], optional = true }
|
||||
typed-path = { version = "0.11", default-features = false, optional = true }
|
||||
typed-path = { version = "0.12", default-features = false, optional = true }
|
||||
|
||||
# config
|
||||
globset = { version = "0.4", default-features = false, optional = true }
|
||||
@@ -155,13 +155,13 @@ powerpc = { version = "0.4", optional = true }
|
||||
rlwinmdec = { version = "1.1", optional = true }
|
||||
|
||||
# mips
|
||||
rabbitizer = { version = "2.0.0-alpha.4", default-features = false, features = ["all_extensions"], optional = true }
|
||||
rabbitizer = { version = "2.0.0-alpha.7", default-features = false, features = ["all_extensions"], optional = true }
|
||||
|
||||
# x86
|
||||
iced-x86 = { version = "1.21", default-features = false, features = ["decoder", "intel", "gas", "masm", "nasm", "exhaustive_enums", "no_std"], optional = true }
|
||||
|
||||
# arm
|
||||
unarm = { version = "1.9", optional = true }
|
||||
unarm = { version = "2.1", optional = true }
|
||||
arm-attr = { version = "0.2", optional = true }
|
||||
|
||||
# arm64
|
||||
@@ -169,17 +169,17 @@ yaxpeax-arch = { version = "0.3", default-features = false, optional = true }
|
||||
yaxpeax-arm = { version = "0.3", default-features = false, optional = true }
|
||||
|
||||
# build
|
||||
notify = { version = "8.1.0", optional = true }
|
||||
notify-debouncer-full = { version = "0.5.0", optional = true }
|
||||
notify = { version = "8.2.0", optional = true }
|
||||
notify-debouncer-full = { version = "0.6.0", optional = true }
|
||||
shell-escape = { version = "0.1", optional = true }
|
||||
tempfile = { version = "3.20", optional = true }
|
||||
tempfile = { version = "3.23", optional = true }
|
||||
time = { version = "0.3", optional = true }
|
||||
encoding_rs = { version = "0.8.35", optional = true }
|
||||
|
||||
# demangler
|
||||
cpp_demangle = { version = "0.4", optional = true, default-features = false, features = ["alloc"] }
|
||||
cpp_demangle = { version = "0.5", optional = true, default-features = false, features = ["alloc"] }
|
||||
cwdemangle = { version = "1.0", optional = true }
|
||||
gnuv2_demangle = { version = "0.1.0", optional = true }
|
||||
gnuv2_demangle = { version = "0.4", optional = true }
|
||||
msvc-demangler = { version = "0.11", optional = true }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
|
||||
@@ -82,6 +82,27 @@
|
||||
"name": "Space between args",
|
||||
"description": "Adds a space between arguments in the diff output."
|
||||
},
|
||||
{
|
||||
"id": "showSymbolSizes",
|
||||
"type": "choice",
|
||||
"default": "off",
|
||||
"name": "Show symbol sizes",
|
||||
"description": "Shows symbol sizes in the symbol view.",
|
||||
"items": [
|
||||
{
|
||||
"value": "off",
|
||||
"name": "Off"
|
||||
},
|
||||
{
|
||||
"value": "hex",
|
||||
"name": "Hex"
|
||||
},
|
||||
{
|
||||
"value": "decimal",
|
||||
"name": "Decimal"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "combineDataSections",
|
||||
"type": "boolean",
|
||||
@@ -107,20 +128,43 @@
|
||||
"value": "auto",
|
||||
"name": "Auto"
|
||||
},
|
||||
{
|
||||
"value": "v4",
|
||||
"name": "ARMv4"
|
||||
},
|
||||
{
|
||||
"value": "v4t",
|
||||
"name": "ARMv4T (GBA)"
|
||||
},
|
||||
{
|
||||
"value": "v5t",
|
||||
"name": "ARMv5T"
|
||||
},
|
||||
{
|
||||
"value": "v5te",
|
||||
"name": "ARMv5TE (DS)"
|
||||
},
|
||||
{
|
||||
"value": "v5tej",
|
||||
"name": "ARMv5TEJ"
|
||||
},
|
||||
{
|
||||
"value": "v6",
|
||||
"name": "ARMv6"
|
||||
},
|
||||
{
|
||||
"value": "v6k",
|
||||
"name": "ARMv6K (3DS)"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "arm.vfpV2",
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"name": "VFPv2 instructions",
|
||||
"description": "Adds floating-point instructions from VFPv2."
|
||||
},
|
||||
{
|
||||
"id": "arm.unifiedSyntax",
|
||||
"type": "boolean",
|
||||
@@ -296,6 +340,7 @@
|
||||
"properties": [
|
||||
"functionRelocDiffs",
|
||||
"demangler",
|
||||
"showSymbolSizes",
|
||||
"spaceBetweenArgs",
|
||||
"combineDataSections",
|
||||
"combineTextSections"
|
||||
@@ -306,6 +351,7 @@
|
||||
"name": "ARM",
|
||||
"properties": [
|
||||
"arm.archVersion",
|
||||
"arm.vfpV2",
|
||||
"arm.unifiedSyntax",
|
||||
"arm.avRegisters",
|
||||
"arm.r9Usage",
|
||||
|
||||
+258
-346
File diff suppressed because it is too large
Load Diff
@@ -293,6 +293,7 @@ impl Arch for ArchMips {
|
||||
}
|
||||
elf::R_MIPS_PC16 => 0, // PC-relative relocation
|
||||
R_MIPS15_S3 => ((code & 0x001FFFC0) >> 3) as i64,
|
||||
elf::R_MIPS_GPREL32 => (code as i32 as i64) + self.ri_gp_value as i64,
|
||||
flags => bail!("Unsupported MIPS implicit relocation {flags:?}"),
|
||||
};
|
||||
Ok(Some(RelocationOverride { target: RelocationOverrideTarget::Keep, addend }))
|
||||
@@ -318,6 +319,7 @@ impl Arch for ArchMips {
|
||||
elf::R_MIPS_GOT16 => Some("R_MIPS_GOT16"),
|
||||
elf::R_MIPS_PC16 => Some("R_MIPS_PC16"),
|
||||
elf::R_MIPS_CALL16 => Some("R_MIPS_CALL16"),
|
||||
elf::R_MIPS_GPREL32 => Some("R_MIPS_GPREL32"),
|
||||
R_MIPS15_S3 => Some("R_MIPS15_S3"),
|
||||
_ => None,
|
||||
},
|
||||
|
||||
@@ -7,12 +7,10 @@ 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::{
|
||||
@@ -44,6 +42,16 @@ pub mod x86;
|
||||
pub const OPCODE_INVALID: u16 = u16::MAX;
|
||||
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
|
||||
#[derive(PartialEq)]
|
||||
pub enum DataType {
|
||||
@@ -75,9 +83,9 @@ 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) {
|
||||
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}"))
|
||||
strs.push(format!("{label}: {literal:?}"))
|
||||
}
|
||||
strs
|
||||
}
|
||||
@@ -86,7 +94,7 @@ impl DataType {
|
||||
&self,
|
||||
endian: object::Endianness,
|
||||
bytes: &[u8],
|
||||
) -> Vec<(String, Option<String>)> {
|
||||
) -> Vec<(String, Option<String>, Option<String>)> {
|
||||
let mut strs = Vec::new();
|
||||
if self.required_len().is_some_and(|l| bytes.len() < l) {
|
||||
log::warn!(
|
||||
@@ -110,34 +118,34 @@ impl DataType {
|
||||
match self {
|
||||
DataType::Int8 => {
|
||||
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 {
|
||||
strs.push((format!("{:#x}", ReallySigned(i)), None));
|
||||
strs.push((format!("{:#x}", ReallySigned(i)), None, None));
|
||||
}
|
||||
}
|
||||
DataType::Int16 => {
|
||||
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 {
|
||||
strs.push((format!("{:#x}", ReallySigned(i)), None));
|
||||
strs.push((format!("{:#x}", ReallySigned(i)), None, None));
|
||||
}
|
||||
}
|
||||
DataType::Int32 => {
|
||||
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 {
|
||||
strs.push((format!("{:#x}", ReallySigned(i)), None));
|
||||
strs.push((format!("{:#x}", ReallySigned(i)), None, None));
|
||||
}
|
||||
}
|
||||
DataType::Int64 => {
|
||||
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 {
|
||||
strs.push((format!("{:#x}", ReallySigned(i)), None));
|
||||
strs.push((format!("{:#x}", ReallySigned(i)), None, None));
|
||||
}
|
||||
}
|
||||
DataType::Float => {
|
||||
@@ -148,6 +156,7 @@ impl DataType {
|
||||
object::Endianness::Big => f32::from_be_bytes(bytes),
|
||||
}),
|
||||
None,
|
||||
None,
|
||||
));
|
||||
}
|
||||
DataType::Double => {
|
||||
@@ -158,22 +167,29 @@ impl DataType {
|
||||
object::Endianness::Big => f64::from_be_bytes(bytes),
|
||||
}),
|
||||
None,
|
||||
None,
|
||||
));
|
||||
}
|
||||
DataType::Bytes => {
|
||||
strs.push((format!("{bytes:#?}"), None));
|
||||
strs.push((format!("{bytes:#?}"), None, None));
|
||||
}
|
||||
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.clone());
|
||||
strs.push((string, Some("ASCII".into()), Some(copy_string)));
|
||||
}
|
||||
for (encoding, encoding_name) in SUPPORTED_ENCODINGS {
|
||||
let (cow, _, had_errors) = encoding.decode(str_bytes);
|
||||
// Avoid showing ASCII-only strings more than once if the encoding is ASCII-compatible.
|
||||
if !had_errors && (!encoding.is_ascii_compatible() || !cow.is_ascii()) {
|
||||
let string = format!("{cow}");
|
||||
let copy_string = escape_special_ascii_characters(string.clone());
|
||||
strs.push((string, Some(encoding_name.into()), Some(copy_string)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -489,3 +505,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: String) -> 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
|
||||
}
|
||||
|
||||
@@ -440,9 +440,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
|
||||
}
|
||||
|
||||
@@ -36,13 +36,27 @@ pub fn diff_bss_symbol(
|
||||
}
|
||||
|
||||
pub fn symbol_name_matches(left_name: &str, right_name: &str) -> bool {
|
||||
// Match Metrowerks symbol$1234 against symbol$2345
|
||||
if let Some((prefix, suffix)) = left_name.split_once('$') {
|
||||
if !suffix.chars().all(char::is_numeric) {
|
||||
return false;
|
||||
if let Some((left_prefix, left_suffix)) = left_name.split_once("@class$")
|
||||
&& let Some((right_prefix, right_suffix)) = right_name.split_once("@class$")
|
||||
{
|
||||
// Match Metrowerks anonymous class symbol names, ignoring the unique ID.
|
||||
// e.g. __dt__Q29dCamera_c23@class$3665d_camera_cppFv
|
||||
if left_prefix == right_prefix
|
||||
&& let Some(left_idx) = left_suffix.chars().position(|c| !c.is_numeric())
|
||||
&& let Some(right_idx) = right_suffix.chars().position(|c| !c.is_numeric())
|
||||
{
|
||||
// e.g. d_camera_cppFv (after the unique ID)
|
||||
left_suffix[left_idx..] == right_suffix[right_idx..]
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else if let Some((prefix, suffix)) = left_name.split_once(['$', '.'])
|
||||
&& suffix.chars().all(char::is_numeric)
|
||||
{
|
||||
// Match Metrowerks symbol$1234 against symbol$2345
|
||||
// and GCC symbol.1234 against symbol.2345
|
||||
right_name
|
||||
.split_once('$')
|
||||
.split_once(['$', '.'])
|
||||
.is_some_and(|(p, s)| p == prefix && s.chars().all(char::is_numeric))
|
||||
} else {
|
||||
left_name == right_name
|
||||
@@ -587,6 +601,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)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -34,14 +34,12 @@ impl Demangler {
|
||||
|
||||
fn demangle_itanium(name: &str) -> Option<String> {
|
||||
let name = name.trim_start_matches('.');
|
||||
cpp_demangle::Symbol::new(name)
|
||||
.ok()
|
||||
.and_then(|s| s.demangle(&cpp_demangle::DemangleOptions::default()).ok())
|
||||
cpp_demangle::Symbol::new(name).ok().and_then(|s| s.demangle().ok())
|
||||
}
|
||||
|
||||
fn demangle_gnu_legacy(name: &str) -> Option<String> {
|
||||
let name = name.trim_start_matches('.');
|
||||
gnuv2_demangle::demangle(name, &gnuv2_demangle::DemangleConfig::new_no_cfilt_mimics()).ok()
|
||||
gnuv2_demangle::demangle(name, &gnuv2_demangle::DemangleConfig::new()).ok()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,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 +187,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))?;
|
||||
}
|
||||
@@ -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));
|
||||
@@ -489,8 +502,8 @@ pub fn relocation_context(
|
||||
let literals = display_ins_data_literals(obj, ins);
|
||||
if !literals.is_empty() {
|
||||
out.push(ContextItem::Separator);
|
||||
for (literal, label_override) in literals {
|
||||
out.push(ContextItem::Copy { value: literal, label: label_override });
|
||||
for (literal, label_override, copy_string) in literals {
|
||||
out.push(ContextItem::Copy { value: literal, label: label_override, copy_string });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -586,24 +599,37 @@ pub fn instruction_context(
|
||||
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,
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@@ -665,10 +691,10 @@ pub fn instruction_hover(
|
||||
let literals = display_ins_data_literals(obj, resolved);
|
||||
if !literals.is_empty() {
|
||||
out.push(HoverItem::Separator);
|
||||
for (literal, label_override) in literals {
|
||||
for (literal, label_override, _) in literals {
|
||||
out.push(HoverItem::Text {
|
||||
label: label_override.unwrap_or_else(|| ty.to_string()),
|
||||
value: literal,
|
||||
value: format!("{literal:?}"),
|
||||
color: HoverItemColor::Normal,
|
||||
});
|
||||
}
|
||||
@@ -859,7 +885,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<(String, Option<String>, Option<String>)> {
|
||||
let Some(reloc) = resolved.relocation else {
|
||||
return Vec::new();
|
||||
};
|
||||
|
||||
@@ -7,6 +7,7 @@ use alloc::{
|
||||
use core::{num::NonZeroU32, ops::Range};
|
||||
|
||||
use anyhow::Result;
|
||||
use itertools::Itertools;
|
||||
|
||||
use crate::{
|
||||
diff::{
|
||||
@@ -759,18 +760,16 @@ 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);
|
||||
}
|
||||
|
||||
if let Some((symbol_idx, _)) = unmatched_symbols(obj, used).find(|&(_, symbol)| {
|
||||
symbol_name_matches(&in_symbol.name, &symbol.name)
|
||||
&& symbol_section_kind(obj, symbol) == section_kind
|
||||
&& symbol_section(obj, symbol).is_some_and(|(name, _)| name == section_name)
|
||||
}) {
|
||||
// Try to find a symbol with a matching name
|
||||
if let Some((symbol_idx, _)) = unmatched_symbols(obj, used)
|
||||
.filter(|&(_, symbol)| {
|
||||
symbol_name_matches(&in_symbol.name, &symbol.name)
|
||||
&& symbol_section_kind(obj, symbol) == section_kind
|
||||
&& symbol_section(obj, symbol).is_some_and(|(name, _)| name == section_name)
|
||||
})
|
||||
.sorted_unstable_by_key(|&(_, symbol)| (symbol.section, symbol.address))
|
||||
.next()
|
||||
{
|
||||
return Some(symbol_idx);
|
||||
}
|
||||
|
||||
|
||||
@@ -824,19 +824,16 @@ fn combine_sections(
|
||||
config: &DiffObjConfig,
|
||||
) -> Result<()> {
|
||||
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() {
|
||||
let base_name =
|
||||
if let Some(i) = section.name.rfind('$') { §ion.name[..i] } else { §ion.name };
|
||||
match section.kind {
|
||||
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);
|
||||
}
|
||||
SectionKind::Code => {
|
||||
text_sections.push(i);
|
||||
text_sections.entry(base_name.to_string()).or_default().push(i);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@@ -847,7 +844,9 @@ fn combine_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(())
|
||||
}
|
||||
|
||||
@@ -2,6 +2,6 @@
|
||||
source: objdiff-core/tests/arch_arm.rs
|
||||
expression: output
|
||||
---
|
||||
[(Line(90), Dim, 5), (Address(0), Dim, 5), (Spacing(4), Normal, 0), (Opcode("ldr", 32799), 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", 32779), Normal, 10), (Argument(Opaque("r12")), 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(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)]
|
||||
|
||||
@@ -8,7 +8,7 @@ expression: diff.instruction_rows
|
||||
InstructionRef {
|
||||
address: 76,
|
||||
size: 4,
|
||||
opcode: 32799,
|
||||
opcode: 32792,
|
||||
branch_dest: None,
|
||||
},
|
||||
),
|
||||
@@ -22,7 +22,7 @@ expression: diff.instruction_rows
|
||||
InstructionRef {
|
||||
address: 80,
|
||||
size: 4,
|
||||
opcode: 32779,
|
||||
opcode: 32777,
|
||||
branch_dest: None,
|
||||
},
|
||||
),
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user