proc: Implement simple static TLS support

This commit is contained in:
Mark Poliakov 2023-11-23 10:41:27 +02:00
parent 76d8235411
commit 8bc8b30362
9 changed files with 448 additions and 108 deletions

View File

@ -58,9 +58,9 @@ impl StackBuilder {
self.sp
}
fn init_common(&mut self, entry: usize, ttbr0: u64) {
fn init_common(&mut self, entry: usize, ttbr0: u64, tpidr_el0: u64) {
self.push(ttbr0 as _); // ttbr0_el1
self.push(0); // tpidr_el0
self.push(tpidr_el0 as _); // tpidr_el0
self.push(entry); // x30/lr
self.push(0); // x29
@ -91,7 +91,7 @@ impl TaskContextImpl for TaskContext {
stack.push(entry as _);
stack.push(arg);
stack.init_common(__aarch64_task_enter_kernel as _, 0);
stack.init_common(__aarch64_task_enter_kernel as _, 0, 0);
let sp = stack.build();
@ -104,7 +104,13 @@ impl TaskContextImpl for TaskContext {
})
}
fn user(entry: usize, arg: usize, ttbr0: u64, user_stack_sp: usize) -> Result<Self, Error> {
fn user(
entry: usize,
arg: usize,
ttbr0: u64,
user_stack_sp: usize,
tpidr_el0: usize,
) -> Result<Self, Error> {
const USER_TASK_PAGES: usize = 16;
let stack_base = phys::alloc_pages_contiguous(USER_TASK_PAGES)?.virtualize_raw();
@ -115,7 +121,7 @@ impl TaskContextImpl for TaskContext {
stack.push(0);
stack.push(user_stack_sp);
stack.init_common(__aarch64_task_enter_user as _, ttbr0);
stack.init_common(__aarch64_task_enter_user as _, ttbr0, tpidr_el0 as _);
let sp = stack.build();

View File

@ -1,5 +1,7 @@
// vi: set ft=asm :
.set MSR_IA32_FS_BASE, 0xC0000100
.macro SAVE_TASK_STATE
sub ${context_size}, %rsp
@ -8,6 +10,16 @@
mov %r13, 16(%rsp)
mov %r14, 24(%rsp)
mov %r15, 32(%rsp)
// Store FS_BASE
mov $MSR_IA32_FS_BASE, %ecx
rdmsr
mov %edx, %ecx
shl $32, %rcx
or %rax, %rcx
mov %rcx, 40(%rsp)
// TODO save %fs
mov %rbp, 48(%rsp)
@ -24,7 +36,16 @@
mov 16(%rsp), %r13
mov 24(%rsp), %r14
mov 32(%rsp), %r15
// TODO
// Load FS_BASE
// edx:eax = fs_base
mov 40(%rsp), %rdx
mov %edx, %eax
shr $32, %rdx
mov $MSR_IA32_FS_BASE, %ecx
wrmsr
// mov 40(%rsp), %fs
mov 48(%rsp), %rbp

View File

@ -65,14 +65,14 @@ impl StackBuilder {
self.sp
}
fn init_common(&mut self, entry: usize, cr3: u64) {
fn init_common(&mut self, entry: usize, cr3: u64, fs_base: usize) {
self.push(entry); // address for ret
// End of common context
self.push(cr3 as _); // %cr3
self.push(0); // %rbp
self.push(0); // %fs (TODO)
self.push(fs_base); // fs_base
self.push(0); // %r15
self.push(0); // %r14
self.push(0); // %r13
@ -98,9 +98,11 @@ impl TaskContextImpl for TaskContext {
stack.push(entry as _);
stack.push(arg);
stack.init_common(__x86_64_task_enter_kernel as _, unsafe {
KERNEL_TABLES.as_physical_address().into_raw()
});
stack.init_common(
__x86_64_task_enter_kernel as _,
unsafe { KERNEL_TABLES.as_physical_address().into_raw() },
0,
);
let sp = stack.build();
infoln!("Kernel stack {:#x}", sp);
@ -114,7 +116,13 @@ impl TaskContextImpl for TaskContext {
})
}
fn user(entry: usize, arg: usize, cr3: u64, user_stack_sp: usize) -> Result<Self, Error> {
fn user(
entry: usize,
arg: usize,
cr3: u64,
user_stack_sp: usize,
fs_base: usize,
) -> Result<Self, Error> {
const USER_TASK_PAGES: usize = 8;
let stack_base = phys::alloc_pages_contiguous(USER_TASK_PAGES)?.virtualize_raw();
@ -125,7 +133,7 @@ impl TaskContextImpl for TaskContext {
stack.push(arg);
stack.push(user_stack_sp);
stack.init_common(__x86_64_task_enter_user as _, cr3);
stack.init_common(__x86_64_task_enter_user as _, cr3, fs_base);
let sp = stack.build();
let rsp0 = stack_base + USER_TASK_PAGES * 0x1000;

View File

@ -2,15 +2,22 @@
use core::ops::DerefMut;
use elf::{
abi::{PF_W, PF_X, PT_LOAD},
abi::{PF_W, PF_X, PT_LOAD, PT_TLS},
endian::AnyEndian,
segment::ProgramHeader,
ElfStream, ParseError,
};
use vfs::{FileRef, Read, Seek};
use yggdrasil_abi::{error::Error, io::SeekFrom};
use crate::mem::{
phys, pointer::PhysicalRefMut, process::ProcessAddressSpace, table::MapAttributes,
use crate::{
mem::{
phys,
pointer::{PhysicalRef, PhysicalRefMut},
process::ProcessAddressSpace,
table::{EntryLevel, MapAttributes},
},
task::process::{ProcessImage, ProcessTlsInfo, ProcessTlsLayout},
};
#[derive(Clone, Copy)]
@ -56,6 +63,54 @@ fn from_parse_error(v: ParseError) -> Error {
Error::InvalidFile
}
pub fn clone_tls(space: &ProcessAddressSpace, image: &ProcessImage) -> Result<usize, Error> {
let Some(tls) = image.tls.as_ref() else {
// No TLS
return Ok(0);
};
assert_ne!(tls.master_copy_base, 0);
assert_ne!(tls.layout.mem_size, 0);
let address = space.allocate(
None,
0x1000,
|_| phys::alloc_page(),
MapAttributes::USER_READ | MapAttributes::USER_WRITE | MapAttributes::NON_GLOBAL,
)?;
debugln!(
"Clone TLS from {:#x} (data={:#x}) to {:#x} (data={:#x})",
tls.master_copy_base,
tls.master_copy_base + tls.layout.data_offset,
address,
address + tls.layout.data_offset
);
debugln!("tls ptr = {:#x}", address + tls.layout.ptr_offset);
let src_phys = space.translate(tls.master_copy_base)?;
let dst_phys = space.translate(address)?;
// Copy data
unsafe {
let src =
PhysicalRef::<u8>::map_slice(src_phys.add(tls.layout.data_offset), tls.layout.mem_size);
let mut dst =
PhysicalRefMut::map_slice(dst_phys.add(tls.layout.data_offset), tls.layout.mem_size);
dst.copy_from_slice(&src);
}
// Setup self-pointer
unsafe {
let mut dst = PhysicalRefMut::<usize>::map(dst_phys.add(tls.layout.ptr_offset));
*dst = address + tls.layout.ptr_offset;
}
Ok(address + tls.layout.ptr_offset)
}
fn load_bytes<F>(
space: &ProcessAddressSpace,
addr: usize,
@ -92,69 +147,162 @@ where
Ok(())
}
/// Loads an ELF binary from `file` into the target address space
pub fn load_elf_from_file(space: &ProcessAddressSpace, file: FileRef) -> Result<usize, Error> {
let file = FileReader { file: &file };
fn load_segment(
space: &ProcessAddressSpace,
phdr: &ProgramHeader,
file: &FileReader,
) -> Result<(), Error> {
if phdr.p_memsz == 0 {
return Ok(());
}
let attrs = match (phdr.p_flags & PF_W, phdr.p_flags & PF_X) {
(0, 0) => MapAttributes::USER_READ,
(_, 0) => MapAttributes::USER_WRITE | MapAttributes::USER_READ,
(0, _) => MapAttributes::USER_READ,
(_, _) => MapAttributes::USER_WRITE | MapAttributes::USER_READ,
} | MapAttributes::NON_GLOBAL;
// Map the range
let aligned_start = (phdr.p_vaddr as usize) & !0xFFF;
let aligned_end = ((phdr.p_vaddr + phdr.p_memsz) as usize + 0xFFF) & !0xFFF;
space.map(
aligned_start,
aligned_end - aligned_start,
|_| phys::alloc_page(),
attrs,
)?;
if phdr.p_filesz > 0 {
load_bytes(
space,
phdr.p_vaddr as usize,
|off, mut dst| {
let mut source = file.file.borrow_mut();
source.seek(SeekFrom::Start(phdr.p_offset + off as u64))?;
source.read_exact(dst.deref_mut())
},
phdr.p_filesz as usize,
)?;
}
if phdr.p_memsz > phdr.p_filesz {
let addr = (phdr.p_vaddr + phdr.p_filesz) as usize;
let len = (phdr.p_memsz - phdr.p_filesz) as usize;
load_bytes(
space,
addr,
|_, mut dst| {
dst.fill(0);
Ok(())
},
len,
)?;
}
Ok(())
}
fn tls_segment(
space: &ProcessAddressSpace,
phdr: &ProgramHeader,
file: &FileReader,
) -> Result<ProcessTlsInfo, Error> {
assert_ne!(phdr.p_memsz, 0);
if !phdr.p_align.is_power_of_two() {
return Err(Error::InvalidArgument);
}
let layout = ProcessTlsLayout::new(phdr.p_align as _, phdr.p_filesz as _, phdr.p_memsz as _);
let data_offset = layout.data_offset;
let data_size = layout.data_size;
let mem_size = layout.mem_size;
let aligned_size = (layout.full_size + 0xFFF) & !0xFFF;
assert!(aligned_size <= 0x1000);
let base_address = space.allocate(
None,
aligned_size,
|_| phys::alloc_page(),
MapAttributes::USER_READ | MapAttributes::USER_WRITE | MapAttributes::NON_GLOBAL,
)?;
debugln!(
"Allocated TLS master copy @ {:#x}, tls={:#x}, tls_data={:#x}",
base_address,
base_address + layout.ptr_offset,
base_address + layout.data_offset
);
let tls = ProcessTlsInfo {
master_copy_base: base_address,
layout,
};
if data_size > 0 {
load_bytes(
space,
base_address + data_offset,
|off, mut dst| {
let mut source = file.file.borrow_mut();
source.seek(SeekFrom::Start(phdr.p_offset + off as u64))?;
source.read_exact(dst.deref_mut())
},
data_size,
);
}
if mem_size > data_size {
load_bytes(
space,
base_address + data_offset + data_size,
|off, mut dst| {
dst.fill(0);
Ok(())
},
mem_size - data_size,
);
}
Ok(tls)
}
/// Loads an ELF binary from `file` into the target address space
pub fn load_elf_from_file(
space: &ProcessAddressSpace,
file: FileRef,
) -> Result<ProcessImage, Error> {
let file = FileReader { file: &file };
let elf = ElfStream::<AnyEndian, _>::open_stream(file).map_err(from_parse_error)?;
struct TlsInfo {
master_address: usize,
offset: usize,
size: usize,
}
let mut tls = None;
for phdr in elf.segments() {
if phdr.p_type != PT_LOAD {
continue;
}
debugln!("LOAD {:#x?}", phdr.p_vaddr..phdr.p_vaddr + phdr.p_memsz);
let attrs = match (phdr.p_flags & PF_W, phdr.p_flags & PF_X) {
(0, 0) => MapAttributes::USER_READ,
(_, 0) => MapAttributes::USER_WRITE | MapAttributes::USER_READ,
(0, _) => MapAttributes::USER_READ,
(_, _) => MapAttributes::USER_WRITE | MapAttributes::USER_READ,
} | MapAttributes::NON_GLOBAL;
if phdr.p_memsz > 0 {
// Map the range
let aligned_start = (phdr.p_vaddr as usize) & !0xFFF;
let aligned_end = ((phdr.p_vaddr + phdr.p_memsz) as usize + 0xFFF) & !0xFFF;
space.map(
aligned_start,
aligned_end - aligned_start,
|_| phys::alloc_page(),
attrs,
)?;
} else {
continue;
}
if phdr.p_filesz > 0 {
load_bytes(
space,
phdr.p_vaddr as usize,
|off, mut dst| {
let mut source = file.file.borrow_mut();
source.seek(SeekFrom::Start(phdr.p_offset + off as u64))?;
source.read_exact(dst.deref_mut())
},
phdr.p_filesz as usize,
)?;
}
if phdr.p_memsz > phdr.p_filesz {
let addr = (phdr.p_vaddr + phdr.p_filesz) as usize;
let len = (phdr.p_memsz - phdr.p_filesz) as usize;
load_bytes(
space,
addr,
|_, mut dst| {
dst.fill(0);
Ok(())
},
len,
)?;
match phdr.p_type {
PT_LOAD => {
load_segment(space, phdr, &file)?;
}
PT_TLS => {
assert!(tls.is_none());
tls.replace(tls_segment(space, phdr, &file)?);
// tls_master_address = tls_segment(space, phdr, &file)?;
// tls_size = phdr.p_memsz as usize;
}
_ => (),
}
}
Ok(elf.ehdr.e_entry as usize)
Ok(ProcessImage {
entry: elf.ehdr.e_entry as usize,
tls,
})
}

View File

@ -11,11 +11,19 @@ use vfs::FileRef;
use crate::{
mem::{
phys, pointer::PhysicalRefMut, process::ProcessAddressSpace, table::MapAttributes,
phys,
pointer::{PhysicalRef, PhysicalRefMut},
process::ProcessAddressSpace,
table::MapAttributes,
ForeignPointer,
},
proc,
task::{context::TaskContextImpl, process::Process, thread::Thread, TaskContext},
task::{
context::TaskContextImpl,
process::{Process, ProcessImage},
thread::Thread,
TaskContext,
},
};
pub struct BufferPlacer<'a> {
@ -99,7 +107,7 @@ fn setup_program_env(
fn setup_binary<S: Into<String>>(
name: S,
space: ProcessAddressSpace,
entry: usize,
image: ProcessImage,
args: &[&str],
envs: &[&str],
) -> Result<(Arc<Process>, Arc<Thread>), Error> {
@ -120,7 +128,7 @@ fn setup_binary<S: Into<String>>(
debugln!(
"Entry: {:#x}, Stack: {:#x}..{:#x}, Args: {:#x}",
entry,
image.entry,
virt_stack_base,
virt_stack_base + USER_STACK_PAGES * 0x1000,
virt_args_base
@ -138,9 +146,22 @@ fn setup_binary<S: Into<String>>(
}
}
let context = TaskContext::user(entry, arg, space.as_address_with_asid(), user_sp)?;
let tls_address = proc::elf::clone_tls(&space, &image)?;
Ok(Process::new_with_main(name, Arc::new(space), context))
let context = TaskContext::user(
image.entry,
arg,
space.as_address_with_asid(),
user_sp,
tls_address,
)?;
Ok(Process::new_with_main(
name,
Arc::new(space),
context,
Some(image),
))
// Ok(Process::new_with_context(name, Some(space), context))
}
@ -152,7 +173,7 @@ pub fn load_elf<S: Into<String>>(
envs: &[&str],
) -> Result<(Arc<Process>, Arc<Thread>), Error> {
let space = ProcessAddressSpace::new()?;
let elf_entry = proc::elf::load_elf_from_file(&space, file)?;
let image = proc::elf::load_elf_from_file(&space, file)?;
setup_binary(name, space, elf_entry, args, envs)
setup_binary(name, space, image, args, envs)
}

View File

@ -4,7 +4,7 @@ use core::{mem::MaybeUninit, time::Duration};
use abi::{
error::Error,
io::{DeviceRequest, DirectoryEntry, FileAttr, FileMode, OpenOptions, RawFd, SeekFrom},
process::{ExitCode, Signal, SpawnOption, SpawnOptions},
process::{ExitCode, Signal, SpawnOption, SpawnOptions, ThreadSpawnOptions},
syscall::SyscallFunction,
};
use alloc::rc::Rc;
@ -68,15 +68,14 @@ fn syscall_handler(func: SyscallFunction, args: &[u64]) -> Result<usize, Error>
Ok(0)
}
SyscallFunction::Nanosleep => {
todo!();
// let seconds = args[0];
// let nanos = args[1] as u32;
// let duration = Duration::new(seconds, nanos);
let seconds = args[0];
let nanos = args[1] as u32;
let duration = Duration::new(seconds, nanos);
// block! {
// runtime::sleep(duration).await
// }
// .map(|_| 0)
block! {
runtime::sleep(duration).await
}
.map(|_| 0)
}
// Resource management
SyscallFunction::MapMemory => {
@ -361,6 +360,11 @@ fn syscall_handler(func: SyscallFunction, args: &[u64]) -> Result<usize, Error>
Ok(pid as _)
})
}
SyscallFunction::SpawnThread => {
let options = arg_user_ref::<ThreadSpawnOptions>(args[0] as usize)?;
let id = process.spawn_thread(options)?;
Ok(id.as_user() as _)
}
SyscallFunction::Exit => {
let code = ExitCode::from(args[0] as i32);
// TODO separate handlers for process exit and thread exit?
@ -368,6 +372,19 @@ fn syscall_handler(func: SyscallFunction, args: &[u64]) -> Result<usize, Error>
// Process::current().exit(code);
panic!();
}
SyscallFunction::SendSignal => {
let pid = ProcessId::from(args[0] as u32);
let signal = Signal::try_from(args[1] as u32).map_err(|_| Error::InvalidArgument)?;
let target = Process::get(pid).ok_or(Error::DoesNotExist)?;
target.raise_signal(signal);
Ok(0)
}
SyscallFunction::ExitSignal => {
panic!("Handled elsewhere");
// Process::current().exit_signal();
}
SyscallFunction::GetPid => Ok(u32::from(process.id()) as usize),
SyscallFunction::GetSessionId => todo!(),
SyscallFunction::GetProcessGroupId => todo!(),
@ -419,20 +436,6 @@ fn syscall_handler(func: SyscallFunction, args: &[u64]) -> Result<usize, Error>
Ok(0)
}
_ => todo!("{:?}", func),
// SyscallFunction::SendSignal => {
// let pid = args[0] as u32;
// let signal = Signal::try_from(args[1] as u32).map_err(|_| Error::InvalidArgument)?;
// let target = Process::get(pid as _).ok_or(Error::DoesNotExist)?;
// target.raise_signal(signal);
// Ok(0)
// }
// SyscallFunction::ExitSignal => {
// panic!("Handled elsewhere");
// // Process::current().exit_signal();
// }
}
}

View File

@ -58,7 +58,13 @@ pub trait TaskContextImpl: Sized {
/// Constructs a user thread context. The caller is responsible for allocating the userspace
/// stack and setting up a valid address space for the context.
fn user(entry: usize, arg: usize, cr3: u64, user_stack_sp: usize) -> Result<Self, Error>;
fn user(
entry: usize,
arg: usize,
cr3: u64,
user_stack_sp: usize,
tls_address: usize,
) -> Result<Self, Error>;
/// Performs an entry into a context.
///

View File

@ -2,6 +2,7 @@
use core::{
fmt,
mem::size_of,
pin::Pin,
sync::atomic::{AtomicU64, Ordering},
task::{Context, Poll},
@ -9,7 +10,7 @@ use core::{
use abi::{
error::Error,
process::{ExitCode, Signal},
process::{ExitCode, Signal, ThreadSpawnOptions},
};
use alloc::{
collections::{BTreeMap, VecDeque},
@ -21,7 +22,17 @@ use futures_util::Future;
use kernel_util::util::OneTimeInit;
use vfs::VnodeRef;
use crate::{mem::process::ProcessAddressSpace, proc::io::ProcessIo, sync::IrqSafeSpinlock};
use crate::{
mem::{
phys,
pointer::{PhysicalRef, PhysicalRefMut},
process::ProcessAddressSpace,
table::MapAttributes,
},
proc::{self, io::ProcessIo},
sync::IrqSafeSpinlock,
task::context::TaskContextImpl,
};
use super::{
runtime::QueueWaker,
@ -39,6 +50,80 @@ pub enum ProcessState {
#[repr(transparent)]
pub struct ProcessId(u64);
// TLS layout (x86-64):
// | mem_size | uthread_size |
// | Data .......| self, ??? |
//
// TLS layout (aarch64):
// | uthread_size (0x10?) | mem_size |
// | ??? | Data .....|
#[derive(Debug)]
pub struct ProcessTlsInfo {
pub master_copy_base: usize,
pub layout: ProcessTlsLayout,
}
#[derive(Debug)]
pub struct ProcessTlsLayout {
pub data_offset: usize,
pub uthread_offset: usize,
pub ptr_offset: usize,
pub data_size: usize,
pub mem_size: usize,
pub full_size: usize,
}
#[cfg(target_arch = "aarch64")]
impl ProcessTlsLayout {
pub fn new(align: usize, data_size: usize, mem_size: usize) -> Self {
debug_assert!(align.is_power_of_two());
let tls_block0_offset = (size_of::<usize>() * 2 + align - 1) & !(align - 1);
let full_size = (tls_block0_offset + mem_size + align - 1) & !(align - 1);
Self {
data_offset: tls_block0_offset,
uthread_offset: 0,
ptr_offset: 0,
data_size,
mem_size,
full_size,
}
}
}
#[cfg(target_arch = "x86_64")]
impl ProcessTlsLayout {
pub fn new(align: usize, data_size: usize, mem_size: usize) -> Self {
// The static TLS blocks are placed below TP
// TP points to the TCB
debug_assert!(align.is_power_of_two());
let back_size = (mem_size + align - 1) & !(align - 1);
// Self-pointer
let forward_size = size_of::<usize>();
let full_size = back_size + forward_size;
Self {
data_offset: 0,
uthread_offset: back_size,
ptr_offset: back_size,
data_size,
mem_size,
full_size,
}
}
}
pub struct ProcessImage {
pub entry: usize,
pub tls: Option<ProcessTlsInfo>,
}
struct ProcessInner {
state: ProcessState,
@ -56,6 +141,7 @@ pub struct Process {
space: Arc<ProcessAddressSpace>,
inner: IrqSafeSpinlock<ProcessInner>,
image: Option<ProcessImage>,
exit_waker: QueueWaker,
pub io: IrqSafeSpinlock<ProcessIo>,
@ -69,6 +155,7 @@ impl Process {
name: S,
space: Arc<ProcessAddressSpace>,
context: TaskContext,
image: Option<ProcessImage>,
) -> (Arc<Self>, Arc<Thread>) {
let name = name.into();
let id = ProcessId::next();
@ -77,6 +164,8 @@ impl Process {
name,
id,
image,
space: space.clone(),
inner: IrqSafeSpinlock::new(ProcessInner {
state: ProcessState::Running,
@ -99,6 +188,37 @@ impl Process {
(process, thread)
}
pub fn spawn_thread(self: &Arc<Self>, options: &ThreadSpawnOptions) -> Result<ThreadId, Error> {
debugln!(
"Spawn thread in {} with options: {:#x?}",
self.id(),
options
);
let tls_address = if let Some(image) = self.image.as_ref() {
proc::elf::clone_tls(&self.space, image)?
} else {
0
};
let space = self.space.clone();
let context = TaskContext::user(
options.entry as _,
options.argument as _,
space.as_address_with_asid(),
options.stack_top,
tls_address,
)?;
let thread = Thread::new_uthread(self.clone(), space, context);
let id = thread.id();
self.inner.lock().threads.push(thread.clone());
thread.enqueue_somewhere();
Ok(id)
}
pub fn id(&self) -> ProcessId {
self.id
}

View File

@ -416,6 +416,13 @@ impl ThreadId {
let id = COUNT.fetch_add(1, Ordering::SeqCst);
Self::User(id)
}
pub fn as_user(&self) -> u64 {
match self {
Self::Kernel(_) => panic!(),
&Self::User(id) => id,
}
}
}
impl fmt::Display for ThreadId {