#![deny(clippy::arithmetic_side_effects)]
use crate::elf::read::consts::{
EM_NONE, SHF_ALLOC, SHF_WRITE, SHN_ABS, SHT_PROGBITS, SHT_STRTAB, SHT_SYMTAB, STB_GLOBAL,
STT_NOTYPE,
};
use crate::elf::read::{ElfFormat, FileHeader, RawRecord, RawSymbol, SectionHeader};
use crate::error::{Error, Result};
#[must_use]
pub fn mangle(path: &[u8], suffix: &str) -> Vec<u8> {
let mut name = Vec::with_capacity(path.len().saturating_add(suffix.len()).saturating_add(9));
name.extend_from_slice(b"_binary_");
name.extend_from_slice(path);
name.push(b'_');
name.extend_from_slice(suffix.as_bytes());
for byte in &mut name {
if !byte.is_ascii_alphanumeric() {
*byte = b'_';
}
}
name
}
fn too_large() -> Error {
Error::Limit("binary input larger than the address space".into())
}
fn u64_of(n: usize) -> Result<u64> {
u64::try_from(n).map_err(|_| too_large())
}
fn pad_to(out: &mut Vec<u8>, align: usize) {
while !out.len().is_multiple_of(align.max(1)) {
out.push(0);
}
}
pub fn convert<F: ElfFormat>(name: &[u8], data: &[u8]) -> Result<Vec<u8>> {
let size = u64_of(data.len())?;
let symbols = [
(mangle(name, "start"), 0u64, 1u16),
(mangle(name, "end"), size, 1),
(mangle(name, "size"), size, SHN_ABS),
];
let mut strtab = vec![0u8];
let mut name_offsets = Vec::with_capacity(symbols.len());
for (symbol, ..) in &symbols {
name_offsets.push(u32::try_from(strtab.len()).map_err(|_| too_large())?);
strtab.extend_from_slice(symbol);
strtab.push(0);
}
let shstrtab_names: [&[u8]; 4] = [b".data", b".symtab", b".strtab", b".shstrtab"];
let mut shstrtab = vec![0u8];
let mut shname = Vec::with_capacity(shstrtab_names.len());
for section in shstrtab_names {
shname.push(u32::try_from(shstrtab.len()).map_err(|_| too_large())?);
shstrtab.extend_from_slice(section);
shstrtab.push(0);
}
let ehdr_size = <F::Ehdr as RawRecord>::SIZE;
let sym_size = <F::Sym as RawRecord>::SIZE;
let align = F::WORD_SIZE;
let mut out = vec![0u8; ehdr_size];
let data_offset = out.len();
out.extend_from_slice(data);
pad_to(&mut out, align);
let symtab_offset = out.len();
out.extend_from_slice(F::encode_sym(&RawSymbol::default()).as_bytes());
for ((_, value, shndx), name_offset) in symbols.iter().zip(&name_offsets) {
let symbol = RawSymbol {
st_name: *name_offset,
st_info: (STB_GLOBAL << 4) | STT_NOTYPE,
st_other: 0,
st_shndx: *shndx,
st_value: *value,
st_size: 0,
};
out.extend_from_slice(F::encode_sym(&symbol).as_bytes());
}
let symtab_size = out.len().checked_sub(symtab_offset).ok_or_else(too_large)?;
let strtab_offset = out.len();
out.extend_from_slice(&strtab);
let shstrtab_offset = out.len();
out.extend_from_slice(&shstrtab);
pad_to(&mut out, align);
let shoff = out.len();
let section_name = |i: usize| shname.get(i).copied().unwrap_or(0);
let headers = [
SectionHeader::default(),
SectionHeader {
sh_name: section_name(0),
sh_type: SHT_PROGBITS,
sh_flags: SHF_ALLOC | SHF_WRITE,
sh_offset: u64_of(data_offset)?,
sh_size: size,
sh_addralign: 1,
..SectionHeader::default()
},
SectionHeader {
sh_name: section_name(1),
sh_type: SHT_SYMTAB,
sh_offset: u64_of(symtab_offset)?,
sh_size: u64_of(symtab_size)?,
sh_link: 3,
sh_info: 1,
sh_addralign: u64_of(align)?,
sh_entsize: u64_of(sym_size)?,
..SectionHeader::default()
},
SectionHeader {
sh_name: section_name(2),
sh_type: SHT_STRTAB,
sh_offset: u64_of(strtab_offset)?,
sh_size: u64_of(strtab.len())?,
sh_addralign: 1,
..SectionHeader::default()
},
SectionHeader {
sh_name: section_name(3),
sh_type: SHT_STRTAB,
sh_offset: u64_of(shstrtab_offset)?,
sh_size: u64_of(shstrtab.len())?,
sh_addralign: 1,
..SectionHeader::default()
},
];
for header in &headers {
out.extend_from_slice(F::encode_shdr(header).as_bytes());
}
let file_header = FileHeader {
ident_version: 1, e_type: 1, e_machine: EM_NONE,
e_version: 1,
e_shoff: u64_of(shoff)?,
e_shnum: u16::try_from(headers.len()).map_err(|_| too_large())?,
e_shstrndx: 4,
..FileHeader::default()
};
let encoded = F::encode_ehdr(&file_header);
out.get_mut(..ehdr_size)
.ok_or_else(too_large)?
.copy_from_slice(encoded.as_bytes());
Ok(out)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::elf::read::{Elf64Le, ObjectFile, Source};
use std::path::Path;
#[test]
fn names_are_mangled_like_gnu() {
assert_eq!(
mangle(b"dir/a-b.bin", "start"),
b"_binary_dir_a_b_bin_start"
);
assert_eq!(mangle(b"x", "size"), b"_binary_x_size");
}
#[test]
fn object_parses_with_three_symbols() {
let bytes = convert::<Elf64Le>(b"blob.bin", b"hello").unwrap();
let object = ObjectFile::<Elf64Le>::parse(&bytes, Source::new(Path::new("blob.bin")))
.expect("valid object");
let names: Vec<Vec<u8>> = object
.symbols()
.globals()
.map(|s| s.unwrap().name.to_vec())
.collect();
assert_eq!(
names,
[
b"_binary_blob_bin_start".to_vec(),
b"_binary_blob_bin_end".to_vec(),
b"_binary_blob_bin_size".to_vec()
]
);
let empty = convert::<Elf64Le>(b"empty", b"").unwrap();
assert!(ObjectFile::<Elf64Le>::parse(&empty, Source::new(Path::new("empty"))).is_ok());
let be = convert::<crate::elf::read::Elf64Be>(b"blob.bin", b"hello").unwrap();
let object =
ObjectFile::<crate::elf::read::Elf64Be>::parse(&be, Source::new(Path::new("blob.bin")))
.expect("valid big-endian object");
assert_eq!(object.symbols().globals().count(), 3);
let small = convert::<crate::elf::read::Elf32Le>(b"blob.bin", b"hello").unwrap();
assert!(
ObjectFile::<crate::elf::read::Elf32Le>::parse(
&small,
Source::new(Path::new("blob.bin"))
)
.is_ok()
);
}
}