Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions src/libs/elf/src/elf32.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
//==================================================================================================
Expand Down Expand Up @@ -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
}
}

//==================================================================================================
Expand Down
152 changes: 152 additions & 0 deletions src/libs/elf/src/relocation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
Expand Down Expand Up @@ -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
//==================================================================================================
Expand Down Expand Up @@ -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
///
Expand Down Expand Up @@ -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
///
Expand Down Expand Up @@ -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);
}
}
}
34 changes: 34 additions & 0 deletions src/libs/syscall/src/dlfcn/syscall/dynlib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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={:?})",
Expand All @@ -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 {
Expand Down