Updates & initial MIPS support

This commit is contained in:
Luke Street 2022-09-11 13:52:55 -04:00
parent 7bbdba5566
commit b55c919f4d
13 changed files with 874 additions and 239 deletions

231
Cargo.lock generated
View File

@ -43,6 +43,15 @@ dependencies = [
"version_check", "version_check",
] ]
[[package]]
name = "aho-corasick"
version = "0.7.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "ansi_term" name = "ansi_term"
version = "0.12.1" version = "0.12.1"
@ -134,6 +143,17 @@ version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73b5e5f48b927f04e952dedc932f31995a65a0bf65ec971c74436e51bf6e970d" checksum = "73b5e5f48b927f04e952dedc932f31995a65a0bf65ec971c74436e51bf6e970d"
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi",
"libc",
"winapi",
]
[[package]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.1.0" version = "1.1.0"
@ -146,6 +166,29 @@ version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
[[package]]
name = "bindgen"
version = "0.60.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "062dddbc1ba4aca46de6338e2bf87771414c335f7b2f2036e8f3e9befebf88e6"
dependencies = [
"bitflags",
"cexpr",
"clang-sys",
"clap",
"env_logger",
"lazy_static",
"lazycell",
"log",
"peeking_take_while",
"proc-macro2",
"quote",
"regex",
"rustc-hash",
"shlex",
"which",
]
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "1.3.2" version = "1.3.2"
@ -225,6 +268,15 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
[[package]]
name = "cexpr"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
dependencies = [
"nom",
]
[[package]] [[package]]
name = "cfg-expr" name = "cfg-expr"
version = "0.10.3" version = "0.10.3"
@ -249,6 +301,41 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "clang-sys"
version = "1.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a050e2153c5be08febd6734e29298e844fdb0fa21aeddd63b4eb7baa106c69b"
dependencies = [
"glob",
"libc",
"libloading",
]
[[package]]
name = "clap"
version = "3.2.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23b71c3ce99b7611011217b366d923f1d0a7e07a92bb2dbf1e84508c673ca3bd"
dependencies = [
"atty",
"bitflags",
"clap_lex",
"indexmap",
"strsim",
"termcolor",
"textwrap",
]
[[package]]
name = "clap_lex"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5"
dependencies = [
"os_str_bytes",
]
[[package]] [[package]]
name = "clipboard-win" name = "clipboard-win"
version = "4.4.2" version = "4.4.2"
@ -433,7 +520,8 @@ checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35"
[[package]] [[package]]
name = "cwdemangle" name = "cwdemangle"
version = "0.1.0" version = "0.1.2"
source = "git+https://github.com/encounter/cwdemangle?rev=ba448f403320f32b808e0dcf3040c6424664acab#ba448f403320f32b808e0dcf3040c6424664acab"
dependencies = [ dependencies = [
"argh", "argh",
] ]
@ -617,6 +705,12 @@ dependencies = [
"web-sys", "web-sys",
] ]
[[package]]
name = "either"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
[[package]] [[package]]
name = "emath" name = "emath"
version = "0.19.0" version = "0.19.0"
@ -627,6 +721,19 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "env_logger"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3"
dependencies = [
"atty",
"humantime",
"log",
"regex",
"termcolor",
]
[[package]] [[package]]
name = "epaint" name = "epaint"
version = "0.19.0" version = "0.19.0"
@ -867,6 +974,12 @@ dependencies = [
"system-deps", "system-deps",
] ]
[[package]]
name = "glob"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
[[package]] [[package]]
name = "glow" name = "glow"
version = "0.11.2" version = "0.11.2"
@ -973,6 +1086,12 @@ dependencies = [
"system-deps", "system-deps",
] ]
[[package]]
name = "hashbrown"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]] [[package]]
name = "heck" name = "heck"
version = "0.3.3" version = "0.3.3"
@ -988,6 +1107,21 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
[[package]]
name = "hermit-abi"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"libc",
]
[[package]]
name = "humantime"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]] [[package]]
name = "ident_case" name = "ident_case"
version = "1.0.1" version = "1.0.1"
@ -1005,6 +1139,16 @@ dependencies = [
"unicode-normalization", "unicode-normalization",
] ]
[[package]]
name = "indexmap"
version = "1.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e"
dependencies = [
"autocfg",
"hashbrown",
]
[[package]] [[package]]
name = "inotify" name = "inotify"
version = "0.9.6" version = "0.9.6"
@ -1098,6 +1242,12 @@ version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "lazycell"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.132" version = "0.2.132"
@ -1427,6 +1577,7 @@ dependencies = [
"notify", "notify",
"object", "object",
"ppc750cl", "ppc750cl",
"rabbitizer",
"rfd", "rfd",
"serde", "serde",
"thiserror", "thiserror",
@ -1450,6 +1601,12 @@ version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f7254b99e31cad77da24b08ebf628882739a608578bb1bcdfc1f9c21260d7c0" checksum = "2f7254b99e31cad77da24b08ebf628882739a608578bb1bcdfc1f9c21260d7c0"
[[package]]
name = "os_str_bytes"
version = "6.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff"
[[package]] [[package]]
name = "osmesa-sys" name = "osmesa-sys"
version = "0.1.2" version = "0.1.2"
@ -1503,6 +1660,12 @@ dependencies = [
"windows-sys", "windows-sys",
] ]
[[package]]
name = "peeking_take_while"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
[[package]] [[package]]
name = "percent-encoding" name = "percent-encoding"
version = "2.1.0" version = "2.1.0"
@ -1571,6 +1734,17 @@ dependencies = [
"proc-macro2", "proc-macro2",
] ]
[[package]]
name = "rabbitizer"
version = "0.1.0"
source = "git+https://github.com/encounter/rabbitizer-rs?rev=10c279b2ef251c62885b1dcdcfe740b0db8e9956#10c279b2ef251c62885b1dcdcfe740b0db8e9956"
dependencies = [
"bindgen",
"cc",
"cty",
"glob",
]
[[package]] [[package]]
name = "raw-window-handle" name = "raw-window-handle"
version = "0.4.3" version = "0.4.3"
@ -1609,6 +1783,23 @@ dependencies = [
"thiserror", "thiserror",
] ]
[[package]]
name = "regex"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244"
[[package]] [[package]]
name = "rfd" name = "rfd"
version = "0.10.0" version = "0.10.0"
@ -1644,6 +1835,12 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "rustc-hash"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]] [[package]]
name = "safe_arch" name = "safe_arch"
version = "0.5.2" version = "0.5.2"
@ -1746,6 +1943,12 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "shlex"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3"
[[package]] [[package]]
name = "slotmap" name = "slotmap"
version = "1.0.6" version = "1.0.6"
@ -1826,6 +2029,21 @@ dependencies = [
"version-compare", "version-compare",
] ]
[[package]]
name = "termcolor"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
dependencies = [
"winapi-util",
]
[[package]]
name = "textwrap"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.33" version = "1.0.33"
@ -2232,6 +2450,17 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "which"
version = "4.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c831fbbee9e129a8cf93e7747a82da9d95ba8e16621cae60ec2cdc849bacb7b"
dependencies = [
"either",
"libc",
"once_cell",
]
[[package]] [[package]]
name = "widestring" name = "widestring"
version = "0.5.1" version = "0.5.1"

View File

@ -20,11 +20,12 @@ thiserror = "1.0.33"
flagset = "0.4.3" flagset = "0.4.3"
object = "0.29.0" object = "0.29.0"
notify = "5.0.0" notify = "5.0.0"
cwdemangle = "0.1.1" cwdemangle = { git = "https://github.com/encounter/cwdemangle", rev = "ba448f403320f32b808e0dcf3040c6424664acab" }
log = "0.4.17" log = "0.4.17"
rfd = { version = "0.10.0" } # , default-features = false, features = ['xdg-portal'] rfd = { version = "0.10.0" } # , default-features = false, features = ['xdg-portal']
egui_extras = "0.19.0" egui_extras = "0.19.0"
ppc750cl = { git = "https://github.com/terorie/ppc750cl" } ppc750cl = { git = "https://github.com/terorie/ppc750cl" }
rabbitizer = { git = "https://github.com/encounter/rabbitizer-rs", rev = "10c279b2ef251c62885b1dcdcfe740b0db8e9956" }
# native: # native:
[target.'cfg(not(target_arch = "wasm32"))'.dependencies] [target.'cfg(not(target_arch = "wasm32"))'.dependencies]

