diff --git a/Cargo.toml b/Cargo.toml index f28d28f..81254b1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,3 +31,7 @@ pest_derive = "2.8.1" llvm-sys = "201.0.1" docopt = "1.1.1" signal-hook = "0.3.18" + +[features] +default = [] +nightly = [] diff --git a/Containerfile b/Containerfile index 7c5a9ab..104a58b 100644 --- a/Containerfile +++ b/Containerfile @@ -1,10 +1,8 @@ FROM registry.fedoraproject.org/fedora:43 as builder +ARG RUST_VERSION=stable + RUN dnf install -y \ - rust \ - cargo \ - clippy \ - rustfmt \ # for stub \ nasm \ # for script jit \ @@ -15,7 +13,19 @@ RUN dnf install -y \ # for bpf \ clang \ kernel-devel \ - libbpf-devel + libbpf-devel && \ + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | \ + sh -s -- -y --default-toolchain $RUST_VERSION --profile minimal + +# $HOME is not evaluated here, any better way of getting the toolchain +# directory? +ENV PATH="${PATH}:/root/.cargo/bin" + +RUN rustup component add rustfmt clippy + +RUN if [ "${RUST_VERSION}" == "nightly" ]; then \ + rustup component add rust-src --toolchain nightly; \ + fi ADD ./ /berserker/ @@ -32,7 +42,12 @@ RUN cargo build -r # Test will require stub binary to be available ENV PATH="${PATH}:/berserker:/berserker/target/release" -RUN cargo test +RUN if [ "${RUST_VERSION}" == "nightly" ]; then \ + TARGET=$(rustc --version --verbose | grep host | cut -d" " -f2) && \ + RUSTFLAGS="-Z sanitizer=address" cargo +nightly test -Z build-std --target "$TARGET"; \ + else \ + cargo test; \ + fi FROM registry.fedoraproject.org/fedora:43 diff --git a/Makefile b/Makefile index 679fb90..8f4408f 100644 --- a/Makefile +++ b/Makefile @@ -7,6 +7,7 @@ endif .PHONY: all all: docker build -t berserker -f Containerfile . + docker build -t berserker -f Containerfile --build-arg=RUST_VERSION=nightly . docker build -t berserker-test -f Containerfile.test . docker run --privileged berserker-test diff --git a/src/lib.rs b/src/lib.rs index 69d4808..0f33f41 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,5 @@ +#![cfg_attr(feature = "nightly", feature(sanitize))] + use core_affinity::CoreId; use serde::{Deserialize, Deserializer}; use std::{collections::HashMap, fmt::Display, net::Ipv4Addr, str::FromStr}; @@ -28,7 +30,8 @@ pub struct WorkloadConfig { /// Custom workload configuration. pub workload: Workload, - /// For how long to run the worker. Default value is zero, meaning no limit. + /// For how long to run the worker. Default value is zero, meaning no + /// limit. #[serde(default = "default_duration")] pub duration: u64, } @@ -194,8 +197,8 @@ pub enum Workload { /// Maximum number of dynamic connections connections_dyn_max: u32, - // How many connections to make to the same server address and port with - // different client ports + /// How many connections to make to the same server address and port + /// with different client ports #[serde(default = "default_conns_per_addr")] conns_per_addr: u16, @@ -211,7 +214,8 @@ pub enum Workload { /// Whether or not to wait for a connection to be removed before adding /// a new one, when the dynamic connection limit is reached. /// if true: an old connection will be forcibly removed - /// if false: wait for a connection to naturally age-off before adding a new one + /// if false: wait for a connection to naturally age-off before adding + /// a new one preempt: bool, }, diff --git a/src/main.rs b/src/main.rs index b9c731c..a4ccd9f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -204,7 +204,8 @@ fn main() { .required(false), ) // Add in settings from the environment (with a prefix of APP) - // Eg.. `BERSERKER__WORKLOAD__ARRIVAL_RATE=1` would set the `arrival_rate` key + // Eg.. `BERSERKER__WORKLOAD__ARRIVAL_RATE=1` would set the + // `arrival_rate` key .add_source( config::Environment::with_prefix("BERSERKER") .try_parsing(true) @@ -230,7 +231,8 @@ fn main() { let elapsed = duration_timer.elapsed().unwrap().as_secs(); // Ignore processes without specified duration -- we don't want - // neither terminate them, nor count against processes to compare. + // neither terminate them, nor count against processes to + // compare. let watched_processes = processes .iter() .filter(|(_, duration)| *duration > 0) diff --git a/src/worker/script.rs b/src/worker/script.rs index bcd1ac8..3ded8e7 100644 --- a/src/worker/script.rs +++ b/src/worker/script.rs @@ -1,6 +1,7 @@ extern crate llvm_sys as llvm; use std::{ + cell::RefCell, collections::HashMap, fmt::Display, fs::OpenOptions, @@ -144,7 +145,24 @@ pub unsafe extern "C" fn random_path(base: *const i8) -> *const i8 { .map(char::from) .collect(); - CString::new(format!("{base}/{uniq}")).unwrap().into_raw() + let result = CString::new(format!("{base}/{uniq}")).unwrap().into_raw(); + POINTERS.with(|ps| ps.borrow_mut().push(result)); + result +} + +/// # Safety +#[unsafe(no_mangle)] +pub unsafe extern "C" fn cleanup(_: *const i8) -> u64 { + POINTERS.with(|ps| { + let mut vec = ps.borrow_mut(); + for p in vec.as_slice() { + let _ = unsafe { CString::from_raw(*p) }; + } + + vec.clear(); + }); + + 0 } #[derive(Debug, Clone)] @@ -155,6 +173,10 @@ pub struct RuntimeFunc { return_type: RuntimeType, } +thread_local! { + static POINTERS: RefCell> = const { RefCell::new(vec![]) }; +} + /// Functions, available in a script at runtime pub static RUNTIME: LazyLock> = LazyLock::new(|| { @@ -206,14 +228,27 @@ pub static RUNTIME: LazyLock> = return_type: RuntimeType::Pointer, }, ), + // utils + ( + "cleanup".to_string(), + RuntimeFunc { + func: cleanup as *const () as usize, + param_count: 1, + param_types: &[RuntimeType::Pointer], + return_type: RuntimeType::Int, + }, + ), ]) }); impl ScriptWorker { - fn jit_instruction(name: String, arg: Arg, ctx: &BuildContext) { + fn jit_instruction(name: &CStr, arg: Arg, ctx: &BuildContext) { let mut arg_ptr = Self::get_arg_value(arg, ctx); - let (func, func_type) = ctx.module_runtime.get(&name).unwrap(); + let (func, func_type) = ctx + .module_runtime + .get(name.to_str().expect("Couldn't convert name to string")) + .unwrap(); unsafe { LLVMBuildCall2( @@ -222,7 +257,7 @@ impl ScriptWorker { *func, &mut arg_ptr, 1, - name.as_str().as_ptr() as *const _, + name.as_ptr() as *const _, ); } } @@ -320,7 +355,8 @@ impl ScriptWorker { &mut err, ) != 0 { - // In case of error, we must avoid using the uninitialized ExecutionEngineRef. + // In case of error, we must avoid using the uninitialized + // ExecutionEngineRef. assert!(!err.is_null()); panic!( "Failed to create execution engine: {:?}", @@ -385,8 +421,8 @@ impl ScriptWorker { function_type, ); - // Create a basic block in the function and set our builder to generate - // code in it. + // Create a basic block in the function and set our builder to + // generate code in it. let bb = LLVMAppendBasicBlockInContext( context, function, @@ -425,30 +461,22 @@ impl ScriptWorker { // JIT the instruction and collect it's name let name = match instr.clone() { Instruction::Task { name, args: _ } => { - Self::jit_instruction(String::from("task"), name, &ctx); + Self::jit_instruction(c"task", name, &ctx); "task" } Instruction::Open { path } => { - Self::jit_instruction(String::from("open"), path, &ctx); + Self::jit_instruction(c"open", path, &ctx); "open" } Instruction::Ping { server } => { - Self::jit_instruction( - String::from("ping"), - server, - &ctx, - ); + Self::jit_instruction(c"ping", server, &ctx); "ping" } Instruction::Debug { text } => { - Self::jit_instruction( - String::from("debug"), - text, - &ctx, - ); + Self::jit_instruction(c"debug", text, &ctx); "debug" } }; @@ -472,6 +500,30 @@ impl ScriptWorker { ); } + // Final instruction to clear dangling pointers + Self::jit_instruction( + c"cleanup", + Arg::Const { + text: "".to_string(), + }, + &ctx, + ); + + let module_func = LLVMGetNamedFunction( + module, + c"cleanup".to_bytes().as_ptr() as *const _, + ); + + let runtime_func = &RUNTIME + .get("cleanup") + .expect("No runtime function with the name"); + + LLVMAddGlobalMapping( + ee, + module_func, + runtime_func.func as *mut c_void, + ); + // Emit a `ret i64` into the function to return the computed sum. let ret = LLVMConstInt(i64t, 0, 0); LLVMBuildRet(builder, ret); diff --git a/src/worker/syscalls/ioctl.rs b/src/worker/syscalls/ioctl.rs index fcb0fc8..650e828 100644 --- a/src/worker/syscalls/ioctl.rs +++ b/src/worker/syscalls/ioctl.rs @@ -14,8 +14,8 @@ pub struct IoctlCall { impl IoctlCall { pub fn new(_: &ArgsMap) -> Self { - // Zero initialize all fields, fd will be initialized in `Syscaller::init`. - // All other fields can be overridden as needed + // Zero initialize all fields, fd will be initialized in + // `Syscaller::init`. All other fields can be overridden as needed Default::default() } }