Compare commits

..

No commits in common. "2ab519d361f0d57cbc6ad78562ac2bb8ef78facb" and "20dcc50695eb5d80232a1eadfba0c2dd2f0578ff" have entirely different histories.

13 changed files with 858 additions and 1047 deletions

1041
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
[package]
name = "objdiff"
version = "0.3.0"
version = "0.2.3"
edition = "2021"
rust-version = "1.65"
authors = ["Luke Street <luke@street.dev>"]
@ -17,40 +17,31 @@ lto = "thin"
strip = "debuginfo"
[dependencies]
anyhow = "1.0.68"
anyhow = "1.0.66"
bytes = "1.3.0"
cfg-if = "1.0.0"
const_format = "0.2.30"
cwdemangle = "0.1.4"
eframe = { version = "0.20.1", features = ["persistence"] } # , "wgpu"
egui = "0.20.1"
egui_extras = "0.20.0"
cwdemangle = { git = "https://github.com/encounter/cwdemangle", rev = "286f3d1d29ee2457db89043782725631845c3e4c" }
eframe = { version = "0.19.0", features = ["persistence"] } # , "wgpu"
egui = "0.19.0"
egui_extras = "0.19.0"
flagset = "0.4.3"
log = "0.4.17"
memmap2 = "0.5.8"
notify = "5.0.0"
object = { version = "0.30.2", features = ["read_core", "std", "elf"], default-features = false }
object = { version = "0.30.0", features = ["read_core", "std", "elf"], default-features = false }
png = "0.17.7"
ppc750cl = { git = "https://github.com/terorie/ppc750cl", rev = "9ae36eef34aa6d74e00972c7671f547a2acfd0aa" }
rabbitizer = "1.5.8"
rfd = { version = "0.10.0" } #, default-features = false, features = ['xdg-portal']
ppc750cl = { git = "https://github.com/encounter/ppc750cl", rev = "aa631a33de7882c679afca89350898b87cb3ba3f" }
rabbitizer = { git = "https://github.com/encounter/rabbitizer-rs", rev = "10c279b2ef251c62885b1dcdcfe740b0db8e9956" }
reqwest = "0.11.13"
rfd = { version = "0.10.0" } # , default-features = false, features = ['xdg-portal']
self_update = "0.32.0"
serde = { version = "1", features = ["derive"] }
tempfile = "3.3.0"
thiserror = "1.0.38"
thiserror = "1.0.37"
time = { version = "0.3.17", features = ["formatting", "local-offset"] }
toml = "0.5.11"
toml = "0.5.9"
twox-hash = "1.6.3"
byteorder = "1.4.3"
# For Linux static binaries, use rustls
[target.'cfg(target_os = "linux")'.dependencies]
reqwest = { version = "0.11.14", default-features = false, features = ["blocking", "json", "rustls"] }
self_update = { version = "0.34.0", default-features = false, features = ["rustls"] }
# For all other platforms, use native TLS
[target.'cfg(not(target_os = "linux"))'.dependencies]
reqwest = "0.11.14"
self_update = "0.34.0"
[target.'cfg(windows)'.dependencies]
path-slash = "0.2.1"
@ -72,5 +63,5 @@ console_error_panic_hook = "0.1.7"
tracing-wasm = "0.2"
[build-dependencies]
anyhow = "1.0.68"
vergen = { version = "7.5.0", features = ["build", "cargo", "git"], default-features = false }
anyhow = "1.0.66"
vergen = { version = "7.4.3", features = ["build", "cargo", "git"], default-features = false }

View File

