Skip to content

Commit

Permalink
build/elf: optimise output of version definitions
Browse files Browse the repository at this point in the history
This handles the common case where there are only two version
definitions which have the same name.

When rewriting files, we are required to optimise at least as well
as the original file so that it fits in the allocated space.
  • Loading branch information
philipc committed Jul 18, 2024
1 parent a7c88f3 commit e0f9842
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 7 deletions.
33 changes: 26 additions & 7 deletions src/build/elf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -997,6 +997,7 @@ impl<'data> Builder<'data> {
// Count the versions and add version strings.
let mut verdef_count = 0;
let mut verdaux_count = 0;
let mut verdef_shared_base = false;
let mut verneed_count = 0;
let mut vernaux_count = 0;
let mut out_version_files = vec![VersionFileOut::default(); self.version_files.len()];
Expand All @@ -1008,11 +1009,15 @@ impl<'data> Builder<'data> {
for version in &self.versions {
match &version.data {
VersionData::Def(def) => {
verdef_count += 1;
verdaux_count += def.names.len();
for name in &def.names {
writer.add_dynamic_string(name);
if def.is_shared(verdef_count, self.version_base.as_ref()) {
verdef_shared_base = true;
} else {
verdaux_count += def.names.len();
for name in &def.names {
writer.add_dynamic_string(name);
}
}
verdef_count += 1;
}
VersionData::Need(need) => {
vernaux_count += 1;
Expand Down Expand Up @@ -1456,13 +1461,18 @@ impl<'data> Builder<'data> {
SectionData::GnuVerdef => {
writer.write_align_gnu_verdef();
if let Some(version_base) = &self.version_base {
writer.write_gnu_verdef(&write::elf::Verdef {
let verdef = write::elf::Verdef {
version: elf::VER_DEF_CURRENT,
flags: elf::VER_FLG_BASE,
index: 1,
aux_count: 1,
name: writer.get_dynamic_string(version_base),
});
};
if verdef_shared_base {
writer.write_gnu_verdef_shared(&verdef);
} else {
writer.write_gnu_verdef(&verdef);
}
}
for version in &self.versions {
if let VersionData::Def(def) = &version.data {
Expand Down Expand Up @@ -1977,8 +1987,10 @@ impl<'data> Builder<'data> {
}
for version in &self.versions {
if let VersionData::Def(def) = &version.data {
if !def.is_shared(verdef_count, self.version_base.as_ref()) {
verdaux_count += def.names.len();
}
verdef_count += 1;
verdaux_count += def.names.len();
}
}
self.class().gnu_verdef_size(verdef_count, verdaux_count)
Expand Down Expand Up @@ -2992,6 +3004,13 @@ pub struct VersionDef<'data> {
pub flags: u16,
}

impl<'data> VersionDef<'data> {
/// Optimise for the common case where the first version is the same as the base version.
fn is_shared(&self, index: usize, base: Option<&ByteString<'_>>) -> bool {
index == 1 && self.names.len() == 1 && self.names.first() == base
}
}

/// A GNU version dependency.
#[derive(Debug)]
pub struct VersionNeed<'data> {
Expand Down
28 changes: 28 additions & 0 deletions src/write/elf/writer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1648,6 +1648,34 @@ impl<'a> Writer<'a> {
self.write_gnu_verdaux(verdef.name);
}

/// Write a version definition entry that shares the names of the next definition.
///
/// This is typically useful when there are only two versions (including the base)
/// and they have the same name.
pub fn write_gnu_verdef_shared(&mut self, verdef: &Verdef) {
debug_assert_ne!(self.gnu_verdef_remaining, 0);
self.gnu_verdef_remaining -= 1;
debug_assert_ne!(self.gnu_verdef_remaining, 0);
let vd_next = mem::size_of::<elf::Verdef<Endianness>>() as u32;

self.gnu_verdaux_remaining = 0;
let vd_aux = if verdef.aux_count == 0 {
0
} else {
2 * mem::size_of::<elf::Verdef<Endianness>>() as u32
};

self.buffer.write(&elf::Verdef {
vd_version: U16::new(self.endian, verdef.version),
vd_flags: U16::new(self.endian, verdef.flags),
vd_ndx: U16::new(self.endian, verdef.index),
vd_cnt: U16::new(self.endian, verdef.aux_count),
vd_hash: U32::new(self.endian, elf::hash(self.dynstr.get_string(verdef.name))),
vd_aux: U32::new(self.endian, vd_aux),
vd_next: U32::new(self.endian, vd_next),
});
}

/// Write a version definition auxiliary entry.
pub fn write_gnu_verdaux(&mut self, name: StringId) {
debug_assert_ne!(self.gnu_verdaux_remaining, 0);
Expand Down

0 comments on commit e0f9842

Please sign in to comment.