mirror of
https://github.com/encounter/objdiff.git
synced 2025-12-19 18:05:35 +00:00
Compare commits
24 Commits
v2.0.0-alp
...
v2.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
| cf937b0be9 | |||
| 23b6d33a98 | |||
| f17ee83622 | |||
| 615ec4c50a | |||
| 2cc10b0d06 | |||
| 8091941448 | |||
| de74dfdba7 | |||
| 177bd5e895 | |||
| e1ccee1e73 | |||
| 952b6a63c3 | |||
|
|
09cc9952df | ||
| fc598af329 | |||
| 871407622d | |||
| e3fff7b0dc | |||
|
|
75b0e7d9e5 | ||
|
|
9f71ce9fea | ||
|
|
d9fb48853e | ||
| 233839346a | |||
| 95615c2ec5 | |||
|
|
97981160f4 | ||
|
|
1fd901a863 | ||
| 759d55994a | |||
| 9710ccc38a | |||
| 79cd460333 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -22,3 +22,4 @@ android.keystore
|
|||||||
*.frag
|
*.frag
|
||||||
*.vert
|
*.vert
|
||||||
*.metal
|
*.metal
|
||||||
|
.vscode/launch.json
|
||||||
|
|||||||
752
Cargo.lock
generated
752
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,5 @@ resolver = "2"
|
|||||||
|
|
||||||
[profile.release-lto]
|
[profile.release-lto]
|
||||||
inherits = "release"
|
inherits = "release"
|
||||||
# Temporarily disabled to fix notify crash
|
lto = "thin"
|
||||||
# See https://github.com/encounter/objdiff/issues/66
|
|
||||||
#lto = "thin"
|
|
||||||
strip = "debuginfo"
|
strip = "debuginfo"
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ Supports:
|
|||||||
- PowerPC 750CL (GameCube, Wii)
|
- PowerPC 750CL (GameCube, Wii)
|
||||||
- MIPS (N64, PS1, PS2, PSP)
|
- MIPS (N64, PS1, PS2, PSP)
|
||||||
- x86 (COFF only at the moment)
|
- x86 (COFF only at the moment)
|
||||||
- ARMv5 (DS)
|
- ARM (GBA, DS, 3DS)
|
||||||
|
|
||||||
See [Usage](#usage) for more information.
|
See [Usage](#usage) for more information.
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "objdiff-cli"
|
name = "objdiff-cli"
|
||||||
version = "2.0.0-alpha.3"
|
version = "2.0.0-beta.3"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.70"
|
rust-version = "1.70"
|
||||||
authors = ["Luke Street <luke@street.dev>"]
|
authors = ["Luke Street <luke@street.dev>"]
|
||||||
|
|||||||
@@ -750,8 +750,11 @@ impl FunctionDiffUi {
|
|||||||
base_color = COLOR_ROTATION[diff.idx % COLOR_ROTATION.len()]
|
base_color = COLOR_ROTATION[diff.idx % COLOR_ROTATION.len()]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DiffText::BranchDest(addr) => {
|
DiffText::BranchDest(addr, diff) => {
|
||||||
label_text = format!("{addr:x}");
|
label_text = format!("{addr:x}");
|
||||||
|
if let Some(diff) = diff {
|
||||||
|
base_color = COLOR_ROTATION[diff.idx % COLOR_ROTATION.len()]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
DiffText::Symbol(sym) => {
|
DiffText::Symbol(sym) => {
|
||||||
let name = sym.demangled_name.as_ref().unwrap_or(&sym.name);
|
let name = sym.demangled_name.as_ref().unwrap_or(&sym.name);
|
||||||
@@ -809,23 +812,35 @@ impl FunctionDiffUi {
|
|||||||
|
|
||||||
fn reload(&mut self) -> Result<()> {
|
fn reload(&mut self) -> Result<()> {
|
||||||
let prev = self.right_obj.take();
|
let prev = self.right_obj.take();
|
||||||
|
let config = diff::DiffObjConfig {
|
||||||
|
relax_reloc_diffs: self.relax_reloc_diffs,
|
||||||
|
space_between_args: true, // TODO
|
||||||
|
combine_data_sections: false, // TODO
|
||||||
|
x86_formatter: Default::default(), // TODO
|
||||||
|
mips_abi: Default::default(), // TODO
|
||||||
|
mips_instr_category: Default::default(), // TODO
|
||||||
|
arm_arch_version: Default::default(), // TODO
|
||||||
|
arm_unified_syntax: true, // TODO
|
||||||
|
arm_av_registers: false, // TODO
|
||||||
|
arm_r9_usage: Default::default(), // TODO
|
||||||
|
arm_sl_usage: false, // TODO
|
||||||
|
arm_fp_usage: false, // TODO
|
||||||
|
arm_ip_usage: false, // TODO
|
||||||
|
};
|
||||||
let target = self
|
let target = self
|
||||||
.target_path
|
.target_path
|
||||||
.as_deref()
|
.as_deref()
|
||||||
.map(|p| obj::read::read(p).with_context(|| format!("Loading {}", p.display())))
|
.map(|p| {
|
||||||
|
obj::read::read(p, &config).with_context(|| format!("Loading {}", p.display()))
|
||||||
|
})
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
let base = self
|
let base = self
|
||||||
.base_path
|
.base_path
|
||||||
.as_deref()
|
.as_deref()
|
||||||
.map(|p| obj::read::read(p).with_context(|| format!("Loading {}", p.display())))
|
.map(|p| {
|
||||||
|
obj::read::read(p, &config).with_context(|| format!("Loading {}", p.display()))
|
||||||
|
})
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
let config = diff::DiffObjConfig {
|
|
||||||
relax_reloc_diffs: self.relax_reloc_diffs,
|
|
||||||
space_between_args: true, // TODO
|
|
||||||
x86_formatter: Default::default(), // TODO
|
|
||||||
mips_abi: Default::default(), // TODO
|
|
||||||
mips_instr_category: Default::default(), // TODO
|
|
||||||
};
|
|
||||||
let result = diff::diff_objs(&config, target.as_ref(), base.as_ref(), prev.as_ref())?;
|
let result = diff::diff_objs(&config, target.as_ref(), base.as_ref(), prev.as_ref())?;
|
||||||
|
|
||||||
let left_sym = target.as_ref().and_then(|o| find_function(o, &self.symbol_name));
|
let left_sym = target.as_ref().and_then(|o| find_function(o, &self.symbol_name));
|
||||||
|
|||||||
@@ -230,17 +230,21 @@ fn report_object(
|
|||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
let config = diff::DiffObjConfig { relax_reloc_diffs: true, ..Default::default() };
|
||||||
let target = object
|
let target = object
|
||||||
.target_path
|
.target_path
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|p| obj::read::read(p).with_context(|| format!("Failed to open {}", p.display())))
|
.map(|p| {
|
||||||
|
obj::read::read(p, &config).with_context(|| format!("Failed to open {}", p.display()))
|
||||||
|
})
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
let base = object
|
let base = object
|
||||||
.base_path
|
.base_path
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|p| obj::read::read(p).with_context(|| format!("Failed to open {}", p.display())))
|
.map(|p| {
|
||||||
|
obj::read::read(p, &config).with_context(|| format!("Failed to open {}", p.display()))
|
||||||
|
})
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
let config = diff::DiffObjConfig { relax_reloc_diffs: true, ..Default::default() };
|
|
||||||
let result = diff::diff_objs(&config, target.as_ref(), base.as_ref(), None)?;
|
let result = diff::diff_objs(&config, target.as_ref(), base.as_ref(), None)?;
|
||||||
let mut unit = ReportUnit {
|
let mut unit = ReportUnit {
|
||||||
name: object.name().to_string(),
|
name: object.name().to_string(),
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "objdiff-core"
|
name = "objdiff-core"
|
||||||
version = "2.0.0-alpha.3"
|
version = "2.0.0-beta.3"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.70"
|
rust-version = "1.70"
|
||||||
authors = ["Luke Street <luke@street.dev>"]
|
authors = ["Luke Street <luke@street.dev>"]
|
||||||
@@ -17,9 +17,9 @@ any-arch = [] # Implicit, used to check if any arch is enabled
|
|||||||
config = ["globset", "semver", "serde_json", "serde_yaml"]
|
config = ["globset", "semver", "serde_json", "serde_yaml"]
|
||||||
dwarf = ["gimli"]
|
dwarf = ["gimli"]
|
||||||
mips = ["any-arch", "rabbitizer"]
|
mips = ["any-arch", "rabbitizer"]
|
||||||
ppc = ["any-arch", "cwdemangle", "ppc750cl"]
|
ppc = ["any-arch", "cwdemangle", "cwextab", "ppc750cl"]
|
||||||
x86 = ["any-arch", "cpp_demangle", "iced-x86", "msvc-demangler"]
|
x86 = ["any-arch", "cpp_demangle", "iced-x86", "msvc-demangler"]
|
||||||
arm = ["any-arch", "cpp_demangle", "unarm"]
|
arm = ["any-arch", "cpp_demangle", "unarm", "arm-attr"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.82"
|
anyhow = "1.0.82"
|
||||||
@@ -45,6 +45,7 @@ gimli = { version = "0.29.0", default-features = false, features = ["read-all"],
|
|||||||
|
|
||||||
# ppc
|
# ppc
|
||||||
cwdemangle = { version = "1.0.0", optional = true }
|
cwdemangle = { version = "1.0.0", optional = true }
|
||||||
|
cwextab = { version = "0.2.3", optional = true }
|
||||||
ppc750cl = { git = "https://github.com/encounter/ppc750cl", rev = "6cbd7d888c7082c2c860f66cbb9848d633f753ed", optional = true }
|
ppc750cl = { git = "https://github.com/encounter/ppc750cl", rev = "6cbd7d888c7082c2c860f66cbb9848d633f753ed", optional = true }
|
||||||
|
|
||||||
# mips
|
# mips
|
||||||
@@ -56,4 +57,5 @@ iced-x86 = { version = "1.21.0", default-features = false, features = ["std", "d
|
|||||||
msvc-demangler = { version = "0.10.0", optional = true }
|
msvc-demangler = { version = "0.10.0", optional = true }
|
||||||
|
|
||||||
# arm
|
# arm
|
||||||
unarm = { version = "1.0.0", optional = true }
|
unarm = { version = "1.4.0", optional = true }
|
||||||
|
arm-attr = { version = "0.1.1", optional = true }
|
||||||
|
|||||||
@@ -4,33 +4,82 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
|
use arm_attr::{enums::CpuArch, tag::Tag, BuildAttrs};
|
||||||
use object::{
|
use object::{
|
||||||
elf, File, Object, ObjectSection, ObjectSymbol, Relocation, RelocationFlags, SectionIndex,
|
elf::{self, SHT_ARM_ATTRIBUTES},
|
||||||
SectionKind, Symbol,
|
Endian, File, Object, ObjectSection, ObjectSymbol, Relocation, RelocationFlags, SectionIndex,
|
||||||
|
SectionKind, Symbol, SymbolKind,
|
||||||
};
|
};
|
||||||
use unarm::{
|
use unarm::{
|
||||||
args::{Argument, OffsetImm, OffsetReg, Register},
|
args::{Argument, OffsetImm, OffsetReg, Register},
|
||||||
parse::{ArmVersion, ParseMode, Parser},
|
parse::{ArmVersion, ParseMode, Parser},
|
||||||
ParsedIns,
|
DisplayOptions, ParseFlags, ParsedIns, RegNames,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
arch::{ObjArch, ProcessCodeResult},
|
arch::{ObjArch, ProcessCodeResult},
|
||||||
diff::DiffObjConfig,
|
diff::{ArmArchVersion, ArmR9Usage, DiffObjConfig},
|
||||||
obj::{ObjIns, ObjInsArg, ObjInsArgValue, ObjReloc, ObjSection},
|
obj::{ObjIns, ObjInsArg, ObjInsArgValue, ObjReloc, ObjSection},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct ObjArchArm {
|
pub struct ObjArchArm {
|
||||||
/// Maps section index, to list of disasm modes (arm, thumb or data) sorted by address
|
/// Maps section index, to list of disasm modes (arm, thumb or data) sorted by address
|
||||||
disasm_modes: HashMap<SectionIndex, Vec<DisasmMode>>,
|
disasm_modes: HashMap<SectionIndex, Vec<DisasmMode>>,
|
||||||
|
detected_version: Option<ArmVersion>,
|
||||||
|
endianness: object::Endianness,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ObjArchArm {
|
impl ObjArchArm {
|
||||||
pub fn new(file: &File) -> Result<Self> {
|
pub fn new(file: &File) -> Result<Self> {
|
||||||
|
let endianness = file.endianness();
|
||||||
match file {
|
match file {
|
||||||
File::Elf32(_) => {
|
File::Elf32(_) => {
|
||||||
let disasm_modes: HashMap<_, _> = file
|
let disasm_modes = Self::elf_get_mapping_symbols(file);
|
||||||
.sections()
|
let detected_version = Self::elf_detect_arm_version(file)?;
|
||||||
|
Ok(Self { disasm_modes, detected_version, endianness })
|
||||||
|
}
|
||||||
|
_ => bail!("Unsupported file format {:?}", file.format()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn elf_detect_arm_version(file: &File) -> Result<Option<ArmVersion>> {
|
||||||
|
// Check ARM attributes
|
||||||
|
if let Some(arm_attrs) = file.sections().find(|s| {
|
||||||
|
s.kind() == SectionKind::Elf(SHT_ARM_ATTRIBUTES) && s.name() == Ok(".ARM.attributes")
|
||||||
|
}) {
|
||||||
|
let attr_data = arm_attrs.uncompressed_data()?;
|
||||||
|
let build_attrs = BuildAttrs::new(&attr_data, match file.endianness() {
|
||||||
|
object::Endianness::Little => arm_attr::Endian::Little,
|
||||||
|
object::Endianness::Big => arm_attr::Endian::Big,
|
||||||
|
})?;
|
||||||
|
for subsection in build_attrs.subsections() {
|
||||||
|
let subsection = subsection?;
|
||||||
|
if !subsection.is_aeabi() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Only checking first CpuArch tag. Others may exist, but that's very unlikely.
|
||||||
|
let cpu_arch = subsection.into_public_tag_iter()?.find_map(|(_, tag)| {
|
||||||
|
if let Tag::CpuArch(cpu_arch) = tag {
|
||||||
|
Some(cpu_arch)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
});
|
||||||
|
match cpu_arch {
|
||||||
|
Some(CpuArch::V4T) => return Ok(Some(ArmVersion::V4T)),
|
||||||
|
Some(CpuArch::V5TE) => return Ok(Some(ArmVersion::V5Te)),
|
||||||
|
Some(CpuArch::V6K) => return Ok(Some(ArmVersion::V6K)),
|
||||||
|
Some(arch) => bail!("ARM arch {} not supported", arch),
|
||||||
|
None => {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn elf_get_mapping_symbols(file: &File) -> HashMap<SectionIndex, Vec<DisasmMode>> {
|
||||||
|
file.sections()
|
||||||
.filter(|s| s.kind() == SectionKind::Text)
|
.filter(|s| s.kind() == SectionKind::Text)
|
||||||
.map(|s| {
|
.map(|s| {
|
||||||
let index = s.index();
|
let index = s.index();
|
||||||
@@ -42,15 +91,20 @@ impl ObjArchArm {
|
|||||||
mapping_symbols.sort_unstable_by_key(|x| x.address);
|
mapping_symbols.sort_unstable_by_key(|x| x.address);
|
||||||
(s.index(), mapping_symbols)
|
(s.index(), mapping_symbols)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect()
|
||||||
Ok(Self { disasm_modes })
|
|
||||||
}
|
|
||||||
_ => bail!("Unsupported file format {:?}", file.format()),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ObjArch for ObjArchArm {
|
impl ObjArch for ObjArchArm {
|
||||||
|
fn symbol_address(&self, symbol: &Symbol) -> u64 {
|
||||||
|
let address = symbol.address();
|
||||||
|
if symbol.kind() == SymbolKind::Text {
|
||||||
|
address & !1
|
||||||
|
} else {
|
||||||
|
address
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn process_code(
|
fn process_code(
|
||||||
&self,
|
&self,
|
||||||
address: u64,
|
address: u64,
|
||||||
@@ -81,10 +135,38 @@ impl ObjArch for ObjArchArm {
|
|||||||
mapping_symbols.iter().skip(first_mapping_idx + 1).take_while(|x| x.address < end_addr);
|
mapping_symbols.iter().skip(first_mapping_idx + 1).take_while(|x| x.address < end_addr);
|
||||||
let mut next_mapping = mappings_iter.next();
|
let mut next_mapping = mappings_iter.next();
|
||||||
|
|
||||||
let ins_count = code.len() / first_mapping.instruction_size();
|
let ins_count = code.len() / first_mapping.instruction_size(start_addr);
|
||||||
let mut ops = Vec::<u16>::with_capacity(ins_count);
|
let mut ops = Vec::<u16>::with_capacity(ins_count);
|
||||||
let mut insts = Vec::<ObjIns>::with_capacity(ins_count);
|
let mut insts = Vec::<ObjIns>::with_capacity(ins_count);
|
||||||
let mut parser = Parser::new(ArmVersion::V5Te, first_mapping, start_addr, code);
|
|
||||||
|
let version = match config.arm_arch_version {
|
||||||
|
ArmArchVersion::Auto => self.detected_version.unwrap_or(ArmVersion::V5Te),
|
||||||
|
ArmArchVersion::V4T => ArmVersion::V4T,
|
||||||
|
ArmArchVersion::V5TE => ArmVersion::V5Te,
|
||||||
|
ArmArchVersion::V6K => ArmVersion::V6K,
|
||||||
|
};
|
||||||
|
let endian = match self.endianness {
|
||||||
|
object::Endianness::Little => unarm::Endian::Little,
|
||||||
|
object::Endianness::Big => unarm::Endian::Big,
|
||||||
|
};
|
||||||
|
|
||||||
|
let parse_flags = ParseFlags { ual: config.arm_unified_syntax };
|
||||||
|
|
||||||
|
let mut parser = Parser::new(version, first_mapping, start_addr, endian, parse_flags, code);
|
||||||
|
|
||||||
|
let display_options = DisplayOptions {
|
||||||
|
reg_names: RegNames {
|
||||||
|
av_registers: config.arm_av_registers,
|
||||||
|
r9_use: match config.arm_r9_usage {
|
||||||
|
ArmR9Usage::GeneralPurpose => unarm::R9Use::GeneralPurpose,
|
||||||
|
ArmR9Usage::Sb => unarm::R9Use::Pid,
|
||||||
|
ArmR9Usage::Tr => unarm::R9Use::Tls,
|
||||||
|
},
|
||||||
|
explicit_stack_limit: config.arm_sl_usage,
|
||||||
|
frame_pointer: config.arm_fp_usage,
|
||||||
|
ip: config.arm_ip_usage,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
while let Some((address, op, ins)) = parser.next() {
|
while let Some((address, op, ins)) = parser.next() {
|
||||||
if let Some(next) = next_mapping {
|
if let Some(next) = next_mapping {
|
||||||
@@ -95,7 +177,6 @@ impl ObjArch for ObjArchArm {
|
|||||||
next_mapping = mappings_iter.next();
|
next_mapping = mappings_iter.next();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let line = line_info.range(..=address as u64).last().map(|(_, &b)| b);
|
let line = line_info.range(..=address as u64).last().map(|(_, &b)| b);
|
||||||
|
|
||||||
let reloc = relocations.iter().find(|r| (r.address as u32 & !1) == address).cloned();
|
let reloc = relocations.iter().find(|r| (r.address as u32 & !1) == address).cloned();
|
||||||
@@ -103,11 +184,19 @@ impl ObjArch for ObjArchArm {
|
|||||||
let mut reloc_arg = None;
|
let mut reloc_arg = None;
|
||||||
if let Some(reloc) = &reloc {
|
if let Some(reloc) = &reloc {
|
||||||
match reloc.flags {
|
match reloc.flags {
|
||||||
|
// Calls
|
||||||
RelocationFlags::Elf { r_type: elf::R_ARM_THM_XPC22 }
|
RelocationFlags::Elf { r_type: elf::R_ARM_THM_XPC22 }
|
||||||
| RelocationFlags::Elf { r_type: elf::R_ARM_PC24 } => {
|
| RelocationFlags::Elf { r_type: elf::R_ARM_THM_PC22 }
|
||||||
|
| RelocationFlags::Elf { r_type: elf::R_ARM_PC24 }
|
||||||
|
| RelocationFlags::Elf { r_type: elf::R_ARM_XPC25 }
|
||||||
|
| RelocationFlags::Elf { r_type: elf::R_ARM_CALL } => {
|
||||||
reloc_arg =
|
reloc_arg =
|
||||||
ins.args.iter().rposition(|a| matches!(a, Argument::BranchDest(_)));
|
ins.args.iter().rposition(|a| matches!(a, Argument::BranchDest(_)));
|
||||||
}
|
}
|
||||||
|
// Data
|
||||||
|
RelocationFlags::Elf { r_type: elf::R_ARM_ABS32 } => {
|
||||||
|
reloc_arg = ins.args.iter().rposition(|a| matches!(a, Argument::UImm(_)));
|
||||||
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -115,7 +204,7 @@ impl ObjArch for ObjArchArm {
|
|||||||
let (args, branch_dest) = if reloc.is_some() && parser.mode == ParseMode::Data {
|
let (args, branch_dest) = if reloc.is_some() && parser.mode == ParseMode::Data {
|
||||||
(vec![ObjInsArg::Reloc], None)
|
(vec![ObjInsArg::Reloc], None)
|
||||||
} else {
|
} else {
|
||||||
push_args(&ins, config, reloc_arg, address)?
|
push_args(&ins, config, reloc_arg, address, display_options)?
|
||||||
};
|
};
|
||||||
|
|
||||||
ops.push(op.id());
|
ops.push(op.id());
|
||||||
@@ -128,7 +217,7 @@ impl ObjArch for ObjArchArm {
|
|||||||
reloc,
|
reloc,
|
||||||
branch_dest,
|
branch_dest,
|
||||||
line,
|
line,
|
||||||
formatted: ins.to_string(),
|
formatted: ins.display(display_options).to_string(),
|
||||||
orig: None,
|
orig: None,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -138,11 +227,43 @@ impl ObjArch for ObjArchArm {
|
|||||||
|
|
||||||
fn implcit_addend(
|
fn implcit_addend(
|
||||||
&self,
|
&self,
|
||||||
_section: &ObjSection,
|
_file: &File<'_>,
|
||||||
|
section: &ObjSection,
|
||||||
address: u64,
|
address: u64,
|
||||||
reloc: &Relocation,
|
reloc: &Relocation,
|
||||||
) -> anyhow::Result<i64> {
|
) -> anyhow::Result<i64> {
|
||||||
bail!("Unsupported ARM implicit relocation {:#x}{:?}", address, reloc.flags())
|
let address = address as usize;
|
||||||
|
Ok(match reloc.flags() {
|
||||||
|
// ARM calls
|
||||||
|
RelocationFlags::Elf { r_type: elf::R_ARM_PC24 }
|
||||||
|
| RelocationFlags::Elf { r_type: elf::R_ARM_XPC25 }
|
||||||
|
| RelocationFlags::Elf { r_type: elf::R_ARM_CALL } => {
|
||||||
|
let data = section.data[address..address + 4].try_into()?;
|
||||||
|
let addend = self.endianness.read_i32_bytes(data);
|
||||||
|
let imm24 = addend & 0xffffff;
|
||||||
|
(imm24 << 2) << 8 >> 8
|
||||||
|
}
|
||||||
|
|
||||||
|
// Thumb calls
|
||||||
|
RelocationFlags::Elf { r_type: elf::R_ARM_THM_PC22 }
|
||||||
|
| RelocationFlags::Elf { r_type: elf::R_ARM_THM_XPC22 } => {
|
||||||
|
let data = section.data[address..address + 2].try_into()?;
|
||||||
|
let high = self.endianness.read_i16_bytes(data) as i32;
|
||||||
|
let data = section.data[address + 2..address + 4].try_into()?;
|
||||||
|
let low = self.endianness.read_i16_bytes(data) as i32;
|
||||||
|
|
||||||
|
let imm22 = ((high & 0x7ff) << 11) | (low & 0x7ff);
|
||||||
|
(imm22 << 1) << 9 >> 9
|
||||||
|
}
|
||||||
|
|
||||||
|
// Data
|
||||||
|
RelocationFlags::Elf { r_type: elf::R_ARM_ABS32 } => {
|
||||||
|
let data = section.data[address..address + 4].try_into()?;
|
||||||
|
self.endianness.read_i32_bytes(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
flags => bail!("Unsupported ARM implicit relocation {flags:?}"),
|
||||||
|
} as i64)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn demangle(&self, name: &str) -> Option<String> {
|
fn demangle(&self, name: &str) -> Option<String> {
|
||||||
@@ -178,6 +299,7 @@ fn push_args(
|
|||||||
config: &DiffObjConfig,
|
config: &DiffObjConfig,
|
||||||
reloc_arg: Option<usize>,
|
reloc_arg: Option<usize>,
|
||||||
cur_addr: u32,
|
cur_addr: u32,
|
||||||
|
display_options: DisplayOptions,
|
||||||
) -> Result<(Vec<ObjInsArg>, Option<u64>)> {
|
) -> Result<(Vec<ObjInsArg>, Option<u64>)> {
|
||||||
let mut args = vec![];
|
let mut args = vec![];
|
||||||
let mut branch_dest = None;
|
let mut branch_dest = None;
|
||||||
@@ -209,12 +331,15 @@ fn push_args(
|
|||||||
args.push(ObjInsArg::Reloc);
|
args.push(ObjInsArg::Reloc);
|
||||||
} else {
|
} else {
|
||||||
match arg {
|
match arg {
|
||||||
|
Argument::None => {}
|
||||||
Argument::Reg(reg) => {
|
Argument::Reg(reg) => {
|
||||||
if reg.deref {
|
if reg.deref {
|
||||||
deref = true;
|
deref = true;
|
||||||
args.push(ObjInsArg::PlainText("[".into()));
|
args.push(ObjInsArg::PlainText("[".into()));
|
||||||
}
|
}
|
||||||
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(reg.reg.to_string().into())));
|
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(
|
||||||
|
reg.reg.display(display_options.reg_names).to_string().into(),
|
||||||
|
)));
|
||||||
if reg.writeback {
|
if reg.writeback {
|
||||||
if reg.deref {
|
if reg.deref {
|
||||||
writeback = true;
|
writeback = true;
|
||||||
@@ -232,7 +357,10 @@ fn push_args(
|
|||||||
args.push(ObjInsArg::PlainText(config.separator().into()));
|
args.push(ObjInsArg::PlainText(config.separator().into()));
|
||||||
}
|
}
|
||||||
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(
|
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(
|
||||||
Register::parse(i).to_string().into(),
|
Register::parse(i)
|
||||||
|
.display(display_options.reg_names)
|
||||||
|
.to_string()
|
||||||
|
.into(),
|
||||||
)));
|
)));
|
||||||
first = false;
|
first = false;
|
||||||
}
|
}
|
||||||
@@ -242,7 +370,7 @@ fn push_args(
|
|||||||
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque("^".to_string().into())));
|
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque("^".to_string().into())));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Argument::UImm(value) | Argument::CoOpcode(value) => {
|
Argument::UImm(value) | Argument::CoOpcode(value) | Argument::SatImm(value) => {
|
||||||
args.push(ObjInsArg::PlainText("#".into()));
|
args.push(ObjInsArg::PlainText("#".into()));
|
||||||
args.push(ObjInsArg::Arg(ObjInsArgValue::Unsigned(*value as u64)));
|
args.push(ObjInsArg::Arg(ObjInsArgValue::Unsigned(*value as u64)));
|
||||||
}
|
}
|
||||||
@@ -272,17 +400,33 @@ fn push_args(
|
|||||||
Argument::ShiftReg(shift) => {
|
Argument::ShiftReg(shift) => {
|
||||||
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(shift.op.to_string().into())));
|
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(shift.op.to_string().into())));
|
||||||
args.push(ObjInsArg::PlainText(" ".into()));
|
args.push(ObjInsArg::PlainText(" ".into()));
|
||||||
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(shift.reg.to_string().into())));
|
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(
|
||||||
|
shift.reg.display(display_options.reg_names).to_string().into(),
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
Argument::OffsetReg(offset) => {
|
Argument::OffsetReg(offset) => {
|
||||||
if !offset.add {
|
if !offset.add {
|
||||||
args.push(ObjInsArg::PlainText("-".into()));
|
args.push(ObjInsArg::PlainText("-".into()));
|
||||||
}
|
}
|
||||||
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(
|
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(
|
||||||
offset.reg.to_string().into(),
|
offset.reg.display(display_options.reg_names).to_string().into(),
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
_ => args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(arg.to_string().into()))),
|
Argument::CpsrMode(mode) => {
|
||||||
|
args.push(ObjInsArg::PlainText("#".into()));
|
||||||
|
args.push(ObjInsArg::Arg(ObjInsArgValue::Unsigned(mode.mode as u64)));
|
||||||
|
if mode.writeback {
|
||||||
|
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque("!".into())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Argument::CoReg(_)
|
||||||
|
| Argument::StatusReg(_)
|
||||||
|
| Argument::StatusMask(_)
|
||||||
|
| Argument::Shift(_)
|
||||||
|
| Argument::CpsrFlags(_)
|
||||||
|
| Argument::Endian(_) => args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(
|
||||||
|
arg.display(display_options).to_string().into(),
|
||||||
|
))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
use std::{borrow::Cow, collections::BTreeMap, sync::Mutex};
|
use std::{borrow::Cow, collections::BTreeMap, sync::Mutex};
|
||||||
|
|
||||||
use anyhow::{anyhow, bail, Result};
|
use anyhow::{anyhow, bail, Result};
|
||||||
use object::{elf, Endian, Endianness, File, FileFlags, Object, Relocation, RelocationFlags};
|
use object::{
|
||||||
|
elf, Endian, Endianness, File, FileFlags, Object, ObjectSection, ObjectSymbol, Relocation,
|
||||||
|
RelocationFlags, RelocationTarget,
|
||||||
|
};
|
||||||
use rabbitizer::{config, Abi, InstrCategory, Instruction, OperandType};
|
use rabbitizer::{config, Abi, InstrCategory, Instruction, OperandType};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -22,13 +25,14 @@ pub struct ObjArchMips {
|
|||||||
pub endianness: Endianness,
|
pub endianness: Endianness,
|
||||||
pub abi: Abi,
|
pub abi: Abi,
|
||||||
pub instr_category: InstrCategory,
|
pub instr_category: InstrCategory,
|
||||||
|
pub ri_gp_value: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
const EF_MIPS_ABI: u32 = 0x0000F000;
|
const EF_MIPS_ABI: u32 = 0x0000F000;
|
||||||
const EF_MIPS_MACH: u32 = 0x00FF0000;
|
const EF_MIPS_MACH: u32 = 0x00FF0000;
|
||||||
|
|
||||||
const E_MIPS_MACH_ALLEGREX: u32 = 0x00840000;
|
const EF_MIPS_MACH_ALLEGREX: u32 = 0x00840000;
|
||||||
const E_MIPS_MACH_5900: u32 = 0x00920000;
|
const EF_MIPS_MACH_5900: u32 = 0x00920000;
|
||||||
|
|
||||||
impl ObjArchMips {
|
impl ObjArchMips {
|
||||||
pub fn new(object: &File) -> Result<Self> {
|
pub fn new(object: &File) -> Result<Self> {
|
||||||
@@ -38,19 +42,37 @@ impl ObjArchMips {
|
|||||||
FileFlags::None => {}
|
FileFlags::None => {}
|
||||||
FileFlags::Elf { e_flags, .. } => {
|
FileFlags::Elf { e_flags, .. } => {
|
||||||
abi = match e_flags & EF_MIPS_ABI {
|
abi = match e_flags & EF_MIPS_ABI {
|
||||||
elf::EF_MIPS_ABI_O32 => Abi::O32,
|
elf::EF_MIPS_ABI_O32 | elf::EF_MIPS_ABI_O64 => Abi::O32,
|
||||||
elf::EF_MIPS_ABI_EABI32 | elf::EF_MIPS_ABI_EABI64 => Abi::N32,
|
elf::EF_MIPS_ABI_EABI32 | elf::EF_MIPS_ABI_EABI64 => Abi::N32,
|
||||||
_ => Abi::NUMERIC,
|
_ => {
|
||||||
|
if e_flags & elf::EF_MIPS_ABI2 != 0 {
|
||||||
|
Abi::N32
|
||||||
|
} else {
|
||||||
|
Abi::NUMERIC
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
instr_category = match e_flags & EF_MIPS_MACH {
|
instr_category = match e_flags & EF_MIPS_MACH {
|
||||||
E_MIPS_MACH_ALLEGREX => InstrCategory::R4000ALLEGREX,
|
EF_MIPS_MACH_ALLEGREX => InstrCategory::R4000ALLEGREX,
|
||||||
E_MIPS_MACH_5900 => InstrCategory::R5900,
|
EF_MIPS_MACH_5900 => InstrCategory::R5900,
|
||||||
_ => InstrCategory::CPU,
|
_ => InstrCategory::CPU,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
_ => bail!("Unsupported MIPS file flags"),
|
_ => bail!("Unsupported MIPS file flags"),
|
||||||
}
|
}
|
||||||
Ok(Self { endianness: object.endianness(), abi, instr_category })
|
|
||||||
|
// Parse the ri_gp_value stored in .reginfo to be able to correctly
|
||||||
|
// calculate R_MIPS_GPREL16 relocations later. The value is stored
|
||||||
|
// 0x14 bytes into .reginfo (on 32 bit platforms)
|
||||||
|
let ri_gp_value = object
|
||||||
|
.section_by_name(".reginfo")
|
||||||
|
.and_then(|section| section.data().ok())
|
||||||
|
.and_then(|data| data.get(0x14..0x18))
|
||||||
|
.and_then(|s| s.try_into().ok())
|
||||||
|
.map(|bytes| object.endianness().read_i32_bytes(bytes))
|
||||||
|
.unwrap_or(0);
|
||||||
|
|
||||||
|
Ok(Self { endianness: object.endianness(), abi, instr_category, ri_gp_value })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,7 +120,7 @@ impl ObjArch for ObjArchMips {
|
|||||||
let mnemonic = instruction.opcode_name().to_string();
|
let mnemonic = instruction.opcode_name().to_string();
|
||||||
let is_branch = instruction.is_branch();
|
let is_branch = instruction.is_branch();
|
||||||
let branch_offset = instruction.branch_offset();
|
let branch_offset = instruction.branch_offset();
|
||||||
let branch_dest = if is_branch {
|
let mut branch_dest = if is_branch {
|
||||||
cur_addr.checked_add_signed(branch_offset).map(|a| a as u64)
|
cur_addr.checked_add_signed(branch_offset).map(|a| a as u64)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
@@ -115,9 +137,7 @@ impl ObjArch for ObjArchMips {
|
|||||||
OperandType::cpu_immediate
|
OperandType::cpu_immediate
|
||||||
| OperandType::cpu_label
|
| OperandType::cpu_label
|
||||||
| OperandType::cpu_branch_target_label => {
|
| OperandType::cpu_branch_target_label => {
|
||||||
if let Some(branch_dest) = branch_dest {
|
if let Some(reloc) = reloc {
|
||||||
args.push(ObjInsArg::BranchDest(branch_dest));
|
|
||||||
} else if let Some(reloc) = reloc {
|
|
||||||
if matches!(&reloc.target_section, Some(s) if s == ".text")
|
if matches!(&reloc.target_section, Some(s) if s == ".text")
|
||||||
&& reloc.target.address > start_address
|
&& reloc.target.address > start_address
|
||||||
&& reloc.target.address < end_address
|
&& reloc.target.address < end_address
|
||||||
@@ -125,7 +145,10 @@ impl ObjArch for ObjArchMips {
|
|||||||
args.push(ObjInsArg::BranchDest(reloc.target.address));
|
args.push(ObjInsArg::BranchDest(reloc.target.address));
|
||||||
} else {
|
} else {
|
||||||
push_reloc(&mut args, reloc)?;
|
push_reloc(&mut args, reloc)?;
|
||||||
|
branch_dest = None;
|
||||||
}
|
}
|
||||||
|
} else if let Some(branch_dest) = branch_dest {
|
||||||
|
args.push(ObjInsArg::BranchDest(branch_dest));
|
||||||
} else {
|
} else {
|
||||||
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(
|
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(
|
||||||
op.disassemble(&instruction, None).into(),
|
op.disassemble(&instruction, None).into(),
|
||||||
@@ -173,6 +196,7 @@ impl ObjArch for ObjArchMips {
|
|||||||
|
|
||||||
fn implcit_addend(
|
fn implcit_addend(
|
||||||
&self,
|
&self,
|
||||||
|
file: &File<'_>,
|
||||||
section: &ObjSection,
|
section: &ObjSection,
|
||||||
address: u64,
|
address: u64,
|
||||||
reloc: &Relocation,
|
reloc: &Relocation,
|
||||||
@@ -185,10 +209,24 @@ impl ObjArch for ObjArchMips {
|
|||||||
((addend & 0x0000FFFF) << 16) as i32 as i64
|
((addend & 0x0000FFFF) << 16) as i32 as i64
|
||||||
}
|
}
|
||||||
RelocationFlags::Elf {
|
RelocationFlags::Elf {
|
||||||
r_type:
|
r_type: elf::R_MIPS_LO16 | elf::R_MIPS_GOT16 | elf::R_MIPS_CALL16,
|
||||||
elf::R_MIPS_LO16 | elf::R_MIPS_GOT16 | elf::R_MIPS_CALL16 | elf::R_MIPS_GPREL16,
|
|
||||||
} => (addend & 0x0000FFFF) as i16 as i64,
|
} => (addend & 0x0000FFFF) as i16 as i64,
|
||||||
|
RelocationFlags::Elf { r_type: elf::R_MIPS_GPREL16 } => {
|
||||||
|
let RelocationTarget::Symbol(idx) = reloc.target() else {
|
||||||
|
bail!("Unsupported R_MIPS_GPREL16 relocation against a non-symbol");
|
||||||
|
};
|
||||||
|
let sym = file.symbol_by_index(idx)?;
|
||||||
|
|
||||||
|
// if the symbol we are relocating against is in a local section we need to add
|
||||||
|
// the ri_gp_value from .reginfo to the addend.
|
||||||
|
if sym.section().index().is_some() {
|
||||||
|
((addend & 0x0000FFFF) as i16 as i64) + self.ri_gp_value as i64
|
||||||
|
} else {
|
||||||
|
(addend & 0x0000FFFF) as i16 as i64
|
||||||
|
}
|
||||||
|
}
|
||||||
RelocationFlags::Elf { r_type: elf::R_MIPS_26 } => ((addend & 0x03FFFFFF) << 2) as i64,
|
RelocationFlags::Elf { r_type: elf::R_MIPS_26 } => ((addend & 0x03FFFFFF) << 2) as i64,
|
||||||
|
RelocationFlags::Elf { r_type: elf::R_MIPS_PC16 } => 0, // PC-relative relocation
|
||||||
flags => bail!("Unsupported MIPS implicit relocation {flags:?}"),
|
flags => bail!("Unsupported MIPS implicit relocation {flags:?}"),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -199,6 +237,7 @@ impl ObjArch for ObjArchMips {
|
|||||||
elf::R_MIPS_HI16 => Cow::Borrowed("R_MIPS_HI16"),
|
elf::R_MIPS_HI16 => Cow::Borrowed("R_MIPS_HI16"),
|
||||||
elf::R_MIPS_LO16 => Cow::Borrowed("R_MIPS_LO16"),
|
elf::R_MIPS_LO16 => Cow::Borrowed("R_MIPS_LO16"),
|
||||||
elf::R_MIPS_GOT16 => Cow::Borrowed("R_MIPS_GOT16"),
|
elf::R_MIPS_GOT16 => Cow::Borrowed("R_MIPS_GOT16"),
|
||||||
|
elf::R_MIPS_PC16 => Cow::Borrowed("R_MIPS_PC16"),
|
||||||
elf::R_MIPS_CALL16 => Cow::Borrowed("R_MIPS_CALL16"),
|
elf::R_MIPS_CALL16 => Cow::Borrowed("R_MIPS_CALL16"),
|
||||||
elf::R_MIPS_GPREL16 => Cow::Borrowed("R_MIPS_GPREL16"),
|
elf::R_MIPS_GPREL16 => Cow::Borrowed("R_MIPS_GPREL16"),
|
||||||
elf::R_MIPS_32 => Cow::Borrowed("R_MIPS_32"),
|
elf::R_MIPS_32 => Cow::Borrowed("R_MIPS_32"),
|
||||||
@@ -238,7 +277,7 @@ fn push_reloc(args: &mut Vec<ObjInsArg>, reloc: &ObjReloc) -> Result<()> {
|
|||||||
args.push(ObjInsArg::Reloc);
|
args.push(ObjInsArg::Reloc);
|
||||||
args.push(ObjInsArg::PlainText(")".into()));
|
args.push(ObjInsArg::PlainText(")".into()));
|
||||||
}
|
}
|
||||||
elf::R_MIPS_32 | elf::R_MIPS_26 => {
|
elf::R_MIPS_32 | elf::R_MIPS_26 | elf::R_MIPS_PC16 => {
|
||||||
args.push(ObjInsArg::Reloc);
|
args.push(ObjInsArg::Reloc);
|
||||||
}
|
}
|
||||||
_ => bail!("Unsupported ELF MIPS relocation type {r_type}"),
|
_ => bail!("Unsupported ELF MIPS relocation type {r_type}"),
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use std::{borrow::Cow, collections::BTreeMap};
|
use std::{borrow::Cow, collections::BTreeMap};
|
||||||
|
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
use object::{Architecture, Object, Relocation, RelocationFlags};
|
use object::{Architecture, File, Object, ObjectSymbol, Relocation, RelocationFlags, Symbol};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
diff::DiffObjConfig,
|
diff::DiffObjConfig,
|
||||||
@@ -28,12 +28,19 @@ pub trait ObjArch: Send + Sync {
|
|||||||
config: &DiffObjConfig,
|
config: &DiffObjConfig,
|
||||||
) -> Result<ProcessCodeResult>;
|
) -> Result<ProcessCodeResult>;
|
||||||
|
|
||||||
fn implcit_addend(&self, section: &ObjSection, address: u64, reloc: &Relocation)
|
fn implcit_addend(
|
||||||
-> Result<i64>;
|
&self,
|
||||||
|
file: &File<'_>,
|
||||||
|
section: &ObjSection,
|
||||||
|
address: u64,
|
||||||
|
reloc: &Relocation,
|
||||||
|
) -> Result<i64>;
|
||||||
|
|
||||||
fn demangle(&self, _name: &str) -> Option<String> { None }
|
fn demangle(&self, _name: &str) -> Option<String> { None }
|
||||||
|
|
||||||
fn display_reloc(&self, flags: RelocationFlags) -> Cow<'static, str>;
|
fn display_reloc(&self, flags: RelocationFlags) -> Cow<'static, str>;
|
||||||
|
|
||||||
|
fn symbol_address(&self, symbol: &Symbol) -> u64 { symbol.address() }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ProcessCodeResult {
|
pub struct ProcessCodeResult {
|
||||||
|
|||||||
@@ -150,6 +150,7 @@ impl ObjArch for ObjArchPpc {
|
|||||||
|
|
||||||
fn implcit_addend(
|
fn implcit_addend(
|
||||||
&self,
|
&self,
|
||||||
|
_file: &File<'_>,
|
||||||
_section: &ObjSection,
|
_section: &ObjSection,
|
||||||
address: u64,
|
address: u64,
|
||||||
reloc: &Relocation,
|
reloc: &Relocation,
|
||||||
|
|||||||
@@ -128,6 +128,7 @@ impl ObjArch for ObjArchX86 {
|
|||||||
|
|
||||||
fn implcit_addend(
|
fn implcit_addend(
|
||||||
&self,
|
&self,
|
||||||
|
_file: &File<'_>,
|
||||||
section: &ObjSection,
|
section: &ObjSection,
|
||||||
address: u64,
|
address: u64,
|
||||||
reloc: &Relocation,
|
reloc: &Relocation,
|
||||||
|
|||||||
@@ -11,27 +11,6 @@ use crate::{
|
|||||||
obj::{ObjInfo, ObjSection, SymbolRef},
|
obj::{ObjInfo, ObjSection, SymbolRef},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Compare the addresses and sizes of each symbol in the BSS sections.
|
|
||||||
pub fn diff_bss_section(
|
|
||||||
left: &ObjSection,
|
|
||||||
right: &ObjSection,
|
|
||||||
) -> Result<(ObjSectionDiff, ObjSectionDiff)> {
|
|
||||||
let deadline = Instant::now() + Duration::from_secs(5);
|
|
||||||
let left_sizes = left.symbols.iter().map(|s| (s.section_address, s.size)).collect::<Vec<_>>();
|
|
||||||
let right_sizes = right.symbols.iter().map(|s| (s.section_address, s.size)).collect::<Vec<_>>();
|
|
||||||
let ops = capture_diff_slices_deadline(
|
|
||||||
Algorithm::Patience,
|
|
||||||
&left_sizes,
|
|
||||||
&right_sizes,
|
|
||||||
Some(deadline),
|
|
||||||
);
|
|
||||||
let match_percent = get_diff_ratio(&ops, left_sizes.len(), right_sizes.len()) * 100.0;
|
|
||||||
Ok((
|
|
||||||
ObjSectionDiff { symbols: vec![], data_diff: vec![], match_percent: Some(match_percent) },
|
|
||||||
ObjSectionDiff { symbols: vec![], data_diff: vec![], match_percent: Some(match_percent) },
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn diff_bss_symbol(
|
pub fn diff_bss_symbol(
|
||||||
left_obj: &ObjInfo,
|
left_obj: &ObjInfo,
|
||||||
right_obj: &ObjInfo,
|
right_obj: &ObjInfo,
|
||||||
@@ -65,10 +44,14 @@ pub fn no_diff_symbol(_obj: &ObjInfo, symbol_ref: SymbolRef) -> ObjSymbolDiff {
|
|||||||
pub fn diff_data_section(
|
pub fn diff_data_section(
|
||||||
left: &ObjSection,
|
left: &ObjSection,
|
||||||
right: &ObjSection,
|
right: &ObjSection,
|
||||||
|
left_section_diff: &ObjSectionDiff,
|
||||||
|
right_section_diff: &ObjSectionDiff,
|
||||||
) -> Result<(ObjSectionDiff, ObjSectionDiff)> {
|
) -> Result<(ObjSectionDiff, ObjSectionDiff)> {
|
||||||
let deadline = Instant::now() + Duration::from_secs(5);
|
let deadline = Instant::now() + Duration::from_secs(5);
|
||||||
let left_max = left.symbols.iter().map(|s| s.section_address + s.size).max().unwrap_or(0);
|
let left_max =
|
||||||
let right_max = right.symbols.iter().map(|s| s.section_address + s.size).max().unwrap_or(0);
|
left.symbols.iter().map(|s| s.section_address + s.size).max().unwrap_or(0).min(left.size);
|
||||||
|
let right_max =
|
||||||
|
right.symbols.iter().map(|s| s.section_address + s.size).max().unwrap_or(0).min(right.size);
|
||||||
let left_data = &left.data[..left_max as usize];
|
let left_data = &left.data[..left_max as usize];
|
||||||
let right_data = &right.data[..right_max as usize];
|
let right_data = &right.data[..right_max as usize];
|
||||||
let ops =
|
let ops =
|
||||||
@@ -143,18 +126,18 @@ pub fn diff_data_section(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok((
|
let (mut left_section_diff, mut right_section_diff) =
|
||||||
ObjSectionDiff {
|
diff_generic_section(left, right, left_section_diff, right_section_diff)?;
|
||||||
symbols: vec![],
|
left_section_diff.data_diff = left_diff;
|
||||||
data_diff: left_diff,
|
right_section_diff.data_diff = right_diff;
|
||||||
match_percent: Some(match_percent),
|
// Use the highest match percent between two options:
|
||||||
},
|
// - Left symbols matching right symbols by name
|
||||||
ObjSectionDiff {
|
// - Diff of the data itself
|
||||||
symbols: vec![],
|
if left_section_diff.match_percent.unwrap_or(-1.0) < match_percent {
|
||||||
data_diff: right_diff,
|
left_section_diff.match_percent = Some(match_percent);
|
||||||
match_percent: Some(match_percent),
|
right_section_diff.match_percent = Some(match_percent);
|
||||||
},
|
}
|
||||||
))
|
Ok((left_section_diff, right_section_diff))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn diff_data_symbol(
|
pub fn diff_data_symbol(
|
||||||
@@ -195,21 +178,56 @@ pub fn diff_data_symbol(
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compare the text sections of two object files.
|
/// Compares a section of two object files.
|
||||||
/// This essentially adds up the match percentage of each symbol in the text section.
|
/// This essentially adds up the match percentage of each symbol in the section.
|
||||||
pub fn diff_text_section(
|
pub fn diff_generic_section(
|
||||||
left: &ObjSection,
|
left: &ObjSection,
|
||||||
_right: &ObjSection,
|
_right: &ObjSection,
|
||||||
left_diff: &ObjSectionDiff,
|
left_diff: &ObjSectionDiff,
|
||||||
_right_diff: &ObjSectionDiff,
|
_right_diff: &ObjSectionDiff,
|
||||||
) -> Result<(ObjSectionDiff, ObjSectionDiff)> {
|
) -> Result<(ObjSectionDiff, ObjSectionDiff)> {
|
||||||
let match_percent = left
|
let match_percent = if left_diff.symbols.iter().all(|d| d.match_percent == Some(100.0)) {
|
||||||
.symbols
|
100.0 // Avoid fp precision issues
|
||||||
|
} else {
|
||||||
|
left.symbols
|
||||||
.iter()
|
.iter()
|
||||||
.zip(left_diff.symbols.iter())
|
.zip(left_diff.symbols.iter())
|
||||||
.map(|(s, d)| d.match_percent.unwrap_or(0.0) * s.size as f32)
|
.map(|(s, d)| d.match_percent.unwrap_or(0.0) * s.size as f32)
|
||||||
.sum::<f32>()
|
.sum::<f32>()
|
||||||
/ left.size as f32;
|
/ left.size as f32
|
||||||
|
};
|
||||||
|
Ok((
|
||||||
|
ObjSectionDiff { symbols: vec![], data_diff: vec![], match_percent: Some(match_percent) },
|
||||||
|
ObjSectionDiff { symbols: vec![], data_diff: vec![], match_percent: Some(match_percent) },
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compare the addresses and sizes of each symbol in the BSS sections.
|
||||||
|
pub fn diff_bss_section(
|
||||||
|
left: &ObjSection,
|
||||||
|
right: &ObjSection,
|
||||||
|
left_diff: &ObjSectionDiff,
|
||||||
|
right_diff: &ObjSectionDiff,
|
||||||
|
) -> Result<(ObjSectionDiff, ObjSectionDiff)> {
|
||||||
|
let deadline = Instant::now() + Duration::from_secs(5);
|
||||||
|
let left_sizes = left.symbols.iter().map(|s| (s.section_address, s.size)).collect::<Vec<_>>();
|
||||||
|
let right_sizes = right.symbols.iter().map(|s| (s.section_address, s.size)).collect::<Vec<_>>();
|
||||||
|
let ops = capture_diff_slices_deadline(
|
||||||
|
Algorithm::Patience,
|
||||||
|
&left_sizes,
|
||||||
|
&right_sizes,
|
||||||
|
Some(deadline),
|
||||||
|
);
|
||||||
|
let mut match_percent = get_diff_ratio(&ops, left_sizes.len(), right_sizes.len()) * 100.0;
|
||||||
|
|
||||||
|
// Use the highest match percent between two options:
|
||||||
|
// - Left symbols matching right symbols by name
|
||||||
|
// - Diff of the addresses and sizes of each symbol
|
||||||
|
let (generic_diff, _) = diff_generic_section(left, right, left_diff, right_diff)?;
|
||||||
|
if generic_diff.match_percent.unwrap_or(-1.0) > match_percent {
|
||||||
|
match_percent = generic_diff.match_percent.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
ObjSectionDiff { symbols: vec![], data_diff: vec![], match_percent: Some(match_percent) },
|
ObjSectionDiff { symbols: vec![], data_diff: vec![], match_percent: Some(match_percent) },
|
||||||
ObjSectionDiff { symbols: vec![], data_diff: vec![], match_percent: Some(match_percent) },
|
ObjSectionDiff { symbols: vec![], data_diff: vec![], match_percent: Some(match_percent) },
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ pub enum DiffText<'a> {
|
|||||||
/// Instruction argument
|
/// Instruction argument
|
||||||
Argument(&'a ObjInsArgValue, Option<&'a ObjInsArgDiff>),
|
Argument(&'a ObjInsArgValue, Option<&'a ObjInsArgDiff>),
|
||||||
/// Branch destination
|
/// Branch destination
|
||||||
BranchDest(u64),
|
BranchDest(u64, Option<&'a ObjInsArgDiff>),
|
||||||
/// Symbol name
|
/// Symbol name
|
||||||
Symbol(&'a ObjSymbol),
|
Symbol(&'a ObjSymbol),
|
||||||
/// Number of spaces
|
/// Number of spaces
|
||||||
@@ -62,12 +62,12 @@ pub fn display_diff<E>(
|
|||||||
if i == 0 {
|
if i == 0 {
|
||||||
cb(DiffText::Spacing(1))?;
|
cb(DiffText::Spacing(1))?;
|
||||||
}
|
}
|
||||||
|
let diff = ins_diff.arg_diff.get(i).and_then(|o| o.as_ref());
|
||||||
match arg {
|
match arg {
|
||||||
ObjInsArg::PlainText(s) => {
|
ObjInsArg::PlainText(s) => {
|
||||||
cb(DiffText::Basic(s))?;
|
cb(DiffText::Basic(s))?;
|
||||||
}
|
}
|
||||||
ObjInsArg::Arg(v) => {
|
ObjInsArg::Arg(v) => {
|
||||||
let diff = ins_diff.arg_diff.get(i).and_then(|o| o.as_ref());
|
|
||||||
cb(DiffText::Argument(v, diff))?;
|
cb(DiffText::Argument(v, diff))?;
|
||||||
}
|
}
|
||||||
ObjInsArg::Reloc => {
|
ObjInsArg::Reloc => {
|
||||||
@@ -75,7 +75,7 @@ pub fn display_diff<E>(
|
|||||||
}
|
}
|
||||||
ObjInsArg::BranchDest(dest) => {
|
ObjInsArg::BranchDest(dest) => {
|
||||||
if let Some(dest) = dest.checked_sub(base_addr) {
|
if let Some(dest) = dest.checked_sub(base_addr) {
|
||||||
cb(DiffText::BranchDest(dest))?;
|
cb(DiffText::BranchDest(dest, diff))?;
|
||||||
} else {
|
} else {
|
||||||
cb(DiffText::Basic("<unknown>"))?;
|
cb(DiffText::Basic("<unknown>"))?;
|
||||||
}
|
}
|
||||||
@@ -107,7 +107,9 @@ impl PartialEq<DiffText<'_>> for HighlightKind {
|
|||||||
(HighlightKind::Opcode(a), DiffText::Opcode(_, b)) => a == b,
|
(HighlightKind::Opcode(a), DiffText::Opcode(_, b)) => a == b,
|
||||||
(HighlightKind::Arg(a), DiffText::Argument(b, _)) => a.loose_eq(b),
|
(HighlightKind::Arg(a), DiffText::Argument(b, _)) => a.loose_eq(b),
|
||||||
(HighlightKind::Symbol(a), DiffText::Symbol(b)) => a == &b.name,
|
(HighlightKind::Symbol(a), DiffText::Symbol(b)) => a == &b.name,
|
||||||
(HighlightKind::Address(a), DiffText::Address(b) | DiffText::BranchDest(b)) => a == b,
|
(HighlightKind::Address(a), DiffText::Address(b) | DiffText::BranchDest(b, _)) => {
|
||||||
|
a == b
|
||||||
|
}
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -123,7 +125,7 @@ impl From<DiffText<'_>> for HighlightKind {
|
|||||||
DiffText::Opcode(_, op) => HighlightKind::Opcode(op),
|
DiffText::Opcode(_, op) => HighlightKind::Opcode(op),
|
||||||
DiffText::Argument(arg, _) => HighlightKind::Arg(arg.clone()),
|
DiffText::Argument(arg, _) => HighlightKind::Arg(arg.clone()),
|
||||||
DiffText::Symbol(sym) => HighlightKind::Symbol(sym.name.to_string()),
|
DiffText::Symbol(sym) => HighlightKind::Symbol(sym.name.to_string()),
|
||||||
DiffText::Address(addr) | DiffText::BranchDest(addr) => HighlightKind::Address(addr),
|
DiffText::Address(addr) | DiffText::BranchDest(addr, _) => HighlightKind::Address(addr),
|
||||||
_ => HighlightKind::None,
|
_ => HighlightKind::None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use crate::{
|
|||||||
code::{diff_code, no_diff_code, process_code_symbol},
|
code::{diff_code, no_diff_code, process_code_symbol},
|
||||||
data::{
|
data::{
|
||||||
diff_bss_section, diff_bss_symbol, diff_data_section, diff_data_symbol,
|
diff_bss_section, diff_bss_symbol, diff_data_section, diff_data_symbol,
|
||||||
diff_text_section, no_diff_symbol,
|
diff_generic_section, no_diff_symbol,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
obj::{ObjInfo, ObjIns, ObjSection, ObjSectionKind, ObjSymbol, SymbolRef},
|
obj::{ObjInfo, ObjIns, ObjSection, ObjSectionKind, ObjSymbol, SymbolRef},
|
||||||
@@ -93,6 +93,58 @@ pub enum MipsInstrCategory {
|
|||||||
R5900,
|
R5900,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(
|
||||||
|
Debug,
|
||||||
|
Copy,
|
||||||
|
Clone,
|
||||||
|
Default,
|
||||||
|
Eq,
|
||||||
|
PartialEq,
|
||||||
|
serde::Deserialize,
|
||||||
|
serde::Serialize,
|
||||||
|
strum::VariantArray,
|
||||||
|
strum::EnumMessage,
|
||||||
|
)]
|
||||||
|
pub enum ArmArchVersion {
|
||||||
|
#[default]
|
||||||
|
#[strum(message = "Auto (default)")]
|
||||||
|
Auto,
|
||||||
|
#[strum(message = "ARMv4T (GBA)")]
|
||||||
|
V4T,
|
||||||
|
#[strum(message = "ARMv5TE (DS)")]
|
||||||
|
V5TE,
|
||||||
|
#[strum(message = "ARMv6K (3DS)")]
|
||||||
|
V6K,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(
|
||||||
|
Debug,
|
||||||
|
Copy,
|
||||||
|
Clone,
|
||||||
|
Default,
|
||||||
|
Eq,
|
||||||
|
PartialEq,
|
||||||
|
serde::Deserialize,
|
||||||
|
serde::Serialize,
|
||||||
|
strum::VariantArray,
|
||||||
|
strum::EnumMessage,
|
||||||
|
)]
|
||||||
|
pub enum ArmR9Usage {
|
||||||
|
#[default]
|
||||||
|
#[strum(
|
||||||
|
message = "R9 or V6 (default)",
|
||||||
|
detailed_message = "Use R9 as a general-purpose register."
|
||||||
|
)]
|
||||||
|
GeneralPurpose,
|
||||||
|
#[strum(
|
||||||
|
message = "SB (static base)",
|
||||||
|
detailed_message = "Used for position-independent data (PID)."
|
||||||
|
)]
|
||||||
|
Sb,
|
||||||
|
#[strum(message = "TR (TLS register)", detailed_message = "Used for thread-local storage.")]
|
||||||
|
Tr,
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
const fn default_true() -> bool { true }
|
const fn default_true() -> bool { true }
|
||||||
|
|
||||||
@@ -102,11 +154,20 @@ pub struct DiffObjConfig {
|
|||||||
pub relax_reloc_diffs: bool,
|
pub relax_reloc_diffs: bool,
|
||||||
#[serde(default = "default_true")]
|
#[serde(default = "default_true")]
|
||||||
pub space_between_args: bool,
|
pub space_between_args: bool,
|
||||||
|
pub combine_data_sections: bool,
|
||||||
// x86
|
// x86
|
||||||
pub x86_formatter: X86Formatter,
|
pub x86_formatter: X86Formatter,
|
||||||
// MIPS
|
// MIPS
|
||||||
pub mips_abi: MipsAbi,
|
pub mips_abi: MipsAbi,
|
||||||
pub mips_instr_category: MipsInstrCategory,
|
pub mips_instr_category: MipsInstrCategory,
|
||||||
|
// ARM
|
||||||
|
pub arm_arch_version: ArmArchVersion,
|
||||||
|
pub arm_unified_syntax: bool,
|
||||||
|
pub arm_av_registers: bool,
|
||||||
|
pub arm_r9_usage: ArmR9Usage,
|
||||||
|
pub arm_sl_usage: bool,
|
||||||
|
pub arm_fp_usage: bool,
|
||||||
|
pub arm_ip_usage: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for DiffObjConfig {
|
impl Default for DiffObjConfig {
|
||||||
@@ -114,9 +175,17 @@ impl Default for DiffObjConfig {
|
|||||||
Self {
|
Self {
|
||||||
relax_reloc_diffs: false,
|
relax_reloc_diffs: false,
|
||||||
space_between_args: true,
|
space_between_args: true,
|
||||||
|
combine_data_sections: false,
|
||||||
x86_formatter: Default::default(),
|
x86_formatter: Default::default(),
|
||||||
mips_abi: Default::default(),
|
mips_abi: Default::default(),
|
||||||
mips_instr_category: Default::default(),
|
mips_instr_category: Default::default(),
|
||||||
|
arm_arch_version: Default::default(),
|
||||||
|
arm_unified_syntax: true,
|
||||||
|
arm_av_registers: false,
|
||||||
|
arm_r9_usage: Default::default(),
|
||||||
|
arm_sl_usage: false,
|
||||||
|
arm_fp_usage: false,
|
||||||
|
arm_ip_usage: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -417,7 +486,7 @@ pub fn diff_objs(
|
|||||||
ObjSectionKind::Code => {
|
ObjSectionKind::Code => {
|
||||||
let left_section_diff = left_out.section_diff(left_section_idx);
|
let left_section_diff = left_out.section_diff(left_section_idx);
|
||||||
let right_section_diff = right_out.section_diff(right_section_idx);
|
let right_section_diff = right_out.section_diff(right_section_idx);
|
||||||
let (left_diff, right_diff) = diff_text_section(
|
let (left_diff, right_diff) = diff_generic_section(
|
||||||
left_section,
|
left_section,
|
||||||
right_section,
|
right_section,
|
||||||
left_section_diff,
|
left_section_diff,
|
||||||
@@ -427,12 +496,26 @@ pub fn diff_objs(
|
|||||||
right_out.section_diff_mut(right_section_idx).merge(right_diff);
|
right_out.section_diff_mut(right_section_idx).merge(right_diff);
|
||||||
}
|
}
|
||||||
ObjSectionKind::Data => {
|
ObjSectionKind::Data => {
|
||||||
let (left_diff, right_diff) = diff_data_section(left_section, right_section)?;
|
let left_section_diff = left_out.section_diff(left_section_idx);
|
||||||
|
let right_section_diff = right_out.section_diff(right_section_idx);
|
||||||
|
let (left_diff, right_diff) = diff_data_section(
|
||||||
|
left_section,
|
||||||
|
right_section,
|
||||||
|
left_section_diff,
|
||||||
|
right_section_diff,
|
||||||
|
)?;
|
||||||
left_out.section_diff_mut(left_section_idx).merge(left_diff);
|
left_out.section_diff_mut(left_section_idx).merge(left_diff);
|
||||||
right_out.section_diff_mut(right_section_idx).merge(right_diff);
|
right_out.section_diff_mut(right_section_idx).merge(right_diff);
|
||||||
}
|
}
|
||||||
ObjSectionKind::Bss => {
|
ObjSectionKind::Bss => {
|
||||||
let (left_diff, right_diff) = diff_bss_section(left_section, right_section)?;
|
let left_section_diff = left_out.section_diff(left_section_idx);
|
||||||
|
let right_section_diff = right_out.section_diff(right_section_idx);
|
||||||
|
let (left_diff, right_diff) = diff_bss_section(
|
||||||
|
left_section,
|
||||||
|
right_section,
|
||||||
|
left_section_diff,
|
||||||
|
right_section_diff,
|
||||||
|
)?;
|
||||||
left_out.section_diff_mut(left_section_idx).merge(left_diff);
|
left_out.section_diff_mut(left_section_idx).merge(left_diff);
|
||||||
right_out.section_diff_mut(right_section_idx).merge(right_diff);
|
right_out.section_diff_mut(right_section_idx).merge(right_diff);
|
||||||
}
|
}
|
||||||
@@ -475,8 +558,8 @@ fn matching_symbols(
|
|||||||
for (symbol_idx, symbol) in section.symbols.iter().enumerate() {
|
for (symbol_idx, symbol) in section.symbols.iter().enumerate() {
|
||||||
let symbol_match = SymbolMatch {
|
let symbol_match = SymbolMatch {
|
||||||
left: Some(SymbolRef { section_idx, symbol_idx }),
|
left: Some(SymbolRef { section_idx, symbol_idx }),
|
||||||
right: find_symbol(right, symbol, section),
|
right: find_symbol(right, symbol, section, Some(&right_used)),
|
||||||
prev: find_symbol(prev, symbol, section),
|
prev: find_symbol(prev, symbol, section, None),
|
||||||
section_kind: section.kind,
|
section_kind: section.kind,
|
||||||
};
|
};
|
||||||
matches.push(symbol_match);
|
matches.push(symbol_match);
|
||||||
@@ -508,7 +591,7 @@ fn matching_symbols(
|
|||||||
matches.push(SymbolMatch {
|
matches.push(SymbolMatch {
|
||||||
left: None,
|
left: None,
|
||||||
right: Some(symbol_ref),
|
right: Some(symbol_ref),
|
||||||
prev: find_symbol(prev, symbol, section),
|
prev: find_symbol(prev, symbol, section, None),
|
||||||
section_kind: section.kind,
|
section_kind: section.kind,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -529,10 +612,25 @@ fn matching_symbols(
|
|||||||
Ok(matches)
|
Ok(matches)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn unmatched_symbols<'section, 'used>(
|
||||||
|
section: &'section ObjSection,
|
||||||
|
section_idx: usize,
|
||||||
|
used: Option<&'used HashSet<SymbolRef>>,
|
||||||
|
) -> impl Iterator<Item = (usize, &'section ObjSymbol)> + 'used
|
||||||
|
where
|
||||||
|
'section: 'used,
|
||||||
|
{
|
||||||
|
section.symbols.iter().enumerate().filter(move |&(symbol_idx, _)| {
|
||||||
|
// Skip symbols that have already been matched
|
||||||
|
!used.map(|u| u.contains(&SymbolRef { section_idx, symbol_idx })).unwrap_or(false)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn find_symbol(
|
fn find_symbol(
|
||||||
obj: Option<&ObjInfo>,
|
obj: Option<&ObjInfo>,
|
||||||
in_symbol: &ObjSymbol,
|
in_symbol: &ObjSymbol,
|
||||||
in_section: &ObjSection,
|
in_section: &ObjSection,
|
||||||
|
used: Option<&HashSet<SymbolRef>>,
|
||||||
) -> Option<SymbolRef> {
|
) -> Option<SymbolRef> {
|
||||||
let obj = obj?;
|
let obj = obj?;
|
||||||
// Try to find an exact name match
|
// Try to find an exact name match
|
||||||
@@ -540,8 +638,8 @@ fn find_symbol(
|
|||||||
if section.kind != in_section.kind {
|
if section.kind != in_section.kind {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if let Some(symbol_idx) =
|
if let Some((symbol_idx, _)) = unmatched_symbols(section, section_idx, used)
|
||||||
section.symbols.iter().position(|symbol| symbol.name == in_symbol.name)
|
.find(|(_, symbol)| symbol.name == in_symbol.name)
|
||||||
{
|
{
|
||||||
return Some(SymbolRef { section_idx, symbol_idx });
|
return Some(SymbolRef { section_idx, symbol_idx });
|
||||||
}
|
}
|
||||||
@@ -554,9 +652,33 @@ fn find_symbol(
|
|||||||
if let Some((section_idx, section)) =
|
if let Some((section_idx, section)) =
|
||||||
obj.sections.iter().enumerate().find(|(_, s)| s.name == in_section.name)
|
obj.sections.iter().enumerate().find(|(_, s)| s.name == in_section.name)
|
||||||
{
|
{
|
||||||
if let Some(symbol_idx) = section.symbols.iter().position(|symbol| {
|
if let Some((symbol_idx, _)) =
|
||||||
|
unmatched_symbols(section, section_idx, used).find(|(_, symbol)| {
|
||||||
symbol.address == in_symbol.address && symbol.name.starts_with('@')
|
symbol.address == in_symbol.address && symbol.name.starts_with('@')
|
||||||
}) {
|
})
|
||||||
|
{
|
||||||
|
return Some(SymbolRef { section_idx, symbol_idx });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Match Metrowerks symbol$1234 against symbol$2345
|
||||||
|
if let Some((prefix, suffix)) = in_symbol.name.split_once('$') {
|
||||||
|
if !suffix.chars().all(char::is_numeric) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
for (section_idx, section) in obj.sections.iter().enumerate() {
|
||||||
|
if section.kind != in_section.kind {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if let Some((symbol_idx, _)) =
|
||||||
|
unmatched_symbols(section, section_idx, used).find(|&(_, symbol)| {
|
||||||
|
if let Some((p, s)) = symbol.name.split_once('$') {
|
||||||
|
prefix == p && s.chars().all(char::is_numeric)
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
{
|
||||||
return Some(SymbolRef { section_idx, symbol_idx });
|
return Some(SymbolRef { section_idx, symbol_idx });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ pub mod split_meta;
|
|||||||
|
|
||||||
use std::{borrow::Cow, collections::BTreeMap, fmt, path::PathBuf};
|
use std::{borrow::Cow, collections::BTreeMap, fmt, path::PathBuf};
|
||||||
|
|
||||||
|
use cwextab::*;
|
||||||
use filetime::FileTime;
|
use filetime::FileTime;
|
||||||
use flagset::{flags, FlagSet};
|
use flagset::{flags, FlagSet};
|
||||||
use object::RelocationFlags;
|
use object::RelocationFlags;
|
||||||
@@ -113,6 +114,9 @@ pub struct ObjIns {
|
|||||||
pub struct ObjSymbol {
|
pub struct ObjSymbol {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub demangled_name: Option<String>,
|
pub demangled_name: Option<String>,
|
||||||
|
pub has_extab: bool,
|
||||||
|
pub extab_name: Option<String>,
|
||||||
|
pub extabindex_name: Option<String>,
|
||||||
pub address: u64,
|
pub address: u64,
|
||||||
pub section_address: u64,
|
pub section_address: u64,
|
||||||
pub size: u64,
|
pub size: u64,
|
||||||
@@ -123,6 +127,13 @@ pub struct ObjSymbol {
|
|||||||
pub virtual_address: Option<u64>,
|
pub virtual_address: Option<u64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ObjExtab {
|
||||||
|
pub func: ObjSymbol,
|
||||||
|
pub data: ExceptionTableData,
|
||||||
|
pub dtors: Vec<ObjSymbol>,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct ObjInfo {
|
pub struct ObjInfo {
|
||||||
pub arch: Box<dyn ObjArch>,
|
pub arch: Box<dyn ObjArch>,
|
||||||
pub path: PathBuf,
|
pub path: PathBuf,
|
||||||
@@ -130,6 +141,8 @@ pub struct ObjInfo {
|
|||||||
pub sections: Vec<ObjSection>,
|
pub sections: Vec<ObjSection>,
|
||||||
/// Common BSS symbols
|
/// Common BSS symbols
|
||||||
pub common: Vec<ObjSymbol>,
|
pub common: Vec<ObjSymbol>,
|
||||||
|
/// Exception tables
|
||||||
|
pub extab: Option<Vec<ObjExtab>>,
|
||||||
/// Split object metadata (.note.split section)
|
/// Split object metadata (.note.split section)
|
||||||
pub split_meta: Option<SplitMeta>,
|
pub split_meta: Option<SplitMeta>,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,22 @@
|
|||||||
use std::{fs, io::Cursor, path::Path};
|
use std::{collections::HashSet, fs, io::Cursor, path::Path};
|
||||||
|
|
||||||
use anyhow::{anyhow, bail, ensure, Context, Result};
|
use anyhow::{anyhow, bail, ensure, Context, Result};
|
||||||
use byteorder::{BigEndian, ReadBytesExt};
|
use byteorder::{BigEndian, ReadBytesExt};
|
||||||
|
use cwextab::decode_extab;
|
||||||
use filetime::FileTime;
|
use filetime::FileTime;
|
||||||
use flagset::Flags;
|
use flagset::Flags;
|
||||||
use object::{
|
use object::{
|
||||||
BinaryFormat, File, Object, ObjectSection, ObjectSymbol, RelocationTarget, SectionIndex,
|
Architecture, BinaryFormat, File, Object, ObjectSection, ObjectSymbol, RelocationTarget,
|
||||||
SectionKind, Symbol, SymbolKind, SymbolScope, SymbolSection,
|
SectionIndex, SectionKind, Symbol, SymbolKind, SymbolScope, SymbolSection,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
arch::{new_arch, ObjArch},
|
arch::{new_arch, ObjArch},
|
||||||
|
diff::DiffObjConfig,
|
||||||
obj::{
|
obj::{
|
||||||
split_meta::{SplitMeta, SPLITMETA_SECTION},
|
split_meta::{SplitMeta, SPLITMETA_SECTION},
|
||||||
ObjInfo, ObjReloc, ObjSection, ObjSectionKind, ObjSymbol, ObjSymbolFlagSet, ObjSymbolFlags,
|
ObjExtab, ObjInfo, ObjReloc, ObjSection, ObjSectionKind, ObjSymbol, ObjSymbolFlagSet,
|
||||||
|
ObjSymbolFlags,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -54,12 +57,13 @@ fn to_obj_symbol(
|
|||||||
if obj_file.format() == BinaryFormat::Elf && symbol.scope() == SymbolScope::Linkage {
|
if obj_file.format() == BinaryFormat::Elf && symbol.scope() == SymbolScope::Linkage {
|
||||||
flags = ObjSymbolFlagSet(flags.0 | ObjSymbolFlags::Hidden);
|
flags = ObjSymbolFlagSet(flags.0 | ObjSymbolFlags::Hidden);
|
||||||
}
|
}
|
||||||
|
let address = arch.symbol_address(symbol);
|
||||||
let section_address = if let Some(section) =
|
let section_address = if let Some(section) =
|
||||||
symbol.section_index().and_then(|idx| obj_file.section_by_index(idx).ok())
|
symbol.section_index().and_then(|idx| obj_file.section_by_index(idx).ok())
|
||||||
{
|
{
|
||||||
symbol.address() - section.address()
|
address - section.address()
|
||||||
} else {
|
} else {
|
||||||
symbol.address()
|
address
|
||||||
};
|
};
|
||||||
let demangled_name = arch.demangle(name);
|
let demangled_name = arch.demangle(name);
|
||||||
// Find the virtual address for the symbol if available
|
// Find the virtual address for the symbol if available
|
||||||
@@ -69,7 +73,10 @@ fn to_obj_symbol(
|
|||||||
Ok(ObjSymbol {
|
Ok(ObjSymbol {
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
demangled_name,
|
demangled_name,
|
||||||
address: symbol.address(),
|
has_extab: false,
|
||||||
|
extab_name: None,
|
||||||
|
extabindex_name: None,
|
||||||
|
address,
|
||||||
section_address,
|
section_address,
|
||||||
size: symbol.size(),
|
size: symbol.size(),
|
||||||
size_known: symbol.size() != 0,
|
size_known: symbol.size() != 0,
|
||||||
@@ -142,7 +149,7 @@ fn symbols_by_section(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
result.sort_by_key(|v| v.address);
|
result.sort_by(|a, b| a.address.cmp(&b.address).then(a.size.cmp(&b.size)));
|
||||||
let mut iter = result.iter_mut().peekable();
|
let mut iter = result.iter_mut().peekable();
|
||||||
while let Some(symbol) = iter.next() {
|
while let Some(symbol) = iter.next() {
|
||||||
if symbol.size == 0 {
|
if symbol.size == 0 {
|
||||||
@@ -153,6 +160,23 @@ fn symbols_by_section(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if result.is_empty() {
|
||||||
|
// Dummy symbol for empty sections
|
||||||
|
result.push(ObjSymbol {
|
||||||
|
name: format!("[{}]", section.name),
|
||||||
|
demangled_name: None,
|
||||||
|
has_extab: false,
|
||||||
|
extab_name: None,
|
||||||
|
extabindex_name: None,
|
||||||
|
address: 0,
|
||||||
|
section_address: 0,
|
||||||
|
size: section.size,
|
||||||
|
size_known: true,
|
||||||
|
flags: Default::default(),
|
||||||
|
addend: 0,
|
||||||
|
virtual_address: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,6 +192,111 @@ fn common_symbols(
|
|||||||
.collect::<Result<Vec<ObjSymbol>>>()
|
.collect::<Result<Vec<ObjSymbol>>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn section_by_name<'a>(sections: &'a mut [ObjSection], name: &str) -> Option<&'a mut ObjSection> {
|
||||||
|
sections.iter_mut().find(|section| section.name == name)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn exception_tables(
|
||||||
|
sections: &mut [ObjSection],
|
||||||
|
obj_file: &File<'_>,
|
||||||
|
) -> Result<Option<Vec<ObjExtab>>> {
|
||||||
|
//PowerPC only
|
||||||
|
if obj_file.architecture() != Architecture::PowerPc {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Find the extab/extabindex sections
|
||||||
|
let extab_section = match section_by_name(sections, "extab") {
|
||||||
|
Some(section) => section.clone(),
|
||||||
|
None => {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let extabindex_section = match section_by_name(sections, "extabindex") {
|
||||||
|
Some(section) => section.clone(),
|
||||||
|
None => {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let text_section = match section_by_name(sections, ".text") {
|
||||||
|
Some(section) => section,
|
||||||
|
None => bail!(".text section is somehow missing, this should not happen"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut result: Vec<ObjExtab> = vec![];
|
||||||
|
let extab_symbol_count = extab_section.symbols.len();
|
||||||
|
let extabindex_symbol_count = extabindex_section.symbols.len();
|
||||||
|
let extab_reloc_count = extab_section.relocations.len();
|
||||||
|
let table_count = extab_symbol_count;
|
||||||
|
let mut extab_reloc_index: usize = 0;
|
||||||
|
|
||||||
|
//Make sure that the number of symbols in the extab/extabindex section matches. If not, exit early
|
||||||
|
if extab_symbol_count != extabindex_symbol_count {
|
||||||
|
bail!("Extab/Extabindex symbol counts do not match");
|
||||||
|
}
|
||||||
|
|
||||||
|
//Convert the extab/extabindex section data
|
||||||
|
|
||||||
|
//Go through each extabindex entry
|
||||||
|
for i in 0..table_count {
|
||||||
|
let extabindex = &extabindex_section.symbols[i];
|
||||||
|
|
||||||
|
/* Get the function symbol and extab symbol from the extabindex relocations array. Each extabindex
|
||||||
|
entry has two relocations (the first for the function, the second for the extab entry) */
|
||||||
|
let extab_func = extabindex_section.relocations[i * 2].target.clone();
|
||||||
|
let extab = &extabindex_section.relocations[(i * 2) + 1].target;
|
||||||
|
|
||||||
|
let extab_start_addr = extab.address;
|
||||||
|
let extab_end_addr = extab_start_addr + extab.size;
|
||||||
|
|
||||||
|
//Find the function in the text section, and set the has extab flag
|
||||||
|
for i in 0..text_section.symbols.len() {
|
||||||
|
let func = &mut text_section.symbols[i];
|
||||||
|
if func.name == extab_func.name {
|
||||||
|
func.has_extab = true;
|
||||||
|
func.extab_name = Some(extab.name.clone());
|
||||||
|
func.extabindex_name = Some(extabindex.name.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Iterate through the list of extab relocations, continuing until we hit a relocation
|
||||||
|
that isn't within the current extab symbol. Get the target dtor function symbol from
|
||||||
|
each relocation used, and add them to the list. */
|
||||||
|
let mut dtors: Vec<ObjSymbol> = vec![];
|
||||||
|
|
||||||
|
while extab_reloc_index < extab_reloc_count {
|
||||||
|
let extab_reloc = &extab_section.relocations[extab_reloc_index];
|
||||||
|
//If the current entry is past the current extab table, stop here
|
||||||
|
if extab_reloc.address >= extab_end_addr {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Otherwise, the current relocation is used by the current table
|
||||||
|
dtors.push(extab_reloc.target.clone());
|
||||||
|
//Go to the next entry
|
||||||
|
extab_reloc_index += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Decode the extab data
|
||||||
|
let start_index = extab_start_addr as usize;
|
||||||
|
let end_index = extab_end_addr as usize;
|
||||||
|
let extab_data = extab_section.data[start_index..end_index].try_into().unwrap();
|
||||||
|
let data = match decode_extab(extab_data) {
|
||||||
|
Some(decoded_data) => decoded_data,
|
||||||
|
None => {
|
||||||
|
log::warn!("Exception table decoding failed for function {}", extab_func.name);
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//Add the new entry to the list
|
||||||
|
let entry = ObjExtab { func: extab_func, data, dtors };
|
||||||
|
result.push(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Some(result))
|
||||||
|
}
|
||||||
|
|
||||||
fn find_section_symbol(
|
fn find_section_symbol(
|
||||||
arch: &dyn ObjArch,
|
arch: &dyn ObjArch,
|
||||||
obj_file: &File<'_>,
|
obj_file: &File<'_>,
|
||||||
@@ -203,6 +332,9 @@ fn find_section_symbol(
|
|||||||
Ok(ObjSymbol {
|
Ok(ObjSymbol {
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
demangled_name: None,
|
demangled_name: None,
|
||||||
|
has_extab: false,
|
||||||
|
extab_name: None,
|
||||||
|
extabindex_name: None,
|
||||||
address: offset,
|
address: offset,
|
||||||
section_address: address - section.address(),
|
section_address: address - section.address(),
|
||||||
size: 0,
|
size: 0,
|
||||||
@@ -249,7 +381,7 @@ fn relocations_by_section(
|
|||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
let addend = if reloc.has_implicit_addend() {
|
let addend = if reloc.has_implicit_addend() {
|
||||||
arch.implcit_addend(section, address, &reloc)?
|
arch.implcit_addend(obj_file, section, address, &reloc)?
|
||||||
} else {
|
} else {
|
||||||
reloc.addend()
|
reloc.addend()
|
||||||
};
|
};
|
||||||
@@ -361,7 +493,108 @@ fn line_info(obj_file: &File<'_>, sections: &mut [ObjSection]) -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read(obj_path: &Path) -> Result<ObjInfo> {
|
fn update_combined_symbol(symbol: ObjSymbol, address_change: i64) -> Result<ObjSymbol> {
|
||||||
|
Ok(ObjSymbol {
|
||||||
|
name: symbol.name,
|
||||||
|
demangled_name: symbol.demangled_name,
|
||||||
|
has_extab: symbol.has_extab,
|
||||||
|
extab_name: symbol.extab_name,
|
||||||
|
extabindex_name: symbol.extabindex_name,
|
||||||
|
address: (symbol.address as i64 + address_change).try_into()?,
|
||||||
|
section_address: (symbol.section_address as i64 + address_change).try_into()?,
|
||||||
|
size: symbol.size,
|
||||||
|
size_known: symbol.size_known,
|
||||||
|
flags: symbol.flags,
|
||||||
|
addend: symbol.addend,
|
||||||
|
virtual_address: if let Some(virtual_address) = symbol.virtual_address {
|
||||||
|
Some((virtual_address as i64 + address_change).try_into()?)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn combine_sections(section: ObjSection, combine: ObjSection) -> Result<ObjSection> {
|
||||||
|
let mut data = section.data;
|
||||||
|
data.extend(combine.data);
|
||||||
|
|
||||||
|
let address_change: i64 = (section.address + section.size) as i64 - combine.address as i64;
|
||||||
|
let mut symbols = section.symbols;
|
||||||
|
for symbol in combine.symbols {
|
||||||
|
symbols.push(update_combined_symbol(symbol, address_change)?);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut relocations = section.relocations;
|
||||||
|
for reloc in combine.relocations {
|
||||||
|
relocations.push(ObjReloc {
|
||||||
|
flags: reloc.flags,
|
||||||
|
address: (reloc.address as i64 + address_change).try_into()?,
|
||||||
|
target: reloc.target, // TODO: Should be updated?
|
||||||
|
target_section: reloc.target_section, // TODO: Same as above
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut line_info = section.line_info;
|
||||||
|
for (addr, line) in combine.line_info {
|
||||||
|
let key = (addr as i64 + address_change).try_into()?;
|
||||||
|
line_info.insert(key, line);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(ObjSection {
|
||||||
|
name: section.name,
|
||||||
|
kind: section.kind,
|
||||||
|
address: section.address,
|
||||||
|
size: section.size + combine.size,
|
||||||
|
data,
|
||||||
|
orig_index: section.orig_index,
|
||||||
|
symbols,
|
||||||
|
relocations,
|
||||||
|
virtual_address: section.virtual_address,
|
||||||
|
line_info,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn combine_data_sections(sections: &mut Vec<ObjSection>) -> Result<()> {
|
||||||
|
let names_to_combine: HashSet<_> = sections
|
||||||
|
.iter()
|
||||||
|
.filter(|s| s.kind == ObjSectionKind::Data)
|
||||||
|
.map(|s| s.name.clone())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
for name in names_to_combine {
|
||||||
|
// Take section with lowest index
|
||||||
|
let (mut section_index, _) = sections
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter(|(_, s)| s.name == name)
|
||||||
|
.min_by_key(|(_, s)| s.orig_index)
|
||||||
|
// Should not happen
|
||||||
|
.context("No combine section found with name")?;
|
||||||
|
let mut section = sections.remove(section_index);
|
||||||
|
|
||||||
|
// Remove equally named sections
|
||||||
|
let mut combines = vec![];
|
||||||
|
for i in (0..sections.len()).rev() {
|
||||||
|
if sections[i].name != name || sections[i].orig_index == section.orig_index {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
combines.push(sections.remove(i));
|
||||||
|
if i < section_index {
|
||||||
|
section_index -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Combine sections ordered by index
|
||||||
|
combines.sort_unstable_by_key(|c| c.orig_index);
|
||||||
|
for combine in combines {
|
||||||
|
section = combine_sections(section, combine)?;
|
||||||
|
}
|
||||||
|
sections.insert(section_index, section);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read(obj_path: &Path, config: &DiffObjConfig) -> Result<ObjInfo> {
|
||||||
let (data, timestamp) = {
|
let (data, timestamp) = {
|
||||||
let file = fs::File::open(obj_path)?;
|
let file = fs::File::open(obj_path)?;
|
||||||
let timestamp = FileTime::from_last_modification_time(&file.metadata()?);
|
let timestamp = FileTime::from_last_modification_time(&file.metadata()?);
|
||||||
@@ -377,9 +610,13 @@ pub fn read(obj_path: &Path) -> Result<ObjInfo> {
|
|||||||
section.relocations =
|
section.relocations =
|
||||||
relocations_by_section(arch.as_ref(), &obj_file, section, split_meta.as_ref())?;
|
relocations_by_section(arch.as_ref(), &obj_file, section, split_meta.as_ref())?;
|
||||||
}
|
}
|
||||||
|
if config.combine_data_sections {
|
||||||
|
combine_data_sections(&mut sections)?;
|
||||||
|
}
|
||||||
line_info(&obj_file, &mut sections)?;
|
line_info(&obj_file, &mut sections)?;
|
||||||
let common = common_symbols(arch.as_ref(), &obj_file, split_meta.as_ref())?;
|
let common = common_symbols(arch.as_ref(), &obj_file, split_meta.as_ref())?;
|
||||||
Ok(ObjInfo { arch, path: obj_path.to_owned(), timestamp, sections, common, split_meta })
|
let extab = exception_tables(&mut sections, &obj_file)?;
|
||||||
|
Ok(ObjInfo { arch, path: obj_path.to_owned(), timestamp, sections, common, extab, split_meta })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn has_function(obj_path: &Path, symbol_name: &str) -> Result<bool> {
|
pub fn has_function(obj_path: &Path, symbol_name: &str) -> Result<bool> {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "objdiff-gui"
|
name = "objdiff-gui"
|
||||||
version = "2.0.0-alpha.3"
|
version = "2.0.0-beta.3"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.70"
|
rust-version = "1.70"
|
||||||
authors = ["Luke Street <luke@street.dev>"]
|
authors = ["Luke Street <luke@street.dev>"]
|
||||||
@@ -18,8 +18,9 @@ name = "objdiff"
|
|||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["wgpu", "wsl"]
|
default = ["glow", "wgpu", "wsl"]
|
||||||
wgpu = ["eframe/wgpu"]
|
glow = ["eframe/glow"]
|
||||||
|
wgpu = ["eframe/wgpu", "dep:wgpu"]
|
||||||
wsl = []
|
wsl = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
@@ -28,8 +29,8 @@ bytes = "1.6.0"
|
|||||||
cfg-if = "1.0.0"
|
cfg-if = "1.0.0"
|
||||||
const_format = "0.2.32"
|
const_format = "0.2.32"
|
||||||
cwdemangle = "1.0.0"
|
cwdemangle = "1.0.0"
|
||||||
|
cwextab = "0.2.3"
|
||||||
dirs = "5.0.1"
|
dirs = "5.0.1"
|
||||||
eframe = { version = "0.27.2", features = ["persistence"] }
|
|
||||||
egui = "0.27.2"
|
egui = "0.27.2"
|
||||||
egui_extras = "0.27.2"
|
egui_extras = "0.27.2"
|
||||||
filetime = "0.2.23"
|
filetime = "0.2.23"
|
||||||
@@ -37,11 +38,13 @@ float-ord = "0.3.2"
|
|||||||
font-kit = "0.13.0"
|
font-kit = "0.13.0"
|
||||||
globset = { version = "0.4.14", features = ["serde1"] }
|
globset = { version = "0.4.14", features = ["serde1"] }
|
||||||
log = "0.4.21"
|
log = "0.4.21"
|
||||||
notify = "6.1.1"
|
notify = { git = "https://github.com/encounter/notify", rev = "4c1783e8e041b5f69d4cf1750b9f07e335a0771e" }
|
||||||
objdiff-core = { path = "../objdiff-core", features = ["all"] }
|
objdiff-core = { path = "../objdiff-core", features = ["all"] }
|
||||||
png = "0.17.13"
|
png = "0.17.13"
|
||||||
pollster = "0.3.0"
|
pollster = "0.3.0"
|
||||||
|
regex = "1.10.5"
|
||||||
rfd = { version = "0.14.1" } #, default-features = false, features = ['xdg-portal']
|
rfd = { version = "0.14.1" } #, default-features = false, features = ['xdg-portal']
|
||||||
|
rlwinmdec = "1.0.1"
|
||||||
ron = "0.8.1"
|
ron = "0.8.1"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1.0.116"
|
serde_json = "1.0.116"
|
||||||
@@ -50,6 +53,28 @@ strum = { version = "0.26.2", features = ["derive"] }
|
|||||||
tempfile = "3.10.1"
|
tempfile = "3.10.1"
|
||||||
time = { version = "0.3.36", features = ["formatting", "local-offset"] }
|
time = { version = "0.3.36", features = ["formatting", "local-offset"] }
|
||||||
|
|
||||||
|
# Keep version in sync with egui
|
||||||
|
[dependencies.eframe]
|
||||||
|
version = "0.27.2"
|
||||||
|
features = [
|
||||||
|
"default_fonts",
|
||||||
|
"persistence",
|
||||||
|
"wayland",
|
||||||
|
"x11",
|
||||||
|
]
|
||||||
|
default-features = false
|
||||||
|
|
||||||
|
# Keep version in sync with eframe
|
||||||
|
[dependencies.wgpu]
|
||||||
|
version = "0.19.1"
|
||||||
|
features = [
|
||||||
|
"dx12",
|
||||||
|
"metal",
|
||||||
|
"webgpu",
|
||||||
|
]
|
||||||
|
optional = true
|
||||||
|
default-features = false
|
||||||
|
|
||||||
# For Linux static binaries, use rustls
|
# For Linux static binaries, use rustls
|
||||||
[target.'cfg(target_os = "linux")'.dependencies]
|
[target.'cfg(target_os = "linux")'.dependencies]
|
||||||
reqwest = { version = "0.12.4", default-features = false, features = ["blocking", "json", "multipart", "rustls-tls"] }
|
reqwest = { version = "0.12.4", default-features = false, features = ["blocking", "json", "multipart", "rustls-tls"] }
|
||||||
|
|||||||
@@ -35,9 +35,12 @@ use crate::{
|
|||||||
data_diff::data_diff_ui,
|
data_diff::data_diff_ui,
|
||||||
debug::debug_window,
|
debug::debug_window,
|
||||||
demangle::{demangle_window, DemangleViewState},
|
demangle::{demangle_window, DemangleViewState},
|
||||||
|
extab_diff::extab_diff_ui,
|
||||||
frame_history::FrameHistory,
|
frame_history::FrameHistory,
|
||||||
function_diff::function_diff_ui,
|
function_diff::function_diff_ui,
|
||||||
|
graphics::{graphics_window, GraphicsConfig, GraphicsViewState},
|
||||||
jobs::jobs_ui,
|
jobs::jobs_ui,
|
||||||
|
rlwinm::{rlwinm_decode_window, RlwinmDecodeViewState},
|
||||||
symbol_diff::{symbol_diff_ui, DiffViewState, View},
|
symbol_diff::{symbol_diff_ui, DiffViewState, View},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -47,13 +50,17 @@ pub struct ViewState {
|
|||||||
pub jobs: JobQueue,
|
pub jobs: JobQueue,
|
||||||
pub config_state: ConfigViewState,
|
pub config_state: ConfigViewState,
|
||||||
pub demangle_state: DemangleViewState,
|
pub demangle_state: DemangleViewState,
|
||||||
|
pub rlwinm_decode_state: RlwinmDecodeViewState,
|
||||||
pub diff_state: DiffViewState,
|
pub diff_state: DiffViewState,
|
||||||
|
pub graphics_state: GraphicsViewState,
|
||||||
pub frame_history: FrameHistory,
|
pub frame_history: FrameHistory,
|
||||||
pub show_appearance_config: bool,
|
pub show_appearance_config: bool,
|
||||||
pub show_demangle: bool,
|
pub show_demangle: bool,
|
||||||
|
pub show_rlwinm_decode: bool,
|
||||||
pub show_project_config: bool,
|
pub show_project_config: bool,
|
||||||
pub show_arch_config: bool,
|
pub show_arch_config: bool,
|
||||||
pub show_debug: bool,
|
pub show_debug: bool,
|
||||||
|
pub show_graphics: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The configuration for a single object file.
|
/// The configuration for a single object file.
|
||||||
@@ -209,6 +216,7 @@ pub struct App {
|
|||||||
config: AppConfigRef,
|
config: AppConfigRef,
|
||||||
modified: Arc<AtomicBool>,
|
modified: Arc<AtomicBool>,
|
||||||
watcher: Option<notify::RecommendedWatcher>,
|
watcher: Option<notify::RecommendedWatcher>,
|
||||||
|
app_path: Option<PathBuf>,
|
||||||
relaunch_path: Rc<Mutex<Option<PathBuf>>>,
|
relaunch_path: Rc<Mutex<Option<PathBuf>>>,
|
||||||
should_relaunch: bool,
|
should_relaunch: bool,
|
||||||
}
|
}
|
||||||
@@ -222,6 +230,9 @@ impl App {
|
|||||||
cc: &eframe::CreationContext<'_>,
|
cc: &eframe::CreationContext<'_>,
|
||||||
utc_offset: UtcOffset,
|
utc_offset: UtcOffset,
|
||||||
relaunch_path: Rc<Mutex<Option<PathBuf>>>,
|
relaunch_path: Rc<Mutex<Option<PathBuf>>>,
|
||||||
|
app_path: Option<PathBuf>,
|
||||||
|
graphics_config: GraphicsConfig,
|
||||||
|
graphics_config_path: Option<PathBuf>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
// Load previous app state (if any).
|
// Load previous app state (if any).
|
||||||
// Note that you must enable the `persistence` feature for this to work.
|
// Note that you must enable the `persistence` feature for this to work.
|
||||||
@@ -244,7 +255,32 @@ impl App {
|
|||||||
}
|
}
|
||||||
app.appearance.init_fonts(&cc.egui_ctx);
|
app.appearance.init_fonts(&cc.egui_ctx);
|
||||||
app.appearance.utc_offset = utc_offset;
|
app.appearance.utc_offset = utc_offset;
|
||||||
|
app.app_path = app_path;
|
||||||
app.relaunch_path = relaunch_path;
|
app.relaunch_path = relaunch_path;
|
||||||
|
#[cfg(feature = "wgpu")]
|
||||||
|
if let Some(wgpu_render_state) = &cc.wgpu_render_state {
|
||||||
|
use eframe::egui_wgpu::wgpu::Backend;
|
||||||
|
let info = wgpu_render_state.adapter.get_info();
|
||||||
|
app.view_state.graphics_state.active_backend = match info.backend {
|
||||||
|
Backend::Empty => "Unknown",
|
||||||
|
Backend::Vulkan => "Vulkan",
|
||||||
|
Backend::Metal => "Metal",
|
||||||
|
Backend::Dx12 => "DirectX 12",
|
||||||
|
Backend::Gl => "OpenGL",
|
||||||
|
Backend::BrowserWebGpu => "WebGPU",
|
||||||
|
}
|
||||||
|
.to_string();
|
||||||
|
app.view_state.graphics_state.active_device.clone_from(&info.name);
|
||||||
|
}
|
||||||
|
#[cfg(feature = "glow")]
|
||||||
|
if let Some(gl) = &cc.gl {
|
||||||
|
use eframe::glow::HasContext;
|
||||||
|
app.view_state.graphics_state.active_backend = "OpenGL (Fallback)".to_string();
|
||||||
|
app.view_state.graphics_state.active_device =
|
||||||
|
unsafe { gl.get_parameter_string(0x1F01) }; // GL_RENDERER
|
||||||
|
}
|
||||||
|
app.view_state.graphics_state.graphics_config = graphics_config;
|
||||||
|
app.view_state.graphics_state.graphics_config_path = graphics_config_path;
|
||||||
app
|
app
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -267,9 +303,9 @@ impl App {
|
|||||||
JobResult::Update(state) => {
|
JobResult::Update(state) => {
|
||||||
if let Ok(mut guard) = self.relaunch_path.lock() {
|
if let Ok(mut guard) = self.relaunch_path.lock() {
|
||||||
*guard = Some(state.exe_path);
|
*guard = Some(state.exe_path);
|
||||||
}
|
|
||||||
self.should_relaunch = true;
|
self.should_relaunch = true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
_ => results.push(result),
|
_ => results.push(result),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -308,7 +344,7 @@ impl App {
|
|||||||
fn post_update(&mut self, ctx: &egui::Context) {
|
fn post_update(&mut self, ctx: &egui::Context) {
|
||||||
self.appearance.post_update(ctx);
|
self.appearance.post_update(ctx);
|
||||||
|
|
||||||
let ViewState { jobs, diff_state, config_state, .. } = &mut self.view_state;
|
let ViewState { jobs, diff_state, config_state, graphics_state, .. } = &mut self.view_state;
|
||||||
config_state.post_update(ctx, jobs, &self.config);
|
config_state.post_update(ctx, jobs, &self.config);
|
||||||
diff_state.post_update(ctx, jobs, &self.config);
|
diff_state.post_update(ctx, jobs, &self.config);
|
||||||
|
|
||||||
@@ -390,6 +426,15 @@ impl App {
|
|||||||
jobs.push(start_build(ctx, diff_config));
|
jobs.push(start_build(ctx, diff_config));
|
||||||
config.queue_reload = false;
|
config.queue_reload = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if graphics_state.should_relaunch {
|
||||||
|
if let Some(app_path) = &self.app_path {
|
||||||
|
if let Ok(mut guard) = self.relaunch_path.lock() {
|
||||||
|
*guard = Some(app_path.clone());
|
||||||
|
self.should_relaunch = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -409,13 +454,17 @@ impl eframe::App for App {
|
|||||||
jobs,
|
jobs,
|
||||||
config_state,
|
config_state,
|
||||||
demangle_state,
|
demangle_state,
|
||||||
|
rlwinm_decode_state,
|
||||||
diff_state,
|
diff_state,
|
||||||
|
graphics_state,
|
||||||
frame_history,
|
frame_history,
|
||||||
show_appearance_config,
|
show_appearance_config,
|
||||||
show_demangle,
|
show_demangle,
|
||||||
|
show_rlwinm_decode,
|
||||||
show_project_config,
|
show_project_config,
|
||||||
show_arch_config,
|
show_arch_config,
|
||||||
show_debug,
|
show_debug,
|
||||||
|
show_graphics,
|
||||||
} = view_state;
|
} = view_state;
|
||||||
|
|
||||||
frame_history.on_new_frame(ctx.input(|i| i.time), frame.info().cpu_usage);
|
frame_history.on_new_frame(ctx.input(|i| i.time), frame.info().cpu_usage);
|
||||||
@@ -457,6 +506,10 @@ impl eframe::App for App {
|
|||||||
*show_appearance_config = !*show_appearance_config;
|
*show_appearance_config = !*show_appearance_config;
|
||||||
ui.close_menu();
|
ui.close_menu();
|
||||||
}
|
}
|
||||||
|
if ui.button("Graphics…").clicked() {
|
||||||
|
*show_graphics = !*show_graphics;
|
||||||
|
ui.close_menu();
|
||||||
|
}
|
||||||
if ui.button("Quit").clicked() {
|
if ui.button("Quit").clicked() {
|
||||||
ctx.send_viewport_cmd(egui::ViewportCommand::Close);
|
ctx.send_viewport_cmd(egui::ViewportCommand::Close);
|
||||||
}
|
}
|
||||||
@@ -466,6 +519,10 @@ impl eframe::App for App {
|
|||||||
*show_demangle = !*show_demangle;
|
*show_demangle = !*show_demangle;
|
||||||
ui.close_menu();
|
ui.close_menu();
|
||||||
}
|
}
|
||||||
|
if ui.button("Rlwinm Decoder…").clicked() {
|
||||||
|
*show_rlwinm_decode = !*show_rlwinm_decode;
|
||||||
|
ui.close_menu();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
ui.menu_button("Diff Options", |ui| {
|
ui.menu_button("Diff Options", |ui| {
|
||||||
if ui.button("Arch Settings…").clicked() {
|
if ui.button("Arch Settings…").clicked() {
|
||||||
@@ -512,6 +569,16 @@ impl eframe::App for App {
|
|||||||
{
|
{
|
||||||
config.queue_reload = true;
|
config.queue_reload = true;
|
||||||
}
|
}
|
||||||
|
if ui
|
||||||
|
.checkbox(
|
||||||
|
&mut config.diff_obj_config.combine_data_sections,
|
||||||
|
"Combine data sections",
|
||||||
|
)
|
||||||
|
.on_hover_text("Combines data sections with equal names.")
|
||||||
|
.changed()
|
||||||
|
{
|
||||||
|
config.queue_reload = true;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -525,6 +592,10 @@ impl eframe::App for App {
|
|||||||
egui::CentralPanel::default().show(ctx, |ui| {
|
egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
data_diff_ui(ui, diff_state, appearance);
|
data_diff_ui(ui, diff_state, appearance);
|
||||||
});
|
});
|
||||||
|
} else if diff_state.current_view == View::ExtabDiff && build_success {
|
||||||
|
egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
|
extab_diff_ui(ui, diff_state, appearance);
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
egui::SidePanel::left("side_panel").show(ctx, |ui| {
|
egui::SidePanel::left("side_panel").show(ctx, |ui| {
|
||||||
egui::ScrollArea::both().show(ui, |ui| {
|
egui::ScrollArea::both().show(ui, |ui| {
|
||||||
@@ -541,8 +612,10 @@ impl eframe::App for App {
|
|||||||
project_window(ctx, config, show_project_config, config_state, appearance);
|
project_window(ctx, config, show_project_config, config_state, appearance);
|
||||||
appearance_window(ctx, show_appearance_config, appearance);
|
appearance_window(ctx, show_appearance_config, appearance);
|
||||||
demangle_window(ctx, show_demangle, demangle_state, appearance);
|
demangle_window(ctx, show_demangle, demangle_state, appearance);
|
||||||
|
rlwinm_decode_window(ctx, show_rlwinm_decode, rlwinm_decode_state, appearance);
|
||||||
arch_config_window(ctx, config, show_arch_config, appearance);
|
arch_config_window(ctx, config, show_arch_config, appearance);
|
||||||
debug_window(ctx, show_debug, frame_history, appearance);
|
debug_window(ctx, show_debug, frame_history, appearance);
|
||||||
|
graphics_window(ctx, show_graphics, frame_history, graphics_state, appearance);
|
||||||
|
|
||||||
self.post_update(ctx);
|
self.post_update(ctx);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ pub struct BuildConfig {
|
|||||||
pub project_dir: Option<PathBuf>,
|
pub project_dir: Option<PathBuf>,
|
||||||
pub custom_make: Option<String>,
|
pub custom_make: Option<String>,
|
||||||
pub custom_args: Option<Vec<String>>,
|
pub custom_args: Option<Vec<String>>,
|
||||||
|
#[allow(unused)]
|
||||||
pub selected_wsl_distro: Option<String>,
|
pub selected_wsl_distro: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,7 +236,7 @@ fn run_build(
|
|||||||
total,
|
total,
|
||||||
&cancel,
|
&cancel,
|
||||||
)?;
|
)?;
|
||||||
Some(read::read(target_path).with_context(|| {
|
Some(read::read(target_path, &config.diff_obj_config).with_context(|| {
|
||||||
format!("Failed to read object '{}'", target_path.display())
|
format!("Failed to read object '{}'", target_path.display())
|
||||||
})?)
|
})?)
|
||||||
}
|
}
|
||||||
@@ -252,7 +253,7 @@ fn run_build(
|
|||||||
&cancel,
|
&cancel,
|
||||||
)?;
|
)?;
|
||||||
Some(
|
Some(
|
||||||
read::read(base_path)
|
read::read(base_path, &config.diff_obj_config)
|
||||||
.with_context(|| format!("Failed to read object '{}'", base_path.display()))?,
|
.with_context(|| format!("Failed to read object '{}'", base_path.display()))?,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ mod views;
|
|||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
|
process::ExitCode,
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
sync::{Arc, Mutex},
|
sync::{Arc, Mutex},
|
||||||
};
|
};
|
||||||
@@ -19,6 +20,8 @@ use anyhow::{ensure, Result};
|
|||||||
use cfg_if::cfg_if;
|
use cfg_if::cfg_if;
|
||||||
use time::UtcOffset;
|
use time::UtcOffset;
|
||||||
|
|
||||||
|
use crate::views::graphics::{load_graphics_config, GraphicsBackend, GraphicsConfig};
|
||||||
|
|
||||||
fn load_icon() -> Result<egui::IconData> {
|
fn load_icon() -> Result<egui::IconData> {
|
||||||
use bytes::Buf;
|
use bytes::Buf;
|
||||||
let decoder = png::Decoder::new(include_bytes!("../assets/icon_64.png").reader());
|
let decoder = png::Decoder::new(include_bytes!("../assets/icon_64.png").reader());
|
||||||
@@ -31,9 +34,11 @@ fn load_icon() -> Result<egui::IconData> {
|
|||||||
Ok(egui::IconData { rgba: buf, width: info.width, height: info.height })
|
Ok(egui::IconData { rgba: buf, width: info.width, height: info.height })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const APP_NAME: &str = "objdiff";
|
||||||
|
|
||||||
// When compiling natively:
|
// When compiling natively:
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
fn main() {
|
fn main() -> ExitCode {
|
||||||
// Log to stdout (if you run with `RUST_LOG=debug`).
|
// Log to stdout (if you run with `RUST_LOG=debug`).
|
||||||
tracing_subscriber::fmt::init();
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
@@ -42,8 +47,8 @@ fn main() {
|
|||||||
// https://github.com/time-rs/time/issues/293
|
// https://github.com/time-rs/time/issues/293
|
||||||
let utc_offset = UtcOffset::current_local_offset().unwrap_or(UtcOffset::UTC);
|
let utc_offset = UtcOffset::current_local_offset().unwrap_or(UtcOffset::UTC);
|
||||||
|
|
||||||
|
let app_path = std::env::current_exe().ok();
|
||||||
let exec_path: Rc<Mutex<Option<PathBuf>>> = Rc::new(Mutex::new(None));
|
let exec_path: Rc<Mutex<Option<PathBuf>>> = Rc::new(Mutex::new(None));
|
||||||
let exec_path_clone = exec_path.clone();
|
|
||||||
let mut native_options =
|
let mut native_options =
|
||||||
eframe::NativeOptions { follow_system_theme: false, ..Default::default() };
|
eframe::NativeOptions { follow_system_theme: false, ..Default::default() };
|
||||||
match load_icon() {
|
match load_icon() {
|
||||||
@@ -51,42 +56,149 @@ fn main() {
|
|||||||
native_options.viewport.icon = Some(Arc::new(data));
|
native_options.viewport.icon = Some(Arc::new(data));
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::warn!("Failed to load application icon: {}", e);
|
log::warn!("Failed to load application icon: {e:?}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let mut graphics_config = GraphicsConfig::default();
|
||||||
|
let mut graphics_config_path = None;
|
||||||
|
if let Some(storage_dir) = eframe::storage_dir(APP_NAME) {
|
||||||
|
let config_path = storage_dir.join("graphics.ron");
|
||||||
|
match load_graphics_config(&config_path) {
|
||||||
|
Ok(Some(config)) => {
|
||||||
|
graphics_config = config;
|
||||||
|
}
|
||||||
|
Ok(None) => {}
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("Failed to load native config: {e:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
graphics_config_path = Some(config_path);
|
||||||
|
}
|
||||||
#[cfg(feature = "wgpu")]
|
#[cfg(feature = "wgpu")]
|
||||||
{
|
{
|
||||||
native_options.renderer = eframe::Renderer::Wgpu;
|
use eframe::egui_wgpu::wgpu::Backends;
|
||||||
|
if graphics_config.desired_backend.is_supported() {
|
||||||
|
native_options.wgpu_options.supported_backends = match graphics_config.desired_backend {
|
||||||
|
GraphicsBackend::Auto => native_options.wgpu_options.supported_backends,
|
||||||
|
GraphicsBackend::Dx12 => Backends::DX12,
|
||||||
|
GraphicsBackend::Metal => Backends::METAL,
|
||||||
|
GraphicsBackend::Vulkan => Backends::VULKAN,
|
||||||
|
GraphicsBackend::OpenGL => Backends::GL,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
eframe::run_native(
|
}
|
||||||
"objdiff",
|
let mut eframe_error = None;
|
||||||
|
if let Err(e) = run_eframe(
|
||||||
|
native_options.clone(),
|
||||||
|
utc_offset,
|
||||||
|
exec_path.clone(),
|
||||||
|
app_path.clone(),
|
||||||
|
graphics_config.clone(),
|
||||||
|
graphics_config_path.clone(),
|
||||||
|
) {
|
||||||
|
eframe_error = Some(e);
|
||||||
|
}
|
||||||
|
#[cfg(feature = "wgpu")]
|
||||||
|
if let Some(e) = eframe_error {
|
||||||
|
// Attempt to relaunch using wgpu auto backend if the desired backend failed
|
||||||
|
#[allow(unused_mut)]
|
||||||
|
let mut should_relaunch = graphics_config.desired_backend != GraphicsBackend::Auto;
|
||||||
|
#[cfg(feature = "glow")]
|
||||||
|
{
|
||||||
|
// If the desired backend is OpenGL, we should try to relaunch using the glow renderer
|
||||||
|
should_relaunch &= graphics_config.desired_backend != GraphicsBackend::OpenGL;
|
||||||
|
}
|
||||||
|
if should_relaunch {
|
||||||
|
log::warn!("Failed to launch application: {e:?}");
|
||||||
|
log::warn!("Attempting to relaunch using auto-detected backend");
|
||||||
|
native_options.wgpu_options.supported_backends = Default::default();
|
||||||
|
if let Err(e) = run_eframe(
|
||||||
|
native_options.clone(),
|
||||||
|
utc_offset,
|
||||||
|
exec_path.clone(),
|
||||||
|
app_path.clone(),
|
||||||
|
graphics_config.clone(),
|
||||||
|
graphics_config_path.clone(),
|
||||||
|
) {
|
||||||
|
eframe_error = Some(e);
|
||||||
|
} else {
|
||||||
|
eframe_error = None;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
eframe_error = Some(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(all(feature = "wgpu", feature = "glow"))]
|
||||||
|
if let Some(e) = eframe_error {
|
||||||
|
// Attempt to relaunch using the glow renderer if the wgpu backend failed
|
||||||
|
log::warn!("Failed to launch application: {e:?}");
|
||||||
|
log::warn!("Attempting to relaunch using fallback OpenGL backend");
|
||||||
|
native_options.renderer = eframe::Renderer::Glow;
|
||||||
|
if let Err(e) = run_eframe(
|
||||||
native_options,
|
native_options,
|
||||||
Box::new(move |cc| Box::new(app::App::new(cc, utc_offset, exec_path_clone))),
|
utc_offset,
|
||||||
)
|
exec_path.clone(),
|
||||||
.expect("Failed to run eframe application");
|
app_path,
|
||||||
|
graphics_config,
|
||||||
|
graphics_config_path,
|
||||||
|
) {
|
||||||
|
eframe_error = Some(e);
|
||||||
|
} else {
|
||||||
|
eframe_error = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(e) = eframe_error {
|
||||||
|
log::error!("Failed to launch application: {e:?}");
|
||||||
|
return ExitCode::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
// Attempt to relaunch application from the updated path
|
// Attempt to relaunch application from the updated path
|
||||||
if let Ok(mut guard) = exec_path.lock() {
|
if let Ok(mut guard) = exec_path.lock() {
|
||||||
if let Some(path) = guard.take() {
|
if let Some(path) = guard.take() {
|
||||||
cfg_if! {
|
cfg_if! {
|
||||||
if #[cfg(unix)] {
|
if #[cfg(unix)] {
|
||||||
let result = exec::Command::new(path)
|
let e = exec::Command::new(path)
|
||||||
.args(&std::env::args().collect::<Vec<String>>())
|
.args(&std::env::args().collect::<Vec<String>>())
|
||||||
.exec();
|
.exec();
|
||||||
log::error!("Failed to relaunch: {result:?}");
|
log::error!("Failed to relaunch: {e:?}");
|
||||||
|
return ExitCode::FAILURE;
|
||||||
} else {
|
} else {
|
||||||
let result = std::process::Command::new(path)
|
let result = std::process::Command::new(path)
|
||||||
.args(std::env::args())
|
.args(std::env::args())
|
||||||
.spawn()
|
.spawn();
|
||||||
.unwrap()
|
|
||||||
.wait();
|
|
||||||
if let Err(e) = result {
|
if let Err(e) = result {
|
||||||
log::error!("Failed to relaunch: {:?}", e);
|
log::error!("Failed to relaunch: {e:?}");
|
||||||
|
return ExitCode::FAILURE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
ExitCode::SUCCESS
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_eframe(
|
||||||
|
native_options: eframe::NativeOptions,
|
||||||
|
utc_offset: UtcOffset,
|
||||||
|
exec_path_clone: Rc<Mutex<Option<PathBuf>>>,
|
||||||
|
app_path: Option<PathBuf>,
|
||||||
|
graphics_config: GraphicsConfig,
|
||||||
|
graphics_config_path: Option<PathBuf>,
|
||||||
|
) -> Result<(), eframe::Error> {
|
||||||
|
eframe::run_native(
|
||||||
|
APP_NAME,
|
||||||
|
native_options,
|
||||||
|
Box::new(move |cc| {
|
||||||
|
Box::new(app::App::new(
|
||||||
|
cc,
|
||||||
|
utc_offset,
|
||||||
|
exec_path_clone,
|
||||||
|
app_path,
|
||||||
|
graphics_config,
|
||||||
|
graphics_config_path,
|
||||||
|
))
|
||||||
|
}),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// when compiling to web using trunk.
|
// when compiling to web using trunk.
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ use egui::{
|
|||||||
use globset::Glob;
|
use globset::Glob;
|
||||||
use objdiff_core::{
|
use objdiff_core::{
|
||||||
config::{ProjectObject, DEFAULT_WATCH_PATTERNS},
|
config::{ProjectObject, DEFAULT_WATCH_PATTERNS},
|
||||||
diff::{MipsAbi, MipsInstrCategory, X86Formatter},
|
diff::{ArmArchVersion, ArmR9Usage, MipsAbi, MipsInstrCategory, X86Formatter},
|
||||||
};
|
};
|
||||||
use self_update::cargo_crate_version;
|
use self_update::cargo_crate_version;
|
||||||
use strum::{EnumMessage, VariantArray};
|
use strum::{EnumMessage, VariantArray};
|
||||||
@@ -242,7 +242,7 @@ pub fn config_ui(
|
|||||||
|| {
|
|| {
|
||||||
Box::pin(
|
Box::pin(
|
||||||
rfd::AsyncFileDialog::new()
|
rfd::AsyncFileDialog::new()
|
||||||
.set_directory(&target_dir)
|
.set_directory(target_dir)
|
||||||
.add_filter("Object file", &["o", "elf", "obj"])
|
.add_filter("Object file", &["o", "elf", "obj"])
|
||||||
.pick_file(),
|
.pick_file(),
|
||||||
)
|
)
|
||||||
@@ -907,4 +907,69 @@ fn arch_config_ui(ui: &mut egui::Ui, config: &mut AppConfig, _appearance: &Appea
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
ui.separator();
|
||||||
|
ui.heading("ARM");
|
||||||
|
egui::ComboBox::new("arm_arch_version", "Architecture Version")
|
||||||
|
.selected_text(config.diff_obj_config.arm_arch_version.get_message().unwrap())
|
||||||
|
.show_ui(ui, |ui| {
|
||||||
|
for &version in ArmArchVersion::VARIANTS {
|
||||||
|
if ui
|
||||||
|
.selectable_label(
|
||||||
|
config.diff_obj_config.arm_arch_version == version,
|
||||||
|
version.get_message().unwrap(),
|
||||||
|
)
|
||||||
|
.clicked()
|
||||||
|
{
|
||||||
|
config.diff_obj_config.arm_arch_version = version;
|
||||||
|
config.queue_reload = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let response = ui
|
||||||
|
.checkbox(&mut config.diff_obj_config.arm_unified_syntax, "Unified syntax")
|
||||||
|
.on_hover_text("Disassemble as unified assembly language (UAL).");
|
||||||
|
if response.changed() {
|
||||||
|
config.queue_reload = true;
|
||||||
|
}
|
||||||
|
let response = ui
|
||||||
|
.checkbox(&mut config.diff_obj_config.arm_av_registers, "Use A/V registers")
|
||||||
|
.on_hover_text("Display R0-R3 as A1-A4 and R4-R11 as V1-V8");
|
||||||
|
if response.changed() {
|
||||||
|
config.queue_reload = true;
|
||||||
|
}
|
||||||
|
egui::ComboBox::new("arm_r9_usage", "Display R9 as")
|
||||||
|
.selected_text(config.diff_obj_config.arm_r9_usage.get_message().unwrap())
|
||||||
|
.show_ui(ui, |ui| {
|
||||||
|
for &usage in ArmR9Usage::VARIANTS {
|
||||||
|
if ui
|
||||||
|
.selectable_label(
|
||||||
|
config.diff_obj_config.arm_r9_usage == usage,
|
||||||
|
usage.get_message().unwrap(),
|
||||||
|
)
|
||||||
|
.on_hover_text(usage.get_detailed_message().unwrap())
|
||||||
|
.clicked()
|
||||||
|
{
|
||||||
|
config.diff_obj_config.arm_r9_usage = usage;
|
||||||
|
config.queue_reload = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let response = ui
|
||||||
|
.checkbox(&mut config.diff_obj_config.arm_sl_usage, "Display R10 as SL")
|
||||||
|
.on_hover_text("Used for explicit stack limits.");
|
||||||
|
if response.changed() {
|
||||||
|
config.queue_reload = true;
|
||||||
|
}
|
||||||
|
let response = ui
|
||||||
|
.checkbox(&mut config.diff_obj_config.arm_fp_usage, "Display R11 as FP")
|
||||||
|
.on_hover_text("Used for frame pointers.");
|
||||||
|
if response.changed() {
|
||||||
|
config.queue_reload = true;
|
||||||
|
}
|
||||||
|
let response = ui
|
||||||
|
.checkbox(&mut config.diff_obj_config.arm_ip_usage, "Display R12 as IP")
|
||||||
|
.on_hover_text("Used for interworking and long branches.");
|
||||||
|
if response.changed() {
|
||||||
|
config.queue_reload = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ pub fn debug_window(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn debug_ui(ui: &mut egui::Ui, frame_history: &mut FrameHistory, _appearance: &Appearance) {
|
fn debug_ui(ui: &mut egui::Ui, frame_history: &mut FrameHistory, _appearance: &Appearance) {
|
||||||
|
if ui.button("Clear memory").clicked() {
|
||||||
|
ui.memory_mut(|m| *m = Default::default());
|
||||||
|
}
|
||||||
ui.label(format!("Repainting the UI each frame. FPS: {:.1}", frame_history.fps()));
|
ui.label(format!("Repainting the UI each frame. FPS: {:.1}", frame_history.fps()));
|
||||||
frame_history.ui(ui);
|
frame_history.ui(ui);
|
||||||
}
|
}
|
||||||
|
|||||||
218
objdiff-gui/src/views/extab_diff.rs
Normal file
218
objdiff-gui/src/views/extab_diff.rs
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
use egui::{text::LayoutJob, Align, Layout, ScrollArea, Ui, Vec2};
|
||||||
|
use egui_extras::{Size, StripBuilder};
|
||||||
|
use objdiff_core::{
|
||||||
|
diff::ObjDiff,
|
||||||
|
obj::{ObjExtab, ObjInfo, ObjSymbol, SymbolRef},
|
||||||
|
};
|
||||||
|
use time::format_description;
|
||||||
|
|
||||||
|
use crate::views::{
|
||||||
|
appearance::Appearance,
|
||||||
|
symbol_diff::{match_color_for_symbol, DiffViewState, SymbolRefByName, View},
|
||||||
|
};
|
||||||
|
|
||||||
|
fn find_symbol(obj: &ObjInfo, selected_symbol: &SymbolRefByName) -> Option<SymbolRef> {
|
||||||
|
for (section_idx, section) in obj.sections.iter().enumerate() {
|
||||||
|
for (symbol_idx, symbol) in section.symbols.iter().enumerate() {
|
||||||
|
if symbol.name == selected_symbol.symbol_name {
|
||||||
|
return Some(SymbolRef { section_idx, symbol_idx });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_extab(extab: &ObjExtab) -> String {
|
||||||
|
let mut text = String::from("");
|
||||||
|
|
||||||
|
let mut dtor_names: Vec<&str> = vec![];
|
||||||
|
for dtor in &extab.dtors {
|
||||||
|
//For each function name, use the demangled name by default,
|
||||||
|
//and if not available fallback to the original name
|
||||||
|
let name = match &dtor.demangled_name {
|
||||||
|
Some(demangled_name) => demangled_name,
|
||||||
|
None => &dtor.name,
|
||||||
|
};
|
||||||
|
dtor_names.push(name.as_str());
|
||||||
|
}
|
||||||
|
if let Some(decoded) = extab.data.to_string(&dtor_names) {
|
||||||
|
text += decoded.as_str();
|
||||||
|
}
|
||||||
|
|
||||||
|
text
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_extab_entry(obj: &ObjInfo, symbol: &ObjSymbol) -> Option<ObjExtab> {
|
||||||
|
if let Some(extab_array) = &obj.extab {
|
||||||
|
for extab_entry in extab_array {
|
||||||
|
if extab_entry.func.name == symbol.name {
|
||||||
|
return Some(extab_entry.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extab_text_ui(
|
||||||
|
ui: &mut Ui,
|
||||||
|
obj: &(ObjInfo, ObjDiff),
|
||||||
|
symbol_ref: SymbolRef,
|
||||||
|
appearance: &Appearance,
|
||||||
|
) -> Option<()> {
|
||||||
|
let (_section, symbol) = obj.0.section_symbol(symbol_ref);
|
||||||
|
|
||||||
|
if let Some(extab_entry) = find_extab_entry(&obj.0, symbol) {
|
||||||
|
let text = decode_extab(&extab_entry);
|
||||||
|
ui.colored_label(appearance.replace_color, &text);
|
||||||
|
return Some(());
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extab_ui(
|
||||||
|
ui: &mut Ui,
|
||||||
|
obj: Option<&(ObjInfo, ObjDiff)>,
|
||||||
|
selected_symbol: &SymbolRefByName,
|
||||||
|
appearance: &Appearance,
|
||||||
|
_left: bool,
|
||||||
|
) {
|
||||||
|
ScrollArea::both().auto_shrink([false, false]).show(ui, |ui| {
|
||||||
|
ui.scope(|ui| {
|
||||||
|
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
|
||||||
|
ui.style_mut().wrap = Some(false);
|
||||||
|
|
||||||
|
let symbol = obj.and_then(|(obj, _)| find_symbol(obj, selected_symbol));
|
||||||
|
|
||||||
|
if let (Some(object), Some(symbol_ref)) = (obj, symbol) {
|
||||||
|
extab_text_ui(ui, object, symbol_ref, appearance);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn extab_diff_ui(ui: &mut egui::Ui, state: &mut DiffViewState, appearance: &Appearance) {
|
||||||
|
let (Some(result), Some(selected_symbol)) = (&state.build, &state.symbol_state.selected_symbol)
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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.horizontal(|ui| {
|
||||||
|
if ui.button("⏴ Back").clicked() {
|
||||||
|
state.current_view = View::SymbolDiff;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let name = selected_symbol
|
||||||
|
.demangled_symbol_name
|
||||||
|
.as_deref()
|
||||||
|
.unwrap_or(&selected_symbol.symbol_name);
|
||||||
|
let mut job = LayoutJob::simple(
|
||||||
|
name.to_string(),
|
||||||
|
appearance.code_font.clone(),
|
||||||
|
appearance.highlight_color,
|
||||||
|
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
|
||||||
|
.add_enabled(!state.build_running, egui::Button::new("Build"))
|
||||||
|
.clicked()
|
||||||
|
{
|
||||||
|
state.queue_build = true;
|
||||||
|
}
|
||||||
|
ui.scope(|ui| {
|
||||||
|
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
|
||||||
|
ui.style_mut().wrap = Some(false);
|
||||||
|
if state.build_running {
|
||||||
|
ui.colored_label(appearance.replace_color, "Building…");
|
||||||
|
} else {
|
||||||
|
ui.label("Last built:");
|
||||||
|
let format =
|
||||||
|
format_description::parse("[hour]:[minute]:[second]").unwrap();
|
||||||
|
ui.label(
|
||||||
|
result
|
||||||
|
.time
|
||||||
|
.to_offset(appearance.utc_offset)
|
||||||
|
.format(&format)
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
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, diff)| {
|
||||||
|
find_symbol(obj, selected_symbol).map(|sref| {
|
||||||
|
&diff.sections[sref.section_idx].symbols[sref.symbol_idx]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.and_then(|symbol| symbol.match_percent)
|
||||||
|
{
|
||||||
|
ui.colored_label(
|
||||||
|
match_color_for_symbol(match_percent, appearance),
|
||||||
|
&format!("{match_percent:.0}%"),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
ui.colored_label(appearance.replace_color, "Missing");
|
||||||
|
}
|
||||||
|
ui.label("Diff base:");
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
ui.separator();
|
||||||
|
|
||||||
|
// Table
|
||||||
|
StripBuilder::new(ui).size(Size::remainder()).vertical(|mut strip| {
|
||||||
|
strip.strip(|builder| {
|
||||||
|
builder.sizes(Size::remainder(), 2).horizontal(|mut strip| {
|
||||||
|
strip.cell(|ui| {
|
||||||
|
extab_ui(ui, result.first_obj.as_ref(), selected_symbol, appearance, true);
|
||||||
|
});
|
||||||
|
strip.cell(|ui| {
|
||||||
|
extab_ui(ui, result.second_obj.as_ref(), selected_symbol, appearance, false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -216,8 +216,11 @@ fn diff_text_ui(
|
|||||||
base_color = appearance.diff_colors[diff.idx % appearance.diff_colors.len()]
|
base_color = appearance.diff_colors[diff.idx % appearance.diff_colors.len()]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DiffText::BranchDest(addr) => {
|
DiffText::BranchDest(addr, diff) => {
|
||||||
label_text = format!("{addr:x}");
|
label_text = format!("{addr:x}");
|
||||||
|
if let Some(diff) = diff {
|
||||||
|
base_color = appearance.diff_colors[diff.idx % appearance.diff_colors.len()]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
DiffText::Symbol(sym) => {
|
DiffText::Symbol(sym) => {
|
||||||
let name = sym.demangled_name.as_ref().unwrap_or(&sym.name);
|
let name = sym.demangled_name.as_ref().unwrap_or(&sym.name);
|
||||||
|
|||||||
158
objdiff-gui/src/views/graphics.rs
Normal file
158
objdiff-gui/src/views/graphics.rs
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
use std::{
|
||||||
|
fs::File,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use egui::{text::LayoutJob, Context, FontId, RichText, TextFormat, TextStyle, Window};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use strum::{EnumIter, EnumMessage, IntoEnumIterator};
|
||||||
|
|
||||||
|
use crate::views::{appearance::Appearance, frame_history::FrameHistory};
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct GraphicsViewState {
|
||||||
|
pub active_backend: String,
|
||||||
|
pub active_device: String,
|
||||||
|
pub graphics_config: GraphicsConfig,
|
||||||
|
pub graphics_config_path: Option<PathBuf>,
|
||||||
|
pub should_relaunch: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(
|
||||||
|
Copy, Clone, Debug, Default, PartialEq, Eq, EnumIter, EnumMessage, Serialize, Deserialize,
|
||||||
|
)]
|
||||||
|
pub enum GraphicsBackend {
|
||||||
|
#[default]
|
||||||
|
#[strum(message = "Auto")]
|
||||||
|
Auto,
|
||||||
|
#[strum(message = "Vulkan")]
|
||||||
|
Vulkan,
|
||||||
|
#[strum(message = "Metal")]
|
||||||
|
Metal,
|
||||||
|
#[strum(message = "DirectX 12")]
|
||||||
|
Dx12,
|
||||||
|
#[strum(message = "OpenGL")]
|
||||||
|
OpenGL,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize)]
|
||||||
|
pub struct GraphicsConfig {
|
||||||
|
#[serde(default)]
|
||||||
|
pub desired_backend: GraphicsBackend,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_graphics_config(path: &Path) -> Result<Option<GraphicsConfig>> {
|
||||||
|
if !path.exists() {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
let file = File::open(path)?;
|
||||||
|
let config: GraphicsConfig = ron::de::from_reader(file)?;
|
||||||
|
Ok(Some(config))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn save_graphics_config(path: &Path, config: &GraphicsConfig) -> Result<()> {
|
||||||
|
let file = File::create(path)?;
|
||||||
|
ron::ser::to_writer(file, config)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GraphicsBackend {
|
||||||
|
pub fn is_supported(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
GraphicsBackend::Auto => true,
|
||||||
|
GraphicsBackend::Vulkan => {
|
||||||
|
cfg!(all(feature = "wgpu", any(target_os = "windows", target_os = "linux")))
|
||||||
|
}
|
||||||
|
GraphicsBackend::Metal => cfg!(all(feature = "wgpu", target_os = "macos")),
|
||||||
|
GraphicsBackend::Dx12 => cfg!(all(feature = "wgpu", target_os = "windows")),
|
||||||
|
GraphicsBackend::OpenGL => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn graphics_window(
|
||||||
|
ctx: &Context,
|
||||||
|
show: &mut bool,
|
||||||
|
frame_history: &mut FrameHistory,
|
||||||
|
state: &mut GraphicsViewState,
|
||||||
|
appearance: &Appearance,
|
||||||
|
) {
|
||||||
|
Window::new("Graphics").open(show).show(ctx, |ui| {
|
||||||
|
ui.label("Graphics backend:");
|
||||||
|
ui.label(
|
||||||
|
RichText::new(&state.active_backend)
|
||||||
|
.color(appearance.emphasized_text_color)
|
||||||
|
.text_style(TextStyle::Monospace),
|
||||||
|
);
|
||||||
|
ui.label("Graphics device:");
|
||||||
|
ui.label(
|
||||||
|
RichText::new(&state.active_device)
|
||||||
|
.color(appearance.emphasized_text_color)
|
||||||
|
.text_style(TextStyle::Monospace),
|
||||||
|
);
|
||||||
|
ui.label(format!("FPS: {:.1}", frame_history.fps()));
|
||||||
|
|
||||||
|
ui.separator();
|
||||||
|
let mut job = LayoutJob::default();
|
||||||
|
job.append(
|
||||||
|
"WARNING: ",
|
||||||
|
0.0,
|
||||||
|
TextFormat::simple(appearance.ui_font.clone(), appearance.delete_color),
|
||||||
|
);
|
||||||
|
job.append(
|
||||||
|
"Changing the graphics backend may cause the application\nto no longer start or display correctly. Use with caution!",
|
||||||
|
0.0,
|
||||||
|
TextFormat::simple(appearance.ui_font.clone(), appearance.emphasized_text_color),
|
||||||
|
);
|
||||||
|
if let Some(config_path) = &state.graphics_config_path {
|
||||||
|
job.append(
|
||||||
|
"\n\nDelete the following file to reset:\n",
|
||||||
|
0.0,
|
||||||
|
TextFormat::simple(appearance.ui_font.clone(), appearance.emphasized_text_color),
|
||||||
|
);
|
||||||
|
job.append(
|
||||||
|
config_path.to_string_lossy().as_ref(),
|
||||||
|
0.0,
|
||||||
|
TextFormat::simple(
|
||||||
|
FontId {
|
||||||
|
family: appearance.code_font.family.clone(),
|
||||||
|
size: appearance.ui_font.size,
|
||||||
|
},
|
||||||
|
appearance.emphasized_text_color,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
job.append(
|
||||||
|
"\n\nChanging the graphics backend will restart the application.",
|
||||||
|
0.0,
|
||||||
|
TextFormat::simple(appearance.ui_font.clone(), appearance.replace_color),
|
||||||
|
);
|
||||||
|
ui.label(job);
|
||||||
|
|
||||||
|
ui.add_enabled_ui(state.graphics_config_path.is_some(), |ui| {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.label("Desired backend:");
|
||||||
|
for backend in GraphicsBackend::iter().filter(GraphicsBackend::is_supported) {
|
||||||
|
let selected = state.graphics_config.desired_backend == backend;
|
||||||
|
if ui.selectable_label(selected, backend.get_message().unwrap()).clicked() {
|
||||||
|
let prev_backend = state.graphics_config.desired_backend;
|
||||||
|
state.graphics_config.desired_backend = backend;
|
||||||
|
match save_graphics_config(
|
||||||
|
state.graphics_config_path.as_ref().unwrap(),
|
||||||
|
&state.graphics_config,
|
||||||
|
) {
|
||||||
|
Ok(()) => {
|
||||||
|
state.should_relaunch = true;
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("Failed to save graphics config: {:?}", e);
|
||||||
|
state.graphics_config.desired_backend = prev_backend;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -5,10 +5,13 @@ pub(crate) mod config;
|
|||||||
pub(crate) mod data_diff;
|
pub(crate) mod data_diff;
|
||||||
pub(crate) mod debug;
|
pub(crate) mod debug;
|
||||||
pub(crate) mod demangle;
|
pub(crate) mod demangle;
|
||||||
|
pub(crate) mod extab_diff;
|
||||||
pub(crate) mod file;
|
pub(crate) mod file;
|
||||||
pub(crate) mod frame_history;
|
pub(crate) mod frame_history;
|
||||||
pub(crate) mod function_diff;
|
pub(crate) mod function_diff;
|
||||||
|
pub(crate) mod graphics;
|
||||||
pub(crate) mod jobs;
|
pub(crate) mod jobs;
|
||||||
|
pub(crate) mod rlwinm;
|
||||||
pub(crate) mod symbol_diff;
|
pub(crate) mod symbol_diff;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|||||||
34
objdiff-gui/src/views/rlwinm.rs
Normal file
34
objdiff-gui/src/views/rlwinm.rs
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
use egui::TextStyle;
|
||||||
|
|
||||||
|
use crate::views::appearance::Appearance;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct RlwinmDecodeViewState {
|
||||||
|
pub text: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rlwinm_decode_window(
|
||||||
|
ctx: &egui::Context,
|
||||||
|
show: &mut bool,
|
||||||
|
state: &mut RlwinmDecodeViewState,
|
||||||
|
appearance: &Appearance,
|
||||||
|
) {
|
||||||
|
egui::Window::new("Rlwinm Decoder").open(show).show(ctx, |ui| {
|
||||||
|
ui.text_edit_singleline(&mut state.text);
|
||||||
|
ui.add_space(10.0);
|
||||||
|
if let Some(demangled) = rlwinmdec::decode(&state.text) {
|
||||||
|
ui.scope(|ui| {
|
||||||
|
ui.style_mut().override_text_style = Some(TextStyle::Monospace);
|
||||||
|
ui.colored_label(appearance.replace_color, &demangled);
|
||||||
|
});
|
||||||
|
if ui.button("Copy").clicked() {
|
||||||
|
ui.output_mut(|output| output.copied_text = demangled);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ui.scope(|ui| {
|
||||||
|
ui.style_mut().override_text_style = Some(TextStyle::Monospace);
|
||||||
|
ui.colored_label(appearance.replace_color, "[invalid]");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ use objdiff_core::{
|
|||||||
diff::{ObjDiff, ObjSymbolDiff},
|
diff::{ObjDiff, ObjSymbolDiff},
|
||||||
obj::{ObjInfo, ObjSection, ObjSectionKind, ObjSymbol, ObjSymbolFlags, SymbolRef},
|
obj::{ObjInfo, ObjSection, ObjSectionKind, ObjSymbol, ObjSymbolFlags, SymbolRef},
|
||||||
};
|
};
|
||||||
|
use regex::{Regex, RegexBuilder};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app::AppConfigRef,
|
app::AppConfigRef,
|
||||||
@@ -33,6 +34,7 @@ pub enum View {
|
|||||||
SymbolDiff,
|
SymbolDiff,
|
||||||
FunctionDiff,
|
FunctionDiff,
|
||||||
DataDiff,
|
DataDiff,
|
||||||
|
ExtabDiff,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
@@ -43,6 +45,7 @@ pub struct DiffViewState {
|
|||||||
pub symbol_state: SymbolViewState,
|
pub symbol_state: SymbolViewState,
|
||||||
pub function_state: FunctionViewState,
|
pub function_state: FunctionViewState,
|
||||||
pub search: String,
|
pub search: String,
|
||||||
|
pub search_regex: Option<Regex>,
|
||||||
pub queue_build: bool,
|
pub queue_build: bool,
|
||||||
pub build_running: bool,
|
pub build_running: bool,
|
||||||
pub scratch_available: bool,
|
pub scratch_available: bool,
|
||||||
@@ -57,6 +60,7 @@ pub struct SymbolViewState {
|
|||||||
pub reverse_fn_order: bool,
|
pub reverse_fn_order: bool,
|
||||||
pub disable_reverse_fn_order: bool,
|
pub disable_reverse_fn_order: bool,
|
||||||
pub show_hidden_symbols: bool,
|
pub show_hidden_symbols: bool,
|
||||||
|
pub queue_extab_decode: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DiffViewState {
|
impl DiffViewState {
|
||||||
@@ -131,7 +135,12 @@ pub fn match_color_for_symbol(match_percent: f32, appearance: &Appearance) -> Co
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn symbol_context_menu_ui(ui: &mut Ui, symbol: &ObjSymbol) {
|
fn symbol_context_menu_ui(
|
||||||
|
ui: &mut Ui,
|
||||||
|
state: &mut SymbolViewState,
|
||||||
|
symbol: &ObjSymbol,
|
||||||
|
section: Option<&ObjSection>,
|
||||||
|
) {
|
||||||
ui.scope(|ui| {
|
ui.scope(|ui| {
|
||||||
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
|
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
|
||||||
ui.style_mut().wrap = Some(false);
|
ui.style_mut().wrap = Some(false);
|
||||||
@@ -152,6 +161,17 @@ fn symbol_context_menu_ui(ui: &mut Ui, symbol: &ObjSymbol) {
|
|||||||
ui.close_menu();
|
ui.close_menu();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if let Some(section) = section {
|
||||||
|
if symbol.has_extab && ui.button("Decode exception table").clicked() {
|
||||||
|
state.queue_extab_decode = true;
|
||||||
|
state.selected_symbol = Some(SymbolRefByName {
|
||||||
|
symbol_name: symbol.name.clone(),
|
||||||
|
demangled_symbol_name: symbol.demangled_name.clone(),
|
||||||
|
section_name: section.name.clone(),
|
||||||
|
});
|
||||||
|
ui.close_menu();
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,6 +193,20 @@ fn symbol_hover_ui(ui: &mut Ui, symbol: &ObjSymbol, appearance: &Appearance) {
|
|||||||
if let Some(address) = symbol.virtual_address {
|
if let Some(address) = symbol.virtual_address {
|
||||||
ui.colored_label(appearance.replace_color, format!("Virtual address: {:#x}", address));
|
ui.colored_label(appearance.replace_color, format!("Virtual address: {:#x}", address));
|
||||||
}
|
}
|
||||||
|
if symbol.has_extab {
|
||||||
|
if let (Some(extab_name), Some(extabindex_name)) =
|
||||||
|
(&symbol.extab_name, &symbol.extabindex_name)
|
||||||
|
{
|
||||||
|
ui.colored_label(
|
||||||
|
appearance.highlight_color,
|
||||||
|
format!("Extab Symbol: {}", extab_name),
|
||||||
|
);
|
||||||
|
ui.colored_label(
|
||||||
|
appearance.highlight_color,
|
||||||
|
format!("Extabindex Symbol: {}", extabindex_name),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,6 +233,7 @@ fn symbol_ui(
|
|||||||
{
|
{
|
||||||
selected = symbol_diff.symbol_ref == sym_ref;
|
selected = symbol_diff.symbol_ref == sym_ref;
|
||||||
}
|
}
|
||||||
|
if !symbol.flags.0.is_empty() {
|
||||||
write_text("[", appearance.text_color, &mut job, appearance.code_font.clone());
|
write_text("[", appearance.text_color, &mut job, appearance.code_font.clone());
|
||||||
if symbol.flags.0.contains(ObjSymbolFlags::Common) {
|
if symbol.flags.0.contains(ObjSymbolFlags::Common) {
|
||||||
write_text("c", appearance.replace_color, &mut job, appearance.code_font.clone());
|
write_text("c", appearance.replace_color, &mut job, appearance.code_font.clone());
|
||||||
@@ -211,9 +246,15 @@ fn symbol_ui(
|
|||||||
write_text("w", appearance.text_color, &mut job, appearance.code_font.clone());
|
write_text("w", appearance.text_color, &mut job, appearance.code_font.clone());
|
||||||
}
|
}
|
||||||
if symbol.flags.0.contains(ObjSymbolFlags::Hidden) {
|
if symbol.flags.0.contains(ObjSymbolFlags::Hidden) {
|
||||||
write_text("h", appearance.deemphasized_text_color, &mut job, appearance.code_font.clone());
|
write_text(
|
||||||
|
"h",
|
||||||
|
appearance.deemphasized_text_color,
|
||||||
|
&mut job,
|
||||||
|
appearance.code_font.clone(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
write_text("] ", appearance.text_color, &mut job, appearance.code_font.clone());
|
write_text("] ", appearance.text_color, &mut job, appearance.code_font.clone());
|
||||||
|
}
|
||||||
if let Some(match_percent) = symbol_diff.match_percent {
|
if let Some(match_percent) = symbol_diff.match_percent {
|
||||||
write_text("(", appearance.text_color, &mut job, appearance.code_font.clone());
|
write_text("(", appearance.text_color, &mut job, appearance.code_font.clone());
|
||||||
write_text(
|
write_text(
|
||||||
@@ -228,7 +269,7 @@ fn symbol_ui(
|
|||||||
let response = SelectableLabel::new(selected, job)
|
let response = SelectableLabel::new(selected, job)
|
||||||
.ui(ui)
|
.ui(ui)
|
||||||
.on_hover_ui_at_pointer(|ui| symbol_hover_ui(ui, symbol, appearance));
|
.on_hover_ui_at_pointer(|ui| symbol_hover_ui(ui, symbol, appearance));
|
||||||
response.context_menu(|ui| symbol_context_menu_ui(ui, symbol));
|
response.context_menu(|ui| symbol_context_menu_ui(ui, state, symbol, section));
|
||||||
if response.clicked() {
|
if response.clicked() {
|
||||||
if let Some(section) = section {
|
if let Some(section) = section {
|
||||||
if section.kind == ObjSectionKind::Code {
|
if section.kind == ObjSectionKind::Code {
|
||||||
@@ -258,17 +299,23 @@ fn symbol_ui(
|
|||||||
(None, None)
|
(None, None)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//If the decode extab context menu option was clicked, switch to the extab view
|
||||||
|
if state.queue_extab_decode {
|
||||||
|
ret = Some(View::ExtabDiff);
|
||||||
|
state.queue_extab_decode = false;
|
||||||
|
}
|
||||||
|
|
||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
|
|
||||||
fn symbol_matches_search(symbol: &ObjSymbol, search_str: &str) -> bool {
|
fn symbol_matches_search(symbol: &ObjSymbol, search_regex: Option<&Regex>) -> bool {
|
||||||
search_str.is_empty()
|
if let Some(search_regex) = search_regex {
|
||||||
|| symbol.name.contains(search_str)
|
search_regex.is_match(&symbol.name)
|
||||||
|| symbol
|
|| symbol.demangled_name.as_ref().map(|s| search_regex.is_match(s)).unwrap_or(false)
|
||||||
.demangled_name
|
} else {
|
||||||
.as_ref()
|
true
|
||||||
.map(|s| s.to_ascii_lowercase().contains(search_str))
|
}
|
||||||
.unwrap_or(false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
@@ -276,7 +323,7 @@ fn symbol_list_ui(
|
|||||||
ui: &mut Ui,
|
ui: &mut Ui,
|
||||||
obj: &(ObjInfo, ObjDiff),
|
obj: &(ObjInfo, ObjDiff),
|
||||||
state: &mut SymbolViewState,
|
state: &mut SymbolViewState,
|
||||||
lower_search: &str,
|
search_regex: Option<&Regex>,
|
||||||
appearance: &Appearance,
|
appearance: &Appearance,
|
||||||
left: bool,
|
left: bool,
|
||||||
) -> Option<View> {
|
) -> Option<View> {
|
||||||
@@ -289,6 +336,9 @@ fn symbol_list_ui(
|
|||||||
if !obj.0.common.is_empty() {
|
if !obj.0.common.is_empty() {
|
||||||
CollapsingHeader::new(".comm").default_open(true).show(ui, |ui| {
|
CollapsingHeader::new(".comm").default_open(true).show(ui, |ui| {
|
||||||
for (symbol, symbol_diff) in obj.0.common.iter().zip(&obj.1.common) {
|
for (symbol, symbol_diff) in obj.0.common.iter().zip(&obj.1.common) {
|
||||||
|
if !symbol_matches_search(symbol, search_regex) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
ret = ret.or(symbol_ui(
|
ret = ret.or(symbol_ui(
|
||||||
ui,
|
ui,
|
||||||
symbol,
|
symbol,
|
||||||
@@ -336,7 +386,7 @@ fn symbol_list_ui(
|
|||||||
for (symbol, symbol_diff) in
|
for (symbol, symbol_diff) in
|
||||||
section.symbols.iter().zip(§ion_diff.symbols).rev()
|
section.symbols.iter().zip(§ion_diff.symbols).rev()
|
||||||
{
|
{
|
||||||
if !symbol_matches_search(symbol, lower_search) {
|
if !symbol_matches_search(symbol, search_regex) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
ret = ret.or(symbol_ui(
|
ret = ret.or(symbol_ui(
|
||||||
@@ -353,7 +403,7 @@ fn symbol_list_ui(
|
|||||||
for (symbol, symbol_diff) in
|
for (symbol, symbol_diff) in
|
||||||
section.symbols.iter().zip(§ion_diff.symbols)
|
section.symbols.iter().zip(§ion_diff.symbols)
|
||||||
{
|
{
|
||||||
if !symbol_matches_search(symbol, lower_search) {
|
if !symbol_matches_search(symbol, search_regex) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
ret = ret.or(symbol_ui(
|
ret = ret.or(symbol_ui(
|
||||||
@@ -407,7 +457,7 @@ fn missing_obj_ui(ui: &mut Ui, appearance: &Appearance) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn symbol_diff_ui(ui: &mut Ui, state: &mut DiffViewState, appearance: &Appearance) {
|
pub fn symbol_diff_ui(ui: &mut Ui, state: &mut DiffViewState, appearance: &Appearance) {
|
||||||
let DiffViewState { build, current_view, symbol_state, search, .. } = state;
|
let DiffViewState { build, current_view, symbol_state, search, search_regex, .. } = state;
|
||||||
let Some(result) = build else {
|
let Some(result) = build else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@@ -442,7 +492,17 @@ pub fn symbol_diff_ui(ui: &mut Ui, state: &mut DiffViewState, appearance: &Appea
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
TextEdit::singleline(search).hint_text("Filter symbols").ui(ui);
|
if TextEdit::singleline(search).hint_text("Filter symbols").ui(ui).changed() {
|
||||||
|
if search.is_empty() {
|
||||||
|
*search_regex = None;
|
||||||
|
} else if let Ok(regex) =
|
||||||
|
RegexBuilder::new(search).case_insensitive(true).build()
|
||||||
|
{
|
||||||
|
*search_regex = Some(regex);
|
||||||
|
} else {
|
||||||
|
*search_regex = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -480,7 +540,6 @@ pub fn symbol_diff_ui(ui: &mut Ui, state: &mut DiffViewState, appearance: &Appea
|
|||||||
|
|
||||||
// Table
|
// Table
|
||||||
let mut ret = None;
|
let mut ret = None;
|
||||||
let lower_search = search.to_ascii_lowercase();
|
|
||||||
StripBuilder::new(ui).size(Size::remainder()).vertical(|mut strip| {
|
StripBuilder::new(ui).size(Size::remainder()).vertical(|mut strip| {
|
||||||
strip.strip(|builder| {
|
strip.strip(|builder| {
|
||||||
builder.sizes(Size::remainder(), 2).horizontal(|mut strip| {
|
builder.sizes(Size::remainder(), 2).horizontal(|mut strip| {
|
||||||
@@ -492,7 +551,7 @@ pub fn symbol_diff_ui(ui: &mut Ui, state: &mut DiffViewState, appearance: &Appea
|
|||||||
ui,
|
ui,
|
||||||
obj,
|
obj,
|
||||||
symbol_state,
|
symbol_state,
|
||||||
&lower_search,
|
search_regex.as_ref(),
|
||||||
appearance,
|
appearance,
|
||||||
true,
|
true,
|
||||||
));
|
));
|
||||||
@@ -512,7 +571,7 @@ pub fn symbol_diff_ui(ui: &mut Ui, state: &mut DiffViewState, appearance: &Appea
|
|||||||
ui,
|
ui,
|
||||||
obj,
|
obj,
|
||||||
symbol_state,
|
symbol_state,
|
||||||
&lower_search,
|
search_regex.as_ref(),
|
||||||
appearance,
|
appearance,
|
||||||
false,
|
false,
|
||||||
));
|
));
|
||||||
|
|||||||
Reference in New Issue
Block a user