@ -48,9 +48,7 @@ notice = "warn"
# A list of advisory IDs to ignore. Note that ignored advisories will still
# output a note when they are encountered.
ignore = [
# git2 (build dependency)
"RUSTSEC-2023-0002",
"RUSTSEC-2023-0003",
#"RUSTSEC-0000-0000",
]
# Threshold for security vulnerabilities, any vulnerability with a CVSS score
# lower than the range specified will be ignored. Note that ignored advisories
@ -83,9 +81,6 @@ allow = [
"Unicode-DFS-2016",
"Zlib",
"0BSD",
"OFL-1.1",
"LicenseRef-UFL-1.0",
"OpenSSL",
]
# List of explictly disallowed licenses
# See https://spdx.org/licenses/ for list of possible licenses
@ -123,22 +118,22 @@ exceptions = [
# Some crates don't have (easily) machine readable licensing information,
# adding a clarification entry for it allows you to manually specify the
# licensing information
[[licenses.clarify]]
#[[licenses.clarify]]
# The name of the crate the clarification applies to
name = "ring"
#name = "ring"
# The optional version constraint for the crate
version = "*"
#version = "*"
# The SPDX expression for the license requirements of the crate
expression = "MIT AND ISC AND OpenSSL"
#expression = "MIT AND ISC AND OpenSSL"
# One or more files in the crate's source used as the "source of truth" for
# the license expression. If the contents match, the clarification will be used
# when running the license check, otherwise the clarification will be ignored
# and the crate will be checked normally, which may produce warnings or errors
# depending on the rest of your configuration
license-files = [
#license-files = [
# Each entry is a crate relative path, and the (opaque) hash of its contents
{ path = "LICENSE", hash = 0xbd0eed23 }
]
#{ path = "LICENSE", hash = 0xbd0eed23 }
#]
[licenses.private]
# If true, ignores workspace crates that aren't published, or are only

View File

@ -17,19 +17,14 @@ fn no_diff_code(
data: &[u8],
symbol: &mut ObjSymbol,
relocs: &[ObjReloc],
line_info: &Option<BTreeMap<u32, u32>>,
) -> Result<()> {
let code =
&data[symbol.section_address as usize..(symbol.section_address + symbol.size) as usize];
let (_, ins) = match arch {
ObjArchitecture::PowerPc => ppc::process_code(code, symbol.address, relocs, line_info)?,
ObjArchitecture::Mips => mips::process_code(
code,
symbol.address,
symbol.address + symbol.size,
relocs,
line_info,
)?,
ObjArchitecture::PowerPc => ppc::process_code(code, symbol.address, relocs)?,
ObjArchitecture::Mips => {
mips::process_code(code, symbol.address, symbol.address + symbol.size, relocs)?
}
};
let mut diff = Vec::<ObjInsDiff>::new();
@ -41,7 +36,6 @@ fn no_diff_code(
Ok(())
}
#[allow(clippy::too_many_arguments)]
pub fn diff_code(
arch: ObjArchitecture,
left_data: &[u8],
@ -50,8 +44,6 @@ pub fn diff_code(
right_symbol: &mut ObjSymbol,
left_relocs: &[ObjReloc],
right_relocs: &[ObjReloc],
left_line_info: &Option<BTreeMap<u32, u32>>,
right_line_info: &Option<BTreeMap<u32, u32>>,
) -> Result<()> {
let left_code = &left_data[left_symbol.section_address as usize
..(left_symbol.section_address + left_symbol.size) as usize];
@ -59,8 +51,8 @@ pub fn diff_code(
..(right_symbol.section_address + right_symbol.size) as usize];
let ((left_ops, left_insts), (right_ops, right_insts)) = match arch {
ObjArchitecture::PowerPc => (
ppc::process_code(left_code, left_symbol.address, left_relocs, left_line_info)?,
ppc::process_code(right_code, right_symbol.address, right_relocs, right_line_info)?,
ppc::process_code(left_code, left_symbol.address, left_relocs)?,
ppc::process_code(right_code, right_symbol.address, right_relocs)?,
),
ObjArchitecture::Mips => (
mips::process_code(
@ -68,14 +60,12 @@ pub fn diff_code(
left_symbol.address,
left_symbol.address + left_symbol.size,
left_relocs,
left_line_info,
)?,
mips::process_code(
right_code,
right_symbol.address,
left_symbol.address + left_symbol.size,
right_relocs,
right_line_info,
)?,
),
};
@ -363,13 +353,17 @@ fn compare_ins(
Ok(result)
}
fn find_section<'a>(obj: &'a mut ObjInfo, name: &str) -> Option<&'a mut ObjSection> {
obj.sections.iter_mut().find(|s| s.name == name)
}
fn find_symbol<'a>(symbols: &'a mut [ObjSymbol], name: &str) -> Option<&'a mut ObjSymbol> {
symbols.iter_mut().find(|s| s.name == name)
}
pub fn diff_objs(left: &mut ObjInfo, right: &mut ObjInfo, _diff_config: &DiffConfig) -> Result<()> {
for left_section in &mut left.sections {
let Some(right_section) = right.sections.iter_mut().find(|s| s.name == left_section.name) else {
let Some(right_section) = find_section(right, &left_section.name) else {
continue;
};
if left_section.kind == ObjSectionKind::Code {
@ -387,8 +381,6 @@ pub fn diff_objs(left: &mut ObjInfo, right: &mut ObjInfo, _diff_config: &DiffCon
right_symbol,
&left_section.relocations,
&right_section.relocations,
&left.line_info,
&right.line_info,
)?;
} else {
no_diff_code(
@ -396,7 +388,6 @@ pub fn diff_objs(left: &mut ObjInfo, right: &mut ObjInfo, _diff_config: &DiffCon
&left_section.data,
left_symbol,
&left_section.relocations,
&left.line_info,
)?;
}
}
@ -407,7 +398,6 @@ pub fn diff_objs(left: &mut ObjInfo, right: &mut ObjInfo, _diff_config: &DiffCon
&right_section.data,
right_symbol,
&right_section.relocations,
&left.line_info,
)?;
}
}

View File

@ -40,18 +40,25 @@ pub struct LevEditOp {
pub second_start: usize, /* destination position */
}
#[derive(Debug, PartialEq, Eq)]
pub struct LevMatchingBlock {
pub first_start: usize,
pub second_start: usize,
pub len: usize,
}
pub fn editops_find<T>(query: &[T], choice: &[T]) -> Vec<LevEditOp>
where T: PartialEq {
let Affix {
prefix_len,
suffix_len,
} = Affix::find(query, choice);
let string_affix = Affix::find(query, choice);
let first_string = &query[prefix_len..query.len() - suffix_len];
let second_string = &choice[prefix_len..choice.len() - suffix_len];
let first_string_len = string_affix.first_string_len;
let second_string_len = string_affix.second_string_len;
let prefix_len = string_affix.prefix_len;
let first_string = &query[prefix_len..prefix_len + first_string_len];
let second_string = &choice[prefix_len..prefix_len + second_string_len];
let matrix_columns = first_string.len() + 1;
let matrix_rows = second_string.len() + 1;
let matrix_columns = first_string_len + 1;
let matrix_rows = second_string_len + 1;
// TODO maybe use an actual matrix for readability
let mut cache_matrix: Vec<usize> = vec![0; matrix_rows * matrix_columns];
@ -179,25 +186,73 @@ where
pub struct Affix {
pub prefix_len: usize,
pub suffix_len: usize,
pub first_string_len: usize,
pub second_string_len: usize,
}
impl Affix {
pub fn find<T>(s1: &[T], s2: &[T]) -> Affix
pub fn find<T>(first_string: &[T], second_string: &[T]) -> Affix
where T: PartialEq {
let prefix_len = s1.iter()
.zip(s2.iter())
.take_while(|t| t.0 == t.1)
.count();
let suffix_len = s1[prefix_len..].iter()
.rev()
.zip(s2[prefix_len..].iter().rev())
.take_while(|t| t.0 == t.1)
.count();
// remove common prefix and suffix (linear vs square runtime for levensthein)
let mut first_iter = first_string.iter();
let mut second_iter = second_string.iter();
Affix {
prefix_len,
suffix_len,
let mut limit_start = 0;
let mut first_iter_char = first_iter.next();
let mut second_iter_char = second_iter.next();
while first_iter_char.is_some() && first_iter_char == second_iter_char {
first_iter_char = first_iter.next();
second_iter_char = second_iter.next();
limit_start += 1;
}
// save char since the iterator was already consumed
let first_iter_cache = first_iter_char;
let second_iter_cache = second_iter_char;
if second_iter_char.is_some() && first_iter_char.is_some() {
first_iter_char = first_iter.next_back();
second_iter_char = second_iter.next_back();
while first_iter_char.is_some() && first_iter_char == second_iter_char {
first_iter_char = first_iter.next_back();
second_iter_char = second_iter.next_back();
}
}
match (first_iter_char, second_iter_char) {
(None, None) => {
// characters might not match even though they were consumed
let remaining_char = (first_iter_cache != second_iter_cache) as usize;
Affix {
prefix_len: limit_start,
first_string_len: remaining_char,
second_string_len: remaining_char,
}
}
(None, _) => {
let remaining_char =
(first_iter_cache.is_some() && first_iter_cache != second_iter_char) as usize;
Affix {
prefix_len: limit_start,
first_string_len: remaining_char,
second_string_len: second_iter.count() + 1 + remaining_char,
}
}
(_, None) => {
let remaining_char =
(second_iter_cache.is_some() && second_iter_cache != first_iter_char) as usize;
Affix {
prefix_len: limit_start,
first_string_len: first_iter.count() + 1 + remaining_char,
second_string_len: remaining_char,
}
}
_ => Affix {
prefix_len: limit_start,
first_string_len: first_iter.count() + 2,
second_string_len: second_iter.count() + 2,
},
}
}
}

View File

@ -1,7 +1,6 @@
use std::{collections::BTreeMap, fs, io::Cursor, path::Path};
use std::{fs, path::Path};
use anyhow::{Context, Result};
use byteorder::{BigEndian, ReadBytesExt};
use cwdemangle::demangle;
use flagset::Flags;
use object::{
@ -18,12 +17,12 @@ use crate::obj::{
ObjSymbolFlagSet, ObjSymbolFlags,
};
fn to_obj_section_kind(kind: SectionKind) -> Option<ObjSectionKind> {
fn to_obj_section_kind(kind: SectionKind) -> ObjSectionKind {
match kind {
SectionKind::Text => Some(ObjSectionKind::Code),
SectionKind::Data | SectionKind::ReadOnlyData => Some(ObjSectionKind::Data),
SectionKind::UninitializedData => Some(ObjSectionKind::Bss),
_ => None,
SectionKind::Text => ObjSectionKind::Code,
SectionKind::Data | SectionKind::ReadOnlyData => ObjSectionKind::Data,
SectionKind::UninitializedData => ObjSectionKind::Bss,
_ => panic!("Unhandled section kind {kind:?}"),
}
}
@ -74,14 +73,18 @@ fn filter_sections(obj_file: &File<'_>) -> Result<Vec<ObjSection>> {
if section.size() == 0 {
continue;
}
let Some(kind) = to_obj_section_kind(section.kind()) else {
if section.kind() != SectionKind::Text
&& section.kind() != SectionKind::Data
&& section.kind() != SectionKind::ReadOnlyData
&& section.kind() != SectionKind::UninitializedData
{
continue;
};
}
let name = section.name().context("Failed to process section name")?;
let data = section.uncompressed_data().context("Failed to read section data")?;
result.push(ObjSection {
name: name.to_string(),
kind,
kind: to_obj_section_kind(section.kind()),
address: section.address(),
size: section.size(),
data: data.to_vec(),
@ -246,7 +249,7 @@ fn relocations_by_section(
};
// println!("Reloc: {:?}, symbol: {:?}", reloc, symbol);
let target = match symbol.kind() {
SymbolKind::Text | SymbolKind::Data | SymbolKind::Label | SymbolKind::Unknown => {
SymbolKind::Text | SymbolKind::Data | SymbolKind::Unknown => {
to_obj_symbol(obj_file, &symbol, reloc.addend())
}
SymbolKind::Section => {
@ -281,32 +284,6 @@ fn relocations_by_section(
Ok(relocations)
}
fn line_info(obj_file: &File<'_>) -> Result<Option<BTreeMap<u32, u32>>> {
if let Some(section) = obj_file.section_by_name(".line") {
if section.size() == 0 {
return Ok(None);
}
let data = section.uncompressed_data()?;
let mut reader = Cursor::new(data.as_ref());
let mut map = BTreeMap::new();
let size = reader.read_u32::<BigEndian>()?;
let base_address = reader.read_u32::<BigEndian>()?;
while reader.position() < size as u64 {
let line_number = reader.read_u32::<BigEndian>()?;
let statement_pos = reader.read_u16::<BigEndian>()?;
if statement_pos != 0xFFFF {
log::warn!("Unhandled statement pos {}", statement_pos);
}
let address_delta = reader.read_u32::<BigEndian>()?;
map.insert(base_address + address_delta, line_number);
}
println!("Line info: {map:#X?}");
return Ok(Some(map));
}
Ok(None)
}
pub fn read(obj_path: &Path) -> Result<ObjInfo> {
let data = {
let file = fs::File::open(obj_path)?;
@ -328,7 +305,6 @@ pub fn read(obj_path: &Path) -> Result<ObjInfo> {
path: obj_path.to_owned(),
sections: filter_sections(&obj_file)?,
common: common_symbols(&obj_file)?,
line_info: line_info(&obj_file)?,
};
for section in &mut result.sections {
section.symbols = symbols_by_section(&obj_file, section)?;

View File

@ -1,24 +1,15 @@
use std::collections::BTreeMap;
use anyhow::Result;
use rabbitizer::{config, Abi, Instruction, InstrCategory, OperandType};
use rabbitizer::{config_set_register_fpr_abi_names, Abi, Instruction, SimpleOperandType};
use crate::obj::{ObjIns, ObjInsArg, ObjReloc};
fn configure_rabbitizer() {
unsafe {
config::RabbitizerConfig_Cfg.reg_names.fpr_abi_names = Abi::O32;
}
}
pub fn process_code(
data: &[u8],
start_address: u64,
end_address: u64,
relocs: &[ObjReloc],
line_info: &Option<BTreeMap<u32, u32>>,
) -> Result<(Vec<u8>, Vec<ObjIns>)> {
configure_rabbitizer();
config_set_register_fpr_abi_names(Abi::RABBITIZER_ABI_O32);
let ins_count = data.len() / 4;
let mut ops = Vec::<u8>::with_capacity(ins_count);
@ -27,21 +18,21 @@ pub fn process_code(
for chunk in data.chunks_exact(4) {
let reloc = relocs.iter().find(|r| (r.address as u32 & !3) == cur_addr);
let code = u32::from_be_bytes(chunk.try_into()?);
let instruction = Instruction::new(code, cur_addr, InstrCategory::CPU);
let mut instruction = Instruction::new(code, cur_addr);
let op = instruction.unique_id as u8;
let op = instruction.instr_id() as u8;
ops.push(op);
let mnemonic = instruction.opcode_name().to_string();
let mnemonic = instruction.instr_id().get_opcode_name().unwrap_or_default().to_string();
let is_branch = instruction.is_branch();
let branch_offset = instruction.branch_offset();
let branch_dest =
if is_branch { Some((cur_addr as i32 + branch_offset) as u32) } else { None };
let args = instruction
.get_operands_slice()
.simple_operands()
.iter()
.map(|op| match op {
OperandType::cpu_immediate | OperandType::cpu_label | OperandType::cpu_branch_target_label => {
.map(|op| match op.kind {
SimpleOperandType::Imm | SimpleOperandType::Label => {
if is_branch {
ObjInsArg::BranchOffset(branch_offset)
} else if let Some(reloc) = reloc {
@ -55,21 +46,19 @@ pub fn process_code(
ObjInsArg::Reloc
}
} else {
ObjInsArg::MipsArg(op.disassemble(&instruction, None))
ObjInsArg::MipsArg(op.disassembled.clone())
}
}
OperandType::cpu_immediate_base => {
SimpleOperandType::ImmBase => {
if reloc.is_some() {
ObjInsArg::RelocWithBase
} else {
ObjInsArg::MipsArg(op.disassemble(&instruction, None))
ObjInsArg::MipsArg(op.disassembled.clone())
}
}
_ => ObjInsArg::MipsArg(op.disassemble(&instruction, None)),
_ => ObjInsArg::MipsArg(op.disassembled.clone()),
})
.collect();
let line =
line_info.as_ref().and_then(|map| map.range(..=cur_addr).last().map(|(_, &b)| b));
insts.push(ObjIns {
address: cur_addr,
code,
@ -78,7 +67,6 @@ pub fn process_code(
args,
reloc: reloc.cloned(),
branch_dest,
line,
});
cur_addr += 4;
}

View File

@ -2,7 +2,7 @@ pub mod elf;
pub mod mips;
pub mod ppc;
use std::{collections::BTreeMap, path::PathBuf};
use std::path::PathBuf;
use flagset::{flags, FlagSet};
@ -83,8 +83,6 @@ pub struct ObjIns {
pub args: Vec<ObjInsArg>,
pub reloc: Option<ObjReloc>,
pub branch_dest: Option<u32>,
/// Line info
pub line: Option<u32>,
}
#[derive(Debug, Clone, Default)]
pub struct ObjInsDiff {
@ -140,7 +138,6 @@ pub struct ObjInfo {
pub path: PathBuf,
pub sections: Vec<ObjSection>,
pub common: Vec<ObjSymbol>,
pub line_info: Option<BTreeMap<u32, u32>>,
}
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
pub enum ObjRelocKind {

View File

@ -1,5 +1,3 @@
use std::collections::BTreeMap;
use anyhow::Result;
use ppc750cl::{disasm_iter, Argument};
@ -21,7 +19,6 @@ pub fn process_code(
data: &[u8],
address: u64,
relocs: &[ObjReloc],
line_info: &Option<BTreeMap<u32, u32>>,
) -> Result<(Vec<u8>, Vec<ObjIns>)> {
let ins_count = data.len() / 4;
let mut ops = Vec::<u8>::with_capacity(ins_count);
@ -77,9 +74,6 @@ pub fn process_code(
}
}
ops.push(simplified.ins.op as u8);
let line = line_info
.as_ref()
.and_then(|map| map.range(..=simplified.ins.addr).last().map(|(_, &b)| b));
insts.push(ObjIns {
address: simplified.ins.addr,
code: simplified.ins.code,
@ -88,7 +82,6 @@ pub fn process_code(
reloc: reloc.cloned(),
op: 0,
branch_dest: None,
line,
});
}
Ok((ops, insts))

View File

@ -1,7 +1,7 @@
use std::{cmp::min, default::Default, mem::take};
use egui::{text::LayoutJob, Align, Color32, Label, Layout, Sense, Vec2};
use egui_extras::{Column, TableBuilder};
use egui::{text::LayoutJob, Color32, Label, Sense};
use egui_extras::{Size, StripBuilder, TableBuilder};
use time::format_description;
use crate::{
@ -14,7 +14,9 @@ use crate::{
const BYTES_PER_ROW: usize = 16;
fn find_section<'a>(obj: &'a ObjInfo, selected_symbol: &SymbolReference) -> Option<&'a ObjSection> {
obj.sections.iter().find(|section| section.name == selected_symbol.section_name)
obj.sections.iter().find(|section| {
section.symbols.iter().any(|symbol| symbol.name == selected_symbol.symbol_name)
})
}
fn data_row_ui(ui: &mut egui::Ui, address: usize, diffs: &[ObjDataDiff], config: &ViewConfig) {
@ -166,89 +168,88 @@ pub fn data_diff_ui(ui: &mut egui::Ui, view_state: &mut ViewState) -> bool {
let (Some(result), Some(selected_symbol)) = (&view_state.build, &view_state.selected_symbol) else {
return rebuild;
};
// Header
let available_width = ui.available_width();
let column_width = available_width / 2.0;
ui.allocate_ui_with_layout(
Vec2 { x: available_width, y: 100.0 },
Layout::left_to_right(Align::Min),
|ui| {
// Left column
ui.allocate_ui_with_layout(
Vec2 { x: column_width, y: 100.0 },
Layout::top_down(Align::Min),
|ui| {
ui.set_width(column_width);
if ui.button("Back").clicked() {
view_state.current_view = View::SymbolDiff;
}
ui.scope(|ui| {
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
ui.style_mut().wrap = Some(false);
ui.colored_label(Color32::WHITE, &selected_symbol.symbol_name);
ui.label("Diff target:");
});
},
);
// Right column
ui.allocate_ui_with_layout(
Vec2 { x: column_width, y: 100.0 },
Layout::top_down(Align::Min),
|ui| {
ui.set_width(column_width);
ui.horizontal(|ui| {
if ui.button("Build").clicked() {
rebuild = true;
}
ui.scope(|ui| {
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
ui.style_mut().wrap = Some(false);
if view_state.jobs.iter().any(|job| job.job_type == Job::ObjDiff) {
ui.label("Building...");
} else {
ui.label("Last built:");
let format =
format_description::parse("[hour]:[minute]:[second]").unwrap();
ui.label(
result
.time
.to_offset(view_state.utc_offset)
.format(&format)
.unwrap(),
);
StripBuilder::new(ui)
.size(Size::exact(20.0))
.size(Size::exact(40.0))
.size(Size::remainder())
.vertical(|mut strip| {
strip.strip(|builder| {
builder.sizes(Size::remainder(), 2).horizontal(|mut strip| {
strip.cell(|ui| {
ui.horizontal(|ui| {
if ui.button("Back").clicked() {
view_state.current_view = View::SymbolDiff;
}
});
});
ui.scope(|ui| {
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
ui.style_mut().wrap = Some(false);
ui.label("");
ui.label("Diff base:");
strip.cell(|ui| {
ui.horizontal(|ui| {
if ui.button("Build").clicked() {
rebuild = true;
}
ui.scope(|ui| {
ui.style_mut().override_text_style =
Some(egui::TextStyle::Monospace);
ui.style_mut().wrap = Some(false);
if view_state.jobs.iter().any(|job| job.job_type == Job::ObjDiff) {
ui.label("Building...");
} else {
ui.label("Last built:");
let format =
format_description::parse("[hour]:[minute]:[second]")
.unwrap();
ui.label(
result
.time
.to_offset(view_state.utc_offset)
.format(&format)
.unwrap(),
);
}
});
});
});
},
);
},
);
ui.separator();
// Table
if let (Some(left_obj), Some(right_obj)) = (&result.first_obj, &result.second_obj) {
let available_height = ui.available_height();
let table = TableBuilder::new(ui)
.striped(false)
.cell_layout(Layout::left_to_right(Align::Min))
.columns(Column::exact(column_width).clip(true), 2)
.resizable(false)
.auto_shrink([false, false])
.min_scrolled_height(available_height);
data_table_ui(table, left_obj, right_obj, selected_symbol, &view_state.view_config);
}
});
});
strip.strip(|builder| {
builder.sizes(Size::remainder(), 2).horizontal(|mut strip| {
strip.cell(|ui| {
ui.scope(|ui| {
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
ui.style_mut().wrap = Some(false);
ui.colored_label(Color32::WHITE, &selected_symbol.symbol_name);
ui.label("Diff target:");
ui.separator();
});
});
strip.cell(|ui| {
ui.scope(|ui| {
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
ui.style_mut().wrap = Some(false);
ui.label("");
ui.label("Diff base:");
ui.separator();
});
});
});
});
strip.cell(|ui| {
if let (Some(left_obj), Some(right_obj)) = (&result.first_obj, &result.second_obj) {
let table = TableBuilder::new(ui)
.striped(false)
.cell_layout(egui::Layout::left_to_right(egui::Align::Min))
.column(Size::relative(0.5))
.column(Size::relative(0.5))
.resizable(false);
data_table_ui(
table,
left_obj,
right_obj,
selected_symbol,
&view_state.view_config,
);
}
});
});
rebuild
}

View File

@ -1,9 +1,8 @@
use std::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, FontId, Label, Sense};
use egui_extras::{Size, StripBuilder, TableBuilder};
use ppc750cl::Argument;
use time::format_description;
@ -265,14 +264,8 @@ fn asm_row_ui(ui: &mut egui::Ui, ins_diff: &ObjInsDiff, symbol: &ObjSymbol, conf
ObjInsDiffKind::Delete => COLOR_RED,
ObjInsDiffKind::Insert => Color32::GREEN,
};
let mut pad = 6;
if let Some(line) = ins.line {
let line_str = format!("{line} ");
write_text(&line_str, Color32::DARK_GRAY, &mut job, config.code_font.clone());
pad = 12 - line_str.len();
}
write_text(
&format!("{:<1$}", format!("{:x}: ", ins.address - symbol.address as u32), pad),
&format!("{:<6}", format!("{:x}:", ins.address - symbol.address as u32)),
base_color,
&mut job,
config.code_font.clone(),
@ -333,109 +326,102 @@ pub fn function_diff_ui(ui: &mut egui::Ui, view_state: &mut ViewState) -> bool {
let (Some(result), Some(selected_symbol)) = (&view_state.build, &view_state.selected_symbol) else {
return rebuild;
};
// Header
let available_width = ui.available_width();
let column_width = available_width / 2.0;
ui.allocate_ui_with_layout(
Vec2 { x: available_width, y: 100.0 },
Layout::left_to_right(Align::Min),
|ui| {
// Left column
ui.allocate_ui_with_layout(
Vec2 { x: column_width, y: 100.0 },
Layout::top_down(Align::Min),
|ui| {
ui.set_width(column_width);
if ui.button("Back").clicked() {
view_state.current_view = View::SymbolDiff;
}
let demangled = demangle(&selected_symbol.symbol_name, &Default::default());
let name = demangled.as_deref().unwrap_or(&selected_symbol.symbol_name);
let mut job = LayoutJob::simple(
name.to_string(),
view_state.view_config.code_font.clone(),
Color32::WHITE,
column_width,
);
job.wrap.break_anywhere = true;
job.wrap.max_rows = 1;
ui.label(job);
ui.scope(|ui| {
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
ui.label("Diff target:");
});
},
);
// Right column
ui.allocate_ui_with_layout(
Vec2 { x: column_width, y: 100.0 },
Layout::top_down(Align::Min),
|ui| {
ui.set_width(column_width);
ui.horizontal(|ui| {
if ui.button("Build").clicked() {
rebuild = true;
}
ui.scope(|ui| {
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
ui.style_mut().wrap = Some(false);
if view_state.jobs.iter().any(|job| job.job_type == Job::ObjDiff) {
ui.label("Building...");
} else {
ui.label("Last built:");
let format =
format_description::parse("[hour]:[minute]:[second]").unwrap();
ui.label(
result
.time
.to_offset(view_state.utc_offset)
.format(&format)
.unwrap(),
);
StripBuilder::new(ui)
.size(Size::exact(20.0))
.size(Size::exact(40.0))
.size(Size::remainder())
.vertical(|mut strip| {
strip.strip(|builder| {
builder.sizes(Size::remainder(), 2).horizontal(|mut strip| {
strip.cell(|ui| {
ui.horizontal(|ui| {
if ui.button("Back").clicked() {
view_state.current_view = View::SymbolDiff;
}
});
});
ui.scope(|ui| {
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
if let Some(match_percent) = result
.second_obj
.as_ref()
.and_then(|obj| find_symbol(obj, selected_symbol))
.and_then(|symbol| symbol.match_percent)
{
ui.colored_label(
match_color_for_symbol(match_percent),
&format!("{match_percent:.0}%"),
);
} else {
ui.label("");
}
ui.label("Diff base:");
strip.cell(|ui| {
ui.horizontal(|ui| {
if ui.button("Build").clicked() {
rebuild = true;
}
ui.scope(|ui| {
ui.style_mut().override_text_style =
Some(egui::TextStyle::Monospace);
ui.style_mut().wrap = Some(false);
if view_state.jobs.iter().any(|job| job.job_type == Job::ObjDiff) {
ui.label("Building...");
} else {
ui.label("Last built:");
let format =
format_description::parse("[hour]:[minute]:[second]")
.unwrap();
ui.label(
result
.time
.to_offset(view_state.utc_offset)
.format(&format)
.unwrap(),
);
}
});
});
});
},
);
},
);
ui.separator();
// Table
if let (Some(left_obj), Some(right_obj)) = (&result.first_obj, &result.second_obj) {
let available_height = ui.available_height();
let table = TableBuilder::new(ui)
.striped(false)
.cell_layout(Layout::left_to_right(Align::Min))
.columns(Column::exact(column_width).clip(true), 2)
.resizable(false)
.auto_shrink([false, false])
.min_scrolled_height(available_height);
asm_table_ui(table, left_obj, right_obj, selected_symbol, &view_state.view_config);
}
});
});
strip.strip(|builder| {
builder.sizes(Size::remainder(), 2).horizontal(|mut strip| {
let demangled = demangle(&selected_symbol.symbol_name, &Default::default());
strip.cell(|ui| {
ui.scope(|ui| {
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
ui.style_mut().wrap = Some(false);
ui.colored_label(
Color32::WHITE,
demangled.as_ref().unwrap_or(&selected_symbol.symbol_name),
);
ui.label("Diff target:");
ui.separator();
});
});
strip.cell(|ui| {
ui.scope(|ui| {
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
ui.style_mut().wrap = Some(false);
if let Some(match_percent) = result
.second_obj
.as_ref()
.and_then(|obj| find_symbol(obj, selected_symbol))
.and_then(|symbol| symbol.match_percent)
{
ui.colored_label(
match_color_for_symbol(match_percent),
&format!("{match_percent:.0}%"),
);
}
ui.label("Diff base:");
ui.separator();
});
});
});
});
strip.cell(|ui| {
if let (Some(left_obj), Some(right_obj)) = (&result.first_obj, &result.second_obj) {
let table = TableBuilder::new(ui)
.striped(false)
.cell_layout(egui::Layout::left_to_right(egui::Align::Min))
.column(Size::relative(0.5))
.column(Size::relative(0.5))
.resizable(false);
asm_table_ui(
table,
left_obj,
right_obj,
selected_symbol,
&view_state.view_config,
);
}
});
});
rebuild
}

View File

@ -9,5 +9,5 @@ pub(crate) mod symbol_diff;
const COLOR_RED: Color32 = Color32::from_rgb(200, 40, 41);
fn write_text(str: &str, color: Color32, job: &mut LayoutJob, font_id: FontId) {
job.append(str, 0.0, TextFormat::simple(font_id, color));
job.append(str, 0.0, TextFormat { font_id, color, ..Default::default() });
}

View File

@ -1,6 +1,5 @@
use egui::{
text::LayoutJob, Align, CollapsingHeader, Color32, Layout, Rgba, ScrollArea, SelectableLabel,
TextEdit, Ui, Vec2, Widget,
text::LayoutJob, CollapsingHeader, Color32, Rgba, ScrollArea, SelectableLabel, Ui, Widget,
};
use egui_extras::{Size, StripBuilder};
@ -136,9 +135,12 @@ fn symbol_list_ui(
selected_symbol: &mut Option<SymbolReference>,
current_view: &mut View,
reverse_function_order: bool,
lower_search: &str,
search: &mut String,
config: &ViewConfig,
) {
ui.text_edit_singleline(search);
let lower_search = search.to_ascii_lowercase();
ScrollArea::both().auto_shrink([false, false]).show(ui, |ui| {
ui.scope(|ui| {
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
@ -166,7 +168,7 @@ fn symbol_list_ui(
.show(ui, |ui| {
if section.kind == ObjSectionKind::Code && reverse_function_order {
for symbol in section.symbols.iter().rev() {
if !symbol_matches_search(symbol, lower_search) {
if !symbol_matches_search(symbol, &lower_search) {
continue;
}
symbol_ui(
@ -181,7 +183,7 @@ fn symbol_list_ui(
}
} else {
for symbol in &section.symbols {
if !symbol_matches_search(symbol, lower_search) {
if !symbol_matches_search(symbol, &lower_search) {
continue;
}
symbol_ui(
@ -213,116 +215,94 @@ fn build_log_ui(ui: &mut Ui, status: &BuildStatus) {
}
pub fn symbol_diff_ui(ui: &mut Ui, view_state: &mut ViewState) {
let (Some(result), highlighted_symbol, selected_symbol, current_view, search) = (
if let (Some(result), highlighted_symbol, selected_symbol, current_view, search) = (
&view_state.build,
&mut view_state.highlighted_symbol,
&mut view_state.selected_symbol,
&mut view_state.current_view,
&mut view_state.search,
) else {
return;
};
) {
StripBuilder::new(ui).size(Size::exact(40.0)).size(Size::remainder()).vertical(
|mut strip| {
strip.strip(|builder| {
builder.sizes(Size::remainder(), 2).horizontal(|mut strip| {
strip.cell(|ui| {
ui.scope(|ui| {
ui.style_mut().override_text_style =
Some(egui::TextStyle::Monospace);
ui.style_mut().wrap = Some(false);
// Header
let available_width = ui.available_width();
let column_width = available_width / 2.0;
ui.allocate_ui_with_layout(
Vec2 { x: available_width, y: 100.0 },
Layout::left_to_right(Align::Min),
|ui| {
// Left column
ui.allocate_ui_with_layout(
Vec2 { x: column_width, y: 100.0 },
Layout::top_down(Align::Min),
|ui| {
ui.set_width(column_width);
ui.scope(|ui| {
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
ui.style_mut().wrap = Some(false);
ui.label("Build target:");
if result.first_status.success {
ui.label("OK");
} else {
ui.colored_label(Rgba::from_rgb(1.0, 0.0, 0.0), "Fail");
}
});
TextEdit::singleline(search).hint_text("Filter symbols").ui(ui);
},
);
// Right column
ui.allocate_ui_with_layout(
Vec2 { x: column_width, y: 100.0 },
Layout::top_down(Align::Min),
|ui| {
ui.set_width(column_width);
ui.scope(|ui| {
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
ui.style_mut().wrap = Some(false);
ui.label("Build base:");
if result.second_status.success {
ui.label("OK");
} else {
ui.colored_label(Rgba::from_rgb(1.0, 0.0, 0.0), "Fail");
}
});
},
);
},
);
ui.separator();
// Table
let lower_search = search.to_ascii_lowercase();
StripBuilder::new(ui).size(Size::remainder()).vertical(|mut strip| {
strip.strip(|builder| {
builder.sizes(Size::remainder(), 2).horizontal(|mut strip| {
strip.cell(|ui| {
if result.first_status.success {
if let Some(obj) = &result.first_obj {
ui.push_id("left", |ui| {
symbol_list_ui(
ui,
obj,
highlighted_symbol,
selected_symbol,
current_view,
view_state.reverse_fn_order,
&lower_search,
&view_state.view_config,
);
ui.label("Build target:");
if result.first_status.success {
ui.label("OK");
} else {
ui.colored_label(Rgba::from_rgb(1.0, 0.0, 0.0), "Fail");
}
});
}
} else {
build_log_ui(ui, &result.first_status);
}
});
strip.cell(|ui| {
if result.second_status.success {
if let Some(obj) = &result.second_obj {
ui.push_id("right", |ui| {
symbol_list_ui(
ui,
obj,
highlighted_symbol,
selected_symbol,
current_view,
view_state.reverse_fn_order,
&lower_search,
&view_state.view_config,
);
ui.separator();
});
strip.cell(|ui| {
ui.scope(|ui| {
ui.style_mut().override_text_style =
Some(egui::TextStyle::Monospace);
ui.style_mut().wrap = Some(false);
ui.label("Build base:");
if result.second_status.success {
ui.label("OK");
} else {
ui.colored_label(Rgba::from_rgb(1.0, 0.0, 0.0), "Fail");
}
});
}
} else {
build_log_ui(ui, &result.second_status);
}
ui.separator();
});
});
});
});
});
});
strip.strip(|builder| {
builder.sizes(Size::remainder(), 2).horizontal(|mut strip| {
strip.cell(|ui| {
if result.first_status.success {
if let Some(obj) = &result.first_obj {
ui.push_id("left", |ui| {
symbol_list_ui(
ui,
obj,
highlighted_symbol,
selected_symbol,
current_view,
view_state.reverse_fn_order,
search,
&view_state.view_config,
);
});
}
} else {
build_log_ui(ui, &result.first_status);
}
});
strip.cell(|ui| {
if result.second_status.success {
if let Some(obj) = &result.second_obj {
ui.push_id("right", |ui| {
symbol_list_ui(
ui,
obj,
highlighted_symbol,
selected_symbol,
current_view,
view_state.reverse_fn_order,
search,
&view_state.view_config,
);
});
}
} else {
build_log_ui(ui, &result.second_status);
}
});
});
});
},
);
}
}