Files
yggdrasil/kernel/libk/src/task/binary/mod.rs
T
2025-07-27 13:43:41 +03:00

382 lines
11 KiB
Rust

use alloc::{borrow::ToOwned, string::String, sync::Arc, vec, vec::Vec};
use bytemuck::Pod;
use kernel_arch::task::{TaskContext, UserContextInfo};
use libk_mm::{
pointer::PhysicalRefMut,
process::{ProcessAddressSpace, VirtualRangeBacking},
table::MapAttributes,
};
use libk_util::io::{Read, Seek};
use yggdrasil_abi::{
error::Error,
io::SeekFrom,
path::Path,
process::{auxv, AuxValue, ProcessGroupId},
};
use crate::{
task::{
mem::ForeignPointer,
process::{Process, ProcessCreateInfo, ProcessImage},
thread::Thread,
TaskContextImpl,
},
vfs::IoContext,
};
use self::elf::ElfKind;
pub mod elf;
pub type LoadedProcess = (Arc<Process>, Arc<Thread>);
#[derive(Debug)]
pub struct LoadOptions<'e, P: AsRef<Path>> {
pub parent: Option<Arc<Process>>,
pub group_id: ProcessGroupId,
pub path: P,
pub args: &'e [&'e str],
pub envs: &'e [&'e str],
pub single_step: bool,
pub disable_aslr: bool,
}
struct ArgPlacer<'a> {
buffer: &'a mut [u8],
position: usize,
}
impl<'a> ArgPlacer<'a> {
fn new(buffer: &'a mut [u8]) -> Self {
Self {
buffer,
position: 0,
}
}
fn align(&mut self, align: usize) -> Result<(), Error> {
debug_assert!(align.is_power_of_two());
let aligned = (self.position + align - 1) & !(align - 1);
if aligned > self.buffer.len() {
return Err(Error::InvalidArgument);
}
self.position = aligned;
Ok(())
}
fn put_str(&mut self, s: &str) -> Result<usize, Error> {
if self.position + s.len() >= self.buffer.len() {
return Err(Error::InvalidArgument);
}
let off = self.position;
self.buffer[off..off + s.len()].copy_from_slice(s.as_bytes());
self.buffer[off + s.len()] = 0;
self.position += s.len() + 1;
Ok(off)
}
fn put<T: Pod>(&mut self, v: &T) -> Result<usize, Error> {
if self.position + size_of::<T>() > self.buffer.len() {
return Err(Error::InvalidArgument);
}
let off = self.position;
self.buffer[off..off + size_of::<T>()].copy_from_slice(bytemuck::bytes_of(v));
self.position += size_of::<T>();
Ok(off)
}
fn put_aux_array(&mut self, s: &[AuxValue]) -> Result<usize, Error> {
self.align(size_of::<u64>())?;
let off = self.position;
for item in s {
self.put(&item.tag)?;
self.put(&item.val)?;
}
self.put(&auxv::NULL)?;
self.put(&0u64)?;
Ok(off)
}
fn put_ptr_array(&mut self, s: &[usize]) -> Result<usize, Error> {
self.align(size_of::<usize>())?;
let off = self.position;
for item in s {
self.put(item)?;
}
self.put(&0usize)?;
Ok(off)
}
}
// args, envs are passed as Vec to ensure kernel ownership
#[allow(clippy::ptr_arg)]
fn setup_program_env(
space: &ProcessAddressSpace,
virt: usize,
real_program: Option<&str>,
args: &Vec<String>,
envs: &Vec<String>,
aux: &[AuxValue],
) -> Result<usize, Error> {
// TODO growing buffer
let phys_page = space.map_single_anon(
virt,
MapAttributes::USER_READ | MapAttributes::USER_WRITE | MapAttributes::NON_GLOBAL,
)?;
let mut buffer = unsafe { PhysicalRefMut::map_slice(phys_page, 4096) };
let mut placer = ArgPlacer::new(&mut buffer);
let mut arg_ptrs = vec![];
let mut env_ptrs = vec![];
for arg in args.iter() {
let ptr = placer.put_str(arg)? + virt;
arg_ptrs.push(ptr);
}
for env in envs.iter() {
let ptr = placer.put_str(env)? + virt;
env_ptrs.push(ptr);
}
let argv = placer.put_ptr_array(&arg_ptrs)? + virt;
let envp = placer.put_ptr_array(&env_ptrs)? + virt;
let auxv = placer.put_aux_array(aux)? + virt;
let real_program = match real_program {
Some(path) => placer.put_str(path)? + virt,
_ => 0,
};
placer.align(size_of::<usize>())?;
// Put ProgramArgumentInner struct
let arg_address = placer.position + virt;
placer.put(&argv)?;
placer.put(&envp)?;
placer.put(&auxv)?;
placer.put(&real_program)?;
Ok(arg_address)
}
fn setup_context<P>(
options: &LoadOptions<P>,
space: &ProcessAddressSpace,
image: &ProcessImage,
image_path: &str,
args: &Vec<String>,
envs: &Vec<String>,
) -> Result<TaskContextImpl, Error>
where
P: AsRef<Path>,
{
const USER_STACK_PAGES: usize = 32;
// TODO dynamically map the stack
let virt_stack_base = 0x3000000;
// 0x1000 of guard page
let virt_args_base = virt_stack_base + (USER_STACK_PAGES + 1) * 0x1000;
space.map(
virt_stack_base,
USER_STACK_PAGES * 0x1000,
VirtualRangeBacking::anonymous(),
MapAttributes::USER_WRITE | MapAttributes::USER_READ | MapAttributes::NON_GLOBAL,
)?;
log::debug!(
"stack: {:#x}..{:#x}",
virt_stack_base,
virt_stack_base + USER_STACK_PAGES * 0x1000
);
let mut auxv = vec![];
if let Some(tls_image) = image.tls_image.as_ref() {
if tls_image.master_copy_base != 0 {
auxv.push(AuxValue {
tag: auxv::TLS_MASTER_COPY,
val: tls_image.master_copy_base as _,
});
auxv.push(AuxValue {
tag: auxv::TLS_DATA_SIZE,
val: tls_image.data_size as _,
});
} else {
assert_eq!(tls_image.data_size, 0);
}
auxv.push(AuxValue {
tag: auxv::TLS_ALIGN,
val: tls_image.align as _,
});
auxv.push(AuxValue {
tag: auxv::TLS_FULL_SIZE,
val: tls_image.full_size as _,
});
}
let argument = setup_program_env(space, virt_args_base, Some(image_path), args, envs, &auxv)?;
let user_sp =
virt_stack_base + USER_STACK_PAGES * 0x1000 - TaskContextImpl::USER_STACK_EXTRA_ALIGN;
// Fill with some sentinel value to detect stack underflows
let ptr = user_sp as *mut usize;
#[allow(clippy::reversed_empty_ranges)]
for i in 0..TaskContextImpl::USER_STACK_EXTRA_ALIGN / 8 {
unsafe {
ptr.add(i).write_foreign_volatile(space, 0xDEADC0DE);
}
}
let ptr = unsafe { Thread::setup_stack_header(space, ptr, argument)? };
// let tls_address = elf::clone_tls(space, image)?;
log::debug!(
"argument = {:#x}, user_sp = {:#x}, stack: {:#x}..{:#x}",
argument,
user_sp,
virt_stack_base,
virt_stack_base + USER_STACK_PAGES * 0x1000 - TaskContextImpl::USER_STACK_EXTRA_ALIGN
);
let (address_space, asid) = space.as_address_with_asid();
TaskContext::user(UserContextInfo {
entry: image.entry,
argument,
stack_pointer: ptr.addr(),
thread_pointer: 0,
address_space,
asid,
single_step: options.single_step,
})
}
fn setup_binary<S, P>(
options: &LoadOptions<P>,
name: S,
space: ProcessAddressSpace,
image: ProcessImage,
image_path: &str,
args: &Vec<String>,
envs: &Vec<String>,
) -> Result<LoadedProcess, Error>
where
S: Into<String>,
P: AsRef<Path>,
{
let context = setup_context(options, &space, &image, image_path, args, envs)?;
let info = ProcessCreateInfo {
name,
context,
group_id: options.group_id,
space: Arc::new(space),
image: Some(image),
};
let (process, main) = Process::spawn(options.parent.as_ref(), info);
Ok((process, main))
}
fn xxx_load_program<P: AsRef<Path>>(
space: &ProcessAddressSpace,
ioctx: &mut IoContext,
path: P,
args: Vec<String>,
envs: Vec<String>,
aslr: bool,
) -> Result<(ProcessImage, Vec<String>, Vec<String>), Error> {
let mut head = [0; 256];
let path = path.as_ref();
let file = ioctx.open_executable(path)?;
file.seek(SeekFrom::Start(0))?;
let count = file.read(&mut head)?;
let head = &head[..count];
// Recognize #!
if let Some(shebang) = head.strip_prefix(b"#!")
&& let Some((shebang, _)) = shebang.split_once(|&ch| ch == b'\n')
{
let shebang = core::str::from_utf8(shebang).map_err(|_| Error::InvalidFile)?;
let mut shebang_args = shebang.split(' ').map(|s| s.to_owned()).collect::<Vec<_>>();
if shebang_args.is_empty() || shebang_args.len() >= 8 {
return Err(Error::UnrecognizedExecutable);
}
shebang_args.extend_from_slice(&args);
return xxx_load_program(
space,
ioctx,
shebang_args[0].clone(),
shebang_args,
envs,
aslr,
);
}
file.seek(SeekFrom::Start(0))?;
log::debug!("open_elf_file({:?})", path);
if head.starts_with(b"\x7FELF") {
match elf::open_elf_file(file)? {
ElfKind::Static(elf, file) => {
let image = elf::load_elf_from_file(space, elf, file, aslr)?;
Ok((image, args, envs))
}
ElfKind::Dynamic(interp) => {
let interp = interp.unwrap_or_else(|| "/libexec/dyn-loader".into());
let mut interp_args = Vec::new();
interp_args.push(interp.as_str().into());
interp_args.extend_from_slice(&args);
xxx_load_program(space, ioctx, interp, interp_args, envs, aslr)
}
}
} else {
Err(Error::UnrecognizedExecutable)
}
}
/// Loads a program from given `path`
pub fn load<P: AsRef<Path>>(
ioctx: &mut IoContext,
options: &LoadOptions<P>,
) -> Result<LoadedProcess, Error> {
let path = options.path.as_ref();
let args = options.args.iter().map(|&s| s.to_owned()).collect();
let envs = options.envs.iter().map(|&s| s.to_owned()).collect();
let space = ProcessAddressSpace::new()?;
let (image, args, envs) =
xxx_load_program(&space, ioctx, path, args, envs, !options.disable_aslr)?;
let real_path = path.display();
let name = match real_path.rsplit_once('/') {
Some((_, name)) => name,
None => real_path,
};
setup_binary(options, name, space, image, real_path, &args, &envs)
}
pub fn load_into<P: AsRef<Path>>(
options: &LoadOptions<P>,
args: Vec<String>,
envs: Vec<String>,
) -> Result<(TaskContextImpl, ProcessImage), Error> {
let process = options.parent.as_ref().ok_or(Error::InvalidOperation)?;
let mut io = process.io.lock();
// Have to make the Path owned, going to drop the address space from which it came
let path = options.path.as_ref().to_owned();
let space = process.space();
space.clear()?;
let (image, args, envs) = xxx_load_program(
&space,
io.ioctx_mut(),
&path,
args,
envs,
!options.disable_aslr,
)?;
let context = setup_context(options, &space, &image, path.display(), &args, &envs)?;
Ok((context, image))
}