use crate::elf::elf_types::{
elf_r_sym, elf_r_sym32, elf_r_type, elf_r_type32, st_bind, st_type, st_visibility, ElfClass,
ElfEndian, ElfMachine, ElfOsAbi, EI_CLASS, EI_DATA, EI_MAG0, EI_MAG1, EI_MAG2, EI_MAG3,
EI_NIDENT, EI_OSABI, EI_VERSION, ELF_MAGIC, EV_CURRENT, SHT_DYNAMIC, SHT_DYNSYM, SHT_NOBITS,
SHT_NOTE, SHT_NULL, SHT_REL, SHT_RELA, SHT_STRTAB, SHT_SYMTAB,
};
#[inline]
pub fn read_u16(data: &[u8], offset: usize, endian: ElfEndian) -> u16 {
let bytes: [u8; 2] = data[offset..offset + 2].try_into().unwrap();
match endian {
ElfEndian::Little => u16::from_le_bytes(bytes),
ElfEndian::Big => u16::from_be_bytes(bytes),
}
}
#[inline]
pub fn read_u32(data: &[u8], offset: usize, endian: ElfEndian) -> u32 {
let bytes: [u8; 4] = data[offset..offset + 4].try_into().unwrap();
match endian {
ElfEndian::Little => u32::from_le_bytes(bytes),
ElfEndian::Big => u32::from_be_bytes(bytes),
}
}
#[inline]
pub fn read_u64(data: &[u8], offset: usize, endian: ElfEndian) -> u64 {
let bytes: [u8; 8] = data[offset..offset + 8].try_into().unwrap();
match endian {
ElfEndian::Little => u64::from_le_bytes(bytes),
ElfEndian::Big => u64::from_be_bytes(bytes),
}
}
#[inline]
pub fn read_i32(data: &[u8], offset: usize, endian: ElfEndian) -> i32 {
let bytes: [u8; 4] = data[offset..offset + 4].try_into().unwrap();
match endian {
ElfEndian::Little => i32::from_le_bytes(bytes),
ElfEndian::Big => i32::from_be_bytes(bytes),
}
}
#[inline]
pub fn read_i64(data: &[u8], offset: usize, endian: ElfEndian) -> i64 {
let bytes: [u8; 8] = data[offset..offset + 8].try_into().unwrap();
match endian {
ElfEndian::Little => i64::from_le_bytes(bytes),
ElfEndian::Big => i64::from_be_bytes(bytes),
}
}
#[derive(Debug, Clone)]
pub struct ElfHeaderInfo {
pub class: ElfClass,
pub endian: ElfEndian,
pub os_abi: ElfOsAbi,
pub abi_version: u8,
pub file_type: u16,
pub machine: ElfMachine,
pub version: u32,
pub entry: u64,
pub program_header_offset: u64,
pub section_header_offset: u64,
pub flags: u32,
pub header_size: u16,
pub program_header_entry_size: u16,
pub program_header_count: u16,
pub section_header_entry_size: u16,
pub section_header_count: u16,
pub section_name_string_table_index: u16,
}
#[derive(Debug, Clone)]
pub struct ElfSectionInfo {
pub name: String,
pub section_type: u32,
pub flags: u64,
pub addr: u64,
pub offset: u64,
pub size: u64,
pub link: u32,
pub info: u32,
pub addralign: u64,
pub entsize: u64,
data: Option<Vec<u8>>,
}
#[derive(Debug, Clone)]
pub struct ElfSymbolInfo {
pub name: String,
pub value: u64,
pub size: u64,
pub binding: u8,
pub sym_type: u8,
pub visibility: u8,
pub section_index: u16,
}
#[derive(Debug, Clone)]
pub struct ElfDynamicInfo {
pub tag: i64,
pub value: u64,
}
#[derive(Debug, Clone)]
pub struct ElfNoteInfo {
pub name: String,
pub note_type: u32,
pub desc: Vec<u8>,
}
pub use crate::elf::elf_writer::ElfPhdrInfo;
#[derive(Debug, Clone, PartialEq)]
pub struct ElfRelocInfo {
pub offset: u64,
pub symbol_index: u32,
pub rel_type: u32,
pub addend: i64,
pub is_rela: bool,
}
struct RawSectionHeader {
sh_name: u32,
section_type: u32,
flags: u64,
addr: u64,
offset: u64,
size: u64,
link: u32,
info: u32,
addralign: u64,
entsize: u64,
}
#[derive(Debug)]
pub struct ElfReader {
pub data: Vec<u8>,
pub header: ElfHeaderInfo,
pub sections: Vec<ElfSectionInfo>,
pub program_headers: Vec<ElfPhdrInfo>,
pub symbols: Vec<ElfSymbolInfo>,
pub dynamic_symbols: Vec<ElfSymbolInfo>,
pub shstrtab: Vec<u8>,
pub strtab: Vec<u8>,
pub dynstr: Vec<u8>,
pub dynamic: Vec<ElfDynamicInfo>,
pub notes: Vec<ElfNoteInfo>,
}
impl ElfReader {
pub fn parse(data: Vec<u8>) -> Result<Self, String> {
if !Self::is_valid_elf(&data) {
return Err("Not a valid ELF file (bad magic)".to_string());
}
let header = Self::parse_header(&data)?;
let raw_sections =
Self::parse_raw_section_headers(&data, &header).map_err(|e| e.to_string())?;
let shstrtab = if !raw_sections.is_empty()
&& (header.section_name_string_table_index as usize) < raw_sections.len()
{
Self::load_raw_section_data(
&data,
&raw_sections[header.section_name_string_table_index as usize],
)
} else {
Vec::new()
};
let sections: Vec<ElfSectionInfo> = raw_sections
.into_iter()
.map(|raw| {
let name = Self::get_string_from_table(raw.sh_name as u64, &shstrtab);
ElfSectionInfo {
name,
section_type: raw.section_type,
flags: raw.flags,
addr: raw.addr,
offset: raw.offset,
size: raw.size,
link: raw.link,
info: raw.info,
addralign: raw.addralign,
entsize: raw.entsize,
data: None,
}
})
.collect();
let program_headers = Self::parse_program_headers_from_data(&data, &header)?;
let (symbols, strtab) = Self::parse_named_symtab(&data, §ions, SHT_SYMTAB, &header);
let (dynamic_symbols, dynstr) =
Self::parse_named_dynsym(&data, §ions, &header, &strtab);
let dynamic = Self::parse_dynamic_from_data(&data, §ions, &header);
let notes = Self::parse_notes_from_data(&data, §ions, &header);
Ok(ElfReader {
data,
header,
sections,
program_headers,
symbols,
dynamic_symbols,
shstrtab,
strtab,
dynstr,
dynamic,
notes,
})
}
pub fn parse_file(path: &str) -> Result<Self, String> {
let data =
std::fs::read(path).map_err(|e| format!("Failed to read file {}: {}", path, e))?;
Self::parse(data)
}
pub fn parse_header(data: &[u8]) -> Result<ElfHeaderInfo, String> {
if data.len() < EI_NIDENT {
return Err("Data too small for ELF identification".to_string());
}
if !Self::is_valid_elf(data) {
return Err("Not a valid ELF file (bad magic)".to_string());
}
let class_byte = data[EI_CLASS];
let class = ElfClass::from_u8(class_byte)
.ok_or_else(|| format!("Unknown ELF class: {:#x}", class_byte))?;
let endian_byte = data[EI_DATA];
let endian = ElfEndian::from_u8(endian_byte)
.ok_or_else(|| format!("Unknown ELF endianness: {:#x}", endian_byte))?;
let os_abi_byte = data[EI_OSABI];
let os_abi = ElfOsAbi::from_u8(os_abi_byte);
let abi_version = data[EI_OSABI + 1];
let header_size = if class.is_64bit() { 64u16 } else { 52u16 };
if data.len() < header_size as usize {
return Err(format!(
"Data too small for ELF header (need {} bytes, have {})",
header_size,
data.len()
));
}
match class {
ElfClass::Elf64 => {
let file_type = read_u16(data, 16, endian);
let machine_raw = read_u16(data, 18, endian);
let machine = ElfMachine::from_u16(machine_raw);
let version = read_u32(data, 20, endian);
let entry = read_u64(data, 24, endian);
let program_header_offset = read_u64(data, 32, endian);
let section_header_offset = read_u64(data, 40, endian);
let flags = read_u32(data, 48, endian);
let header_size_read = read_u16(data, 52, endian);
let program_header_entry_size = read_u16(data, 54, endian);
let program_header_count = read_u16(data, 56, endian);
let section_header_entry_size = read_u16(data, 58, endian);
let section_header_count = read_u16(data, 60, endian);
let section_name_string_table_index = read_u16(data, 62, endian);
Ok(ElfHeaderInfo {
class,
endian,
os_abi,
abi_version,
file_type,
machine,
version,
entry,
program_header_offset,
section_header_offset,
flags,
header_size: header_size_read,
program_header_entry_size,
program_header_count,
section_header_entry_size,
section_header_count,
section_name_string_table_index,
})
}
ElfClass::Elf32 => {
let file_type = read_u16(data, 16, endian);
let machine_raw = read_u16(data, 18, endian);
let machine = ElfMachine::from_u16(machine_raw);
let version = read_u32(data, 20, endian);
let entry = read_u32(data, 24, endian) as u64;
let program_header_offset = read_u32(data, 28, endian) as u64;
let section_header_offset = read_u32(data, 32, endian) as u64;
let flags = read_u32(data, 36, endian);
let header_size_read = read_u16(data, 40, endian);
let program_header_entry_size = read_u16(data, 42, endian);
let program_header_count = read_u16(data, 44, endian);
let section_header_entry_size = read_u16(data, 46, endian);
let section_header_count = read_u16(data, 48, endian);
let section_name_string_table_index = read_u16(data, 50, endian);
Ok(ElfHeaderInfo {
class,
endian,
os_abi,
abi_version,
file_type,
machine,
version,
entry,
program_header_offset,
section_header_offset,
flags,
header_size: header_size_read,
program_header_entry_size,
program_header_count,
section_header_entry_size,
section_header_count,
section_name_string_table_index,
})
}
}
}
pub fn parse_section_headers(data: &[u8]) -> Result<Vec<ElfSectionInfo>, String> {
let header = Self::parse_header(data)?;
let raw = Self::parse_raw_section_headers(data, &header).map_err(|e| e.to_string())?;
Ok(raw
.into_iter()
.map(|r| ElfSectionInfo {
name: String::new(),
section_type: r.section_type,
flags: r.flags,
addr: r.addr,
offset: r.offset,
size: r.size,
link: r.link,
info: r.info,
addralign: r.addralign,
entsize: r.entsize,
data: None,
})
.collect())
}
pub fn parse_program_headers(data: &[u8]) -> Result<Vec<ElfPhdrInfo>, String> {
let header = Self::parse_header(data)?;
Self::parse_program_headers_from_data(data, &header)
}
pub fn parse_symbol_table(&self, section_index: usize) -> Result<Vec<ElfSymbolInfo>, String> {
let strtab = if section_index < self.sections.len()
&& self.sections[section_index].section_type == SHT_DYNSYM
{
&self.dynstr
} else {
&self.strtab
};
Self::parse_symbol_table_internal(&self.data, section_index, strtab, &self.header)
}
pub fn parse_string_table(&self, section_index: usize) -> Result<Vec<u8>, String> {
if section_index >= self.sections.len() {
return Err(format!(
"Section index {} out of range ({} sections)",
section_index,
self.sections.len()
));
}
let section = &self.sections[section_index];
if section.section_type != SHT_STRTAB {
return Err(format!(
"Section {} is not a string table (type {})",
section_index, section.section_type
));
}
Ok(Self::load_section_data_from_raw(&self.data, section))
}
pub fn parse_dynamic(&self) -> Result<Vec<ElfDynamicInfo>, String> {
Ok(Self::parse_dynamic_from_data(
&self.data,
&self.sections,
&self.header,
))
}
pub fn parse_notes(&self) -> Result<Vec<ElfNoteInfo>, String> {
Ok(Self::parse_notes_from_data(
&self.data,
&self.sections,
&self.header,
))
}
pub fn get_relocations(&self, section_index: usize) -> Result<Vec<ElfRelocInfo>, String> {
if section_index >= self.sections.len() {
return Err(format!(
"Section index {} out of range ({} sections)",
section_index,
self.sections.len()
));
}
let section = &self.sections[section_index];
if section.section_type != SHT_REL && section.section_type != SHT_RELA {
return Err(format!(
"Section {} is not a relocation section (type {})",
section_index, section.section_type
));
}
let is_rela = section.section_type == SHT_RELA;
let is_64 = self.header.class.is_64bit();
let endian = self.header.endian;
let entsize = section.entsize as usize;
if entsize == 0 {
return Ok(Vec::new());
}
let sec_data = self.section_data(section_index);
let sec_data = match sec_data {
Some(d) => d,
None => {
return Err(format!(
"Failed to load relocation section data for index {}",
section_index
))
}
};
let num_entries = sec_data.len() / entsize;
let mut relocs = Vec::with_capacity(num_entries);
for i in 0..num_entries {
let entry_offset = i * entsize;
if is_64 {
if is_rela {
let r_offset = read_u64(sec_data, entry_offset, endian);
let r_info = read_u64(sec_data, entry_offset + 8, endian);
let r_addend = read_i64(sec_data, entry_offset + 16, endian);
relocs.push(ElfRelocInfo {
offset: r_offset,
symbol_index: elf_r_sym(r_info),
rel_type: elf_r_type(r_info),
addend: r_addend,
is_rela: true,
});
} else {
let r_offset = read_u64(sec_data, entry_offset, endian);
let r_info = read_u64(sec_data, entry_offset + 8, endian);
relocs.push(ElfRelocInfo {
offset: r_offset,
symbol_index: elf_r_sym(r_info),
rel_type: elf_r_type(r_info),
addend: 0,
is_rela: false,
});
}
} else {
if is_rela {
let r_offset = read_u32(sec_data, entry_offset, endian) as u64;
let r_info = read_u32(sec_data, entry_offset + 4, endian);
let r_addend = read_i32(sec_data, entry_offset + 8, endian) as i64;
relocs.push(ElfRelocInfo {
offset: r_offset,
symbol_index: elf_r_sym32(r_info),
rel_type: elf_r_type32(r_info) as u32,
addend: r_addend,
is_rela: true,
});
} else {
let r_offset = read_u32(sec_data, entry_offset, endian) as u64;
let r_info = read_u32(sec_data, entry_offset + 4, endian);
relocs.push(ElfRelocInfo {
offset: r_offset,
symbol_index: elf_r_sym32(r_info),
rel_type: elf_r_type32(r_info) as u32,
addend: 0,
is_rela: false,
});
}
}
}
Ok(relocs)
}
pub fn section_data(&self, index: usize) -> Option<&[u8]> {
if index >= self.sections.len() {
return None;
}
let section = &self.sections[index];
if section.section_type == SHT_NULL || section.section_type == SHT_NOBITS {
return None;
}
let offset = section.offset as usize;
let size = section.size as usize;
if offset + size > self.data.len() {
return None;
}
let data_ptr = self.data.as_ptr();
unsafe {
let slice = std::slice::from_raw_parts(data_ptr.add(offset), size);
Some(slice)
}
}
pub fn section_by_name(&self, name: &str) -> Option<&ElfSectionInfo> {
self.sections.iter().find(|s| s.name == name)
}
pub fn section_by_type(&self, section_type: u32) -> Vec<&ElfSectionInfo> {
self.sections
.iter()
.filter(|s| s.section_type == section_type)
.collect()
}
pub fn symbol_by_name(&self, name: &str) -> Option<&ElfSymbolInfo> {
self.symbols
.iter()
.find(|s| s.name == name)
.or_else(|| self.dynamic_symbols.iter().find(|s| s.name == name))
}
pub fn symbol_at_address(&self, addr: u64) -> Option<&ElfSymbolInfo> {
let mut best: Option<&ElfSymbolInfo> = None;
for sym in self.symbols.iter().chain(self.dynamic_symbols.iter()) {
if sym.value <= addr && sym.size > 0 {
match best {
None => best = Some(sym),
Some(b) => {
if sym.value > b.value {
best = Some(sym);
}
}
}
}
}
best
}
pub fn get_string(&self, strtab_offset: u64, strtab: &[u8]) -> String {
Self::get_string_from_table(strtab_offset, strtab)
}
pub fn is_valid_elf(data: &[u8]) -> bool {
data.len() >= 4
&& data[EI_MAG0] == ELF_MAGIC[0]
&& data[EI_MAG1] == ELF_MAGIC[1]
&& data[EI_MAG2] == ELF_MAGIC[2]
&& data[EI_MAG3] == ELF_MAGIC[3]
}
pub fn validate(&self) -> Result<(), Vec<String>> {
let mut errors = Vec::new();
if !Self::is_valid_elf(&self.data) {
errors.push("Invalid ELF magic bytes".to_string());
}
let expected_ehsize = if self.header.class.is_64bit() {
64u16
} else {
52u16
};
if self.header.header_size != expected_ehsize {
errors.push(format!(
"Header size mismatch: expected {} for class, got {}",
expected_ehsize, self.header.header_size
));
}
if self.header.version != EV_CURRENT {
errors.push(format!(
"Unsupported ELF version: {} (expected {})",
self.header.version, EV_CURRENT
));
}
let expected_shentsize = if self.header.class.is_64bit() {
64u16
} else {
40u16
};
if self.header.section_header_count > 0
&& self.header.section_header_entry_size != expected_shentsize
{
errors.push(format!(
"Section header entry size mismatch: expected {}, got {}",
expected_shentsize, self.header.section_header_entry_size
));
}
if self.header.section_header_count > 0
&& self.header.section_name_string_table_index as usize >= self.sections.len()
{
errors.push(format!(
"Section name string table index {} out of range ({} sections)",
self.header.section_name_string_table_index,
self.sections.len()
));
}
for (i, section) in self.sections.iter().enumerate() {
if section.section_type != SHT_NULL && section.section_type != SHT_NOBITS {
let off = section.offset as usize;
let size = section.size as usize;
if off + size > self.data.len() {
errors.push(format!(
"Section {} ({}) extends past end of file: offset={:#x} size={:#x}",
i, section.name, off, size
));
}
}
}
for (i, phdr) in self.program_headers.iter().enumerate() {
let off = phdr.offset as usize;
let size = phdr.filesz as usize;
if off + size > self.data.len() {
errors.push(format!(
"Program header {} extends past end of file: offset={:#x} filesz={:#x}",
i, off, size
));
}
}
if errors.is_empty() {
Ok(())
} else {
Err(errors)
}
}
fn parse_raw_section_headers(
data: &[u8],
header: &ElfHeaderInfo,
) -> Result<Vec<RawSectionHeader>, &'static str> {
let shoff = header.section_header_offset as usize;
let shnum = header.section_header_count as usize;
let shentsize = header.section_header_entry_size as usize;
let is_64 = header.class.is_64bit();
let endian = header.endian;
if shnum == 0 || shoff == 0 {
return Ok(Vec::new());
}
let mut raw_sections = Vec::with_capacity(shnum);
for i in 0..shnum {
let shdr_offset = shoff + i * shentsize;
if shdr_offset + shentsize > data.len() {
return Err("Section header table extends past end of file");
}
let raw = if is_64 {
RawSectionHeader {
sh_name: read_u32(data, shdr_offset, endian),
section_type: read_u32(data, shdr_offset + 4, endian),
flags: read_u64(data, shdr_offset + 8, endian),
addr: read_u64(data, shdr_offset + 16, endian),
offset: read_u64(data, shdr_offset + 24, endian),
size: read_u64(data, shdr_offset + 32, endian),
link: read_u32(data, shdr_offset + 40, endian),
info: read_u32(data, shdr_offset + 44, endian),
addralign: read_u64(data, shdr_offset + 48, endian),
entsize: read_u64(data, shdr_offset + 56, endian),
}
} else {
RawSectionHeader {
sh_name: read_u32(data, shdr_offset, endian),
section_type: read_u32(data, shdr_offset + 4, endian),
flags: read_u32(data, shdr_offset + 8, endian) as u64,
addr: read_u32(data, shdr_offset + 12, endian) as u64,
offset: read_u32(data, shdr_offset + 16, endian) as u64,
size: read_u32(data, shdr_offset + 20, endian) as u64,
link: read_u32(data, shdr_offset + 24, endian),
info: read_u32(data, shdr_offset + 28, endian),
addralign: read_u32(data, shdr_offset + 32, endian) as u64,
entsize: read_u32(data, shdr_offset + 36, endian) as u64,
}
};
raw_sections.push(raw);
}
Ok(raw_sections)
}
fn parse_program_headers_from_data(
data: &[u8],
header: &ElfHeaderInfo,
) -> Result<Vec<ElfPhdrInfo>, String> {
let phnum = header.program_header_count as usize;
if phnum == 0 || header.program_header_offset == 0 {
return Ok(Vec::new());
}
let is_64 = header.class.is_64bit();
let endian = header.endian;
let phoff = header.program_header_offset as usize;
let phentsize = header.program_header_entry_size as usize;
let mut phdrs = Vec::with_capacity(phnum);
for i in 0..phnum {
let offset = phoff + i * phentsize;
if offset + phentsize > data.len() {
return Err(format!("Program header {} extends past end of file", i));
}
let phdr = if is_64 {
ElfPhdrInfo {
segment_type: read_u32(data, offset, endian),
flags: read_u32(data, offset + 4, endian),
offset: read_u64(data, offset + 8, endian),
vaddr: read_u64(data, offset + 16, endian),
paddr: read_u64(data, offset + 24, endian),
filesz: read_u64(data, offset + 32, endian),
memsz: read_u64(data, offset + 40, endian),
align: read_u64(data, offset + 48, endian),
}
} else {
ElfPhdrInfo {
segment_type: read_u32(data, offset, endian),
flags: read_u32(data, offset + 24, endian),
offset: read_u32(data, offset + 4, endian) as u64,
vaddr: read_u32(data, offset + 8, endian) as u64,
paddr: read_u32(data, offset + 12, endian) as u64,
filesz: read_u32(data, offset + 16, endian) as u64,
memsz: read_u32(data, offset + 20, endian) as u64,
align: read_u32(data, offset + 28, endian) as u64,
}
};
phdrs.push(phdr);
}
Ok(phdrs)
}
fn parse_named_symtab(
data: &[u8],
sections: &[ElfSectionInfo],
symtab_type: u32,
header: &ElfHeaderInfo,
) -> (Vec<ElfSymbolInfo>, Vec<u8>) {
let symtab_section = sections.iter().find(|s| s.section_type == symtab_type);
let symtab_section = match symtab_section {
Some(s) => s,
None => return (Vec::new(), Vec::new()),
};
let symtab_index = sections
.iter()
.position(|s| std::ptr::eq(s, symtab_section))
.unwrap_or(0);
let strtab_index = symtab_section.link as usize;
let strtab =
if strtab_index < sections.len() && sections[strtab_index].section_type == SHT_STRTAB {
Self::load_section_data_from_raw(data, §ions[strtab_index])
} else {
Vec::new()
};
let symbols = Self::parse_symbol_table_from_section(data, symtab_index, &strtab, header);
(symbols, strtab)
}
fn parse_named_dynsym(
data: &[u8],
sections: &[ElfSectionInfo],
header: &ElfHeaderInfo,
_strtab: &[u8],
) -> (Vec<ElfSymbolInfo>, Vec<u8>) {
let dynsym_section = sections.iter().find(|s| s.section_type == SHT_DYNSYM);
let dynsym_section = match dynsym_section {
Some(s) => s,
None => return (Vec::new(), Vec::new()),
};
let dynstr_index = dynsym_section.link as usize;
let dynstr_data =
if dynstr_index < sections.len() && sections[dynstr_index].section_type == SHT_STRTAB {
Self::load_section_data_from_raw(data, §ions[dynstr_index])
} else {
Vec::new()
};
let dynsym_index = sections
.iter()
.position(|s| std::ptr::eq(s, dynsym_section))
.unwrap_or(0);
let symbols =
Self::parse_symbol_table_from_section(data, dynsym_index, &dynstr_data, header);
(symbols, dynstr_data)
}
fn parse_symbol_table_from_section(
data: &[u8],
section_index: usize,
strtab: &[u8],
header: &ElfHeaderInfo,
) -> Vec<ElfSymbolInfo> {
Self::parse_symbol_table_internal(data, section_index, strtab, header).unwrap_or_default()
}
fn parse_symbol_table_internal(
data: &[u8],
section_index: usize,
strtab: &[u8],
header: &ElfHeaderInfo,
) -> Result<Vec<ElfSymbolInfo>, String> {
let raw_sections = Self::parse_raw_section_headers(data, header)
.map_err(|e| format!("Failed to parse section headers: {}", e))?;
if section_index >= raw_sections.len() {
return Err(format!(
"Section index {} out of range ({} sections)",
section_index,
raw_sections.len()
));
}
let section = &raw_sections[section_index];
if section.section_type != SHT_SYMTAB && section.section_type != SHT_DYNSYM {
return Err(format!(
"Section {} is not a symbol table (type {})",
section_index, section.section_type
));
}
let is_64 = header.class.is_64bit();
let endian = header.endian;
let entsize = section.entsize as usize;
if entsize == 0 {
return Ok(Vec::new());
}
let sec_offset = section.offset as usize;
let sec_size = section.size as usize;
if sec_offset + sec_size > data.len() {
return Err("Symbol table section extends past end of file".to_string());
}
let num_symbols = sec_size / entsize;
let mut symbols = Vec::with_capacity(num_symbols);
for i in 0..num_symbols {
let sym_offset = sec_offset + i * entsize;
let sym = if is_64 {
let st_name = read_u32(data, sym_offset, endian);
let st_info = data[sym_offset + 4];
let st_other = data[sym_offset + 5];
let st_shndx = read_u16(data, sym_offset + 6, endian);
let st_value = read_u64(data, sym_offset + 8, endian);
let st_size = read_u64(data, sym_offset + 16, endian);
let name = Self::get_string_from_table(st_name as u64, strtab);
ElfSymbolInfo {
name,
value: st_value,
size: st_size,
binding: st_bind(st_info),
sym_type: st_type(st_info),
visibility: st_visibility(st_other),
section_index: st_shndx,
}
} else {
let st_name = read_u32(data, sym_offset, endian);
let st_value = read_u32(data, sym_offset + 4, endian) as u64;
let st_size = read_u32(data, sym_offset + 8, endian) as u64;
let st_info = data[sym_offset + 12];
let st_other = data[sym_offset + 13];
let st_shndx = read_u16(data, sym_offset + 14, endian);
let name = Self::get_string_from_table(st_name as u64, strtab);
ElfSymbolInfo {
name,
value: st_value,
size: st_size,
binding: st_bind(st_info),
sym_type: st_type(st_info),
visibility: st_visibility(st_other),
section_index: st_shndx,
}
};
symbols.push(sym);
}
Ok(symbols)
}
fn parse_dynamic_from_data(
data: &[u8],
sections: &[ElfSectionInfo],
header: &ElfHeaderInfo,
) -> Vec<ElfDynamicInfo> {
let dynamic_section = match sections.iter().find(|s| s.section_type == SHT_DYNAMIC) {
Some(s) => s,
None => return Vec::new(),
};
let is_64 = header.class.is_64bit();
let endian = header.endian;
let sec_offset = dynamic_section.offset as usize;
let sec_size = dynamic_section.size as usize;
if sec_offset + sec_size > data.len() {
return Vec::new();
}
let entry_size = if is_64 {
16usize
} else {
8usize
};
if entry_size == 0 {
return Vec::new();
}
let num_entries = sec_size / entry_size;
let mut dynamic = Vec::with_capacity(num_entries);
for i in 0..num_entries {
let offset = sec_offset + i * entry_size;
let (tag, value) = if is_64 {
(
read_i64(data, offset, endian),
read_u64(data, offset + 8, endian),
)
} else {
(
read_i32(data, offset, endian) as i64,
read_u32(data, offset + 4, endian) as u64,
)
};
dynamic.push(ElfDynamicInfo { tag, value });
}
dynamic
}
fn parse_notes_from_data(
data: &[u8],
sections: &[ElfSectionInfo],
header: &ElfHeaderInfo,
) -> Vec<ElfNoteInfo> {
let mut notes = Vec::new();
for section in sections {
if section.section_type == SHT_NOTE && section.size > 0 {
let offset = section.offset as usize;
let size = section.size as usize;
if offset + size <= data.len() {
let sec_notes =
Self::parse_note_data(&data[offset..offset + size], header.endian);
notes.extend(sec_notes);
}
}
}
if header.program_header_count > 0 && header.program_header_offset > 0 {
if let Ok(phdrs) = Self::parse_program_headers_from_data(data, header) {
for phdr in &phdrs {
if phdr.segment_type == 4 {
let offset = phdr.offset as usize;
let size = phdr.filesz as usize;
if offset + size <= data.len() {
let seg_notes =
Self::parse_note_data(&data[offset..offset + size], header.endian);
notes.extend(seg_notes);
}
}
}
}
}
notes
}
fn parse_note_data(data: &[u8], endian: ElfEndian) -> Vec<ElfNoteInfo> {
let mut notes = Vec::new();
let mut offset = 0usize;
while offset + 12 <= data.len() {
let namesz = read_u32(data, offset, endian) as usize;
let descsz = read_u32(data, offset + 4, endian) as usize;
let note_type = read_u32(data, offset + 8, endian);
if namesz == 0 && descsz == 0 && note_type == 0 {
break;
}
let name_offset = offset + 12;
if name_offset + namesz > data.len() {
break;
}
let name = if namesz > 0 {
let name_bytes = &data[name_offset..name_offset + namesz];
let nul_pos = name_bytes.iter().position(|&b| b == 0).unwrap_or(namesz);
String::from_utf8_lossy(&name_bytes[..nul_pos]).into_owned()
} else {
String::new()
};
let name_end = name_offset + namesz;
let name_padding = (4 - (namesz % 4)) % 4;
let desc_offset = name_end + name_padding;
if desc_offset + descsz > data.len() {
break;
}
let desc = data[desc_offset..desc_offset + descsz].to_vec();
notes.push(ElfNoteInfo {
name,
note_type,
desc,
});
let desc_padding = (4 - (descsz % 4)) % 4;
offset = desc_offset + descsz + desc_padding;
}
notes
}
fn get_string_from_table(offset: u64, strtab: &[u8]) -> String {
let start = offset as usize;
if start >= strtab.len() {
return String::new();
}
let remaining = &strtab[start..];
let nul_pos = remaining
.iter()
.position(|&b| b == 0)
.unwrap_or(remaining.len());
String::from_utf8_lossy(&remaining[..nul_pos]).into_owned()
}
fn load_section_data_from_raw(data: &[u8], section: &ElfSectionInfo) -> Vec<u8> {
if section.section_type == SHT_NULL || section.section_type == SHT_NOBITS {
return Vec::new();
}
let offset = section.offset as usize;
let size = section.size as usize;
if offset + size > data.len() {
return Vec::new();
}
data[offset..offset + size].to_vec()
}
fn load_raw_section_data(data: &[u8], raw: &RawSectionHeader) -> Vec<u8> {
if raw.section_type == SHT_NULL || raw.section_type == SHT_NOBITS {
return Vec::new();
}
let offset = raw.offset as usize;
let size = raw.size as usize;
if offset + size > data.len() {
return Vec::new();
}
data[offset..offset + size].to_vec()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::elf::elf_symbols::ElfSymbolTable;
use crate::elf::elf_types::{
ElfClass, ElfEndian, ElfMachine, ElfOsAbi, ET_DYN, ET_EXEC, ET_REL, PF_R, PF_W, PF_X,
PT_LOAD, SHT_NOBITS, SHT_PROGBITS, SHT_STRTAB, SHT_SYMTAB,
};
use crate::elf::elf_writer::{ElfNote, ElfPhdrInfo, ElfRelocation, ElfWriter};
fn read_u64_le(data: &[u8], offset: usize) -> u64 {
read_u64(data, offset, ElfEndian::Little)
}
#[test]
fn test_is_valid_elf_good() {
let data = vec![0x7f, b'E', b'L', b'F', 0, 0, 0, 0];
assert!(ElfReader::is_valid_elf(&data));
}
#[test]
fn test_is_valid_elf_bad_magic() {
let data = vec![0x7f, b'X', b'X', b'X', 0, 0, 0, 0];
assert!(!ElfReader::is_valid_elf(&data));
}
#[test]
fn test_is_valid_elf_too_short() {
let data = vec![0x7f, b'E', b'L'];
assert!(!ElfReader::is_valid_elf(&data));
}
#[test]
fn test_is_valid_elf_empty() {
let data: Vec<u8> = vec![];
assert!(!ElfReader::is_valid_elf(&data));
}
#[test]
fn test_parse_valid_elf64_object() {
let mut writer = ElfWriter::new_elf64_rel(ElfMachine::X86_64);
writer.add_section(".text", vec![0x90; 16], SHT_PROGBITS, 0x6, 16);
let symtab = ElfSymbolTable::new();
writer.add_symbol_table(symtab);
let data = writer.write();
let reader = ElfReader::parse(data).expect("Failed to parse ELF64 object");
assert_eq!(reader.header.class, ElfClass::Elf64);
assert_eq!(reader.header.endian, ElfEndian::Little);
assert_eq!(reader.header.file_type, ET_REL);
assert_eq!(reader.header.machine, ElfMachine::X86_64);
assert!(reader.sections.len() >= 4);
let text = reader.section_by_name(".text").expect(".text not found");
assert_eq!(text.section_type, SHT_PROGBITS);
assert_eq!(text.size, 16);
let text_idx = reader
.sections
.iter()
.position(|s| s.name == ".text")
.unwrap();
let text_data = reader.section_data(text_idx);
assert!(text_data.is_some());
assert_eq!(text_data.unwrap(), &[0x90u8; 16]);
}
#[test]
fn test_parse_valid_elf32_object() {
let mut writer = ElfWriter::new_elf32_rel(ElfMachine::X86);
writer.add_section(".text", vec![0xcc; 8], SHT_PROGBITS, 0x6, 4);
let data = writer.write();
let reader = ElfReader::parse(data).expect("Failed to parse ELF32 object");
assert_eq!(reader.header.class, ElfClass::Elf32);
assert_eq!(reader.header.endian, ElfEndian::Little);
assert_eq!(reader.header.file_type, ET_REL);
assert_eq!(reader.header.machine, ElfMachine::X86);
assert_eq!(reader.header.header_size, 52);
let text = reader
.section_by_name(".text")
.expect(".text section not found");
assert_eq!(text.section_type, SHT_PROGBITS);
assert_eq!(text.size, 8);
let text_data = reader.section_data(
reader
.sections
.iter()
.position(|s| s.name == ".text")
.unwrap(),
);
assert!(text_data.is_some());
assert_eq!(text_data.unwrap(), &[0xccu8; 8]);
}
#[test]
fn test_parse_header_machine_x86_64() {
let mut writer = ElfWriter::new_elf64_rel(ElfMachine::X86_64);
let data = writer.write();
let reader = ElfReader::parse(data).unwrap();
assert_eq!(reader.header.machine, ElfMachine::X86_64);
}
#[test]
fn test_parse_header_machine_aarch64() {
let mut writer = ElfWriter::new_elf64_rel(ElfMachine::AArch64);
let data = writer.write();
let reader = ElfReader::parse(data).unwrap();
assert_eq!(reader.header.machine, ElfMachine::AArch64);
}
#[test]
fn test_parse_header_machine_riscv() {
let mut writer = ElfWriter::new_elf64_rel(ElfMachine::RiscV);
let data = writer.write();
let reader = ElfReader::parse(data).unwrap();
assert_eq!(reader.header.machine, ElfMachine::RiscV);
}
#[test]
fn test_parse_header_machine_arm() {
let mut writer = ElfWriter::new_elf32_rel(ElfMachine::ARM);
let data = writer.write();
let reader = ElfReader::parse(data).unwrap();
assert_eq!(reader.header.machine, ElfMachine::ARM);
}
#[test]
fn test_parse_header_type_exec() {
let mut writer = ElfWriter::new_elf64_exec(ElfMachine::X86_64, 0);
let data = writer.write();
let reader = ElfReader::parse(data).unwrap();
assert_eq!(reader.header.file_type, ET_EXEC);
}
#[test]
fn test_parse_header_type_shared() {
let mut writer = ElfWriter::new_elf64_shared(ElfMachine::X86_64);
let data = writer.write();
let reader = ElfReader::parse(data).unwrap();
assert_eq!(reader.header.file_type, ET_DYN);
}
#[test]
fn test_parse_header_endian_little() {
let mut writer = ElfWriter::new_elf64_rel(ElfMachine::X86_64);
let data = writer.write();
let reader = ElfReader::parse(data).unwrap();
assert_eq!(reader.header.endian, ElfEndian::Little);
}
#[test]
fn test_parse_header_version() {
let mut writer = ElfWriter::new_elf64_rel(ElfMachine::X86_64);
let data = writer.write();
let reader = ElfReader::parse(data).unwrap();
assert_eq!(reader.header.version, 1);
}
#[test]
fn test_parse_header_entry_point() {
let mut writer = ElfWriter::new_elf64_exec(ElfMachine::X86_64, 0x400000);
let data = writer.write();
let reader = ElfReader::parse(data).unwrap();
assert_eq!(reader.header.entry, 0x400000);
}
#[test]
fn test_parse_header_os_abi_linux() {
let mut writer = ElfWriter::new(
ElfClass::Elf64,
ElfEndian::Little,
ElfOsAbi::Linux,
ElfMachine::X86_64,
ET_REL,
);
let data = writer.write();
let reader = ElfReader::parse(data).unwrap();
assert!(matches!(reader.header.os_abi, ElfOsAbi::Linux));
}
#[test]
fn test_parse_header_os_abi_freebsd() {
let mut writer = ElfWriter::new(
ElfClass::Elf64,
ElfEndian::Little,
ElfOsAbi::FreeBSD,
ElfMachine::X86_64,
ET_REL,
);
let data = writer.write();
let reader = ElfReader::parse(data).unwrap();
assert!(matches!(reader.header.os_abi, ElfOsAbi::FreeBSD));
}
#[test]
fn test_parse_header_os_abi_systemv() {
let mut writer = ElfWriter::new_elf64_rel(ElfMachine::X86_64);
let data = writer.write();
let reader = ElfReader::parse(data).unwrap();
assert!(matches!(reader.header.os_abi, ElfOsAbi::SystemV));
}
#[test]
fn test_parse_section_headers_names() {
let mut writer = ElfWriter::new_elf64_rel(ElfMachine::X86_64);
writer.add_section(".text", vec![0x90; 8], SHT_PROGBITS, 0x6, 16);
writer.add_section(".data", vec![0x00; 4], SHT_PROGBITS, 0x3, 8);
writer.add_section(".rodata", vec![0x01; 16], SHT_PROGBITS, 0x2, 4);
let symtab = ElfSymbolTable::new();
writer.add_symbol_table(symtab);
let data = writer.write();
let reader = ElfReader::parse(data).unwrap();
assert_eq!(
reader.section_by_name(".text").unwrap().section_type,
SHT_PROGBITS
);
assert!(reader.section_by_name(".shstrtab").is_some());
assert!(reader.section_by_name(".strtab").is_some());
assert!(reader.section_by_name(".symtab").is_some());
}
#[test]
fn test_parse_section_headers_null_first() {
let mut writer = ElfWriter::new_elf64_rel(ElfMachine::X86_64);
let data = writer.write();
let reader = ElfReader::parse(data).unwrap();
let null_sec = &reader.sections[0];
assert_eq!(null_sec.section_type, SHT_NULL);
assert_eq!(null_sec.name, "");
}
#[test]
fn test_parse_section_flags() {
let mut writer = ElfWriter::new_elf64_rel(ElfMachine::X86_64);
writer.add_section(
".text",
vec![0x90; 4],
SHT_PROGBITS,
0x6, 16,
);
let data = writer.write();
let reader = ElfReader::parse(data).unwrap();
let text = reader.section_by_name(".text").unwrap();
assert_eq!(text.flags, 0x6);
}
#[test]
fn test_parse_file_symbols() {
let mut writer = ElfWriter::new_elf64_rel(ElfMachine::X86_64);
writer.add_section(".text", vec![0x90; 4], SHT_PROGBITS, 0x6, 16);
let symtab = ElfSymbolTable::new();
writer.add_symbol_table(symtab);
let data = writer.write();
let reader = ElfReader::parse(data).unwrap();
assert!(!reader.symbols.is_empty());
}
#[test]
fn test_parse_symbol_tables_with_null_entry() {
let mut writer = ElfWriter::new_elf64_rel(ElfMachine::X86_64);
writer.add_section(".text", vec![0x90; 4], SHT_PROGBITS, 0x6, 16);
let symtab = ElfSymbolTable::new();
writer.add_symbol_table(symtab);
let data = writer.write();
let reader = ElfReader::parse(data).unwrap();
assert!(!reader.symbols.is_empty());
let null_sym = &reader.symbols[0];
assert_eq!(null_sym.name, "");
assert_eq!(null_sym.value, 0);
assert_eq!(null_sym.size, 0);
assert_eq!(null_sym.binding, 0); assert_eq!(null_sym.sym_type, 0); }
#[test]
fn test_parse_string_tables() {
let mut writer = ElfWriter::new_elf64_rel(ElfMachine::X86_64);
writer.add_section(".text", vec![0x90; 4], SHT_PROGBITS, 0x6, 16);
let symtab = ElfSymbolTable::new();
writer.add_symbol_table(symtab);
let data = writer.write();
let reader = ElfReader::parse(data).unwrap();
let shstrtab = &reader.shstrtab;
assert!(!shstrtab.is_empty());
let shstrtab_str = String::from_utf8_lossy(shstrtab);
assert!(shstrtab_str.contains(".text"));
assert!(shstrtab_str.contains(".shstrtab"));
assert!(shstrtab_str.contains(".strtab"));
assert!(shstrtab_str.contains(".symtab"));
}
#[test]
fn test_parse_string_table_method() {
let mut writer = ElfWriter::new_elf64_rel(ElfMachine::X86_64);
let symtab = ElfSymbolTable::new();
writer.add_symbol_table(symtab);
let data = writer.write();
let reader = ElfReader::parse(data).unwrap();
let strtab_idx = reader
.sections
.iter()
.position(|s| s.name == ".strtab")
.expect(".strtab not found");
let strtab = reader.parse_string_table(strtab_idx).unwrap();
assert!(!strtab.is_empty());
}
#[test]
fn test_parse_rela_relocations_elf64() {
let mut writer = ElfWriter::new_elf64_rel(ElfMachine::X86_64);
let text_idx = writer.add_section(".text", vec![0; 16], SHT_PROGBITS, 0x6, 16);
writer.add_relocation(text_idx, ElfRelocation::rela(0, 1, 1, -4));
let data = writer.write();
let reader = ElfReader::parse(data).expect("Failed to parse ELF with relocations");
let rela_section = reader
.sections
.iter()
.find(|s| s.name == ".rela.text" && s.section_type == SHT_RELA)
.expect(".rela.text section not found");
let rela_idx = reader
.sections
.iter()
.position(|s| std::ptr::eq(s, rela_section))
.unwrap();
let relocs = reader
.get_relocations(rela_idx)
.expect("Failed to parse relocations");
assert_eq!(relocs.len(), 1);
assert_eq!(relocs[0].offset, 0);
assert_eq!(relocs[0].symbol_index, 1);
assert_eq!(relocs[0].rel_type, 1);
assert_eq!(relocs[0].addend, -4);
assert!(relocs[0].is_rela);
}
#[test]
fn test_parse_multiple_rela_relocations() {
let mut writer = ElfWriter::new_elf64_rel(ElfMachine::X86_64);
let text_idx = writer.add_section(".text", vec![0; 32], SHT_PROGBITS, 0x6, 16);
writer.add_relocation(text_idx, ElfRelocation::rela(0, 1, 1, 0));
writer.add_relocation(text_idx, ElfRelocation::rela(8, 2, 2, -4));
writer.add_relocation(text_idx, ElfRelocation::rela(16, 3, 1, 42));
let data = writer.write();
let reader = ElfReader::parse(data).unwrap();
let rela_section = reader
.sections
.iter()
.find(|s| s.name == ".rela.text")
.unwrap();
let rela_idx = reader
.sections
.iter()
.position(|s| std::ptr::eq(s, rela_section))
.unwrap();
let relocs = reader.get_relocations(rela_idx).unwrap();
assert_eq!(relocs.len(), 3);
assert_eq!(relocs[0].offset, 0);
assert_eq!(relocs[0].symbol_index, 1);
assert_eq!(relocs[0].rel_type, 1);
assert_eq!(relocs[0].addend, 0);
assert_eq!(relocs[1].offset, 8);
assert_eq!(relocs[1].symbol_index, 2);
assert_eq!(relocs[1].rel_type, 2);
assert_eq!(relocs[1].addend, -4);
assert_eq!(relocs[2].offset, 16);
assert_eq!(relocs[2].symbol_index, 3);
assert_eq!(relocs[2].rel_type, 1);
assert_eq!(relocs[2].addend, 42);
}
#[test]
fn test_parse_rela_relocations_elf32() {
let mut writer = ElfWriter::new_elf32_rel(ElfMachine::X86);
let text_idx = writer.add_section(".text", vec![0; 8], SHT_PROGBITS, 0x6, 4);
writer.add_relocation(text_idx, ElfRelocation::rela(0, 1, 1, 100));
let data = writer.write();
let reader = ElfReader::parse(data).unwrap();
let rela_section = reader
.sections
.iter()
.find(|s| s.name == ".rela.text")
.unwrap();
let rela_idx = reader
.sections
.iter()
.position(|s| std::ptr::eq(s, rela_section))
.unwrap();
let relocs = reader.get_relocations(rela_idx).unwrap();
assert_eq!(relocs.len(), 1);
assert_eq!(relocs[0].offset, 0);
assert_eq!(relocs[0].symbol_index, 1);
assert_eq!(relocs[0].addend, 100);
assert!(relocs[0].is_rela);
}
#[test]
fn test_parse_program_headers() {
let mut writer = ElfWriter::new_elf64_exec(ElfMachine::X86_64, 0);
writer.add_section(".text", vec![0x90; 16], SHT_PROGBITS, 0x6, 16);
writer.add_program_header(ElfPhdrInfo::load(PF_R | PF_X, 0, 0x400000, 16, 16, 0x1000));
let data = writer.write();
let reader = ElfReader::parse(data).unwrap();
assert_eq!(reader.program_headers.len(), 1);
let phdr = &reader.program_headers[0];
assert_eq!(phdr.segment_type, PT_LOAD);
assert_eq!(phdr.flags, PF_R | PF_X);
}
#[test]
fn test_parse_multiple_program_headers() {
let mut writer = ElfWriter::new_elf64_exec(ElfMachine::X86_64, 0);
writer.add_section(".text", vec![0x90; 16], SHT_PROGBITS, 0x5, 16);
writer.add_section(".data", vec![0; 8], SHT_PROGBITS, 0x3, 8);
writer.add_program_header(ElfPhdrInfo::load(
PF_R | PF_X,
0x1000,
0x401000,
16,
16,
0x1000,
));
writer.add_program_header(ElfPhdrInfo::load(
PF_R | PF_W,
0x2000,
0x402000,
8,
8,
0x1000,
));
let data = writer.write();
let reader = ElfReader::parse(data).unwrap();
assert_eq!(reader.program_headers.len(), 2);
assert_eq!(reader.program_headers[0].flags, PF_R | PF_X);
assert_eq!(reader.program_headers[1].flags, PF_R | PF_W);
}
#[test]
fn test_parse_dynamic_entries_elf64() {
let mut writer = ElfWriter::new_elf64_shared(ElfMachine::X86_64);
let data = writer.write();
let reader = ElfReader::parse(data);
assert!(reader.is_ok());
let reader = reader.unwrap();
let _ = reader.dynamic;
}
#[test]
fn test_parse_notes() {
let mut writer = ElfWriter::new_elf64_rel(ElfMachine::X86_64);
writer.add_section(".text", vec![0x90; 4], SHT_PROGBITS, 0x6, 16);
writer.add_note(ElfNote::new("GNU", 3, vec![0x01, 0x02, 0x03, 0x04]));
let data = writer.write();
let reader = ElfReader::parse(data).unwrap();
assert!(!reader.notes.is_empty());
let note = &reader.notes[0];
assert_eq!(note.name, "GNU");
assert_eq!(note.note_type, 3);
assert_eq!(note.desc, vec![0x01, 0x02, 0x03, 0x04]);
}
#[test]
fn test_parse_multiple_notes() {
let mut writer = ElfWriter::new_elf64_rel(ElfMachine::X86_64);
writer.add_section(".text", vec![0x90; 4], SHT_PROGBITS, 0x6, 16);
writer.add_note(ElfNote::new("GNU", 1, vec![0x00]));
writer.add_note(ElfNote::new("test", 2, vec![0xAA, 0xBB]));
let data = writer.write();
let reader = ElfReader::parse(data).unwrap();
assert_eq!(reader.notes.len(), 2);
assert_eq!(reader.notes[0].name, "GNU");
assert_eq!(reader.notes[0].note_type, 1);
assert_eq!(reader.notes[0].desc, vec![0x00]);
assert_eq!(reader.notes[1].name, "test");
assert_eq!(reader.notes[1].note_type, 2);
assert_eq!(reader.notes[1].desc, vec![0xAA, 0xBB]);
}
#[test]
fn test_parse_note_with_empty_desc() {
let mut writer = ElfWriter::new_elf64_rel(ElfMachine::X86_64);
writer.add_section(".text", vec![0x90; 4], SHT_PROGBITS, 0x6, 16);
writer.add_note(ElfNote::new("empty", 5, vec![]));
let data = writer.write();
let reader = ElfReader::parse(data).unwrap();
assert!(!reader.notes.is_empty());
let note = &reader.notes[0];
assert_eq!(note.name, "empty");
assert_eq!(note.note_type, 5);
assert!(note.desc.is_empty());
}
#[test]
fn test_read_section_data() {
let mut writer = ElfWriter::new_elf64_rel(ElfMachine::X86_64);
writer.add_section(".text", vec![0xde, 0xad, 0xbe, 0xef], SHT_PROGBITS, 0x6, 16);
writer.add_section(".data", vec![0x01, 0x02, 0x03], SHT_PROGBITS, 0x3, 8);
let data = writer.write();
let reader = ElfReader::parse(data).unwrap();
let text_idx = reader
.sections
.iter()
.position(|s| s.name == ".text")
.unwrap();
let text_data = reader.section_data(text_idx).unwrap();
assert_eq!(text_data, &[0xde, 0xad, 0xbe, 0xef]);
let data_idx = reader
.sections
.iter()
.position(|s| s.name == ".data")
.unwrap();
let data_sec_data = reader.section_data(data_idx).unwrap();
assert_eq!(data_sec_data, &[0x01, 0x02, 0x03]);
}
#[test]
fn test_read_section_data_no_bits() {
let mut writer = ElfWriter::new_elf64_rel(ElfMachine::X86_64);
writer.add_section(".bss", vec![], SHT_NOBITS, 0x3, 16);
let data = writer.write();
let reader = ElfReader::parse(data).unwrap();
let bss_idx = reader
.sections
.iter()
.position(|s| s.name == ".bss")
.unwrap();
assert!(reader.section_data(bss_idx).is_none());
}
#[test]
fn test_section_by_name_found() {
let mut writer = ElfWriter::new_elf64_rel(ElfMachine::X86_64);
writer.add_section(".text", vec![0x90; 4], SHT_PROGBITS, 0x6, 16);
let data = writer.write();
let reader = ElfReader::parse(data).unwrap();
assert!(reader.section_by_name(".text").is_some());
}
#[test]
fn test_section_by_name_not_found() {
let mut writer = ElfWriter::new_elf64_rel(ElfMachine::X86_64);
let data = writer.write();
let reader = ElfReader::parse(data).unwrap();
assert!(reader.section_by_name(".nonexistent").is_none());
}
#[test]
fn test_section_by_type() {
let mut writer = ElfWriter::new_elf64_rel(ElfMachine::X86_64);
writer.add_section(".text", vec![0x90; 4], SHT_PROGBITS, 0x6, 16);
writer.add_section(".rodata", vec![0x01; 8], SHT_PROGBITS, 0x2, 4);
let data = writer.write();
let reader = ElfReader::parse(data).unwrap();
let progbits = reader.section_by_type(SHT_PROGBITS);
assert_eq!(progbits.len(), 2);
let strtabs = reader.section_by_type(SHT_STRTAB);
assert!(!strtabs.is_empty());
let not_found = reader.section_by_type(0xFFFF);
assert!(not_found.is_empty());
}
#[test]
fn test_parse_truncated_header() {
let data = vec![0x7f, b'E', b'L', b'F', 2, 1, 1, 0]; let result = ElfReader::parse(data);
assert!(result.is_err());
}
#[test]
fn test_parse_truncated_mid_header() {
let mut data = vec![0u8; 40];
data[0] = 0x7f;
data[1] = b'E';
data[2] = b'L';
data[3] = b'F';
data[EI_CLASS] = 2; data[EI_DATA] = 1; data[EI_VERSION] = 1;
let result = ElfReader::parse(data);
assert!(result.is_err());
}
#[test]
fn test_parse_truncated_section_headers() {
let mut writer = ElfWriter::new_elf64_rel(ElfMachine::X86_64);
writer.add_section(".text", vec![0x90; 16], SHT_PROGBITS, 0x6, 16);
let mut data = writer.write();
let shoff = read_u64_le(&data, 0x28) as usize;
data.truncate(shoff);
let result = ElfReader::parse(data);
assert!(result.is_err());
}
#[test]
fn test_parse_bad_magic() {
let data = vec![0x00u8; 64];
let result = ElfReader::parse(data);
assert!(result.is_err());
}
#[test]
fn test_parse_invalid_class() {
let mut data = vec![0u8; 64];
data[0] = 0x7f;
data[1] = b'E';
data[2] = b'L';
data[3] = b'F';
data[EI_CLASS] = 0xFF; data[EI_DATA] = 1;
let result = ElfReader::parse(data);
assert!(result.is_err());
}
#[test]
fn test_parse_invalid_endian() {
let mut data = vec![0u8; 64];
data[0] = 0x7f;
data[1] = b'E';
data[2] = b'L';
data[3] = b'F';
data[EI_CLASS] = 2;
data[EI_DATA] = 0xFF;
let result = ElfReader::parse(data);
assert!(result.is_err());
}
#[test]
fn test_roundtrip_elf64_basic() {
let mut writer = ElfWriter::new_elf64_rel(ElfMachine::X86_64);
writer.add_section(".text", vec![0x90; 16], SHT_PROGBITS, 0x6, 16);
let symtab = ElfSymbolTable::new();
writer.add_symbol_table(symtab);
let data = writer.write();
let reader = ElfReader::parse(data).expect("Roundtrip parse failed");
assert_eq!(reader.header.class, ElfClass::Elf64);
assert_eq!(reader.header.endian, ElfEndian::Little);
assert_eq!(reader.header.machine, ElfMachine::X86_64);
assert_eq!(reader.header.file_type, ET_REL);
assert_eq!(reader.header.version, EV_CURRENT);
assert_eq!(reader.header.header_size, 64);
assert!(reader.section_by_name(".text").is_some());
assert!(reader.section_by_name(".shstrtab").is_some());
assert!(reader.section_by_name(".strtab").is_some());
assert!(reader.section_by_name(".symtab").is_some());
assert_eq!(reader.sections[0].section_type, SHT_NULL);
}
#[test]
fn test_roundtrip_elf64_with_data() {
let original_data = vec![0xde, 0xad, 0xbe, 0xef, 0xca, 0xfe, 0xba, 0xbe];
let mut writer = ElfWriter::new_elf64_rel(ElfMachine::X86_64);
writer.add_section(".rodata", original_data.clone(), SHT_PROGBITS, 0x2, 8);
let data = writer.write();
let reader = ElfReader::parse(data).unwrap();
let rodata = reader.section_by_name(".rodata").unwrap();
assert_eq!(rodata.size, 8);
let rodata_idx = reader
.sections
.iter()
.position(|s| s.name == ".rodata")
.unwrap();
let rodata_data = reader.section_data(rodata_idx).unwrap();
assert_eq!(rodata_data, original_data.as_slice());
}
#[test]
fn test_roundtrip_elf64_with_relocations() {
let mut writer = ElfWriter::new_elf64_rel(ElfMachine::X86_64);
let text_idx = writer.add_section(".text", vec![0; 32], SHT_PROGBITS, 0x6, 16);
writer.add_relocation(text_idx, ElfRelocation::rela(0, 1, 1, 42));
writer.add_relocation(text_idx, ElfRelocation::rela(8, 2, 2, -8));
let data = writer.write();
let reader = ElfReader::parse(data).unwrap();
let rela_section = reader
.sections
.iter()
.find(|s| s.section_type == SHT_RELA)
.unwrap();
let rela_idx = reader
.sections
.iter()
.position(|s| std::ptr::eq(s, rela_section))
.unwrap();
let relocs = reader.get_relocations(rela_idx).unwrap();
assert_eq!(relocs.len(), 2);
assert_eq!(relocs[0].offset, 0);
assert_eq!(relocs[0].symbol_index, 1);
assert_eq!(relocs[0].rel_type, 1);
assert_eq!(relocs[0].addend, 42);
assert_eq!(relocs[1].offset, 8);
assert_eq!(relocs[1].symbol_index, 2);
assert_eq!(relocs[1].rel_type, 2);
assert_eq!(relocs[1].addend, -8);
}
#[test]
fn test_roundtrip_elf64_with_notes() {
let mut writer = ElfWriter::new_elf64_rel(ElfMachine::X86_64);
writer.add_section(".text", vec![0x90; 8], SHT_PROGBITS, 0x6, 16);
writer.add_note(ElfNote::new("GNU", 3, vec![0x11, 0x22, 0x33]));
let data = writer.write();
let reader = ElfReader::parse(data).unwrap();
assert_eq!(reader.notes.len(), 1);
assert_eq!(reader.notes[0].name, "GNU");
assert_eq!(reader.notes[0].note_type, 3);
assert_eq!(reader.notes[0].desc, vec![0x11, 0x22, 0x33]);
}
#[test]
fn test_roundtrip_elf64_with_program_headers() {
let mut writer = ElfWriter::new_elf64_exec(ElfMachine::X86_64, 0x400000);
writer.add_section(".text", vec![0x90; 64], SHT_PROGBITS, 0x5, 16);
writer.add_program_header(ElfPhdrInfo::load(PF_R | PF_X, 0, 0x400000, 64, 64, 0x1000));
let data = writer.write();
let reader = ElfReader::parse(data).unwrap();
assert_eq!(reader.header.entry, 0x400000);
assert_eq!(reader.header.file_type, ET_EXEC);
assert_eq!(reader.program_headers.len(), 1);
assert_eq!(reader.program_headers[0].segment_type, PT_LOAD);
assert_eq!(reader.program_headers[0].flags, PF_R | PF_X);
assert_eq!(reader.program_headers[0].vaddr, 0x400000);
}
#[test]
fn test_parse_big_endian_elf64() {
let mut writer = ElfWriter::new(
ElfClass::Elf64,
ElfEndian::Big,
ElfOsAbi::SystemV,
ElfMachine::X86_64,
ET_REL,
);
writer.add_section(".text", vec![0x90; 8], SHT_PROGBITS, 0x6, 16);
let data = writer.write();
let reader = ElfReader::parse(data).expect("Failed to parse big-endian ELF64");
assert_eq!(reader.header.endian, ElfEndian::Big);
assert_eq!(reader.header.class, ElfClass::Elf64);
assert_eq!(reader.header.machine, ElfMachine::X86_64);
assert_eq!(reader.header.header_size, 64);
let text = reader.section_by_name(".text").unwrap();
assert_eq!(text.size, 8);
}
#[test]
fn test_parse_big_endian_elf32() {
let mut writer = ElfWriter::new(
ElfClass::Elf32,
ElfEndian::Big,
ElfOsAbi::SystemV,
ElfMachine::X86,
ET_REL,
);
writer.add_section(".text", vec![0xcc; 4], SHT_PROGBITS, 0x6, 4);
let data = writer.write();
let reader = ElfReader::parse(data).expect("Failed to parse big-endian ELF32");
assert_eq!(reader.header.endian, ElfEndian::Big);
assert_eq!(reader.header.class, ElfClass::Elf32);
assert_eq!(reader.header.machine, ElfMachine::X86);
assert_eq!(reader.header.header_size, 52);
let text = reader.section_by_name(".text").unwrap();
assert_eq!(text.size, 4);
}
#[test]
fn test_roundtrip_big_endian_elf64() {
let mut writer = ElfWriter::new(
ElfClass::Elf64,
ElfEndian::Big,
ElfOsAbi::SystemV,
ElfMachine::X86_64,
ET_REL,
);
writer.add_section(".text", vec![0x01, 0x02, 0x03, 0x04], SHT_PROGBITS, 0x6, 16);
let data = writer.write();
let reader = ElfReader::parse(data).unwrap();
assert_eq!(reader.header.endian, ElfEndian::Big);
assert_eq!(reader.header.class, ElfClass::Elf64);
let text = reader.section_by_name(".text").unwrap();
let text_idx = reader
.sections
.iter()
.position(|s| s.name == ".text")
.unwrap();
let text_data = reader.section_data(text_idx).unwrap();
assert_eq!(text_data, &[0x01, 0x02, 0x03, 0x04]);
}
#[test]
fn test_parse_file() {
let mut writer = ElfWriter::new_elf64_rel(ElfMachine::X86_64);
writer.add_section(".text", vec![0x90; 4], SHT_PROGBITS, 0x6, 16);
let data = writer.write();
let path = "/tmp/test_elf_reader_parse_file.o";
std::fs::write(path, &data).expect("Failed to write temp file");
let reader = ElfReader::parse_file(path).expect("Failed to parse file");
assert_eq!(reader.header.machine, ElfMachine::X86_64);
assert!(reader.section_by_name(".text").is_some());
std::fs::remove_file(path).ok();
}
#[test]
fn test_parse_file_not_found() {
let result = ElfReader::parse_file("/tmp/nonexistent_elf_file_12345.o");
assert!(result.is_err());
}
#[test]
fn test_validate_valid_elf() {
let mut writer = ElfWriter::new_elf64_rel(ElfMachine::X86_64);
writer.add_section(".text", vec![0x90; 16], SHT_PROGBITS, 0x6, 16);
let data = writer.write();
let reader = ElfReader::parse(data).unwrap();
let result = reader.validate();
assert!(result.is_ok(), "Validation failed: {:?}", result);
}
#[test]
fn test_validate_elf32() {
let mut writer = ElfWriter::new_elf32_rel(ElfMachine::X86);
writer.add_section(".text", vec![0xcc; 8], SHT_PROGBITS, 0x6, 4);
let data = writer.write();
let reader = ElfReader::parse(data).unwrap();
assert!(reader.validate().is_ok());
}
#[test]
fn test_parse_header_standalone() {
let mut writer = ElfWriter::new_elf64_rel(ElfMachine::X86_64);
let data = writer.write();
let header = ElfReader::parse_header(&data).unwrap();
assert_eq!(header.class, ElfClass::Elf64);
assert_eq!(header.endian, ElfEndian::Little);
assert_eq!(header.machine, ElfMachine::X86_64);
assert_eq!(header.file_type, ET_REL);
}
#[test]
fn test_parse_section_headers_standalone() {
let mut writer = ElfWriter::new_elf64_rel(ElfMachine::X86_64);
writer.add_section(".text", vec![0x90; 8], SHT_PROGBITS, 0x6, 16);
let data = writer.write();
let sections = ElfReader::parse_section_headers(&data).unwrap();
assert!(!sections.is_empty());
assert_eq!(sections[0].section_type, SHT_NULL);
}
#[test]
fn test_parse_program_headers_standalone() {
let mut writer = ElfWriter::new_elf64_exec(ElfMachine::X86_64, 0);
writer.add_section(".text", vec![0x90; 16], SHT_PROGBITS, 0x5, 16);
writer.add_program_header(ElfPhdrInfo::load(PF_R | PF_X, 0, 0x400000, 16, 16, 0x1000));
let data = writer.write();
let phdrs = ElfReader::parse_program_headers(&data).unwrap();
assert_eq!(phdrs.len(), 1);
assert_eq!(phdrs[0].segment_type, PT_LOAD);
}
#[test]
fn test_read_helper_u16_le() {
let data = [0x34, 0x12];
assert_eq!(read_u16(&data, 0, ElfEndian::Little), 0x1234);
}
#[test]
fn test_read_helper_u16_be() {
let data = [0x12, 0x34];
assert_eq!(read_u16(&data, 0, ElfEndian::Big), 0x1234);
}
#[test]
fn test_read_helper_u32_le() {
let data = [0x78, 0x56, 0x34, 0x12];
assert_eq!(read_u32(&data, 0, ElfEndian::Little), 0x12345678);
}
#[test]
fn test_read_helper_u32_be() {
let data = [0x12, 0x34, 0x56, 0x78];
assert_eq!(read_u32(&data, 0, ElfEndian::Big), 0x12345678);
}
#[test]
fn test_read_helper_u64_le() {
let data = [0xef, 0xcd, 0xab, 0x89, 0x67, 0x45, 0x23, 0x01];
assert_eq!(read_u64(&data, 0, ElfEndian::Little), 0x0123456789abcdef);
}
#[test]
fn test_read_helper_u64_be() {
let data = [0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef];
assert_eq!(read_u64(&data, 0, ElfEndian::Big), 0x0123456789abcdef);
}
#[test]
fn test_read_helper_i32_le() {
let data = [0xff, 0xff, 0xff, 0xff]; assert_eq!(read_i32(&data, 0, ElfEndian::Little), -1);
}
#[test]
fn test_read_helper_i64_le() {
let data = [0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]; assert_eq!(read_i64(&data, 0, ElfEndian::Little), -1);
}
#[test]
fn test_symbol_by_name_not_found() {
let mut writer = ElfWriter::new_elf64_rel(ElfMachine::X86_64);
let data = writer.write();
let reader = ElfReader::parse(data).unwrap();
assert!(reader.symbol_by_name("nonexistent_symbol").is_none());
}
#[test]
fn test_symbol_at_address_empty() {
let mut writer = ElfWriter::new_elf64_rel(ElfMachine::X86_64);
let data = writer.write();
let reader = ElfReader::parse(data).unwrap();
assert!(reader.symbol_at_address(0x1000).is_none());
}
#[test]
fn test_get_string() {
let strtab = b"hello\0world\0\0extra";
let reader = ElfReader::parse(vec![
0x7f, b'E', b'L', b'F', 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
])
.unwrap();
assert_eq!(reader.get_string(0, strtab), "hello");
assert_eq!(reader.get_string(6, strtab), "world");
assert_eq!(reader.get_string(12, strtab), "");
}
#[test]
fn test_get_relocations_invalid_section_index() {
let mut writer = ElfWriter::new_elf64_rel(ElfMachine::X86_64);
let data = writer.write();
let reader = ElfReader::parse(data).unwrap();
let result = reader.get_relocations(9999);
assert!(result.is_err());
}
#[test]
fn test_get_relocations_non_reloc_section() {
let mut writer = ElfWriter::new_elf64_rel(ElfMachine::X86_64);
writer.add_section(".text", vec![0x90; 4], SHT_PROGBITS, 0x6, 16);
let data = writer.write();
let reader = ElfReader::parse(data).unwrap();
let text_idx = reader
.sections
.iter()
.position(|s| s.name == ".text")
.unwrap();
let result = reader.get_relocations(text_idx);
assert!(result.is_err());
}
#[test]
fn test_section_data_invalid_index() {
let mut writer = ElfWriter::new_elf64_rel(ElfMachine::X86_64);
let data = writer.write();
let reader = ElfReader::parse(data).unwrap();
assert!(reader.section_data(9999).is_none());
}
}