Skip to content

feat: support JIT in no_std mode #125

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jun 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ include = [
# Default features (std) are disabled so that the dependencies don't pull in the
# standard library when the crate is compiled for no_std
byteorder = { version = "1.2", default-features = false }
log = {version = "0.4.21", default-features = false }
log = { version = "0.4.21", default-features = false }
combine = { version = "4.6", default-features = false }

# Optional Dependencies when using the standard library
Expand All @@ -41,9 +41,10 @@ cranelift-frontend = { version = "0.99", optional = true }
cranelift-jit = { version = "0.99", optional = true }
cranelift-native = { version = "0.99", optional = true }
cranelift-module = { version = "0.99", optional = true }
hashbrown = { version = "0.15", default-features = false, features = ["default-hasher"] }

[dev-dependencies]

libc = { version = "0.2" }
elf = "0.0.10"
json = "0.11"
hex = "0.4.3"
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ useful for programs that should be compatible with the Linux kernel and
therefore must use specific helper numbers.

```rust,ignore
pub fn register_allowed_memory(&mut self,, addr: &[u64]) -> ()
pub fn register_allowed_memory(&mut self, addrs_range: Range<u64>) -> ()
```

This function adds a list of memory addresses that the eBPF program is allowed
Expand Down
5 changes: 2 additions & 3 deletions examples/allowed_memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// Copyright 2024 Akenes SA <[email protected]>

extern crate elf;
use std::{iter::FromIterator, ptr::addr_of};
use std::{ptr::addr_of};

extern crate rbpf;

Expand Down Expand Up @@ -40,8 +40,7 @@ fn main() {
.unwrap();

let start = addr_of!(MAP_VALUE) as u64;
let addrs = Vec::from_iter(start..start + size_of::<Value>() as u64);
vm.register_allowed_memory(&addrs);
vm.register_allowed_memory(start..start + size_of::<Value>() as u64);

let res = vm.execute_program().unwrap();
assert_eq!(res, 1);
Expand Down
7 changes: 4 additions & 3 deletions src/interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use crate::ebpf;
use crate::ebpf::MAX_CALL_DEPTH;
use crate::lib::*;
use crate::stack::{StackFrame, StackUsage};
use core::ops::Range;

#[allow(clippy::too_many_arguments)]
fn check_mem(
Expand All @@ -19,7 +20,7 @@ fn check_mem(
mbuff: &[u8],
mem: &[u8],
stack: &[u8],
allowed_memory: &HashSet<u64>,
allowed_memory: &HashSet<Range<u64>>,
) -> Result<(), Error> {
if let Some(addr_end) = addr.checked_add(len as u64) {
if mbuff.as_ptr() as u64 <= addr && addr_end <= mbuff.as_ptr() as u64 + mbuff.len() as u64 {
Expand All @@ -31,7 +32,7 @@ fn check_mem(
if stack.as_ptr() as u64 <= addr && addr_end <= stack.as_ptr() as u64 + stack.len() as u64 {
return Ok(());
}
if allowed_memory.contains(&addr) {
if allowed_memory.iter().any(|range| range.contains(&addr)) {
return Ok(());
}
}
Expand All @@ -51,7 +52,7 @@ pub fn execute_program(
mem: &[u8],
mbuff: &[u8],
helpers: &HashMap<u32, ebpf::Helper>,
allowed_memory: &HashSet<u64>,
allowed_memory: &HashSet<Range<u64>>,
) -> Result<u64, Error> {
const U32MAX: u64 = u32::MAX as u64;
const SHIFT_MASK_64: u64 = 0x3f;
Expand Down
69 changes: 50 additions & 19 deletions src/jit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,12 @@

#![allow(clippy::single_match)]

use std::alloc;
use std::collections::HashMap;
use std::fmt::Error as FormatterError;
use std::fmt::Formatter;
use std::io::{Error, ErrorKind};
use std::mem;
use std::ops::{Index, IndexMut};
use std::ptr;

use crate::ebpf;

extern crate libc;
use crate::{ebpf, format, vec, Error, ErrorKind, HashMap, Vec};
use core::fmt::Error as FormatterError;
use core::fmt::Formatter;
use core::mem;
use core::ops::{Index, IndexMut};
use core::ptr;

type MachineCode = unsafe fn(*mut u8, usize, *mut u8, usize, usize, usize) -> u64;

Expand Down Expand Up @@ -1001,11 +995,11 @@ impl JitCompiler {

// Assumes jump offset is at end of instruction
unsafe {
let offset_loc = jump.offset_loc as i32 + std::mem::size_of::<i32>() as i32;
let offset_loc = jump.offset_loc as i32 + core::mem::size_of::<i32>() as i32;
let rel = &(target_loc as i32 - offset_loc) as *const i32;

let offset_ptr = mem.contents.as_ptr().add(jump.offset_loc) as *mut u8;
ptr::copy_nonoverlapping(rel.cast::<u8>(), offset_ptr, std::mem::size_of::<i32>());
ptr::copy_nonoverlapping(rel.cast::<u8>(), offset_ptr, core::mem::size_of::<i32>());
}
}
Ok(())
Expand All @@ -1014,11 +1008,13 @@ impl JitCompiler {

pub struct JitMemory<'a> {
contents: &'a mut [u8],
layout: alloc::Layout,
#[cfg(feature = "std")]
layout: std::alloc::Layout,
offset: usize,
}

impl<'a> JitMemory<'a> {
#[cfg(feature = "std")]
pub fn new(
prog: &[u8],
helpers: &HashMap<u32, ebpf::Helper>,
Expand All @@ -1031,10 +1027,10 @@ impl<'a> JitMemory<'a> {
let contents = unsafe {
// Create a layout with the proper size and alignment.
let size = NUM_PAGES * PAGE_SIZE;
layout = alloc::Layout::from_size_align_unchecked(size, PAGE_SIZE);
layout = std::alloc::Layout::from_size_align_unchecked(size, PAGE_SIZE);

// Allocate the region of memory.
let ptr = alloc::alloc(layout);
let ptr = std::alloc::alloc(layout);
if ptr.is_null() {
return Err(Error::from(std::io::ErrorKind::OutOfMemory));
}
Expand All @@ -1059,6 +1055,40 @@ impl<'a> JitMemory<'a> {
Ok(mem)
}

#[cfg(not(feature = "std"))]
pub fn new(
prog: &[u8],
executable_memory: &'a mut [u8],
helpers: &HashMap<u32, ebpf::Helper>,
use_mbuff: bool,
update_data_ptr: bool,
) -> Result<JitMemory<'a>, Error> {
let contents = executable_memory;
if contents.len() < NUM_PAGES * PAGE_SIZE {
return Err(Error::new(
ErrorKind::Other,
"Executable memory is too small",
));
}
if contents.as_ptr() as usize % PAGE_SIZE != 0 {
return Err(Error::new(
ErrorKind::Other,
"Executable memory is not aligned",
));
}

let mut mem = JitMemory {
contents,
offset: 0,
};

let mut jit = JitCompiler::new();
jit.jit_compile(&mut mem, prog, use_mbuff, update_data_ptr, helpers)?;
jit.resolve_jumps(&mut mem)?;

Ok(mem)
}

pub fn get_prog(&self) -> MachineCode {
unsafe { mem::transmute(self.contents.as_ptr()) }
}
Expand All @@ -1078,15 +1108,16 @@ impl IndexMut<usize> for JitMemory<'_> {
}
}

#[cfg(feature = "std")]
impl Drop for JitMemory<'_> {
fn drop(&mut self) {
unsafe {
alloc::dealloc(self.contents.as_mut_ptr(), self.layout);
std::alloc::dealloc(self.contents.as_mut_ptr(), self.layout);
}
}
}

impl std::fmt::Debug for JitMemory<'_> {
impl core::fmt::Debug for JitMemory<'_> {
fn fmt(&self, fmt: &mut Formatter) -> Result<(), FormatterError> {
fmt.write_str("JIT contents: [")?;
fmt.write_str(" ] | ")?;
Expand Down
Loading
Loading