Skip to content

Commit

Permalink
Add File::symbol_version_table() interface to get the GNU extension s…
Browse files Browse the repository at this point in the history
…ymbol versioning table
  • Loading branch information
cole14 committed Oct 29, 2022
1 parent 37a0da0 commit 8577748
Show file tree
Hide file tree
Showing 2 changed files with 187 additions and 4 deletions.
156 changes: 156 additions & 0 deletions src/file.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::compression::CompressionHeader;
use crate::dynamic::DynIterator;
use crate::gabi;
use crate::gnu_symver::{SymbolVersionTable, VerDefIterator, VerNeedIterator, VersionIndexTable};
use crate::note::NoteIterator;
use crate::parse::{
parse_u16_at, parse_u32_at, parse_u64_at, Class, Endian, ParseAt, ParseError, ReadBytesAt,
Expand Down Expand Up @@ -382,6 +383,141 @@ impl<R: ReadBytesAt> File<R> {
Ok(None)
}

/// Read the section data for the various GNU Symbol Versioning sections (if any)
/// and return them in a [SymbolVersionTable] that which can interpret them in-place to
/// yield [SymbolRequirement](crate::gnu_symver::SymbolRequirement)s
/// and [SymbolDefinition](crate::gnu_symver::SymbolDefinition)s
///
/// This is a GNU extension and not all objects use symbol versioning.
/// Returns an empty Option if the object does not use symbol versioning.
pub fn symbol_version_table(&mut self) -> Result<Option<SymbolVersionTable>, ParseError> {
let mut versym_opt: Option<SectionHeader> = None;
let mut needs_opt: Option<SectionHeader> = None;
let mut defs_opt: Option<SectionHeader> = None;
// Find the GNU Symbol versioning sections (if any)
for shdr in self.section_headers()? {
if shdr.sh_type == gabi::SHT_GNU_VERSYM {
versym_opt = Some(shdr);
} else if shdr.sh_type == gabi::SHT_GNU_VERNEED {
needs_opt = Some(shdr);
} else if shdr.sh_type == gabi::SHT_GNU_VERDEF {
defs_opt = Some(shdr);
}
}

// No VERSYM section means the object doesn't use symbol versioning, which is ok.
if versym_opt.is_none() {
return Ok(None);
}

// Load the versym table
let versym_shdr = versym_opt.unwrap();
let versym_start = versym_shdr.sh_offset as usize;
let versym_size = versym_shdr.sh_size as usize;
self.reader
.load_bytes_at(versym_start..versym_start + versym_size)?;

// Get the VERNEED string shdr and load the VERNEED section data (if any)
let needs_shdrs = match needs_opt {
Some(shdr) => {
let start = shdr.sh_offset as usize;
let size = shdr.sh_size as usize;
self.reader.load_bytes_at(start..start + size)?;

Some((shdr, self.section_header_by_index(shdr.sh_link as usize)?))
}
// It's possible to have symbol versioning with no NEEDs if we're an object that only
// exports defined symbols.
None => None,
};

// Get the VERDEF string shdr and load the VERDEF section data (if any)
let defs_shdrs = match defs_opt {
Some(shdr) => {
let start = shdr.sh_offset as usize;
let size = shdr.sh_size as usize;
self.reader.load_bytes_at(start..start + size)?;

Some((shdr, self.section_header_by_index(shdr.sh_link as usize)?))
}
// It's possible to have symbol versioning with no DEFs if we're an object that doesn't
// export any symbols but does use dynamic symbols from other objects.
None => None,
};

// Wrap the VERNEED section and strings data in an iterator and string table
let (verneeds, verneed_strs) = match needs_shdrs {
Some((shdr, strs_shdr)) => {
let strs_start = strs_shdr.sh_offset as usize;
let strs_size = strs_shdr.sh_size as usize;
let strs_buf = self
.reader
.get_loaded_bytes_at(strs_start..strs_start + strs_size);

let start = shdr.sh_offset as usize;
let size = shdr.sh_size as usize;
let buf = self.reader.get_loaded_bytes_at(start..start + size);
(
VerNeedIterator::new(
self.ehdr.endianness,
self.ehdr.class,
shdr.sh_info as u64,
0,
buf,
),
StringTable::new(strs_buf),
)
}
// If there's no NEEDs, then construct empty wrappers for them
None => (VerNeedIterator::default(), StringTable::default()),
};

// Wrap the VERDEF section and strings data in an iterator and string table
let (verdefs, verdef_strs) = match defs_shdrs {
Some((shdr, strs_shdr)) => {
let strs_start = strs_shdr.sh_offset as usize;
let strs_size = strs_shdr.sh_size as usize;
let strs_buf = self
.reader
.get_loaded_bytes_at(strs_start..strs_start + strs_size);

let start = shdr.sh_offset as usize;
let size = shdr.sh_size as usize;
let buf = self.reader.get_loaded_bytes_at(start..start + size);
(
VerDefIterator::new(
self.ehdr.endianness,
self.ehdr.class,
shdr.sh_info as u64,
0,
buf,
),
StringTable::new(strs_buf),
)
}
// If there's no DEFs, then construct empty wrappers for them
None => (VerDefIterator::default(), StringTable::default()),
};

// Wrap the versym section data in a parsing table
let version_ids = VersionIndexTable::new(
self.ehdr.endianness,
self.ehdr.class,
versym_shdr.sh_entsize as usize,
self.reader
.get_loaded_bytes_at(versym_start..versym_start + versym_size),
);

// whew, we're done here!
Ok(Some(SymbolVersionTable::new(
version_ids,
verneeds,
verneed_strs,
verdefs,
verdef_strs,
)))
}

/// Read the section data for the given
/// [SectionHeader](SectionHeader) and interpret it in-place as a
/// [RelIterator](RelIterator).
Expand Down Expand Up @@ -1342,6 +1478,26 @@ mod interface_tests {
);
assert!(notes.next().is_none());
}

