Compare commits

..

12 Commits

Author SHA1 Message Date
OndrikB a06382c27e Disambiguate dummy symbols (#107)
* Disambiguate dummy symbols

* Small formatting improvement

* Put HashMap logic into symbol creation
2024-09-27 00:33:36 -06:00
Luke Street e013638c5a clippy fixes 2024-09-27 00:30:30 -06:00
Luke Street 70ab82f1f7 gui: Highlight registers in columns separately
This matches the behavior of decomp.me and the
CLI.

Resolves #71
2024-09-27 00:27:36 -06:00
Luke Street c5896689cf Use ppc750cl Opcode::from 2024-09-27 00:12:21 -06:00
Luke Street 67719dd93e report: Exclude "hidden" functions
Fixes #111
2024-09-27 00:12:21 -06:00
Luke Street 258e141017 Upgrade all dependencies 2024-09-27 00:12:16 -06:00
Luke Street dbdda55065 Add Report::split
A hack for supporting games that build
all versions at once.
2024-09-26 23:47:03 -06:00
Steven Casper a43320af1f PPC: Guess reloc data type based on the instruction. (#108)
* Guess reloc data type based on the instruction.

Adds an entry to the reloc tooltip to show the inferred data type
and value.

* Fix clippy warning

* Match on Opcode rather than mnemonic string
2024-09-25 23:45:37 -06:00
Amber Brault 35bbd40f5d Actually update extab stuff (#110)
* Update cwextab

* Update

* Update ppc.rs

* Make fmt shut up
2024-09-24 09:16:14 -06:00
Amber Brault c1cb4b0b19 Update cwextab (#109) 2024-09-23 21:24:33 -06:00
Luke Street 2379853faa Remove unused imports 2024-09-10 23:29:22 -06:00
Luke Street 5e1aff180f Remove vergen / GIT_COMMIT_SHA handling 2024-09-10 23:22:40 -06:00
19 changed files with 833 additions and 700 deletions
Generated
+464 -629
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -13,7 +13,7 @@ strip = "debuginfo"
codegen-units = 1
[workspace.package]
version = "2.0.0"
version = "2.1.0"
authors = ["Luke Street <luke@street.dev>"]
edition = "2021"
license = "MIT OR Apache-2.0"
-1
View File
@@ -11,7 +11,6 @@ description = """
A local diffing tool for decompilation projects.
"""
publish = false
build = "build.rs"
[dependencies]
anyhow = "1.0"
-9
View File
@@ -1,9 +0,0 @@
fn main() {
let output = std::process::Command::new("git")
.args(["rev-parse", "HEAD"])
.output()
.expect("Failed to execute git");
let rev = String::from_utf8(output.stdout).expect("Failed to parse git output");
println!("cargo:rustc-env=GIT_COMMIT_SHA={rev}");
println!("cargo:rustc-rerun-if-changed=.git/HEAD");
}
+1 -2
View File
@@ -31,10 +31,9 @@ where T: FromArgs
Ok(v) => {
if v.version {
println!(
"{} {} {}",
"{} {}",
command_name.first().unwrap_or(&""),
env!("CARGO_PKG_VERSION"),
env!("GIT_COMMIT_SHA"),
);
std::process::exit(0);
} else {
+1 -1
View File
@@ -237,7 +237,7 @@ fn report_object(
}
for (symbol, symbol_diff) in section.symbols.iter().zip(&section_diff.symbols) {
if symbol.size == 0 {
if symbol.size == 0 || symbol.flags.0.contains(ObjSymbolFlags::Hidden) {
continue;
}
if let Some(existing_functions) = &mut existing_functions {
+4 -1
View File
@@ -27,6 +27,9 @@ arm = ["any-arch", "cpp_demangle", "unarm", "arm-attr"]
bindings = ["serde_json", "prost", "pbjson"]
wasm = ["bindings", "console_error_panic_hook", "console_log"]
[package.metadata.docs.rs]
features = ["all"]
[dependencies]
anyhow = "1.0"
byteorder = "1.5"
@@ -57,7 +60,7 @@ gimli = { version = "0.31", default-features = false, features = ["read-all"], o
# ppc
cwdemangle = { version = "1.0", optional = true }
cwextab = { version = "0.2", optional = true }
cwextab = { version = "0.3", optional = true }
ppc750cl = { version = "0.3", optional = true }
# mips
+100 -1
View File
@@ -1,11 +1,13 @@
use std::{borrow::Cow, collections::BTreeMap};
use std::{borrow::Cow, collections::BTreeMap, ffi::CStr};
use anyhow::{bail, Result};
use byteorder::ByteOrder;
use object::{Architecture, File, Object, ObjectSymbol, Relocation, RelocationFlags, Symbol};
use crate::{
diff::DiffObjConfig,
obj::{ObjIns, ObjReloc, ObjSection},
util::ReallySigned,
};
#[cfg(feature = "arm")]
@@ -17,6 +19,97 @@ pub mod ppc;
#[cfg(feature = "x86")]
pub mod x86;
/// Represents the type of data associated with an instruction
pub enum DataType {
Int8,
Int16,
Int32,
Int64,
Int128,
Float,
Double,
Bytes,
String,
}
impl DataType {
pub fn display_bytes<Endian: ByteOrder>(&self, bytes: &[u8]) -> Option<String> {
if self.required_len().is_some_and(|l| bytes.len() < l) {
return None;
}
match self {
DataType::Int8 => {
let i = i8::from_ne_bytes(bytes.try_into().unwrap());
if i < 0 {
format!("Int8: {:#x} ({:#x})", i, ReallySigned(i))
} else {
format!("Int8: {:#x}", i)
}
}
DataType::Int16 => {
let i = Endian::read_i16(bytes);
if i < 0 {
format!("Int16: {:#x} ({:#x})", i, ReallySigned(i))
} else {
format!("Int16: {:#x}", i)
}
}
DataType::Int32 => {
let i = Endian::read_i32(bytes);
if i < 0 {
format!("Int32: {:#x} ({:#x})", i, ReallySigned(i))
} else {
format!("Int32: {:#x}", i)
}
}
DataType::Int64 => {
let i = Endian::read_i64(bytes);
if i < 0 {
format!("Int64: {:#x} ({:#x})", i, ReallySigned(i))
} else {
format!("Int64: {:#x}", i)
}
}
DataType::Int128 => {
let i = Endian::read_i128(bytes);
if i < 0 {
format!("Int128: {:#x} ({:#x})", i, ReallySigned(i))
} else {
format!("Int128: {:#x}", i)
}
}
DataType::Float => {
format!("Float: {}", Endian::read_f32(bytes))
}
DataType::Double => {
format!("Double: {}", Endian::read_f64(bytes))
}
DataType::Bytes => {
format!("Bytes: {:#?}", bytes)
}
DataType::String => {
format!("String: {:?}", CStr::from_bytes_until_nul(bytes).ok()?)
}
}
.into()
}
fn required_len(&self) -> Option<usize> {
match self {
DataType::Int8 => Some(1),
DataType::Int16 => Some(2),
DataType::Int32 => Some(4),
DataType::Int64 => Some(8),
DataType::Int128 => Some(16),
DataType::Float => Some(4),
DataType::Double => Some(8),
DataType::Bytes => None,
DataType::String => None,
}
}
}
pub trait ObjArch: Send + Sync {
fn process_code(
&self,
@@ -42,6 +135,12 @@ pub trait ObjArch: Send + Sync {
fn symbol_address(&self, symbol: &Symbol) -> u64 { symbol.address() }
fn guess_data_type(&self, _instruction: &ObjIns) -> Option<DataType> { None }
fn display_data_type(&self, _ty: DataType, bytes: &[u8]) -> Option<String> {
Some(format!("Bytes: {:#x?}", bytes))
}
// Downcast methods
#[cfg(feature = "ppc")]
fn ppc(&self) -> Option<&ppc::ObjArchPpc> { None }
+38 -5
View File
@@ -1,15 +1,16 @@
use std::{borrow::Cow, collections::BTreeMap};
use anyhow::{bail, ensure, Result};
use byteorder::BigEndian;
use cwextab::{decode_extab, ExceptionTableData};
use object::{
elf, File, Object, ObjectSection, ObjectSymbol, Relocation, RelocationFlags, RelocationTarget,
Symbol, SymbolKind,
};
use ppc750cl::{Argument, InsIter, GPR};
use ppc750cl::{Argument, InsIter, Opcode, GPR};
use crate::{
arch::{ObjArch, ProcessCodeResult},
arch::{DataType, ObjArch, ProcessCodeResult},
diff::DiffObjConfig,
obj::{ObjIns, ObjInsArg, ObjInsArgValue, ObjReloc, ObjSection, ObjSymbol},
};
@@ -186,6 +187,34 @@ impl ObjArch for ObjArchPpc {
}
}
fn guess_data_type(&self, instruction: &ObjIns) -> Option<super::DataType> {
// Always shows the first string of the table. Not ideal, but it's really hard to find
// the actual string being referenced.
if instruction.reloc.as_ref().is_some_and(|r| r.target.name.starts_with("@stringBase")) {
return Some(DataType::String);
}
match Opcode::from(instruction.op as u8) {
Opcode::Lbz | Opcode::Lbzu | Opcode::Lbzux | Opcode::Lbzx => Some(DataType::Int8),
Opcode::Lhz | Opcode::Lhzu | Opcode::Lhzux | Opcode::Lhzx => Some(DataType::Int16),
Opcode::Lha | Opcode::Lhau | Opcode::Lhaux | Opcode::Lhax => Some(DataType::Int16),
Opcode::Lwz | Opcode::Lwzu | Opcode::Lwzux | Opcode::Lwzx => Some(DataType::Int32),
Opcode::Lfs | Opcode::Lfsu | Opcode::Lfsux | Opcode::Lfsx => Some(DataType::Float),
Opcode::Lfd | Opcode::Lfdu | Opcode::Lfdux | Opcode::Lfdx => Some(DataType::Double),
Opcode::Stb | Opcode::Stbu | Opcode::Stbux | Opcode::Stbx => Some(DataType::Int8),
Opcode::Sth | Opcode::Sthu | Opcode::Sthux | Opcode::Sthx => Some(DataType::Int16),
Opcode::Stw | Opcode::Stwu | Opcode::Stwux | Opcode::Stwx => Some(DataType::Int32),
Opcode::Stfs | Opcode::Stfsu | Opcode::Stfsux | Opcode::Stfsx => Some(DataType::Float),
Opcode::Stfd | Opcode::Stfdu | Opcode::Stfdux | Opcode::Stfdx => Some(DataType::Double),
_ => None,
}
}
fn display_data_type(&self, ty: DataType, bytes: &[u8]) -> Option<String> {
ty.display_bytes::<BigEndian>(bytes)
}
fn ppc(&self) -> Option<&ObjArchPpc> { Some(self) }
}
@@ -303,9 +332,13 @@ fn decode_exception_info(file: &File<'_>) -> Result<Option<BTreeMap<usize, Excep
continue;
};
let data = match decode_extab(extab_data) {
Some(decoded_data) => decoded_data,
None => {
log::warn!("Exception table decoding failed for function {}", extab_func_name);
Ok(decoded_data) => decoded_data,
Err(e) => {
log::warn!(
"Exception table decoding failed for function {}, reason: {}",
extab_func_name,
e.to_string()
);
return Ok(None);
}
};
+66
View File
@@ -117,6 +117,72 @@ impl Report {
measures.calc_matched_percent();
}
}
/// Split the report into multiple reports based on progress categories.
/// Assumes progress categories are in the format `version`, `version.category`.
/// This is a hack for projects that generate all versions in a single report.
pub fn split(self) -> Vec<(String, Report)> {
let mut reports = Vec::new();
// Map units to Option to allow taking ownership
let mut units = self.units.into_iter().map(Some).collect::<Vec<_>>();
for category in &self.categories {
if category.id.contains(".") {
// Skip subcategories
continue;
}
fn is_sub_category(id: &str, parent: &str, sep: char) -> bool {
id.starts_with(parent)
&& id.get(parent.len()..).map_or(false, |s| s.starts_with(sep))
}
let mut sub_categories = self
.categories
.iter()
.filter(|c| is_sub_category(&c.id, &category.id, '.'))
.cloned()
.collect::<Vec<_>>();
// Remove category prefix
for sub_category in &mut sub_categories {
sub_category.id = sub_category.id[category.id.len() + 1..].to_string();
}
let mut sub_units = units
.iter_mut()
.filter_map(|opt| {
let unit = opt.as_mut()?;
let metadata = unit.metadata.as_ref()?;
if metadata.progress_categories.contains(&category.id) {
opt.take()
} else {
None
}
})
.collect::<Vec<_>>();
for sub_unit in &mut sub_units {
// Remove leading version/ from unit name
if let Some(name) =
sub_unit.name.strip_prefix(&category.id).and_then(|s| s.strip_prefix('/'))
{
sub_unit.name = name.to_string();
}
// Filter progress categories
let Some(metadata) = sub_unit.metadata.as_mut() else {
continue;
};
metadata.progress_categories = metadata
.progress_categories
.iter()
.filter(|c| is_sub_category(c, &category.id, '.'))
.map(|c| c[category.id.len() + 1..].to_string())
.collect();
}
reports.push((category.id.clone(), Report {
measures: category.measures,
units: sub_units,
version: self.version,
categories: sub_categories,
}));
}
reports
}
}
impl Measures {
+1
View File
@@ -126,6 +126,7 @@ pub struct ObjSymbol {
pub virtual_address: Option<u64>,
/// Original index in object symbol table
pub original_index: Option<usize>,
pub bytes: Vec<u8>,
}
pub struct ObjInfo {
+37 -4
View File
@@ -1,4 +1,10 @@
use std::{collections::HashSet, fs, io::Cursor, mem::size_of, path::Path};
use std::{
collections::{HashMap, HashSet},
fs,
io::Cursor,
mem::size_of,
path::Path,
};
use anyhow::{anyhow, bail, ensure, Context, Result};
use filetime::FileTime;
@@ -78,6 +84,16 @@ fn to_obj_symbol(
let virtual_address = split_meta
.and_then(|m| m.virtual_addresses.as_ref())
.and_then(|v| v.get(symbol.index().0).cloned());
let bytes = symbol
.section_index()
.and_then(|idx| obj_file.section_by_index(idx).ok())
.and_then(|section| section.data().ok())
.and_then(|data| {
data.get(section_address as usize..(section_address + symbol.size()) as usize)
})
.unwrap_or(&[]);
Ok(ObjSymbol {
name: name.to_string(),
demangled_name,
@@ -89,6 +105,7 @@ fn to_obj_symbol(
addend,
virtual_address,
original_index: Some(symbol.index().0),
bytes: bytes.to_vec(),
})
}
@@ -136,6 +153,7 @@ fn symbols_by_section(
obj_file: &File<'_>,
section: &ObjSection,
split_meta: Option<&SplitMeta>,
name_counts: &mut HashMap<String, u32>,
) -> Result<Vec<ObjSymbol>> {
let mut result = Vec::<ObjSymbol>::new();
for symbol in obj_file.symbols() {
@@ -168,8 +186,14 @@ fn symbols_by_section(
}
if result.is_empty() {
// Dummy symbol for empty sections
*name_counts.entry(section.name.clone()).or_insert(0) += 1;
let current_count: u32 = *name_counts.get(&section.name).unwrap();
result.push(ObjSymbol {
name: format!("[{}]", section.name),
name: if current_count > 1 {
format!("[{} ({})]", section.name, current_count)
} else {
format!("[{}]", section.name)
},
demangled_name: None,
address: 0,
section_address: 0,
@@ -179,6 +203,7 @@ fn symbols_by_section(
addend: 0,
virtual_address: None,
original_index: None,
bytes: Vec::new(),
});
}
Ok(result)
@@ -239,6 +264,7 @@ fn find_section_symbol(
addend: offset_addr as i64,
virtual_address: None,
original_index: None,
bytes: Vec::new(),
})
}
@@ -521,6 +547,7 @@ fn update_combined_symbol(symbol: ObjSymbol, address_change: i64) -> Result<ObjS
None
},
original_index: symbol.original_index,
bytes: symbol.bytes,
})
}
@@ -621,9 +648,15 @@ pub fn parse(data: &[u8], config: &DiffObjConfig) -> Result<ObjInfo> {
let arch = new_arch(&obj_file)?;
let split_meta = split_meta(&obj_file)?;
let mut sections = filter_sections(&obj_file, split_meta.as_ref())?;
let mut name_counts: HashMap<String, u32> = HashMap::new();
for section in &mut sections {
section.symbols =
symbols_by_section(arch.as_ref(), &obj_file, section, split_meta.as_ref())?;
section.symbols = symbols_by_section(
arch.as_ref(),
&obj_file,
section,
split_meta.as_ref(),
&mut name_counts,
)?;
section.relocations =
relocations_by_section(arch.as_ref(), &obj_file, section, split_meta.as_ref())?;
}
+9 -10
View File
@@ -29,10 +29,10 @@ bytes = "1.7"
cfg-if = "1.0"
const_format = "0.2"
cwdemangle = "1.0"
cwextab = "0.2"
cwextab = "0.3.1"
dirs = "5.0"
egui = "0.28"
egui_extras = "0.28"
egui = "0.29"
egui_extras = "0.29"
filetime = "0.2"
float-ord = "0.3"
font-kit = "0.14"
@@ -43,7 +43,7 @@ objdiff-core = { path = "../objdiff-core", features = ["all"] }
png = "0.17"
pollster = "0.3"
regex = "1.10"
rfd = { version = "0.14" } #, default-features = false, features = ['xdg-portal']
rfd = { version = "0.15" } #, default-features = false, features = ['xdg-portal']
rlwinmdec = "1.0"
ron = "0.8"
serde = { version = "1.0", features = ["derive"] }
@@ -55,7 +55,7 @@ time = { version = "0.3", features = ["formatting", "local-offset"] }
# Keep version in sync with egui
[dependencies.eframe]
version = "0.28"
version = "0.29"
features = [
"default_fonts",
"persistence",
@@ -66,7 +66,7 @@ default-features = false
# Keep version in sync with eframe
[dependencies.wgpu]
version = "0.20"
version = "22.1"
features = [
"dx12",
"metal",
@@ -89,9 +89,6 @@ self_update = "0.41"
path-slash = "0.2"
winapi = "0.3"
[target.'cfg(windows)'.build-dependencies]
winres = "0.1"
[target.'cfg(unix)'.dependencies]
exec = "0.3"
@@ -106,4 +103,6 @@ tracing-wasm = "0.2"
[build-dependencies]
anyhow = "1.0"
vergen-gitcl = { version = "1.0", features = ["build", "cargo"] }
[target.'cfg(windows)'.build-dependencies]
tauri-winres = "0.1"
+5 -7
View File
@@ -1,14 +1,12 @@
use anyhow::Result;
use vergen_gitcl::{BuildBuilder, CargoBuilder, Emitter, GitclBuilder};
fn main() -> Result<()> {
#[cfg(windows)]
{
winres::WindowsResource::new().set_icon("assets/icon.ico").compile()?;
let mut res = tauri_winres::WindowsResource::new();
res.set_icon("assets/icon.ico");
res.set_language(0x0409); // US English
res.compile()?;
}
Emitter::default()
.add_instructions(&BuildBuilder::all_build()?)?
.add_instructions(&CargoBuilder::all_cargo()?)?
.add_instructions(&GitclBuilder::all_git()?)?
.emit()
Ok(())
}
+12 -3
View File
@@ -18,6 +18,7 @@ use std::{
use anyhow::{ensure, Result};
use cfg_if::cfg_if;
use time::UtcOffset;
use tracing_subscriber::EnvFilter;
use crate::views::graphics::{load_graphics_config, GraphicsBackend, GraphicsConfig};
@@ -39,7 +40,16 @@ const APP_NAME: &str = "objdiff";
#[cfg(not(target_arch = "wasm32"))]
fn main() -> ExitCode {
// Log to stdout (if you run with `RUST_LOG=debug`).
tracing_subscriber::fmt::init();
tracing_subscriber::fmt()
.with_env_filter(
EnvFilter::builder()
// Default to info level
.with_default_directive(tracing_subscriber::filter::LevelFilter::INFO.into())
.from_env_lossy()
// This module is noisy at info level
.add_directive("wgpu_core::device::resource=warn".parse().unwrap()),
)
.init();
// Because localtime_r is unsound in multithreaded apps,
// we must call this before initializing eframe.
@@ -48,8 +58,7 @@ fn main() -> ExitCode {
let app_path = std::env::current_exe().ok();
let exec_path: Rc<Mutex<Option<PathBuf>>> = Rc::new(Mutex::new(None));
let mut native_options =
eframe::NativeOptions { follow_system_theme: false, ..Default::default() };
let mut native_options = eframe::NativeOptions::default();
match load_icon() {
Ok(data) => {
native_options.viewport.icon = Some(Arc::new(data));
+6 -6
View File
@@ -11,7 +11,7 @@ pub struct Appearance {
pub ui_font: FontId,
pub code_font: FontId,
pub diff_colors: Vec<Color32>,
pub theme: eframe::Theme,
pub theme: egui::Theme,
// Applied by theme
#[serde(skip)]
@@ -56,7 +56,7 @@ impl Default for Appearance {
ui_font: DEFAULT_UI_FONT,
code_font: DEFAULT_CODE_FONT,
diff_colors: DEFAULT_COLOR_ROTATION.to_vec(),
theme: eframe::Theme::Dark,
theme: egui::Theme::Dark,
text_color: Color32::GRAY,
emphasized_text_color: Color32::LIGHT_GRAY,
deemphasized_text_color: Color32::DARK_GRAY,
@@ -98,7 +98,7 @@ impl Appearance {
});
style.text_styles.insert(TextStyle::Monospace, self.code_font.clone());
match self.theme {
eframe::Theme::Dark => {
egui::Theme::Dark => {
style.visuals = egui::Visuals::dark();
self.text_color = Color32::GRAY;
self.emphasized_text_color = Color32::LIGHT_GRAY;
@@ -108,7 +108,7 @@ impl Appearance {
self.insert_color = Color32::GREEN;
self.delete_color = Color32::from_rgb(200, 40, 41);
}
eframe::Theme::Light => {
egui::Theme::Light => {
style.visuals = egui::Visuals::light();
self.text_color = Color32::GRAY;
self.emphasized_text_color = Color32::DARK_GRAY;
@@ -274,8 +274,8 @@ pub fn appearance_window(ctx: &egui::Context, show: &mut bool, appearance: &mut
egui::ComboBox::from_label("Theme")
.selected_text(format!("{:?}", appearance.theme))
.show_ui(ui, |ui| {
ui.selectable_value(&mut appearance.theme, eframe::Theme::Dark, "Dark");
ui.selectable_value(&mut appearance.theme, eframe::Theme::Light, "Light");
ui.selectable_value(&mut appearance.theme, egui::Theme::Dark, "Dark");
ui.selectable_value(&mut appearance.theme, egui::Theme::Light, "Light");
});
ui.separator();
appearance.next_ui_font =
+1 -8
View File
@@ -7,7 +7,6 @@ use std::{
#[cfg(all(windows, feature = "wsl"))]
use anyhow::{Context, Result};
use const_format::formatcp;
use egui::{
output::OpenUrl, text::LayoutJob, CollapsingHeader, FontFamily, FontId, RichText,
SelectableLabel, TextFormat, Widget,
@@ -17,7 +16,6 @@ use objdiff_core::{
config::{ProjectObject, DEFAULT_WATCH_PATTERNS},
diff::{ArmArchVersion, ArmR9Usage, MipsAbi, MipsInstrCategory, X86Formatter},
};
use self_update::cargo_crate_version;
use strum::{EnumMessage, VariantArray};
use crate::{
@@ -190,12 +188,7 @@ pub fn config_ui(
if ui.add_enabled(!state.check_update_running, egui::Button::new("Check now")).clicked() {
state.queue_check_update = true;
}
ui.label(format!("Current version: {}", cargo_crate_version!())).on_hover_ui_at_pointer(|ui| {
ui.label(formatcp!("Git branch: {}", env!("VERGEN_GIT_BRANCH")));
ui.label(formatcp!("Git commit: {}", env!("VERGEN_GIT_SHA")));
ui.label(formatcp!("Build target: {}", env!("VERGEN_CARGO_TARGET_TRIPLE")));
ui.label(formatcp!("Debug: {}", env!("VERGEN_CARGO_DEBUG")));
});
ui.label(format!("Current version: {}", env!("CARGO_PKG_VERSION")));
if let Some(result) = &state.check_update {
ui.label(format!("Latest version: {}", result.latest_release.version));
if result.update_available {
+86 -11
View File
@@ -17,9 +17,54 @@ use crate::views::{
symbol_diff::{match_color_for_symbol, DiffViewState, SymbolRefByName, View},
};
#[derive(Copy, Clone, Eq, PartialEq)]
enum ColumnId {
Left,
Right,
}
#[derive(Default)]
pub struct FunctionViewState {
pub highlight: HighlightKind,
left_highlight: HighlightKind,
right_highlight: HighlightKind,
}
impl FunctionViewState {
fn highlight(&self, column: ColumnId) -> &HighlightKind {
match column {
ColumnId::Left => &self.left_highlight,
ColumnId::Right => &self.right_highlight,
}
}
fn set_highlight(&mut self, column: ColumnId, highlight: HighlightKind) {
match column {
ColumnId::Left => {
if highlight == self.left_highlight {
if highlight == self.right_highlight {
self.left_highlight = HighlightKind::None;
self.right_highlight = HighlightKind::None;
} else {
self.right_highlight = self.left_highlight.clone();
}
} else {
self.left_highlight = highlight;
}
}
ColumnId::Right => {
if highlight == self.right_highlight {
if highlight == self.left_highlight {
self.left_highlight = HighlightKind::None;
self.right_highlight = HighlightKind::None;
} else {
self.left_highlight = self.right_highlight.clone();
}
} else {
self.right_highlight = highlight;
}
}
}
}
}
fn ins_hover_ui(
@@ -79,6 +124,12 @@ fn ins_hover_ui(
appearance.highlight_color,
format!("Size: {:x}", reloc.target.size),
);
if let Some(s) = arch
.guess_data_type(ins)
.and_then(|ty| arch.display_data_type(ty, &reloc.target.bytes))
{
ui.colored_label(appearance.highlight_color, s);
}
} else {
ui.colored_label(appearance.highlight_color, "Extern".to_string());
}
@@ -167,12 +218,14 @@ fn find_symbol(obj: &ObjInfo, selected_symbol: &SymbolRefByName) -> Option<Symbo
None
}
#[allow(clippy::too_many_arguments)]
fn diff_text_ui(
ui: &mut egui::Ui,
text: DiffText<'_>,
ins_diff: &ObjInsDiff,
appearance: &Appearance,
ins_view_state: &mut FunctionViewState,
column: ColumnId,
space_width: f32,
response_cb: impl Fn(Response) -> Response,
) {
@@ -237,7 +290,7 @@ fn diff_text_ui(
}
let len = label_text.len();
let highlight = ins_view_state.highlight == text;
let highlight = *ins_view_state.highlight(column) == text;
let mut response = Label::new(LayoutJob::single_section(
label_text,
appearance.code_text_format(base_color, highlight),
@@ -246,11 +299,7 @@ fn diff_text_ui(
.ui(ui);
response = response_cb(response);
if response.clicked() {
if highlight {
ins_view_state.highlight = HighlightKind::None;
} else {
ins_view_state.highlight = text.into();
}
ins_view_state.set_highlight(column, text.into());
}
if len < pad_to {
ui.add_space((pad_to - len) as f32 * space_width);
@@ -263,15 +312,26 @@ fn asm_row_ui(
symbol: &ObjSymbol,
appearance: &Appearance,
ins_view_state: &mut FunctionViewState,
column: ColumnId,
response_cb: impl Fn(Response) -> Response,
) {
ui.spacing_mut().item_spacing.x = 0.0;
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
if ins_diff.kind != ObjInsDiffKind::None {
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, |text| {
diff_text_ui(ui, text, ins_diff, appearance, ins_view_state, space_width, &response_cb);
diff_text_ui(
ui,
text,
ins_diff,
appearance,
ins_view_state,
column,
space_width,
&response_cb,
);
Ok::<_, ()>(())
})
.unwrap();
@@ -283,6 +343,7 @@ fn asm_col_ui(
symbol_ref: SymbolRef,
appearance: &Appearance,
ins_view_state: &mut FunctionViewState,
column: ColumnId,
) {
let (section, symbol) = obj.0.section_symbol(symbol_ref);
let section = section.unwrap();
@@ -298,7 +359,7 @@ fn asm_col_ui(
}
};
let (_, response) = row.col(|ui| {
asm_row_ui(ui, ins_diff, symbol, appearance, ins_view_state, response_cb);
asm_row_ui(ui, ins_diff, symbol, appearance, ins_view_state, column, response_cb);
});
response_cb(response);
}
@@ -337,12 +398,26 @@ fn asm_table_ui(
table.body(|body| {
body.rows(appearance.code_font.size, instructions_len, |mut row| {
if let (Some(left_obj), Some(left_symbol_ref)) = (left_obj, left_symbol) {
asm_col_ui(&mut row, left_obj, left_symbol_ref, appearance, ins_view_state);
asm_col_ui(
&mut row,
left_obj,
left_symbol_ref,
appearance,
ins_view_state,
ColumnId::Left,
);
} else {
empty_col_ui(&mut row);
}
if let (Some(right_obj), Some(right_symbol_ref)) = (right_obj, right_symbol) {
asm_col_ui(&mut row, right_obj, right_symbol_ref, appearance, ins_view_state);
asm_col_ui(
&mut row,
right_obj,
right_symbol_ref,
appearance,
ins_view_state,
ColumnId::Right,
);
} else {
empty_col_ui(&mut row);
}
+1 -1
View File
@@ -381,7 +381,7 @@ fn symbol_list_ui(
);
}
CollapsingHeader::new(header)
.id_source(Id::new(section.name.clone()).with(section.orig_index))
.id_salt(Id::new(section.name.clone()).with(section.orig_index))
.default_open(true)
.show(ui, |ui| {
if section.kind == ObjSectionKind::Code && state.reverse_fn_order {