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 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<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,
// 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<F>(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::<F>;
let function: unsafe extern "C" fn(*mut c_void, *mut c_void) -> *mut c_void = Self::call_closure::<F>;
debug_assert_eq!(
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
F: FnMut() -> Result,
F: FnMut(*mut c_void) -> Result,
{
let cb: &mut F = user_data.cast::<F>().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<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,
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<F: FnMut() -> 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)
})
});

View File

@ -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

View File

@ -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");

View File

@ -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
);