aboutsummaryrefslogtreecommitdiff
path: root/vendor/object/src/read/elf/version.rs
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/object/src/read/elf/version.rs')
-rw-r--r--vendor/object/src/read/elf/version.rs424
1 files changed, 424 insertions, 0 deletions
diff --git a/vendor/object/src/read/elf/version.rs b/vendor/object/src/read/elf/version.rs
new file mode 100644
index 0000000..28eeed0
--- /dev/null
+++ b/vendor/object/src/read/elf/version.rs
@@ -0,0 +1,424 @@
+use alloc::vec::Vec;
+
+use crate::read::{Bytes, ReadError, ReadRef, Result, StringTable};
+use crate::{elf, endian};
+
+use super::FileHeader;
+
+/// A version index.
+#[derive(Debug, Default, Clone, Copy)]
+pub struct VersionIndex(pub u16);
+
+impl VersionIndex {
+ /// Return the version index.
+ pub fn index(&self) -> u16 {
+ self.0 & elf::VERSYM_VERSION
+ }
+
+ /// Return true if it is the local index.
+ pub fn is_local(&self) -> bool {
+ self.index() == elf::VER_NDX_LOCAL
+ }
+
+ /// Return true if it is the global index.
+ pub fn is_global(&self) -> bool {
+ self.index() == elf::VER_NDX_GLOBAL
+ }
+
+ /// Return the hidden flag.
+ pub fn is_hidden(&self) -> bool {
+ self.0 & elf::VERSYM_HIDDEN != 0
+ }
+}
+
+/// A version definition or requirement.
+///
+/// This is derived from entries in the [`elf::SHT_GNU_VERDEF`] and [`elf::SHT_GNU_VERNEED`] sections.
+#[derive(Debug, Default, Clone, Copy)]
+pub struct Version<'data> {
+ name: &'data [u8],
+ hash: u32,
+ // Used to keep track of valid indices in `VersionTable`.
+ valid: bool,
+}
+
+impl<'data> Version<'data> {
+ /// Return the version name.
+ pub fn name(&self) -> &'data [u8] {
+ self.name
+ }
+
+ /// Return hash of the version name.
+ pub fn hash(&self) -> u32 {
+ self.hash
+ }
+}
+
+/// A table of version definitions and requirements.
+///
+/// It allows looking up the version information for a given symbol index.
+///
+/// This is derived from entries in the [`elf::SHT_GNU_VERSYM`], [`elf::SHT_GNU_VERDEF`]
+/// and [`elf::SHT_GNU_VERNEED`] sections.
+///
+/// Returned by [`SectionTable::versions`](super::SectionTable::versions).
+#[derive(Debug, Clone)]
+pub struct VersionTable<'data, Elf: FileHeader> {
+ symbols: &'data [elf::Versym<Elf::Endian>],
+ versions: Vec<Version<'data>>,
+}
+
+impl<'data, Elf: FileHeader> Default for VersionTable<'data, Elf> {
+ fn default() -> Self {
+ VersionTable {
+ symbols: &[],
+ versions: Vec::new(),
+ }
+ }
+}
+
+impl<'data, Elf: FileHeader> VersionTable<'data, Elf> {
+ /// Parse the version sections.
+ pub fn parse<R: ReadRef<'data>>(
+ endian: Elf::Endian,
+ versyms: &'data [elf::Versym<Elf::Endian>],
+ verdefs: Option<VerdefIterator<'data, Elf>>,
+ verneeds: Option<VerneedIterator<'data, Elf>>,
+ strings: StringTable<'data, R>,
+ ) -> Result<Self> {
+ let mut max_index = 0;
+ if let Some(mut verdefs) = verdefs.clone() {
+ while let Some((verdef, _)) = verdefs.next()? {
+ if verdef.vd_flags.get(endian) & elf::VER_FLG_BASE != 0 {
+ continue;
+ }
+ let index = verdef.vd_ndx.get(endian) & elf::VERSYM_VERSION;
+ if max_index < index {
+ max_index = index;
+ }
+ }
+ }
+ if let Some(mut verneeds) = verneeds.clone() {
+ while let Some((_, mut vernauxs)) = verneeds.next()? {
+ while let Some(vernaux) = vernauxs.next()? {
+ let index = vernaux.vna_other.get(endian) & elf::VERSYM_VERSION;
+ if max_index < index {
+ max_index = index;
+ }
+ }
+ }
+ }
+
+ // Indices should be sequential, but this could be up to
+ // 32k * size_of::<Version>() if max_index is bad.
+ let mut versions = vec![Version::default(); max_index as usize + 1];
+
+ if let Some(mut verdefs) = verdefs {
+ while let Some((verdef, mut verdauxs)) = verdefs.next()? {
+ if verdef.vd_flags.get(endian) & elf::VER_FLG_BASE != 0 {
+ continue;
+ }
+ let index = verdef.vd_ndx.get(endian) & elf::VERSYM_VERSION;
+ if index <= elf::VER_NDX_GLOBAL {
+ // TODO: return error?
+ continue;
+ }
+ if let Some(verdaux) = verdauxs.next()? {
+ versions[usize::from(index)] = Version {
+ name: verdaux.name(endian, strings)?,
+ hash: verdef.vd_hash.get(endian),
+ valid: true,
+ };
+ }
+ }
+ }
+ if let Some(mut verneeds) = verneeds {
+ while let Some((_, mut vernauxs)) = verneeds.next()? {
+ while let Some(vernaux) = vernauxs.next()? {
+ let index = vernaux.vna_other.get(endian) & elf::VERSYM_VERSION;
+ if index <= elf::VER_NDX_GLOBAL {
+ // TODO: return error?
+ continue;
+ }
+ versions[usize::from(index)] = Version {
+ name: vernaux.name(endian, strings)?,
+ hash: vernaux.vna_hash.get(endian),
+ valid: true,
+ };
+ }
+ }
+ }
+
+ Ok(VersionTable {
+ symbols: versyms,
+ versions,
+ })
+ }
+
+ /// Return true if the version table is empty.
+ pub fn is_empty(&self) -> bool {
+ self.symbols.is_empty()
+ }
+
+ /// Return version index for a given symbol index.
+ pub fn version_index(&self, endian: Elf::Endian, index: usize) -> VersionIndex {
+ let version_index = match self.symbols.get(index) {
+ Some(x) => x.0.get(endian),
+ // Ideally this would be VER_NDX_LOCAL for undefined symbols,
+ // but currently there are no checks that need this distinction.
+ None => elf::VER_NDX_GLOBAL,
+ };
+ VersionIndex(version_index)
+ }
+
+ /// Return version information for a given symbol version index.
+ ///
+ /// Returns `Ok(None)` for local and global versions.
+ /// Returns `Err(_)` if index is invalid.
+ pub fn version(&self, index: VersionIndex) -> Result<Option<&Version<'data>>> {
+ if index.index() <= elf::VER_NDX_GLOBAL {
+ return Ok(None);
+ }
+ self.versions
+ .get(usize::from(index.index()))
+ .filter(|version| version.valid)
+ .read_error("Invalid ELF symbol version index")
+ .map(Some)
+ }
+
+ /// Return true if the given symbol index satisfies the requirements of `need`.
+ ///
+ /// Returns false for any error.
+ ///
+ /// Note: this function hasn't been fully tested and is likely to be incomplete.
+ pub fn matches(&self, endian: Elf::Endian, index: usize, need: Option<&Version<'_>>) -> bool {
+ let version_index = self.version_index(endian, index);
+ let def = match self.version(version_index) {
+ Ok(def) => def,
+ Err(_) => return false,
+ };
+ match (def, need) {
+ (Some(def), Some(need)) => need.hash == def.hash && need.name == def.name,
+ (None, Some(_need)) => {
+ // Version must be present if needed.
+ false
+ }
+ (Some(_def), None) => {
+ // For a dlsym call, use the newest version.
+ // TODO: if not a dlsym call, then use the oldest version.
+ !version_index.is_hidden()
+ }
+ (None, None) => true,
+ }
+ }
+}
+
+/// An iterator for the entries in an ELF [`elf::SHT_GNU_VERDEF`] section.
+#[derive(Debug, Clone)]
+pub struct VerdefIterator<'data, Elf: FileHeader> {
+ endian: Elf::Endian,
+ data: Bytes<'data>,
+}
+
+impl<'data, Elf: FileHeader> VerdefIterator<'data, Elf> {
+ pub(super) fn new(endian: Elf::Endian, data: &'data [u8]) -> Self {
+ VerdefIterator {
+ endian,
+ data: Bytes(data),
+ }
+ }
+
+ /// Return the next `Verdef` entry.
+ pub fn next(
+ &mut self,
+ ) -> Result<Option<(&'data elf::Verdef<Elf::Endian>, VerdauxIterator<'data, Elf>)>> {
+ if self.data.is_empty() {
+ return Ok(None);
+ }
+
+ let verdef = self
+ .data
+ .read_at::<elf::Verdef<_>>(0)
+ .read_error("ELF verdef is too short")?;
+
+ let mut verdaux_data = self.data;
+ verdaux_data
+ .skip(verdef.vd_aux.get(self.endian) as usize)
+ .read_error("Invalid ELF vd_aux")?;
+ let verdaux =
+ VerdauxIterator::new(self.endian, verdaux_data.0, verdef.vd_cnt.get(self.endian));
+
+ let next = verdef.vd_next.get(self.endian);
+ if next != 0 {
+ self.data
+ .skip(next as usize)
+ .read_error("Invalid ELF vd_next")?;
+ } else {
+ self.data = Bytes(&[]);
+ }
+ Ok(Some((verdef, verdaux)))
+ }
+}
+
+/// An iterator for the auxiliary records for an entry in an ELF [`elf::SHT_GNU_VERDEF`] section.
+#[derive(Debug, Clone)]
+pub struct VerdauxIterator<'data, Elf: FileHeader> {
+ endian: Elf::Endian,
+ data: Bytes<'data>,
+ count: u16,
+}
+
+impl<'data, Elf: FileHeader> VerdauxIterator<'data, Elf> {
+ pub(super) fn new(endian: Elf::Endian, data: &'data [u8], count: u16) -> Self {
+ VerdauxIterator {
+ endian,
+ data: Bytes(data),
+ count,
+ }
+ }
+
+ /// Return the next `Verdaux` entry.
+ pub fn next(&mut self) -> Result<Option<&'data elf::Verdaux<Elf::Endian>>> {
+ if self.count == 0 {
+ return Ok(None);
+ }
+
+ let verdaux = self
+ .data
+ .read_at::<elf::Verdaux<_>>(0)
+ .read_error("ELF verdaux is too short")?;
+
+ self.data
+ .skip(verdaux.vda_next.get(self.endian) as usize)
+ .read_error("Invalid ELF vda_next")?;
+ self.count -= 1;
+ Ok(Some(verdaux))
+ }
+}
+
+/// An iterator for the entries in an ELF [`elf::SHT_GNU_VERNEED`] section.
+#[derive(Debug, Clone)]
+pub struct VerneedIterator<'data, Elf: FileHeader> {
+ endian: Elf::Endian,
+ data: Bytes<'data>,
+}
+
+impl<'data, Elf: FileHeader> VerneedIterator<'data, Elf> {
+ pub(super) fn new(endian: Elf::Endian, data: &'data [u8]) -> Self {
+ VerneedIterator {
+ endian,
+ data: Bytes(data),
+ }
+ }
+
+ /// Return the next `Verneed` entry.
+ pub fn next(
+ &mut self,
+ ) -> Result<
+ Option<(
+ &'data elf::Verneed<Elf::Endian>,
+ VernauxIterator<'data, Elf>,
+ )>,
+ > {
+ if self.data.is_empty() {
+ return Ok(None);
+ }
+
+ let verneed = self
+ .data
+ .read_at::<elf::Verneed<_>>(0)
+ .read_error("ELF verneed is too short")?;
+
+ let mut vernaux_data = self.data;
+ vernaux_data
+ .skip(verneed.vn_aux.get(self.endian) as usize)
+ .read_error("Invalid ELF vn_aux")?;
+ let vernaux =
+ VernauxIterator::new(self.endian, vernaux_data.0, verneed.vn_cnt.get(self.endian));
+
+ let next = verneed.vn_next.get(self.endian);
+ if next != 0 {
+ self.data
+ .skip(next as usize)
+ .read_error("Invalid ELF vn_next")?;
+ } else {
+ self.data = Bytes(&[]);
+ }
+ Ok(Some((verneed, vernaux)))
+ }
+}
+
+/// An iterator for the auxiliary records for an entry in an ELF [`elf::SHT_GNU_VERNEED`] section.
+#[derive(Debug, Clone)]
+pub struct VernauxIterator<'data, Elf: FileHeader> {
+ endian: Elf::Endian,
+ data: Bytes<'data>,
+ count: u16,
+}
+
+impl<'data, Elf: FileHeader> VernauxIterator<'data, Elf> {
+ pub(super) fn new(endian: Elf::Endian, data: &'data [u8], count: u16) -> Self {
+ VernauxIterator {
+ endian,
+ data: Bytes(data),
+ count,
+ }
+ }
+
+ /// Return the next `Vernaux` entry.
+ pub fn next(&mut self) -> Result<Option<&'data elf::Vernaux<Elf::Endian>>> {
+ if self.count == 0 {
+ return Ok(None);
+ }
+
+ let vernaux = self
+ .data
+ .read_at::<elf::Vernaux<_>>(0)
+ .read_error("ELF vernaux is too short")?;
+
+ self.data
+ .skip(vernaux.vna_next.get(self.endian) as usize)
+ .read_error("Invalid ELF vna_next")?;
+ self.count -= 1;
+ Ok(Some(vernaux))
+ }
+}
+
+impl<Endian: endian::Endian> elf::Verdaux<Endian> {
+ /// Parse the version name from the string table.
+ pub fn name<'data, R: ReadRef<'data>>(
+ &self,
+ endian: Endian,
+ strings: StringTable<'data, R>,
+ ) -> Result<&'data [u8]> {
+ strings
+ .get(self.vda_name.get(endian))
+ .read_error("Invalid ELF vda_name")
+ }
+}
+
+impl<Endian: endian::Endian> elf::Verneed<Endian> {
+ /// Parse the file from the string table.
+ pub fn file<'data, R: ReadRef<'data>>(
+ &self,
+ endian: Endian,
+ strings: StringTable<'data, R>,
+ ) -> Result<&'data [u8]> {
+ strings
+ .get(self.vn_file.get(endian))
+ .read_error("Invalid ELF vn_file")
+ }
+}
+
+impl<Endian: endian::Endian> elf::Vernaux<Endian> {
+ /// Parse the version name from the string table.
+ pub fn name<'data, R: ReadRef<'data>>(
+ &self,
+ endian: Endian,
+ strings: StringTable<'data, R>,
+ ) -> Result<&'data [u8]> {
+ strings
+ .get(self.vna_name.get(endian))
+ .read_error("Invalid ELF vna_name")
+ }
+}