diff --git a/src/kernel/src/arnd.rs b/src/kernel/src/arnd.rs index 4bb766932..905859147 100644 --- a/src/kernel/src/arnd.rs +++ b/src/kernel/src/arnd.rs @@ -1,5 +1,5 @@ use std::ops::DerefMut; -use std::sync::Mutex; +use std::sync::{Arc, Mutex}; /// Random number generator based on /// https://github.com/freebsd/freebsd-src/blob/release/9.1.0/sys/libkern/arc4random.c. @@ -9,16 +9,16 @@ pub struct Arnd { } impl Arnd { - pub fn new() -> Self { + pub fn new() -> Arc { let mut sbox = [0u8; 256]; for (i, e) in sbox.iter_mut().enumerate() { *e = i as u8; } - Self { + Arc::new(Self { state: Mutex::new(State { i: 0, j: 0, sbox }), - } + }) } pub fn rand_bytes(&self, buf: &mut [u8]) { diff --git a/src/kernel/src/ee/llvm/codegen.rs b/src/kernel/src/ee/llvm/codegen.rs index 3e91de2d8..ac2b3d4ae 100644 --- a/src/kernel/src/ee/llvm/codegen.rs +++ b/src/kernel/src/ee/llvm/codegen.rs @@ -3,13 +3,13 @@ use crate::llvm::module::LlvmModule; use thiserror::Error; /// Contains states for lifting a module. -pub(super) struct Codegen<'a, 'b: 'a> { +pub(super) struct Codegen<'a> { input: Disassembler<'a>, - output: &'a mut LlvmModule<'b>, + output: &'a mut LlvmModule, } -impl<'a, 'b: 'a> Codegen<'a, 'b> { - pub fn new(input: Disassembler<'a>, output: &'a mut LlvmModule<'b>) -> Self { +impl<'a> Codegen<'a> { + pub fn new(input: Disassembler<'a>, output: &'a mut LlvmModule) -> Self { Self { input, output } } diff --git a/src/kernel/src/ee/llvm/mod.rs b/src/kernel/src/ee/llvm/mod.rs index bdcafb733..af868776b 100644 --- a/src/kernel/src/ee/llvm/mod.rs +++ b/src/kernel/src/ee/llvm/mod.rs @@ -1,38 +1,29 @@ use self::codegen::Codegen; -use super::{EntryArg, ExecutionEngine}; +use super::{ExecutionEngine, SysErr, SysIn, SysOut}; use crate::disasm::Disassembler; use crate::fs::VPathBuf; use crate::llvm::Llvm; -use crate::rtld::{Module, RuntimeLinker}; -use std::ops::Deref; +use crate::rtld::Module; +use std::sync::Arc; use thiserror::Error; mod codegen; /// An implementation of [`ExecutionEngine`] using JIT powered by LLVM IR. +#[derive(Debug)] pub struct LlvmEngine { - llvm: &'static Llvm, - rtld: &'static RuntimeLinker, + llvm: Arc, } impl LlvmEngine { - pub fn new(llvm: &'static Llvm, rtld: &'static RuntimeLinker) -> Self { - Self { llvm, rtld } - } - - pub fn lift_initial_modules(&mut self) -> Result<(), LiftError> { - for module in self.rtld.list().deref() { - // TODO: Store the lifted module somewhere. - self.lift(module)?; - } - - Ok(()) + pub fn new(llvm: &Arc) -> Arc { + Arc::new(Self { llvm: llvm.clone() }) } fn lift( &self, - module: &Module, - ) -> Result, LiftError> { + module: &Module, + ) -> Result { // Get a list of public functions. let path = module.path(); let targets = match module.entry() { @@ -74,20 +65,62 @@ impl LlvmEngine { } impl ExecutionEngine for LlvmEngine { - type RunErr = RunError; + type RawFn = RawFn; + type SetupModuleErr = SetupModuleError; + type GetFunctionErr = GetFunctionError; + + fn register_syscall( + &self, + id: u32, + o: &Arc, + h: fn(&Arc, &SysIn) -> Result, + ) { + todo!() + } + + fn setup_module(&self, md: &mut Module) -> Result<(), Self::SetupModuleErr> + where + E: ExecutionEngine, + { + todo!() + } - unsafe fn run(&mut self, arg: EntryArg) -> Result<(), Self::RunErr> { + unsafe fn get_function( + &self, + md: &Arc>, + addr: usize, + ) -> Result, Self::GetFunctionErr> + where + E: ExecutionEngine, + { todo!() } } -/// Represent an error when [`LlvmEngine::run()`] is failed. +/// An implementation of [`ExecutionEngine::RawFn`]. +pub struct RawFn {} + +impl super::RawFn for RawFn { + fn addr(&self) -> usize { + todo!() + } + + unsafe fn exec1(&self, a: A) -> R { + todo!() + } +} + +/// An implementation of [`ExecutionEngine::SetupModuleErr`]. +#[derive(Debug, Error)] +pub enum SetupModuleError {} + +/// An implementation of [`ExecutionEngine::GetFunctionErr`]. #[derive(Debug, Error)] -pub enum RunError {} +pub enum GetFunctionError {} /// Represents an error when module lifting is failed. #[derive(Debug, Error)] -pub enum LiftError { +enum LiftError { #[error("cannot disassemble function {1:#018x} on {0}")] DisassembleFailed(VPathBuf, usize, #[source] crate::disasm::DisassembleError), diff --git a/src/kernel/src/ee/mod.rs b/src/kernel/src/ee/mod.rs index 1ebbc3d71..b7b227eff 100644 --- a/src/kernel/src/ee/mod.rs +++ b/src/kernel/src/ee/mod.rs @@ -1,11 +1,15 @@ use crate::arnd::Arnd; +use crate::errno::{strerror, Errno, ENAMETOOLONG, ENOENT}; +use crate::fs::{VPath, VPathBuf}; use crate::memory::MemoryManager; use crate::process::{ResourceLimit, VProc}; use crate::rtld::Module; use std::error::Error; -use std::ffi::CString; +use std::ffi::{c_char, CStr, CString}; +use std::fmt::{Debug, Display, Formatter}; use std::marker::PhantomPinned; use std::mem::size_of_val; +use std::num::{NonZeroI32, TryFromIntError}; use std::pin::Pin; use std::sync::Arc; @@ -14,22 +18,249 @@ pub mod llvm; pub mod native; /// An object to execute the PS4 binary. -pub trait ExecutionEngine: Sync + 'static { - type RunErr: Error; +pub trait ExecutionEngine: Debug + Send + Sync + 'static { + type RawFn: RawFn; + type SetupModuleErr: Error; + type GetFunctionErr: Error; - /// This method will never return in case of success. + /// The implementor must not have any variable that need to be dropped on the stack before + /// invoking the registered handler. The reason is because the handler might exit the calling + /// thread without returning from the handler. /// + /// See https://github.com/freebsd/freebsd-src/blob/release/9.1.0/sys/kern/init_sysent.c#L36 for + /// standard FreeBSD syscalls. + /// + /// # Panics + /// If `id` is not a valid number or the syscall with identifier `id` is already registered. + fn register_syscall( + &self, + id: u32, + o: &Arc, + h: fn(&Arc, &SysIn) -> Result, + ); + + // TODO: Is it possible to force E as Self? + fn setup_module(&self, md: &mut Module) -> Result<(), Self::SetupModuleErr> + where + E: ExecutionEngine; + + // TODO: Is it possible to force E as Self? + unsafe fn get_function( + &self, + md: &Arc>, + addr: usize, + ) -> Result, Self::GetFunctionErr> + where + E: ExecutionEngine; +} + +/// A function that was produced by [`ExecutionEngine`]. +pub trait RawFn: Send + Sync + 'static { + fn addr(&self) -> usize; + /// # Safety - /// This method will transfer control to the PS4 application. If the PS4 application is not in - /// the correct state calling this method will cause undefined behavior. - unsafe fn run(&mut self, arg: EntryArg) -> Result<(), Self::RunErr>; + /// The provided signature must be matched with the underlying function. + unsafe fn exec1(&self, a: A) -> R; +} + +/// Input of the syscall handler. +#[repr(C)] +pub struct SysIn<'a> { + pub id: u32, + pub offset: usize, + pub module: &'a VPathBuf, + pub args: [SysArg; 6], +} + +/// An argument of the syscall. +#[repr(transparent)] +#[derive(Clone, Copy)] +pub struct SysArg(usize); + +impl SysArg { + pub unsafe fn to_path<'a>(self) -> Result, SysErr> { + if self.0 == 0 { + return Ok(None); + } + + // TODO: Check maximum path length on the PS4. + let path = CStr::from_ptr(self.0 as _); + let path = match path.to_str() { + Ok(v) => match VPath::new(v) { + Some(v) => v, + None => todo!("syscall with non-absolute path {v}"), + }, + Err(_) => return Err(SysErr::Raw(ENOENT)), + }; + + Ok(Some(path)) + } + + /// See `copyinstr` on the PS4 for a reference. + pub unsafe fn to_str<'a>(self, max: usize) -> Result, SysErr> { + if self.0 == 0 { + return Ok(None); + } + + let ptr = self.0 as *const c_char; + let mut len = None; + + for i in 0..max { + if *ptr.add(i) == 0 { + len = Some(i); + break; + } + } + + match len { + Some(i) => Ok(Some( + std::str::from_utf8(std::slice::from_raw_parts(ptr as _, i)).unwrap(), + )), + None => Err(SysErr::Raw(ENAMETOOLONG)), + } + } + + pub fn get(self) -> usize { + self.0 + } +} + +impl From for *const T { + fn from(v: SysArg) -> Self { + v.0 as _ + } +} + +impl From for *mut T { + fn from(v: SysArg) -> Self { + v.0 as _ + } +} + +impl From for u64 { + fn from(v: SysArg) -> Self { + v.0 as _ + } +} + +impl From for usize { + fn from(v: SysArg) -> Self { + v.0 + } +} + +impl TryFrom for i32 { + type Error = TryFromIntError; + + fn try_from(v: SysArg) -> Result { + TryInto::::try_into(v.0).map(|v| v as i32) + } +} + +impl TryFrom for u32 { + type Error = TryFromIntError; + + fn try_from(v: SysArg) -> Result { + v.0.try_into() + } +} + +/// Outputs of the syscall handler. +#[repr(C)] +#[derive(Clone, Copy)] +pub struct SysOut { + rax: usize, + rdx: usize, +} + +impl SysOut { + pub const ZERO: Self = Self { rax: 0, rdx: 0 }; +} + +impl From<*mut T> for SysOut { + fn from(value: *mut T) -> Self { + Self { + rax: value as _, + rdx: 0, + } + } +} + +impl From for SysOut { + fn from(value: i32) -> Self { + Self { + rax: value as isize as usize, // Sign extended. + rdx: 0, + } + } +} + +impl From for SysOut { + fn from(value: usize) -> Self { + Self { rax: value, rdx: 0 } + } +} + +impl From for SysOut { + fn from(value: NonZeroI32) -> Self { + Self { + rax: value.get() as isize as usize, // Sign extended. + rdx: 0, + } + } +} + +/// Error of each syscall. +#[derive(Debug)] +pub enum SysErr { + Raw(NonZeroI32), + Object(Box), +} + +impl SysErr { + pub fn errno(&self) -> NonZeroI32 { + match self { + Self::Raw(v) => *v, + Self::Object(v) => v.errno(), + } + } +} + +impl From> for SysErr { + fn from(value: Box) -> Self { + Self::Object(value) + } +} + +impl From for SysErr { + fn from(value: T) -> Self { + Self::Object(Box::new(value)) + } +} + +impl Error for SysErr { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match self { + Self::Raw(_) => None, + Self::Object(e) => e.source(), + } + } +} + +impl Display for SysErr { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Self::Raw(v) => f.write_str(strerror(*v)), + Self::Object(e) => Display::fmt(&e, f), + } + } } /// Encapsulate an argument of the PS4 entry point. -pub struct EntryArg { - vp: &'static VProc, - mm: &'static MemoryManager, - app: Arc, +pub struct EntryArg { + vp: Arc, + mm: Arc, + app: Arc>, name: CString, path: CString, canary: [u8; 64], @@ -38,13 +269,8 @@ pub struct EntryArg { _pin: PhantomPinned, } -impl EntryArg { - pub fn new( - arnd: &Arnd, - vp: &'static VProc, - mm: &'static MemoryManager, - app: Arc, - ) -> Self { +impl EntryArg { + pub fn new(arnd: &Arnd, vp: &Arc, mm: &Arc, app: Arc>) -> Self { let path = app.path(); let name = CString::new(path.file_name().unwrap()).unwrap(); let path = CString::new(path.as_str()).unwrap(); @@ -53,8 +279,8 @@ impl EntryArg { arnd.rand_bytes(&mut canary); Self { - vp, - mm, + vp: vp.clone(), + mm: mm.clone(), app, name, path, @@ -93,7 +319,8 @@ impl EntryArg { pin.vec.push(8); // AT_FLAGS pin.vec.push(0); pin.vec.push(9); // AT_ENTRY - pin.vec.push(mem.addr() + pin.app.entry().unwrap()); + pin.vec + .push(mem.addr() + mem.base() + pin.app.entry().unwrap()); pin.vec.push(7); // AT_BASE pin.vec.push( (mem.addr() diff --git a/src/kernel/src/ee/native/mod.rs b/src/kernel/src/ee/native/mod.rs index ea7732586..66ebcd3f0 100644 --- a/src/kernel/src/ee/native/mod.rs +++ b/src/kernel/src/ee/native/mod.rs @@ -1,64 +1,39 @@ -use super::{EntryArg, ExecutionEngine}; +use super::{ExecutionEngine, SysErr, SysIn, SysOut}; use crate::fs::{VPath, VPathBuf}; use crate::memory::{MemoryManager, Protections}; -use crate::process::VProc; -use crate::rtld::{CodeWorkspaceError, Memory, Module, RuntimeLinker, UnprotectSegmentError}; -use crate::syscalls::{Input, Output, Syscalls}; +use crate::rtld::{CodeWorkspaceError, Memory, Module, UnprotectSegmentError}; +use crate::warn; use byteorder::{ByteOrder, LE}; use iced_x86::code_asm::{ dword_ptr, qword_ptr, r10, r11, r11d, r12, r8, r9, rax, rbp, rbx, rcx, rdi, rdx, rsi, rsp, CodeAssembler, }; -use llt::Thread; +use std::any::Any; +use std::fmt::{Debug, Formatter}; use std::mem::{size_of, transmute}; -use std::ops::Deref; +use std::sync::{Arc, OnceLock}; use thiserror::Error; /// An implementation of [`ExecutionEngine`] for running the PS4 binary natively. pub struct NativeEngine { - vp: &'static VProc, - mm: &'static MemoryManager, - rtld: &'static RuntimeLinker, - syscalls: &'static Syscalls, + mm: Arc, + syscalls: [OnceLock Result + Send + Sync>>; 678], } impl NativeEngine { - pub fn new( - vp: &'static VProc, - mm: &'static MemoryManager, - rtld: &'static RuntimeLinker, - syscalls: &'static Syscalls, - ) -> Self { - Self { - vp, - mm, - rtld, - syscalls, - } - } - - /// # SAFETY - /// No other threads may read or write any module memory. - pub unsafe fn patch_mods(&mut self) -> Result, PatchModsError> { - let mut counts: Vec<(VPathBuf, usize)> = Vec::new(); - - for module in self.rtld.list().deref() { - let count = self.patch_mod(module)?; - let path = module.path(); - - counts.push((path.to_owned(), count)); - } - - Ok(counts) - } - - fn syscalls(&self) -> *const Syscalls { - self.syscalls + pub fn new(mm: &Arc) -> Arc { + Arc::new(Self { + mm: mm.clone(), + syscalls: std::array::from_fn(|_| OnceLock::new()), + }) } /// # Safety /// No other threads may access the memory of `module`. - unsafe fn patch_mod(&self, module: &Module) -> Result { + unsafe fn patch_mod(&self, module: &Module) -> Result + where + E: ExecutionEngine, + { let path = module.path(); // Patch all executable sections. @@ -74,13 +49,7 @@ impl NativeEngine { // Unprotect the segment. let mut seg = match mem.unprotect_segment(i) { Ok(v) => v, - Err(e) => { - return Err(PatchModsError::UnprotectSegmentFailed( - path.to_owned(), - i, - e, - )); - } + Err(e) => return Err(SetupModuleError::UnprotectSegmentFailed(i, e)), }; // Patch segment. @@ -98,7 +67,7 @@ impl NativeEngine { base: usize, mem: &Memory, seg: &mut [u8], - ) -> Result { + ) -> Result { let addr = seg.as_ptr() as usize; let offset = addr - base; let mut count = 0; @@ -143,7 +112,7 @@ impl NativeEngine { target: &mut [u8], offset: usize, addr: usize, - ) -> Result, PatchModsError> { + ) -> Result, SetupModuleError> { // Check if "mov rax, imm32". if target.len() < 7 { return Ok(None); @@ -161,19 +130,13 @@ impl NativeEngine { let ret = addr + 7 + 5; let tp = match self.build_syscall_trampoline(mem, module, offset + 10, id, ret) { Ok(v) => v, - Err(e) => { - return Err(PatchModsError::BuildTrampolineFailed( - module.to_owned(), - offset, - e, - )) - } + Err(e) => return Err(SetupModuleError::BuildTrampolineFailed(offset, e)), }; // Patch "mov rax, imm32" with "jmp rel32". let tp = match Self::get_relative_offset(addr + 5, tp) { Some(v) => v.to_ne_bytes(), - None => return Err(PatchModsError::WorkspaceTooFar(module.to_owned())), + None => return Err(SetupModuleError::WorkspaceTooFar), }; target[0] = 0xe9; @@ -203,18 +166,12 @@ impl NativeEngine { target: &mut [u8], offset: usize, addr: usize, - ) -> Result, PatchModsError> { + ) -> Result, SetupModuleError> { // Build trampoline. let ret = addr + 2 + 5; let tp = match self.build_int44_trampoline(mem, module, offset, ret) { Ok(v) => v, - Err(e) => { - return Err(PatchModsError::BuildTrampolineFailed( - module.to_owned(), - offset, - e, - )) - } + Err(e) => return Err(SetupModuleError::BuildTrampolineFailed(offset, e)), }; // Patch "int 0x44" with "nop". @@ -224,7 +181,7 @@ impl NativeEngine { // Patch "mov edx, 0xcccccccc" with "jmp rel32". let tp = match Self::get_relative_offset(addr + 7, tp) { Some(v) => v.to_ne_bytes(), - None => return Err(PatchModsError::WorkspaceTooFar(module.to_owned())), + None => return Err(SetupModuleError::WorkspaceTooFar), }; target[2] = 0xe9; @@ -248,8 +205,8 @@ impl NativeEngine { ) -> Result { let mut asm = CodeAssembler::new(64).unwrap(); - assert_eq!(72, size_of::()); - assert_eq!(16, size_of::()); + assert_eq!(72, size_of::()); + assert_eq!(16, size_of::()); // Create function prologue. asm.push(rbp).unwrap(); @@ -297,8 +254,8 @@ impl NativeEngine { asm.add(rax, 0x50).unwrap(); asm.mov(rdx, rax).unwrap(); asm.mov(rsi, rbx).unwrap(); - asm.mov(rdi, self.syscalls() as u64).unwrap(); - asm.mov(rax, Syscalls::invoke as u64).unwrap(); + asm.mov(rdi, self as *const Self as u64).unwrap(); + asm.mov(rax, Self::syscall as u64).unwrap(); asm.call(rax).unwrap(); // Check error. This mimic the behavior of @@ -340,6 +297,30 @@ impl NativeEngine { self.write_trampoline(mem, ret, asm) } + /// # Safety + /// This method cannot be called from Rust. + unsafe extern "sysv64" fn syscall(&self, i: &SysIn, o: &mut SysOut) -> i64 { + // Beware that we cannot have any variable that need to be dropped before calling the + // handler. + let h = match self.syscalls[i.id as usize].get() { + Some(v) => v, + None => todo!("syscall {} at {:#x} on {}", i.id, i.offset, i.module), + }; + + // Execute the handler. + let v = match h(i) { + Ok(v) => v, + Err(e) => { + warn!(e, "Syscall {} failed", i.id); + return e.errno().get().into(); + } + }; + + // Write the output. + *o = v; + 0 + } + /// # Safety /// No other threads may execute the memory in the code workspace on `mem`. unsafe fn build_int44_trampoline( @@ -368,8 +349,8 @@ impl NativeEngine { asm.mov(rdx, module as u64).unwrap(); asm.mov(rsi, offset as u64).unwrap(); - asm.mov(rdi, self.syscalls() as u64).unwrap(); - asm.mov(rax, Syscalls::int44 as u64).unwrap(); + asm.mov(rdi, self as *const Self as u64).unwrap(); + asm.mov(rax, Self::int44 as u64).unwrap(); asm.call(rax).unwrap(); // Restore registers. @@ -382,6 +363,12 @@ impl NativeEngine { self.write_trampoline(mem, ret, asm) } + /// # Safety + /// This method cannot be called from Rust. + unsafe extern "sysv64" fn int44(&self, offset: usize, module: &VPathBuf) -> ! { + panic!("Exiting with int 0x44 at {offset:#x} on {module}."); + } + /// # Safety /// No other threads may execute the memory in the code workspace on `mem`. unsafe fn write_trampoline( @@ -449,87 +436,86 @@ impl NativeEngine { offset.try_into().ok() } +} - #[cfg(unix)] - fn join_thread(thr: Thread) -> Result<(), std::io::Error> { - let err = unsafe { libc::pthread_join(thr, std::ptr::null_mut()) }; +impl ExecutionEngine for NativeEngine { + type RawFn = RawFn; + type SetupModuleErr = SetupModuleError; + type GetFunctionErr = GetFunctionError; - if err != 0 { - Err(std::io::Error::from_raw_os_error(err)) - } else { - Ok(()) - } + fn register_syscall( + &self, + id: u32, + o: &Arc, + h: fn(&Arc, &SysIn) -> Result, + ) { + let o = o.clone(); + + assert!(self.syscalls[id as usize] + .set(Box::new(move |i| h(&o, i))) + .is_ok()); } - #[cfg(windows)] - fn join_thread(thr: Thread) -> Result<(), std::io::Error> { - use windows_sys::Win32::Foundation::{CloseHandle, WAIT_OBJECT_0}; - use windows_sys::Win32::System::Threading::{WaitForSingleObject, INFINITE}; - - if unsafe { WaitForSingleObject(thr, INFINITE) } != WAIT_OBJECT_0 { - return Err(std::io::Error::last_os_error()); - } - - assert_ne!(unsafe { CloseHandle(thr) }, 0); - + fn setup_module(&self, md: &mut Module) -> Result<(), Self::SetupModuleErr> + where + E: ExecutionEngine, + { + unsafe { self.patch_mod(md)? }; Ok(()) } -} - -impl ExecutionEngine for NativeEngine { - type RunErr = RunError; - unsafe fn run(&mut self, arg: EntryArg) -> Result<(), Self::RunErr> { - // Get eboot.bin. - if self.rtld.app().file_info().is_none() { - todo!("statically linked eboot.bin"); - } - - // Get entry point. - let boot = self.rtld.kernel().unwrap(); - let mem = boot.memory().addr(); - let entry: unsafe extern "sysv64" fn(*const usize) -> ! = - unsafe { transmute(mem + boot.entry().unwrap()) }; - - // Spawn main thread. - let stack = self.mm.stack(); - let mut arg = Box::pin(arg); - let entry = move || unsafe { entry(arg.as_mut().as_vec().as_ptr()) }; - let runner = match self.vp.new_thread(stack.start(), stack.len(), entry) { - Ok(v) => v, - Err(e) => return Err(RunError::CreateMainThreadFailed(e)), - }; - - // Wait for main thread to exit. This should never return. - if let Err(e) = Self::join_thread(runner) { - return Err(RunError::JoinMainThreadFailed(e)); - } + unsafe fn get_function( + &self, + md: &Arc>, + addr: usize, + ) -> Result, Self::GetFunctionErr> + where + E: ExecutionEngine, + { + Ok(Arc::new(RawFn { + md: md.clone(), + addr, + })) + } +} - Ok(()) +impl Debug for NativeEngine { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("NativeEngine") + .field("mm", &self.mm) + .finish() } } -/// Represents an error when [`NativeEngine::run()`] is failed. -#[derive(Debug, Error)] -pub enum RunError { - #[error("cannot create main thread")] - CreateMainThreadFailed(#[source] llt::SpawnError), +/// An implementation of [`super::RawFn`]. +pub struct RawFn { + #[allow(unused)] + md: Arc, // Keep module alive. + addr: usize, +} + +impl super::RawFn for RawFn { + fn addr(&self) -> usize { + self.addr + } - #[error("cannot join with main thread")] - JoinMainThreadFailed(#[source] std::io::Error), + unsafe fn exec1(&self, a: A) -> R { + let f: unsafe extern "sysv64" fn(A) -> R = transmute(self.addr); + f(a) + } } -/// Represents an error when [`NativeEngine::patch_mods()`] is failed. +/// An implementation of [`ExecutionEngine::SetupModuleErr`]. #[derive(Debug, Error)] -pub enum PatchModsError { - #[error("cannot unprotect segment {1} on {0}")] - UnprotectSegmentFailed(VPathBuf, usize, #[source] UnprotectSegmentError), +pub enum SetupModuleError { + #[error("cannot unprotect segment {0}")] + UnprotectSegmentFailed(usize, #[source] UnprotectSegmentError), - #[error("cannot build a trampoline for {1:#018x} on {0}")] - BuildTrampolineFailed(VPathBuf, usize, #[source] TrampolineError), + #[error("cannot build a trampoline for {0:#x}")] + BuildTrampolineFailed(usize, #[source] TrampolineError), - #[error("workspace of {0} is too far")] - WorkspaceTooFar(VPathBuf), + #[error("module workspace is too far")] + WorkspaceTooFar, } /// Errors for trampoline building. @@ -544,3 +530,7 @@ pub enum TrampolineError { #[error("the address to return is too far")] ReturnTooFar, } + +/// An implementation of [`ExecutionEngine::GetFunctionErr`]. +#[derive(Debug, Error)] +pub enum GetFunctionError {} diff --git a/src/kernel/src/fs/file.rs b/src/kernel/src/fs/file.rs index aeeb17ddd..dad923a6a 100644 --- a/src/kernel/src/fs/file.rs +++ b/src/kernel/src/fs/file.rs @@ -6,19 +6,20 @@ use bitflags::bitflags; use std::fmt::{Debug, Display, Formatter}; use std::ops::Deref; use std::sync::atomic::Ordering; +use std::sync::Arc; /// An implementation of `file` structure. #[derive(Debug)] -pub struct VFile<'a> { - fs: &'a Fs, - ops: Option>, // f_data + f_ops - flags: VFileFlags, // f_flag +pub struct VFile { + fs: Arc, + ops: Option>, // f_data + f_ops + flags: VFileFlags, // f_flag } -impl<'a> VFile<'a> { - pub(super) fn new(fs: &'a Fs) -> Self { +impl VFile { + pub(super) fn new(fs: &Arc) -> Self { Self { - fs, + fs: fs.clone(), flags: VFileFlags::empty(), ops: None, } @@ -28,7 +29,7 @@ impl<'a> VFile<'a> { self.ops.as_ref().map(|o| o.deref()) } - pub fn set_ops(&mut self, v: Option>) { + pub fn set_ops(&mut self, v: Option>) { self.ops = v; } @@ -41,13 +42,13 @@ impl<'a> VFile<'a> { } } -impl<'a> Drop for VFile<'a> { +impl Drop for VFile { fn drop(&mut self) { self.fs.opens.fetch_sub(1, Ordering::Relaxed); } } -impl<'a> Display for VFile<'a> { +impl Display for VFile { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { Display::fmt(self.ops.as_ref().unwrap(), f) } diff --git a/src/kernel/src/fs/item.rs b/src/kernel/src/fs/item.rs index 3bc69ea24..b4d4a9cae 100644 --- a/src/kernel/src/fs/item.rs +++ b/src/kernel/src/fs/item.rs @@ -1,19 +1,19 @@ -use super::{Fs, FsError, VFileOps, VPath, VPathBuf}; +use super::{FsError, VFileOps, VPath, VPathBuf}; use crate::console::Console; use std::path::{Path, PathBuf}; /// An item in the virtual filesystem. -pub enum FsItem<'a> { +pub enum FsItem { Directory(HostDir), File(HostFile), - Device(VDev<'a>), + Device(VDev), } -impl<'a> FsItem<'a> { +impl FsItem { pub fn is_character(&self) -> bool { match self { Self::Device(d) => match d { - VDev::Console(_) => true, + VDev::Console => true, }, _ => false, } @@ -24,12 +24,12 @@ impl<'a> FsItem<'a> { Self::Directory(v) => &v.vpath, Self::File(v) => &v.vpath, Self::Device(d) => match d { - VDev::Console(_) => Console::PATH, + VDev::Console => Console::PATH, }, } } - pub fn open(&self) -> Result, FsError> { + pub fn open(&self) -> Result, FsError> { match self { Self::Directory(_) => todo!("VFileOps for host directory"), Self::File(_) => todo!("VFileOps for host file"), @@ -79,14 +79,14 @@ impl HostFile { } /// A virtual device. -pub enum VDev<'a> { - Console(&'a Fs), +pub enum VDev { + Console, } -impl<'a> VDev<'a> { - pub fn open(&self) -> Result, FsError> { +impl VDev { + pub fn open(&self) -> Result, FsError> { let ops = match self { - Self::Console(_) => Box::new(Console::new()), + Self::Console => Box::new(Console::new()), }; Ok(ops) diff --git a/src/kernel/src/fs/mod.rs b/src/kernel/src/fs/mod.rs index 62f040231..cd845df1e 100644 --- a/src/kernel/src/fs/mod.rs +++ b/src/kernel/src/fs/mod.rs @@ -2,16 +2,22 @@ pub use self::file::*; pub use self::item::*; pub use self::path::*; -use crate::errno::{Errno, ENOENT}; +use crate::ee::{ExecutionEngine, SysArg, SysErr, SysIn, SysOut}; +use crate::errno::{Errno, EBADF, EINVAL, ENOENT, ENOTTY}; +use crate::info; +use crate::process::{VProc, VThread}; +use crate::ucred::Privilege; +use bitflags::bitflags; use gmtx::{GroupMutex, MutexGroup}; use param::Param; use std::borrow::Borrow; use std::collections::HashMap; -use std::fmt::Debug; +use std::fmt::{Display, Formatter}; use std::fs::File; -use std::num::NonZeroI32; +use std::num::{NonZeroI32, TryFromIntError}; use std::path::PathBuf; use std::sync::atomic::{AtomicI32, Ordering}; +use std::sync::Arc; use thiserror::Error; mod file; @@ -21,13 +27,19 @@ mod path; /// A virtual filesystem for emulating a PS4 filesystem. #[derive(Debug)] pub struct Fs { + vp: Arc, mounts: GroupMutex>, opens: AtomicI32, // openfiles app: VPathBuf, } impl Fs { - pub fn new>(system: P, game: P) -> Self { + pub fn new(vp: &Arc, ee: &E, system: S, game: G) -> Arc + where + E: ExecutionEngine, + S: Into, + G: Into, + { let system = system.into(); let game = game.into(); let mut mounts: HashMap = HashMap::new(); @@ -87,20 +99,28 @@ impl Fs { mounts.insert(app.join("app0").unwrap(), MountSource::Bind(pfs)); - Self { + // Install syscall handlers. + let fs = Arc::new(Self { + vp: vp.clone(), mounts: mg.new_member(mounts), opens: AtomicI32::new(0), app, - } + }); + + ee.register_syscall(5, &fs, Self::sys_open); + ee.register_syscall(54, &fs, Self::sys_ioctl); + ee.register_syscall(56, &fs, Self::sys_revoke); + + fs } pub fn app(&self) -> &VPath { self.app.borrow() } - pub fn get(&self, path: &VPath) -> Result, FsError> { + pub fn get(&self, path: &VPath) -> Result { let item = match path.as_str() { - "/dev/console" => FsItem::Device(VDev::Console(self)), + "/dev/console" => FsItem::Device(VDev::Console), _ => self.resolve(path).ok_or(FsError::NotFound)?, }; @@ -108,7 +128,7 @@ impl Fs { } /// See `falloc_noinstall_budget` on the PS4 for a reference. - pub fn alloc(&self) -> VFile<'_> { + pub fn alloc(self: &Arc) -> VFile { // TODO: Check if openfiles exceed rlimit. // TODO: Implement budget_resource_use. self.opens.fetch_add(1, Ordering::Relaxed); @@ -120,7 +140,143 @@ impl Fs { // TODO: Implement this. } - fn resolve(&self, path: &VPath) -> Option> { + fn sys_open(self: &Arc, i: &SysIn) -> Result { + // Get arguments. + let path = unsafe { i.args[0].to_path()?.unwrap() }; + let flags: OpenFlags = i.args[1].try_into().unwrap(); + let mode: u32 = i.args[2].try_into().unwrap(); + + // Check flags. + if flags.intersects(OpenFlags::O_EXEC) { + if flags.intersects(OpenFlags::O_ACCMODE) { + return Err(SysErr::Raw(EINVAL)); + } + } else if flags.contains(OpenFlags::O_ACCMODE) { + return Err(SysErr::Raw(EINVAL)); + } + + // Allocate file object. + let mut file = self.alloc(); + + // Get full path. + if flags.intersects(OpenFlags::UNK1) { + todo!("open({path}) with flags & 0x400000 != 0"); + } else if flags.intersects(OpenFlags::O_SHLOCK) { + todo!("open({path}) with flags & O_SHLOCK"); + } else if flags.intersects(OpenFlags::O_EXLOCK) { + todo!("open({path}) with flags & O_EXLOCK"); + } else if flags.intersects(OpenFlags::O_TRUNC) { + todo!("open({path}) with flags & O_TRUNC"); + } else if mode != 0 { + todo!("open({path}, {flags}) with mode = {mode}"); + } + + info!("Opening {path} with {flags}."); + + // Lookup file. + *file.flags_mut() = flags.to_fflags(); + file.set_ops(Some(self.get(path)?.open()?)); + + // Install to descriptor table. + let fd = self.vp.files().alloc(Arc::new(file)); + + info!("File descriptor {fd} was allocated for {path}."); + + Ok(fd.into()) + } + + fn sys_ioctl(self: &Arc, i: &SysIn) -> Result { + const IOC_VOID: u64 = 0x20000000; + const IOC_OUT: u64 = 0x40000000; + const IOC_IN: u64 = 0x80000000; + const IOCPARM_MASK: u64 = 0x1FFF; + + let fd: i32 = i.args[0].try_into().unwrap(); + let mut com: u64 = i.args[1].into(); + let data: *const u8 = i.args[2].into(); + + if com > 0xffffffff { + com &= 0xffffffff; + } + + let size = (com >> 16) & IOCPARM_MASK; + + if com & (IOC_VOID | IOC_OUT | IOC_IN) == 0 + || com & (IOC_OUT | IOC_IN) != 0 && size == 0 + || com & IOC_VOID != 0 && size != 0 && size != 4 + { + return Err(SysErr::Raw(ENOTTY)); + } + + // Get data. + let data = if size == 0 { + if com & IOC_IN != 0 { + todo!("ioctl with IOC_IN"); + } else if com & IOC_OUT != 0 { + todo!("ioctl with IOC_OUT"); + } + + &[] + } else { + todo!("ioctl with size != 0"); + }; + + // Get target file. + let file = self.vp.files().get(fd).ok_or(SysErr::Raw(EBADF))?; + let ops = file.ops().ok_or(SysErr::Raw(EBADF))?; + + if !file + .flags() + .intersects(VFileFlags::FREAD | VFileFlags::FWRITE) + { + return Err(SysErr::Raw(EBADF)); + } + + // Execute the operation. + let td = VThread::current(); + + info!("Executing ioctl({com:#x}) on {file}."); + + match com { + 0x20006601 => todo!("ioctl with com = 0x20006601"), + 0x20006602 => todo!("ioctl with com = 0x20006602"), + 0x8004667d => todo!("ioctl with com = 0x8004667d"), + 0x8004667e => todo!("ioctl with com = 0x8004667e"), + _ => {} + } + + ops.ioctl(&file, com, data, td.cred(), &td)?; + + if com & IOC_OUT != 0 { + todo!("ioctl with IOC_OUT"); + } + + Ok(SysOut::ZERO) + } + + fn sys_revoke(self: &Arc, i: &SysIn) -> Result { + let path = unsafe { i.args[0].to_path()?.unwrap() }; + + info!("Revoking access to {path}."); + + // Check current thread privilege. + VThread::current().priv_check(Privilege::SCE683)?; + + // TODO: Check vnode::v_rdev. + let file = self.get(path)?; + + if !file.is_character() { + return Err(SysErr::Raw(EINVAL)); + } + + // TODO: It seems like the initial ucred of the process is either root or has PRIV_VFS_ADMIN + // privilege. + self.revoke(path); + + Ok(SysOut::ZERO) + } + + fn resolve(&self, path: &VPath) -> Option { let mounts = self.mounts.read(); let mut current = VPathBuf::new(); let root = match mounts.get(¤t).unwrap() { @@ -180,6 +336,42 @@ impl Fs { } } +bitflags! { + /// Flags for [`Fs::sys_open()`]. + struct OpenFlags: u32 { + const O_WRONLY = 0x00000001; + const O_RDWR = 0x00000002; + const O_ACCMODE = Self::O_WRONLY.bits() | Self::O_RDWR.bits(); + const O_SHLOCK = 0x00000010; + const O_EXLOCK = 0x00000020; + const O_TRUNC = 0x00000400; + const O_EXEC = 0x00040000; + const O_CLOEXEC = 0x00100000; + const UNK1 = 0x00400000; + } +} + +impl OpenFlags { + /// An implementation of `FFLAGS` macro. + fn to_fflags(self) -> VFileFlags { + VFileFlags::from_bits_truncate(self.bits() + 1) + } +} + +impl TryFrom for OpenFlags { + type Error = TryFromIntError; + + fn try_from(value: SysArg) -> Result { + Ok(Self::from_bits_retain(value.get().try_into()?)) + } +} + +impl Display for OpenFlags { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } +} + /// Source of mount point. #[derive(Debug)] pub enum MountSource { diff --git a/src/kernel/src/llvm/mod.rs b/src/kernel/src/llvm/mod.rs index 26e2807d4..dec04dbd2 100644 --- a/src/kernel/src/llvm/mod.rs +++ b/src/kernel/src/llvm/mod.rs @@ -3,7 +3,7 @@ use llvm_sys::core::{LLVMContextCreate, LLVMContextDispose, LLVMModuleCreateWith use llvm_sys::prelude::LLVMContextRef; use std::ffi::{c_char, CStr, CString}; use std::fmt::Display; -use std::sync::Mutex; +use std::sync::{Arc, Mutex}; pub mod module; @@ -14,15 +14,15 @@ pub struct Llvm { } impl Llvm { - pub fn new() -> Self { + pub fn new() -> Arc { let context = unsafe { LLVMContextCreate() }; - Self { + Arc::new(Self { context: Mutex::new(context), - } + }) } - pub fn create_module(&self, name: &str) -> LlvmModule<'_> { + pub fn create_module(self: &Arc, name: &str) -> LlvmModule { let context = self.context.lock().unwrap(); let name = CString::new(name).unwrap(); let module = unsafe { LLVMModuleCreateWithNameInContext(name.as_ptr(), *context) }; diff --git a/src/kernel/src/llvm/module.rs b/src/kernel/src/llvm/module.rs index ee01a8acb..a12152329 100644 --- a/src/kernel/src/llvm/module.rs +++ b/src/kernel/src/llvm/module.rs @@ -6,19 +6,23 @@ use llvm_sys::execution_engine::{ use llvm_sys::prelude::LLVMModuleRef; use std::ffi::c_char; use std::ptr::null_mut; +use std::sync::Arc; /// A wrapper on LLVM module for thread-safe. -pub struct LlvmModule<'a> { - llvm: &'a Llvm, +pub struct LlvmModule { + llvm: Arc, module: LLVMModuleRef, } -impl<'a> LlvmModule<'a> { - pub(super) fn new(llvm: &'a Llvm, module: LLVMModuleRef) -> Self { - Self { llvm, module } +impl LlvmModule { + pub(super) fn new(llvm: &Arc, module: LLVMModuleRef) -> Self { + Self { + llvm: llvm.clone(), + module, + } } - pub fn create_execution_engine(mut self) -> Result, Error> { + pub fn create_execution_engine(mut self) -> Result { let mut ee: LLVMExecutionEngineRef = null_mut(); let module = self.module; let mut error: *mut c_char = null_mut(); @@ -33,13 +37,13 @@ impl<'a> LlvmModule<'a> { } Ok(ExecutionEngine { - llvm: self.llvm, + llvm: self.llvm.clone(), ee, }) } } -impl<'a> Drop for LlvmModule<'a> { +impl Drop for LlvmModule { fn drop(&mut self) { let m = self.module; @@ -53,12 +57,12 @@ impl<'a> Drop for LlvmModule<'a> { /// /// # Safety /// All JITed functions from this EE must not invoked once this EE has been droped. -pub struct ExecutionEngine<'a> { - llvm: &'a Llvm, +pub struct ExecutionEngine { + llvm: Arc, ee: LLVMExecutionEngineRef, } -impl<'a> Drop for ExecutionEngine<'a> { +impl Drop for ExecutionEngine { fn drop(&mut self) { self.llvm .with_context(|_| unsafe { LLVMDisposeExecutionEngine(self.ee) }); diff --git a/src/kernel/src/main.rs b/src/kernel/src/main.rs index 8f5ac02ec..395d1edd3 100644 --- a/src/kernel/src/main.rs +++ b/src/kernel/src/main.rs @@ -1,5 +1,5 @@ use crate::arnd::Arnd; -use crate::ee::EntryArg; +use crate::ee::{EntryArg, RawFn}; use crate::fs::Fs; use crate::llvm::Llvm; use crate::log::{print, LOGGER}; @@ -7,15 +7,16 @@ use crate::memory::MemoryManager; use crate::process::VProc; use crate::regmgr::RegMgr; use crate::rtld::{ModuleFlags, RuntimeLinker}; -use crate::syscalls::Syscalls; use crate::sysctl::Sysctl; use clap::{Parser, ValueEnum}; +use llt::Thread; use macros::vpath; use serde::Deserialize; use std::fs::{create_dir_all, remove_dir_all, File}; use std::io::Write; use std::path::PathBuf; use std::process::ExitCode; +use std::sync::Arc; mod arnd; mod console; @@ -31,7 +32,6 @@ mod process; mod regmgr; mod rtld; mod signal; -mod syscalls; mod sysctl; mod ucred; @@ -97,13 +97,11 @@ fn main() -> ExitCode { print(log); - // Initialize base systems. - let arnd: &'static Arnd = Box::leak(Arnd::new().into()); - let llvm: &'static Llvm = Box::leak(Llvm::new().into()); - let regmgr: &'static RegMgr = Box::leak(RegMgr::new().into()); - let fs: &'static Fs = Box::leak(Fs::new(args.system, args.game).into()); - let vp: &'static VProc = match VProc::new() { - Ok(v) => Box::leak(v.into()), + // Initialize foundations. + let arnd = Arnd::new(); + let llvm = Llvm::new(); + let vp = match VProc::new() { + Ok(v) => v, Err(e) => { error!(e, "Virtual process initialization failed"); return ExitCode::FAILURE; @@ -111,8 +109,8 @@ fn main() -> ExitCode { }; // Initialize memory management. - let mm: &'static MemoryManager = match MemoryManager::new(vp) { - Ok(v) => Box::leak(v.into()), + let mm = match MemoryManager::new(&vp) { + Ok(v) => v, Err(e) => { error!(e, "Memory manager initialization failed"); return ExitCode::FAILURE; @@ -138,22 +136,68 @@ fn main() -> ExitCode { print(log); + // Select execution engine. + match args.execution_engine.unwrap_or_default() { + #[cfg(target_arch = "x86_64")] + ExecutionEngine::Native => run( + args.system, + args.game, + &arnd, + &vp, + &mm, + crate::ee::native::NativeEngine::new(&mm), + ), + #[cfg(not(target_arch = "x86_64"))] + ExecutionEngine::Native => { + error!("Native execution engine cannot be used on your machine."); + return ExitCode::FAILURE; + } + ExecutionEngine::Llvm => run( + args.system, + args.game, + &arnd, + &vp, + &mm, + crate::ee::llvm::LlvmEngine::new(&llvm), + ), + } +} + +fn run( + root: PathBuf, + app: PathBuf, + arnd: &Arc, + vp: &Arc, + mm: &Arc, + ee: Arc, +) -> ExitCode { + // Initialize kernel components. + vp.install_syscalls(ee.as_ref()); + mm.install_syscalls(ee.as_ref()); + + let fs = Fs::new(vp, ee.as_ref(), root, app); + RegMgr::new(ee.as_ref()); + // Initialize runtime linker. info!("Initializing runtime linker."); - let ld: &'static mut RuntimeLinker = match RuntimeLinker::new(fs, mm, vp) { - Ok(v) => Box::leak(v.into()), + let ld = match RuntimeLinker::new(&fs, mm, &ee, vp) { + Ok(v) => v, Err(e) => { error!(e, "Initialize failed"); return ExitCode::FAILURE; } }; + // Initialize sysctl. + Sysctl::new(arnd, vp, mm, ee.as_ref()); + // Print application module. + let app = ld.app(); let mut log = info!(); - writeln!(log, "Application : {}", ld.app().path()).unwrap(); - ld.app().print(log); + writeln!(log, "Application : {}", app.path()).unwrap(); + app.print(log); // Preload libkernel. let path = vpath!("/system/common/lib/libkernel.sprx"); @@ -191,79 +235,61 @@ fn main() -> ExitCode { drop(module); - // Bootstrap execution engine. - let sysctl: &'static Sysctl = Box::leak(Sysctl::new(arnd, vp, mm).into()); - let syscalls: &'static Syscalls = - Box::leak(Syscalls::new(vp, fs, mm, ld, sysctl, regmgr).into()); - let arg = EntryArg::new(arnd, vp, mm, ld.app().clone()); - let ee = match args.execution_engine { - Some(v) => v, - #[cfg(target_arch = "x86_64")] - None => ExecutionEngine::Native, - #[cfg(not(target_arch = "x86_64"))] - None => ExecutionEngine::Llvm, - }; - - match ee { - #[cfg(target_arch = "x86_64")] - ExecutionEngine::Native => { - let mut ee = ee::native::NativeEngine::new(vp, mm, ld, syscalls); - - info!("Patching modules."); - - match unsafe { ee.patch_mods() } { - Ok(r) => { - let mut l = info!(); - let mut t = 0; + // Get eboot.bin. + if app.file_info().is_none() { + todo!("statically linked eboot.bin"); + } - for (m, c) in r { - if c != 0 { - writeln!(l, "{c} patch(es) have been applied to {m}.").unwrap(); - t += 1; - } - } + // Get entry point. + let boot = ld.kernel().unwrap(); + let mut arg = Box::pin(EntryArg::::new(arnd, vp, mm, app.clone())); + let entry = unsafe { boot.get_function(boot.entry().unwrap()) }; + let entry = move || unsafe { entry.exec1(arg.as_mut().as_vec().as_ptr()) }; - writeln!(l, "{t} module(s) have been patched successfully.").unwrap(); - print(l); - } - Err(e) => { - error!(e, "Patch failed"); - return ExitCode::FAILURE; - } - } + // Spawn main thread. + info!("Starting application."); - exec(ee, arg) - } - #[cfg(not(target_arch = "x86_64"))] - ExecutionEngine::Native => { - error!("Native execution engine cannot be used on your machine."); + let stack = mm.stack(); + let runner = match unsafe { vp.new_thread(stack.start(), stack.len(), entry) } { + Ok(v) => v, + Err(e) => { + error!(e, "Create main thread failed"); return ExitCode::FAILURE; } - ExecutionEngine::Llvm => { - let mut ee = ee::llvm::LlvmEngine::new(llvm, ld); + }; - info!("Lifting modules."); + // Wait for main thread to exit. This should never return. + if let Err(e) = join_thread(runner) { + error!(e, "Failed join with main thread"); + return ExitCode::FAILURE; + } - if let Err(e) = ee.lift_initial_modules() { - error!(e, "Lift failed"); - return ExitCode::FAILURE; - } + ExitCode::SUCCESS +} - exec(ee, arg) - } +#[cfg(unix)] +fn join_thread(thr: Thread) -> Result<(), std::io::Error> { + let err = unsafe { libc::pthread_join(thr, std::ptr::null_mut()) }; + + if err != 0 { + Err(std::io::Error::from_raw_os_error(err)) + } else { + Ok(()) } } -fn exec(mut ee: E, arg: EntryArg) -> ExitCode { - // Start the application. - info!("Starting application."); +#[cfg(windows)] +fn join_thread(thr: Thread) -> Result<(), std::io::Error> { + use windows_sys::Win32::Foundation::{CloseHandle, WAIT_OBJECT_0}; + use windows_sys::Win32::System::Threading::{WaitForSingleObject, INFINITE}; - if let Err(e) = unsafe { ee.run(arg) } { - error!(e, "Start failed"); - return ExitCode::FAILURE; + if unsafe { WaitForSingleObject(thr, INFINITE) } != WAIT_OBJECT_0 { + return Err(std::io::Error::last_os_error()); } - ExitCode::SUCCESS + assert_ne!(unsafe { CloseHandle(thr) }, 0); + + Ok(()) } #[derive(Parser, Deserialize)] @@ -291,3 +317,15 @@ enum ExecutionEngine { Native, Llvm, } + +impl Default for ExecutionEngine { + #[cfg(target_arch = "x86_64")] + fn default() -> Self { + ExecutionEngine::Native + } + + #[cfg(not(target_arch = "x86_64"))] + fn default() -> Self { + ExecutionEngine::Llvm + } +} diff --git a/src/kernel/src/memory/mod.rs b/src/kernel/src/memory/mod.rs index 932c81078..452d4914c 100644 --- a/src/kernel/src/memory/mod.rs +++ b/src/kernel/src/memory/mod.rs @@ -3,12 +3,14 @@ pub use self::stack::*; use self::iter::StartFromMut; use self::storage::Storage; +use crate::ee::{ExecutionEngine, SysArg, SysErr, SysIn, SysOut}; use crate::errno::{Errno, EINVAL, ENOMEM}; use crate::process::VProc; +use crate::{info, warn}; use bitflags::bitflags; use std::collections::BTreeMap; use std::fmt::{Display, Formatter}; -use std::num::NonZeroI32; +use std::num::{NonZeroI32, TryFromIntError}; use std::ptr::null_mut; use std::sync::{Arc, RwLock}; use thiserror::Error; @@ -21,7 +23,7 @@ mod storage; /// Manage all paged memory that can be seen by a PS4 app. #[derive(Debug)] pub struct MemoryManager { - vp: &'static VProc, + vp: Arc, page_size: usize, allocation_granularity: usize, allocations: RwLock>, // Key is Alloc::addr. @@ -32,7 +34,7 @@ impl MemoryManager { /// Size of a memory page on PS4. pub const VIRTUAL_PAGE_SIZE: usize = 0x4000; - pub fn new(vp: &'static VProc) -> Result { + pub fn new(vp: &Arc) -> Result, MemoryManagerError> { // Check if page size on the host is supported. We don't need to check allocation // granularity because it is always multiply by page size, which is a correct value. let (page_size, allocation_granularity) = Self::get_memory_model(); @@ -48,7 +50,7 @@ impl MemoryManager { // TODO: Check exec_new_vmspace on the PS4 to see what we have missed here. let mut mm = Self { - vp, + vp: vp.clone(), page_size, allocation_granularity, allocations: RwLock::default(), @@ -78,7 +80,7 @@ impl MemoryManager { mm.stack .set_stack(unsafe { guard.add(Self::VIRTUAL_PAGE_SIZE) }); - Ok(mm) + Ok(Arc::new(mm)) } /// Gets size of page on the host system. @@ -95,6 +97,11 @@ impl MemoryManager { &self.stack } + pub fn install_syscalls(self: &Arc, ee: &E) { + ee.register_syscall(477, self, Self::sys_mmap); + ee.register_syscall(588, self, Self::sys_mname); + } + pub fn mmap>( &self, addr: usize, @@ -319,7 +326,7 @@ impl MemoryManager { return Err(MmapError::NoMem(len)); } else { // We should not hit other error except for out of memory. - panic!("Failed to allocate {len}: {e}."); + panic!("Failed to allocate {len} bytes: {e}."); } } }; @@ -504,6 +511,79 @@ impl MemoryManager { } } + fn sys_mmap(self: &Arc, i: &SysIn) -> Result { + // Get arguments. + let addr: usize = i.args[0].into(); + let len: usize = i.args[1].into(); + let prot: Protections = i.args[2].try_into().unwrap(); + let flags: MappingFlags = i.args[3].try_into().unwrap(); + let fd: i32 = i.args[4].try_into().unwrap(); + let pos: usize = i.args[5].into(); + + // Check if the request is a guard for main stack. + if addr == self.stack.guard() { + assert_eq!(len, MemoryManager::VIRTUAL_PAGE_SIZE); + assert_eq!(prot.is_empty(), true); + assert_eq!(flags.intersects(MappingFlags::MAP_ANON), true); + assert_eq!(fd, -1); + assert_eq!(pos, 0); + + info!("Guard page has been requested for main stack."); + + return Ok(self.stack.guard().into()); + } + + // TODO: Make a proper name. + let pages = self.mmap(addr, len, prot, "", flags, fd, pos)?; + + if addr != 0 && pages.addr() != addr { + warn!( + "mmap({:#x}, {:#x}, {}, {}, {}, {}) was success with {:#x} instead of {:#x}.", + addr, + len, + prot, + flags, + fd, + pos, + pages.addr(), + addr + ); + } else { + info!( + "{:#x}:{:p} is mapped as {} with {}.", + pages.addr(), + pages.end(), + prot, + flags, + ); + } + + Ok(pages.into_raw().into()) + } + + fn sys_mname(self: &Arc, i: &SysIn) -> Result { + let addr: usize = i.args[0].into(); + let len: usize = i.args[1].into(); + let name = unsafe { i.args[2].to_str(32)?.unwrap() }; + + info!( + "Setting name for {:#x}:{:#x} to '{}'.", + addr, + addr + len, + name + ); + + // PS4 does not check if vm_map_set_name is failed. + let len = (addr & 0x3fff) + len + 0x3fff & 0xffffffffffffc000; + let addr = (addr & 0xffffffffffffc000) as *mut u8; + + if let Err(e) = self.mname(addr, len, name) { + warn!(e, "mname({addr:p}, {len:#x}, {name}) was failed"); + } + + Ok(SysOut::ZERO) + } + fn align_virtual_page(ptr: *mut u8) -> *mut u8 { match (ptr as usize) % Self::VIRTUAL_PAGE_SIZE { 0 => ptr, @@ -620,6 +700,14 @@ impl Protections { } } +impl TryFrom for Protections { + type Error = TryFromIntError; + + fn try_from(v: SysArg) -> Result { + Ok(Self::from_bits_retain(v.get().try_into()?)) + } +} + impl Display for Protections { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { self.0.fmt(f) @@ -643,6 +731,14 @@ bitflags! { } } +impl TryFrom for MappingFlags { + type Error = TryFromIntError; + + fn try_from(v: SysArg) -> Result { + Ok(Self::from_bits_retain(v.get().try_into()?)) + } +} + impl Display for MappingFlags { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { self.0.fmt(f) diff --git a/src/kernel/src/memory/stack.rs b/src/kernel/src/memory/stack.rs index 790e78fc3..9e85c87b3 100644 --- a/src/kernel/src/memory/stack.rs +++ b/src/kernel/src/memory/stack.rs @@ -48,3 +48,5 @@ impl AppStack { self.stack = v; } } + +unsafe impl Send for AppStack {} diff --git a/src/kernel/src/memory/storage.rs b/src/kernel/src/memory/storage.rs index 041270bfb..054c39bcc 100644 --- a/src/kernel/src/memory/storage.rs +++ b/src/kernel/src/memory/storage.rs @@ -37,18 +37,33 @@ impl Memory { #[cfg(windows)] pub fn new(addr: usize, len: usize) -> Result { + use std::ptr::null; + use windows_sys::Win32::Foundation::{GetLastError, ERROR_INVALID_ADDRESS}; use windows_sys::Win32::System::Memory::{VirtualAlloc, MEM_RESERVE, PAGE_NOACCESS}; - let addr = unsafe { VirtualAlloc(addr as _, len, MEM_RESERVE, PAGE_NOACCESS) }; + let ptr = unsafe { VirtualAlloc(addr as _, len, MEM_RESERVE, PAGE_NOACCESS) }; - if addr.is_null() { + if !ptr.is_null() { + return Ok(Self { + addr: ptr as _, + len, + }); + } else if addr == 0 || unsafe { GetLastError() } != ERROR_INVALID_ADDRESS { return Err(Error::last_os_error()); } - Ok(Self { - addr: addr as _, - len, - }) + // Windows will fail with ERROR_INVALID_ADDRESS instead of returning the allocated memory + // somewhere else if the requested address cannot be reserved. + let ptr = unsafe { VirtualAlloc(null(), len, MEM_RESERVE, PAGE_NOACCESS) }; + + if ptr.is_null() { + Err(Error::last_os_error()) + } else { + return Ok(Self { + addr: ptr as _, + len, + }); + } } #[cfg(unix)] diff --git a/src/kernel/src/process/file.rs b/src/kernel/src/process/file.rs index 7dc81848d..962f34226 100644 --- a/src/kernel/src/process/file.rs +++ b/src/kernel/src/process/file.rs @@ -5,7 +5,7 @@ use std::sync::Arc; /// An implementation of `filedesc` structure. #[derive(Debug)] pub struct VProcFiles { - files: GroupMutex>>>>, // fd_ofiles + files: GroupMutex>>>, // fd_ofiles } impl VProcFiles { @@ -16,7 +16,7 @@ impl VProcFiles { } /// See `finstall` on the PS4 for a reference. - pub fn alloc(&self, file: Arc>) -> i32 { + pub fn alloc(&self, file: Arc) -> i32 { // TODO: Implement fdalloc. let mut files = self.files.write(); @@ -39,7 +39,7 @@ impl VProcFiles { } /// See `fget` on the PS4 for a reference. - pub fn get(&self, fd: i32) -> Option>> { + pub fn get(&self, fd: i32) -> Option> { // TODO: Check what we have missed here. if fd < 0 { return None; diff --git a/src/kernel/src/process/mod.rs b/src/kernel/src/process/mod.rs index 70f1717d4..bb79c4ab3 100644 --- a/src/kernel/src/process/mod.rs +++ b/src/kernel/src/process/mod.rs @@ -5,11 +5,16 @@ pub use self::rlimit::*; pub use self::session::*; pub use self::thread::*; +use crate::ee::{ExecutionEngine, SysErr, SysIn, SysOut}; +use crate::errno::{EINVAL, ENAMETOOLONG, ENOENT, ENOSYS, EPERM, ESRCH}; use crate::idt::IdTable; -use crate::rtld::Module; -use crate::ucred::{AuthInfo, Ucred}; +use crate::info; +use crate::signal::{SignalSet, SIGKILL, SIGSTOP, SIG_BLOCK, SIG_SETMASK, SIG_UNBLOCK}; +use crate::ucred::{AuthInfo, Privilege, Ucred}; use gmtx::{GroupMutex, GroupMutexWriteGuard, MutexGroup}; use llt::{SpawnError, Thread}; +use std::any::Any; +use std::mem::zeroed; use std::num::NonZeroI32; use std::sync::atomic::{AtomicI32, Ordering}; use std::sync::Arc; @@ -35,18 +40,18 @@ pub struct VProc { group: GroupMutex>, // p_pgrp files: VProcFiles, // p_fd limits: [ResourceLimit; ResourceLimit::NLIMITS], // p_limit - objects: GroupMutex>, + objects: GroupMutex>>, app_info: AppInfo, mtxg: Arc, } impl VProc { - pub fn new() -> Result { + pub fn new() -> Result, VProcError> { // TODO: Check how ucred is constructed for a process. let mg = MutexGroup::new("virtual process"); let limits = Self::load_limits()?; - Ok(Self { + Ok(Arc::new(Self { id: Self::new_id(), threads: mg.new_member(Vec::new()), cred: Ucred::new(AuthInfo::EXE.clone()), @@ -56,7 +61,7 @@ impl VProc { limits, app_info: AppInfo::new(), mtxg: mg, - }) + })) } pub fn id(&self) -> NonZeroI32 { @@ -67,10 +72,6 @@ impl VProc { &self.cred } - pub fn group_mut(&self) -> GroupMutexWriteGuard<'_, Option> { - self.group.write() - } - pub fn files(&self) -> &VProcFiles { &self.files } @@ -79,7 +80,7 @@ impl VProc { self.limits.get(ty) } - pub fn objects_mut(&self) -> GroupMutexWriteGuard<'_, IdTable> { + pub fn objects_mut(&self) -> GroupMutexWriteGuard<'_, IdTable>> { self.objects.write() } @@ -91,6 +92,19 @@ impl VProc { &self.mtxg } + pub fn install_syscalls(self: &Arc, ee: &E) { + ee.register_syscall(20, self, |p, _| Ok(p.id().into())); + ee.register_syscall(50, self, Self::sys_setlogin); + ee.register_syscall(147, self, Self::sys_setsid); + ee.register_syscall(340, self, Self::sys_sigprocmask); + ee.register_syscall(432, self, Self::sys_thr_self); + ee.register_syscall(466, self, Self::sys_rtprio_thread); + ee.register_syscall(557, self, Self::sys_namedobj_create); + ee.register_syscall(585, self, Self::sys_is_in_sandbox); + ee.register_syscall(587, self, Self::sys_get_authinfo); + ee.register_syscall(610, self, Self::sys_budget_get_ptype); + } + /// Spawn a new [`VThread`]. /// /// The caller is responsible for `stack` deallocation. @@ -99,7 +113,7 @@ impl VProc { /// The range of memory specified by `stack` and `stack_size` must be valid throughout lifetime /// of the thread. Specify an unaligned stack will cause undefined behavior. pub unsafe fn new_thread( - &'static self, + self: &Arc, stack: *mut u8, stack_size: usize, mut routine: F, @@ -115,7 +129,7 @@ impl VProc { let cred = Ucred::new(AuthInfo::EXE.clone()); let td = Arc::new(VThread::new(Self::new_id(), cred, &self.mtxg)); let active = Box::new(ActiveThread { - proc: self, + proc: self.clone(), id: td.id(), }); @@ -143,6 +157,235 @@ impl VProc { ]) } + fn sys_setlogin(self: &Arc, i: &SysIn) -> Result { + // Check current thread privilege. + VThread::current().priv_check(Privilege::PROC_SETLOGIN)?; + + // Get login name. + let login = unsafe { i.args[0].to_str(17) } + .map_err(|e| { + if e.errno() == ENAMETOOLONG { + SysErr::Raw(EINVAL) + } else { + e + } + })? + .unwrap(); + + // Set login name. + let mut group = self.group.write(); + let session = group.as_mut().unwrap().session_mut(); + + session.set_login(login); + + info!("Login name was changed to '{login}'."); + + Ok(SysOut::ZERO) + } + + fn sys_setsid(self: &Arc, _: &SysIn) -> Result { + // Check if current thread has privilege. + VThread::current().priv_check(Privilege::SCE680)?; + + // Check if the process already become a group leader. + let mut group = self.group.write(); + + if group.is_some() { + return Err(SysErr::Raw(EPERM)); + } + + // TODO: Find out the correct login name for VSession. + let session = VSession::new(self.id, String::from("root")); + + *group = Some(VProcGroup::new(self.id, session)); + info!("Virtual process now set as group leader."); + + Ok(self.id.into()) + } + + fn sys_sigprocmask(self: &Arc, i: &SysIn) -> Result { + // Get arguments. + let how: i32 = i.args[0].try_into().unwrap(); + let set: *const SignalSet = i.args[1].into(); + let oset: *mut SignalSet = i.args[2].into(); + + // Convert set to an option. + let set = if set.is_null() { + None + } else { + Some(unsafe { *set }) + }; + + // Keep the current mask for copying to the oset. We need to copy to the oset only when this + // function succees. + let vt = VThread::current(); + let mut mask = vt.sigmask_mut(); + let prev = mask.clone(); + + // Update the mask. + if let Some(mut set) = set { + match how { + SIG_BLOCK => { + // Remove uncatchable signals. + set.remove(SIGKILL); + set.remove(SIGSTOP); + + // Update mask. + *mask |= set; + } + SIG_UNBLOCK => { + // Update mask. + *mask &= !set; + + // TODO: Invoke signotify at the end. + } + SIG_SETMASK => { + // Remove uncatchable signals. + set.remove(SIGKILL); + set.remove(SIGSTOP); + + // Replace mask. + *mask = set; + + // TODO: Invoke signotify at the end. + } + _ => return Err(SysErr::Raw(EINVAL)), + } + + // TODO: Check if we need to invoke reschedule_signals. + info!("Signal mask was changed from {} to {}.", prev, mask); + } + + // Copy output. + if !oset.is_null() { + unsafe { *oset = prev }; + } + + Ok(SysOut::ZERO) + } + + fn sys_thr_self(self: &Arc, i: &SysIn) -> Result { + let id: *mut i64 = i.args[0].into(); + unsafe { *id = VThread::current().id().get().into() }; + Ok(SysOut::ZERO) + } + + fn sys_rtprio_thread(self: &Arc, i: &SysIn) -> Result { + const RTP_LOOKUP: i32 = 0; + const RTP_SET: i32 = 1; + const RTP_UNK: i32 = 2; + + let td = VThread::current(); + let function: i32 = i.args[0].try_into().unwrap(); + let lwpid: i32 = i.args[1].try_into().unwrap(); + let rtp: *mut RtPrio = i.args[2].into(); + let rtp = unsafe { &mut *rtp }; + + if function == RTP_SET { + todo!("rtprio_thread with function = 1"); + } + + if function == RTP_UNK && td.cred().is_system() { + todo!("rtprio_thread with function = 2"); + } else if lwpid != 0 && lwpid != td.id().get() { + return Err(SysErr::Raw(ESRCH)); + } else if function == RTP_LOOKUP { + (*rtp).ty = td.pri_class(); + (*rtp).prio = match td.pri_class() & 0xfff7 { + 2 | 3 | 4 => td.base_user_pri(), + _ => 0, + }; + } else { + todo!("rtprio_thread with function = {function}"); + } + + Ok(SysOut::ZERO) + } + + // TODO: This should not be here. + fn sys_namedobj_create(self: &Arc, i: &SysIn) -> Result { + // Get arguments. + let name = unsafe { i.args[0].to_str(32)?.ok_or(SysErr::Raw(EINVAL))? }; + let data: usize = i.args[1].into(); + let flags: u32 = i.args[2].try_into().unwrap(); + + // Allocate the entry. + let mut table = self.objects.write(); + let (entry, id) = table + .alloc::<_, ()>(|_| Ok(Arc::new(NamedObj::new(name.to_owned(), data)))) + .unwrap(); + + entry.set_name(Some(name.to_owned())); + entry.set_flags((flags as u16) | 0x1000); + + info!( + "Named object '{}' (ID = {}) was created with data = {:#x} and flags = {:#x}.", + name, id, data, flags + ); + + Ok(id.into()) + } + + fn sys_is_in_sandbox(self: &Arc, _: &SysIn) -> Result { + // TODO: Get the actual value from the PS4. + info!("Returning is_in_sandbox as 0."); + Ok(0.into()) + } + + fn sys_get_authinfo(self: &Arc, i: &SysIn) -> Result { + // Get arguments. + let pid: i32 = i.args[0].try_into().unwrap(); + let buf: *mut AuthInfo = i.args[1].into(); + + // Check if PID is our process. + if pid != 0 && pid != self.id.get() { + return Err(SysErr::Raw(ESRCH)); + } + + // Check privilege. + let mut info: AuthInfo = unsafe { zeroed() }; + let td = VThread::current(); + + if td.priv_check(Privilege::SCE686).is_ok() { + info = self.cred.auth().clone(); + } else { + // TODO: Refactor this for readability. + let paid = self.cred.auth().paid.wrapping_add(0xc7ffffffeffffffc); + + if paid < 0xf && ((0x6001u32 >> (paid & 0x3f)) & 1) != 0 { + info.paid = self.cred.auth().paid; + } + + info.caps[0] = self.cred.auth().caps[0] & 0x7000000000000000; + + info!( + "Retrieved authinfo for non-system credential (paid = {:#x}, caps[0] = {:#x}).", + info.paid, info.caps[0] + ); + } + + // Copy into. + if buf.is_null() { + todo!("get_authinfo with buf = null"); + } else { + unsafe { *buf = info }; + } + + Ok(SysOut::ZERO) + } + + fn sys_budget_get_ptype(self: &Arc, i: &SysIn) -> Result { + // Check if PID is our process. + let pid: i32 = i.args[0].try_into().unwrap(); + + if pid != -1 && pid != self.id.get() { + return Err(SysErr::Raw(ENOSYS)); + } + + // TODO: Invoke id_rlock. Not sure why return ENOENT is working here. + Err(SysErr::Raw(ENOENT)) + } + fn new_id() -> NonZeroI32 { let id = NEXT_ID.fetch_add(1, Ordering::Relaxed); @@ -156,7 +399,7 @@ impl VProc { // An object for removing the thread from the list when dropped. struct ActiveThread { - proc: &'static VProc, + proc: Arc, id: NonZeroI32, } @@ -169,13 +412,14 @@ impl Drop for ActiveThread { } } -/// An object in the process object table. -#[derive(Debug)] -pub enum ProcObj { - Module(Arc), - Named(NamedObj), +/// Outout of sys_rtprio_thread. +#[repr(C)] +struct RtPrio { + ty: u16, + prio: u16, } +/// TODO: Move this to somewhere else. #[derive(Debug)] pub struct NamedObj { name: String, diff --git a/src/kernel/src/regmgr/mod.rs b/src/kernel/src/regmgr/mod.rs index 007982f16..79bb61d80 100644 --- a/src/kernel/src/regmgr/mod.rs +++ b/src/kernel/src/regmgr/mod.rs @@ -1,7 +1,12 @@ pub use self::key::*; +use crate::ee::{ExecutionEngine, SysErr, SysIn, SysOut}; +use crate::process::VThread; use crate::ucred::Ucred; +use crate::{info, warn}; use std::fmt::{Display, Formatter}; +use std::ptr::read; +use std::sync::Arc; use thiserror::Error; mod key; @@ -10,11 +15,78 @@ mod key; pub struct RegMgr {} impl RegMgr { - pub fn new() -> Self { - Self {} + pub fn new(ee: &E) -> Arc { + let mgr = Arc::new(Self {}); + + ee.register_syscall(532, &mgr, Self::sys_regmgr_call); + + mgr + } + + fn sys_regmgr_call(self: &Arc, i: &SysIn) -> Result { + // Get arguments. + let op: u32 = i.args[0].try_into().unwrap(); + let buf: *mut i32 = i.args[2].into(); + let req: *const u8 = i.args[3].into(); + let reqlen: usize = i.args[4].into(); + + // TODO: Check the result of priv_check(td, 682). + if buf.is_null() { + todo!("regmgr_call with buf = null"); + } + + if req.is_null() { + todo!("regmgr_call with req = null"); + } + + if reqlen > 2048 { + todo!("regmgr_call with reqlen > 2048"); + } + + // Execute the operation. + let td = VThread::current(); + let r = match op { + 0x18 => { + let v1 = unsafe { read::(req as _) }; + let v2 = unsafe { read::(req.add(8) as _) }; + let value = unsafe { read::(req.add(12) as _) }; + + info!( + "Attempting to set registry with v1: {}, v2: {}, value: {}.", + v1, v2, value + ); + + self.decode_key(v1, v2, td.cred(), 2).and_then(|k| { + info!("Setting registry key {} to value {}.", k, value); + self.set_int(k, value) + }) + } + 0x19 => { + let v1 = unsafe { read::(req as _) }; + let v2 = unsafe { read::(req.add(8) as _) }; + + self.decode_key(v1, v2, td.cred(), 1) + .and_then(|k| todo!("regmgr_call({op}) with matched key = {k}")) + } + 0x27 | 0x40.. => Err(RegError::V800d0219), + v => todo!("regmgr_call({v})"), + }; + + // Write the result. + unsafe { + *buf = match r { + Ok(v) => v, + Err(e) => { + warn!(e, "regmgr_call({op}) failed"); + e.code() + } + } + }; + + Ok(SysOut::ZERO) } - pub fn decode_key(&self, v1: u64, v2: u32, cred: &Ucred, v3: u32) -> Result { + fn decode_key(&self, v1: u64, v2: u32, cred: &Ucred, v3: u32) -> Result { // Check checksum. let a = (v1 & 0xff) as i32; let b = ((v1 >> 8) & 0xff) as i32; @@ -83,7 +155,7 @@ impl RegMgr { } /// See `sceRegMgrSetInt` on the PS4 for a reference. - pub fn set_int(&self, key: RegKey, value: i32) -> Result { + fn set_int(&self, key: RegKey, value: i32) -> Result { let value = value.to_le_bytes(); if let Err(e) = self.check_param(key, 0, value.len()) { diff --git a/src/kernel/src/rtld/mem.rs b/src/kernel/src/rtld/mem.rs index cf624c6dd..afa8657d2 100644 --- a/src/kernel/src/rtld/mem.rs +++ b/src/kernel/src/rtld/mem.rs @@ -11,7 +11,7 @@ use thiserror::Error; /// A memory of the loaded module. pub struct Memory { - mm: &'static MemoryManager, + mm: Arc, ptr: *mut u8, len: usize, segments: Vec, @@ -27,7 +27,7 @@ pub struct Memory { impl Memory { pub(super) fn new>( - mm: &'static MemoryManager, + mm: &Arc, image: &Elf, base: usize, name: N, @@ -170,7 +170,7 @@ impl Memory { } Ok(Self { - mm, + mm: mm.clone(), ptr: pages.into_raw(), len, segments, @@ -287,7 +287,7 @@ impl Memory { } Ok(UnprotectedSegment { - mm: self.mm, + mm: &self.mm, ptr, len, prot: seg.prot, @@ -320,7 +320,7 @@ impl Memory { } Ok(UnprotectedMemory { - mm: self.mm, + mm: &self.mm, ptr: self.ptr, len: end, segments: &self.segments, @@ -400,7 +400,7 @@ impl MemorySegment { /// A memory segment in an unprotected form. pub struct UnprotectedSegment<'a> { - mm: &'static MemoryManager, + mm: &'a MemoryManager, ptr: *mut u8, len: usize, prot: Protections, @@ -421,7 +421,7 @@ impl<'a> Drop for UnprotectedSegment<'a> { /// The unprotected form of [`Memory`], not including our custom segments. pub struct UnprotectedMemory<'a> { - mm: &'static MemoryManager, + mm: &'a MemoryManager, ptr: *mut u8, len: usize, segments: &'a [MemorySegment], diff --git a/src/kernel/src/rtld/mod.rs b/src/kernel/src/rtld/mod.rs index c16af95d6..d86e55803 100644 --- a/src/kernel/src/rtld/mod.rs +++ b/src/kernel/src/rtld/mod.rs @@ -2,14 +2,19 @@ pub use self::mem::*; pub use self::module::*; use self::resolver::{ResolveFlags, SymbolResolver}; -use crate::errno::{Errno, EINVAL, ENOEXEC}; +use crate::ee::{ExecutionEngine, SysErr, SysIn, SysOut}; +use crate::errno::{Errno, EINVAL, ENOEXEC, ENOMEM, EPERM, ESRCH}; use crate::fs::{Fs, FsError, FsItem, VPath, VPathBuf}; +use crate::info; +use crate::log::print; use crate::memory::{MemoryManager, MemoryUpdateError, MmapError, Protections}; -use crate::process::{ProcObj, VProc}; +use crate::process::VProc; use bitflags::bitflags; use elf::{DynamicFlags, Elf, FileType, ReadProgramError, Relocation}; -use gmtx::{GroupMutex, GroupMutexReadGuard}; +use gmtx::GroupMutex; use std::fs::File; +use std::io::Write; +use std::mem::{size_of, zeroed}; use std::num::NonZeroI32; use std::ops::Deref; use std::ptr::{read_unaligned, write_unaligned}; @@ -23,24 +28,26 @@ mod resolver; /// An implementation of /// https://github.com/freebsd/freebsd-src/blob/release/9.1.0/libexec/rtld-elf/rtld.c. #[derive(Debug)] -pub struct RuntimeLinker { - fs: &'static Fs, - mm: &'static MemoryManager, - vp: &'static VProc, - list: GroupMutex>>, // obj_list + obj_tail - app: Arc, // obj_main - kernel: GroupMutex>>, // obj_kernel - mains: GroupMutex>>, // list_main +pub struct RuntimeLinker { + fs: Arc, + mm: Arc, + ee: Arc, + vp: Arc, + list: GroupMutex>>>, // obj_list + obj_tail + app: Arc>, // obj_main + kernel: GroupMutex>>>, // obj_kernel + mains: GroupMutex>>>, // list_main tls: GroupMutex, flags: LinkerFlags, } -impl RuntimeLinker { +impl RuntimeLinker { pub fn new( - fs: &'static Fs, - mm: &'static MemoryManager, - vp: &'static VProc, - ) -> Result { + fs: &Arc, + mm: &Arc, + ee: &Arc, + vp: &Arc, + ) -> Result, RuntimeLinkerError> { // Get path to eboot.bin. let mut path = fs.app().join("app0").unwrap(); @@ -84,13 +91,17 @@ impl RuntimeLinker { // TODO: Apply remaining checks from exec_self_imgact. // Map eboot.bin. - let app = match Module::map(mm, elf, base, "executable", 0, 1, vp.mutex_group()) { - Ok(v) => Arc::new(v), + let mut app = match Module::map(mm, ee, elf, base, "executable", 0, 1, vp.mutex_group()) { + Ok(v) => v, Err(e) => return Err(RuntimeLinkerError::MapExeFailed(file.into_vpath(), e)), }; *app.flags_mut() |= ModuleFlags::MAIN_PROG; + if let Err(e) = ee.setup_module(&mut app) { + return Err(RuntimeLinkerError::SetupExeFailed(file.into_vpath(), e)); + } + // Check if application need certain modules. let mut flags = LinkerFlags::empty(); @@ -108,11 +119,12 @@ impl RuntimeLinker { // TODO: Apply logic from aio_proc_rundown_exec. // TODO: Apply logic from gs_is_event_handler_process_exec. let mg = vp.mutex_group(); - - Ok(Self { - fs, - mm, - vp, + let app = Arc::new(app); + let ld = Arc::new(Self { + fs: fs.clone(), + mm: mm.clone(), + ee: ee.clone(), + vp: vp.clone(), list: mg.new_member(vec![app.clone()]), app: app.clone(), kernel: mg.new_member(None), @@ -124,28 +136,31 @@ impl RuntimeLinker { static_space: 0, }), flags, - }) - } + }); - pub fn list(&self) -> GroupMutexReadGuard<'_, Vec>> { - self.list.read() + ee.register_syscall(592, &ld, Self::sys_dynlib_get_list); + ee.register_syscall(598, &ld, Self::sys_dynlib_get_proc_param); + ee.register_syscall(599, &ld, Self::sys_dynlib_process_needed_and_relocate); + ee.register_syscall(608, &ld, Self::sys_dynlib_get_info_ex); + + Ok(ld) } - pub fn app(&self) -> &Arc { + pub fn app(&self) -> &Arc> { &self.app } - pub fn kernel(&self) -> Option> { + pub fn kernel(&self) -> Option>> { self.kernel.read().clone() } - pub fn set_kernel(&self, md: Arc) { + pub fn set_kernel(&self, md: Arc>) { *self.kernel.write() = Some(md); } /// This method **ALWAYS** load the specified module without checking if the same module is /// already loaded. - pub fn load(&mut self, path: &VPath, main: bool) -> Result, LoadError> { + pub fn load(&self, path: &VPath, main: bool) -> Result>, LoadError> { // Get file. let file = match self.fs.get(path) { Ok(v) => match v { @@ -200,12 +215,14 @@ impl RuntimeLinker { // Map file. let mut table = self.vp.objects_mut(); let (entry, _) = table.alloc(|id| { + let name = path.file_name().unwrap(); let id: u32 = (id + 1).try_into().unwrap(); - let md = match Module::map( - self.mm, + let mut md = match Module::map( + &self.mm, + &self.ee, elf, 0, - path.file_name().unwrap(), + name, id, tls, self.vp.mutex_group(), @@ -218,36 +235,106 @@ impl RuntimeLinker { return Err(LoadError::ImpureText); } - Ok(ProcObj::Module(Arc::new(md))) - })?; + // TODO: Check the call to sceSblAuthMgrIsLoadable in the self_load_shared_object on the PS4 + // to see how it is return the value. + if name != "libc.sprx" && name != "libSceFios2.sprx" { + *md.flags_mut() |= ModuleFlags::UNK1; + } - entry.set_flags(0x2000); + if let Err(e) = self.ee.setup_module(&mut md) { + return Err(LoadError::SetupFailed(e)); + } - // TODO: Check the call to sceSblAuthMgrIsLoadable in the self_load_shared_object on the PS4 - // to see how it is return the value. - let name = path.file_name().unwrap(); - let module = match entry.data() { - ProcObj::Module(v) => v, - _ => unreachable!(), - }; + Ok(Arc::new(md)) + })?; - if name != "libc.sprx" && name != "libSceFios2.sprx" { - *module.flags_mut() |= ModuleFlags::UNK1; - } + entry.set_flags(0x2000); // Add to list. + let module = entry.data().clone().downcast::>().unwrap(); + list.push(module.clone()); if main { self.mains.write().push(module.clone()); } - Ok(module.clone()) + Ok(module) + } + + fn sys_dynlib_get_list(self: &Arc, i: &SysIn) -> Result { + // Get arguments. + let buf: *mut u32 = i.args[0].into(); + let max: usize = i.args[1].into(); + let copied: *mut usize = i.args[2].into(); + + // Check if application is dynamic linking. + if self.app.file_info().is_none() { + return Err(SysErr::Raw(EPERM)); + } + + // Copy module ID. + let list = self.list.read(); + + if list.len() > max { + return Err(SysErr::Raw(ENOMEM)); + } + + for (i, m) in list.iter().enumerate() { + unsafe { *buf.add(i) = m.id() }; + } + + // Set copied. + unsafe { *copied = list.len() }; + + info!("Copied {} module IDs for dynamic linking.", list.len()); + + Ok(SysOut::ZERO) + } + + fn sys_dynlib_get_proc_param(self: &Arc, i: &SysIn) -> Result { + // Get arguments. + let param: *mut usize = i.args[0].into(); + let size: *mut usize = i.args[1].into(); + + // Check if application is a dynamic SELF. + if self.app.file_info().is_none() { + return Err(SysErr::Raw(EPERM)); + } + + // Get param. + match self.app.proc_param() { + Some(v) => { + // TODO: Seems like ET_SCE_DYNEXEC is mapped at a fixed address. + unsafe { *param = self.app.memory().addr() + v.0 }; + unsafe { *size = v.1 }; + } + None => todo!("app is dynamic but no PT_SCE_PROCPARAM"), + } + + Ok(SysOut::ZERO) + } + + fn sys_dynlib_process_needed_and_relocate( + self: &Arc, + _: &SysIn, + ) -> Result { + // Check if application is dynamic linking. + if self.app.file_info().is_none() { + return Err(SysErr::Raw(EINVAL)); + } + + // TODO: Implement dynlib_load_needed_shared_objects. + info!("Relocating loaded modules."); + + unsafe { self.relocate() }?; + + Ok(SysOut::ZERO) } /// # Safety /// No other threads may access the memory of all loaded modules. - pub unsafe fn relocate(&self) -> Result<(), RelocateError> { + unsafe fn relocate(&self) -> Result<(), RelocateError> { // Initialize TLS. let mains = self.mains.read(); let mut alloc = self.tls.write(); @@ -306,8 +393,8 @@ impl RuntimeLinker { /// No other thread may access the module memory. unsafe fn relocate_single<'b>( &self, - md: &'b Arc, - resolver: &SymbolResolver<'b>, + md: &'b Arc>, + resolver: &SymbolResolver<'b, E>, ) -> Result<(), RelocateError> { // Unprotect the memory. let mut mem = match md.memory().unprotect() { @@ -330,10 +417,10 @@ impl RuntimeLinker { /// See `reloc_non_plt` on the PS4 kernel for a reference. fn relocate_rela<'b>( &self, - md: &'b Arc, + md: &'b Arc>, mem: &mut [u8], relocated: &mut [bool], - resolver: &SymbolResolver<'b>, + resolver: &SymbolResolver<'b, E>, ) -> Result<(), RelocateError> { let info = md.file_info().unwrap(); // Let it panic because the PS4 assume it is available. let addr = mem.as_ptr() as usize; @@ -405,10 +492,10 @@ impl RuntimeLinker { /// See `reloc_jmplots` on the PS4 for a reference. fn relocate_plt<'b>( &self, - md: &'b Arc, + md: &'b Arc>, mem: &mut [u8], relocated: &mut [bool], - resolver: &SymbolResolver<'b>, + resolver: &SymbolResolver<'b, E>, ) -> Result<(), RelocateError> { // Do nothing if not a dynamic module. let info = match md.file_info() { @@ -455,6 +542,156 @@ impl RuntimeLinker { Ok(()) } + + fn sys_dynlib_get_info_ex(self: &Arc, i: &SysIn) -> Result { + // Get arguments. + let handle: u32 = i.args[0].try_into().unwrap(); + let flags: u32 = i.args[1].try_into().unwrap(); + let info: *mut DynlibInfoEx = i.args[2].into(); + + // Check if application is dynamic linking. + if self.app.file_info().is_none() { + return Err(SysErr::Raw(EPERM)); + } + + // Check buffer size. + let size: usize = unsafe { (*info).size.try_into().unwrap() }; + + if size != size_of::() { + return Err(SysErr::Raw(EINVAL)); + } + + // Lookup the module. + let modules = self.list.read(); + let md = match modules.iter().find(|m| m.id() == handle) { + Some(v) => v, + None => return Err(SysErr::Raw(ESRCH)), + }; + + // Fill the info. + let info = unsafe { &mut *info }; + let mem = md.memory(); + let addr = mem.addr(); + + *info = unsafe { zeroed() }; + info.handle = md.id(); + info.mapbase = addr + mem.base(); + info.textsize = mem.text_segment().len().try_into().unwrap(); + info.unk3 = 5; + info.database = addr + mem.data_segment().start(); + info.datasize = mem.data_segment().len().try_into().unwrap(); + info.unk4 = 3; + info.unk6 = 2; + info.refcount = Arc::strong_count(md).try_into().unwrap(); + + // Copy module name. + if flags & 2 == 0 || !md.flags().contains(ModuleFlags::UNK1) { + let name = md.path().file_name().unwrap(); + + (*info).name[..name.len()].copy_from_slice(name.as_bytes()); + (*info).name[0xff] = 0; + } + + // Set TLS information. Not sure if the tlsinit can be zero when the tlsinitsize is zero. + // Let's keep the same behavior as the PS4 for now. + (*info).tlsindex = if flags & 1 != 0 { + let flags = md.flags(); + let mut upper = if flags.contains(ModuleFlags::UNK1) { + 1 + } else { + 0 + }; + + if flags.contains(ModuleFlags::MAIN_PROG) { + upper += 2; + } + + (upper << 16) | (md.tls_index() & 0xffff) + } else { + md.tls_index() & 0xffff + }; + + if let Some(i) = md.tls_info() { + (*info).tlsinit = addr + i.init(); + (*info).tlsinitsize = i.init_size().try_into().unwrap(); + (*info).tlssize = i.size().try_into().unwrap(); + (*info).tlsalign = i.align().try_into().unwrap(); + } else { + (*info).tlsinit = addr; + } + + (*info).tlsoffset = (*md.tls_offset()).try_into().unwrap(); + + // Initialization and finalization functions. + if !md.flags().contains(ModuleFlags::UNK5) { + (*info).init = md.init().map(|v| addr + v).unwrap_or(0); + (*info).fini = md.fini().map(|v| addr + v).unwrap_or(0); + } + + // Exception handling. + if let Some(i) = md.eh_info() { + (*info).eh_frame_hdr = addr + i.header(); + (*info).eh_frame_hdr_size = i.header_size().try_into().unwrap(); + (*info).eh_frame = addr + i.frame(); + (*info).eh_frame_size = i.frame_size().try_into().unwrap(); + } else { + (*info).eh_frame_hdr = addr; + } + + let mut e = info!(); + + writeln!( + e, + "Retrieved info for module {} (ID = {}).", + md.path(), + handle + ) + .unwrap(); + writeln!(e, "mapbase : {:#x}", (*info).mapbase).unwrap(); + writeln!(e, "textsize : {:#x}", (*info).textsize).unwrap(); + writeln!(e, "database : {:#x}", (*info).database).unwrap(); + writeln!(e, "datasize : {:#x}", (*info).datasize).unwrap(); + writeln!(e, "tlsindex : {}", (*info).tlsindex).unwrap(); + writeln!(e, "tlsinit : {:#x}", (*info).tlsinit).unwrap(); + writeln!(e, "tlsoffset : {:#x}", (*info).tlsoffset).unwrap(); + writeln!(e, "init : {:#x}", (*info).init).unwrap(); + writeln!(e, "fini : {:#x}", (*info).fini).unwrap(); + writeln!(e, "eh_frame_hdr: {:#x}", (*info).eh_frame_hdr).unwrap(); + + print(e); + + Ok(SysOut::ZERO) + } +} + +#[repr(C)] +struct DynlibInfoEx { + size: u64, + name: [u8; 256], + handle: u32, + tlsindex: u32, + tlsinit: usize, + tlsinitsize: u32, + tlssize: u32, + tlsoffset: u32, + tlsalign: u32, + init: usize, + fini: usize, + unk1: u64, // Always zero. + unk2: u64, // Same here. + eh_frame_hdr: usize, + eh_frame: usize, + eh_frame_hdr_size: u32, + eh_frame_size: u32, + mapbase: usize, + textsize: u32, + unk3: u32, // Always 5. + database: usize, + datasize: u32, + unk4: u32, // Always 3. + unk5: [u8; 0x20], // Always zeroes. + unk6: u32, // Always 2. + refcount: u32, } /// Contains how TLS was allocated so far. @@ -477,7 +714,7 @@ bitflags! { /// Represents the error for [`RuntimeLinker`] initialization. #[derive(Debug, Error)] -pub enum RuntimeLinkerError { +pub enum RuntimeLinkerError { #[error("cannot get {0}")] GetExeFailed(VPathBuf, #[source] FsError), @@ -492,6 +729,9 @@ pub enum RuntimeLinkerError { #[error("cannot map {0}")] MapExeFailed(VPathBuf, #[source] MapError), + + #[error("cannot setup {0}")] + SetupExeFailed(VPathBuf, #[source] E::SetupModuleErr), } /// Represents an error for (S)ELF mapping. @@ -542,7 +782,7 @@ pub enum MapError { /// Represents an error for (S)ELF loading. #[derive(Debug, Error)] -pub enum LoadError { +pub enum LoadError { #[error("cannot get the specified file")] GetFileFailed(#[source] FsError), @@ -560,6 +800,9 @@ pub enum LoadError { #[error("the specified file has impure text")] ImpureText, + + #[error("cannot setup the module")] + SetupFailed(#[source] E::SetupModuleErr), } /// Represents an error for modules relocation. diff --git a/src/kernel/src/rtld/module.rs b/src/kernel/src/rtld/module.rs index 26505068c..df5be6378 100644 --- a/src/kernel/src/rtld/module.rs +++ b/src/kernel/src/rtld/module.rs @@ -1,4 +1,5 @@ use super::{MapError, Memory}; +use crate::ee::ExecutionEngine; use crate::fs::{VPath, VPathBuf}; use crate::log::{print, LogEntry}; use crate::memory::MemoryManager; @@ -17,7 +18,8 @@ use std::sync::Arc; /// An implementation of /// https://github.com/freebsd/freebsd-src/blob/release/9.1.0/libexec/rtld-elf/rtld.h#L147. #[derive(Debug)] -pub struct Module { +pub struct Module { + ee: Arc, id: u32, init: Option, entry: Option, @@ -42,9 +44,10 @@ pub struct Module { symbols: Vec, } -impl Module { +impl Module { pub(super) fn map>( - mm: &'static MemoryManager, + mm: &Arc, + ee: &Arc, mut image: Elf, base: usize, mem_name: N, @@ -100,7 +103,7 @@ impl Module { } // Extract image info. - let entry = image.entry_addr().map(|v| base + v); + let entry = image.entry_addr(); // TODO: Check if PT_TLS with zero value is valid or not. If not, set this to None instead. let tls_info = image @@ -144,6 +147,7 @@ impl Module { // Parse dynamic info. let mut module = Self { + ee: ee.clone(), id, init: None, entry, @@ -322,7 +326,12 @@ impl Module { } if let Some(v) = self.entry { - writeln!(entry, "Entry address : {:#018x}", mem.addr() + v).unwrap(); + writeln!( + entry, + "Entry address : {:#018x}", + mem.addr() + mem.base() + v + ) + .unwrap(); } if let Some(v) = self.fini { @@ -393,6 +402,15 @@ impl Module { print(entry); } + /// # Safety + /// `off` must be a valid offset without base adjustment of a function in the memory of this + /// module. + pub unsafe fn get_function(self: &Arc, off: usize) -> Arc { + self.ee + .get_function(self, self.memory.addr() + self.memory.base() + off) + .unwrap() + } + unsafe fn digest_eh(mem: &Memory, off: usize, len: usize) -> (usize, usize) { // Get frame header. let mem = mem.as_bytes(); diff --git a/src/kernel/src/rtld/resolver.rs b/src/kernel/src/rtld/resolver.rs index c40aa5008..267083fe7 100644 --- a/src/kernel/src/rtld/resolver.rs +++ b/src/kernel/src/rtld/resolver.rs @@ -1,17 +1,18 @@ use super::Module; +use crate::ee::ExecutionEngine; use bitflags::bitflags; use elf::Symbol; use std::borrow::Cow; use std::sync::Arc; /// An object to resolve a symbol from loaded (S)ELF. -pub struct SymbolResolver<'a> { - mains: &'a [Arc], +pub struct SymbolResolver<'a, E: ExecutionEngine> { + mains: &'a [Arc>], new_algorithm: bool, } -impl<'a> SymbolResolver<'a> { - pub fn new(mains: &'a [Arc], new_algorithm: bool) -> Self { +impl<'a, E: ExecutionEngine> SymbolResolver<'a, E> { + pub fn new(mains: &'a [Arc>], new_algorithm: bool) -> Self { Self { mains, new_algorithm, @@ -21,10 +22,10 @@ impl<'a> SymbolResolver<'a> { /// See `find_symdef` on the PS4 for a reference. pub fn resolve_with_local( &self, - md: &'a Arc, + md: &'a Arc>, index: usize, mut flags: ResolveFlags, - ) -> Option<(&'a Arc, usize)> { + ) -> Option<(&'a Arc>, usize)> { // Check if symbol index is valid. let sym = md.symbols().get(index)?; let data = md.file_info().unwrap(); @@ -94,14 +95,14 @@ impl<'a> SymbolResolver<'a> { /// See `symlook_default` on the PS4 for a reference. pub fn resolve( &self, - refmod: &'a Arc, + refmod: &'a Arc>, name: Option<&str>, decoded_name: Option>, symmod: Option<&str>, symlib: Option<&str>, hash: u64, flags: ResolveFlags, - ) -> Option<(&'a Arc, usize)> { + ) -> Option<(&'a Arc>, usize)> { // TODO: Resolve from DAGs. self.resolve_from_global(refmod, name, decoded_name, symmod, symlib, hash, flags) } @@ -109,14 +110,14 @@ impl<'a> SymbolResolver<'a> { /// See `symlook_global` on the PS4 for a reference. pub fn resolve_from_global( &self, - refmod: &'a Arc, + refmod: &'a Arc>, name: Option<&str>, decoded_name: Option>, symmod: Option<&str>, symlib: Option<&str>, hash: u64, flags: ResolveFlags, - ) -> Option<(&'a Arc, usize)> { + ) -> Option<(&'a Arc>, usize)> { // TODO: Resolve from list_global. self.resolve_from_list( refmod, @@ -133,15 +134,15 @@ impl<'a> SymbolResolver<'a> { /// See `symlook_list` on the PS4 for a reference. pub fn resolve_from_list( &self, - refmod: &'a Arc, + refmod: &'a Arc>, name: Option<&str>, decoded_name: Option>, symmod: Option<&str>, symlib: Option<&str>, hash: u64, flags: ResolveFlags, - list: &'a [Arc], - ) -> Option<(&'a Arc, usize)> { + list: &'a [Arc>], + ) -> Option<(&'a Arc>, usize)> { // Get module name. let symmod = if !flags.contains(ResolveFlags::UNK2) { symmod @@ -204,15 +205,15 @@ impl<'a> SymbolResolver<'a> { /// See `symlook_obj` on the PS4 for a reference. pub fn resolve_from_module( &self, - _refmod: &'a Arc, + refmod: &'a Arc>, name: Option<&str>, decoded_name: Option<&str>, symmod: Option<&str>, symlib: Option<&str>, hash: u64, flags: ResolveFlags, - md: &'a Arc, - ) -> Option<(&'a Arc, usize)> { + md: &'a Arc>, + ) -> Option<(&'a Arc>, usize)> { let info = md.file_info().unwrap(); let buckets = info.buckets(); let hash: usize = hash.try_into().unwrap(); @@ -364,7 +365,7 @@ impl<'a> SymbolResolver<'a> { symlib: Option<&str>, sym: &Symbol, flags: ResolveFlags, - md: &'a Arc, + md: &'a Arc>, ) -> bool { // Check type. let ty = sym.ty(); @@ -450,7 +451,7 @@ impl<'a> SymbolResolver<'a> { } /// See `convert_mangled_name_to_long` on the PS4 for a reference. - fn decode_legacy(md: &Module, name: &str) -> Option { + fn decode_legacy(md: &Module, name: &str) -> Option { // Split the name. let mut p = name.splitn(3, '#'); let n = p.next()?; diff --git a/src/kernel/src/signal/mod.rs b/src/kernel/src/signal/mod.rs index 4d621e503..d06c34c99 100644 --- a/src/kernel/src/signal/mod.rs +++ b/src/kernel/src/signal/mod.rs @@ -1,4 +1,4 @@ -pub use set::*; +pub use self::set::*; use std::borrow::Cow; use std::num::NonZeroI32; diff --git a/src/kernel/src/syscalls/error.rs b/src/kernel/src/syscalls/error.rs deleted file mode 100644 index 3f2399ed6..000000000 --- a/src/kernel/src/syscalls/error.rs +++ /dev/null @@ -1,49 +0,0 @@ -use crate::errno::{strerror, Errno}; -use std::fmt::{Display, Formatter}; -use std::num::NonZeroI32; - -/// Error result of each syscall. -#[derive(Debug)] -pub enum Error { - Raw(NonZeroI32), - Object(Box), -} - -impl Error { - pub fn errno(&self) -> NonZeroI32 { - match self { - Error::Raw(v) => *v, - Error::Object(v) => v.errno(), - } - } -} - -impl From> for Error { - fn from(value: Box) -> Self { - Self::Object(value) - } -} - -impl From for Error { - fn from(value: T) -> Self { - Self::Object(Box::new(value)) - } -} - -impl Display for Error { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - Self::Raw(v) => f.write_str(strerror(*v)), - Self::Object(e) => e.fmt(f), - } - } -} - -impl std::error::Error for Error { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - Error::Raw(_) => None, - Error::Object(e) => e.source(), - } - } -} diff --git a/src/kernel/src/syscalls/input.rs b/src/kernel/src/syscalls/input.rs deleted file mode 100644 index 4ad7a21f2..000000000 --- a/src/kernel/src/syscalls/input.rs +++ /dev/null @@ -1,148 +0,0 @@ -use crate::fs::{VFileFlags, VPathBuf}; -use bitflags::bitflags; -use std::fmt::{Display, Formatter}; -use std::num::TryFromIntError; - -/// Input of the syscall entry point. -#[repr(C)] -pub struct Input<'a> { - pub id: u32, - pub offset: usize, - pub module: &'a VPathBuf, - pub args: [Arg; 6], -} - -/// An argument of the syscall. -#[repr(transparent)] -#[derive(Clone, Copy)] -pub struct Arg(usize); - -impl From for *const T { - fn from(v: Arg) -> Self { - v.0 as _ - } -} - -impl From for *mut T { - fn from(v: Arg) -> Self { - v.0 as _ - } -} - -impl From for u64 { - fn from(v: Arg) -> Self { - v.0 as _ - } -} - -impl From for usize { - fn from(v: Arg) -> Self { - v.0 - } -} - -impl TryFrom for i32 { - type Error = TryFromIntError; - - fn try_from(v: Arg) -> Result { - TryInto::::try_into(v.0).map(|v| v as i32) - } -} - -impl TryFrom for u32 { - type Error = TryFromIntError; - - fn try_from(v: Arg) -> Result { - v.0.try_into() - } -} - -impl TryFrom for crate::memory::Protections { - type Error = TryFromIntError; - - fn try_from(v: Arg) -> Result { - Ok(Self::from_bits_retain(v.0.try_into()?)) - } -} - -impl TryFrom for crate::memory::MappingFlags { - type Error = TryFromIntError; - - fn try_from(v: Arg) -> Result { - Ok(Self::from_bits_retain(v.0.try_into()?)) - } -} - -bitflags! { - /// Flags for open syscall. - pub struct OpenFlags: u32 { - const O_WRONLY = 0x00000001; - const O_RDWR = 0x00000002; - const O_ACCMODE = Self::O_WRONLY.bits() | Self::O_RDWR.bits(); - const O_SHLOCK = 0x00000010; - const O_EXLOCK = 0x00000020; - const O_TRUNC = 0x00000400; - const O_EXEC = 0x00040000; - const O_CLOEXEC = 0x00100000; - const UNK1 = 0x00400000; - } -} - -impl OpenFlags { - /// An implementation of `FFLAGS` macro. - pub fn to_fflags(self) -> VFileFlags { - VFileFlags::from_bits_truncate(self.bits() + 1) - } -} - -impl TryFrom for OpenFlags { - type Error = TryFromIntError; - - fn try_from(value: Arg) -> Result { - Ok(Self::from_bits_retain(value.0.try_into()?)) - } -} - -impl Display for OpenFlags { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - self.0.fmt(f) - } -} - -/// Outout pf rtprio_thread. -#[repr(C)] -pub struct RtPrio { - pub ty: u16, - pub prio: u16, -} - -/// Contains information about the loaded SELF. -#[repr(C)] -pub struct DynlibInfoEx { - pub size: u64, - pub name: [u8; 256], - pub handle: u32, - pub tlsindex: u32, - pub tlsinit: usize, - pub tlsinitsize: u32, - pub tlssize: u32, - pub tlsoffset: u32, - pub tlsalign: u32, - pub init: usize, - pub fini: usize, - pub unk1: u64, // Always zero. - pub unk2: u64, // Same here. - pub eh_frame_hdr: usize, - pub eh_frame: usize, - pub eh_frame_hdr_size: u32, - pub eh_frame_size: u32, - pub mapbase: usize, - pub textsize: u32, - pub unk3: u32, // Always 5. - pub database: usize, - pub datasize: u32, - pub unk4: u32, // Always 3. - pub unk5: [u8; 0x20], // Always zeroes. - pub unk6: u32, // Always 2. - pub refcount: u32, -} diff --git a/src/kernel/src/syscalls/mod.rs b/src/kernel/src/syscalls/mod.rs deleted file mode 100644 index ab27277fe..000000000 --- a/src/kernel/src/syscalls/mod.rs +++ /dev/null @@ -1,947 +0,0 @@ -pub use self::error::*; -pub use self::input::*; -pub use self::output::*; - -use crate::errno::{ - EBADF, EFAULT, EINVAL, ENAMETOOLONG, ENOENT, ENOMEM, ENOSYS, ENOTTY, EPERM, ESRCH, -}; -use crate::fs::{Fs, VFileFlags, VPath, VPathBuf}; -use crate::log::print; -use crate::memory::{MappingFlags, MemoryManager, Protections}; -use crate::process::{NamedObj, ProcObj, VProc, VProcGroup, VSession, VThread}; -use crate::regmgr::{RegError, RegMgr}; -use crate::rtld::{ModuleFlags, RuntimeLinker}; -use crate::signal::{SignalSet, SIGKILL, SIGSTOP, SIG_BLOCK, SIG_SETMASK, SIG_UNBLOCK}; -use crate::sysctl::{Sysctl, SysctlReq}; -use crate::ucred::{AuthInfo, Privilege}; -use crate::{info, warn}; -use macros::cpu_abi; -use std::ffi::{c_char, CStr}; -use std::io::Write; -use std::mem::{size_of, zeroed}; -use std::ptr::read; -use std::sync::Arc; - -mod error; -mod input; -mod output; - -/// Provides PS4 kernel routines for PS4 process. -pub struct Syscalls { - vp: &'static VProc, - fs: &'static Fs, - mm: &'static MemoryManager, - ld: &'static RuntimeLinker, - sysctl: &'static Sysctl, - regmgr: &'static RegMgr, -} - -impl Syscalls { - pub fn new( - vp: &'static VProc, - fs: &'static Fs, - mm: &'static MemoryManager, - ld: &'static RuntimeLinker, - sysctl: &'static Sysctl, - regmgr: &'static RegMgr, - ) -> Self { - Self { - vp, - fs, - mm, - ld, - sysctl, - regmgr, - } - } - - /// # Safety - /// This method may treat any [`Input::args`] as a pointer (depend on [`Input::id`]). Also this - /// method must de directly invoked by the PS4 application. - #[cpu_abi] - pub unsafe fn invoke(&self, i: &Input, o: &mut Output) -> i64 { - // Beware that we cannot have any variables that need to be dropped before invoke each - // syscall handler. The reason is because the handler might exit the calling thread without - // returning from the handler. - // - // See https://github.com/freebsd/freebsd-src/blob/release/9.1.0/sys/kern/init_sysent.c#L36 - // for standard FreeBSD syscalls. - let r = match i.id { - 5 => self.open( - i.args[0].into(), - i.args[1].try_into().unwrap(), - i.args[2].try_into().unwrap(), - ), - 20 => self.getpid(), - 50 => self.setlogin(i.args[0].into()), - 54 => self.ioctl( - i.args[0].try_into().unwrap(), - i.args[1].into(), - i.args[2].into(), - ), - 56 => self.revoke(i.args[0].into()), - 147 => self.setsid(), - 202 => self.sysctl( - i.args[0].into(), - i.args[1].try_into().unwrap(), - i.args[2].into(), - i.args[3].into(), - i.args[4].into(), - i.args[5].into(), - ), - 340 => self.sigprocmask( - i.args[0].try_into().unwrap(), - i.args[1].into(), - i.args[2].into(), - ), - 432 => self.thr_self(i.args[0].into()), - 466 => self.rtprio_thread( - i.args[0].try_into().unwrap(), - i.args[1].try_into().unwrap(), - i.args[2].into(), - ), - 477 => self.mmap( - i.args[0].into(), - i.args[1].into(), - i.args[2].try_into().unwrap(), - i.args[3].try_into().unwrap(), - i.args[4].try_into().unwrap(), - i.args[5].into(), - ), - 532 => self.regmgr_call( - i.args[0].try_into().unwrap(), - i.args[1].into(), - i.args[2].into(), - i.args[3].into(), - i.args[4].into(), - ), - 557 => self.namedobj_create( - i.args[0].into(), - i.args[1].into(), - i.args[2].try_into().unwrap(), - ), - 585 => self.is_in_sandbox(), - 587 => self.get_authinfo(i.args[0].try_into().unwrap(), i.args[1].into()), - 588 => self.mname(i.args[0].into(), i.args[1].into(), i.args[2].into()), - 592 => self.dynlib_get_list(i.args[0].into(), i.args[1].into(), i.args[2].into()), - 598 => self.dynlib_get_proc_param(i.args[0].into(), i.args[1].into()), - 599 => self.dynlib_process_needed_and_relocate(), - 608 => self.dynlib_get_info_ex( - i.args[0].try_into().unwrap(), - i.args[1].try_into().unwrap(), - i.args[2].into(), - ), - 610 => self.budget_get_ptype(i.args[0].try_into().unwrap()), - _ => todo!("syscall {} at {:#018x} on {}", i.id, i.offset, i.module), - }; - - // Get the output. - let v = match r { - Ok(v) => v, - Err(e) => { - warn!(e, "Syscall {} failed", i.id); - return e.errno().get().into(); - } - }; - - // Write the output. - *o = v; - 0 - } - - /// # Safety - /// This method must be directly invoked by the PS4 application. - #[cpu_abi] - pub unsafe fn int44(&self, offset: usize, module: &VPathBuf) -> ! { - panic!("Exiting with int 0x44 at {offset:#x} on {module}."); - } - - unsafe fn open( - &self, - path: *const c_char, - flags: OpenFlags, - mode: u32, - ) -> Result { - // Check flags. - if flags.intersects(OpenFlags::O_EXEC) { - if flags.intersects(OpenFlags::O_ACCMODE) { - return Err(Error::Raw(EINVAL)); - } - } else if flags.contains(OpenFlags::O_ACCMODE) { - return Err(Error::Raw(EINVAL)); - } - - // Allocate file object. - let mut file = self.fs.alloc(); - - // Get full path. - let path = Self::read_path(path)?; - - if flags.intersects(OpenFlags::UNK1) { - todo!("open({path}) with flags & 0x400000 != 0"); - } else if flags.intersects(OpenFlags::O_SHLOCK) { - todo!("open({path}) with flags & O_SHLOCK"); - } else if flags.intersects(OpenFlags::O_EXLOCK) { - todo!("open({path}) with flags & O_EXLOCK"); - } else if flags.intersects(OpenFlags::O_TRUNC) { - todo!("open({path}) with flags & O_TRUNC"); - } else if mode != 0 { - todo!("open({path}, {flags}) with mode = {mode}"); - } - - info!("Opening {path} with {flags}."); - - // Lookup file. - *file.flags_mut() = flags.to_fflags(); - file.set_ops(Some(self.fs.get(path)?.open()?)); - - // Install to descriptor table. - let fd = self.vp.files().alloc(Arc::new(file)); - - info!("File descriptor {fd} was allocated for {path}."); - - Ok(fd.into()) - } - - unsafe fn getpid(&self) -> Result { - Ok(self.vp.id().into()) - } - - unsafe fn setlogin(&self, namebuf: *const c_char) -> Result { - // Check current thread privilege. - VThread::current().priv_check(Privilege::PROC_SETLOGIN)?; - - // Get login name. - let login = Self::read_str(namebuf, 17).map_err(|e| { - if e.errno() == ENAMETOOLONG { - Error::Raw(EINVAL) - } else { - e - } - })?; - - // Set login name. - let mut group = self.vp.group_mut(); - let session = group.as_mut().unwrap().session_mut(); - - session.set_login(login); - info!("Login name was changed to '{login}'."); - - Ok(Output::ZERO) - } - - unsafe fn ioctl(&self, fd: i32, mut com: u64, data: *const u8) -> Result { - const IOC_VOID: u64 = 0x20000000; - const IOC_OUT: u64 = 0x40000000; - const IOC_IN: u64 = 0x80000000; - const IOCPARM_MASK: u64 = 0x1FFF; - - if com > 0xffffffff { - com &= 0xffffffff; - } - - let size = (com >> 16) & IOCPARM_MASK; - - if com & (IOC_VOID | IOC_OUT | IOC_IN) == 0 - || com & (IOC_OUT | IOC_IN) != 0 && size == 0 - || com & IOC_VOID != 0 && size != 0 && size != 4 - { - return Err(Error::Raw(ENOTTY)); - } - - // Get data. - let data = if size == 0 { - if com & IOC_IN != 0 { - todo!("ioctl with IOC_IN"); - } else if com & IOC_OUT != 0 { - todo!("ioctl with IOC_OUT"); - } - - &[] - } else { - todo!("ioctl with size != 0"); - }; - - // Get target file. - let file = self.vp.files().get(fd).ok_or(Error::Raw(EBADF))?; - let ops = file.ops().ok_or(Error::Raw(EBADF))?; - - if !file - .flags() - .intersects(VFileFlags::FREAD | VFileFlags::FWRITE) - { - return Err(Error::Raw(EBADF)); - } - - // Execute the operation. - let td = VThread::current(); - - info!("Executing ioctl({com:#x}) on {file}."); - - match com { - 0x20006601 => todo!("ioctl with com = 0x20006601"), - 0x20006602 => todo!("ioctl with com = 0x20006602"), - 0x8004667d => todo!("ioctl with com = 0x8004667d"), - 0x8004667e => todo!("ioctl with com = 0x8004667e"), - _ => {} - } - - ops.ioctl(&file, com, data, td.cred(), &td)?; - - if com & IOC_OUT != 0 { - todo!("ioctl with IOC_OUT"); - } - - Ok(Output::ZERO) - } - - unsafe fn revoke(&self, path: *const c_char) -> Result { - // Check current thread privilege. - VThread::current().priv_check(Privilege::SCE683)?; - - // Get full path. - let path = Self::read_path(path)?; - - info!("Revoking access to {path}."); - - // TODO: Check vnode::v_rdev. - let file = self.fs.get(path)?; - - if !file.is_character() { - return Err(Error::Raw(EINVAL)); - } - - // TODO: It seems like the initial ucred of the process is either root or has PRIV_VFS_ADMIN - // privilege. - self.fs.revoke(path); - - Ok(Output::ZERO) - } - - unsafe fn setsid(&self) -> Result { - // Check if current thread has privilege. - VThread::current().priv_check(Privilege::SCE680)?; - - // Check if the process already become a group leader. - let mut group = self.vp.group_mut(); - - if group.is_some() { - return Err(Error::Raw(EPERM)); - } - - // TODO: Find out the correct login name for VSession. - let id = self.vp.id(); - let session = VSession::new(id, String::from("root")); - - *group = Some(VProcGroup::new(id, session)); - info!("Virtual process now set as group leader."); - - Ok(id.into()) - } - - unsafe fn sysctl( - &self, - name: *const i32, - namelen: u32, - old: *mut u8, - oldlenp: *mut usize, - new: *const u8, - newlen: usize, - ) -> Result { - // Convert name to a slice. - let name = if namelen < 2 || namelen > 24 { - return Err(Error::Raw(EINVAL)); - } else if name.is_null() { - return Err(Error::Raw(EFAULT)); - } else { - std::slice::from_raw_parts(name, namelen as _) - }; - - if name[0] == Sysctl::CTL_DEBUG && !self.vp.cred().is_system() { - return Err(Error::Raw(EINVAL)); - } - - if name[0] == Sysctl::CTL_VM && name[1] == Sysctl::VM_TOTAL { - todo!("sysctl CTL_VM:VM_TOTAL") - } - - // Setup a request. - let mut req = SysctlReq::default(); - - if !oldlenp.is_null() { - req.validlen = *oldlenp; - } - - req.old = if old.is_null() { - None - } else if oldlenp.is_null() { - Some(std::slice::from_raw_parts_mut(old, 0)) - } else { - Some(std::slice::from_raw_parts_mut(old, *oldlenp)) - }; - - req.new = if new.is_null() { - None - } else { - Some(std::slice::from_raw_parts(new, newlen)) - }; - - // Execute. - if let Err(e) = self.sysctl.exec(name, &mut req) { - if e.errno() != ENOMEM { - return Err(e); - } - } - - if !oldlenp.is_null() { - *oldlenp = if req.old.is_none() || req.oldidx <= req.validlen { - req.oldidx - } else { - req.validlen - }; - } - - Ok(Output::ZERO) - } - - unsafe fn sigprocmask( - &self, - how: i32, - set: *const SignalSet, - oset: *mut SignalSet, - ) -> Result { - // Convert set to an option. - let set = if set.is_null() { None } else { Some(*set) }; - - // Keep the current mask for copying to the oset. We need to copy to the oset only when this - // function succees. - let vt = VThread::current(); - let mut mask = vt.sigmask_mut(); - let prev = mask.clone(); - - // Update the mask. - if let Some(mut set) = set { - match how { - SIG_BLOCK => { - // Remove uncatchable signals. - set.remove(SIGKILL); - set.remove(SIGSTOP); - - // Update mask. - *mask |= set; - } - SIG_UNBLOCK => { - // Update mask. - *mask &= !set; - - // TODO: Invoke signotify at the end. - } - SIG_SETMASK => { - // Remove uncatchable signals. - set.remove(SIGKILL); - set.remove(SIGSTOP); - - // Replace mask. - *mask = set; - - // TODO: Invoke signotify at the end. - } - _ => return Err(Error::Raw(EINVAL)), - } - - // TODO: Check if we need to invoke reschedule_signals. - info!("Signal mask was changed from {} to {}.", prev, mask); - } - - // Copy output. - if !oset.is_null() { - *oset = prev; - } - - Ok(Output::ZERO) - } - - unsafe fn thr_self(&self, id: *mut i64) -> Result { - *id = VThread::current().id().get().into(); - Ok(Output::ZERO) - } - - unsafe fn rtprio_thread( - &self, - function: i32, - lwpid: i32, - rtp: *mut RtPrio, - ) -> Result { - const RTP_LOOKUP: i32 = 0; - const RTP_SET: i32 = 1; - const RTP_UNK: i32 = 2; - - let td = VThread::current(); - - if function == RTP_SET { - todo!("rtprio_thread with function = 1"); - } - - if function == RTP_UNK && td.cred().is_system() { - todo!("rtprio_thread with function = 2"); - } else if lwpid != 0 && lwpid != td.id().get() { - return Err(Error::Raw(ESRCH)); - } else if function == RTP_LOOKUP { - (*rtp).ty = td.pri_class(); - (*rtp).prio = match td.pri_class() & 0xfff7 { - 2 | 3 | 4 => td.base_user_pri(), - _ => 0, - }; - } else { - todo!("rtprio_thread with function = {function}"); - } - - Ok(Output::ZERO) - } - - unsafe fn mmap( - &self, - addr: usize, - len: usize, - prot: Protections, - flags: MappingFlags, - fd: i32, - pos: usize, - ) -> Result { - // Check if the request is a guard for main stack. - let ms = self.mm.stack(); - - if addr == ms.guard() { - assert_eq!(len, MemoryManager::VIRTUAL_PAGE_SIZE); - assert_eq!(prot.is_empty(), true); - assert_eq!(flags.intersects(MappingFlags::MAP_ANON), true); - assert_eq!(fd, -1); - assert_eq!(pos, 0); - - info!("Guard page has been requested for main stack."); - - return Ok(ms.guard().into()); - } - - // TODO: Make a proper name. - let pages = self.mm.mmap(addr, len, prot, "", flags, fd, pos)?; - - if addr != 0 && pages.addr() != addr { - warn!( - "mmap({:#x}, {:#x}, {}, {}, {}, {}) was success with {:#x} instead of {:#x}.", - addr, - len, - prot, - flags, - fd, - pos, - pages.addr(), - addr - ); - } else { - info!( - "{:#x}:{:p} is mapped as {} with {}.", - pages.addr(), - pages.end(), - prot, - flags, - ); - } - - Ok(pages.into_raw().into()) - } - - unsafe fn regmgr_call( - &self, - op: u32, - _: usize, - buf: *mut i32, - req: *const u8, - reqlen: usize, - ) -> Result { - // TODO: Check the result of priv_check(td, 682). - if buf.is_null() { - todo!("regmgr_call with buf = null"); - } - - if req.is_null() { - todo!("regmgr_call with req = null"); - } - - if reqlen > 2048 { - todo!("regmgr_call with reqlen > 2048"); - } - - // Execute the operation. - let td = VThread::current(); - let r = match op { - 0x18 => { - let v1 = read::(req as _); - let v2 = read::(req.add(8) as _); - let value = read::(req.add(12) as _); - - info!( - "Attempting to set registry with v1: {}, v2: {}, value: {}.", - v1, v2, value - ); - self.regmgr.decode_key(v1, v2, td.cred(), 2).and_then(|k| { - info!("Setting registry key {} to value {}.", k, value); - self.regmgr.set_int(k, value) - }) - } - 0x19 => { - let v1 = read::(req as _); - let v2 = read::(req.add(8) as _); - - self.regmgr - .decode_key(v1, v2, td.cred(), 1) - .and_then(|k| todo!("regmgr_call({op}) with matched key = {k}")) - } - 0x27 | 0x40.. => Err(RegError::V800d0219), - v => todo!("regmgr_call({v})"), - }; - - // Write the result. - *buf = match r { - Ok(v) => v, - Err(e) => { - warn!(e, "regmgr_call({op}) failed"); - e.code() - } - }; - - Ok(Output::ZERO) - } - - unsafe fn namedobj_create( - &self, - name: *const c_char, - data: usize, - flags: u32, - ) -> Result { - // Check name. - if name.is_null() { - return Err(Error::Raw(EINVAL)); - } - - // Allocate the entry. - let name = Self::read_str(name, 32)?; - let mut table = self.vp.objects_mut(); - let (entry, id) = table - .alloc::<_, ()>(|_| Ok(ProcObj::Named(NamedObj::new(name.to_owned(), data)))) - .unwrap(); - - entry.set_name(Some(name.to_owned())); - entry.set_flags((flags as u16) | 0x1000); - - info!( - "Named object '{}' (ID = {}) was created with data = {:#x} and flags = {:#x}.", - name, id, data, flags - ); - - Ok(id.into()) - } - - unsafe fn is_in_sandbox(&self) -> Result { - // TODO: Get the actual value from the PS4. - info!("Returning is_in_sandbox as 0."); - Ok(0.into()) - } - - unsafe fn get_authinfo(&self, pid: i32, buf: *mut AuthInfo) -> Result { - info!("Getting authinfo for PID: {pid}"); - - // Check if PID is our process. - if pid != 0 && pid != self.vp.id().get() { - return Err(Error::Raw(ESRCH)); - } - - // Check privilege. - let mut info: AuthInfo = zeroed(); - let td = VThread::current(); - let cred = self.vp.cred(); - - if td.priv_check(Privilege::SCE686).is_ok() { - info = cred.auth().clone(); - } else { - // TODO: Refactor this for readability. - let paid = cred.auth().paid.wrapping_add(0xc7ffffffeffffffc); - - if paid < 0xf && ((0x6001u32 >> (paid & 0x3f)) & 1) != 0 { - info.paid = cred.auth().paid; - } - - info.caps[0] = cred.auth().caps[0] & 0x7000000000000000; - - info!( - "Retrieved authinfo for non-system credential (paid = {:#x}, caps[0] = {:#x}).", - info.paid, info.caps[0] - ); - } - - // Copy into. - if buf.is_null() { - todo!("get_authinfo with buf = null"); - } else { - *buf = info; - } - - Ok(Output::ZERO) - } - - unsafe fn mname(&self, addr: usize, len: usize, name: *const c_char) -> Result { - let name = Self::read_str(name, 32)?; - - info!( - "Setting name for {:#x}:{:#x} to '{}'.", - addr, - addr + len, - name - ); - - // PS4 does not check if vm_map_set_name is failed. - let len = (addr & 0x3fff) + len + 0x3fff & 0xffffffffffffc000; - let addr = (addr & 0xffffffffffffc000) as *mut u8; - - if let Err(e) = self.mm.mname(addr, len, name) { - warn!(e, "mname({addr:p}, {len:#x}, {name}) was failed"); - } - - Ok(Output::ZERO) - } - - unsafe fn dynlib_get_list( - &self, - buf: *mut u32, - max: usize, - copied: *mut usize, - ) -> Result { - // Check if application is dynamic linking. - let app = self.ld.app(); - - if app.file_info().is_none() { - return Err(Error::Raw(EPERM)); - } - - // Copy module ID. - let list = self.ld.list(); - - if list.len() > max { - return Err(Error::Raw(ENOMEM)); - } - - for (i, m) in list.iter().enumerate() { - *buf.add(i) = m.id(); - } - - // Set copied. - *copied = list.len(); - - info!("Copied {} module IDs for dynamic linking.", list.len()); - - Ok(Output::ZERO) - } - - unsafe fn dynlib_get_proc_param( - &self, - param: *mut usize, - size: *mut usize, - ) -> Result { - // Check if application is a dynamic SELF. - let app = self.ld.app(); - - if app.file_info().is_none() { - return Err(Error::Raw(EPERM)); - } - - // Get param. - match app.proc_param() { - Some(v) => { - // TODO: Seems like ET_SCE_DYNEXEC is mapped at a fixed address. - *param = app.memory().addr() + v.0; - *size = v.1; - } - None => todo!("app is dynamic but no PT_SCE_PROCPARAM"), - } - - Ok(Output::ZERO) - } - - unsafe fn dynlib_process_needed_and_relocate(&self) -> Result { - // Check if application is dynamic linking. - if self.ld.app().file_info().is_none() { - return Err(Error::Raw(EINVAL)); - } - - // TODO: Implement dynlib_load_needed_shared_objects. - info!("Relocating loaded modules."); - - self.ld.relocate()?; - - Ok(Output::ZERO) - } - - unsafe fn dynlib_get_info_ex( - &self, - handle: u32, - flags: u32, - info: *mut DynlibInfoEx, - ) -> Result { - // Check if application is dynamic linking. - let app = self.ld.app(); - - if app.file_info().is_none() { - return Err(Error::Raw(EPERM)); - } - - // Check buffer size. - let size: usize = (*info).size.try_into().unwrap(); - - if size != size_of::() { - return Err(Error::Raw(EINVAL)); - } - - // Lookup the module. - let modules = self.ld.list(); - let md = match modules.iter().find(|m| m.id() == handle) { - Some(v) => v, - None => return Err(Error::Raw(ESRCH)), - }; - - // Fill the info. - let mem = md.memory(); - let addr = mem.addr(); - - *info = zeroed(); - - (*info).handle = md.id(); - (*info).mapbase = addr + mem.base(); - (*info).textsize = mem.text_segment().len().try_into().unwrap(); - (*info).unk3 = 5; - (*info).database = addr + mem.data_segment().start(); - (*info).datasize = mem.data_segment().len().try_into().unwrap(); - (*info).unk4 = 3; - (*info).unk6 = 2; - (*info).refcount = Arc::strong_count(md).try_into().unwrap(); - - // Copy module name. - if flags & 2 == 0 || !md.flags().contains(ModuleFlags::UNK1) { - let name = md.path().file_name().unwrap(); - - (*info).name[..name.len()].copy_from_slice(name.as_bytes()); - (*info).name[0xff] = 0; - } - - // Set TLS information. Not sure if the tlsinit can be zero when the tlsinitsize is zero. - // Let's keep the same behavior as the PS4 for now. - (*info).tlsindex = if flags & 1 != 0 { - let flags = md.flags(); - let mut upper = if flags.contains(ModuleFlags::UNK1) { - 1 - } else { - 0 - }; - - if flags.contains(ModuleFlags::MAIN_PROG) { - upper += 2; - } - - (upper << 16) | (md.tls_index() & 0xffff) - } else { - md.tls_index() & 0xffff - }; - - if let Some(i) = md.tls_info() { - (*info).tlsinit = addr + i.init(); - (*info).tlsinitsize = i.init_size().try_into().unwrap(); - (*info).tlssize = i.size().try_into().unwrap(); - (*info).tlsalign = i.align().try_into().unwrap(); - } else { - (*info).tlsinit = addr; - } - - (*info).tlsoffset = (*md.tls_offset()).try_into().unwrap(); - - // Initialization and finalization functions. - if !md.flags().contains(ModuleFlags::UNK5) { - (*info).init = md.init().map(|v| addr + v).unwrap_or(0); - (*info).fini = md.fini().map(|v| addr + v).unwrap_or(0); - } - - // Exception handling. - if let Some(i) = md.eh_info() { - (*info).eh_frame_hdr = addr + i.header(); - (*info).eh_frame_hdr_size = i.header_size().try_into().unwrap(); - (*info).eh_frame = addr + i.frame(); - (*info).eh_frame_size = i.frame_size().try_into().unwrap(); - } else { - (*info).eh_frame_hdr = addr; - } - - let mut e = info!(); - - writeln!( - e, - "Retrieved info for module {} (ID = {}).", - md.path(), - handle - ) - .unwrap(); - writeln!(e, "mapbase : {:#x}", (*info).mapbase).unwrap(); - writeln!(e, "textsize : {:#x}", (*info).textsize).unwrap(); - writeln!(e, "database : {:#x}", (*info).database).unwrap(); - writeln!(e, "datasize : {:#x}", (*info).datasize).unwrap(); - writeln!(e, "tlsindex : {}", (*info).tlsindex).unwrap(); - writeln!(e, "tlsinit : {:#x}", (*info).tlsinit).unwrap(); - writeln!(e, "tlsoffset : {:#x}", (*info).tlsoffset).unwrap(); - writeln!(e, "init : {:#x}", (*info).init).unwrap(); - writeln!(e, "fini : {:#x}", (*info).fini).unwrap(); - writeln!(e, "eh_frame_hdr: {:#x}", (*info).eh_frame_hdr).unwrap(); - - print(e); - - Ok(Output::ZERO) - } - - unsafe fn budget_get_ptype(&self, pid: i32) -> Result { - info!("Getting ptype for PID: {}", pid); - // Check if PID is our process. - if pid != -1 && pid != self.vp.id().get() { - return Err(Error::Raw(ENOSYS)); - } - - // TODO: Invoke id_rlock. Not sure why return ENOENT is working here. - Err(Error::Raw(ENOENT)) - } - - unsafe fn read_path<'a>(path: *const c_char) -> Result<&'a VPath, Error> { - // TODO: Check maximum path length on the PS4. - let path = CStr::from_ptr(path); - let path = match path.to_str() { - Ok(v) => match VPath::new(v) { - Some(v) => v, - None => todo!("syscall with non-absolute path {v}"), - }, - Err(_) => return Err(Error::Raw(ENOENT)), - }; - - Ok(path) - } - - /// See `copyinstr` on the PS4 for a reference. - unsafe fn read_str<'a>(ptr: *const c_char, max: usize) -> Result<&'a str, Error> { - let mut len = None; - - for i in 0..max { - if *ptr.add(i) == 0 { - len = Some(i); - break; - } - } - - match len { - Some(i) => Ok(std::str::from_utf8(std::slice::from_raw_parts(ptr as _, i)).unwrap()), - None => Err(Error::Raw(EFAULT)), - } - } -} diff --git a/src/kernel/src/sysctl/mod.rs b/src/kernel/src/sysctl/mod.rs index fb287947f..6beb0b3d9 100644 --- a/src/kernel/src/sysctl/mod.rs +++ b/src/kernel/src/sysctl/mod.rs @@ -1,19 +1,20 @@ use crate::arnd::Arnd; -use crate::errno::{EINVAL, EISDIR, ENAMETOOLONG, ENOENT, ENOMEM, ENOTDIR, EPERM, ESRCH}; +use crate::ee::{ExecutionEngine, SysErr, SysIn, SysOut}; +use crate::errno::{EFAULT, EINVAL, EISDIR, ENAMETOOLONG, ENOENT, ENOMEM, ENOTDIR, EPERM, ESRCH}; use crate::memory::MemoryManager; use crate::process::VProc; -use crate::syscalls::Error; use std::any::Any; use std::cmp::min; +use std::sync::Arc; /// A registry of system parameters. /// /// This is an implementation of /// https://github.com/freebsd/freebsd-src/blob/release/9.1.0/sys/kern/kern_sysctl.c. pub struct Sysctl { - arnd: &'static Arnd, - vp: &'static VProc, - mm: &'static MemoryManager, + arnd: Arc, + vp: Arc, + mm: Arc, } impl Sysctl { @@ -40,12 +41,90 @@ impl Sysctl { const CTLFLAG_ANYBODY: u32 = 0x10000000; const CTLFLAG_WR: u32 = 0x40000000; - pub fn new(arnd: &'static Arnd, vp: &'static VProc, mm: &'static MemoryManager) -> Self { - Self { arnd, vp, mm } + pub fn new(arnd: &Arc, vp: &Arc, mm: &Arc, ee: &E) -> Arc + where + E: ExecutionEngine, + { + let ctl = Arc::new(Self { + arnd: arnd.clone(), + vp: vp.clone(), + mm: mm.clone(), + }); + + ee.register_syscall(202, &ctl, Self::sys_sysctl); + + ctl + } + + fn sys_sysctl(self: &Arc, i: &SysIn) -> Result { + // Get arguments. + let name: *const i32 = i.args[0].into(); + let namelen: u32 = i.args[1].try_into().unwrap(); + let old: *mut u8 = i.args[2].into(); + let oldlenp: *mut usize = i.args[3].into(); + let new: *const u8 = i.args[4].into(); + let newlen: usize = i.args[5].into(); + + // Convert name to a slice. + let name = if namelen < 2 || namelen > 24 { + return Err(SysErr::Raw(EINVAL)); + } else if name.is_null() { + return Err(SysErr::Raw(EFAULT)); + } else { + unsafe { std::slice::from_raw_parts(name, namelen as _) } + }; + + if name[0] == Self::CTL_DEBUG && !self.vp.cred().is_system() { + return Err(SysErr::Raw(EINVAL)); + } + + if name[0] == Self::CTL_VM && name[1] == Self::VM_TOTAL { + todo!("sysctl CTL_VM:VM_TOTAL") + } + + // Setup a request. + let mut req = SysctlReq::default(); + + if !oldlenp.is_null() { + req.validlen = unsafe { *oldlenp }; + } + + req.old = if old.is_null() { + None + } else if oldlenp.is_null() { + Some(unsafe { std::slice::from_raw_parts_mut(old, 0) }) + } else { + Some(unsafe { std::slice::from_raw_parts_mut(old, *oldlenp) }) + }; + + req.new = if new.is_null() { + None + } else { + Some(unsafe { std::slice::from_raw_parts(new, newlen) }) + }; + + // Execute. + if let Err(e) = self.exec(name, &mut req) { + if e.errno() != ENOMEM { + return Err(e); + } + } + + if !oldlenp.is_null() { + unsafe { + *oldlenp = if req.old.is_none() || req.oldidx <= req.validlen { + req.oldidx + } else { + req.validlen + } + }; + } + + Ok(SysOut::ZERO) } /// See `sysctl_root` on the PS4 for a reference. - pub fn exec(&self, name: &[i32], req: &mut SysctlReq) -> Result<(), Error> { + fn exec(&self, name: &[i32], req: &mut SysctlReq) -> Result<(), SysErr> { let mut indx = 0; let mut list = &CHILDREN; @@ -63,7 +142,7 @@ impl Sysctl { // Check type. if (oid.kind & Self::CTLTYPE) != Self::CTLTYPE_NODE { if indx != name.len() { - return Err(Error::Raw(ENOTDIR)); + return Err(SysErr::Raw(ENOTDIR)); } } else if indx != name.len() && oid.handler.is_none() { if indx == 24 { @@ -76,15 +155,15 @@ impl Sysctl { // Check if enabled. if !oid.enabled { - return Err(Error::Raw(ENOENT)); + return Err(SysErr::Raw(ENOENT)); } else if (oid.kind & Self::CTLTYPE) == Self::CTLTYPE_NODE && oid.handler.is_none() { - return Err(Error::Raw(EISDIR)); + return Err(SysErr::Raw(EISDIR)); } // Check if write is allowed. if req.new.is_some() { if (oid.kind & Self::CTLFLAG_WR) == 0 { - return Err(Error::Raw(EPERM)); + return Err(SysErr::Raw(EPERM)); } else if (oid.kind & Self::CTLFLAG_SECURE) != 0 { todo!("sysctl on kind & CTLFLAG_SECURE"); } @@ -97,7 +176,7 @@ impl Sysctl { // Get the handler. let handler = match oid.handler { Some(v) => v, - None => return Err(Error::Raw(EINVAL)), + None => return Err(SysErr::Raw(EINVAL)), }; // Get handler arguments. @@ -121,14 +200,14 @@ impl Sysctl { _: &Arg, _: usize, req: &mut SysctlReq, - ) -> Result<(), Error> { + ) -> Result<(), SysErr> { // Check input size. let newlen = req.new.as_ref().map(|b| b.len()).unwrap_or(0); if newlen == 0 { - return Err(Error::Raw(ENOENT)); + return Err(SysErr::Raw(ENOENT)); } else if newlen >= 0x400 { - return Err(Error::Raw(ENAMETOOLONG)); + return Err(SysErr::Raw(ENAMETOOLONG)); } // Read name. @@ -142,7 +221,7 @@ impl Sysctl { }; if name.is_empty() { - return Err(Error::Raw(ENOENT)); + return Err(SysErr::Raw(ENOENT)); } // Remove '.' at the end if present. @@ -184,7 +263,7 @@ impl Sysctl { }; if (oid.kind & Self::CTLTYPE) != Self::CTLTYPE_NODE || oid.handler.is_some() { - return Err(Error::Raw(ENOENT)); + return Err(SysErr::Raw(ENOENT)); } next = oid.arg1.unwrap().downcast_ref::().unwrap().first; @@ -203,12 +282,12 @@ impl Sysctl { arg1: &Arg, _: usize, req: &mut SysctlReq, - ) -> Result<(), Error> { + ) -> Result<(), SysErr> { // Check the buffer. let oldlen = req.old.as_ref().map(|b| b.len()).unwrap_or(0); if oldlen >= 73 { - return Err(Error::Raw(EINVAL)); + return Err(SysErr::Raw(EINVAL)); } // Check if the request is for our process. @@ -218,7 +297,7 @@ impl Sysctl { }; if arg1[0] != self.vp.id().get() { - return Err(Error::Raw(ESRCH)); + return Err(SysErr::Raw(ESRCH)); } // TODO: Implement sceSblACMgrIsSystemUcred. @@ -241,7 +320,7 @@ impl Sysctl { _: &Arg, _: usize, req: &mut SysctlReq, - ) -> Result<(), Error> { + ) -> Result<(), SysErr> { let stack = self.mm.stack().end() as usize; let value = stack.to_ne_bytes(); @@ -254,7 +333,7 @@ impl Sysctl { _: &Arg, _: usize, req: &mut SysctlReq, - ) -> Result<(), Error> { + ) -> Result<(), SysErr> { let mut buf = [0; 256]; let len = min(req.old.as_ref().map(|b| b.len()).unwrap_or(0), 256); @@ -270,7 +349,7 @@ impl Sysctl { arg1: &Arg, arg2: usize, req: &mut SysctlReq, - ) -> Result<(), Error> { + ) -> Result<(), SysErr> { // Read old value. let value: i32 = match arg1 { Arg::Name(v) => v[0], @@ -301,7 +380,7 @@ pub struct SysctlReq<'a> { impl<'a> SysctlReq<'a> { /// See `sysctl_new_user` on the PS4 for a reference. - pub fn read(&mut self, buf: &mut [u8]) -> Result<(), Error> { + pub fn read(&mut self, buf: &mut [u8]) -> Result<(), SysErr> { let new = match self.new.as_ref() { Some(v) => v, None => return Ok(()), @@ -312,12 +391,12 @@ impl<'a> SysctlReq<'a> { self.newidx += buf.len(); Ok(()) } else { - Err(Error::Raw(EINVAL)) + Err(SysErr::Raw(EINVAL)) } } /// See `sysctl_old_user` on the PS4 for a reference. - pub fn write(&mut self, data: &[u8]) -> Result<(), Error> { + pub fn write(&mut self, data: &[u8]) -> Result<(), SysErr> { // Update the index. let origidx = self.oldidx; self.oldidx += data.len(); @@ -338,7 +417,7 @@ impl<'a> SysctlReq<'a> { }; if data.len() > i { - Err(Error::Raw(ENOMEM)) + Err(SysErr::Raw(ENOMEM)) } else { Ok(()) } @@ -370,7 +449,7 @@ enum Arg<'a> { Static(Option<&'static (dyn Any + Send + Sync)>), } -type Handler = fn(&Sysctl, &'static Oid, &Arg, usize, &mut SysctlReq) -> Result<(), Error>; +type Handler = fn(&Sysctl, &'static Oid, &Arg, usize, &mut SysctlReq) -> Result<(), SysErr>; static CHILDREN: OidList = OidList { first: Some(&SYSCTL),