View File

@ -10,11 +10,12 @@ use std::{
}; };
use eframe::Frame; use eframe::Frame;
use egui::Widget;
use notify::{RecursiveMode, Watcher}; use notify::{RecursiveMode, Watcher};
use crate::{ use crate::{
jobs::{ jobs::{
build::{queue_build, BuildResult}, build::{queue_build, BuildResult, BuildStatus},
Job, JobResult, JobState, Job, JobResult, JobState,
}, },
views::{ views::{
@ -30,6 +31,13 @@ pub enum View {
FunctionDiff, FunctionDiff,
} }
#[derive(Default, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
pub enum DiffKind {
#[default]
SplitObj,
WholeBinary,
}
#[derive(Default, serde::Deserialize, serde::Serialize)] #[derive(Default, serde::Deserialize, serde::Serialize)]
#[serde(default)] #[serde(default)]
pub struct ViewState { pub struct ViewState {
@ -43,17 +51,24 @@ pub struct ViewState {
pub selected_symbol: Option<String>, pub selected_symbol: Option<String>,
#[serde(skip)] #[serde(skip)]
pub current_view: View, pub current_view: View,
#[serde(skip)]
pub show_config: bool,
// Config // Config
pub diff_kind: DiffKind,
pub reverse_fn_order: bool, pub reverse_fn_order: bool,
} }
#[derive(Default, Clone, serde::Deserialize, serde::Serialize)] #[derive(Default, Clone, serde::Deserialize, serde::Serialize)]
#[serde(default)] #[serde(default)]
pub struct AppConfig { pub struct AppConfig {
// Split obj
pub project_dir: Option<PathBuf>, pub project_dir: Option<PathBuf>,
pub build_asm_dir: Option<PathBuf>, pub build_asm_dir: Option<PathBuf>,
pub build_src_dir: Option<PathBuf>, pub build_src_dir: Option<PathBuf>,
pub build_obj: Option<String>, pub build_obj: Option<String>,
// Whole binary
pub left_obj: Option<PathBuf>,
pub right_obj: Option<PathBuf>,
#[serde(skip)] #[serde(skip)]
pub project_dir_change: bool, pub project_dir_change: bool,
} }
@ -118,6 +133,9 @@ impl eframe::App for App {
if ui.button("Quit").clicked() { if ui.button("Quit").clicked() {
frame.close(); frame.close();
} }
if ui.button("Show config").clicked() {
view_state.show_config = !view_state.show_config;
}
}); });
}); });
}); });
@ -148,6 +166,34 @@ impl eframe::App for App {
}); });
} }
egui::Window::new("Config").open(&mut view_state.show_config).show(ctx, |ui| {
ui.label("Diff type:");
if egui::RadioButton::new(
view_state.diff_kind == DiffKind::SplitObj,
"Split object diff",
)
.ui(ui)
.on_hover_text("Compare individual object files")
.clicked()
{
view_state.diff_kind = DiffKind::SplitObj;
}
if egui::RadioButton::new(
view_state.diff_kind == DiffKind::WholeBinary,
"Whole binary diff",
)
.ui(ui)
.on_hover_text("Compare two full binaries")
.clicked()
{
view_state.diff_kind = DiffKind::WholeBinary;
}
ui.separator();
});
if view_state.jobs.iter().any(|job| { if view_state.jobs.iter().any(|job| {
if let Some(handle) = &job.handle { if let Some(handle) = &job.handle {
return !handle.is_finished(); return !handle.is_finished();
@ -156,7 +202,6 @@ impl eframe::App for App {
}) { }) {
ctx.request_repaint(); ctx.request_repaint();
} else { } else {
ctx.request_repaint();
ctx.request_repaint_after(Duration::from_millis(100)); ctx.request_repaint_after(Duration::from_millis(100));
} }
} }
@ -187,6 +232,20 @@ impl eframe::App for App {
JobResult::Build(state) => { JobResult::Build(state) => {
self.view_state.build = Some(state); self.view_state.build = Some(state);
} }
JobResult::BinDiff(state) => {
self.view_state.build = Some(Box::new(BuildResult {
first_status: BuildStatus {
success: true,
log: "".to_string(),
},
second_status: BuildStatus {
success: true,
log: "".to_string(),
},
first_obj: Some(state.first_obj),
second_obj: Some(state.second_obj),
}));
}
} }
} }
Err(e) => { Err(e) => {

View File

@ -1,87 +1,18 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use anyhow::Result; use anyhow::Result;
use ppc750cl::{disasm_iter, Argument};
use crate::{ use crate::{
editops::{editops_find, LevEditType}, editops::{editops_find, LevEditType},
obj::{ obj::{
ObjInfo, ObjIns, ObjInsArg, ObjInsArgDiff, ObjInsBranchFrom, ObjInsBranchTo, ObjInsDiff, mips, ppc, ObjArchitecture, ObjInfo, ObjInsArg, ObjInsArgDiff, ObjInsBranchFrom,
ObjInsDiffKind, ObjReloc, ObjRelocKind, ObjSection, ObjSectionKind, ObjSymbol, ObjInsBranchTo, ObjInsDiff, ObjInsDiffKind, ObjReloc, ObjSection, ObjSectionKind,
ObjSymbolFlags, ObjSymbol, ObjSymbolFlags,
}, },
}; };
// Relative relocation, can be Simm or BranchDest
fn is_relative_arg(arg: &ObjInsArg) -> bool {
matches!(arg, ObjInsArg::Arg(arg) if matches!(arg, Argument::Simm(_) | Argument::BranchDest(_)))
}
// Relative or absolute relocation, can be Uimm, Simm or Offset
fn is_rel_abs_arg(arg: &ObjInsArg) -> bool {
matches!(arg, ObjInsArg::Arg(arg) if matches!(arg, Argument::Uimm(_) | Argument::Simm(_) | Argument::Offset(_)))
}
fn is_offset_arg(arg: &ObjInsArg) -> bool { matches!(arg, ObjInsArg::Arg(Argument::Offset(_))) }
fn process_code(data: &[u8], address: u64, relocs: &[ObjReloc]) -> Result<(Vec<u8>, Vec<ObjIns>)> {
let ins_count = data.len() / 4;
let mut ops = Vec::<u8>::with_capacity(ins_count);
let mut insts = Vec::<ObjIns>::with_capacity(ins_count);
for mut ins in disasm_iter(data, address as u32) {
let reloc = relocs.iter().find(|r| (r.address as u32 & !3) == ins.addr);
if let Some(reloc) = reloc {
// Zero out relocations
ins.code = match reloc.kind {
ObjRelocKind::PpcEmbSda21 => ins.code & !0x1FFFFF,
ObjRelocKind::PpcRel24 => ins.code & !0x3FFFFFC,
ObjRelocKind::PpcRel14 => ins.code & !0xFFFC,
ObjRelocKind::PpcAddr16Hi
| ObjRelocKind::PpcAddr16Ha
| ObjRelocKind::PpcAddr16Lo => ins.code & !0xFFFF,
_ => ins.code,
};
}
let simplified = ins.simplified();
let mut args: Vec<ObjInsArg> =
simplified.args.iter().map(|a| ObjInsArg::Arg(a.clone())).collect();
if let Some(reloc) = reloc {
match reloc.kind {
ObjRelocKind::PpcEmbSda21 => {
args = vec![args[0].clone(), ObjInsArg::Reloc];
}
ObjRelocKind::PpcRel24 | ObjRelocKind::PpcRel14 => {
let arg = args
.iter_mut()
.rfind(|a| is_relative_arg(a))
.ok_or_else(|| anyhow::Error::msg("Failed to locate rel arg for reloc"))?;
*arg = ObjInsArg::Reloc;
}
ObjRelocKind::PpcAddr16Hi
| ObjRelocKind::PpcAddr16Ha
| ObjRelocKind::PpcAddr16Lo => {
let arg = args.iter_mut().rfind(|a| is_rel_abs_arg(a)).ok_or_else(|| {
anyhow::Error::msg("Failed to locate rel/abs arg for reloc")
})?;
*arg =
if is_offset_arg(arg) { ObjInsArg::RelocOffset } else { ObjInsArg::Reloc };
}
_ => {}
}
}
ops.push(simplified.ins.op as u8);
let suffix = simplified.ins.suffix();
insts.push(ObjIns {
ins: simplified.ins,
mnemonic: format!("{}{}", simplified.mnemonic, suffix),
args,
reloc: reloc.cloned(),
});
}
Ok((ops, insts))
}
pub fn diff_code( pub fn diff_code(
arch: ObjArchitecture,
left_data: &[u8], left_data: &[u8],
right_data: &[u8], right_data: &[u8],
left_symbol: &mut ObjSymbol, left_symbol: &mut ObjSymbol,
@ -89,12 +20,30 @@ pub fn diff_code(
left_relocs: &[ObjReloc], left_relocs: &[ObjReloc],
right_relocs: &[ObjReloc], right_relocs: &[ObjReloc],
) -> Result<()> { ) -> Result<()> {
let left_code = let left_code = &left_data[left_symbol.section_address as usize
&left_data[left_symbol.address as usize..(left_symbol.address + left_symbol.size) as usize]; ..(left_symbol.section_address + left_symbol.size) as usize];
let (left_ops, left_insts) = process_code(left_code, left_symbol.address, left_relocs)?; let right_code = &right_data[right_symbol.section_address as usize
let right_code = &right_data ..(right_symbol.section_address + right_symbol.size) as usize];
[right_symbol.address as usize..(right_symbol.address + right_symbol.size) as usize]; let ((left_ops, left_insts), (right_ops, right_insts)) = match arch {
let (right_ops, right_insts) = process_code(right_code, right_symbol.address, right_relocs)?; ObjArchitecture::PowerPc => (
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(
left_code,
left_symbol.address,
left_symbol.address + left_symbol.size,
left_relocs,
)?,
mips::process_code(
right_code,
right_symbol.address,
left_symbol.address + left_symbol.size,
right_relocs,
)?,
),
};
let mut left_diff = Vec::<ObjInsDiff>::new(); let mut left_diff = Vec::<ObjInsDiff>::new();
let mut right_diff = Vec::<ObjInsDiff>::new(); let mut right_diff = Vec::<ObjInsDiff>::new();
@ -111,7 +60,7 @@ pub fn diff_code(
let left_addr = op.first_start as u32 * 4; let left_addr = op.first_start as u32 * 4;
let right_addr = op.second_start as u32 * 4; let right_addr = op.second_start as u32 * 4;
while let (Some(left), Some(right)) = (cur_left, cur_right) { while let (Some(left), Some(right)) = (cur_left, cur_right) {
if (left.ins.addr - left_symbol.address as u32) < left_addr { if (left.address - left_symbol.address as u32) < left_addr {
left_diff.push(ObjInsDiff { ins: Some(left.clone()), ..ObjInsDiff::default() }); left_diff.push(ObjInsDiff { ins: Some(left.clone()), ..ObjInsDiff::default() });
right_diff right_diff
.push(ObjInsDiff { ins: Some(right.clone()), ..ObjInsDiff::default() }); .push(ObjInsDiff { ins: Some(right.clone()), ..ObjInsDiff::default() });
@ -122,10 +71,10 @@ pub fn diff_code(
cur_right = right_iter.next(); cur_right = right_iter.next();
} }
if let (Some(left), Some(right)) = (cur_left, cur_right) { if let (Some(left), Some(right)) = (cur_left, cur_right) {
if (left.ins.addr - left_symbol.address as u32) != left_addr { if (left.address - left_symbol.address as u32) != left_addr {
return Err(anyhow::Error::msg("Instruction address mismatch (left)")); return Err(anyhow::Error::msg("Instruction address mismatch (left)"));
} }
if (right.ins.addr - right_symbol.address as u32) != right_addr { if (right.address - right_symbol.address as u32) != right_addr {
return Err(anyhow::Error::msg("Instruction address mismatch (right)")); return Err(anyhow::Error::msg("Instruction address mismatch (right)"));
} }
match op.op_type { match op.op_type {
@ -178,7 +127,11 @@ pub fn diff_code(
} }
let total = left_insts.len(); let total = left_insts.len();
let percent = ((total - diff_state.diff_count) as f32 / total as f32) * 100.0; let percent = if diff_state.diff_count >= total {
0.0
} else {
((total - diff_state.diff_count) as f32 / total as f32) * 100.0
};
left_symbol.match_percent = percent; left_symbol.match_percent = percent;
right_symbol.match_percent = percent; right_symbol.match_percent = percent;
@ -194,17 +147,22 @@ fn resolve_branches(vec: &mut [ObjInsDiff]) {
let mut addr_map = BTreeMap::<u32, usize>::new(); let mut addr_map = BTreeMap::<u32, usize>::new();
for (i, ins_diff) in vec.iter().enumerate() { for (i, ins_diff) in vec.iter().enumerate() {
if let Some(ins) = &ins_diff.ins { if let Some(ins) = &ins_diff.ins {
addr_map.insert(ins.ins.addr, i); addr_map.insert(ins.address, i);
} }
} }
// Generate branches // Generate branches
let mut branches = BTreeMap::<usize, ObjInsBranchFrom>::new(); let mut branches = BTreeMap::<usize, ObjInsBranchFrom>::new();
for (i, ins_diff) in vec.iter_mut().enumerate() { for (i, ins_diff) in vec.iter_mut().enumerate() {
if let Some(ins) = &ins_diff.ins { if let Some(ins) = &ins_diff.ins {
if ins.ins.is_blr() || ins.reloc.is_some() { // if ins.ins.is_blr() || ins.reloc.is_some() {
continue; // continue;
} // }
if let Some(ins_idx) = ins.ins.branch_dest().and_then(|dest| addr_map.get(&dest)) { if let Some(ins_idx) = ins
.args
.iter()
.find_map(|a| if let ObjInsArg::BranchOffset(offs) = a { Some(offs) } else { None })
.and_then(|offs| addr_map.get(&((ins.address as i32 + offs) as u32)))
{
if let Some(branch) = branches.get_mut(ins_idx) { if let Some(branch) = branches.get_mut(ins_idx) {
ins_diff.branch_to = ins_diff.branch_to =
Some(ObjInsBranchTo { ins_idx: *ins_idx, branch_idx: branch.branch_idx }); Some(ObjInsBranchTo { ins_idx: *ins_idx, branch_idx: branch.branch_idx });
@ -253,15 +211,8 @@ fn arg_eq(
right_diff: &ObjInsDiff, right_diff: &ObjInsDiff,
) -> bool { ) -> bool {
return match left { return match left {
ObjInsArg::Arg(l) => match right { ObjInsArg::PpcArg(l) => match right {
ObjInsArg::Arg(r) => match r { ObjInsArg::PpcArg(r) => format!("{}", l) == format!("{}", r),
Argument::BranchDest(_) => {
// Compare dest instruction idx after diffing
left_diff.branch_to.as_ref().map(|b| b.ins_idx)
== right_diff.branch_to.as_ref().map(|b| b.ins_idx)
}
_ => format!("{}", l) == format!("{}", r),
},
_ => false, _ => false,
}, },
ObjInsArg::Reloc => { ObjInsArg::Reloc => {
@ -271,13 +222,21 @@ fn arg_eq(
right_diff.ins.as_ref().and_then(|i| i.reloc.as_ref()), right_diff.ins.as_ref().and_then(|i| i.reloc.as_ref()),
) )
} }
ObjInsArg::RelocOffset => { ObjInsArg::RelocWithBase => {
matches!(right, ObjInsArg::RelocOffset) matches!(right, ObjInsArg::RelocWithBase)
&& reloc_eq( && reloc_eq(
left_diff.ins.as_ref().and_then(|i| i.reloc.as_ref()), left_diff.ins.as_ref().and_then(|i| i.reloc.as_ref()),
right_diff.ins.as_ref().and_then(|i| i.reloc.as_ref()), right_diff.ins.as_ref().and_then(|i| i.reloc.as_ref()),
) )
} }
ObjInsArg::MipsArg(ls) => {
matches!(right, ObjInsArg::MipsArg(rs) if ls == rs)
}
ObjInsArg::BranchOffset(_) => {
// Compare dest instruction idx after diffing
left_diff.branch_to.as_ref().map(|b| b.ins_idx)
== right_diff.branch_to.as_ref().map(|b| b.ins_idx)
}
}; };
} }
@ -303,7 +262,7 @@ fn compare_ins(
) -> Result<InsDiffResult> { ) -> Result<InsDiffResult> {
let mut result = InsDiffResult::default(); let mut result = InsDiffResult::default();
if let (Some(left_ins), Some(right_ins)) = (&left.ins, &right.ins) { if let (Some(left_ins), Some(right_ins)) = (&left.ins, &right.ins) {
if left_ins.args.len() != right_ins.args.len() || left_ins.ins.op != right_ins.ins.op { if left_ins.args.len() != right_ins.args.len() || left_ins.op != right_ins.op {
// Totally different op // Totally different op
result.kind = ObjInsDiffKind::Replace; result.kind = ObjInsDiffKind::Replace;
state.diff_count += 1; state.diff_count += 1;
@ -324,8 +283,10 @@ fn compare_ins(
state.diff_count += 1; state.diff_count += 1;
} }
let a_str = match a { let a_str = match a {
ObjInsArg::Arg(arg) => format!("{}", arg), ObjInsArg::PpcArg(arg) => format!("{}", arg),
ObjInsArg::Reloc | ObjInsArg::RelocOffset => String::new(), ObjInsArg::Reloc | ObjInsArg::RelocWithBase => String::new(),
ObjInsArg::MipsArg(str) => str.clone(),
ObjInsArg::BranchOffset(arg) => format!("{}", arg),
}; };
let a_diff = if let Some(idx) = state.left_args_idx.get(&a_str) { let a_diff = if let Some(idx) = state.left_args_idx.get(&a_str) {
ObjInsArgDiff { idx: *idx } ObjInsArgDiff { idx: *idx }
@ -336,8 +297,10 @@ fn compare_ins(
ObjInsArgDiff { idx } ObjInsArgDiff { idx }
}; };
let b_str = match b { let b_str = match b {
ObjInsArg::Arg(arg) => format!("{}", arg), ObjInsArg::PpcArg(arg) => format!("{}", arg),
ObjInsArg::Reloc | ObjInsArg::RelocOffset => String::new(), ObjInsArg::Reloc | ObjInsArg::RelocWithBase => String::new(),
ObjInsArg::MipsArg(str) => str.clone(),
ObjInsArg::BranchOffset(arg) => format!("{}", arg),
}; };
let b_diff = if let Some(idx) = state.right_args_idx.get(&b_str) { let b_diff = if let Some(idx) = state.right_args_idx.get(&b_str) {
ObjInsArgDiff { idx: *idx } ObjInsArgDiff { idx: *idx }
@ -380,6 +343,7 @@ pub fn diff_objs(left: &mut ObjInfo, right: &mut ObjInfo) -> Result<()> {
right_symbol.diff_symbol = Some(left_symbol.name.clone()); right_symbol.diff_symbol = Some(left_symbol.name.clone());
if left_section.kind == ObjSectionKind::Code { if left_section.kind == ObjSectionKind::Code {
diff_code( diff_code(
left.architecture,
&left_section.data, &left_section.data,
&right_section.data, &right_section.data,
left_symbol, left_symbol,

View File

@ -4,13 +4,13 @@ use anyhow::{Context, Result};
use cwdemangle::demangle; use cwdemangle::demangle;
use flagset::Flags; use flagset::Flags;
use object::{ use object::{
Object, ObjectSection, ObjectSymbol, RelocationKind, RelocationTarget, SectionKind, SymbolKind, Architecture, File, Object, ObjectSection, ObjectSymbol, RelocationKind, RelocationTarget,
SymbolSection, SectionKind, Symbol, SymbolKind, SymbolSection,
}; };
use crate::obj::{ use crate::obj::{
ObjInfo, ObjReloc, ObjRelocKind, ObjSection, ObjSectionKind, ObjSymbol, ObjSymbolFlagSet, ObjArchitecture, ObjInfo, ObjReloc, ObjRelocKind, ObjSection, ObjSectionKind, ObjSymbol,
ObjSymbolFlags, ObjSymbolFlagSet, ObjSymbolFlags,
}; };
fn to_obj_section_kind(kind: SectionKind) -> ObjSectionKind { fn to_obj_section_kind(kind: SectionKind) -> ObjSectionKind {
@ -22,7 +22,7 @@ fn to_obj_section_kind(kind: SectionKind) -> ObjSectionKind {
} }
} }
fn to_obj_symbol(symbol: &object::Symbol<'_, '_>) -> Result<ObjSymbol> { fn to_obj_symbol(obj_file: &File<'_>, symbol: &Symbol<'_, '_>) -> Result<ObjSymbol> {
let mut name = symbol.name().context("Failed to process symbol name")?; let mut name = symbol.name().context("Failed to process symbol name")?;
if name.is_empty() { if name.is_empty() {
println!("Found empty sym: {:?}", symbol); println!("Found empty sym: {:?}", symbol);
@ -41,10 +41,18 @@ fn to_obj_symbol(symbol: &object::Symbol<'_, '_>) -> Result<ObjSymbol> {
if symbol.is_weak() { if symbol.is_weak() {
flags = ObjSymbolFlagSet(flags.0 | ObjSymbolFlags::Weak); flags = ObjSymbolFlagSet(flags.0 | ObjSymbolFlags::Weak);
} }
let section_address = if let Some(section) =
symbol.section_index().and_then(|idx| obj_file.section_by_index(idx).ok())
{
symbol.address() - section.address()
} else {
symbol.address()
};
Ok(ObjSymbol { Ok(ObjSymbol {
name: name.to_string(), name: name.to_string(),
demangled_name: demangle(name), demangled_name: demangle(name),
address: symbol.address(), address: symbol.address(),
section_address,
size: symbol.size(), size: symbol.size(),
size_known: symbol.size() != 0, size_known: symbol.size() != 0,
flags, flags,
@ -61,7 +69,11 @@ const R_PPC_REL24: u32 = 10;
const R_PPC_REL14: u32 = 11; const R_PPC_REL14: u32 = 11;
const R_PPC_EMB_SDA21: u32 = 109; const R_PPC_EMB_SDA21: u32 = 109;
fn filter_sections(obj_file: &object::File<'_>) -> Result<Vec<ObjSection>> { const R_MIPS_26: u32 = 4;
const R_MIPS_HI16: u32 = 5;
const R_MIPS_LO16: u32 = 6;
fn filter_sections(obj_file: &File<'_>) -> Result<Vec<ObjSection>> {
let mut result = Vec::<ObjSection>::new(); let mut result = Vec::<ObjSection>::new();
for section in obj_file.sections() { for section in obj_file.sections() {
if section.size() == 0 { if section.size() == 0 {
@ -91,7 +103,7 @@ fn filter_sections(obj_file: &object::File<'_>) -> Result<Vec<ObjSection>> {
Ok(result) Ok(result)
} }
fn symbols_by_section(obj_file: &object::File<'_>, section: &ObjSection) -> Result<Vec<ObjSymbol>> { fn symbols_by_section(obj_file: &File<'_>, section: &ObjSection) -> Result<Vec<ObjSymbol>> {
let mut result = Vec::<ObjSymbol>::new(); let mut result = Vec::<ObjSymbol>::new();
for symbol in obj_file.symbols() { for symbol in obj_file.symbols() {
if symbol.kind() == SymbolKind::Section { if symbol.kind() == SymbolKind::Section {
@ -102,11 +114,11 @@ fn symbols_by_section(obj_file: &object::File<'_>, section: &ObjSection) -> Resu
if symbol.is_local() && section.kind == ObjSectionKind::Code { if symbol.is_local() && section.kind == ObjSectionKind::Code {
// TODO strip local syms in diff? // TODO strip local syms in diff?
let name = symbol.name().context("Failed to process symbol name")?; let name = symbol.name().context("Failed to process symbol name")?;
if name.starts_with("lbl_") { if symbol.size() == 0 || name.starts_with("lbl_") {
continue; continue;
} }
} }
result.push(to_obj_symbol(&symbol)?); result.push(to_obj_symbol(obj_file, &symbol)?);
} }
} }
} }
@ -124,38 +136,64 @@ fn symbols_by_section(obj_file: &object::File<'_>, section: &ObjSection) -> Resu
Ok(result) Ok(result)
} }
fn common_symbols(obj_file: &object::File<'_>) -> Result<Vec<ObjSymbol>> { fn common_symbols(obj_file: &File<'_>) -> Result<Vec<ObjSymbol>> {
let mut result = Vec::<ObjSymbol>::new(); let mut result = Vec::<ObjSymbol>::new();
for symbol in obj_file.symbols() { for symbol in obj_file.symbols() {
if symbol.is_common() { if symbol.is_common() {
result.push(to_obj_symbol(&symbol)?); result.push(to_obj_symbol(obj_file, &symbol)?);
} }
} }
Ok(result) Ok(result)
} }
fn locate_section_symbol( fn find_section_symbol(
obj_file: &object::File<'_>, obj_file: &File<'_>,
target: &object::Symbol<'_, '_>, target: &Symbol<'_, '_>,
address: u64, address: u64,
) -> Result<ObjSymbol> { ) -> Result<ObjSymbol> {
let section_index = let section_index =
target.section_index().ok_or_else(|| anyhow::Error::msg("Unknown section index"))?; target.section_index().ok_or_else(|| anyhow::Error::msg("Unknown section index"))?;
let section = obj_file.section_by_index(section_index)?;
let mut closest_symbol: Option<Symbol<'_, '_>> = None;
for symbol in obj_file.symbols() { for symbol in obj_file.symbols() {
if !matches!(symbol.section_index(), Some(idx) if idx == section_index) { if !matches!(symbol.section_index(), Some(idx) if idx == section_index) {
continue; continue;
} }
if symbol.kind() == SymbolKind::Section || symbol.address() != address { if symbol.kind() == SymbolKind::Section || symbol.address() != address {
if symbol.address() < address
&& symbol.size() != 0
&& (closest_symbol.is_none()
|| matches!(&closest_symbol, Some(s) if s.address() <= symbol.address()))
{
closest_symbol = Some(symbol);
}
continue; continue;
} }
return to_obj_symbol(&symbol); return to_obj_symbol(obj_file, &symbol);
} }
Err(anyhow::Error::msg("Failed to locate reloc offset sym")) let (name, offset) = closest_symbol
.and_then(|s| s.name().map(|n| (n, s.address())).ok())
.or_else(|| section.name().map(|n| (n, section.address())).ok())
.unwrap_or(("<unknown>", 0));
let offset_addr = address - offset;
Ok(ObjSymbol {
name: if offset_addr > 0 { format!("{}+{:#X}", name, address) } else { name.to_string() },
demangled_name: None,
address,
section_address: address - section.address(),
size: 0,
size_known: false,
flags: Default::default(),
diff_symbol: None,
instructions: vec![],
match_percent: 0.0,
})
} }
fn relocations_by_section( fn relocations_by_section(
obj_file: &object::File<'_>, arch: ObjArchitecture,
section: &ObjSection, obj_file: &File<'_>,
section: &mut ObjSection,
) -> Result<Vec<ObjReloc>> { ) -> Result<Vec<ObjReloc>> {
let obj_section = obj_file let obj_section = obj_file
.section_by_name(&section.name) .section_by_name(&section.name)
@ -175,19 +213,32 @@ fn relocations_by_section(
}; };
let kind = match reloc.kind() { let kind = match reloc.kind() {
RelocationKind::Absolute => ObjRelocKind::Absolute, RelocationKind::Absolute => ObjRelocKind::Absolute,
RelocationKind::Elf(kind) => match kind { RelocationKind::Elf(kind) => match arch {
R_PPC_ADDR16_LO => ObjRelocKind::PpcAddr16Lo, ObjArchitecture::PowerPc => match kind {
R_PPC_ADDR16_HI => ObjRelocKind::PpcAddr16Hi, R_PPC_ADDR16_LO => ObjRelocKind::PpcAddr16Lo,
R_PPC_ADDR16_HA => ObjRelocKind::PpcAddr16Ha, R_PPC_ADDR16_HI => ObjRelocKind::PpcAddr16Hi,
R_PPC_REL24 => ObjRelocKind::PpcRel24, R_PPC_ADDR16_HA => ObjRelocKind::PpcAddr16Ha,
R_PPC_REL14 => ObjRelocKind::PpcRel14, R_PPC_REL24 => ObjRelocKind::PpcRel24,
R_PPC_EMB_SDA21 => ObjRelocKind::PpcEmbSda21, R_PPC_REL14 => ObjRelocKind::PpcRel14,
_ => { R_PPC_EMB_SDA21 => ObjRelocKind::PpcEmbSda21,
return Err(anyhow::Error::msg(format!( _ => {
"Unhandled ELF relocation type: {}", return Err(anyhow::Error::msg(format!(
kind "Unhandled PPC relocation type: {}",
))) kind
} )))
}
},
ObjArchitecture::Mips => match kind {
R_MIPS_26 => ObjRelocKind::Mips26,
R_MIPS_HI16 => ObjRelocKind::MipsHi16,
R_MIPS_LO16 => ObjRelocKind::MipsLo16,
_ => {
return Err(anyhow::Error::msg(format!(
"Unhandled MIPS relocation type: {}",
kind
)))
}
},
}, },
_ => { _ => {
return Err(anyhow::Error::msg(format!( return Err(anyhow::Error::msg(format!(
@ -198,23 +249,39 @@ fn relocations_by_section(
}; };
let target_section = match symbol.section() { let target_section = match symbol.section() {
SymbolSection::Common => Some(".comm".to_string()), SymbolSection::Common => Some(".comm".to_string()),
SymbolSection::Section(idx) => obj_file SymbolSection::Section(idx) => {
.section_by_index(idx) obj_file.section_by_index(idx).and_then(|s| s.name().map(|s| s.to_string())).ok()
.map(|s| s.name().map(|s| s.to_string()).ok()) }
.ok()
.flatten(),
_ => None, _ => None,
}; };
// println!("Reloc: {:?}", reloc.addend()); // println!("Reloc: {:?}, symbol: {:?}", reloc, symbol);
let target = match symbol.kind() { let target = match symbol.kind() {
SymbolKind::Text | SymbolKind::Data | SymbolKind::Unknown => to_obj_symbol(&symbol), SymbolKind::Text | SymbolKind::Data | SymbolKind::Unknown => {
to_obj_symbol(obj_file, &symbol)
}
SymbolKind::Section => { SymbolKind::Section => {
let addend = reloc.addend(); let addend = if reloc.has_implicit_addend() {
if addend < 0 { let addend = u32::from_be_bytes(
Err(anyhow::Error::msg(format!("Negative addend in section reloc: {}", addend))) section.data[address as usize..address as usize + 4].try_into()?,
);
match kind {
ObjRelocKind::MipsHi16 | ObjRelocKind::MipsLo16 => {
(addend & 0x0000FFFF) * 4
}
ObjRelocKind::Mips26 => (addend & 0x03FFFFFF) * 4,
_ => todo!(),
}
} else { } else {
locate_section_symbol(obj_file, &symbol, addend as u64) let addend = reloc.addend();
} if addend < 0 {
return Err(anyhow::Error::msg(format!(
"Negative addend in section reloc: {}",
addend
)));
}
addend as u32
};
find_section_symbol(obj_file, &symbol, addend as u64)
} }
_ => Err(anyhow::Error::msg(format!( _ => Err(anyhow::Error::msg(format!(
"Unhandled relocation symbol type {:?}", "Unhandled relocation symbol type {:?}",
@ -228,15 +295,26 @@ fn relocations_by_section(
pub fn read(obj_path: &Path) -> Result<ObjInfo> { pub fn read(obj_path: &Path) -> Result<ObjInfo> {
let bin_data = fs::read(obj_path)?; let bin_data = fs::read(obj_path)?;
let obj_file = object::File::parse(&*bin_data)?; let obj_file = File::parse(&*bin_data)?;
let architecture = match obj_file.architecture() {
Architecture::PowerPc => ObjArchitecture::PowerPc,
Architecture::Mips => ObjArchitecture::Mips,
_ => {
return Err(anyhow::Error::msg(format!(
"Unsupported architecture: {:?}",
obj_file.architecture()
)))
}
};
let mut result = ObjInfo { let mut result = ObjInfo {
architecture,
path: obj_path.to_owned(), path: obj_path.to_owned(),
sections: filter_sections(&obj_file)?, sections: filter_sections(&obj_file)?,
common: common_symbols(&obj_file)?, common: common_symbols(&obj_file)?,
}; };
for section in &mut result.sections { for section in &mut result.sections {
section.symbols = symbols_by_section(&obj_file, section)?; section.symbols = symbols_by_section(&obj_file, section)?;
section.relocations = relocations_by_section(&obj_file, section)?; section.relocations = relocations_by_section(architecture, &obj_file, section)?;
} }
Ok(result) Ok(result)
} }

45
src/jobs/bindiff.rs Normal file
View File

@ -0,0 +1,45 @@
use std::sync::{mpsc::Receiver, Arc, RwLock};
use anyhow::{Error, Result};
use crate::{
app::AppConfig,
diff::diff_objs,
elf,
jobs::{queue_job, update_status, Job, JobResult, JobState, Status},
obj::ObjInfo,
};
pub struct BinDiffResult {
pub first_obj: ObjInfo,
pub second_obj: ObjInfo,
}
fn run_build(
status: &Status,
cancel: Receiver<()>,
config: Arc<RwLock<AppConfig>>,
) -> Result<Box<BinDiffResult>> {
let config = config.read().map_err(|_| Error::msg("Failed to lock app config"))?.clone();
let left_path = config.left_obj.as_ref().ok_or_else(|| Error::msg("Missing left obj path"))?;
let right_path =
config.right_obj.as_ref().ok_or_else(|| Error::msg("Missing right obj path"))?;
update_status(status, "Loading left obj".to_string(), 0, 3, &cancel)?;
let mut left_obj = elf::read(left_path)?;
update_status(status, "Loading right obj".to_string(), 1, 3, &cancel)?;
let mut right_obj = elf::read(right_path)?;
update_status(status, "Performing diff".to_string(), 2, 3, &cancel)?;
diff_objs(&mut left_obj, &mut right_obj)?;
update_status(status, "Complete".to_string(), 3, 3, &cancel)?;
Ok(Box::new(BinDiffResult { first_obj: left_obj, second_obj: right_obj }))
}
pub fn queue_bindiff(config: Arc<RwLock<AppConfig>>) -> JobState {
queue_job(Job::BinDiff, move |status, cancel| {
run_build(status, cancel, config).map(JobResult::BinDiff)
})
}

View File

@ -9,13 +9,15 @@ use std::{
use anyhow::Result; use anyhow::Result;
use crate::jobs::build::BuildResult; use crate::jobs::{bindiff::BinDiffResult, build::BuildResult};
pub mod bindiff;
pub mod build; pub mod build;
#[derive(Debug, Eq, PartialEq, Copy, Clone)] #[derive(Debug, Eq, PartialEq, Copy, Clone)]
pub enum Job { pub enum Job {
Build, Build,
BinDiff,
} }
pub static JOB_ID: AtomicUsize = AtomicUsize::new(0); pub static JOB_ID: AtomicUsize = AtomicUsize::new(0);
pub struct JobState { pub struct JobState {
@ -37,6 +39,7 @@ pub struct JobStatus {
pub enum JobResult { pub enum JobResult {
None, None,
Build(Box<BuildResult>), Build(Box<BuildResult>),
BinDiff(Box<BinDiffResult>),
} }
fn should_cancel(rx: &Receiver<()>) -> bool { fn should_cancel(rx: &Receiver<()>) -> bool {

View File

@ -1,3 +1,6 @@
pub mod mips;
pub mod ppc;
use std::path::PathBuf; use std::path::PathBuf;
use flagset::{flags, FlagSet}; use flagset::{flags, FlagSet};
@ -31,9 +34,11 @@ pub struct ObjSection {
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum ObjInsArg { pub enum ObjInsArg {
Arg(ppc750cl::Argument), PpcArg(ppc750cl::Argument),
MipsArg(String),
Reloc, Reloc,
RelocOffset, RelocWithBase,
BranchOffset(i32),
} }
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct ObjInsArgDiff { pub struct ObjInsArgDiff {
@ -66,10 +71,13 @@ pub enum ObjInsDiffKind {
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ObjIns { pub struct ObjIns {
pub ins: ppc750cl::Ins, pub address: u32,
pub code: u32,
pub op: u8,
pub mnemonic: String, pub mnemonic: String,
pub args: Vec<ObjInsArg>, pub args: Vec<ObjInsArg>,
pub reloc: Option<ObjReloc>, pub reloc: Option<ObjReloc>,
pub branch_dest: Option<u32>,
} }
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct ObjInsDiff { pub struct ObjInsDiff {
@ -88,6 +96,7 @@ pub struct ObjSymbol {
pub name: String, pub name: String,
pub demangled_name: Option<String>, pub demangled_name: Option<String>,
pub address: u64, pub address: u64,
pub section_address: u64,
pub size: u64, pub size: u64,
pub size_known: bool, pub size_known: bool,
pub flags: ObjSymbolFlagSet, pub flags: ObjSymbolFlagSet,
@ -97,8 +106,14 @@ pub struct ObjSymbol {
pub instructions: Vec<ObjInsDiff>, pub instructions: Vec<ObjInsDiff>,
pub match_percent: f32, pub match_percent: f32,
} }
#[derive(Debug, Copy, Clone)]
pub enum ObjArchitecture {
PowerPc,
Mips,
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ObjInfo { pub struct ObjInfo {
pub architecture: ObjArchitecture,
pub path: PathBuf, pub path: PathBuf,
pub sections: Vec<ObjSection>, pub sections: Vec<ObjSection>,
pub common: Vec<ObjSymbol>, pub common: Vec<ObjSymbol>,
@ -116,6 +131,9 @@ pub enum ObjRelocKind {
// PpcAddr14, // PpcAddr14,
PpcRel14, PpcRel14,
PpcEmbSda21, PpcEmbSda21,
Mips26,
MipsHi16,
MipsLo16,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ObjReloc { pub struct ObjReloc {

74
src/obj/mips.rs Normal file
View File

@ -0,0 +1,74 @@
use anyhow::Result;
use rabbitizer::{config_set_register_fpr_abi_names, Abi, Instruction, SimpleOperandType};
use crate::obj::{ObjIns, ObjInsArg, ObjReloc};
pub fn process_code(
data: &[u8],
start_address: u64,
end_address: u64,
relocs: &[ObjReloc],
) -> Result<(Vec<u8>, Vec<ObjIns>)> {
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);
let mut insts = Vec::<ObjIns>::with_capacity(ins_count);
let mut cur_addr = start_address as u32;
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 mut instruction = Instruction::new(code, cur_addr);
let op = instruction.instr_id() as u8;
ops.push(op);
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
.simple_operands()
.iter()
.map(|op| match op.kind {
SimpleOperandType::Imm | SimpleOperandType::Label => {
if is_branch {
ObjInsArg::BranchOffset(branch_offset)
} else if let Some(reloc) = reloc {
if matches!(&reloc.target_section, Some(s) if s == ".text")
&& reloc.target.address > start_address
&& reloc.target.address < end_address
{
// Inter-function reloc, convert to branch offset
ObjInsArg::BranchOffset(reloc.target.address as i32 - cur_addr as i32)
} else {
ObjInsArg::Reloc
}
} else {
ObjInsArg::MipsArg(op.disassembled.clone())
}
}
SimpleOperandType::ImmBase => {
if reloc.is_some() {
ObjInsArg::RelocWithBase
} else {
ObjInsArg::MipsArg(op.disassembled.clone())
}
}
_ => ObjInsArg::MipsArg(op.disassembled.clone()),
})
.collect();
insts.push(ObjIns {
address: cur_addr,
code,
op,
mnemonic,
args,
reloc: reloc.cloned(),
branch_dest,
});
cur_addr += 4;
}
Ok((ops, insts))
}

89
src/obj/ppc.rs Normal file
View File

@ -0,0 +1,89 @@
use anyhow::Result;
use ppc750cl::{disasm_iter, Argument};
use crate::obj::{ObjIns, ObjInsArg, ObjReloc, ObjRelocKind};
// Relative relocation, can be Simm or BranchOffset
fn is_relative_arg(arg: &ObjInsArg) -> bool {
matches!(arg, ObjInsArg::PpcArg(Argument::Simm(_)) | ObjInsArg::BranchOffset(_))
}
// Relative or absolute relocation, can be Uimm, Simm or Offset
fn is_rel_abs_arg(arg: &ObjInsArg) -> bool {
matches!(arg, ObjInsArg::PpcArg(arg) if matches!(arg, Argument::Uimm(_) | Argument::Simm(_) | Argument::Offset(_)))
}
fn is_offset_arg(arg: &ObjInsArg) -> bool { matches!(arg, ObjInsArg::PpcArg(Argument::Offset(_))) }
pub fn process_code(
data: &[u8],
address: u64,
relocs: &[ObjReloc],
) -> Result<(Vec<u8>, Vec<ObjIns>)> {
let ins_count = data.len() / 4;
let mut ops = Vec::<u8>::with_capacity(ins_count);
let mut insts = Vec::<ObjIns>::with_capacity(ins_count);
for mut ins in disasm_iter(data, address as u32) {
let reloc = relocs.iter().find(|r| (r.address as u32 & !3) == ins.addr);
if let Some(reloc) = reloc {
// Zero out relocations
ins.code = match reloc.kind {
ObjRelocKind::PpcEmbSda21 => ins.code & !0x1FFFFF,
ObjRelocKind::PpcRel24 => ins.code & !0x3FFFFFC,
ObjRelocKind::PpcRel14 => ins.code & !0xFFFC,
ObjRelocKind::PpcAddr16Hi
| ObjRelocKind::PpcAddr16Ha
| ObjRelocKind::PpcAddr16Lo => ins.code & !0xFFFF,
_ => ins.code,
};
}
let simplified = ins.simplified();
let mut args: Vec<ObjInsArg> = simplified
.args
.iter()
.map(|a| match a {
Argument::BranchDest(dest) => ObjInsArg::BranchOffset(dest.0),
_ => ObjInsArg::PpcArg(a.clone()),
})
.collect();
if let Some(reloc) = reloc {
match reloc.kind {
ObjRelocKind::PpcEmbSda21 => {
args = vec![args[0].clone(), ObjInsArg::Reloc];
}
ObjRelocKind::PpcRel24 | ObjRelocKind::PpcRel14 => {
let arg = args
.iter_mut()
.rfind(|a| is_relative_arg(a))
.ok_or_else(|| anyhow::Error::msg("Failed to locate rel arg for reloc"))?;
*arg = ObjInsArg::Reloc;
}
ObjRelocKind::PpcAddr16Hi
| ObjRelocKind::PpcAddr16Ha
| ObjRelocKind::PpcAddr16Lo => {
let arg = args.iter_mut().rfind(|a| is_rel_abs_arg(a)).ok_or_else(|| {
anyhow::Error::msg("Failed to locate rel/abs arg for reloc")
})?;
*arg = if is_offset_arg(arg) {
ObjInsArg::RelocWithBase
} else {
ObjInsArg::Reloc
};
}
_ => {}
}
}
ops.push(simplified.ins.op as u8);
let suffix = simplified.ins.suffix();
insts.push(ObjIns {
address: simplified.ins.addr,
code: simplified.ins.code,
mnemonic: format!("{}{}", simplified.mnemonic, suffix),
args,
reloc: reloc.cloned(),
op: 0,
branch_dest: None,
});
}
Ok((ops, insts))
}

View File

@ -1,85 +1,124 @@
use std::sync::{Arc, RwLock}; use std::sync::{Arc, RwLock};
use crate::{ use crate::{
app::{AppConfig, ViewState}, app::{AppConfig, DiffKind, ViewState},
jobs::build::queue_build, jobs::{bindiff::queue_bindiff, build::queue_build},
}; };
pub fn config_ui(ui: &mut egui::Ui, config: &Arc<RwLock<AppConfig>>, view_state: &mut ViewState) { pub fn config_ui(ui: &mut egui::Ui, config: &Arc<RwLock<AppConfig>>, view_state: &mut ViewState) {
let mut config_guard = config.write().unwrap(); let mut config_guard = config.write().unwrap();
let AppConfig { project_dir, project_dir_change, build_asm_dir, build_src_dir, build_obj } = let AppConfig {
&mut *config_guard; project_dir,
project_dir_change,
build_asm_dir,
build_src_dir,
build_obj,
left_obj,
right_obj,
} = &mut *config_guard;
if ui.button("Select project dir").clicked() { if view_state.diff_kind == DiffKind::SplitObj {
if let Some(path) = rfd::FileDialog::new().pick_folder() { if ui.button("Select project dir").clicked() {
*project_dir = Some(path); if let Some(path) = rfd::FileDialog::new().pick_folder() {
*project_dir_change = true; *project_dir = Some(path);
*build_asm_dir = None; *project_dir_change = true;
*build_src_dir = None; *build_asm_dir = None;
*build_obj = None; *build_src_dir = None;
}
}
if let Some(dir) = project_dir {
ui.label(dir.to_string_lossy());
}
ui.separator();
if let Some(project_dir) = project_dir {
if ui.button("Select asm build dir").clicked() {
if let Some(path) = rfd::FileDialog::new().set_directory(&project_dir).pick_folder() {
*build_asm_dir = Some(path);
*build_obj = None; *build_obj = None;
} }
} }
if let Some(dir) = build_asm_dir { if let Some(dir) = project_dir {
ui.label(dir.to_string_lossy()); ui.label(dir.to_string_lossy());
} }
ui.separator(); ui.separator();
if ui.button("Select src build dir").clicked() { if let Some(project_dir) = project_dir {
if let Some(path) = rfd::FileDialog::new().set_directory(&project_dir).pick_folder() { if ui.button("Select asm build dir").clicked() {
*build_src_dir = Some(path); if let Some(path) = rfd::FileDialog::new().set_directory(&project_dir).pick_folder()
*build_obj = None; {
*build_asm_dir = Some(path);
*build_obj = None;
}
} }
} if let Some(dir) = build_asm_dir {
if let Some(dir) = build_src_dir { ui.label(dir.to_string_lossy());
ui.label(dir.to_string_lossy()); }
ui.separator();
if ui.button("Select src build dir").clicked() {
if let Some(path) = rfd::FileDialog::new().set_directory(&project_dir).pick_folder()
{
*build_src_dir = Some(path);
*build_obj = None;
}
}
if let Some(dir) = build_src_dir {
ui.label(dir.to_string_lossy());
}
ui.separator();
} }
ui.separator(); if let Some(build_src_dir) = build_src_dir {
} if ui.button("Select obj").clicked() {
if let Some(path) = rfd::FileDialog::new()
if let Some(build_src_dir) = build_src_dir { .set_directory(&build_src_dir)
if ui.button("Select obj").clicked() { .add_filter("Object file", &["o", "elf"])
if let Some(path) = rfd::FileDialog::new() .pick_file()
.set_directory(&build_src_dir) {
.add_filter("Object file", &["o"]) let mut new_build_obj: Option<String> = None;
.pick_file() if let Ok(obj_path) = path.strip_prefix(&build_src_dir) {
{
let mut new_build_obj: Option<String> = None;
if let Ok(obj_path) = path.strip_prefix(&build_src_dir) {
new_build_obj = Some(obj_path.display().to_string());
} else if let Some(build_asm_dir) = build_asm_dir {
if let Ok(obj_path) = path.strip_prefix(&build_asm_dir) {
new_build_obj = Some(obj_path.display().to_string()); new_build_obj = Some(obj_path.display().to_string());
} else if let Some(build_asm_dir) = build_asm_dir {
if let Ok(obj_path) = path.strip_prefix(&build_asm_dir) {
new_build_obj = Some(obj_path.display().to_string());
}
}
if let Some(new_build_obj) = new_build_obj {
*build_obj = Some(new_build_obj.clone());
view_state.jobs.push(queue_build(new_build_obj, config.clone()));
} }
} }
if let Some(new_build_obj) = new_build_obj { }
*build_obj = Some(new_build_obj.clone()); if let Some(build_obj) = build_obj {
view_state.jobs.push(queue_build(new_build_obj, config.clone())); ui.label(&*build_obj);
if ui.button("Build").clicked() {
view_state.jobs.push(queue_build(build_obj.clone(), config.clone()));
} }
} }
ui.separator();
} }
if let Some(build_obj) = build_obj { } else if view_state.diff_kind == DiffKind::WholeBinary {
ui.label(&*build_obj); if ui.button("Select left obj").clicked() {
if ui.button("Build").clicked() { if let Some(path) =
view_state.jobs.push(queue_build(build_obj.clone(), config.clone())); rfd::FileDialog::new().add_filter("Object file", &["o", "elf"]).pick_file()
{
*left_obj = Some(path);
} }
} }
if let Some(obj) = left_obj {
ui.label(obj.to_string_lossy());
}
ui.separator(); if ui.button("Select right obj").clicked() {
if let Some(path) =
rfd::FileDialog::new().add_filter("Object file", &["o", "elf"]).pick_file()
{
*right_obj = Some(path);
}
}
if let Some(obj) = right_obj {
ui.label(obj.to_string_lossy());
}
if let (Some(_), Some(_)) = (left_obj, right_obj) {
if ui.button("Build").clicked() {
view_state.jobs.push(queue_bindiff(config.clone()));
}
}
} }
ui.checkbox(&mut view_state.reverse_fn_order, "Reverse function order (deferred)"); ui.checkbox(&mut view_state.reverse_fn_order, "Reverse function order (deferred)");

View File

@ -25,13 +25,39 @@ fn write_text(str: &str, color: Color32, job: &mut LayoutJob) {
fn write_reloc(reloc: &ObjReloc, job: &mut LayoutJob) { fn write_reloc(reloc: &ObjReloc, job: &mut LayoutJob) {
let name = reloc.target.demangled_name.as_ref().unwrap_or(&reloc.target.name); let name = reloc.target.demangled_name.as_ref().unwrap_or(&reloc.target.name);
write_text(name, Color32::LIGHT_GRAY, job);
match reloc.kind { match reloc.kind {
ObjRelocKind::PpcAddr16Lo => write_text("@l", Color32::GRAY, job), ObjRelocKind::PpcAddr16Lo => {
ObjRelocKind::PpcAddr16Hi => write_text("@h", Color32::GRAY, job), write_text(name, Color32::LIGHT_GRAY, job);
ObjRelocKind::PpcAddr16Ha => write_text("@ha", Color32::GRAY, job), write_text("@l", Color32::GRAY, job);
ObjRelocKind::PpcEmbSda21 => write_text("@sda21", Color32::GRAY, job), }
_ => {} ObjRelocKind::PpcAddr16Hi => {
write_text(name, Color32::LIGHT_GRAY, job);
write_text("@h", Color32::GRAY, job);
}
ObjRelocKind::PpcAddr16Ha => {
write_text(name, Color32::LIGHT_GRAY, job);
write_text("@ha", Color32::GRAY, job);
}
ObjRelocKind::PpcEmbSda21 => {
write_text(name, Color32::LIGHT_GRAY, job);
write_text("@sda21", Color32::GRAY, job);
}
ObjRelocKind::MipsHi16 => {
write_text("%hi(", Color32::GRAY, job);
write_text(name, Color32::LIGHT_GRAY, job);
write_text(")", Color32::GRAY, job);
}
ObjRelocKind::MipsLo16 => {
write_text("%lo(", Color32::GRAY, job);
write_text(name, Color32::LIGHT_GRAY, job);
write_text(")", Color32::GRAY, job);
}
ObjRelocKind::Absolute
| ObjRelocKind::PpcRel24
| ObjRelocKind::PpcRel14
| ObjRelocKind::Mips26 => {
write_text(name, Color32::LIGHT_GRAY, job);
}
}; };
} }
@ -51,7 +77,7 @@ fn write_ins(
ObjInsDiffKind::Insert => Color32::GREEN, ObjInsDiffKind::Insert => Color32::GREEN,
}; };
write_text( write_text(
&ins.mnemonic, &format!("{:<11}", ins.mnemonic),
match diff_kind { match diff_kind {
ObjInsDiffKind::OpMismatch => Color32::LIGHT_BLUE, ObjInsDiffKind::OpMismatch => Color32::LIGHT_BLUE,
_ => base_color, _ => base_color,
@ -72,17 +98,13 @@ fn write_ins(
base_color base_color
}; };
match arg { match arg {
ObjInsArg::Arg(arg) => match arg { ObjInsArg::PpcArg(arg) => match arg {
Argument::Offset(val) => { Argument::Offset(val) => {
write_text(&format!("{}", val), color, job); write_text(&format!("{}", val), color, job);
write_text("(", base_color, job); write_text("(", base_color, job);
writing_offset = true; writing_offset = true;
continue; continue;
} }
Argument::BranchDest(dest) => {
let addr = dest.0 + ins.ins.addr as i32 - base_addr as i32;
write_text(&format!("{:x}", addr), color, job);
}
Argument::Uimm(_) | Argument::Simm(_) => { Argument::Uimm(_) | Argument::Simm(_) => {
write_text(&format!("{}", arg), color, job); write_text(&format!("{}", arg), color, job);
} }
@ -93,12 +115,19 @@ fn write_ins(
ObjInsArg::Reloc => { ObjInsArg::Reloc => {
write_reloc(ins.reloc.as_ref().unwrap(), job); write_reloc(ins.reloc.as_ref().unwrap(), job);
} }
ObjInsArg::RelocOffset => { ObjInsArg::RelocWithBase => {
write_reloc(ins.reloc.as_ref().unwrap(), job); write_reloc(ins.reloc.as_ref().unwrap(), job);
write_text("(", base_color, job); write_text("(", base_color, job);
writing_offset = true; writing_offset = true;
continue; continue;
} }
ObjInsArg::MipsArg(str) => {
write_text(str.strip_prefix('$').unwrap_or(str), color, job);
}
ObjInsArg::BranchOffset(offset) => {
let addr = offset + ins.address as i32 - base_addr as i32;
write_text(&format!("{:x}", addr), color, job);
}
} }
if writing_offset { if writing_offset {
write_text(")", base_color, job); write_text(")", base_color, job);
@ -112,10 +141,10 @@ fn ins_hover_ui(ui: &mut egui::Ui, ins: &ObjIns) {
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace); ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
ui.style_mut().wrap = Some(false); ui.style_mut().wrap = Some(false);
ui.label(format!("{:02X?}", ins.ins.code.to_be_bytes())); ui.label(format!("{:02X?}", ins.code.to_be_bytes()));
for arg in &ins.args { for arg in &ins.args {
if let ObjInsArg::Arg(arg) = arg { if let ObjInsArg::PpcArg(arg) = arg {
match arg { match arg {
Argument::Uimm(v) => { Argument::Uimm(v) => {
ui.label(format!("{} == {}", v, v.0)); ui.label(format!("{} == {}", v, v.0));
@ -153,7 +182,7 @@ fn ins_context_menu(ui: &mut egui::Ui, ins: &ObjIns) {
// if ui.button("Copy hex").clicked() {} // if ui.button("Copy hex").clicked() {}
for arg in &ins.args { for arg in &ins.args {
if let ObjInsArg::Arg(arg) = arg { if let ObjInsArg::PpcArg(arg) = arg {
match arg { match arg {
Argument::Uimm(v) => { Argument::Uimm(v) => {
if ui.button(format!("Copy \"{}\"", v)).clicked() { if ui.button(format!("Copy \"{}\"", v)).clicked() {
@ -236,7 +265,7 @@ fn asm_row_ui(ui: &mut egui::Ui, ins_diff: &ObjInsDiff, symbol: &ObjSymbol) {
ObjInsDiffKind::Insert => Color32::GREEN, ObjInsDiffKind::Insert => Color32::GREEN,
}; };
write_text( write_text(
&format!("{:<6}", format!("{:x}:", ins.ins.addr - symbol.address as u32)), &format!("{:<6}", format!("{:x}:", ins.address - symbol.address as u32)),
base_color, base_color,
&mut job, &mut job,
); );

View File

@ -66,21 +66,28 @@ fn symbol_ui(
..Default::default() ..Default::default()
}); });
} }
job.append("] (", 0.0, TextFormat { job.append("] ", 0.0, TextFormat {
font_id: font_id.clone(),
color: Color32::GRAY,
..Default::default()
});
job.append(&format!("{:.0}%", symbol.match_percent), 0.0, TextFormat {
font_id: font_id.clone(),
color: match_color_for_symbol(symbol),
..Default::default()
});
job.append(") ", 0.0, TextFormat {
font_id: font_id.clone(), font_id: font_id.clone(),
color: Color32::GRAY, color: Color32::GRAY,
..Default::default() ..Default::default()
}); });
if symbol.match_percent > 0.0 {
job.append("(", 0.0, TextFormat {
font_id: font_id.clone(),
color: Color32::GRAY,
..Default::default()
});
job.append(&format!("{:.0}%", symbol.match_percent), 0.0, TextFormat {
font_id: font_id.clone(),
color: match_color_for_symbol(symbol),
..Default::default()
});
job.append(") ", 0.0, TextFormat {
font_id: font_id.clone(),
color: Color32::GRAY,
..Default::default()
});
}
job.append(name, 0.0, TextFormat { font_id, color: Color32::WHITE, ..Default::default() }); job.append(name, 0.0, TextFormat { font_id, color: Color32::WHITE, ..Default::default() });
let response = SelectableLabel::new(selected, job).ui(ui); let response = SelectableLabel::new(selected, job).ui(ui);
if response.clicked() { if response.clicked() {