mirror of
https://github.com/asterinas/asterinas.git
synced 2025-06-30 10:43:56 +00:00
Refactor project structure
This commit is contained in:
committed by
Tate, Hongliang Tian
parent
bd878dd1c9
commit
e3c227ae06
96
kernel/aster-nix/src/process/program_loader/elf/aux_vec.rs
Normal file
96
kernel/aster-nix/src/process/program_loader/elf/aux_vec.rs
Normal file
@ -0,0 +1,96 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
/// This implementation is from occlum.
|
||||
|
||||
/// Auxiliary Vector.
|
||||
///
|
||||
/// # What is Auxiliary Vector?
|
||||
///
|
||||
/// Here is a concise description of Auxiliary Vector from GNU's manual:
|
||||
///
|
||||
/// > When a program is executed, it receives information from the operating system
|
||||
/// about the environment in which it is operating. The form of this information
|
||||
/// is a table of key-value pairs, where the keys are from the set of ‘AT_’
|
||||
/// values in elf.h.
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
#[repr(u8)]
|
||||
pub enum AuxKey {
|
||||
AT_NULL = 0, /* end of vector */
|
||||
AT_IGNORE = 1, /* entry should be ignored */
|
||||
AT_EXECFD = 2, /* file descriptor of program */
|
||||
AT_PHDR = 3, /* program headers for program */
|
||||
AT_PHENT = 4, /* size of program header entry */
|
||||
AT_PHNUM = 5, /* number of program headers */
|
||||
AT_PAGESZ = 6, /* system page size */
|
||||
AT_BASE = 7, /* base address of interpreter */
|
||||
AT_FLAGS = 8, /* flags */
|
||||
AT_ENTRY = 9, /* entry point of program */
|
||||
AT_NOTELF = 10, /* program is not ELF */
|
||||
AT_UID = 11, /* real uid */
|
||||
AT_EUID = 12, /* effective uid */
|
||||
AT_GID = 13, /* real gid */
|
||||
AT_EGID = 14, /* effective gid */
|
||||
AT_PLATFORM = 15, /* string identifying CPU for optimizations */
|
||||
AT_HWCAP = 16, /* arch dependent hints at CPU capabilities */
|
||||
AT_CLKTCK = 17, /* frequency at which times() increments */
|
||||
|
||||
/* 18...22 not used */
|
||||
AT_SECURE = 23, /* secure mode boolean */
|
||||
AT_BASE_PLATFORM = 24, /* string identifying real platform, may
|
||||
* differ from AT_PLATFORM. */
|
||||
AT_RANDOM = 25, /* address of 16 random bytes */
|
||||
AT_HWCAP2 = 26, /* extension of AT_HWCAP */
|
||||
|
||||
/* 28...30 not used */
|
||||
AT_EXECFN = 31, /* filename of program */
|
||||
AT_SYSINFO = 32,
|
||||
AT_SYSINFO_EHDR = 33, /* the start address of the page containing the VDSO */
|
||||
}
|
||||
|
||||
impl AuxKey {
|
||||
pub fn as_u64(&self) -> u64 {
|
||||
*self as u64
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, Debug)]
|
||||
pub struct AuxVec {
|
||||
table: BTreeMap<AuxKey, u64>,
|
||||
}
|
||||
|
||||
impl AuxVec {
|
||||
pub const fn new() -> AuxVec {
|
||||
AuxVec {
|
||||
table: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AuxVec {
|
||||
pub fn set(&mut self, key: AuxKey, val: u64) -> Result<()> {
|
||||
if key == AuxKey::AT_NULL || key == AuxKey::AT_IGNORE {
|
||||
return_errno_with_message!(Errno::EINVAL, "Illegal key");
|
||||
}
|
||||
self.table
|
||||
.entry(key)
|
||||
.and_modify(|val_mut| *val_mut = val)
|
||||
.or_insert(val);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get(&self, key: AuxKey) -> Option<u64> {
|
||||
self.table.get(&key).copied()
|
||||
}
|
||||
|
||||
pub fn del(&mut self, key: AuxKey) -> Option<u64> {
|
||||
self.table.remove(&key)
|
||||
}
|
||||
|
||||
pub fn table(&self) -> &BTreeMap<AuxKey, u64> {
|
||||
&self.table
|
||||
}
|
||||
}
|
213
kernel/aster-nix/src/process/program_loader/elf/elf_file.rs
Normal file
213
kernel/aster-nix/src/process/program_loader/elf/elf_file.rs
Normal file
@ -0,0 +1,213 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
/// A wrapper of xmas_elf's elf parsing
|
||||
use xmas_elf::{
|
||||
header::{self, Header, HeaderPt1, HeaderPt2, HeaderPt2_, Machine_, Type_},
|
||||
program::{self, ProgramHeader64},
|
||||
};
|
||||
|
||||
use crate::prelude::*;
|
||||
pub struct Elf {
|
||||
pub elf_header: ElfHeader,
|
||||
pub program_headers: Vec<ProgramHeader64>,
|
||||
}
|
||||
|
||||
impl Elf {
|
||||
pub fn parse_elf(input: &[u8]) -> Result<Self> {
|
||||
// first parse elf header
|
||||
// The elf header is usually 64 bytes. pt1 is 16bytes and pt2 is 48 bytes.
|
||||
// We require 128 bytes here is to keep consistency with linux implementations.
|
||||
debug_assert!(input.len() >= 128);
|
||||
let header = xmas_elf::header::parse_header(input)
|
||||
.map_err(|_| Error::with_message(Errno::ENOEXEC, "parse elf header fails"))?;
|
||||
let elf_header = ElfHeader::parse_elf_header(header)?;
|
||||
check_elf_header(&elf_header)?;
|
||||
// than parse the program headers table
|
||||
// FIXME: we should acquire enough pages before parse
|
||||
let ph_offset = elf_header.pt2.ph_offset;
|
||||
let ph_count = elf_header.pt2.ph_count;
|
||||
let ph_entry_size = elf_header.pt2.ph_entry_size;
|
||||
debug_assert!(
|
||||
input.len() >= ph_offset as usize + ph_count as usize * ph_entry_size as usize
|
||||
);
|
||||
let mut program_headers = Vec::with_capacity(ph_count as usize);
|
||||
for index in 0..ph_count {
|
||||
let program_header = xmas_elf::program::parse_program_header(input, header, index)
|
||||
.map_err(|_| Error::with_message(Errno::ENOEXEC, "parse program header fails"))?;
|
||||
let ph64 = match program_header {
|
||||
xmas_elf::program::ProgramHeader::Ph64(ph64) => *ph64,
|
||||
xmas_elf::program::ProgramHeader::Ph32(_) => {
|
||||
return_errno_with_message!(Errno::ENOEXEC, "Not 64 byte executable")
|
||||
}
|
||||
};
|
||||
program_headers.push(ph64);
|
||||
}
|
||||
Ok(Self {
|
||||
elf_header,
|
||||
program_headers,
|
||||
})
|
||||
}
|
||||
|
||||
// The following info is used to setup init stack
|
||||
/// the entry point of the elf
|
||||
pub fn entry_point(&self) -> Vaddr {
|
||||
self.elf_header.pt2.entry_point as Vaddr
|
||||
}
|
||||
/// program header table offset
|
||||
pub fn ph_off(&self) -> u64 {
|
||||
self.elf_header.pt2.ph_offset
|
||||
}
|
||||
/// number of program headers
|
||||
pub fn ph_count(&self) -> u16 {
|
||||
self.elf_header.pt2.ph_count
|
||||
}
|
||||
/// The size of a program header
|
||||
pub fn ph_ent(&self) -> u16 {
|
||||
self.elf_header.pt2.ph_entry_size
|
||||
}
|
||||
|
||||
/// The virtual addr of program headers table address
|
||||
pub fn ph_addr(&self) -> Result<Vaddr> {
|
||||
let ph_offset = self.ph_off();
|
||||
for program_header in &self.program_headers {
|
||||
if program_header.offset <= ph_offset
|
||||
&& ph_offset < program_header.offset + program_header.file_size
|
||||
{
|
||||
return Ok(
|
||||
(ph_offset - program_header.offset + program_header.virtual_addr) as Vaddr,
|
||||
);
|
||||
}
|
||||
}
|
||||
return_errno_with_message!(
|
||||
Errno::ENOEXEC,
|
||||
"can not find program header table address in elf"
|
||||
);
|
||||
}
|
||||
|
||||
/// whether the elf is a shared object
|
||||
pub fn is_shared_object(&self) -> bool {
|
||||
self.elf_header.pt2.type_.as_type() == header::Type::SharedObject
|
||||
}
|
||||
|
||||
/// read the ldso path from the elf interpret section
|
||||
pub fn ldso_path(&self, file_header_buf: &[u8]) -> Result<String> {
|
||||
for program_header in &self.program_headers {
|
||||
let type_ = program_header.get_type().map_err(|_| {
|
||||
Error::with_message(Errno::ENOEXEC, "parse program header type fails")
|
||||
})?;
|
||||
if type_ == program::Type::Interp {
|
||||
let file_size = program_header.file_size as usize;
|
||||
let file_offset = program_header.offset as usize;
|
||||
debug_assert!(file_offset + file_size <= file_header_buf.len());
|
||||
let ldso = CStr::from_bytes_with_nul(
|
||||
&file_header_buf[file_offset..file_offset + file_size],
|
||||
)?;
|
||||
return Ok(ldso.to_string_lossy().to_string());
|
||||
}
|
||||
}
|
||||
return_errno_with_message!(
|
||||
Errno::ENOEXEC,
|
||||
"cannot find interpreter section in dyn-link program"
|
||||
)
|
||||
}
|
||||
|
||||
// An offset to be subtracted from ELF vaddr for PIE
|
||||
pub fn base_load_address_offset(&self) -> u64 {
|
||||
let phdr = self.program_headers.first().unwrap();
|
||||
phdr.virtual_addr - phdr.offset
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ElfHeader {
|
||||
pub pt1: HeaderPt1,
|
||||
pub pt2: HeaderPt2_64,
|
||||
}
|
||||
|
||||
impl ElfHeader {
|
||||
fn parse_elf_header(header: Header) -> Result<Self> {
|
||||
let pt1 = *header.pt1;
|
||||
let pt2 = match header.pt2 {
|
||||
HeaderPt2::Header64(header_pt2) => {
|
||||
let HeaderPt2_ {
|
||||
type_,
|
||||
machine,
|
||||
version,
|
||||
entry_point,
|
||||
ph_offset,
|
||||
sh_offset,
|
||||
flags,
|
||||
header_size,
|
||||
ph_entry_size,
|
||||
ph_count,
|
||||
sh_entry_size,
|
||||
sh_count,
|
||||
sh_str_index,
|
||||
} = header_pt2;
|
||||
HeaderPt2_64 {
|
||||
type_: *type_,
|
||||
machine: *machine,
|
||||
version: *version,
|
||||
entry_point: *entry_point,
|
||||
ph_offset: *ph_offset,
|
||||
sh_offset: *sh_offset,
|
||||
flags: *flags,
|
||||
header_size: *header_size,
|
||||
ph_entry_size: *ph_entry_size,
|
||||
ph_count: *ph_count,
|
||||
sh_entry_size: *sh_entry_size,
|
||||
sh_count: *sh_count,
|
||||
sh_str_index: *sh_str_index,
|
||||
}
|
||||
}
|
||||
_ => return_errno_with_message!(Errno::ENOEXEC, "parse elf header failed"),
|
||||
};
|
||||
Ok(ElfHeader { pt1, pt2 })
|
||||
}
|
||||
}
|
||||
|
||||
pub struct HeaderPt2_64 {
|
||||
pub type_: Type_,
|
||||
pub machine: Machine_,
|
||||
pub version: u32,
|
||||
pub entry_point: u64,
|
||||
pub ph_offset: u64,
|
||||
pub sh_offset: u64,
|
||||
pub flags: u32,
|
||||
pub header_size: u16,
|
||||
pub ph_entry_size: u16,
|
||||
pub ph_count: u16,
|
||||
pub sh_entry_size: u16,
|
||||
pub sh_count: u16,
|
||||
pub sh_str_index: u16,
|
||||
}
|
||||
|
||||
fn check_elf_header(elf_header: &ElfHeader) -> Result<()> {
|
||||
// 64bit
|
||||
debug_assert_eq!(elf_header.pt1.class(), header::Class::SixtyFour);
|
||||
if elf_header.pt1.class() != header::Class::SixtyFour {
|
||||
return_errno_with_message!(Errno::ENOEXEC, "Not 64 byte executable");
|
||||
}
|
||||
// little endian
|
||||
debug_assert_eq!(elf_header.pt1.data(), header::Data::LittleEndian);
|
||||
if elf_header.pt1.data() != header::Data::LittleEndian {
|
||||
return_errno_with_message!(Errno::ENOEXEC, "Not little endian executable");
|
||||
}
|
||||
// system V ABI
|
||||
// debug_assert_eq!(elf_header.pt1.os_abi(), header::OsAbi::SystemV);
|
||||
// if elf_header.pt1.os_abi() != header::OsAbi::SystemV {
|
||||
// return Error::new(Errno::ENOEXEC);
|
||||
// }
|
||||
// x86_64 architecture
|
||||
debug_assert_eq!(elf_header.pt2.machine.as_machine(), header::Machine::X86_64);
|
||||
if elf_header.pt2.machine.as_machine() != header::Machine::X86_64 {
|
||||
return_errno_with_message!(Errno::ENOEXEC, "Not x86_64 executable");
|
||||
}
|
||||
// Executable file or shared object
|
||||
let elf_type = elf_header.pt2.type_.as_type();
|
||||
debug_assert!(elf_type == header::Type::Executable || elf_type == header::Type::SharedObject);
|
||||
if elf_type != header::Type::Executable && elf_type != header::Type::SharedObject {
|
||||
return_errno_with_message!(Errno::ENOEXEC, "Not executable file");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
360
kernel/aster-nix/src/process/program_loader/elf/init_stack.rs
Normal file
360
kernel/aster-nix/src/process/program_loader/elf/init_stack.rs
Normal file
@ -0,0 +1,360 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
//! This module defines the process initial stack.
|
||||
//! The process initial stack, contains arguments, environmental variables and auxiliary vectors
|
||||
//! The data layout of init stack can be seen in Figure 3.9 in https://uclibc.org/docs/psABI-x86_64.pdf
|
||||
|
||||
use core::mem;
|
||||
|
||||
use align_ext::AlignExt;
|
||||
use aster_frame::vm::{VmIo, VmPerm};
|
||||
use aster_rights::{Full, Rights};
|
||||
|
||||
use super::{
|
||||
aux_vec::{AuxKey, AuxVec},
|
||||
elf_file::Elf,
|
||||
load_elf::LdsoLoadInfo,
|
||||
};
|
||||
use crate::{
|
||||
prelude::*,
|
||||
vm::{perms::VmPerms, vmar::Vmar, vmo::VmoOptions},
|
||||
};
|
||||
|
||||
pub const INIT_STACK_BASE: Vaddr = 0x0000_0000_2000_0000;
|
||||
pub const INIT_STACK_SIZE: usize = 0x1000 * 16; // 64KB
|
||||
|
||||
/*
|
||||
* The initial stack of a process looks like below(This figure is from occlum):
|
||||
*
|
||||
*
|
||||
* +---------------------+ <------+ Top of stack
|
||||
* | | (high address)
|
||||
* | Null-terminated |
|
||||
* | strings referenced |
|
||||
* | by variables below |
|
||||
* | |
|
||||
* +---------------------+
|
||||
* | AT_NULL |
|
||||
* +---------------------+
|
||||
* | AT_NULL |
|
||||
* +---------------------+
|
||||
* | ... |
|
||||
* +---------------------+
|
||||
* | aux_val[0] |
|
||||
* +---------------------+
|
||||
* | aux_key[0] | <------+ Auxiliary table
|
||||
* +---------------------+
|
||||
* | NULL |
|
||||
* +---------------------+
|
||||
* | ... |
|
||||
* +---------------------+
|
||||
* | char* envp[0] | <------+ Environment variables
|
||||
* +---------------------+
|
||||
* | NULL |
|
||||
* +---------------------+
|
||||
* | char* argv[argc-1] |
|
||||
* +---------------------+
|
||||
* | ... |
|
||||
* +---------------------+
|
||||
* | char* argv[0] |
|
||||
* +---------------------+
|
||||
* | long argc | <------+ Program arguments
|
||||
* +---------------------+
|
||||
* | |
|
||||
* | |
|
||||
* + +
|
||||
*
|
||||
*/
|
||||
pub struct InitStack {
|
||||
/// The high address of init stack
|
||||
init_stack_top: Vaddr,
|
||||
init_stack_size: usize,
|
||||
pos: usize,
|
||||
/// Command line args
|
||||
argv: Vec<CString>,
|
||||
/// Environmental variables
|
||||
envp: Vec<CString>,
|
||||
}
|
||||
|
||||
impl InitStack {
|
||||
/// initialize user stack on base addr
|
||||
pub fn new(
|
||||
init_stack_top: Vaddr,
|
||||
init_stack_size: usize,
|
||||
argv: Vec<CString>,
|
||||
envp: Vec<CString>,
|
||||
) -> Self {
|
||||
Self {
|
||||
init_stack_top,
|
||||
init_stack_size,
|
||||
pos: init_stack_top,
|
||||
argv,
|
||||
envp,
|
||||
}
|
||||
}
|
||||
|
||||
/// This function only work for first process
|
||||
pub fn new_default_config(argv: Vec<CString>, envp: Vec<CString>) -> Self {
|
||||
let init_stack_top = INIT_STACK_BASE - PAGE_SIZE;
|
||||
let init_stack_size = INIT_STACK_SIZE;
|
||||
InitStack::new(init_stack_top, init_stack_size, argv, envp)
|
||||
}
|
||||
|
||||
/// the user stack top(high address), used to setup rsp
|
||||
pub fn user_stack_top(&self) -> Vaddr {
|
||||
let stack_top = self.pos;
|
||||
// ensure stack top is 16-bytes aligned
|
||||
debug_assert!(stack_top & !0xf == stack_top);
|
||||
stack_top
|
||||
}
|
||||
|
||||
/// the user stack bottom(low address)
|
||||
const fn user_stack_bottom(&self) -> Vaddr {
|
||||
self.init_stack_top - self.init_stack_size
|
||||
}
|
||||
|
||||
pub fn init(
|
||||
&mut self,
|
||||
root_vmar: &Vmar<Full>,
|
||||
elf: &Elf,
|
||||
ldso_load_info: &Option<LdsoLoadInfo>,
|
||||
aux_vec: &mut AuxVec,
|
||||
) -> Result<()> {
|
||||
self.map_and_zeroed(root_vmar)?;
|
||||
self.write_stack_content(root_vmar, elf, ldso_load_info, aux_vec)?;
|
||||
self.debug_print_stack_content(root_vmar);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn map_and_zeroed(&self, root_vmar: &Vmar<Full>) -> Result<()> {
|
||||
let vmo_options = VmoOptions::<Rights>::new(self.init_stack_size);
|
||||
let vmo = vmo_options.alloc()?;
|
||||
vmo.clear(0..vmo.size())?;
|
||||
let perms = VmPerms::READ | VmPerms::WRITE;
|
||||
let vmar_map_options = root_vmar
|
||||
.new_map(vmo, perms)?
|
||||
.offset(self.user_stack_bottom());
|
||||
vmar_map_options.build().unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Libc ABI requires 16-byte alignment of the stack entrypoint.
|
||||
/// Current postion of the stack is 8-byte aligned already, insert 8 byte
|
||||
/// to meet the requirement if necessary.
|
||||
fn adjust_stack_alignment(
|
||||
&mut self,
|
||||
root_vmar: &Vmar<Full>,
|
||||
envp_pointers: &[u64],
|
||||
argv_pointers: &[u64],
|
||||
aux_vec: &AuxVec,
|
||||
) -> Result<()> {
|
||||
// ensure 8-byte alignment
|
||||
self.write_u64(0, root_vmar)?;
|
||||
let auxvec_size = (aux_vec.table().len() + 1) * (mem::size_of::<u64>() * 2);
|
||||
let envp_pointers_size = (envp_pointers.len() + 1) * mem::size_of::<u64>();
|
||||
let argv_pointers_size = (argv_pointers.len() + 1) * mem::size_of::<u64>();
|
||||
let argc_size = mem::size_of::<u64>();
|
||||
let to_write_size = auxvec_size + envp_pointers_size + argv_pointers_size + argc_size;
|
||||
if (self.pos - to_write_size) % 16 != 0 {
|
||||
self.write_u64(0, root_vmar)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_stack_content(
|
||||
&mut self,
|
||||
root_vmar: &Vmar<Full>,
|
||||
elf: &Elf,
|
||||
ldso_load_info: &Option<LdsoLoadInfo>,
|
||||
aux_vec: &mut AuxVec,
|
||||
) -> Result<()> {
|
||||
// write a zero page. When a user program tries to read a cstring(like argv) from init stack,
|
||||
// it will typically read 4096 bytes and then find the first '\0' in the buffer
|
||||
// (we now read 128 bytes, which is set by MAX_FILENAME_LEN).
|
||||
// If we don't have this zero page, the read may go into guard page,
|
||||
// which will cause unrecoverable page fault(The guard page is not backed up by any vmo).
|
||||
// So we add a zero page here, to ensure the read will not go into guard page.
|
||||
// FIXME: Some other OSes put the first page of excutable file here.
|
||||
self.write_bytes(&[0u8; PAGE_SIZE], root_vmar)?;
|
||||
// write envp string
|
||||
let envp_pointers = self.write_envp_strings(root_vmar)?;
|
||||
// write argv string
|
||||
let argv_pointers = self.write_argv_strings(root_vmar)?;
|
||||
// write random value
|
||||
let random_value = generate_random_for_aux_vec();
|
||||
let random_value_pointer = self.write_bytes(&random_value, root_vmar)?;
|
||||
aux_vec.set(AuxKey::AT_RANDOM, random_value_pointer)?;
|
||||
if let Some(ldso_load_info) = ldso_load_info {
|
||||
let ldso_base = ldso_load_info.base_addr();
|
||||
aux_vec.set(AuxKey::AT_BASE, ldso_base as u64)?;
|
||||
}
|
||||
self.adjust_stack_alignment(root_vmar, &envp_pointers, &argv_pointers, aux_vec)?;
|
||||
self.write_aux_vec(root_vmar, aux_vec)?;
|
||||
self.write_envp_pointers(root_vmar, envp_pointers)?;
|
||||
self.write_argv_pointers(root_vmar, argv_pointers)?;
|
||||
// write argc
|
||||
let argc = self.argc();
|
||||
self.write_u64(argc, root_vmar)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_envp_strings(&mut self, root_vmar: &Vmar<Full>) -> Result<Vec<u64>> {
|
||||
let envp = self.envp.to_vec();
|
||||
let mut envp_pointers = Vec::with_capacity(envp.len());
|
||||
for envp in envp.iter() {
|
||||
let pointer = self.write_cstring(envp, root_vmar)?;
|
||||
envp_pointers.push(pointer);
|
||||
}
|
||||
Ok(envp_pointers)
|
||||
}
|
||||
|
||||
fn write_argv_strings(&mut self, root_vmar: &Vmar<Full>) -> Result<Vec<u64>> {
|
||||
let argv = self.argv.to_vec();
|
||||
let mut argv_pointers = Vec::with_capacity(argv.len());
|
||||
for argv in argv.iter().rev() {
|
||||
let pointer = self.write_cstring(argv, root_vmar)?;
|
||||
debug!("argv address = 0x{:x}", pointer);
|
||||
argv_pointers.push(pointer);
|
||||
}
|
||||
argv_pointers.reverse();
|
||||
Ok(argv_pointers)
|
||||
}
|
||||
|
||||
fn write_aux_vec(&mut self, root_vmar: &Vmar<Full>, aux_vec: &AuxVec) -> Result<()> {
|
||||
// Write NULL auxilary
|
||||
self.write_u64(0, root_vmar)?;
|
||||
self.write_u64(AuxKey::AT_NULL as u64, root_vmar)?;
|
||||
// Write Auxiliary vectors
|
||||
let aux_vec: Vec<_> = aux_vec
|
||||
.table()
|
||||
.iter()
|
||||
.map(|(aux_key, aux_value)| (*aux_key, *aux_value))
|
||||
.collect();
|
||||
for (aux_key, aux_value) in aux_vec.iter() {
|
||||
self.write_u64(*aux_value, root_vmar)?;
|
||||
self.write_u64(*aux_key as u64, root_vmar)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_envp_pointers(
|
||||
&mut self,
|
||||
root_vmar: &Vmar<Full>,
|
||||
mut envp_pointers: Vec<u64>,
|
||||
) -> Result<()> {
|
||||
// write NULL pointer
|
||||
self.write_u64(0, root_vmar)?;
|
||||
// write envp pointers
|
||||
envp_pointers.reverse();
|
||||
for envp_pointer in envp_pointers {
|
||||
self.write_u64(envp_pointer, root_vmar)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_argv_pointers(
|
||||
&mut self,
|
||||
root_vmar: &Vmar<Full>,
|
||||
mut argv_pointers: Vec<u64>,
|
||||
) -> Result<()> {
|
||||
// write 0
|
||||
self.write_u64(0, root_vmar)?;
|
||||
// write argv pointers
|
||||
argv_pointers.reverse();
|
||||
for argv_pointer in argv_pointers {
|
||||
self.write_u64(argv_pointer, root_vmar)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Command line argument counter
|
||||
pub fn argc(&self) -> u64 {
|
||||
self.argv.len() as u64
|
||||
}
|
||||
|
||||
/// Command linke argument start address
|
||||
pub fn argv(&self) -> u64 {
|
||||
self.user_stack_top() as u64 + 8
|
||||
}
|
||||
|
||||
/// Environmental variables counter
|
||||
pub fn envc(&self) -> u64 {
|
||||
self.envp.len() as u64
|
||||
}
|
||||
|
||||
/// Environmental variables pointers
|
||||
pub fn envp(&self) -> u64 {
|
||||
0
|
||||
}
|
||||
|
||||
/// returns the top address of init stack.
|
||||
/// It should points to a fixed address.
|
||||
pub const fn init_stack_top(&self) -> Vaddr {
|
||||
self.init_stack_top
|
||||
}
|
||||
|
||||
/// returns the u64 start address
|
||||
fn write_u64(&mut self, val: u64, root_vmar: &Vmar<Full>) -> Result<u64> {
|
||||
let start_address = (self.pos - 8).align_down(8);
|
||||
self.pos = start_address;
|
||||
root_vmar.write_val(start_address, &val)?;
|
||||
Ok(self.pos as u64)
|
||||
}
|
||||
|
||||
fn write_bytes(&mut self, bytes: &[u8], root_vmar: &Vmar<Full>) -> Result<u64> {
|
||||
let len = bytes.len();
|
||||
self.pos -= len;
|
||||
root_vmar.write_bytes(self.pos, bytes)?;
|
||||
Ok(self.pos as u64)
|
||||
}
|
||||
|
||||
/// returns the string start address
|
||||
/// cstring will with end null byte.
|
||||
fn write_cstring(&mut self, val: &CString, root_vmar: &Vmar<Full>) -> Result<u64> {
|
||||
let bytes = val.as_bytes_with_nul();
|
||||
self.write_bytes(bytes, root_vmar)
|
||||
}
|
||||
|
||||
pub const fn perm() -> VmPerm {
|
||||
VmPerm::RWU
|
||||
}
|
||||
|
||||
fn debug_print_stack_content(&self, root_vmar: &Vmar<Full>) {
|
||||
debug!("print stack content:");
|
||||
let stack_top = self.user_stack_top();
|
||||
let argc = root_vmar.read_val::<u64>(stack_top).unwrap();
|
||||
debug!("argc = {}", argc);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init_aux_vec(elf: &Elf, elf_map_addr: Vaddr, vdso_text_base: Vaddr) -> Result<AuxVec> {
|
||||
let mut aux_vec = AuxVec::new();
|
||||
aux_vec.set(AuxKey::AT_PAGESZ, PAGE_SIZE as _)?;
|
||||
let ph_addr = if elf.is_shared_object() {
|
||||
elf.ph_addr()? + elf_map_addr
|
||||
} else {
|
||||
elf.ph_addr()?
|
||||
};
|
||||
aux_vec.set(AuxKey::AT_PHDR, ph_addr as u64)?;
|
||||
aux_vec.set(AuxKey::AT_PHNUM, elf.ph_count() as u64)?;
|
||||
aux_vec.set(AuxKey::AT_PHENT, elf.ph_ent() as u64)?;
|
||||
let elf_entry = if elf.is_shared_object() {
|
||||
let base_load_offset = elf.base_load_address_offset();
|
||||
elf.entry_point() + elf_map_addr - base_load_offset as usize
|
||||
} else {
|
||||
elf.entry_point()
|
||||
};
|
||||
aux_vec.set(AuxKey::AT_ENTRY, elf_entry as u64)?;
|
||||
aux_vec.set(AuxKey::AT_SYSINFO_EHDR, vdso_text_base as u64)?;
|
||||
Ok(aux_vec)
|
||||
}
|
||||
|
||||
/// generate random [u8; 16].
|
||||
/// FIXME: generate really random value. Now only return array with fixed values.
|
||||
fn generate_random_for_aux_vec() -> [u8; 16] {
|
||||
let mut rand_val = [0; 16];
|
||||
for i in 0..16u8 {
|
||||
rand_val[i as usize] = 0xff - i;
|
||||
}
|
||||
rand_val
|
||||
}
|
344
kernel/aster-nix/src/process/program_loader/elf/load_elf.rs
Normal file
344
kernel/aster-nix/src/process/program_loader/elf/load_elf.rs
Normal file
@ -0,0 +1,344 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
//! This module is used to parse elf file content to get elf_load_info.
|
||||
//! When create a process from elf file, we will use the elf_load_info to construct the VmSpace
|
||||
|
||||
use align_ext::AlignExt;
|
||||
use aster_frame::{
|
||||
task::Task,
|
||||
vm::{VmIo, VmPerm},
|
||||
};
|
||||
use aster_rights::{Full, Rights};
|
||||
use xmas_elf::program::{self, ProgramHeader64};
|
||||
|
||||
use super::elf_file::Elf;
|
||||
use crate::{
|
||||
fs::{
|
||||
fs_resolver::{FsPath, FsResolver, AT_FDCWD},
|
||||
utils::Dentry,
|
||||
},
|
||||
prelude::*,
|
||||
process::{
|
||||
do_exit_group,
|
||||
process_vm::ProcessVm,
|
||||
program_loader::elf::init_stack::{init_aux_vec, InitStack},
|
||||
TermStatus,
|
||||
},
|
||||
vm::{
|
||||
perms::VmPerms,
|
||||
vmar::Vmar,
|
||||
vmo::{Vmo, VmoOptions, VmoRightsOp},
|
||||
},
|
||||
};
|
||||
|
||||
/// load elf to the root vmar. this function will
|
||||
/// 1. read the vaddr of each segment to get all elf pages.
|
||||
/// 2. create a vmo for each elf segment, create a pager for each segment. Then map the vmo to the root vmar.
|
||||
/// 3. write proper content to the init stack.
|
||||
pub fn load_elf_to_vm(
|
||||
process_vm: &ProcessVm,
|
||||
file_header: &[u8],
|
||||
elf_file: Arc<Dentry>,
|
||||
fs_resolver: &FsResolver,
|
||||
argv: Vec<CString>,
|
||||
envp: Vec<CString>,
|
||||
vdso_text_base: Vaddr,
|
||||
) -> Result<ElfLoadInfo> {
|
||||
let elf = Elf::parse_elf(file_header)?;
|
||||
|
||||
let ldso = if elf.is_shared_object() {
|
||||
Some(lookup_and_parse_ldso(&elf, file_header, fs_resolver)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
match init_and_map_vmos(
|
||||
process_vm,
|
||||
ldso,
|
||||
&elf,
|
||||
&elf_file,
|
||||
argv,
|
||||
envp,
|
||||
vdso_text_base,
|
||||
) {
|
||||
Ok(elf_load_info) => Ok(elf_load_info),
|
||||
Err(e) => {
|
||||
// Since the process_vm is cleared, the process cannot return to user space again,
|
||||
// so exit_group is called here.
|
||||
|
||||
// FIXME: if `current` macro is used when creating the init process,
|
||||
// the macro will panic. This corner case should be handled later.
|
||||
// FIXME: how to set the correct exit status?
|
||||
do_exit_group(TermStatus::Exited(1));
|
||||
Task::current().exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn lookup_and_parse_ldso(
|
||||
elf: &Elf,
|
||||
file_header: &[u8],
|
||||
fs_resolver: &FsResolver,
|
||||
) -> Result<(Arc<Dentry>, Elf)> {
|
||||
let ldso_file = {
|
||||
let ldso_path = elf.ldso_path(file_header)?;
|
||||
let fs_path = FsPath::new(AT_FDCWD, &ldso_path)?;
|
||||
fs_resolver.lookup(&fs_path)?
|
||||
};
|
||||
let ldso_elf = {
|
||||
let mut buf = Box::new([0u8; PAGE_SIZE]);
|
||||
let inode = ldso_file.inode();
|
||||
inode.read_at(0, &mut *buf)?;
|
||||
Elf::parse_elf(&*buf)?
|
||||
};
|
||||
Ok((ldso_file, ldso_elf))
|
||||
}
|
||||
|
||||
fn load_ldso(root_vmar: &Vmar<Full>, ldso_file: &Dentry, ldso_elf: &Elf) -> Result<LdsoLoadInfo> {
|
||||
let map_addr = map_segment_vmos(ldso_elf, root_vmar, ldso_file)?;
|
||||
Ok(LdsoLoadInfo::new(
|
||||
ldso_elf.entry_point() + map_addr,
|
||||
map_addr,
|
||||
))
|
||||
}
|
||||
|
||||
fn init_and_map_vmos(
|
||||
process_vm: &ProcessVm,
|
||||
ldso: Option<(Arc<Dentry>, Elf)>,
|
||||
elf: &Elf,
|
||||
elf_file: &Dentry,
|
||||
argv: Vec<CString>,
|
||||
envp: Vec<CString>,
|
||||
vdso_text_base: Vaddr,
|
||||
) -> Result<ElfLoadInfo> {
|
||||
let root_vmar = process_vm.root_vmar();
|
||||
|
||||
// After we clear process vm, if any error happens, we must call exit_group instead of return to user space.
|
||||
let ldso_load_info = if let Some((ldso_file, ldso_elf)) = ldso {
|
||||
Some(load_ldso(root_vmar, &ldso_file, &ldso_elf)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let map_addr = map_segment_vmos(elf, root_vmar, elf_file)?;
|
||||
let mut aux_vec = init_aux_vec(elf, map_addr, vdso_text_base)?;
|
||||
let mut init_stack = InitStack::new_default_config(argv, envp);
|
||||
init_stack.init(root_vmar, elf, &ldso_load_info, &mut aux_vec)?;
|
||||
let entry_point = if let Some(ldso_load_info) = ldso_load_info {
|
||||
// Normal shared object
|
||||
ldso_load_info.entry_point()
|
||||
} else if elf.is_shared_object() {
|
||||
// ldso itself
|
||||
elf.entry_point() + map_addr
|
||||
} else {
|
||||
// statically linked executable
|
||||
elf.entry_point()
|
||||
};
|
||||
|
||||
let elf_load_info = ElfLoadInfo::new(entry_point, init_stack.user_stack_top());
|
||||
Ok(elf_load_info)
|
||||
}
|
||||
|
||||
pub struct LdsoLoadInfo {
|
||||
entry_point: Vaddr,
|
||||
base_addr: Vaddr,
|
||||
}
|
||||
|
||||
impl LdsoLoadInfo {
|
||||
pub fn new(entry_point: Vaddr, base_addr: Vaddr) -> Self {
|
||||
Self {
|
||||
entry_point,
|
||||
base_addr,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn entry_point(&self) -> Vaddr {
|
||||
self.entry_point
|
||||
}
|
||||
|
||||
pub fn base_addr(&self) -> Vaddr {
|
||||
self.base_addr
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ElfLoadInfo {
|
||||
entry_point: Vaddr,
|
||||
user_stack_top: Vaddr,
|
||||
}
|
||||
|
||||
impl ElfLoadInfo {
|
||||
pub fn new(entry_point: Vaddr, user_stack_top: Vaddr) -> Self {
|
||||
Self {
|
||||
entry_point,
|
||||
user_stack_top,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn entry_point(&self) -> Vaddr {
|
||||
self.entry_point
|
||||
}
|
||||
|
||||
pub fn user_stack_top(&self) -> Vaddr {
|
||||
self.user_stack_top
|
||||
}
|
||||
}
|
||||
|
||||
/// init vmo for each segment and then map segment to root vmar
|
||||
pub fn map_segment_vmos(elf: &Elf, root_vmar: &Vmar<Full>, elf_file: &Dentry) -> Result<Vaddr> {
|
||||
// all segments of the shared object must be mapped to a continuous vm range
|
||||
// to ensure the relative offset of each segment not changed.
|
||||
let base_addr = if elf.is_shared_object() {
|
||||
base_map_addr(elf, root_vmar)?
|
||||
} else {
|
||||
0
|
||||
};
|
||||
for program_header in &elf.program_headers {
|
||||
let type_ = program_header
|
||||
.get_type()
|
||||
.map_err(|_| Error::with_message(Errno::ENOEXEC, "parse program header type fails"))?;
|
||||
if type_ == program::Type::Load {
|
||||
check_segment_align(program_header)?;
|
||||
let vmo = init_segment_vmo(program_header, elf_file)?;
|
||||
map_segment_vmo(program_header, vmo, root_vmar, base_addr)?;
|
||||
}
|
||||
}
|
||||
Ok(base_addr)
|
||||
}
|
||||
|
||||
fn base_map_addr(elf: &Elf, root_vmar: &Vmar<Full>) -> Result<Vaddr> {
|
||||
let elf_size = elf
|
||||
.program_headers
|
||||
.iter()
|
||||
.filter_map(|program_header| {
|
||||
if let Ok(type_) = program_header.get_type()
|
||||
&& type_ == program::Type::Load
|
||||
{
|
||||
let ph_max_addr = program_header.virtual_addr + program_header.mem_size;
|
||||
Some(ph_max_addr as usize)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.max()
|
||||
.ok_or(Error::with_message(
|
||||
Errno::ENOEXEC,
|
||||
"executable file does not has loadable sections",
|
||||
))?;
|
||||
let map_size = elf_size.align_up(PAGE_SIZE);
|
||||
let vmo = VmoOptions::<Rights>::new(0).alloc()?;
|
||||
let vmar_map_options = root_vmar.new_map(vmo, VmPerms::empty())?.size(map_size);
|
||||
vmar_map_options.build()
|
||||
}
|
||||
|
||||
/// map the segment vmo to root_vmar
|
||||
fn map_segment_vmo(
|
||||
program_header: &ProgramHeader64,
|
||||
vmo: Vmo,
|
||||
root_vmar: &Vmar<Full>,
|
||||
base_addr: Vaddr,
|
||||
) -> Result<()> {
|
||||
let perms = VmPerms::from(parse_segment_perm(program_header.flags));
|
||||
let offset = (program_header.virtual_addr as Vaddr).align_down(PAGE_SIZE);
|
||||
trace!(
|
||||
"map segment vmo: virtual addr = 0x{:x}, size = 0x{:x}, perms = {:?}",
|
||||
offset,
|
||||
program_header.mem_size,
|
||||
perms
|
||||
);
|
||||
let mut vm_map_options = root_vmar.new_map(vmo, perms)?.can_overwrite(true);
|
||||
let offset = base_addr + offset;
|
||||
vm_map_options = vm_map_options.offset(offset);
|
||||
let map_addr = vm_map_options.build()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// create vmo for each segment
|
||||
fn init_segment_vmo(program_header: &ProgramHeader64, elf_file: &Dentry) -> Result<Vmo> {
|
||||
trace!(
|
||||
"mem range = 0x{:x} - 0x{:x}, mem_size = 0x{:x}",
|
||||
program_header.virtual_addr,
|
||||
program_header.virtual_addr + program_header.mem_size,
|
||||
program_header.mem_size
|
||||
);
|
||||
trace!(
|
||||
"file range = 0x{:x} - 0x{:x}, file_size = 0x{:x}",
|
||||
program_header.offset,
|
||||
program_header.offset + program_header.file_size,
|
||||
program_header.file_size
|
||||
);
|
||||
|
||||
let file_offset = program_header.offset as usize;
|
||||
let virtual_addr = program_header.virtual_addr as usize;
|
||||
debug_assert!(file_offset % PAGE_SIZE == virtual_addr % PAGE_SIZE);
|
||||
let page_cache_vmo = {
|
||||
let inode = elf_file.inode();
|
||||
inode.page_cache().ok_or(Error::with_message(
|
||||
Errno::ENOENT,
|
||||
"executable has no page cache",
|
||||
))?
|
||||
};
|
||||
|
||||
let segment_vmo = {
|
||||
let vmo_offset = file_offset.align_down(PAGE_SIZE);
|
||||
let map_start = virtual_addr.align_down(PAGE_SIZE);
|
||||
let map_end = (virtual_addr + program_header.mem_size as usize).align_up(PAGE_SIZE);
|
||||
let vmo_size = map_end - map_start;
|
||||
debug_assert!(vmo_size >= (program_header.file_size as usize).align_up(PAGE_SIZE));
|
||||
page_cache_vmo
|
||||
.new_cow_child(vmo_offset..vmo_offset + vmo_size)?
|
||||
.alloc()?
|
||||
};
|
||||
|
||||
// Write zero as paddings. There are head padding and tail padding.
|
||||
// Head padding: if the segment's virtual address is not page-aligned,
|
||||
// then the bytes in first page from start to virtual address should be padded zeros.
|
||||
// Tail padding: If the segment's mem_size is larger than file size,
|
||||
// then the bytes that are not backed up by file content should be zeros.(usually .data/.bss sections).
|
||||
// FIXME: Head padding may be removed.
|
||||
|
||||
// Head padding.
|
||||
let page_offset = file_offset % PAGE_SIZE;
|
||||
if page_offset != 0 {
|
||||
let buffer = vec![0u8; page_offset];
|
||||
segment_vmo.write_bytes(0, &buffer)?;
|
||||
}
|
||||
// Tail padding.
|
||||
let segment_vmo_size = segment_vmo.size();
|
||||
let tail_padding_offset = program_header.file_size as usize + page_offset;
|
||||
if segment_vmo_size > tail_padding_offset {
|
||||
let buffer = vec![0u8; segment_vmo_size - tail_padding_offset];
|
||||
segment_vmo.write_bytes(tail_padding_offset, &buffer)?;
|
||||
}
|
||||
Ok(segment_vmo.to_dyn())
|
||||
}
|
||||
|
||||
fn parse_segment_perm(flags: xmas_elf::program::Flags) -> VmPerm {
|
||||
let mut vm_perm = VmPerm::empty();
|
||||
if flags.is_read() {
|
||||
vm_perm |= VmPerm::R;
|
||||
}
|
||||
if flags.is_write() {
|
||||
vm_perm |= VmPerm::W;
|
||||
}
|
||||
if flags.is_execute() {
|
||||
vm_perm |= VmPerm::X;
|
||||
}
|
||||
vm_perm
|
||||
}
|
||||
|
||||
fn check_segment_align(program_header: &ProgramHeader64) -> Result<()> {
|
||||
let align = program_header.align;
|
||||
if align == 0 || align == 1 {
|
||||
// no align requirement
|
||||
return Ok(());
|
||||
}
|
||||
debug_assert!(align.is_power_of_two());
|
||||
if !align.is_power_of_two() {
|
||||
return_errno_with_message!(Errno::ENOEXEC, "segment align is invalid.");
|
||||
}
|
||||
debug_assert!(program_header.offset % align == program_header.virtual_addr % align);
|
||||
if program_header.offset % align != program_header.virtual_addr % align {
|
||||
return_errno_with_message!(Errno::ENOEXEC, "segment align is not satisfied.");
|
||||
}
|
||||
Ok(())
|
||||
}
|
9
kernel/aster-nix/src/process/program_loader/elf/mod.rs
Normal file
9
kernel/aster-nix/src/process/program_loader/elf/mod.rs
Normal file
@ -0,0 +1,9 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
mod aux_vec;
|
||||
mod elf_file;
|
||||
mod init_stack;
|
||||
mod load_elf;
|
||||
|
||||
pub use init_stack::INIT_STACK_SIZE;
|
||||
pub use load_elf::{load_elf_to_vm, ElfLoadInfo};
|
116
kernel/aster-nix/src/process/program_loader/mod.rs
Normal file
116
kernel/aster-nix/src/process/program_loader/mod.rs
Normal file
@ -0,0 +1,116 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
pub mod elf;
|
||||
mod shebang;
|
||||
|
||||
use self::{
|
||||
elf::{load_elf_to_vm, ElfLoadInfo},
|
||||
shebang::parse_shebang_line,
|
||||
};
|
||||
use super::process_vm::ProcessVm;
|
||||
use crate::{
|
||||
fs::{
|
||||
fs_resolver::{FsPath, FsResolver, AT_FDCWD},
|
||||
utils::Dentry,
|
||||
},
|
||||
prelude::*,
|
||||
vdso::vdso_vmo,
|
||||
vm::perms::VmPerms,
|
||||
};
|
||||
|
||||
/// Map the vdso vmo to the corresponding virtual memory address.
|
||||
pub fn map_vdso_to_vm(process_vm: &ProcessVm) -> Vaddr {
|
||||
let root_vmar = process_vm.root_vmar();
|
||||
let vdso_vmo = vdso_vmo();
|
||||
|
||||
let options = root_vmar
|
||||
.new_map(vdso_vmo.dup().unwrap(), VmPerms::empty())
|
||||
.unwrap()
|
||||
.size(5 * PAGE_SIZE);
|
||||
let vdso_data_base = options.build().unwrap();
|
||||
let vdso_text_base = vdso_data_base + 0x4000;
|
||||
|
||||
let data_perms = VmPerms::READ | VmPerms::WRITE;
|
||||
let text_perms = VmPerms::READ | VmPerms::EXEC;
|
||||
root_vmar
|
||||
.protect(data_perms, vdso_data_base..vdso_data_base + PAGE_SIZE)
|
||||
.unwrap();
|
||||
root_vmar
|
||||
.protect(text_perms, vdso_text_base..vdso_text_base + PAGE_SIZE)
|
||||
.unwrap();
|
||||
vdso_text_base
|
||||
}
|
||||
|
||||
/// Load an executable to root vmar, including loading programe image, preparing heap and stack,
|
||||
/// initializing argv, envp and aux tables.
|
||||
/// About recursion_limit: recursion limit is used to limit th recursion depth of shebang executables.
|
||||
/// If the interpreter(the program behind #!) of shebang executable is also a shebang,
|
||||
/// then it will trigger recursion. We will try to setup root vmar for the interpreter.
|
||||
/// I guess for most cases, setting the recursion_limit as 1 should be enough.
|
||||
/// because the interpreter is usually an elf binary(e.g., /bin/bash)
|
||||
pub fn load_program_to_vm(
|
||||
process_vm: &ProcessVm,
|
||||
elf_file: Arc<Dentry>,
|
||||
argv: Vec<CString>,
|
||||
envp: Vec<CString>,
|
||||
fs_resolver: &FsResolver,
|
||||
recursion_limit: usize,
|
||||
) -> Result<(String, ElfLoadInfo)> {
|
||||
let abs_path = elf_file.abs_path();
|
||||
let inode = elf_file.inode();
|
||||
let file_header = {
|
||||
// read the first page of file header
|
||||
let mut file_header_buffer = Box::new([0u8; PAGE_SIZE]);
|
||||
inode.read_at(0, &mut *file_header_buffer)?;
|
||||
file_header_buffer
|
||||
};
|
||||
if let Some(mut new_argv) = parse_shebang_line(&*file_header)? {
|
||||
if recursion_limit == 0 {
|
||||
return_errno_with_message!(Errno::ELOOP, "the recursieve limit is reached");
|
||||
}
|
||||
new_argv.extend_from_slice(&argv);
|
||||
let interpreter = {
|
||||
let filename = new_argv[0].to_str()?.to_string();
|
||||
let fs_path = FsPath::new(AT_FDCWD, &filename)?;
|
||||
fs_resolver.lookup(&fs_path)?
|
||||
};
|
||||
check_executable_file(&interpreter)?;
|
||||
return load_program_to_vm(
|
||||
process_vm,
|
||||
interpreter,
|
||||
new_argv,
|
||||
envp,
|
||||
fs_resolver,
|
||||
recursion_limit - 1,
|
||||
);
|
||||
}
|
||||
process_vm.clear();
|
||||
let vdso_text_base = map_vdso_to_vm(process_vm);
|
||||
let elf_load_info = load_elf_to_vm(
|
||||
process_vm,
|
||||
&*file_header,
|
||||
elf_file,
|
||||
fs_resolver,
|
||||
argv,
|
||||
envp,
|
||||
vdso_text_base,
|
||||
)?;
|
||||
|
||||
Ok((abs_path, elf_load_info))
|
||||
}
|
||||
|
||||
pub fn check_executable_file(dentry: &Arc<Dentry>) -> Result<()> {
|
||||
if dentry.type_().is_directory() {
|
||||
return_errno_with_message!(Errno::EISDIR, "the file is a directory");
|
||||
}
|
||||
|
||||
if !dentry.type_().is_reguler_file() {
|
||||
return_errno_with_message!(Errno::EACCES, "the dentry is not a regular file");
|
||||
}
|
||||
|
||||
if !dentry.mode()?.is_executable() {
|
||||
return_errno_with_message!(Errno::EACCES, "the dentry is not executable");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
33
kernel/aster-nix/src/process/program_loader/shebang.rs
Normal file
33
kernel/aster-nix/src/process/program_loader/shebang.rs
Normal file
@ -0,0 +1,33 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
/// Try to parse a buffer as a shebang line.
|
||||
///
|
||||
/// If the buffer starts with `#!` and its header is a valid shebang sequence,
|
||||
/// then the function returns `Ok(Some(parts))`,
|
||||
/// where `parts` is a `Vec` that contains the path of and the arguments for the interpreter.
|
||||
/// If the buffer starts with `#!` but some error occurs while parsing the file,
|
||||
/// then `Err(_)` is returned.
|
||||
/// If the buffer does not start with `#!`, then `Ok(None)` is returned.
|
||||
pub fn parse_shebang_line(file_header_buffer: &[u8]) -> Result<Option<Vec<CString>>> {
|
||||
if !file_header_buffer.starts_with(b"#!") || !file_header_buffer.contains(&b'\n') {
|
||||
// the file is not a shebang
|
||||
return Ok(None);
|
||||
}
|
||||
let first_line_len = file_header_buffer.iter().position(|&c| c == b'\n').unwrap();
|
||||
// skip #!
|
||||
let shebang_header = &file_header_buffer[2..first_line_len];
|
||||
let mut shebang_argv = Vec::new();
|
||||
for arg in shebang_header.split(|&c| c == b' ') {
|
||||
let arg = CString::new(arg)?;
|
||||
shebang_argv.push(arg);
|
||||
}
|
||||
if shebang_argv.len() != 1 {
|
||||
return_errno_with_message!(
|
||||
Errno::EINVAL,
|
||||
"One and only one intpreter program should be specified"
|
||||
);
|
||||
}
|
||||
Ok(Some(shebang_argv))
|
||||
}
|
Reference in New Issue
Block a user