Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for .gnu.attributes sections #509

Merged
merged 2 commits into from
Feb 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 46 additions & 1 deletion crates/examples/src/bin/elfcopy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ use object::read::elf::{Dyn, FileHeader, ProgramHeader, Rel, Rela, SectionHeader
use object::Endianness;

use std::collections::HashMap;
use std::ffi::CStr;
use std::fs::File;
use std::io::{self, BufRead};

Expand Down Expand Up @@ -183,6 +182,7 @@ fn copy_file<Elf: FileHeader<Endian = Endianness>>(
let mut in_versym = None;
let mut in_verdef = None;
let mut in_verneed = None;
let mut in_attrib = None;
let mut out_sections = Vec::with_capacity(in_sections.len());
let mut out_sections_index = Vec::with_capacity(in_sections.len());
for (i, in_section) in in_sections.iter().enumerate() {
Expand Down Expand Up @@ -267,6 +267,11 @@ fn copy_file<Elf: FileHeader<Endian = Endianness>>(
debug_assert!(in_verneed.is_some());
index = writer.reserve_gnu_verneed_section_index();
}
elf::SHT_GNU_ATTRIBUTES => {
in_attrib = in_section.gnu_attributes(endian, in_data)?;
debug_assert!(in_attrib.is_some());
index = writer.reserve_gnu_attributes_section_index();
}
other => {
panic!("Unsupported section type {:x}", other);
}
Expand Down Expand Up @@ -429,6 +434,26 @@ fn copy_file<Elf: FileHeader<Endian = Endianness>>(
}
}

// Add one byte for the version field at the start of the section
let mut gnu_attributes_size = 1;
// We will cache all of our attribute data here for later
let mut gnu_attrib_vendor_sections = Vec::new();
if let Some(mut vsections) = in_attrib.clone() {
while let Some((vendor_name, mut tags)) = vsections.next()? {
let mut subsection = Vec::new();
let mut subsection_length = 0;
while let Some((tag, attrib_data)) = tags.next()? {
// Add the size of our tag (1-byte) + the size of the size field (4-bytes)
// plus the size of the sub-section data
subsection_length += 5 + attrib_data.len() as u32;
subsection.push((tag, attrib_data.clone()));
}
// Add the length of our size field + the length of the vendor string + the null byte
gnu_attributes_size += subsection_length as usize + 4 + vendor_name.len() + 1;
gnu_attrib_vendor_sections.push((vendor_name, subsection_length, subsection));
}
}

// Start reserving file ranges.
writer.reserve_file_header();

Expand All @@ -440,6 +465,7 @@ fn copy_file<Elf: FileHeader<Endian = Endianness>>(
let mut dynamic_addr = 0;
let mut dynsym_addr = 0;
let mut dynstr_addr = 0;
let mut attributes_addr = 0;

let mut alloc_sections = Vec::new();
if in_segments.is_empty() {
Expand Down Expand Up @@ -543,6 +569,10 @@ fn copy_file<Elf: FileHeader<Endian = Endianness>>(
in_section.sh_addralign(endian).into() as usize,
);
}
elf::SHT_GNU_ATTRIBUTES => {
attributes_addr = in_section.sh_addr(endian).into();
writer.reserve_gnu_attributes(gnu_attributes_size);
}
_ => {}
}
}
Expand Down Expand Up @@ -775,6 +805,18 @@ fn copy_file<Elf: FileHeader<Endian = Endianness>>(
debug_assert_eq!(out_sections[i].offset, writer.len());
writer.write(in_section.data(endian, in_data)?);
}
elf::SHT_GNU_ATTRIBUTES => {
writer.write_align_gnu_attributes();
writer.write_gnu_attributes_version();
for (vendor_name, section_length, subsections) in
gnu_attrib_vendor_sections.iter()
{
writer.write_gnu_attributes_subsection(*section_length, vendor_name);
for (tag, tag_data) in subsections.iter() {
writer.write_gnu_attributes_subsubsection(*tag, tag_data);
}
}
}
_ => {}
}
}
Expand Down Expand Up @@ -936,6 +978,9 @@ fn copy_file<Elf: FileHeader<Endian = Endianness>>(
elf::SHT_GNU_VERNEED => {
writer.write_gnu_verneed_section_header(verneed_addr);
}
elf::SHT_GNU_ATTRIBUTES => {
writer.write_gnu_attributes_section_header(attributes_addr);
}
other => {
panic!("Unsupported section type {:x}", other);
}
Expand Down
109 changes: 109 additions & 0 deletions src/read/elf/attributes.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
use crate::endian;
use crate::read::{Bytes, ReadError, Result};

use super::FileHeader;

/// An iterator over the section entries in an ELF `SHT_GNU_attributes` section.
#[derive(Debug, Clone)]
pub struct AttribSubsectionIterator<'data, Elf: FileHeader> {
endian: Elf::Endian,
data: Bytes<'data>,
}

