can dynamically load applications

This commit is contained in:
2025-09-07 18:13:55 -06:00
parent 8dad3ce6bb
commit 70ecbcafc3
8 changed files with 198 additions and 119 deletions

View File

@@ -1,51 +1,22 @@
#![allow(static_mut_refs)]
use crate::{abi, storage::SDCARD};
use abi_sys::{CallAbiTable, EntryFn};
use alloc::{boxed::Box, vec::Vec};
use core::{
alloc::Layout,
ffi::c_void,
pin::Pin,
ptr::NonNull,
slice::from_raw_parts_mut,
task::{Context, Poll},
use crate::{
abi,
storage::{File, SDCARD},
};
use abi_sys::{CallAbiTable, EntryFn};
use alloc::{vec, vec::Vec};
use embedded_sdmmc::ShortFileName;
use goblin::elf::{Elf, header::ET_DYN, program_header::PT_LOAD, sym};
use goblin::{
elf::{
header::header32::Header,
program_header::program_header32::{PT_LOAD, ProgramHeader},
section_header::SHT_SYMTAB,
},
elf32::{section_header::SectionHeader, sym::Sym},
};
pub async fn read_binary(name: &ShortFileName) -> Option<Vec<u8>> {
let mut guard = SDCARD.get().lock().await;
let sd = guard.as_mut()?;
let mut buf = Vec::new();
defmt::info!("sd closure");
sd.access_root_dir(|root_dir| {
// Try to open the file directly by name
defmt::info!("trying to open file: {:?}", name);
if let Ok(file) = root_dir.open_file_in_dir(name, embedded_sdmmc::Mode::ReadOnly) {
defmt::info!("opened");
let mut temp = [0u8; 512];
defmt::info!("caching binary");
loop {
match file.read(&mut temp) {
Ok(n) if n > 0 => buf.extend_from_slice(&temp[..n]),
_ => break,
}
}
defmt::info!("done");
let _ = file.close();
}
});
if buf.is_empty() {
return None;
}
Some(buf)
}
const ELF32_HDR_SIZE: usize = 52;
// userland ram region defined in memory.x
unsafe extern "C" {
@@ -53,67 +24,181 @@ unsafe extern "C" {
static __userapp_end__: u8;
}
pub unsafe fn load_binary(bytes: &[u8]) -> Result<EntryFn, &str> {
let elf = Elf::parse(&bytes).expect("Failed to parse ELF");
pub async unsafe fn load_binary(name: &ShortFileName) -> Result<EntryFn, &str> {
let mut sd_lock = SDCARD.get().lock().await;
let sd = sd_lock.as_mut().unwrap();
if elf.is_64 || elf.is_lib || !elf.little_endian {
return Err("Unsupported ELF type");
}
let mut error = "";
let mut entry = 0;
for ph in &elf.program_headers {
if ph.p_type == PT_LOAD {
let vaddr = ph.p_vaddr as usize;
let memsz = ph.p_memsz as usize;
let filesz = ph.p_filesz as usize;
let offset = ph.p_offset as usize;
let mut header_buf = [0; ELF32_HDR_SIZE];
let seg_start = vaddr;
let seg_end = vaddr + memsz;
sd.read_file(name, |mut file| {
file.read(&mut header_buf).unwrap();
let elf_header = Header::from_bytes(&header_buf);
// Bounds check: make sure segment fits inside payload region
let user_start = unsafe { &__userapp_start__ as *const u8 as usize };
let user_end = unsafe { &__userapp_end__ as *const u8 as usize };
if seg_start < user_start || seg_end > user_end {
panic!(
"Segment out of bounds: {:x}..{:x} not within {:x}..{:x}",
seg_start, seg_end, user_start, user_end
);
let mut program_headers_buf = vec![0_u8; elf_header.e_phentsize as usize];
for i in 1..=elf_header.e_phnum {
file.seek_from_start(elf_header.e_phoff + (elf_header.e_phentsize * i) as u32)
.unwrap();
file.read(&mut program_headers_buf).unwrap();
let ph = cast_phdr(&program_headers_buf);
if ph.p_type == PT_LOAD {
load_segment(&mut file, &ph).unwrap()
}
}
unsafe {
let dst = seg_start as *mut u8;
let src = bytes.as_ptr().add(offset);
// MUST MATCH ABI EXACTLY
let entries: &[(CallAbiTable, usize)] = &[
(CallAbiTable::Print, abi::print as usize),
(CallAbiTable::DrawIter, abi::draw_iter as usize),
(CallAbiTable::GetKey, abi::get_key as usize),
];
assert!(entries.len() == CallAbiTable::COUNT);
// Copy initialized part
core::ptr::copy_nonoverlapping(src, dst, filesz);
patch_abi(entries, &elf_header, &mut file).unwrap();
// Zero BSS region (memsz - filesz)
if memsz > filesz {
core::ptr::write_bytes(dst.add(filesz), 0, memsz - filesz);
// TODO: dynamically search for abi table
entry = elf_header.e_entry as u32;
})
.await
.unwrap();
if entry != 0 {
Ok(unsafe { core::mem::transmute(entry) })
} else {
Err(error)
}
}
fn patch_abi(
entries: &[(CallAbiTable, usize)],
elf_header: &Header,
file: &mut File,
) -> Result<(), ()> {
for i in 1..=elf_header.e_shnum {
let sh = read_section(file, &elf_header, i.into());
// find the symbol table
if sh.sh_type == SHT_SYMTAB {
let mut symtab_buf = vec![0u8; sh.sh_size as usize];
file.seek_from_start(sh.sh_offset).unwrap();
file.read(&mut symtab_buf).unwrap();
// Cast buffer into symbols
let sym_count = sh.sh_size as usize / sh.sh_entsize as usize;
for i in 0..sym_count {
let sym_bytes =
&symtab_buf[i * sh.sh_entsize as usize..(i + 1) * sh.sh_entsize as usize];
let sym = cast_sym(sym_bytes);
let str_sh = read_section(file, &elf_header, sh.sh_link);
let mut name = Vec::new();
file.seek_from_start(str_sh.sh_offset + sym.st_name)
.unwrap();
loop {
let mut byte = [0u8; 1];
file.read(&mut byte).unwrap();
if byte[0] == 0 {
break;
}
name.push(byte[0]);
}
let symbol_name = core::str::from_utf8(&name).unwrap();
if symbol_name == "CALL_ABI_TABLE" {
let table_base = sym.st_value as *mut usize;
for &(abi_idx, func_ptr) in entries {
unsafe {
table_base.add(abi_idx as usize).write(func_ptr);
}
}
return Ok(());
}
}
}
}
Err(())
}
let call_abi_sym = elf
.syms
.iter()
.find(|s| elf.strtab.get_at(s.st_name).unwrap() == "CALL_ABI_TABLE")
.expect("syscall table not found");
fn read_section(file: &mut File, elf_header: &Header, section: u32) -> SectionHeader {
let mut section_header_buf = vec![0_u8; elf_header.e_shentsize as usize];
let table_base = call_abi_sym.st_value as *mut usize;
file.seek_from_start(elf_header.e_shoff + (elf_header.e_shentsize as u32 * section))
.unwrap();
file.read(&mut section_header_buf).unwrap();
let entries: &[(CallAbiTable, usize)] = &[
(CallAbiTable::Print, abi::print as usize),
(CallAbiTable::DrawIter, abi::draw_iter as usize),
(CallAbiTable::GetKey, abi::get_key as usize),
];
assert!(entries.len() == CallAbiTable::COUNT);
cast_shdr(&section_header_buf)
}
fn load_segment(file: &mut File, ph: &ProgramHeader) -> Result<(), ()> {
let dst_start = ph.p_vaddr as *mut u8;
let filesz = ph.p_filesz as usize;
let memsz = ph.p_memsz as usize;
let vaddr = ph.p_vaddr as usize;
let mut remaining = filesz;
let mut dst_ptr = dst_start;
let mut file_offset = ph.p_offset;
let seg_start = vaddr;
let seg_end = vaddr + memsz;
// Bounds check: make sure segment fits inside payload region
let user_start = unsafe { &__userapp_start__ as *const u8 as usize };
let user_end = unsafe { &__userapp_end__ as *const u8 as usize };
if seg_start < user_start || seg_end > user_end {
panic!(
"Segment out of bounds: {:x}..{:x} not within {:x}..{:x}",
seg_start, seg_end, user_start, user_end
);
}
// Buffer for chunked reads (512 bytes is typical SD sector size)
let mut buf = [0u8; 512];
while remaining > 0 {
let to_read = core::cmp::min(remaining, buf.len());
// Read chunk from file
file.seek_from_start(file_offset).unwrap();
file.read(&mut buf[..to_read]).unwrap();
for &(abi_idx, func_ptr) in entries {
unsafe {
table_base.add(abi_idx as usize).write(func_ptr);
// Copy chunk directly into destination memory
core::ptr::copy_nonoverlapping(buf.as_ptr(), dst_ptr, to_read);
dst_ptr = dst_ptr.add(to_read);
}
remaining -= to_read;
file_offset += to_read as u32;
}
// Zero BSS (memsz - filesz)
if memsz > filesz {
unsafe {
core::ptr::write_bytes(dst_ptr, 0, memsz - filesz);
}
}
Ok(unsafe { core::mem::transmute(elf.entry as u32) })
Ok(())
}
fn cast_phdr(buf: &[u8]) -> ProgramHeader {
assert!(buf.len() >= core::mem::size_of::<ProgramHeader>());
unsafe { core::ptr::read(buf.as_ptr() as *const ProgramHeader) }
}
fn cast_shdr(buf: &[u8]) -> SectionHeader {
assert!(buf.len() >= core::mem::size_of::<SectionHeader>());
unsafe { core::ptr::read(buf.as_ptr() as *const SectionHeader) }
}
fn cast_sym(buf: &[u8]) -> Sym {
assert!(buf.len() >= core::mem::size_of::<Sym>());
unsafe { core::ptr::read(buf.as_ptr() as *const Sym) }
}