mirror of
https://github.com/encounter/objdiff.git
synced 2025-12-20 18:29:19 +00:00
Compare commits
7 Commits
v2.0.0-alp
...
v2.0.0-alp
| Author | SHA1 | Date | |
|---|---|---|---|
| 233839346a | |||
| 95615c2ec5 | |||
|
|
97981160f4 | ||
|
|
1fd901a863 | ||
| 759d55994a | |||
| 9710ccc38a | |||
| 79cd460333 |
702
Cargo.lock
generated
702
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-alpha.5"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.70"
|
rust-version = "1.70"
|
||||||
authors = ["Luke Street <luke@street.dev>"]
|
authors = ["Luke Street <luke@street.dev>"]
|
||||||
|
|||||||
@@ -809,23 +809,29 @@ 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
|
||||||
|
};
|
||||||
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-alpha.5"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.70"
|
rust-version = "1.70"
|
||||||
authors = ["Luke Street <luke@street.dev>"]
|
authors = ["Luke Street <luke@street.dev>"]
|
||||||
@@ -19,7 +19,7 @@ dwarf = ["gimli"]
|
|||||||
mips = ["any-arch", "rabbitizer"]
|
mips = ["any-arch", "rabbitizer"]
|
||||||
ppc = ["any-arch", "cwdemangle", "ppc750cl"]
|
ppc = ["any-arch", "cwdemangle", "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"
|
||||||
@@ -56,4 +56,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.3.0", optional = true }
|
||||||
|
arm-attr = { version = "0.1.1", optional = true }
|
||||||
|
|||||||
@@ -4,9 +4,11 @@ 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},
|
||||||
@@ -16,21 +18,68 @@ use unarm::{
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
arch::{ObjArch, ProcessCodeResult},
|
arch::{ObjArch, ProcessCodeResult},
|
||||||
diff::DiffObjConfig,
|
diff::{ArmArchVersion, 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,21 @@ 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 mut parser = Parser::new(version, first_mapping, start_addr, endian, code);
|
||||||
|
|
||||||
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 +160,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 +167,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(_)));
|
||||||
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -138,11 +210,42 @@ impl ObjArch for ObjArchArm {
|
|||||||
|
|
||||||
fn implcit_addend(
|
fn implcit_addend(
|
||||||
&self,
|
&self,
|
||||||
_section: &ObjSection,
|
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> {
|
||||||
@@ -209,6 +312,7 @@ 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;
|
||||||
@@ -242,7 +346,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)));
|
||||||
}
|
}
|
||||||
@@ -282,7 +386,21 @@ fn push_args(
|
|||||||
offset.reg.to_string().into(),
|
offset.reg.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.to_string().into())))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,8 +27,8 @@ pub struct ObjArchMips {
|
|||||||
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,13 +38,19 @@ 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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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, Object, ObjectSymbol, Relocation, RelocationFlags, Symbol};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
diff::DiffObjConfig,
|
diff::DiffObjConfig,
|
||||||
@@ -34,6 +34,8 @@ pub trait ObjArch: Send + Sync {
|
|||||||
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 {
|
||||||
|
|||||||
@@ -93,6 +93,30 @@ 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,
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
const fn default_true() -> bool { true }
|
const fn default_true() -> bool { true }
|
||||||
|
|
||||||
@@ -102,11 +126,14 @@ 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,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for DiffObjConfig {
|
impl Default for DiffObjConfig {
|
||||||
@@ -114,9 +141,11 @@ 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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
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};
|
||||||
@@ -11,6 +11,7 @@ use object::{
|
|||||||
|
|
||||||
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,
|
ObjInfo, ObjReloc, ObjSection, ObjSectionKind, ObjSymbol, ObjSymbolFlagSet, ObjSymbolFlags,
|
||||||
@@ -54,12 +55,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 +71,7 @@ fn to_obj_symbol(
|
|||||||
Ok(ObjSymbol {
|
Ok(ObjSymbol {
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
demangled_name,
|
demangled_name,
|
||||||
address: symbol.address(),
|
address,
|
||||||
section_address,
|
section_address,
|
||||||
size: symbol.size(),
|
size: symbol.size(),
|
||||||
size_known: symbol.size() != 0,
|
size_known: symbol.size() != 0,
|
||||||
@@ -361,7 +363,105 @@ 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,
|
||||||
|
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,6 +477,9 @@ 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 })
|
Ok(ObjInfo { arch, path: obj_path.to_owned(), timestamp, sections, common, split_meta })
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "objdiff-gui"
|
name = "objdiff-gui"
|
||||||
version = "2.0.0-alpha.3"
|
version = "2.0.0-alpha.5"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.70"
|
rust-version = "1.70"
|
||||||
authors = ["Luke Street <luke@street.dev>"]
|
authors = ["Luke Street <luke@street.dev>"]
|
||||||
@@ -19,7 +19,8 @@ path = "src/main.rs"
|
|||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["wgpu", "wsl"]
|
default = ["wgpu", "wsl"]
|
||||||
wgpu = ["eframe/wgpu"]
|
glow = ["eframe/glow"]
|
||||||
|
wgpu = ["eframe/wgpu", "dep:wgpu"]
|
||||||
wsl = []
|
wsl = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
@@ -29,7 +30,6 @@ cfg-if = "1.0.0"
|
|||||||
const_format = "0.2.32"
|
const_format = "0.2.32"
|
||||||
cwdemangle = "1.0.0"
|
cwdemangle = "1.0.0"
|
||||||
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,7 +37,7 @@ 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"
|
||||||
@@ -50,6 +50,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"] }
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ use crate::{
|
|||||||
demangle::{demangle_window, DemangleViewState},
|
demangle::{demangle_window, DemangleViewState},
|
||||||
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,
|
||||||
symbol_diff::{symbol_diff_ui, DiffViewState, View},
|
symbol_diff::{symbol_diff_ui, DiffViewState, View},
|
||||||
},
|
},
|
||||||
@@ -48,12 +49,14 @@ pub struct ViewState {
|
|||||||
pub config_state: ConfigViewState,
|
pub config_state: ConfigViewState,
|
||||||
pub demangle_state: DemangleViewState,
|
pub demangle_state: DemangleViewState,
|
||||||
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_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 +212,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 +226,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 +251,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".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 +299,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 +340,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 +422,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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -410,12 +451,14 @@ impl eframe::App for App {
|
|||||||
config_state,
|
config_state,
|
||||||
demangle_state,
|
demangle_state,
|
||||||
diff_state,
|
diff_state,
|
||||||
|
graphics_state,
|
||||||
frame_history,
|
frame_history,
|
||||||
show_appearance_config,
|
show_appearance_config,
|
||||||
show_demangle,
|
show_demangle,
|
||||||
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 +500,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);
|
||||||
}
|
}
|
||||||
@@ -512,6 +559,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;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -543,6 +600,7 @@ impl eframe::App for App {
|
|||||||
demangle_window(ctx, show_demangle, demangle_state, appearance);
|
demangle_window(ctx, show_demangle, demangle_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()))?,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,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,6 +33,8 @@ 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() {
|
||||||
@@ -42,6 +46,7 @@ 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 exec_path_clone = exec_path.clone();
|
||||||
let mut native_options =
|
let mut native_options =
|
||||||
@@ -54,14 +59,47 @@ fn main() {
|
|||||||
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(
|
eframe::run_native(
|
||||||
"objdiff",
|
APP_NAME,
|
||||||
native_options,
|
native_options,
|
||||||
Box::new(move |cc| Box::new(app::App::new(cc, utc_offset, exec_path_clone))),
|
Box::new(move |cc| {
|
||||||
|
Box::new(app::App::new(
|
||||||
|
cc,
|
||||||
|
utc_offset,
|
||||||
|
exec_path_clone,
|
||||||
|
app_path,
|
||||||
|
graphics_config,
|
||||||
|
graphics_config_path,
|
||||||
|
))
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
.expect("Failed to run eframe application");
|
.expect("Failed to run eframe application");
|
||||||
|
|
||||||
@@ -77,9 +115,7 @@ fn main() {
|
|||||||
} 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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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, 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,22 @@ 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
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(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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ pub(crate) mod demangle;
|
|||||||
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 symbol_diff;
|
pub(crate) mod symbol_diff;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user