use alloc::{borrow::ToOwned, string::String, vec, vec::Vec};
use core::{ffi::CStr, iter};
use ax_errno::{AxError, AxResult};
use ax_fs::{CachedFile, FS_CONTEXT, FileBackend};
use ax_memory_addr::{MemoryAddr, PAGE_SIZE_4K, VirtAddr};
use ax_runtime::hal::{
mem::virt_to_phys,
paging::{MappingFlags, PageSize},
};
use ax_sync::Mutex;
use axfs_ng_vfs::Location;
use kernel_elf_parser::{
AuxEntry, AuxType, ELFHeaders, ELFHeadersBuilder, ELFParser, app_stack_region,
};
use ouroboros::self_referencing;
use uluru::LRUCache;
use crate::{
config::{USER_SPACE_BASE, USER_SPACE_SIZE},
mm::aspace::{AddrSpace, Backend},
};
#[cfg(target_arch = "riscv64")]
const RISCV_COMPAT_HWCAP_IMAFDC: usize = (1 << (b'I' - b'A'))
| (1 << (b'M' - b'A'))
| (1 << (b'A' - b'A'))
| (1 << (b'F' - b'A'))
| (1 << (b'D' - b'A'))
| (1 << (b'C' - b'A'));
#[cfg(target_arch = "riscv64")]
const R_RISCV_RELATIVE: u32 = 3;
#[cfg(target_arch = "riscv64")]
const R_RISCV_JUMP_SLOT: u32 = 5;
#[cfg(target_arch = "riscv64")]
const R_RISCV_64: u32 = 2;
#[cfg(target_arch = "riscv64")]
const R_RISCV_COPY: u32 = 4;
pub fn new_user_aspace_empty() -> AxResult<AddrSpace> {
AddrSpace::new_empty(VirtAddr::from_usize(USER_SPACE_BASE), USER_SPACE_SIZE)
}
pub fn copy_from_kernel(_aspace: &mut AddrSpace) -> AxResult {
#[cfg(not(any(target_arch = "aarch64", target_arch = "loongarch64")))]
{
let kspace = ax_mm::kernel_aspace().lock();
_aspace.page_table_mut().cursor().copy_from(
kspace.page_table(),
kspace.base(),
kspace.size(),
);
}
Ok(())
}
pub fn map_trampoline(aspace: &mut AddrSpace) -> AxResult {
let signal_trampoline_paddr =
virt_to_phys(starry_signal::arch::signal_trampoline_address().into());
aspace.map_linear(
crate::config::SIGNAL_TRAMPOLINE.into(),
signal_trampoline_paddr,
PAGE_SIZE_4K,
MappingFlags::READ | MappingFlags::EXECUTE | MappingFlags::USER,
)?;
Ok(())
}
fn mapping_flags(flags: xmas_elf::program::Flags) -> MappingFlags {
let mut mapping_flags = MappingFlags::USER;
if flags.is_read() {
mapping_flags |= MappingFlags::READ;
}
if flags.is_write() {
mapping_flags |= MappingFlags::WRITE | MappingFlags::READ;
}
if flags.is_execute() {
mapping_flags |= MappingFlags::EXECUTE;
}
mapping_flags
}
fn map_elf<'a>(
uspace: &mut AddrSpace,
base: usize,
entry: &'a ElfCacheEntry,
) -> AxResult<ELFParser<'a>> {
let elf_parser = ELFParser::new(entry.borrow_elf(), base).map_err(|_| AxError::InvalidData)?;
let cache = entry.borrow_cache();
for ph in elf_parser
.headers()
.ph
.iter()
.filter(|ph| ph.get_type() == Ok(xmas_elf::program::Type::Load))
{
let vaddr = ph.virtual_addr as usize + elf_parser.base();
debug!(
"Mapping ELF segment: [{:#x?}, {:#x?}) flags: {}",
vaddr,
vaddr + ph.mem_size as usize,
ph.flags
);
let seg_pad = vaddr.align_offset_4k();
assert_eq!(seg_pad, ph.offset as usize % PAGE_SIZE_4K);
let seg_align_size =
(ph.mem_size as usize + seg_pad + PAGE_SIZE_4K - 1) & !(PAGE_SIZE_4K - 1);
let seg_start = VirtAddr::from_usize(vaddr);
let backend = Backend::new_cow(
seg_start,
PageSize::Size4K,
FileBackend::Cached(cache.clone()),
ph.offset,
Some(ph.offset + ph.file_size),
false,
);
uspace.map(
seg_start.align_down_4k(),
seg_align_size,
mapping_flags(ph.flags),
false,
backend,
)?;
}
if elf_parser.headers().header.pt1.class() == xmas_elf::header::Class::SixtyFour {
let is_pie = elf_parser.headers().header.pt2.type_().as_type()
== xmas_elf::header::Type::SharedObject;
if is_pie {
#[cfg(target_arch = "riscv64")]
{
for seg in elf_parser
.headers()
.ph
.iter()
.filter(|p| p.get_type() == Ok(xmas_elf::program::Type::Load))
{
let seg_start =
VirtAddr::from_usize(base + seg.virtual_addr as usize).align_down_4k();
let seg_pad = (base + seg.virtual_addr as usize).align_offset_4k();
let seg_size =
(seg.mem_size as usize + seg_pad + PAGE_SIZE_4K - 1) & !(PAGE_SIZE_4K - 1);
uspace.populate_area(seg_start, seg_size, mapping_flags(seg.flags))?;
}
}
apply_relocations(uspace, base, entry.borrow_cache(), &elf_parser.headers().ph)?;
}
}
Ok(elf_parser)
}
#[cfg(target_arch = "riscv64")]
fn vaddr_to_file_offset(vaddr: u64, ph: &[xmas_elf::program::ProgramHeader64]) -> Option<usize> {
let vaddr = vaddr as usize;
for seg in ph {
if seg.get_type() != Ok(xmas_elf::program::Type::Load) {
continue;
}
let seg_vaddr = seg.virtual_addr as usize;
let seg_filesz = seg.file_size as usize;
if vaddr >= seg_vaddr && vaddr < seg_vaddr + seg_filesz {
let offset_in_segment = vaddr - seg_vaddr;
return Some(seg.offset as usize + offset_in_segment);
}
}
None
}
#[cfg(target_arch = "riscv64")]
fn apply_relocations(
uspace: &mut AddrSpace,
base: usize,
cache: &CachedFile,
ph: &[xmas_elf::program::ProgramHeader64],
) -> AxResult {
let dynamic_ph = ph
.iter()
.find(|p| p.get_type() == Ok(xmas_elf::program::Type::Dynamic));
let dynamic_ph = match dynamic_ph {
Some(ph) => ph,
None => return Ok(()), };
let dyn_offset = dynamic_ph.offset as usize;
let dyn_size = dynamic_ph.file_size as usize;
if dyn_offset + dyn_size > (cache.location().len().unwrap_or(0) as usize) {
debug!("Dynamic section extends beyond file");
return Err(AxError::InvalidData);
}
let mut dyn_data = vec![0u8; dyn_size];
cache.read_at(&mut dyn_data, dyn_offset as u64)?;
let entry_size = 16; let num_entries = dyn_size / entry_size;
let mut rela_addr: u64 = 0;
let mut rela_size: u64 = 0;
let mut jmprel_addr: u64 = 0;
let mut jmprel_size: u64 = 0;
let mut symtab_addr: u64 = 0;
let mut strtab_addr: u64 = 0;
for i in 0..num_entries {
let offset = i * entry_size;
let entry_data = &dyn_data[offset..offset + entry_size];
let tag = u64::from_le_bytes(entry_data[0..8].try_into().unwrap());
let value = u64::from_le_bytes(entry_data[8..16].try_into().unwrap());
match tag {
7 => rela_addr = value, 8 => rela_size = value, 23 => jmprel_addr = value, 2 => jmprel_size = value, 6 => symtab_addr = value, 5 => strtab_addr = value, 0 => break, _ => {}
}
}
if rela_addr != 0 && rela_size != 0 {
let rela_offset = vaddr_to_file_offset(rela_addr, ph).ok_or(AxError::InvalidData)?;
let rela_entry_size = 24; let rela_count = rela_size as usize / rela_entry_size;
let mut copy_count: usize = 0;
debug!("Processing {} RELATIVE relocations", rela_count);
for i in 0..rela_count {
let entry_offset = rela_offset + i * rela_entry_size;
if entry_offset + rela_entry_size > (cache.location().len().unwrap_or(0) as usize) {
break;
}
let mut entry_data = vec![0u8; rela_entry_size];
cache.read_at(&mut entry_data, entry_offset as u64)?;
let offset = u64::from_le_bytes(entry_data[0..8].try_into().unwrap()) as usize;
let info = u64::from_le_bytes(entry_data[8..16].try_into().unwrap());
let addend = i64::from_le_bytes(entry_data[16..24].try_into().unwrap());
let reloc_type = (info & 0xffffffff) as u32;
match reloc_type {
R_RISCV_RELATIVE => {
let target = base + offset;
let value = (base as i64 + addend) as u64;
uspace.write(VirtAddr::from_usize(target), &value.to_le_bytes())?;
debug!("RELATIVE: [{:#x}] = {:#x}", target, value);
}
R_RISCV_64 => {
let sym_idx = (info >> 32) as usize;
if symtab_addr == 0 || strtab_addr == 0 {
debug!("Missing symtab/strtab for R_RISCV_64");
continue;
}
let sym_file_offset =
vaddr_to_file_offset(symtab_addr, ph).ok_or(AxError::InvalidData)?;
let sym_entry_offset = sym_file_offset + sym_idx * 24;
let file_len = cache.location().len().unwrap_or(0) as usize;
if sym_entry_offset + 24 > file_len {
continue;
}
let mut sym_data = vec![0u8; 24];
cache.read_at(&mut sym_data, sym_entry_offset as u64)?;
let st_value = u64::from_le_bytes(sym_data[8..16].try_into().unwrap());
if st_value == 0 {
continue;
}
let target = base + offset;
let value = (base as i64 + st_value as i64 + addend) as u64;
uspace.write(VirtAddr::from_usize(target), &value.to_le_bytes())?;
}
R_RISCV_COPY => {
copy_count += 1;
}
_ => {
debug!("[apply_relocations] unknown .rela.dyn type={}", reloc_type);
}
}
}
if copy_count > 0 {
debug!(
"[apply_relocations] skipped {} R_RISCV_COPY relocations",
copy_count
);
}
}
if jmprel_addr != 0 && jmprel_size != 0 {
let jmprel_offset = vaddr_to_file_offset(jmprel_addr, ph).ok_or(AxError::InvalidData)?;
let rela_entry_size = 24; let jmprel_count = jmprel_size as usize / rela_entry_size;
debug!("Processing {} JUMP_SLOT relocations", jmprel_count);
for i in 0..jmprel_count {
let entry_offset = jmprel_offset + i * rela_entry_size;
if entry_offset + rela_entry_size > (cache.location().len().unwrap_or(0) as usize) {
break;
}
let mut entry_data = vec![0u8; rela_entry_size];
cache.read_at(&mut entry_data, entry_offset as u64)?;
let offset = u64::from_le_bytes(entry_data[0..8].try_into().unwrap()) as usize;
let info = u64::from_le_bytes(entry_data[8..16].try_into().unwrap());
let _addend = i64::from_le_bytes(entry_data[16..24].try_into().unwrap());
let reloc_type = (info & 0xffffffff) as u32;
let sym_idx = (info >> 32) as usize;
match reloc_type {
R_RISCV_JUMP_SLOT => {
if symtab_addr == 0 || strtab_addr == 0 {
debug!("Missing symtab/strtab for JUMP_SLOT");
continue;
}
let sym_file_offset =
vaddr_to_file_offset(symtab_addr, ph).ok_or(AxError::InvalidData)?;
let sym_entry_offset = sym_file_offset + sym_idx * 24;
let file_len = cache.location().len().unwrap_or(0) as usize;
if sym_entry_offset + 24 > file_len {
continue;
}
let mut sym_data = vec![0u8; 24];
cache.read_at(&mut sym_data, sym_entry_offset as u64)?;
let st_value = u64::from_le_bytes(sym_data[8..16].try_into().unwrap());
if st_value == 0 {
continue;
}
let target = base + offset;
let value = base as u64 + st_value;
uspace.write(VirtAddr::from_usize(target), &value.to_le_bytes())?;
}
_ => {
debug!("Unsupported relocation type: {}", reloc_type);
}
}
}
}
Ok(())
}
#[cfg(not(target_arch = "riscv64"))]
fn apply_relocations(
_uspace: &mut AddrSpace,
_base: usize,
_cache: &CachedFile,
_ph: &[xmas_elf::program::ProgramHeader64],
) -> AxResult {
Ok(())
}
fn map_elf_error(err: &'static str) -> AxError {
debug!("Failed to parse ELF file: {err}");
AxError::InvalidExecutable
}
#[self_referencing]
struct ElfCacheEntry {
cache: CachedFile,
data: Vec<u8>,
#[borrows(data)]
#[covariant]
elf: ELFHeaders<'this>,
}
impl ElfCacheEntry {
fn load(loc: Location) -> AxResult<Result<Self, Vec<u8>>> {
let cache = CachedFile::get_or_create(loc);
let mut data = vec![0; 4096];
let read = cache.read_at(&mut data[..], 0)?;
data.truncate(read);
match ElfCacheEntry::try_new_or_recover::<AxError>(cache.clone(), data, |data| {
let builder = ELFHeadersBuilder::new(data).map_err(map_elf_error)?;
let range = builder.ph_range();
if range.end as usize <= data.len() {
builder.build(&data[range.start as usize..range.end as usize])
} else {
let mut buf = vec![0; (range.end - range.start) as usize];
cache.read_at(&mut buf[..], range.start)?;
builder.build(&buf)
}
.map_err(map_elf_error)
}) {
Ok(e) => Ok(Ok(e)),
Err((_, heads)) => Ok(Err(heads.data)),
}
}
}
const fn hwcap_value() -> usize {
#[cfg(target_arch = "loongarch64")]
{
const HWCAP_LOONGARCH_CPUCFG: usize = 1 << 0;
const HWCAP_LOONGARCH_LAM: usize = 1 << 1;
const HWCAP_LOONGARCH_UAL: usize = 1 << 2;
const HWCAP_LOONGARCH_FPU: usize = 1 << 3;
const HWCAP_LOONGARCH_LSX: usize = 1 << 4;
HWCAP_LOONGARCH_CPUCFG
| HWCAP_LOONGARCH_LAM
| HWCAP_LOONGARCH_UAL
| HWCAP_LOONGARCH_FPU
| HWCAP_LOONGARCH_LSX
}
#[cfg(target_arch = "riscv64")]
{
RISCV_COMPAT_HWCAP_IMAFDC
}
#[cfg(not(any(target_arch = "loongarch64", target_arch = "riscv64")))]
{
0
}
}
struct ElfLoader(LRUCache<ElfCacheEntry, 32>);
type LoadResult = Result<(VirtAddr, Vec<AuxEntry>), Vec<u8>>;
impl ElfLoader {
const fn new() -> Self {
Self(LRUCache::new())
}
fn load(&mut self, uspace: &mut AddrSpace, path: &str) -> AxResult<LoadResult> {
let loc = FS_CONTEXT.lock().resolve(path)?;
if !self.0.touch(|e| e.borrow_cache().location().ptr_eq(&loc)) {
match ElfCacheEntry::load(loc)? {
Ok(e) => {
self.0.insert(e);
}
Err(data) => {
return Ok(Err(data));
}
}
}
uspace.clear();
map_trampoline(uspace)?;
let entry = self.0.front().unwrap();
let ldso = if let Some(header) = entry
.borrow_elf()
.ph
.iter()
.find(|ph| ph.get_type() == Ok(xmas_elf::program::Type::Interp))
{
let cache = entry.borrow_cache();
let mut data = vec![0; header.file_size as usize];
let read = cache.read_at(&mut data[..], header.offset)?;
assert_eq!(data.len(), read);
let ldso = CStr::from_bytes_with_nul(&data)
.ok()
.and_then(|cstr| cstr.to_str().ok())
.ok_or(AxError::InvalidInput)?;
debug!("Loading dynamic linker: {ldso}");
Some(ldso.to_owned())
} else {
None
};
let (elf, ldso) = if let Some(ldso) = ldso {
let loc = FS_CONTEXT.lock().resolve(ldso)?;
if !self.0.touch(|e| e.borrow_cache().location().ptr_eq(&loc)) {
let e = ElfCacheEntry::load(loc)?.map_err(|_| AxError::InvalidInput)?;
self.0.insert(e);
}
let mut iter = self.0.iter();
let ldso = iter.next().unwrap();
let elf = iter.next().unwrap();
(elf, Some(ldso))
} else {
(entry, None)
};
let elf = map_elf(uspace, crate::config::USER_SPACE_BASE, elf)?;
let ldso = if ldso.is_some() {
let max_end = uspace
.areas()
.map(|area| area.end().as_usize())
.max()
.unwrap_or(crate::config::USER_SPACE_BASE);
let interp_base = (max_end + 0x100000 - 1) & !(0x100000 - 1);
ldso.map(|elf| map_elf(uspace, interp_base, elf))
.transpose()?
} else {
None
};
let entry = VirtAddr::from_usize(
ldso.as_ref()
.map_or_else(|| elf.entry(), |ldso| ldso.entry()),
);
let mut auxv = elf
.aux_vector(PAGE_SIZE_4K, ldso.map(|elf| elf.base()))
.collect::<Vec<_>>();
auxv.push(AuxEntry::new(AuxType::HWCAP, hwcap_value()));
Ok(Ok((entry, auxv)))
}
}
static ELF_LOADER: Mutex<ElfLoader> = Mutex::new(ElfLoader::new());
#[cfg(feature = "memtrack")]
pub fn clear_elf_cache() {
ELF_LOADER.lock().0.clear();
}
pub fn load_user_app(
uspace: &mut AddrSpace,
path: Option<&str>,
args: &[String],
envs: &[String],
) -> AxResult<(VirtAddr, VirtAddr, Vec<AuxEntry>)> {
let path = path
.or_else(|| args.first().map(String::as_str))
.ok_or(AxError::InvalidInput)?;
if path.ends_with(".sh") {
let new_args: Vec<String> = iter::once("/bin/sh".to_owned())
.chain(args.iter().cloned())
.collect();
return load_user_app(uspace, None, &new_args, envs);
}
let (entry, auxv) = match { ELF_LOADER.lock().load(uspace, path)? } {
Ok((entry, auxv)) => (entry, auxv),
Err(data) => {
if data.starts_with(b"#!") {
let head = &data[2..data.len().min(256)];
let pos = head.iter().position(|c| *c == b'\n').unwrap_or(head.len());
let line = core::str::from_utf8(&head[..pos]).map_err(|_| AxError::InvalidInput)?;
let new_args: Vec<String> = line
.trim()
.splitn(2, |c: char| c.is_ascii_whitespace())
.map(|s| s.trim_ascii().to_owned())
.chain(iter::once(path.to_owned()))
.chain(args.iter().skip(1).cloned())
.collect();
return load_user_app(uspace, None, &new_args, envs);
}
return Err(AxError::InvalidExecutable);
}
};
let ustack_top = VirtAddr::from_usize(crate::config::USER_STACK_TOP);
let ustack_size = crate::config::USER_STACK_SIZE;
let ustack_start = ustack_top - ustack_size;
debug!("Mapping user stack: {ustack_start:#x?} -> {ustack_top:#x?}");
uspace.map(
ustack_start,
ustack_size,
MappingFlags::READ | MappingFlags::WRITE | MappingFlags::USER,
false,
Backend::new_alloc(ustack_start, PageSize::Size4K, "[stack]"),
)?;
let stack_data = app_stack_region(args, envs, &auxv, ustack_top.into());
let user_sp = ustack_top - stack_data.len();
let user_sp_aligned = user_sp.align_down_4k();
uspace.populate_area(
user_sp_aligned,
(ustack_top - user_sp_aligned).align_up_4k(),
MappingFlags::READ | MappingFlags::WRITE,
)?;
uspace.write(user_sp, stack_data.as_slice())?;
let heap_start = VirtAddr::from_usize(crate::config::USER_HEAP_BASE);
let heap_size = crate::config::USER_HEAP_SIZE;
uspace.map(
heap_start,
heap_size,
MappingFlags::READ | MappingFlags::WRITE | MappingFlags::USER,
true,
Backend::new_alloc(heap_start, PageSize::Size4K, "[heap]"),
)?;
Ok((entry, user_sp, auxv))
}