Compare commits

...

43 Commits

Author SHA1 Message Date
Luke Street 8250d26b77 Support R_MIPS_LITERAL, R_MIPS15_S3 relocations
Resolves #92
Resolves #95
2024-08-18 22:05:16 -06:00
Luke Street fd555a6e0f Fix reading little-endian .line section 2024-08-18 21:57:53 -06:00
Luke Street 3710b6a91e Try even harder to recover from protoc missing 2024-08-18 14:01:49 -06:00
Luke Street faebddbc5e More updates to report types 2024-08-18 13:42:41 -06:00
Luke Street a733a950a3 Avoid requiring protoc unless protos change 2024-08-18 13:40:49 -06:00
Luke Street cad9b70632 Support protobuf format for reports
This migrates to using protobuf to
define the "report" and "changes"
formats in objdiff-cli.

The JSON output now uses the Proto3
"JSON Mapping", which is slightly
incompatible with the existing JSON
format. Mainly, 64-bit numbers are
represented as strings, and addresses
are decimal strings instead of hex.

However, the older JSON format is
still accepted by "report changes"
to ease migration.
2024-08-16 00:52:24 -06:00
Luke Street cf937b0be9 Guard against symbols larger than section 2024-08-11 16:54:24 -06:00
Luke Street 23b6d33a98 Fix botched find/replace 2024-08-11 16:53:35 -06:00
Luke Street f17ee83622 Version v2.0.0-beta.3 2024-08-11 16:02:36 -06:00
Luke Street 615ec4c50a mips: Support R_MIPS_PC16 relocations 2024-08-11 16:00:10 -06:00
Luke Street 2cc10b0d06 cargo +nightly fmt 2024-08-11 14:29:58 -06:00
Luke Street 8091941448 Version v2.0.0-beta.2 2024-08-11 14:27:47 -06:00
Luke Street de74dfdba7 Add dummy symbols to empty sections
Allows diffing sections without symbols

Resolves #87
2024-08-11 14:27:27 -06:00
Luke Street 177bd5e895 Fix branch immediates missing diff colors
Resolves #86
2024-08-11 14:01:53 -06:00
Luke Street e1ccee1e73 Improve data section diffing more
This re-implements the older algorithm
used for data and BSS section match
percentages. We perform both and
choose the highest match percent
between the two options.

Resolves #84, #85
2024-08-11 13:53:52 -06:00
Luke Street 952b6a63c3 Better graphics backend fallback
This attempts the following in order:
- wgpu with user-selected backend
- wgpu with automatic backend
- glow (fallback OpenGL backend)