#[test]
fn symbol_version_table() {
let path = std::path::PathBuf::from("tests/samples/test1");
let file_data = std::fs::read(path).expect("Could not read file.");
let slice = file_data.as_slice();
let mut file = File::open_stream(slice).expect("Open test1");
let vst = file
.symbol_version_table()
.expect("Failed to parse GNU symbol versions")
.expect("Failed to find GNU symbol versions");

let req1 = vst
.get_requirement(1)
.expect("Failed to parse NEED")
.expect("Failed to find NEED");
assert_eq!(req1.file, "libc.so.6");
assert_eq!(req1.name, "GLIBC_2.2.5");
assert_eq!(req1.hash, 0x9691A75);
}
}

#[cfg(test)]
Expand Down
35 changes: 31 additions & 4 deletions src/gnu_symver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ impl<'data> Iterator for SymbolNamesIterator<'data> {
}
}

pub struct VersionTable<'data> {
pub struct SymbolVersionTable<'data> {
version_ids: VersionIndexTable<'data>,

verneeds: VerNeedIterator<'data>,
Expand All @@ -51,15 +51,15 @@ pub struct VersionTable<'data> {
verdef_strs: StringTable<'data>,
}

impl<'data> VersionTable<'data> {
impl<'data> SymbolVersionTable<'data> {
pub fn new(
version_ids: VersionIndexTable<'data>,
verneeds: VerNeedIterator<'data>,
verneed_strs: StringTable<'data>,
verdefs: VerDefIterator<'data>,
verdef_strs: StringTable<'data>,
) -> Self {
VersionTable {
SymbolVersionTable {
version_ids,
verneeds,
verneed_strs,
Expand Down Expand Up @@ -275,6 +275,19 @@ impl<'data> VerDefIterator<'data> {
}
}

/// Create an empty iterator that yields nothing
impl<'data> Default for VerDefIterator<'data> {
fn default() -> Self {
VerDefIterator {
endianness: Endian::Little,
class: Class::ELF64,
count: 0,
data: &[],
offset: 0,
}
}
}

impl<'data> Iterator for VerDefIterator<'data> {
type Item = (VerDef, VerDefAuxIterator<'data>);
fn next(&mut self) -> Option<Self::Item> {
Expand Down Expand Up @@ -483,6 +496,19 @@ impl<'data> VerNeedIterator<'data> {
}
}

/// Create an empty iterator that yields nothing
impl<'data> Default for VerNeedIterator<'data> {
fn default() -> Self {
VerNeedIterator {
endianness: Endian::Little,
class: Class::ELF64,
count: 0,
data: &[],
offset: 0,
}
}
}

impl<'data> Iterator for VerNeedIterator<'data> {
type Item = (VerNeed, VerNeedAuxIterator<'data>);
fn next(&mut self) -> Option<Self::Item> {
Expand Down Expand Up @@ -1040,7 +1066,8 @@ mod iter_tests {
let verneeds = VerNeedIterator::new(Endian::Little, Class::ELF64, 2, 0, &GNU_VERNEED_DATA);
let verdef_strs = StringTable::new(&GNU_VERDEF_STRINGS);

let table = VersionTable::new(version_ids, verneeds, verneed_strs, verdefs, verdef_strs);
let table =
SymbolVersionTable::new(version_ids, verneeds, verneed_strs, verdefs, verdef_strs);

let def1 = table
.get_definition(0)
Expand Down

0 comments on commit 8577748

Please sign in to comment.