impl<'data, Elf: FileHeader> AttribSubsectionIterator<'data, Elf> {
pub(super) fn new(endian: Elf::Endian, data: Bytes<'data>) -> Self {
AttribSubsectionIterator { endian, data: data }
}

/// Return the next Vendor attribute section
pub fn next(
&mut self,
) -> Result<Option<(&'data [u8], AttribSubSubsectionIterator<'data, Elf>)>> {
if self.data.is_empty() {
return Ok(None);
}

// First read the section length
let mut data = self.data;
let section_length = data
.read::<endian::U32Bytes<Elf::Endian>>()
.read_error("ELF GNU attributes vendor section is too short")?
.get(self.endian);

// Now read the entire section
let mut section = self
.data
.read_bytes(section_length as usize)
.read_error("ELF GNU attributes section incorrectly sized")?;
// Skip the section length field
section
.skip(core::mem::size_of::<endian::U32<Elf::Endian>>())
.read_error("ELF GNU attributes vendor section is too short")?;

let vendor_name = section
.read_string()
.read_error("ELF GNU attributes vendor section is too short")?;

// Pass the remainder of this section to the tag iterator
let tags = AttribSubSubsectionIterator::new(self.endian, section);

Ok(Some((vendor_name, tags)))
}
}

/// An iterator over the attribute tags in a GNU attributes section
#[derive(Debug, Clone)]
pub struct AttribSubSubsectionIterator<'data, Elf: FileHeader> {
endian: Elf::Endian,
data: Bytes<'data>,
}

impl<'data, Elf: FileHeader> AttribSubSubsectionIterator<'data, Elf> {
pub(super) fn new(endian: Elf::Endian, data: Bytes<'data>) -> Self {
AttribSubSubsectionIterator { endian, data: data }
}

/// Return the next tag.
///
/// The format of attributes looks like this:
/// ```text
/// [ <file-tag> <size> <attribute>*
/// | <section-tag> <size> <section-number>* 0 <attribute>*
/// | <symbol-tag> <size> <symbol-number>* 0 <attribute>*
/// ]+
/// ```
/// This iterator returns the (tag, data) pair, allowing the user to access the raw data for
/// the tags. The data is an array of attributes, each of which is an attribute tag folowed by
/// either a uleb128 encoded integer or a NULL terminated string
pub fn next(&mut self) -> Result<Option<(u8, &'data [u8])>> {
if self.data.is_empty() {
return Ok(None);
}

let tag = self
.data
.read::<u8>()
.read_error("GNU Attributes tag not correctly sized")?;

let tag_length = self
.data
.read::<endian::U32Bytes<Elf::Endian>>()
.read_error("ELF GNU attributes vendor section is too short")?
.get(self.endian) as usize;

// Subtract the size of our tag and length fields here
let tag_size = tag_length
.checked_sub(core::mem::size_of::<endian::U32<Elf::Endian>>())
.ok_or(())
.read_error("GNU attriutes tag size is too short")?
.checked_sub(1)
.ok_or(())
.read_error("GNU attriutes tag size is too short")?;

let tag_data = self
.data
.read_slice(tag_size)
.read_error("GNU attributes tag data does not match size requested")?;
return Ok(Some((*tag, tag_data)));
}
}
3 changes: 3 additions & 0 deletions src/read/elf/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,6 @@ pub use hash::*;

mod version;
pub use version::*;

mod attributes;
pub use attributes::*;
37 changes: 35 additions & 2 deletions src/read/elf/section.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ use crate::read::{
};

use super::{
CompressionHeader, ElfFile, ElfSectionRelocationIterator, FileHeader, GnuHashTable, HashTable,
NoteIterator, RelocationSections, SymbolTable, VerdefIterator, VerneedIterator, VersionTable,
AttribSubsectionIterator, CompressionHeader, ElfFile, ElfSectionRelocationIterator, FileHeader,
GnuHashTable, HashTable, NoteIterator, RelocationSections, SymbolTable, VerdefIterator,
VerneedIterator, VersionTable,
};

/// The table of section headers in an ELF file.
Expand Down Expand Up @@ -975,6 +976,38 @@ pub trait SectionHeader: Debug + Pod {
let link = SectionIndex(self.sh_link(endian) as usize);
Ok(Some((VerneedIterator::new(endian, verneed), link)))
}

