Compare commits

..

8 Commits

Author SHA1 Message Date
LagoLunatic 02daae417c Version 3.7.2 2026-05-31 23:25:27 -04:00
LagoLunatic 8b1b4a9a79 Add config option to limit which string literal encodings are used (#356)
* Fix annoying rust-analyzer error with a cfg

* Refactor literal info into a struct

* Add field hide a literal from the GUI but still use for diffing

* Add config option to limit string literal diffing

* Fix deprecation warning `float_literal_f32_fallback`

* Hide non-preferred string encodings from the GUI

* Fix new cargo check warning

* Clippy again

* Rename "None" -> "Auto"
2026-05-31 23:23:10 -04:00
LagoLunatic 13f1267eac Fix several issues with string literal detection (#351)
* Support detecting more symbol names for strings

* Fix string detectiong being broken for encodings that use null terminators

* Add test cases for string detection and decoding

* Fix test helper function sometimes being considered dead code

* Clippy fixes
2026-05-03 21:27:12 -04:00
Luke Street 3cebee6766 Version 3.7.1 2026-03-21 09:54:09 -06:00
Amber Brault ab0538e9a4 Fix extab hover window display issue (#344)
* Bleh

* Fix formatting

* Update arch_ppc__read_extab.snap

* Put code in separate function

* Make compiler shut up

* Update mod.rs
2026-03-21 09:38:01 -06:00
Luke Street b643265809 Disable advisories check in CI 2026-03-20 23:29:54 -06:00
Bruno Macabeus 7a65e07e9b Add support for R_ARM_THM_PC8 and R_ARM_THM_PC11 (#342)
* Add support for `R_ARM_THM_JUMP8` and `R_ARM_THM_JUMP11`

* Fix `scan_instructions_internal` when reading 2-bytes instruction
2026-03-16 22:08:03 -06:00
Bruno Macabeus 3c4092b965 Fix cache issue when parsing the same file with different side params (#341) 2026-03-15 20:21:05 -06:00
25 changed files with 410 additions and 99 deletions
+1 -1
View File
@@ -62,7 +62,7 @@ jobs:
strategy:
matrix:
checks:
- advisories
# - advisories
- bans licenses sources
# Prevent new advisories from failing CI
continue-on-error: ${{ matrix.checks == 'advisories' }}
Generated
+4 -4
View File
@@ -3491,7 +3491,7 @@ dependencies = [
[[package]]
name = "objdiff-cli"
version = "3.7.0"
version = "3.7.2"
dependencies = [
"anyhow",
"argp",
@@ -3514,7 +3514,7 @@ dependencies = [
[[package]]
name = "objdiff-core"
version = "3.7.0"
version = "3.7.2"
dependencies = [
"anyhow",
"arm-attr",
@@ -3570,7 +3570,7 @@ dependencies = [
[[package]]
name = "objdiff-gui"
version = "3.7.0"
version = "3.7.2"
dependencies = [
"anyhow",
"argp",
@@ -3608,7 +3608,7 @@ dependencies = [
[[package]]
name = "objdiff-wasm"
version = "3.7.0"
version = "3.7.2"
dependencies = [
"log",
"objdiff-core",
+1 -1
View File
@@ -14,7 +14,7 @@ default-members = [
resolver = "3"
[workspace.package]
version = "3.7.0"
version = "3.7.2"
authors = ["Luke Street <luke@street.dev>"]
edition = "2024"
license = "MIT OR Apache-2.0"
+2 -2
View File
@@ -198,8 +198,8 @@ fn run_oneshot(
.transpose()?;
let result =
diff::diff_objs(target.as_ref(), base.as_ref(), None, &diff_config, &mapping_config)?;
let left = target.as_ref().and_then(|o| result.left.as_ref().map(|d| (o, d)));
let right = base.as_ref().and_then(|o| result.right.as_ref().map(|d| (o, d)));
let left = target.as_ref().zip(result.left.as_ref());
let right = base.as_ref().zip(result.right.as_ref());
let diff_result = DiffResult::new(left, right, &diff_config)?;
write_output(&diff_result, Some(output), output_format)?;
Ok(())
+46
View File
@@ -117,6 +117,51 @@
"name": "Combine text sections",
"description": "Combines all text sections into one."
},
{
"id": "preferredStringEncoding",
"type": "choice",
"default": "auto",
"name": "Preferred string encoding",
"description": "Which encoding to use when diffing string literals used in functions.",
"items": [
{
"value": "auto",
"name": "Auto"
},
{
"value": "ascii",
"name": "ASCII"
},
{
"value": "utf_8",
"name": "UTF-8"
},
{
"value": "shift_jis",
"name": "Shift JIS"
},
{
"value": "windows_1252",
"name": "Windows-1252"
},
{
"value": "euc_jp",
"name": "EUC-JP"
},
{
"value": "big5",
"name": "Big5"
},
{
"value": "utf_16be",
"name": "UTF-16BE"
},
{
"value": "utf_16le",
"name": "UTF-16LE"
}
]
},
{
"id": "arm.archVersion",
"type": "choice",
@@ -338,6 +383,7 @@
"id": "general",
"name": "General",
"properties": [
"preferredStringEncoding",
"functionRelocDiffs",
"demangler",
"showSymbolSizes",
+27 -3
View File
@@ -162,7 +162,7 @@ impl ArchArm {
}
impl Arch for ArchArm {
fn post_init(&mut self, sections: &[Section], symbols: &[Symbol]) {
fn post_init(&mut self, sections: &[Section], symbols: &[Symbol], _symbol_indices: &[usize]) {
self.disasm_modes = Self::get_mapping_symbols(sections, symbols);
}
@@ -224,7 +224,9 @@ impl Arch for ArchArm {
}
// Check how many bytes we can/should read
let num_code_bytes = if data.len() >= 4 {
let bytes_until_next_mapping =
next_mapping.map(|m| (m.address - address) as usize).unwrap_or(usize::MAX);
let num_code_bytes = if data.len() >= 4 && bytes_until_next_mapping >= 4 {
if mode == unarm::ParseMode::Data && address & 3 != 0 {
// 32-bit .word value should be aligned on a 4-byte boundary, otherwise use .hword
2
@@ -366,6 +368,22 @@ impl Arch for ArchArm {
(imm22 << 1) << 9 >> 9
}
// Thumb unconditional branch (B, 11-bit offset)
elf::R_ARM_THM_PC11 => {
let data = section_data[address..address + 2].try_into()?;
let insn = self.endianness.read_u16_bytes(data) as i32;
let imm11 = insn & 0x7ff;
(imm11 << 1) << 20 >> 20
}
// Thumb conditional branch (B<cond>, 8-bit offset)
elf::R_ARM_THM_PC9 => {
let data = section_data[address..address + 2].try_into()?;
let insn = self.endianness.read_u16_bytes(data) as i32;
let imm8 = insn & 0xff;
(imm8 << 1) << 23 >> 23
}
// Data
elf::R_ARM_ABS32 => {
let data = section_data[address..address + 4].try_into()?;
@@ -399,6 +417,8 @@ impl Arch for ArchArm {
elf::R_ARM_PC24 => Some("R_ARM_PC24"),
elf::R_ARM_XPC25 => Some("R_ARM_XPC25"),
elf::R_ARM_CALL => Some("R_ARM_CALL"),
elf::R_ARM_THM_PC11 => Some("R_ARM_THM_PC11"),
elf::R_ARM_THM_PC9 => Some("R_ARM_THM_PC9"),
_ => None,
},
_ => None,
@@ -418,6 +438,8 @@ impl Arch for ArchArm {
elf::R_ARM_PC24 => 4,
elf::R_ARM_XPC25 => 4,
elf::R_ARM_CALL => 4,
elf::R_ARM_THM_PC11 => 2,
elf::R_ARM_THM_PC9 => 2,
_ => 1,
},
_ => 1,
@@ -544,7 +566,9 @@ impl unarm::FormatIns for ArgsFormatter<'_> {
| RelocationFlags::Elf(elf::R_ARM_THM_PC22)
| RelocationFlags::Elf(elf::R_ARM_PC24)
| RelocationFlags::Elf(elf::R_ARM_XPC25)
| RelocationFlags::Elf(elf::R_ARM_CALL) => {
| RelocationFlags::Elf(elf::R_ARM_CALL)
| RelocationFlags::Elf(elf::R_ARM_THM_PC11)
| RelocationFlags::Elf(elf::R_ARM_THM_PC9) => {
return self.write(InstructionPart::reloc());
}
_ => {}
+104 -52
View File
@@ -15,7 +15,7 @@ use object::Endian as _;
use crate::{
diff::{
DiffObjConfig, DiffSide,
ConfigEnum, DiffObjConfig, DiffSide, PreferredStringEncoding,
display::{ContextItem, HoverItem, InstructionPart},
},
obj::{
@@ -42,15 +42,42 @@ pub mod x86;
pub const OPCODE_INVALID: u16 = u16::MAX;
pub const OPCODE_DATA: u16 = u16::MAX - 1;
const SUPPORTED_ENCODINGS: [(&encoding_rs::Encoding, &str); 7] = [
const SUPPORTED_ENCODINGS_WITH_NULL_TERM: [(&encoding_rs::Encoding, &str); 5] = [
(encoding_rs::UTF_8, "UTF-8"),
(encoding_rs::SHIFT_JIS, "Shift JIS"),
(encoding_rs::UTF_16BE, "UTF-16BE"),
(encoding_rs::UTF_16LE, "UTF-16LE"),
(encoding_rs::WINDOWS_1252, "Windows-1252"),
(encoding_rs::EUC_JP, "EUC-JP"),
(encoding_rs::BIG5, "Big5"),
];
const SUPPORTED_ENCODINGS_NO_NULL_TERM: [(&encoding_rs::Encoding, &str); 2] =
[(encoding_rs::UTF_16BE, "UTF-16BE"), (encoding_rs::UTF_16LE, "UTF-16LE")];
#[derive(Debug, Clone, Default, PartialEq)]
pub struct LiteralInfo {
pub literal: String,
pub label_override: Option<String>,
pub copy_string: Option<String>,
pub hidden: bool, // Only used when the user hasn't set a preferred string encoding
pub is_string: bool,
}
impl LiteralInfo {
pub fn hidden(&self, diff_config: Option<&DiffObjConfig>) -> bool {
let Some(diff_config) = diff_config else {
return self.hidden;
};
if !self.is_string {
return self.hidden;
}
if diff_config.preferred_string_encoding == PreferredStringEncoding::Auto {
return self.hidden;
}
let Some(ref label) = self.label_override else {
return self.hidden;
};
*label != diff_config.preferred_string_encoding.name()
}
}
/// Represents the type of data associated with an instruction
#[derive(PartialEq)]
@@ -83,18 +110,14 @@ impl fmt::Display for DataType {
impl DataType {
pub fn display_labels(&self, endian: object::Endianness, bytes: &[u8]) -> Vec<String> {
let mut strs = Vec::new();
for (literal, label_override, _) in self.display_literals(endian, bytes) {
let label = label_override.unwrap_or_else(|| self.to_string());
strs.push(format!("{label}: {literal:?}"))
for lit_info in self.display_literals(endian, bytes) {
let label = lit_info.label_override.unwrap_or_else(|| self.to_string());
strs.push(format!("{}: {:?}", label, lit_info.literal))
}
strs
}
pub fn display_literals(
&self,
endian: object::Endianness,
bytes: &[u8],
) -> Vec<(String, Option<String>, Option<String>)> {
pub fn display_literals(&self, endian: object::Endianness, bytes: &[u8]) -> Vec<LiteralInfo> {
let mut strs = Vec::new();
if self.required_len().is_some_and(|l| bytes.len() < l) {
log::warn!(
@@ -118,80 +141,106 @@ impl DataType {
match self {
DataType::Int8 => {
let i = i8::from_ne_bytes(bytes.try_into().unwrap());
strs.push((format!("{i:#x}"), None, None));
strs.push(LiteralInfo { literal: format!("{i:#x}"), ..Default::default() });
if i < 0 {
strs.push((format!("{:#x}", ReallySigned(i)), None, None));
strs.push(LiteralInfo {
literal: format!("{:#x}", ReallySigned(i)),
..Default::default()
});
}
}
DataType::Int16 => {
let i = endian.read_i16_bytes(bytes.try_into().unwrap());
strs.push((format!("{i:#x}"), None, None));
strs.push(LiteralInfo { literal: format!("{i:#x}"), ..Default::default() });
if i < 0 {
strs.push((format!("{:#x}", ReallySigned(i)), None, None));
strs.push(LiteralInfo {
literal: format!("{:#x}", ReallySigned(i)),
..Default::default()
});
}
}
DataType::Int32 => {
let i = endian.read_i32_bytes(bytes.try_into().unwrap());
strs.push((format!("{i:#x}"), None, None));
strs.push(LiteralInfo { literal: format!("{i:#x}"), ..Default::default() });
if i < 0 {
strs.push((format!("{:#x}", ReallySigned(i)), None, None));
strs.push(LiteralInfo {
literal: format!("{:#x}", ReallySigned(i)),
..Default::default()
});
}
}
DataType::Int64 => {
let i = endian.read_i64_bytes(bytes.try_into().unwrap());
strs.push((format!("{i:#x}"), None, None));
strs.push(LiteralInfo { literal: format!("{i:#x}"), ..Default::default() });
if i < 0 {
strs.push((format!("{:#x}", ReallySigned(i)), None, None));
strs.push(LiteralInfo {
literal: format!("{:#x}", ReallySigned(i)),
..Default::default()
});
}
}
DataType::Float => {
let bytes: [u8; 4] = bytes.try_into().unwrap();
strs.push((
format!("{:?}f", match endian {
strs.push(LiteralInfo {
literal: format!("{:?}f", match endian {
object::Endianness::Little => f32::from_le_bytes(bytes),
object::Endianness::Big => f32::from_be_bytes(bytes),
}),
None,
None,
));
..Default::default()
});
}
DataType::Double => {
let bytes: [u8; 8] = bytes.try_into().unwrap();
strs.push((
format!("{:?}", match endian {
strs.push(LiteralInfo {
literal: format!("{:?}", match endian {
object::Endianness::Little => f64::from_le_bytes(bytes),
object::Endianness::Big => f64::from_be_bytes(bytes),
}),
None,
None,
));
..Default::default()
});
}
DataType::Bytes => {
strs.push((format!("{bytes:#?}"), None, None));
strs.push(LiteralInfo { literal: format!("{bytes:#?}"), ..Default::default() });
}
DataType::String => {
// Special case to display (ASCII) as the label for ASCII-only strings.
let mut is_ascii = false;
if bytes.is_ascii()
&& let Ok(str) = str::from_utf8(bytes)
{
let trimmed = str.trim_end_matches('\0');
if !trimmed.is_empty() {
let copy_string = escape_special_ascii_characters(trimmed);
strs.push((trimmed.to_string(), Some("ASCII".into()), Some(copy_string)));
is_ascii = true;
if let Some(nul_idx) = bytes.iter().position(|&c| c == b'\0') {
let str_bytes = &bytes[..nul_idx];
// Special case to display (ASCII) as the label for ASCII-only strings.
let (cow, _, had_errors) = encoding_rs::UTF_8.decode(str_bytes);
if !had_errors && cow.is_ascii() {
let string = format!("{cow}");
let copy_string = escape_special_ascii_characters(&string);
strs.push(LiteralInfo {
literal: string,
label_override: Some("ASCII".into()),
copy_string: Some(copy_string),
hidden: false,
is_string: true,
});
}
for (encoding, encoding_name) in SUPPORTED_ENCODINGS_WITH_NULL_TERM {
let (cow, _, had_errors) = encoding.decode(str_bytes);
if !had_errors {
let string = format!("{cow}");
let copy_string = escape_special_ascii_characters(&string);
// Avoid showing ASCII-only strings more than once if the encoding is ASCII-compatible.
let hidden = encoding.is_ascii_compatible() && cow.is_ascii();
strs.push(LiteralInfo {
literal: string,
label_override: Some(encoding_name.into()),
copy_string: Some(copy_string),
hidden,
is_string: true,
});
}
}
}
for (encoding, encoding_name) in SUPPORTED_ENCODINGS {
// Avoid showing ASCII-only strings more than once if the encoding is ASCII-compatible.
if is_ascii && encoding.is_ascii_compatible() {
continue;
}
for (encoding, encoding_name) in SUPPORTED_ENCODINGS_NO_NULL_TERM {
let (cow, _, had_errors) = encoding.decode(bytes);
if had_errors {
continue;
@@ -199,11 +248,13 @@ impl DataType {
let trimmed = cow.trim_end_matches('\0');
if !trimmed.is_empty() {
let copy_string = escape_special_ascii_characters(trimmed);
strs.push((
trimmed.to_string(),
Some(encoding_name.into()),
Some(copy_string),
));
strs.push(LiteralInfo {
literal: trimmed.to_string(),
label_override: Some(encoding_name.into()),
copy_string: Some(copy_string),
hidden: false,
is_string: true,
});
}
}
}
@@ -340,7 +391,8 @@ impl dyn Arch {
pub trait Arch: Any + Debug + Send + Sync {
/// Finishes arch-specific initialization that must be done after sections have been combined.
fn post_init(&mut self, _sections: &[Section], _symbols: &[Symbol]) {}
fn post_init(&mut self, _sections: &[Section], _symbols: &[Symbol], _symbol_indices: &[usize]) {
}
/// Generate a list of instructions references (offset, size, opcode) from the given code.
///
@@ -513,6 +513,8 @@ pub fn ppc_data_flow_analysis(
)
}
// Note: This function only supports MWCC ASCII strings.
// Other encodings and other compilers are currently not supported.
fn get_string_data(obj: &Object, symbol_index: usize, offset: Simm) -> Option<&str> {
if let Some(sym) = obj.symbols.get(symbol_index)
&& sym.name.starts_with("@stringBase")
+20 -2
View File
@@ -359,9 +359,12 @@ impl Arch for ArchPpc {
fn guess_data_type(&self, resolved: ResolvedInstructionRef, bytes: &[u8]) -> Option<DataType> {
if resolved.relocation.is_some_and(|r| {
r.symbol.name.starts_with("@stringBase") || r.symbol.name.starts_with("$SG")
r.symbol.name.starts_with("@stringBase")
|| r.symbol.name.starts_with("@wstringBase")
|| r.symbol.name.starts_with("$SG")
|| r.symbol.demangled_name == Some("`string'".to_string())
}) {
// Pooled string.
// Compiler-generated symbol name for a string or a pool of strings.
return Some(DataType::String);
}
let opcode = powerpc::Opcode::from(resolved.ins_ref.opcode);
@@ -472,12 +475,27 @@ impl Arch for ArchPpc {
}
Ok(next_address.saturating_sub(symbol.address))
}
fn post_init(&mut self, _sections: &[Section], _symbols: &[Symbol], symbol_indices: &[usize]) {
// Change the indices used as keys from the original symbol indices to the new symbol array indices
self.extab = Self::convert_extab_map_indices(self, symbol_indices);
}
}
impl ArchPpc {
pub fn extab_for_symbol(&self, symbol_index: usize) -> Option<&ExceptionInfo> {
self.extab.as_ref()?.get(&symbol_index)
}
pub fn convert_extab_map_indices(
&self,
symbol_indices: &[usize],
) -> Option<BTreeMap<usize, ExceptionInfo>> {
let new_map: BTreeMap<usize, ExceptionInfo> =
self.extab.as_ref()?.iter().map(|e| (symbol_indices[*e.0 + 1], e.1.clone())).collect();
Some(new_map)
}
}
fn zero_reloc(code: u32, reloc: &Relocation) -> u32 {
+23 -4
View File
@@ -9,8 +9,8 @@ use anyhow::{Context, Result, anyhow, ensure};
use super::{
DiffObjConfig, FunctionRelocDiffs, InstructionArgDiffIndex, InstructionBranchFrom,
InstructionBranchTo, InstructionDiffKind, InstructionDiffRow, SymbolDiff,
display::display_ins_data_literals,
InstructionBranchTo, InstructionDiffKind, InstructionDiffRow, PreferredStringEncoding,
SymbolDiff, display::display_ins_data_literals,
};
use crate::obj::{
InstructionArg, InstructionArgValue, InstructionRef, Object, ResolvedInstructionRef,
@@ -296,6 +296,26 @@ pub(crate) fn section_name_eq(
})
}
fn ins_data_literals_eq(
left_obj: &Object,
right_obj: &Object,
left_ins: ResolvedInstructionRef,
right_ins: ResolvedInstructionRef,
diff_config: &DiffObjConfig,
) -> bool {
let mut left_literals = display_ins_data_literals(left_obj, left_ins);
let mut right_literals = display_ins_data_literals(right_obj, right_ins);
if left_literals == right_literals {
return true;
}
if diff_config.preferred_string_encoding == PreferredStringEncoding::Auto {
return left_literals == right_literals;
}
left_literals.retain(|lit_info| !lit_info.hidden(Some(diff_config)));
right_literals.retain(|lit_info| !lit_info.hidden(Some(diff_config)));
left_literals == right_literals
}
fn reloc_eq(
left_obj: &Object,
right_obj: &Object,
@@ -330,8 +350,7 @@ fn reloc_eq(
&& (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))
|| ins_data_literals_eq(left_obj, right_obj, left_ins, right_ins, diff_config))
}
(Some(_), None) | (None, Some(_)) | (None, None) => symbol_name_addend_matches,
}
+20 -10
View File
@@ -12,6 +12,7 @@ use itertools::Itertools;
use regex::Regex;
use crate::{
arch::LiteralInfo,
diff::{
DataDiffKind, DataDiffRow, DiffObjConfig, InstructionDiffKind, InstructionDiffRow,
ObjectDiff, SymbolDiff, data::resolve_relocation,
@@ -494,15 +495,21 @@ pub fn relocation_context(
obj: &Object,
reloc: ResolvedRelocation,
ins: Option<ResolvedInstructionRef>,
diff_config: Option<&DiffObjConfig>,
) -> Vec<ContextItem> {
let mut out = Vec::new();
out.append(&mut symbol_context(obj, reloc.relocation.target_symbol));
if let Some(ins) = ins {
let literals = display_ins_data_literals(obj, ins);
let mut literals = display_ins_data_literals(obj, ins);
literals.retain(|lit_info| !lit_info.hidden(diff_config));
if !literals.is_empty() {
out.push(ContextItem::Separator);
for (literal, label_override, copy_string) in literals {
out.push(ContextItem::Copy { value: literal, label: label_override, copy_string });
for lit_info in literals {
out.push(ContextItem::Copy {
value: lit_info.literal,
label: lit_info.label_override,
copy_string: lit_info.copy_string,
});
}
}
}
@@ -555,7 +562,7 @@ pub fn data_row_context(obj: &Object, diff_row: &DataDiffRow) -> Vec<ContextItem
prev_reloc = Some(reloc);
let reloc = resolve_relocation(&obj.symbols, reloc);
out.append(&mut relocation_context(obj, reloc, None));
out.append(&mut relocation_context(obj, reloc, None, None));
}
out
}
@@ -592,6 +599,7 @@ pub fn instruction_context(
obj: &Object,
resolved: ResolvedInstructionRef,
ins: &ParsedInstruction,
diff_config: &DiffObjConfig,
) -> Vec<ContextItem> {
let mut out = Vec::new();
let mut hex_string = String::new();
@@ -636,7 +644,7 @@ pub fn instruction_context(
}
if let Some(reloc) = resolved.relocation {
out.push(ContextItem::Separator);
out.append(&mut relocation_context(obj, reloc, Some(resolved)));
out.append(&mut relocation_context(obj, reloc, Some(resolved), Some(diff_config)));
}
out
}
@@ -645,6 +653,7 @@ pub fn instruction_hover(
obj: &Object,
resolved: ResolvedInstructionRef,
ins: &ParsedInstruction,
diff_config: &DiffObjConfig,
) -> Vec<HoverItem> {
let mut out = Vec::new();
out.push(HoverItem::Text {
@@ -687,13 +696,14 @@ pub fn instruction_hover(
out.append(&mut relocation_hover(obj, reloc, None));
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);
let mut literals = display_ins_data_literals(obj, resolved);
literals.retain(|lit_info| !lit_info.hidden(Some(diff_config)));
if !literals.is_empty() {
out.push(HoverItem::Separator);
for (literal, label_override, _) in literals {
for lit_info in literals {
out.push(HoverItem::Text {
label: label_override.unwrap_or_else(|| ty.to_string()),
value: format!("{literal:?}"),
label: lit_info.label_override.unwrap_or_else(|| ty.to_string()),
value: format!("{:?}", lit_info.literal),
color: HoverItemColor::Normal,
});
}
@@ -880,7 +890,7 @@ pub fn display_ins_data_labels(obj: &Object, resolved: ResolvedInstructionRef) -
pub fn display_ins_data_literals(
obj: &Object,
resolved: ResolvedInstructionRef,
) -> Vec<(String, Option<String>, Option<String>)> {
) -> Vec<LiteralInfo> {
let Some(reloc) = resolved.relocation else {
return Vec::new();
};
+2 -2
View File
@@ -175,8 +175,8 @@ fn run_build(
Ok(Box::new(ObjDiffResult {
first_status,
second_status,
first_obj: first_obj.and_then(|o| result.left.map(|d| (o, d))),
second_obj: second_obj.and_then(|o| result.right.map(|d| (o, d))),
first_obj: first_obj.zip(result.left),
second_obj: second_obj.zip(result.right),
time,
}))
}
+1 -1
View File
@@ -1089,7 +1089,7 @@ pub fn parse(data: &[u8], config: &DiffObjConfig, diff_side: DiffSide) -> Result
combine_sections(&mut sections, &mut symbols, config)?;
}
add_section_symbols(&sections, &mut symbols);
arch.post_init(&sections, &symbols);
arch.post_init(&sections, &symbols, &symbol_indices);
let mut obj = Object {
arch,
endianness: obj_file.endianness(),
+25
View File
@@ -57,6 +57,31 @@ fn combine_text_sections() {
insta::assert_snapshot!(output);
}
#[test]
#[cfg(feature = "arm")]
fn thumb_short_data_mapping() {
// When a .2byte directive is used in Thumb code, the assembler emits
// $d/$t mapping symbols for a 2-byte data region. The disassembler must
// not read 4 bytes as a .word when the next mapping symbol limits the
// data region to 2 bytes.
let diff_config = diff::DiffObjConfig::default();
let obj = obj::read::parse(
include_object!("data/arm/code_1_vblank.o"),
&diff_config,
diff::DiffSide::Base,
)
.unwrap();
let symbol_idx = obj.symbols.iter().position(|s| s.name == "VBlankDMA_Level1").unwrap();
let diff = diff::code::no_diff_code(&obj, symbol_idx, &diff_config).unwrap();
let output = common::display_diff(&obj, &diff, symbol_idx, &diff_config);
// .2byte data followed by Thumb code must not be merged into a 4-byte .word
assert!(
!output.contains(".word"),
"2-byte data regions should not be decoded as 4-byte .word values.\n\
The disassembler must respect mapping symbol boundaries."
);
}
#[test]
#[cfg(feature = "arm")]
fn trim_trailing_hword() {
+87
View File
@@ -122,3 +122,90 @@ fn read_vmx128_coff() {
let output = common::display_diff(&obj, &diff, symbol_idx, &diff_config);
insta::assert_snapshot!(output);
}
#[test]
#[cfg(feature = "ppc")]
fn decode_sjis_pooled_strings() {
// Test multiple pooled Shift JIS strings separated by a single null byte between entries. (MWCC)
let diff_config = diff::DiffObjConfig { combine_data_sections: true, ..Default::default() };
let obj = obj::read::parse(
include_object!("data/ppc/m_Do_hostIO.o"),
&diff_config,
diff::DiffSide::Base,
)
.unwrap();
common::assert_literal_value(
&obj,
&diff_config,
"createChild__16mDoHIO_subRoot_cFPCcP13JORReflexible",
15,
"Shift JIS",
"危険:既に登録されているホストIOをふたたび登録しようとしています<%s>\n",
);
common::assert_literal_value(
&obj,
&diff_config,
"createChild__16mDoHIO_subRoot_cFPCcP13JORReflexible",
42,
"Shift JIS",
"ホストIOの空きエントリがありません。登録できませんでした。\n",
);
}
#[test]
#[cfg(feature = "ppc")]
fn decode_utf16_unpooled_strings() {
// Test unpooled UTF-16BE wide strings with null bytes at the start, end, and in the middle of the string. (MSVC)
let diff_config = diff::DiffObjConfig { combine_data_sections: true, ..Default::default() };
let obj = obj::read::parse(
include_object!("data/ppc/KinectSharePanel.obj"),
&diff_config,
diff::DiffSide::Base,
)
.unwrap();
common::assert_literal_value(
&obj,
&diff_config,
"?OnPostLink@KinectSharePanel@@AAA?AVDataNode@@PAVDataArray@@@Z",
84,
"UTF-16BE",
"Title Text",
);
common::assert_literal_value(
&obj,
&diff_config,
"?OnPostLink@KinectSharePanel@@AAA?AVDataNode@@PAVDataArray@@@Z",
120,
"UTF-16BE",
"http://www.dancecentral.com/content-assets/2012/06/2012E3LogoBox_tn.jpg",
);
}
#[test]
#[cfg(feature = "ppc")]
fn decode_ascii_strings_with_null_padding() {
// Test unpooled ASCII strings with more than one null byte at the end.
let diff_config = diff::DiffObjConfig { combine_data_sections: true, ..Default::default() };
let obj = obj::read::parse(
include_object!("data/ppc/vmx128.obj"),
&diff_config,
diff::DiffSide::Base,
)
.unwrap();
common::assert_literal_value(
&obj,
&diff_config,
"?PrintVector@@YAXPBDU__vector4@@@Z",
24,
"ASCII",
"%s: [%.2f, %.2f, %.2f, %.2f]\n",
);
common::assert_literal_value(
&obj,
&diff_config,
"?ReservedRegisterExample@@YAXXZ",
59,
"ASCII",
"Result from vr66",
);
}
+21
View File
@@ -50,3 +50,24 @@ macro_rules! include_object {
include_bytes_align_as!(u64, $path)
};
}
#[allow(dead_code)]
pub fn assert_literal_value(
obj: &Object,
diff_config: &DiffObjConfig,
function_name: &str,
ins_row_idx: usize,
literal_label: &str,
literal_value: &str,
) {
let symbol_idx = obj.symbols.iter().position(|s| s.name == function_name).unwrap();
let diff = objdiff_core::diff::code::no_diff_code(obj, symbol_idx, diff_config).unwrap();
let ins_ref = diff.instruction_rows[ins_row_idx].ins_ref.unwrap();
let resolved = obj.resolve_instruction_ref(symbol_idx, ins_ref).unwrap();
let literals = objdiff_core::diff::display::display_ins_data_literals(obj, resolved);
let target_literal =
literals.iter().find(|lit_info| lit_info.label_override == Some(literal_label.to_string()));
assert_ne!(target_literal, None);
let lit_info = target_literal.unwrap();
assert_eq!(lit_info.literal, literal_value);
}
Binary file not shown.
Binary file not shown.
@@ -9,7 +9,7 @@ Object {
),
extab: Some(
{
10: ExceptionInfo {
1: ExceptionInfo {
eti_symbol: ExtabSymbolRef {
original_index: 5,
name: "@31",
@@ -35,7 +35,7 @@ Object {
},
dtors: [],
},
11: ExceptionInfo {
2: ExceptionInfo {
eti_symbol: ExtabSymbolRef {
original_index: 7,
name: "@52",
@@ -95,7 +95,7 @@ Object {
},
],
},
12: ExceptionInfo {
3: ExceptionInfo {
eti_symbol: ExtabSymbolRef {
original_index: 9,
name: "@60",
+1 -1
View File
@@ -94,7 +94,7 @@ impl FrameHistory {
let rect = rect.shrink(4.0);
let color = ui.visuals().text_color();
let line_stroke = Stroke::new(1.0, color);
let line_stroke = Stroke::new(1.0_f32, color);
if let Some(pointer_pos) = response.hover_pos() {
let y = pointer_pos.y;

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