From fe8e7029f62083384391b3167c6d18c292933459 Mon Sep 17 00:00:00 2001 From: Anghelo Carvajal Date: Mon, 22 Sep 2025 20:24:29 -0300 Subject: [PATCH] Arch-independent demangling and add `gnuv2_demangle` for old g++ projects (#262) --- Cargo.lock | 7 ++++ objdiff-core/Cargo.toml | 25 ++++++++------- objdiff-core/config-schema.json | 32 ++++++++++++++++++ objdiff-core/src/arch/arm.rs | 13 +------- objdiff-core/src/arch/arm64.rs | 12 +------ objdiff-core/src/arch/mips.rs | 9 +----- objdiff-core/src/arch/mod.rs | 2 -- objdiff-core/src/arch/ppc/mod.rs | 12 ------- objdiff-core/src/arch/superh/mod.rs | 8 +---- objdiff-core/src/arch/x86.rs | 12 +------ objdiff-core/src/diff/demangler.rs | 50 +++++++++++++++++++++++++++++ objdiff-core/src/diff/mod.rs | 1 + objdiff-core/src/obj/read.rs | 16 ++++++--- objdiff-gui/src/views/demangle.rs | 12 ++++++- 14 files changed, 132 insertions(+), 79 deletions(-) create mode 100644 objdiff-core/src/diff/demangler.rs diff --git a/Cargo.lock b/Cargo.lock index dd9a5b8..a91c47f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2020,6 +2020,12 @@ dependencies = [ "gl_generator", ] +[[package]] +name = "gnuv2_demangle" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73012a2b235359cdf9e71e153da760f7268a4ad1e2c86fa3a70dca2113015695" + [[package]] name = "gpu-alloc" version = "0.6.0" @@ -3504,6 +3510,7 @@ dependencies = [ "flagset", "gimli 0.32.0", "globset", + "gnuv2_demangle", "heck", "iced-x86", "insta", diff --git a/objdiff-core/Cargo.toml b/objdiff-core/Cargo.toml index 52acf71..eb91ad8 100644 --- a/objdiff-core/Cargo.toml +++ b/objdiff-core/Cargo.toml @@ -41,7 +41,8 @@ any-arch = [ "dep:regex", "dep:similar", "dep:syn", - "dep:encoding_rs" + "dep:encoding_rs", + "demangler", ] bindings = [ "dep:prost", @@ -88,38 +89,37 @@ std = [ ] mips = [ "any-arch", - "dep:cpp_demangle", - "dep:cwdemangle", "dep:rabbitizer", ] ppc = [ "any-arch", - "dep:cwdemangle", "dep:cwextab", "dep:powerpc", "dep:rlwinmdec", ] x86 = [ "any-arch", - "dep:cpp_demangle", "dep:iced-x86", - "dep:msvc-demangler", ] arm = [ "any-arch", "dep:arm-attr", - "dep:cpp_demangle", "dep:unarm", ] arm64 = [ "any-arch", - "dep:cpp_demangle", "dep:yaxpeax-arch", "dep:yaxpeax-arm", ] superh = [ "any-arch", ] +demangler = [ + "dep:cpp_demangle", + "dep:cwdemangle", + "dep:gnuv2_demangle", + "dep:msvc-demangler", +] [package.metadata.docs.rs] features = ["all"] @@ -150,7 +150,6 @@ gimli = { git = "https://github.com/gimli-rs/gimli", rev = "7335f00e7c39fd501511 typed-arena = { version = "2.0", default-features = false, optional = true } # ppc -cwdemangle = { version = "1.0", optional = true } cwextab = { version = "1.1", optional = true } powerpc = { version = "0.4", optional = true } rlwinmdec = { version = "1.1", optional = true } @@ -159,9 +158,7 @@ rlwinmdec = { version = "1.1", optional = true } rabbitizer = { version = "2.0.0-alpha.4", default-features = false, features = ["all_extensions"], optional = true } # x86 -cpp_demangle = { version = "0.4", default-features = false, features = ["alloc"], optional = true } iced-x86 = { version = "1.21", default-features = false, features = ["decoder", "intel", "gas", "masm", "nasm", "exhaustive_enums", "no_std"], optional = true } -msvc-demangler = { version = "0.11", optional = true } # arm unarm = { version = "1.9", optional = true } @@ -179,6 +176,12 @@ tempfile = { version = "3.20", optional = true } time = { version = "0.3", optional = true } encoding_rs = { version = "0.8.35", optional = true } +# demangler +cpp_demangle = { version = "0.4", optional = true, default-features = false, features = ["alloc"] } +cwdemangle = { version = "1.0", optional = true } +gnuv2_demangle = { version = "0.1.0", optional = true } +msvc-demangler = { version = "0.11", optional = true } + [target.'cfg(windows)'.dependencies] winapi = { version = "0.3", optional = true, features = ["winbase"] } diff --git a/objdiff-core/config-schema.json b/objdiff-core/config-schema.json index 9dd46d3..792b3c8 100644 --- a/objdiff-core/config-schema.json +++ b/objdiff-core/config-schema.json @@ -25,6 +25,37 @@ } ] }, + { + "id": "demangler", + "type": "choice", + "default": "auto", + "name": "Demangler", + "description": "Which demangler should be used to demangle each symbol.", + "items": [ + { + "value": "auto", + "name": "Auto", + "description": "Try to automatically guess the mangling format." + }, + { + "value": "codewarrior", + "name": "CodeWarrior" + }, + { + "value": "msvc", + "name": "MSVC" + }, + { + "value": "itanium", + "name": "Itanium" + }, + { + "value": "gnu_legacy", + "name": "GNU g++ (Legacy)", + "description": "Use the old GNU mangling ABI. Used up to g++ 2.9.x" + } + ] + }, { "id": "analyzeDataFlow", "type": "boolean", @@ -259,6 +290,7 @@ "name": "General", "properties": [ "functionRelocDiffs", + "demangler", "spaceBetweenArgs", "combineDataSections", "combineTextSections" diff --git a/objdiff-core/src/arch/arm.rs b/objdiff-core/src/arch/arm.rs index 4397336..fc67d10 100644 --- a/objdiff-core/src/arch/arm.rs +++ b/objdiff-core/src/arch/arm.rs @@ -1,9 +1,4 @@ -use alloc::{ - collections::BTreeMap, - format, - string::{String, ToString}, - vec::Vec, -}; +use alloc::{collections::BTreeMap, format, string::ToString, vec::Vec}; use anyhow::{Result, bail}; use arm_attr::{BuildAttrs, enums::CpuArch, tag::Tag}; @@ -409,12 +404,6 @@ impl Arch for ArchArm { } } - fn demangle(&self, name: &str) -> Option { - cpp_demangle::Symbol::new(name) - .ok() - .and_then(|s| s.demangle(&cpp_demangle::DemangleOptions::default()).ok()) - } - fn reloc_name(&self, flags: RelocationFlags) -> Option<&'static str> { match flags { RelocationFlags::Elf(r_type) => match r_type { diff --git a/objdiff-core/src/arch/arm64.rs b/objdiff-core/src/arch/arm64.rs index e9d9a63..891a105 100644 --- a/objdiff-core/src/arch/arm64.rs +++ b/objdiff-core/src/arch/arm64.rs @@ -1,8 +1,4 @@ -use alloc::{ - format, - string::{String, ToString}, - vec::Vec, -}; +use alloc::{format, string::ToString, vec::Vec}; use core::cmp::Ordering; use anyhow::Result; @@ -108,12 +104,6 @@ impl Arch for ArchArm64 { Ok(()) } - fn demangle(&self, name: &str) -> Option { - cpp_demangle::Symbol::new(name) - .ok() - .and_then(|s| s.demangle(&cpp_demangle::DemangleOptions::default()).ok()) - } - fn reloc_name(&self, flags: RelocationFlags) -> Option<&'static str> { match flags { RelocationFlags::Elf(r_type) => match r_type { diff --git a/objdiff-core/src/arch/mips.rs b/objdiff-core/src/arch/mips.rs index 134e2f1..d38fc2d 100644 --- a/objdiff-core/src/arch/mips.rs +++ b/objdiff-core/src/arch/mips.rs @@ -1,6 +1,6 @@ use alloc::{ collections::{BTreeMap, BTreeSet}, - string::{String, ToString}, + string::ToString, vec::Vec, }; @@ -304,13 +304,6 @@ impl Arch for ArchMips { } } - fn demangle(&self, name: &str) -> Option { - cpp_demangle::Symbol::new(name) - .ok() - .and_then(|s| s.demangle(&cpp_demangle::DemangleOptions::default()).ok()) - .or_else(|| cwdemangle::demangle(name, &cwdemangle::DemangleOptions::default())) - } - fn reloc_name(&self, flags: RelocationFlags) -> Option<&'static str> { match flags { RelocationFlags::Elf(r_type) => match r_type { diff --git a/objdiff-core/src/arch/mod.rs b/objdiff-core/src/arch/mod.rs index 3505ea9..601847d 100644 --- a/objdiff-core/src/arch/mod.rs +++ b/objdiff-core/src/arch/mod.rs @@ -371,8 +371,6 @@ pub trait Arch: Any + Debug + Send + Sync { Ok(None) } - fn demangle(&self, _name: &str) -> Option { None } - fn reloc_name(&self, _flags: RelocationFlags) -> Option<&'static str> { None } fn data_reloc_size(&self, flags: RelocationFlags) -> usize; diff --git a/objdiff-core/src/arch/ppc/mod.rs b/objdiff-core/src/arch/ppc/mod.rs index c23ba95..31ca501 100644 --- a/objdiff-core/src/arch/ppc/mod.rs +++ b/objdiff-core/src/arch/ppc/mod.rs @@ -308,18 +308,6 @@ impl Arch for ArchPpc { } } - fn demangle(&self, mut name: &str) -> Option { - if name.starts_with('?') { - msvc_demangler::demangle(name, msvc_demangler::DemangleFlags::llvm()).ok() - } else { - name = name.trim_start_matches('.'); - cpp_demangle::Symbol::new(name) - .ok() - .and_then(|s| s.demangle(&cpp_demangle::DemangleOptions::default()).ok()) - .or_else(|| cwdemangle::demangle(name, &cwdemangle::DemangleOptions::default())) - } - } - fn reloc_name(&self, flags: RelocationFlags) -> Option<&'static str> { match flags { RelocationFlags::Elf(r_type) => match r_type { diff --git a/objdiff-core/src/arch/superh/mod.rs b/objdiff-core/src/arch/superh/mod.rs index 39fc453..fe5ba9c 100644 --- a/objdiff-core/src/arch/superh/mod.rs +++ b/objdiff-core/src/arch/superh/mod.rs @@ -1,4 +1,4 @@ -use alloc::{collections::BTreeMap, format, string::String, vec, vec::Vec}; +use alloc::{collections::BTreeMap, format, vec, vec::Vec}; use anyhow::Result; use object::elf; @@ -132,12 +132,6 @@ impl Arch for ArchSuperH { Ok(()) } - fn demangle(&self, name: &str) -> Option { - cpp_demangle::Symbol::new(name) - .ok() - .and_then(|s| s.demangle(&cpp_demangle::DemangleOptions::default()).ok()) - } - fn reloc_name(&self, flags: RelocationFlags) -> Option<&'static str> { match flags { RelocationFlags::Elf(r_type) => match r_type { diff --git a/objdiff-core/src/arch/x86.rs b/objdiff-core/src/arch/x86.rs index ab5ffec..0e4bee1 100644 --- a/objdiff-core/src/arch/x86.rs +++ b/objdiff-core/src/arch/x86.rs @@ -1,4 +1,4 @@ -use alloc::{boxed::Box, format, string::String, vec::Vec}; +use alloc::{boxed::Box, format, vec::Vec}; use core::cmp::Ordering; use anyhow::{Context, Result, anyhow, bail}; @@ -300,16 +300,6 @@ impl Arch for ArchX86 { Ok(Some(RelocationOverride { target: RelocationOverrideTarget::Keep, addend })) } - fn demangle(&self, name: &str) -> Option { - if name.starts_with('?') { - msvc_demangler::demangle(name, msvc_demangler::DemangleFlags::llvm()).ok() - } else { - cpp_demangle::Symbol::new(name) - .ok() - .and_then(|s| s.demangle(&cpp_demangle::DemangleOptions::default()).ok()) - } - } - fn reloc_name(&self, flags: RelocationFlags) -> Option<&'static str> { match self.arch { Architecture::X86 => match flags { diff --git a/objdiff-core/src/diff/demangler.rs b/objdiff-core/src/diff/demangler.rs new file mode 100644 index 0000000..5bfbb90 --- /dev/null +++ b/objdiff-core/src/diff/demangler.rs @@ -0,0 +1,50 @@ +use alloc::string::String; + +use crate::diff::Demangler; + +#[cfg(feature = "demangler")] +impl Demangler { + pub fn demangle(&self, name: &str) -> Option { + match self { + Demangler::Codewarrior => Self::demangle_codewarrior(name), + Demangler::Msvc => Self::demangle_msvc(name), + Demangler::Itanium => Self::demangle_itanium(name), + Demangler::GnuLegacy => Self::demangle_gnu_legacy(name), + Demangler::Auto => { + // Try to guess + if name.starts_with('?') { + Self::demangle_msvc(name) + } else { + Self::demangle_codewarrior(name) + .or_else(|| Self::demangle_gnu_legacy(name)) + .or_else(|| Self::demangle_itanium(name)) + } + } + } + } + + fn demangle_codewarrior(name: &str) -> Option { + cwdemangle::demangle(name, &cwdemangle::DemangleOptions::default()) + } + + fn demangle_msvc(name: &str) -> Option { + msvc_demangler::demangle(name, msvc_demangler::DemangleFlags::llvm()).ok() + } + + fn demangle_itanium(name: &str) -> Option { + let name = name.trim_start_matches('.'); + cpp_demangle::Symbol::new(name) + .ok() + .and_then(|s| s.demangle(&cpp_demangle::DemangleOptions::default()).ok()) + } + + fn demangle_gnu_legacy(name: &str) -> Option { + let name = name.trim_start_matches('.'); + gnuv2_demangle::demangle(name, &gnuv2_demangle::DemangleConfig::new_no_cfilt_mimics()).ok() + } +} + +#[cfg(not(feature = "demangler"))] +impl Demangler { + pub fn demangle(&self, _name: &str) -> Option { None } +} diff --git a/objdiff-core/src/diff/mod.rs b/objdiff-core/src/diff/mod.rs index 26cc7aa..efbe2c2 100644 --- a/objdiff-core/src/diff/mod.rs +++ b/objdiff-core/src/diff/mod.rs @@ -22,6 +22,7 @@ use crate::{ pub mod code; pub mod data; +pub mod demangler; pub mod display; include!(concat!(env!("OUT_DIR"), "/config.gen.rs")); diff --git a/objdiff-core/src/obj/read.rs b/objdiff-core/src/obj/read.rs index 2bb6ad1..2dea685 100644 --- a/objdiff-core/src/obj/read.rs +++ b/objdiff-core/src/obj/read.rs @@ -41,6 +41,7 @@ fn map_symbol( symbol: &object::Symbol, section_indices: &[usize], split_meta: Option<&SplitMeta>, + config: &DiffObjConfig, ) -> Result { let mut name = symbol.name().context("Failed to process symbol name")?.to_string(); let mut size = symbol.size(); @@ -90,7 +91,7 @@ fn map_symbol( _ => SymbolKind::Unknown, }; let address = arch.symbol_address(symbol.address(), kind); - let demangled_name = arch.demangle(&name); + let demangled_name = config.demangler.demangle(&name); // Find the virtual address for the symbol if available let virtual_address = split_meta .and_then(|m| m.virtual_addresses.as_ref()) @@ -116,6 +117,7 @@ fn map_symbols( sections: &[Section], section_indices: &[usize], split_meta: Option<&SplitMeta>, + config: &DiffObjConfig, ) -> Result<(Vec, Vec)> { let symbol_count = obj_file.symbols().count(); let mut symbols = Vec::::with_capacity(symbol_count + obj_file.sections().count()); @@ -124,7 +126,7 @@ fn map_symbols( if symbol_indices.len() <= obj_symbol.index().0 { symbol_indices.resize(obj_symbol.index().0 + 1, usize::MAX); } - let symbol = map_symbol(arch, obj_file, &obj_symbol, section_indices, split_meta)?; + let symbol = map_symbol(arch, obj_file, &obj_symbol, section_indices, split_meta, config)?; symbol_indices[obj_symbol.index().0] = symbols.len(); symbols.push(symbol); } @@ -997,8 +999,14 @@ pub fn parse(data: &[u8], config: &DiffObjConfig, diff_side: DiffSide) -> Result let split_meta = parse_split_meta(&obj_file)?; let (mut sections, section_indices) = map_sections(arch.as_ref(), &obj_file, split_meta.as_ref())?; - let (mut symbols, symbol_indices) = - map_symbols(arch.as_ref(), &obj_file, §ions, §ion_indices, split_meta.as_ref())?; + let (mut symbols, symbol_indices) = map_symbols( + arch.as_ref(), + &obj_file, + §ions, + §ion_indices, + split_meta.as_ref(), + config, + )?; map_relocations(arch.as_ref(), &obj_file, &mut sections, §ion_indices, &symbol_indices)?; parse_line_info(&obj_file, &mut sections, §ion_indices, data)?; if config.combine_data_sections || config.combine_text_sections { diff --git a/objdiff-gui/src/views/demangle.rs b/objdiff-gui/src/views/demangle.rs index c00f1c4..31a4788 100644 --- a/objdiff-gui/src/views/demangle.rs +++ b/objdiff-gui/src/views/demangle.rs @@ -1,10 +1,12 @@ use egui::TextStyle; +use objdiff_core::diff::{ConfigEnum, Demangler}; use crate::views::appearance::Appearance; #[derive(Default)] pub struct DemangleViewState { pub text: String, + pub demangler: Demangler, } pub fn demangle_window( @@ -14,9 +16,17 @@ pub fn demangle_window( appearance: &Appearance, ) { egui::Window::new("Demangle").open(show).show(ctx, |ui| { + egui::ComboBox::from_label("Demangler") + .selected_text(state.demangler.name().to_string()) + .show_ui(ui, |ui| { + for demangler in Demangler::variants() { + ui.selectable_value(&mut state.demangler, *demangler, demangler.name()); + } + }); + ui.separator(); ui.text_edit_singleline(&mut state.text); ui.add_space(10.0); - if let Some(demangled) = cwdemangle::demangle(&state.text, &Default::default()) { + if let Some(demangled) = state.demangler.demangle(&state.text) { ui.scope(|ui| { ui.style_mut().override_text_style = Some(TextStyle::Monospace); ui.colored_label(appearance.replace_color, &demangled);