diff --git a/.gitignore b/.gitignore index e365361..e79a2ac 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ android.keystore *.frag *.vert *.metal +.vscode/launch.json diff --git a/Cargo.lock b/Cargo.lock index f7b6012..e9b3f78 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -970,6 +970,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2e06f9bce634a3c898eb1e5cb949ff63133cbb218af93cc9b38b31d6f3ea285" +[[package]] +name = "cwextab" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92e840be31d11ead5f357b8fc503133d3657845d0ccf539afcc7d1212fad226" +dependencies = [ + "anyhow", +] + [[package]] name = "d3d12" version = "0.19.0" @@ -2815,6 +2824,7 @@ dependencies = [ "byteorder", "cpp_demangle", "cwdemangle", + "cwextab", "filetime", "flagset", "gimli 0.29.0", @@ -2846,6 +2856,7 @@ dependencies = [ "console_error_panic_hook", "const_format", "cwdemangle", + "cwextab", "dirs", "eframe", "egui", diff --git a/objdiff-core/Cargo.toml b/objdiff-core/Cargo.toml index e6b2171..6aa8fb6 100644 --- a/objdiff-core/Cargo.toml +++ b/objdiff-core/Cargo.toml @@ -17,7 +17,7 @@ any-arch = [] # Implicit, used to check if any arch is enabled config = ["globset", "semver", "serde_json", "serde_yaml"] dwarf = ["gimli"] mips = ["any-arch", "rabbitizer"] -ppc = ["any-arch", "cwdemangle", "ppc750cl"] +ppc = ["any-arch", "cwdemangle", "cwextab", "ppc750cl"] x86 = ["any-arch", "cpp_demangle", "iced-x86", "msvc-demangler"] arm = ["any-arch", "cpp_demangle", "unarm", "arm-attr"] @@ -45,6 +45,7 @@ gimli = { version = "0.29.0", default-features = false, features = ["read-all"], # ppc 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 } # mips diff --git a/objdiff-core/src/obj/mod.rs b/objdiff-core/src/obj/mod.rs index 6769f1f..128b536 100644 --- a/objdiff-core/src/obj/mod.rs +++ b/objdiff-core/src/obj/mod.rs @@ -3,6 +3,7 @@ pub mod split_meta; use std::{borrow::Cow, collections::BTreeMap, fmt, path::PathBuf}; +use cwextab::*; use filetime::FileTime; use flagset::{flags, FlagSet}; use object::RelocationFlags; @@ -113,6 +114,9 @@ pub struct ObjIns { pub struct ObjSymbol { pub name: String, pub demangled_name: Option, + pub has_extab: bool, + pub extab_name: Option, + pub extabindex_name: Option, pub address: u64, pub section_address: u64, pub size: u64, @@ -123,6 +127,13 @@ pub struct ObjSymbol { pub virtual_address: Option, } +#[derive(Debug, Clone)] +pub struct ObjExtab { + pub func: ObjSymbol, + pub data: ExceptionTableData, + pub dtors: Vec, +} + pub struct ObjInfo { pub arch: Box, pub path: PathBuf, @@ -130,6 +141,8 @@ pub struct ObjInfo { pub sections: Vec, /// Common BSS symbols pub common: Vec, + /// Exception tables + pub extab: Option>, /// Split object metadata (.note.split section) pub split_meta: Option, } diff --git a/objdiff-core/src/obj/read.rs b/objdiff-core/src/obj/read.rs index 4d800b6..ab14314 100644 --- a/objdiff-core/src/obj/read.rs +++ b/objdiff-core/src/obj/read.rs @@ -2,11 +2,12 @@ use std::{collections::HashSet, fs, io::Cursor, path::Path}; use anyhow::{anyhow, bail, ensure, Context, Result}; use byteorder::{BigEndian, ReadBytesExt}; +use cwextab::decode_extab; use filetime::FileTime; use flagset::Flags; use object::{ - BinaryFormat, File, Object, ObjectSection, ObjectSymbol, RelocationTarget, SectionIndex, - SectionKind, Symbol, SymbolKind, SymbolScope, SymbolSection, + Architecture, BinaryFormat, File, Object, ObjectSection, ObjectSymbol, RelocationTarget, + SectionIndex, SectionKind, Symbol, SymbolKind, SymbolScope, SymbolSection, }; use crate::{ @@ -14,7 +15,8 @@ use crate::{ diff::DiffObjConfig, obj::{ split_meta::{SplitMeta, SPLITMETA_SECTION}, - ObjInfo, ObjReloc, ObjSection, ObjSectionKind, ObjSymbol, ObjSymbolFlagSet, ObjSymbolFlags, + ObjExtab, ObjInfo, ObjReloc, ObjSection, ObjSectionKind, ObjSymbol, ObjSymbolFlagSet, + ObjSymbolFlags, }, }; @@ -71,6 +73,9 @@ fn to_obj_symbol( Ok(ObjSymbol { name: name.to_string(), demangled_name, + has_extab: false, + extab_name: None, + extabindex_name: None, address, section_address, size: symbol.size(), @@ -170,6 +175,111 @@ fn common_symbols( .collect::>>() } +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>> { + //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 = 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 = 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( arch: &dyn ObjArch, obj_file: &File<'_>, @@ -205,6 +315,9 @@ fn find_section_symbol( Ok(ObjSymbol { name: name.to_string(), demangled_name: None, + has_extab: false, + extab_name: None, + extabindex_name: None, address: offset, section_address: address - section.address(), size: 0, @@ -367,6 +480,9 @@ fn update_combined_symbol(symbol: ObjSymbol, address_change: i64) -> Result Result { } line_info(&obj_file, &mut sections)?; 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 { diff --git a/objdiff-gui/Cargo.toml b/objdiff-gui/Cargo.toml index 955b7a7..6a86121 100644 --- a/objdiff-gui/Cargo.toml +++ b/objdiff-gui/Cargo.toml @@ -30,6 +30,7 @@ cfg-if = "1.0.0" const_format = "0.2.32" cwdemangle = "1.0.0" rlwinmdec = "1.0.1" +cwextab = "0.2.3" dirs = "5.0.1" egui = "0.27.2" egui_extras = "0.27.2" diff --git a/objdiff-gui/src/app.rs b/objdiff-gui/src/app.rs index 0e74073..639d126 100644 --- a/objdiff-gui/src/app.rs +++ b/objdiff-gui/src/app.rs @@ -35,6 +35,7 @@ use crate::{ data_diff::data_diff_ui, debug::debug_window, demangle::{demangle_window, DemangleViewState}, + extab_diff::extab_diff_ui, frame_history::FrameHistory, function_diff::function_diff_ui, graphics::{graphics_window, GraphicsConfig, GraphicsViewState}, @@ -591,6 +592,10 @@ impl eframe::App for App { egui::CentralPanel::default().show(ctx, |ui| { 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 { egui::SidePanel::left("side_panel").show(ctx, |ui| { egui::ScrollArea::both().show(ui, |ui| { diff --git a/objdiff-gui/src/views/extab_diff.rs b/objdiff-gui/src/views/extab_diff.rs new file mode 100644 index 0000000..934d6d5 --- /dev/null +++ b/objdiff-gui/src/views/extab_diff.rs @@ -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 { + 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 { + 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); + }); + }); + }); + }); +} diff --git a/objdiff-gui/src/views/mod.rs b/objdiff-gui/src/views/mod.rs index c24a30f..7b31c54 100644 --- a/objdiff-gui/src/views/mod.rs +++ b/objdiff-gui/src/views/mod.rs @@ -5,6 +5,7 @@ pub(crate) mod config; pub(crate) mod data_diff; pub(crate) mod debug; pub(crate) mod demangle; +pub(crate) mod extab_diff; pub(crate) mod file; pub(crate) mod frame_history; pub(crate) mod function_diff; diff --git a/objdiff-gui/src/views/symbol_diff.rs b/objdiff-gui/src/views/symbol_diff.rs index 7dadf98..430b31f 100644 --- a/objdiff-gui/src/views/symbol_diff.rs +++ b/objdiff-gui/src/views/symbol_diff.rs @@ -33,6 +33,7 @@ pub enum View { SymbolDiff, FunctionDiff, DataDiff, + ExtabDiff, } #[derive(Default)] @@ -57,6 +58,7 @@ pub struct SymbolViewState { pub reverse_fn_order: bool, pub disable_reverse_fn_order: bool, pub show_hidden_symbols: bool, + pub queue_extab_decode: bool, } impl DiffViewState { @@ -131,7 +133,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.style_mut().override_text_style = Some(egui::TextStyle::Monospace); ui.style_mut().wrap = Some(false); @@ -152,6 +159,17 @@ fn symbol_context_menu_ui(ui: &mut Ui, symbol: &ObjSymbol) { 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 +191,20 @@ fn symbol_hover_ui(ui: &mut Ui, symbol: &ObjSymbol, appearance: &Appearance) { if let Some(address) = symbol.virtual_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), + ); + } + } }); } @@ -228,7 +260,7 @@ fn symbol_ui( let response = SelectableLabel::new(selected, job) .ui(ui) .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 let Some(section) = section { if section.kind == ObjSectionKind::Code { @@ -258,6 +290,13 @@ fn symbol_ui( (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 }