Initial x86 support

Includes a bit of work to make adding new
architectures easier in the future
This commit is contained in:
Luke Street 2024-03-16 23:30:27 -06:00
parent aecb078b2a
commit bbe49eb8b4
17 changed files with 844 additions and 289 deletions

38
Cargo.lock generated
View File

@ -1257,7 +1257,7 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412"
dependencies = [
"libloading 0.8.1",
"libloading 0.8.3",
]
[[package]]
@ -2017,7 +2017,7 @@ dependencies = [
"glutin_glx_sys",
"glutin_wgl_sys",
"icrate",
"libloading 0.8.1",
"libloading 0.8.3",
"objc2 0.4.1",
"once_cell",
"raw-window-handle 0.5.2",
@ -2157,7 +2157,7 @@ dependencies = [
"bitflags 2.4.2",
"com",
"libc",
"libloading 0.8.1",
"libloading 0.8.3",
"thiserror",
"widestring",
"winapi",
@ -2281,6 +2281,15 @@ dependencies = [
"tokio-native-tls",
]
[[package]]
name = "iced-x86"
version = "1.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c447cff8c7f384a7d4f741cfcff32f75f3ad02b406432e8d6c878d56b1edf6b"
dependencies = [
"lazy_static",
]
[[package]]
name = "icrate"
version = "0.0.4"
@ -2458,7 +2467,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76"
dependencies = [
"libc",
"libloading 0.8.1",
"libloading 0.8.3",
"pkg-config",
]
@ -2512,12 +2521,12 @@ dependencies = [
[[package]]
name = "libloading"
version = "0.8.1"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161"
checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19"
dependencies = [
"cfg-if",
"windows-sys 0.48.0",
"windows-targets 0.52.4",
]
[[package]]
@ -2699,6 +2708,15 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "msvc-demangler"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2588c982e3a7fbfbd73b21f824cacc43fc6392a1103c709ffd6001c0bf33fdb3"
dependencies = [
"bitflags 2.4.2",
]
[[package]]
name = "naga"
version = "0.19.0"
@ -3019,8 +3037,10 @@ dependencies = [
"flagset",
"gimli",
"globset",
"iced-x86",
"log",
"memmap2",
"msvc-demangler",
"num-traits",
"object 0.34.0",
"ppc750cl",
@ -5050,7 +5070,7 @@ dependencies = [
"js-sys",
"khronos-egl",
"libc",
"libloading 0.8.1",
"libloading 0.8.3",
"log",
"metal",
"naga",
@ -5497,7 +5517,7 @@ dependencies = [
"as-raw-xcb-connection",
"gethostname",
"libc",
"libloading 0.8.1",
"libloading 0.8.3",
"once_cell",
"rustix 0.38.31",
"x11rb-protocol",

View File

@ -115,7 +115,7 @@ pub fn run(args: Args) -> Result<()> {
if obj
.target_path
.as_deref()
.map(|o| obj::elf::has_function(o, &args.symbol))
.map(|o| obj::read::has_function(o, &args.symbol))
.transpose()?
.unwrap_or(false)
{
@ -523,7 +523,7 @@ impl FunctionDiffUi {
rect: Rect,
highlight: &HighlightKind,
) -> Option<HighlightKind> {
let base_addr = symbol.address as u32;
let base_addr = symbol.address;
let mut new_highlight = None;
for (y, ins_diff) in
symbol.instructions.iter().skip(self.scroll_y).take(rect.height as usize).enumerate()
@ -572,7 +572,7 @@ impl FunctionDiffUi {
base_color = COLOR_ROTATION[diff.idx % COLOR_ROTATION.len()]
}
}
DiffText::BranchTarget(addr) => {
DiffText::BranchDest(addr) => {
label_text = format!("{addr:x}");
}
DiffText::Symbol(sym) => {
@ -633,14 +633,18 @@ impl FunctionDiffUi {
let mut target = self
.target_path
.as_deref()
.map(|p| obj::elf::read(p).with_context(|| format!("Loading {}", p.display())))
.map(|p| obj::read::read(p).with_context(|| format!("Loading {}", p.display())))
.transpose()?;
let mut base = self
.base_path
.as_deref()
.map(|p| obj::elf::read(p).with_context(|| format!("Loading {}", p.display())))
.map(|p| obj::read::read(p).with_context(|| format!("Loading {}", p.display())))
.transpose()?;
let config = diff::DiffObjConfig { relax_reloc_diffs: self.relax_reloc_diffs };
let config = diff::DiffObjConfig {
relax_reloc_diffs: self.relax_reloc_diffs,
space_between_args: true, // TODO
x86_formatter: Default::default(), // TODO
};
diff::diff_objs(&config, target.as_mut(), base.as_mut())?;
let left_sym = target.as_ref().and_then(|o| find_function(o, &self.symbol_name));

View File

@ -219,14 +219,14 @@ fn report_object(
let mut target = object
.target_path
.as_ref()
.map(|p| obj::elf::read(p).with_context(|| format!("Failed to open {}", p.display())))
.map(|p| obj::read::read(p).with_context(|| format!("Failed to open {}", p.display())))
.transpose()?;
let mut base = object
.base_path
.as_ref()
.map(|p| obj::elf::read(p).with_context(|| format!("Failed to open {}", p.display())))
.map(|p| obj::read::read(p).with_context(|| format!("Failed to open {}", p.display())))
.transpose()?;
let config = diff::DiffObjConfig { relax_reloc_diffs: true };
let config = diff::DiffObjConfig { relax_reloc_diffs: true, ..Default::default() };
diff::diff_objs(&config, target.as_mut(), base.as_mut())?;
let mut unit = ReportUnit {
name: object.name().to_string(),

View File

@ -12,12 +12,13 @@ A local diffing tool for decompilation projects.
"""
[features]
all = ["config", "dwarf", "mips", "ppc"]
all = ["config", "dwarf", "mips", "ppc", "x86"]
any-arch = [] # Implicit, used to check if any arch is enabled
config = []
dwarf = ["gimli"]
mips = ["any-arch", "rabbitizer"]
ppc = ["any-arch", "cwdemangle", "ppc750cl"]
x86 = ["any-arch", "iced-x86", "msvc-demangler"]
[dependencies]
anyhow = "1.0.81"
@ -29,7 +30,7 @@ gimli = { version = "0.28.1", default-features = false, features = ["read-all"],
log = "0.4.21"
memmap2 = "0.9.4"
num-traits = "0.2.18"
object = { version = "0.34.0", features = ["read_core", "std", "elf"], default-features = false }
object = { version = "0.34.0", features = ["read_core", "std", "elf", "pe"], default-features = false }
ppc750cl = { git = "https://github.com/encounter/ppc750cl", rev = "4a2bbbc6f84dcb76255ab6f3595a8d4a0ce96618", optional = true }
rabbitizer = { version = "1.9.2", optional = true }
serde = { version = "1", features = ["derive"] }
@ -40,3 +41,12 @@ globset = { version = "0.4.14", features = ["serde1"] }
semver = "1.0.22"
serde_json = "1.0.114"
serde_yaml = "0.9.32"
# x86
msvc-demangler = { version = "0.10.0", optional = true }
[dependencies.iced-x86]
version = "1.21.0"
#default-features = false
#features = ["std", "decoder", "intel"]
features = ["exhaustive_enums"]
optional = true

View File

@ -17,6 +17,7 @@ use crate::{
};
pub fn no_diff_code(
config: &DiffObjConfig,
arch: ObjArchitecture,
data: &[u8],
symbol: &mut ObjSymbol,
@ -28,16 +29,25 @@ pub fn no_diff_code(
let out: ProcessCodeResult = match arch {
#[cfg(feature = "ppc")]
ObjArchitecture::PowerPc => {
obj::ppc::process_code(code, symbol.address, relocs, line_info)?
obj::ppc::process_code(config, code, symbol.address, relocs, line_info)?
}
#[cfg(feature = "mips")]
ObjArchitecture::Mips => obj::mips::process_code(
config,
code,
symbol.address,
symbol.address + symbol.size,
relocs,
line_info,
)?,
#[cfg(feature = "x86")]
ObjArchitecture::X86_32 => {
obj::x86::process_code(config, code, 32, symbol.address, relocs, line_info)?
}
#[cfg(feature = "x86")]
ObjArchitecture::X86_64 => {
obj::x86::process_code(config, code, 64, symbol.address, relocs, line_info)?
}
};
let mut diff = Vec::<ObjInsDiff>::new();
@ -69,8 +79,15 @@ pub fn diff_code(
let (left_out, right_out) = match arch {
#[cfg(feature = "ppc")]
ObjArchitecture::PowerPc => (
obj::ppc::process_code(left_code, left_symbol.address, left_relocs, left_line_info)?,
obj::ppc::process_code(
config,
left_code,
left_symbol.address,
left_relocs,
left_line_info,
)?,
obj::ppc::process_code(
config,
right_code,
right_symbol.address,
right_relocs,
@ -80,6 +97,7 @@ pub fn diff_code(
#[cfg(feature = "mips")]
ObjArchitecture::Mips => (
obj::mips::process_code(
config,
left_code,
left_symbol.address,
left_symbol.address + left_symbol.size,
@ -87,6 +105,7 @@ pub fn diff_code(
left_line_info,
)?,
obj::mips::process_code(
config,
right_code,
right_symbol.address,
left_symbol.address + left_symbol.size,
@ -94,6 +113,44 @@ pub fn diff_code(
right_line_info,
)?,
),
#[cfg(feature = "x86")]
ObjArchitecture::X86_32 => (
obj::x86::process_code(
config,
left_code,
32,
left_symbol.address,
left_relocs,
left_line_info,
)?,
obj::x86::process_code(
config,
right_code,
32,
right_symbol.address,
right_relocs,
right_line_info,
)?,
),
#[cfg(feature = "x86")]
ObjArchitecture::X86_64 => (
obj::x86::process_code(
config,
left_code,
64,
left_symbol.address,
left_relocs,
left_line_info,
)?,
obj::x86::process_code(
config,
right_code,
64,
right_symbol.address,
right_relocs,
right_line_info,
)?,
),
};
let mut left_diff = Vec::<ObjInsDiff>::new();
@ -183,7 +240,7 @@ fn diff_instructions(
fn resolve_branches(vec: &mut [ObjInsDiff]) {
let mut branch_idx = 0usize;
// Map addresses to indices
let mut addr_map = BTreeMap::<u32, usize>::new();
let mut addr_map = BTreeMap::<u64, usize>::new();
for (i, ins_diff) in vec.iter().enumerate() {
if let Some(ins) = &ins_diff.ins {
addr_map.insert(ins.address, i);
@ -193,15 +250,7 @@ fn resolve_branches(vec: &mut [ObjInsDiff]) {
let mut branches = BTreeMap::<usize, ObjInsBranchFrom>::new();
for (i, ins_diff) in vec.iter_mut().enumerate() {
if let Some(ins) = &ins_diff.ins {
// if ins.ins.is_blr() || ins.reloc.is_some() {
// continue;
// }
if let Some(ins_idx) = ins
.args
.iter()
.find_map(|a| if let ObjInsArg::BranchOffset(offs) = a { Some(offs) } else { None })
.and_then(|offs| addr_map.get(&((ins.address as i32 + offs) as u32)))
{
if let Some(ins_idx) = ins.branch_dest.and_then(|a| addr_map.get(&a)) {
if let Some(branch) = branches.get_mut(ins_idx) {
ins_diff.branch_to =
Some(ObjInsBranchTo { ins_idx: *ins_idx, branch_idx: branch.branch_idx });
@ -262,8 +311,12 @@ fn arg_eq(
right_diff: &ObjInsDiff,
) -> bool {
return match left {
ObjInsArg::Arg(l) | ObjInsArg::ArgWithBase(l) => match right {
ObjInsArg::Arg(r) | ObjInsArg::ArgWithBase(r) => l == r,
ObjInsArg::PlainText(l) => match right {
ObjInsArg::PlainText(r) => l == r,
_ => false,
},
ObjInsArg::Arg(l) => match right {
ObjInsArg::Arg(r) => l == r,
_ => false,
},
ObjInsArg::Reloc => {
@ -274,15 +327,7 @@ fn arg_eq(
right_diff.ins.as_ref().and_then(|i| i.reloc.as_ref()),
)
}
ObjInsArg::RelocWithBase => {
matches!(right, ObjInsArg::RelocWithBase)
&& reloc_eq(
config,
left_diff.ins.as_ref().and_then(|i| i.reloc.as_ref()),
right_diff.ins.as_ref().and_then(|i| i.reloc.as_ref()),
)
}
ObjInsArg::BranchOffset(_) => {
ObjInsArg::BranchDest(_) => {
// Compare dest instruction idx after diffing
left_diff.branch_to.as_ref().map(|b| b.ins_idx)
== right_diff.branch_to.as_ref().map(|b| b.ins_idx)
@ -314,7 +359,15 @@ fn compare_ins(
) -> Result<InsDiffResult> {
let mut result = InsDiffResult::default();
if let (Some(left_ins), Some(right_ins)) = (&left.ins, &right.ins) {
if left_ins.args.len() != right_ins.args.len() || left_ins.op != right_ins.op {
if left_ins.args.len() != right_ins.args.len()
|| left_ins.op != right_ins.op
// Check if any PlainText segments differ (punctuation and spacing)
// This indicates a more significant difference than a simple arg mismatch
|| !left_ins.args.iter().zip(&right_ins.args).all(|(a, b)| match (a, b) {
(ObjInsArg::PlainText(l), ObjInsArg::PlainText(r)) => l == r,
_ => true,
})
{
// Totally different op
result.kind = ObjInsDiffKind::Replace;
state.diff_count += 1;
@ -335,9 +388,10 @@ fn compare_ins(
state.diff_count += 1;
}
let a_str = match a {
ObjInsArg::Arg(arg) | ObjInsArg::ArgWithBase(arg) => arg.to_string(),
ObjInsArg::Reloc | ObjInsArg::RelocWithBase => String::new(),
ObjInsArg::BranchOffset(arg) => format!("{arg}"),
ObjInsArg::PlainText(arg) => arg.clone(),
ObjInsArg::Arg(arg) => arg.to_string(),
ObjInsArg::Reloc => String::new(),
ObjInsArg::BranchDest(arg) => format!("{arg}"),
};
let a_diff = if let Some(idx) = state.left_args_idx.get(&a_str) {
ObjInsArgDiff { idx: *idx }
@ -348,9 +402,10 @@ fn compare_ins(
ObjInsArgDiff { idx }
};
let b_str = match b {
ObjInsArg::Arg(arg) | ObjInsArg::ArgWithBase(arg) => arg.to_string(),
ObjInsArg::Reloc | ObjInsArg::RelocWithBase => String::new(),
ObjInsArg::BranchOffset(arg) => format!("{arg}"),
ObjInsArg::PlainText(arg) => arg.clone(),
ObjInsArg::Arg(arg) => arg.to_string(),
ObjInsArg::Reloc => String::new(),
ObjInsArg::BranchDest(arg) => format!("{arg}"),
};
let b_diff = if let Some(idx) = state.right_args_idx.get(&b_str) {
ObjInsArgDiff { idx: *idx }

View File

@ -1,8 +1,6 @@
use std::cmp::Ordering;
use crate::obj::{
ObjInsArg, ObjInsArgDiff, ObjInsArgValue, ObjInsDiff, ObjReloc, ObjRelocKind, ObjSymbol,
};
use crate::obj::{ObjInsArg, ObjInsArgDiff, ObjInsArgValue, ObjInsDiff, ObjReloc, ObjSymbol};
#[derive(Debug, Clone)]
pub enum DiffText<'a> {
@ -13,13 +11,13 @@ pub enum DiffText<'a> {
/// Line number
Line(usize),
/// Instruction address
Address(u32),
Address(u64),
/// Instruction mnemonic
Opcode(&'a str, u8),
Opcode(&'a str, u16),
/// Instruction argument
Argument(&'a ObjInsArgValue, Option<&'a ObjInsArgDiff>),
/// Branch target
BranchTarget(u32),
/// Branch destination
BranchDest(u64),
/// Symbol name
Symbol(&'a ObjSymbol),
/// Number of spaces
@ -32,15 +30,15 @@ pub enum DiffText<'a> {
pub enum HighlightKind {
#[default]
None,
Opcode(u8),
Opcode(u16),
Arg(ObjInsArgValue),
Symbol(String),
Address(u32),
Address(u64),
}
pub fn display_diff<E>(
ins_diff: &ObjInsDiff,
base_addr: u32,
base_addr: u64,
mut cb: impl FnMut(DiffText) -> Result<(), E>,
) -> Result<(), E> {
let Some(ins) = &ins_diff.ins else {
@ -57,43 +55,25 @@ pub fn display_diff<E>(
cb(DiffText::Spacing(4))?;
}
cb(DiffText::Opcode(&ins.mnemonic, ins.op))?;
let mut writing_offset = false;
for (i, arg) in ins.args.iter().enumerate() {
if i == 0 {
cb(DiffText::Spacing(1))?;
}
if i > 0 && !writing_offset {
cb(DiffText::Basic(", "))?;
}
let mut new_writing_offset = false;
match arg {
ObjInsArg::PlainText(s) => {
cb(DiffText::Basic(s))?;
}
ObjInsArg::Arg(v) => {
let diff = ins_diff.arg_diff.get(i).and_then(|o| o.as_ref());
cb(DiffText::Argument(v, diff))?;
}
ObjInsArg::ArgWithBase(v) => {
let diff = ins_diff.arg_diff.get(i).and_then(|o| o.as_ref());
cb(DiffText::Argument(v, diff))?;
cb(DiffText::Basic("("))?;
new_writing_offset = true;
}
ObjInsArg::Reloc => {
display_reloc(ins.reloc.as_ref().unwrap(), &mut cb)?;
display_reloc_name(ins.reloc.as_ref().unwrap(), &mut cb)?;
}
ObjInsArg::RelocWithBase => {
display_reloc(ins.reloc.as_ref().unwrap(), &mut cb)?;
cb(DiffText::Basic("("))?;
new_writing_offset = true;
}
ObjInsArg::BranchOffset(offset) => {
let addr = offset + ins.address as i32 - base_addr as i32;
cb(DiffText::BranchTarget(addr as u32))?;
ObjInsArg::BranchDest(dest) => {
cb(DiffText::BranchDest(*dest))?;
}
}
if writing_offset {
cb(DiffText::Basic(")"))?;
}
writing_offset = new_writing_offset;
}
if let Some(branch) = &ins_diff.branch_to {
cb(DiffText::BasicColor(" ~>", branch.branch_idx))?;
@ -114,87 +94,13 @@ fn display_reloc_name<E>(
}
}
fn display_reloc<E>(
reloc: &ObjReloc,
mut cb: impl FnMut(DiffText) -> Result<(), E>,
) -> Result<(), E> {
match reloc.kind {
#[cfg(feature = "ppc")]
ObjRelocKind::PpcAddr16Lo => {
display_reloc_name(reloc, &mut cb)?;
cb(DiffText::Basic("@l"))?;
}
#[cfg(feature = "ppc")]
ObjRelocKind::PpcAddr16Hi => {
display_reloc_name(reloc, &mut cb)?;
cb(DiffText::Basic("@h"))?;
}
#[cfg(feature = "ppc")]
ObjRelocKind::PpcAddr16Ha => {
display_reloc_name(reloc, &mut cb)?;
cb(DiffText::Basic("@ha"))?;
}
#[cfg(feature = "ppc")]
ObjRelocKind::PpcEmbSda21 => {
display_reloc_name(reloc, &mut cb)?;
cb(DiffText::Basic("@sda21"))?;
}
#[cfg(feature = "ppc")]
ObjRelocKind::PpcRel24 | ObjRelocKind::PpcRel14 => {
display_reloc_name(reloc, &mut cb)?;
}
#[cfg(feature = "mips")]
ObjRelocKind::MipsHi16 => {
cb(DiffText::Basic("%hi("))?;
display_reloc_name(reloc, &mut cb)?;
cb(DiffText::Basic(")"))?;
}
#[cfg(feature = "mips")]
ObjRelocKind::MipsLo16 => {
cb(DiffText::Basic("%lo("))?;
display_reloc_name(reloc, &mut cb)?;
cb(DiffText::Basic(")"))?;
}
#[cfg(feature = "mips")]
ObjRelocKind::MipsGot16 => {
cb(DiffText::Basic("%got("))?;
display_reloc_name(reloc, &mut cb)?;
cb(DiffText::Basic(")"))?;
}
#[cfg(feature = "mips")]
ObjRelocKind::MipsCall16 => {
cb(DiffText::Basic("%call16("))?;
display_reloc_name(reloc, &mut cb)?;
cb(DiffText::Basic(")"))?;
}
#[cfg(feature = "mips")]
ObjRelocKind::MipsGpRel16 => {
cb(DiffText::Basic("%gp_rel("))?;
display_reloc_name(reloc, &mut cb)?;
cb(DiffText::Basic(")"))?;
}
#[cfg(feature = "mips")]
ObjRelocKind::Mips26 => {
display_reloc_name(reloc, &mut cb)?;
}
#[cfg(feature = "mips")]
ObjRelocKind::MipsGpRel32 => {
todo!("unimplemented: mips gp_rel32");
}
ObjRelocKind::Absolute => {
cb(DiffText::Basic("[INVALID]"))?;
}
}
Ok(())
}
impl PartialEq<DiffText<'_>> for HighlightKind {
fn eq(&self, other: &DiffText) -> bool {
match (self, other) {
(HighlightKind::Opcode(a), DiffText::Opcode(_, b)) => a == b,
(HighlightKind::Arg(a), DiffText::Argument(b, _)) => a.loose_eq(b),
(HighlightKind::Symbol(a), DiffText::Symbol(b)) => a == &b.name,
(HighlightKind::Address(a), DiffText::Address(b) | DiffText::BranchTarget(b)) => a == b,
(HighlightKind::Address(a), DiffText::Address(b) | DiffText::BranchDest(b)) => a == b,
_ => false,
}
}
@ -210,7 +116,7 @@ impl From<DiffText<'_>> for HighlightKind {
DiffText::Opcode(_, op) => HighlightKind::Opcode(op),
DiffText::Argument(arg, _) => HighlightKind::Arg(arg.clone()),
DiffText::Symbol(sym) => HighlightKind::Symbol(sym.name.to_string()),
DiffText::Address(addr) | DiffText::BranchTarget(addr) => HighlightKind::Address(addr),
DiffText::Address(addr) | DiffText::BranchDest(addr) => HighlightKind::Address(addr),
_ => HighlightKind::None,
}
}

View File

@ -9,16 +9,19 @@ use crate::{
code::{diff_code, find_section_and_symbol, no_diff_code},
data::{diff_bss_symbols, diff_data, no_diff_data},
},
obj::{ObjInfo, ObjIns, ObjSectionKind},
obj::{x86::X86Formatter, ObjInfo, ObjIns, ObjSectionKind},
};
#[derive(Debug, Clone, Default, Eq, PartialEq)]
#[derive(Debug, Clone, Default, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
#[serde(default)]
pub struct DiffObjConfig {
pub relax_reloc_diffs: bool,
pub space_between_args: bool,
pub x86_formatter: X86Formatter,
}
pub struct ProcessCodeResult {
pub ops: Vec<u8>,
pub ops: Vec<u16>,
pub insts: Vec<ObjIns>,
}
@ -54,6 +57,7 @@ pub fn diff_objs(
)?;
} else {
no_diff_code(
config,
left.architecture,
&left_section.data,
left_symbol,
@ -82,6 +86,7 @@ pub fn diff_objs(
for right_symbol in &mut right_section.symbols {
if right_symbol.instructions.is_empty() {
no_diff_code(
config,
right.architecture,
&right_section.data,
right_symbol,

View File

@ -4,8 +4,8 @@ use anyhow::Result;
use rabbitizer::{config, Abi, InstrCategory, Instruction, OperandType};
use crate::{
diff::ProcessCodeResult,
obj::{ObjIns, ObjInsArg, ObjInsArgValue, ObjReloc},
diff::{DiffObjConfig, ProcessCodeResult},
obj::{ObjIns, ObjInsArg, ObjInsArgValue, ObjReloc, ObjRelocKind},
};
fn configure_rabbitizer() {
@ -15,6 +15,7 @@ fn configure_rabbitizer() {
}
pub fn process_code(
config: &DiffObjConfig,
data: &[u8],
start_address: u64,
end_address: u64,
@ -24,7 +25,7 @@ pub fn process_code(
configure_rabbitizer();
let ins_count = data.len() / 4;
let mut ops = Vec::<u8>::with_capacity(ins_count);
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 data.chunks_exact(4) {
@ -32,35 +33,43 @@ pub fn process_code(
let code = u32::from_be_bytes(chunk.try_into()?);
let instruction = Instruction::new(code, cur_addr, InstrCategory::CPU);
let op = instruction.unique_id as u8;
let op = instruction.unique_id as u16;
ops.push(op);
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 { Some((cur_addr as i32 + branch_offset) as u32) } else { None };
let branch_dest = if is_branch {
cur_addr.checked_add_signed(branch_offset).map(|a| a as u64)
} else {
None
};
let operands = instruction.get_operands_slice();
let mut args = Vec::with_capacity(operands.len() + 1);
for op in operands {
for (idx, op) in operands.iter().enumerate() {
if idx > 0 {
if config.space_between_args {
args.push(ObjInsArg::PlainText(", ".to_string()));
} else {
args.push(ObjInsArg::PlainText(",".to_string()));
}
}
match op {
OperandType::cpu_immediate
| OperandType::cpu_label
| OperandType::cpu_branch_target_label => {
if is_branch {
args.push(ObjInsArg::BranchOffset(branch_offset));
if let Some(branch_dest) = branch_dest {
args.push(ObjInsArg::BranchDest(branch_dest));
} else 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
{
// Inter-function reloc, convert to branch offset
args.push(ObjInsArg::BranchOffset(
reloc.target.address as i32 - cur_addr as i32,
));
args.push(ObjInsArg::BranchDest(reloc.target.address));
} else {
args.push(ObjInsArg::Reloc);
push_reloc(&mut args, reloc);
}
} else {
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(
@ -69,16 +78,18 @@ pub fn process_code(
}
}
OperandType::cpu_immediate_base => {
if reloc.is_some() {
args.push(ObjInsArg::RelocWithBase);
if let Some(reloc) = reloc {
push_reloc(&mut args, reloc);
} else {
args.push(ObjInsArg::ArgWithBase(ObjInsArgValue::Opaque(
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(
OperandType::cpu_immediate.disassemble(&instruction, None),
)));
}
args.push(ObjInsArg::PlainText("(".to_string()));
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(
OperandType::cpu_rs.disassemble(&instruction, None),
)));
args.push(ObjInsArg::PlainText(")".to_string()));
}
_ => {
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(
@ -91,8 +102,8 @@ pub fn process_code(
.as_ref()
.and_then(|map| map.range(..=cur_addr as u64).last().map(|(_, &b)| b));
insts.push(ObjIns {
address: cur_addr,
code,
address: cur_addr as u64,
size: 4,
op,
mnemonic,
args,
@ -105,3 +116,40 @@ pub fn process_code(
}
Ok(ProcessCodeResult { ops, insts })
}
fn push_reloc(args: &mut Vec<ObjInsArg>, reloc: &ObjReloc) {
match reloc.kind {
ObjRelocKind::MipsHi16 => {
args.push(ObjInsArg::PlainText("%hi(".to_string()));
args.push(ObjInsArg::Reloc);
args.push(ObjInsArg::PlainText(")".to_string()));
}
ObjRelocKind::MipsLo16 => {
args.push(ObjInsArg::PlainText("%lo(".to_string()));
args.push(ObjInsArg::Reloc);
args.push(ObjInsArg::PlainText(")".to_string()));
}
ObjRelocKind::MipsGot16 => {
args.push(ObjInsArg::PlainText("%got(".to_string()));
args.push(ObjInsArg::Reloc);
args.push(ObjInsArg::PlainText(")".to_string()));
}
ObjRelocKind::MipsCall16 => {
args.push(ObjInsArg::PlainText("%call16(".to_string()));
args.push(ObjInsArg::Reloc);
args.push(ObjInsArg::PlainText(")".to_string()));
}
ObjRelocKind::MipsGpRel16 => {
args.push(ObjInsArg::PlainText("%gp_rel(".to_string()));
args.push(ObjInsArg::Reloc);
args.push(ObjInsArg::PlainText(")".to_string()));
}
ObjRelocKind::Mips26 => {
args.push(ObjInsArg::Reloc);
}
ObjRelocKind::MipsGpRel32 => {
todo!("unimplemented: mips gp_rel32");
}
kind => panic!("Unsupported MIPS relocation kind: {:?}", kind),
}
}

View File

@ -1,9 +1,11 @@
pub mod elf;
#[cfg(feature = "mips")]
pub mod mips;
#[cfg(feature = "ppc")]
pub mod ppc;
pub mod read;
pub mod split_meta;
#[cfg(feature = "x86")]
pub mod x86;
use std::{collections::BTreeMap, fmt, path::PathBuf};
@ -50,8 +52,8 @@ pub struct ObjSection {
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum ObjInsArgValue {
Signed(i16),
Unsigned(u16),
Signed(i64),
Unsigned(u64),
Opaque(String),
}
@ -61,7 +63,7 @@ impl ObjInsArgValue {
(ObjInsArgValue::Signed(a), ObjInsArgValue::Signed(b)) => a == b,
(ObjInsArgValue::Unsigned(a), ObjInsArgValue::Unsigned(b)) => a == b,
(ObjInsArgValue::Signed(a), ObjInsArgValue::Unsigned(b))
| (ObjInsArgValue::Unsigned(b), ObjInsArgValue::Signed(a)) => *a as u16 == *b,
| (ObjInsArgValue::Unsigned(b), ObjInsArgValue::Signed(a)) => *a as u64 == *b,
(ObjInsArgValue::Opaque(a), ObjInsArgValue::Opaque(b)) => a == b,
_ => false,
}
@ -80,21 +82,18 @@ impl fmt::Display for ObjInsArgValue {
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum ObjInsArg {
PlainText(String),
Arg(ObjInsArgValue),
ArgWithBase(ObjInsArgValue),
Reloc,
RelocWithBase,
BranchOffset(i32),
BranchDest(u64),
}
impl ObjInsArg {
pub fn loose_eq(&self, other: &ObjInsArg) -> bool {
match (self, other) {
(ObjInsArg::Arg(a), ObjInsArg::Arg(b)) => a.loose_eq(b),
(ObjInsArg::ArgWithBase(a), ObjInsArg::ArgWithBase(b)) => a.loose_eq(b),
(ObjInsArg::Reloc, ObjInsArg::Reloc) => true,
(ObjInsArg::RelocWithBase, ObjInsArg::RelocWithBase) => true,
(ObjInsArg::BranchOffset(a), ObjInsArg::BranchOffset(b)) => a == b,
(ObjInsArg::BranchDest(a), ObjInsArg::BranchDest(b)) => a == b,
_ => false,
}
}
@ -135,13 +134,13 @@ pub enum ObjInsDiffKind {
#[derive(Debug, Clone)]
pub struct ObjIns {
pub address: u32,
pub code: u32,
pub op: u8,
pub address: u64,
pub size: u8,
pub op: u16,
pub mnemonic: String,
pub args: Vec<ObjInsArg>,
pub reloc: Option<ObjReloc>,
pub branch_dest: Option<u32>,
pub branch_dest: Option<u64>,
/// Line number
pub line: Option<u64>,
/// Original (unsimplified) instruction
@ -203,6 +202,10 @@ pub enum ObjArchitecture {
PowerPc,
#[cfg(feature = "mips")]
Mips,
#[cfg(feature = "x86")]
X86_32,
#[cfg(feature = "x86")]
X86_64,
}
#[derive(Debug, Clone)]
@ -256,6 +259,8 @@ pub enum ObjRelocKind {
MipsGpRel16,
#[cfg(feature = "mips")]
MipsGpRel32,
#[cfg(feature = "x86")]
X86PcRel32,
}
#[derive(Debug, Clone)]

View File

@ -1,39 +1,34 @@
use std::collections::BTreeMap;
use anyhow::Result;
use ppc750cl::{disasm_iter, Argument, SimplifiedIns};
use anyhow::{bail, Result};
use ppc750cl::{disasm_iter, Argument, SimplifiedIns, GPR};
use crate::{
diff::ProcessCodeResult,
diff::{DiffObjConfig, ProcessCodeResult},
obj::{ObjIns, ObjInsArg, ObjInsArgValue, ObjReloc, ObjRelocKind},
};
// Relative relocation, can be Simm or BranchOffset
fn is_relative_arg(arg: &ObjInsArg) -> bool {
matches!(arg, ObjInsArg::Arg(ObjInsArgValue::Signed(_)) | ObjInsArg::BranchOffset(_))
// Relative relocation, can be Simm, Offset or BranchDest
fn is_relative_arg(arg: &Argument) -> bool {
matches!(arg, Argument::Simm(_) | Argument::Offset(_) | Argument::BranchDest(_))
}
// Relative or absolute relocation, can be Uimm, Simm or Offset
fn is_rel_abs_arg(arg: &ObjInsArg) -> bool {
matches!(
arg,
ObjInsArg::Arg(ObjInsArgValue::Signed(_) | ObjInsArgValue::Unsigned(_))
| ObjInsArg::ArgWithBase(ObjInsArgValue::Signed(_))
)
fn is_rel_abs_arg(arg: &Argument) -> bool {
matches!(arg, Argument::Uimm(_) | Argument::Simm(_) | Argument::Offset(_))
}
fn is_offset_arg(arg: &ObjInsArg) -> bool {
matches!(arg, ObjInsArg::ArgWithBase(ObjInsArgValue::Signed(_)))
}
fn is_offset_arg(arg: &Argument) -> bool { matches!(arg, Argument::Offset(_)) }
pub fn process_code(
config: &DiffObjConfig,
data: &[u8],
address: u64,
relocs: &[ObjReloc],
line_info: &Option<BTreeMap<u64, u64>>,
) -> Result<ProcessCodeResult> {
let ins_count = data.len() / 4;
let mut ops = Vec::<u8>::with_capacity(ins_count);
let mut ops = Vec::<u16>::with_capacity(ins_count);
let mut insts = Vec::<ObjIns>::with_capacity(ins_count);
for mut ins in disasm_iter(data, address as u32) {
let reloc = relocs.iter().find(|r| (r.address as u32 & !3) == ins.addr);
@ -50,65 +45,120 @@ pub fn process_code(
};
}
let simplified = ins.clone().simplified();
let mut args: Vec<ObjInsArg> = simplified
.args
.iter()
.map(|a| match a {
Argument::Simm(simm) => ObjInsArg::Arg(ObjInsArgValue::Signed(simm.0)),
Argument::Uimm(uimm) => ObjInsArg::Arg(ObjInsArgValue::Unsigned(uimm.0)),
Argument::Offset(offset) => {
ObjInsArg::ArgWithBase(ObjInsArgValue::Signed(offset.0))
}
Argument::BranchDest(dest) => ObjInsArg::BranchOffset(dest.0),
_ => ObjInsArg::Arg(ObjInsArgValue::Opaque(a.to_string())),
})
.collect();
let mut reloc_arg = None;
if let Some(reloc) = reloc {
match reloc.kind {
ObjRelocKind::PpcEmbSda21 => {
args = vec![args[0].clone(), ObjInsArg::Reloc];
reloc_arg = Some(1);
}
ObjRelocKind::PpcRel24 | ObjRelocKind::PpcRel14 => {
let arg = args
.iter_mut()
.rfind(|a| is_relative_arg(a))
.ok_or_else(|| anyhow::Error::msg("Failed to locate rel arg for reloc"))?;
*arg = ObjInsArg::Reloc;
reloc_arg = simplified.args.iter().rposition(is_relative_arg);
}
ObjRelocKind::PpcAddr16Hi
| ObjRelocKind::PpcAddr16Ha
| ObjRelocKind::PpcAddr16Lo => {
match args.iter_mut().rfind(|a| is_rel_abs_arg(a)) {
Some(arg) => {
*arg = if is_offset_arg(arg) {
ObjInsArg::RelocWithBase
} else {
ObjInsArg::Reloc
};
}
None => {
log::warn!("Failed to locate rel/abs arg for reloc");
}
};
reloc_arg = simplified.args.iter().rposition(is_rel_abs_arg);
}
_ => {}
}
}
ops.push(simplified.ins.op as u8);
let mut args = vec![];
let mut branch_dest = None;
let mut writing_offset = false;
for (idx, arg) in simplified.args.iter().enumerate() {
if idx > 0 && !writing_offset {
if config.space_between_args {
args.push(ObjInsArg::PlainText(", ".to_string()));
} else {
args.push(ObjInsArg::PlainText(",".to_string()));
}
}
if reloc_arg == Some(idx) {
let reloc = reloc.unwrap();
push_reloc(&mut args, reloc)?;
// For @sda21, we can omit the register argument
if reloc.kind == ObjRelocKind::PpcEmbSda21
// Sanity check: the next argument should be r0
&& matches!(simplified.args.get(idx + 1), Some(Argument::GPR(GPR(0))))
{
break;
}
} else {
match arg {
Argument::Simm(simm) => {
args.push(ObjInsArg::Arg(ObjInsArgValue::Signed(simm.0 as i64)));
}
Argument::Uimm(uimm) => {
args.push(ObjInsArg::Arg(ObjInsArgValue::Unsigned(uimm.0 as u64)));
}
Argument::Offset(offset) => {
args.push(ObjInsArg::Arg(ObjInsArgValue::Signed(offset.0 as i64)));
}
Argument::BranchDest(dest) => {
let dest = ins.addr.wrapping_add_signed(dest.0) as u64;
args.push(ObjInsArg::BranchDest(dest));
branch_dest = Some(dest);
}
_ => {
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(arg.to_string())));
}
};
}
if writing_offset {
args.push(ObjInsArg::PlainText(")".to_string()));
writing_offset = false;
}
if is_offset_arg(arg) {
args.push(ObjInsArg::PlainText("(".to_string()));
writing_offset = true;
}
}
ops.push(simplified.ins.op as u16);
let line = line_info
.as_ref()
.and_then(|map| map.range(..=simplified.ins.addr as u64).last().map(|(_, &b)| b));
insts.push(ObjIns {
address: simplified.ins.addr,
code: simplified.ins.code,
address: simplified.ins.addr as u64,
size: 4,
mnemonic: format!("{}{}", simplified.mnemonic, simplified.suffix),
args,
reloc: reloc.cloned(),
op: ins.op as u8,
branch_dest: None,
op: ins.op as u16,
branch_dest,
line,
orig: Some(format!("{}", SimplifiedIns::basic_form(ins))),
});
}
Ok(ProcessCodeResult { ops, insts })
}
fn push_reloc(args: &mut Vec<ObjInsArg>, reloc: &ObjReloc) -> Result<()> {
match reloc.kind {
ObjRelocKind::PpcAddr16Lo => {
args.push(ObjInsArg::Reloc);
args.push(ObjInsArg::PlainText("@l".to_string()));
}
ObjRelocKind::PpcAddr16Hi => {
args.push(ObjInsArg::Reloc);
args.push(ObjInsArg::PlainText("@h".to_string()));
}
ObjRelocKind::PpcAddr16Ha => {
args.push(ObjInsArg::Reloc);
args.push(ObjInsArg::PlainText("@ha".to_string()));
}
ObjRelocKind::PpcEmbSda21 => {
args.push(ObjInsArg::Reloc);
args.push(ObjInsArg::PlainText("@sda21".to_string()));
}
ObjRelocKind::PpcRel24 | ObjRelocKind::PpcRel14 => {
args.push(ObjInsArg::Reloc);
}
kind => bail!("Unsupported PPC relocation kind: {:?}", kind),
};
Ok(())
}

View File

@ -5,8 +5,9 @@ use byteorder::{BigEndian, ReadBytesExt};
use filetime::FileTime;
use flagset::Flags;
use object::{
elf, Architecture, File, Object, ObjectSection, ObjectSymbol, RelocationFlags,
RelocationTarget, SectionIndex, SectionKind, Symbol, SymbolKind, SymbolScope, SymbolSection,
elf, pe, Architecture, BinaryFormat, Endian, File, Object, ObjectSection, ObjectSymbol,
RelocationFlags, RelocationTarget, SectionIndex, SectionKind, Symbol, SymbolKind, SymbolScope,
SymbolSection,
};
use crate::obj::{
@ -48,7 +49,7 @@ fn to_obj_symbol(
if symbol.is_weak() {
flags = ObjSymbolFlagSet(flags.0 | ObjSymbolFlags::Weak);
}
if symbol.scope() == SymbolScope::Linkage {
if obj_file.format() == BinaryFormat::Elf && symbol.scope() == SymbolScope::Linkage {
flags = ObjSymbolFlagSet(flags.0 | ObjSymbolFlags::Hidden);
}
let section_address = if let Some(section) =
@ -63,6 +64,10 @@ fn to_obj_symbol(
if obj_file.architecture() == Architecture::PowerPc {
demangled_name = cwdemangle::demangle(name, &Default::default());
}
#[cfg(feature = "x86")]
if matches!(obj_file.format(), BinaryFormat::Coff | BinaryFormat::Pe) && name.starts_with('?') {
demangled_name = msvc_demangler::demangle(name, msvc_demangler::DemangleFlags::llvm()).ok();
}
// Find the virtual address for the symbol if available
let virtual_address = split_meta
.and_then(|m| m.virtual_addresses.as_ref())
@ -225,9 +230,17 @@ fn relocations_by_section(
let mut relocations = Vec::<ObjReloc>::new();
for (address, reloc) in obj_section.relocations() {
let symbol = match reloc.target() {
RelocationTarget::Symbol(idx) => obj_file
.symbol_by_index(idx)
.context("Failed to locate relocation target symbol")?,
RelocationTarget::Symbol(idx) => {
let Ok(symbol) = obj_file.symbol_by_index(idx) else {
log::warn!(
"Failed to locate relocation {:#x} target symbol {}",
address,
idx.0
);
continue;
};
symbol
}
_ => bail!("Unhandled relocation target: {:?}", reloc.target()),
};
let kind = match reloc.flags() {
@ -241,7 +254,7 @@ fn relocations_by_section(
elf::R_PPC_REL24 => ObjRelocKind::PpcRel24,
elf::R_PPC_REL14 => ObjRelocKind::PpcRel14,
elf::R_PPC_EMB_SDA21 => ObjRelocKind::PpcEmbSda21,
_ => bail!("Unhandled PPC relocation type: {r_type}"),
_ => bail!("Unhandled ELF PPC relocation type: {r_type}"),
},
#[cfg(feature = "mips")]
ObjArchitecture::Mips => match r_type {
@ -253,8 +266,34 @@ fn relocations_by_section(
elf::R_MIPS_CALL16 => ObjRelocKind::MipsCall16,
elf::R_MIPS_GPREL16 => ObjRelocKind::MipsGpRel16,
elf::R_MIPS_GPREL32 => ObjRelocKind::MipsGpRel32,
_ => bail!("Unhandled MIPS relocation type: {r_type}"),
_ => bail!("Unhandled ELF MIPS relocation type: {r_type}"),
},
#[cfg(feature = "x86")]
ObjArchitecture::X86_32 => match r_type {
elf::R_386_32 => ObjRelocKind::Absolute,
elf::R_386_PC32 => ObjRelocKind::X86PcRel32,
_ => bail!("Unhandled ELF x86_32 relocation type: {r_type}"),
},
#[cfg(feature = "x86")]
ObjArchitecture::X86_64 => match r_type {
elf::R_X86_64_32 => ObjRelocKind::Absolute,
elf::R_X86_64_PC32 => ObjRelocKind::X86PcRel32,
_ => bail!("Unhandled ELF x86_64 relocation type: {r_type}"),
},
},
RelocationFlags::Coff { typ } => match arch {
#[cfg(feature = "ppc")]
ObjArchitecture::PowerPc => bail!("Unhandled PE/COFF PPC relocation type: {typ}"),
#[cfg(feature = "mips")]
ObjArchitecture::Mips => bail!("Unhandled PE/COFF MIPS relocation type: {typ}"),
#[cfg(feature = "x86")]
ObjArchitecture::X86_32 => match typ {
pe::IMAGE_REL_I386_DIR32 => ObjRelocKind::Absolute,
pe::IMAGE_REL_I386_REL32 => ObjRelocKind::X86PcRel32,
_ => bail!("Unhandled PE/COFF x86 relocation type: {typ}"),
},
#[cfg(feature = "x86")]
ObjArchitecture::X86_64 => bail!("Unhandled PE/COFF x86_64 relocation type: {typ}"),
},
flags => bail!("Unhandled relocation flags: {:?}", flags),
};
@ -266,9 +305,8 @@ fn relocations_by_section(
_ => None,
};
let addend = if reloc.has_implicit_addend() {
let addend = u32::from_be_bytes(
section.data[address as usize..address as usize + 4].try_into()?,
);
let data = section.data[address as usize..address as usize + 4].try_into()?;
let addend = obj_file.endianness().read_u32_bytes(data);
match kind {
ObjRelocKind::Absolute => addend as i64,
#[cfg(feature = "mips")]
@ -282,6 +320,8 @@ fn relocations_by_section(
ObjRelocKind::MipsGpRel32 => addend as i32 as i64,
#[cfg(feature = "mips")]
ObjRelocKind::Mips26 => ((addend & 0x03FFFFFF) << 2) as i64,
#[cfg(feature = "x86")]
ObjRelocKind::X86PcRel32 => addend as i32 as i64,
_ => bail!("Unsupported implicit relocation {kind:?}"),
}
} else {
@ -373,6 +413,10 @@ pub fn read(obj_path: &Path) -> Result<ObjInfo> {
Architecture::PowerPc => ObjArchitecture::PowerPc,
#[cfg(feature = "mips")]
Architecture::Mips => ObjArchitecture::Mips,
#[cfg(feature = "x86")]
Architecture::I386 => ObjArchitecture::X86_32,
#[cfg(feature = "x86")]
Architecture::X86_64 => ObjArchitecture::X86_64,
_ => bail!("Unsupported architecture: {:?}", obj_file.architecture()),
};
let split_meta = split_meta(&obj_file)?;

334
objdiff-core/src/obj/x86.rs Normal file
View File

@ -0,0 +1,334 @@
use std::collections::BTreeMap;
use anyhow::{anyhow, bail, ensure, Result};
use iced_x86::{
Decoder, DecoderOptions, DecoratorKind, Formatter, FormatterOutput, FormatterTextKind,
GasFormatter, Instruction, IntelFormatter, MasmFormatter, NasmFormatter, NumberKind, OpKind,
PrefixKind, Register, SymbolResult,
};
use crate::{
diff::{DiffObjConfig, ProcessCodeResult},
obj::{ObjIns, ObjInsArg, ObjInsArgValue, ObjReloc, ObjRelocKind},
};
#[derive(Debug, Copy, Clone, Default, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
pub enum X86Formatter {
#[default]
Intel,
Gas,
Nasm,
Masm,
}
pub fn process_code(
config: &DiffObjConfig,
data: &[u8],
bitness: u32,
start_address: u64,
relocs: &[ObjReloc],
line_info: &Option<BTreeMap<u64, u64>>,
) -> Result<ProcessCodeResult> {
let mut result = ProcessCodeResult { ops: Vec::new(), insts: Vec::new() };
let mut decoder = Decoder::with_ip(bitness, data, start_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()),
X86Formatter::Nasm => Box::new(NasmFormatter::new()),
X86Formatter::Masm => Box::new(MasmFormatter::new()),
};
formatter.options_mut().set_space_after_operand_separator(config.space_between_args);
let mut output = InstructionFormatterOutput {
formatted: String::new(),
ins: ObjIns {
address: 0,
size: 0,
op: 0,
mnemonic: "".to_string(),
args: vec![],
reloc: None,
branch_dest: None,
line: None,
orig: None,
},
error: None,
ins_operands: vec![],
};
let mut instruction = Instruction::default();
while decoder.can_decode() {
decoder.decode_out(&mut instruction);
let address = instruction.ip();
let op = instruction.mnemonic() as u16;
let reloc = relocs
.iter()
.find(|r| r.address >= address && r.address < address + instruction.len() as u64);
output.ins = ObjIns {
address,
size: instruction.len() as u8,
op,
mnemonic: "".to_string(),
args: vec![],
reloc: reloc.cloned(),
branch_dest: None,
line: line_info.as_ref().and_then(|m| m.get(&address).cloned()),
orig: None,
};
// Run the formatter, which will populate output.ins
formatter.format(&instruction, &mut output);
if let Some(error) = output.error.take() {
return Err(error);
}
ensure!(output.ins_operands.len() == output.ins.args.len());
output.ins.orig = Some(output.formatted.clone());
// print!("{:016X} ", instruction.ip());
// let start_index = (instruction.ip() - address) as usize;
// let instr_bytes = &data[start_index..start_index + instruction.len()];
// for b in instr_bytes.iter() {
// print!("{:02X}", b);
// }
// if instr_bytes.len() < 32 {
// for _ in 0..32 - instr_bytes.len() {
// print!(" ");
// }
// }
// println!(" {}", output.formatted);
//
// if let Some(reloc) = reloc {
// println!("\tReloc: {:?}", reloc);
// }
//
// for i in 0..instruction.op_count() {
// let kind = instruction.op_kind(i);
// print!("{:?} ", kind);
// }
// println!();
// Make sure we've put the relocation somewhere in the instruction
if reloc.is_some() && !output.ins.args.iter().any(|a| matches!(a, ObjInsArg::Reloc)) {
let mut found = replace_arg(
OpKind::Memory,
ObjInsArg::Reloc,
&mut output.ins.args,
&instruction,
&output.ins_operands,
)?;
if !found {
found = replace_arg(
OpKind::Immediate32,
ObjInsArg::Reloc,
&mut output.ins.args,
&instruction,
&output.ins_operands,
)?;
}
ensure!(found, "x86: Failed to find operand for Absolute relocation");
}
if reloc.is_some() && !output.ins.args.iter().any(|a| matches!(a, ObjInsArg::Reloc)) {
bail!("Failed to find relocation in instruction");
}
result.ops.push(op);
result.insts.push(output.ins.clone());
// Clear for next iteration
output.formatted.clear();
output.ins_operands.clear();
}
Ok(result)
}
fn replace_arg(
from: OpKind,
to: ObjInsArg,
args: &mut [ObjInsArg],
instruction: &Instruction,
ins_operands: &[Option<u32>],
) -> Result<bool> {
let mut replace = None;
for i in 0..instruction.op_count() {
let op_kind = instruction.op_kind(i);
if op_kind == from {
replace = Some(i);
break;
}
}
if let Some(i) = replace {
for (j, arg) in args.iter_mut().enumerate() {
if ins_operands[j] == Some(i) {
*arg = to;
return Ok(true);
}
}
}
Ok(false)
}
struct InstructionFormatterOutput {
formatted: String,
ins: ObjIns,
error: Option<anyhow::Error>,
ins_operands: Vec<Option<u32>>,
}
impl InstructionFormatterOutput {
fn push_signed(&mut self, value: i64) {
// The formatter writes the '-' operator and then gives us a negative value,
// so convert it to a positive value to avoid double negatives
if value < 0
&& matches!(self.ins.args.last(), Some(ObjInsArg::Arg(ObjInsArgValue::Opaque(v))) if v == "-")
{
self.ins.args.push(ObjInsArg::Arg(ObjInsArgValue::Signed(value.wrapping_abs())));
} else {
self.ins.args.push(ObjInsArg::Arg(ObjInsArgValue::Signed(value)));
}
}
}
impl FormatterOutput for InstructionFormatterOutput {
fn write(&mut self, text: &str, kind: FormatterTextKind) {
// log::debug!("write {} {:?}", text, kind);
self.formatted.push_str(text);
// Skip whitespace after the mnemonic
if self.ins.args.is_empty() && kind == FormatterTextKind::Text {
return;
}
self.ins_operands.push(None);
match kind {
FormatterTextKind::Text | FormatterTextKind::Punctuation => {
self.ins.args.push(ObjInsArg::PlainText(text.to_string()));
}
FormatterTextKind::Keyword | FormatterTextKind::Operator => {
self.ins.args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(text.to_string())));
}
_ => {
if self.error.is_none() {
self.error = Some(anyhow!("x86: Unsupported FormatterTextKind {:?}", kind));
}
}
}
}
fn write_prefix(&mut self, _instruction: &Instruction, text: &str, _prefix: PrefixKind) {
// log::debug!("write_prefix {} {:?}", text, prefix);
self.formatted.push_str(text);
self.ins_operands.push(None);
self.ins.args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(text.to_string())));
}
fn write_mnemonic(&mut self, _instruction: &Instruction, text: &str) {
// log::debug!("write_mnemonic {}", text);
self.formatted.push_str(text);
self.ins.mnemonic = text.to_string();
}
fn write_number(
&mut self,
_instruction: &Instruction,
_operand: u32,
instruction_operand: Option<u32>,
text: &str,
value: u64,
number_kind: NumberKind,
kind: FormatterTextKind,
) {
// log::debug!("write_number {} {:?} {} {} {:?} {:?}", operand, instruction_operand, text, value, number_kind, kind);
self.formatted.push_str(text);
self.ins_operands.push(instruction_operand);
// Handle relocations
match kind {
FormatterTextKind::LabelAddress => {
if let Some(reloc) = self.ins.reloc.as_ref() {
if reloc.kind == ObjRelocKind::Absolute {
self.ins.args.push(ObjInsArg::Reloc);
return;
} else if self.error.is_none() {
self.error = Some(anyhow!(
"x86: Unsupported LabelAddress relocation kind {:?}",
reloc.kind
));
}
}
self.ins.args.push(ObjInsArg::BranchDest(value));
self.ins.branch_dest = Some(value);
return;
}
FormatterTextKind::FunctionAddress => {
if let Some(reloc) = self.ins.reloc.as_ref() {
if reloc.kind == ObjRelocKind::X86PcRel32 {
self.ins.args.push(ObjInsArg::Reloc);
return;
} else if self.error.is_none() {
self.error = Some(anyhow!(
"x86: Unsupported FunctionAddress relocation kind {:?}",
reloc.kind
));
}
}
}
_ => {}
}
match number_kind {
NumberKind::Int8 => {
self.push_signed(value as i8 as i64);
}
NumberKind::Int16 => {
self.push_signed(value as i16 as i64);
}
NumberKind::Int32 => {
self.push_signed(value as i32 as i64);
}
NumberKind::Int64 => {
self.push_signed(value as i64);
}
NumberKind::UInt8 | NumberKind::UInt16 | NumberKind::UInt32 | NumberKind::UInt64 => {
self.ins.args.push(ObjInsArg::Arg(ObjInsArgValue::Unsigned(value)));
}
}
}
fn write_decorator(
&mut self,
_instruction: &Instruction,
_operand: u32,
instruction_operand: Option<u32>,
text: &str,
_decorator: DecoratorKind,
) {
// log::debug!("write_decorator {} {:?} {} {:?}", operand, instruction_operand, text, decorator);
self.formatted.push_str(text);
self.ins_operands.push(instruction_operand);
self.ins.args.push(ObjInsArg::PlainText(text.to_string()));
}
fn write_register(
&mut self,
_instruction: &Instruction,
_operand: u32,
instruction_operand: Option<u32>,
text: &str,
_register: Register,
) {
// log::debug!("write_register {} {:?} {} {:?}", operand, instruction_operand, text, register);
self.formatted.push_str(text);
self.ins_operands.push(instruction_operand);
self.ins.args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(text.to_string())));
}
fn write_symbol(
&mut self,
_instruction: &Instruction,
_operand: u32,
_instruction_operand: Option<u32>,
_address: u64,
_symbol: &SymbolResult<'_>,
) {
if self.error.is_none() {
self.error = Some(anyhow!("x86: Unsupported write_symbol"));
}
}
}

View File

@ -7,7 +7,7 @@ pub(crate) struct ReallySigned<N: PrimInt>(pub(crate) N);
impl<N: PrimInt> LowerHex for ReallySigned<N> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let num = self.0.to_i32().unwrap();
let num = self.0.to_i64().unwrap();
let prefix = if f.alternate() { "0x" } else { "" };
let bare_hex = format!("{:x}", num.abs());
f.pad_integral(num >= 0, prefix, &bare_hex)
@ -16,7 +16,7 @@ impl<N: PrimInt> LowerHex for ReallySigned<N> {
impl<N: PrimInt> UpperHex for ReallySigned<N> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let num = self.0.to_i32().unwrap();
let num = self.0.to_i64().unwrap();
let prefix = if f.alternate() { "0x" } else { "" };
let bare_hex = format!("{:X}", num.abs());
f.pad_integral(num >= 0, prefix, &bare_hex)

View File

@ -12,8 +12,11 @@ use std::{
use filetime::FileTime;
use globset::{Glob, GlobSet};
use notify::{RecursiveMode, Watcher};
use objdiff_core::config::{
build_globset, ProjectConfigInfo, ProjectObject, ScratchConfig, DEFAULT_WATCH_PATTERNS,
use objdiff_core::{
config::{
build_globset, ProjectConfigInfo, ProjectObject, ScratchConfig, DEFAULT_WATCH_PATTERNS,
},
diff::DiffObjConfig,
};
use time::UtcOffset;
@ -26,7 +29,9 @@ use crate::{
},
views::{
appearance::{appearance_window, Appearance},
config::{config_ui, project_window, ConfigViewState, CONFIG_DISABLED_TEXT},
config::{
config_ui, diff_config_window, project_window, ConfigViewState, CONFIG_DISABLED_TEXT,
},
data_diff::data_diff_ui,
debug::debug_window,
demangle::{demangle_window, DemangleViewState},
@ -47,6 +52,7 @@ pub struct ViewState {
pub show_appearance_config: bool,
pub show_demangle: bool,
pub show_project_config: bool,
pub show_diff_config: bool,
pub show_debug: bool,
}
@ -100,7 +106,7 @@ pub struct AppConfig {
#[serde(default)]
pub recent_projects: Vec<PathBuf>,
#[serde(default)]
pub relax_reloc_diffs: bool,
pub diff_obj_config: DiffObjConfig,
#[serde(skip)]
pub objects: Vec<ProjectObject>,
@ -138,7 +144,7 @@ impl Default for AppConfig {
auto_update_check: true,
watch_patterns: DEFAULT_WATCH_PATTERNS.iter().map(|s| Glob::new(s).unwrap()).collect(),
recent_projects: vec![],
relax_reloc_diffs: false,
diff_obj_config: Default::default(),
objects: vec![],
object_nodes: vec![],
watcher_change: false,
@ -408,6 +414,7 @@ impl eframe::App for App {
show_appearance_config,
show_demangle,
show_project_config,
show_diff_config,
show_debug,
} = view_state;
@ -461,6 +468,10 @@ impl eframe::App for App {
}
});
ui.menu_button("Diff Options", |ui| {
if ui.button("More…").clicked() {
*show_diff_config = !*show_diff_config;
ui.close_menu();
}
let mut config = config.write().unwrap();
let response = ui
.checkbox(&mut config.rebuild_on_changes, "Rebuild on changes")
@ -481,7 +492,10 @@ impl eframe::App for App {
"Show hidden symbols",
);
if ui
.checkbox(&mut config.relax_reloc_diffs, "Relax relocation diffs")
.checkbox(
&mut config.diff_obj_config.relax_reloc_diffs,
"Relax relocation diffs",
)
.on_hover_text(
"Ignores differences in relocation targets. (Address, name, etc)",
)
@ -489,6 +503,15 @@ impl eframe::App for App {
{
config.queue_reload = true;
}
if ui
.checkbox(
&mut config.diff_obj_config.space_between_args,
"Space between args",
)
.changed()
{
config.queue_reload = true;
}
});
});
});
@ -518,6 +541,7 @@ impl eframe::App for App {
project_window(ctx, config, show_project_config, config_state, appearance);
appearance_window(ctx, show_appearance_config, appearance);
demangle_window(ctx, show_demangle, demangle_state, appearance);
diff_config_window(ctx, config, show_diff_config, appearance);
debug_window(ctx, show_debug, frame_history, appearance);
self.post_update(ctx);

View File

@ -8,7 +8,7 @@ use std::{
use anyhow::{anyhow, Context, Error, Result};
use objdiff_core::{
diff::{diff_objs, DiffObjConfig},
obj::{elf, ObjInfo},
obj::{read, ObjInfo},
};
use time::OffsetDateTime;
@ -57,7 +57,7 @@ pub struct ObjDiffConfig {
pub build_base: bool,
pub build_target: bool,
pub selected_obj: Option<ObjectConfig>,
pub relax_reloc_diffs: bool,
pub diff_obj_config: DiffObjConfig,
}
impl ObjDiffConfig {
@ -67,7 +67,7 @@ impl ObjDiffConfig {
build_base: config.build_base,
build_target: config.build_target,
selected_obj: config.selected_obj.clone(),
relax_reloc_diffs: config.relax_reloc_diffs,
diff_obj_config: config.diff_obj_config.clone(),
}
}
}
@ -224,7 +224,7 @@ fn run_build(
total,
&cancel,
)?;
Some(elf::read(target_path).with_context(|| {
Some(read::read(target_path).with_context(|| {
format!("Failed to read object '{}'", target_path.display())
})?)
}
@ -241,7 +241,7 @@ fn run_build(
&cancel,
)?;
Some(
elf::read(base_path)
read::read(base_path)
.with_context(|| format!("Failed to read object '{}'", base_path.display()))?,
)
}
@ -249,8 +249,7 @@ fn run_build(
};
update_status(context, "Performing diff".to_string(), 4, total, &cancel)?;
let diff_config = DiffObjConfig { relax_reloc_diffs: config.relax_reloc_diffs };
diff_objs(&diff_config, first_obj.as_mut(), second_obj.as_mut())?;
diff_objs(&config.diff_obj_config, first_obj.as_mut(), second_obj.as_mut())?;
update_status(context, "Complete".to_string(), total, total, &cancel)?;
Ok(Box::new(ObjDiffResult { first_status, second_status, first_obj, second_obj, time }))

View File

@ -14,7 +14,10 @@ use egui::{
SelectableLabel, TextFormat, Widget,
};
use globset::Glob;
use objdiff_core::config::{ProjectObject, DEFAULT_WATCH_PATTERNS};
use objdiff_core::{
config::{ProjectObject, DEFAULT_WATCH_PATTERNS},
obj::x86::X86Formatter,
};
use self_update::cargo_crate_version;
use crate::{
@ -838,3 +841,36 @@ fn split_obj_config_ui(
}
});
}
pub fn diff_config_window(
ctx: &egui::Context,
config: &AppConfigRef,
show: &mut bool,
appearance: &Appearance,
) {
let mut config_guard = config.write().unwrap();
egui::Window::new("Diff Config").open(show).show(ctx, |ui| {
diff_config_ui(ui, &mut config_guard, appearance);
});
}
fn diff_config_ui(ui: &mut egui::Ui, config: &mut AppConfig, _appearance: &Appearance) {
egui::ComboBox::new("x86_formatter", "X86 Format")
.selected_text(format!("{:?}", config.diff_obj_config.x86_formatter))
.show_ui(ui, |ui| {
for &formatter in
&[X86Formatter::Intel, X86Formatter::Gas, X86Formatter::Nasm, X86Formatter::Masm]
{
if ui
.selectable_label(
config.diff_obj_config.x86_formatter == formatter,
format!("{:?}", formatter),
)
.clicked()
{
config.diff_obj_config.x86_formatter = formatter;
config.queue_reload = true;
}
}
});
}

View File

@ -4,7 +4,10 @@ use egui::{text::LayoutJob, Align, Label, Layout, Sense, Vec2, Widget};
use egui_extras::{Column, TableBuilder, TableRow};
use objdiff_core::{
diff::display::{display_diff, DiffText, HighlightKind},
obj::{ObjInfo, ObjIns, ObjInsArg, ObjInsArgValue, ObjInsDiff, ObjInsDiffKind, ObjSymbol},
obj::{
ObjInfo, ObjIns, ObjInsArg, ObjInsArgValue, ObjInsDiff, ObjInsDiffKind, ObjSection,
ObjSymbol,
},
};
use time::format_description;
@ -18,19 +21,23 @@ pub struct FunctionViewState {
pub highlight: HighlightKind,
}
fn ins_hover_ui(ui: &mut egui::Ui, ins: &ObjIns, appearance: &Appearance) {
fn ins_hover_ui(ui: &mut egui::Ui, section: &ObjSection, ins: &ObjIns, appearance: &Appearance) {
ui.scope(|ui| {
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
ui.style_mut().wrap = Some(false);
ui.label(format!("{:02X?}", ins.code.to_be_bytes()));
let offset = ins.address - section.address;
ui.label(format!(
"{:02X?}",
&section.data[offset as usize..(offset + ins.size as u64) as usize]
));
if let Some(orig) = &ins.orig {
ui.label(format!("Original: {}", orig));
}
for arg in &ins.args {
if let ObjInsArg::Arg(arg) | ObjInsArg::ArgWithBase(arg) = arg {
if let ObjInsArg::Arg(arg) = arg {
match arg {
ObjInsArgValue::Signed(v) => {
ui.label(format!("{arg} == {v}"));
@ -71,7 +78,7 @@ fn ins_context_menu(ui: &mut egui::Ui, ins: &ObjIns) {
// if ui.button("Copy hex").clicked() {}
for arg in &ins.args {
if let ObjInsArg::Arg(arg) | ObjInsArg::ArgWithBase(arg) = arg {
if let ObjInsArg::Arg(arg) = arg {
match arg {
ObjInsArgValue::Signed(v) => {
if ui.button(format!("Copy \"{arg}\"")).clicked() {
@ -112,9 +119,14 @@ fn ins_context_menu(ui: &mut egui::Ui, ins: &ObjIns) {
});
}
fn find_symbol<'a>(obj: &'a ObjInfo, selected_symbol: &SymbolReference) -> Option<&'a ObjSymbol> {
fn find_symbol<'a>(
obj: &'a ObjInfo,
selected_symbol: &SymbolReference,
) -> Option<(&'a ObjSection, &'a ObjSymbol)> {
obj.sections.iter().find_map(|section| {
section.symbols.iter().find(|symbol| symbol.name == selected_symbol.symbol_name)
section.symbols.iter().find_map(|symbol| {
(symbol.name == selected_symbol.symbol_name).then_some((section, symbol))
})
})
}
@ -166,7 +178,7 @@ fn diff_text_ui(
base_color = appearance.diff_colors[diff.idx % appearance.diff_colors.len()]
}
}
DiffText::BranchTarget(addr) => {
DiffText::BranchDest(addr) => {
label_text = format!("{addr:x}");
}
DiffText::Symbol(sym) => {
@ -216,7 +228,7 @@ fn asm_row_ui(
ui.painter().rect_filled(ui.available_rect_before_wrap(), 0.0, ui.visuals().faint_bg_color);
}
let space_width = ui.fonts(|f| f.glyph_width(&appearance.code_font, ' '));
display_diff(ins_diff, symbol.address as u32, |text| {
display_diff(ins_diff, symbol.address, |text| {
diff_text_ui(ui, text, ins_diff, appearance, ins_view_state, space_width);
Ok::<_, ()>(())
})
@ -226,6 +238,7 @@ fn asm_row_ui(
fn asm_col_ui(
row: &mut TableRow<'_, '_>,
ins_diff: &ObjInsDiff,
section: &ObjSection,
symbol: &ObjSymbol,
appearance: &Appearance,
ins_view_state: &mut FunctionViewState,
@ -234,7 +247,7 @@ fn asm_col_ui(
asm_row_ui(ui, ins_diff, symbol, appearance, ins_view_state);
});
if let Some(ins) = &ins_diff.ins {
response.on_hover_ui_at_pointer(|ui| ins_hover_ui(ui, ins, appearance));
response.on_hover_ui_at_pointer(|ui| ins_hover_ui(ui, section, ins, appearance));
}
}
@ -254,14 +267,15 @@ fn asm_table_ui(
) -> Option<()> {
let left_symbol = left_obj.and_then(|obj| find_symbol(obj, selected_symbol));
let right_symbol = right_obj.and_then(|obj| find_symbol(obj, selected_symbol));
let instructions_len = left_symbol.or(right_symbol).map(|s| s.instructions.len())?;
let instructions_len = left_symbol.or(right_symbol).map(|(_, s)| s.instructions.len())?;
table.body(|body| {
body.rows(appearance.code_font.size, instructions_len, |mut row| {
let row_index = row.index();
if let Some(symbol) = left_symbol {
if let Some((section, symbol)) = left_symbol {
asm_col_ui(
&mut row,
&symbol.instructions[row_index],
section,
symbol,
appearance,
ins_view_state,
@ -269,10 +283,11 @@ fn asm_table_ui(
} else {
empty_col_ui(&mut row);
}
if let Some(symbol) = right_symbol {
if let Some((section, symbol)) = right_symbol {
asm_col_ui(
&mut row,
&symbol.instructions[row_index],
section,
symbol,
appearance,
ins_view_state,
@ -384,7 +399,7 @@ pub fn function_diff_ui(ui: &mut egui::Ui, state: &mut DiffViewState, appearance
.second_obj
.as_ref()
.and_then(|obj| find_symbol(obj, selected_symbol))
.and_then(|symbol| symbol.match_percent)
.and_then(|(_, symbol)| symbol.match_percent)
{
ui.colored_label(
match_color_for_symbol(match_percent, appearance),