diff --git a/src/arch/x86_64/interrupt/exception.rs b/src/arch/x86_64/interrupt/exception.rs index 864a4c32f81c90fd27de00f44e3dbdda40360994..7d58efd98267d5a185431b2bea12583e03549232 100644 --- a/src/arch/x86_64/interrupt/exception.rs +++ b/src/arch/x86_64/interrupt/exception.rs @@ -1,5 +1,10 @@ -use crate::interrupt::stack_trace; -use crate::syscall::flag::*; +use crate::{ + common::unique::Unique, + context, + interrupt::stack_trace, + ptrace, + syscall::flag::* +}; extern { fn ksignal(signal: usize); @@ -13,9 +18,41 @@ interrupt_stack_p!(divide_by_zero, stack, { }); interrupt_stack!(debug, stack, { - println!("Debug trap"); - stack.dump(); - ksignal(SIGTRAP); + match ptrace::breakpoint_callback_dryrun(true) { + Some(_) => { + { + let contexts = context::contexts(); + if let Some(context) = contexts.current() { + let mut context = context.write(); + if let Some(ref mut kstack) = context.kstack { + context.regs = Some((kstack.as_mut_ptr() as usize, Unique::new_unchecked(stack))); + } + } + } + + let had_singlestep = stack.iret.rflags & (1 << 8) == 1 << 8; + stack.set_singlestep(false); + if ptrace::breakpoint_callback(true).is_none() { + // There is no guarantee that this is Some(_) just + // because the dryrun is Some(_). So, if there wasn't + // *actually* any breakpoint, restore the trap flag. + stack.set_singlestep(had_singlestep); + } + + { + let contexts = context::contexts(); + if let Some(context) = contexts.current() { + let mut context = context.write(); + context.regs = None; + } + } + }, + None => { + println!("Debug trap"); + stack.dump(); + ksignal(SIGTRAP); + } + } }); interrupt_stack!(non_maskable, stack, { diff --git a/src/arch/x86_64/interrupt/irq.rs b/src/arch/x86_64/interrupt/irq.rs index 013bbb56692436972e1e1827ef01c71c7c158b82..fbc5bb87159574d344a5c9f9faee516aa2830f9d 100644 --- a/src/arch/x86_64/interrupt/irq.rs +++ b/src/arch/x86_64/interrupt/irq.rs @@ -68,7 +68,9 @@ interrupt_stack!(pit, stack, { let mut context = context.write(); // Make all registers available to e.g. the proc: // scheme - context.interrupt_stack = Some(Unique::new_unchecked(stack)); + if let Some(ref mut kstack) = context.kstack { + context.regs = Some((kstack.as_mut_ptr() as usize, Unique::new_unchecked(stack))); + } } } let _ = context::switch(); @@ -76,7 +78,7 @@ interrupt_stack!(pit, stack, { let contexts = crate::context::contexts(); if let Some(context) = contexts.current() { let mut context = context.write(); - context.interrupt_stack = None; + context.regs = None; } } } diff --git a/src/arch/x86_64/interrupt/syscall.rs b/src/arch/x86_64/interrupt/syscall.rs index 079ae1ecea806981aa7603c8e554bccb899fd6a7..2a4d68e74b74ebb158727b4cddf909ef30da123e 100644 --- a/src/arch/x86_64/interrupt/syscall.rs +++ b/src/arch/x86_64/interrupt/syscall.rs @@ -7,7 +7,7 @@ use x86::shared::msr; pub unsafe fn init() { msr::wrmsr(msr::IA32_STAR, ((gdt::GDT_KERNEL_CODE as u64) << 3) << 32); msr::wrmsr(msr::IA32_LSTAR, syscall_instruction as u64); - msr::wrmsr(msr::IA32_FMASK, 1 << 9); + msr::wrmsr(msr::IA32_FMASK, 0x0300); // Clear trap flag and interrupt enable msr::wrmsr(msr::IA32_KERNEL_GS_BASE, &gdt::TSS as *const _ as u64); let efer = msr::rdmsr(msr::IA32_EFER); @@ -29,11 +29,13 @@ macro_rules! with_interrupt_stack { let contexts = context::contexts(); if let Some(context) = contexts.current() { let mut context = context.write(); - context.interrupt_stack = Some(Unique::new_unchecked(&mut stack.interrupt_stack)); + if let Some(ref mut kstack) = context.kstack { + context.regs = Some((kstack.as_mut_ptr() as usize, Unique::new_unchecked(&mut stack.interrupt_stack))); + } } } - let is_sysemu = ptrace::syscall_callback(); + let is_sysemu = ptrace::breakpoint_callback(false); if !is_sysemu.unwrap_or(false) { // If not on a sysemu breakpoint let $stack = &mut *stack; @@ -42,7 +44,7 @@ macro_rules! with_interrupt_stack { if is_sysemu.is_some() { // Only callback if there was a pre-syscall // callback too. - ptrace::syscall_callback(); + ptrace::breakpoint_callback(false); } } @@ -50,7 +52,7 @@ macro_rules! with_interrupt_stack { let contexts = context::contexts(); if let Some(context) = contexts.current() { let mut context = context.write(); - context.interrupt_stack = None; + context.regs = None; } } } @@ -167,8 +169,16 @@ pub struct SyscallStack { } #[naked] -pub unsafe extern fn clone_ret() { - asm!("pop rbp - xor rax, rax" - : : : : "intel", "volatile"); +pub unsafe extern "C" fn clone_ret() { + // The C x86_64 ABI specifies that rbp is pushed to save the old + // call frame. Popping rbp means we're using the parent's call + // frame and thus will not only return from this function but also + // from the function above this one. + // When this is called, the stack should have been + // interrupt->inner->syscall->clone + // then changed to + // interrupt->inner->clone_ret->clone + // so this will return from "inner". + + asm!("pop rbp" : : : : "intel", "volatile"); } diff --git a/src/arch/x86_64/macros.rs b/src/arch/x86_64/macros.rs index 2afdb90711870cd95a269ee147c2c059bfd588e8..55d434c5435e7d3b93b88ca1a6672c6d7f318804 100644 --- a/src/arch/x86_64/macros.rs +++ b/src/arch/x86_64/macros.rs @@ -251,6 +251,16 @@ impl InterruptStack { self.iret.cs = all.cs; self.iret.rflags = all.eflags; } + /// Enables the "Trap Flag" in the FLAGS register, causing the CPU + /// to send a Debug exception after the next instruction. This is + /// used for singlestep in the proc: scheme. + pub fn set_singlestep(&mut self, enabled: bool) { + if enabled { + self.iret.rflags |= 1 << 8; + } else { + self.iret.rflags &= !(1 << 8); + } + } } #[macro_export] diff --git a/src/context/context.rs b/src/context/context.rs index fea302dfb9922ca475d393cdd4c55978f0514d3c..269b4ee4095953e4fa4302edac360205d76cfe67 100644 --- a/src/context/context.rs +++ b/src/context/context.rs @@ -166,10 +166,13 @@ pub struct Context { pub files: Arc<Mutex<Vec<Option<FileDescriptor>>>>, /// Signal actions pub actions: Arc<Mutex<Vec<(SigAction, usize)>>>, - /// The interrupt stack which holds all the context's registers - pub interrupt_stack: Option<Unique<InterruptStack>>, + /// The pointer to the user-space registers, saved after certain + /// interrupts. This pointer is somewhere inside kstack, and the + /// kstack address at the time of creation is the first element in + /// this tuple. + pub regs: Option<(usize, Unique<InterruptStack>)>, /// A somewhat hacky way to initially stop a context when creating - /// a new instance of the proc: scheme, entirely separate from any + /// a new instance of the proc: scheme, entirely separate from /// signals or any other way to restart a process. pub ptrace_stop: bool } @@ -223,7 +226,7 @@ impl Context { }, 0 ); 128])), - interrupt_stack: None, + regs: None, ptrace_stop: false } } diff --git a/src/ptrace.rs b/src/ptrace.rs index d0e928c4f0afb9a1ae5501b1b0ec2a861f67a4b5..2b34179650e77e8f6f325e991bad9488732ccba9 100644 --- a/src/ptrace.rs +++ b/src/ptrace.rs @@ -1,31 +1,42 @@ use crate::{ - context::{self, ContextId, Status}, + arch::macros::InterruptStack, + common::unique::Unique, + context::{self, Context, ContextId, Status}, sync::WaitCondition }; use alloc::{ + boxed::Box, collections::BTreeMap, sync::Arc }; use spin::{Once, RwLock, RwLockReadGuard, RwLockWriteGuard}; use syscall::error::*; +// ____ _ _ _ +// | __ ) _ __ ___ __ _| | ___ __ ___ (_)_ __ | |_ ___ +// | _ \| '__/ _ \/ _` | |/ / '_ \ / _ \| | '_ \| __/ __| +// | |_) | | | __/ (_| | <| |_) | (_) | | | | | |_\__ \ +// |____/|_| \___|\__,_|_|\_\ .__/ \___/|_|_| |_|\__|___/ +// |_| + struct Handle { tracee: Arc<WaitCondition>, tracer: Arc<WaitCondition>, - sysemu: bool + sysemu: bool, + singlestep: bool } -static SYSCALL_BREAKPOINTS: Once<RwLock<BTreeMap<ContextId, Handle>>> = Once::new(); +static BREAKPOINTS: Once<RwLock<BTreeMap<ContextId, Handle>>> = Once::new(); fn init_breakpoints() -> RwLock<BTreeMap<ContextId, Handle>> { RwLock::new(BTreeMap::new()) } fn breakpoints() -> RwLockReadGuard<'static, BTreeMap<ContextId, Handle>> { - SYSCALL_BREAKPOINTS.call_once(init_breakpoints).read() + BREAKPOINTS.call_once(init_breakpoints).read() } fn breakpoints_mut() -> RwLockWriteGuard<'static, BTreeMap<ContextId, Handle>> { - SYSCALL_BREAKPOINTS.call_once(init_breakpoints).write() + BREAKPOINTS.call_once(init_breakpoints).write() } /// Continue the process with the specified ID @@ -37,7 +48,7 @@ pub fn cont(pid: ContextId) { } /// Create a new breakpoint for the specified tracee, optionally with a sysemu flag -pub fn break_syscall(pid: ContextId, sysemu: bool) { +pub fn set_breakpoint(pid: ContextId, sysemu: bool, singlestep: bool) { // Continue execution of the tracee and therefore also release // locks on breakpoints(). This has to be done before trying a // mutable lock. @@ -54,7 +65,8 @@ pub fn break_syscall(pid: ContextId, sysemu: bool) { breakpoints_mut().insert(pid, Handle { tracee, tracer, - sysemu + sysemu, + singlestep }); } @@ -79,9 +91,26 @@ pub fn wait_breakpoint(pid: ContextId) -> Result<()> { Ok(()) } +/// Returns the same value as breakpoint_callback would do, but +/// doesn't actually perform the action. You should not rely too +/// heavily on this value, as the lock *is* released between this call +/// and another. +pub fn breakpoint_callback_dryrun(singlestep: bool) -> Option<bool> { + let contexts = context::contexts(); + let context = contexts.current()?; + let context = context.read(); + + let breakpoints = breakpoints(); + let breakpoint = breakpoints.get(&context.id)?; + if breakpoint.singlestep != singlestep { + return None; + } + Some(breakpoint.sysemu) +} + /// Notify the tracer and await green flag to continue. /// Note: Don't call while holding any locks, this will switch contexts -pub fn syscall_callback() -> Option<bool> { +pub fn breakpoint_callback(singlestep: bool) -> Option<bool> { // Can't hold any locks when executing wait() let (tracee, sysemu) = { let contexts = context::contexts(); @@ -90,6 +119,15 @@ pub fn syscall_callback() -> Option<bool> { let breakpoints = breakpoints(); let breakpoint = breakpoints.get(&context.id)?; + + // TODO: How should singlesteps interact with syscalls? How + // does Linux handle this? + + // if singlestep && !breakpoint.singlestep { + if breakpoint.singlestep != singlestep { + return None; + } + breakpoint.tracer.notify(); ( Arc::clone(&breakpoint.tracee), @@ -113,3 +151,51 @@ pub fn close(pid: ContextId) { breakpoints_mut().remove(&pid); } + +// ____ _ _ +// | _ \ ___ __ _(_)___| |_ ___ _ __ ___ +// | |_) / _ \/ _` | / __| __/ _ \ '__/ __| +// | _ < __/ (_| | \__ \ || __/ | \__ \ +// |_| \_\___|\__, |_|___/\__\___|_| |___/ +// |___/ + +/// Return the InterruptStack pointer, but relative to the specified +/// stack instead of the original. +pub unsafe fn rebase_regs_ptr( + regs: Option<(usize, Unique<InterruptStack>)>, + kstack: Option<&Box<[u8]>> +) -> Option<*const InterruptStack> { + let (old_base, ptr) = regs?; + let new_base = kstack?.as_ptr() as usize; + Some((ptr.as_ptr() as usize - old_base + new_base) as *const _) +} +/// Return the InterruptStack pointer, but relative to the specified +/// stack instead of the original. +pub unsafe fn rebase_regs_ptr_mut( + regs: Option<(usize, Unique<InterruptStack>)>, + kstack: Option<&mut Box<[u8]>> +) -> Option<*mut InterruptStack> { + let (old_base, ptr) = regs?; + let new_base = kstack?.as_mut_ptr() as usize; + Some((ptr.as_ptr() as usize - old_base + new_base) as *mut _) +} + +/// Return a reference to the InterruptStack struct in memory. If the +/// kernel stack has been backed up by a signal handler, this instead +/// returns the struct inside that memory, as that will later be +/// restored and otherwise undo all your changes. See `update(...)` in +/// context/switch.rs. +pub unsafe fn regs_for(context: &Context) -> Option<&InterruptStack> { + Some(&*match context.ksig { + Some((_, _, ref kstack)) => rebase_regs_ptr(context.regs, kstack.as_ref())?, + None => context.regs?.1.as_ptr() + }) +} + +/// Mutable version of `regs_for` +pub unsafe fn regs_for_mut(context: &mut Context) -> Option<&mut InterruptStack> { + Some(&mut *match context.ksig { + Some((_, _, ref mut kstack)) => rebase_regs_ptr_mut(context.regs, kstack.as_mut())?, + None => context.regs?.1.as_ptr() + }) +} diff --git a/src/scheme/proc.rs b/src/scheme/proc.rs index d7cb36fc2c039db2da6841f01484d5968dc85558..9cd7e6e13c8ea591db69145cda7d067e44e69755 100644 --- a/src/scheme/proc.rs +++ b/src/scheme/proc.rs @@ -156,7 +156,7 @@ impl Scheme for ProcScheme { let mut first = true; let (output, size) = loop { if !first { - // `continue` = Delay and repeat + // We've tried this before, so lets wait before retrying unsafe { context::switch(); } } first = false; @@ -171,7 +171,7 @@ impl Scheme for ProcScheme { // (Output { float: FloatRegisters::default() }, mem::size_of::<FloatRegisters>()) return Err(Error::new(EBADF)); }, - RegsKind::Int => match context.interrupt_stack { + RegsKind::Int => match unsafe { ptrace::regs_for(&context) } { None => { // Another CPU is running this process, wait until it's stopped. continue; @@ -179,9 +179,7 @@ impl Scheme for ProcScheme { Some(stack) => { let mut regs = IntRegisters::default(); - unsafe { - (&*stack.as_ptr()).save(&mut regs); - } + stack.save(&mut regs); (Output { int: regs }, mem::size_of::<IntRegisters>()) } @@ -217,20 +215,21 @@ impl Scheme for ProcScheme { }, Operation::Regs(kind) => loop { if !first { + // We've tried this before, so lets wait before retrying unsafe { context::switch(); } } first = false; let contexts = context::contexts(); let context = contexts.get(handle.pid).ok_or(Error::new(ESRCH))?; - let context = context.write(); + let mut context = context.write(); break match kind { RegsKind::Float => { // TODO!! unimplemented!(); }, - RegsKind::Int => match context.interrupt_stack { + RegsKind::Int => match unsafe { ptrace::regs_for_mut(&mut context) } { None => { // Another CPU is running this process, wait until it's stopped. continue; @@ -243,9 +242,8 @@ impl Scheme for ProcScheme { *(buf as *const _ as *const IntRegisters) }; - unsafe { - (&mut *stack.as_ptr()).load(®s); - } + stack.load(®s); + Ok(mem::size_of::<IntRegisters>()) } } @@ -259,19 +257,27 @@ impl Scheme for ProcScheme { let sysemu = op & PTRACE_SYSEMU == PTRACE_SYSEMU; let mut wait_breakpoint = false; + let mut singlestep = false; match op & PTRACE_OPERATIONMASK { PTRACE_CONT => { ptrace::cont(handle.pid); }, - // PTRACE_SINGLESTEP => unimplemented!(), - PTRACE_SYSCALL => { - ptrace::break_syscall(handle.pid, sysemu); + PTRACE_SYSCALL | PTRACE_SINGLESTEP => { // <- not a bitwise OR + singlestep = op & PTRACE_OPERATIONMASK == PTRACE_SINGLESTEP; + ptrace::set_breakpoint(handle.pid, sysemu, singlestep); wait_breakpoint = true; }, // PTRACE_WAIT => {}, _ => return Err(Error::new(EINVAL)) } - { + let mut first = true; + loop { + if !first { + // We've tried this before, so lets wait before retrying + unsafe { context::switch(); } + } + first = false; + let contexts = context::contexts(); let context = contexts.get(handle.pid).ok_or(Error::new(ESRCH))?; let mut context = context.write(); @@ -279,7 +285,15 @@ impl Scheme for ProcScheme { return Err(Error::new(ESRCH)); } + if singlestep { + match unsafe { ptrace::regs_for_mut(&mut context) } { + None => continue, + Some(stack) => stack.set_singlestep(true) + } + } + context.ptrace_stop = false; + break; } if wait_breakpoint && handle.flags & O_NONBLOCK != O_NONBLOCK { diff --git a/src/syscall/process.rs b/src/syscall/process.rs index 228be2628defbebd740413c167df746e41f31335..7d07c4191ad00baf1750211421a76451c0ab7fc7 100644 --- a/src/syscall/process.rs +++ b/src/syscall/process.rs @@ -6,7 +6,6 @@ use core::{intrinsics, mem}; use core::ops::DerefMut; use spin::Mutex; -use crate::arch::macros::InterruptStack; use crate::context::file::FileDescriptor; use crate::context::{ContextId, WaitpidKey}; use crate::context; @@ -130,18 +129,25 @@ pub fn clone(flags: usize, stack_base: usize) -> Result<ContextId> { } if let Some(ref stack) = context.kstack { + // Get the relative offset to the return address of this function + // (base pointer - start of stack) - one offset = stack_base - stack.as_ptr() as usize - mem::size_of::<usize>(); // Add clone ret let mut new_stack = stack.clone(); unsafe { - if let Some(old_ptr) = context.interrupt_stack { - let new_ptr = ( - old_ptr.as_ptr() as usize - stack.as_ptr() as usize + new_stack.as_ptr() as usize - ) as *mut InterruptStack; - - (&mut *new_ptr).scratch.rax = 0; + if let Some(regs) = ptrace::rebase_regs_ptr_mut(context.regs, Some(&mut new_stack)) { + // We'll need to tell the clone that it should + // return 0, but that's it. We don't actually + // clone the registers, because it will then + // become None and be exempt from all kinds of + // ptracing until the current syscall has + // completed. + (*regs).scratch.rax = 0; } + // Change the return address of the child + // (previously syscall) to the arch-specific + // clone_ret callback let func_ptr = new_stack.as_mut_ptr().offset(offset as isize); *(func_ptr as *mut usize) = interrupt::syscall::clone_ret as usize; }