mirror of
https://github.com/encounter/objdiff.git
synced 2025-12-17 17:05:29 +00:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c02eb31dbb | |||
|
|
86d92866aa | ||
| 51c3af2bbe | |||
|
|
d0b8b449d9 | ||
|
|
32f5f202f7 | ||
|
|
481dbc185a | ||
| 26a4cc79cf | |||
| 5c96c2ee51 | |||
| d162fe847e | |||
| 2a24eb5aec | |||
|
|
f7c291bd55 | ||
| 03a578c1bb | |||
| d09ef8e207 | |||
| 63552a58ae | |||
| 827f4a42bd | |||
|
|
b2dcecc5d8 | ||
|
|
67b237eab6 | ||
|
|
66da80ff69 |
1476
Cargo.lock
generated
1476
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -14,7 +14,7 @@ default-members = [
|
|||||||
resolver = "3"
|
resolver = "3"
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "3.4.0"
|
version = "3.4.5"
|
||||||
authors = ["Luke Street <luke@street.dev>"]
|
authors = ["Luke Street <luke@street.dev>"]
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ ignore = [
|
|||||||
#{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" },
|
#{ 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-2024-0436", reason = "Unmaintained paste crate is an indirect dependency" },
|
||||||
{ id = "RUSTSEC-2025-0052", reason = "Unmaintained async-std crate is an indirect dependency" },
|
{ id = "RUSTSEC-2025-0052", reason = "Unmaintained async-std crate is an indirect dependency" },
|
||||||
|
{ id = "RUSTSEC-2025-0119", reason = "Unmaintained number_prefix crate is an indirect dependency" },
|
||||||
]
|
]
|
||||||
# If this is true, then cargo deny will use the git executable to fetch advisory database.
|
# If this is true, then cargo deny will use the git executable to fetch advisory database.
|
||||||
# If this is false, then it uses a built-in git library.
|
# If this is false, then it uses a built-in git library.
|
||||||
|
|||||||
@@ -16,19 +16,19 @@ publish = false
|
|||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
argp = "0.4"
|
argp = "0.4"
|
||||||
crossterm = "0.29"
|
crossterm = "0.29"
|
||||||
enable-ansi-support = "0.2"
|
enable-ansi-support = "0.3"
|
||||||
memmap2 = "0.9"
|
memmap2 = "0.9"
|
||||||
objdiff-core = { path = "../objdiff-core", features = ["all"] }
|
objdiff-core = { path = "../objdiff-core", features = ["all"] }
|
||||||
prost = "0.14"
|
prost = "0.14"
|
||||||
ratatui = "0.29"
|
ratatui = "0.29"
|
||||||
rayon = "1.10"
|
rayon = "1.11"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
supports-color = "3.0"
|
supports-color = "3.0"
|
||||||
time = { version = "0.3", features = ["formatting", "local-offset"] }
|
time = { version = "0.3", features = ["formatting", "local-offset"] }
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
typed-path = "0.11"
|
typed-path = "0.12"
|
||||||
|
|
||||||
[target.'cfg(target_env = "musl")'.dependencies]
|
[target.'cfg(target_env = "musl")'.dependencies]
|
||||||
mimalloc = "0.1"
|
mimalloc = "0.1"
|
||||||
|
|||||||
@@ -66,15 +66,6 @@ pub struct Args {
|
|||||||
#[argp(option, short = 'c')]
|
#[argp(option, short = 'c')]
|
||||||
/// Configuration property (key=value)
|
/// Configuration property (key=value)
|
||||||
config: Vec<String>,
|
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<()> {
|
pub fn run(args: Args) -> Result<()> {
|
||||||
@@ -183,17 +174,7 @@ fn build_config_from_args(
|
|||||||
apply_project_options(&mut diff_config, options)?;
|
apply_project_options(&mut diff_config, options)?;
|
||||||
}
|
}
|
||||||
apply_config_args(&mut diff_config, &args.config)?;
|
apply_config_args(&mut diff_config, &args.config)?;
|
||||||
let mut mapping_config = MappingConfig {
|
Ok((diff_config, MappingConfig::default()))
|
||||||
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))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct AppState {
|
pub struct AppState {
|
||||||
|
|||||||
@@ -378,7 +378,6 @@ impl UiView for FunctionDiffUi {
|
|||||||
}
|
}
|
||||||
// Reload
|
// Reload
|
||||||
KeyCode::Char('r') => {
|
KeyCode::Char('r') => {
|
||||||
result.redraw = true;
|
|
||||||
return EventControlFlow::Reload;
|
return EventControlFlow::Reload;
|
||||||
}
|
}
|
||||||
// Scroll right
|
// Scroll right
|
||||||
@@ -400,7 +399,6 @@ impl UiView for FunctionDiffUi {
|
|||||||
FunctionRelocDiffs::DataValue => FunctionRelocDiffs::All,
|
FunctionRelocDiffs::DataValue => FunctionRelocDiffs::All,
|
||||||
FunctionRelocDiffs::All => FunctionRelocDiffs::None,
|
FunctionRelocDiffs::All => FunctionRelocDiffs::None,
|
||||||
};
|
};
|
||||||
result.redraw = true;
|
|
||||||
return EventControlFlow::Reload;
|
return EventControlFlow::Reload;
|
||||||
}
|
}
|
||||||
// Toggle three-way diff
|
// Toggle three-way diff
|
||||||
|
|||||||
@@ -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"] }
|
object = { version = "0.37", default-features = false, features = ["read_core", "elf", "coff"] }
|
||||||
pbjson = { version = "0.8", default-features = false, optional = true }
|
pbjson = { version = "0.8", default-features = false, optional = true }
|
||||||
prost = { version = "0.14", default-features = false, features = ["derive"], 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 }
|
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 }
|
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
|
# config
|
||||||
globset = { version = "0.4", default-features = false, optional = true }
|
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 }
|
rlwinmdec = { version = "1.1", optional = true }
|
||||||
|
|
||||||
# mips
|
# 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
|
# x86
|
||||||
iced-x86 = { version = "1.21", default-features = false, features = ["decoder", "intel", "gas", "masm", "nasm", "exhaustive_enums", "no_std"], optional = true }
|
iced-x86 = { version = "1.21", default-features = false, features = ["decoder", "intel", "gas", "masm", "nasm", "exhaustive_enums", "no_std"], optional = true }
|
||||||
|
|
||||||
# arm
|
# arm
|
||||||
unarm = { version = "2.0", optional = true }
|
unarm = { version = "2.1", optional = true }
|
||||||
arm-attr = { version = "0.2", optional = true }
|
arm-attr = { version = "0.2", optional = true }
|
||||||
|
|
||||||
# arm64
|
# arm64
|
||||||
@@ -169,15 +169,15 @@ yaxpeax-arch = { version = "0.3", default-features = false, optional = true }
|
|||||||
yaxpeax-arm = { version = "0.3", default-features = false, optional = true }
|
yaxpeax-arm = { version = "0.3", default-features = false, optional = true }
|
||||||
|
|
||||||
# build
|
# build
|
||||||
notify = { version = "8.1.0", optional = true }
|
notify = { version = "8.2.0", optional = true }
|
||||||
notify-debouncer-full = { version = "0.5.0", optional = true }
|
notify-debouncer-full = { version = "0.6.0", optional = true }
|
||||||
shell-escape = { version = "0.1", 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 }
|
time = { version = "0.3", optional = true }
|
||||||
encoding_rs = { version = "0.8.35", optional = true }
|
encoding_rs = { version = "0.8.35", optional = true }
|
||||||
|
|
||||||
# demangler
|
# 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 }
|
cwdemangle = { version = "1.0", optional = true }
|
||||||
gnuv2_demangle = { version = "0.4", optional = true }
|
gnuv2_demangle = { version = "0.4", optional = true }
|
||||||
msvc-demangler = { version = "0.11", optional = true }
|
msvc-demangler = { version = "0.11", optional = true }
|
||||||
|
|||||||
@@ -225,8 +225,13 @@ impl Arch for ArchArm {
|
|||||||
|
|
||||||
// Check how many bytes we can/should read
|
// Check how many bytes we can/should read
|
||||||
let num_code_bytes = if data.len() >= 4 {
|
let num_code_bytes = if data.len() >= 4 {
|
||||||
// Read 4 bytes even for Thumb, as the parser will determine if it's a 2 or 4 byte instruction
|
if mode == unarm::ParseMode::Data && address & 3 != 0 {
|
||||||
4
|
// 32-bit .word value should be aligned on a 4-byte boundary, otherwise use .hword
|
||||||
|
2
|
||||||
|
} else {
|
||||||
|
// Read 4 bytes even for Thumb, as the parser will determine if it's a 2 or 4 byte instruction
|
||||||
|
4
|
||||||
|
}
|
||||||
} else if mode != unarm::ParseMode::Arm {
|
} else if mode != unarm::ParseMode::Arm {
|
||||||
2
|
2
|
||||||
} else {
|
} else {
|
||||||
@@ -340,7 +345,10 @@ impl Arch for ArchArm {
|
|||||||
let address = address as usize;
|
let address = address as usize;
|
||||||
let addend = match r_type {
|
let addend = match r_type {
|
||||||
// ARM calls
|
// ARM calls
|
||||||
elf::R_ARM_PC24 | elf::R_ARM_XPC25 | elf::R_ARM_CALL => {
|
elf::R_ARM_PC24
|
||||||
|
| elf::R_ARM_XPC25
|
||||||
|
| elf::R_ARM_CALL
|
||||||
|
| elf::R_ARM_JUMP24 => {
|
||||||
let data = section_data[address..address + 4].try_into()?;
|
let data = section_data[address..address + 4].try_into()?;
|
||||||
let addend = self.endianness.read_i32_bytes(data);
|
let addend = self.endianness.read_i32_bytes(data);
|
||||||
let imm24 = addend & 0xffffff;
|
let imm24 = addend & 0xffffff;
|
||||||
|
|||||||
@@ -7,12 +7,10 @@ use alloc::{
|
|||||||
};
|
};
|
||||||
use core::{
|
use core::{
|
||||||
any::Any,
|
any::Any,
|
||||||
ffi::CStr,
|
|
||||||
fmt::{self, Debug},
|
fmt::{self, Debug},
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::{Result, bail};
|
use anyhow::{Result, bail};
|
||||||
use encoding_rs::SHIFT_JIS;
|
|
||||||
use object::Endian as _;
|
use object::Endian as _;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -44,6 +42,16 @@ pub mod x86;
|
|||||||
pub const OPCODE_INVALID: u16 = u16::MAX;
|
pub const OPCODE_INVALID: u16 = u16::MAX;
|
||||||
pub const OPCODE_DATA: u16 = u16::MAX - 1;
|
pub const OPCODE_DATA: u16 = u16::MAX - 1;
|
||||||
|
|
||||||
|
const SUPPORTED_ENCODINGS: [(&encoding_rs::Encoding, &str); 7] = [
|
||||||
|
(encoding_rs::UTF_8, "UTF-8"),
|
||||||
|
(encoding_rs::SHIFT_JIS, "Shift JIS"),
|
||||||
|
(encoding_rs::UTF_16BE, "UTF-16BE"),
|
||||||
|
(encoding_rs::UTF_16LE, "UTF-16LE"),
|
||||||
|
(encoding_rs::WINDOWS_1252, "Windows-1252"),
|
||||||
|
(encoding_rs::EUC_JP, "EUC-JP"),
|
||||||
|
(encoding_rs::BIG5, "Big5"),
|
||||||
|
];
|
||||||
|
|
||||||
/// Represents the type of data associated with an instruction
|
/// Represents the type of data associated with an instruction
|
||||||
#[derive(PartialEq)]
|
#[derive(PartialEq)]
|
||||||
pub enum DataType {
|
pub enum DataType {
|
||||||
@@ -77,7 +85,7 @@ impl DataType {
|
|||||||
let mut strs = Vec::new();
|
let mut strs = Vec::new();
|
||||||
for (literal, label_override) in self.display_literals(endian, bytes) {
|
for (literal, label_override) in self.display_literals(endian, bytes) {
|
||||||
let label = label_override.unwrap_or_else(|| self.to_string());
|
let label = label_override.unwrap_or_else(|| self.to_string());
|
||||||
strs.push(format!("{label}: {literal}"))
|
strs.push(format!("{label}: {literal:?}"))
|
||||||
}
|
}
|
||||||
strs
|
strs
|
||||||
}
|
}
|
||||||
@@ -164,16 +172,18 @@ impl DataType {
|
|||||||
strs.push((format!("{bytes:#?}"), None));
|
strs.push((format!("{bytes:#?}"), None));
|
||||||
}
|
}
|
||||||
DataType::String => {
|
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') {
|
if let Some(nul_idx) = bytes.iter().position(|&c| c == b'\0') {
|
||||||
let (cow, _, had_errors) = SHIFT_JIS.decode(&bytes[..nul_idx]);
|
let str_bytes = &bytes[..nul_idx];
|
||||||
if !had_errors {
|
// Special case to display (ASCII) as the label for ASCII-only strings.
|
||||||
let str = format!("{cow:?}");
|
let (cow, _, had_errors) = encoding_rs::UTF_8.decode(str_bytes);
|
||||||
// Only add the Shift JIS string if it's different from the ASCII string.
|
if !had_errors && cow.is_ascii() {
|
||||||
if !strs.iter().any(|x| x.0 == str) {
|
strs.push((format!("{cow}"), Some("ASCII".into())));
|
||||||
strs.push((str, Some("Shift JIS".into())));
|
}
|
||||||
|
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()) {
|
||||||
|
strs.push((format!("{cow}"), Some(encoding_name.into())));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,12 +37,13 @@ pub fn diff_bss_symbol(
|
|||||||
|
|
||||||
pub fn symbol_name_matches(left_name: &str, right_name: &str) -> bool {
|
pub fn symbol_name_matches(left_name: &str, right_name: &str) -> bool {
|
||||||
// Match Metrowerks symbol$1234 against symbol$2345
|
// Match Metrowerks symbol$1234 against symbol$2345
|
||||||
if let Some((prefix, suffix)) = left_name.split_once('$') {
|
// and GCC symbol.1234 against symbol.2345
|
||||||
|
if let Some((prefix, suffix)) = left_name.split_once(['$', '.']) {
|
||||||
if !suffix.chars().all(char::is_numeric) {
|
if !suffix.chars().all(char::is_numeric) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
right_name
|
right_name
|
||||||
.split_once('$')
|
.split_once(['$', '.'])
|
||||||
.is_some_and(|(p, s)| p == prefix && s.chars().all(char::is_numeric))
|
.is_some_and(|(p, s)| p == prefix && s.chars().all(char::is_numeric))
|
||||||
} else {
|
} else {
|
||||||
left_name == right_name
|
left_name == right_name
|
||||||
@@ -587,6 +588,7 @@ fn symbols_matching_section(
|
|||||||
s.section == Some(section_idx)
|
s.section == Some(section_idx)
|
||||||
&& s.kind != SymbolKind::Section
|
&& s.kind != SymbolKind::Section
|
||||||
&& s.size > 0
|
&& s.size > 0
|
||||||
|
&& !s.flags.contains(SymbolFlag::Hidden)
|
||||||
&& !s.flags.contains(SymbolFlag::Ignored)
|
&& !s.flags.contains(SymbolFlag::Ignored)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,9 +34,7 @@ impl Demangler {
|
|||||||
|
|
||||||
fn demangle_itanium(name: &str) -> Option<String> {
|
fn demangle_itanium(name: &str) -> Option<String> {
|
||||||
let name = name.trim_start_matches('.');
|
let name = name.trim_start_matches('.');
|
||||||
cpp_demangle::Symbol::new(name)
|
cpp_demangle::Symbol::new(name).ok().and_then(|s| s.demangle().ok())
|
||||||
.ok()
|
|
||||||
.and_then(|s| s.demangle(&cpp_demangle::DemangleOptions::default()).ok())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn demangle_gnu_legacy(name: &str) -> Option<String> {
|
fn demangle_gnu_legacy(name: &str) -> Option<String> {
|
||||||
|
|||||||
@@ -668,7 +668,7 @@ pub fn instruction_hover(
|
|||||||
for (literal, label_override) in literals {
|
for (literal, label_override) in literals {
|
||||||
out.push(HoverItem::Text {
|
out.push(HoverItem::Text {
|
||||||
label: label_override.unwrap_or_else(|| ty.to_string()),
|
label: label_override.unwrap_or_else(|| ty.to_string()),
|
||||||
value: literal,
|
value: format!("{literal:?}"),
|
||||||
color: HoverItemColor::Normal,
|
color: HoverItemColor::Normal,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,9 +30,9 @@ cfg-if = "1.0"
|
|||||||
const_format = "0.2"
|
const_format = "0.2"
|
||||||
cwdemangle = "1.0"
|
cwdemangle = "1.0"
|
||||||
dirs = "6.0"
|
dirs = "6.0"
|
||||||
egui = "0.32"
|
egui = "0.33"
|
||||||
egui_extras = "0.32"
|
egui_extras = "0.33"
|
||||||
egui-notify = "0.20"
|
egui-notify = "0.21"
|
||||||
filetime = "0.2"
|
filetime = "0.2"
|
||||||
float-ord = "0.3"
|
float-ord = "0.3"
|
||||||
font-kit = "0.14"
|
font-kit = "0.14"
|
||||||
@@ -40,21 +40,21 @@ globset = { version = "0.4", features = ["serde1"] }
|
|||||||
log = "0.4"
|
log = "0.4"
|
||||||
objdiff-core = { path = "../objdiff-core", features = ["all"] }
|
objdiff-core = { path = "../objdiff-core", features = ["all"] }
|
||||||
open = "5.3"
|
open = "5.3"
|
||||||
png = "0.17"
|
png = "0.18"
|
||||||
pollster = "0.4"
|
pollster = "0.4"
|
||||||
regex = "1.11"
|
regex = "1.12"
|
||||||
rfd = { version = "0.15" } #, default-features = false, features = ['xdg-portal']
|
rfd = { version = "0.15" } #, default-features = false, features = ['xdg-portal']
|
||||||
rlwinmdec = "1.1"
|
rlwinmdec = "1.1"
|
||||||
ron = "0.10"
|
ron = "0.12"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
time = { version = "0.3", features = ["formatting", "local-offset"] }
|
time = { version = "0.3", features = ["formatting", "local-offset"] }
|
||||||
typed-path = "0.11"
|
typed-path = "0.12"
|
||||||
winit = { version = "0.30", features = ["default"] }
|
winit = { version = "0.30", features = ["default"] }
|
||||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
|
|
||||||
# Keep version in sync with egui
|
# Keep version in sync with egui
|
||||||
[dependencies.eframe]
|
[dependencies.eframe]
|
||||||
version = "0.32"
|
version = "0.33"
|
||||||
features = [
|
features = [
|
||||||
"default_fonts",
|
"default_fonts",
|
||||||
"glow",
|
"glow",
|
||||||
@@ -66,7 +66,7 @@ default-features = false
|
|||||||
|
|
||||||
# Keep version in sync with eframe
|
# Keep version in sync with eframe
|
||||||
[dependencies.wgpu]
|
[dependencies.wgpu]
|
||||||
version = "25.0"
|
version = "27.0"
|
||||||
features = [
|
features = [
|
||||||
"dx12",
|
"dx12",
|
||||||
"metal",
|
"metal",
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ mod views;
|
|||||||
use std::{
|
use std::{
|
||||||
ffi::OsStr,
|
ffi::OsStr,
|
||||||
fmt::Display,
|
fmt::Display,
|
||||||
|
io::Cursor,
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
process::ExitCode,
|
process::ExitCode,
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
@@ -21,7 +22,7 @@ use std::{
|
|||||||
sync::{Arc, Mutex},
|
sync::{Arc, Mutex},
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::{Result, ensure};
|
use anyhow::{Context, Result, ensure};
|
||||||
use argp::{FromArgValue, FromArgs};
|
use argp::{FromArgValue, FromArgs};
|
||||||
use cfg_if::cfg_if;
|
use cfg_if::cfg_if;
|
||||||
use objdiff_core::config::path::check_path_buf;
|
use objdiff_core::config::path::check_path_buf;
|
||||||
@@ -90,9 +91,9 @@ struct TopLevel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn load_icon() -> Result<egui::IconData> {
|
fn load_icon() -> Result<egui::IconData> {
|
||||||
let decoder = png::Decoder::new(include_bytes!("../assets/icon_64.png").as_ref());
|
let decoder = png::Decoder::new(Cursor::new(include_bytes!("../assets/icon_64.png").as_ref()));
|
||||||
let mut reader = decoder.read_info()?;
|
let mut reader = decoder.read_info()?;
|
||||||
let mut buf = vec![0; reader.output_buffer_size()];
|
let mut buf = vec![0; reader.output_buffer_size().context("Buffer too large")?];
|
||||||
let info = reader.next_frame(&mut buf)?;
|
let info = reader.next_frame(&mut buf)?;
|
||||||
ensure!(info.bit_depth == png::BitDepth::Eight);
|
ensure!(info.bit_depth == png::BitDepth::Eight);
|
||||||
ensure!(info.color_type == png::ColorType::Rgba);
|
ensure!(info.color_type == png::ColorType::Rgba);
|
||||||
|
|||||||
@@ -1,12 +1,18 @@
|
|||||||
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
use egui::{Id, Layout, RichText, ScrollArea, TextEdit, Ui, Widget, text::LayoutJob};
|
use egui::{Id, Layout, RichText, ScrollArea, TextEdit, Ui, Widget, text::LayoutJob};
|
||||||
use objdiff_core::{
|
use objdiff_core::{
|
||||||
build::BuildStatus,
|
build::BuildStatus,
|
||||||
diff::{
|
diff::{
|
||||||
DiffObjConfig, ObjectDiff, SymbolDiff,
|
DiffObjConfig, ObjectDiff, SymbolDiff,
|
||||||
data::BYTES_PER_ROW,
|
data::BYTES_PER_ROW,
|
||||||
display::{ContextItem, HoverItem, HoverItemColor, SymbolFilter, SymbolNavigationKind},
|
display::{
|
||||||
|
ContextItem, DiffText, HoverItem, HoverItemColor, SymbolFilter, SymbolNavigationKind,
|
||||||
|
display_row,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
obj::{Object, Symbol},
|
obj::{InstructionArgValue, Object, Symbol},
|
||||||
|
util::ReallySigned,
|
||||||
};
|
};
|
||||||
use time::format_description;
|
use time::format_description;
|
||||||
|
|
||||||
@@ -64,6 +70,51 @@ impl<'a> DiffColumnContext<'a> {
|
|||||||
pub fn id(&self) -> Option<&str> { self.symbol.map(|(symbol, _, _)| symbol.name.as_str()) }
|
pub fn id(&self) -> Option<&str> { self.symbol.map(|(symbol, _, _)| symbol.name.as_str()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Obtains the assembly text for a given symbol diff, suitable for copying to clipboard.
|
||||||
|
fn get_asm_text(
|
||||||
|
obj: &Object,
|
||||||
|
symbol_diff: &SymbolDiff,
|
||||||
|
symbol_idx: usize,
|
||||||
|
diff_config: &DiffObjConfig,
|
||||||
|
) -> String {
|
||||||
|
let mut asm_text = String::new();
|
||||||
|
|
||||||
|
for ins_row in &symbol_diff.instruction_rows {
|
||||||
|
let mut line = String::new();
|
||||||
|
let result = display_row(obj, symbol_idx, ins_row, diff_config, |segment| {
|
||||||
|
let text = match segment.text {
|
||||||
|
DiffText::Basic(text) => text.to_string(),
|
||||||
|
DiffText::Line(num) => format!("{num} "),
|
||||||
|
DiffText::Address(addr) => format!("{addr:x}:"),
|
||||||
|
DiffText::Opcode(mnemonic, _op) => format!("{mnemonic} "),
|
||||||
|
DiffText::Argument(arg) => match arg {
|
||||||
|
InstructionArgValue::Signed(v) => format!("{:#x}", ReallySigned(v)),
|
||||||
|
InstructionArgValue::Unsigned(v) => format!("{v:#x}"),
|
||||||
|
InstructionArgValue::Opaque(v) => v.into_owned(),
|
||||||
|
},
|
||||||
|
DiffText::BranchDest(addr) => format!("{addr:x}"),
|
||||||
|
DiffText::Symbol(sym) => sym.demangled_name.as_ref().unwrap_or(&sym.name).clone(),
|
||||||
|
DiffText::Addend(addend) => match addend.cmp(&0i64) {
|
||||||
|
Ordering::Greater => format!("+{addend:#x}"),
|
||||||
|
Ordering::Less => format!("-{:#x}", -addend),
|
||||||
|
_ => String::new(),
|
||||||
|
},
|
||||||
|
DiffText::Spacing(n) => " ".repeat(n.into()),
|
||||||
|
DiffText::Eol => "\n".to_string(),
|
||||||
|
};
|
||||||
|
line.push_str(&text);
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
|
||||||
|
if result.is_ok() {
|
||||||
|
asm_text.push_str(line.trim_end());
|
||||||
|
asm_text.push('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
asm_text
|
||||||
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn diff_view_ui(
|
pub fn diff_view_ui(
|
||||||
ui: &mut Ui,
|
ui: &mut Ui,
|
||||||
@@ -162,6 +213,17 @@ pub fn diff_view_ui(
|
|||||||
ret = Some(DiffViewAction::CreateScratch(symbol.name.clone()));
|
ret = Some(DiffViewAction::CreateScratch(symbol.name.clone()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if state.current_view == View::FunctionDiff
|
||||||
|
&& let Some((_, symbol_diff, symbol_idx)) = left_ctx.symbol
|
||||||
|
&& let Some((obj, _)) = left_ctx.obj
|
||||||
|
&& ui
|
||||||
|
.button("📋 Copy")
|
||||||
|
.on_hover_text_at_pointer("Copy all rows to clipboard")
|
||||||
|
.clicked()
|
||||||
|
{
|
||||||
|
ui.ctx().copy_text(get_asm_text(obj, symbol_diff, symbol_idx, diff_config));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,16 +270,20 @@ pub fn diff_view_ui(
|
|||||||
|
|
||||||
// Third row
|
// Third row
|
||||||
if left_ctx.has_symbol() && right_ctx.has_symbol() {
|
if left_ctx.has_symbol() && right_ctx.has_symbol() {
|
||||||
if (state.current_view == View::FunctionDiff
|
ui.horizontal(|ui| {
|
||||||
&& ui
|
if (state.current_view == View::FunctionDiff
|
||||||
.button("Change target")
|
&& ui
|
||||||
.on_hover_text_at_pointer("Choose a different symbol to use as the target")
|
.button("Change target")
|
||||||
.clicked()
|
.on_hover_text_at_pointer(
|
||||||
|| hotkeys::consume_change_target_shortcut(ui.ctx()))
|
"Choose a different symbol to use as the target",
|
||||||
&& let Some(symbol_ref) = state.symbol_state.right_symbol.as_ref()
|
)
|
||||||
{
|
.clicked()
|
||||||
ret = Some(DiffViewAction::SelectingLeft(symbol_ref.clone()));
|
|| hotkeys::consume_change_target_shortcut(ui.ctx()))
|
||||||
}
|
&& let Some(symbol_ref) = state.symbol_state.right_symbol.as_ref()
|
||||||
|
{
|
||||||
|
ret = Some(DiffViewAction::SelectingLeft(symbol_ref.clone()));
|
||||||
|
}
|
||||||
|
});
|
||||||
} else if left_ctx.status.success && !left_ctx.has_symbol() {
|
} else if left_ctx.status.success && !left_ctx.has_symbol() {
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
let mut search = state.search.clone();
|
let mut search = state.search.clone();
|
||||||
@@ -291,6 +357,15 @@ pub fn diff_view_ui(
|
|||||||
{
|
{
|
||||||
ret = Some(DiffViewAction::OpenSourcePath);
|
ret = Some(DiffViewAction::OpenSourcePath);
|
||||||
}
|
}
|
||||||
|
if let Some((_, symbol_diff, symbol_idx)) = right_ctx.symbol
|
||||||
|
&& let Some((obj, _)) = right_ctx.obj
|
||||||
|
&& ui
|
||||||
|
.button("📋 Copy")
|
||||||
|
.on_hover_text_at_pointer("Copy all rows to clipboard")
|
||||||
|
.clicked()
|
||||||
|
{
|
||||||
|
ui.ctx().copy_text(get_asm_text(obj, symbol_diff, symbol_idx, diff_config));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Second row
|
// Second row
|
||||||
@@ -796,19 +871,13 @@ pub fn context_menu_items_ui(
|
|||||||
match item {
|
match item {
|
||||||
ContextItem::Copy { value, label } => {
|
ContextItem::Copy { value, label } => {
|
||||||
let mut job = LayoutJob::default();
|
let mut job = LayoutJob::default();
|
||||||
|
write_text("Copy ", appearance.text_color, &mut job, appearance.code_font.clone());
|
||||||
write_text(
|
write_text(
|
||||||
"Copy \"",
|
&format!("{value:?}"),
|
||||||
appearance.text_color,
|
|
||||||
&mut job,
|
|
||||||
appearance.code_font.clone(),
|
|
||||||
);
|
|
||||||
write_text(
|
|
||||||
&value,
|
|
||||||
appearance.highlight_color,
|
appearance.highlight_color,
|
||||||
&mut job,
|
&mut job,
|
||||||
appearance.code_font.clone(),
|
appearance.code_font.clone(),
|
||||||
);
|
);
|
||||||
write_text("\"", appearance.text_color, &mut job, appearance.code_font.clone());
|
|
||||||
if let Some(label) = label {
|
if let Some(label) = label {
|
||||||
write_text(" (", appearance.text_color, &mut job, appearance.code_font.clone());
|
write_text(" (", appearance.text_color, &mut job, appearance.code_font.clone());
|
||||||
write_text(
|
write_text(
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ impl FrameHistory {
|
|||||||
));
|
));
|
||||||
let cpu_usage = to_screen.inverse().transform_pos(pointer_pos).y;
|
let cpu_usage = to_screen.inverse().transform_pos(pointer_pos).y;
|
||||||
let text = format!("{:.1} ms", 1e3 * cpu_usage);
|
let text = format!("{:.1} ms", 1e3 * cpu_usage);
|
||||||
shapes.push(ui.fonts(|f| {
|
shapes.push(ui.fonts_mut(|f| {
|
||||||
Shape::text(
|
Shape::text(
|
||||||
f,
|
f,
|
||||||
pos2(rect.left(), y),
|
pos2(rect.left(), y),
|
||||||
|
|||||||
@@ -217,7 +217,7 @@ fn asm_row_ui(
|
|||||||
if ins_diff.kind != InstructionDiffKind::None {
|
if ins_diff.kind != InstructionDiffKind::None {
|
||||||
ui.painter().rect_filled(ui.available_rect_before_wrap(), 0.0, ui.visuals().faint_bg_color);
|
ui.painter().rect_filled(ui.available_rect_before_wrap(), 0.0, ui.visuals().faint_bg_color);
|
||||||
}
|
}
|
||||||
let space_width = ui.fonts(|f| f.glyph_width(&appearance.code_font, ' '));
|
let space_width = ui.fonts_mut(|f| f.glyph_width(&appearance.code_font, ' '));
|
||||||
display_row(obj, symbol_idx, ins_diff, diff_config, |segment| {
|
display_row(obj, symbol_idx, ins_diff, diff_config, |segment| {
|
||||||
if let Some(action) =
|
if let Some(action) =
|
||||||
diff_text_ui(ui, segment, appearance, ins_view_state, column, space_width, &response_cb)
|
diff_text_ui(ui, segment, appearance, ins_view_state, column, space_width, &response_cb)
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ std = ["objdiff-core/std"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
log = { version = "0.4", default-features = false }
|
log = { version = "0.4", default-features = false }
|
||||||
regex = { version = "1.11", default-features = false, features = ["unicode-case"] }
|
regex = { version = "1.12", default-features = false, features = ["unicode-case"] }
|
||||||
wit-bindgen = { version = "0.44", default-features = false, features = ["macros"] }
|
wit-bindgen = { version = "0.48", default-features = false, features = ["macros"] }
|
||||||
xxhash-rust = { version = "0.8", default-features = false, features = ["xxh3"] }
|
xxhash-rust = { version = "0.8", default-features = false, features = ["xxh3"] }
|
||||||
|
|
||||||
[dependencies.objdiff-core]
|
[dependencies.objdiff-core]
|
||||||
|
|||||||
8
objdiff-wasm/package-lock.json
generated
8
objdiff-wasm/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "objdiff-wasm",
|
"name": "objdiff-wasm",
|
||||||
"version": "3.4.0",
|
"version": "3.4.5",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "objdiff-wasm",
|
"name": "objdiff-wasm",
|
||||||
"version": "3.4.0",
|
"version": "3.4.5",
|
||||||
"license": "MIT OR Apache-2.0",
|
"license": "MIT OR Apache-2.0",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "^1.9.3",
|
"@biomejs/biome": "^1.9.3",
|
||||||
@@ -852,7 +852,6 @@
|
|||||||
"integrity": "sha512-Y3b4Y7lGIvNPywspP38deHkp/EEkTXrJEHeX1K5yz8U/94PkWvPlDebjjiSvmI6TT+9iVzsq22qDlBEdDuJZhA==",
|
"integrity": "sha512-Y3b4Y7lGIvNPywspP38deHkp/EEkTXrJEHeX1K5yz8U/94PkWvPlDebjjiSvmI6TT+9iVzsq22qDlBEdDuJZhA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@rspack/core": "1.2.3",
|
"@rspack/core": "1.2.3",
|
||||||
"@rspack/lite-tapable": "~1.0.1",
|
"@rspack/lite-tapable": "~1.0.1",
|
||||||
@@ -1084,7 +1083,6 @@
|
|||||||
"integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==",
|
"integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "^2.8.0"
|
"tslib": "^2.8.0"
|
||||||
}
|
}
|
||||||
@@ -2141,7 +2139,6 @@
|
|||||||
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
|
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
@@ -2169,7 +2166,6 @@
|
|||||||
"integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==",
|
"integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "objdiff-wasm",
|
"name": "objdiff-wasm",
|
||||||
"version": "3.4.0",
|
"version": "3.4.5",
|
||||||
"description": "A local diffing tool for decompilation projects.",
|
"description": "A local diffing tool for decompilation projects.",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Luke Street",
|
"name": "Luke Street",
|
||||||
|
|||||||
@@ -469,16 +469,17 @@ impl From<obj::SymbolFlagSet> for SymbolFlags {
|
|||||||
fn from(flags: obj::SymbolFlagSet) -> SymbolFlags {
|
fn from(flags: obj::SymbolFlagSet) -> SymbolFlags {
|
||||||
let mut out = SymbolFlags::empty();
|
let mut out = SymbolFlags::empty();
|
||||||
for flag in flags {
|
for flag in flags {
|
||||||
out |= match flag {
|
out = out
|
||||||
obj::SymbolFlag::Global => SymbolFlags::GLOBAL,
|
| match flag {
|
||||||
obj::SymbolFlag::Local => SymbolFlags::LOCAL,
|
obj::SymbolFlag::Global => SymbolFlags::GLOBAL,
|
||||||
obj::SymbolFlag::Weak => SymbolFlags::WEAK,
|
obj::SymbolFlag::Local => SymbolFlags::LOCAL,
|
||||||
obj::SymbolFlag::Common => SymbolFlags::COMMON,
|
obj::SymbolFlag::Weak => SymbolFlags::WEAK,
|
||||||
obj::SymbolFlag::Hidden => SymbolFlags::HIDDEN,
|
obj::SymbolFlag::Common => SymbolFlags::COMMON,
|
||||||
obj::SymbolFlag::HasExtra => SymbolFlags::HAS_EXTRA,
|
obj::SymbolFlag::Hidden => SymbolFlags::HIDDEN,
|
||||||
obj::SymbolFlag::SizeInferred => SymbolFlags::SIZE_INFERRED,
|
obj::SymbolFlag::HasExtra => SymbolFlags::HAS_EXTRA,
|
||||||
obj::SymbolFlag::Ignored => SymbolFlags::IGNORED,
|
obj::SymbolFlag::SizeInferred => SymbolFlags::SIZE_INFERRED,
|
||||||
};
|
obj::SymbolFlag::Ignored => SymbolFlags::IGNORED,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
out
|
out
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user