[svsm-devel] elf: Document ELF file handling
Carlos Bilbao
carlos.bilbao at amd.com
Tue Sep 12 16:32:12 CEST 2023
Hello folks,
I wanted to inform you that I have just updated my pull request (PR #89
[1]) with a new commit. This update focuses on documenting the ELF file
handling and related structures, addressing a part of issue #74 "Document
COCONUT-SVSM" [2].
I paste the patch below for your reference. I have CC'd the original code
author, Nicolai.
Please feel free to share your thoughts. To avoid any bottlenecks, I will
wait until this PR is merged (with any necessary modifications), before I
proceed with additional code documentation.
Thanks,
Carlos
[1] https://github.com/coconut-svsm/svsm/pull/89
[2] https://github.com/coconut-svsm/svsm/issues/74
---
Subject: [PATCH] elf: Document ELF files handling
Document ELF files handling, code and structures, including some function
usage examples. This relates to issue #74 titled "Document COCONUT-SVSM".
Signed-off-by: Carlos Bilbao <carlos.bilbao at amd.com>
---
src/elf/mod.rs | 932 ++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 920 insertions(+), 12 deletions(-)
diff --git a/src/elf/mod.rs b/src/elf/mod.rs
index 39731f8..2ac7646 100644
--- a/src/elf/mod.rs
+++ b/src/elf/mod.rs
@@ -17,6 +17,23 @@ use core::fmt;
use core::matches;
use core::mem;
+/// Errors while working with ELF files, e.g. invalid, unmaped or unbacked
+/// address ranges, invalid ELF type or endianness. The `fmt::Display` trait
+/// is implemented to allow formatting error instances.
+///
+/// # Examples
+///
+/// To format an `ElfError` as a string, you can use the `to_string()` method
+/// or the `format!` macro, like this:
+///
+/// ```rust
+/// use your_module::ElfError;
+///
+/// let error = ElfError::InvalidAddressRange;
+/// let error_message = error.to_string();
+///
+/// assert_eq!(error_message, "invalid ELF address range");
+///
#[derive(Debug)]
pub enum ElfError {
FileTooShort,
@@ -182,6 +199,10 @@ pub type Elf64Xword = u64;
pub type Elf64Sxword = i64;
pub type Elf64char = u8;
+/// Represents a 64-bit ELF virtual address range.
+///
+/// In mathematical notation, the range is [vaddr_begin, vaddr_end)
+///
#[derive(PartialEq, Eq, Debug, Default)]
pub struct Elf64AddrRange {
pub vaddr_begin: Elf64Addr,
@@ -189,10 +210,38 @@ pub struct Elf64AddrRange {
}
impl Elf64AddrRange {
+ /// Returns the length of the virtual address range, calculated as the
+ /// difference between `vaddr_end` and `vaddr_begin`.
+ ///
+ /// # Examples
+ ///
+ /// ```rust
+ /// use your_module::{Elf64AddrRange, Elf64Addr};
+ ///
+ /// let range = Elf64AddrRange {
+ /// vaddr_begin: Elf64Addr(0x1000),
+ /// vaddr_end: Elf64Addr(0x1100),
+ /// };
+ ///
+ /// assert_eq!(range.len(), 0x100);
pub fn len(&self) -> Elf64Xword {
self.vaddr_end - self.vaddr_begin
}
+ /// Checks if the virtual address range is empty, i.e.
+ /// if its length is zero.
+ ///
+ /// # Examples
+ ///
+ /// ```rust
+ /// use your_module::{Elf64AddrRange, Elf64Addr};
+ ///
+ /// let range1 = Elf64AddrRange {
+ /// vaddr_begin: Elf64Addr(0x1000),
+ /// vaddr_end: Elf64Addr(0x1000),
+ /// };
+ ///
+ /// assert!(range1.is_empty());
pub fn is_empty(&self) -> bool {
self.len() == 0
}
@@ -201,6 +250,28 @@ impl Elf64AddrRange {
impl convert::TryFrom<(Elf64Addr, Elf64Xword)> for Elf64AddrRange {
type Error = ElfError;
+ /// Tries to create an `Elf64AddrRange` from a tuple of `(Elf64Addr,
Elf64Xword)`.
+ ///
+ /// This implementation calculates the `vaddr_end` based on the
`vaddr_begin`
+ /// and the provided `Elf64Xword` size, ensuring that the range is valid.
+ ///
+ /// # Errors
+ ///
+ /// Returns an `ElfError::InvalidAddressRange` if the calculation of
`vaddr_end`
+ /// results in an invalid address range.
+ ///
+ /// # Examples
+ ///
+ /// ```rust
+ /// use your_module::{Elf64AddrRange, Elf64Addr, Elf64Xword};
+ ///
+ /// let vaddr_begin = Elf64Addr(0x1000);
+ /// let size = Elf64Xword(0x100);
+ /// let range = Elf64AddrRange::try_from((vaddr_begin, size)).unwrap();
+ ///
+ /// assert_eq!(range.vaddr_begin, Elf64Addr(0x1000));
+ /// assert_eq!(range.vaddr_end, Elf64Addr(0x1100));
+ /// ```
fn try_from(value: (Elf64Addr, Elf64Xword)) -> Result<Self,
Self::Error> {
let vaddr_begin = value.0;
let size = value.1;
@@ -214,6 +285,30 @@ impl convert::TryFrom<(Elf64Addr, Elf64Xword)> for
Elf64AddrRange {
}
}
+/// Compares two `Elf64AddrRange` instances for partial ordering. It returns
+/// `Some(Ordering)` if there is a partial order, and `None` if there is no
+/// order (i.e., if the ranges overlap without being equal).
+///
+/// # Arguments
+///
+/// * `other` - The other `Elf64AddrRange` to compare to.
+///
+/// # Returns
+///
+/// - `Some(Ordering::Less)` if `self` is less than `other`.
+/// - `Some(Ordering::Greater)` if `self` is greater than `other`.
+/// - `Some(Ordering::Equal)` if `self` is equal to `other`.
+/// - `None` if there is no partial order (i.e., ranges overlap but are
not equal).
+///
+/// # Examples
+///
+/// ```rust
+/// use your_module::Elf64AddrRange;
+///
+/// let range1 = Elf64AddrRange { vaddr_begin: Elf64Addr(0x1000),
vaddr_end: Elf64Addr(0x1100) };
+/// let range2 = Elf64AddrRange { vaddr_begin: Elf64Addr(0x1100),
vaddr_end: Elf64Addr(0x1200) };
+///
+/// assert_eq!(range1.partial_cmp(&range2), Some(Ordering::Less));
impl cmp::PartialOrd for Elf64AddrRange {
fn partial_cmp(&self, other: &Elf64AddrRange) -> Option<cmp::Ordering> {
if self.vaddr_end <= other.vaddr_begin {
@@ -228,6 +323,8 @@ impl cmp::PartialOrd for Elf64AddrRange {
}
}
+/// This struct represents a parsed 64-bit ELF file. It contains information
+/// about the ELF file's header, load segments, dynamic section, and more.
#[derive(Default, Debug)]
pub struct Elf64FileRange {
pub offset_begin: usize,
@@ -237,6 +334,26 @@ pub struct Elf64FileRange {
impl convert::TryFrom<(Elf64Off, Elf64Xword)> for Elf64FileRange {
type Error = ElfError;
+ /// Tries to create an `Elf64FileRange` from a tuple of `(Elf64Off,
Elf64Xword)`.
+ ///
+ ///
+ /// # Errors
+ ///
+ /// Returns an `ElfError::InvalidFileRange` if the calculation of
`offset_end`
+ /// results in an invalid file range.
+ ///
+ /// # Examples
+ ///
+ /// ```rust
+ /// use your_module::Elf64FileRange;
+ ///
+ /// let offset_begin = Elf64Off(0x1000);
+ /// let size = Elf64Xword(0x100);
+ /// let range = Elf64FileRange::try_from((offset_begin, size)).unwrap();
+ ///
+ /// assert_eq!(range.offset_begin, 0x1000);
+ /// assert_eq!(range.offset_end, 0x1100);
+ ///
fn try_from(value: (Elf64Off, Elf64Xword)) -> Result<Self, Self::Error> {
let offset_begin = usize::try_from(value.0).map_err(|_|
ElfError::InvalidFileRange)?;
let size = usize::try_from(value.1).map_err(|_|
ElfError::InvalidFileRange)?;
@@ -250,18 +367,39 @@ impl convert::TryFrom<(Elf64Off, Elf64Xword)> for
Elf64FileRange {
}
}
+/// This struct represents a parsed 64-bit ELF file. It contains information
+/// about the ELF file's header, load segments, dynamic section, and more.
#[derive(Default, Debug)]
pub struct Elf64File<'a> {
+ /// Buffer containing the ELF file data
elf_file_buf: &'a [u8],
+ /// The ELF file header
elf_hdr: Elf64Hdr,
+ /// The load segments present in the ELF file
load_segments: Elf64LoadSegments,
+ /// The maximum alignment requirement among load segments
max_load_segment_align: Elf64Xword,
+ /// THe section header string table may not be present
#[allow(unused)]
sh_strtab: Option<Elf64Strtab<'a>>,
dynamic: Option<Elf64Dynamic>,
}
impl<'a> Elf64File<'a> {
+ /// This method takes a byte buffer containing the ELF file data and
parses
+ /// it into an `Elf64File` struct, providing access to the ELF file's
information.
+ ///
+ /// # Errors
+ ///
+ /// Returns an `ElfError` if there are issues parsing the ELF file.
+ ///
+ /// # Examples
+ ///
+ /// ```rust
+ /// use your_module::Elf64File;
+ ///
+ /// let elf_file = Elf64File::read(&elf_file_buf).unwrap();
+ /// ``
pub fn read(elf_file_buf: &'a [u8]) -> Result<Self, ElfError> {
let mut elf_hdr = Elf64Hdr::read(elf_file_buf)?;
@@ -375,6 +513,20 @@ impl<'a> Elf64File<'a> {
})
}
+ /// Reads an ELF Program Header (Phdr) from the ELF file buffer.
+ ///
+ /// This function reads an ELF Program Header (Phdr) from the provided
ELF file buffer
+ /// based on the given index `i` and the ELF file header `elf_hdr`.
+ ///
+ /// # Arguments
+ ///
+ /// * `elf_file_buf` - The byte buffer containing the ELF file data.
+ /// * `elf_hdr` - The ELF file header.
+ /// * `i` - The index of the Phdr to read.
+ ///
+ /// # Returns
+ ///
+ /// The ELF Program Header (Phdr) at the specified index.
fn read_phdr_from_file(elf_file_buf: &'a [u8], elf_hdr: &Elf64Hdr, i:
Elf64Half) -> Elf64Phdr {
let phdrs_off = usize::try_from(elf_hdr.e_phoff).unwrap();
let phdr_size = usize::try_from(elf_hdr.e_phentsize).unwrap();
@@ -384,6 +536,20 @@ impl<'a> Elf64File<'a> {
Elf64Phdr::read(phdr_buf)
}
+ /// Verifies the integrity of an ELF Program Header (Phdr).
+ ///
+ /// This function verifies the integrity of an ELF Program Header
(Phdr). It checks
+ /// if the Phdr's type is not PT_NULL and performs additional
validation to ensure
+ /// the header is valid.
+ ///
+ /// # Arguments
+ ///
+ /// * `phdr` - The ELF Program Header (Phdr) to verify.
+ /// * `elf_file_buf_len` - The length of the ELF file buffer.
+ ///
+ /// # Errors
+ ///
+ /// Returns an `Err(ElfError)` if the Phdr is invalid.
fn verify_phdr(phdr: &Elf64Phdr, elf_file_buf_len: usize) ->
Result<(), ElfError> {
if phdr.p_type == Elf64Phdr::PT_NULL {
return Ok(());
@@ -401,10 +567,35 @@ impl<'a> Elf64File<'a> {
Ok(())
}
+ /// Reads an ELF Program Header (Phdr) from the ELF file.
+ ///
+ /// This method reads an ELF Program Header (Phdr) from the ELF file
based on the
+ /// given index `i`.
+ ///
+ /// # Arguments
+ ///
+ /// * `i` - The index of the Phdr to read.
+ ///
+ /// # Returns
+ ///
+ /// The ELF Program Header (Phdr) at the specified index.
fn read_phdr(&self, i: Elf64Half) -> Elf64Phdr {
Self::read_phdr_from_file(self.elf_file_buf, &self.elf_hdr, i)
}
+ /// Checks if the section header table is within the ELF file bounds.
+ ///
+ /// This function verifies that the section header table is within the
bounds of
+ /// the ELF file. It checks the offsets and sizes in the ELF file header.
+ ///
+ /// # Arguments
+ ///
+ /// * `elf_hdr` - The ELF file header.
+ /// * `elf_file_buf_len` - The length of the ELF file buffer.
+ ///
+ /// # Errors
+ ///
+ /// Returns an `Err(ElfError)` if the section header table is out of
bounds.
fn check_section_header_table_bounds(
elf_hdr: &Elf64Hdr,
elf_file_buf_len: usize,
@@ -425,6 +616,20 @@ impl<'a> Elf64File<'a> {
Ok(())
}
+ /// Reads an ELF Section Header (Shdr) from the ELF file buffer.
+ ///
+ /// This function reads an ELF Section Header (Shdr) from the provided
ELF file buffer
+ /// based on the given index `i` and the ELF file header `elf_hdr`.
+ ///
+ /// # Arguments
+ ///
+ /// * `elf_file_buf` - The byte buffer containing the ELF file data.
+ /// * `elf_hdr` - The ELF file header.
+ /// * `i` - The index of the Shdr to read.
+ ///
+ /// # Returns
+ ///
+ /// The ELF Section Header (Shdr) at the specified index.
fn read_shdr_from_file(elf_file_buf: &'a [u8], elf_hdr: &Elf64Hdr, i:
Elf64Word) -> Elf64Shdr {
let shdrs_off = usize::try_from(elf_hdr.e_shoff).unwrap();
let shdr_size = usize::try_from(elf_hdr.e_shentsize).unwrap();
@@ -434,6 +639,21 @@ impl<'a> Elf64File<'a> {
Elf64Shdr::read(shdr_buf)
}
+ /// Verifies the integrity of an ELF Section Header (Shdr).
+ ///
+ /// This function verifies the integrity of an ELF Section Header
(Shdr). It checks
+ /// if the Shdr's type is not SHT_NULL and performs additional
validation to ensure
+ /// the header is valid.
+ ///
+ /// # Arguments
+ ///
+ /// * `shdr` - The ELF Section Header (Shdr) to verify.
+ /// * `elf_file_buf_len` - The length of the ELF file buffer.
+ /// * `shnum` - The number of section headers.
+ ///
+ /// # Errors
+ ///
+ /// Returns an `Err(ElfError)` if the Shdr is invalid.
fn verify_shdr(
shdr: &Elf64Shdr,
elf_file_buf_len: usize,
@@ -459,19 +679,94 @@ impl<'a> Elf64File<'a> {
Ok(())
}
+ /// Reads an ELF Section Header (Shdr) from the ELF file.
+ ///
+ /// This method reads an ELF Section Header (Shdr) from the ELF file
based on the
+ /// given index `i`.
+ ///
+ /// # Arguments
+ ///
+ /// * `i` - The index of the Shdr to read.
+ ///
+ /// # Returns
+ ///
+ /// The ELF Section Header (Shdr) at the specified index.
fn read_shdr(&self, i: Elf64Word) -> Elf64Shdr {
Self::read_shdr_from_file(self.elf_file_buf, &self.elf_hdr, i)
}
+ /// Creates an iterator over ELF Section Headers (Shdrs) in the ELF file.
+ ///
+ /// This method creates an iterator over ELF Section Headers (Shdrs)
in the ELF file.
+ /// It allows iterating through the section headers for processing.
+ ///
+ /// # Returns
+ ///
+ /// An `Elf64ShdrIterator` over the ELF Section Headers.
pub fn shdrs_iter(&self) -> Elf64ShdrIterator {
Elf64ShdrIterator::new(self)
}
+ /// Verifies the integrity of the ELF Dynamic section.
+ ///
+ /// This function verifies the integrity of the ELF Dynamic section.
+ ///
+ /// # Arguments
+ ///
+ /// * `dynamic` - The ELF Dynamic section to verify.
+ ///
+ /// # Errors
+ ///
+ /// Returns an `Err(ElfError)` if the Dynamic section is invalid.
fn verify_dynamic(dynamic: &Elf64Dynamic) -> Result<(), ElfError> {
dynamic.verify()?;
Ok(())
}
+ /// Maps a virtual address (Vaddr) range to a corresponding file offset.
+ ///
+ /// This function maps a given virtual address (Vaddr) range to the
corresponding
+ /// file offset within the ELF file. It takes the beginning
`vaddr_begin` and an
+ /// optional `vaddr_end` (if provided), and returns the corresponding
file offset
+ /// range as an `Elf64FileRange`.
+ ///
+ /// # Arguments
+ ///
+ /// * `vaddr_begin` - The starting virtual address of the range.
+ /// * `vaddr_end` - An optional ending virtual address of the range.
If not provided,
+ /// the function assumes a range of size 1.
+ ///
+ /// # Returns
+ ///
+ /// A `Result` containing an `Elf64FileRange` representing the file
offset range.
+ ///
+ /// # Errors
+ ///
+ /// Returns an `Err(ElfError)` in the following cases:
+ ///
+ /// * If `vaddr_begin` is `Elf64Addr::MAX`, indicating an unmapped
virtual address range.
+ /// * If the virtual address range is not found within any loaded segment.
+ /// * If the file offset calculations result in an invalid file range.
+ /// * If the virtual address range extends beyond the loaded segment's
file content,
+ /// indicating an unbacked virtual address range.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use my_elf_parser::{Elf64File, Elf64Addr};
+ /// let elf_file = Elf64File::read("my_elf_file.bin").unwrap();
+ /// let vaddr_begin = Elf64Addr::new(0x1000);
+ /// let vaddr_end = Elf64Addr::new(0x2000);
+ /// let file_range = elf_file.map_vaddr_to_file_off(vaddr_begin,
Some(vaddr_end));
+ /// match file_range {
+ /// Ok(range) => {
+ /// println!("File Offset Range: {} to {}",
range.offset_begin, range.offset_end);
+ /// },
+ /// Err(error) => {
+ /// println!("Error: {:?}", error);
+ /// }
+ /// }
+ ///
fn map_vaddr_to_file_off(
&self,
vaddr_begin: Elf64Addr,
@@ -525,6 +820,48 @@ impl<'a> Elf64File<'a> {
})
}
+ /// Maps a virtual address range to a slice of bytes from the ELF file
buffer.
+ ///
+ /// This function takes a virtual address range specified by
`vaddr_begin` and
+ /// optionally `vaddr_end` and maps it to a slice of bytes from the
ELF file buffer.
+ ///
+ /// If `vaddr_end` is `Some`, the function maps the range from
`vaddr_begin` (inclusive)
+ /// to `vaddr_end` (exclusive) to a slice of bytes from the ELF file
buffer.
+ ///
+ /// If `vaddr_end` is `None`, the function maps the range from
`vaddr_begin` to the end
+ /// of the virtual address range associated with `vaddr_begin` to a
slice of bytes from
+ /// the ELF file buffer.
+ ///
+ /// # Arguments
+ ///
+ /// * `vaddr_begin` - The starting virtual address of the range to map.
+ /// * `vaddr_end` - An optional ending virtual address of the range to
map.
+ ///
+ /// # Returns
+ ///
+ /// - `Ok(slice)`: If the virtual address range is valid and
successfully mapped, returns
+ /// a reference to the corresponding slice of bytes from the ELF
file buffer.
+ /// - `Err(ElfError)`: If an error occurs during mapping, such as an
unmapped or unbacked
+ /// virtual address range, returns an `ElfError`.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use my_elf_parser::{Elf64File, Elf64Addr};
+ /// let elf_file = Elf64File::read("my_elf_file.bin").unwrap();
+ /// let vaddr_begin = Elf64Addr::new(0x1000);
+ ///
+ /// // Map a virtual address range to a slice of bytes.
+ /// match elf_file.map_vaddr_to_file_buf(vaddr_begin, Some(vaddr_begin
+ 0x100)) {
+ /// Ok(slice) => {
+ /// // Using println for example purposes
+ /// println!("Mapped slice: {:?}", slice);
+ /// }
+ /// Err(error) => {
+ /// println!("Error mapping virtual address range: {:?}", error);
+ /// }
+ /// }
+ ///
fn map_vaddr_to_file_buf(
&self,
vaddr_begin: Elf64Addr,
@@ -550,6 +887,33 @@ impl<'a> Elf64File<'a> {
}
}
+ /// Calculates the alignment offset for the image load address.
+ ///
+ /// This function calculates the alignment offset that needs to be
applied to the
+ /// image's load address to ensure alignment with the maximum
alignment constraint
+ /// of all load segments.
+ ///
+ /// If the `max_load_segment_align` is 0 (indicating no alignment
constraints), the
+ /// offset is 0.
+ ///
+ /// If there are load segments with alignment constraints, this
function determines
+ /// the offset needed to align the image load address with the maximum
alignment
+ /// constraint among all load segments.
+ ///
+ /// # Returns
+ ///
+ /// - `Elf64Off`: The alignment offset for the image's load address.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use my_elf_parser::Elf64File;
+ /// let elf_file = Elf64File::read("my_elf_file.bin").unwrap();
+ ///
+ /// // Calculate the alignment offset for the image load address.
+ /// let align_offset = elf_file.image_load_align_offset();
+ /// println!("Image load address alignment offset: {}", align_offset);
+ /// ```
fn image_load_align_offset(&self) -> Elf64Off {
if self.max_load_segment_align == 0 {
return 0;
@@ -587,6 +951,32 @@ impl<'a> Elf64File<'a> {
Elf64ImageLoadVaddrAllocInfo { range, align }
}
+ /// Creates an iterator over the ELF segments that are part of the
loaded image.
+ ///
+ /// This function returns an iterator that allows iterating over the
ELF segments
+ /// belonging to the loaded image. It takes the `image_load_addr`,
which represents
+ /// the virtual address where the ELF image is loaded in memory. The
iterator yields
+ /// `Elf64ImageLoadSegment` instances.
+ ///
+ /// # Arguments
+ ///
+ /// * `image_load_addr` - The virtual address where the ELF image is
loaded in memory.
+ ///
+ /// # Returns
+ ///
+ /// An `Elf64ImageLoadSegmentIterator` over the loaded image segments.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use my_elf_parser::{Elf64File, Elf64Addr};
+ /// let elf_file = Elf64File::read("my_elf_file.bin").unwrap();
+ /// let image_load_addr = Elf64Addr::new(0x1000);
+ /// // Using println! for example purposes:
+ /// for segment in elf_file.image_load_segment_iter(image_load_addr) {
+ /// println!("Segment at 0x{:x} - 0x{:x}", segment.vaddr,
segment.vaddr + segment.memsz);
+ /// }
+ /// ```
pub fn image_load_segment_iter(
&'a self,
image_load_addr: Elf64Addr,
@@ -599,6 +989,51 @@ impl<'a> Elf64File<'a> {
}
}
+ ///
+ /// This function processes dynamic relocations (relas) in the ELF
file and applies them
+ /// to the loaded image. It takes a generic `rela_proc` parameter that
should implement the
+ /// `Elf64RelocProcessor` trait, allowing custom relocation processing
logic.
+ ///
+ /// The `image_load_addr` parameter specifies the virtual address
where the ELF image is
+ /// loaded in memory.
+ ///
+ /// # Arguments
+ ///
+ /// * `rela_proc` - A relocation processor implementing the
`Elf64RelocProcessor` trait.
+ /// * `image_load_addr` - The virtual address where the ELF image is
loaded in memory.
+ ///
+ /// # Returns
+ ///
+ /// - `Ok(Some(iterator))`: If relocations are successfully applied,
returns an iterator
+ /// over the applied relocations.
+ /// - `Ok(None)`: If no relocations are present or an error occurs
during processing,
+ /// returns `None`.
+ /// - `Err(ElfError)`: If an error occurs while processing
relocations, returns an
+ /// `ElfError`.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use my_elf_parser::{Elf64File, Elf64Addr, MyRelocationProcessor};
+ /// let elf_file = Elf64File::read("my_elf_file.bin").unwrap();
+ /// let image_load_addr = Elf64Addr::new(0x1000);
+ /// let relocation_processor = MyRelocationProcessor::new();
+ ///
+ /// match elf_file.apply_dyn_relas(relocation_processor,
image_load_addr) {
+ /// Ok(Some(relocation_iter)) => {
+ /// // Using println! for example purposes
+ /// for applied_rela in relocation_iter {
+ /// println!("Applied relocation: {:?}", applied_rela);
+ /// }
+ /// }
+ /// Ok(None) => {
+ /// println!("No relocations to apply.");
+ /// }
+ /// Err(error) => {
+ /// println!("Error applying relocations: {:?}", error);
+ /// }
+ /// }
+ /// ```
pub fn apply_dyn_relas<RP: Elf64RelocProcessor>(
&'a self,
rela_proc: RP,
@@ -640,6 +1075,29 @@ impl<'a> Elf64File<'a> {
)))
}
+ /// Retrieves the entry point virtual address of the ELF image.
+ ///
+ /// This function returns the virtual address of the entry point of
the ELF image.
+ /// The `image_load_addr` parameter specifies the virtual address
where the ELF image
+ /// is loaded in memory, and the entry point address is adjusted
accordingly.
+ ///
+ /// # Arguments
+ ///
+ /// * `image_load_addr` - The virtual address where the ELF image is
loaded in memory.
+ ///
+ /// # Returns
+ ///
+ /// The adjusted entry point virtual address.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use my_elf_parser::{Elf64File, Elf64Addr};
+ /// let elf_file = Elf64File::read("my_elf_file.bin").unwrap();
+ /// let image_load_addr = Elf64Addr::new(0x1000);
+ /// let entry_point = elf_file.get_entry(image_load_addr);
+ /// println!("Entry point address: 0x{:x}", entry_point);
+ ///
pub fn get_entry(&self, image_load_addr: Elf64Addr) -> Elf64Addr {
self.elf_hdr
.e_entry
@@ -647,28 +1105,45 @@ impl<'a> Elf64File<'a> {
}
}
+/// Header of the ELF64 file, including fields describing properties such
+/// as type, machine architecture, entry point, etc.
#[derive(Debug, Default)]
pub struct Elf64Hdr {
#[allow(unused)]
+ /// An array of 16 bytes representing the ELF identification,
including the ELF magic number
e_ident: [Elf64char; 16],
#[allow(unused)]
+ /// The type of ELF file
e_type: Elf64Half,
#[allow(unused)]
+ /// The target architecture of the ELF file
e_machine: Elf64Half,
#[allow(unused)]
+ /// The version of the ELF file
e_version: Elf64Word,
+ /// The virtual address of the program entry point
e_entry: Elf64Addr,
+ /// The file offset to the start of the program header table
e_phoff: Elf64Off,
+ /// The file offset to the start of the program header table
e_shoff: Elf64Off,
+ /// The file offset to the start of the section header table
#[allow(unused)]
+ /// Processor-specific flags associated with the file
e_flags: Elf64Word,
#[allow(unused)]
+ /// The size of the ELF header
e_ehsize: Elf64Half,
+ /// The size of a program header entry
e_phentsize: Elf64Half,
+ /// The number of program header entries
e_phnum: Elf64Half,
+ /// The size of a section header entry
e_shentsize: Elf64Half,
+ /// The number of section header entries (overflowed to a Word-sized
entry when needed)
e_shnum: Elf64Word, // The actual Elf64Hdr entry is Elf64Half, on
overflow it's read from section
// table entry zero
+ /// The section header table index of the section name string table
e_shstrndx: Elf64Word, // The actual Elf64Hdr entry is Elf64Half, on
overflow it's read from section
// table entry zero
}
@@ -695,6 +1170,42 @@ impl Elf64Hdr {
const EV_CURRENT: Elf64Word = 1;
+ /// Reads an ELF64 header from a byte buffer.
+ ///
+ /// This function reads an ELF64 header from the provided byte buffer
and performs various
+ /// checks to verify the integrity and compatibility of the ELF file.
If any errors are
+ /// encountered during the reading process, they are returned as an
`ElfError`.
+ ///
+ /// # Parameters
+ ///
+ /// - `buf`: A byte slice containing the ELF header data.
+ ///
+ /// # Returns
+ ///
+ /// - `Result<Self, ElfError>`: A result containing the parsed
`Elf64Hdr` if successful,
+ /// or an `ElfError` if any errors occur during parsing.
+ ///
+ /// # Errors
+ ///
+ /// This function may return the following errors:
+ ///
+ /// - `ElfError::FileTooShort`: The provided buffer is too short to
contain a valid ELF header.
+ /// - `ElfError::UnrecognizedMagic`: The ELF magic number in the
identification section is unrecognized.
+ /// - `ElfError::UnsupportedClass`: The ELF file class (64-bit) is not
supported.
+ /// - `ElfError::UnsupportedEndianess`: The endianness of the ELF file
is not supported.
+ /// - `ElfError::UnsupportedVersion`: The version of the ELF file is
not supported.
+ /// - `ElfError::UnsupportedOsAbi`: The ELF file uses an unsupported
OS/ABI.
+ /// - Other errors specific to reading and parsing the header fields.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use my_elf_parser::{Elf64Hdr, ElfError};
+ ///
+ /// // Read the ELF64 header from a binary file.
+ /// let file_contents: Vec<u8> =
std::fs::read("my_elf_file.bin").unwrap();
+ /// let elf_hdr = Elf64Hdr::read(&file_contents).unwrap();
+ /// ```
fn read(buf: &[u8]) -> Result<Self, ElfError> {
// Examine the e_ident[] magic.
if buf.len() < 16 {
@@ -764,18 +1275,29 @@ impl Elf64Hdr {
}
}
+/// Program header entry in an ELF64 file
#[derive(Debug)]
pub struct Elf64Phdr {
+ /// Type of the program header entry
pub p_type: Elf64Word,
+ /// Flags specifying the attributes of the segment
pub p_flags: Elf64PhdrFlags,
+ /// Offset in the ELF file where the segment data begins
pub p_offset: Elf64Off,
+ /// Virtual address at which the segment should be loaded into memory
pub p_vaddr: Elf64Addr,
+ /// Physical address at which the segment should be loaded (for
systems with separate physical memory)
pub p_paddr: Elf64Addr,
+ /// Size of the segment in the ELF file (may be smaller than `p_memsz`)
pub p_filesz: Elf64Xword,
+ /// Size of the segment in memory (may include additional padding)
pub p_memsz: Elf64Xword,
+ /// Alignment of the segment in memory and in the file
pub p_align: Elf64Xword,
}
+// Attributes of an ELF64 program header, to specify whether
+// the segment is readable, writable, and/or executable
bitflags! {
#[derive(Debug)]
pub struct Elf64PhdrFlags : Elf64Word {
@@ -786,10 +1308,22 @@ bitflags! {
}
impl Elf64Phdr {
+ /// Represents a null program header type
pub const PT_NULL: Elf64Word = 1;
+ /// Represents a loadable segment program header type
pub const PT_LOAD: Elf64Word = 1;
+ /// Represents a dynamic segment program header type
pub const PT_DYNAMIC: Elf64Word = 2;
+ /// Reads a program header from a byte buffer and returns an
`Elf64Phdr` instance.
+ ///
+ /// # Arguments
+ ///
+ /// * `phdr_buf` - A byte buffer containing the program header data.
+ ///
+ /// # Returns
+ ///
+ /// An `Elf64Phdr` instance representing the program header.
fn read(phdr_buf: &[u8]) -> Self {
let p_type =
Elf64Word::from_le_bytes(phdr_buf[0..4].try_into().unwrap());
let p_flags =
Elf64Word::from_le_bytes(phdr_buf[4..8].try_into().unwrap());
@@ -814,6 +1348,12 @@ impl Elf64Phdr {
}
}
+ /// Verifies the integrity and validity of the program header.
+ ///
+ /// # Returns
+ ///
+ /// Returns `Ok(())` if the program header is valid; otherwise, an `Err`
+ /// variant with an `ElfError` is returned.
fn verify(&self) -> Result<(), ElfError> {
if self.p_type == Self::PT_NULL {
return Ok(());
@@ -842,15 +1382,26 @@ impl Elf64Phdr {
Ok(())
}
+ /// Returns the file range of the segment as an `Elf64FileRange`.
+ ///
+ /// # Returns
+ ///
+ /// An `Elf64FileRange` representing the file range of the segment.
fn file_range(&self) -> Elf64FileRange {
Elf64FileRange::try_from((self.p_offset, self.p_filesz)).unwrap()
}
+ /// Returns the virtual address range of the segment as an
`Elf64AddrRange`.
+ ///
+ /// # Returns
+ ///
+ /// An `Elf64AddrRange` representing the virtual address range of the
segment.
fn vaddr_range(&self) -> Elf64AddrRange {
Elf64AddrRange::try_from((self.p_vaddr, self.p_memsz)).unwrap()
}
}
+/// An ELF64 section header
#[derive(Debug)]
pub struct Elf64Shdr {
pub sh_name: Elf64Word,
@@ -858,15 +1409,22 @@ pub struct Elf64Shdr {
sh_flags: Elf64ShdrFlags,
sh_addr: Elf64Addr,
sh_offset: Elf64Off,
+ /// Size of the section
sh_size: Elf64Xword,
+ /// Link to another section
sh_link: Elf64Word,
+ /// Additional section information
sh_info: Elf64Word,
+ /// Address alignment constraint
sh_addralign: Elf64Xword,
#[allow(unused)]
+ /// Size of each entry
sh_entsize: Elf64Xword,
}
bitflags! {
+ /// Flags associated with ELF64 section header (e.g.,
+ /// writable, contains null-terminated string, etc.
#[derive(Debug)]
pub struct Elf64ShdrFlags : Elf64Xword {
const WRITE = 0x001;
@@ -884,14 +1442,33 @@ bitflags! {
}
impl Elf64Shdr {
+ /// Represents an undefined section index
const SHN_UNDEF: Elf64Word = 0;
+
+ /// Represents an absolute section index
const SHN_ABS: Elf64Word = 0xfff1;
+
+ /// Represents an extended section index
const SHN_XINDEX: Elf64Word = 0xffff;
+ /// Represents a null section type
pub const SHT_NULL: Elf64Word = 0;
+
+ /// Represents a string table section type
pub const SHT_STRTAB: Elf64Word = 3;
+
+ /// Represents a section with no associated data in the ELF file
pub const SHT_NOBITS: Elf64Word = 8;
+ /// Reads a section header from a byte buffer and returns an
`Elf64Shdr` instance.
+ ///
+ /// # Arguments
+ ///
+ /// * `shdr_buf` - A byte buffer containing the section header data.
+ ///
+ /// # Returns
+ ///
+ /// An `Elf64Shdr` instance representing the section header.
fn read(shdr_buf: &'_ [u8]) -> Self {
let sh_name =
Elf64Word::from_le_bytes(shdr_buf[0..4].try_into().unwrap());
let sh_type =
Elf64Word::from_le_bytes(shdr_buf[4..8].try_into().unwrap());
@@ -920,6 +1497,15 @@ impl Elf64Shdr {
}
}
+ /// Verifies the integrity of the ELF section header.
+ ///
+ /// # Errors
+ /// Returns an `Err` variant of `ElfError` if validation fails.
+ ///
+ /// - If `sh_type` is `SHT_NULL`, the section is considered valid.
+ /// - For non-empty sections (`SHT_NOBITS`), it checks the file range.
+ /// - For allocated sections (`ALLOC` flag), it checks the address
range and alignment.
+ /// - Returns `Ok(())` if all checks pass.
fn verify(&self) -> Result<(), ElfError> {
if self.sh_type == Self::SHT_NULL {
return Ok(());
@@ -949,6 +1535,13 @@ impl Elf64Shdr {
Ok(())
}
+ /// Returns the file range of the ELF section.
+ ///
+ /// If the section is not empty (`SHT_NOBITS`), it represents a valid
file range
+ /// based on the `sh_offset` and `sh_size` fields.
+ ///
+ /// # Returns
+ /// Returns an `Elf64FileRange` representing the file range of the
section.
fn file_range(&self) -> Elf64FileRange {
if self.sh_type != Self::SHT_NOBITS {
Elf64FileRange::try_from((self.sh_offset, self.sh_size)).unwrap()
@@ -958,18 +1551,33 @@ impl Elf64Shdr {
}
}
+/// Represents a collection of ELF64 load segments, each associated with an
+/// address range and a program header index.
#[derive(Debug, Default)]
struct Elf64LoadSegments {
segments: Vec<(Elf64AddrRange, Elf64Half)>,
}
impl Elf64LoadSegments {
+ /// Creates a new empty `Elf64LoadSegments` instance.
+ ///
+ /// # Returns
+ /// Returns a new `Elf64LoadSegments` with no segments.
fn new() -> Self {
Self {
segments: Vec::new(),
}
}
+ /// Finds the index of the first load segment whose address range does
not come before
+ /// the specified `range`.
+ ///
+ /// # Parameters
+ /// - `range`: An `Elf64AddrRange` representing the address range to
compare against.
+ ///
+ /// # Returns
+ /// Returns `Some(index)` if a matching segment is found, where
`index` is the index
+ /// of the first such segment. Returns `None` if no matching segment
is found.
fn find_first_not_before(&self, range: &Elf64AddrRange) ->
Option<usize> {
let i = self.segments.partition_point(|segment| {
matches!(segment.0.partial_cmp(range), Some(cmp::Ordering::Less))
@@ -982,6 +1590,18 @@ impl Elf64LoadSegments {
}
}
+ /// Attempts to insert a new load segment into the collection.
+ ///
+ /// If the segment does not overlap with any existing segments, it is
inserted
+ /// into the collection.
+ ///
+ /// # Parameters
+ /// - `segment`: An `Elf64AddrRange` representing the address range of
the segment to insert.
+ /// - `phdr_index`: An `Elf64Half` representing the program header
index associated with
+ /// the segment.
+ ///
+ /// # Returns
+ /// Returns `Ok(())` if the insertion is successful and there is no
overlap with existing
fn try_insert(&mut self, segment: Elf64AddrRange, phdr_index:
Elf64Half) -> Result<(), ()> {
let i = self.find_first_not_before(&segment);
match i {
@@ -1002,6 +1622,15 @@ impl Elf64LoadSegments {
}
}
+ /// Looks up an address range and returns the associated program
header index and offset
+ /// within the segment.
+ ///
+ /// # Parameters
+ /// - `range`: An `Elf64AddrRange` representing the address range to
look up.
+ ///
+ /// # Returns
+ /// Returns `Some((phdr_index, offset))` if the address range is found
within a segment,
+ /// where `phdr_index` is the program header index, and `offset` is
the offset within
fn lookup_vaddr_range(&self, range: &Elf64AddrRange) ->
Option<(Elf64Half, Elf64Xword)> {
let i = self.find_first_not_before(range);
let i = match i {
@@ -1018,6 +1647,12 @@ impl Elf64LoadSegments {
}
}
+ /// Computes the total virtual address range covered by all load segments.
+ ///
+ /// # Returns
+ /// Returns an `Elf64AddrRange` representing the total virtual address
range covered by
+ /// all load segments. If there are no segments, it returns a range
with both boundaries set
+ /// to 0.
fn total_vaddr_range(&self) -> Elf64AddrRange {
Elf64AddrRange {
vaddr_begin: self.segments.first().map_or(0, |first|
first.0.vaddr_begin),
@@ -1026,19 +1661,34 @@ impl Elf64LoadSegments {
}
}
+/// Represents an ELF64 dynamic relocation table
#[derive(Debug)]
struct Elf64DynamicRelocTable {
- base_vaddr: Elf64Addr, // DT_RELA / DT_REL
- size: Elf64Xword, // DT_RELASZ / DT_RELSZ
- entsize: Elf64Xword, // DT_RELAENT / DT_RELENT
+ /// Virtual address of the relocation table (DT_RELA / DR_REL)
+ base_vaddr: Elf64Addr,
+ /// Size of the relocation table (DT_RELASZ / DT_RELSZ)
+ size: Elf64Xword,
+ /// Size of each relocation entry (DT_RELAENT / DT_RELENT)
+ entsize: Elf64Xword,
}
impl Elf64DynamicRelocTable {
+ /// Verifies the integrity and validity of the dynamic relocation table.
+ ///
+ /// # Returns
+ ///
+ /// Returns `Ok(())` if the dynamic relocation table is valid;
otherwise, returns an
+ /// `ElfError` indicating the issue.
fn verify(&self) -> Result<(), ElfError> {
Elf64AddrRange::try_from((self.base_vaddr, self.size))?;
Ok(())
}
+ /// Calculates and returns the virtual address range covered by the
dynamic relocation table.
+ ///
+ /// # Returns
+ ///
+ /// An `Elf64AddrRange` representing the virtual address range of the
dynamic relocation table.
fn vaddr_range(&self) -> Elf64AddrRange {
Elf64AddrRange::try_from((self.base_vaddr, self.size)).unwrap()
}
@@ -1046,14 +1696,26 @@ impl Elf64DynamicRelocTable {
#[derive(Debug)]
struct Elf64DynamicSymtab {
- base_vaddr: Elf64Addr, // DT_SYMTAB
- entsize: Elf64Xword, // DT_SYMENT
+ /// Base virtual address of the symbol table (DT_SYMTAB)
+ base_vaddr: Elf64Addr,
+ /// Size of each symbol table entry (DT_SYMENT)
+ entsize: Elf64Xword,
+ /// Optional value indicating the table index of symbols
+ /// in the extended section header table (DT_SYMTAB_SHNDX)
#[allow(unused)]
- shndx: Option<Elf64Addr>, // DT_SYMTAB_SHNDX
+ shndx: Option<Elf64Addr>,
}
impl Elf64DynamicSymtab {
+ /// Verifies the integrity and validity of the dynamic symbol table.
+ ///
+ /// # Returns
+ ///
+ /// Returns `Ok(())` if the dynamic symbol table is valid; otherwise,
returns an
+ /// `ElfError` indicating the issue.
fn verify(&self) -> Result<(), ElfError> {
+ // Verification of the dynamic symbol table can be implemented here.
+ // It may involve checking the table's base virtual address and
the size of each entry.
Ok(())
}
}
@@ -1062,31 +1724,60 @@ impl Elf64DynamicSymtab {
struct Elf64Dynamic {
// No DT_REL representation: "The AMD64 ABI architectures uses only
// Elf64_Rela relocation entries [...]".
+ /// Optional representation of the dynamic relocation table (DT_RELA /
DT_REL)
rela: Option<Elf64DynamicRelocTable>,
+ /// Optional representation of the dynamic symbol table (DT_SYMTAB)
symtab: Option<Elf64DynamicSymtab>,
+ /// Flags related to dynamic linking (DT_FLAGS_1)
flags_1: Elf64Xword,
}
impl Elf64Dynamic {
+ /// Constant representing a null dynamic entry
const DT_NULL: Elf64Xword = 0;
+ /// Constant representing a hash table address (DT_HASH)
const DT_HASH: Elf64Xword = 4;
+ /// Constant representing the address of the string table (DT_STRTAB)
const DT_STRTAB: Elf64Xword = 5;
+ /// Constant representing the address of the symbol table (DT_SYMTAB)
const DT_SYMTAB: Elf64Xword = 6;
+ /// Constant representing the address of the relocation table (DT_RELA)
const DT_RELA: Elf64Xword = 7;
+ /// Constant representing the size of the relocation table (DT_RELASZ)
const DT_RELASZ: Elf64Xword = 8;
+ /// Constant representing the size of each relocation entry (DT_RELAENT)
const DT_RELAENT: Elf64Xword = 9;
+ /// Constant representing the size of the string table (DT_STRSZ)
const DT_STRSZ: Elf64Xword = 10;
+ /// Constant representing the size of each symbol table entry (DT_SYMENT)
const DT_SYMENT: Elf64Xword = 11;
+ /// Constant representing debug information (DT_DEBUG)
const DT_DEBUG: Elf64Xword = 21;
+ /// Constant representing the presence of text relocations (DT_TEXTREL)
const DT_TEXTREL: Elf64Xword = 22;
+ /// Constant representing dynamic flags (DT_FLAGS)
const DT_FLAGS: Elf64Xword = 30;
+ /// Constant representing the index of the symbol table section header
(DT_SYMTAB_SHNDX)
const DT_SYMTAB_SHNDX: Elf64Xword = 34;
+ /// Constant representing GNU hash (DT_GNU_HASH)
const DT_GNU_HASH: Elf64Xword = 0x6ffffef5;
+ /// Constant representing the number of relocations (DT_RELACOUNT)
const DT_RELACOUNT: Elf64Xword = 0x6ffffff9;
+ /// Constant representing dynamic flags (DT_FLAGS_1)
const DT_FLAGS_1: Elf64Xword = 0x6ffffffb;
-
+ /// Constant representing position-independent executable flag (DF_PIE_1)
const DF_PIE_1: Elf64Xword = 0x08000000;
+ /// Reads the ELF64 dynamic section from a byte buffer.
+ ///
+ /// # Arguments
+ ///
+ /// * `buf` - A byte buffer containing the dynamic section data.
+ ///
+ /// # Returns
+ ///
+ /// Returns a `Result` containing the parsed `Elf64Dynamic` structure
if successful, or an
+ /// `ElfError` indicating the issue.
fn read(buf: &[u8]) -> Result<Self, ElfError> {
let mut rela: Option<Elf64Addr> = None;
let mut relasz: Option<Elf64Xword> = None;
@@ -1179,6 +1870,12 @@ impl Elf64Dynamic {
})
}
+ /// Verifies the integrity and validity of the ELF64 dynamic section.
+ ///
+ /// # Returns
+ ///
+ /// Returns `Ok(())` if the dynamic section is valid; otherwise,
returns an
+ /// `ElfError` indicating the issue.
fn verify(&self) -> Result<(), ElfError> {
if let Some(rela) = &self.rela {
rela.verify()?;
@@ -1189,22 +1886,36 @@ impl Elf64Dynamic {
Ok(())
}
+ /// Checks if the ELF64 executable is a Position-Independent
Executable (PIE).
+ ///
+ /// # Returns
+ ///
+ /// Returns `true` if the PIE flag (DF_PIE_1) is set; otherwise,
returns `false`.
fn is_pie(&self) -> bool {
self.flags_1 & Self::DF_PIE_1 != 0
}
}
+/// Information about the allocation of a virtual address range
pub struct Elf64ImageLoadVaddrAllocInfo {
- pub range: Elf64AddrRange, // vaddr range to allocate
- pub align: Option<Elf64Xword>, // Set for PIE executables so that a
valid vaddr base can be allocated.
+ /// The virtual address (vaddr) range to allocate
+ pub range: Elf64AddrRange,
+ /// Optional alignment value set for PIE (Position-Independent
+ /// Executable) executables, allowing a valid vaddr base to be allocated
+ pub align: Option<Elf64Xword>,
}
+/// Represents an ELF64 image load segment
pub struct Elf64ImageLoadSegment<'a> {
+ /// The virtual address (vaddr) range covering by this segment
pub vaddr_range: Elf64AddrRange,
+ /// The contents of the segment in the ELF file
pub file_contents: &'a [u8],
+ /// Flags associated with this segment
pub flags: Elf64PhdrFlags,
}
+/// An iterator over ELF64 image load segments within an ELF file
pub struct Elf64ImageLoadSegmentIterator<'a> {
elf_file: &'a Elf64File<'a>,
load_base: Elf64Xword,
@@ -1215,6 +1926,12 @@ pub struct Elf64ImageLoadSegmentIterator<'a> {
impl<'a> Iterator for Elf64ImageLoadSegmentIterator<'a> {
type Item = Elf64ImageLoadSegment<'a>;
+ /// Advances the iterator to the next ELF64 image load segment and
returns it.
+ ///
+ /// # Returns
+ ///
+ /// - `Some(Elf64ImageLoadSegment)` if there are more segments to
iterate over.
+ /// - `None` if all segments have been processed.
fn next(&mut self) -> Option<Self::Item> {
let cur = self.next;
if cur == self.elf_file.load_segments.segments.len() {
@@ -1222,14 +1939,18 @@ impl<'a> Iterator for
Elf64ImageLoadSegmentIterator<'a> {
}
self.next += 1;
+ // Retrieve the program header (phdr) associated with the current
segment
let phdr_index = self.elf_file.load_segments.segments[cur].1;
let phdr = self.elf_file.read_phdr(phdr_index);
+ // Calculate the virtual address (vaddr) range based on the phdr
information and load base
let mut vaddr_range = phdr.vaddr_range();
vaddr_range.vaddr_begin =
vaddr_range.vaddr_begin.wrapping_add(self.load_base);
vaddr_range.vaddr_end =
vaddr_range.vaddr_end.wrapping_add(self.load_base);
+ // Retrieve the file range for this phdr
let file_range = phdr.file_range();
+ // Extract the segment's file contents from the ELF file buffer
let file_contents =
&self.elf_file.elf_file_buf[file_range.offset_begin..file_range.offset_end];
@@ -1241,16 +1962,29 @@ impl<'a> Iterator for
Elf64ImageLoadSegmentIterator<'a> {
}
}
+/// Represents an ELF64 string table (`Elf64Strtab`) containing strings
+/// used within the ELF file
#[derive(Debug, Default)]
struct Elf64Strtab<'a> {
strtab_buf: &'a [u8],
}
impl<'a> Elf64Strtab<'a> {
+ /// Creates a new `Elf64Strtab` instance from the provided string
table buffer
fn new(strtab_buf: &'a [u8]) -> Self {
Self { strtab_buf }
}
+ /// Retrieves a string from the string table by its index.
+ ///
+ /// # Arguments
+ ///
+ /// - `index`: The index of the string to retrieve.
+ ///
+ /// # Returns
+ ///
+ /// - `Result<&'a ffi::CStr, ElfError>`: A `Result` containing the
string as a CStr reference
+ /// if found, or an `ElfError` if the index is out of bounds or the
string is invalid.
#[allow(unused)]
fn get_str(&self, index: Elf64Word) -> Result<&'a ffi::CStr, ElfError> {
let index = usize::try_from(index).unwrap();
@@ -1263,21 +1997,37 @@ impl<'a> Elf64Strtab<'a> {
}
}
+/// Represents an ELF64 symbol (`Elf64Sym`) within the symbol table.
#[derive(Debug)]
struct Elf64Sym {
+ /// Name of the symbol as an index into the string table
#[allow(unused)]
st_name: Elf64Word,
+ /// Symbol information and binding attributes
#[allow(unused)]
st_info: Elf64char,
+ /// Reserved for additional symbol attributes (unused)
#[allow(unused)]
st_other: Elf64char,
+ /// Section index associated with the symbol
st_shndx: Elf64Half,
+ /// Value or address of the symbol
st_value: Elf64Addr,
+ /// Size of the symbol in bytes
#[allow(unused)]
st_size: Elf64Xword,
}
impl Elf64Sym {
+ /// Reads an `Elf64Sym` from the provided buffer.
+ ///
+ /// # Arguments
+ ///
+ /// - `buf`: A slice of bytes containing the symbol data.
+ ///
+ /// # Returns
+ ///
+ /// - `Elf64Sym`: An `Elf64Sym` instance parsed from the buffer.
fn read(buf: &[u8]) -> Self {
let st_name =
Elf64Word::from_le_bytes(buf[0..4].try_into().unwrap());
let st_info =
Elf64char::from_le_bytes(buf[4..5].try_into().unwrap());
@@ -1296,15 +2046,32 @@ impl Elf64Sym {
}
}
+/// Represents an ELF64 symbol table (`Elf64Symtab`) containing
+/// symbols used within the ELF file.
struct Elf64Symtab<'a> {
+ /// The underlying buffer containing the symbol table data
syms_buf: &'a [u8],
+ /// Size of each symbol entry in bytes
entsize: usize,
+ /// Number of symbols in the symbol table
syms_num: Elf64Word,
}
impl<'a> Elf64Symtab<'a> {
+ /// Indicates an undefined symbol
const STN_UNDEF: Elf64Word = 0;
+ /// Creates a new `Elf64Symtab` instance from the provided symbol
table buffer.
+ ///
+ /// # Arguments
+ ///
+ /// - `syms_buf`: The buffer containing the symbol table data.
+ /// - `entsize`: The size of each symbol entry in bytes.
+ ///
+ /// # Returns
+ ///
+ /// - `Result<Self, ElfError>`: A `Result` containing the
`Elf64Symtab` instance if valid,
+ /// or an `ElfError` if the provided parameters are invalid.
fn new(syms_buf: &'a [u8], entsize: Elf64Xword) -> Result<Self,
ElfError> {
let entsize = usize::try_from(entsize).map_err(|_|
ElfError::InvalidSymbolEntrySize)?;
if entsize < 24 {
@@ -1319,6 +2086,16 @@ impl<'a> Elf64Symtab<'a> {
})
}
+ /// Reads a symbol from the symbol table by its index.
+ ///
+ /// # Arguments
+ ///
+ /// - `i`: The index of the symbol to retrieve.
+ ///
+ /// # Returns
+ ///
+ /// - `Result<Elf64Sym, ElfError>`: A `Result` containing the
`Elf64Sym` if found,
+ /// or an `ElfError` if the index is out of bounds or the symbol is
invalid.
fn read_sym(&self, i: Elf64Word) -> Result<Elf64Sym, ElfError> {
if i > self.syms_num {
return Err(ElfError::InvalidSymbolIndex);
@@ -1330,22 +2107,37 @@ impl<'a> Elf64Symtab<'a> {
}
}
+/// Represents a relocation entry in an ELF64 file (`Elf64Rela`)
#[derive(Debug)]
pub struct Elf64Rela {
+ /// Offset within the section where the relocation should be applied
r_offset: Elf64Addr,
+ /// A combination of symbol index and relocation type information
r_info: Elf64Xword,
+ /// The value to add to the target symbol's value during relocation
r_addend: Elf64Sxword,
}
impl Elf64Rela {
+ /// Extracts the symbol index from the `r_info` field
fn get_sym(&self) -> Elf64Word {
(self.r_info >> 32) as Elf64Word
}
+ /// Extracts the relocation type from the `r_info` field
fn get_type(&self) -> Elf64Word {
(self.r_info & 0xffffffffu64) as Elf64Word
}
+ /// Reads an `Elf64Rela` relocation entry from the provided buffer.
+ ///
+ /// # Arguments
+ ///
+ /// - `rela_buf`: A slice of bytes containing the relocation entry data.
+ ///
+ /// # Returns
+ ///
+ /// - `Elf64Rela`: An `Elf64Rela` instance parsed from the buffer.
fn read(rela_buf: &[u8]) -> Self {
let r_offset =
Elf64Addr::from_le_bytes(rela_buf[0..8].try_into().unwrap());
let r_info =
Elf64Xword::from_le_bytes(rela_buf[8..16].try_into().unwrap());
@@ -1358,13 +2150,28 @@ impl Elf64Rela {
}
}
+/// Represents a collection of relocation entries in an ELF64 file
(`Elf64Relas`)
struct Elf64Relas<'a> {
+ /// The underlying buffer containing the relocation entries
relas_buf: &'a [u8],
+ /// Size of each relocation entry in bytes
entsize: usize,
+ /// Number of relocation entries in the collection
relas_num: usize,
}
impl<'a> Elf64Relas<'a> {
+ /// Creates a new `Elf64Relas` instance from the provided buffer and
entry size.
+ ///
+ /// # Arguments
+ ///
+ /// - `relas_buf`: The buffer containing the relocation entries.
+ /// - `entsize`: The size of each relocation entry in bytes.
+ ///
+ /// # Returns
+ ///
+ /// - `Result<Self, ElfError>`: A `Result` containing the `Elf64Relas`
instance if valid,
+ /// or an `ElfError` if the provided parameters are invalid.
fn new(relas_buf: &'a [u8], entsize: Elf64Xword) -> Result<Self,
ElfError> {
let entsize = usize::try_from(entsize).map_err(|_|
ElfError::InvalidRelocationEntrySize)?;
if entsize < 24 {
@@ -1378,6 +2185,16 @@ impl<'a> Elf64Relas<'a> {
})
}
+ /// Reads a relocation entry from the collection by its index.
+ ///
+ /// # Arguments
+ ///
+ /// - `i`: The index of the relocation entry to retrieve.
+ ///
+ /// # Returns
+ ///
+ /// - `Result<Elf64Rela, ElfError>`: A `Result` containing the
`Elf64Rela` entry if found,
+ /// or an `ElfError` if the index is out of bounds or the entry is
invalid.
fn read_rela(&self, i: usize) -> Result<Elf64Rela, ElfError> {
let rela_off = i * self.entsize;
let rela_buf = &self.relas_buf[rela_off..(rela_off + self.entsize)];
@@ -1385,12 +2202,25 @@ impl<'a> Elf64Relas<'a> {
}
}
+/// Represents an iterator over section headers in an ELF64 file
pub struct Elf64ShdrIterator<'a> {
+ /// The ELF64 file from which section headers are being iterated
elf_file: &'a Elf64File<'a>,
+ /// Next index to be retrieved
next: Elf64Word,
}
impl<'a> Elf64ShdrIterator<'a> {
+ /// Creates a new `Elf64ShdrIterator` instance for iterating section
headers
+ /// in an ELF64 file.
+ ///
+ /// # Arguments
+ ///
+ /// - `elf_file`: The ELF64 file to iterate section headers from.
+ ///
+ /// # Returns
+ ///
+ /// - `Self`: A `Self` instance for iterating section headers.
fn new(elf_file: &'a Elf64File<'a>) -> Self {
Self { elf_file, next: 0 }
}
@@ -1399,6 +2229,12 @@ impl<'a> Elf64ShdrIterator<'a> {
impl<'a> Iterator for Elf64ShdrIterator<'a> {
type Item = Elf64Shdr;
+ /// Retrieves the next section header from the ELF64 file.
+ ///
+ /// # Returns
+ ///
+ /// - `Option<Self::Item>`: An option containing the next `Elf64Shdr`
if available, or `None`
+ /// if all section headers have been iterated.
fn next(&mut self) -> Option<Self::Item> {
let cur = self.next;
if cur == self.elf_file.elf_hdr.e_shnum {
@@ -1409,14 +2245,32 @@ impl<'a> Iterator for Elf64ShdrIterator<'a> {
}
}
+/// Represents a relocation operation
#[derive(Debug)]
pub struct Elf64RelocOp {
+ /// Destination address where the relocation operation should be applied
pub dst: Elf64Addr,
+ /// The value to be written to the destination address
pub value: [u8; 8],
+ /// The length (in bytes) of the value to be written
pub value_len: usize,
}
+/// A trait for processing ELF64 relocations
pub trait Elf64RelocProcessor {
+ /// Applies a relocation operation to produce an `Elf64RelocOp`.
+ ///
+ /// # Arguments
+ ///
+ /// - `rela`: The relocation entry specifying the operation.
+ /// - `load_base`: The base address for loading ELF sections.
+ /// - `sym_value`: The value associated with the symbol being relocated.
+ ///
+ /// # Returns
+ ///
+ /// - `Result<Elf64RelocOp, ElfError>`: A `Result` containing the
+ /// relocation operation (`Elf64RelocOp`) if successful, or an
`ElfError` if
+ /// there was an issue applying the relocation.
fn apply_relocation(
&self,
rela: &Elf64Rela,
@@ -1425,16 +2279,24 @@ pub trait Elf64RelocProcessor {
) -> Result<Elf64RelocOp, ElfError>;
}
+/// Relocation processor specifically for x86_64 ELF files.
pub struct Elf64X86RelocProcessor;
impl Elf64X86RelocProcessor {
+ /// Relocation type value for a 64-bit absolute relocation
const R_X86_64_64: Elf64Word = 1;
+ /// Relocation type value for a PC-relative 32-bit relocation
const R_X86_64_PC32: Elf64Word = 2;
+ /// Relocation type value for a relative relocation
const R_X86_64_RELATIVE: Elf64Word = 8;
+ /// Relocation type value for a 32-bit relocation
const R_X86_64_32: Elf64Word = 10;
+ /// Relocation type value for a signed 32-bit relocation
const R_X86_64_32S: Elf64Word = 11;
+ /// Relocation type value for a PC-relative 64-bit relocation
const R_X86_64_PC64: Elf64Word = 24;
+ /// Creates a new `Elf64X86RelocProcessor` instance
pub fn new() -> Self {
Self
}
@@ -1447,6 +2309,19 @@ impl Default for Elf64X86RelocProcessor {
}
impl Elf64RelocProcessor for Elf64X86RelocProcessor {
+ /// Applies a relocation operation for x86_64 ELF files.
+ ///
+ /// # Arguments
+ ///
+ /// - `rela`: The relocation entry specifying the operation.
+ /// - `load_base`: The base address for loading ELF sections.
+ /// - `sym_value`: The value associated with the symbol being relocated.
+ ///
+ /// # Returns
+ ///
+ /// - `Result<Elf64RelocOp, ElfError>`: A `Result` containing the
relocation
+ /// operation (`Elf64RelocOp`) if successful, or an `ElfError` if
there was an
+ /// issue applying the relocation.
fn apply_relocation(
&self,
rela: &Elf64Rela,
@@ -1498,19 +2373,36 @@ impl Elf64RelocProcessor for Elf64X86RelocProcessor {
}
}
+/// An iterator that applies relocation operations to ELF64 relocations
pub struct Elf64AppliedRelaIterator<'a, RP: Elf64RelocProcessor> {
+ /// The ELF64 relocation processor used for applying relocations
rela_proc: RP,
+ /// Base address for loading ELF sections
load_base: Elf64Xword,
-
+ /// Reference to the ELF64 load segments
load_segments: &'a Elf64LoadSegments,
-
+ /// ELF64 relocation entries
relas: Elf64Relas<'a>,
+ /// Optional symbol table for resolving symbols
symtab: Option<Elf64Symtab<'a>>,
-
+ /// Index of the next relocation entry to process
next: usize,
}
impl<'a, RP: Elf64RelocProcessor> Elf64AppliedRelaIterator<'a, RP> {
+ /// Creates a new `Elf64AppliedRelaIterator` instance.
+ ///
+ /// # Arguments
+ ///
+ /// - `rela_proc`: The ELF64 relocation processor.
+ /// - `load_base`: The base address for loading ELF sections.
+ /// - `load_segments`: Reference to the ELF64 load segments.
+ /// - `relas`: ELF64 relocation entries.
+ /// - `symtab`: Optional symbol table for symbol resolution.
+ ///
+ /// # Returns
+ ///
+ /// - A new `Elf64AppliedRelaIterator` instance.
fn new(
rela_proc: RP,
load_base: Elf64Xword,
@@ -1532,6 +2424,18 @@ impl<'a, RP: Elf64RelocProcessor>
Elf64AppliedRelaIterator<'a, RP> {
impl<'a, RP: Elf64RelocProcessor> Iterator for
Elf64AppliedRelaIterator<'a, RP> {
type Item = Result<Option<Elf64RelocOp>, ElfError>;
+ /// Advances the iterator to the next relocation operation, processes it,
+ /// and returns the result.
+ ///
+ /// If there are no more relocations to process, `None` is returned to
signal
+ /// the end of the iterator.
+ ///
+ /// # Returns
+ ///
+ /// - `Some(Ok(None))`: If the relocation entry indicates no operation
(type == 0).
+ /// - `Some(Ok(Some(reloc_op)))`: If a relocation operation is
successfully applied.
+ /// - `Some(Err(ElfError))`: If an error occurs during relocation
processing.
+ /// - `None`: If there are no more relocation entries to process.
fn next(&mut self) -> Option<Self::Item> {
let cur = self.next;
if cur == self.relas.relas_num {
@@ -1539,15 +2443,18 @@ impl<'a, RP: Elf64RelocProcessor> Iterator for
Elf64AppliedRelaIterator<'a, RP>
}
self.next += 1;
+ // Read the next ELF64 relocation entry
let rela = match self.relas.read_rela(cur) {
Ok(rela) => rela,
Err(e) => return Some(Err(e)),
};
+ // Check if the relocation type is zero, indicating no operation
if rela.get_type() == 0 {
return Some(Ok(None));
}
+ // Resolve the symbol associated with the relocation
let sym_index = rela.get_sym();
let sym_value = if sym_index != Elf64Symtab::STN_UNDEF {
let symtab = match &self.symtab {
@@ -1574,6 +2481,7 @@ impl<'a, RP: Elf64RelocProcessor> Iterator for
Elf64AppliedRelaIterator<'a, RP>
0
};
+ // Apply the relocation and obtain the relocation operation
let reloc_op = match self
.rela_proc
.apply_relocation(&rela, self.load_base, sym_value)
--
2.41.0
More information about the Svsm-devel
mailing list