use std::collections::HashSet;
use std::path::Path;
use std::str;
use object::elf::*;
use object::read::elf::{
Dyn, FileHeader, ProgramHeader, Rel, Rela, SectionHeader, SectionTable, Sym, SymbolTable,
VersionTable,
};
use object::read::SymbolIndex;
use object::Endianness;
#[derive(Debug)]
pub struct SymbolRef {
pub name: String,
pub version: Option<String>,
pub weak: bool,
pub plt: bool,
pub copy: bool,
}
#[derive(Debug)]
pub struct VersionNeed {
pub file: String,
pub version: String,
pub weak: bool,
}
#[derive(Debug, Default)]
pub struct ObjectSymbols {
pub defined: HashSet<String>,
pub defined_versioned: HashSet<(String, String)>,
pub has_verdef: bool,
pub verdef_names: HashSet<String>,
pub verneeded: Vec<VersionNeed>,
pub references: Vec<SymbolRef>,
pub bind_now: bool,
}
pub fn parse<P: AsRef<Path>>(filename: &P) -> Option<ObjectSymbols> {
let file = std::fs::File::open(filename).ok()?;
let mmap = unsafe { memmap2::Mmap::map(&file) }.ok()?;
let data: &[u8] = &mmap;
match object::FileKind::parse(data).ok()? {
object::FileKind::Elf32 => parse_elf(FileHeader32::<Endianness>::parse(data).ok()?, data),
object::FileKind::Elf64 => parse_elf(FileHeader64::<Endianness>::parse(data).ok()?, data),
_ => None,
}
}
fn parse_elf<Elf: FileHeader<Endian = Endianness>>(
elf: &Elf,
data: &[u8],
) -> Option<ObjectSymbols> {
let endian = elf.endian().ok()?;
let sections = elf.sections(endian, data).ok()?;
let dynsyms = sections.symbols(endian, data, SHT_DYNSYM).ok()?;
let versions = sections.versions(endian, data).ok().flatten();
let mut obj = ObjectSymbols::default();
parse_verdef(&mut obj, endian, §ions, data);
parse_verneed(&mut obj, endian, §ions, data);
for (symidx, sym) in dynsyms.enumerate() {
if sym.st_shndx(endian) == SHN_UNDEF {
continue;
}
let bind = sym.st_bind();
if bind != STB_GLOBAL && bind != STB_WEAK && bind != STB_GNU_UNIQUE {
continue;
}
let visibility = sym.st_visibility();
if visibility != STV_DEFAULT && visibility != STV_PROTECTED {
continue;
}
if let Some(name) = symbol_name(endian, &dynsyms, sym) {
obj.defined.insert(name.to_string());
if let Some(version) = symbol_version(endian, &versions, symidx) {
obj.defined_versioned.insert((name.to_string(), version));
}
}
}
let dyninfo = parse_dynamic_info(endian, elf, data);
let jmprel = dyninfo.jmprel;
obj.bind_now = dyninfo.bind_now;
let is_mips64el = elf.is_mips64el(endian);
let copy_reloc = copy_relocation_type(elf.e_machine(endian));
let tlsdesc_reloc = tlsdesc_relocation_type(elf.e_machine(endian));
let mut seen = HashSet::<(String, bool)>::new();
for section in sections.iter() {
if !section.sh_flags(endian).contains(SHF_ALLOC) {
continue;
}
let plt = jmprel.is_some_and(|addr| section.sh_addr(endian).into() == addr);
if let Ok(Some((relas, _link))) = section.rela(endian, data) {
for rela in relas {
let r_type = rela.r_type(endian, is_mips64el);
add_reference(
endian,
&dynsyms,
&versions,
rela.symbol(endian, is_mips64el),
plt && tlsdesc_reloc != Some(r_type),
copy_reloc == Some(r_type),
&mut seen,
&mut obj.references,
);
}
} else if let Ok(Some((rels, _link))) = section.rel(endian, data) {
for rel in rels {
let (symidx, r_type) = if is_mips64el {
let info: u64 = rel.r_info(endian).into();
let info = (info << 32)
| ((info >> 8) & 0xff000000)
| ((info >> 24) & 0x00ff0000)
| ((info >> 40) & 0x0000ff00)
| ((info >> 56) & 0x000000ff);
let sym = (info >> 32) as u32;
(
if sym == 0 {
None
} else {
Some(SymbolIndex(sym as usize))
},
RelocationType((info & 0xffff_ffff) as u32),
)
} else {
(rel.symbol(endian), rel.r_type(endian))
};
add_reference(
endian,
&dynsyms,
&versions,
symidx,
plt && tlsdesc_reloc != Some(r_type),
copy_reloc == Some(r_type),
&mut seen,
&mut obj.references,
);
}
}
}
if elf.e_machine(endian) == EM_MIPS {
if let (Some(gotsym), Some(symtabno)) = (dyninfo.mips_gotsym, dyninfo.mips_symtabno) {
for idx in gotsym..symtabno {
let symidx = SymbolIndex(idx as usize);
let Ok(sym) = dynsyms.symbol(symidx) else {
continue;
};
let lazy = sym.st_type() == STT_FUNC && sym.st_value(endian).into() != 0;
add_reference(
endian,
&dynsyms,
&versions,
Some(symidx),
lazy,
false,
&mut seen,
&mut obj.references,
);
}
}
}
Some(obj)
}
fn symbol_name<'data, Elf: FileHeader>(
endian: Elf::Endian,
dynsyms: &SymbolTable<'data, Elf, &'data [u8]>,
sym: &Elf::Sym,
) -> Option<&'data str> {
let name = dynsyms.symbol_name(endian, sym).ok()?;
let name = str::from_utf8(name).ok()?;
if name.is_empty() {
return None;
}
Some(name)
}
fn copy_relocation_type(e_machine: Machine) -> Option<RelocationType> {
match e_machine {
EM_386 => Some(R_386_COPY),
EM_X86_64 => Some(R_X86_64_COPY),
EM_AARCH64 => Some(R_AARCH64_COPY),
EM_ARM => Some(R_ARM_COPY),
EM_68K => Some(R_68K_COPY),
EM_ALPHA => Some(R_ALPHA_COPY),
EM_CSKY => Some(R_CKCORE_COPY),
EM_IA_64 => Some(R_IA64_COPY),
EM_LOONGARCH => Some(R_LARCH_COPY),
EM_MICROBLAZE => Some(R_MICROBLAZE_COPY),
EM_MIPS => Some(R_MIPS_COPY),
EM_PARISC => Some(R_PARISC_COPY),
EM_PPC => Some(R_PPC_COPY),
EM_PPC64 => Some(R_PPC64_COPY),
EM_RISCV => Some(R_RISCV_COPY),
EM_S390 => Some(R_390_COPY),
EM_SH => Some(R_SH_COPY),
EM_SPARC | EM_SPARC32PLUS | EM_SPARCV9 => Some(R_SPARC_COPY),
_ => None,
}
}
fn tlsdesc_relocation_type(e_machine: Machine) -> Option<RelocationType> {
match e_machine {
EM_386 => Some(R_386_TLS_DESC),
EM_X86_64 => Some(R_X86_64_TLSDESC),
EM_AARCH64 => Some(R_AARCH64_TLSDESC),
EM_LOONGARCH => Some(R_LARCH_TLS_DESC64),
EM_RISCV => Some(R_RISCV_TLSDESC),
_ => None,
}
}
#[allow(clippy::too_many_arguments)]
fn add_reference<'data, Elf: FileHeader>(
endian: Elf::Endian,
dynsyms: &SymbolTable<'data, Elf, &'data [u8]>,
versions: &Option<VersionTable<'data, Elf>>,
symidx: Option<SymbolIndex>,
plt: bool,
copy: bool,
seen: &mut HashSet<(String, bool)>,
references: &mut Vec<SymbolRef>,
) {
let Some(symidx) = symidx else {
return;
};
let Ok(sym) = dynsyms.symbol(symidx) else {
return;
};
if sym.st_shndx(endian) != SHN_UNDEF && !copy {
return;
}
let Some(name) = symbol_name(endian, dynsyms, sym) else {
return;
};
if seen.insert((name.to_string(), plt)) {
references.push(SymbolRef {
name: name.to_string(),
version: symbol_version(endian, versions, symidx),
weak: sym.st_bind() == STB_WEAK,
plt,
copy,
});
}
}
fn symbol_version<'data, Elf: FileHeader>(
endian: Elf::Endian,
versions: &Option<VersionTable<'data, Elf>>,
symidx: SymbolIndex,
) -> Option<String> {
let versions = versions.as_ref()?;
let version = versions
.version(versions.version_index(endian, symidx).index())
.ok()??;
str::from_utf8(version.name()).ok().map(|s| s.to_string())
}
fn parse_verdef<'data, Elf: FileHeader>(
obj: &mut ObjectSymbols,
endian: Elf::Endian,
sections: &SectionTable<'data, Elf, &'data [u8]>,
data: &'data [u8],
) {
let Ok(Some((mut verdefs, link))) = sections.gnu_verdef(endian, data) else {
return;
};
let Ok(strings) = sections.strings(endian, data, link) else {
return;
};
while let Ok(Some((verdef, mut verdauxs))) = verdefs.next() {
obj.has_verdef = true;
if verdef.vd_flags.get(endian).contains(VER_FLG_BASE) {
continue;
}
if let Ok(Some(verdaux)) = verdauxs.next() {
if let Some(name) = verdaux
.name(endian, strings)
.ok()
.and_then(|name| str::from_utf8(name).ok())
{
obj.verdef_names.insert(name.to_string());
}
}
}
}
fn parse_verneed<'data, Elf: FileHeader>(
obj: &mut ObjectSymbols,
endian: Elf::Endian,
sections: &SectionTable<'data, Elf, &'data [u8]>,
data: &'data [u8],
) {
let Ok(Some((mut verneeds, link))) = sections.gnu_verneed(endian, data) else {
return;
};
let Ok(strings) = sections.strings(endian, data, link) else {
return;
};
while let Ok(Some((verneed, mut vernauxs))) = verneeds.next() {
let Some(file) = verneed
.file(endian, strings)
.ok()
.and_then(|file| str::from_utf8(file).ok())
else {
continue;
};
while let Ok(Some(vernaux)) = vernauxs.next() {
if let Some(version) = vernaux
.name(endian, strings)
.ok()
.and_then(|name| str::from_utf8(name).ok())
{
obj.verneeded.push(VersionNeed {
file: file.to_string(),
version: version.to_string(),
weak: vernaux.vna_flags.get(endian).contains(VER_FLG_WEAK),
});
}
}
}
}
struct DynamicInfo {
jmprel: Option<u64>,
bind_now: bool,
mips_gotsym: Option<u64>,
mips_symtabno: Option<u64>,
}
fn parse_dynamic_info<Elf: FileHeader>(endian: Elf::Endian, elf: &Elf, data: &[u8]) -> DynamicInfo {
let mut r = DynamicInfo {
jmprel: None,
bind_now: false,
mips_gotsym: None,
mips_symtabno: None,
};
let Ok(headers) = elf.program_headers(endian, data) else {
return r;
};
let Some(segment) = headers
.iter()
.find(|&&hdr| hdr.p_type(endian) == PT_DYNAMIC)
else {
return r;
};
let Ok(Some(dynamic)) = segment.dynamic(endian, data) else {
return r;
};
for d in dynamic {
let tag = d.d_tag(endian);
if tag == DT_NULL {
break;
}
if tag == DT_JMPREL {
r.jmprel = Some(d.d_val(endian).into());
} else if tag == DT_BIND_NOW {
r.bind_now = true;
} else if tag == DT_FLAGS {
r.bind_now |= DynamicFlags(d.d_val(endian).into()).contains(DF_BIND_NOW);
} else if tag == DT_FLAGS_1 {
r.bind_now |= DynamicFlags1(d.d_val(endian).into()).contains(DF_1_NOW);
} else if tag == DT_MIPS_GOTSYM {
r.mips_gotsym = Some(d.d_val(endian).into());
} else if tag == DT_MIPS_SYMTABNO {
r.mips_symtabno = Some(d.d_val(endian).into());
}
}
r
}