///|
/// Lazy table of GNU symbol-version indices from `.gnu.version`.
///
/// This table has the same number of entries as the dynamic symbol table. Entry
/// `N` describes the version requirement or definition associated with dynamic
/// symbol `N`.
pub struct VersionIndexTable {
table : ParsingTable[VersionIndex]
}
///|
fn VersionIndexTable::VersionIndexTable(
endian : Endian,
class : Class,
data : BytesView,
) -> VersionIndexTable {
{
table: ParsingTable(
endian,
class,
data,
VersionIndex::size_for(class),
VersionIndex::parse_at,
),
}
}
///|
/// Number of version-index entries.
pub fn VersionIndexTable::len(self : VersionIndexTable) -> Int {
self.table.len()
}
///|
/// Returns `true` when there are no version-index entries.
pub fn VersionIndexTable::is_empty(self : VersionIndexTable) -> Bool {
self.table.is_empty()
}
///|
/// Parse the version index for dynamic symbol `index`.
pub fn VersionIndexTable::get(
self : VersionIndexTable,
index : Int,
) -> VersionIndex raise ParseError {
self.table.get(index)
}
///|
/// Iterate over version indices lazily.
pub fn VersionIndexTable::iter(self : VersionIndexTable) -> Iter[VersionIndex] {
self.table.iter()
}
///|
/// Resolved GNU symbol-version requirement.
///
/// A requirement describes a dynamic symbol that must be provided by another
/// shared object with a specific version name.
pub(all) struct SymbolRequirement {
/// Required shared object file name, such as `libc.so.6`.
file : String
/// Required version name, such as `GLIBC_2.2.5`.
name : String
/// SysV ELF hash of the version name.
hash : UInt
/// Version requirement flags (`VER_FLG_*`).
flags : UInt16
/// Whether the version index has the `VER_NDX_HIDDEN` bit set.
hidden : Bool
} derive(Debug, Eq)
///|
/// Resolved GNU symbol-version definition.
///
/// A definition describes a version exported by the current object. Multiple
/// names may be associated with one definition through auxiliary entries.
pub(all) struct SymbolDefinition {
/// SysV ELF hash of the primary version name.
hash : UInt
/// Version definition flags (`VER_FLG_*`).
flags : UInt16
/// Version or dependency names from linked auxiliary entries.
names : Array[String]
/// Whether the version index has the `VER_NDX_HIDDEN` bit set.
hidden : Bool
} derive(Debug, Eq)
///|
/// Resolver for GNU symbol versioning tables.
///
/// The table combines `.gnu.version` with optional `.gnu.version_r` and
/// `.gnu.version_d` data plus their linked string tables.
pub struct SymbolVersionTable {
/// Per-dynamic-symbol version indices.
version_ids : VersionIndexTable
/// Version requirements and linked strings, if the object imports versions.
verneeds : VersionNeedData?
/// Version definitions and linked strings, if the object exports versions.
verdefs : VersionDefData?
}
///|
struct VersionNeedData {
iter : VerNeedIterator
strtab : StringTable
}
///|
struct VersionDefData {
iter : VerDefIterator
strtab : StringTable
}
///|
/// Construct a symbol-version resolver from parsed component tables.
pub fn SymbolVersionTable::new(
version_ids : VersionIndexTable,
verneeds : (VerNeedIterator, StringTable)?,
verdefs : (VerDefIterator, StringTable)?,
) -> SymbolVersionTable {
let verneeds = match verneeds {
Some((iter, strtab)) => {
let data : VersionNeedData = { iter, strtab }
Some(data)
}
None => None
}
let verdefs = match verdefs {
Some((iter, strtab)) => {
let data : VersionDefData = { iter, strtab }
Some(data)
}
None => None
}
{ version_ids, verneeds, verdefs }
}
///|
/// Resolve the version requirement for dynamic symbol `sym_idx`.
///
/// Returns `None` when the symbol has no imported version requirement or the
/// object has no `.gnu.version_r` section.
pub fn SymbolVersionTable::get_requirement(
self : SymbolVersionTable,
sym_idx : Int,
) -> SymbolRequirement? raise ParseError {
let data = match self.verneeds {
Some(data) => data
None => return None
}
let ver_ndx = self.version_ids.get(sym_idx)
// Walk every needed library and its auxiliary version records until the
// `vna_other` version index matches `.gnu.version[sym_idx]`.
for item in data.iter.copy().iter() {
let (vn, vna_iter) = item
for vna in vna_iter.iter() {
if vna.vna_other == ver_ndx.index() {
return Some({
file: data.strtab.get(uint_to_int_checked(vn.vn_file)),
name: data.strtab.get(uint_to_int_checked(vna.vna_name)),
hash: vna.vna_hash,
flags: vna.vna_flags,
hidden: ver_ndx.is_hidden(),
})
}
}
}
None
}
///|
/// Resolve the version definition for dynamic symbol `sym_idx`.
///
/// Returns `None` when the symbol has no exported version definition or the
/// object has no `.gnu.version_d` section.
pub fn SymbolVersionTable::get_definition(
self : SymbolVersionTable,
sym_idx : Int,
) -> SymbolDefinition? raise ParseError {
let data = match self.verdefs {
Some(data) => data
None => return None
}
let ver_ndx = self.version_ids.get(sym_idx)
// Definition records are keyed by `vd_ndx`, which corresponds to the masked
// `.gnu.version[sym_idx]` value.
for item in data.iter.copy().iter() {
let (vd, vda_iter) = item
if vd.vd_ndx != ver_ndx.index() {
continue
}
let names : Array[String] = []
for vda in vda_iter.iter() {
names.push(data.strtab.get(uint_to_int_checked(vda.vda_name)))
}
return Some({
hash: vd.vd_hash,
flags: vd.vd_flags,
names,
hidden: ver_ndx.is_hidden(),
})
}
None
}
///|
/// Raw 16-bit entry from `.gnu.version`.
///
/// The low bits identify local/global/specific versions. The high hidden bit
/// indicates that references should bind to an exact version.
pub(all) struct VersionIndex(UInt16) derive(Debug, Eq)
///|
/// Return the version identifier with the hidden bit masked out.
pub fn VersionIndex::index(self : VersionIndex) -> UInt16 {
self.0 & VER_NDX_VERSION
}
///|
/// Returns `true` for the special local version index.
pub fn VersionIndex::is_local(self : VersionIndex) -> Bool {
self.index() == VER_NDX_LOCAL
}
///|
/// Returns `true` for the special global version index.
pub fn VersionIndex::is_global(self : VersionIndex) -> Bool {
self.index() == VER_NDX_GLOBAL
}
///|
/// Returns `true` when the `VER_NDX_HIDDEN` bit is set.
pub fn VersionIndex::is_hidden(self : VersionIndex) -> Bool {
(self.0 & VER_NDX_HIDDEN) != 0
}
///|
impl ParseAt for VersionIndex with parse_at(endian, _class, offset, data) {
let end = checked_add(offset, VersionIndex::size_for(ELF32))
let record = slice_checked(data, offset, end)
let value = match endian {
Little =>
match record {
[u16le(value)] => value.to_uint16()
_ => raise SliceReadError(offset, end)
}
Big =>
match record {
[u16be(value)] => value.to_uint16()
_ => raise SliceReadError(offset, end)
}
}
(VersionIndex(value), end)
}
///|
/// Size in bytes of one `.gnu.version` entry.
pub fn VersionIndex::size_for(_class : Class) -> Int {
2
}
///|
/// Version definition record from `.gnu.version_d`.
///
/// The section describes versions exported by the current object. Auxiliary
/// records reachable through `vd_aux` provide one or more version names.
pub(all) struct VerDef {
/// Version definition flags.
vd_flags : UInt16
/// Version index referenced by `.gnu.version`.
vd_ndx : UInt16
/// Number of associated `VerDefAux` records.
vd_cnt : UInt16
/// SysV ELF hash of the version name.
vd_hash : UInt
/// Offset from this record to the first `VerDefAux`.
vd_aux : UInt
/// Offset from this record to the next `VerDef`, or zero to stop.
vd_next : UInt
} derive(Debug, Eq)
///|
impl ParseAt for VerDef with parse_at(endian, _class, offset, data) {
let end = checked_add(offset, VerDef::size_for(ELF32))
let record = slice_checked(data, offset, end)
let (vd_version, value) = match endian {
Little =>
match record {
[
u16le(vd_version),
u16le(vd_flags),
u16le(vd_ndx),
u16le(vd_cnt),
u32le(vd_hash),
u32le(vd_aux),
u32le(vd_next),
] =>
(
vd_version.to_uint16(),
{
vd_flags: vd_flags.to_uint16(),
vd_ndx: vd_ndx.to_uint16(),
vd_cnt: vd_cnt.to_uint16(),
vd_hash,
vd_aux,
vd_next,
},
)
_ => raise SliceReadError(offset, end)
}
Big =>
match record {
[
u16be(vd_version),
u16be(vd_flags),
u16be(vd_ndx),
u16be(vd_cnt),
u32be(vd_hash),
u32be(vd_aux),
u32be(vd_next),
] =>
(
vd_version.to_uint16(),
{
vd_flags: vd_flags.to_uint16(),
vd_ndx: vd_ndx.to_uint16(),
vd_cnt: vd_cnt.to_uint16(),
vd_hash,
vd_aux,
vd_next,
},
)
_ => raise SliceReadError(offset, end)
}
}
if vd_version != VER_DEF_CURRENT {
raise UnsupportedVersion(
vd_version.to_uint64(),
VER_DEF_CURRENT.to_uint64(),
)
}
(value, end)
}
///|
/// Size in bytes of one version-definition record.
pub fn VerDef::size_for(_class : Class) -> Int {
20
}
///|
/// Iterator over `VerDef` records and their auxiliary-name iterators.
pub struct VerDefIterator {
/// Byte order used to parse records.
endian : Endian
/// ELF class of the owning object.
class : Class
/// Remaining number of definition records to inspect.
mut count : UInt64
/// Raw `.gnu.version_d` bytes.
data : BytesView
/// Current offset in `data`.
mut offset : Int
}
///|
/// Create a version-definition iterator.
pub fn VerDefIterator::new(
endian : Endian,
class : Class,
count : UInt64,
starting_offset : Int,
data : BytesView,
) -> VerDefIterator {
{ endian, class, count, data, offset: starting_offset }
}
///|
fn VerDefIterator::copy(self : VerDefIterator) -> VerDefIterator {
{
endian: self.endian,
class: self.class,
count: self.count,
data: self.data,
offset: self.offset,
}
}
///|
/// Parse the next `VerDef` and create an iterator over its auxiliary records.
pub fn VerDefIterator::next(
self : VerDefIterator,
) -> (VerDef, VerDefAuxIterator)? {
if self.data.is_empty() || self.count == 0UL {
return None
}
try VerDef::parse_at(self.endian, self.class, self.offset, self.data) catch {
_ => {
self.count = 0UL
None
}
} noraise {
(vd, _) => {
let aux_offset = try
checked_add(self.offset, uint_to_int_checked(vd.vd_aux))
catch {
_ => {
self.count = 0UL
return None
}
} noraise {
value => value
}
let aux_iter = VerDefAuxIterator::new(
self.endian,
self.class,
vd.vd_cnt,
aux_offset,
self.data,
)
// `vd_next` is an increment relative to the current VerDef record, not an
// absolute offset from the start of the section.
try checked_add(self.offset, uint_to_int_checked(vd.vd_next)) catch {
_ => self.count = 0UL
} noraise {
new_offset => self.offset = new_offset
}
if self.count > 0UL {
self.count -= 1UL
}
if self.count > 0UL && vd.vd_next == 0U {
self.count = 0UL
}
Some((vd, aux_iter))
}
}
}
///|
/// Convert this iterator to `Iter[(VerDef, VerDefAuxIterator)]`.
pub fn VerDefIterator::iter(
self : VerDefIterator,
) -> Iter[(VerDef, VerDefAuxIterator)] {
Iter::new(fn() { self.next() })
}
///|
/// Auxiliary version-definition entry from `.gnu.version_d`.
///
/// Each entry names a version or dependency through the string table linked by
/// the section header.
pub(all) struct VerDefAux {
/// Offset to the version/dependency name in the linked string table.
vda_name : UInt
/// Offset from this auxiliary entry to the next one, or zero to stop.
vda_next : UInt
} derive(Debug, Eq)
///|
impl ParseAt for VerDefAux with parse_at(endian, _class, offset, data) {
let end = checked_add(offset, VerDefAux::size_for(ELF32))
let record = slice_checked(data, offset, end)
let value = match endian {
Little =>
match record {
[u32le(vda_name), u32le(vda_next)] => { vda_name, vda_next }
_ => raise SliceReadError(offset, end)
}
Big =>
match record {
[u32be(vda_name), u32be(vda_next)] => { vda_name, vda_next }
_ => raise SliceReadError(offset, end)
}
}
(value, end)
}
///|
/// Size in bytes of one version-definition auxiliary record.
pub fn VerDefAux::size_for(_class : Class) -> Int {
8
}
///|
/// Iterator over the auxiliary records for one `VerDef`.
pub struct VerDefAuxIterator {
/// Byte order used to parse records.
endian : Endian
/// ELF class of the owning object.
class : Class
/// Remaining number of auxiliary records.
mut count : UInt16
/// Raw `.gnu.version_d` bytes.
data : BytesView
/// Current offset in `data`.
mut offset : Int
}
///|
/// Create an iterator over `VerDefAux` records.
pub fn VerDefAuxIterator::new(
endian : Endian,
class : Class,
count : UInt16,
starting_offset : Int,
data : BytesView,
) -> VerDefAuxIterator {
{ endian, class, count, data, offset: starting_offset }
}
///|
/// Parse the next version-definition auxiliary record.
pub fn VerDefAuxIterator::next(self : VerDefAuxIterator) -> VerDefAux? {
if self.data.is_empty() || self.count == 0 {
return None
}
try
VerDefAux::parse_at(self.endian, self.class, self.offset, self.data)
catch {
_ => {
self.count = 0
None
}
} noraise {
(vda, _) => {
// `vda_next` advances relative to the current auxiliary record.
try checked_add(self.offset, uint_to_int_checked(vda.vda_next)) catch {
_ => self.count = 0
} noraise {
new_offset => self.offset = new_offset
}
if self.count > 0 {
self.count -= 1
}
if self.count > 0 && vda.vda_next == 0U {
self.count = 0
}
Some(vda)
}
}
}
///|
/// Convert this iterator to `Iter[VerDefAux]`.
pub fn VerDefAuxIterator::iter(self : VerDefAuxIterator) -> Iter[VerDefAux] {
Iter::new(fn() { self.next() })
}
///|
/// Version requirement record from `.gnu.version_r`.
///
/// The section describes versioned symbols required from another shared object.
/// Auxiliary records reachable through `vn_aux` provide the individual version
/// names required from `vn_file`.
pub(all) struct VerNeed {
/// Number of associated `VerNeedAux` records.
vn_cnt : UInt16
/// Offset to the required shared object file name in the linked string table.
vn_file : UInt
/// Offset from this record to the first `VerNeedAux`.
vn_aux : UInt
/// Offset from this record to the next `VerNeed`, or zero to stop.
vn_next : UInt
} derive(Debug, Eq)
///|
impl ParseAt for VerNeed with parse_at(endian, _class, offset, data) {
let end = checked_add(offset, VerNeed::size_for(ELF32))
let record = slice_checked(data, offset, end)
let (vn_version, value) = match endian {
Little =>
match record {
[
u16le(vn_version),
u16le(vn_cnt),
u32le(vn_file),
u32le(vn_aux),
u32le(vn_next),
] =>
(
vn_version.to_uint16(),
{ vn_cnt: vn_cnt.to_uint16(), vn_file, vn_aux, vn_next },
)
_ => raise SliceReadError(offset, end)
}
Big =>
match record {
[
u16be(vn_version),
u16be(vn_cnt),
u32be(vn_file),
u32be(vn_aux),
u32be(vn_next),
] =>
(
vn_version.to_uint16(),
{ vn_cnt: vn_cnt.to_uint16(), vn_file, vn_aux, vn_next },
)
_ => raise SliceReadError(offset, end)
}
}
if vn_version != VER_NEED_CURRENT {
raise UnsupportedVersion(
vn_version.to_uint64(),
VER_NEED_CURRENT.to_uint64(),
)
}
(value, end)
}
///|
/// Size in bytes of one version-requirement record.
pub fn VerNeed::size_for(_class : Class) -> Int {
16
}
///|
/// Iterator over `VerNeed` records and their auxiliary-version iterators.
pub struct VerNeedIterator {
/// Byte order used to parse records.
endian : Endian
/// ELF class of the owning object.
class : Class
/// Remaining number of requirement records.
mut count : UInt64
/// Raw `.gnu.version_r` bytes.
data : BytesView
/// Current offset in `data`.
mut offset : Int
}
///|
/// Create a version-requirement iterator.
pub fn VerNeedIterator::new(
endian : Endian,
class : Class,
count : UInt64,
starting_offset : Int,
data : BytesView,
) -> VerNeedIterator {
{ endian, class, count, data, offset: starting_offset }
}
///|
fn VerNeedIterator::copy(self : VerNeedIterator) -> VerNeedIterator {
{
endian: self.endian,
class: self.class,
count: self.count,
data: self.data,
offset: self.offset,
}
}
///|
/// Parse the next `VerNeed` and create an iterator over its auxiliary records.
pub fn VerNeedIterator::next(
self : VerNeedIterator,
) -> (VerNeed, VerNeedAuxIterator)? {
if self.data.is_empty() || self.count == 0UL {
return None
}
try VerNeed::parse_at(self.endian, self.class, self.offset, self.data) catch {
_ => {
self.count = 0UL
None
}
} noraise {
(vn, _) => {
let aux_offset = try
checked_add(self.offset, uint_to_int_checked(vn.vn_aux))
catch {
_ => {
self.count = 0UL
return None
}
} noraise {
value => value
}
let aux_iter = VerNeedAuxIterator::new(
self.endian,
self.class,
vn.vn_cnt,
aux_offset,
self.data,
)
// `vn_next` is an increment relative to the current VerNeed record.
try checked_add(self.offset, uint_to_int_checked(vn.vn_next)) catch {
_ => self.count = 0UL
} noraise {
new_offset => self.offset = new_offset
}
if self.count > 0UL {
self.count -= 1UL
}
if self.count > 0UL && vn.vn_next == 0U {
self.count = 0UL
}
Some((vn, aux_iter))
}
}
}
///|
/// Convert this iterator to `Iter[(VerNeed, VerNeedAuxIterator)]`.
pub fn VerNeedIterator::iter(
self : VerNeedIterator,
) -> Iter[(VerNeed, VerNeedAuxIterator)] {
Iter::new(fn() { self.next() })
}
///|
/// Auxiliary version requirement entry from `.gnu.version_r`.
pub(all) struct VerNeedAux {
/// SysV ELF hash of the required version name.
vna_hash : UInt
/// Version requirement flags.
vna_flags : UInt16
/// Version index used in `.gnu.version`.
vna_other : UInt16
/// Offset to the required version name in the linked string table.
vna_name : UInt
/// Offset from this auxiliary entry to the next one, or zero to stop.
vna_next : UInt
} derive(Debug, Eq)
///|
impl ParseAt for VerNeedAux with parse_at(endian, _class, offset, data) {
let end = checked_add(offset, VerNeedAux::size_for(ELF32))
let record = slice_checked(data, offset, end)
let value = match endian {
Little =>
match record {
[
u32le(vna_hash),
u16le(vna_flags),
u16le(vna_other),
u32le(vna_name),
u32le(vna_next),
] =>
{
vna_hash,
vna_flags: vna_flags.to_uint16(),
vna_other: vna_other.to_uint16(),
vna_name,
vna_next,
}
_ => raise SliceReadError(offset, end)
}
Big =>
match record {
[
u32be(vna_hash),
u16be(vna_flags),
u16be(vna_other),
u32be(vna_name),
u32be(vna_next),
] =>
{
vna_hash,
vna_flags: vna_flags.to_uint16(),
vna_other: vna_other.to_uint16(),
vna_name,
vna_next,
}
_ => raise SliceReadError(offset, end)
}
}
(value, end)
}
///|
/// Size in bytes of one version-requirement auxiliary record.
pub fn VerNeedAux::size_for(_class : Class) -> Int {
16
}
///|
/// Iterator over the auxiliary records for one `VerNeed`.
pub struct VerNeedAuxIterator {
/// Byte order used to parse records.
endian : Endian
/// ELF class of the owning object.
class : Class
/// Remaining number of auxiliary records.
mut count : UInt16
/// Raw `.gnu.version_r` bytes.
data : BytesView
/// Current offset in `data`.
mut offset : Int
}
///|
/// Create an iterator over `VerNeedAux` records.
pub fn VerNeedAuxIterator::new(
endian : Endian,
class : Class,
count : UInt16,
starting_offset : Int,
data : BytesView,
) -> VerNeedAuxIterator {
{ endian, class, count, data, offset: starting_offset }
}
///|
/// Parse the next version-requirement auxiliary record.
pub fn VerNeedAuxIterator::next(self : VerNeedAuxIterator) -> VerNeedAux? {
if self.data.is_empty() || self.count == 0 {
return None
}
try
VerNeedAux::parse_at(self.endian, self.class, self.offset, self.data)
catch {
_ => {
self.count = 0
None
}
} noraise {
(vna, _) => {
// `vna_next` advances relative to the current auxiliary record.
try checked_add(self.offset, uint_to_int_checked(vna.vna_next)) catch {
_ => self.count = 0
} noraise {
new_offset => self.offset = new_offset
}
if self.count > 0 {
self.count -= 1
}
if self.count > 0 && vna.vna_next == 0U {
self.count = 0
}
Some(vna)
}
}
}
///|
/// Convert this iterator to `Iter[VerNeedAux]`.
pub fn VerNeedAuxIterator::iter(self : VerNeedAuxIterator) -> Iter[VerNeedAux] {
Iter::new(fn() { self.next() })
}