exec with interpreter

This commit is contained in:
Ashwin Naren
2025-12-31 23:35:11 -08:00
parent a8200dc067
commit f0771dde4f
5 changed files with 132 additions and 49 deletions

View File

@@ -13,7 +13,7 @@ const MMAP_BASE: usize = 0x4000_0000_0000;
/// Manages mappings in a process's address space.
pub struct MemoryMap<AS: UserAddressSpace> {
vmas: BTreeMap<VA, VMArea>,
pub(super) vmas: BTreeMap<VA, VMArea>,
address_space: AS,
}

View File

@@ -48,10 +48,21 @@ impl<AS: UserAddressSpace> ProcessVM<AS> {
Ok(Self { mm, brk })
}
pub fn from_map(map: MemoryMap<AS>, brk: VA) -> Self {
pub fn from_map(map: MemoryMap<AS>) -> Self {
// Last entry will be the VMA with the highest address.
let brk = map
.vmas
.last_key_value()
.expect("No VMAs in map")
.1
.region
.end_address()
// VMAs should already be page-aligned, but just in case.
.align_up(PAGE_SIZE);
Self {
mm: map,
brk: VirtMemoryRegion::new(brk.align_up(PAGE_SIZE), 0),
brk: VirtMemoryRegion::new(brk, 0),
}
}

View File

@@ -172,7 +172,7 @@ impl VMAreaKind {
/// managing a process's memory layout.
#[derive(Clone, PartialEq)]
pub struct VMArea {
pub(super) region: VirtMemoryRegion,
pub region: VirtMemoryRegion,
pub(super) kind: VMAreaKind,
pub(super) permissions: VMAPermissions,
}
@@ -205,11 +205,15 @@ impl VMArea {
/// * `f`: A handle to the ELF file's inode.
/// * `hdr`: The ELF program header (`LOAD` segment) to create the VMA from.
/// * `endian`: The endianness of the ELF file, for correctly parsing header fields.
/// * `address_bias`: A bias added to the VAs of the segment.
pub fn from_pheader<E: Endian>(
f: Arc<dyn Inode>,
hdr: ProgramHeader64<E>,
endian: E,
address_bias: Option<usize>,
) -> VMArea {
let address_bias = address_bias.unwrap_or(0);
let mut permissions = VMAPermissions {
read: false,
write: false,
@@ -229,7 +233,7 @@ impl VMArea {
}
let mappable_region = VirtMemoryRegion::new(
VA::from_value(hdr.p_vaddr(endian) as usize),
VA::from_value(hdr.p_vaddr(endian) as usize + address_bias),
hdr.p_memsz(endian) as usize,
)
.to_mappable_region();
@@ -446,6 +450,11 @@ impl VMArea {
VMAreaKind::Anon => new_vma,
}
}
/// Return the virtual memory region managed by this VMA.
pub fn region(&self) -> VirtMemoryRegion {
self.region
}
}
#[cfg(test)]

View File

@@ -17,7 +17,6 @@ const PROT_READ: u64 = 1;
const PROT_WRITE: u64 = 2;
const PROT_EXEC: u64 = 4;
const MAP_FILE: u64 = 0x0000;
const MAP_SHARED: u64 = 0x0001;
const MAP_PRIVATE: u64 = 0x0002;
const MAP_FIXED: u64 = 0x0010;
@@ -86,9 +85,10 @@ pub async fn sys_mmap(
let requested_len = len as usize;
let kind = if flags & (MAP_ANON | MAP_ANONYMOUS) != 0 {
let kind = if (flags & (MAP_ANON | MAP_ANONYMOUS)) != 0 {
VMAreaKind::Anon
} else if flags == MAP_FILE {
} else {
// File-backed mapping: require a valid fd and use the provided offset.
let fd = current_task()
.fd_table
.lock_save_irq()
@@ -98,9 +98,6 @@ pub async fn sys_mmap(
let inode = fd.inode().ok_or(KernelError::BadFd)?;
VMAreaKind::new_file(inode, offset, len)
} else {
// One of MAP_FILE or MAP_ANONYMOUS must be set.
return Err(KernelError::InvalidValue);
};
let address_request = if addr.is_null() {

View File

@@ -31,6 +31,8 @@ use libkernel::{
region::VirtMemoryRegion,
},
};
use object::Endian;
use object::elf::{ET_DYN, ProgramHeader64};
use object::{
LittleEndian,
elf::{self, PT_LOAD},
@@ -39,10 +41,44 @@ use object::{
mod auxv;
const LINKER_BIAS: usize = 0x0000_7000_0000_0000;
const PROG_BIAS: usize = 0x0000_5000_0000_0000;
const STACK_END: usize = 0x0000_8000_0000_0000;
const STACK_SZ: usize = 0x2000 * 0x400;
const STACK_START: usize = STACK_END - STACK_SZ;
/// Process a set of progream headers from an ELF. Create VMAs for all `PT_LOAD`
/// segments, optionally applying `bias` to the load address.
///
/// If a VMA was found that contains the headers themselves, the address of the
/// *VMA* is returned.
fn process_prog_headers<E: Endian>(
hdrs: &[ProgramHeader64<E>],
vmas: &mut Vec<VMArea>,
bias: Option<usize>,
elf_file: Arc<dyn Inode>,
endian: E,
) -> Option<VA> {
let mut hdr_addr = None;
for hdr in hdrs {
if hdr.p_type(endian) == PT_LOAD {
let vma = VMArea::from_pheader(elf_file.clone(), *hdr, endian, bias);
// Find PHDR: Assumption segment with p_offset == 0 contains
// headers.
if hdr.p_offset.get(endian) == 0 {
hdr_addr = Some(vma.region().start_address());
}
vmas.push(vma);
}
}
hdr_addr
}
pub async fn kernel_exec(
inode: Arc<dyn Inode>,
argv: Vec<String>,
@@ -69,7 +105,7 @@ pub async fn kernel_exec(
// Detect PT_INTERP (dynamic linker) if present
let mut interp_path: Option<String> = None;
for hdr in hdrs {
for hdr in hdrs.iter() {
if hdr.p_type(endian) == elf::PT_INTERP {
let off = hdr.p_offset(endian) as usize;
let filesz = hdr.p_filesz(endian) as usize;
@@ -87,46 +123,45 @@ pub async fn kernel_exec(
}
}
if let Some(path) = interp_path {
panic!("Dynamic linker not supported yet: {}", path);
// return exec_with_interp(inode, &elf, endian, &ph_buf, &hdrs, path, argv, envp).await;
}
// Setup a program bias for PIE.
let main_bias = if elf.e_type.get(endian) == ET_DYN {
Some(PROG_BIAS)
} else {
None
};
// static ELF ...
let mut auxv = Vec::new();
// Push program header params (for the main executable)
auxv.push(AT_PHNUM);
auxv.push(elf.e_phnum.get(endian) as _);
auxv.push(AT_PHENT);
auxv.push(elf.e_phentsize(endian) as _);
let mut auxv = vec![
AT_PHNUM,
elf.e_phnum.get(endian) as _,
AT_PHENT,
elf.e_phentsize(endian) as _,
];
let mut vmas = Vec::new();
let mut highest_addr = 0;
for hdr in hdrs {
let kind = hdr.p_type(endian);
if kind == PT_LOAD {
vmas.push(VMArea::from_pheader(inode.clone(), *hdr, endian));
if hdr.p_offset.get(endian) == 0 {
// TODO: potentially more validation that this VA will contain
// the program headers.
auxv.push(AT_PHDR);
auxv.push(hdr.p_vaddr.get(endian) + elf.e_phoff.get(endian));
}
let mapping_end = hdr.p_vaddr(endian) + hdr.p_memsz(endian);
if mapping_end > highest_addr {
highest_addr = mapping_end;
}
}
// Process the binary progream headers.
if let Some(hdr_addr) = process_prog_headers(hdrs, &mut vmas, main_bias, inode.clone(), endian)
{
auxv.push(AT_PHDR);
auxv.push(hdr_addr.add_bytes(elf.e_phoff(endian) as _).value() as _);
}
let main_entry = VA::from_value(elf.e_entry(endian) as usize + main_bias.unwrap_or(0));
// AT_ENTRY is the same in the static and interp case.
auxv.push(AT_ENTRY);
auxv.push(elf.e_entry(endian) as u64);
auxv.push(main_entry.value() as _);
let entry_addr = if let Some(path) = interp_path {
auxv.push(AT_BASE);
auxv.push(LINKER_BIAS as _);
// Returns the entry address of the interp program.
process_interp(path, &mut vmas).await?
} else {
// Otherwise, it's just the binary itself.
main_entry
};
vmas.push(VMArea::new(
VirtMemoryRegion::new(VA::from_value(STACK_START), STACK_SZ),
@@ -135,12 +170,10 @@ pub async fn kernel_exec(
));
let mut mem_map = MemoryMap::from_vmas(vmas)?;
let stack_ptr = setup_user_stack(&mut mem_map, &argv, &envp, auxv)?;
let user_ctx =
ArchImpl::new_user_context(VA::from_value(elf.e_entry(endian) as usize), stack_ptr);
let mut vm = ProcessVM::from_map(mem_map, VA::from_value(highest_addr as usize));
let user_ctx = ArchImpl::new_user_context(entry_addr, stack_ptr);
let mut vm = ProcessVM::from_map(mem_map);
// We don't have to worry about actually calling for a full context switch
// here. Parts of the old process that are replaced will go out of scope and
@@ -274,6 +307,39 @@ fn setup_user_stack(
Ok(VA::from_value(final_sp_val))
}
// Dynamic linker path: map PT_INTERP interpreter and return start address of
// the interpreter program.
async fn process_interp(interp_path: String, vmas: &mut Vec<VMArea>) -> Result<VA> {
// Resolve interpreter path from root; this assumes interp_path is absolute.
let task = current_task_shared();
let path = Path::new(&interp_path);
let interp_inode = VFS.resolve_path(path, VFS.root_inode(), &task).await?;
// Parse interpreter ELF header
let mut hdr_buf = [0u8; core::mem::size_of::<elf::FileHeader64<LittleEndian>>()];
interp_inode.read_at(0, &mut hdr_buf).await?;
let interp_elf = elf::FileHeader64::<LittleEndian>::parse(&hdr_buf[..])
.map_err(|_| ExecError::InvalidElfFormat)?;
let iendian = interp_elf.endian().unwrap();
// Read interpreter program headers
let interp_ph_table_size = interp_elf.e_phnum.get(iendian) as usize
* interp_elf.e_phentsize.get(iendian) as usize
+ interp_elf.e_phoff.get(iendian) as usize;
let mut interp_ph_buf = vec![0u8; interp_ph_table_size];
interp_inode.read_at(0, &mut interp_ph_buf).await?;
let interp_hdrs = interp_elf
.program_headers(iendian, &interp_ph_buf[..])
.map_err(|_| ExecError::InvalidPHdrFormat)?;
// Build VMAs for interpreter
process_prog_headers(interp_hdrs, vmas, Some(LINKER_BIAS), interp_inode, iendian);
let interp_entry = VA::from_value(LINKER_BIAS + interp_elf.e_entry(iendian) as usize);
Ok(interp_entry)
}
pub async fn sys_execve(
path: TUA<c_char>,
mut usr_argv: TUA<TUA<c_char>>,