mirror of
https://github.com/encounter/objdiff.git
synced 2026-07-10 12:18:36 -07:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a06382c27e | |||
| e013638c5a | |||
| 70ab82f1f7 | |||
| c5896689cf | |||
| 67719dd93e | |||
| 258e141017 | |||
| dbdda55065 | |||
| a43320af1f | |||
| 35bbd40f5d | |||
| c1cb4b0b19 | |||
| 2379853faa | |||
| 5e1aff180f |
Generated
+464
-629
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -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"
|
||||
|
||||
@@ -11,7 +11,6 @@ description = """
|
||||
A local diffing tool for decompilation projects.
|
||||
"""
|
||||
publish = false
|
||||
build = "build.rs"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -237,7 +237,7 @@ fn report_object(
|
||||
}
|
||||
|
||||
for (symbol, symbol_diff) in section.symbols.iter().zip(§ion_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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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(§ion.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
@@ -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"
|
||||
|
||||
@@ -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
@@ -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));
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user