Compare commits

..

17 Commits

Author SHA1 Message Date
Luke Street 737b3782db ci: Add id-token write permission 2025-05-06 22:51:11 -06:00
sozud 1d782243e0 Add data info for superh (#198)
* Add data info for superh

* fmt

* Fetch symbol data dynamically from resolved.section

* Remove unused var

---------

Co-authored-by: Luke Street <luke@street.dev>
2025-05-06 22:30:53 -06:00
Luke Street a1499f475d Update test snapshot 2025-05-06 22:07:21 -06:00
LagoLunatic f263e490e3 Combine data/text sections: Pad sections to alignment (#197)
* Combine data/text sections: Pad all sections to 4-byte minimum alignment

* Update x86 test snapshot

* Read and store object section alignment

* Combine data/text sections: Pad sections to more than 4-byte alignment if they have alignment specified
2025-05-06 21:47:08 -06:00
Aetias d0e6c5c057 Display branch destinations after PC-relative loads (#194) 2025-05-06 21:45:03 -06:00
LagoLunatic e1c51ac297 ARM: Fix "Combine text sections" confusing code and data (#195)
* ARM: Fix parsing of mapping symbols when "Combine text sections" is enabled

* Add test
2025-04-26 11:14:16 -06:00
Luke Street 39b1b49985 cli: Add --config arg to report generate 2025-04-22 18:13:12 -06:00
Luke Street 6c7160ab7e Use rabbitizer 2.0.0-alpha.1 2025-04-22 18:13:12 -06:00
sozud 644d4762f0 Preliminary SuperH support (#186)
* Preliminary SuperH support

* fixes

* clippy

* clippy
2025-04-17 14:20:30 -06:00
LagoLunatic b40fae5140 Allow copying Shift JIS encoded string literals (#189)
* Update openssl and tokio for Cargo deny

* Allow copying Shift JIS encoded strings

* Override data type label for Shift JIS strings
2025-04-17 10:07:05 -06:00
LagoLunatic fbf85632ab PPC: Detect unpooled string literal references (#188)
* Update openssl and tokio for Cargo deny

* PPC: Detect unpooled string literal references
2025-04-16 23:33:55 -06:00
Luke Street 73a89d2768 Version v3.0.0-beta.6 2025-04-02 11:03:40 -06:00
Luke Street a162c2f840 Fix to_string_in_format_args warning 2025-04-01 23:36:47 -06:00
Luke Street a474b27d55 Update dependencies 2025-04-01 23:33:17 -06:00
Luke Street 3d7f2b70dc Replace some git dependencies with cargo registry 2025-04-01 22:56:16 -06:00
Luke Street fe886f862d ci: Auto cargo publish / npm publish 2025-04-01 21:49:01 -06:00
Luke Street 2bcbc34850 wasm: Improve API error handling 2025-04-01 21:45:13 -06:00
42 changed files with 3599 additions and 556 deletions
+85 -8
View File
@@ -8,6 +8,10 @@ on:
- "LICENSE*"
workflow_dispatch:
permissions:
# For npm publish provenance
id-token: write
env:
BUILD_PROFILE: release-lto
CARGO_INCREMENTAL: 0
@@ -72,7 +76,7 @@ jobs:
name: Test
strategy:
matrix:
platform: [ubuntu-latest, windows-latest, macos-latest]
platform: [ ubuntu-latest, windows-latest, macos-latest ]
fail-fast: false
runs-on: ${{ matrix.platform }}
steps:
@@ -249,22 +253,30 @@ jobs:
components: rust-src
- name: Cache Rust workspace
uses: Swatinem/rust-cache@v2
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: lts/*
- name: Install dependencies
run: npm -C objdiff-wasm install
run: npm -C objdiff-wasm ci
- name: Build
run: npm -C objdiff-wasm run build
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: wasm
path: objdiff-wasm/dist/
if-no-files-found: error
release:
name: Release
check-version:
name: Check package versions
if: startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
needs: [build-cli, build-gui]
permissions:
contents: write
needs: [ build-cli, build-gui, build-wasm ]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Check git tag against Cargo version
- name: Check git tag against package versions
shell: bash
run: |
set -eou pipefail
@@ -276,9 +288,24 @@ jobs:
echo "::error::Git tag doesn't match the Cargo version! ($tag != $version)"
exit 1
fi
version="v$(jq -r .version objdiff-wasm/package.json)"
if [ "$tag" != "$version" ]; then
echo "::error::Git tag doesn't match the npm version! ($tag != $version)"
exit 1
fi
release-github:
name: Release (GitHub)
if: startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
needs: [ check-version ]
permissions:
contents: write
steps:
- name: Download artifacts
uses: actions/download-artifact@v4
with:
pattern: objdiff-*
path: artifacts
- name: Rename artifacts
working-directory: artifacts
@@ -308,3 +335,53 @@ jobs:
files: out/*
draft: true
generate_release_notes: true
release-cargo:
name: Release (Cargo)
if: 'false' # TODO re-enable when all dependencies are published
runs-on: ubuntu-latest
needs: [ check-version ]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Publish
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
run: cargo publish -p objdiff-core
release-npm:
name: Release (npm)
if: startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
needs: [ check-version ]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: lts/*
registry-url: 'https://registry.npmjs.org'
- name: Download artifacts
uses: actions/download-artifact@v4
with:
name: wasm
path: objdiff-wasm/dist
- name: Publish
working-directory: objdiff-wasm
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: |
set -euo pipefail
version=$(jq -r '.version' package.json)
tag="latest"
# Check for prerelease by looking for a dash
case "$version" in
*-*)
tag=$(echo "$version" | sed -e 's/^[^-]*-//' -e 's/\..*$//')
;;
esac
echo "Publishing version $version with tag '$tag'..."
npm publish --provenance --access public --tag "$tag"
Generated
+514 -320
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -14,7 +14,7 @@ strip = "debuginfo"
codegen-units = 1
[workspace.package]
version = "3.0.0-beta.5"
version = "3.0.0-beta.6"
authors = ["Luke Street <luke@street.dev>"]
edition = "2024"
license = "MIT OR Apache-2.0"
-2
View File
@@ -240,8 +240,6 @@ allow-git = []
[sources.allow-org]
# github.com organizations to allow git sources for
github = [
"CelestialAmber", # cwextab, rlwinmdec
"Decompollaborate", # rabbitizer
"enarx", # flagset
"encounter",
]
+3 -27
View File
@@ -1,7 +1,6 @@
use std::{
io::stdout,
mem,
str::FromStr,
sync::{
Arc,
atomic::{AtomicBool, Ordering},
@@ -29,10 +28,7 @@ use objdiff_core::{
ProjectConfig, ProjectObject, ProjectObjectMetadata, build_globset,
path::{check_path_buf, platform_path, platform_path_serde_option},
},
diff::{
self, ConfigEnum, ConfigPropertyId, ConfigPropertyKind, DiffObjConfig, MappingConfig,
ObjectDiff,
},
diff::{self, DiffObjConfig, MappingConfig, ObjectDiff},
jobs::{
Job, JobQueue, JobResult,
objdiff::{ObjDiffConfig, start_build},
@@ -43,6 +39,7 @@ use ratatui::prelude::*;
use typed_path::{Utf8PlatformPath, Utf8PlatformPathBuf};
use crate::{
cmd::apply_config_args,
util::{
output::{OutputFormat, write_output},
term::crossterm_panic_handler,
@@ -183,28 +180,7 @@ pub fn run(args: Args) -> Result<()> {
fn build_config_from_args(args: &Args) -> Result<(DiffObjConfig, MappingConfig)> {
let mut diff_config = DiffObjConfig::default();
for config in &args.config {
let (key, value) = config.split_once('=').context("--config expects \"key=value\"")?;
let property_id = ConfigPropertyId::from_str(key)
.map_err(|()| anyhow!("Invalid configuration property: {}", key))?;
diff_config.set_property_value_str(property_id, value).map_err(|()| {
let mut options = String::new();
match property_id.kind() {
ConfigPropertyKind::Boolean => {
options = "true, false".to_string();
}
ConfigPropertyKind::Choice(variants) => {
for (i, variant) in variants.iter().enumerate() {
if i > 0 {
options.push_str(", ");
}
options.push_str(variant.value);
}
}
}
anyhow!("Invalid value for {}. Expected one of: {}", property_id.name(), options)
})?;
}
apply_config_args(&mut diff_config, &args.config)?;
let mut mapping_config = MappingConfig {
mappings: Default::default(),
selecting_left: args.selecting_left.clone(),
+31
View File
@@ -1,2 +1,33 @@
pub mod diff;
pub mod report;
use std::str::FromStr;
use anyhow::{Context, Result, anyhow};
use objdiff_core::diff::{ConfigEnum, ConfigPropertyId, ConfigPropertyKind, DiffObjConfig};
pub fn apply_config_args(diff_config: &mut DiffObjConfig, args: &[String]) -> Result<()> {
for config in args {
let (key, value) = config.split_once('=').context("--config expects \"key=value\"")?;
let property_id = ConfigPropertyId::from_str(key)
.map_err(|()| anyhow!("Invalid configuration property: {}", key))?;
diff_config.set_property_value_str(property_id, value).map_err(|()| {
let mut options = String::new();
match property_id.kind() {
ConfigPropertyKind::Boolean => {
options = "true, false".to_string();
}
ConfigPropertyKind::Choice(variants) => {
for (i, variant) in variants.iter().enumerate() {
if i > 0 {
options.push_str(", ");
}
options.push_str(variant.value);
}
}
}
anyhow!("Invalid value for {}. Expected one of: {}", property_id.name(), options)
})?;
}
Ok(())
}
+17 -10
View File
@@ -17,7 +17,7 @@ use tracing::{info, warn};
use typed_path::{Utf8PlatformPath, Utf8PlatformPathBuf};
use crate::{
cmd::diff::ObjectConfig,
cmd::{apply_config_args, diff::ObjectConfig},
util::output::{OutputFormat, write_output},
};
@@ -52,6 +52,9 @@ pub struct GenerateArgs {
#[argp(option, short = 'f')]
/// Output format (json, json-pretty, proto) (default: json)
format: Option<String>,
#[argp(option, short = 'c')]
/// Configuration property (key=value)
config: Vec<String>,
}
#[derive(FromArgs, PartialEq, Debug)]
@@ -80,6 +83,12 @@ pub fn run(args: Args) -> Result<()> {
}
fn generate(args: GenerateArgs) -> Result<()> {
let mut diff_config = diff::DiffObjConfig {
function_reloc_diffs: diff::FunctionRelocDiffs::None,
..Default::default()
};
apply_config_args(&mut diff_config, &args.config)?;
let output_format = OutputFormat::from_option(args.format.as_deref())?;
let project_dir = args.project.as_deref().unwrap_or_else(|| Utf8PlatformPath::new("."));
info!("Loading project {}", project_dir);
@@ -114,14 +123,15 @@ fn generate(args: GenerateArgs) -> Result<()> {
if args.deduplicate {
// If deduplicating, we need to run single-threaded
for object in &objects {
if let Some(unit) = report_object(object, Some(&mut existing_functions))? {
if let Some(unit) = report_object(object, &diff_config, Some(&mut existing_functions))?
{
units.push(unit);
}
}
} else {
let vec = objects
.par_iter()
.map(|object| report_object(object, None))
.map(|object| report_object(object, &diff_config, None))
.collect::<Result<Vec<Option<ReportUnit>>>>()?;
units = vec.into_iter().flatten().collect();
}
@@ -145,6 +155,7 @@ fn generate(args: GenerateArgs) -> Result<()> {
fn report_object(
object: &ObjectConfig,
diff_config: &diff::DiffObjConfig,
mut existing_functions: Option<&mut HashSet<String>>,
) -> Result<Option<ReportUnit>> {
match (&object.target_path, &object.base_path) {
@@ -158,16 +169,12 @@ fn report_object(
}
_ => {}
}
let diff_config = diff::DiffObjConfig {
function_reloc_diffs: diff::FunctionRelocDiffs::None,
..Default::default()
};
let mapping_config = diff::MappingConfig::default();
let target = object
.target_path
.as_ref()
.map(|p| {
obj::read::read(p.as_ref(), &diff_config)
obj::read::read(p.as_ref(), diff_config)
.with_context(|| format!("Failed to open {}", p))
})
.transpose()?;
@@ -175,12 +182,12 @@ fn report_object(
.base_path
.as_ref()
.map(|p| {
obj::read::read(p.as_ref(), &diff_config)
obj::read::read(p.as_ref(), diff_config)
.with_context(|| format!("Failed to open {}", p))
})
.transpose()?;
let result =
diff::diff_objs(target.as_ref(), base.as_ref(), None, &diff_config, &mapping_config)?;
diff::diff_objs(target.as_ref(), base.as_ref(), None, diff_config, &mapping_config)?;
let metadata = ReportUnitMetadata {
complete: object.metadata.complete,
+12 -6
View File
@@ -27,6 +27,7 @@ all = [
"mips",
"ppc",
"x86",
"superh"
]
# Implicit, used to check if any arch is enabled
any-arch = [
@@ -75,6 +76,7 @@ std = [
"object/std",
"prost?/std",
"serde?/std",
"similar?/std",
"typed-path?/std",
"dep:filetime",
"dep:memmap2",
@@ -110,6 +112,9 @@ arm64 = [
"dep:yaxpeax-arch",
"dep:yaxpeax-arm",
]
superh = [
"any-arch",
]
[package.metadata.docs.rs]
features = ["all"]
@@ -122,12 +127,12 @@ 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.36", default-features = false, features = ["read_core", "elf", "pe"] }
object = { git = "https://github.com/gimli-rs/object", rev = "a74579249e21ab8fcd3a86be588de336f18297cb", default-features = false, features = ["read_core", "elf", "pe"] }
pbjson = { version = "0.7", default-features = false, optional = true }
prost = { version = "0.13", default-features = false, features = ["prost-derive"], optional = true }
regex = { version = "1.11", default-features = false, features = [], optional = true }
serde = { version = "1.0", default-features = false, features = ["derive"], optional = true }
similar = { version = "2.7", default-features = false, optional = true, git = "https://github.com/encounter/similar.git", branch = "no_std" }
similar = { version = "2.7", default-features = false, features = ["hashbrown"], optional = true, git = "https://github.com/encounter/similar.git", branch = "no_std" }
typed-path = { version = "0.10", default-features = false, optional = true }
# config
@@ -140,12 +145,12 @@ gimli = { version = "0.31", default-features = false, features = ["read"], optio
# ppc
cwdemangle = { version = "1.0", optional = true }
cwextab = { version = "1.0", optional = true, git = "https://github.com/CelestialAmber/cwextab.git" }
cwextab = { version = "1.0", optional = true }
ppc750cl = { version = "0.3", optional = true }
rlwinmdec = { version = "1.1", optional = true, git = "https://github.com/CelestialAmber/rlwinmdec.git" }
rlwinmdec = { version = "1.1", optional = true }
# mips
rabbitizer = { git = "https://github.com/Decompollaborate/rabbitizer.git", branch = "🦀", default-features = false, features = ["all_extensions"], optional = true }
rabbitizer = { version = "2.0.0-alpha.1", default-features = false, features = ["all_extensions"], optional = true }
# x86
cpp_demangle = { version = "0.4", default-features = false, features = ["alloc"], optional = true }
@@ -164,8 +169,9 @@ yaxpeax-arm = { version = "0.3", default-features = false, optional = true }
notify = { version = "8.0.0", optional = true }
notify-debouncer-full = { version = "0.5.0", optional = true }
shell-escape = { version = "0.1", optional = true }
tempfile = { version = "3.17", optional = true }
tempfile = { version = "3.19", optional = true }
time = { version = "0.3", optional = true }
encoding_rs = "0.8.35"
[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3", optional = true }
+56 -14
View File
@@ -15,7 +15,7 @@ use crate::{
diff::{ArmArchVersion, ArmR9Usage, DiffObjConfig, display::InstructionPart},
obj::{
InstructionRef, Relocation, RelocationFlags, ResolvedInstructionRef, ResolvedRelocation,
ScannedInstruction, SymbolFlag, SymbolFlagSet, SymbolKind,
ScannedInstruction, Section, SectionKind, Symbol, SymbolFlag, SymbolFlagSet, SymbolKind,
},
};
@@ -32,7 +32,8 @@ impl ArchArm {
let endianness = file.endianness();
match file {
object::File::Elf32(_) => {
let disasm_modes = Self::elf_get_mapping_symbols(file);
// The disasm_modes mapping is populated later in the post_init step so that we have access to merged sections.
let disasm_modes = BTreeMap::new();
let detected_version = Self::elf_detect_arm_version(file)?;
Ok(Self { disasm_modes, detected_version, endianness })
}
@@ -73,18 +74,22 @@ impl ArchArm {
Ok(None)
}
fn elf_get_mapping_symbols(file: &object::File) -> BTreeMap<usize, Vec<DisasmMode>> {
file.sections()
.filter(|s| s.kind() == object::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))
fn get_mapping_symbols(
sections: &[Section],
symbols: &[Symbol],
) -> BTreeMap<usize, Vec<DisasmMode>> {
sections
.iter()
.enumerate()
.filter(|(_, section)| section.kind == SectionKind::Code)
.map(|(index, _)| {
let mut mapping_symbols: Vec<_> = symbols
.iter()
.filter(|s| s.section.map(|i| i == index).unwrap_or(false))
.filter_map(DisasmMode::from_symbol)
.collect();
mapping_symbols.sort_unstable_by_key(|x| x.address);
(s.index().0 - 1, mapping_symbols)
(index, mapping_symbols)
})
.collect()
}
@@ -178,6 +183,10 @@ impl ArchArm {
}
impl Arch for ArchArm {
fn post_init(&mut self, sections: &[Section], symbols: &[Symbol]) {
self.disasm_modes = Self::get_mapping_symbols(sections, symbols);
}
fn scan_instructions(
&self,
address: u64,
@@ -338,6 +347,7 @@ impl Arch for ArchArm {
cb(InstructionPart::reloc())?;
} else {
push_args(
ins,
&parsed_ins,
resolved.relocation,
resolved.ins_ref.address as u32,
@@ -441,7 +451,7 @@ impl Arch for ArchArm {
fn extra_symbol_flags(&self, symbol: &object::Symbol) -> SymbolFlagSet {
let mut flags = SymbolFlagSet::default();
if DisasmMode::from_symbol(symbol).is_some() {
if DisasmMode::from_object_symbol(symbol).is_some() {
flags |= SymbolFlag::Hidden;
}
flags
@@ -455,15 +465,21 @@ struct DisasmMode {
}
impl DisasmMode {
fn from_symbol<'a>(sym: &object::Symbol<'a, '_, &'a [u8]>) -> Option<Self> {
fn from_object_symbol<'a>(sym: &object::Symbol<'a, '_, &'a [u8]>) -> Option<Self> {
sym.name()
.ok()
.and_then(unarm::ParseMode::from_mapping_symbol)
.map(|mapping| DisasmMode { address: sym.address() as u32, mapping })
}
fn from_symbol(sym: &Symbol) -> Option<Self> {
unarm::ParseMode::from_mapping_symbol(&sym.name)
.map(|mapping| DisasmMode { address: sym.address as u32, mapping })
}
}
fn push_args(
ins: unarm::Ins,
parsed_ins: &unarm::ParsedIns,
relocation: Option<ResolvedRelocation>,
cur_addr: u32,
@@ -609,6 +625,14 @@ fn push_args(
arg_cb(InstructionPart::opaque("!"))?;
}
}
let branch_dest = get_pc_relative_load_address(ins, cur_addr);
if let Some(branch_dest) = branch_dest {
arg_cb(InstructionPart::basic(" (->"))?;
arg_cb(InstructionPart::branch_dest(branch_dest))?;
arg_cb(InstructionPart::basic(")"))?;
}
Ok(())
}
@@ -636,3 +660,21 @@ fn find_reloc_arg(
None
}
}
fn get_pc_relative_load_address(ins: unarm::Ins, address: u32) -> Option<u32> {
match ins {
unarm::Ins::Arm(ins)
if ins.op == arm::Opcode::Ldr
&& ins.modifier_addr_ldr_str() == arm::AddrLdrStr::Imm
&& ins.field_rn_deref().reg == args::Register::Pc =>
{
let offset = ins.field_offset_12().value;
Some(address.wrapping_add_signed(offset + 8))
}
unarm::Ins::Thumb(ins) if ins.op == thumb::Opcode::LdrPc => {
let offset = ins.field_rel_immed_8().value;
Some((address & !3).wrapping_add_signed(offset + 4))
}
_ => None,
}
}
+58 -23
View File
@@ -2,6 +2,7 @@ use alloc::{borrow::Cow, boxed::Box, format, string::String, vec::Vec};
use core::{ffi::CStr, fmt, fmt::Debug};
use anyhow::{Result, bail};
use encoding_rs::SHIFT_JIS;
use object::Endian as _;
use crate::{
@@ -11,7 +12,7 @@ use crate::{
},
obj::{
InstructionArg, Object, ParsedInstruction, Relocation, RelocationFlags,
ResolvedInstructionRef, ScannedInstruction, Symbol, SymbolFlagSet, SymbolKind,
ResolvedInstructionRef, ScannedInstruction, Section, Symbol, SymbolFlagSet, SymbolKind,
},
util::ReallySigned,
};
@@ -24,6 +25,8 @@ pub mod arm64;
pub mod mips;
#[cfg(feature = "ppc")]
pub mod ppc;
#[cfg(feature = "superh")]
pub mod superh;
#[cfg(feature = "x86")]
pub mod x86;
@@ -57,13 +60,18 @@ 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 in self.display_literals(endian, bytes) {
strs.push(format!("{}: {}", self, literal))
for (literal, label_override) in self.display_literals(endian, bytes) {
let label = label_override.unwrap_or_else(|| format!("{}", self));
strs.push(format!("{}: {}", label, literal))
}
strs
}
pub fn display_literals(&self, endian: object::Endianness, bytes: &[u8]) -> Vec<String> {
pub fn display_literals(
&self,
endian: object::Endianness,
bytes: &[u8],
) -> Vec<(String, Option<String>)> {
let mut strs = Vec::new();
if self.required_len().is_some_and(|l| bytes.len() < l) {
log::warn!(
@@ -87,56 +95,72 @@ impl DataType {
match self {
DataType::Int8 => {
let i = i8::from_ne_bytes(bytes.try_into().unwrap());
strs.push(format!("{:#x}", i));
strs.push((format!("{:#x}", i), None));
if i < 0 {
strs.push(format!("{:#x}", ReallySigned(i)));
strs.push((format!("{:#x}", ReallySigned(i)), None));
}
}
DataType::Int16 => {
let i = endian.read_i16_bytes(bytes.try_into().unwrap());
strs.push(format!("{:#x}", i));
strs.push((format!("{:#x}", i), None));
if i < 0 {
strs.push(format!("{:#x}", ReallySigned(i)));
strs.push((format!("{:#x}", ReallySigned(i)), None));
}
}
DataType::Int32 => {
let i = endian.read_i32_bytes(bytes.try_into().unwrap());
strs.push(format!("{:#x}", i));
strs.push((format!("{:#x}", i), None));
if i < 0 {
strs.push(format!("{:#x}", ReallySigned(i)));
strs.push((format!("{:#x}", ReallySigned(i)), None));
}
}
DataType::Int64 => {
let i = endian.read_i64_bytes(bytes.try_into().unwrap());
strs.push(format!("{:#x}", i));
strs.push((format!("{:#x}", i), None));
if i < 0 {
strs.push(format!("{:#x}", ReallySigned(i)));
strs.push((format!("{:#x}", ReallySigned(i)), None));
}
}
DataType::Float => {
let bytes: [u8; 4] = bytes.try_into().unwrap();
strs.push(format!("{:?}f", match endian {
object::Endianness::Little => f32::from_le_bytes(bytes),
object::Endianness::Big => f32::from_be_bytes(bytes),
}));
strs.push((
format!("{:?}f", match endian {
object::Endianness::Little => f32::from_le_bytes(bytes),
object::Endianness::Big => f32::from_be_bytes(bytes),
}),
None,
));
}
DataType::Double => {
let bytes: [u8; 8] = bytes.try_into().unwrap();
strs.push(format!("{:?}", match endian {
object::Endianness::Little => f64::from_le_bytes(bytes),
object::Endianness::Big => f64::from_be_bytes(bytes),
}));
strs.push((
format!("{:?}", match endian {
object::Endianness::Little => f64::from_le_bytes(bytes),
object::Endianness::Big => f64::from_be_bytes(bytes),
}),
None,
));
}
DataType::Bytes => {
strs.push(format!("{:#?}", bytes));
strs.push((format!("{:#?}", bytes), None));
}
DataType::String => {
if let Ok(cstr) = CStr::from_bytes_until_nul(bytes) {
strs.push(format!("{:?}", cstr));
strs.push((format!("{:?}", cstr), None));
}
if let Some(nul_idx) = bytes.iter().position(|&c| c == b'\0') {
let (cow, _, had_errors) = SHIFT_JIS.decode(&bytes[..nul_idx]);
if !had_errors {
let str = format!("{:?}", cow);
// Only add the Shift JIS string if it's different from the ASCII string.
if !strs.iter().any(|x| x.0 == str) {
strs.push((str, Some("Shift JIS".into())));
}
}
}
}
}
@@ -159,6 +183,9 @@ impl DataType {
}
pub trait Arch: Send + Sync + Debug {
// Finishes arch-specific initialization that must be done after sections have been combined.
fn post_init(&mut self, _sections: &[Section], _symbols: &[Symbol]) {}
/// Generate a list of instructions references (offset, size, opcode) from the given code.
///
/// The opcode IDs are used to generate the initial diff. Implementations should do as little
@@ -247,7 +274,13 @@ pub trait Arch: Send + Sync + Debug {
SymbolFlagSet::default()
}
fn guess_data_type(&self, _resolved: ResolvedInstructionRef) -> Option<DataType> { None }
fn guess_data_type(
&self,
_resolved: ResolvedInstructionRef,
_bytes: &[u8],
) -> Option<DataType> {
None
}
fn symbol_hover(&self, _obj: &Object, _symbol_index: usize) -> Vec<HoverItem> { Vec::new() }
@@ -285,6 +318,8 @@ pub fn new_arch(object: &object::File) -> Result<Box<dyn Arch>> {
object::Architecture::Arm => Box::new(arm::ArchArm::new(object)?),
#[cfg(feature = "arm64")]
object::Architecture::Aarch64 => Box::new(arm64::ArchArm64::new(object)?),
#[cfg(feature = "superh")]
object::Architecture::SuperH => Box::new(superh::ArchSuperH::new(object)?),
arch => bail!("Unsupported architecture: {arch:?}"),
})
}
+12 -3
View File
@@ -221,12 +221,21 @@ impl Arch for ArchPpc {
}
}
fn guess_data_type(&self, resolved: ResolvedInstructionRef) -> Option<DataType> {
fn guess_data_type(&self, resolved: ResolvedInstructionRef, bytes: &[u8]) -> Option<DataType> {
if resolved.relocation.is_some_and(|r| r.symbol.name.starts_with("@stringBase")) {
// Pooled string.
return Some(DataType::String);
}
let opcode = ppc750cl::Opcode::from(resolved.ins_ref.opcode as u8);
guess_data_type_from_load_store_inst_op(opcode)
if let Some(ty) = guess_data_type_from_load_store_inst_op(opcode) {
// Numeric type.
return Some(ty);
}
if bytes.len() >= 2 && bytes.iter().position(|&c| c == b'\0') == Some(bytes.len() - 1) {
// It may be an unpooled string if the symbol contains exactly one null byte at the end of the symbol.
return Some(DataType::String);
}
None
}
fn symbol_hover(&self, _obj: &Object, symbol_index: usize) -> Vec<HoverItem> {
@@ -449,7 +458,7 @@ fn decode_exception_info(
log::warn!(
"Exception table decoding failed for function {}, reason: {}",
extab_func_name,
e.to_string()
e
);
return Ok(None);
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+1
View File
@@ -330,6 +330,7 @@ fn reloc_eq(
|| address_eq(left_reloc, right_reloc))
&& (diff_config.function_reloc_diffs == FunctionRelocDiffs::NameAddress
|| left_reloc.symbol.kind != SymbolKind::Object
|| right_reloc.symbol.size == 0 // Likely a pool symbol like ...data, don't treat this as a diff
|| display_ins_data_literals(left_obj, left_ins)
== display_ins_data_literals(right_obj, right_ins))
}
+12 -8
View File
@@ -444,8 +444,8 @@ pub fn relocation_context(
let literals = display_ins_data_literals(obj, ins);
if !literals.is_empty() {
out.push(ContextItem::Separator);
for literal in literals {
out.push(ContextItem::Copy { value: literal, label: None });
for (literal, label_override) in literals {
out.push(ContextItem::Copy { value: literal, label: label_override });
}
}
}
@@ -564,13 +564,14 @@ pub fn instruction_hover(
if let Some(reloc) = resolved.relocation {
out.push(HoverItem::Separator);
out.append(&mut relocation_hover(obj, reloc, None));
if let Some(ty) = obj.arch.guess_data_type(resolved) {
let bytes = obj.symbol_data(reloc.relocation.target_symbol).unwrap_or(&[]);
if let Some(ty) = obj.arch.guess_data_type(resolved, bytes) {
let literals = display_ins_data_literals(obj, resolved);
if !literals.is_empty() {
out.push(HoverItem::Separator);
for literal in literals {
for (literal, label_override) in literals {
out.push(HoverItem::Text {
label: format!("{}", ty),
label: label_override.unwrap_or_else(|| format!("{}", ty)),
value: literal,
color: HoverItemColor::Normal,
});
@@ -758,12 +759,15 @@ pub fn display_ins_data_labels(obj: &Object, resolved: ResolvedInstructionRef) -
};
let bytes = &data[reloc.relocation.addend as usize..];
obj.arch
.guess_data_type(resolved)
.guess_data_type(resolved, bytes)
.map(|ty| ty.display_labels(obj.endianness, bytes))
.unwrap_or_default()
}
pub fn display_ins_data_literals(obj: &Object, resolved: ResolvedInstructionRef) -> Vec<String> {
pub fn display_ins_data_literals(
obj: &Object,
resolved: ResolvedInstructionRef,
) -> Vec<(String, Option<String>)> {
let Some(reloc) = resolved.relocation else {
return Vec::new();
};
@@ -775,7 +779,7 @@ pub fn display_ins_data_literals(obj: &Object, resolved: ResolvedInstructionRef)
};
let bytes = &data[reloc.relocation.addend as usize..];
obj.arch
.guess_data_type(resolved)
.guess_data_type(resolved, bytes)
.map(|ty| ty.display_literals(obj.endianness, bytes))
.unwrap_or_default()
}
+17 -1
View File
@@ -9,7 +9,10 @@ use alloc::{
vec,
vec::Vec,
};
use core::{fmt, num::NonZeroU32};
use core::{
fmt,
num::{NonZeroU32, NonZeroU64},
};
use flagset::{FlagSet, flags};
@@ -70,6 +73,7 @@ pub struct Section {
pub kind: SectionKind,
pub data: SectionData,
pub flags: SectionFlagSet,
pub align: Option<NonZeroU64>,
pub relocations: Vec<Relocation>,
/// Line number info (.line or .debug_line section)
pub line_info: BTreeMap<u64, u32>,
@@ -105,6 +109,17 @@ impl Section {
}
}
pub fn symbol_data(&self, symbol: &Symbol) -> Option<&[u8]> {
let offset = symbol.address.checked_sub(self.address)?;
self.data.get(offset as usize..offset as usize + symbol.size as usize)
}
// The alignment to use when "Combine data/text sections" is enabled.
pub fn combined_alignment(&self) -> u64 {
const MIN_ALIGNMENT: u64 = 4;
self.align.map(|align| align.get().max(MIN_ALIGNMENT)).unwrap_or(MIN_ALIGNMENT)
}
pub fn relocation_at<'obj>(
&'obj self,
obj: &'obj Object,
@@ -363,6 +378,7 @@ static DUMMY_SECTION: Section = Section {
kind: SectionKind::Unknown,
data: SectionData(Vec::new()),
flags: SectionFlagSet::empty(),
align: None,
relocations: Vec::new(),
line_info: BTreeMap::new(),
virtual_address: None,
+9 -3
View File
@@ -4,7 +4,7 @@ use alloc::{
string::{String, ToString},
vec::Vec,
};
use core::cmp::Ordering;
use core::{cmp::Ordering, num::NonZeroU64};
use anyhow::{Context, Result, anyhow, bail, ensure};
use object::{Object as _, ObjectSection as _, ObjectSymbol as _};
@@ -17,7 +17,7 @@ use crate::{
Symbol, SymbolFlag, SymbolKind,
split_meta::{SPLITMETA_SECTION, SplitMeta},
},
util::{read_u16, read_u32},
util::{align_data_slice_to, align_u64_to, read_u16, read_u32},
};
fn map_section_kind(section: &object::Section) -> SectionKind {
@@ -257,6 +257,7 @@ fn map_sections(
kind,
data: SectionData(data),
flags: Default::default(),
align: NonZeroU64::new(section.align()),
relocations: Default::default(),
virtual_address,
line_info: Default::default(),
@@ -739,7 +740,10 @@ fn do_combine_sections(
}
offsets.push(current_offset);
current_offset += section.size;
let align = section.combined_alignment();
current_offset = align_u64_to(current_offset, align);
data_size += section.data.len();
data_size = align_u64_to(data_size as u64, align) as usize;
num_relocations += section.relocations.len();
}
if data_size > 0 {
@@ -754,6 +758,7 @@ fn do_combine_sections(
let section = &mut sections[i];
section.size = 0;
data.append(&mut section.data.0);
align_data_slice_to(&mut data, section.combined_alignment());
section.relocations.iter_mut().for_each(|r| r.address += offset);
relocations.append(&mut section.relocations);
line_info.append(&mut section.line_info.iter().map(|(&a, &l)| (a + offset, l)).collect());
@@ -843,7 +848,7 @@ pub fn read(obj_path: &std::path::Path, config: &DiffObjConfig) -> Result<Object
pub fn parse(data: &[u8], config: &DiffObjConfig) -> Result<Object> {
let obj_file = object::File::parse(data)?;
let arch = new_arch(&obj_file)?;
let mut arch = new_arch(&obj_file)?;
let split_meta = parse_split_meta(&obj_file)?;
let (mut sections, section_indices) =
map_sections(arch.as_ref(), &obj_file, split_meta.as_ref())?;
@@ -857,6 +862,7 @@ pub fn parse(data: &[u8], config: &DiffObjConfig) -> Result<Object> {
if config.combine_data_sections || config.combine_text_sections {
combine_sections(&mut sections, &mut symbols, config)?;
}
arch.post_init(&sections, &symbols);
Ok(Object {
arch,
endianness: obj_file.endianness(),
@@ -14,6 +14,7 @@ expression: "(sections, symbols)"
8,
),
flags: FlagSet(),
align: None,
relocations: [
Relocation {
flags: Elf(
@@ -53,6 +54,7 @@ expression: "(sections, symbols)"
12,
),
flags: FlagSet(Combined),
align: None,
relocations: [
Relocation {
flags: Elf(
@@ -87,6 +89,7 @@ expression: "(sections, symbols)"
0,
),
flags: FlagSet(Hidden),
align: None,
relocations: [],
line_info: {},
virtual_address: None,
@@ -101,6 +104,7 @@ expression: "(sections, symbols)"
0,
),
flags: FlagSet(Hidden),
align: None,
relocations: [],
line_info: {},
virtual_address: None,
+4 -11
View File
@@ -3,6 +3,10 @@ use alloc::{string::String, vec, vec::Vec};
use anyhow::{Result, anyhow};
use object::{Endian, ObjectSection, elf::SHT_NOTE};
#[cfg(feature = "std")]
use crate::util::align_data_to_4;
use crate::util::align_size_to_4;
pub const SPLITMETA_SECTION: &str = ".note.split";
pub const SHT_SPLITMETA: u32 = SHT_NOTE;
pub const ELF_NOTE_SPLIT: &[u8] = b"Split";
@@ -190,17 +194,6 @@ where E: Endian
}
}
fn align_size_to_4(size: usize) -> usize { (size + 3) & !3 }
#[cfg(feature = "std")]
fn align_data_to_4<W: std::io::Write + ?Sized>(writer: &mut W, len: usize) -> std::io::Result<()> {
const ALIGN_BYTES: &[u8] = &[0; 4];
if len % 4 != 0 {
writer.write_all(&ALIGN_BYTES[..4 - len % 4])?;
}
Ok(())
}
// ELF note format:
// Name Size | 4 bytes (integer)
// Desc Size | 4 bytes (integer)
+22 -1
View File
@@ -1,4 +1,4 @@
use alloc::format;
use alloc::{format, vec::Vec};
use core::fmt;
use anyhow::{Result, ensure};
@@ -39,3 +39,24 @@ pub fn read_u16(obj_file: &object::File, reader: &mut &[u8]) -> Result<u16> {
*reader = &reader[2..];
Ok(obj_file.endianness().read_u16(value))
}
pub fn align_size_to_4(size: usize) -> usize { (size + 3) & !3 }
#[cfg(feature = "std")]
pub fn align_data_to_4<W: std::io::Write + ?Sized>(
writer: &mut W,
len: usize,
) -> std::io::Result<()> {
const ALIGN_BYTES: &[u8] = &[0; 4];
if len % 4 != 0 {
writer.write_all(&ALIGN_BYTES[..4 - len % 4])?;
}
Ok(())
}
pub fn align_u64_to(len: u64, align: u64) -> u64 { len + ((align - (len % align)) % align) }
pub fn align_data_slice_to(data: &mut Vec<u8>, align: u64) {
const ALIGN_BYTE: u8 = 0;
data.resize(align_u64_to(data.len() as u64, align) as usize, ALIGN_BYTE);
}

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