use crate::{
aligned_memory::Pod,
ebpf,
error::{EbpfError, ProgramResult, StableResult},
program::SBPFVersion,
vm::Config,
};
use std::fmt::Formatter;
use std::{array, cell::UnsafeCell, fmt, mem, ops::Range, ptr};
pub type AccessViolationHandler = Box<dyn Fn(&mut MemoryRegion, u64, AccessType, u64, u64)>;
#[allow(clippy::result_unit_err)]
pub fn default_access_violation_handler(
_region: &mut MemoryRegion,
_region_max_len: u64,
_access_type: AccessType,
_vm_addr: u64,
_len: u64,
) {
}
pub unsafe trait HostMemoryObject {
fn host(self) -> HostBuffer;
}
pub trait VmExposable {}
pub unsafe trait VmExposableMut {}
unsafe impl VmExposableMut for u8 {}
impl<T: VmExposableMut> VmExposable for T {}
unsafe impl<T: VmExposable> HostMemoryObject for *const T {
fn host(self) -> HostBuffer {
HostBuffer::Immutable(ptr::slice_from_raw_parts(
self.cast(),
std::mem::size_of::<T>(),
))
}
}
unsafe impl<T: VmExposableMut> HostMemoryObject for *mut T {
fn host(self) -> HostBuffer {
HostBuffer::Mutable(ptr::slice_from_raw_parts_mut(
self.cast(),
std::mem::size_of::<T>(),
))
}
}
unsafe impl<T: VmExposable> HostMemoryObject for *const [T] {
fn host(self) -> HostBuffer {
HostBuffer::Immutable(ptr::slice_from_raw_parts(
self.cast(),
self.len().checked_mul(core::mem::size_of::<T>()).unwrap(),
))
}
}
unsafe impl<T: VmExposableMut> HostMemoryObject for *mut [T] {
fn host(self) -> HostBuffer {
HostBuffer::Mutable(ptr::slice_from_raw_parts_mut(
self.cast(),
self.len().checked_mul(core::mem::size_of::<T>()).unwrap(),
))
}
}
unsafe impl<T: VmExposable, const N: usize> HostMemoryObject for *const [T; N] {
fn host(self) -> HostBuffer {
HostBuffer::Immutable(ptr::slice_from_raw_parts(
self.cast(),
N.checked_mul(core::mem::size_of::<T>()).unwrap(),
))
}
}
unsafe impl<T: VmExposableMut, const N: usize> HostMemoryObject for *mut [T; N] {
fn host(self) -> HostBuffer {
HostBuffer::Mutable(ptr::slice_from_raw_parts_mut(
self.cast(),
N.checked_mul(core::mem::size_of::<T>()).unwrap(),
))
}
}
#[derive(PartialEq, Eq, Copy, Clone, Debug)]
pub enum HostBuffer {
Immutable(*const [u8]),
Mutable(*mut [u8]),
}
impl HostBuffer {
pub fn len(&self) -> usize {
match self {
HostBuffer::Immutable(p) => p.len(),
HostBuffer::Mutable(p) => p.len(),
}
}
pub fn is_empty(&self) -> bool {
match self {
HostBuffer::Immutable(p) => p.is_empty(),
HostBuffer::Mutable(p) => p.is_empty(),
}
}
pub fn is_mutable(&self) -> bool {
matches!(self, HostBuffer::Mutable(_))
}
pub unsafe fn mutable(self) -> Self {
match self {
HostBuffer::Immutable(p) => HostBuffer::Mutable(p.cast_mut()),
HostBuffer::Mutable(_) => self,
}
}
pub fn immutable(self) -> Self {
match self {
HostBuffer::Immutable(_) => self,
HostBuffer::Mutable(p) => Self::Immutable(p.cast_const()),
}
}
#[inline]
pub fn get(self, range: std::ops::Range<usize>) -> Option<Self> {
if range.end > self.len() {
return None;
}
let new_len = range.len();
unsafe {
Some(match self {
HostBuffer::Immutable(p) => HostBuffer::Immutable(ptr::slice_from_raw_parts(
p.byte_add(range.start).cast(),
new_len,
)),
HostBuffer::Mutable(p) => HostBuffer::Mutable(ptr::slice_from_raw_parts_mut(
p.byte_add(range.start).cast(),
new_len,
)),
})
}
}
#[inline(always)]
pub fn ptr(self) -> *const [u8] {
match self {
HostBuffer::Immutable(p) => p,
HostBuffer::Mutable(p) => p,
}
}
#[inline(always)]
pub fn ptr_mut(self) -> *mut [u8] {
match self {
HostBuffer::Immutable(p) => {
debug_assert!(false, "ptr_mut, but buffer is immutable");
p.cast_mut()
}
HostBuffer::Mutable(p) => p,
}
}
}
unsafe impl HostMemoryObject for HostBuffer {
fn host(self) -> HostBuffer {
self
}
}
#[derive(Eq, PartialEq, Clone)]
pub struct MemoryRegion {
host: HostBuffer,
vm_addr: u64,
vm_gap_shift: u8,
pub access_violation_handler_payload: Option<u16>,
}
impl MemoryRegion {
fn new_internal(host: HostBuffer, vm_addr: u64, vm_gap_size: u64) -> Self {
let mut vm_gap_shift = (std::mem::size_of::<u64>() as u8)
.saturating_mul(8)
.saturating_sub(1);
if vm_gap_size > 0 {
vm_gap_shift = vm_gap_shift.saturating_sub(vm_gap_size.leading_zeros() as u8);
debug_assert_eq!(Some(vm_gap_size), 1_u64.checked_shl(vm_gap_shift as u32));
};
MemoryRegion {
host,
vm_addr,
vm_gap_shift,
access_violation_handler_payload: None,
}
}
pub fn new_empty(vm_addr: u64) -> Self {
const EMPTY: &[u8] = &[];
Self::new_internal((&raw const *EMPTY).host(), vm_addr, 0)
}
pub fn new<HO: HostMemoryObject>(host: HO, vm_addr: u64) -> Self {
Self::new_internal(host.host(), vm_addr, 0)
}
pub fn new_gapped<HO: HostMemoryObject>(host: HO, vm_addr: u64, vm_gap_size: u64) -> Self {
Self::new_internal(host.host(), vm_addr, vm_gap_size)
}
pub unsafe fn redirect<HO: HostMemoryObject>(&mut self, host: HO) {
self.host = host.host();
}
pub fn make_immutable(&mut self) {
unsafe {
self.redirect(self.host_buffer().immutable());
}
}
pub fn vm_addr_range(&self) -> Range<u64> {
let bytes = self.len() as u64;
if self.vm_gap_shift == 63 {
self.vm_addr..self.vm_addr.saturating_add(bytes)
} else {
self.vm_addr..self.vm_addr.saturating_add(bytes.saturating_mul(2))
}
}
pub fn host_buffer(&self) -> HostBuffer {
self.host
}
pub fn len(&self) -> usize {
self.host.len()
}
pub fn is_empty(&self) -> bool {
self.host.is_empty()
}
pub fn gap_size(&self) -> u64 {
if self.vm_gap_shift == 63 {
0
} else {
1 << self.vm_gap_shift
}
}
#[inline]
pub(crate) fn vm_to_host_buffer(&self, vm_addr: u64, len: u64) -> Option<HostBuffer> {
if vm_addr < self.vm_addr {
return None;
}
let begin_offset = vm_addr.saturating_sub(self.vm_addr);
if self.vm_gap_shift == 63 {
if let Some(end_offset) = begin_offset.checked_add(len) {
return self.host.get(begin_offset as usize..end_offset as usize);
}
return None;
}
let is_in_gap = (begin_offset
.checked_shr(self.vm_gap_shift as u32)
.unwrap_or(0)
& 1)
== 1;
let gap_mask = (-1i64).checked_shl(self.vm_gap_shift as u32).unwrap_or(0) as u64;
let gapped_offset =
(begin_offset & gap_mask).checked_shr(1).unwrap_or(0) | (begin_offset & !gap_mask);
if let Some(end_offset) = gapped_offset.checked_add(len) {
if !is_in_gap {
return self.host.get(gapped_offset as usize..end_offset as usize);
}
}
None
}
}
impl fmt::Debug for MemoryRegion {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let vm_addr = self.vm_addr_range();
let (host_addr, len, writable) = match self.host {
HostBuffer::Immutable(p) => (p.addr() as u64, p.len() as u64, false),
HostBuffer::Mutable(p) => (p.addr() as u64, p.len() as u64, true),
};
write!(
f,
"host_addr: {:#x?}-{:#x?}, vm_addr: {:#x?}-{:#x?}, len: {}, writable: {}, payload {:?}",
host_addr,
host_addr.saturating_add(len),
vm_addr.start,
vm_addr.end,
len,
writable,
self.access_violation_handler_payload,
)
}
}
impl std::cmp::PartialOrd for MemoryRegion {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl std::cmp::Ord for MemoryRegion {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.vm_addr.cmp(&other.vm_addr)
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum AccessType {
Load,
Store,
}
impl std::fmt::Display for AccessType {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.write_str(match self {
Self::Load => "reading",
Self::Store => "writing",
})
}
}
pub struct UnalignedMemoryMapping {
regions: Box<[MemoryRegion]>,
region_addresses: Box<[u64]>,
region_index_lookup: Box<[usize]>,
cache: UnsafeCell<MappingCache>,
}
impl fmt::Debug for UnalignedMemoryMapping {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("UnalignedMemoryMapping")
.field("regions", &self.regions)
.field("cache", &self.cache)
.finish()
}
}
impl UnalignedMemoryMapping {
fn construct_eytzinger_order(&mut self, mut in_index: usize, out_index: usize) -> usize {
if out_index >= self.regions.len() {
return in_index;
}
in_index =
self.construct_eytzinger_order(in_index, out_index.saturating_mul(2).saturating_add(1));
self.region_addresses[out_index] = self.regions[in_index].vm_addr;
self.region_index_lookup[out_index] = in_index;
self.construct_eytzinger_order(
in_index.saturating_add(1),
out_index.saturating_mul(2).saturating_add(2),
)
}
pub unsafe fn new_uninitialized(regions: Vec<MemoryRegion>) -> Self {
let number_of_regions = regions.len();
Self {
regions: regions.into_boxed_slice(),
region_addresses: vec![0; number_of_regions].into_boxed_slice(),
region_index_lookup: vec![0; number_of_regions].into_boxed_slice(),
cache: UnsafeCell::new(MappingCache::new()),
}
}
pub unsafe fn new(regions: Vec<MemoryRegion>) -> Result<Self, EbpfError> {
let mut mapping = Self::new_uninitialized(regions);
mapping.initialize()?;
Ok(mapping)
}
pub fn initialize(&mut self) -> Result<(), EbpfError> {
self.regions.sort();
let number_of_regions = self.regions.len();
for index in 1..number_of_regions {
let first = &self.regions[index.saturating_sub(1)];
let second = &self.regions[index];
if first.vm_addr_range().end > second.vm_addr {
return Err(EbpfError::InvalidMemoryRegion(index));
}
}
self.construct_eytzinger_order(0, 0);
Ok(())
}
#[allow(clippy::arithmetic_side_effects)]
#[inline(always)]
pub fn find_region(&self, vm_addr: u64) -> Option<(usize, &MemoryRegion)> {
let cache = unsafe { &mut *self.cache.get() };
if let Some(index) = cache.find(vm_addr) {
Some((index, unsafe { self.regions.get_unchecked(index) }))
} else {
let mut index = 1;
while index <= self.region_addresses.len() {
index = (index << 1)
+ unsafe { *self.region_addresses.get_unchecked(index - 1) <= vm_addr }
as usize;
}
index >>= index.trailing_zeros() + 1;
if index == 0 {
return None;
}
index = unsafe { *self.region_index_lookup.get_unchecked(index - 1) };
let region = unsafe { self.regions.get_unchecked(index) };
cache.insert(region.vm_addr_range(), index);
Some((index, region))
}
}
#[inline(always)]
pub unsafe fn replace_region(
&mut self,
index: usize,
region: MemoryRegion,
) -> Result<(), EbpfError> {
self.regions[index] = region;
self.cache.get_mut().flush();
Ok(())
}
}
#[derive(Debug)]
pub struct AlignedMemoryMapping {
regions: Vec<MemoryRegion>,
allow_memory_region_zero: bool,
}
impl AlignedMemoryMapping {
pub unsafe fn new(regions: Vec<MemoryRegion>, config: &Config) -> Result<Self, EbpfError> {
let mut mapping = Self::new_uninitialized(regions, config);
mapping.initialize()?;
Ok(mapping)
}
pub unsafe fn new_uninitialized(regions: Vec<MemoryRegion>, config: &Config) -> Self {
Self {
regions,
allow_memory_region_zero: config.allow_memory_region_zero,
}
}
pub fn initialize(&mut self) -> Result<(), EbpfError> {
static EMPTY_SLICE: &[u8] = &[];
if self.allow_memory_region_zero {
self.regions.sort();
let mut expected_region_index = 0;
while expected_region_index < self.regions.len() {
let actual_region_index = self
.regions
.get(expected_region_index)
.unwrap()
.vm_addr
.checked_shr(ebpf::VIRTUAL_ADDRESS_BITS as u32)
.unwrap_or(0) as usize;
if actual_region_index > expected_region_index {
self.regions.insert(
expected_region_index,
MemoryRegion::new(
&raw const *EMPTY_SLICE,
(expected_region_index as u64).saturating_mul(ebpf::MM_REGION_SIZE),
),
);
} else if actual_region_index < expected_region_index {
return Err(EbpfError::InvalidMemoryRegion(actual_region_index));
}
expected_region_index = expected_region_index.saturating_add(1);
}
} else {
self.regions
.push(MemoryRegion::new(&raw const *EMPTY_SLICE, 0));
self.regions.sort();
for (index, region) in self.regions.iter().enumerate() {
if region
.vm_addr
.checked_shr(ebpf::VIRTUAL_ADDRESS_BITS as u32)
.unwrap_or(0)
!= index as u64
{
return Err(EbpfError::InvalidMemoryRegion(index));
}
}
}
Ok(())
}
#[inline(always)]
pub fn find_region(&self, vm_addr: u64) -> Option<(usize, &MemoryRegion)> {
let index = vm_addr.wrapping_shr(ebpf::VIRTUAL_ADDRESS_BITS as u32) as usize;
if index < self.regions.len() && (index > 0 || self.allow_memory_region_zero) {
let region = unsafe { self.regions.get_unchecked(index) };
return Some((index, region));
}
None
}
#[inline(always)]
pub unsafe fn replace_region(
&mut self,
index: usize,
region: MemoryRegion,
) -> Result<(), EbpfError> {
let begin_index = region
.vm_addr
.checked_shr(ebpf::VIRTUAL_ADDRESS_BITS as u32)
.unwrap_or(0) as usize;
let end_index = region
.vm_addr
.saturating_add((region.len() as u64).saturating_sub(1))
.checked_shr(ebpf::VIRTUAL_ADDRESS_BITS as u32)
.unwrap_or(0) as usize;
if begin_index != index || end_index != index {
return Err(EbpfError::InvalidMemoryRegion(index));
}
self.regions[index] = region;
Ok(())
}
}
pub struct MemoryMapping {
access_violation_handler: AccessViolationHandler,
max_call_depth: i64,
stack_frame_size: i64,
disable_address_translation: bool,
sbpf_version: SBPFVersion,
initialized: bool,
ty: MemoryMappingType,
}
impl fmt::Debug for MemoryMapping {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("MemoryMapping")
.field("max_call_depth", &self.max_call_depth)
.field("stack_frame_size", &self.stack_frame_size)
.field("sbpf_version", &self.sbpf_version)
.field("ty", &self.ty)
.finish()
}
}
#[derive(Debug)]
pub enum MemoryMappingType {
Aligned(AlignedMemoryMapping),
Unaligned(UnalignedMemoryMapping),
}
impl MemoryMapping {
pub unsafe fn new_with_access_violation_handler(
regions: Vec<MemoryRegion>,
config: &Config,
sbpf_version: SBPFVersion,
access_violation_handler: AccessViolationHandler,
) -> Result<Self, EbpfError> {
let mut mapping =
Self::new_uninitialized(regions, config, sbpf_version, access_violation_handler);
mapping.initialize()?;
Ok(mapping)
}
pub unsafe fn new_uninitialized(
regions: Vec<MemoryRegion>,
config: &Config,
sbpf_version: SBPFVersion,
access_violation_handler: AccessViolationHandler,
) -> Self {
let ty = if sbpf_version >= SBPFVersion::V4 || config.aligned_memory_mapping {
MemoryMappingType::Aligned(AlignedMemoryMapping::new_uninitialized(regions, config))
} else {
debug_assert!(
sbpf_version <= SBPFVersion::V3,
"SBPFv4 and later versions do not support unaligned memory"
);
MemoryMappingType::Unaligned(UnalignedMemoryMapping::new_uninitialized(regions))
};
Self {
access_violation_handler: Box::new(access_violation_handler),
max_call_depth: config.max_call_depth as i64,
stack_frame_size: config.stack_frame_size as i64,
disable_address_translation: !config.enable_address_translation,
sbpf_version,
initialized: false,
ty,
}
}
pub unsafe fn new(
regions: Vec<MemoryRegion>,
config: &Config,
sbpf_version: SBPFVersion,
) -> Result<Self, EbpfError> {
Self::new_with_access_violation_handler(
regions,
config,
sbpf_version,
Box::new(default_access_violation_handler),
)
}
pub fn map(
&self,
access_type: AccessType,
vm_addr: u64,
len: u64,
) -> StableResult<HostBuffer, EbpfError> {
debug_assert!(self.initialized);
if self.disable_address_translation {
let ptr = ptr::with_exposed_provenance_mut(vm_addr as usize);
let buffer = HostBuffer::Mutable(ptr::slice_from_raw_parts_mut(ptr, len as usize));
return StableResult::Ok(buffer);
}
if let Some((_index, region)) = self.find_region(vm_addr) {
if region.host_buffer().is_mutable() || access_type != AccessType::Store {
if let Some(host_buffer) = region.vm_to_host_buffer(vm_addr, len) {
return StableResult::Ok(host_buffer);
}
}
}
StableResult::Err(self.generate_access_violation(access_type, vm_addr, len))
}
#[inline(always)]
pub fn map_with_access_violation_handler(
&mut self,
access_type: AccessType,
vm_addr: u64,
len: u64,
) -> StableResult<HostBuffer, EbpfError> {
debug_assert!(self.initialized);
if self.disable_address_translation {
let ptr = ptr::with_exposed_provenance_mut(vm_addr as usize);
let buffer = HostBuffer::Mutable(ptr::slice_from_raw_parts_mut(ptr, len as usize));
return StableResult::Ok(buffer);
}
if let Some((index, region)) = self.find_region(vm_addr) {
if region.host_buffer().is_mutable() || access_type != AccessType::Store {
if let Some(host_buffer) = region.vm_to_host_buffer(vm_addr, len) {
return StableResult::Ok(host_buffer);
}
}
let mut region = (*region).clone();
let max_len = self
.get_regions()
.get(index.saturating_add(1))
.map_or(u64::MAX, |next_region| next_region.vm_addr)
.saturating_sub(region.vm_addr);
(self.access_violation_handler)(&mut region, max_len, access_type, vm_addr, len);
if region.host_buffer().is_mutable() || access_type != AccessType::Store {
if let Some(host_buffer) = region.vm_to_host_buffer(vm_addr, len) {
if let Err(err) = unsafe { self.replace_region(index, region) } {
return StableResult::Err(err);
}
return StableResult::Ok(host_buffer);
}
}
}
StableResult::Err(self.generate_access_violation(access_type, vm_addr, len))
}
pub fn load<T: Pod + Into<u64>>(&mut self, vm_addr: u64) -> ProgramResult {
let len = mem::size_of::<T>() as u64;
debug_assert!(len <= mem::size_of::<u64>() as u64);
debug_assert!(self.initialized);
let ptr = match self.map_with_access_violation_handler(AccessType::Load, vm_addr, len) {
StableResult::Err(e) => return ProgramResult::Err(e),
StableResult::Ok(buf) => buf.ptr(),
};
ProgramResult::Ok(unsafe {
ptr::read_unaligned::<T>(ptr.cast()).into()
})
}
pub fn store<T: Pod>(&mut self, value: T, vm_addr: u64) -> ProgramResult {
let len = mem::size_of::<T>() as u64;
debug_assert!(len <= mem::size_of::<u64>() as u64);
debug_assert!(self.initialized);
let ptr = match self.map_with_access_violation_handler(AccessType::Store, vm_addr, len) {
StableResult::Err(e) => return ProgramResult::Err(e),
StableResult::Ok(buf) => buf.ptr_mut(),
};
StableResult::Ok(unsafe {
ptr::write_unaligned::<T>(ptr.cast(), value);
0
})
}
#[inline(always)]
pub fn find_region(&self, vm_addr: u64) -> Option<(usize, &MemoryRegion)> {
debug_assert!(self.initialized);
match &self.ty {
MemoryMappingType::Aligned(inner) => inner.find_region(vm_addr),
MemoryMappingType::Unaligned(inner) => inner.find_region(vm_addr),
}
}
#[inline(always)]
pub fn get_regions(&self) -> &[MemoryRegion] {
match &self.ty {
MemoryMappingType::Aligned(inner) => &inner.regions,
MemoryMappingType::Unaligned(inner) => &inner.regions,
}
}
pub fn get_regions_mut(&mut self) -> &mut [MemoryRegion] {
self.initialized = false;
let regions = match &mut self.ty {
MemoryMappingType::Aligned(inner) => inner.regions.as_mut_slice(),
MemoryMappingType::Unaligned(inner) => &mut inner.regions,
};
regions
}
#[inline(always)]
pub unsafe fn replace_region(
&mut self,
index: usize,
region: MemoryRegion,
) -> Result<(), EbpfError> {
debug_assert!(self.initialized);
let regions = self.get_regions();
let next_region_start = regions
.get(index.saturating_add(1))
.map_or(u64::MAX, |next_region| next_region.vm_addr);
if index >= regions.len()
|| regions[index].vm_addr != region.vm_addr
|| region.vm_addr_range().end > next_region_start
{
return Err(EbpfError::InvalidMemoryRegion(index));
}
match &mut self.ty {
MemoryMappingType::Aligned(inner) => inner.replace_region(index, region),
MemoryMappingType::Unaligned(inner) => inner.replace_region(index, region),
}
}
pub fn initialize(&mut self) -> Result<(), EbpfError> {
let result = match &mut self.ty {
MemoryMappingType::Aligned(inner) => inner.initialize(),
MemoryMappingType::Unaligned(inner) => inner.initialize(),
};
self.initialized = result.is_ok();
result
}
fn generate_access_violation(
&self,
access_type: AccessType,
vm_addr: u64,
len: u64,
) -> EbpfError {
let stack_frame = (vm_addr as i64)
.saturating_sub(ebpf::MM_STACK_START as i64)
.checked_div(self.stack_frame_size)
.unwrap_or(0);
if !self.sbpf_version.manual_stack_frame_bump()
&& (-1..self.max_call_depth.saturating_add(1)).contains(&stack_frame)
{
EbpfError::StackAccessViolation(access_type, vm_addr, len, stack_frame)
} else {
let region = self.find_region(vm_addr);
let region_name = match vm_addr & (!ebpf::MM_BYTECODE_START.saturating_sub(1)) {
_ if region.map(|(_, r)| r.vm_addr_range().contains(&vm_addr)) != Some(true) => {
"unallocated"
}
ebpf::MM_BYTECODE_START => "program",
ebpf::MM_STACK_START => "stack",
ebpf::MM_HEAP_START => "heap",
ebpf::MM_INPUT_START => "input",
_ => "allocated",
};
EbpfError::AccessViolation(access_type, vm_addr, len, region_name)
}
}
}
#[derive(Debug)]
struct MappingCache {
entries: [(Range<u64>, usize); MappingCache::SIZE],
head: usize,
}
impl MappingCache {
const SIZE: usize = 4;
fn new() -> MappingCache {
MappingCache {
entries: array::from_fn(|_| (0..0, 0)),
head: 0,
}
}
#[inline]
fn find(&self, vm_addr: u64) -> Option<usize> {
for i in 0..Self::SIZE {
let index = self.head.wrapping_add(i) % Self::SIZE;
let (vm_range, region_index) = unsafe { self.entries.get_unchecked(index) };
if vm_range.contains(&vm_addr) {
return Some(*region_index);
}
}
None
}
#[inline]
fn insert(&mut self, vm_range: Range<u64>, region_index: usize) {
self.head = self.head.wrapping_sub(1) % Self::SIZE;
unsafe { *self.entries.get_unchecked_mut(self.head) = (vm_range, region_index) };
}
#[inline]
fn flush(&mut self) {
self.entries = array::from_fn(|_| (0..0, 0));
self.head = 0;
}
}
#[cfg(test)]
mod test {
use std::{cell::RefCell, rc::Rc};
use test_utils::assert_error;
use super::*;
#[test]
fn test_mapping_cache() {
let mut cache = MappingCache::new();
assert_eq!(cache.find(0), None);
let mut ranges = vec![10u64..20, 20..30, 30..40, 40..50];
for (region, range) in ranges.iter().cloned().enumerate() {
cache.insert(range, region);
}
for (region, range) in ranges.iter().enumerate() {
if region > 0 {
assert_eq!(cache.find(range.start - 1), Some(region - 1));
} else {
assert_eq!(cache.find(range.start - 1), None);
}
assert_eq!(cache.find(range.start), Some(region));
assert_eq!(cache.find(range.start + 1), Some(region));
assert_eq!(cache.find(range.end - 1), Some(region));
if region < 3 {
assert_eq!(cache.find(range.end), Some(region + 1));
} else {
assert_eq!(cache.find(range.end), None);
}
}
cache.insert(50..60, 4);
ranges.push(50..60);
for (region, range) in ranges.iter().enumerate() {
if region == 0 {
assert_eq!(cache.find(range.start), None);
continue;
}
if region > 1 {
assert_eq!(cache.find(range.start - 1), Some(region - 1));
} else {
assert_eq!(cache.find(range.start - 1), None);
}
assert_eq!(cache.find(range.start), Some(region));
assert_eq!(cache.find(range.start + 1), Some(region));
assert_eq!(cache.find(range.end - 1), Some(region));
if region < 4 {
assert_eq!(cache.find(range.end), Some(region + 1));
} else {
assert_eq!(cache.find(range.end), None);
}
}
}
#[test]
fn test_mapping_cache_flush() {
let mut cache = MappingCache::new();
assert_eq!(cache.find(0), None);
cache.insert(0..10, 0);
assert_eq!(cache.find(0), Some(0));
cache.flush();
assert_eq!(cache.find(0), None);
}
#[test]
fn test_map_empty() {
for aligned_memory_mapping in [false, true] {
let config = Config {
aligned_memory_mapping,
..Config::default()
};
let m = unsafe { MemoryMapping::new(vec![], &config, SBPFVersion::V3) }.unwrap();
assert_error!(
m.map(AccessType::Load, ebpf::MM_REGION_SIZE, 8),
"AccessViolation"
);
}
}
#[test]
fn test_gapped_map() {
for aligned_memory_mapping in [false, true] {
let config = Config {
aligned_memory_mapping,
..Config::default()
};
let mut mem1 = [0xff; 8];
let mem2 = [0; 8];
let mut m = unsafe {
MemoryMapping::new(
vec![
MemoryRegion::new(&raw const mem2[..], ebpf::MM_REGION_SIZE),
MemoryRegion::new_gapped(&raw mut mem1[..], ebpf::MM_REGION_SIZE * 2, 2),
],
&config,
SBPFVersion::V3,
)
.unwrap()
};
for frame in 0..4 {
let address = ebpf::MM_STACK_START + frame * 4;
assert!(m.find_region(address).is_some());
assert!(m.map(AccessType::Load, address, 2).is_ok());
assert_error!(m.map(AccessType::Load, address + 2, 2), "AccessViolation");
assert_eq!(m.load::<u16>(address).unwrap(), 0xFFFF);
assert_error!(m.load::<u16>(address + 2), "AccessViolation");
assert!(m.store::<u16>(0xFFFF, address).is_ok());
assert_error!(m.store::<u16>(0xFFFF, address + 2), "AccessViolation");
}
}
}
#[test]
fn test_unaligned_map_overlap() {
let config = Config {
aligned_memory_mapping: false,
..Config::default()
};
let mem1 = [1, 2, 3, 4];
let mem2 = [5, 6];
assert_error!(
unsafe {
MemoryMapping::new(
vec![
MemoryRegion::new(&raw const mem1, ebpf::MM_REGION_SIZE),
MemoryRegion::new(
&raw const mem2,
ebpf::MM_REGION_SIZE + mem1.len() as u64 - 1,
),
],
&config,
SBPFVersion::V3,
)
},
"InvalidMemoryRegion(1)"
);
assert!(unsafe {
MemoryMapping::new(
vec![
MemoryRegion::new(&raw const mem1, ebpf::MM_REGION_SIZE),
MemoryRegion::new(&raw const mem2, ebpf::MM_REGION_SIZE + mem1.len() as u64),
],
&config,
SBPFVersion::V3,
)
}
.is_ok());
}
#[test]
fn test_unaligned_map() {
let config = Config {
aligned_memory_mapping: false,
..Config::default()
};
let mut mem1 = [11];
let mem2 = [22, 22];
let mem3 = [33];
let mem4 = [44, 44];
let m = unsafe {
MemoryMapping::new(
vec![
MemoryRegion::new(&raw mut mem1, ebpf::MM_REGION_SIZE),
MemoryRegion::new(&raw const mem2, ebpf::MM_REGION_SIZE + mem1.len() as u64),
MemoryRegion::new(
&raw const mem3,
ebpf::MM_REGION_SIZE + (mem1.len() + mem2.len()) as u64,
),
MemoryRegion::new(
&raw const mem4,
ebpf::MM_REGION_SIZE + (mem1.len() + mem2.len() + mem3.len()) as u64,
),
],
&config,
SBPFVersion::V3,
)
.unwrap()
};
assert_eq!(
m.map(AccessType::Load, ebpf::MM_REGION_SIZE, 1)
.unwrap()
.ptr()
.addr(),
mem1.as_ptr().addr()
);
assert_eq!(
m.map(AccessType::Store, ebpf::MM_REGION_SIZE, 1)
.unwrap()
.ptr()
.addr(),
mem1.as_ptr().addr()
);
assert_error!(
m.map(AccessType::Load, ebpf::MM_REGION_SIZE, 2),
"AccessViolation"
);
assert_eq!(
m.map(
AccessType::Load,
ebpf::MM_REGION_SIZE + mem1.len() as u64,
1,
)
.unwrap()
.ptr()
.addr(),
mem2.as_ptr().addr()
);
assert_eq!(
m.map(
AccessType::Load,
ebpf::MM_REGION_SIZE + (mem1.len() + mem2.len()) as u64,
1,
)
.unwrap()
.ptr()
.addr(),
mem3.as_ptr().addr()
);
assert_eq!(
m.map(
AccessType::Load,
ebpf::MM_REGION_SIZE + (mem1.len() + mem2.len() + mem3.len()) as u64,
1,
)
.unwrap()
.ptr()
.addr(),
mem4.as_ptr().addr()
);
assert_error!(
m.map(
AccessType::Load,
ebpf::MM_REGION_SIZE + (mem1.len() + mem2.len() + mem3.len() + mem4.len()) as u64,
1,
),
"AccessViolation"
);
}
#[test]
fn test_unaligned_region() {
let config = Config {
aligned_memory_mapping: false,
..Config::default()
};
let mut mem1 = [0xFF; 4];
let mem2 = [0xDD; 4];
let m = unsafe {
MemoryMapping::new(
vec![
MemoryRegion::new(&raw mut mem1, ebpf::MM_REGION_SIZE),
MemoryRegion::new(&raw const mem2, ebpf::MM_REGION_SIZE + 4),
],
&config,
SBPFVersion::V3,
)
.unwrap()
};
assert!(m.find_region(ebpf::MM_REGION_SIZE - 1).is_none());
assert_eq!(
HostBuffer::Mutable(&raw mut mem1[..]),
m.find_region(ebpf::MM_REGION_SIZE).unwrap().1.host,
);
assert_eq!(
HostBuffer::Mutable(&raw mut mem1[..]),
m.find_region(ebpf::MM_REGION_SIZE + 3).unwrap().1.host,
);
assert_eq!(
HostBuffer::Immutable(&raw const mem2[..]),
m.find_region(ebpf::MM_REGION_SIZE + 4).unwrap().1.host,
);
assert_eq!(
HostBuffer::Immutable(&raw const mem2[..]),
m.find_region(ebpf::MM_REGION_SIZE + 7).unwrap().1.host,
);
assert!(m.find_region(ebpf::MM_REGION_SIZE + 8).is_some());
}
#[test]
fn test_aligned_region() {
let config = Config {
aligned_memory_mapping: true,
..Config::default()
};
let mut mem1 = [0xFF; 4];
let mem2 = [0xDD; 4];
let m = unsafe {
MemoryMapping::new(
vec![
MemoryRegion::new(&raw mut mem1, ebpf::MM_REGION_SIZE),
MemoryRegion::new(&raw const mem2, ebpf::MM_REGION_SIZE * 2),
],
&config,
SBPFVersion::V4,
)
.unwrap()
};
assert_eq!(m.find_region(ebpf::MM_REGION_SIZE - 1).unwrap().1.len(), 0);
assert_eq!(
HostBuffer::Mutable(&raw mut mem1[..]),
m.find_region(ebpf::MM_REGION_SIZE).unwrap().1.host,
);
assert_eq!(
HostBuffer::Mutable(&raw mut mem1[..]),
m.find_region(ebpf::MM_REGION_SIZE + 3).unwrap().1.host,
);
assert!(m.find_region(ebpf::MM_REGION_SIZE + 4).is_some());
assert_eq!(
HostBuffer::Immutable(&raw const mem2[..]),
m.find_region(ebpf::MM_REGION_SIZE * 2).unwrap().1.host,
);
assert_eq!(
HostBuffer::Immutable(&raw const mem2[..]),
m.find_region(ebpf::MM_REGION_SIZE * 2 + 3).unwrap().1.host,
);
assert!(m.find_region(ebpf::MM_REGION_SIZE * 3 + 4).is_none());
}
#[test]
fn test_unaligned_map_load() {
let config = Config {
aligned_memory_mapping: false,
..Config::default()
};
let mem1 = [0x11, 0x22];
let mem2 = [0x33];
let mut m = unsafe {
MemoryMapping::new(
vec![
MemoryRegion::new(&raw const mem1, ebpf::MM_REGION_SIZE),
MemoryRegion::new(&raw const mem2, ebpf::MM_REGION_SIZE + mem1.len() as u64),
],
&config,
SBPFVersion::V3,
)
.unwrap()
};
assert_eq!(m.load::<u16>(ebpf::MM_REGION_SIZE).unwrap(), 0x2211);
assert_error!(m.load::<u32>(ebpf::MM_REGION_SIZE), "AccessViolation");
assert_error!(m.load::<u32>(ebpf::MM_REGION_SIZE + 4), "AccessViolation");
}
#[test]
fn test_unaligned_map_store() {
let config = Config {
aligned_memory_mapping: false,
..Config::default()
};
let mut mem1 = [0xff, 0xff];
let mut mem2 = [0xff];
let mut m = unsafe {
MemoryMapping::new(
vec![
MemoryRegion::new(&raw mut mem1, ebpf::MM_REGION_SIZE),
MemoryRegion::new(&raw mut mem2, ebpf::MM_REGION_SIZE + mem1.len() as u64),
],
&config,
SBPFVersion::V3,
)
.unwrap()
};
m.store(0x1122u16, ebpf::MM_REGION_SIZE).unwrap();
assert_eq!(m.load::<u16>(ebpf::MM_REGION_SIZE).unwrap(), 0x1122);
assert_error!(
m.store(0x33445566u32, ebpf::MM_REGION_SIZE),
"AccessViolation"
);
}
#[test]
fn test_unaligned_map_store_out_of_bounds() {
let config = Config {
aligned_memory_mapping: false,
..Config::default()
};
let mut mem1 = [0xFF];
let mut m = unsafe {
MemoryMapping::new(
vec![MemoryRegion::new(&raw mut mem1, ebpf::MM_REGION_SIZE)],
&config,
SBPFVersion::V3,
)
.unwrap()
};
m.store(0x11u8, ebpf::MM_REGION_SIZE).unwrap();
assert_error!(m.store(0x11u8, ebpf::MM_REGION_SIZE - 1), "AccessViolation");
assert_error!(m.store(0x11u8, ebpf::MM_REGION_SIZE + 1), "AccessViolation");
assert_error!(m.store(0x11u8, ebpf::MM_REGION_SIZE + 2), "AccessViolation");
let mut mem1 = [0xFF; 4];
let mut mem2 = [0xDD; 4];
let mut m = unsafe {
MemoryMapping::new(
vec![
MemoryRegion::new(&raw mut mem1, ebpf::MM_REGION_SIZE),
MemoryRegion::new(&raw mut mem2, ebpf::MM_REGION_SIZE + 4),
],
&config,
SBPFVersion::V3,
)
.unwrap()
};
assert_error!(
m.store(0x1122334455667788u64, ebpf::MM_REGION_SIZE),
"AccessViolation"
);
}
#[test]
fn test_unaligned_map_load_out_of_bounds() {
let config = Config {
aligned_memory_mapping: false,
..Config::default()
};
let mem1 = [0xff];
let mut m = unsafe {
MemoryMapping::new(
vec![MemoryRegion::new(&raw const mem1, ebpf::MM_REGION_SIZE)],
&config,
SBPFVersion::V3,
)
.unwrap()
};
assert_eq!(m.load::<u8>(ebpf::MM_REGION_SIZE).unwrap(), 0xff);
assert_error!(m.load::<u8>(ebpf::MM_REGION_SIZE - 1), "AccessViolation");
assert_error!(m.load::<u8>(ebpf::MM_REGION_SIZE + 1), "AccessViolation");
assert_error!(m.load::<u8>(ebpf::MM_REGION_SIZE + 2), "AccessViolation");
let mem1 = [0xFF; 4];
let mem2 = [0xDD; 4];
let mut m = unsafe {
MemoryMapping::new(
vec![
MemoryRegion::new(&raw const mem1, ebpf::MM_REGION_SIZE),
MemoryRegion::new(&raw const mem2, ebpf::MM_REGION_SIZE + 4),
],
&config,
SBPFVersion::V3,
)
.unwrap()
};
assert_error!(m.load::<u64>(ebpf::MM_REGION_SIZE), "AccessViolation");
}
#[test]
#[should_panic(expected = "AccessViolation")]
fn test_store_readonly() {
let config = Config {
aligned_memory_mapping: false,
..Config::default()
};
let mut mem1 = [0xff, 0xff];
let mem2 = [0xff, 0xff];
let mut m = unsafe {
MemoryMapping::new(
vec![
MemoryRegion::new(&raw mut mem1, ebpf::MM_REGION_SIZE),
MemoryRegion::new(&raw const mem2, ebpf::MM_REGION_SIZE + mem1.len() as u64),
],
&config,
SBPFVersion::V3,
)
.unwrap()
};
m.store(0x11223344, ebpf::MM_REGION_SIZE).unwrap();
}
#[test]
fn test_unaligned_map_replace_region() {
let config = Config {
aligned_memory_mapping: false,
..Config::default()
};
let mem1 = [11];
let mem2 = [22, 22];
let mem3 = [33];
let mut m = unsafe {
MemoryMapping::new(
vec![
MemoryRegion::new(&raw const mem1, ebpf::MM_REGION_SIZE),
MemoryRegion::new(&raw const mem2, ebpf::MM_REGION_SIZE + mem1.len() as u64),
],
&config,
SBPFVersion::V3,
)
.unwrap()
};
assert_eq!(
m.map(AccessType::Load, ebpf::MM_REGION_SIZE, 1)
.unwrap()
.ptr()
.addr(),
mem1.as_ptr().addr()
);
assert_eq!(
m.map(
AccessType::Load,
ebpf::MM_REGION_SIZE + mem1.len() as u64,
1,
)
.unwrap()
.ptr()
.addr(),
mem2.as_ptr().addr()
);
assert_error!(
unsafe {
m.replace_region(
2,
MemoryRegion::new(&raw const mem3, ebpf::MM_REGION_SIZE + mem1.len() as u64),
)
},
"InvalidMemoryRegion(2)"
);
let region_index = m
.get_regions()
.iter()
.position(|mem| mem.vm_addr == ebpf::MM_REGION_SIZE + mem1.len() as u64)
.unwrap();
assert_error!(
unsafe {
m.replace_region(
region_index,
MemoryRegion::new(
&raw const mem3,
ebpf::MM_REGION_SIZE + mem1.len() as u64 + 1,
),
)
},
"InvalidMemoryRegion({})",
region_index
);
unsafe {
m.replace_region(
region_index,
MemoryRegion::new(&raw const mem3, ebpf::MM_REGION_SIZE + mem1.len() as u64),
)
.unwrap()
};
assert_eq!(
m.map(
AccessType::Load,
ebpf::MM_REGION_SIZE + mem1.len() as u64,
1,
)
.unwrap()
.ptr()
.addr(),
mem3.as_ptr().addr()
);
}
#[test]
fn test_aligned_map_replace_region() {
let config = Config {
aligned_memory_mapping: true,
..Config::default()
};
let mem1 = [11];
let mem2 = [22, 22];
let mem3 = [33, 33];
let mut m = unsafe {
MemoryMapping::new(
vec![
MemoryRegion::new(&raw const mem1, ebpf::MM_REGION_SIZE),
MemoryRegion::new(&raw const mem2, ebpf::MM_REGION_SIZE * 2),
],
&config,
SBPFVersion::V4,
)
.unwrap()
};
assert_eq!(
m.map(AccessType::Load, ebpf::MM_REGION_SIZE * 2, 1)
.unwrap()
.ptr()
.addr(),
mem2.as_ptr().addr()
);
assert_error!(
unsafe {
m.replace_region(
3,
MemoryRegion::new(&raw const mem3, ebpf::MM_REGION_SIZE * 2),
)
},
"InvalidMemoryRegion(3)"
);
assert_error!(
unsafe {
m.replace_region(
2,
MemoryRegion::new(&raw const mem3, ebpf::MM_REGION_SIZE * 3),
)
},
"InvalidMemoryRegion(2)"
);
assert_error!(
unsafe {
m.replace_region(
2,
MemoryRegion::new(&raw const mem3, ebpf::MM_REGION_SIZE * 3 - 1),
)
},
"InvalidMemoryRegion(2)"
);
unsafe {
m.replace_region(
2,
MemoryRegion::new(&raw const mem3, ebpf::MM_REGION_SIZE * 2),
)
.unwrap()
};
assert_eq!(
m.map(AccessType::Load, ebpf::MM_REGION_SIZE * 2, 1)
.unwrap()
.ptr()
.addr(),
mem3.as_ptr().addr()
);
}
#[test]
fn test_access_violation_handler_map() {
for aligned_memory_mapping in [true, false] {
let config = Config {
aligned_memory_mapping,
..Config::default()
};
let original = [11, 22];
let copied = Rc::new(RefCell::new(Vec::new()));
let mut regions = vec![MemoryRegion::new(&raw const original, ebpf::MM_REGION_SIZE)];
regions[0].access_violation_handler_payload = Some(0);
let c = Rc::clone(&copied);
let mut m = unsafe {
MemoryMapping::new_with_access_violation_handler(
regions,
&config,
SBPFVersion::V3,
Box::new(move |region, _, _, _, _| {
let mut vec = c.borrow_mut();
vec.extend_from_slice(&original);
region.redirect(&raw mut vec[..]);
}),
)
.unwrap()
};
assert_eq!(
m.map_with_access_violation_handler(AccessType::Load, ebpf::MM_REGION_SIZE, 1)
.unwrap()
.ptr()
.addr(),
original.as_ptr().addr()
);
assert_eq!(
m.map_with_access_violation_handler(AccessType::Store, ebpf::MM_REGION_SIZE, 1)
.unwrap()
.ptr()
.addr(),
copied.borrow().as_ptr().addr()
);
}
}
#[test]
fn test_access_violation_handler_load_store() {
for aligned_memory_mapping in [true, false] {
let config = Config {
aligned_memory_mapping,
..Config::default()
};
let original = [11, 22];
let copied = Rc::new(RefCell::new(Vec::new()));
let mut regions = vec![MemoryRegion::new(&raw const original, ebpf::MM_REGION_SIZE)];
regions[0].access_violation_handler_payload = Some(0);
let c = Rc::clone(&copied);
let mut m = unsafe {
MemoryMapping::new_with_access_violation_handler(
regions,
&config,
SBPFVersion::V3,
Box::new(move |region, _, _, _, _| {
let mut vec = c.borrow_mut();
vec.extend_from_slice(&original);
region.redirect(&raw mut vec[..]);
}),
)
.unwrap()
};
assert_eq!(
m.map(AccessType::Load, ebpf::MM_REGION_SIZE, 1)
.unwrap()
.ptr()
.addr(),
original.as_ptr().addr()
);
assert_eq!(m.load::<u8>(ebpf::MM_REGION_SIZE).unwrap(), 11);
assert_eq!(m.load::<u8>(ebpf::MM_REGION_SIZE + 1).unwrap(), 22);
assert!(copied.borrow().is_empty());
m.store(33u8, ebpf::MM_REGION_SIZE).unwrap();
assert_eq!(original[0], 11);
assert_eq!(m.load::<u8>(ebpf::MM_REGION_SIZE).unwrap(), 33);
assert_eq!(m.load::<u8>(ebpf::MM_REGION_SIZE + 1).unwrap(), 22);
}
}
#[test]
fn test_access_violation_handler_region_id() {
for aligned_memory_mapping in [true, false] {
let config = Config {
aligned_memory_mapping,
..Config::default()
};
let original1 = [11, 22];
let original2 = [33, 44];
let copied = Rc::new(RefCell::new(Vec::new()));
let mut regions = vec![
MemoryRegion::new(&raw const original1, ebpf::MM_REGION_SIZE),
MemoryRegion::new(&raw const original2, ebpf::MM_REGION_SIZE * 2),
];
regions[0].access_violation_handler_payload = Some(42);
let c = Rc::clone(&copied);
let mut m = unsafe {
MemoryMapping::new_with_access_violation_handler(
regions,
&config,
SBPFVersion::V3,
Box::new(move |region, _, _, _, _| {
assert_eq!(region.access_violation_handler_payload, Some(42));
let mut vec = c.borrow_mut();
vec.extend_from_slice(&original1);
region.redirect(&raw mut vec[..]);
}),
)
.unwrap()
};
m.store(55u8, ebpf::MM_REGION_SIZE).unwrap();
assert_eq!(original1[0], 11);
assert_eq!(m.load::<u8>(ebpf::MM_REGION_SIZE).unwrap(), 55);
}
}
#[test]
#[should_panic(expected = "AccessViolation")]
fn test_map_access_violation_handler_error() {
let config = Config::default();
let original = [11, 22];
let m = unsafe {
MemoryMapping::new_with_access_violation_handler(
vec![MemoryRegion::new(&raw const original, ebpf::MM_REGION_SIZE)],
&config,
SBPFVersion::V4,
Box::new(default_access_violation_handler),
)
.unwrap()
};
m.map(AccessType::Store, ebpf::MM_REGION_SIZE, 1).unwrap();
}
#[test]
#[should_panic(expected = "AccessViolation")]
fn test_store_access_violation_handler_error() {
let config = Config::default();
let original = [11, 22];
let mut m = unsafe {
MemoryMapping::new_with_access_violation_handler(
vec![MemoryRegion::new(&raw const original, ebpf::MM_REGION_SIZE)],
&config,
SBPFVersion::V4,
Box::new(default_access_violation_handler),
)
.unwrap()
};
m.store(33u8, ebpf::MM_REGION_SIZE).unwrap();
}
#[test]
fn test_access_violation_region_identification() {
let config = Config::default();
let original = [11, 22];
let region = 0x10_0000_0000;
let mut m = unsafe {
MemoryMapping::new(
vec![MemoryRegion::new(&raw const original, region)],
&config,
SBPFVersion::V4,
)
.unwrap()
};
let store_err_inbound = m.store(33u8, region).unwrap_err();
assert_eq!(
store_err_inbound.to_string(),
"Access violation writing 1 bytes at address 0x1000000000 (in allocated region)"
);
let store_err_oob = m.load::<u64>(region + 3).unwrap_err();
assert_eq!(
store_err_oob.to_string(),
"Access violation reading 8 bytes at address 0x1000000003 (in unallocated region)"
);
}
#[test]
fn v4_aligned_mapping() {
let config = Config {
aligned_memory_mapping: false,
..Config::default()
};
let mem = [11, 12];
let mapping = unsafe {
MemoryMapping::new_with_access_violation_handler(
vec![MemoryRegion::new(&raw const mem, ebpf::MM_REGION_SIZE)],
&config,
SBPFVersion::V4,
Box::new(default_access_violation_handler),
)
.unwrap()
};
assert!(matches!(mapping.ty, MemoryMappingType::Aligned(_)));
}
}