From a7cc3352dbae065cbac118fcef9d0cd7be7d0f97 Mon Sep 17 00:00:00 2001 From: MonsterDruide1 <5958456@gmail.com> Date: Thu, 21 May 2026 01:44:27 +0200 Subject: [PATCH] viking: Check metadata of symbols in `.dynsym` --- viking/src/elf.rs | 27 +++++-- viking/src/tools/check.rs | 116 ++++++++++++++++++++++++++++++- viking/src/tools/list_symbols.rs | 7 +- 3 files changed, 139 insertions(+), 11 deletions(-) diff --git a/viking/src/elf.rs b/viking/src/elf.rs index 1fbb5d6..3d4b2af 100644 --- a/viking/src/elf.rs +++ b/viking/src/elf.rs @@ -138,10 +138,10 @@ pub struct SymbolStringTable<'elf> { } impl<'elf> SymbolStringTable<'elf> { - pub fn from_elf(elf: &'elf OwnedElf) -> Result { + pub fn from_elf(elf: &'elf OwnedElf, sh_type: u32) -> Result { let bytes = &*elf.as_owner().1; for shdr in &elf.section_headers { - if shdr.sh_type == section_header::SHT_SYMTAB { + if shdr.sh_type == sh_type { let table_hdr = elf .section_headers .get(shdr.sh_link as usize) @@ -182,14 +182,14 @@ pub fn is_undefined_sym(sym: &Sym) -> bool { } pub fn find_function_symbol_by_name(elf: &OwnedElf, name: &str) -> Result { - let strtab = SymbolStringTable::from_elf(elf)?; + let strtab = SymbolStringTable::from_elf(elf, section_header::SHT_SYMTAB)?; for symbol in elf.syms.iter().filter(filter_out_useless_syms) { if name == strtab.get_string(symbol.st_name) { return Ok(symbol); } } - bail!("unknown function") + bail!("Unknown function: {:?}", name) } pub fn make_symbol_map_by_name(elf: &OwnedElf) -> Result> { @@ -198,7 +198,7 @@ pub fn make_symbol_map_by_name(elf: &OwnedElf) -> Result> Default::default(), ); - let strtab = SymbolStringTable::from_elf(elf)?; + let strtab = SymbolStringTable::from_elf(elf, section_header::SHT_SYMTAB)?; for symbol in elf.syms.iter().filter(filter_out_useless_syms) { map.entry(strtab.get_string(symbol.st_name)) @@ -207,6 +207,21 @@ pub fn make_symbol_map_by_name(elf: &OwnedElf) -> Result> Ok(map) } +pub fn make_dynsym_map_by_name(elf: &OwnedElf) -> Result> { + let mut map = SymbolTableByName::with_capacity_and_hasher( + elf.dynsyms.iter().filter(filter_out_useless_syms).count(), + Default::default(), + ); + + let strtab = SymbolStringTable::from_elf(elf, section_header::SHT_DYNSYM)?; + + for symbol in elf.dynsyms.iter().filter(filter_out_useless_syms) { + map.entry(strtab.get_string(symbol.st_name)) + .or_insert(symbol); + } + Ok(map) +} + pub fn make_symbol_map_by_addr(elf: &OwnedElf) -> SymbolTableByAddr { let mut map = SymbolTableByAddr::with_capacity_and_hasher( elf.syms.iter().filter(filter_out_useless_syms).count(), @@ -224,7 +239,7 @@ pub fn make_addr_to_name_map(elf: &OwnedElf) -> Result> { Default::default(), ); - let strtab = SymbolStringTable::from_elf(elf)?; + let strtab = SymbolStringTable::from_elf(elf, section_header::SHT_SYMTAB)?; for symbol in elf.syms.iter().filter(filter_out_useless_syms) { map.entry(symbol.st_value) diff --git a/viking/src/tools/check.rs b/viking/src/tools/check.rs index 08339e7..d60bd4e 100644 --- a/viking/src/tools/check.rs +++ b/viking/src/tools/check.rs @@ -66,12 +66,16 @@ fn main() -> Result<()> { let mut decomp_glob_data_table = None; let mut functions = None; let mut plt_functions = None; + let mut orig_dynsym = None; + let mut decomp_dynsym = None; rayon::scope(|s| { s.spawn(|_| decomp_symtab = Some(elf::make_symbol_map_by_name(&decomp_elf))); s.spawn(|_| decomp_glob_data_table = Some(elf::build_glob_data_table(&decomp_elf))); s.spawn(|_| functions = Some(functions::get_functions(version))); s.spawn(|_| plt_functions = Some(elf::get_plt_functions(&orig_elf))); + s.spawn(|_| orig_dynsym = Some(elf::make_dynsym_map_by_name(&orig_elf))); + s.spawn(|_| decomp_dynsym = Some(elf::make_dynsym_map_by_name(&decomp_elf))); }); let decomp_symtab = decomp_symtab @@ -83,6 +87,15 @@ fn main() -> Result<()> { let functions = functions.unwrap().context("failed to load function CSV")?; let plt_functions = plt_functions.unwrap().context("failed to load plt functions")?; + + let orig_dynsym = orig_dynsym + .unwrap() + .context("failed to make original ELF dynsym map")?; + + let decomp_dynsym = decomp_dynsym + .unwrap() + .context("failed to make decomp ELF dynsym map")?; + let all_functions = vec![functions.clone(), plt_functions].concat(); let checker = FunctionChecker::new( @@ -97,10 +110,15 @@ fn main() -> Result<()> { if let Some(func) = &args.function { check_single(&checker, &functions, &all_functions, func, &args)?; - } else { - check_all(&checker, &functions, &args)?; + return Ok(()) + } + check_all(&checker, &all_functions, &args)?; + + check_symbols(&orig_dynsym, &decomp_dynsym)?; + + eprintln!("{}", "OK".green().bold()); Ok(()) } @@ -473,7 +491,6 @@ fn check_all(checker: &FunctionChecker, functions: &[functions::Info], args: &Ar if failed.load(atomic::Ordering::Relaxed) { bail!("found at least one error"); } else { - eprintln!("{}", "OK".green().bold()); Ok(()) } } @@ -752,3 +769,96 @@ fn check_mismatch_comment( Ok(()) } + +#[derive(Debug)] +pub enum SymbolMismatchCause { + // st_size + Size(u64, u64), + // st_info + Bind(u8, u8), + Type(u8, u8), + // st_other + Visibility(u8, u8), +} + +impl std::fmt::Display for SymbolMismatchCause { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let name = match &self { + Self::Size(_, _) => "Size", + Self::Bind(_, _) => "Bind", + Self::Type(_, _) => "Type", + Self::Visibility(_, _) => "Visibility", + }; + let orig = match &self { + Self::Size(orig_size, _) => orig_size.to_string(), + Self::Bind(orig_bind, _) => goblin::elf::sym::bind_to_str(*orig_bind).to_string(), + Self::Type(orig_type, _) => goblin::elf::sym::type_to_str(*orig_type).to_string(), + Self::Visibility(orig_vis, _) => goblin::elf::sym::visibility_to_str(*orig_vis).to_string(), + }; + let decomp = match &self { + Self::Size(_, decomp_size) => decomp_size.to_string(), + Self::Bind(_, decomp_bind) => goblin::elf::sym::bind_to_str(*decomp_bind).to_string(), + Self::Type(_, decomp_type) => goblin::elf::sym::type_to_str(*decomp_type).to_string(), + Self::Visibility(_, decomp_vis) => goblin::elf::sym::visibility_to_str(*decomp_vis).to_string(), + }; + write!( + f, + "incorrect {name}; expected to see {orig}\n\ + --> decomp contains {decomp} instead", + name = name, + orig = orig, + decomp = decomp, + ) + } +} + +fn check_symbols( + orig_dynsym: &elf::SymbolTableByName, + decomp_dynsym: &elf::SymbolTableByName, +) -> Result<()> { + + let mut mismatching = false; + + for (name, symbol) in orig_dynsym.iter() { + let Some(decomp_symbol) = decomp_dynsym.get(name) else { + continue; + }; + + if let Some(mismatch) = check_symbol(symbol, decomp_symbol)? { + mismatching = true; + ui::print_error(&format!( + "symbol {} is different between the original and decomp ELF:\n{}", + ui::format_symbol_name(name), + mismatch + )); + } + } + + if mismatching { + bail!("found at least one error"); + } else { + Ok(()) + } +} + +pub fn check_symbol(symbol: &goblin::elf::Sym, decomp_symbol: &goblin::elf::Sym) -> Result> { + // mismatching function size will cause issues in FunctionChecker already, so ignore here + if symbol.st_size != decomp_symbol.st_size && !symbol.is_function() { + return Ok(Some(SymbolMismatchCause::Size(symbol.st_size, decomp_symbol.st_size))) + } + + // st_info + if symbol.st_bind() != decomp_symbol.st_bind() { + return Ok(Some(SymbolMismatchCause::Bind(symbol.st_bind(), decomp_symbol.st_bind()))) + } + if symbol.st_type() != decomp_symbol.st_type() { + return Ok(Some(SymbolMismatchCause::Type(symbol.st_type(), decomp_symbol.st_type()))) + } + + // st_other + if symbol.st_visibility() != decomp_symbol.st_visibility() { + return Ok(Some(SymbolMismatchCause::Visibility(symbol.st_visibility(), decomp_symbol.st_visibility()))) + } + + Ok(None) +} diff --git a/viking/src/tools/list_symbols.rs b/viking/src/tools/list_symbols.rs index ad39bd7..6c5f9cc 100644 --- a/viking/src/tools/list_symbols.rs +++ b/viking/src/tools/list_symbols.rs @@ -3,7 +3,10 @@ use argh::FromArgs; use colored::Colorize; use goblin::{ elf::Sym, - elf64::sym::{STT_FILE, STT_NOTYPE, STT_OBJECT}, + elf64::{ + section_header::SHT_SYMTAB, + sym::{STT_FILE, STT_NOTYPE, STT_OBJECT}, + }, }; use itertools::Itertools; use viking::{elf, functions}; @@ -41,7 +44,7 @@ fn main() -> Result<()> { let known_funcs = functions::make_known_function_name_map(&functions); let elf = elf::load_decomp_elf(args.version.as_deref())?; - let symtab = elf::SymbolStringTable::from_elf(&elf)?; + let symtab = elf::SymbolStringTable::from_elf(&elf, SHT_SYMTAB)?; let filter = |sym: &Sym| { if sym.st_type() == STT_NOTYPE && sym.st_value != 0 {