mirror of
https://github.com/urbit/ares.git
synced 2024-11-26 09:57:56 +03:00
gaurd: initial guard page commit
This commit is contained in:
parent
3a64e7f656
commit
42ffde2208
53
rust/ares/Cargo.lock
generated
53
rust/ares/Cargo.lock
generated
@ -59,9 +59,10 @@ name = "ares"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"ares_crypto",
|
||||
"ares_guard",
|
||||
"ares_macros",
|
||||
"ares_pma",
|
||||
"assert_no_alloc",
|
||||
"assert_no_alloc 1.1.2",
|
||||
"autotools",
|
||||
"bitvec",
|
||||
"cc",
|
||||
@ -86,7 +87,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"aes-siv",
|
||||
"assert_no_alloc",
|
||||
"assert_no_alloc 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"curve25519-dalek",
|
||||
"ed25519-dalek",
|
||||
"ibig 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@ -95,6 +96,14 @@ dependencies = [
|
||||
"x25519-dalek",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ares_guard"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bindgen",
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ares_macros"
|
||||
version = "0.1.0"
|
||||
@ -111,6 +120,10 @@ dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "assert_no_alloc"
|
||||
version = "1.1.2"
|
||||
|
||||
[[package]]
|
||||
name = "assert_no_alloc"
|
||||
version = "1.1.2"
|
||||
@ -145,11 +158,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "bindgen"
|
||||
version = "0.69.1"
|
||||
version = "0.69.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ffcebc3849946a7170a05992aac39da343a90676ab392c51a4280981d6379c2"
|
||||
checksum = "a4c69fae65a523209d34240b60abe0c42d33d1045d445c0839d8a4894a736e2d"
|
||||
dependencies = [
|
||||
"bitflags 2.4.1",
|
||||
"bitflags 2.4.2",
|
||||
"cexpr",
|
||||
"clang-sys",
|
||||
"lazy_static",
|
||||
@ -174,9 +187,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.4.1"
|
||||
version = "2.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
|
||||
checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
|
||||
|
||||
[[package]]
|
||||
name = "bitvec"
|
||||
@ -664,9 +677,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.4.12"
|
||||
version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456"
|
||||
checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
@ -801,9 +814,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.76"
|
||||
version = "1.0.78"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c"
|
||||
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@ -860,9 +873,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.10.2"
|
||||
version = "1.10.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
|
||||
checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
@ -872,9 +885,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.3"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
|
||||
checksum = "3b7fa1134405e2ec9353fd416b17f8dacd46c473d7d3fd1cf202706a14eb792a"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
@ -904,11 +917,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.28"
|
||||
version = "0.38.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316"
|
||||
checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca"
|
||||
dependencies = [
|
||||
"bitflags 2.4.1",
|
||||
"bitflags 2.4.2",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
@ -991,9 +1004,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.2.0"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook"
|
||||
|
@ -11,14 +11,15 @@ edition = "2018"
|
||||
|
||||
# Please keep these alphabetized
|
||||
[dependencies]
|
||||
ares_guard = { path = "../ares_guard" }
|
||||
ares_crypto = { path = "../ares_crypto" }
|
||||
ares_macros = { path = "../ares_macros" }
|
||||
# Use this when debugging requires the debug printfs in the PMA
|
||||
# ares_pma = { path = "../ares_pma", features=["debug_prints"] }
|
||||
ares_pma = { path = "../ares_pma" }
|
||||
assert_no_alloc = "1.1.2"
|
||||
# use this when debugging requires allocation (e.g. eprintln)
|
||||
# assert_no_alloc = {version="1.1.2", features=["warn_debug"]}
|
||||
# assert_no_alloc = { path = "../assert-no-alloc", features=["warn_debug"] }
|
||||
assert_no_alloc = { path = "../assert-no-alloc" }
|
||||
bitvec = "1.0.0"
|
||||
criterion = "0.4"
|
||||
either = "1.9.0"
|
||||
@ -35,8 +36,8 @@ signal-hook = "0.3"
|
||||
static_assertions = "1.1.0"
|
||||
|
||||
[build-dependencies]
|
||||
autotools = "0.2.6"
|
||||
cc = "1.0.79"
|
||||
autotools = "0.2"
|
||||
cc = "1.0"
|
||||
|
||||
[[bin]]
|
||||
name = "ares"
|
||||
|
114
rust/ares/src/guard.rs
Normal file
114
rust/ares/src/guard.rs
Normal file
@ -0,0 +1,114 @@
|
||||
use crate::interpreter::{Error, Mote, Result};
|
||||
use crate::noun::D;
|
||||
use ares_guard::*;
|
||||
use assert_no_alloc::permit_alloc;
|
||||
use std::convert::TryFrom;
|
||||
use std::ffi::c_void;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum GuardError {
|
||||
InvalidSignal,
|
||||
MemoryProtection,
|
||||
NullPointer,
|
||||
OutOfMemory,
|
||||
Setup,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl From<u32> for GuardError {
|
||||
fn from(value: u32) -> Self {
|
||||
match value {
|
||||
GUARD_NULL => Self::NullPointer,
|
||||
GUARD_SIGNAL => Self::InvalidSignal,
|
||||
GUARD_OOM => Self::OutOfMemory,
|
||||
x if (x & GUARD_MPROTECT) != 0 => Self::MemoryProtection,
|
||||
x if (x & (GUARD_MALLOC | GUARD_SIGACTION)) != 0 => Self::Setup,
|
||||
_ => Self::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CCallback<'closure> {
|
||||
pub function: unsafe extern "C" fn(*mut c_void) -> *mut c_void,
|
||||
pub input: *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> {
|
||||
pub fn new<F>(closure: &'closure mut F) -> Self
|
||||
where
|
||||
F: FnMut() -> Result,
|
||||
{
|
||||
let function: unsafe extern "C" fn(*mut c_void) -> *mut c_void = 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,
|
||||
input: closure as *mut F as *mut c_void,
|
||||
_lifetime: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn call_closure<F>(input: *mut c_void) -> *mut c_void
|
||||
where
|
||||
F: FnMut() -> Result,
|
||||
{
|
||||
let cb: &mut F = input.cast::<F>().as_mut().unwrap();
|
||||
let v = (*cb)();
|
||||
permit_alloc(|| {
|
||||
let v_box = Box::new(v);
|
||||
let v_ptr = Box::into_raw(v_box);
|
||||
v_ptr as *mut c_void
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn call_with_guard<F: FnMut() -> Result>(
|
||||
stack_pp: *const *const u64,
|
||||
alloc_pp: *const *const u64,
|
||||
closure: &mut F,
|
||||
) -> Result {
|
||||
let cb = CCallback::new(closure);
|
||||
let mut ret_p: *mut c_void = std::ptr::null_mut();
|
||||
let ret_pp = &mut ret_p as *mut *mut c_void;
|
||||
|
||||
unsafe {
|
||||
let res = guard(
|
||||
Some(cb.function as unsafe extern "C" fn(*mut c_void) -> *mut c_void),
|
||||
cb.input,
|
||||
stack_pp as *const usize,
|
||||
alloc_pp as *const usize,
|
||||
ret_pp,
|
||||
);
|
||||
|
||||
// eprintln!("\r BEFORE:");
|
||||
// eprintln!("\r ret = {:?}", ret);
|
||||
// eprintln!("\r ret_p = {:p}, {:?}", ret_p as *mut Result, *(ret_p as *mut Result));
|
||||
// eprintln!("\r ret_pp = {:p}, {:p}, {:?}", ret_pp, *ret_pp, **(ret_pp as *mut *mut Result));
|
||||
if res == 0 {
|
||||
// TODO: come back to this
|
||||
permit_alloc(|| {
|
||||
let result_box = Box::from_raw(ret_p as *mut Result);
|
||||
*result_box
|
||||
})
|
||||
} else {
|
||||
let err = GuardError::from(u32::try_from(res).unwrap());
|
||||
match err {
|
||||
GuardError::OutOfMemory => Err(Error::NonDeterministic(Mote::Meme, D(0))),
|
||||
_ => {
|
||||
panic!("serf: guard: unexpected error {:?}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@ extern crate num_derive;
|
||||
extern crate lazy_static;
|
||||
#[macro_use]
|
||||
extern crate static_assertions;
|
||||
pub mod guard;
|
||||
pub mod hamt;
|
||||
pub mod interpreter;
|
||||
pub mod jets;
|
||||
|
@ -152,6 +152,16 @@ impl NockStack {
|
||||
self.alloc_pointer
|
||||
}
|
||||
|
||||
/** Current stack pointer of this NockStack */
|
||||
pub fn get_stack_pointer_pointer(&self) -> *const *mut u64 {
|
||||
&self.stack_pointer
|
||||
}
|
||||
|
||||
/** Current alloc pointer of this NockStack */
|
||||
pub fn get_alloc_pointer_pointer(&self) -> *const *mut u64 {
|
||||
&self.alloc_pointer
|
||||
}
|
||||
|
||||
/** Start of the memory range for this NockStack */
|
||||
pub fn get_start(&self) -> *const u64 {
|
||||
self.start
|
||||
|
@ -1,3 +1,4 @@
|
||||
use crate::guard::call_with_guard;
|
||||
use crate::hamt::Hamt;
|
||||
use crate::interpreter;
|
||||
use crate::interpreter::{inc, interpret, Error, Mote};
|
||||
@ -143,7 +144,7 @@ impl Context {
|
||||
snapshot: Option<Snapshot>,
|
||||
constant_hot_state: &[HotEntry],
|
||||
) -> Self {
|
||||
let mut stack = NockStack::new(4096 << 10 << 10, 0);
|
||||
let mut stack = NockStack::new(2048 << 10 << 10, 0);
|
||||
let newt = Newt::new();
|
||||
let cache = Hamt::<Noun>::new(&mut stack);
|
||||
|
||||
@ -401,7 +402,28 @@ fn slam(context: &mut Context, axis: u64, ovo: Noun) -> Result<Noun, Error> {
|
||||
let sam = T(stack, &[D(6), D(0), D(7)]);
|
||||
let fol = T(stack, &[D(8), pul, D(9), D(2), D(10), sam, D(0), D(2)]);
|
||||
let sub = T(stack, &[arvo, ovo]);
|
||||
interpret(&mut context.nock_context, sub, fol)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
fn peek(context: &mut Context, ovo: Noun) -> Noun {
|
||||
@ -604,16 +626,12 @@ fn work_swap(context: &mut Context, job: Noun, goof: Noun) {
|
||||
context.work_swap(ovo, fec);
|
||||
}
|
||||
Err(goof_crud) => {
|
||||
work_bail(context, &[goof_crud, goof]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn work_bail(context: &mut Context, goofs: &[Noun]) {
|
||||
eprintln!("\r serf: bail");
|
||||
let stack = &mut context.nock_context.stack;
|
||||
let lest = T(stack, goofs);
|
||||
let lud = T(stack, &[lest, D(0)]);
|
||||
let lud = T(stack, &[goof_crud, goof, D(0)]);
|
||||
context.work_bail(lud);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn work_trace_name(stack: &mut NockStack, wire: Noun, vent: Atom) -> String {
|
||||
|
432
rust/ares_guard/Cargo.lock
generated
Normal file
432
rust/ares_guard/Cargo.lock
generated
Normal file
@ -0,0 +1,432 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ares_guard"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bindgen",
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bindgen"
|
||||
version = "0.69.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4c69fae65a523209d34240b60abe0c42d33d1045d445c0839d8a4894a736e2d"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cexpr",
|
||||
"clang-sys",
|
||||
"lazy_static",
|
||||
"lazycell",
|
||||
"log",
|
||||
"peeking_take_while",
|
||||
"prettyplease",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"rustc-hash",
|
||||
"shlex",
|
||||
"syn",
|
||||
"which",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cexpr"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
|
||||
dependencies = [
|
||||
"nom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "clang-sys"
|
||||
version = "1.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67523a3b4be3ce1989d607a828d036249522dd9c1c8de7f4dd2dae43a37369d1"
|
||||
dependencies = [
|
||||
"glob",
|
||||
"libc",
|
||||
"libloading",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
|
||||
|
||||
[[package]]
|
||||
name = "home"
|
||||
version = "0.5.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5"
|
||||
dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "lazycell"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.152"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7"
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
|
||||
|
||||
[[package]]
|
||||
name = "minimal-lexical"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"minimal-lexical",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
||||
|
||||
[[package]]
|
||||
name = "peeking_take_while"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
|
||||
|
||||
[[package]]
|
||||
name = "prettyplease"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a41cf62165e97c7f814d2221421dbb9afcbcdb0a88068e5ea206e19951c2cbb5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.76"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.48"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||
|
||||
[[package]]
|
||||
name = "which"
|
||||
version = "4.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7"
|
||||
dependencies = [
|
||||
"either",
|
||||
"home",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
||||
dependencies = [
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.48.5",
|
||||
"windows_aarch64_msvc 0.48.5",
|
||||
"windows_i686_gnu 0.48.5",
|
||||
"windows_i686_msvc 0.48.5",
|
||||
"windows_x86_64_gnu 0.48.5",
|
||||
"windows_x86_64_gnullvm 0.48.5",
|
||||
"windows_x86_64_msvc 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.52.0",
|
||||
"windows_aarch64_msvc 0.52.0",
|
||||
"windows_i686_gnu 0.52.0",
|
||||
"windows_i686_msvc 0.52.0",
|
||||
"windows_x86_64_gnu 0.52.0",
|
||||
"windows_x86_64_gnullvm 0.52.0",
|
||||
"windows_x86_64_msvc 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
|
12
rust/ares_guard/Cargo.toml
Normal file
12
rust/ares_guard/Cargo.toml
Normal file
@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "ares_guard"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
|
||||
[build-dependencies]
|
||||
bindgen = "0.69"
|
||||
cc = "1.0"
|
77
rust/ares_guard/build.rs
Normal file
77
rust/ares_guard/build.rs
Normal file
@ -0,0 +1,77 @@
|
||||
extern crate bindgen;
|
||||
|
||||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn main() {
|
||||
let opt_level = env::var("OPT_LEVEL").unwrap();
|
||||
let define_debug = if env::var("CARGO_FEATURE_DEBUG_PRINTS").is_ok() {
|
||||
"-DDEBUG"
|
||||
} else {
|
||||
"-UDEBUG"
|
||||
};
|
||||
|
||||
// This is the directory where the `c` library is located.
|
||||
let libdir_path = PathBuf::from("c-src")
|
||||
// Canonicalize the path as `rustc-link-search` requires an absolute
|
||||
// path.
|
||||
.canonicalize()
|
||||
.expect("cannot canonicalize path");
|
||||
let libdir_path_str = libdir_path.to_str().expect("Path is not a valid string");
|
||||
|
||||
// This is the path to the `c` headers file.
|
||||
let headers_path = libdir_path.join("wrapper.h");
|
||||
let headers_path_str = headers_path.to_str().expect("Path is not a valid string");
|
||||
|
||||
println!("cargo:rerun-if-changed={}", libdir_path_str);
|
||||
|
||||
let res = cc::Build::new()
|
||||
.file(
|
||||
libdir_path
|
||||
.join("guard.c")
|
||||
.to_str()
|
||||
.expect("Path is not a valid string"),
|
||||
)
|
||||
.flag(format!("-O{}", opt_level).as_ref())
|
||||
.flag(define_debug)
|
||||
.flag("-g3")
|
||||
.flag("-Wall")
|
||||
.flag("-Wextra")
|
||||
.flag("-Wformat=2")
|
||||
.flag("-Wmissing-include-dirs")
|
||||
.flag("-Wnested-externs")
|
||||
.flag("-Wpedantic")
|
||||
.flag("-Wredundant-decls")
|
||||
.flag("-Wshadow")
|
||||
.flag("-Wwrite-strings")
|
||||
.flag("-Wno-unused-parameter")
|
||||
.flag("-Wno-pointer-arith")
|
||||
.flag("-Wno-strict-prototypes")
|
||||
.flag("-Wno-unused-function")
|
||||
.try_compile("guard");
|
||||
|
||||
if let Err(err) = res {
|
||||
panic!("{}", err);
|
||||
}
|
||||
|
||||
// The bindgen::Builder is the main entry point
|
||||
// to bindgen, and lets you build up options for
|
||||
// the resulting bindings.
|
||||
let bindings = bindgen::Builder::default()
|
||||
// The input header we would like to generate
|
||||
// bindings for.
|
||||
.header(headers_path_str)
|
||||
// Tell cargo to invalidate the built crate whenever any of the
|
||||
// included header files changed.
|
||||
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
|
||||
// Finish the builder and generate the bindings.
|
||||
.generate()
|
||||
// Unwrap the Result and panic on failure.
|
||||
.expect("Unable to generate bindings");
|
||||
|
||||
// Write the bindings to the $OUT_DIR/bindings.rs file.
|
||||
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("bindings.rs");
|
||||
bindings
|
||||
.write_to_file(out_path)
|
||||
.expect("Couldn't write bindings!");
|
||||
}
|
287
rust/ares_guard/c-src/guard.c
Normal file
287
rust/ares_guard/c-src/guard.c
Normal file
@ -0,0 +1,287 @@
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <setjmp.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "guard.h"
|
||||
|
||||
#define GD_PAGE_BITS 14ULL
|
||||
#define GD_PAGE_SIZE (1ULL << GD_PAGE_BITS) // 16 KB
|
||||
#define GD_PAGE_MASK (GD_PAGE_SIZE - 1)
|
||||
#define GD_PAGE_ROUND_DOWN(foo) (foo & (~GD_PAGE_MASK))
|
||||
|
||||
/**
|
||||
* XX: documentation
|
||||
*/
|
||||
typedef struct _gs {
|
||||
uintptr_t guard_p;
|
||||
const uintptr_t *stack_pp;
|
||||
const uintptr_t *alloc_pp;
|
||||
jmp_buf env_buffer;
|
||||
struct sigaction prev_sa;
|
||||
} GuardState;
|
||||
|
||||
static GuardState *_guard_state = NULL;
|
||||
|
||||
|
||||
static int32_t
|
||||
_prot_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 0;
|
||||
}
|
||||
|
||||
static int32_t
|
||||
_mark_page(void *address)
|
||||
{
|
||||
return _prot_page(address, PROT_NONE);
|
||||
}
|
||||
|
||||
static int32_t
|
||||
_unmark_page(void *address)
|
||||
{
|
||||
return _prot_page(address, PROT_READ | PROT_WRITE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Center the guard page.
|
||||
*/
|
||||
static int32_t
|
||||
_focus_guard(
|
||||
uintptr_t *guard_pp,
|
||||
const uintptr_t stack_p,
|
||||
const uintptr_t alloc_p
|
||||
) {
|
||||
// Check for strange situations.
|
||||
fprintf(stderr, "guard: focus: stack pointer at %p\r\n", (void *)stack_p);
|
||||
fprintf(stderr, "guard: focus: alloc pointer at %p\r\n", (void *)alloc_p);
|
||||
if (stack_p == 0 || alloc_p == 0) {
|
||||
fprintf(stderr, "guard: focus: stack or alloc pointer is null\r\n");
|
||||
return guard_null;
|
||||
} else if (stack_p == alloc_p) {
|
||||
fprintf(stderr, "guard: focus: stack and alloc pointers equal\r\n");
|
||||
return guard_oom;
|
||||
}
|
||||
|
||||
uintptr_t old_guard_p = *guard_pp;
|
||||
uintptr_t new_guard_p;
|
||||
int32_t err = 0;
|
||||
|
||||
fprintf(stderr, "guard: focus: old guard = %p\r\n", (void *)old_guard_p);
|
||||
|
||||
// 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\r\n");
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
// Compute new guard page
|
||||
// XX: Should we also check for new_guard_p < min(stack_p, alloc_p)?
|
||||
new_guard_p = GD_PAGE_ROUND_DOWN((stack_p + alloc_p) / 2);
|
||||
fprintf(stderr, "guard: focus: new guard = %p\r\n", (void *)new_guard_p);
|
||||
if (new_guard_p == old_guard_p) {
|
||||
fprintf(stderr, "guard: focus: OOM\r\n");
|
||||
return guard_oom;
|
||||
}
|
||||
|
||||
// Mark new guard page
|
||||
if ((err = _mark_page((void *)new_guard_p))) {
|
||||
fprintf(stderr, "guard: focus: mark error\r\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
// Update guard page tracker
|
||||
fprintf(stderr, "guard: focus: installed guard page at %p\r\n", (void *)new_guard_p);
|
||||
*guard_pp = new_guard_p;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
_signal_handler(int sig, siginfo_t *si, void *unused)
|
||||
{
|
||||
uintptr_t sig_addr;
|
||||
int32_t err = 0;
|
||||
|
||||
fprintf(stderr, "guard: sig_handle: %d received\r\n", sig);
|
||||
|
||||
if (sig != SIGSEGV) {
|
||||
fprintf(stderr, "guard: sig_handle: invalid signal\r\n");
|
||||
siglongjmp(_guard_state->env_buffer, guard_signal);
|
||||
}
|
||||
|
||||
sig_addr = (uintptr_t)si->si_addr;
|
||||
fprintf(stderr, "guard: SIGSEGV address = %p\r\n", (void *)sig_addr);
|
||||
|
||||
fprintf(stderr, "guard: sig_handle: %p \r\n", _guard_state);
|
||||
if (
|
||||
sig_addr >= _guard_state->guard_p &&
|
||||
sig_addr < (_guard_state->guard_p + GD_PAGE_SIZE))
|
||||
{
|
||||
fprintf(stderr, "guard: page at %p hit\r\n", (void *)_guard_state->guard_p);
|
||||
err = _focus_guard(
|
||||
&(_guard_state->guard_p),
|
||||
*(_guard_state->stack_pp),
|
||||
*(_guard_state->alloc_pp));
|
||||
if (err) {
|
||||
fprintf(stderr, "guard: sig_handle: focus error\r\n");
|
||||
siglongjmp(_guard_state->env_buffer, err);
|
||||
}
|
||||
} else {
|
||||
struct sigaction prev_sa = _guard_state->prev_sa;
|
||||
|
||||
fprintf(stderr, "guard: page at %p miss\r\n", (void *)_guard_state->guard_p);
|
||||
|
||||
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
|
||||
assert(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int32_t
|
||||
_register_handler(struct sigaction *prev_sa)
|
||||
{
|
||||
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
|
||||
// TODO: 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(SIGSEGV, &sa, prev_sa)) {
|
||||
fprintf(stderr, "guard: register: sigaction error\r\n");
|
||||
fprintf(stderr, "%s\r\n", strerror(errno));
|
||||
return guard_sigaction | errno;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t
|
||||
_setup(
|
||||
GuardState **gs_p,
|
||||
const uintptr_t *const stack_pp,
|
||||
const uintptr_t *const alloc_pp
|
||||
) {
|
||||
GuardState *gs;
|
||||
int32_t err = 0;
|
||||
|
||||
assert(*gs_p == NULL);
|
||||
fprintf(stderr, "guard: setup: stack pointer at %p\r\n", (void *)(*stack_pp));
|
||||
fprintf(stderr, "guard: setup: alloc pointer at %p\r\n", (void *)(*alloc_pp));
|
||||
|
||||
// Setup guard page state
|
||||
*gs_p = (GuardState *)malloc(sizeof(GuardState));
|
||||
gs = *gs_p;
|
||||
if (gs == NULL) {
|
||||
fprintf(stderr, "guard: malloc error\r\n");
|
||||
fprintf(stderr, "%s\r\n", strerror(errno));
|
||||
return guard_malloc | errno;
|
||||
}
|
||||
fprintf(stderr, "guard: state allocated to %p \r\n", gs);
|
||||
|
||||
gs->guard_p = 0;
|
||||
gs->stack_pp = stack_pp;
|
||||
gs->alloc_pp = alloc_pp;
|
||||
|
||||
// Initialize the guard page
|
||||
if ((err = _focus_guard(&(gs->guard_p), *stack_pp, *alloc_pp))) {
|
||||
fprintf(stderr, "guard: setup: _focus_guard error\r\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
// Register guard page signal handler
|
||||
if ((err = _register_handler(&(gs->prev_sa)))) {
|
||||
fprintf(stderr, "guard: setup: _register_handler error\r\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t
|
||||
_teardown(GuardState** gs_p)
|
||||
{
|
||||
int32_t err = 0;
|
||||
|
||||
if (*gs_p != NULL) {
|
||||
GuardState *gs = *gs_p;
|
||||
|
||||
if (gs->guard_p != 0) {
|
||||
err = _unmark_page((void *)gs->guard_p);
|
||||
}
|
||||
|
||||
if (sigaction(SIGSEGV, &(gs->prev_sa), NULL)) {
|
||||
fprintf(stderr, "guard: teardown: sigaction error\r\n");
|
||||
fprintf(stderr, "%s\r\n", strerror(errno));
|
||||
err = guard_sigaction | errno;
|
||||
}
|
||||
|
||||
free(gs);
|
||||
*gs_p = NULL;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
int32_t
|
||||
guard(
|
||||
callback f,
|
||||
void *closure,
|
||||
const uintptr_t *const stack_pp,
|
||||
const uintptr_t *const alloc_pp,
|
||||
void ** ret
|
||||
) {
|
||||
int32_t err = 0;
|
||||
|
||||
// Setup the guard page
|
||||
fprintf(stderr, "guard: setup\r\n");
|
||||
if ((err = _setup(&_guard_state, stack_pp, alloc_pp))) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
// Run given closure
|
||||
fprintf(stderr, "guard: run\r\n");
|
||||
if (!(err = sigsetjmp(_guard_state->env_buffer, 1))) {
|
||||
*ret = f(closure);
|
||||
|
||||
// Clean up
|
||||
fprintf(stderr, "guard: teardown\r\n");
|
||||
err = _teardown(&_guard_state);
|
||||
|
||||
fprintf(stderr, "guard: return\r\n");
|
||||
return err;
|
||||
} else {
|
||||
done:
|
||||
// Clean up
|
||||
fprintf(stderr, "guard: teardown\r\n");
|
||||
_teardown(&_guard_state);
|
||||
|
||||
fprintf(stderr, "guard: return\r\n");
|
||||
return err;
|
||||
}
|
||||
}
|
59
rust/ares_guard/c-src/guard.h
Normal file
59
rust/ares_guard/c-src/guard.h
Normal file
@ -0,0 +1,59 @@
|
||||
#ifndef __GUARD_H__
|
||||
#define __GUARD_H__
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* Error codes and flags.
|
||||
*
|
||||
* The flags are bitwise added to the errno of their respective errors.
|
||||
*/
|
||||
typedef enum {
|
||||
guard_null = 1, // null stack or alloc pointer
|
||||
guard_signal, // invalid signal
|
||||
guard_oom, // OOM
|
||||
guard_malloc = 0x10000000, // malloc error flag
|
||||
guard_mprotect = 0x20000000, // mprotect error flag
|
||||
guard_sigaction = 0x40000000, // sigaction error flag
|
||||
} 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.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* 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`.
|
||||
*
|
||||
* 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`.
|
||||
*/
|
||||
int32_t
|
||||
guard(
|
||||
callback f,
|
||||
void *closure,
|
||||
const uintptr_t *const stack_pp,
|
||||
const uintptr_t *const alloc_pp,
|
||||
void ** ret
|
||||
);
|
||||
|
||||
#endif // __GUARD_H__
|
1
rust/ares_guard/c-src/wrapper.h
Normal file
1
rust/ares_guard/c-src/wrapper.h
Normal file
@ -0,0 +1 @@
|
||||
#include "guard.h"
|
12
rust/ares_guard/src/lib.rs
Normal file
12
rust/ares_guard/src/lib.rs
Normal file
@ -0,0 +1,12 @@
|
||||
#![allow(non_upper_case_globals)]
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
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;
|
||||
pub const GUARD_SIGACTION: u32 = guard_err_guard_sigaction;
|
32
rust/assert-no-alloc/Cargo.toml
Normal file
32
rust/assert-no-alloc/Cargo.toml
Normal file
@ -0,0 +1,32 @@
|
||||
[package]
|
||||
name = "assert_no_alloc"
|
||||
version = "1.1.2"
|
||||
authors = ["Florian Jung <flo@windfis.ch>"]
|
||||
edition = "2018"
|
||||
license = "BSD-1-Clause"
|
||||
description = "Custom Rust allocator allowing to temporarily disable memory (de)allocations for a thread. Aborts or prints a warning if allocating although forbidden."
|
||||
homepage = "https://github.com/Windfisch/rust-assert-no-alloc"
|
||||
repository = "https://github.com/Windfisch/rust-assert-no-alloc"
|
||||
readme = "README.md"
|
||||
keywords = ["allocator", "real-time", "debug", "audio"]
|
||||
categories = ["development-tools::debugging"]
|
||||
|
||||
[features]
|
||||
default = ["disable_release"]
|
||||
warn_debug = []
|
||||
warn_release = []
|
||||
disable_release = []
|
||||
|
||||
# Print a backtrace before aborting the program when an allocation failure happens
|
||||
backtrace = ["dep:backtrace"]
|
||||
# Use the `log` crate instead of printing to STDERR
|
||||
# WARNING: If the allocation failure happens during a logger call, then
|
||||
# depending on the logger's implementation this may block indefinitely
|
||||
log = ["dep:log"]
|
||||
|
||||
[dependencies]
|
||||
backtrace = { version = "0.3", optional = true }
|
||||
log = { version = "0.4", optional = true }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["warn_debug"]
|
21
rust/assert-no-alloc/LICENSE
Normal file
21
rust/assert-no-alloc/LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
assert_no_alloc -- A custom Rust allocator allowing to temporarily disable
|
||||
memory (de)allocations for a thread.
|
||||
|
||||
Copyright (c) 2020 Florian Jung <flo@windfis.ch>
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED
|
||||
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
||||
EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
|
||||
OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
||||
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
143
rust/assert-no-alloc/README.md
Normal file
143
rust/assert-no-alloc/README.md
Normal file
@ -0,0 +1,143 @@
|
||||
assert_no_alloc
|
||||
===============
|
||||
|
||||
This crate provides a custom allocator that allows to temporarily disable
|
||||
memory (de)allocations for a thread. If a (de)allocation is attempted
|
||||
anyway, the program will abort or print a warning.
|
||||
|
||||
It uses thread local storage for the "disabled-flag/counter", and thus
|
||||
should be thread safe, if the underlying allocator (currently hard-coded
|
||||
to `std::alloc::System`) is.
|
||||
|
||||
[documentation @ docs.rs](https://docs.rs/assert_no_alloc/1.1.0/assert_no_alloc/),
|
||||
[crates.io](https://crates.io/crates/assert_no_alloc)
|
||||
|
||||
Rationale
|
||||
---------
|
||||
|
||||
No-allocation-zones are relevant e.g. in real-time scenarios like audio
|
||||
callbacks. Allocation and deallocation can take unpredictable amounts of
|
||||
time, and thus can *sometimes* lead to audible glitches because the audio
|
||||
data is not served in time.
|
||||
|
||||
Debugging such problems can be hard, because it is difficult to reproduce
|
||||
such problems consistently. Avoiding such problems is also hard, since
|
||||
allocation/deallocation is a common thing to do and most libraries are not
|
||||
explicit whether certain functions can allocate or not. Also, this might
|
||||
even depend on the run-time situation (e.g. a `Vec::push` might allocate,
|
||||
but it is guaranteed to not allocate *if* enough space has been `reserve()`d
|
||||
before).
|
||||
|
||||
To aid the developer in tackling these problems, this crate offers an easy
|
||||
way of detecting all forbidden allocations.
|
||||
|
||||
How to use
|
||||
----------
|
||||
|
||||
First, configure the features: `warn_debug` and `warn_release` change the
|
||||
behaviour from aborting your program into just printing an error message
|
||||
on `stderr`. Aborting is useful for debugging purposes, as it allows you
|
||||
to retrieve a stacktrace, while warning is less intrusive.
|
||||
|
||||
Note that you need to disable the (default-enabled) `disable_release` feature
|
||||
by specify `default-features = false` if you want to use `warn_release`. If
|
||||
`disable_release` is set (which is the default), then this crate will do
|
||||
nothing if built in `--release` mode.
|
||||
|
||||
Second, use the allocator provided by this crate. Add this to `main.rs`:
|
||||
|
||||
```rust
|
||||
use assert_no_alloc::*;
|
||||
|
||||
#[cfg(debug_assertions)] // required when disable_release is set (default)
|
||||
#[global_allocator]
|
||||
static A: AllocDisabler = AllocDisabler;
|
||||
```
|
||||
|
||||
Third, wrap code sections that may not allocate like this:
|
||||
|
||||
```rust
|
||||
assert_no_alloc(|| {
|
||||
println!("This code can not allocate.");
|
||||
});
|
||||
```
|
||||
|
||||
Advanced use
|
||||
------------
|
||||
|
||||
Values can be returned using:
|
||||
|
||||
```rust
|
||||
let answer = assert_no_alloc(|| { 42 });
|
||||
```
|
||||
|
||||
The effect of `assert_no_alloc` can be overridden using `permit_alloc`:
|
||||
|
||||
```rust
|
||||
assert_no_alloc(|| {
|
||||
permit_alloc(|| {
|
||||
// Allocate some memory here. This will work.
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
This is useful for test stubs whose code is executed in an `assert_no_alloc`
|
||||
context.
|
||||
|
||||
Objects that deallocate upon `Drop` can be wrapped in `PermitDrop`:
|
||||
|
||||
```rust
|
||||
let foo = PermitDrop::new(
|
||||
permit_alloc(||
|
||||
Box::new(...)
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
Dropping `foo` will not trigger an assertion (but dropping a `Box` would).
|
||||
|
||||
`assert_no_alloc()` calls can be nested, with proper panic unwinding handling.
|
||||
|
||||
Note that to fully bypass this crate, e.g. when in release mode, you need to
|
||||
*both* have the `disable_release` feature flag enabled (which it is by default)
|
||||
and to not register `AllocDisabler` as `global_allocator`.
|
||||
|
||||
Optional features
|
||||
-----------------
|
||||
|
||||
These compile time features are not enabled by default:
|
||||
|
||||
- `backtrace` causes a backtrace to be printed before the allocation failure.
|
||||
This backtrace is gathered at runtime, and its accuracy depends on the
|
||||
platform and the compilation options used.
|
||||
- `log` uses the `log` crate to write the allocation failure message to the
|
||||
configured logger. If the `backtrace` feature is also enabled, then the
|
||||
backtrace will also be written to the logger This can be useful when using a
|
||||
logger that writes directly to a file or any other place that isn't STDERR.
|
||||
|
||||
The main caveat here is that if the allocation was caused by the logger and if
|
||||
the logger wraps its entire log function in a regular non-entrant mutex, then
|
||||
this may result in a deadlock. Make sure your logger doesn't do this before
|
||||
enabling this feature.
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
See [examples/main.rs](https://github.com/Windfisch/rust-assert-no-alloc/blob/master/examples/main.rs) for an example.
|
||||
|
||||
You can try out the different feature flags:
|
||||
|
||||
- `cargo run --example main` -> memory allocation of 4 bytes failed. Aborted (core dumped)
|
||||
- `cargo run --example main --release --no-default-features` -> same as above.
|
||||
- `cargo run --example main --features=warn_debug` -> Tried to (de)allocate memory in a thread that forbids allocator calls! This will not be executed if the above allocation has aborted.
|
||||
- `cargo run --example main --features=warn_release --release --no-default-features` -> same as above.
|
||||
- `cargo run --example main --release` will not even check for forbidden allocations
|
||||
|
||||
Test suite
|
||||
----------
|
||||
|
||||
The tests will fail to compile with the default features. Run them using:
|
||||
|
||||
```
|
||||
cargo test --features=warn_debug --tests
|
||||
```
|
34
rust/assert-no-alloc/examples/main.rs
Normal file
34
rust/assert-no-alloc/examples/main.rs
Normal file
@ -0,0 +1,34 @@
|
||||
use assert_no_alloc::*;
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
#[global_allocator]
|
||||
static A: AllocDisabler = AllocDisabler;
|
||||
|
||||
fn main() {
|
||||
println!("Alloc is allowed. Let's allocate some memory...");
|
||||
let mut vec_can_push = Vec::new();
|
||||
vec_can_push.push(42);
|
||||
|
||||
println!();
|
||||
|
||||
let fib5 = assert_no_alloc(|| {
|
||||
println!("Alloc is forbidden. Let's calculate something without memory allocations...");
|
||||
|
||||
fn fib(n: u32) -> u32 {
|
||||
if n<=1 { 1 }
|
||||
else { fib(n-1) + fib(n-2) }
|
||||
}
|
||||
|
||||
fib(5)
|
||||
});
|
||||
println!("\tSuccess, the 5th fibonacci number is {}", fib5);
|
||||
println!();
|
||||
|
||||
assert_no_alloc(|| {
|
||||
println!("Alloc is forbidden. Let's allocate some memory...");
|
||||
let mut vec_cannot_push = Vec::new();
|
||||
vec_cannot_push.push(42); // panics
|
||||
});
|
||||
|
||||
println!("This will not be executed if the above allocation has aborted.");
|
||||
}
|
249
rust/assert-no-alloc/src/lib.rs
Normal file
249
rust/assert-no-alloc/src/lib.rs
Normal file
@ -0,0 +1,249 @@
|
||||
/* assert_no_alloc -- A custom Rust allocator allowing to temporarily disable
|
||||
* memory (de)allocations for a thread.
|
||||
*
|
||||
* Copyright (c) 2020 Florian Jung <flo@windfis.ch>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED
|
||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
||||
* EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
|
||||
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
||||
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#![doc = include_str!("../README.md")]
|
||||
|
||||
use std::alloc::{System,GlobalAlloc,Layout};
|
||||
use std::cell::Cell;
|
||||
|
||||
// check for mutually exclusive features.
|
||||
#[cfg(all(feature = "disable_release", feature = "warn_release"))]
|
||||
compile_error!("disable_release cannot be active at the same time with warn_release");
|
||||
|
||||
|
||||
#[cfg(not(all(feature = "disable_release", not(debug_assertions))))] // if not disabled
|
||||
thread_local! {
|
||||
static ALLOC_FORBID_COUNT: Cell<u32> = Cell::new(0);
|
||||
static ALLOC_PERMIT_COUNT: Cell<u32> = Cell::new(0);
|
||||
|
||||
#[cfg(any( all(feature="warn_debug", debug_assertions), all(feature="warn_release", not(debug_assertions)) ))]
|
||||
static ALLOC_VIOLATION_COUNT: Cell<u32> = Cell::new(0);
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "disable_release", not(debug_assertions)))] // if disabled
|
||||
pub fn assert_no_alloc<T, F: FnOnce() -> T> (func: F) -> T { // no-op
|
||||
func()
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "disable_release", not(debug_assertions)))] // if disabled
|
||||
pub fn permit_alloc<T, F: FnOnce() -> T> (func: F) -> T { // no-op
|
||||
func()
|
||||
}
|
||||
|
||||
#[cfg(not(all(feature = "disable_release", not(debug_assertions))))] // if not disabled
|
||||
/// Calls the `func` closure, but forbids any (de)allocations.
|
||||
///
|
||||
/// If a call to the allocator is made, the program will abort with an error,
|
||||
/// print a warning (depending on the `warn_debug` feature flag. Or ignore
|
||||
/// the situation, when compiled in `--release` mode with the `disable_release`
|
||||
///feature flag set (which is the default)).
|
||||
pub fn assert_no_alloc<T, F: FnOnce() -> T> (func: F) -> T {
|
||||
// RAII guard for managing the forbid counter. This is to ensure correct behaviour
|
||||
// when catch_unwind is used
|
||||
struct Guard;
|
||||
impl Guard {
|
||||
fn new() -> Guard {
|
||||
ALLOC_FORBID_COUNT.with(|c| c.set(c.get()+1));
|
||||
Guard
|
||||
}
|
||||
}
|
||||
impl Drop for Guard {
|
||||
fn drop(&mut self) {
|
||||
ALLOC_FORBID_COUNT.with(|c| c.set(c.get()-1));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(any( all(feature="warn_debug", debug_assertions), all(feature="warn_release", not(debug_assertions)) ))] // if warn mode is selected
|
||||
let old_violation_count = violation_count();
|
||||
|
||||
|
||||
let guard = Guard::new(); // increment the forbid counter
|
||||
let ret = func();
|
||||
std::mem::drop(guard); // decrement the forbid counter
|
||||
|
||||
|
||||
#[cfg(any( all(feature="warn_debug", debug_assertions), all(feature="warn_release", not(debug_assertions)) ))] // if warn mode is selected
|
||||
if violation_count() > old_violation_count {
|
||||
eprintln!("Tried to (de)allocate memory in a thread that forbids allocator calls!");
|
||||
}
|
||||
|
||||
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.
|
||||
pub fn permit_alloc<T, F: FnOnce() -> T> (func: F) -> T {
|
||||
// RAII guard for managing the permit counter
|
||||
struct Guard;
|
||||
impl Guard {
|
||||
fn new() -> Guard {
|
||||
ALLOC_PERMIT_COUNT.with(|c| c.set(c.get()+1));
|
||||
Guard
|
||||
}
|
||||
}
|
||||
impl Drop for Guard {
|
||||
fn drop(&mut self) {
|
||||
ALLOC_PERMIT_COUNT.with(|c| c.set(c.get()-1));
|
||||
}
|
||||
}
|
||||
|
||||
let guard = Guard::new(); // increment the forbid counter
|
||||
let ret = func();
|
||||
std::mem::drop(guard); // decrement the forbid counter
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#[cfg(any( all(feature="warn_debug", debug_assertions), all(feature="warn_release", not(debug_assertions)) ))] // if warn mode is selected
|
||||
/// Returns the count of allocation warnings emitted so far.
|
||||
///
|
||||
/// Only available when the `warn_debug` or `warn release` features are enabled.
|
||||
pub fn violation_count() -> u32 {
|
||||
ALLOC_VIOLATION_COUNT.with(|c| c.get())
|
||||
}
|
||||
|
||||
#[cfg(any( all(feature="warn_debug", debug_assertions), all(feature="warn_release", not(debug_assertions)) ))] // if warn mode is selected
|
||||
/// Resets the count of allocation warnings to zero.
|
||||
///
|
||||
/// Only available when the `warn_debug` or `warn release` features are enabled.
|
||||
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));
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[cfg(not(all(feature = "disable_release", not(debug_assertions))))] // if not disabled
|
||||
/// The custom allocator that handles the checking.
|
||||
///
|
||||
/// To use this crate, you must add the following in your `main.rs`:
|
||||
/// ```rust
|
||||
/// use assert_no_alloc::*;
|
||||
/// // ...
|
||||
/// #[cfg(debug_assertions)]
|
||||
/// #[global_allocator]
|
||||
/// static A: AllocDisabler = AllocDisabler;
|
||||
/// ```
|
||||
pub struct AllocDisabler;
|
||||
|
||||
#[cfg(not(all(feature = "disable_release", not(debug_assertions))))] // if not disabled
|
||||
impl AllocDisabler {
|
||||
fn check(&self, layout: Layout) {
|
||||
let forbid_count = ALLOC_FORBID_COUNT.with(|f| f.get());
|
||||
let permit_count = ALLOC_PERMIT_COUNT.with(|p| p.get());
|
||||
if forbid_count > 0 && permit_count == 0 {
|
||||
#[cfg(any( all(feature="warn_debug", debug_assertions), all(feature="warn_release", not(debug_assertions)) ))] // if warn mode is selected
|
||||
ALLOC_VIOLATION_COUNT.with(|c| c.set(c.get()+1));
|
||||
|
||||
#[cfg(any( all(not(feature="warn_debug"), debug_assertions), all(not(feature="warn_release"), not(debug_assertions)) ))] // if abort mode is selected
|
||||
{
|
||||
#[cfg(all(feature = "log", feature = "backtrace"))]
|
||||
permit_alloc(|| log::error!("Memory allocation of {} bytes failed from:\n{:?}", layout.size(), backtrace::Backtrace::new()));
|
||||
#[cfg(all(feature = "log", not(feature = "backtrace")))]
|
||||
permit_alloc(|| log::error!("Memory allocation of {} bytes failed", layout.size()));
|
||||
|
||||
#[cfg(all(not(feature = "log"), feature = "backtrace"))]
|
||||
permit_alloc(|| eprintln!("Allocation failure from:\n{:?}", backtrace::Backtrace::new()));
|
||||
|
||||
// This handler can be overridden (although as of writing, the API to do so is still
|
||||
// unstable) so we must always call this even when the log feature is enabled
|
||||
std::alloc::handle_alloc_error(layout);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(all(feature = "disable_release", not(debug_assertions))))] // if not disabled
|
||||
unsafe impl GlobalAlloc for AllocDisabler {
|
||||
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
|
||||
self.check(layout);
|
||||
System.alloc(layout)
|
||||
}
|
||||
|
||||
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
|
||||
self.check(layout);
|
||||
System.dealloc(ptr, layout)
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapper for objects whose Drop implementation shall be permitted
|
||||
/// to (de)allocate.
|
||||
///
|
||||
/// Typical usage:
|
||||
///
|
||||
/// ```rust
|
||||
/// let foo = PermitDrop::new(
|
||||
/// permit_alloc(||
|
||||
/// Box::new(...)
|
||||
/// )
|
||||
/// );
|
||||
/// ```
|
||||
///
|
||||
/// Here, creation of the Box is guarded by the explicit `permit_alloc` call,
|
||||
/// and destruction of the Box is guarded by PermitDrop. Neither creation nor
|
||||
/// destruction will cause an assertion failure from within `assert_no_alloc`.
|
||||
pub struct PermitDrop<T>(Option<T>);
|
||||
|
||||
impl<T> PermitDrop<T> {
|
||||
pub fn new(t: T) -> PermitDrop<T> {
|
||||
permit_alloc(|| {
|
||||
PermitDrop(Some(t))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> std::ops::Deref for PermitDrop<T> {
|
||||
type Target = T;
|
||||
fn deref(&self) -> &T { self.0.as_ref().unwrap() }
|
||||
}
|
||||
|
||||
impl<T> std::ops::DerefMut for PermitDrop<T> {
|
||||
fn deref_mut(&mut self) -> &mut T { self.0.as_mut().unwrap() }
|
||||
}
|
||||
|
||||
impl<I: Iterator> Iterator for PermitDrop<I> {
|
||||
type Item = I::Item;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
(**self).next()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl<T> Drop for PermitDrop<T> {
|
||||
fn drop(&mut self) {
|
||||
let mut tmp = None;
|
||||
std::mem::swap(&mut tmp, &mut self.0);
|
||||
permit_alloc(|| {
|
||||
std::mem::drop(tmp);
|
||||
});
|
||||
}
|
||||
}
|
148
rust/assert-no-alloc/tests/test.rs
Normal file
148
rust/assert-no-alloc/tests/test.rs
Normal file
@ -0,0 +1,148 @@
|
||||
use assert_no_alloc::*;
|
||||
use std::panic::catch_unwind;
|
||||
|
||||
#[global_allocator]
|
||||
static A: AllocDisabler = AllocDisabler;
|
||||
|
||||
#[cfg(not(feature = "warn_debug"))]
|
||||
compile_error!("The test suite requires the warn_debug feature to be enabled. Use `cargo test --features warn_debug`");
|
||||
|
||||
// This is only a kludge; what we actually want to check is "will do_alloc() be optimized out?", e.g. due to
|
||||
// compiler optimizations turned on in --release mode. We can't do that, the closest we can get is to check
|
||||
// whether debug_assertions are disabled, which coincidentially also happens in release mode.
|
||||
#[cfg(not(debug_assertions))]
|
||||
compile_error!("The test suite only works in debug mode. Use `cargo test --features warn_debug`");
|
||||
|
||||
#[cfg(feature = "warn_debug")]
|
||||
fn check_and_reset() -> bool {
|
||||
let result = violation_count() > 0;
|
||||
reset_violation_count();
|
||||
result
|
||||
}
|
||||
|
||||
// Provide a stub check_and_reset() function if warn_debug is disabled. This will never be compiled due to the
|
||||
// compile_error!() above, but this stub ensures that the output will not be cluttered with spurious error
|
||||
// messages.
|
||||
#[cfg(not(feature = "warn_debug"))]
|
||||
fn check_and_reset() -> bool { unreachable!() }
|
||||
|
||||
fn do_alloc() {
|
||||
let _tmp: Box<u32> = Box::new(42);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ok_noop() {
|
||||
assert_eq!(check_and_reset(), false);
|
||||
do_alloc();
|
||||
assert_eq!(check_and_reset(), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ok_simple() {
|
||||
assert_eq!(check_and_reset(), false);
|
||||
assert_no_alloc(|| {
|
||||
});
|
||||
|
||||
do_alloc();
|
||||
assert_eq!(check_and_reset(), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ok_nested() {
|
||||
assert_eq!(check_and_reset(), false);
|
||||
assert_no_alloc(|| {
|
||||
assert_no_alloc(|| {
|
||||
});
|
||||
});
|
||||
|
||||
do_alloc();
|
||||
assert_eq!(check_and_reset(), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn forbidden_simple() {
|
||||
assert_eq!(check_and_reset(), false);
|
||||
assert_no_alloc(|| {
|
||||
do_alloc();
|
||||
});
|
||||
assert_eq!(check_and_reset(), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn forbidden_in_nested() {
|
||||
assert_eq!(check_and_reset(), false);
|
||||
assert_no_alloc(|| {
|
||||
assert_no_alloc(|| {
|
||||
do_alloc();
|
||||
});
|
||||
});
|
||||
assert_eq!(check_and_reset(), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn forbidden_after_nested() {
|
||||
assert_eq!(check_and_reset(), false);
|
||||
assert_no_alloc(|| {
|
||||
assert_no_alloc(|| {
|
||||
});
|
||||
do_alloc();
|
||||
});
|
||||
assert_eq!(check_and_reset(), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unwind_ok() {
|
||||
assert_eq!(check_and_reset(), false);
|
||||
assert_no_alloc(|| {
|
||||
let r = catch_unwind(|| {
|
||||
assert_no_alloc(|| {
|
||||
panic!();
|
||||
});
|
||||
});
|
||||
assert!(r.is_err());
|
||||
});
|
||||
check_and_reset(); // unwinding might have allocated memory; we don't care about that.
|
||||
do_alloc();
|
||||
assert_eq!(check_and_reset(), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unwind_nested() {
|
||||
assert_eq!(check_and_reset(), false);
|
||||
assert_no_alloc(|| {
|
||||
let r = catch_unwind(|| {
|
||||
assert_no_alloc(|| {
|
||||
panic!();
|
||||
});
|
||||
});
|
||||
assert!(r.is_err());
|
||||
|
||||
check_and_reset(); // unwinding might have allocated memory; we don't care about that.
|
||||
do_alloc();
|
||||
assert_eq!(check_and_reset(), true);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unwind_nested2() {
|
||||
assert_eq!(check_and_reset(), false);
|
||||
assert_no_alloc(|| {
|
||||
assert_no_alloc(|| {
|
||||
let r = catch_unwind(|| {
|
||||
assert_no_alloc(|| {
|
||||
assert_no_alloc(|| {
|
||||
panic!();
|
||||
});
|
||||
});
|
||||
});
|
||||
assert!(r.is_err());
|
||||
|
||||
check_and_reset(); // unwinding might have allocated memory; we don't care about that.
|
||||
do_alloc();
|
||||
assert_eq!(check_and_reset(), true);
|
||||
});
|
||||
});
|
||||
check_and_reset(); // unwinding might have allocated memory; we don't care about that.
|
||||
do_alloc();
|
||||
assert_eq!(check_and_reset(), false);
|
||||
}
|
Loading…
Reference in New Issue
Block a user