Compare commits

...

6 Commits

Author SHA1 Message Date
LagoLunatic 247377d153
Merge a51a5ac93f into abe68ef2f2 2024-12-03 00:45:01 -05:00
LagoLunatic a51a5ac93f Add hack to detect strings via the addi opcode 2024-12-03 00:43:20 -05:00
LagoLunatic 9b1205d9aa Revert ObjArch API changes, add fake target symbol hack
Because we no longer have access to the actual symbol name via sections, guess_data_type can no longer detect the String data type for pooled references.
2024-12-03 00:37:34 -05:00
LagoLunatic abe68ef2f2
objdiff-gui: Implement keyboard shortcuts (#139)
* Fix missing dependency feature for objdiff-gui

* Update .gitignore

* Add enter and back hotkeys

* Add scroll hotkeys

* Add hotkeys to select the next symbol above/below the current one in the listing

* Do not clear highlighted symbol when backing out of diff view

* Do not clear highlighted symbol when hovering mouse over an unpaired symbol

* Auto-scroll the keyboard-selected symbols into view if offscreen

* Fix some hotkeys stealing input from focused widgets

e.g. The symbol list was stealing the W/S key presses when typing into the symbol filter text edit.

If the user actually wants to use these shortcuts while a widget is focused, they can simply press the escape key to unfocus all widgets and then press the shortcut.

* Add Ctrl+F/S shortcuts for focusing the object and symbol filter text edits

* Add space as alternative to enter hotkey

This is for consistency with egui's builtint enter/space hotkey for interacting with the focused widget.

* Add hotkeys to change target and base functions

* Split function diff view: Enable PageUp/PageDown/Home/End for scrolling

* Add escape as an alternative to back hotkey

* Fix auto-scrolling to highlighted symbol only working for the left side

The flag is cleared after one scroll to avoid doing it continuously, but this breaks when we need to scroll to both the left and the right symbol at the same time. So now each side has its own flag to keep track of this state independently.

* Simplify clearing of the autoscroll flag, remove &mut State

* Found a better place to clear the autoscroll flag

DiffViewState::post_update is where the flag gets set, so clearing it right before that at the start of the function seems to make the most sense, instead of doing it in App::update.
2024-12-02 21:51:37 -07:00
LagoLunatic 304df96411
Display decoded rlwinm info to hover tooltip (#141)
* Fix missing dependency feature for objdiff-gui

* Update .gitignore

* Display decoded rlwinm info to hover tooltip

* Remove trailing newline when displaying decoded rlwinm info

* Change variable name

* Also update variable name in rlwinm.rs
2024-12-02 21:40:05 -07:00
LagoLunatic 507b988aa3 Clear pool relocs in volatile registers on function call
This fixes some false positives.
2024-12-02 18:05:32 -05:00
15 changed files with 339 additions and 95 deletions

View File

@ -113,7 +113,6 @@ impl ObjArch for ObjArchArm {
relocations: &[ObjReloc], relocations: &[ObjReloc],
line_info: &BTreeMap<u64, u32>, line_info: &BTreeMap<u64, u32>,
config: &DiffObjConfig, config: &DiffObjConfig,
_sections: &[ObjSection],
) -> Result<ProcessCodeResult> { ) -> Result<ProcessCodeResult> {
let start_addr = address as u32; let start_addr = address as u32;
let end_addr = start_addr + code.len() as u32; let end_addr = start_addr + code.len() as u32;

View File

@ -29,7 +29,6 @@ impl ObjArch for ObjArchArm64 {
relocations: &[ObjReloc], relocations: &[ObjReloc],
line_info: &BTreeMap<u64, u32>, line_info: &BTreeMap<u64, u32>,
config: &DiffObjConfig, config: &DiffObjConfig,
_sections: &[ObjSection],
) -> Result<ProcessCodeResult> { ) -> Result<ProcessCodeResult> {
let start_address = address; let start_address = address;
let end_address = address + code.len() as u64; let end_address = address + code.len() as u64;

View File

@ -87,7 +87,6 @@ impl ObjArch for ObjArchMips {
relocations: &[ObjReloc], relocations: &[ObjReloc],
line_info: &BTreeMap<u64, u32>, line_info: &BTreeMap<u64, u32>,
config: &DiffObjConfig, config: &DiffObjConfig,
_sections: &[ObjSection],
) -> Result<ProcessCodeResult> { ) -> Result<ProcessCodeResult> {
let _guard = RABBITIZER_MUTEX.lock().map_err(|e| anyhow!("Failed to lock mutex: {e}"))?; let _guard = RABBITIZER_MUTEX.lock().map_err(|e| anyhow!("Failed to lock mutex: {e}"))?;
configure_rabbitizer(match config.mips_abi { configure_rabbitizer(match config.mips_abi {

View File

@ -126,7 +126,6 @@ impl DataType {
} }
pub trait ObjArch: Send + Sync { pub trait ObjArch: Send + Sync {
#[expect(clippy::too_many_arguments)]
fn process_code( fn process_code(
&self, &self,
address: u64, address: u64,
@ -135,7 +134,6 @@ pub trait ObjArch: Send + Sync {
relocations: &[ObjReloc], relocations: &[ObjReloc],
line_info: &BTreeMap<u64, u32>, line_info: &BTreeMap<u64, u32>,
config: &DiffObjConfig, config: &DiffObjConfig,
sections: &[ObjSection],
) -> Result<ProcessCodeResult>; ) -> Result<ProcessCodeResult>;
fn implcit_addend( fn implcit_addend(

View File

@ -48,13 +48,12 @@ impl ObjArch for ObjArchPpc {
relocations: &[ObjReloc], relocations: &[ObjReloc],
line_info: &BTreeMap<u64, u32>, line_info: &BTreeMap<u64, u32>,
config: &DiffObjConfig, config: &DiffObjConfig,
sections: &[ObjSection],
) -> Result<ProcessCodeResult> { ) -> Result<ProcessCodeResult> {
let ins_count = code.len() / 4; let ins_count = code.len() / 4;
let mut ops = Vec::<u16>::with_capacity(ins_count); let mut ops = Vec::<u16>::with_capacity(ins_count);
let mut insts = Vec::<ObjIns>::with_capacity(ins_count); let mut insts = Vec::<ObjIns>::with_capacity(ins_count);
let fake_pool_reloc_for_addr = let fake_pool_reloc_for_addr =
generate_fake_pool_reloc_for_addr_mapping(address, code, relocations, sections); generate_fake_pool_reloc_for_addr_mapping(address, code, relocations);
for (cur_addr, mut ins) in InsIter::new(code, address as u32) { for (cur_addr, mut ins) in InsIter::new(code, address as u32) {
let reloc = relocations.iter().find(|r| (r.address as u32 & !3) == cur_addr); let reloc = relocations.iter().find(|r| (r.address as u32 & !3) == cur_addr);
if let Some(reloc) = reloc { if let Some(reloc) = reloc {
@ -205,7 +204,21 @@ impl ObjArch for ObjArchPpc {
return Some(DataType::String); return Some(DataType::String);
} }
guess_data_type_from_load_store_inst_op(Opcode::from(instruction.op as u8)) let op = Opcode::from(instruction.op as u8);
if let Some(ty) = guess_data_type_from_load_store_inst_op(op) {
Some(ty)
} else if op == Opcode::Addi {
// Assume that any addi instruction that references a local symbol is loading a string.
// This hack is not ideal and results in tons of false positives where it will show
// garbage strings (e.g. misinterpreting arrays, float literals, etc).
// But there isn't much other choice as not all strings are in the @stringBase pool.
// And even those that are would be missed by the target.name.starts_with("@stringBase")
// hack above for fake pooled relocations, as they have an empty string placeholder for
// the target symbol name.
Some(DataType::String)
} else {
None
}
} }
fn display_data_type(&self, ty: DataType, bytes: &[u8]) -> Option<String> { fn display_data_type(&self, ty: DataType, bytes: &[u8]) -> Option<String> {
@ -423,7 +436,7 @@ fn get_offset_and_addr_gpr_for_possible_pool_reference(
} }
} else { } else {
// If it's not a load/store instruction, there's two more possibilities we need to handle. // If it's not a load/store instruction, there's two more possibilities we need to handle.
// 1. It could be a reference to @stringBase. // 1. It could be loading a pointer to a string.
// 2. It could be moving the relocation address plus an offset into a different register to // 2. It could be moving the relocation address plus an offset into a different register to
// load from later. // load from later.
// If either of these match, we also want to return the destination register that the // If either of these match, we also want to return the destination register that the
@ -453,26 +466,38 @@ fn get_offset_and_addr_gpr_for_possible_pool_reference(
// there isn't really a relocation here, as copying the pool relocation's type wouldn't make sense. // there isn't really a relocation here, as copying the pool relocation's type wouldn't make sense.
// Also, if this instruction is accessing the middle of a symbol instead of the start, we add an // Also, if this instruction is accessing the middle of a symbol instead of the start, we add an
// addend to indicate that. // addend to indicate that.
fn make_fake_pool_reloc( fn make_fake_pool_reloc(offset: i16, cur_addr: u32, pool_reloc: &ObjReloc) -> Option<ObjReloc> {
offset: i16,
cur_addr: u32,
pool_reloc: &ObjReloc,
sections: &[ObjSection],
) -> Option<ObjReloc> {
let offset_from_pool = pool_reloc.addend + offset as i64; let offset_from_pool = pool_reloc.addend + offset as i64;
let target_address = pool_reloc.target.address.checked_add_signed(offset_from_pool)?; let target_address = pool_reloc.target.address.checked_add_signed(offset_from_pool)?;
let orig_section_index = pool_reloc.target.orig_section_index?; let orig_section_index = pool_reloc.target.orig_section_index?;
let section = sections.iter().find(|s| s.orig_index == orig_section_index)?; // We also need to create a fake target symbol to go inside our fake relocation.
let target_symbol = section // This is because we don't have access to list of all symbols in this section, so we can't find
.symbols // the real symbol yet. Instead we make a placeholder that has the correct `orig_section_index`
.iter() // and `address` fields, and then later on when this information is displayed to the user, we
.find(|s| s.size > 0 && (s.address..s.address + s.size).contains(&target_address))?; // can find the real symbol by searching through the object's section's symbols for one that
let addend = (target_address - target_symbol.address) as i64; // contains this address.
let fake_target_symbol = ObjSymbol {
name: "".to_string(),
demangled_name: None,
address: target_address,
section_address: 0,
size: 0,
size_known: false,
kind: Default::default(),
flags: Default::default(),
orig_section_index: Some(orig_section_index),
virtual_address: None,
original_index: None,
bytes: vec![],
};
// The addend is also fake because we don't know yet if the `target_address` here is the exact
// start of the symbol or if it's in the middle of it.
let fake_addend = 0;
Some(ObjReloc { Some(ObjReloc {
flags: RelocationFlags::Elf { r_type: elf::R_PPC_NONE }, flags: RelocationFlags::Elf { r_type: elf::R_PPC_NONE },
address: cur_addr as u64, address: cur_addr as u64,
target: target_symbol.clone(), target: fake_target_symbol,
addend, addend: fake_addend,
}) })
} }
@ -491,7 +516,6 @@ fn generate_fake_pool_reloc_for_addr_mapping(
address: u64, address: u64,
code: &[u8], code: &[u8],
relocations: &[ObjReloc], relocations: &[ObjReloc],
sections: &[ObjSection],
) -> HashMap<u32, ObjReloc> { ) -> HashMap<u32, ObjReloc> {
let mut active_pool_relocs = HashMap::new(); let mut active_pool_relocs = HashMap::new();
let mut pool_reloc_for_addr = HashMap::new(); let mut pool_reloc_for_addr = HashMap::new();
@ -520,6 +544,16 @@ fn generate_fake_pool_reloc_for_addr_mapping(
) => { ) => {
active_pool_relocs.insert(addr_dst_gpr.0, reloc.clone()); // `lis` + `ori` active_pool_relocs.insert(addr_dst_gpr.0, reloc.clone()); // `lis` + `ori`
} }
(Opcode::B, _, _, _) => {
if simplified.mnemonic == "bl" {
// When encountering a function call, clear any active pool relocations from
// the volatile registers (r0, r3-r12), but not the nonvolatile registers.
active_pool_relocs.remove(&0);
for gpr in 3..12 {
active_pool_relocs.remove(&gpr);
}
}
}
_ => {} _ => {}
} }
} else if let Some((offset, addr_src_gpr, addr_dst_gpr)) = } else if let Some((offset, addr_src_gpr, addr_dst_gpr)) =
@ -528,9 +562,7 @@ fn generate_fake_pool_reloc_for_addr_mapping(
// This instruction doesn't have a real relocation, so it may be a reference to one of // This instruction doesn't have a real relocation, so it may be a reference to one of
// the already-loaded pools. // the already-loaded pools.
if let Some(pool_reloc) = active_pool_relocs.get(&addr_src_gpr.0) { if let Some(pool_reloc) = active_pool_relocs.get(&addr_src_gpr.0) {
if let Some(fake_pool_reloc) = if let Some(fake_pool_reloc) = make_fake_pool_reloc(offset, cur_addr, pool_reloc) {
make_fake_pool_reloc(offset, cur_addr, pool_reloc, sections)
{
pool_reloc_for_addr.insert(cur_addr, fake_pool_reloc); pool_reloc_for_addr.insert(cur_addr, fake_pool_reloc);
} }
if let Some(addr_dst_gpr) = addr_dst_gpr { if let Some(addr_dst_gpr) = addr_dst_gpr {

View File

@ -34,7 +34,6 @@ impl ObjArch for ObjArchX86 {
relocations: &[ObjReloc], relocations: &[ObjReloc],
line_info: &BTreeMap<u64, u32>, line_info: &BTreeMap<u64, u32>,
config: &DiffObjConfig, config: &DiffObjConfig,
_sections: &[ObjSection],
) -> Result<ProcessCodeResult> { ) -> Result<ProcessCodeResult> {
let mut result = ProcessCodeResult { ops: Vec::new(), insts: Vec::new() }; let mut result = ProcessCodeResult { ops: Vec::new(), insts: Vec::new() };
let mut decoder = Decoder::with_ip(self.bits, code, address, DecoderOptions::NONE); let mut decoder = Decoder::with_ip(self.bits, code, address, DecoderOptions::NONE);

View File

@ -28,7 +28,6 @@ pub fn process_code_symbol(
&section.relocations, &section.relocations,
&section.line_info, &section.line_info,
config, config,
&obj.sections,
) )
} }

108
objdiff-gui/src/hotkeys.rs Normal file
View File

@ -0,0 +1,108 @@
use egui::{
style::ScrollAnimation, vec2, Context, Key, KeyboardShortcut, Modifiers, PointerButton,
};
fn any_widget_focused(ctx: &Context) -> bool { ctx.memory(|mem| mem.focused().is_some()) }
pub fn enter_pressed(ctx: &Context) -> bool {
if any_widget_focused(ctx) {
return false;
}
ctx.input_mut(|i| {
i.key_pressed(Key::Enter)
|| i.key_pressed(Key::Space)
|| i.pointer.button_pressed(PointerButton::Extra2)
})
}
pub fn back_pressed(ctx: &Context) -> bool {
if any_widget_focused(ctx) {
return false;
}
ctx.input_mut(|i| {
i.key_pressed(Key::Backspace)
|| i.key_pressed(Key::Escape)
|| i.pointer.button_pressed(PointerButton::Extra1)
})
}
pub fn up_pressed(ctx: &Context) -> bool {
if any_widget_focused(ctx) {
return false;
}
ctx.input_mut(|i| i.key_pressed(Key::ArrowUp) || i.key_pressed(Key::W))
}
pub fn down_pressed(ctx: &Context) -> bool {
if any_widget_focused(ctx) {
return false;
}
ctx.input_mut(|i| i.key_pressed(Key::ArrowDown) || i.key_pressed(Key::S))
}
pub fn page_up_pressed(ctx: &Context) -> bool { ctx.input_mut(|i| i.key_pressed(Key::PageUp)) }
pub fn page_down_pressed(ctx: &Context) -> bool { ctx.input_mut(|i| i.key_pressed(Key::PageDown)) }
pub fn home_pressed(ctx: &Context) -> bool { ctx.input_mut(|i| i.key_pressed(Key::Home)) }
pub fn end_pressed(ctx: &Context) -> bool { ctx.input_mut(|i| i.key_pressed(Key::End)) }
pub fn check_scroll_hotkeys(ui: &mut egui::Ui, include_small_increments: bool) {
let ui_height = ui.available_rect_before_wrap().height();
if up_pressed(ui.ctx()) && include_small_increments {
ui.scroll_with_delta_animation(vec2(0.0, ui_height / 10.0), ScrollAnimation::none());
} else if down_pressed(ui.ctx()) && include_small_increments {
ui.scroll_with_delta_animation(vec2(0.0, -ui_height / 10.0), ScrollAnimation::none());
} else if page_up_pressed(ui.ctx()) {
ui.scroll_with_delta_animation(vec2(0.0, ui_height), ScrollAnimation::none());
} else if page_down_pressed(ui.ctx()) {
ui.scroll_with_delta_animation(vec2(0.0, -ui_height), ScrollAnimation::none());
} else if home_pressed(ui.ctx()) {
ui.scroll_with_delta_animation(vec2(0.0, f32::INFINITY), ScrollAnimation::none());
} else if end_pressed(ui.ctx()) {
ui.scroll_with_delta_animation(vec2(0.0, -f32::INFINITY), ScrollAnimation::none());
}
}
pub fn consume_up_key(ctx: &Context) -> bool {
if any_widget_focused(ctx) {
return false;
}
ctx.input_mut(|i| {
i.consume_key(Modifiers::NONE, Key::ArrowUp) || i.consume_key(Modifiers::NONE, Key::W)
})
}
pub fn consume_down_key(ctx: &Context) -> bool {
if any_widget_focused(ctx) {
return false;
}
ctx.input_mut(|i| {
i.consume_key(Modifiers::NONE, Key::ArrowDown) || i.consume_key(Modifiers::NONE, Key::S)
})
}
const OBJECT_FILTER_SHORTCUT: KeyboardShortcut = KeyboardShortcut::new(Modifiers::CTRL, Key::F);
pub fn consume_object_filter_shortcut(ctx: &Context) -> bool {
ctx.input_mut(|i| i.consume_shortcut(&OBJECT_FILTER_SHORTCUT))
}
const SYMBOL_FILTER_SHORTCUT: KeyboardShortcut = KeyboardShortcut::new(Modifiers::CTRL, Key::S);
pub fn consume_symbol_filter_shortcut(ctx: &Context) -> bool {
ctx.input_mut(|i| i.consume_shortcut(&SYMBOL_FILTER_SHORTCUT))
}
const CHANGE_TARGET_SHORTCUT: KeyboardShortcut = KeyboardShortcut::new(Modifiers::CTRL, Key::T);
pub fn consume_change_target_shortcut(ctx: &Context) -> bool {
ctx.input_mut(|i| i.consume_shortcut(&CHANGE_TARGET_SHORTCUT))
}
const CHANGE_BASE_SHORTCUT: KeyboardShortcut = KeyboardShortcut::new(Modifiers::CTRL, Key::B);
pub fn consume_change_base_shortcut(ctx: &Context) -> bool {
ctx.input_mut(|i| i.consume_shortcut(&CHANGE_BASE_SHORTCUT))
}

View File

@ -4,6 +4,7 @@ mod app;
mod app_config; mod app_config;
mod config; mod config;
mod fonts; mod fonts;
mod hotkeys;
mod jobs; mod jobs;
mod update; mod update;
mod views; mod views;

View File

@ -21,6 +21,7 @@ use strum::{EnumMessage, VariantArray};
use crate::{ use crate::{
app::{AppConfig, AppState, AppStateRef, ObjectConfig}, app::{AppConfig, AppState, AppStateRef, ObjectConfig},
config::ProjectObjectNode, config::ProjectObjectNode,
hotkeys,
jobs::{ jobs::{
check_update::{start_check_update, CheckUpdateResult}, check_update::{start_check_update, CheckUpdateResult},
update::start_update, update::start_update,
@ -254,7 +255,11 @@ pub fn config_ui(
} }
} else { } else {
let had_search = !config_state.object_search.is_empty(); let had_search = !config_state.object_search.is_empty();
egui::TextEdit::singleline(&mut config_state.object_search).hint_text("Filter").ui(ui); let response =
egui::TextEdit::singleline(&mut config_state.object_search).hint_text("Filter").ui(ui);
if hotkeys::consume_object_filter_shortcut(ui.ctx()) {
response.request_focus();
}
let mut root_open = None; let mut root_open = None;
let mut node_open = NodeOpen::Default; let mut node_open = NodeOpen::Default;

View File

@ -7,11 +7,14 @@ use objdiff_core::{
}; };
use time::format_description; use time::format_description;
use crate::views::{ use crate::{
appearance::Appearance, hotkeys,
column_layout::{render_header, render_table}, views::{
symbol_diff::{DiffViewAction, DiffViewNavigation, DiffViewState}, appearance::Appearance,
write_text, column_layout::{render_header, render_table},
symbol_diff::{DiffViewAction, DiffViewNavigation, DiffViewState},
write_text,
},
}; };
const BYTES_PER_ROW: usize = 16; const BYTES_PER_ROW: usize = 16;
@ -176,6 +179,8 @@ fn data_table_ui(
let left_diffs = left_section.map(|(_, section)| split_diffs(&section.data_diff)); let left_diffs = left_section.map(|(_, section)| split_diffs(&section.data_diff));
let right_diffs = right_section.map(|(_, section)| split_diffs(&section.data_diff)); let right_diffs = right_section.map(|(_, section)| split_diffs(&section.data_diff));
hotkeys::check_scroll_hotkeys(ui, true);
render_table(ui, available_width, 2, config.code_font.size, total_rows, |row, column| { render_table(ui, available_width, 2, config.code_font.size, total_rows, |row, column| {
let i = row.index(); let i = row.index();
let address = i * BYTES_PER_ROW; let address = i * BYTES_PER_ROW;
@ -224,7 +229,7 @@ pub fn data_diff_ui(
render_header(ui, available_width, 2, |ui, column| { render_header(ui, available_width, 2, |ui, column| {
if column == 0 { if column == 0 {
// Left column // Left column
if ui.button("⏴ Back").clicked() { if ui.button("⏴ Back").clicked() || hotkeys::back_pressed(ui.ctx()) {
ret = Some(DiffViewAction::Navigate(DiffViewNavigation::symbol_diff())); ret = Some(DiffViewAction::Navigate(DiffViewNavigation::symbol_diff()));
} }

View File

@ -5,13 +5,16 @@ use objdiff_core::{
}; };
use time::format_description; use time::format_description;
use crate::views::{ use crate::{
appearance::Appearance, hotkeys,
column_layout::{render_header, render_strips}, views::{
function_diff::FunctionDiffContext, appearance::Appearance,
symbol_diff::{ column_layout::{render_header, render_strips},
match_color_for_symbol, DiffViewAction, DiffViewNavigation, DiffViewState, SymbolRefByName, function_diff::FunctionDiffContext,
View, symbol_diff::{
match_color_for_symbol, DiffViewAction, DiffViewNavigation, DiffViewState,
SymbolRefByName, View,
},
}, },
}; };
@ -136,7 +139,7 @@ pub fn extab_diff_ui(
if column == 0 { if column == 0 {
// Left column // Left column
ui.horizontal(|ui| { ui.horizontal(|ui| {
if ui.button("⏴ Back").clicked() { if ui.button("⏴ Back").clicked() || hotkeys::back_pressed(ui.ctx()) {
ret = Some(DiffViewAction::Navigate(DiffViewNavigation::symbol_diff())); ret = Some(DiffViewAction::Navigate(DiffViewNavigation::symbol_diff()));
} }
ui.separator(); ui.separator();
@ -232,6 +235,8 @@ pub fn extab_diff_ui(
} }
}); });
hotkeys::check_scroll_hotkeys(ui, true);
// Table // Table
render_strips(ui, available_width, 2, |ui, column| { render_strips(ui, available_width, 2, |ui, column| {
if column == 0 { if column == 0 {

View File

@ -14,12 +14,15 @@ use objdiff_core::{
}; };
use time::format_description; use time::format_description;
use crate::views::{ use crate::{
appearance::Appearance, hotkeys,
column_layout::{render_header, render_strips, render_table}, views::{
symbol_diff::{ appearance::Appearance,
match_color_for_symbol, symbol_list_ui, DiffViewAction, DiffViewNavigation, DiffViewState, column_layout::{render_header, render_strips, render_table},
SymbolDiffContext, SymbolFilter, SymbolRefByName, SymbolViewState, View, symbol_diff::{
match_color_for_symbol, symbol_list_ui, DiffViewAction, DiffViewNavigation,
DiffViewState, SymbolDiffContext, SymbolFilter, SymbolRefByName, SymbolViewState, View,
},
}, },
}; };
@ -74,6 +77,19 @@ impl FunctionViewState {
} }
} }
fn find_symbol_matching_fake_symbol_in_sections(
fake_symbol: &ObjSymbol,
sections: &[ObjSection],
) -> Option<ObjSymbol> {
let orig_section_index = fake_symbol.orig_section_index?;
let section = sections.iter().find(|s| s.orig_index == orig_section_index)?;
let real_symbol = section
.symbols
.iter()
.find(|s| s.size > 0 && (s.address..s.address + s.size).contains(&fake_symbol.address))?;
Some(real_symbol.clone())
}
fn ins_hover_ui( fn ins_hover_ui(
ui: &mut egui::Ui, ui: &mut egui::Ui,
obj: &ObjInfo, obj: &ObjInfo,
@ -119,17 +135,29 @@ fn ins_hover_ui(
} }
if let Some(reloc) = ins.reloc.as_ref().or(ins.fake_pool_reloc.as_ref()) { if let Some(reloc) = ins.reloc.as_ref().or(ins.fake_pool_reloc.as_ref()) {
let mut target = reloc.target.clone();
let mut addend = reloc.addend;
if target.size == 0 && target.name.is_empty() {
// Fake target symbol we added as a placeholder. We need to find the real one.
if let Some(real_target) =
find_symbol_matching_fake_symbol_in_sections(&target, &obj.sections)
{
target = real_target;
addend = (reloc.target.address - target.address) as i64;
}
}
ui.label(format!("Relocation type: {}", obj.arch.display_reloc(reloc.flags))); ui.label(format!("Relocation type: {}", obj.arch.display_reloc(reloc.flags)));
let addend_str = match reloc.addend.cmp(&0i64) { let addend_str = match addend.cmp(&0i64) {
Ordering::Greater => format!("+{:x}", reloc.addend), Ordering::Greater => format!("+{:x}", addend),
Ordering::Less => format!("-{:x}", -reloc.addend), Ordering::Less => format!("-{:x}", -addend),
_ => "".to_string(), _ => "".to_string(),
}; };
ui.colored_label( ui.colored_label(
appearance.highlight_color, appearance.highlight_color,
format!("Name: {}{}", reloc.target.name, addend_str), format!("Name: {}{}", target.name, addend_str),
); );
if let Some(orig_section_index) = reloc.target.orig_section_index { if let Some(orig_section_index) = target.orig_section_index {
if let Some(section) = if let Some(section) =
obj.sections.iter().find(|s| s.orig_index == orig_section_index) obj.sections.iter().find(|s| s.orig_index == orig_section_index)
{ {
@ -140,15 +168,12 @@ fn ins_hover_ui(
} }
ui.colored_label( ui.colored_label(
appearance.highlight_color, appearance.highlight_color,
format!("Address: {:x}{}", reloc.target.address, addend_str), format!("Address: {:x}{}", target.address, addend_str),
); );
ui.colored_label( ui.colored_label(appearance.highlight_color, format!("Size: {:x}", target.size));
appearance.highlight_color, if addend >= 0 && target.bytes.len() > addend as usize {
format!("Size: {:x}", reloc.target.size),
);
if reloc.addend >= 0 && reloc.target.bytes.len() > reloc.addend as usize {
if let Some(s) = obj.arch.guess_data_type(ins).and_then(|ty| { if let Some(s) = obj.arch.guess_data_type(ins).and_then(|ty| {
obj.arch.display_data_type(ty, &reloc.target.bytes[reloc.addend as usize..]) obj.arch.display_data_type(ty, &target.bytes[addend as usize..])
}) { }) {
ui.colored_label(appearance.highlight_color, s); ui.colored_label(appearance.highlight_color, s);
} }
@ -157,6 +182,10 @@ fn ins_hover_ui(
ui.colored_label(appearance.highlight_color, "Extern".to_string()); ui.colored_label(appearance.highlight_color, "Extern".to_string());
} }
} }
if let Some(decoded) = rlwinmdec::decode(&ins.formatted) {
ui.colored_label(appearance.highlight_color, decoded.trim());
}
}); });
} }
@ -440,6 +469,7 @@ fn asm_table_ui(
}; };
if left_len.is_some() && right_len.is_some() { if left_len.is_some() && right_len.is_some() {
// Joint view // Joint view
hotkeys::check_scroll_hotkeys(ui, true);
render_table( render_table(
ui, ui,
available_width, available_width,
@ -475,6 +505,7 @@ fn asm_table_ui(
if column == 0 { if column == 0 {
if let Some(ctx) = left_ctx { if let Some(ctx) = left_ctx {
if ctx.has_symbol() { if ctx.has_symbol() {
hotkeys::check_scroll_hotkeys(ui, false);
render_table( render_table(
ui, ui,
available_width / 2.0, available_width / 2.0,
@ -520,9 +551,6 @@ fn asm_table_ui(
SymbolRefByName::new(right_symbol, right_section), SymbolRefByName::new(right_symbol, right_section),
)); ));
} }
DiffViewAction::SetSymbolHighlight(_, _) => {
// Ignore
}
_ => { _ => {
ret = Some(action); ret = Some(action);
} }
@ -535,6 +563,7 @@ fn asm_table_ui(
} else if column == 1 { } else if column == 1 {
if let Some(ctx) = right_ctx { if let Some(ctx) = right_ctx {
if ctx.has_symbol() { if ctx.has_symbol() {
hotkeys::check_scroll_hotkeys(ui, false);
render_table( render_table(
ui, ui,
available_width / 2.0, available_width / 2.0,
@ -580,9 +609,6 @@ fn asm_table_ui(
right_symbol_ref, right_symbol_ref,
)); ));
} }
DiffViewAction::SetSymbolHighlight(_, _) => {
// Ignore
}
_ => { _ => {
ret = Some(action); ret = Some(action);
} }
@ -683,7 +709,7 @@ pub fn function_diff_ui(
if column == 0 { if column == 0 {
// Left column // Left column
ui.horizontal(|ui| { ui.horizontal(|ui| {
if ui.button("⏴ Back").clicked() { if ui.button("⏴ Back").clicked() || hotkeys::back_pressed(ui.ctx()) {
ret = Some(DiffViewAction::Navigate(DiffViewNavigation::symbol_diff())); ret = Some(DiffViewAction::Navigate(DiffViewNavigation::symbol_diff()));
} }
ui.separator(); ui.separator();
@ -716,10 +742,11 @@ pub fn function_diff_ui(
.color(appearance.highlight_color), .color(appearance.highlight_color),
); );
if right_ctx.is_some_and(|m| m.has_symbol()) if right_ctx.is_some_and(|m| m.has_symbol())
&& ui && (ui
.button("Change target") .button("Change target")
.on_hover_text_at_pointer("Choose a different symbol to use as the target") .on_hover_text_at_pointer("Choose a different symbol to use as the target")
.clicked() .clicked()
|| hotkeys::consume_change_target_shortcut(ui.ctx()))
{ {
if let Some(symbol_ref) = state.symbol_state.right_symbol.as_ref() { if let Some(symbol_ref) = state.symbol_state.right_symbol.as_ref() {
ret = Some(DiffViewAction::SelectingLeft(symbol_ref.clone())); ret = Some(DiffViewAction::SelectingLeft(symbol_ref.clone()));
@ -793,6 +820,7 @@ pub fn function_diff_ui(
"Choose a different symbol to use as the base", "Choose a different symbol to use as the base",
) )
.clicked() .clicked()
|| hotkeys::consume_change_base_shortcut(ui.ctx())
{ {
if let Some(symbol_ref) = state.symbol_state.left_symbol.as_ref() { if let Some(symbol_ref) = state.symbol_state.left_symbol.as_ref() {
ret = Some(DiffViewAction::SelectingRight(symbol_ref.clone())); ret = Some(DiffViewAction::SelectingRight(symbol_ref.clone()));

View File

@ -16,13 +16,13 @@ pub fn rlwinm_decode_window(
egui::Window::new("Rlwinm Decoder").open(show).show(ctx, |ui| { egui::Window::new("Rlwinm Decoder").open(show).show(ctx, |ui| {
ui.text_edit_singleline(&mut state.text); ui.text_edit_singleline(&mut state.text);
ui.add_space(10.0); ui.add_space(10.0);
if let Some(demangled) = rlwinmdec::decode(&state.text) { if let Some(decoded) = rlwinmdec::decode(&state.text) {
ui.scope(|ui| { ui.scope(|ui| {
ui.style_mut().override_text_style = Some(TextStyle::Monospace); ui.style_mut().override_text_style = Some(TextStyle::Monospace);
ui.colored_label(appearance.replace_color, &demangled); ui.colored_label(appearance.replace_color, decoded.trim());
}); });
if ui.button("Copy").clicked() { if ui.button("Copy").clicked() {
ui.output_mut(|output| output.copied_text = demangled); ui.output_mut(|output| output.copied_text = decoded);
} }
} else { } else {
ui.scope(|ui| { ui.scope(|ui| {

View File

@ -1,8 +1,8 @@
use std::{collections::BTreeMap, mem::take}; use std::{collections::BTreeMap, mem::take, ops::Bound};
use egui::{ use egui::{
text::LayoutJob, CollapsingHeader, Color32, Id, OpenUrl, ScrollArea, SelectableLabel, TextEdit, style::ScrollAnimation, text::LayoutJob, CollapsingHeader, Color32, Id, OpenUrl, ScrollArea,
Ui, Widget, SelectableLabel, TextEdit, Ui, Widget,
}; };
use objdiff_core::{ use objdiff_core::{
arch::ObjArch, arch::ObjArch,
@ -15,6 +15,7 @@ use regex::{Regex, RegexBuilder};
use crate::{ use crate::{
app::AppStateRef, app::AppStateRef,
hotkeys,
jobs::{ jobs::{
create_scratch::{start_create_scratch, CreateScratchConfig, CreateScratchResult}, create_scratch::{start_create_scratch, CreateScratchConfig, CreateScratchResult},
objdiff::{BuildStatus, ObjDiffResult}, objdiff::{BuildStatus, ObjDiffResult},
@ -56,8 +57,8 @@ pub enum DiffViewAction {
Build, Build,
/// Navigate to a new diff view /// Navigate to a new diff view
Navigate(DiffViewNavigation), Navigate(DiffViewNavigation),
/// Set the highlighted symbols in the symbols view /// Set the highlighted symbols in the symbols view, optionally scrolling them into view.
SetSymbolHighlight(Option<SymbolRef>, Option<SymbolRef>), SetSymbolHighlight(Option<SymbolRef>, Option<SymbolRef>, bool),
/// Set the symbols view search filter /// Set the symbols view search filter
SetSearch(String), SetSearch(String),
/// Submit the current function to decomp.me /// Submit the current function to decomp.me
@ -135,6 +136,7 @@ pub struct DiffViewState {
#[derive(Default)] #[derive(Default)]
pub struct SymbolViewState { pub struct SymbolViewState {
pub highlighted_symbol: (Option<SymbolRef>, Option<SymbolRef>), pub highlighted_symbol: (Option<SymbolRef>, Option<SymbolRef>),
pub autoscroll_to_highlighted_symbols: bool,
pub left_symbol: Option<SymbolRefByName>, pub left_symbol: Option<SymbolRefByName>,
pub right_symbol: Option<SymbolRefByName>, pub right_symbol: Option<SymbolRefByName>,
pub reverse_fn_order: bool, pub reverse_fn_order: bool,
@ -197,6 +199,9 @@ impl DiffViewState {
ctx.output_mut(|o| o.open_url = Some(OpenUrl::new_tab(result.scratch_url))); ctx.output_mut(|o| o.open_url = Some(OpenUrl::new_tab(result.scratch_url)));
} }
// Clear the autoscroll flag so that it doesn't scroll continuously.
self.symbol_state.autoscroll_to_highlighted_symbols = false;
let Some(action) = action else { let Some(action) = action else {
return; return;
}; };
@ -211,7 +216,6 @@ impl DiffViewState {
// Ignore action if we're already navigating // Ignore action if we're already navigating
return; return;
} }
self.symbol_state.highlighted_symbol = (None, None);
let Ok(mut state) = state.write() else { let Ok(mut state) = state.write() else {
return; return;
}; };
@ -247,8 +251,9 @@ impl DiffViewState {
self.post_build_nav = Some(nav); self.post_build_nav = Some(nav);
} }
} }
DiffViewAction::SetSymbolHighlight(left, right) => { DiffViewAction::SetSymbolHighlight(left, right, autoscroll) => {
self.symbol_state.highlighted_symbol = (left, right); self.symbol_state.highlighted_symbol = (left, right);
self.symbol_state.autoscroll_to_highlighted_symbols = autoscroll;
} }
DiffViewAction::SetSearch(search) => { DiffViewAction::SetSearch(search) => {
self.search_regex = if search.is_empty() { self.search_regex = if search.is_empty() {
@ -534,7 +539,15 @@ fn symbol_ui(
ret = Some(DiffViewAction::Navigate(result)); ret = Some(DiffViewAction::Navigate(result));
} }
}); });
if response.clicked() { if selected && state.autoscroll_to_highlighted_symbols {
// Automatically scroll the view to encompass the selected symbol in case the user selected
// an offscreen symbol by using a keyboard shortcut.
ui.scroll_to_rect_animation(response.rect, None, ScrollAnimation::none());
// This autoscroll state flag will be reset in DiffViewState::post_update at the end of
// every frame so that we don't continuously scroll the view back when the user is trying to
// manually scroll away.
}
if response.clicked() || (selected && hotkeys::enter_pressed(ui.ctx())) {
if let Some(section) = section { if let Some(section) = section {
match section.kind { match section.kind {
ObjSectionKind::Code => { ObjSectionKind::Code => {
@ -561,20 +574,18 @@ fn symbol_ui(
} }
} }
} else if response.hovered() { } else if response.hovered() {
ret = Some(if let Some(target_symbol) = symbol_diff.target_symbol { ret = Some(if column == 0 {
if column == 0 { DiffViewAction::SetSymbolHighlight(
DiffViewAction::SetSymbolHighlight( Some(symbol_diff.symbol_ref),
Some(symbol_diff.symbol_ref), symbol_diff.target_symbol,
Some(target_symbol), false,
) )
} else {
DiffViewAction::SetSymbolHighlight(
Some(target_symbol),
Some(symbol_diff.symbol_ref),
)
}
} else { } else {
DiffViewAction::SetSymbolHighlight(None, None) DiffViewAction::SetSymbolHighlight(
symbol_diff.target_symbol,
Some(symbol_diff.symbol_ref),
false,
)
}); });
} }
ret ret
@ -648,6 +659,58 @@ pub fn symbol_list_ui(
} }
} }
hotkeys::check_scroll_hotkeys(ui, false);
let mut new_key_value_to_highlight = None;
if let Some(sym_ref) =
if column == 0 { state.highlighted_symbol.0 } else { state.highlighted_symbol.1 }
{
let up = if hotkeys::consume_up_key(ui.ctx()) {
Some(true)
} else if hotkeys::consume_down_key(ui.ctx()) {
Some(false)
} else {
None
};
if let Some(mut up) = up {
if state.reverse_fn_order {
up = !up;
}
new_key_value_to_highlight = if up {
mapping.range(..sym_ref).next_back()
} else {
mapping.range((Bound::Excluded(sym_ref), Bound::Unbounded)).next()
};
};
} else {
// No symbol is highlighted in this column. Select the topmost symbol instead.
// Note that we intentionally do not consume the up/down key presses in this case, but
// we do when a symbol is highlighted. This is so that if only one column has a symbol
// highlighted, that one takes precedence over the one with nothing highlighted.
if hotkeys::up_pressed(ui.ctx()) || hotkeys::down_pressed(ui.ctx()) {
new_key_value_to_highlight = if state.reverse_fn_order {
mapping.last_key_value()
} else {
mapping.first_key_value()
};
}
}
if let Some((new_sym_ref, new_symbol_diff)) = new_key_value_to_highlight {
ret = Some(if column == 0 {
DiffViewAction::SetSymbolHighlight(
Some(*new_sym_ref),
new_symbol_diff.target_symbol,
true,
)
} else {
DiffViewAction::SetSymbolHighlight(
new_symbol_diff.target_symbol,
Some(*new_sym_ref),
true,
)
});
}
ui.scope(|ui| { ui.scope(|ui| {
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace); ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend); ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
@ -838,7 +901,11 @@ pub fn symbol_diff_ui(
}); });
let mut search = state.search.clone(); let mut search = state.search.clone();
if TextEdit::singleline(&mut search).hint_text("Filter symbols").ui(ui).changed() { let response = TextEdit::singleline(&mut search).hint_text("Filter symbols").ui(ui);
if hotkeys::consume_symbol_filter_shortcut(ui.ctx()) {
response.request_focus();
}
if response.changed() {
ret = Some(DiffViewAction::SetSearch(search)); ret = Some(DiffViewAction::SetSearch(search));
} }
} else if column == 1 { } else if column == 1 {