This should eliminate most issues
where objdiff fails to launch.
2024-08-11 13:33:10 -06:00
Steven Casper 09cc9952df Support R_MIPS_GPREL16 relocations correctly (#88)
* Support R_MIPS_GPREL16 relocations correctly

symbols defined in the same file require adding a
special ri_gp_value from the .reginfo section to
their relocation calculations.

* Run nightly rustfmt

* Prevent potential panic when slicing .reginfo
2024-08-08 20:20:41 -06:00
Luke Street fc598af329 Version 2.0.0-beta.1 2024-07-21 23:03:15 -06:00
Luke Street 871407622d Use regex for symbol search
Also fixes case insensitivity and
properly searches the .comm section

Fixes #80
2024-07-21 23:01:58 -06:00
Luke Street e3fff7b0dc Improve data section diff logic
More accurate in more cases

Fixes #81
2024-07-21 22:52:46 -06:00
Amber Brault 75b0e7d9e5 Exception table diff view (#82)
* Basic integration

* Implement basic right click option

Needs lotsa work

* nothing to worry about

* Convert extab diff to separate view

* Make clippy and fmt shut up

* Make clippy fmt shut up for real this time

* Print extab/extabindex symbol names in extab view

* I hate fmt

* Basic integration

* Implement basic right click option

Needs lotsa work

* nothing to worry about

* Convert extab diff to separate view

* Make clippy and fmt shut up

* Make clippy fmt shut up for real this time

* Print extab/extabindex symbol names in extab view

* I hate fmt

* Fix scroll position not being maintained from extab view

* Silly me

* Add rlwinm decoder window

* Remove extra files

* Create Cargo.lock

* Show extab symbol names in hover window

* Appease fmt

* Update symbol_diff.rs

* Update symbol_diff.rs

* Get extab symbol from extabindex relocations instead

* Update Cargo.lock

* Update Cargo.lock
2024-07-21 22:25:54 -06:00
Amber Brault 9f71ce9fea Add rlwinm decoder window (#83)
* Add rlwinm decoder window

* Remove extra files

* Create Cargo.lock

* Make fmt happy

* Update Cargo.lock

* Update Cargo.lock

* Update Cargo.lock
2024-07-21 17:56:46 -06:00
Aetias d9fb48853e Options for ARM disassembly style (#78) 2024-07-14 16:00:57 -06:00
Luke Street 233839346a Version v2.0.0-alpha.5 2024-06-20 20:31:31 -06:00
Luke Street 95615c2ec5 Improve MIPS ABI auto-detection 2024-06-20 19:57:18 -06:00
Aetias 97981160f4 ARMv4T (GBA) and ARMv6K (3DS) support (#75)
* Initial ARM support

* Disassemble const pool reloc

* Disasm ARM/Thumb/data based on mapping symbols

* Fallback to mapping symbol `$a`

* Support multiple DWARF sequences

* Update line info

* Rework DWARF line info parsing

- Properly handles multiple sections
  in DWARF 1
- line_info moved into ObjSection
- DWARF 2 parser no longer errors with
  no .text section
- Both parsers properly skip empty
  sections

* Simplify line_info (no Option)

* Get line info from section; output formatted ins string

* Unwrap code section in `arm.rs`

* Handle reloc `R_ARM_SBREL32`

* Update ARM disassembler

* Update README.md

* Format

* Revert "Update README.md"

This reverts commit 8bbfcc6f45.

* Update README.md

* Detect ARM version; support ARMv4T and v6K

* Combobox to force ARM version

* Clear LSB in ARM symbol addresses

* Support big-endian ARM ELF files

* Bump `unarm`, `arm-attr`

* Handle ARM implicit addends

* Update README.md

* Explicitly handle all ARM argument types

* Format

* Display more ARM relocs

* Mask LSB on ARM code symbols only

* Read ARM implicit addends

* Format

---------

Co-authored-by: Luke Street <luke.street@encounterpc.com>
2024-06-20 18:36:25 -06:00
Aetias 1fd901a863 Option to combine data sections (#76)
Co-authored-by: Luke Street <luke.street@encounterpc.com>
2024-06-18 22:05:24 -06:00
Luke Street 759d55994a Fix clippy warning 2024-06-18 21:49:19 -06:00
Luke Street 9710ccc38a Add graphics backend configuration
Hopefully #74, #73, #56
2024-06-05 18:01:03 -06:00
Luke Street 79cd460333 Update notify-rs to fix WSL crash
Fixes #66
2024-06-04 17:13:54 -06:00
Aetias a5a6a3928e Fix read error on objects with no .text section (#67)
* Fix read error on objects with no .text section

* Fix read error on DWARF 1.1 objects

* Revert DWARF 1 changes

---------

Co-authored-by: Luke Street <luke@street.dev>
2024-06-03 19:47:38 -06:00
Luke Street fc54e93681 API updates for ARM backend 2024-06-03 19:37:48 -06:00
Luke Street c9b11db2fa Update README.md 2024-06-03 19:09:35 -06:00
Aetias b991960080 ARMv5TE (DS) support (#68)
* Initial ARM support

* Disassemble const pool reloc

* Disasm ARM/Thumb/data based on mapping symbols

* Fallback to mapping symbol `$a`

* Support multiple DWARF sequences

* Update line info

* Rework DWARF line info parsing

- Properly handles multiple sections
  in DWARF 1
- line_info moved into ObjSection
- DWARF 2 parser no longer errors with
  no .text section
- Both parsers properly skip empty
  sections

* Simplify line_info (no Option)

* Get line info from section; output formatted ins string

* Unwrap code section in `arm.rs`

* Handle reloc `R_ARM_SBREL32`

* Update ARM disassembler

* Update README.md

* Format

* Revert "Update README.md"

This reverts commit 8bbfcc6f45.

* Update README.md

---------

Co-authored-by: Luke Street <luke.street@encounterpc.com>
2024-06-03 19:08:49 -06:00
Luke Street 425dc8546b More descriptive message for build failure
Resolves #64
2024-06-03 19:06:19 -06:00
Luke Street 9e04357d9f Use solid scrollbar in egui
Resolves #69
2024-06-03 19:03:33 -06:00
Luke Street 6037c12ad0 Disable lto to workaround crash
See #66
2024-06-03 18:58:25 -06:00
Luke Street b15f643713 Bump version to 2.0.0-alpha.3 2024-06-03 18:54:46 -06:00
Luke Street 3f82c1a50f objdiff-core API adjustments
- Allows using process_code without
  constructing an ObjInfo
- Allows creating an arch without
  having to provide an object

Used in decomp-toolkit
2024-06-03 18:52:32 -06:00
Luke Street 0ea6242669 Bump rabbitizer version (fixes crash) 2024-06-03 18:50:22 -06:00
Luke Street 0c20a0d9cd Update README.md 2024-05-21 18:12:58 -06:00
Luke Street f30b3cfae2 Default "Space between args" -> true 2024-05-21 18:09:46 -06:00
Luke Street 9e57a66a05 Auto-detect MIPS ABI/category & add config
Under Diff Options -> Arch Settings, one
can override the ABI/instruction category
2024-05-21 18:06:14 -06:00
38 changed files with 3015 additions and 1152 deletions
+1
View File
@@ -22,3 +22,4 @@ android.keystore
*.frag
*.vert
*.metal
.vscode/launch.json
Generated
+276 -616
View File
File diff suppressed because it is too large Load Diff
+3 -2
View File
@@ -14,9 +14,10 @@ Features:
- Click to highlight all instances of values and registers.
Supports:
- PowerPC 750CL (GameCube & Wii)
- MIPS (Nintendo 64 & PS2)
- PowerPC 750CL (GameCube, Wii)
- MIPS (N64, PS1, PS2, PSP)
- x86 (COFF only at the moment)
- ARM (GBA, DS, 3DS)
See [Usage](#usage) for more information.
+9 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "objdiff-cli"
version = "2.0.0-alpha.1"
version = "2.0.0-beta.4"
edition = "2021"
rust-version = "1.70"
authors = ["Luke Street <luke@street.dev>"]
@@ -18,7 +18,10 @@ anyhow = "1.0.82"
argp = "0.3.0"
crossterm = "0.27.0"
enable-ansi-support = "0.2.1"
memmap2 = "0.9.4"
objdiff-core = { path = "../objdiff-core", features = ["all"] }
pbjson = "0.7.0"
prost = "0.13.1"
ratatui = "0.26.2"
rayon = "1.10.0"
serde = { version = "1", features = ["derive"] }
@@ -27,3 +30,8 @@ supports-color = "3.0.0"
time = { version = "0.3.36", features = ["formatting", "local-offset"] }
tracing = "0.1.40"
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
[build-dependencies]
prost-build = "0.13.1"
pbjson-build = "0.7.0"
+53
View File
@@ -1,3 +1,5 @@
use std::path::{Path, PathBuf};
fn main() {
let output = std::process::Command::new("git")
.args(["rev-parse", "HEAD"])
@@ -6,4 +8,55 @@ fn main() {
let rev = String::from_utf8(output.stdout).expect("Failed to parse git output");
println!("cargo:rustc-env=GIT_COMMIT_SHA={rev}");
println!("cargo:rustc-rerun-if-changed=.git/HEAD");
let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("protos");
let descriptor_path = root.join("proto_descriptor.bin");
println!("cargo:rerun-if-changed={}", descriptor_path.display());
let descriptor_mtime = std::fs::metadata(&descriptor_path)
.map(|m| m.modified().unwrap())
.unwrap_or(std::time::SystemTime::UNIX_EPOCH);
let mut run_protoc = false;
let proto_files = vec![root.join("report.proto")];
for proto_file in &proto_files {
println!("cargo:rerun-if-changed={}", proto_file.display());
let mtime = match std::fs::metadata(proto_file) {
Ok(m) => m.modified().unwrap(),
Err(e) => panic!("Failed to stat proto file {}: {:?}", proto_file.display(), e),
};
if mtime > descriptor_mtime {
run_protoc = true;
}
}
fn prost_config(descriptor_path: &Path, run_protoc: bool) -> prost_build::Config {
let mut config = prost_build::Config::new();
config.file_descriptor_set_path(descriptor_path);
// If our cached descriptor is up-to-date, we don't need to run protoc.
// This is helpful so that users don't need to have protoc installed
// unless they're updating the protos.
if !run_protoc {
config.skip_protoc_run();
}
config
}
if let Err(e) =
prost_config(&descriptor_path, run_protoc).compile_protos(&proto_files, &[root.as_path()])
{
if e.kind() == std::io::ErrorKind::NotFound && e.to_string().contains("protoc") {
eprintln!("protoc not found, skipping protobuf compilation");
prost_config(&descriptor_path, false)
.compile_protos(&proto_files, &[root.as_path()])
.expect("Failed to compile protos");
} else {
panic!("Failed to compile protos: {e:?}");
}
}
let descriptor_set = std::fs::read(descriptor_path).expect("Failed to read descriptor set");
pbjson_build::Builder::new()
.register_descriptors(&descriptor_set)
.expect("Failed to register descriptors")
.preserve_proto_field_names()
.build(&[".objdiff"])
.expect("Failed to build pbjson");
}
Binary file not shown.
+135
View File
@@ -0,0 +1,135 @@
syntax = "proto3";
package objdiff.report;
// Progress info for a report or unit
message Measures {
// Overall match percent, including partially matched functions and data
float fuzzy_match_percent = 1;
// Total size of code in bytes
uint64 total_code = 2;
// Fully matched code size in bytes
uint64 matched_code = 3;
// Fully matched code percent
float matched_code_percent = 4;
// Total size of data in bytes
uint64 total_data = 5;
// Fully matched data size in bytes
uint64 matched_data = 6;
// Fully matched data percent
float matched_data_percent = 7;
// Total number of functions
uint32 total_functions = 8;
// Fully matched functions
uint32 matched_functions = 9;
// Fully matched functions percent
float matched_functions_percent = 10;
}
// Project progress report
message Report {
// Overall progress info
Measures measures = 1;
// Units within this report
repeated ReportUnit units = 2;
}
// A unit of the report (usually a translation unit)
message ReportUnit {
// The name of the unit
string name = 1;
// Progress info for this unit
Measures measures = 2;
// Sections within this unit
repeated ReportItem sections = 3;
// Functions within this unit
repeated ReportItem functions = 4;
// Extra metadata for this unit
optional ReportUnitMetadata metadata = 5;
}
// Extra metadata for a unit
message ReportUnitMetadata {
// Whether this unit is marked as complete (or "linked")
optional bool complete = 1;
// The name of the module this unit belongs to
optional string module_name = 2;
// The ID of the module this unit belongs to
optional uint32 module_id = 3;
// The path to the source file of this unit
optional string source_path = 4;
}
// A section or function within a unit
message ReportItem {
// The name of the item
string name = 1;
// The size of the item in bytes
uint64 size = 2;
// The overall match percent for this item
float fuzzy_match_percent = 3;
// Extra metadata for this item
optional ReportItemMetadata metadata = 4;
}
// Extra metadata for an item
message ReportItemMetadata {
// The demangled name of the function
optional string demangled_name = 1;
// The virtual address of the function or section
optional uint64 virtual_address = 2;
}
// A pair of reports to compare and generate changes
message ChangesInput {
// The previous report
Report from = 1;
// The current report
Report to = 2;
}
// Changes between two reports
message Changes {
// The progress info for the previous report
Measures from = 1;
// The progress info for the current report
Measures to = 2;
// Units that changed
repeated ChangeUnit units = 3;
}
// A changed unit
message ChangeUnit {
// The name of the unit
string name = 1;
// The previous progress info (omitted if new)
optional Measures from = 2;
// The current progress info (omitted if removed)
optional Measures to = 3;
// Sections that changed
repeated ChangeItem sections = 4;
// Functions that changed
repeated ChangeItem functions = 5;
// Extra metadata for this unit
optional ReportUnitMetadata metadata = 6;
}
// A changed section or function
message ChangeItem {
// The name of the item
string name = 1;
// The previous progress info (omitted if new)
optional ChangeItemInfo from = 2;
// The current progress info (omitted if removed)
optional ChangeItemInfo to = 3;
// Extra metadata for this item
optional ReportItemMetadata metadata = 4;
}
// Progress info for a section or function
message ChangeItemInfo {
// The overall match percent for this item
float fuzzy_match_percent = 1;
// The size of the item in bytes
uint64 size = 2;
}
+25 -8
View File
@@ -750,8 +750,11 @@ impl FunctionDiffUi {
base_color = COLOR_ROTATION[diff.idx % COLOR_ROTATION.len()]
}
}
DiffText::BranchDest(addr) => {
DiffText::BranchDest(addr, diff) => {
label_text = format!("{addr:x}");
if let Some(diff) = diff {
base_color = COLOR_ROTATION[diff.idx % COLOR_ROTATION.len()]
}
}
DiffText::Symbol(sym) => {
let name = sym.demangled_name.as_ref().unwrap_or(&sym.name);
@@ -809,21 +812,35 @@ impl FunctionDiffUi {
fn reload(&mut self) -> Result<()> {
let prev = self.right_obj.take();
let config = diff::DiffObjConfig {
relax_reloc_diffs: self.relax_reloc_diffs,
space_between_args: true, // TODO
combine_data_sections: false, // TODO
x86_formatter: Default::default(), // TODO
mips_abi: Default::default(), // TODO
mips_instr_category: Default::default(), // TODO
arm_arch_version: Default::default(), // TODO
arm_unified_syntax: true, // TODO
arm_av_registers: false, // TODO
arm_r9_usage: Default::default(), // TODO
arm_sl_usage: false, // TODO
arm_fp_usage: false, // TODO
arm_ip_usage: false, // TODO
};
let target = self
.target_path
.as_deref()
.map(|p| obj::read::read(p).with_context(|| format!("Loading {}", p.display())))
.map(|p| {
obj::read::read(p, &config).with_context(|| format!("Loading {}", p.display()))
})
.transpose()?;
let base = self
.base_path
.as_deref()
.map(|p| obj::read::read(p).with_context(|| format!("Loading {}", p.display())))
.map(|p| {
obj::read::read(p, &config).with_context(|| format!("Loading {}", p.display()))
})
.transpose()?;
let config = diff::DiffObjConfig {
relax_reloc_diffs: self.relax_reloc_diffs,
space_between_args: true, // TODO
x86_formatter: Default::default(), // TODO
};
let result = diff::diff_objs(&config, target.as_ref(), base.as_ref(), prev.as_ref())?;
let left_sym = target.as_ref().and_then(|o| find_function(o, &self.symbol_name));
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -96,7 +96,7 @@ fn main() {
// Try to enable ANSI support on Windows.
let _ = enable_ansi_support();
// Disable isatty check for supports-color. (e.g. when used with ninja)
env::set_var("IGNORE_IS_TERMINAL", "1");
unsafe { env::set_var("IGNORE_IS_TERMINAL", "1") };
supports_color::on(Stream::Stdout).is_some_and(|c| c.has_basic)
};
+1
View File
@@ -1 +1,2 @@
pub mod report;
pub mod term;
+226
View File
@@ -0,0 +1,226 @@
use anyhow::{bail, Result};
use prost::Message;
use serde_json::error::Category;
// Protobuf report types
include!(concat!(env!("OUT_DIR"), "/objdiff.report.rs"));
include!(concat!(env!("OUT_DIR"), "/objdiff.report.serde.rs"));
impl Report {
pub fn parse(data: &[u8]) -> Result<Self> {
if data.is_empty() {
bail!(std::io::Error::from(std::io::ErrorKind::UnexpectedEof));
}
if data[0] == b'{' {
// Load as JSON
Self::from_json(data).map_err(anyhow::Error::new)
} else {
// Load as binary protobuf
Self::decode(data).map_err(anyhow::Error::new)
}
}
fn from_json(bytes: &[u8]) -> Result<Self, serde_json::Error> {
match serde_json::from_slice::<Self>(bytes) {
Ok(report) => Ok(report),
Err(e) => {
match e.classify() {
Category::Io | Category::Eof | Category::Syntax => Err(e),
Category::Data => {
// Try to load as legacy report
match serde_json::from_slice::<LegacyReport>(bytes) {
Ok(legacy_report) => Ok(Report::from(legacy_report)),
Err(_) => Err(e),
}
}
}
}
}
}
}
impl Measures {
/// Average the fuzzy match percentage over total code bytes.
pub fn calc_fuzzy_match_percent(&mut self) {
if self.total_code == 0 {
self.fuzzy_match_percent = 100.0;
} else {
self.fuzzy_match_percent /= self.total_code as f32;
}
}
/// Calculate the percentage of matched code, data, and functions.
pub fn calc_matched_percent(&mut self) {
self.matched_code_percent = if self.total_code == 0 {
100.0
} else {
self.matched_code as f32 / self.total_code as f32 * 100.0
};
self.matched_data_percent = if self.total_data == 0 {
100.0
} else {
self.matched_data as f32 / self.total_data as f32 * 100.0
};
self.matched_functions_percent = if self.total_functions == 0 {
100.0
} else {
self.matched_functions as f32 / self.total_functions as f32 * 100.0
};
}
}
/// Allows [collect](Iterator::collect) to be used on an iterator of [Measures].
impl FromIterator<Measures> for Measures {
fn from_iter<T>(iter: T) -> Self
where T: IntoIterator<Item = Measures> {
let mut measures = Measures::default();
for other in iter {
measures.fuzzy_match_percent += other.fuzzy_match_percent * other.total_code as f32;
measures.total_code += other.total_code;
measures.matched_code += other.matched_code;
measures.total_data += other.total_data;
measures.matched_data += other.matched_data;
measures.total_functions += other.total_functions;
measures.matched_functions += other.matched_functions;
}
measures.calc_fuzzy_match_percent();
measures.calc_matched_percent();
measures
}
}
// Older JSON report types
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
struct LegacyReport {
fuzzy_match_percent: f32,
total_code: u64,
matched_code: u64,
matched_code_percent: f32,
total_data: u64,
matched_data: u64,
matched_data_percent: f32,
total_functions: u32,
matched_functions: u32,
matched_functions_percent: f32,
units: Vec<LegacyReportUnit>,
}
impl From<LegacyReport> for Report {
fn from(value: LegacyReport) -> Self {
Self {
measures: Some(Measures {
fuzzy_match_percent: value.fuzzy_match_percent,
total_code: value.total_code,
matched_code: value.matched_code,
matched_code_percent: value.matched_code_percent,
total_data: value.total_data,
matched_data: value.matched_data,
matched_data_percent: value.matched_data_percent,
total_functions: value.total_functions,
matched_functions: value.matched_functions,
matched_functions_percent: value.matched_functions_percent,
}),
units: value.units.into_iter().map(ReportUnit::from).collect(),
}
}
}
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
struct LegacyReportUnit {
name: String,
fuzzy_match_percent: f32,
total_code: u64,
matched_code: u64,
total_data: u64,
matched_data: u64,
total_functions: u32,
matched_functions: u32,
#[serde(skip_serializing_if = "Option::is_none")]
complete: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
module_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
module_id: Option<u32>,
sections: Vec<LegacyReportItem>,
functions: Vec<LegacyReportItem>,
}
impl From<LegacyReportUnit> for ReportUnit {
fn from(value: LegacyReportUnit) -> Self {
let mut measures = Measures {
fuzzy_match_percent: value.fuzzy_match_percent,
total_code: value.total_code,
matched_code: value.matched_code,
total_data: value.total_data,
matched_data: value.matched_data,
total_functions: value.total_functions,
matched_functions: value.matched_functions,
..Default::default()
};
measures.calc_matched_percent();
Self {
name: value.name.clone(),
measures: Some(measures),
sections: value.sections.into_iter().map(ReportItem::from).collect(),
functions: value.functions.into_iter().map(ReportItem::from).collect(),
metadata: Some(ReportUnitMetadata {
complete: value.complete,
module_name: value.module_name.clone(),
module_id: value.module_id,
..Default::default()
}),
}
}
}
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
struct LegacyReportItem {
name: String,
#[serde(skip_serializing_if = "Option::is_none")]
demangled_name: Option<String>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
serialize_with = "serialize_hex",
deserialize_with = "deserialize_hex"
)]
address: Option<u64>,
size: u64,
fuzzy_match_percent: f32,
}
impl From<LegacyReportItem> for ReportItem {
fn from(value: LegacyReportItem) -> Self {
Self {
name: value.name,
size: value.size,
fuzzy_match_percent: value.fuzzy_match_percent,
metadata: Some(ReportItemMetadata {
demangled_name: value.demangled_name,
virtual_address: value.address,
}),
}
}
}
fn serialize_hex<S>(x: &Option<u64>, s: S) -> Result<S::Ok, S::Error>
where S: serde::Serializer {
if let Some(x) = x {
s.serialize_str(&format!("{:#x}", x))
} else {
s.serialize_none()
}
}
fn deserialize_hex<'de, D>(d: D) -> Result<Option<u64>, D::Error>
where D: serde::Deserializer<'de> {
use serde::Deserialize;
let s = String::deserialize(d)?;
if s.is_empty() {
Ok(None)
} else if !s.starts_with("0x") {
Err(serde::de::Error::custom("expected hex string"))
} else {
u64::from_str_radix(&s[2..], 16).map(Some).map_err(serde::de::Error::custom)
}
}
+11 -4
View File
@@ -1,6 +1,6 @@
[package]
name = "objdiff-core"
version = "2.0.0-alpha.1"
version = "2.0.0-beta.4"
edition = "2021"
rust-version = "1.70"
authors = ["Luke Street <luke@street.dev>"]
@@ -12,13 +12,14 @@ A local diffing tool for decompilation projects.
"""
[features]
all = ["config", "dwarf", "mips", "ppc", "x86"]
all = ["config", "dwarf", "mips", "ppc", "x86", "arm"]
any-arch = [] # Implicit, used to check if any arch is enabled
config = ["globset", "semver", "serde_json", "serde_yaml"]
dwarf = ["gimli"]
mips = ["any-arch", "rabbitizer"]
ppc = ["any-arch", "cwdemangle", "ppc750cl"]
ppc = ["any-arch", "cwdemangle", "cwextab", "ppc750cl"]
x86 = ["any-arch", "cpp_demangle", "iced-x86", "msvc-demangler"]
arm = ["any-arch", "cpp_demangle", "unarm", "arm-attr"]
[dependencies]
anyhow = "1.0.82"
@@ -31,6 +32,7 @@ num-traits = "0.2.18"
object = { version = "0.35.0", features = ["read_core", "std", "elf", "pe"], default-features = false }
serde = { version = "1", features = ["derive"] }
similar = { version = "2.5.0", default-features = false }
strum = { version = "0.26.2", features = ["derive"] }
# config
globset = { version = "0.4.14", features = ["serde1"], optional = true }
@@ -43,12 +45,17 @@ gimli = { version = "0.29.0", default-features = false, features = ["read-all"],
# ppc
cwdemangle = { version = "1.0.0", optional = true }
cwextab = { version = "0.2.3", optional = true }
ppc750cl = { git = "https://github.com/encounter/ppc750cl", rev = "6cbd7d888c7082c2c860f66cbb9848d633f753ed", optional = true }
# mips
rabbitizer = { version = "1.10.0", optional = true }
rabbitizer = { version = "1.11.0", optional = true }
# x86
cpp_demangle = { version = "0.4.3", optional = true }
iced-x86 = { version = "1.21.0", default-features = false, features = ["std", "decoder", "intel", "gas", "masm", "nasm", "exhaustive_enums"], optional = true }
msvc-demangler = { version = "0.10.0", optional = true }
# arm
unarm = { version = "1.4.0", optional = true }
arm-attr = { version = "0.1.1", optional = true }
+440
View File
@@ -0,0 +1,440 @@
use std::{
borrow::Cow,
collections::{BTreeMap, HashMap},
};
use anyhow::{bail, Result};
use arm_attr::{enums::CpuArch, tag::Tag, BuildAttrs};
use object::{
elf::{self, SHT_ARM_ATTRIBUTES},
Endian, File, Object, ObjectSection, ObjectSymbol, Relocation, RelocationFlags, SectionIndex,
SectionKind, Symbol, SymbolKind,
};
use unarm::{
args::{Argument, OffsetImm, OffsetReg, Register},
parse::{ArmVersion, ParseMode, Parser},
DisplayOptions, ParseFlags, ParsedIns, RegNames,
};
use crate::{
arch::{ObjArch, ProcessCodeResult},
diff::{ArmArchVersion, ArmR9Usage, DiffObjConfig},
obj::{ObjIns, ObjInsArg, ObjInsArgValue, ObjReloc, ObjSection},
};
pub struct ObjArchArm {
/// Maps section index, to list of disasm modes (arm, thumb or data) sorted by address
disasm_modes: HashMap<SectionIndex, Vec<DisasmMode>>,
detected_version: Option<ArmVersion>,
endianness: object::Endianness,
}
impl ObjArchArm {
pub fn new(file: &File) -> Result<Self> {
let endianness = file.endianness();
match file {
File::Elf32(_) => {
let disasm_modes = Self::elf_get_mapping_symbols(file);
let detected_version = Self::elf_detect_arm_version(file)?;
Ok(Self { disasm_modes, detected_version, endianness })
}
_ => bail!("Unsupported file format {:?}", file.format()),
}
}
fn elf_detect_arm_version(file: &File) -> Result<Option<ArmVersion>> {
// Check ARM attributes
if let Some(arm_attrs) = file.sections().find(|s| {
s.kind() == SectionKind::Elf(SHT_ARM_ATTRIBUTES) && s.name() == Ok(".ARM.attributes")
}) {
let attr_data = arm_attrs.uncompressed_data()?;
let build_attrs = BuildAttrs::new(&attr_data, match file.endianness() {
object::Endianness::Little => arm_attr::Endian::Little,
object::Endianness::Big => arm_attr::Endian::Big,
})?;
for subsection in build_attrs.subsections() {
let subsection = subsection?;
if !subsection.is_aeabi() {
continue;
}
// Only checking first CpuArch tag. Others may exist, but that's very unlikely.
let cpu_arch = subsection.into_public_tag_iter()?.find_map(|(_, tag)| {
if let Tag::CpuArch(cpu_arch) = tag {
Some(cpu_arch)
} else {
None
}
});
match cpu_arch {
Some(CpuArch::V4T) => return Ok(Some(ArmVersion::V4T)),
Some(CpuArch::V5TE) => return Ok(Some(ArmVersion::V5Te)),
Some(CpuArch::V6K) => return Ok(Some(ArmVersion::V6K)),
Some(arch) => bail!("ARM arch {} not supported", arch),
None => {}
};
}
}
Ok(None)
}
fn elf_get_mapping_symbols(file: &File) -> HashMap<SectionIndex, Vec<DisasmMode>> {
file.sections()
.filter(|s| s.kind() == SectionKind::Text)
.map(|s| {
let index = s.index();
let mut mapping_symbols: Vec<_> = file
.symbols()
.filter(|s| s.section_index().map(|i| i == index).unwrap_or(false))
.filter_map(|s| DisasmMode::from_symbol(&s))
.collect();
mapping_symbols.sort_unstable_by_key(|x| x.address);
(s.index(), mapping_symbols)
})
.collect()
}
}
impl ObjArch for ObjArchArm {
fn symbol_address(&self, symbol: &Symbol) -> u64 {
let address = symbol.address();
if symbol.kind() == SymbolKind::Text {
address & !1
} else {
address
}
}
fn process_code(
&self,
address: u64,
code: &[u8],
section_index: usize,
relocations: &[ObjReloc],
line_info: &BTreeMap<u64, u64>,
config: &DiffObjConfig,
) -> Result<ProcessCodeResult> {
let start_addr = address as u32;
let end_addr = start_addr + code.len() as u32;
// Mapping symbols decide what kind of data comes after it. $a for ARM code, $t for Thumb code and $d for data.
let fallback_mappings = [DisasmMode { address: start_addr, mapping: ParseMode::Arm }];
let mapping_symbols = self
.disasm_modes
.get(&SectionIndex(section_index))
.map(|x| x.as_slice())
.unwrap_or(&fallback_mappings);
let first_mapping_idx =
match mapping_symbols.binary_search_by_key(&start_addr, |x| x.address) {
Ok(idx) => idx,
Err(idx) => idx - 1,
};
let first_mapping = mapping_symbols[first_mapping_idx].mapping;
let mut mappings_iter =
mapping_symbols.iter().skip(first_mapping_idx + 1).take_while(|x| x.address < end_addr);
let mut next_mapping = mappings_iter.next();
let ins_count = code.len() / first_mapping.instruction_size(start_addr);
let mut ops = Vec::<u16>::with_capacity(ins_count);
let mut insts = Vec::<ObjIns>::with_capacity(ins_count);
let version = match config.arm_arch_version {
ArmArchVersion::Auto => self.detected_version.unwrap_or(ArmVersion::V5Te),
ArmArchVersion::V4T => ArmVersion::V4T,
ArmArchVersion::V5TE => ArmVersion::V5Te,
ArmArchVersion::V6K => ArmVersion::V6K,
};
let endian = match self.endianness {
object::Endianness::Little => unarm::Endian::Little,
object::Endianness::Big => unarm::Endian::Big,
};
let parse_flags = ParseFlags { ual: config.arm_unified_syntax };
let mut parser = Parser::new(version, first_mapping, start_addr, endian, parse_flags, code);
let display_options = DisplayOptions {
reg_names: RegNames {
av_registers: config.arm_av_registers,
r9_use: match config.arm_r9_usage {
ArmR9Usage::GeneralPurpose => unarm::R9Use::GeneralPurpose,
ArmR9Usage::Sb => unarm::R9Use::Pid,
ArmR9Usage::Tr => unarm::R9Use::Tls,
},
explicit_stack_limit: config.arm_sl_usage,
frame_pointer: config.arm_fp_usage,
ip: config.arm_ip_usage,
},
};
while let Some((address, op, ins)) = parser.next() {
if let Some(next) = next_mapping {
let next_address = parser.address;
if next_address >= next.address {
// Change mapping
parser.mode = next.mapping;
next_mapping = mappings_iter.next();
}
}
let line = line_info.range(..=address as u64).last().map(|(_, &b)| b);
let reloc = relocations.iter().find(|r| (r.address as u32 & !1) == address).cloned();
let mut reloc_arg = None;
if let Some(reloc) = &reloc {
match reloc.flags {
// Calls
RelocationFlags::Elf { r_type: elf::R_ARM_THM_XPC22 }
| RelocationFlags::Elf { r_type: elf::R_ARM_THM_PC22 }
| RelocationFlags::Elf { r_type: elf::R_ARM_PC24 }
| RelocationFlags::Elf { r_type: elf::R_ARM_XPC25 }
| RelocationFlags::Elf { r_type: elf::R_ARM_CALL } => {
reloc_arg =
ins.args.iter().rposition(|a| matches!(a, Argument::BranchDest(_)));
}
// Data
RelocationFlags::Elf { r_type: elf::R_ARM_ABS32 } => {
reloc_arg = ins.args.iter().rposition(|a| matches!(a, Argument::UImm(_)));
}
_ => (),
}
};
let (args, branch_dest) = if reloc.is_some() && parser.mode == ParseMode::Data {
(vec![ObjInsArg::Reloc], None)
} else {
push_args(&ins, config, reloc_arg, address, display_options)?
};
ops.push(op.id());
insts.push(ObjIns {
address: address as u64,
size: (parser.address - address) as u8,
op: op.id(),
mnemonic: ins.mnemonic.to_string(),
args,
reloc,
branch_dest,
line,
formatted: ins.display(display_options).to_string(),
orig: None,
});
}
Ok(ProcessCodeResult { ops, insts })
}
fn implcit_addend(
&self,
_file: &File<'_>,
section: &ObjSection,
address: u64,
reloc: &Relocation,
) -> anyhow::Result<i64> {
let address = address as usize;
Ok(match reloc.flags() {
// ARM calls
RelocationFlags::Elf { r_type: elf::R_ARM_PC24 }
| RelocationFlags::Elf { r_type: elf::R_ARM_XPC25 }
| RelocationFlags::Elf { r_type: elf::R_ARM_CALL } => {
let data = section.data[address..address + 4].try_into()?;
let addend = self.endianness.read_i32_bytes(data);
let imm24 = addend & 0xffffff;
(imm24 << 2) << 8 >> 8
}
// Thumb calls
RelocationFlags::Elf { r_type: elf::R_ARM_THM_PC22 }
| RelocationFlags::Elf { r_type: elf::R_ARM_THM_XPC22 } => {
let data = section.data[address..address + 2].try_into()?;
let high = self.endianness.read_i16_bytes(data) as i32;
let data = section.data[address + 2..address + 4].try_into()?;
let low = self.endianness.read_i16_bytes(data) as i32;
let imm22 = ((high & 0x7ff) << 11) | (low & 0x7ff);
(imm22 << 1) << 9 >> 9
}
// Data
RelocationFlags::Elf { r_type: elf::R_ARM_ABS32 } => {
let data = section.data[address..address + 4].try_into()?;
self.endianness.read_i32_bytes(data)
}
flags => bail!("Unsupported ARM implicit relocation {flags:?}"),
} as i64)
}
fn demangle(&self, name: &str) -> Option<String> {
cpp_demangle::Symbol::new(name)
.ok()
.and_then(|s| s.demangle(&cpp_demangle::DemangleOptions::default()).ok())
}
fn display_reloc(&self, flags: RelocationFlags) -> Cow<'static, str> {
Cow::Owned(format!("<{flags:?}>"))
}
}
#[derive(Clone, Copy, Debug)]
struct DisasmMode {
address: u32,
mapping: ParseMode,
}
impl DisasmMode {
fn from_symbol<'a>(sym: &Symbol<'a, '_, &'a [u8]>) -> Option<Self> {
if let Ok(name) = sym.name() {
ParseMode::from_mapping_symbol(name)
.map(|mapping| DisasmMode { address: sym.address() as u32, mapping })
} else {
None
}
}
}
fn push_args(
parsed_ins: &ParsedIns,
config: &DiffObjConfig,
reloc_arg: Option<usize>,
cur_addr: u32,
display_options: DisplayOptions,
) -> Result<(Vec<ObjInsArg>, Option<u64>)> {
let mut args = vec![];
let mut branch_dest = None;
let mut writeback = false;
let mut deref = false;
for (i, arg) in parsed_ins.args_iter().enumerate() {
// Emit punctuation before separator
if deref {
match arg {
Argument::OffsetImm(OffsetImm { post_indexed: true, value: _ })
| Argument::OffsetReg(OffsetReg { add: _, post_indexed: true, reg: _ })
| Argument::CoOption(_) => {
deref = false;
args.push(ObjInsArg::PlainText("]".into()));
if writeback {
writeback = false;
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque("!".into())));
}
}
_ => {}
}
}
if i > 0 {
args.push(ObjInsArg::PlainText(config.separator().into()));
}
if reloc_arg == Some(i) {
args.push(ObjInsArg::Reloc);
} else {
match arg {
Argument::None => {}
Argument::Reg(reg) => {
if reg.deref {
deref = true;
args.push(ObjInsArg::PlainText("[".into()));
}
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(
reg.reg.display(display_options.reg_names).to_string().into(),
)));
if reg.writeback {
if reg.deref {
writeback = true;
} else {
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque("!".into())));
}
}
}
Argument::RegList(reg_list) => {
args.push(ObjInsArg::PlainText("{".into()));
let mut first = true;
for i in 0..16 {
if (reg_list.regs & (1 << i)) != 0 {
if !first {
args.push(ObjInsArg::PlainText(config.separator().into()));
}
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(
Register::parse(i)
.display(display_options.reg_names)
.to_string()
.into(),
)));
first = false;
}
}
args.push(ObjInsArg::PlainText("}".into()));
if reg_list.user_mode {
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque("^".to_string().into())));
}
}
Argument::UImm(value) | Argument::CoOpcode(value) | Argument::SatImm(value) => {
args.push(ObjInsArg::PlainText("#".into()));
args.push(ObjInsArg::Arg(ObjInsArgValue::Unsigned(*value as u64)));
}
Argument::SImm(value)
| Argument::OffsetImm(OffsetImm { post_indexed: _, value }) => {
args.push(ObjInsArg::PlainText("#".into()));
args.push(ObjInsArg::Arg(ObjInsArgValue::Signed(*value as i64)));
}
Argument::BranchDest(value) => {
let dest = cur_addr.wrapping_add_signed(*value) as u64;
args.push(ObjInsArg::BranchDest(dest));
branch_dest = Some(dest);
}
Argument::CoOption(value) => {
args.push(ObjInsArg::PlainText("{".into()));
args.push(ObjInsArg::Arg(ObjInsArgValue::Unsigned(*value as u64)));
args.push(ObjInsArg::PlainText("}".into()));
}
Argument::CoprocNum(value) => {
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(format!("p{}", value).into())));
}
Argument::ShiftImm(shift) => {
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(shift.op.to_string().into())));
args.push(ObjInsArg::PlainText(" #".into()));
args.push(ObjInsArg::Arg(ObjInsArgValue::Unsigned(shift.imm as u64)));
}
Argument::ShiftReg(shift) => {
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(shift.op.to_string().into())));
args.push(ObjInsArg::PlainText(" ".into()));
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(
shift.reg.display(display_options.reg_names).to_string().into(),
)));
}
Argument::OffsetReg(offset) => {
if !offset.add {
args.push(ObjInsArg::PlainText("-".into()));
}
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(
offset.reg.display(display_options.reg_names).to_string().into(),
)));
}
Argument::CpsrMode(mode) => {
args.push(ObjInsArg::PlainText("#".into()));
args.push(ObjInsArg::Arg(ObjInsArgValue::Unsigned(mode.mode as u64)));
if mode.writeback {
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque("!".into())));
}
}
Argument::CoReg(_)
| Argument::StatusReg(_)
| Argument::StatusMask(_)
| Argument::Shift(_)
| Argument::CpsrFlags(_)
| Argument::Endian(_) => args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(
arg.display(display_options).to_string().into(),
))),
}
}
}
if deref {
args.push(ObjInsArg::PlainText("]".into()));
if writeback {
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque("!".into())));
}
}
Ok((args, branch_dest))
}
+133 -32
View File
@@ -1,54 +1,119 @@
use std::borrow::Cow;
use std::{borrow::Cow, collections::BTreeMap, sync::Mutex};
use anyhow::{anyhow, bail, Result};
use object::{elf, Endian, Endianness, File, Object, Relocation, RelocationFlags};
use object::{
elf, Endian, Endianness, File, FileFlags, Object, ObjectSection, ObjectSymbol, Relocation,
RelocationFlags, RelocationTarget,
};
use rabbitizer::{config, Abi, InstrCategory, Instruction, OperandType};
use crate::{
arch::{ObjArch, ProcessCodeResult},
diff::DiffObjConfig,
obj::{ObjInfo, ObjIns, ObjInsArg, ObjInsArgValue, ObjReloc, ObjSection, SymbolRef},
diff::{DiffObjConfig, MipsAbi, MipsInstrCategory},
obj::{ObjIns, ObjInsArg, ObjInsArgValue, ObjReloc, ObjSection},
};
fn configure_rabbitizer() {
static RABBITIZER_MUTEX: Mutex<()> = Mutex::new(());
fn configure_rabbitizer(abi: Abi) {
unsafe {
config::RabbitizerConfig_Cfg.reg_names.fpr_abi_names = Abi::O32;
config::RabbitizerConfig_Cfg.reg_names.fpr_abi_names = abi;
}
}
pub struct ObjArchMips {
pub endianness: Endianness,
pub abi: Abi,
pub instr_category: InstrCategory,
pub ri_gp_value: i32,
}
const EF_MIPS_ABI: u32 = 0x0000F000;
const EF_MIPS_MACH: u32 = 0x00FF0000;
const EF_MIPS_MACH_ALLEGREX: u32 = 0x00840000;
const EF_MIPS_MACH_5900: u32 = 0x00920000;
const R_MIPS15_S3: u32 = 119;
impl ObjArchMips {
pub fn new(object: &File) -> Result<Self> {
configure_rabbitizer();
Ok(Self { endianness: object.endianness() })
let mut abi = Abi::NUMERIC;
let mut instr_category = InstrCategory::CPU;
match object.flags() {
FileFlags::None => {}
FileFlags::Elf { e_flags, .. } => {
abi = match e_flags & EF_MIPS_ABI {
elf::EF_MIPS_ABI_O32 | elf::EF_MIPS_ABI_O64 => Abi::O32,
elf::EF_MIPS_ABI_EABI32 | elf::EF_MIPS_ABI_EABI64 => Abi::N32,
_ => {
if e_flags & elf::EF_MIPS_ABI2 != 0 {
Abi::N32
} else {
Abi::NUMERIC
}
}
};
instr_category = match e_flags & EF_MIPS_MACH {
EF_MIPS_MACH_ALLEGREX => InstrCategory::R4000ALLEGREX,
EF_MIPS_MACH_5900 => InstrCategory::R5900,
_ => InstrCategory::CPU,
};
}
_ => bail!("Unsupported MIPS file flags"),
}
// Parse the ri_gp_value stored in .reginfo to be able to correctly
// calculate R_MIPS_GPREL16 relocations later. The value is stored
// 0x14 bytes into .reginfo (on 32 bit platforms)
let ri_gp_value = object
.section_by_name(".reginfo")
.and_then(|section| section.data().ok())
.and_then(|data| data.get(0x14..0x18))
.and_then(|s| s.try_into().ok())
.map(|bytes| object.endianness().read_i32_bytes(bytes))
.unwrap_or(0);
Ok(Self { endianness: object.endianness(), abi, instr_category, ri_gp_value })
}
}
impl ObjArch for ObjArchMips {
fn process_code(
&self,
obj: &ObjInfo,
symbol_ref: SymbolRef,
address: u64,
code: &[u8],
_section_index: usize,
relocations: &[ObjReloc],
line_info: &BTreeMap<u64, u64>,
config: &DiffObjConfig,
) -> Result<ProcessCodeResult> {
let (section, symbol) = obj.section_symbol(symbol_ref);
let section = section.ok_or_else(|| anyhow!("Code symbol section not found"))?;
let code = &section.data
[symbol.section_address as usize..(symbol.section_address + symbol.size) as usize];
let _guard = RABBITIZER_MUTEX.lock().map_err(|e| anyhow!("Failed to lock mutex: {e}"))?;
configure_rabbitizer(match config.mips_abi {
MipsAbi::Auto => self.abi,
MipsAbi::O32 => Abi::O32,
MipsAbi::N32 => Abi::N32,
MipsAbi::N64 => Abi::N64,
});
let instr_category = match config.mips_instr_category {
MipsInstrCategory::Auto => self.instr_category,
MipsInstrCategory::Cpu => InstrCategory::CPU,
MipsInstrCategory::Rsp => InstrCategory::RSP,
MipsInstrCategory::R3000Gte => InstrCategory::R3000GTE,
MipsInstrCategory::R4000Allegrex => InstrCategory::R4000ALLEGREX,
MipsInstrCategory::R5900 => InstrCategory::R5900,
};
let start_address = symbol.address;
let end_address = symbol.address + symbol.size;
let start_address = address;
let end_address = address + code.len() as u64;
let ins_count = code.len() / 4;
let mut ops = Vec::<u16>::with_capacity(ins_count);
let mut insts = Vec::<ObjIns>::with_capacity(ins_count);
let mut cur_addr = start_address as u32;
for chunk in code.chunks_exact(4) {
let reloc = section.relocations.iter().find(|r| (r.address as u32 & !3) == cur_addr);
let reloc = relocations.iter().find(|r| (r.address as u32 & !3) == cur_addr);
let code = self.endianness.read_u32_bytes(chunk.try_into()?);
let instruction = Instruction::new(code, cur_addr, InstrCategory::CPU);
let instruction = Instruction::new(code, cur_addr, instr_category);
let formatted = instruction.disassemble(None, 0);
let op = instruction.unique_id as u16;
@@ -57,7 +122,7 @@ impl ObjArch for ObjArchMips {
let mnemonic = instruction.opcode_name().to_string();
let is_branch = instruction.is_branch();
let branch_offset = instruction.branch_offset();
let branch_dest = if is_branch {
let mut branch_dest = if is_branch {
cur_addr.checked_add_signed(branch_offset).map(|a| a as u64)
} else {
None
@@ -74,9 +139,7 @@ impl ObjArch for ObjArchMips {
OperandType::cpu_immediate
| OperandType::cpu_label
| OperandType::cpu_branch_target_label => {
if let Some(branch_dest) = branch_dest {
args.push(ObjInsArg::BranchDest(branch_dest));
} else if let Some(reloc) = reloc {
if let Some(reloc) = reloc {
if matches!(&reloc.target_section, Some(s) if s == ".text")
&& reloc.target.address > start_address
&& reloc.target.address < end_address
@@ -84,7 +147,10 @@ impl ObjArch for ObjArchMips {
args.push(ObjInsArg::BranchDest(reloc.target.address));
} else {
push_reloc(&mut args, reloc)?;
branch_dest = None;
}
} else if let Some(branch_dest) = branch_dest {
args.push(ObjInsArg::BranchDest(branch_dest));
} else {
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(
op.disassemble(&instruction, None).into(),
@@ -105,6 +171,18 @@ impl ObjArch for ObjArchMips {
)));
args.push(ObjInsArg::PlainText(")".into()));
}
// OperandType::r5900_immediate15 => match reloc {
// Some(reloc)
// if reloc.flags == RelocationFlags::Elf { r_type: R_MIPS15_S3 } =>
// {
// push_reloc(&mut args, reloc)?;
// }
// _ => {
// args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(
// op.disassemble(&instruction, None).into(),
// )));
// }
// },
_ => {
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(
op.disassemble(&instruction, None).into(),
@@ -112,7 +190,7 @@ impl ObjArch for ObjArchMips {
}
}
}
let line = section.line_info.range(..=cur_addr as u64).last().map(|(_, &b)| b);
let line = line_info.range(..=cur_addr as u64).last().map(|(_, &b)| b);
insts.push(ObjIns {
address: cur_addr as u64,
size: 4,
@@ -132,6 +210,7 @@ impl ObjArch for ObjArchMips {
fn implcit_addend(
&self,
file: &File<'_>,
section: &ObjSection,
address: u64,
reloc: &Relocation,
@@ -140,14 +219,29 @@ impl ObjArch for ObjArchMips {
let addend = self.endianness.read_u32_bytes(data);
Ok(match reloc.flags() {
RelocationFlags::Elf { r_type: elf::R_MIPS_32 } => addend as i64,
RelocationFlags::Elf { r_type: elf::R_MIPS_26 } => ((addend & 0x03FFFFFF) << 2) as i64,
RelocationFlags::Elf { r_type: elf::R_MIPS_HI16 } => {
((addend & 0x0000FFFF) << 16) as i32 as i64
}
RelocationFlags::Elf {
r_type:
elf::R_MIPS_LO16 | elf::R_MIPS_GOT16 | elf::R_MIPS_CALL16 | elf::R_MIPS_GPREL16,
r_type: elf::R_MIPS_LO16 | elf::R_MIPS_GOT16 | elf::R_MIPS_CALL16,
} => (addend & 0x0000FFFF) as i16 as i64,
RelocationFlags::Elf { r_type: elf::R_MIPS_26 } => ((addend & 0x03FFFFFF) << 2) as i64,
RelocationFlags::Elf { r_type: elf::R_MIPS_GPREL16 | elf::R_MIPS_LITERAL } => {
let RelocationTarget::Symbol(idx) = reloc.target() else {
bail!("Unsupported R_MIPS_GPREL16 relocation against a non-symbol");
};
let sym = file.symbol_by_index(idx)?;
// if the symbol we are relocating against is in a local section we need to add
// the ri_gp_value from .reginfo to the addend.
if sym.section().index().is_some() {
((addend & 0x0000FFFF) as i16 as i64) + self.ri_gp_value as i64
} else {
(addend & 0x0000FFFF) as i16 as i64
}
}
RelocationFlags::Elf { r_type: elf::R_MIPS_PC16 } => 0, // PC-relative relocation
RelocationFlags::Elf { r_type: R_MIPS15_S3 } => ((addend & 0x001FFFC0) >> 3) as i64,
flags => bail!("Unsupported MIPS implicit relocation {flags:?}"),
})
}
@@ -155,13 +249,16 @@ impl ObjArch for ObjArchMips {
fn display_reloc(&self, flags: RelocationFlags) -> Cow<'static, str> {
match flags {
RelocationFlags::Elf { r_type } => match r_type {
elf::R_MIPS_HI16 => Cow::Borrowed("R_MIPS_HI16"),
elf::R_MIPS_LO16 => Cow::Borrowed("R_MIPS_LO16"),
elf::R_MIPS_GOT16 => Cow::Borrowed("R_MIPS_GOT16"),
elf::R_MIPS_CALL16 => Cow::Borrowed("R_MIPS_CALL16"),
elf::R_MIPS_GPREL16 => Cow::Borrowed("R_MIPS_GPREL16"),
elf::R_MIPS_32 => Cow::Borrowed("R_MIPS_32"),
elf::R_MIPS_26 => Cow::Borrowed("R_MIPS_26"),
elf::R_MIPS_HI16 => Cow::Borrowed("R_MIPS_HI16"),
elf::R_MIPS_LO16 => Cow::Borrowed("R_MIPS_LO16"),
elf::R_MIPS_GPREL16 => Cow::Borrowed("R_MIPS_GPREL16"),
elf::R_MIPS_LITERAL => Cow::Borrowed("R_MIPS_LITERAL"),
elf::R_MIPS_GOT16 => Cow::Borrowed("R_MIPS_GOT16"),
elf::R_MIPS_PC16 => Cow::Borrowed("R_MIPS_PC16"),
elf::R_MIPS_CALL16 => Cow::Borrowed("R_MIPS_CALL16"),
R_MIPS15_S3 => Cow::Borrowed("R_MIPS15_S3"),
_ => Cow::Owned(format!("<{flags:?}>")),
},
_ => Cow::Owned(format!("<{flags:?}>")),
@@ -197,7 +294,11 @@ fn push_reloc(args: &mut Vec<ObjInsArg>, reloc: &ObjReloc) -> Result<()> {
args.push(ObjInsArg::Reloc);
args.push(ObjInsArg::PlainText(")".into()));
}
elf::R_MIPS_32 | elf::R_MIPS_26 => {
elf::R_MIPS_32
| elf::R_MIPS_26
| elf::R_MIPS_LITERAL
| elf::R_MIPS_PC16
| R_MIPS15_S3 => {
args.push(ObjInsArg::Reloc);
}
_ => bail!("Unsupported ELF MIPS relocation type {r_type}"),
+24 -10
View File
@@ -1,34 +1,46 @@
use std::borrow::Cow;
use std::{borrow::Cow, collections::BTreeMap};
use anyhow::{bail, Result};
use object::{Architecture, Object, Relocation, RelocationFlags};
use object::{Architecture, File, Object, ObjectSymbol, Relocation, RelocationFlags, Symbol};
use crate::{
diff::DiffObjConfig,
obj::{ObjInfo, ObjIns, ObjSection, SymbolRef},
obj::{ObjIns, ObjReloc, ObjSection},
};
#[cfg(feature = "arm")]
mod arm;
#[cfg(feature = "mips")]
mod mips;
pub mod mips;
#[cfg(feature = "ppc")]
mod ppc;
pub mod ppc;
#[cfg(feature = "x86")]
mod x86;
pub mod x86;
pub trait ObjArch: Send + Sync {
fn process_code(
&self,
obj: &ObjInfo,
symbol_ref: SymbolRef,
address: u64,
code: &[u8],
section_index: usize,
relocations: &[ObjReloc],
line_info: &BTreeMap<u64, u64>,
config: &DiffObjConfig,
) -> Result<ProcessCodeResult>;
fn implcit_addend(&self, section: &ObjSection, address: u64, reloc: &Relocation)
-> Result<i64>;
fn implcit_addend(
&self,
file: &File<'_>,
section: &ObjSection,
address: u64,
reloc: &Relocation,
) -> Result<i64>;
fn demangle(&self, _name: &str) -> Option<String> { None }
fn display_reloc(&self, flags: RelocationFlags) -> Cow<'static, str>;
fn symbol_address(&self, symbol: &Symbol) -> u64 { symbol.address() }
}
pub struct ProcessCodeResult {
@@ -44,6 +56,8 @@ pub fn new_arch(object: &object::File) -> Result<Box<dyn ObjArch>> {
Architecture::Mips => Box::new(mips::ObjArchMips::new(object)?),
#[cfg(feature = "x86")]
Architecture::I386 | Architecture::X86_64 => Box::new(x86::ObjArchX86::new(object)?),
#[cfg(feature = "arm")]
Architecture::Arm => Box::new(arm::ObjArchArm::new(object)?),
arch => bail!("Unsupported architecture: {arch:?}"),
})
}
+12 -13
View File
@@ -1,13 +1,13 @@
use std::borrow::Cow;
use std::{borrow::Cow, collections::BTreeMap};
use anyhow::{anyhow, bail, Result};
use anyhow::{bail, Result};
use object::{elf, File, Relocation, RelocationFlags};
use ppc750cl::{Argument, InsIter, GPR};
use crate::{
arch::{ObjArch, ProcessCodeResult},
diff::DiffObjConfig,
obj::{ObjInfo, ObjIns, ObjInsArg, ObjInsArgValue, ObjReloc, ObjSection, SymbolRef},
obj::{ObjIns, ObjInsArg, ObjInsArgValue, ObjReloc, ObjSection},
};
// Relative relocation, can be Simm, Offset or BranchDest
@@ -31,20 +31,18 @@ impl ObjArchPpc {
impl ObjArch for ObjArchPpc {
fn process_code(
&self,
obj: &ObjInfo,
symbol_ref: SymbolRef,
address: u64,
code: &[u8],
_section_index: usize,
relocations: &[ObjReloc],
line_info: &BTreeMap<u64, u64>,
config: &DiffObjConfig,
) -> Result<ProcessCodeResult> {
let (section, symbol) = obj.section_symbol(symbol_ref);
let section = section.ok_or_else(|| anyhow!("Code symbol section not found"))?;
let code = &section.data
[symbol.section_address as usize..(symbol.section_address + symbol.size) as usize];
let ins_count = code.len() / 4;
let mut ops = Vec::<u16>::with_capacity(ins_count);
let mut insts = Vec::<ObjIns>::with_capacity(ins_count);
for (cur_addr, mut ins) in InsIter::new(code, symbol.address as u32) {
let reloc = section.relocations.iter().find(|r| (r.address as u32 & !3) == cur_addr);
for (cur_addr, mut ins) in InsIter::new(code, address as u32) {
let reloc = relocations.iter().find(|r| (r.address as u32 & !3) == cur_addr);
if let Some(reloc) = reloc {
// Zero out relocations
ins.code = match reloc.flags {
@@ -133,7 +131,7 @@ impl ObjArch for ObjArchPpc {
}
ops.push(ins.op as u16);
let line = section.line_info.range(..=cur_addr as u64).last().map(|(_, &b)| b);
let line = line_info.range(..=cur_addr as u64).last().map(|(_, &b)| b);
insts.push(ObjIns {
address: cur_addr as u64,
size: 4,
@@ -152,6 +150,7 @@ impl ObjArch for ObjArchPpc {
fn implcit_addend(
&self,
_file: &File<'_>,
_section: &ObjSection,
address: u64,
reloc: &Relocation,
+11 -13
View File
@@ -1,4 +1,4 @@
use std::borrow::Cow;
use std::{borrow::Cow, collections::BTreeMap};
use anyhow::{anyhow, bail, ensure, Result};
use iced_x86::{
@@ -11,7 +11,7 @@ use object::{pe, Endian, Endianness, File, Object, Relocation, RelocationFlags};
use crate::{
arch::{ObjArch, ProcessCodeResult},
diff::{DiffObjConfig, X86Formatter},
obj::{ObjInfo, ObjIns, ObjInsArg, ObjInsArgValue, ObjSection, SymbolRef},
obj::{ObjIns, ObjInsArg, ObjInsArgValue, ObjReloc, ObjSection},
};
pub struct ObjArchX86 {
@@ -28,17 +28,15 @@ impl ObjArchX86 {
impl ObjArch for ObjArchX86 {
fn process_code(
&self,
obj: &ObjInfo,
symbol_ref: SymbolRef,
address: u64,
code: &[u8],
_section_index: usize,
relocations: &[ObjReloc],
line_info: &BTreeMap<u64, u64>,
config: &DiffObjConfig,
) -> Result<ProcessCodeResult> {
let (section, symbol) = obj.section_symbol(symbol_ref);
let section = section.ok_or_else(|| anyhow!("Code symbol section not found"))?;
let code = &section.data
[symbol.section_address as usize..(symbol.section_address + symbol.size) as usize];
let mut result = ProcessCodeResult { ops: Vec::new(), insts: Vec::new() };
let mut decoder = Decoder::with_ip(self.bits, code, symbol.address, DecoderOptions::NONE);
let mut decoder = Decoder::with_ip(self.bits, code, address, DecoderOptions::NONE);
let mut formatter: Box<dyn Formatter> = match config.x86_formatter {
X86Formatter::Intel => Box::new(IntelFormatter::new()),
X86Formatter::Gas => Box::new(GasFormatter::new()),
@@ -70,11 +68,10 @@ impl ObjArch for ObjArchX86 {
let address = instruction.ip();
let op = instruction.mnemonic() as u16;
let reloc = section
.relocations
let reloc = relocations
.iter()
.find(|r| r.address >= address && r.address < address + instruction.len() as u64);
let line = section.line_info.range(..=address).last().map(|(_, &b)| b);
let line = line_info.range(..=address).last().map(|(_, &b)| b);
output.ins = ObjIns {
address,
size: instruction.len() as u8,
@@ -131,6 +128,7 @@ impl ObjArch for ObjArchX86 {
fn implcit_addend(
&self,
_file: &File<'_>,
section: &ObjSection,
address: u64,
reloc: &Relocation,
+26 -12
View File
@@ -4,7 +4,7 @@ use std::{
time::{Duration, Instant},
};
use anyhow::Result;
use anyhow::{anyhow, Result};
use similar::{capture_diff_slices_deadline, Algorithm};
use crate::{
@@ -16,34 +16,48 @@ use crate::{
obj::{ObjInfo, ObjInsArg, ObjReloc, ObjSymbol, ObjSymbolFlags, SymbolRef},
};
pub fn no_diff_code(
pub fn process_code_symbol(
obj: &ObjInfo,
symbol_ref: SymbolRef,
config: &DiffObjConfig,
) -> Result<ObjSymbolDiff> {
let out = obj.arch.process_code(obj, symbol_ref, config)?;
) -> Result<ProcessCodeResult> {
let (section, symbol) = obj.section_symbol(symbol_ref);
let section = section.ok_or_else(|| anyhow!("Code symbol section not found"))?;
let code = &section.data
[symbol.section_address as usize..(symbol.section_address + symbol.size) as usize];
obj.arch.process_code(
symbol.address,
code,
section.orig_index,
&section.relocations,
&section.line_info,
config,
)
}
pub fn no_diff_code(out: &ProcessCodeResult, symbol_ref: SymbolRef) -> Result<ObjSymbolDiff> {
let mut diff = Vec::<ObjInsDiff>::new();
for i in out.insts {
diff.push(ObjInsDiff { ins: Some(i), kind: ObjInsDiffKind::None, ..Default::default() });
for i in &out.insts {
diff.push(ObjInsDiff {
ins: Some(i.clone()),
kind: ObjInsDiffKind::None,
..Default::default()
});
}
resolve_branches(&mut diff);
Ok(ObjSymbolDiff { symbol_ref, diff_symbol: None, instructions: diff, match_percent: None })
}
pub fn diff_code(
left_obj: &ObjInfo,
right_obj: &ObjInfo,
left_out: &ProcessCodeResult,
right_out: &ProcessCodeResult,
left_symbol_ref: SymbolRef,
right_symbol_ref: SymbolRef,
config: &DiffObjConfig,
) -> Result<(ObjSymbolDiff, ObjSymbolDiff)> {
let left_out = left_obj.arch.process_code(left_obj, left_symbol_ref, config)?;
let right_out = right_obj.arch.process_code(right_obj, right_symbol_ref, config)?;
let mut left_diff = Vec::<ObjInsDiff>::new();
let mut right_diff = Vec::<ObjInsDiff>::new();
diff_instructions(&mut left_diff, &mut right_diff, &left_out, &right_out)?;
diff_instructions(&mut left_diff, &mut right_diff, left_out, right_out)?;
resolve_branches(&mut left_diff);
resolve_branches(&mut right_diff);
+63 -45
View File
@@ -11,27 +11,6 @@ use crate::{
obj::{ObjInfo, ObjSection, SymbolRef},
};
/// Compare the addresses and sizes of each symbol in the BSS sections.
pub fn diff_bss_section(
left: &ObjSection,
right: &ObjSection,
) -> Result<(ObjSectionDiff, ObjSectionDiff)> {
let deadline = Instant::now() + Duration::from_secs(5);
let left_sizes = left.symbols.iter().map(|s| (s.section_address, s.size)).collect::<Vec<_>>();
let right_sizes = right.symbols.iter().map(|s| (s.section_address, s.size)).collect::<Vec<_>>();
let ops = capture_diff_slices_deadline(
Algorithm::Patience,
&left_sizes,
&right_sizes,
Some(deadline),
);
let match_percent = get_diff_ratio(&ops, left_sizes.len(), right_sizes.len()) * 100.0;
Ok((
ObjSectionDiff { symbols: vec![], data_diff: vec![], match_percent: Some(match_percent) },
ObjSectionDiff { symbols: vec![], data_diff: vec![], match_percent: Some(match_percent) },
))
}
pub fn diff_bss_symbol(
left_obj: &ObjInfo,
right_obj: &ObjInfo,
@@ -65,10 +44,14 @@ pub fn no_diff_symbol(_obj: &ObjInfo, symbol_ref: SymbolRef) -> ObjSymbolDiff {
pub fn diff_data_section(
left: &ObjSection,
right: &ObjSection,
left_section_diff: &ObjSectionDiff,
right_section_diff: &ObjSectionDiff,
) -> Result<(ObjSectionDiff, ObjSectionDiff)> {
let deadline = Instant::now() + Duration::from_secs(5);
let left_max = left.symbols.iter().map(|s| s.section_address + s.size).max().unwrap_or(0);
let right_max = right.symbols.iter().map(|s| s.section_address + s.size).max().unwrap_or(0);
let left_max =
left.symbols.iter().map(|s| s.section_address + s.size).max().unwrap_or(0).min(left.size);
let right_max =
right.symbols.iter().map(|s| s.section_address + s.size).max().unwrap_or(0).min(right.size);
let left_data = &left.data[..left_max as usize];
let right_data = &right.data[..right_max as usize];
let ops =
@@ -143,18 +126,18 @@ pub fn diff_data_section(
}
}
Ok((
ObjSectionDiff {
symbols: vec![],
data_diff: left_diff,
match_percent: Some(match_percent),
},
ObjSectionDiff {
symbols: vec![],
data_diff: right_diff,
match_percent: Some(match_percent),
},
))
let (mut left_section_diff, mut right_section_diff) =
diff_generic_section(left, right, left_section_diff, right_section_diff)?;
left_section_diff.data_diff = left_diff;
right_section_diff.data_diff = right_diff;
// Use the highest match percent between two options:
// - Left symbols matching right symbols by name
// - Diff of the data itself
if left_section_diff.match_percent.unwrap_or(-1.0) < match_percent {
left_section_diff.match_percent = Some(match_percent);
right_section_diff.match_percent = Some(match_percent);
}
Ok((left_section_diff, right_section_diff))
}
pub fn diff_data_symbol(
@@ -195,21 +178,56 @@ pub fn diff_data_symbol(
))
}
/// Compare the text sections of two object files.
/// This essentially adds up the match percentage of each symbol in the text section.
pub fn diff_text_section(
/// Compares a section of two object files.
/// This essentially adds up the match percentage of each symbol in the section.
pub fn diff_generic_section(
left: &ObjSection,
_right: &ObjSection,
left_diff: &ObjSectionDiff,
_right_diff: &ObjSectionDiff,
) -> Result<(ObjSectionDiff, ObjSectionDiff)> {
let match_percent = left
.symbols
.iter()
.zip(left_diff.symbols.iter())
.map(|(s, d)| d.match_percent.unwrap_or(0.0) * s.size as f32)
.sum::<f32>()
/ left.size as f32;
let match_percent = if left_diff.symbols.iter().all(|d| d.match_percent == Some(100.0)) {
100.0 // Avoid fp precision issues
} else {
left.symbols
.iter()
.zip(left_diff.symbols.iter())
.map(|(s, d)| d.match_percent.unwrap_or(0.0) * s.size as f32)
.sum::<f32>()
/ left.size as f32
};
Ok((
ObjSectionDiff { symbols: vec![], data_diff: vec![], match_percent: Some(match_percent) },
ObjSectionDiff { symbols: vec![], data_diff: vec![], match_percent: Some(match_percent) },
))
}
/// Compare the addresses and sizes of each symbol in the BSS sections.
pub fn diff_bss_section(
left: &ObjSection,
right: &ObjSection,
left_diff: &ObjSectionDiff,
right_diff: &ObjSectionDiff,
) -> Result<(ObjSectionDiff, ObjSectionDiff)> {
let deadline = Instant::now() + Duration::from_secs(5);
let left_sizes = left.symbols.iter().map(|s| (s.section_address, s.size)).collect::<Vec<_>>();
let right_sizes = right.symbols.iter().map(|s| (s.section_address, s.size)).collect::<Vec<_>>();
let ops = capture_diff_slices_deadline(
Algorithm::Patience,
&left_sizes,
&right_sizes,
Some(deadline),
);
let mut match_percent = get_diff_ratio(&ops, left_sizes.len(), right_sizes.len()) * 100.0;
// Use the highest match percent between two options:
// - Left symbols matching right symbols by name
// - Diff of the addresses and sizes of each symbol
let (generic_diff, _) = diff_generic_section(left, right, left_diff, right_diff)?;
if generic_diff.match_percent.unwrap_or(-1.0) > match_percent {
match_percent = generic_diff.match_percent.unwrap();
}
Ok((
ObjSectionDiff { symbols: vec![], data_diff: vec![], match_percent: Some(match_percent) },
ObjSectionDiff { symbols: vec![], data_diff: vec![], match_percent: Some(match_percent) },

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