Exception table diff view (#82)

* Basic integration

* Implement basic right click option

Needs lotsa work

* nothing to worry about

* Convert extab diff to separate view

* Make clippy and fmt shut up

* Make clippy fmt shut up for real this time

* Print extab/extabindex symbol names in extab view

* I hate fmt

* Basic integration

* Implement basic right click option

Needs lotsa work

* nothing to worry about

* Convert extab diff to separate view

* Make clippy and fmt shut up

* Make clippy fmt shut up for real this time

* Print extab/extabindex symbol names in extab view

* I hate fmt

* Fix scroll position not being maintained from extab view

* Silly me

* Add rlwinm decoder window

* Remove extra files

* Create Cargo.lock

* Show extab symbol names in hover window

* Appease fmt

* Update symbol_diff.rs

* Update symbol_diff.rs

* Get extab symbol from extabindex relocations instead

* Update Cargo.lock

* Update Cargo.lock
This commit is contained in:
Amber Brault 2024-07-22 00:25:54 -04:00 committed by GitHub
parent 9f71ce9fea
commit 75b0e7d9e5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 414 additions and 7 deletions

1
.gitignore vendored
View File

@ -22,3 +22,4 @@ android.keystore
*.frag *.frag
*.vert *.vert
*.metal *.metal
.vscode/launch.json

11
Cargo.lock generated
View File

@ -970,6 +970,15 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2e06f9bce634a3c898eb1e5cb949ff63133cbb218af93cc9b38b31d6f3ea285" checksum = "c2e06f9bce634a3c898eb1e5cb949ff63133cbb218af93cc9b38b31d6f3ea285"
[[package]]
name = "cwextab"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a92e840be31d11ead5f357b8fc503133d3657845d0ccf539afcc7d1212fad226"
dependencies = [
"anyhow",
]
[[package]] [[package]]
name = "d3d12" name = "d3d12"
version = "0.19.0" version = "0.19.0"
@ -2815,6 +2824,7 @@ dependencies = [
"byteorder", "byteorder",
"cpp_demangle", "cpp_demangle",
"cwdemangle", "cwdemangle",
"cwextab",
"filetime", "filetime",
"flagset", "flagset",
"gimli 0.29.0", "gimli 0.29.0",
@ -2846,6 +2856,7 @@ dependencies = [
"console_error_panic_hook", "console_error_panic_hook",
"const_format", "const_format",
"cwdemangle", "cwdemangle",
"cwextab",
"dirs", "dirs",
"eframe", "eframe",
"egui", "egui",

View File

@ -17,7 +17,7 @@ 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-attr"] arm = ["any-arch", "cpp_demangle", "unarm", "arm-attr"]
@ -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

View File

@ -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>,
} }

View File

@ -2,11 +2,12 @@ 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::{
@ -14,7 +15,8 @@ use crate::{
diff::DiffObjConfig, 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,
}, },
}; };
@ -71,6 +73,9 @@ fn to_obj_symbol(
Ok(ObjSymbol { Ok(ObjSymbol {
name: name.to_string(), name: name.to_string(),
demangled_name, demangled_name,
has_extab: false,
extab_name: None,
extabindex_name: None,
address, address,
section_address, section_address,
size: symbol.size(), size: symbol.size(),
@ -170,6 +175,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<'_>,
@ -205,6 +315,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,
@ -367,6 +480,9 @@ fn update_combined_symbol(symbol: ObjSymbol, address_change: i64) -> Result<ObjS
Ok(ObjSymbol { Ok(ObjSymbol {
name: symbol.name, name: symbol.name,
demangled_name: symbol.demangled_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()?, address: (symbol.address as i64 + address_change).try_into()?,
section_address: (symbol.section_address as i64 + address_change).try_into()?, section_address: (symbol.section_address as i64 + address_change).try_into()?,
size: symbol.size, size: symbol.size,
@ -482,7 +598,8 @@ pub fn read(obj_path: &Path, config: &DiffObjConfig) -> Result<ObjInfo> {
} }
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> {

View File

@ -30,6 +30,7 @@ cfg-if = "1.0.0"
const_format = "0.2.32" const_format = "0.2.32"
cwdemangle = "1.0.0" cwdemangle = "1.0.0"
rlwinmdec = "1.0.1" rlwinmdec = "1.0.1"
cwextab = "0.2.3"
dirs = "5.0.1" dirs = "5.0.1"
egui = "0.27.2" egui = "0.27.2"
egui_extras = "0.27.2" egui_extras = "0.27.2"

View File

@ -35,6 +35,7 @@ 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}, graphics::{graphics_window, GraphicsConfig, GraphicsViewState},
@ -591,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| {

View 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);
});
});
});
});
}

View File

@ -5,6 +5,7 @@ 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;

View File

@ -33,6 +33,7 @@ pub enum View {
SymbolDiff, SymbolDiff,
FunctionDiff, FunctionDiff,
DataDiff, DataDiff,
ExtabDiff,
} }
#[derive(Default)] #[derive(Default)]
@ -57,6 +58,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 +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.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 +159,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 +191,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),
);
}
}
}); });
} }
@ -228,7 +260,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,6 +290,13 @@ 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
} }