mod convert;
mod error;
use core::ops::DerefMut;
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
use elf::{
abi::{EM_RISCV, SHF_ALLOC, SHF_EXECINSTR, SHT_PROGBITS},
endian::LittleEndian,
file::Class,
ElfBytes,
};
#[doc(inline)]
pub use error::Error;
use convert::convert;
pub(crate) fn transpile_raw(code: &mut [u8]) -> Result<bool, Error> {
let code_size = code.len();
let mut needs_padding = false;
let mut i = 0;
while i + 2 <= code_size {
let raw = if i + 4 > code_size {
needs_padding = true;
u16::from_le_bytes(code[i..i + 2].try_into().unwrap()) as u32
} else {
u32::from_le_bytes(code[i..i + 4].try_into().unwrap())
};
let instruction = convert(raw)?;
let inst_bytes = instruction.data.to_le_bytes();
let inst_size = instruction.size as usize;
code[i..i + inst_size].copy_from_slice(&inst_bytes[..inst_size]);
i += inst_size;
}
Ok(needs_padding)
}
fn elf_transpiler_impl<O, F>(elf: &[u8], output: &mut O, append_fn: F) -> Result<usize, Error>
where
O: DerefMut<Target = [u8]>,
F: Fn(&mut O, usize, &[u8]) -> Result<(), Error>,
{
let elf_bytes = ElfBytes::<LittleEndian>::minimal_parse(elf)?;
let segments = elf_bytes.segments().ok_or(Error::NoProgramHeader)?;
let sections = elf_bytes.section_headers().ok_or(Error::NoSectionHeader)?;
if elf_bytes.ehdr.e_machine != EM_RISCV || elf_bytes.ehdr.class != Class::ELF32 {
return Err(Error::InvalidPlatform);
}
let entry = elf_bytes.ehdr.e_entry as u32;
let mut binary_size = 0;
let mut needs_padding = false;
'section: for (i, section) in sections.iter().enumerate() {
if section.sh_type == SHT_PROGBITS && (section.sh_flags as u32 & SHF_ALLOC) != 0 {
let addr = section.sh_addr as u32;
'segment: {
for segment in segments.iter() {
if addr >= segment.p_vaddr as u32
&& addr + section.sh_size as u32
<= segment.p_vaddr as u32 + segment.p_memsz as u32
{
let paddr = addr - segment.p_vaddr as u32 + segment.p_paddr as u32;
let alignment = section.sh_addralign as u32;
let offset = ((paddr - entry).div_ceil(alignment) * alignment) as usize;
let end_offset = offset + section.sh_size as usize;
if end_offset == paddr as usize {
continue 'section;
}
if end_offset > binary_size {
binary_size = end_offset;
}
let (data, compression) = elf_bytes.section_data(§ion)?;
if let Some(value) = compression {
return Err(Error::UnsupportedCompression(value));
}
if data.len() >= 2 {
needs_padding = false;
}
append_fn(output, offset, data)?;
if (section.sh_flags as u32 & SHF_EXECINSTR) != 0 {
needs_padding = transpile_raw(&mut output[offset..end_offset])?;
}
break 'segment;
}
}
return Err(Error::NoSegmentForSection(i));
}
}
}
if needs_padding {
append_fn(output, binary_size, &[0, 0])?;
binary_size += 2;
}
Ok::<usize, Error>(binary_size)
}
pub fn transpile_elf(elf: &[u8], mut output: &mut [u8]) -> Result<usize, Error> {
elf_transpiler_impl(elf, &mut output, |output, offset, data| {
output
.get_mut(offset..offset + data.len())
.ok_or(Error::BufferTooSmall)?
.copy_from_slice(data);
Ok(())
})
}
#[cfg(feature = "alloc")]
pub fn transpile_elf_vec(elf: &[u8]) -> Result<Vec<u8>, Error> {
let mut output = Vec::new();
let out_ptr = &mut output;
elf_transpiler_impl(elf, out_ptr, |output, _offset, data| {
output.extend_from_slice(data);
Ok(())
})?;
Ok(output)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_transpile() {
let elf = include_bytes!("../tests/test.elf");
let mut output = [0; 16384];
let result = transpile_elf(elf, &mut output);
assert!(result.is_ok());
let expected = include_bytes!("../tests/test.bin");
assert_eq!(&output[..result.unwrap()], expected);
}
#[cfg(feature = "alloc")]
#[test]
fn test_transpile_vec() {
let elf = include_bytes!("../tests/test.elf");
let result = transpile_elf_vec(elf).expect("Failed to transpile ELF");
let expected = include_bytes!("../tests/test.bin");
assert_eq!(&result, expected);
}
}