diff --git a/src/libs/elf/src/elf32.rs b/src/libs/elf/src/elf32.rs index 23fa20fff2..85eaa6e5ce 100644 --- a/src/libs/elf/src/elf32.rs +++ b/src/libs/elf/src/elf32.rs @@ -356,6 +356,23 @@ pub const STT_OBJECT: u8 = 1; /// Function entry point. pub const STT_FUNC: u8 = 2; +//================================================================================================== +// Symbol Bindings (high 4 bits of `st_info`) +//================================================================================================== + +/// Right-shift required to extract the symbol binding from `st_info`. +pub const ST_BIND_SHIFT: u8 = 4; +/// Local symbol — not visible outside the object file containing its definition. +pub const STB_LOCAL: u8 = 0; +/// Global symbol — visible to all object files being combined. +pub const STB_GLOBAL: u8 = 1; +/// Weak symbol — resembles a global symbol but has lower precedence. Per the System V ABI +/// (gABI, chapter "Symbol Table"), an undefined weak symbol that cannot be resolved at +/// dynamic-link time is taken to have address zero (or `NULL` for function symbols). This +/// is the contract every mainstream ELF dynamic loader (glibc, musl, FreeBSD `rtld-elf`, +/// Android Bionic) implements, and which our `dlfcn` loader honours. +pub const STB_WEAK: u8 = 2; + //================================================================================================== // ELF32 Section Header //================================================================================================== @@ -437,6 +454,11 @@ impl Elf32Sym { pub fn st_type(&self) -> u8 { self.st_info & ST_TYPE_MASK } + + /// Returns the symbol binding (high 4 bits of `st_info`). + pub fn st_bind(&self) -> u8 { + self.st_info >> ST_BIND_SHIFT + } } //================================================================================================== diff --git a/src/libs/elf/src/relocation.rs b/src/libs/elf/src/relocation.rs index 4fa2e6647b..66b5efc258 100644 --- a/src/libs/elf/src/relocation.rs +++ b/src/libs/elf/src/relocation.rs @@ -62,7 +62,11 @@ use ::goblin::{ }, section_header::SHN_UNDEF, sym::{ + st_bind, st_type, + STB_GLOBAL, + STB_LOCAL, + STB_WEAK, STT_FUNC, STT_OBJECT, }, @@ -343,6 +347,44 @@ pub enum SymbolType { Object = STT_OBJECT, } +//================================================================================================== +// Symbol Binding +//================================================================================================== + +/// +/// # Description +/// +/// A high-level representation of the binding attribute encoded in the high 4 bits of an +/// ELF symbol's `st_info` field. +/// +/// Bindings control how the link editor and the dynamic loader treat a symbol when multiple +/// definitions are visible and when no definition can be found. +/// +/// Per the System V ABI (gABI, chapter "Symbol Table"): +/// - `STB_LOCAL` — definitions are not visible to other object files. +/// - `STB_GLOBAL` — definitions are visible to all combined object files; an undefined +/// global reference that cannot be resolved is an error. +/// - `STB_WEAK` — like global, but with lower precedence; **an undefined weak reference +/// that cannot be resolved at dynamic-link time is silently taken to be the value zero** +/// (or `NULL` for function symbols). The dynamic loader is required to honour this rule. +/// +#[repr(u8)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive)] +pub enum SymbolBinding { + /// Local symbol — invisible outside the defining object file. + Local = STB_LOCAL, + /// Global symbol — visible to all object files. + Global = STB_GLOBAL, + /// Weak symbol — global with lower precedence; an unresolved weak undefined reference + /// is resolved to address zero per the System V ABI. + Weak = STB_WEAK, + /// Any other binding value defined by the platform or the processor supplement + /// (for example reserved or processor-specific bindings) that we do not interpret + /// further. + #[num_enum(default)] + Other, +} + //================================================================================================== // Symbol //================================================================================================== @@ -386,6 +428,20 @@ impl Symbol { st_type(self.0.st_info).into() } + /// + /// # Description + /// + /// Returns the binding attribute of the symbol (high 4 bits of `st_info`). + /// + /// # Returns + /// + /// A [`SymbolBinding`] value. Bindings the loader does not interpret further are + /// returned as [`SymbolBinding::Other`]. + /// + pub fn binding(&self) -> SymbolBinding { + st_bind(self.0.st_info).into() + } + /// /// # Description /// @@ -425,6 +481,23 @@ impl Symbol { self.0.st_shndx as u32 == SHN_UNDEF } + /// + /// # Description + /// + /// Tests if the symbol has weak binding (`STB_WEAK`). + /// + /// Per the System V ABI, a weak undefined symbol that cannot be resolved at + /// dynamic-link time is silently resolved to address zero. Loaders that consume this + /// helper are expected to follow that rule rather than reporting a lookup failure. + /// + /// # Returns + /// + /// `true` if the symbol's binding is `STB_WEAK`, `false` otherwise. + /// + pub fn is_weak(&self) -> bool { + self.binding() == SymbolBinding::Weak + } + /// /// # Description /// @@ -599,3 +672,82 @@ impl RelocationEntry { self.0.r_offset } } + +//================================================================================================== +// Unit Tests +//================================================================================================== + +#[cfg(test)] +mod tests { + use super::*; + use crate::elf32::STT_NOTYPE; + use ::goblin::elf32::sym::Sym; + + /// Builds a Symbol with the given binding (high 4 bits) and type (low 4 bits), + /// and the given section index. + fn make_symbol(binding: u8, sym_type: u8, st_shndx: u16) -> Symbol { + Symbol(Sym { + st_name: 0, + st_value: 0, + st_size: 0, + st_info: (binding << 4) | (sym_type & 0xf), + st_other: 0, + st_shndx, + }) + } + + #[test] + fn binding_decodes_local_global_weak() { + let local = make_symbol(STB_LOCAL, STT_FUNC, 1); + let global = make_symbol(STB_GLOBAL, STT_FUNC, 1); + let weak = make_symbol(STB_WEAK, STT_FUNC, 1); + + assert_eq!(local.binding(), SymbolBinding::Local); + assert_eq!(global.binding(), SymbolBinding::Global); + assert_eq!(weak.binding(), SymbolBinding::Weak); + } + + #[test] + fn binding_falls_back_to_other_for_unknown_values() { + // Reserved/processor-specific binding values must not be classified as a known + // binding; the loader is expected to treat them conservatively (i.e., not weak). + let exotic = make_symbol(10, STT_FUNC, 1); + assert_eq!(exotic.binding(), SymbolBinding::Other); + assert!(!exotic.is_weak()); + } + + #[test] + fn is_weak_matches_stb_weak_only() { + assert!(!make_symbol(STB_LOCAL, STT_FUNC, 0).is_weak()); + assert!(!make_symbol(STB_GLOBAL, STT_FUNC, 0).is_weak()); + assert!(make_symbol(STB_WEAK, STT_FUNC, 0).is_weak()); + assert!(make_symbol(STB_WEAK, STT_OBJECT, 0).is_weak()); + } + + #[test] + fn is_undefined_and_is_weak_are_independent() { + // Weak + undefined is the case the dynamic loader must resolve to 0. + let weak_undef = make_symbol(STB_WEAK, STT_NOTYPE, SHN_UNDEF as u16); + assert!(weak_undef.is_undefined()); + assert!(weak_undef.is_weak()); + + // Weak + defined: a definition that may be overridden by a strong one. + let weak_def = make_symbol(STB_WEAK, STT_FUNC, 1); + assert!(!weak_def.is_undefined()); + assert!(weak_def.is_weak()); + + // Strong + undefined: must remain an error in the loader. + let strong_undef = make_symbol(STB_GLOBAL, STT_NOTYPE, SHN_UNDEF as u16); + assert!(strong_undef.is_undefined()); + assert!(!strong_undef.is_weak()); + } + + #[test] + fn binding_does_not_depend_on_type_nibble() { + // The low 4 bits encode the symbol type; the high 4 bits encode the binding. + // Changing the type must not affect the binding decoding. + for sym_type in [STT_NOTYPE, STT_OBJECT, STT_FUNC] { + assert_eq!(make_symbol(STB_WEAK, sym_type, 1).binding(), SymbolBinding::Weak); + } + } +} diff --git a/src/libs/syscall/src/dlfcn/syscall/dynlib.rs b/src/libs/syscall/src/dlfcn/syscall/dynlib.rs index ceb85cbfa8..d0b6c954e7 100644 --- a/src/libs/syscall/src/dlfcn/syscall/dynlib.rs +++ b/src/libs/syscall/src/dlfcn/syscall/dynlib.rs @@ -561,6 +561,29 @@ impl DynamicLibrary { let symbol_value: usize = match self.lookup(symbol_name)? { Some((base, symbol_value)) => base + symbol_value, None => { + // Per the System V ABI (gABI, chapter "Symbol Table"), an undefined + // symbol whose binding is `STB_WEAK` and which cannot be resolved at + // dynamic-link time is silently taken to have the value zero (or `NULL` + // for function symbols). Every mainstream ELF dynamic loader (glibc + // `elf/dl-lookup.c`, musl `ldso/dynlink.c`, FreeBSD `rtld-elf/rtld.c`, + // Android Bionic `linker/linker_relocate.cpp`) implements this rule, + // and we follow them here. + // + // Substituting zero is safe across the relocation types we currently + // handle (R_386_32, R_386_PC32, R_386_JMP_SLOT, R_386_GLOB_DAT): the + // resulting GOT/PLT entry or in-place 32-bit slot will be null, so any + // code path that actually dereferences the symbol traps deterministically + // — matching the contract the spec puts on the program (it must + // null-check before use). + if sym.is_undefined() && sym.is_weak() { + ::syslog::debug!( + "get_symbol_value(): resolving unresolved weak undefined symbol to zero \ + per System V ABI (symbol_name={:?})", + symbol_name + ); + return Ok(0); + } + let reason: &str = "symbol not found"; ::syslog::warn!( "get_symbol_value(): {} (symbol_name={:?}, symbol={:?})", @@ -586,6 +609,17 @@ impl DynamicLibrary { None; for sym in self.dynsym.iter() { + // Skip undefined symbols: with the STB_WEAK handling in + // `get_symbol_value()`, an unresolved weak undefined symbol resolves + // to 0 — that's the right behaviour for relocation but would cause + // `dladdr()` to report a ghost symbol at address 0 for every weak + // undefined entry in the dynsym. Symbols that have an in-module + // definition (or that resolved to a real address elsewhere) are + // never `SHN_UNDEF` in this DSO's dynsym, so this filter only + // excludes references the loader had to substitute zero for. + if sym.is_undefined() { + continue; + } if let Ok(symbol_value) = self.get_symbol_value(sym) { let sym_addr: VirtualAddress = VirtualAddress::from_raw_value(symbol_value); if sym_addr <= symbol_addr {