guard: experiment with frame boundary callbacks

This commit is contained in:
Matthew LeVan 2024-02-02 21:26:28 -05:00
parent 3620c7b894
commit 07ee849175
4 changed files with 147 additions and 69 deletions

View File

@ -21,7 +21,7 @@ use assert_no_alloc::{assert_no_alloc, ensure_alloc_counters, permit_alloc};
use bitvec::prelude::{BitSlice, Lsb0}; use bitvec::prelude::{BitSlice, Lsb0};
use either::*; use either::*;
use std::convert::TryFrom; use std::convert::TryFrom;
use std::ffi::c_void; use std::ffi::{c_void, c_ulonglong};
use std::result; use std::result;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use std::sync::Arc; use std::sync::Arc;
@ -394,20 +394,61 @@ fn debug_assertions(stack: &mut NockStack, noun: Noun) {
use std::marker::PhantomData; use std::marker::PhantomData;
pub struct CCallback<'closure> { /// See: https://users.rust-lang.org/t/passing-a-closure-to-an-external-c-ffi-library/100271/2
pub function: unsafe extern "C" fn(*mut c_void) -> *mut c_void, 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<F>(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::<F>;
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<F>(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::<F>().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, pub user_data: *mut c_void,
// without this it's too easy to accidentally drop the closure too soon // without this it's too easy to accidentally drop the closure too soon
_lifetime: PhantomData<&'closure mut c_void>, _lifetime: PhantomData<&'closure mut c_void>,
} }
impl<'closure> CCallback<'closure> { impl<'closure> WorkCallback<'closure> {
pub fn new<F>(closure: &'closure mut F) -> Self pub fn new<F>(closure: &'closure mut F) -> Self
where 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::<F>; let function: unsafe extern "C" fn(*mut c_void, *mut c_void) -> *mut c_void = Self::call_closure::<F>;
debug_assert_eq!( debug_assert_eq!(
std::mem::size_of::<&'closure mut F>(), std::mem::size_of::<&'closure mut F>(),
@ -425,12 +466,12 @@ impl<'closure> CCallback<'closure> {
} }
} }
unsafe extern "C" fn call_closure<F>(user_data: *mut c_void) -> *mut c_void unsafe extern "C" fn call_closure<F>(user_data: *mut c_void, context_p: *mut c_void) -> *mut c_void
where where
F: FnMut() -> Result, F: FnMut(*mut c_void) -> Result,
{ {
let cb: &mut F = user_data.cast::<F>().as_mut().unwrap(); let cb: &mut F = user_data.cast::<F>().as_mut().unwrap();
let v = (*cb)(); let v = (*cb)(context_p);
permit_alloc(|| { permit_alloc(|| {
let v_box = Box::new(v); let v_box = Box::new(v);
let v_ptr = Box::into_raw(v_box); let v_ptr = Box::into_raw(v_box);
@ -439,22 +480,29 @@ impl<'closure> CCallback<'closure> {
} }
} }
pub fn call_with_guard<F: FnMut() -> Result>( pub fn call_with_guard<F: FnMut(*mut c_void) -> Result, G: FnMut(*mut c_void) -> *const c_ulonglong, H: FnMut(*mut c_void) -> *const c_ulonglong>(
f: &mut F, f: &mut F,
stack: *const *mut c_void, low_f: &mut G,
alloc: *const *mut c_void, high_f: &mut H,
context: &mut Context,
) -> Result { ) -> 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 mut ret: Result = Ok(D(0));
let ret_p = &mut ret as *mut _ as *mut c_void; let ret_p = &mut ret as *mut _ as *mut c_void;
let ret_pp = &ret_p as *const *mut c_void; let ret_pp = &ret_p as *const *mut c_void;
unsafe { unsafe {
let err = guard( let err = guard(
Some(c.function as unsafe extern "C" fn(*mut c_void) -> *mut c_void), Some(work.function as unsafe extern "C" fn(*mut c_void, *mut c_void) -> *mut c_void),
c.user_data as *mut c_void, work.user_data as *mut c_void,
stack, Some(low.function as unsafe extern "C" fn(*mut c_void, *mut c_void) -> *const c_ulonglong),
alloc, 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, ret_pp,
); );
@ -488,15 +536,12 @@ pub fn call_with_guard<F: FnMut() -> Result>(
/** Interpret nock */ /** Interpret nock */
pub fn interpret(context: &mut Context, mut subject: Noun, formula: Noun) -> Result { 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 terminator = Arc::clone(&TERMINATOR);
let orig_subject = subject; // for debugging let orig_subject = subject; // for debugging
let snapshot = context.save(); let snapshot = context.save();
let virtual_frame: *const u64 = context.stack.get_frame_pointer(); let virtual_frame: *const u64 = context.stack.get_frame_pointer();
let mut res: Noun = D(0); 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 // Setup stack for Nock computation
unsafe { 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) // (See https://docs.rs/assert_no_alloc/latest/assert_no_alloc/#advanced-use)
let nock = assert_no_alloc(|| { let nock = assert_no_alloc(|| {
ensure_alloc_counters(|| { 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)?; push_formula(&mut context.stack, formula, true)?;
loop { 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)
}) })
}); });

View File

@ -147,11 +147,21 @@ impl NockStack {
self.stack_pointer 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 */ /** Current alloc pointer of this NockStack */
pub fn get_alloc_pointer(&self) -> *const u64 { pub fn get_alloc_pointer(&self) -> *const u64 {
self.alloc_pointer 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 */ /** Start of the memory range for this NockStack */
pub fn get_start(&self) -> *const u64 { pub fn get_start(&self) -> *const u64 {
self.start 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 { unsafe fn prev_alloc_pointer_pointer(&self) -> *mut *mut u64 {
if !self.pc { if !self.pc {
self.slot_pointer(ALLOC) as *mut *mut u64 self.slot_pointer(ALLOC) as *mut *mut u64

View File

@ -11,23 +11,30 @@
#define GD_PAGESIZE (1ULL << GD_PAGEBITS) /* 16K */ #define GD_PAGESIZE (1ULL << GD_PAGEBITS) /* 16K */
static uint64_t *guard_p = 0; static uint64_t *guard_p = 0;
static uint64_t **stack = 0;
static uint64_t **alloc = 0;
static jmp_buf env_buffer; static jmp_buf env_buffer;
volatile sig_atomic_t err = guard_sound; volatile sig_atomic_t err = guard_sound;
static void (*prev_sigsegv_handler)(int, siginfo_t *, void *); 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. // Center the guard page.
static guard_err static guard_err
_focus_guard() _focus_guard()
{ {
uint64_t *stack_p = *stack; const uint64_t *low_p = low(bounds, context);
uint64_t *alloc_p = *alloc; 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. // Check for strange situations.
if (stack_p == 0 || alloc_p == 0) { if (low_p == 0 || high_p == 0) {
fprintf(stderr, "guard: stack or alloc pointer is null\r\n"); fprintf(stderr, "guard: low or high bound pointer is null\r\n");
return guard_weird; return guard_weird;
} }
@ -38,18 +45,9 @@ _focus_guard()
return guard_armor; return guard_armor;
} }
// Place the new guard page in the center. // Place the new guard page in the low-aligned center.
if (stack_p > alloc_p) { guard_p = (uint64_t *)low_p + ((high_p - low_p) / 2);
guard_p = stack_p - ((stack_p - alloc_p) / 2); guard_p = (uint64_t *)((uintptr_t)guard_p & ~(GD_PAGESIZE - 1));
}
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));
// Mark the new guard page. // Mark the new guard page.
if (guard_p != old_guard_p) { if (guard_p != old_guard_p) {
@ -60,8 +58,6 @@ _focus_guard()
return guard_spent; return guard_spent;
} }
fprintf(stderr, "guard: installed guard page at %p\r\n", (void *) guard_p);
return guard_sound; return guard_sound;
} }
@ -73,12 +69,12 @@ _signal_handler(int sig, siginfo_t *si, void *unused)
if (si->si_addr >= (void *)guard_p && if (si->si_addr >= (void *)guard_p &&
si->si_addr < (void *)guard_p + GD_PAGESIZE) 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(); err = _focus_guard();
break; break;
} }
else { 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) { if (NULL != prev_sigsegv_handler) {
prev_sigsegv_handler(sig, si, unused); prev_sigsegv_handler(sig, si, unused);
break; break;
@ -124,26 +120,34 @@ _register_handler()
guard_err guard_err
guard( guard(
void *(*f)(void *), void *(*work_f)(void *, void *),
void *user_data, void *work_data,
void *const *stack_pp, const uint64_t *(*low_f)(void *, void *),
void *const *alloc_pp, const uint64_t *(*high_f)(void *, void *),
void *bounds_data,
void *context_p,
void *const *ret void *const *ret
) )
{ {
stack = (uint64_t**) stack_pp; // Set globals.
alloc = (uint64_t**) alloc_pp; low = low_f;
high = high_f;
bounds= bounds_data;
context = context_p;
// fprintf(stderr, "guard: stack pointer at %p\r\n", (void *) *stack); const uint64_t *low_p = low_f(bounds_data, context_p);
// fprintf(stderr, "guard: alloc pointer at %p\r\n", (void *) *alloc); const uint64_t *high_p = high_f(bounds_data, context_p);
if (guard_p == 0) { // fprintf(stderr, "guard: low: %p high: %p\r\n", (void *)low_p, (void *)high_p);
guard_err install_err = _focus_guard();
if (install_err != guard_sound) { const unsigned long free_mb = (unsigned long)(high_p - low_p) / 1024 / 1024;
fprintf(stderr, "guard: failed to install guard page\r\n"); // fprintf(stderr, "guard: free space: %lu MB\r\n", free_mb);
err = install_err;
goto fail; 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) { if (_register_handler() != guard_sound) {
@ -153,7 +157,7 @@ guard(
void *result; void *result;
if (sigsetjmp(env_buffer, 1) == 0) { if (sigsetjmp(env_buffer, 1) == 0) {
result = f(user_data); result = work_f(work_data, context_p);
} }
else { else {
if (err != guard_sound) { if (err != guard_sound) {
@ -167,15 +171,15 @@ guard(
err = guard_armor; err = guard_armor;
goto fail; goto fail;
} }
// fprintf(stderr, "guard: sound; uninstalled guard page\r\n");
return guard_sound; return guard_sound;
fail: 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: failed to uninstall guard page\r\n");
} }
fprintf(stderr, "guard: fail; uninstalled guard page\r\n");
switch (err) { switch (err) {
case guard_armor: case guard_armor:
fprintf(stderr, "guard: armor error\r\n"); fprintf(stderr, "guard: armor error\r\n");

View File

@ -42,10 +42,12 @@ typedef enum {
*/ */
guard_err guard_err
guard( guard(
void *(*f)(void *), void *(*work_f)(void *, void *),
void *user_data, void *work_data,
void *const *stack_pp, const uint64_t *(*low_f)(void *, void *),
void *const *alloc_pp, const uint64_t *(*high_f)(void *, void *),
void *bounds_data,
void *context_p,
void *const *ret void *const *ret
); );