mirror of
https://github.com/urbit/ares.git
synced 2024-12-23 13:25:03 +03:00
Merge pull request #202 from urbit/msl/guard
guard page and `bail:meme`
This commit is contained in:
commit
7316368e29
10
rust/ares/Cargo.lock
generated
10
rust/ares/Cargo.lock
generated
@ -62,7 +62,7 @@ dependencies = [
|
||||
"ares_guard",
|
||||
"ares_macros",
|
||||
"ares_pma",
|
||||
"assert_no_alloc 1.1.2",
|
||||
"assert_no_alloc",
|
||||
"autotools",
|
||||
"bitvec",
|
||||
"cc",
|
||||
@ -87,7 +87,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"aes-siv",
|
||||
"assert_no_alloc 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"assert_no_alloc",
|
||||
"curve25519-dalek",
|
||||
"ed25519-dalek",
|
||||
"ibig 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@ -124,12 +124,6 @@ dependencies = [
|
||||
name = "assert_no_alloc"
|
||||
version = "1.1.2"
|
||||
|
||||
[[package]]
|
||||
name = "assert_no_alloc"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55ca83137a482d61d916ceb1eba52a684f98004f18e0cafea230fe5579c178a3"
|
||||
|
||||
[[package]]
|
||||
name = "atty"
|
||||
version = "0.2.14"
|
||||
|
@ -18,8 +18,8 @@ ares_macros = { path = "../ares_macros" }
|
||||
# ares_pma = { path = "../ares_pma", features=["debug_prints"] }
|
||||
ares_pma = { path = "../ares_pma" }
|
||||
# use this when debugging requires allocation (e.g. eprintln)
|
||||
# assert_no_alloc = { path = "../assert-no-alloc", features=["warn_debug"] }
|
||||
assert_no_alloc = { path = "../assert-no-alloc" }
|
||||
# assert_no_alloc = { path = "../rust-assert-no-alloc", features=["warn_debug"] }
|
||||
assert_no_alloc = { path = "../rust-assert-no-alloc" }
|
||||
bitvec = "1.0.0"
|
||||
criterion = "0.4"
|
||||
either = "1.9.0"
|
||||
|
@ -1,6 +1,7 @@
|
||||
use crate::assert_acyclic;
|
||||
use crate::assert_no_forwarding_pointers;
|
||||
use crate::assert_no_junior_pointers;
|
||||
use crate::guard::call_with_guard;
|
||||
use crate::hamt::Hamt;
|
||||
use crate::jets::cold;
|
||||
use crate::jets::cold::Cold;
|
||||
@ -16,9 +17,9 @@ use crate::serf::TERMINATOR;
|
||||
use crate::trace::{write_nock_trace, TraceInfo, TraceStack};
|
||||
use crate::unifying_equality::unifying_equality;
|
||||
use ares_macros::tas;
|
||||
use assert_no_alloc::assert_no_alloc;
|
||||
use assert_no_alloc::{assert_no_alloc, ensure_alloc_counters};
|
||||
use bitvec::prelude::{BitSlice, Lsb0};
|
||||
use either::Either::*;
|
||||
use either::*;
|
||||
use std::result;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
@ -400,7 +401,11 @@ 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(|| unsafe {
|
||||
let nock = assert_no_alloc(|| {
|
||||
ensure_alloc_counters(|| {
|
||||
let stack_pp = context.stack.get_stack_pointer_pointer() as *const *const u64;
|
||||
let alloc_pp = context.stack.get_alloc_pointer_pointer() as *const *const u64;
|
||||
let work_f = &mut || unsafe {
|
||||
push_formula(&mut context.stack, formula, true)?;
|
||||
|
||||
loop {
|
||||
@ -687,7 +692,9 @@ pub fn interpret(context: &mut Context, mut subject: Noun, formula: Noun) -> Res
|
||||
// '.*', so we can assume it's never directly used to invoke
|
||||
// jetted code.
|
||||
if context.trace_info.is_some() {
|
||||
if let Some(path) = context.cold.matches(stack, &mut res) {
|
||||
if let Some(path) =
|
||||
context.cold.matches(stack, &mut res)
|
||||
{
|
||||
append_trace(stack, path);
|
||||
};
|
||||
};
|
||||
@ -712,7 +719,9 @@ pub fn interpret(context: &mut Context, mut subject: Noun, formula: Noun) -> Res
|
||||
// '.*', so we can assume it's never directly used to invoke
|
||||
// jetted code.
|
||||
if context.trace_info.is_some() {
|
||||
if let Some(path) = context.cold.matches(stack, &mut res) {
|
||||
if let Some(path) =
|
||||
context.cold.matches(stack, &mut res)
|
||||
{
|
||||
append_trace(stack, path);
|
||||
};
|
||||
};
|
||||
@ -748,16 +757,21 @@ pub fn interpret(context: &mut Context, mut subject: Noun, formula: Noun) -> Res
|
||||
push_formula(&mut context.stack, diet.patch, false)?;
|
||||
}
|
||||
Todo10::Edit => {
|
||||
res = edit(&mut context.stack, diet.axis.as_bitslice(), res, diet.tree);
|
||||
res = edit(
|
||||
&mut context.stack,
|
||||
diet.axis.as_bitslice(),
|
||||
res,
|
||||
diet.tree,
|
||||
);
|
||||
context.stack.pop::<NockWork>();
|
||||
}
|
||||
}
|
||||
}
|
||||
NockWork::Work11D(mut dint) => match dint.todo {
|
||||
Todo11D::ComputeHint => {
|
||||
if let Some(ret) =
|
||||
hint::match_pre_hint(context, subject, dint.tag, dint.hint, dint.body)
|
||||
{
|
||||
if let Some(ret) = hint::match_pre_hint(
|
||||
context, subject, dint.tag, dint.hint, dint.body,
|
||||
) {
|
||||
match ret {
|
||||
Ok(found) => {
|
||||
res = found;
|
||||
@ -817,9 +831,9 @@ pub fn interpret(context: &mut Context, mut subject: Noun, formula: Noun) -> Res
|
||||
},
|
||||
NockWork::Work11S(mut sint) => match sint.todo {
|
||||
Todo11S::ComputeResult => {
|
||||
if let Some(ret) =
|
||||
hint::match_pre_nock(context, subject, sint.tag, None, sint.body)
|
||||
{
|
||||
if let Some(ret) = hint::match_pre_nock(
|
||||
context, subject, sint.tag, None, sint.body,
|
||||
) {
|
||||
match ret {
|
||||
Ok(found) => {
|
||||
res = found;
|
||||
@ -840,9 +854,9 @@ pub fn interpret(context: &mut Context, mut subject: Noun, formula: Noun) -> Res
|
||||
}
|
||||
}
|
||||
Todo11S::Done => {
|
||||
if let Some(found) =
|
||||
hint::match_post_nock(context, subject, sint.tag, None, sint.body, res)
|
||||
{
|
||||
if let Some(found) = hint::match_post_nock(
|
||||
context, subject, sint.tag, None, sint.body, res,
|
||||
) {
|
||||
res = found;
|
||||
}
|
||||
context.stack.pop::<NockWork>();
|
||||
@ -877,7 +891,8 @@ pub fn interpret(context: &mut Context, mut subject: Noun, formula: Noun) -> Res
|
||||
scry_gate.tail().as_cell()?.tail(),
|
||||
],
|
||||
);
|
||||
let scry_form = T(&mut context.stack, &[D(9), D(2), D(1), scry_core]);
|
||||
let scry_form =
|
||||
T(&mut context.stack, &[D(9), D(2), D(1), scry_core]);
|
||||
|
||||
context.scry_stack = cell.tail();
|
||||
// Alternately, we could use scry_core as the subject and [9 2 0 1] as
|
||||
@ -892,11 +907,18 @@ pub fn interpret(context: &mut Context, mut subject: Noun, formula: Noun) -> Res
|
||||
break Err(Error::ScryCrashed(D(0)));
|
||||
}
|
||||
}
|
||||
Right(cell) => match cell.tail().as_either_atom_cell() {
|
||||
Right(cell) => {
|
||||
match cell.tail().as_either_atom_cell() {
|
||||
Left(_) => {
|
||||
let stack = &mut context.stack;
|
||||
let hunk =
|
||||
T(stack, &[D(tas!(b"hunk")), scry.reff, scry.path]);
|
||||
let hunk = T(
|
||||
stack,
|
||||
&[
|
||||
D(tas!(b"hunk")),
|
||||
scry.reff,
|
||||
scry.path,
|
||||
],
|
||||
);
|
||||
mean_push(stack, hunk);
|
||||
break Err(Error::ScryCrashed(D(0)));
|
||||
}
|
||||
@ -905,10 +927,12 @@ pub fn interpret(context: &mut Context, mut subject: Noun, formula: Noun) -> Res
|
||||
context.scry_stack = scry_stack;
|
||||
context.stack.pop::<NockWork>();
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(error) => match error {
|
||||
Error::Deterministic(_, trace) | Error::ScryCrashed(trace) => {
|
||||
Error::Deterministic(_, trace)
|
||||
| Error::ScryCrashed(trace) => {
|
||||
break Err(Error::ScryCrashed(trace));
|
||||
}
|
||||
Error::NonDeterministic(_, _) => {
|
||||
@ -927,6 +951,10 @@ pub fn interpret(context: &mut Context, mut subject: Noun, formula: Noun) -> Res
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
call_with_guard(stack_pp, alloc_pp, work_f)
|
||||
})
|
||||
});
|
||||
|
||||
match nock {
|
||||
@ -1163,6 +1191,7 @@ fn exit(
|
||||
Error::Deterministic(_, t) | Error::NonDeterministic(_, t) | Error::ScryCrashed(t) => {
|
||||
// Return $tang of traces
|
||||
let h = *(stack.local_noun_pointer(0));
|
||||
// XX: Small chance of clobbering something important after OOM?
|
||||
T(stack, &[h, t])
|
||||
}
|
||||
};
|
||||
|
@ -1,4 +1,3 @@
|
||||
use crate::guard::call_with_guard;
|
||||
use crate::hamt::Hamt;
|
||||
use crate::interpreter;
|
||||
use crate::interpreter::{inc, interpret, Error, Mote};
|
||||
@ -403,27 +402,7 @@ fn slam(context: &mut Context, axis: u64, ovo: Noun) -> Result<Noun, Error> {
|
||||
let fol = T(stack, &[D(8), pul, D(9), D(2), D(10), sam, D(0), D(2)]);
|
||||
let sub = T(stack, &[arvo, ovo]);
|
||||
|
||||
let frame_p = stack.get_frame_pointer();
|
||||
let stack_pp = stack.get_stack_pointer_pointer();
|
||||
let alloc_pp = stack.get_alloc_pointer_pointer();
|
||||
|
||||
let res = call_with_guard(
|
||||
stack_pp as *const *const u64,
|
||||
alloc_pp as *const *const u64,
|
||||
&mut || interpret(&mut context.nock_context, sub, fol),
|
||||
);
|
||||
|
||||
if let Err(Error::NonDeterministic(Mote::Meme, _)) = res {
|
||||
unsafe {
|
||||
let stack = &mut context.nock_context.stack;
|
||||
assert_no_alloc::reset_counters();
|
||||
while stack.get_frame_pointer() != frame_p {
|
||||
stack.frame_pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res
|
||||
interpret(&mut context.nock_context, sub, fol)
|
||||
}
|
||||
|
||||
fn peek(context: &mut Context, ovo: Noun) -> Noun {
|
||||
|
@ -7,8 +7,8 @@ edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
# use this when debugging requires allocation (e.g. eprintln)
|
||||
# assert_no_alloc = {version="1.1.2", features=["warn_debug"]}
|
||||
assert_no_alloc = "1.1.2"
|
||||
# assert_no_alloc = { path = "../rust-assert-no-alloc", features=["warn_debug"] }
|
||||
assert_no_alloc = { path = "../rust-assert-no-alloc" }
|
||||
ibig = "0.3.6"
|
||||
|
||||
# ed25519
|
||||
|
@ -22,54 +22,56 @@
|
||||
#endif
|
||||
|
||||
/**
|
||||
* XX: documentation
|
||||
* Linked list stack of jump buffers.
|
||||
*/
|
||||
typedef struct _GD_state GD_state;
|
||||
struct _GD_state {
|
||||
typedef struct GD_buflistnode GD_buflistnode;
|
||||
struct GD_buflistnode {
|
||||
jmp_buf buffer;
|
||||
GD_buflistnode *next;
|
||||
};
|
||||
|
||||
/**
|
||||
* Global guard page state.
|
||||
*/
|
||||
typedef struct GD_state GD_state;
|
||||
struct GD_state {
|
||||
uintptr_t guard_p;
|
||||
const uintptr_t *stack_pp;
|
||||
const uintptr_t *alloc_pp;
|
||||
jmp_buf env_buffer;
|
||||
GD_buflistnode *buffer_list;
|
||||
struct sigaction prev_sa;
|
||||
};
|
||||
|
||||
static GD_state *_guard_state = NULL;
|
||||
|
||||
static GD_state _gd_state = {
|
||||
.guard_p = 0,
|
||||
.stack_pp = NULL,
|
||||
.alloc_pp = NULL,
|
||||
.buffer_list = NULL,
|
||||
.prev_sa = { .sa_sigaction = NULL, .sa_flags = 0 },
|
||||
};
|
||||
|
||||
static uint32_t
|
||||
_prot_page(void *address, int prot)
|
||||
_protect_page(void *address, int prot)
|
||||
{
|
||||
if (mprotect(address, GD_PAGE_SIZE, prot)) {
|
||||
fprintf(stderr, "guard: prot: mprotect error %d\r\n", errno);
|
||||
fprintf(stderr, "%s\r\n", strerror(errno));
|
||||
return guard_mprotect | errno;
|
||||
return guard_mprotect ;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Center the guard page.
|
||||
static uint32_t
|
||||
_mark_page(void *address)
|
||||
_focus_guard(GD_state *gd)
|
||||
{
|
||||
return _prot_page(address, PROT_NONE);
|
||||
}
|
||||
uintptr_t stack_p = *(gd->stack_pp);
|
||||
uintptr_t alloc_p = *(gd->alloc_pp);
|
||||
uintptr_t old_guard_p = (gd->guard_p);
|
||||
uintptr_t new_guard_p;
|
||||
uint32_t err = 0;
|
||||
|
||||
static uint32_t
|
||||
_unmark_page(void *address)
|
||||
{
|
||||
return _prot_page(address, PROT_READ | PROT_WRITE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Center the guard page.
|
||||
*/
|
||||
static uint32_t
|
||||
_focus_guard(GD_state *gs) {
|
||||
uintptr_t *guard_pp = &(gs->guard_p);
|
||||
uintptr_t stack_p = *(gs->stack_pp);
|
||||
uintptr_t alloc_p = *(gs->alloc_pp);
|
||||
|
||||
// Check anomalous arguments
|
||||
if (stack_p == 0 || alloc_p == 0) {
|
||||
fprintf(stderr, "guard: focus: stack or alloc pointer is null\r\n");
|
||||
return guard_null;
|
||||
@ -77,30 +79,25 @@ _focus_guard(GD_state *gs) {
|
||||
return guard_oom;
|
||||
}
|
||||
|
||||
uintptr_t old_guard_p = *guard_pp;
|
||||
uintptr_t new_guard_p;
|
||||
int32_t err = 0;
|
||||
|
||||
// Compute new guard page
|
||||
// XX: Should we also check for new_guard_p < min(stack_p, alloc_p)?
|
||||
// Compute new guard page.
|
||||
new_guard_p = GD_PAGE_ROUND_DOWN((stack_p + alloc_p) / 2);
|
||||
if (new_guard_p == old_guard_p) {
|
||||
return guard_oom;
|
||||
}
|
||||
|
||||
// Mark new guard page
|
||||
if ((err = _mark_page((void *)new_guard_p))) {
|
||||
fprintf(stderr, "guard: focus: mark error %p\r\n", (void *)new_guard_p);
|
||||
// Mark new guard page.
|
||||
if ((err = _protect_page((void *)new_guard_p, PROT_NONE))) {
|
||||
fprintf(stderr, "guard: focus: mark error\r\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
// Update guard page tracker
|
||||
*guard_pp = new_guard_p;
|
||||
// Update guard page tracker.
|
||||
gd->guard_p = new_guard_p;
|
||||
|
||||
// Unmark the old guard page (if there is one)
|
||||
// Unmark the old guard page if there is one.
|
||||
if (old_guard_p) {
|
||||
if ((err = _unmark_page((void *)old_guard_p))) {
|
||||
fprintf(stderr, "guard: focus: unmark error, %p\r\n", (void *)old_guard_p);
|
||||
if ((err = _protect_page((void *)old_guard_p, PROT_READ | PROT_WRITE))) {
|
||||
fprintf(stderr, "guard: focus: unmark error\r\n");
|
||||
return err;
|
||||
}
|
||||
}
|
||||
@ -108,58 +105,55 @@ _focus_guard(GD_state *gs) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
static void
|
||||
_signal_handler(int sig, siginfo_t *si, void *unused)
|
||||
{
|
||||
uintptr_t sig_addr;
|
||||
int32_t err = 0;
|
||||
uint32_t err = 0;
|
||||
|
||||
assert(sig == GD_SIGNAL);
|
||||
assert(_gd_state.guard_p);
|
||||
if (sig != GD_SIGNAL) {
|
||||
fprintf(stderr, "guard: handler: invalid signal: %d\r\n", sig);
|
||||
assert(0);
|
||||
}
|
||||
|
||||
sig_addr = (uintptr_t)si->si_addr;
|
||||
if (
|
||||
sig_addr >= _guard_state->guard_p &&
|
||||
sig_addr < (_guard_state->guard_p + GD_PAGE_SIZE))
|
||||
|
||||
if (sig_addr >= _gd_state.guard_p &&
|
||||
sig_addr < _gd_state.guard_p + GD_PAGE_SIZE)
|
||||
{
|
||||
err = _focus_guard(_guard_state);
|
||||
err = _focus_guard(&_gd_state);
|
||||
if (err) {
|
||||
siglongjmp(_guard_state->env_buffer, err);
|
||||
siglongjmp(_gd_state.buffer_list->buffer, err);
|
||||
}
|
||||
} else {
|
||||
struct sigaction prev_sa = _guard_state->prev_sa;
|
||||
}
|
||||
else {
|
||||
struct sigaction prev_sa = _gd_state.prev_sa;
|
||||
|
||||
if (prev_sa.sa_sigaction != NULL) {
|
||||
prev_sa.sa_sigaction(sig, si, unused);
|
||||
} else if (prev_sa.sa_handler != NULL) {
|
||||
prev_sa.sa_handler(sig);
|
||||
} else {
|
||||
// There should always be a default SIGSEGV handler
|
||||
// There should always be a default handler
|
||||
assert(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t
|
||||
_register_handler(GD_state *gs)
|
||||
// Registers the handler function.
|
||||
static uint32_t
|
||||
_register_handler(GD_state *gd)
|
||||
{
|
||||
struct sigaction sa;
|
||||
|
||||
// Flag to use sa_sigaction
|
||||
sa.sa_flags = SA_SIGINFO;
|
||||
// Must use sa_sigaction; sa-handler takes signal handler as its only argument
|
||||
sa.sa_sigaction = _signal_handler;
|
||||
// Set mask of signals to ignore while running signal handler
|
||||
// XX: By default the signal that triggered the signal handler is automatically added to the
|
||||
// mask while it's being handled, so unless we plan to add more signals to this then I don't
|
||||
// think it's necessary.
|
||||
// sigemptyset(&sa.sa_mask);
|
||||
// sigaddset(&(sa.sa_mask), SIGSEGV);
|
||||
|
||||
// Set the new SIGSEGV handler, and save the old SIGSEGV handler (if any)
|
||||
if (sigaction(GD_SIGNAL, &sa, &(gs->prev_sa))) {
|
||||
if (sigaction(GD_SIGNAL, &sa, &(gd->prev_sa))) {
|
||||
fprintf(stderr, "guard: register: sigaction error\r\n");
|
||||
fprintf(stderr, "%s\r\n", strerror(errno));
|
||||
return guard_sigaction | errno;
|
||||
return guard_sigaction;
|
||||
}
|
||||
|
||||
return 0;
|
||||
@ -167,66 +161,83 @@ _register_handler(GD_state *gs)
|
||||
|
||||
uint32_t
|
||||
guard(
|
||||
callback f,
|
||||
void *(*f)(void *),
|
||||
void *closure,
|
||||
const uintptr_t *const stack_pp,
|
||||
const uintptr_t *const alloc_pp,
|
||||
const uintptr_t *const s_pp,
|
||||
const uintptr_t *const a_pp,
|
||||
void **ret
|
||||
) {
|
||||
int32_t err = 0;
|
||||
int32_t err_c = 0;
|
||||
GD_buflistnode *new_buffer;
|
||||
uint32_t err = 0;
|
||||
uint32_t td_err = 0;
|
||||
|
||||
//
|
||||
// Setup guard page state
|
||||
//
|
||||
if (_gd_state.guard_p == 0) {
|
||||
assert(_gd_state.buffer_list == NULL);
|
||||
|
||||
// guard() presumes that it is only ever called once at a time
|
||||
assert(_guard_state == NULL);
|
||||
_gd_state.stack_pp = s_pp;
|
||||
_gd_state.alloc_pp = a_pp;
|
||||
|
||||
_guard_state = (GD_state *)malloc(sizeof(GD_state));
|
||||
if (_guard_state == NULL) {
|
||||
// Initialize the guard page.
|
||||
if ((err = _focus_guard(&_gd_state))) {
|
||||
fprintf(stderr, "guard: initial focus error\r\n");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
// Register guard page signal handler.
|
||||
if ((err = _register_handler(&_gd_state))) {
|
||||
fprintf(stderr, "guard: registration error\r\n");
|
||||
goto tidy;
|
||||
}
|
||||
} else {
|
||||
assert(_gd_state.buffer_list != NULL);
|
||||
}
|
||||
|
||||
// Setup new longjmp buffer.
|
||||
new_buffer = (GD_buflistnode *)malloc(sizeof(GD_buflistnode));
|
||||
if (new_buffer == NULL) {
|
||||
fprintf(stderr, "guard: malloc error\r\n");
|
||||
fprintf(stderr, "%s\r\n", strerror(errno));
|
||||
return guard_malloc | errno;
|
||||
err = guard_malloc;
|
||||
goto skip;
|
||||
}
|
||||
_guard_state->guard_p = 0;
|
||||
_guard_state->stack_pp = stack_pp;
|
||||
_guard_state->alloc_pp = alloc_pp;
|
||||
new_buffer->next = _gd_state.buffer_list;
|
||||
_gd_state.buffer_list = new_buffer;
|
||||
|
||||
// Initialize the guard page
|
||||
if ((err = _focus_guard(_guard_state))) {
|
||||
goto clear;
|
||||
}
|
||||
|
||||
// Register guard page signal handler
|
||||
if ((err = _register_handler(_guard_state))) {
|
||||
goto unmark;
|
||||
}
|
||||
|
||||
//
|
||||
// Run closure
|
||||
//
|
||||
|
||||
if (!(err = sigsetjmp(_guard_state->env_buffer, 1))) {
|
||||
// Run given closure.
|
||||
if (!(err = sigsetjmp(_gd_state.buffer_list->buffer, 1))) {
|
||||
*ret = f(closure);
|
||||
}
|
||||
|
||||
//
|
||||
// Clean up guard page state
|
||||
//
|
||||
// Restore previous longjmp buffer.
|
||||
_gd_state.buffer_list = _gd_state.buffer_list->next;
|
||||
free((void *)new_buffer);
|
||||
|
||||
if (sigaction(GD_SIGNAL, &(_guard_state->prev_sa), NULL)) {
|
||||
fprintf(stderr, "guard: sigaction error\r\n");
|
||||
skip:
|
||||
if (_gd_state.buffer_list == NULL) {
|
||||
if (sigaction(GD_SIGNAL, &_gd_state.prev_sa, NULL)) {
|
||||
fprintf(stderr, "guard: error replacing sigsegv handler\r\n");
|
||||
fprintf(stderr, "%s\r\n", strerror(errno));
|
||||
err_c = guard_sigaction | errno;
|
||||
td_err = guard_sigaction;
|
||||
|
||||
if (!err) {
|
||||
err = td_err;
|
||||
}
|
||||
}
|
||||
|
||||
unmark:
|
||||
err_c = _unmark_page((void *)_guard_state->guard_p);
|
||||
|
||||
clear:
|
||||
free(_guard_state);
|
||||
_guard_state = NULL;
|
||||
|
||||
return err ? err : err_c;
|
||||
tidy:
|
||||
// Unmark guard page.
|
||||
assert(_gd_state.guard_p != 0);
|
||||
td_err = _protect_page((void *)_gd_state.guard_p, PROT_READ | PROT_WRITE);
|
||||
if (td_err) {
|
||||
fprintf(stderr, "guard: unmark error\r\n");
|
||||
fprintf(stderr, "%s\r\n", strerror(errno));
|
||||
if (!err) {
|
||||
err = td_err;
|
||||
}
|
||||
}
|
||||
_gd_state.guard_p = 0;
|
||||
}
|
||||
|
||||
exit:
|
||||
return err;
|
||||
}
|
||||
|
@ -1,57 +1,72 @@
|
||||
#ifndef __GUARD_H__
|
||||
#define __GUARD_H__
|
||||
|
||||
#include <setjmp.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* Error codes and flags.
|
||||
*
|
||||
* The flags are bitwise added to the errno of their respective errors.
|
||||
* Error codes.
|
||||
*/
|
||||
typedef enum {
|
||||
guard_null = 1, // null stack or alloc pointer
|
||||
guard_oom, // OOM
|
||||
guard_malloc = 0x10000000, // malloc error flag
|
||||
guard_mprotect = 0x20000000, // mprotect error flag
|
||||
guard_sigaction = 0x40000000, // sigaction error flag
|
||||
guard_null, // null stack or alloc pointer
|
||||
guard_signal, // invalid signal
|
||||
guard_oom, // out of memory
|
||||
guard_malloc, // malloc error
|
||||
guard_mprotect, // mprotect error
|
||||
guard_sigaction, // sigaction error
|
||||
} guard_err;
|
||||
|
||||
typedef void *(*callback)(void *);
|
||||
|
||||
/**
|
||||
* Execute the given closure `f` within the memory arena between the
|
||||
* `stack` and `alloc` pointers, with guard page protection. Write either
|
||||
* `f`'s succesful result or a `guard_err` to the given `ret` pointer.
|
||||
* @brief Executes the given callback function `f` within the memory arena
|
||||
* between the stack and allocation pointers pointed to by `s_pp` and `a_pp`,
|
||||
* with guard page protection. If `f`'s execution succeeds, its result is
|
||||
* written to the return pointer `*ret`. If `f`'s execution triggers an
|
||||
* out of memory error or any other `guard_err`, the `guard_err` is
|
||||
* returned and `*ret` is left empty. In either case, cleanup is performed
|
||||
* before returning.
|
||||
*
|
||||
* Memory
|
||||
* ------
|
||||
* The free memory arena between the `stack` and `alloc` pointers is part of a
|
||||
* NockStack frame, which may either face east or west. If the frame faces
|
||||
* east, the `stack` pointer will be greater than the `alloc` pointer. If it
|
||||
* faces west, the `stack` pointer will be less than the `alloc` pointer.
|
||||
* Definitions:
|
||||
* - A guard page is marked `PROT_NONE`.
|
||||
*
|
||||
* All the pages in the memory arena are marked clean (`PROT_READ | PROT_WRITE`)
|
||||
* by default, with the exception of a single guard page in the middle of the
|
||||
* arena, which is marked with `PROT_NONE`.
|
||||
* Assumptions:
|
||||
* - `NockStack` pages are marked `PROT_READ|PROT_WRITE` by default.
|
||||
* - All memory access patterns are outside-in.
|
||||
* - Callback functions are compatible with the C ABI.
|
||||
* - `NockStack` stack and allocation pointer locations are fixed.
|
||||
* - The caller is responsible for return value memory allocation.
|
||||
* - The caller is responsible for managing any external state the callback
|
||||
* function may mutate.
|
||||
* - The callback function may be interrupted in the case of memory exhaustion
|
||||
* or other `guard_err` error (failure to `mprotect`, `malloc`, etc.).
|
||||
* - `SIGSEGV` (`SIGBUS` on macOS) signals are expected to be raised only on
|
||||
* guard page accesses.
|
||||
*
|
||||
* Guard
|
||||
* -----
|
||||
* This function protects the free memory arena between the `stack` and `alloc`
|
||||
* pointers with a guard page. A guard page is simply a single page of memory
|
||||
* which is marked with `PROT_NONE`. Since all other pages are marked clean by
|
||||
* default, a SIGSEGV will only be raised if the `f` function attempts to write
|
||||
* to the guard page. When it does, the signal handler will attempt to re-center
|
||||
* the guard page in the remaining free space left in the arena. If there is no
|
||||
* more free space, then memory exhaustion has occurred and the `guard_spent`
|
||||
* error will be written to the `ret` pointer. The caller is then responsible
|
||||
* for handling this error and aborting with a `bail:meme`.
|
||||
* Invariants:
|
||||
* - A single guard page is installed and maintained in the approximate center
|
||||
* until `crate::guard::call_with_guard` returns.
|
||||
* - A return value is only written to `*ret` on successful callback execution.
|
||||
* - A `guard_err` is returned.
|
||||
*
|
||||
* Enhancements:
|
||||
* - Use only a single, static jump buffer variable instead of a linked list.
|
||||
* We currently use a linked list of jump buffers because we don't have a
|
||||
* function for preserving stack traces across `crate::interpreter::interpret`
|
||||
* calls.
|
||||
*
|
||||
* @param f The callback function to execute.
|
||||
* @param closure A pointer to the closure data for the callback function.
|
||||
* @param s_pp A pointer to the stack pointer location.
|
||||
* @param a_pp A pointer to the allocation pointer location.
|
||||
* @param ret A pointer to a location where the callback's result can be stored.
|
||||
*
|
||||
* @return 0 on callback success; otherwise `guard_err` error code.
|
||||
*/
|
||||
uint32_t
|
||||
guard(
|
||||
callback f,
|
||||
void *(*f)(void *),
|
||||
void *closure,
|
||||
const uintptr_t *const stack_pp,
|
||||
const uintptr_t *const alloc_pp,
|
||||
const uintptr_t *const s_pp,
|
||||
const uintptr_t *const a_pp,
|
||||
void **ret
|
||||
);
|
||||
|
||||
|
@ -5,6 +5,7 @@
|
||||
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
|
||||
|
||||
pub const GUARD_NULL: u32 = guard_err_guard_null;
|
||||
pub const GUARD_SIGNAL: u32 = guard_err_guard_signal;
|
||||
pub const GUARD_OOM: u32 = guard_err_guard_oom;
|
||||
pub const GUARD_MALLOC: u32 = guard_err_guard_malloc;
|
||||
pub const GUARD_MPROTECT: u32 = guard_err_guard_mprotect;
|
||||
|
@ -76,6 +76,7 @@ STATIC_ASSERT(0, "debugger break instruction unimplemented");
|
||||
/* the opposite of P2BYTES */
|
||||
#define B2PAGES(x) ((size_t)(x) >> BT_PAGEBITS)
|
||||
|
||||
|
||||
#define __packed __attribute__((__packed__))
|
||||
#define UNUSED(x) ((void)(x))
|
||||
|
||||
@ -94,6 +95,7 @@ STATIC_ASSERT(0, "debugger break instruction unimplemented");
|
||||
/* given a pointer p returns the low page-aligned addr */
|
||||
#define LO_ALIGN_PAGE(p) ((BT_page *)(((uintptr_t)p) & ~(BT_PAGESIZE - 1)))
|
||||
|
||||
|
||||
#define BT_MAPADDR ((BYTE *) S(0x1000,0000,0000))
|
||||
|
||||
static inline vaof_t
|
||||
@ -339,6 +341,7 @@ struct BT_state {
|
||||
|
||||
/*
|
||||
|
||||
|
||||
//// ===========================================================================
|
||||
//// btree internal routines
|
||||
|
||||
|
@ -12,6 +12,7 @@ keywords = ["allocator", "real-time", "debug", "audio"]
|
||||
categories = ["development-tools::debugging"]
|
||||
|
||||
[features]
|
||||
default = []
|
||||
warn_debug = []
|
||||
warn_release = []
|
||||
disable_release = []
|
@ -91,6 +91,22 @@ pub fn assert_no_alloc<T, F: FnOnce() -> T> (func: F) -> T {
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// Calls the `func` closure, but ensures that the forbid and permit counters
|
||||
/// are maintained accurately even if a longjmp originates and terminates
|
||||
/// within the closure. If you longjmp over this function, we can't fix
|
||||
/// anything about it.
|
||||
pub fn ensure_alloc_counters<T, F: FnOnce() -> T> (func: F) -> T {
|
||||
let forbid_counter = ALLOC_FORBID_COUNT.with(|c| c.get());
|
||||
let permit_counter = ALLOC_PERMIT_COUNT.with(|c| c.get());
|
||||
|
||||
let ret = func();
|
||||
|
||||
ALLOC_FORBID_COUNT.with(|c| c.set(forbid_counter));
|
||||
ALLOC_PERMIT_COUNT.with(|c| c.set(permit_counter));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#[cfg(not(all(feature = "disable_release", not(debug_assertions))))] // if not disabled
|
||||
/// Calls the `func` closure. Allocations are temporarily allowed, even if this
|
||||
/// code runs inside of assert_no_alloc.
|
||||
@ -132,13 +148,6 @@ pub fn reset_violation_count() {
|
||||
ALLOC_VIOLATION_COUNT.with(|c| c.set(0));
|
||||
}
|
||||
|
||||
pub fn reset_counters() {
|
||||
ALLOC_FORBID_COUNT.with(|c| c.set(0));
|
||||
ALLOC_PERMIT_COUNT.with(|c| c.set(0));
|
||||
|
||||
#[cfg(any( all(feature="warn_debug", debug_assertions), all(feature="warn_release", not(debug_assertions)) ))]
|
||||
ALLOC_VIOLATION_COUNT.with(|c| c.set(0));
|
||||
}
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user