mirror of
https://github.com/encounter/objdiff.git
synced 2026-07-10 12:18:36 -07:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 02daae417c | |||
| 8b1b4a9a79 | |||
| 13f1267eac |
Generated
+4
-4
@@ -3491,7 +3491,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "objdiff-cli"
|
||||
version = "3.7.1"
|
||||
version = "3.7.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argp",
|
||||
@@ -3514,7 +3514,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "objdiff-core"
|
||||
version = "3.7.1"
|
||||
version = "3.7.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"arm-attr",
|
||||
@@ -3570,7 +3570,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "objdiff-gui"
|
||||
version = "3.7.1"
|
||||
version = "3.7.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argp",
|
||||
@@ -3608,7 +3608,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "objdiff-wasm"
|
||||
version = "3.7.1"
|
||||
version = "3.7.2"
|
||||
dependencies = [
|
||||
"log",
|
||||
"objdiff-core",
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ default-members = [
|
||||
resolver = "3"
|
||||
|
||||
[workspace.package]
|
||||
version = "3.7.1"
|
||||
version = "3.7.2"
|
||||
authors = ["Luke Street <luke@street.dev>"]
|
||||
edition = "2024"
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
@@ -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(())
|
||||
|
||||
@@ -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",
|
||||
|
||||
+102
-51
@@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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.
@@ -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;
|
||||
|
||||
@@ -97,7 +97,7 @@ fn ins_hover_ui(
|
||||
ui.scope(|ui| {
|
||||
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
|
||||
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Wrap);
|
||||
hover_items_ui(ui, instruction_hover(obj, resolved, &ins), appearance);
|
||||
hover_items_ui(ui, instruction_hover(obj, resolved, &ins, diff_config), appearance);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -128,7 +128,12 @@ fn ins_context_menu(
|
||||
ui.scope(|ui| {
|
||||
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
|
||||
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Truncate);
|
||||
context_menu_items_ui(ui, instruction_context(obj, resolved, &ins), column, appearance);
|
||||
context_menu_items_ui(
|
||||
ui,
|
||||
instruction_context(obj, resolved, &ins, diff_config),
|
||||
column,
|
||||
appearance,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "objdiff-wasm",
|
||||
"version": "3.7.1",
|
||||
"version": "3.7.2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "objdiff-wasm",
|
||||
"version": "3.7.1",
|
||||
"version": "3.7.2",
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^1.9.3",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "objdiff-wasm",
|
||||
"version": "3.7.1",
|
||||
"version": "3.7.2",
|
||||
"description": "A local diffing tool for decompilation projects.",
|
||||
"author": {
|
||||
"name": "Luke Street",
|
||||
|
||||
@@ -225,7 +225,7 @@ impl GuestDisplay for Component {
|
||||
let obj_diff = diff.get::<ResourceObjectDiff>();
|
||||
let obj = obj_diff.0.as_ref();
|
||||
let symbol_display = from_symbol_ref(symbol_ref);
|
||||
diff::display::symbol_context(obj, symbol_display.symbol as usize)
|
||||
diff::display::symbol_context(obj, symbol_display.symbol)
|
||||
.into_iter()
|
||||
.map(ContextItem::from)
|
||||
.collect()
|
||||
@@ -237,7 +237,7 @@ impl GuestDisplay for Component {
|
||||
let addend = 0; // TODO
|
||||
let override_color = None; // TODO: colorize replaced/deleted/inserted relocations
|
||||
let symbol_display = from_symbol_ref(symbol_ref);
|
||||
diff::display::symbol_hover(obj, symbol_display.symbol as usize, addend, override_color)
|
||||
diff::display::symbol_hover(obj, symbol_display.symbol, addend, override_color)
|
||||
.into_iter()
|
||||
.map(HoverItem::from)
|
||||
.collect()
|
||||
@@ -286,7 +286,7 @@ impl GuestDisplay for Component {
|
||||
})];
|
||||
}
|
||||
};
|
||||
diff::display::instruction_context(obj, resolved, &ins)
|
||||
diff::display::instruction_context(obj, resolved, &ins, &diff_config)
|
||||
.into_iter()
|
||||
.map(ContextItem::from)
|
||||
.collect()
|
||||
@@ -335,7 +335,7 @@ impl GuestDisplay for Component {
|
||||
})];
|
||||
}
|
||||
};
|
||||
diff::display::instruction_hover(obj, resolved, &ins)
|
||||
diff::display::instruction_hover(obj, resolved, &ins, &diff_config)
|
||||
.into_iter()
|
||||
.map(HoverItem::from)
|
||||
.collect()
|
||||
|
||||
@@ -44,6 +44,7 @@ pub fn init(level: wasi_logging::Level) {
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[panic_handler]
|
||||
fn panic(info: &core::panic::PanicInfo) -> ! {
|
||||
use alloc::string::ToString;
|
||||
|
||||
Reference in New Issue
Block a user