/// Return an iterator for the entries of a `SHT_GNU_ATTRIBUTES` section.
///
/// Also returns the linked string table index.
///
/// Returns `Ok(None)` if the section type is not `SHT_GNU_ATTRIBUTES`.
/// Returns `Err` for invalid values.
fn gnu_attributes<'data, R: ReadRef<'data>>(
&self,
endian: Self::Endian,
data: R,
) -> read::Result<Option<AttribSubsectionIterator<'data, Self::Elf>>> {
if self.sh_type(endian) != elf::SHT_GNU_ATTRIBUTES {
return Ok(None);
}
let mut bytes = Bytes(
self.data(endian, data)
.read_error("Invalid ELF GNU bytes section offset or size")?,
);

// Skip the version field that is one byte long
let version = *bytes
.read::<u8>()
.read_error("Invalid ELF GNU bytes section offset or size")?;
amshafer marked this conversation as resolved.
Show resolved Hide resolved

// There is currently only one format version
if version != 65 {
return Err(Error(".gnu.attributes section not supported version 0x41"));
}

Ok(Some(AttribSubsectionIterator::new(endian, bytes)))
}
}

impl<Endian: endian::Endian> SectionHeader for elf::SectionHeader32<Endian> {
Expand Down
77 changes: 77 additions & 0 deletions src/write/elf/writer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@ pub struct Writer<'a> {
gnu_verneed_count: u16,
gnu_verneed_remaining: u16,
gnu_vernaux_remaining: u16,

gnu_attributes_str_id: Option<StringId>,
gnu_attributes_offset: usize,
gnu_attributes_size: usize,
}

impl<'a> Writer<'a> {
Expand Down Expand Up @@ -198,6 +202,10 @@ impl<'a> Writer<'a> {
gnu_verneed_count: 0,
gnu_verneed_remaining: 0,
gnu_vernaux_remaining: 0,

gnu_attributes_str_id: None,
gnu_attributes_offset: 0,
gnu_attributes_size: 0,
}
}

Expand Down Expand Up @@ -1735,6 +1743,75 @@ impl<'a> Writer<'a> {
});
}

/// Reserve the section index for the `.gnu.attributes` section.
pub fn reserve_gnu_attributes_section_index(&mut self) -> SectionIndex {
debug_assert!(self.gnu_attributes_str_id.is_none());
self.gnu_attributes_str_id = Some(self.add_section_name(&b".gnu.attributes"[..]));
self.reserve_section_index()
}

/// Reserve the range for the `.gnu.attributes` section.
pub fn reserve_gnu_attributes(&mut self, gnu_attributes_size: usize) {
debug_assert_eq!(self.gnu_attributes_offset, 0);
if gnu_attributes_size == 0 {
return;
}
self.gnu_attributes_size = gnu_attributes_size;
self.gnu_attributes_offset = self.reserve(self.gnu_attributes_size, self.elf_align);
}

/// Write the section header for the `.gnu.attributes` section.
///
/// This function does nothing if the section index was not reserved.
pub fn write_gnu_attributes_section_header(&mut self, sh_addr: u64) {
if self.gnu_attributes_str_id.is_none() {
return;
}
self.write_section_header(&SectionHeader {
name: self.gnu_attributes_str_id,
sh_type: elf::SHT_GNU_ATTRIBUTES,
sh_flags: 0,
sh_addr,
sh_offset: self.gnu_attributes_offset as u64,
sh_size: self.gnu_attributes_size as u64,
sh_link: self.dynstr_index.0,
sh_info: 0, // TODO
sh_addralign: self.elf_align as u64,
sh_entsize: 0,
});
}

/// Write alignment padding bytes prior to a `.gnu.attributes` section.
pub fn write_align_gnu_attributes(&mut self) {
if self.gnu_attributes_offset == 0 {
return;
}
util::write_align(self.buffer, self.elf_align);
debug_assert_eq!(self.gnu_attributes_offset, self.buffer.len());
}

/// Begin a `.gnu.attributes` section
pub fn write_gnu_attributes_version(&mut self) {
let version: u8 = 0x41;
self.buffer.write(&version);
}

/// Begin a `.gnu.attributes` sub-section
pub fn write_gnu_attributes_subsection(&mut self, section_length: u32, vendor_name: &[u8]) {
self.buffer.write(&U32::new(self.endian, section_length));
self.buffer.write_slice(vendor_name);
// Write a null byte to end the vendor name
self.buffer.write_slice(&[0 as u8]);
}

/// Write a `.gnu.attributes` subsection and attribute data
pub fn write_gnu_attributes_subsubsection(&mut self, tag: u8, attributes: &[u8]) {
self.buffer.write(&tag);
let size: u32 = 1 + mem::size_of::<u32>() as u32 + attributes.len() as u32;
self.buffer.write(&U32::new(self.endian, size));
self.buffer.write_slice(attributes);
}

/// Reserve a file range for the given number of relocations.
///
/// Returns the offset of the range.
Expand Down