diff --git a/rust/ares/src/interpreter.rs b/rust/ares/src/interpreter.rs index 1572f4e..e651985 100644 --- a/rust/ares/src/interpreter.rs +++ b/rust/ares/src/interpreter.rs @@ -21,7 +21,7 @@ use assert_no_alloc::{assert_no_alloc, ensure_alloc_counters, permit_alloc}; use bitvec::prelude::{BitSlice, Lsb0}; use either::*; use std::convert::TryFrom; -use std::ffi::c_void; +use std::ffi::{c_void, c_ulonglong}; use std::result; use std::sync::atomic::Ordering; use std::sync::Arc; @@ -394,20 +394,61 @@ fn debug_assertions(stack: &mut NockStack, noun: Noun) { use std::marker::PhantomData; -pub struct CCallback<'closure> { - pub function: unsafe extern "C" fn(*mut c_void) -> *mut c_void, +/// See: https://users.rust-lang.org/t/passing-a-closure-to-an-external-c-ffi-library/100271/2 +pub struct BoundsCallback<'closure> { + pub function: unsafe extern "C" fn(*mut c_void, *mut c_void) -> *const c_ulonglong, + pub bounds_data: *mut c_void, + + // without this it's too easy to accidentally drop the closure too soon + _lifetime: PhantomData<&'closure mut c_void>, +} + +impl<'closure> BoundsCallback<'closure> { + pub fn new(closure: &'closure mut F) -> Self + where + F: FnMut(*mut c_void) -> *const c_ulonglong, + { + let function: unsafe extern "C" fn(*mut c_void, *mut c_void) -> *const c_ulonglong = Self::call_closure::; + + debug_assert_eq!( + std::mem::size_of::<&'closure mut F>(), + std::mem::size_of::<*const c_void>() + ); + debug_assert_eq!( + std::mem::size_of_val(&function), + std::mem::size_of::<*const c_void>() + ); + + Self { + function, + bounds_data: closure as *mut F as *mut c_void, + _lifetime: PhantomData, + } + } + + unsafe extern "C" fn call_closure(bounds_data: *mut c_void, context_p: *mut c_void) -> *const c_ulonglong + where + F: FnMut(*mut c_void) -> *const c_ulonglong, + { + let cb: &mut F = bounds_data.cast::().as_mut().unwrap(); + (*cb)(context_p) + } +} + +pub struct WorkCallback<'closure> { + pub function: unsafe extern "C" fn(*mut c_void, *mut c_void) -> *mut c_void, pub user_data: *mut c_void, // without this it's too easy to accidentally drop the closure too soon _lifetime: PhantomData<&'closure mut c_void>, } -impl<'closure> CCallback<'closure> { +impl<'closure> WorkCallback<'closure> { pub fn new(closure: &'closure mut F) -> Self where - F: FnMut() -> Result, + F: FnMut(*mut c_void) -> Result, { - let function: unsafe extern "C" fn(*mut c_void) -> *mut c_void = Self::call_closure::; + let function: unsafe extern "C" fn(*mut c_void, *mut c_void) -> *mut c_void = Self::call_closure::; debug_assert_eq!( std::mem::size_of::<&'closure mut F>(), @@ -425,12 +466,12 @@ impl<'closure> CCallback<'closure> { } } - unsafe extern "C" fn call_closure(user_data: *mut c_void) -> *mut c_void + unsafe extern "C" fn call_closure(user_data: *mut c_void, context_p: *mut c_void) -> *mut c_void where - F: FnMut() -> Result, + F: FnMut(*mut c_void) -> Result, { let cb: &mut F = user_data.cast::().as_mut().unwrap(); - let v = (*cb)(); + let v = (*cb)(context_p); permit_alloc(|| { let v_box = Box::new(v); let v_ptr = Box::into_raw(v_box); @@ -439,22 +480,29 @@ impl<'closure> CCallback<'closure> { } } -pub fn call_with_guard Result>( +pub fn call_with_guard Result, G: FnMut(*mut c_void) -> *const c_ulonglong, H: FnMut(*mut c_void) -> *const c_ulonglong>( f: &mut F, - stack: *const *mut c_void, - alloc: *const *mut c_void, + low_f: &mut G, + high_f: &mut H, + context: &mut Context, ) -> Result { - let c = CCallback::new(f); + let work = WorkCallback::new(f); + let low = BoundsCallback::new(low_f); + let high = BoundsCallback::new(high_f); + let context_p = context as *mut Context as *mut c_void; + let mut ret: Result = Ok(D(0)); let ret_p = &mut ret as *mut _ as *mut c_void; let ret_pp = &ret_p as *const *mut c_void; unsafe { let err = guard( - Some(c.function as unsafe extern "C" fn(*mut c_void) -> *mut c_void), - c.user_data as *mut c_void, - stack, - alloc, + Some(work.function as unsafe extern "C" fn(*mut c_void, *mut c_void) -> *mut c_void), + work.user_data as *mut c_void, + Some(low.function as unsafe extern "C" fn(*mut c_void, *mut c_void) -> *const c_ulonglong), + Some(high.function as unsafe extern "C" fn(*mut c_void, *mut c_void) -> *const c_ulonglong), + high.bounds_data as *mut c_void, + context_p, ret_pp, ); @@ -488,15 +536,12 @@ pub fn call_with_guard Result>( /** Interpret nock */ pub fn interpret(context: &mut Context, mut subject: Noun, formula: Noun) -> Result { + // print the addresses of the context.stack.stack_pointer and context.stack.alloc_pointer let terminator = Arc::clone(&TERMINATOR); let orig_subject = subject; // for debugging let snapshot = context.save(); let virtual_frame: *const u64 = context.stack.get_frame_pointer(); let mut res: Noun = D(0); - let stack_p = context.stack.get_stack_pointer() as *mut c_void; - let alloc_p = context.stack.get_alloc_pointer() as *mut c_void; - let stack_pp = &stack_p as *const *mut c_void; - let alloc_pp = &alloc_p as *const *mut c_void; // Setup stack for Nock computation unsafe { @@ -523,7 +568,24 @@ pub fn interpret(context: &mut Context, mut subject: Noun, formula: Noun) -> Res // (See https://docs.rs/assert_no_alloc/latest/assert_no_alloc/#advanced-use) let nock = assert_no_alloc(|| { ensure_alloc_counters(|| { - let work_closure = &mut || unsafe { + let low_f = &mut |context_p: *mut c_void| { + let context = unsafe { &mut *(context_p as *mut Context) }; + if context.stack.is_west() { + context.stack.get_stack_pointer() as *const c_ulonglong + } else { + context.stack.get_alloc_pointer() as *const c_ulonglong + } + }; + let high_f = &mut |context_p: *mut c_void| { + let context = unsafe { &mut *(context_p as *mut Context) }; + if context.stack.is_west() { + context.stack.get_alloc_pointer() as *const c_ulonglong + } else { + context.stack.get_stack_pointer() as *const c_ulonglong + } + }; + let work_closure = &mut |context_p: *mut c_void| unsafe { + let context = &mut *(context_p as *mut Context); push_formula(&mut context.stack, formula, true)?; loop { @@ -1072,7 +1134,7 @@ pub fn interpret(context: &mut Context, mut subject: Noun, formula: Noun) -> Res }; } }; - call_with_guard(work_closure, stack_pp, alloc_pp) + call_with_guard(work_closure, low_f, high_f, context) }) }); diff --git a/rust/ares/src/mem.rs b/rust/ares/src/mem.rs index 31f81c5..58838b1 100644 --- a/rust/ares/src/mem.rs +++ b/rust/ares/src/mem.rs @@ -147,11 +147,21 @@ impl NockStack { self.stack_pointer } + /** Current stack pointer's address */ + pub fn get_stack_pointer_pointer(&self) -> *const *mut u64 { + &self.stack_pointer as *const *mut u64 + } + /** Current alloc pointer of this NockStack */ pub fn get_alloc_pointer(&self) -> *const u64 { self.alloc_pointer } + /** Current alloc pointer's address */ + pub fn get_alloc_pointer_pointer(&self) -> *const *mut u64 { + &self.alloc_pointer as *const *mut u64 + } + /** Start of the memory range for this NockStack */ pub fn get_start(&self) -> *const u64 { self.start @@ -255,7 +265,7 @@ impl NockStack { } } - /** Pointer to where the previous stack pointer is saved in a frame */ + /** Pointer to where the previous alloc pointer is saved in a frame */ unsafe fn prev_alloc_pointer_pointer(&self) -> *mut *mut u64 { if !self.pc { self.slot_pointer(ALLOC) as *mut *mut u64 diff --git a/rust/ares_guard/c-src/guard.c b/rust/ares_guard/c-src/guard.c index 15f0c31..c10d1d0 100644 --- a/rust/ares_guard/c-src/guard.c +++ b/rust/ares_guard/c-src/guard.c @@ -11,23 +11,30 @@ #define GD_PAGESIZE (1ULL << GD_PAGEBITS) /* 16K */ static uint64_t *guard_p = 0; -static uint64_t **stack = 0; -static uint64_t **alloc = 0; static jmp_buf env_buffer; volatile sig_atomic_t err = guard_sound; static void (*prev_sigsegv_handler)(int, siginfo_t *, void *); +static const uint64_t *(*low)(void *, void *) = 0; +static const uint64_t *(*high)(void *, void *) = 0; +static void *bounds = 0; +static void *context = 0; // Center the guard page. static guard_err _focus_guard() { - uint64_t *stack_p = *stack; - uint64_t *alloc_p = *alloc; + const uint64_t *low_p = low(bounds, context); + const uint64_t *high_p = high(bounds, context); + + // Check if we're spent already. + if (low_p == high_p || low_p > high_p) { + return guard_spent; + } // Check for strange situations. - if (stack_p == 0 || alloc_p == 0) { - fprintf(stderr, "guard: stack or alloc pointer is null\r\n"); + if (low_p == 0 || high_p == 0) { + fprintf(stderr, "guard: low or high bound pointer is null\r\n"); return guard_weird; } @@ -38,18 +45,9 @@ _focus_guard() return guard_armor; } - // Place the new guard page in the center. - if (stack_p > alloc_p) { - guard_p = stack_p - ((stack_p - alloc_p) / 2); - } - else if (stack_p < alloc_p) { - guard_p = stack_p + ((alloc_p - stack_p) / 2); - } - else { - fprintf(stderr, "guard: weird; stack and alloc pointers are equal\r\n"); - return guard_weird; - } - guard_p = (void *)((uintptr_t)guard_p & ~(GD_PAGESIZE - 1)); + // Place the new guard page in the low-aligned center. + guard_p = (uint64_t *)low_p + ((high_p - low_p) / 2); + guard_p = (uint64_t *)((uintptr_t)guard_p & ~(GD_PAGESIZE - 1)); // Mark the new guard page. if (guard_p != old_guard_p) { @@ -60,8 +58,6 @@ _focus_guard() return guard_spent; } - fprintf(stderr, "guard: installed guard page at %p\r\n", (void *) guard_p); - return guard_sound; } @@ -73,12 +69,12 @@ _signal_handler(int sig, siginfo_t *si, void *unused) if (si->si_addr >= (void *)guard_p && si->si_addr < (void *)guard_p + GD_PAGESIZE) { - fprintf(stderr, "guard: fault in guard\r\n"); + fprintf(stderr, "guard: fault in guard: %p\r\n", si->si_addr); err = _focus_guard(); break; } else { - fprintf(stderr, "guard: fault outside guard\r\n"); + fprintf(stderr, "guard: fault outside guard %p\r\n", si->si_addr); if (NULL != prev_sigsegv_handler) { prev_sigsegv_handler(sig, si, unused); break; @@ -124,26 +120,34 @@ _register_handler() guard_err guard( - void *(*f)(void *), - void *user_data, - void *const *stack_pp, - void *const *alloc_pp, + void *(*work_f)(void *, void *), + void *work_data, + const uint64_t *(*low_f)(void *, void *), + const uint64_t *(*high_f)(void *, void *), + void *bounds_data, + void *context_p, void *const *ret ) { - stack = (uint64_t**) stack_pp; - alloc = (uint64_t**) alloc_pp; + // Set globals. + low = low_f; + high = high_f; + bounds= bounds_data; + context = context_p; - // fprintf(stderr, "guard: stack pointer at %p\r\n", (void *) *stack); - // fprintf(stderr, "guard: alloc pointer at %p\r\n", (void *) *alloc); + const uint64_t *low_p = low_f(bounds_data, context_p); + const uint64_t *high_p = high_f(bounds_data, context_p); - if (guard_p == 0) { - guard_err install_err = _focus_guard(); - if (install_err != guard_sound) { - fprintf(stderr, "guard: failed to install guard page\r\n"); - err = install_err; - goto fail; - } + // fprintf(stderr, "guard: low: %p high: %p\r\n", (void *)low_p, (void *)high_p); + + const unsigned long free_mb = (unsigned long)(high_p - low_p) / 1024 / 1024; + // fprintf(stderr, "guard: free space: %lu MB\r\n", free_mb); + + guard_err focus_err = _focus_guard(); + if (focus_err != guard_sound && focus_err != guard_spent) { + fprintf(stderr, "guard: failed to install guard page\r\n"); + err = focus_err; + goto fail; } if (_register_handler() != guard_sound) { @@ -153,7 +157,7 @@ guard( void *result; if (sigsetjmp(env_buffer, 1) == 0) { - result = f(user_data); + result = work_f(work_data, context_p); } else { if (err != guard_sound) { @@ -167,15 +171,15 @@ guard( err = guard_armor; goto fail; } - // fprintf(stderr, "guard: sound; uninstalled guard page\r\n"); return guard_sound; fail: - if (mprotect(guard_p, GD_PAGESIZE, PROT_READ | PROT_WRITE) == -1) { + if (guard_p != NULL && + mprotect(guard_p, GD_PAGESIZE, PROT_READ | PROT_WRITE) == -1) + { fprintf(stderr, "guard: failed to uninstall guard page\r\n"); } - fprintf(stderr, "guard: fail; uninstalled guard page\r\n"); switch (err) { case guard_armor: fprintf(stderr, "guard: armor error\r\n"); diff --git a/rust/ares_guard/c-src/guard.h b/rust/ares_guard/c-src/guard.h index f91b188..7354701 100644 --- a/rust/ares_guard/c-src/guard.h +++ b/rust/ares_guard/c-src/guard.h @@ -42,10 +42,12 @@ typedef enum { */ guard_err guard( - void *(*f)(void *), - void *user_data, - void *const *stack_pp, - void *const *alloc_pp, + void *(*work_f)(void *, void *), + void *work_data, + const uint64_t *(*low_f)(void *, void *), + const uint64_t *(*high_f)(void *, void *), + void *bounds_data, + void *context_p, void *const *ret );