Support reading argv and envp from init stack

This commit is contained in:
Jianfeng Jiang
2024-03-26 07:54:08 +00:00
committed by Tate, Hongliang Tian
parent cc4111cab2
commit 29ebf8e60c
18 changed files with 654 additions and 519 deletions

View File

@ -1,96 +0,0 @@
// 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
}
}

View File

@ -1,363 +0,0 @@
// 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, MAX_USERSPACE_VADDR};
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},
};
/// Set the initial stack size to 8 megabytes, following the default Linux stack size limit.
pub const INIT_STACK_SIZE: usize = 8 * 1024 * 1024; // 8 MB
/*
* Illustration of the virtual memory space containing the processes' init stack:
*
* (high address)
* +---------------------+ <------+ Highest address
* | | Random stack paddings
* +---------------------+ <------+ The base of stack (stack grows down)
* | |
* | 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
* +---------------------+
* | |
* | |
* +---------------------+
* | |
* +---------------------+ <------+ User stack default rlimit
* (low address)
*/
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,
}
}
pub fn new_default_config(argv: Vec<CString>, envp: Vec<CString>) -> Self {
let nr_pages_padding = {
let mut random_nr_pages_padding: u8 = 0;
getrandom::getrandom(random_nr_pages_padding.as_bytes_mut()).unwrap();
random_nr_pages_padding as usize
};
let init_stack_top = MAX_USERSPACE_VADDR - PAGE_SIZE * nr_pages_padding;
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<()> {
// FIXME: Some OSes may put the first page of excutable file here
// for interpreting elf headers.
// 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
}

View File

@ -20,10 +20,10 @@ use crate::{
prelude::*,
process::{
do_exit_group,
process_vm::ProcessVm,
program_loader::elf::init_stack::{init_aux_vec, InitStack},
process_vm::{AuxKey, AuxVec, ProcessVm},
TermStatus,
},
vdso::vdso_vmo,
vm::{
perms::VmPerms,
vmar::Vmar,
@ -31,10 +31,10 @@ use crate::{
},
};
/// 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.
/// Loads elf to the process vm.
///
/// This function will map elf segments and
/// initialize process init stack.
pub fn load_elf_to_vm(
process_vm: &ProcessVm,
file_header: &[u8],
@ -42,31 +42,48 @@ pub fn load_elf_to_vm(
fs_resolver: &FsResolver,
argv: Vec<CString>,
envp: Vec<CString>,
vdso_text_base: Vaddr,
) -> Result<ElfLoadInfo> {
let elf = Elf::parse_elf(file_header)?;
let parsed_elf = Elf::parse_elf(file_header)?;
let ldso = if elf.is_shared_object() {
Some(lookup_and_parse_ldso(&elf, file_header, fs_resolver)?)
let ldso = if parsed_elf.is_shared_object() {
Some(lookup_and_parse_ldso(
&parsed_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.
match init_and_map_vmos(process_vm, ldso, &parsed_elf, &elf_file) {
Ok((entry_point, mut aux_vec)) => {
// Map and set vdso entry.
// Since vdso does not require being mapped to any specific address,
// vdso is mapped after the elf file, heap and stack are mapped.
if let Some(vdso_text_base) = map_vdso_to_vm(process_vm) {
aux_vec
.set(AuxKey::AT_SYSINFO_EHDR, vdso_text_base as u64)
.unwrap();
}
// FIXME: if `current` macro is used when creating the init process,
let init_stack_writer = process_vm.init_stack_writer(argv, envp, aux_vec);
init_stack_writer.write().unwrap();
let user_stack_top = process_vm.init_stack_reader().user_stack_top();
Ok(ElfLoadInfo {
entry_point,
user_stack_top,
})
}
Err(_) => {
// Since the process_vm is in invalid state,
// the process cannot return to user space again,
// so `Vmar::clear` and `do_exit_group` are called here.
// FIXME: sending a fault signal is an alternative approach.
process_vm.root_vmar().clear().unwrap();
// FIXME: `current` macro will be used in `do_exit_group`.
// if the 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));
@ -105,12 +122,9 @@ fn load_ldso(root_vmar: &Vmar<Full>, ldso_file: &Dentry, ldso_elf: &Elf) -> Resu
fn init_and_map_vmos(
process_vm: &ProcessVm,
ldso: Option<(Arc<Dentry>, Elf)>,
elf: &Elf,
parsed_elf: &Elf,
elf_file: &Dentry,
argv: Vec<CString>,
envp: Vec<CString>,
vdso_text_base: Vaddr,
) -> Result<ElfLoadInfo> {
) -> Result<(Vaddr, AuxVec)> {
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.
@ -120,23 +134,27 @@ fn init_and_map_vmos(
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 elf_map_addr = map_segment_vmos(parsed_elf, root_vmar, elf_file)?;
let aux_vec = {
let ldso_base = ldso_load_info
.as_ref()
.map(|load_info| load_info.base_addr());
init_aux_vec(parsed_elf, elf_map_addr, ldso_base)?
};
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() {
} else if parsed_elf.is_shared_object() {
// ldso itself
elf.entry_point() + map_addr
parsed_elf.entry_point() + elf_map_addr
} else {
// statically linked executable
elf.entry_point()
parsed_elf.entry_point()
};
let elf_load_info = ElfLoadInfo::new(entry_point, init_stack.user_stack_top());
Ok(elf_load_info)
Ok((entry_point, aux_vec))
}
pub struct LdsoLoadInfo {
@ -371,3 +389,51 @@ fn check_segment_align(program_header: &ProgramHeader64) -> Result<()> {
}
Ok(())
}
pub fn init_aux_vec(elf: &Elf, elf_map_addr: Vaddr, ldso_base: Option<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)?;
if let Some(ldso_base) = ldso_base {
aux_vec.set(AuxKey::AT_BASE, ldso_base as u64)?;
}
Ok(aux_vec)
}
/// Map the vdso vmo to the corresponding virtual memory address.
fn map_vdso_to_vm(process_vm: &ProcessVm) -> Option<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();
Some(vdso_text_base)
}

View File

@ -1,9 +1,6 @@
// 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};

View File

@ -14,33 +14,8 @@ use crate::{
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.
@ -84,17 +59,11 @@ pub fn load_program_to_vm(
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,
)?;
process_vm.clear_and_map();
let elf_load_info =
load_elf_to_vm(process_vm, &*file_header, elf_file, fs_resolver, argv, envp)?;
Ok((abs_path, elf_load_info))
}