diff --git a/Cargo.lock b/Cargo.lock index 4e9918a..4f20b02 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -28,6 +28,7 @@ dependencies = [ name = "abi_sys" version = "0.1.0" dependencies = [ + "defmt 0.3.100", "embedded-graphics", "shared", "strum", @@ -1307,6 +1308,7 @@ dependencies = [ "assign-resources", "bitflags 2.9.4", "bt-hci", + "bumpalo", "cortex-m", "cortex-m-rt", "cyw43", diff --git a/abi_sys/Cargo.toml b/abi_sys/Cargo.toml index e08c57c..502ac83 100644 --- a/abi_sys/Cargo.toml +++ b/abi_sys/Cargo.toml @@ -3,7 +3,11 @@ name = "abi_sys" version = "0.1.0" edition = "2024" +[features] +defmt = ["dep:defmt"] + [dependencies] embedded-graphics = "0.8.1" strum = { version = "0.27.2", default-features = false, features = ["derive"] } +defmt = { version = "0.3", optional = true } shared = { path = "../shared" } diff --git a/abi_sys/src/lib.rs b/abi_sys/src/lib.rs index 2f15778..5a200f8 100644 --- a/abi_sys/src/lib.rs +++ b/abi_sys/src/lib.rs @@ -9,16 +9,17 @@ use embedded_graphics::{ pixelcolor::{Rgb565, RgbColor}, }; pub use shared::keyboard::{KeyCode, KeyEvent, KeyState, Modifiers}; -use strum::EnumIter; +use strum::{EnumCount, EnumIter}; pub type EntryFn = fn(); #[unsafe(no_mangle)] -#[unsafe(link_section = ".user_reloc")] +#[unsafe(link_section = ".syscall_table")] pub static mut CALL_ABI_TABLE: [usize; CallAbiTable::COUNT] = [0; CallAbiTable::COUNT]; #[repr(usize)] -#[derive(Clone, Copy, EnumIter)] +#[derive(Clone, Copy, EnumIter, EnumCount)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum CallAbiTable { Print = 0, Sleep = 1, @@ -28,10 +29,6 @@ pub enum CallAbiTable { GenRand = 5, } -impl CallAbiTable { - pub const COUNT: usize = 6; -} - pub type PrintAbi = extern "Rust" fn(msg: &str); #[allow(unused)] diff --git a/justfile b/justfile index 499e29d..40f9fad 100644 --- a/justfile +++ b/justfile @@ -1,6 +1,8 @@ +binary-args := "RUSTFLAGS=\"-C link-arg=-pie -C relocation-model=pic\"" + kernel: cargo run --bin kernel calculator: - RUSTFLAGS="-C link-arg=--noinhibit-exec" cargo build --bin calculator --profile release-binary + {{binary-args}} cargo build --bin calculator --profile release-binary snake: - RUSTFLAGS="-C link-arg=--noinhibit-exec" cargo build --bin snake --profile release-binary + {{binary-args}} cargo build --bin snake --profile release-binary diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index f1c8459..ab8d173 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -18,6 +18,7 @@ trouble = ["dep:bt-hci", "dep:cyw43", "dep:cyw43-pio", "dep:trouble-host"] defmt = [ "dep:defmt", "shared/defmt", + "abi_sys/defmt", "panic-probe/print-defmt", "embassy-executor/defmt", "embassy-time/defmt", @@ -85,10 +86,11 @@ once_cell = { version = "1.21.3", default-features = false } static_cell = "2.1.1" bitflags = "2.9.4" heapless = "0.8.0" +spin = "0.10.0" num_enum = { version = "0.7.4", default-features = false } goblin = { version = "0.10.1", default-features = false, features = ["elf32"] } talc = "4.4.3" -spin = "0.10.0" +bumpalo = "3.19.0" shared = { path = "../shared" } abi_sys = { path = "../abi_sys" } diff --git a/kernel/memory.x b/kernel/memory.x index fd22299..a2edb86 100644 --- a/kernel/memory.x +++ b/kernel/memory.x @@ -1,10 +1,7 @@ MEMORY { FLASH : ORIGIN = 0x10000000, LENGTH = 4096K + RAM : ORIGIN = 0x20000000, LENGTH = 512K - - /* Reserve a block of RAM for the user app */ - USERAPP : ORIGIN = 0x20010000, LENGTH = 192K - SRAM4 : ORIGIN = 0x20080000, LENGTH = 4K SRAM5 : ORIGIN = 0x20081000, LENGTH = 4K } @@ -61,11 +58,3 @@ SECTIONS { PROVIDE(start_to_end = __end_block_addr - __start_block_addr); PROVIDE(end_to_start = __start_block_addr - __end_block_addr); - -SECTIONS { - .userapp (NOLOAD) : - { - __userapp_start__ = ORIGIN(USERAPP); - __userapp_end__ = ORIGIN(USERAPP) + LENGTH(USERAPP); - } > USERAPP -} diff --git a/kernel/src/elf.rs b/kernel/src/elf.rs index 72e2161..5f66daf 100644 --- a/kernel/src/elf.rs +++ b/kernel/src/elf.rs @@ -1,73 +1,175 @@ #![allow(static_mut_refs)] +use core::ptr; + use crate::{ abi, storage::{File, SDCARD}, }; use abi_sys::{CallAbiTable, EntryFn}; use alloc::{vec, vec::Vec}; +use bumpalo::Bump; use embedded_sdmmc::ShortFileName; use goblin::{ elf::{ header::header32::Header, program_header::program_header32::{PT_LOAD, ProgramHeader}, - section_header::SHT_SYMTAB, + reloc::R_ARM_RELATIVE, + section_header::{SHT_REL, SHT_SYMTAB}, }, - elf32::{section_header::SectionHeader, sym::Sym}, + elf32::{header, reloc::Rel, section_header::SectionHeader, sym::Sym}, }; use strum::IntoEnumIterator; const ELF32_HDR_SIZE: usize = 52; -// userland ram region defined in memory.x -unsafe extern "C" { - static __userapp_start__: u8; - static __userapp_end__: u8; -} - -pub async unsafe fn load_binary(name: &ShortFileName) -> Result { +pub async unsafe fn load_binary(name: &ShortFileName) -> Option<(EntryFn, Bump)> { let mut sd_lock = SDCARD.get().lock().await; let sd = sd_lock.as_mut().unwrap(); - let error = ""; - let mut entry = 0; - let mut header_buf = [0; ELF32_HDR_SIZE]; - sd.read_file(name, |mut file| { - file.read(&mut header_buf).unwrap(); - let elf_header = Header::from_bytes(&header_buf); + let (entry, bump) = sd + .read_file(name, |mut file| { + file.read(&mut header_buf).unwrap(); + let elf_header = Header::from_bytes(&header_buf); - 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() + // reject non-PIE + if elf_header.e_type != header::ET_DYN { + return None; } - } - patch_abi(&elf_header, &mut file).unwrap(); + let mut ph_buf = vec![0_u8; elf_header.e_phentsize as usize]; - // TODO: dynamically search for abi table + let (total_size, min_vaddr, _max_vaddr) = + total_loadable_size(&mut file, &elf_header, &mut ph_buf); - entry = elf_header.e_entry as u32; - }) - .await - .unwrap(); + defmt::info!("total_size: {}", total_size); + let bump = Bump::with_capacity(total_size); + let base = bump.alloc_slice_fill_default::(total_size); + defmt::info!("base ptr: {}", base.as_ptr()); - if entry != 0 { - Ok(unsafe { core::mem::transmute(entry) }) - } else { - Err(error) - } + // load each segment into bump, relative to base_ptr + for i in 0..elf_header.e_phnum { + file.seek_from_start(elf_header.e_phoff + (elf_header.e_phentsize * i) as u32) + .unwrap(); + file.read(&mut ph_buf).unwrap(); + let ph = cast_phdr(&ph_buf); + + let seg_offset = (ph.p_vaddr - min_vaddr) as usize; + defmt::info!("segment offset {}", seg_offset); + let mut segment = &mut base[seg_offset..seg_offset + ph.p_memsz as usize]; + + if ph.p_type == PT_LOAD { + load_segment(&mut file, &ph, &mut segment).unwrap(); + } + } + + let mut sh_buf = vec![0_u8; elf_header.e_shentsize as usize]; + + for i in 0..elf_header.e_shnum { + file.seek_from_start(elf_header.e_shoff + (elf_header.e_shentsize * i) as u32) + .unwrap(); + file.read(&mut sh_buf).unwrap(); + let sh = cast_shdr(&sh_buf); + + match sh.sh_type { + SHT_REL => { + apply_relocations(&sh, min_vaddr, base.as_mut_ptr(), &mut file).unwrap(); + } + _ => {} + } + } + + patch_abi(&elf_header, base.as_mut_ptr(), min_vaddr, &mut file).unwrap(); + + defmt::info!("elf entry point before offset: {}", elf_header.e_entry); + // entry pointer is base_ptr + (entry - min_vaddr) + let entry_ptr: EntryFn = unsafe { + core::mem::transmute(base.as_ptr().add((elf_header.e_entry - min_vaddr) as usize)) + }; + defmt::info!("entry ptr: {}", entry_ptr); + + Some((entry_ptr, bump)) + }) + .await + .expect("Failed to read file")?; + + Some((entry, bump)) } -fn patch_abi(elf_header: &Header, file: &mut File) -> Result<(), ()> { +fn load_segment(file: &mut File, ph: &ProgramHeader, segment: &mut [u8]) -> Result<(), ()> { + let filesz = ph.p_filesz as usize; + let memsz = ph.p_memsz as usize; + + // read file contents + let mut remaining = filesz; + let mut dst_offset = 0; + let mut file_offset = ph.p_offset; + let mut buf = [0u8; 512]; + + while remaining > 0 { + let to_read = core::cmp::min(remaining, buf.len()); + file.seek_from_start(file_offset).unwrap(); + file.read(&mut buf[..to_read]).unwrap(); + + segment[dst_offset..dst_offset + to_read].copy_from_slice(&buf[..to_read]); + + remaining -= to_read; + dst_offset += to_read; + file_offset += to_read as u32; + } + + // zero BSS if needed + if memsz > filesz { + segment[filesz..].fill(0); + } + + Ok(()) +} + +fn apply_relocations( + sh: &SectionHeader, + min_vaddr: u32, + base: *mut u8, + file: &mut File, +) -> Result<(), ()> { + let mut reloc = [0_u8; 8]; + + let num_relocs = sh.sh_size as usize / sh.sh_entsize as usize; + + for i in 0..num_relocs { + file.seek_from_start(sh.sh_offset + (i as u32 * 8)).unwrap(); + file.read(&mut reloc).unwrap(); + + let rel = cast_rel(&reloc); + + let reloc_type = rel.r_info & 0xff; + let reloc_addr = unsafe { base.add((rel.r_offset - min_vaddr) as usize) as *mut u32 }; + + match reloc_type { + R_ARM_RELATIVE => { + // REL: add base to the word already stored there + unsafe { + let val = ptr::read_unaligned(reloc_addr); + ptr::write_unaligned(reloc_addr, val.wrapping_add(base as u32)); + } + } + _ => { + defmt::warn!("Unsupported relocation type: {}", reloc_type); + return Err(()); + } + } + } + Ok(()) +} + +fn patch_abi( + elf_header: &Header, + base: *mut u8, + min_vaddr: u32, + file: &mut File, +) -> Result<(), ()> { for i in 1..=elf_header.e_shnum { let sh = read_section(file, &elf_header, i.into()); @@ -101,7 +203,11 @@ fn patch_abi(elf_header: &Header, file: &mut File) -> Result<(), ()> { let symbol_name = core::str::from_utf8(&name).unwrap(); if symbol_name == "CALL_ABI_TABLE" { - let table_base = sym.st_value as *mut usize; + let table_base = + unsafe { base.add((sym.st_value as usize) - min_vaddr as usize) } + as *mut usize; + defmt::info!("CALL_ABI_TABLE st_value: {:x}", sym.st_value); + defmt::info!("table base {}", table_base); for (idx, call) in CallAbiTable::iter().enumerate() { let ptr = match call { @@ -113,6 +219,13 @@ fn patch_abi(elf_header: &Header, file: &mut File) -> Result<(), ()> { CallAbiTable::GenRand => abi::gen_rand as usize, }; unsafe { + defmt::info!( + "table {:?}#{} @ {} -> 0x{:X}", + call, + idx, + table_base.wrapping_add(idx), + ptr + ); table_base.add(idx as usize).write(ptr); } } @@ -124,6 +237,33 @@ fn patch_abi(elf_header: &Header, file: &mut File) -> Result<(), ()> { Err(()) } +fn total_loadable_size( + file: &mut File, + elf_header: &Header, + ph_buf: &mut [u8], +) -> (usize, u32, u32) { + let mut min_vaddr = u32::MAX; + let mut max_vaddr = 0u32; + for i in 0..elf_header.e_phnum { + file.seek_from_start(elf_header.e_phoff + (elf_header.e_phentsize * i) as u32) + .unwrap(); + file.read(ph_buf).unwrap(); + let ph = cast_phdr(&ph_buf); + + if ph.p_type == PT_LOAD { + if ph.p_vaddr < min_vaddr { + min_vaddr = ph.p_vaddr; + } + if ph.p_vaddr + ph.p_memsz > max_vaddr { + max_vaddr = ph.p_vaddr + ph.p_memsz; + } + } + } + + let total_size = (max_vaddr - min_vaddr) as usize; + (total_size, min_vaddr, max_vaddr) +} + 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]; @@ -134,57 +274,6 @@ fn read_section(file: &mut File, elf_header: &Header, section: u32) -> SectionHe cast_shdr(§ion_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(); - - unsafe { - // 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(()) -} - fn cast_phdr(buf: &[u8]) -> ProgramHeader { assert!(buf.len() >= core::mem::size_of::()); unsafe { core::ptr::read(buf.as_ptr() as *const ProgramHeader) } @@ -199,3 +288,8 @@ fn cast_sym(buf: &[u8]) -> Sym { assert!(buf.len() >= core::mem::size_of::()); unsafe { core::ptr::read(buf.as_ptr() as *const Sym) } } + +fn cast_rel(buf: &[u8]) -> Rel { + assert!(buf.len() >= core::mem::size_of::()); + unsafe { core::ptr::read(buf.as_ptr() as *const Rel) } +} diff --git a/kernel/src/main.rs b/kernel/src/main.rs index 2de46c4..894b971 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -29,6 +29,7 @@ use crate::{ ui::{SELECTIONS, clear_selection, ui_handler}, }; use abi_sys::EntryFn; +use bumpalo::Bump; use embedded_graphics::{ pixelcolor::Rgb565, prelude::{DrawTarget, RgbColor}, @@ -72,7 +73,7 @@ static mut CORE1_STACK: Stack<16384> = Stack::new(); static EXECUTOR0: StaticCell = StaticCell::new(); static EXECUTOR1: StaticCell = StaticCell::new(); -static mut ARENA: [u8; 10 * 1024] = [0; 10 * 1024]; +static mut ARENA: [u8; 200 * 1024] = [0; 200 * 1024]; #[global_allocator] static ALLOCATOR: Talck, ClaimOnOom> = @@ -124,14 +125,14 @@ async fn main(_spawner: Spawner) { } // One-slot channel to pass EntryFn from core1 -static BINARY_CH: Channel = Channel::new(); +static BINARY_CH: Channel = Channel::new(); // runs dynamically loaded elf files #[embassy_executor::task] async fn userland_task() { let recv = BINARY_CH.receiver(); loop { - let entry = recv.receive().await; + let (entry, _bump) = recv.receive().await; defmt::info!("Got Entry"); // disable kernel ui diff --git a/kernel/src/storage.rs b/kernel/src/storage.rs index bac9f44..ad2b4a3 100644 --- a/kernel/src/storage.rs +++ b/kernel/src/storage.rs @@ -114,16 +114,15 @@ impl SdCard { access(root_dir); } - pub async fn read_file( + pub async fn read_file( &mut self, name: &ShortFileName, - mut access: impl FnMut(File), - ) -> Result<(), ()> { + mut access: impl FnMut(File) -> T, + ) -> Result { let mut res = Err(()); self.access_root_dir(|root_dir| { if let Ok(file) = root_dir.open_file_in_dir(name, Mode::ReadOnly) { - res = Ok(()); - access(file); + res = Ok(access(file)); } }); diff --git a/kernel/src/ui.rs b/kernel/src/ui.rs index c7efd85..f4b9d85 100644 --- a/kernel/src/ui.rs +++ b/kernel/src/ui.rs @@ -45,7 +45,11 @@ pub async fn ui_handler() { let selection = selections.selections[selections.current_selection as usize].clone(); - let entry = unsafe { load_binary(&selection.short_name).await.unwrap() }; + let entry = unsafe { + load_binary(&selection.short_name) + .await + .expect("unable to load binary") + }; BINARY_CH.send(entry).await; } _ => (), diff --git a/user-apps/memory.x b/user-apps/memory.x index 19fa9a5..36c9663 100644 --- a/user-apps/memory.x +++ b/user-apps/memory.x @@ -1,19 +1,10 @@ MEMORY { - /* Must match the USERAPP region in the kernel linker script */ - RAM : ORIGIN = 0x20010000, LENGTH = 192K + RAM : ORIGIN = 0x0, LENGTH = 100K } SECTIONS { - /* Reserve first 1KB for patchable symbols */ - .user_reloc (NOLOAD) : ALIGN(4) - { - __user_reloc_start = .; - KEEP(*(.user_reloc*)); - __user_reloc_end = .; - } > RAM - .text : ALIGN(4) { *(.text .text.*); @@ -30,4 +21,11 @@ SECTIONS *(.bss .bss.*); *(COMMON); } > RAM + + .syscall_table (NOLOAD) : ALIGN(4) + { + __user_reloc_start = .; + KEEP(*(.user_reloc*)); + __user_reloc_end = .; + } > RAM }