diff --git a/Cargo.lock b/Cargo.lock index df7f2e6..c09b4fc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2457,7 +2457,7 @@ dependencies = [ [[package]] name = "objdiff" -version = "0.4.4" +version = "0.5.0" dependencies = [ "anyhow", "byteorder", diff --git a/Cargo.toml b/Cargo.toml index c658753..ca8794c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "objdiff" -version = "0.4.4" +version = "0.5.0" edition = "2021" rust-version = "1.65" authors = ["Luke Street "] diff --git a/src/obj/mips.rs b/src/obj/mips.rs index a4532e9..d9b1b0c 100644 --- a/src/obj/mips.rs +++ b/src/obj/mips.rs @@ -91,6 +91,7 @@ pub fn process_code( reloc: reloc.cloned(), branch_dest, line, + orig: None, }); cur_addr += 4; } diff --git a/src/obj/mod.rs b/src/obj/mod.rs index 29fe2e1..d03bebb 100644 --- a/src/obj/mod.rs +++ b/src/obj/mod.rs @@ -37,6 +37,7 @@ pub struct ObjSection { pub data_diff: Vec, pub match_percent: f32, } + #[derive(Debug, Clone)] pub enum ObjInsArg { PpcArg(ppc750cl::Argument), @@ -46,6 +47,40 @@ pub enum ObjInsArg { RelocWithBase, BranchOffset(i32), } + +// TODO derive PartialEq on ppc750cl::Argument so this isn't necessary +impl PartialEq for ObjInsArg { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (ObjInsArg::PpcArg(a), ObjInsArg::PpcArg(b)) => { + use ppc750cl::Argument; + match (a, b) { + (Argument::GPR(a), Argument::GPR(b)) => a == b, + (Argument::FPR(a), Argument::FPR(b)) => a == b, + (Argument::SR(a), Argument::SR(b)) => a == b, + (Argument::SPR(a), Argument::SPR(b)) => a == b, + (Argument::CRField(a), Argument::CRField(b)) => a == b, + (Argument::CRBit(a), Argument::CRBit(b)) => a == b, + (Argument::GQR(a), Argument::GQR(b)) => a == b, + (Argument::Uimm(a), Argument::Uimm(b)) => a == b, + (Argument::Simm(a), Argument::Simm(b)) => a == b, + (Argument::Offset(a), Argument::Offset(b)) => a == b, + (Argument::BranchDest(a), Argument::BranchDest(b)) => a == b, + (Argument::Bit(a), Argument::Bit(b)) => a == b, + (Argument::OpaqueU(a), Argument::OpaqueU(b)) => a == b, + (_, _) => false, + } + } + (ObjInsArg::MipsArg(a), ObjInsArg::MipsArg(b)) => a == b, + (ObjInsArg::MipsArgWithBase(a), ObjInsArg::MipsArgWithBase(b)) => a == b, + (ObjInsArg::Reloc, ObjInsArg::Reloc) => true, + (ObjInsArg::RelocWithBase, ObjInsArg::RelocWithBase) => true, + (ObjInsArg::BranchOffset(a), ObjInsArg::BranchOffset(b)) => a == b, + (_, _) => false, + } + } +} + #[derive(Debug, Copy, Clone)] pub struct ObjInsArgDiff { /// Incrementing index for coloring @@ -86,6 +121,8 @@ pub struct ObjIns { pub branch_dest: Option, /// Line info pub line: Option, + /// Original (unsimplified) instruction + pub orig: Option, } #[derive(Debug, Clone, Default)] pub struct ObjInsDiff { diff --git a/src/obj/ppc.rs b/src/obj/ppc.rs index 85fe2e0..b332fd4 100644 --- a/src/obj/ppc.rs +++ b/src/obj/ppc.rs @@ -1,7 +1,7 @@ use std::collections::BTreeMap; use anyhow::Result; -use ppc750cl::{disasm_iter, Argument}; +use ppc750cl::{disasm_iter, Argument, Ins, SimplifiedIns}; use crate::obj::{ObjIns, ObjInsArg, ObjReloc, ObjRelocKind}; @@ -40,7 +40,7 @@ pub fn process_code( _ => ins.code, }; } - let simplified = ins.simplified(); + let simplified = ins.clone().simplified(); let mut args: Vec = simplified .args .iter() @@ -86,10 +86,21 @@ pub fn process_code( mnemonic: format!("{}{}", simplified.mnemonic, simplified.suffix), args, reloc: reloc.cloned(), - op: 0, + op: ins.op as u8, branch_dest: None, line, + orig: Some(format!("{}", basic_form(ins))), }); } Ok((ops, insts)) } + +// TODO make public in ppc750cl +fn basic_form(ins: Ins) -> SimplifiedIns { + SimplifiedIns { + mnemonic: ins.op.mnemonic(), + suffix: ins.suffix(), + args: ins.fields().iter().flat_map(|field| field.argument()).collect(), + ins, + } +} diff --git a/src/views/function_diff.rs b/src/views/function_diff.rs index c827c1f..c7239c8 100644 --- a/src/views/function_diff.rs +++ b/src/views/function_diff.rs @@ -1,9 +1,12 @@ -use std::{cmp::Ordering, default::Default}; +use std::{ + cmp::{max, Ordering}, + default::Default, +}; use cwdemangle::demangle; use eframe::emath::Align; -use egui::{text::LayoutJob, Color32, FontId, Label, Layout, Sense, Vec2}; -use egui_extras::{Column, TableBuilder}; +use egui::{text::LayoutJob, Color32, Label, Layout, RichText, Sense, TextFormat, Vec2}; +use egui_extras::{Column, TableBuilder, TableRow}; use ppc750cl::Argument; use time::format_description; @@ -19,21 +22,49 @@ use crate::{ }, }; +#[derive(Default)] +pub enum HighlightKind { + #[default] + None, + Opcode(u8), + Arg(ObjInsArg), + Symbol(String), + Address(u32), +} + +#[derive(Default)] +pub struct FunctionViewState { + pub highlight: HighlightKind, +} + fn write_reloc_name( reloc: &ObjReloc, color: Color32, + background_color: Color32, job: &mut LayoutJob, - font_id: FontId, appearance: &Appearance, ) { let name = reloc.target.demangled_name.as_ref().unwrap_or(&reloc.target.name); - write_text(name, appearance.emphasized_text_color, job, font_id.clone()); + job.append(name, 0.0, TextFormat { + font_id: appearance.code_font.clone(), + color: appearance.emphasized_text_color, + background: background_color, + ..Default::default() + }); match reloc.target.addend.cmp(&0i64) { - Ordering::Greater => { - write_text(&format!("+{:#X}", reloc.target.addend), color, job, font_id) - } + Ordering::Greater => write_text( + &format!("+{:#X}", reloc.target.addend), + color, + job, + appearance.code_font.clone(), + ), Ordering::Less => { - write_text(&format!("-{:#X}", -reloc.target.addend), color, job, font_id); + write_text( + &format!("-{:#X}", -reloc.target.addend), + color, + job, + appearance.code_font.clone(), + ); } _ => {} } @@ -42,57 +73,57 @@ fn write_reloc_name( fn write_reloc( reloc: &ObjReloc, color: Color32, + background_color: Color32, job: &mut LayoutJob, - font_id: FontId, appearance: &Appearance, ) { match reloc.kind { ObjRelocKind::PpcAddr16Lo => { - write_reloc_name(reloc, color, job, font_id.clone(), appearance); - write_text("@l", color, job, font_id); + write_reloc_name(reloc, color, background_color, job, appearance); + write_text("@l", color, job, appearance.code_font.clone()); } ObjRelocKind::PpcAddr16Hi => { - write_reloc_name(reloc, color, job, font_id.clone(), appearance); - write_text("@h", color, job, font_id); + write_reloc_name(reloc, color, background_color, job, appearance); + write_text("@h", color, job, appearance.code_font.clone()); } ObjRelocKind::PpcAddr16Ha => { - write_reloc_name(reloc, color, job, font_id.clone(), appearance); - write_text("@ha", color, job, font_id); + write_reloc_name(reloc, color, background_color, job, appearance); + write_text("@ha", color, job, appearance.code_font.clone()); } ObjRelocKind::PpcEmbSda21 => { - write_reloc_name(reloc, color, job, font_id.clone(), appearance); - write_text("@sda21", color, job, font_id); + write_reloc_name(reloc, color, background_color, job, appearance); + write_text("@sda21", color, job, appearance.code_font.clone()); } ObjRelocKind::MipsHi16 => { - write_text("%hi(", color, job, font_id.clone()); - write_reloc_name(reloc, color, job, font_id.clone(), appearance); - write_text(")", color, job, font_id); + write_text("%hi(", color, job, appearance.code_font.clone()); + write_reloc_name(reloc, color, background_color, job, appearance); + write_text(")", color, job, appearance.code_font.clone()); } ObjRelocKind::MipsLo16 => { - write_text("%lo(", color, job, font_id.clone()); - write_reloc_name(reloc, color, job, font_id.clone(), appearance); - write_text(")", color, job, font_id); + write_text("%lo(", color, job, appearance.code_font.clone()); + write_reloc_name(reloc, color, background_color, job, appearance); + write_text(")", color, job, appearance.code_font.clone()); } ObjRelocKind::MipsGot16 => { - write_text("%got(", color, job, font_id.clone()); - write_reloc_name(reloc, color, job, font_id.clone(), appearance); - write_text(")", color, job, font_id); + write_text("%got(", color, job, appearance.code_font.clone()); + write_reloc_name(reloc, color, background_color, job, appearance); + write_text(")", color, job, appearance.code_font.clone()); } ObjRelocKind::MipsCall16 => { - write_text("%call16(", color, job, font_id.clone()); - write_reloc_name(reloc, color, job, font_id.clone(), appearance); - write_text(")", color, job, font_id); + write_text("%call16(", color, job, appearance.code_font.clone()); + write_reloc_name(reloc, color, background_color, job, appearance); + write_text(")", color, job, appearance.code_font.clone()); } ObjRelocKind::MipsGpRel16 => { - write_text("%gp_rel(", color, job, font_id.clone()); - write_reloc_name(reloc, color, job, font_id.clone(), appearance); - write_text(")", color, job, font_id); + write_text("%gp_rel(", color, job, appearance.code_font.clone()); + write_reloc_name(reloc, color, background_color, job, appearance); + write_text(")", color, job, appearance.code_font.clone()); } ObjRelocKind::PpcRel24 | ObjRelocKind::PpcRel14 | ObjRelocKind::Mips26 => { - write_reloc_name(reloc, color, job, font_id, appearance); + write_reloc_name(reloc, color, background_color, job, appearance); } ObjRelocKind::Absolute | ObjRelocKind::MipsGpRel32 => { - write_text("[INVALID]", color, job, font_id); + write_text("[INVALID]", color, job, appearance.code_font.clone()); } }; } @@ -102,8 +133,9 @@ fn write_ins( diff_kind: &ObjInsDiffKind, args: &[Option], base_addr: u32, - job: &mut LayoutJob, + ui: &mut egui::Ui, appearance: &Appearance, + ins_view_state: &mut FunctionViewState, ) { let base_color = match diff_kind { ObjInsDiffKind::None | ObjInsDiffKind::OpMismatch | ObjInsDiffKind::ArgMismatch => { @@ -113,49 +145,92 @@ fn write_ins( ObjInsDiffKind::Delete => appearance.delete_color, ObjInsDiffKind::Insert => appearance.insert_color, }; - write_text( - &format!("{:<11}", ins.mnemonic), - match diff_kind { - ObjInsDiffKind::OpMismatch => appearance.replace_color, - _ => base_color, - }, - job, - appearance.code_font.clone(), - ); + + let highlighted_op = + matches!(ins_view_state.highlight, HighlightKind::Opcode(op) if op == ins.op); + let op_label = RichText::new(ins.mnemonic.clone()) + .font(appearance.code_font.clone()) + .color(if highlighted_op { + appearance.emphasized_text_color + } else { + match diff_kind { + ObjInsDiffKind::OpMismatch => appearance.replace_color, + _ => base_color, + } + }) + .background_color(if highlighted_op { + appearance.deemphasized_text_color + } else { + Color32::TRANSPARENT + }); + if ui.add(Label::new(op_label).sense(Sense::click())).clicked() { + if highlighted_op { + ins_view_state.highlight = HighlightKind::None; + } else { + ins_view_state.highlight = HighlightKind::Opcode(ins.op); + } + } + let space_width = ui.fonts(|f| f.glyph_width(&appearance.code_font, ' ')); + ui.add_space(space_width * (max(11, ins.mnemonic.len()) - ins.mnemonic.len()) as f32); + let mut writing_offset = false; for (i, arg) in ins.args.iter().enumerate() { + let mut job = LayoutJob::default(); if i == 0 { - write_text(" ", base_color, job, appearance.code_font.clone()); + write_text(" ", base_color, &mut job, appearance.code_font.clone()); } if i > 0 && !writing_offset { - write_text(", ", base_color, job, appearance.code_font.clone()); + write_text(", ", base_color, &mut job, appearance.code_font.clone()); } - let color = if let Some(diff) = args.get(i).and_then(|a| a.as_ref()) { + let highlighted_arg = match &ins_view_state.highlight { + HighlightKind::Symbol(v) => { + matches!(arg, ObjInsArg::Reloc | ObjInsArg::RelocWithBase) + && matches!(&ins.reloc, Some(reloc) if &reloc.target.name == v) + } + HighlightKind::Address(v) => { + matches!(arg, ObjInsArg::BranchOffset(offset) if (offset + ins.address as i32 - base_addr as i32) as u32 == *v) + } + HighlightKind::Arg(v) => v == arg, + _ => false, + }; + let color = if highlighted_arg { + appearance.emphasized_text_color + } else if let Some(diff) = args.get(i).and_then(|a| a.as_ref()) { appearance.diff_colors[diff.idx % appearance.diff_colors.len()] } else { base_color }; + let text_format = TextFormat { + font_id: appearance.code_font.clone(), + color, + background: if highlighted_arg { + appearance.deemphasized_text_color + } else { + Color32::TRANSPARENT + }, + ..Default::default() + }; + let mut new_writing_offset = false; match arg { ObjInsArg::PpcArg(arg) => match arg { Argument::Offset(val) => { - write_text(&format!("{val}"), color, job, appearance.code_font.clone()); - write_text("(", base_color, job, appearance.code_font.clone()); - writing_offset = true; - continue; + job.append(&format!("{val}"), 0.0, text_format); + write_text("(", base_color, &mut job, appearance.code_font.clone()); + new_writing_offset = true; } Argument::Uimm(_) | Argument::Simm(_) => { - write_text(&format!("{arg}"), color, job, appearance.code_font.clone()); + job.append(&format!("{arg}"), 0.0, text_format); } _ => { - write_text(&format!("{arg}"), color, job, appearance.code_font.clone()); + job.append(&format!("{arg}"), 0.0, text_format); } }, ObjInsArg::Reloc => { write_reloc( ins.reloc.as_ref().unwrap(), base_color, - job, - appearance.code_font.clone(), + text_format.background, + &mut job, appearance, ); } @@ -163,41 +238,42 @@ fn write_ins( write_reloc( ins.reloc.as_ref().unwrap(), base_color, - job, - appearance.code_font.clone(), + text_format.background, + &mut job, appearance, ); - write_text("(", base_color, job, appearance.code_font.clone()); - writing_offset = true; - continue; + write_text("(", base_color, &mut job, appearance.code_font.clone()); + new_writing_offset = true; } ObjInsArg::MipsArg(str) => { - write_text( - str.strip_prefix('$').unwrap_or(str), - color, - job, - appearance.code_font.clone(), - ); + job.append(str.strip_prefix('$').unwrap_or(str), 0.0, text_format); } ObjInsArg::MipsArgWithBase(str) => { - write_text( - str.strip_prefix('$').unwrap_or(str), - color, - job, - appearance.code_font.clone(), - ); - write_text("(", base_color, job, appearance.code_font.clone()); - writing_offset = true; - continue; + job.append(str.strip_prefix('$').unwrap_or(str), 0.0, text_format); + write_text("(", base_color, &mut job, appearance.code_font.clone()); + new_writing_offset = true; } ObjInsArg::BranchOffset(offset) => { let addr = offset + ins.address as i32 - base_addr as i32; - write_text(&format!("{addr:x}"), color, job, appearance.code_font.clone()); + job.append(&format!("{addr:x}"), 0.0, text_format); } } if writing_offset { - write_text(")", base_color, job, appearance.code_font.clone()); - writing_offset = false; + write_text(")", base_color, &mut job, appearance.code_font.clone()); + } + writing_offset = new_writing_offset; + if ui.add(Label::new(job).sense(Sense::click())).clicked() { + if highlighted_arg { + ins_view_state.highlight = HighlightKind::None; + } else if matches!(arg, ObjInsArg::Reloc | ObjInsArg::RelocWithBase) { + ins_view_state.highlight = + HighlightKind::Symbol(ins.reloc.as_ref().unwrap().target.name.clone()); + } else if let ObjInsArg::BranchOffset(offset) = arg { + ins_view_state.highlight = + HighlightKind::Address((offset + ins.address as i32 - base_addr as i32) as u32); + } else { + ins_view_state.highlight = HighlightKind::Arg(arg.clone()); + } } } } @@ -209,6 +285,10 @@ fn ins_hover_ui(ui: &mut egui::Ui, ins: &ObjIns, appearance: &Appearance) { ui.label(format!("{:02X?}", ins.code.to_be_bytes())); + if let Some(orig) = &ins.orig { + ui.label(format!("Original: {}", orig)); + } + for arg in &ins.args { if let ObjInsArg::PpcArg(arg) = arg { match arg { @@ -316,7 +396,9 @@ fn asm_row_ui( ins_diff: &ObjInsDiff, symbol: &ObjSymbol, appearance: &Appearance, + ins_view_state: &mut FunctionViewState, ) { + ui.spacing_mut().item_spacing.x = 0.0; if ins_diff.kind != ObjInsDiffKind::None { ui.painter().rect_filled(ui.available_rect_before_wrap(), 0.0, ui.visuals().faint_bg_color); } @@ -345,34 +427,91 @@ fn asm_row_ui( ); pad = 12 - line_str.len(); } - write_text( - &format!("{:<1$}", format!("{:x}: ", ins.address - symbol.address as u32), pad), - base_color, - &mut job, - appearance.code_font.clone(), + let base_addr = symbol.address as u32; + let addr_highlight = matches!( + &ins_view_state.highlight, + HighlightKind::Address(v) if *v == (ins.address - base_addr) ); - if let Some(branch) = &ins_diff.branch_from { - write_text( - "~> ", - appearance.diff_colors[branch.branch_idx % appearance.diff_colors.len()], - &mut job, - appearance.code_font.clone(), - ); - } else { - write_text(" ", base_color, &mut job, appearance.code_font.clone()); + let addr_string = format!("{:x}", ins.address - symbol.address as u32); + pad -= addr_string.len(); + job.append(&addr_string, 0.0, TextFormat { + font_id: appearance.code_font.clone(), + color: if addr_highlight { appearance.emphasized_text_color } else { base_color }, + background: if addr_highlight { + appearance.deemphasized_text_color + } else { + Color32::TRANSPARENT + }, + ..Default::default() + }); + if ui.add(Label::new(job).sense(Sense::click())).clicked() { + if addr_highlight { + ins_view_state.highlight = HighlightKind::None; + } else { + ins_view_state.highlight = HighlightKind::Address(ins.address - base_addr); + } } - write_ins(ins, &ins_diff.kind, &ins_diff.arg_diff, symbol.address as u32, &mut job, appearance); + + let mut job = LayoutJob::default(); + let space_width = ui.fonts(|f| f.glyph_width(&appearance.code_font, ' ')); + let spacing = space_width * pad as f32; + job.append(": ", 0.0, TextFormat { + font_id: appearance.code_font.clone(), + color: base_color, + ..Default::default() + }); + if let Some(branch) = &ins_diff.branch_from { + job.append("~> ", spacing, TextFormat { + font_id: appearance.code_font.clone(), + color: appearance.diff_colors[branch.branch_idx % appearance.diff_colors.len()], + ..Default::default() + }); + } else { + job.append(" ", spacing, TextFormat { + font_id: appearance.code_font.clone(), + color: base_color, + ..Default::default() + }); + } + ui.add(Label::new(job)); + write_ins(ins, &ins_diff.kind, &ins_diff.arg_diff, base_addr, ui, appearance, ins_view_state); if let Some(branch) = &ins_diff.branch_to { + let mut job = LayoutJob::default(); write_text( " ~>", appearance.diff_colors[branch.branch_idx % appearance.diff_colors.len()], &mut job, appearance.code_font.clone(), ); + ui.add(Label::new(job)); } - ui.add(Label::new(job).sense(Sense::click())) - .on_hover_ui_at_pointer(|ui| ins_hover_ui(ui, ins, appearance)) - .context_menu(|ui| ins_context_menu(ui, ins)); +} + +fn asm_col_ui( + row: &mut TableRow<'_, '_>, + ins_diff: &ObjInsDiff, + symbol: &ObjSymbol, + appearance: &Appearance, + ins_view_state: &mut FunctionViewState, +) { + let (_, response) = row.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); + }) + .context_menu(|ui| { + ins_context_menu(ui, ins); + }); + } +} + +fn empty_col_ui(row: &mut TableRow<'_, '_>) { + row.col(|ui| { + ui.label(""); + }); } fn asm_table_ui( @@ -381,22 +520,35 @@ fn asm_table_ui( right_obj: Option<&ObjInfo>, selected_symbol: &SymbolReference, appearance: &Appearance, + ins_view_state: &mut FunctionViewState, ) -> 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())?; table.body(|body| { body.rows(appearance.code_font.size, instructions_len, |row_index, mut row| { - row.col(|ui| { - if let Some(symbol) = left_symbol { - asm_row_ui(ui, &symbol.instructions[row_index], symbol, appearance); - } - }); - row.col(|ui| { - if let Some(symbol) = right_symbol { - asm_row_ui(ui, &symbol.instructions[row_index], symbol, appearance); - } - }); + if let Some(symbol) = left_symbol { + asm_col_ui( + &mut row, + &symbol.instructions[row_index], + symbol, + appearance, + ins_view_state, + ); + } else { + empty_col_ui(&mut row); + } + if let Some(symbol) = right_symbol { + asm_col_ui( + &mut row, + &symbol.instructions[row_index], + symbol, + appearance, + ins_view_state, + ); + } else { + empty_col_ui(&mut row); + } }); }); Some(()) @@ -517,5 +669,6 @@ pub fn function_diff_ui(ui: &mut egui::Ui, state: &mut DiffViewState, appearance result.second_obj.as_ref(), selected_symbol, appearance, + &mut state.function_state, ); } diff --git a/src/views/symbol_diff.rs b/src/views/symbol_diff.rs index 35b74f1..30cc5c2 100644 --- a/src/views/symbol_diff.rs +++ b/src/views/symbol_diff.rs @@ -13,7 +13,7 @@ use crate::{ Job, JobQueue, JobResult, }, obj::{ObjInfo, ObjSection, ObjSectionKind, ObjSymbol, ObjSymbolFlags}, - views::{appearance::Appearance, write_text}, + views::{appearance::Appearance, function_diff::FunctionViewState, write_text}, }; pub struct SymbolReference { @@ -35,6 +35,7 @@ pub struct DiffViewState { pub build: Option>, pub current_view: View, pub symbol_state: SymbolViewState, + pub function_state: FunctionViewState, pub search: String, pub queue_build: bool, pub build_running: bool,