Compare commits

..

1 Commits

Author SHA1 Message Date
Luke Street 9f37d99fbd [WIP] OMF object support 2025-09-25 00:50:35 -06:00
71 changed files with 6453 additions and 8428 deletions
Generated
+730 -755
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -14,7 +14,7 @@ default-members = [
resolver = "3"
[workspace.package]
version = "3.6.0"
version = "3.3.0"
authors = ["Luke Street <luke@street.dev>"]
edition = "2024"
license = "MIT OR Apache-2.0"
-3
View File
@@ -75,9 +75,6 @@ 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" },
{ 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.
+3 -3
View File
@@ -16,19 +16,19 @@ publish = false
anyhow = "1.0"
argp = "0.4"
crossterm = "0.29"
enable-ansi-support = "0.3"
enable-ansi-support = "0.2"
memmap2 = "0.9"
objdiff-core = { path = "../objdiff-core", features = ["all"] }
prost = "0.14"
ratatui = "0.29"
rayon = "1.11"
rayon = "1.10"
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.12"
typed-path = "0.11"
[target.'cfg(target_env = "musl")'.dependencies]
mimalloc = "0.1"
+23 -48
View File
@@ -19,7 +19,6 @@ use crossterm::{
},
};
use objdiff_core::{
bindings::diff::DiffResult,
build::{
BuildConfig, BuildStatus,
watcher::{Watcher, create_watcher},
@@ -29,7 +28,7 @@ use objdiff_core::{
build_globset,
path::{check_path_buf, platform_path, platform_path_serde_option},
},
diff::{self, DiffObjConfig, DiffSide, MappingConfig, ObjectDiff},
diff::{DiffObjConfig, MappingConfig, ObjectDiff},
jobs::{
Job, JobQueue, JobResult,
objdiff::{ObjDiffConfig, start_build},
@@ -41,10 +40,7 @@ use typed_path::{Utf8PlatformPath, Utf8PlatformPathBuf};
use crate::{
cmd::apply_config_args,
util::{
output::{OutputFormat, write_output},
term::crossterm_panic_handler,
},
util::term::crossterm_panic_handler,
views::{EventControlFlow, EventResult, UiView, function_diff::FunctionDiffUi},
};
@@ -64,18 +60,21 @@ 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<()> {
@@ -168,41 +167,7 @@ pub fn run(args: Args) -> Result<()> {
_ => bail!("Either target and base or project and unit must be specified"),
};
if let Some(output) = &args.output {
run_oneshot(&args, output, target_path.as_deref(), base_path.as_deref(), unit_options)
} else {
run_interactive(args, target_path, base_path, project_config, unit_options)
}
}
fn run_oneshot(
args: &Args,
output: &Utf8PlatformPath,
target_path: Option<&Utf8PlatformPath>,
base_path: Option<&Utf8PlatformPath>,
unit_options: Option<ProjectOptions>,
) -> Result<()> {
let output_format = OutputFormat::from_option(args.format.as_deref())?;
let (diff_config, mapping_config) = build_config_from_args(args, None, unit_options.as_ref())?;
let target = target_path
.map(|p| {
obj::read::read(p.as_ref(), &diff_config, DiffSide::Target)
.with_context(|| format!("Loading {p}"))
})
.transpose()?;
let base = base_path
.map(|p| {
obj::read::read(p.as_ref(), &diff_config, DiffSide::Base)
.with_context(|| format!("Loading {p}"))
})
.transpose()?;
let result =
diff::diff_objs(target.as_ref(), base.as_ref(), None, &diff_config, &mapping_config)?;
let left = target.as_ref().and_then(|o| result.left.as_ref().map(|d| (o, d)));
let right = base.as_ref().and_then(|o| result.right.as_ref().map(|d| (o, d)));
let diff_result = DiffResult::new(left, right, &diff_config)?;
write_output(&diff_result, Some(output), output_format)?;
Ok(())
run_interactive(args, target_path, base_path, project_config, unit_options)
}
fn build_config_from_args(
@@ -218,7 +183,17 @@ fn build_config_from_args(
apply_project_options(&mut diff_config, options)?;
}
apply_config_args(&mut diff_config, &args.config)?;
Ok((diff_config, MappingConfig::default()))
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))
}
pub struct AppState {
+2 -1
View File
@@ -378,6 +378,7 @@ impl UiView for FunctionDiffUi {
}
// Reload
KeyCode::Char('r') => {
result.redraw = true;
return EventControlFlow::Reload;
}
// Scroll right
@@ -399,6 +400,7 @@ impl UiView for FunctionDiffUi {
FunctionRelocDiffs::DataValue => FunctionRelocDiffs::All,
FunctionRelocDiffs::All => FunctionRelocDiffs::None,
};
result.redraw = true;
return EventControlFlow::Reload;
}
// Toggle three-way diff
@@ -539,7 +541,6 @@ 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()
}
+10 -10
View File
@@ -132,13 +132,13 @@ itertools = { version = "0.14", default-features = false, features = ["use_alloc
log = { version = "0.4", default-features = false, optional = true }
memmap2 = { version = "0.9", optional = true }
num-traits = { version = "0.2", default-features = false, optional = true }
object = { version = "0.37", default-features = false, features = ["read_core", "elf", "coff"] }
object = { git = "https://github.com/encounter/object", branch = "omf", default-features = false, features = ["read_core", "elf", "coff", "omf"] }
pbjson = { version = "0.8", default-features = false, optional = true }
prost = { version = "0.14", default-features = false, features = ["derive"], optional = true }
regex = { version = "1.12", default-features = false, features = [], optional = true }
regex = { version = "1.11", 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.12", default-features = false, optional = true }
typed-path = { version = "0.11", 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.7", default-features = false, features = ["all_extensions"], optional = true }
rabbitizer = { version = "2.0.0-alpha.4", 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 = "2.1", optional = true }
unarm = { version = "1.9", 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.2.0", optional = true }
notify-debouncer-full = { version = "0.6.0", optional = true }
notify = { version = "8.1.0", optional = true }
notify-debouncer-full = { version = "0.5.0", optional = true }
shell-escape = { version = "0.1", optional = true }
tempfile = { version = "3.23", optional = true }
tempfile = { version = "3.20", optional = true }
time = { version = "0.3", optional = true }
encoding_rs = { version = "0.8.35", optional = true }
# demangler
cpp_demangle = { version = "0.5", optional = true, default-features = false, features = ["alloc"] }
cpp_demangle = { version = "0.4", optional = true, default-features = false, features = ["alloc"] }
cwdemangle = { version = "1.0", optional = true }
gnuv2_demangle = { version = "0.4", optional = true }
gnuv2_demangle = { version = "0.1.0", optional = true }
msvc-demangler = { version = "0.11", optional = true }
[target.'cfg(windows)'.dependencies]
-46
View File
@@ -82,27 +82,6 @@
"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",
@@ -128,43 +107,20 @@
"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",
@@ -340,7 +296,6 @@
"properties": [
"functionRelocDiffs",
"demangler",
"showSymbolSizes",
"spaceBetweenArgs",
"combineDataSections",
"combineTextSections"
@@ -351,7 +306,6 @@
"name": "ARM",
"properties": [
"arm.archVersion",
"arm.vfpV2",
"arm.unifiedSyntax",
"arm.avRegisters",
"arm.r9Usage",
-162
View File
@@ -1,162 +0,0 @@
syntax = "proto3";
package objdiff.diff;
// Top-level diff result containing left and right object diffs
message DiffResult {
optional DiffObject left = 1;
optional DiffObject right = 2;
}
// Diff information for a single object file
message DiffObject {
repeated DiffSection sections = 1;
repeated DiffSymbol symbols = 2;
}
// Diff information for a section
message DiffSection {
string name = 1;
DiffSectionKind kind = 2;
uint64 size = 3;
uint64 address = 4;
optional float match_percent = 5;
repeated DiffDataSegment data_diff = 6;
repeated DiffDataRelocation reloc_diff = 7;
}
enum DiffSectionKind {
SECTION_UNKNOWN = 0;
SECTION_CODE = 1;
SECTION_DATA = 2;
SECTION_BSS = 3;
SECTION_COMMON = 4;
}
// Symbol with diff information
message DiffSymbol {
string name = 1;
optional string demangled_name = 2;
uint64 address = 3;
uint64 size = 4;
uint32 flags = 5;
// The symbol index in the _other_ object that this symbol was diffed against
optional uint32 target_symbol = 6;
optional float match_percent = 7;
// Instruction diff rows (for code symbols)
repeated DiffInstructionRow instructions = 8;
// Data diff (for data symbols)
repeated DiffDataSegment data_diff = 9;
}
// Symbol visibility flags (bitmask)
enum DiffSymbolFlag {
SYMBOL_NONE = 0;
SYMBOL_GLOBAL = 1;
SYMBOL_LOCAL = 2;
SYMBOL_WEAK = 4;
SYMBOL_COMMON = 8;
SYMBOL_HIDDEN = 16;
}
// A single instruction diff row
message DiffInstructionRow {
DiffKind diff_kind = 1;
optional DiffInstruction instruction = 2;
repeated DiffInstructionArgDiff arg_diff = 3;
}
// Parsed instruction information
message DiffInstruction {
uint64 address = 1;
uint32 size = 2;
// Formatted instruction string
string formatted = 3;
// Formatted instruction parts
repeated DiffInstructionPart parts = 4;
// Relocation information (if present)
optional DiffRelocation relocation = 5;
// Branch destination address
optional uint64 branch_dest = 6;
// Source line number (if present)
optional uint32 line_number = 7;
}
// An instruction part
message DiffInstructionPart {
oneof part {
// Basic text (whitespace, punctuation, etc.)
string basic = 1;
// Opcode/mnemonic
DiffOpcode opcode = 2;
// Argument
DiffInstructionArg arg = 3;
// Separator between arguments (comma)
bool separator = 4;
}
}
message DiffOpcode {
// Mnemonic string
string mnemonic = 1;
// Base opcode ID
uint32 opcode = 2;
}
// An instruction argument
message DiffInstructionArg {
oneof arg {
// Signed immediate value
sint64 signed = 1;
// Unsigned immediate value
uint64 unsigned = 2;
// Opaque string value (register names, etc.)
string opaque = 3;
// Relocation
bool reloc = 4;
// Branch destination address
uint64 branch_dest = 5;
}
}
// Relocation information
message DiffRelocation {
uint32 type = 1;
string type_name = 2;
uint32 target_symbol = 3;
int64 addend = 4;
}
// Argument diff information
message DiffInstructionArgDiff {
// If set, this argument differs from the other side.
// The value is a rotating index for coloring.
optional uint32 diff_index = 1;
}
// Diff kind for instructions and data
enum DiffKind {
DIFF_NONE = 0;
DIFF_REPLACE = 1;
DIFF_DELETE = 2;
DIFF_INSERT = 3;
DIFF_OP_MISMATCH = 4;
DIFF_ARG_MISMATCH = 5;
}
// Data diff segment
message DiffDataSegment {
DiffKind kind = 1;
bytes data = 2;
// Size may be larger than data length for BSS
uint64 size = 3;
}
// Data relocation diff
message DiffDataRelocation {
DiffRelocation relocation = 1;
DiffKind kind = 2;
// Address range this relocation covers
uint64 start = 3;
uint64 end = 4;
}
Binary file not shown.
File diff suppressed because it is too large Load Diff
-2
View File
@@ -293,7 +293,6 @@ 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 }))
@@ -319,7 +318,6 @@ 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,
},
+24 -71
View File
@@ -7,10 +7,12 @@ 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::{
@@ -42,16 +44,6 @@ 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 {
@@ -83,9 +75,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
}
@@ -94,7 +86,7 @@ impl DataType {
&self,
endian: object::Endianness,
bytes: &[u8],
) -> Vec<(String, Option<String>, Option<String>)> {
) -> Vec<(String, Option<String>)> {
let mut strs = Vec::new();
if self.required_len().is_some_and(|l| bytes.len() < l) {
log::warn!(
@@ -118,34 +110,34 @@ impl DataType {
match self {
DataType::Int8 => {
let i = i8::from_ne_bytes(bytes.try_into().unwrap());
strs.push((format!("{i:#x}"), None, None));
strs.push((format!("{i:#x}"), None));
if i < 0 {
strs.push((format!("{:#x}", ReallySigned(i)), None, None));
strs.push((format!("{:#x}", ReallySigned(i)), None));
}
}
DataType::Int16 => {
let i = endian.read_i16_bytes(bytes.try_into().unwrap());
strs.push((format!("{i:#x}"), None, None));
strs.push((format!("{i:#x}"), None));
if i < 0 {
strs.push((format!("{:#x}", ReallySigned(i)), None, None));
strs.push((format!("{:#x}", ReallySigned(i)), None));
}
}
DataType::Int32 => {
let i = endian.read_i32_bytes(bytes.try_into().unwrap());
strs.push((format!("{i:#x}"), None, None));
strs.push((format!("{i:#x}"), None));
if i < 0 {
strs.push((format!("{:#x}", ReallySigned(i)), None, None));
strs.push((format!("{:#x}", ReallySigned(i)), None));
}
}
DataType::Int64 => {
let i = endian.read_i64_bytes(bytes.try_into().unwrap());
strs.push((format!("{i:#x}"), None, None));
strs.push((format!("{i:#x}"), None));
if i < 0 {
strs.push((format!("{:#x}", ReallySigned(i)), None, None));
strs.push((format!("{:#x}", ReallySigned(i)), None));
}
}
DataType::Float => {
@@ -156,7 +148,6 @@ impl DataType {
object::Endianness::Big => f32::from_be_bytes(bytes),
}),
None,
None,
));
}
DataType::Double => {
@@ -167,43 +158,23 @@ impl DataType {
object::Endianness::Big => f64::from_be_bytes(bytes),
}),
None,
None,
));
}
DataType::Bytes => {
strs.push((format!("{bytes:#?}"), None, None));
strs.push((format!("{bytes:#?}"), None));
}
DataType::String => {
// Special case to display (ASCII) as the label for ASCII-only strings.
let mut is_ascii = false;
if bytes.is_ascii()
&& let Ok(str) = str::from_utf8(bytes)
{
let trimmed = str.trim_end_matches('\0');
if !trimmed.is_empty() {
let copy_string = escape_special_ascii_characters(trimmed);
strs.push((trimmed.to_string(), Some("ASCII".into()), Some(copy_string)));
is_ascii = true;
}
if let Ok(cstr) = CStr::from_bytes_until_nul(bytes) {
strs.push((format!("{cstr:?}"), None));
}
for (encoding, encoding_name) in SUPPORTED_ENCODINGS {
// Avoid showing ASCII-only strings more than once if the encoding is ASCII-compatible.
if is_ascii && encoding.is_ascii_compatible() {
continue;
}
let (cow, _, had_errors) = encoding.decode(bytes);
if had_errors {
continue;
}
let trimmed = cow.trim_end_matches('\0');
if !trimmed.is_empty() {
let copy_string = escape_special_ascii_characters(trimmed);
strs.push((
trimmed.to_string(),
Some(encoding_name.into()),
Some(copy_string),
));
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())));
}
}
}
}
@@ -518,21 +489,3 @@ 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
}
+5 -13
View File
@@ -332,6 +332,7 @@ impl Arch for ArchPpc {
pe::IMAGE_REL_PPC_PAIR => Some("IMAGE_REL_PPC_PAIR"),
_ => None,
},
_ => None,
}
}
@@ -342,10 +343,7 @@ impl Arch for ArchPpc {
elf::R_PPC_UADDR32 => 4,
_ => 1,
},
RelocationFlags::Coff(r_type) => match r_type {
pe::IMAGE_REL_PPC_ADDR32 => 4,
_ => 1,
},
_ => 1,
}
}
@@ -358,9 +356,7 @@ 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") || r.symbol.name.starts_with("$SG")
}) {
if resolved.relocation.is_some_and(|r| r.symbol.name.starts_with("@stringBase")) {
// Pooled string.
return Some(DataType::String);
}
@@ -445,13 +441,9 @@ 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, copy_string: None });
out.push(ContextItem::Copy { value: simplified, label: None });
if show_orig {
out.push(ContextItem::Copy {
value: orig,
label: Some("original".to_string()),
copy_string: None,
});
out.push(ContextItem::Copy { value: orig, label: Some("original".to_string()) });
}
out
}
+5 -25
View File
@@ -68,15 +68,11 @@ impl ArchX86 {
_ => None,
},
RelocationFlags::Elf(typ) => match typ {
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_32 | elf::R_386_PC32 => Some(4),
elf::R_386_16 => Some(2),
_ => None,
},
RelocationFlags::Omf() => Some(4), // TODO
},
Architecture::X86_64 => match flags {
RelocationFlags::Coff(typ) => match typ {
@@ -89,6 +85,7 @@ impl ArchX86 {
elf::R_X86_64_64 => Some(8),
_ => None,
},
RelocationFlags::Omf() => None, // TODO
},
}
}
@@ -277,15 +274,7 @@ 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
| elf::R_386_GOT32
| elf::R_386_PLT32
| elf::R_386_GOTOFF
| elf::R_386_GOTPC,
} => {
| object::RelocationFlags::Elf { r_type: elf::R_386_32 | elf::R_386_PC32 } => {
let data =
section.data()?[address as usize..address as usize + 4].try_into()?;
self.endianness.read_i32_bytes(data) as i64
@@ -321,16 +310,7 @@ impl Arch for ArchX86 {
pe::IMAGE_REL_I386_REL32 => Some("IMAGE_REL_I386_REL32"),
_ => 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,
},
_ => None,
},
Architecture::X86_64 => match flags {
RelocationFlags::Coff(typ) => match typ {
-350
View File
@@ -1,350 +0,0 @@
#![allow(clippy::needless_lifetimes)] // Generated serde code
use alloc::{
string::{String, ToString},
vec,
vec::Vec,
};
use core::fmt::Write;
use anyhow::Result;
use crate::{
diff::{self, DiffObjConfig, display::InstructionPart},
obj::{self, Object, SymbolFlag},
};
// Protobuf diff types
include!(concat!(env!("OUT_DIR"), "/objdiff.diff.rs"));
#[cfg(feature = "serde")]
include!(concat!(env!("OUT_DIR"), "/objdiff.diff.serde.rs"));
impl DiffResult {
pub fn new(
left: Option<(&Object, &diff::ObjectDiff)>,
right: Option<(&Object, &diff::ObjectDiff)>,
diff_config: &DiffObjConfig,
) -> Result<Self> {
Ok(Self {
left: left.map(|(obj, diff)| DiffObject::new(obj, diff, diff_config)).transpose()?,
right: right.map(|(obj, diff)| DiffObject::new(obj, diff, diff_config)).transpose()?,
})
}
}
impl DiffObject {
pub fn new(obj: &Object, diff: &diff::ObjectDiff, diff_config: &DiffObjConfig) -> Result<Self> {
let mut sections = Vec::with_capacity(obj.sections.len());
for (section_idx, section) in obj.sections.iter().enumerate() {
let section_diff = &diff.sections[section_idx];
sections.push(DiffSection::new(obj, section, section_diff));
}
let mut symbols = Vec::with_capacity(obj.symbols.len());
for (symbol_idx, symbol) in obj.symbols.iter().enumerate() {
let symbol_diff = &diff.symbols[symbol_idx];
if symbol.size == 0 || symbol.flags.contains(SymbolFlag::Ignored) {
continue;
}
symbols.push(DiffSymbol::new(obj, symbol_idx, symbol, symbol_diff, diff_config)?);
}
Ok(Self { sections, symbols })
}
}
impl DiffSection {
pub fn new(obj: &Object, section: &obj::Section, section_diff: &diff::SectionDiff) -> Self {
Self {
name: section.name.clone(),
kind: DiffSectionKind::from(section.kind) as i32,
size: section.size,
address: section.address,
match_percent: section_diff.match_percent,
data_diff: section_diff.data_diff.iter().map(DiffDataSegment::from).collect(),
reloc_diff: section_diff
.reloc_diff
.iter()
.map(|r| DiffDataRelocation::new(obj, r))
.collect(),
}
}
}
impl From<obj::SectionKind> for DiffSectionKind {
fn from(value: obj::SectionKind) -> Self {
match value {
obj::SectionKind::Unknown => DiffSectionKind::SectionUnknown,
obj::SectionKind::Code => DiffSectionKind::SectionCode,
obj::SectionKind::Data => DiffSectionKind::SectionData,
obj::SectionKind::Bss => DiffSectionKind::SectionBss,
obj::SectionKind::Common => DiffSectionKind::SectionCommon,
}
}
}
impl DiffSymbol {
pub fn new(
obj: &Object,
symbol_idx: usize,
symbol: &obj::Symbol,
symbol_diff: &diff::SymbolDiff,
diff_config: &DiffObjConfig,
) -> Result<Self> {
// Convert instruction rows
let instructions = symbol_diff
.instruction_rows
.iter()
.map(|row| DiffInstructionRow::new(obj, symbol_idx, row, diff_config))
.collect::<Result<Vec<_>>>()?;
// Convert data diff - flatten DataDiffRow segments into a single list
let data_diff: Vec<DiffDataSegment> = symbol_diff
.data_rows
.iter()
.flat_map(|row| row.segments.iter().map(DiffDataSegment::from))
.collect();
Ok(Self {
// Symbol metadata
name: symbol.name.clone(),
demangled_name: symbol.demangled_name.clone(),
address: symbol.address,
size: symbol.size,
flags: symbol_flags(&symbol.flags),
// Diff information
target_symbol: symbol_diff.target_symbol.map(|i| i as u32),
match_percent: symbol_diff.match_percent,
instructions,
data_diff,
})
}
}
fn symbol_flags(flags: &obj::SymbolFlagSet) -> u32 {
let mut result = 0u32;
if flags.contains(SymbolFlag::Global) {
result |= DiffSymbolFlag::SymbolGlobal as u32;
}
if flags.contains(SymbolFlag::Local) {
result |= DiffSymbolFlag::SymbolLocal as u32;
}
if flags.contains(SymbolFlag::Weak) {
result |= DiffSymbolFlag::SymbolWeak as u32;
}
if flags.contains(SymbolFlag::Common) {
result |= DiffSymbolFlag::SymbolCommon as u32;
}
if flags.contains(SymbolFlag::Hidden) {
result |= DiffSymbolFlag::SymbolHidden as u32;
}
result
}
impl DiffInstructionRow {
pub fn new(
obj: &Object,
symbol_idx: usize,
row: &diff::InstructionDiffRow,
diff_config: &DiffObjConfig,
) -> Result<Self> {
let instruction = if let Some(ins_ref) = row.ins_ref {
let resolved = obj.resolve_instruction_ref(symbol_idx, ins_ref);
resolved.map(|r| DiffInstruction::new(obj, r, diff_config)).transpose()?
} else {
None
};
let arg_diff =
row.arg_diff.iter().map(|d| DiffInstructionArgDiff { diff_index: d.get() }).collect();
Ok(Self { diff_kind: DiffKind::from(row.kind) as i32, instruction, arg_diff })
}
}
impl DiffInstruction {
pub fn new(
obj: &Object,
resolved: obj::ResolvedInstructionRef,
diff_config: &DiffObjConfig,
) -> Result<Self> {
let mut formatted = String::new();
let mut parts = vec![];
let separator = diff_config.separator();
// Use the arch's display_instruction to get formatted parts
obj.arch.display_instruction(resolved, diff_config, &mut |part| {
write_instruction_part(&mut formatted, &part, separator, resolved.relocation);
parts
.push(DiffInstructionPart { part: Some(diff_instruction_part::Part::from(&part)) });
Ok(())
})?;
let relocation = resolved.relocation.map(|r| DiffRelocation::new(obj, r));
let line_number = resolved
.section
.line_info
.range(..=resolved.ins_ref.address)
.last()
.map(|(_, &line)| line);
Ok(Self {
address: resolved.ins_ref.address,
size: resolved.ins_ref.size as u32,
formatted,
parts,
relocation,
branch_dest: resolved.ins_ref.branch_dest,
line_number,
})
}
}
fn write_instruction_part(
out: &mut String,
part: &InstructionPart,
separator: &str,
reloc: Option<obj::ResolvedRelocation>,
) {
match part {
InstructionPart::Basic(s) => out.push_str(s),
InstructionPart::Opcode(s, _) => {
out.push_str(s);
out.push(' ');
}
InstructionPart::Arg(arg) => match arg {
obj::InstructionArg::Value(v) => {
let _ = write!(out, "{}", v);
}
obj::InstructionArg::Reloc => {
if let Some(resolved) = reloc {
out.push_str(&resolved.symbol.name);
if resolved.relocation.addend != 0 {
if resolved.relocation.addend < 0 {
let _ = write!(out, "-{:#x}", -resolved.relocation.addend);
} else {
let _ = write!(out, "+{:#x}", resolved.relocation.addend);
}
}
}
}
obj::InstructionArg::BranchDest(dest) => {
let _ = write!(out, "{:#x}", dest);
}
},
InstructionPart::Separator => out.push_str(separator),
}
}
impl diff_instruction_part::Part {
fn from(part: &InstructionPart) -> Self {
match part {
InstructionPart::Basic(s) => diff_instruction_part::Part::Basic(s.to_string()),
InstructionPart::Opcode(mnemonic, opcode) => {
diff_instruction_part::Part::Opcode(DiffOpcode {
mnemonic: mnemonic.to_string(),
opcode: *opcode as u32,
})
}
InstructionPart::Arg(arg) => {
diff_instruction_part::Part::Arg(DiffInstructionArg::from(arg))
}
InstructionPart::Separator => diff_instruction_part::Part::Separator(true),
}
}
}
impl From<&obj::InstructionArg<'_>> for DiffInstructionArg {
fn from(arg: &obj::InstructionArg) -> Self {
let arg = match arg {
obj::InstructionArg::Value(v) => match v {
obj::InstructionArgValue::Signed(v) => diff_instruction_arg::Arg::Signed(*v),
obj::InstructionArgValue::Unsigned(v) => diff_instruction_arg::Arg::Unsigned(*v),
obj::InstructionArgValue::Opaque(v) => {
diff_instruction_arg::Arg::Opaque(v.to_string())
}
},
obj::InstructionArg::Reloc => diff_instruction_arg::Arg::Reloc(true),
obj::InstructionArg::BranchDest(dest) => diff_instruction_arg::Arg::BranchDest(*dest),
};
DiffInstructionArg { arg: Some(arg) }
}
}
impl DiffRelocation {
pub fn new(obj: &Object, resolved: obj::ResolvedRelocation) -> Self {
let type_val = relocation_type(resolved.relocation.flags);
let type_name = obj
.arch
.reloc_name(resolved.relocation.flags)
.map(|s| s.to_string())
.unwrap_or_default();
Self {
r#type: type_val,
type_name,
target_symbol: resolved.relocation.target_symbol as u32,
addend: resolved.relocation.addend,
}
}
}
fn relocation_type(flags: obj::RelocationFlags) -> u32 {
match flags {
obj::RelocationFlags::Elf(r_type) => r_type,
obj::RelocationFlags::Coff(typ) => typ as u32,
}
}
impl From<diff::InstructionDiffKind> for DiffKind {
fn from(value: diff::InstructionDiffKind) -> Self {
match value {
diff::InstructionDiffKind::None => DiffKind::DiffNone,
diff::InstructionDiffKind::OpMismatch => DiffKind::DiffOpMismatch,
diff::InstructionDiffKind::ArgMismatch => DiffKind::DiffArgMismatch,
diff::InstructionDiffKind::Replace => DiffKind::DiffReplace,
diff::InstructionDiffKind::Delete => DiffKind::DiffDelete,
diff::InstructionDiffKind::Insert => DiffKind::DiffInsert,
}
}
}
impl From<diff::DataDiffKind> for DiffKind {
fn from(value: diff::DataDiffKind) -> Self {
match value {
diff::DataDiffKind::None => DiffKind::DiffNone,
diff::DataDiffKind::Replace => DiffKind::DiffReplace,
diff::DataDiffKind::Delete => DiffKind::DiffDelete,
diff::DataDiffKind::Insert => DiffKind::DiffInsert,
}
}
}
impl From<&diff::DataDiff> for DiffDataSegment {
fn from(value: &diff::DataDiff) -> Self {
Self {
kind: DiffKind::from(value.kind) as i32,
data: value.data.clone(),
size: value.size as u64,
}
}
}
impl DiffDataRelocation {
pub fn new(obj: &Object, value: &diff::DataRelocationDiff) -> Self {
let type_val = relocation_type(value.reloc.flags);
let type_name =
obj.arch.reloc_name(value.reloc.flags).map(|s| s.to_string()).unwrap_or_default();
Self {
relocation: Some(DiffRelocation {
r#type: type_val,
type_name,
target_symbol: value.reloc.target_symbol as u32,
addend: value.reloc.addend,
}),
kind: DiffKind::from(value.kind) as i32,
start: value.range.start,
end: value.range.end,
}
}
}
-2
View File
@@ -1,3 +1 @@
#[cfg(feature = "any-arch")]
pub mod diff;
pub mod report;
+11 -8
View File
@@ -35,13 +35,17 @@ pub fn diff_bss_symbol(
))
}
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
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;
}
right_name
.split_once('$')
.is_some_and(|(p, s)| p == prefix && s.chars().all(char::is_numeric))
} else {
left.name == right.name
left_name == right_name
}
}
@@ -55,7 +59,7 @@ fn reloc_eq(
return false;
}
let symbol_name_addend_matches = symbol_name_matches(left.symbol, right.symbol)
let symbol_name_addend_matches = symbol_name_matches(&left.symbol.name, &right.symbol.name)
&& left.relocation.addend == right.relocation.addend;
match (left.symbol.section, right.symbol.section) {
(Some(sl), Some(sr)) => {
@@ -583,7 +587,6 @@ 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)
})
}
+4 -2
View File
@@ -34,12 +34,14 @@ 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().ok())
cpp_demangle::Symbol::new(name)
.ok()
.and_then(|s| s.demangle(&cpp_demangle::DemangleOptions::default()).ok())
}
fn demangle_gnu_legacy(name: &str) -> Option<String> {
let name = name.trim_start_matches('.');
gnuv2_demangle::demangle(name, &gnuv2_demangle::DemangleConfig::new()).ok()
gnuv2_demangle::demangle(name, &gnuv2_demangle::DemangleConfig::new_no_cfilt_mimics()).ok()
}
}
+29 -50
View File
@@ -37,8 +37,6 @@ 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
@@ -187,13 +185,7 @@ pub fn display_row(
pad_to: 5,
})?;
if let Some(branch) = &ins_row.branch_from {
// 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,
})?;
cb(DiffTextSegment::basic(" ~> ", DiffTextColor::Rotating(branch.branch_idx as u8)))?;
} else {
cb(DiffTextSegment::spacing(4))?;
}
@@ -298,8 +290,9 @@ pub fn display_row(
}
})?;
// Fallback for relocation that wasn't displayed
if !displayed_relocation && let Some(resolved) = resolved.relocation {
if resolved.relocation.is_some() && !displayed_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));
@@ -314,11 +307,7 @@ pub fn display_row(
cb(DiffTextSegment::basic(">", base_color))?;
}
if let Some(branch) = &ins_row.branch_to {
cb(DiffTextSegment {
text: DiffText::BranchArrow(branch.ins_idx),
color: DiffTextColor::Rotating(branch.branch_idx as u8),
pad_to: 0,
})?;
cb(DiffTextSegment::basic(" ~>", DiffTextColor::Rotating(branch.branch_idx as u8)))?;
}
cb(EOL_SEGMENT)?;
Ok(())
@@ -365,7 +354,7 @@ impl From<&DiffText<'_>> for HighlightKind {
}
pub enum ContextItem {
Copy { value: String, label: Option<String>, copy_string: Option<String> },
Copy { value: String, label: Option<String> },
Navigate { label: String, symbol_index: usize, kind: SymbolNavigationKind },
Separator,
}
@@ -397,9 +386,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, copy_string: None });
out.push(ContextItem::Copy { value: symbol.name.clone(), label: None });
if let Some(name) = &symbol.demangled_name {
out.push(ContextItem::Copy { value: name.clone(), label: None, copy_string: None });
out.push(ContextItem::Copy { value: name.clone(), label: None });
}
if symbol.section.is_some()
&& let Some(address) = symbol.virtual_address
@@ -407,7 +396,6 @@ 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));
@@ -501,8 +489,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, copy_string) in literals {
out.push(ContextItem::Copy { value: literal, label: label_override, copy_string });
for (literal, label_override) in literals {
out.push(ContextItem::Copy { value: literal, label: label_override });
}
}
}
@@ -598,37 +586,24 @@ 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()),
copy_string: None,
});
out.push(ContextItem::Copy { value: hex_string, label: Some("instruction bytes".to_string()) });
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, copy_string: None });
out.push(ContextItem::Copy { value: arg.to_string(), label: None });
match arg {
InstructionArgValue::Signed(v) => {
out.push(ContextItem::Copy {
value: v.to_string(),
label: None,
copy_string: None,
});
out.push(ContextItem::Copy { value: v.to_string(), label: None });
}
InstructionArgValue::Unsigned(v) => {
out.push(ContextItem::Copy {
value: v.to_string(),
label: None,
copy_string: None,
});
out.push(ContextItem::Copy { value: v.to_string(), label: None });
}
_ => {}
}
@@ -690,10 +665,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: format!("{literal:?}"),
value: literal,
color: HoverItemColor::Normal,
});
}
@@ -812,15 +787,19 @@ pub fn display_sections(
}
let section_diff = &diff.sections[section_idx];
let reverse_fn_order = section.kind == SectionKind::Code && reverse_fn_order;
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))
});
}
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))
});
sections.push(SectionDisplay {
id: section.id.clone(),
name: if section.flags.contains(SectionFlag::Combined) {
@@ -880,7 +859,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>, Option<String>)> {
) -> Vec<(String, Option<String>)> {
let Some(reloc) = resolved.relocation else {
return Vec::new();
};

Some files were not shown because too many files have changed in this diff Show More