diff --git a/libkernel/src/memory/proc_vm/memory_map/mod.rs b/libkernel/src/memory/proc_vm/memory_map/mod.rs index 7498ab1..95c35d1 100644 --- a/libkernel/src/memory/proc_vm/memory_map/mod.rs +++ b/libkernel/src/memory/proc_vm/memory_map/mod.rs @@ -13,7 +13,7 @@ const MMAP_BASE: usize = 0x4000_0000_0000; /// Manages mappings in a process's address space. pub struct MemoryMap { - vmas: BTreeMap, + pub(super) vmas: BTreeMap, address_space: AS, } diff --git a/libkernel/src/memory/proc_vm/mod.rs b/libkernel/src/memory/proc_vm/mod.rs index d1a71e2..ecfb5b2 100644 --- a/libkernel/src/memory/proc_vm/mod.rs +++ b/libkernel/src/memory/proc_vm/mod.rs @@ -48,10 +48,21 @@ impl ProcessVM { Ok(Self { mm, brk }) } - pub fn from_map(map: MemoryMap, brk: VA) -> Self { + pub fn from_map(map: MemoryMap) -> 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), } } diff --git a/libkernel/src/memory/proc_vm/vmarea.rs b/libkernel/src/memory/proc_vm/vmarea.rs index d3a8d44..928608f 100644 --- a/libkernel/src/memory/proc_vm/vmarea.rs +++ b/libkernel/src/memory/proc_vm/vmarea.rs @@ -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( f: Arc, hdr: ProgramHeader64, endian: E, + address_bias: Option, ) -> 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)] diff --git a/src/memory/mmap.rs b/src/memory/mmap.rs index 74c2809..46ee046 100644 --- a/src/memory/mmap.rs +++ b/src/memory/mmap.rs @@ -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() { diff --git a/src/process/exec.rs b/src/process/exec.rs index a741575..67299f1 100644 --- a/src/process/exec.rs +++ b/src/process/exec.rs @@ -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( + hdrs: &[ProgramHeader64], + vmas: &mut Vec, + bias: Option, + elf_file: Arc, + endian: E, +) -> Option { + 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, argv: Vec, @@ -69,7 +105,7 @@ pub async fn kernel_exec( // Detect PT_INTERP (dynamic linker) if present let mut interp_path: Option = 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) -> Result { + // 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::>()]; + interp_inode.read_at(0, &mut hdr_buf).await?; + let interp_elf = elf::FileHeader64::::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, mut usr_argv: TUA>,