extern crate elfkit;
extern crate num_traits;
use byteorder::{ByteOrder, LittleEndian, ReadBytesExt};
use ebpf;
use elf::num_traits::FromPrimitive;
use std::collections::HashMap;
use std::io::Cursor;
use std::mem;
use std::str;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum ELFError {
#[error("Failed to parse ELF file: {0:?}")]
FailedToParse(elfkit::Error),
#[error("Entrypoint out of bounds")]
EntrypointOutOfBounds,
#[error("Invaid entrypoint")]
InvalidEntrypoint,
#[error("Failed to get section {0}")]
FailedToGetSection(String),
#[error("Unresolved symbol ({0}) at instruction #{1:?} (ELF file offset {2:#x})")]
UnresolvedSymbol(String, usize, usize),
#[error("Section not found: {0}")]
SectionNotFound(String),
#[error("Relative jump out of bounds at instruction #{0}")]
RelativeJumpOutOfBounds(usize),
#[error("Relocation hash collision while encoding instruction #{0}")]
RelocationHashCollision(usize),
#[error("Incompatible ELF: wrong endianess")]
WrongEndianess,
#[error("Incompatible ELF: wrong ABI")]
WrongABI,
#[error("Incompatible ELF: wrong machine")]
WrongMachine,
#[error("Incompatible ELF: wrong class")]
WrongClass,
#[error("Multiple text sections, consider removing llc option: -function-sections")]
MultipleTextSections,
#[error(".bss section not supported")]
BSSNotSupported,
#[error("Relocation failed, no loadable section contains virtual address {0:#x}")]
AddressOutsideLoadableSection(u64),
#[error("Relocation failed, invalid referenced virtual address {0:#x}")]
InvalidVirtualAddress(u64),
#[error("Relocation failed, unknown type {0:?}")]
UnknownRelocation(u32),
#[error("Failed to read relocation info")]
FailedToReadRelocationInfo,
}
const BYTE_OFFSET_IMMEDIATE: usize = 4;
const BYTE_LENGTH_IMMEIDATE: usize = 4;
const TEXT_SECTION_INDEX: usize = 0;
#[allow(non_camel_case_types)]
#[derive(Debug, PartialEq, Copy, Clone)]
pub enum BPFRelocationType {
R_BPF_NONE = 0,
R_BPF_64_RELATIVE = 8,
R_BPF_64_32 = 10,
}
struct SectionInfo<'a> {
va: u64,
len: u64,
bytes: &'a mut Vec<u8>,
}
impl BPFRelocationType {
fn from_x86_relocation_type(
from: &elfkit::relocation::RelocationType,
) -> Option<BPFRelocationType> {
match *from {
elfkit::relocation::RelocationType::R_X86_64_NONE => {
Some(BPFRelocationType::R_BPF_NONE)
}
elfkit::relocation::RelocationType::R_X86_64_RELATIVE => {
Some(BPFRelocationType::R_BPF_64_RELATIVE)
}
elfkit::relocation::RelocationType::R_X86_64_32 => Some(BPFRelocationType::R_BPF_64_32),
_ => None,
}
}
}
pub struct EBpfElf {
elf: elfkit::Elf,
calls: HashMap<u32, usize>,
}
impl EBpfElf {
pub fn load(elf_bytes: &[u8]) -> Result<EBpfElf, ELFError> {
let mut reader = Cursor::new(elf_bytes);
let mut elf = match elfkit::Elf::from_reader(&mut reader) {
Ok(elf) => elf,
Err(e) => {
return Err(ELFError::FailedToParse(e));
}
};
if let Err(e) = elf.load_all(&mut reader) {
return Err(ELFError::FailedToParse(e));
}
let mut ebpf_elf = EBpfElf {
elf,
calls: HashMap::new(),
};
ebpf_elf.validate()?;
ebpf_elf.relocate()?;
Ok(ebpf_elf)
}
pub fn get_text_bytes(&self) -> Result<(u64, &[u8]), ELFError> {
Ok(EBpfElf::content_to_bytes(self.get_section(".text")?)?)
}
pub fn get_ro_sections(&self) -> Result<Vec<(u64, &[u8])>, ELFError> {
self.elf
.sections
.iter()
.filter(|section| {
section.name == b".rodata"
|| section.name == b".data.rel.ro"
|| section.name == b".eh_frame"
})
.map(EBpfElf::content_to_bytes)
.collect()
}
pub fn get_entrypoint_instruction_offset(&self) -> Result<usize, ELFError> {
let entry = self.elf.header.entry;
let text = self.get_section(".text")?;
if entry < text.header.addr || entry > text.header.addr + text.header.size {
return Err(ELFError::EntrypointOutOfBounds);
}
let offset = (entry - text.header.addr) as usize;
if offset % ebpf::INSN_SIZE != 0 {
return Err(ELFError::InvalidEntrypoint);
}
Ok(offset / ebpf::INSN_SIZE)
}
pub fn lookup_bpf_call(&self, hash: u32) -> Option<&usize> {
self.calls.get(&hash)
}
pub fn report_unresolved_symbol(&self, insn_offset: usize) -> Result<(), ELFError> {
let file_offset =
insn_offset * ebpf::INSN_SIZE + self.get_section(".text")?.header.addr as usize;
let symbols = match self.get_section(".dynsym")?.content {
elfkit::SectionContent::Symbols(ref bytes) => bytes,
_ => {
return Err(ELFError::FailedToGetSection(".dynsym".to_string()));
}
};
let raw_relocation_bytes = match self.get_section(".rel.dyn")?.content {
elfkit::SectionContent::Raw(ref bytes) => bytes,
_ => return Err(ELFError::FailedToGetSection(".rel.dyn".to_string())),
};
let relocations = EBpfElf::get_relocations(&raw_relocation_bytes[..])?;
let mut name = "Unknown";
for relocation in relocations.iter() {
match BPFRelocationType::from_x86_relocation_type(&relocation.rtype) {
Some(BPFRelocationType::R_BPF_64_32) => {
if relocation.addr as usize == file_offset {
name = match str::from_utf8(&symbols[relocation.sym as usize].name) {
Ok(string) => string,
Err(_) => "Malformed symbol name",
};
}
}
_ => (),
}
}
Err(ELFError::UnresolvedSymbol(
name.to_string(),
file_offset / ebpf::INSN_SIZE + ebpf::ELF_INSN_DUMP_OFFSET,
file_offset,
))
}
fn get_section(&self, name: &str) -> Result<&elfkit::Section, ELFError> {
match self
.elf
.sections
.iter()
.find(|section| section.name == name.as_bytes())
{
Some(section) => Ok(section),
None => Err(ELFError::SectionNotFound(name.to_string())),
}
}
fn content_to_bytes(section: &elfkit::section::Section) -> Result<(u64, &[u8]), ELFError> {
match section.content {
elfkit::SectionContent::Raw(ref bytes) => {
Ok((ebpf::MM_PROGRAM_START + section.header.addr, bytes))
}
_ => Err(ELFError::FailedToGetSection(
std::str::from_utf8(§ion.name)
.unwrap_or("Invalid UTF8 section name")
.to_string(),
)),
}
}
fn fixup_relative_calls(
calls: &mut HashMap<u32, usize>,
prog: &mut Vec<u8>,
) -> Result<(), ELFError> {
for i in 0..prog.len() / ebpf::INSN_SIZE {
let mut insn = ebpf::get_insn(prog, i);
if insn.opc == 0x85 && insn.imm != -1 {
let insn_idx = (i as i32 + 1 + insn.imm) as isize;
if insn_idx < 0 || insn_idx as usize >= prog.len() / ebpf::INSN_SIZE {
return Err(ELFError::RelativeJumpOutOfBounds(
i + ebpf::ELF_INSN_DUMP_OFFSET,
));
}
let mut key = [0u8; mem::size_of::<i64>()];
LittleEndian::write_u64(&mut key, i as u64);
let hash = ebpf::hash_symbol_name(&key);
if calls.insert(hash, insn_idx as usize).is_some() {
return Err(ELFError::RelocationHashCollision(
i + ebpf::ELF_INSN_DUMP_OFFSET,
));
}
insn.imm = hash as i32;
prog.splice(
i * ebpf::INSN_SIZE..(i * ebpf::INSN_SIZE) + ebpf::INSN_SIZE,
insn.to_vec(),
);
}
}
Ok(())
}
fn validate(&self) -> Result<(), ELFError> {
if self.elf.header.ident_class != elfkit::types::Class::Class64 {
return Err(ELFError::WrongClass);
}
if self.elf.header.ident_endianness != elfkit::types::Endianness::LittleEndian {
return Err(ELFError::WrongEndianess);
}
if self.elf.header.ident_abi != elfkit::types::Abi::SYSV {
return Err(ELFError::WrongABI);
}
if self.elf.header.machine != elfkit::types::Machine::BPF {
return Err(ELFError::WrongMachine);
}
if self.elf.header.etype != elfkit::types::ElfType::DYN {
return Err(ELFError::WrongClass);
}
let text_sections: Vec<_> = self
.elf
.sections
.iter()
.filter(|section| section.name.starts_with(b".text"))
.collect();
if text_sections.len() > 1 {
return Err(ELFError::MultipleTextSections);
}
if self
.elf
.sections
.iter()
.any(|section| section.name.starts_with(b".bss"))
{
return Err(ELFError::BSSNotSupported);
}
Ok(())
}
fn split_sections(sections: &mut [elfkit::Section]) -> Vec<&mut elfkit::Section> {
let mut section_refs = Vec::new();
if !sections.is_empty() {
let (s, rest) = sections.split_at_mut(1);
section_refs.push(&mut s[0]);
section_refs.append(&mut EBpfElf::split_sections(rest));
}
section_refs
}
fn get_section_ref<'a, 'b>(
sections: &'b mut Vec<&'a mut elfkit::Section>,
name: &str,
) -> Result<&'a mut elfkit::Section, ELFError> {
match sections
.iter()
.enumerate()
.find(|section| section.1.name == name.as_bytes())
{
Some((index, _)) => Ok(sections.remove(index)),
None => Err(ELFError::SectionNotFound(name.to_string())),
}
}
fn get_load_sections<'a, 'b>(
sections: &'a mut Vec<&'b mut elfkit::Section>,
) -> Result<Vec<SectionInfo<'b>>, ELFError> {
let mut section_infos = Vec::new();
let mut section = EBpfElf::get_section_ref(sections, ".text")?;
match (&mut section.content).as_raw_mut() {
Some(bytes) => {
section_infos.push(SectionInfo {
va: section.header.addr,
len: section.header.size,
bytes: bytes,
});
}
None => {
return Err(ELFError::FailedToGetSection(".text".to_string()));
}
};
let mut section = EBpfElf::get_section_ref(sections, ".rodata");
if let Ok(section) = section {
match (&mut section.content).as_raw_mut() {
Some(bytes) => {
section_infos.push(SectionInfo {
va: section.header.addr,
len: section.header.size,
bytes: bytes,
});
}
None => (),
};
}
let mut section = EBpfElf::get_section_ref(sections, ".data.rel.ro");
if let Ok(section) = section {
match (&mut section.content).as_raw_mut() {
Some(bytes) => {
section_infos.push(SectionInfo {
va: section.header.addr,
len: section.header.size,
bytes: bytes,
});
}
None => (),
};
}
let mut section = EBpfElf::get_section_ref(sections, ".eh_frame");
if let Ok(section) = section {
match (&mut section.content).as_raw_mut() {
Some(bytes) => {
section_infos.push(SectionInfo {
va: section.header.addr,
len: section.header.size,
bytes: bytes,
});
}
None => (),
};
}
Ok(section_infos)
}
fn relocate(&mut self) -> Result<(), ELFError> {
let mut sections = EBpfElf::split_sections(&mut self.elf.sections);
let mut section_infos = EBpfElf::get_load_sections(&mut sections)?;
EBpfElf::fixup_relative_calls(&mut self.calls, &mut section_infos[0].bytes)?;
let relocations = match EBpfElf::get_section_ref(&mut sections, ".rel.dyn") {
Ok(rel_dyn_section) => match (&mut rel_dyn_section.content).as_raw_mut() {
Some(bytes) => Some(EBpfElf::get_relocations(&bytes[..])?),
_ => None,
},
Err(_) => None,
};
if let Some(relocations) = relocations {
let dynsym_section = EBpfElf::get_section_ref(&mut sections, ".dynsym")?;
let symbols = match (&dynsym_section.content).as_symbols() {
Some(bytes) => bytes,
None => return Err(ELFError::FailedToGetSection(".dynsym".to_string())),
};
for relocation in relocations.iter() {
match BPFRelocationType::from_x86_relocation_type(&relocation.rtype) {
Some(BPFRelocationType::R_BPF_64_RELATIVE) => {
let mut target_section = None;
for (i, info) in section_infos.iter().enumerate() {
if info.va <= relocation.addr && relocation.addr < info.va + info.len {
target_section = Some(i);
break;
}
}
let target_section = match target_section {
Some(i) => i,
None => {
return Err(ELFError::AddressOutsideLoadableSection(
relocation.addr,
))
}
};
let target_offset =
(relocation.addr - section_infos[target_section].va) as usize;
let mut imm_offset = target_offset + BYTE_OFFSET_IMMEDIATE;
let refd_va = LittleEndian::read_u32(
§ion_infos[target_section].bytes
[imm_offset..imm_offset + BYTE_LENGTH_IMMEIDATE],
) as u64;
if refd_va == 0 {
return Err(ELFError::InvalidVirtualAddress(refd_va));
}
let refd_pa = ebpf::MM_PROGRAM_START + refd_va;
if target_section == TEXT_SECTION_INDEX {
LittleEndian::write_u32(
&mut section_infos[target_section].bytes
[imm_offset..imm_offset + BYTE_LENGTH_IMMEIDATE],
(refd_pa & 0xFFFFFFFF) as u32,
);
LittleEndian::write_u32(
&mut section_infos[target_section].bytes[imm_offset
+ ebpf::INSN_SIZE
..imm_offset + ebpf::INSN_SIZE + BYTE_LENGTH_IMMEIDATE],
(refd_pa >> 32) as u32,
);
} else {
LittleEndian::write_u64(
&mut section_infos[target_section].bytes
[target_offset..target_offset + mem::size_of::<u64>()],
refd_pa,
);
}
}
Some(BPFRelocationType::R_BPF_64_32) => {
let symbol = &symbols[relocation.sym as usize];
let hash = ebpf::hash_symbol_name(&symbol.name);
let insn_offset = (relocation.addr - section_infos[0].va) as usize;
let imm_offset = insn_offset + BYTE_OFFSET_IMMEDIATE;
LittleEndian::write_u32(
&mut section_infos[0].bytes
[imm_offset..imm_offset + BYTE_LENGTH_IMMEIDATE],
hash,
);
if symbol.stype == elfkit::types::SymbolType::FUNC && symbol.value != 0 {
self.calls.insert(
hash,
(symbol.value - section_infos[0].va) as usize / ebpf::INSN_SIZE,
);
}
}
_ => return Err(ELFError::UnknownRelocation(relocation.rtype.clone() as u32)),
}
}
}
Ok(())
}
fn get_relocations<R>(mut io: R) -> Result<Vec<elfkit::Relocation>, ELFError>
where
R: std::io::Read,
{
let mut relocs = Vec::new();
while let Ok(addr) = io.read_u64::<LittleEndian>() {
let info = match io.read_u64::<LittleEndian>() {
Ok(v) => v,
_ => {
return Err(ELFError::FailedToReadRelocationInfo);
}
};
let sym = (info >> 32) as u32;
let rtype = (info & 0xffffffff) as u32;
let rtype = match elfkit::relocation::RelocationType::from_u32(rtype) {
Some(v) => v,
None => return Err(ELFError::UnknownRelocation(rtype)),
};
let addend = 0;
relocs.push(elfkit::relocation::Relocation {
addr,
sym,
rtype,
addend,
});
}
Ok(relocs)
}
#[allow(dead_code)]
fn dump_data(name: &str, prog: &[u8]) {
let mut eight_bytes: Vec<u8> = Vec::new();
println!("{}", name);
for i in prog.iter() {
if eight_bytes.len() >= 7 {
println!("{:02X?}", eight_bytes);
eight_bytes.clear();
} else {
eight_bytes.push(i.clone());
}
}
}
}
#[cfg(test)]
mod test {
use super::*;
use std::fs::File;
use std::io::Read;
#[test]
fn test_validate() {
let mut file = File::open("tests/elfs/noop.so").expect("file open failed");
let mut elf_bytes = Vec::new();
file.read_to_end(&mut elf_bytes)
.expect("failed to read elf file");
let mut elf = EBpfElf::load(&elf_bytes).unwrap();
elf.validate().expect("validation failed");
elf.elf.header.ident_class = elfkit::types::Class::Class32;
elf.validate().expect_err("allowed bad class");
elf.elf.header.ident_class = elfkit::types::Class::Class64;
elf.validate().expect("validation failed");
elf.elf.header.ident_endianness = elfkit::types::Endianness::BigEndian;
elf.validate().expect_err("allowed big endian");
elf.elf.header.ident_endianness = elfkit::types::Endianness::LittleEndian;
elf.validate().expect("validation failed");
elf.elf.header.ident_abi = elfkit::types::Abi::ARM;
elf.validate().expect_err("allowed wrong abi");
elf.elf.header.ident_abi = elfkit::types::Abi::SYSV;
elf.validate().expect("validation failed");
elf.elf.header.machine = elfkit::types::Machine::QDSP6;
elf.validate().expect_err("allowed wrong machine");
elf.elf.header.machine = elfkit::types::Machine::BPF;
elf.validate().expect("validation failed");
elf.elf.header.etype = elfkit::types::ElfType::REL;
elf.validate().expect_err("allowed wrong type");
elf.elf.header.etype = elfkit::types::ElfType::DYN;
elf.validate().expect("validation failed");
}
#[test]
fn test_relocate() {
let mut file = File::open("tests/elfs/noop.so").expect("file open failed");
let mut elf_bytes = Vec::new();
file.read_to_end(&mut elf_bytes)
.expect("failed to read elf file");
EBpfElf::load(&elf_bytes).expect("validation failed");
}
#[test]
fn test_entrypoint() {
let mut file = File::open("tests/elfs/noop.so").expect("file open failed");
let mut elf_bytes = Vec::new();
file.read_to_end(&mut elf_bytes)
.expect("failed to read elf file");
let mut elf = EBpfElf::load(&elf_bytes).expect("validation failed");
assert_eq!(
0,
elf.get_entrypoint_instruction_offset()
.expect("failed to get entrypoint")
);
elf.elf.header.entry = elf.elf.header.entry + 8;
assert_eq!(
1,
elf.get_entrypoint_instruction_offset()
.expect("failed to get entrypoint")
);
}
#[test]
#[should_panic(expected = "called `Result::unwrap()` on an `Err` value: EntrypointOutOfBounds")]
fn test_entrypoint_before_text() {
let mut file = File::open("tests/elfs/noop.so").expect("file open failed");
let mut elf_bytes = Vec::new();
file.read_to_end(&mut elf_bytes)
.expect("failed to read elf file");
let mut elf = EBpfElf::load(&elf_bytes).expect("validation failed");
elf.elf.header.entry = 1;
elf.get_entrypoint_instruction_offset().unwrap();
}
#[test]
#[should_panic(expected = "called `Result::unwrap()` on an `Err` value: EntrypointOutOfBounds")]
fn test_entrypoint_after_text() {
let mut file = File::open("tests/elfs/noop.so").expect("file open failed");
let mut elf_bytes = Vec::new();
file.read_to_end(&mut elf_bytes)
.expect("failed to read elf file");
let mut elf = EBpfElf::load(&elf_bytes).expect("validation failed");
elf.elf.header.entry = 1;
elf.get_entrypoint_instruction_offset().unwrap();
}
#[test]
#[should_panic(expected = "called `Result::unwrap()` on an `Err` value: InvalidEntrypoint")]
fn test_entrypoint_not_multiple_of_instruction_size() {
let mut file = File::open("tests/elfs/noop.so").expect("file open failed");
let mut elf_bytes = Vec::new();
file.read_to_end(&mut elf_bytes)
.expect("failed to read elf file");
let mut elf = EBpfElf::load(&elf_bytes).expect("validation failed");
elf.elf.header.entry = elf.elf.header.entry + ebpf::INSN_SIZE as u64 + 1;
elf.get_entrypoint_instruction_offset().unwrap();
}
#[test]
fn test_fixup_relative_calls_back() {
let mut calls: HashMap<u32, usize> = HashMap::new();
#[rustfmt::skip]
let mut prog = vec![
0xb7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xb7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xb7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xb7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xb7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x85, 0x10, 0x00, 0x00, 0xfe, 0xff, 0xff, 0xff];
EBpfElf::fixup_relative_calls(&mut calls, &mut prog).unwrap();
let key = ebpf::hash_symbol_name(&[5, 0, 0, 0, 0, 0, 0, 0]);
let insn = ebpf::Insn {
opc: 0x85,
dst: 0,
src: 1,
off: 0,
imm: key as i32,
};
assert_eq!(insn.to_array(), prog[40..]);
assert_eq!(*calls.get(&key).unwrap(), 4);
let mut calls: HashMap<u32, usize> = HashMap::new();
prog.splice(44.., vec![0xfa, 0xff, 0xff, 0xff]);
EBpfElf::fixup_relative_calls(&mut calls, &mut prog).unwrap();
let key = ebpf::hash_symbol_name(&[5, 0, 0, 0, 0, 0, 0, 0]);
let insn = ebpf::Insn {
opc: 0x85,
dst: 0,
src: 1,
off: 0,
imm: key as i32,
};
assert_eq!(insn.to_array(), prog[40..]);
assert_eq!(*calls.get(&key).unwrap(), 0);
}
#[test]
fn test_fixup_relative_calls_forward() {
let mut calls: HashMap<u32, usize> = HashMap::new();
#[rustfmt::skip]
let mut prog = vec![
0x85, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xb7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xb7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xb7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xb7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xb7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
EBpfElf::fixup_relative_calls(&mut calls, &mut prog).unwrap();
let key = ebpf::hash_symbol_name(&[0, 0, 0, 0, 0, 0, 0, 0]);
let insn = ebpf::Insn {
opc: 0x85,
dst: 0,
src: 1,
off: 0,
imm: key as i32,
};
assert_eq!(insn.to_array(), prog[..8]);
assert_eq!(*calls.get(&key).unwrap(), 1);
let mut calls: HashMap<u32, usize> = HashMap::new();
prog.splice(4..8, vec![0x04, 0x00, 0x00, 0x00]);
EBpfElf::fixup_relative_calls(&mut calls, &mut prog).unwrap();
let key = ebpf::hash_symbol_name(&[0, 0, 0, 0, 0, 0, 0, 0]);
let insn = ebpf::Insn {
opc: 0x85,
dst: 0,
src: 1,
off: 0,
imm: key as i32,
};
assert_eq!(insn.to_array(), prog[..8]);
assert_eq!(*calls.get(&key).unwrap(), 5);
}
#[test]
#[should_panic(
expected = "called `Result::unwrap()` on an `Err` value: RelativeJumpOutOfBounds(29)"
)]
fn test_fixup_relative_calls_out_of_bounds_forward() {
let mut calls: HashMap<u32, usize> = HashMap::new();
#[rustfmt::skip]
let mut prog = vec![
0x85, 0x10, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
0xb7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xb7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xb7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xb7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xb7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
EBpfElf::fixup_relative_calls(&mut calls, &mut prog).unwrap();
let key = ebpf::hash_symbol_name(&[0]);
let insn = ebpf::Insn {
opc: 0x85,
dst: 0,
src: 1,
off: 0,
imm: key as i32,
};
assert_eq!(insn.to_array(), prog[..8]);
assert_eq!(*calls.get(&key).unwrap(), 1);
}
#[test]
#[should_panic(
expected = "called `Result::unwrap()` on an `Err` value: RelativeJumpOutOfBounds(34)"
)]
fn test_fixup_relative_calls_out_of_bounds_back() {
let mut calls: HashMap<u32, usize> = HashMap::new();
#[rustfmt::skip]
let mut prog = vec![
0xb7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xb7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xb7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xb7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xb7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x85, 0x10, 0x00, 0x00, 0xf9, 0xff, 0xff, 0xff];
EBpfElf::fixup_relative_calls(&mut calls, &mut prog).unwrap();
let key = ebpf::hash_symbol_name(&[5]);
let insn = ebpf::Insn {
opc: 0x85,
dst: 0,
src: 1,
off: 0,
imm: key as i32,
};
assert_eq!(insn.to_array(), prog[40..]);
assert_eq!(*calls.get(&key).unwrap(), 4);
}
}