mirror of
https://github.com/DragonOS-Community/DragonOS.git
synced 2025-06-19 00:46:31 +00:00
feat(ebpf):[WIP] add eBPF support (#948)
* feat(kprobe): Add basic kprobe support for x86_64 * feat: add ebpf support (#912) - 实现bpf()一部分命令,包括几种基本map,相关的helper函数 - 实现部分perf相关的数据结构 - 暂时为文件实现简单mmap - 实现一个使用kprobe统计syscall 调用次数的ebpf程序 对eBPF支持程度(基本): - 简单的eBPF程序(没有指定特殊的Map) - 使用内核已经实现的Map的eBPF程序 - 可以和kprobe配合使用 - 内核Map相关的接口定义已经实现,添加新的Map较为简单 不支持的功能: - 区分不同的eBPF程序类型(Network/Cgroup)并限定可调用的helper函数集 - 与内核其它跟踪机制配合(tracepoint) - 其它helper和Map todo - [ ] 修改mmap,需要讨论,因为这个和块缓存层相关 - [x] 添加文档 - [x] 修复可能的错误 - [x] 增加rbpf版本信息 * feat: add /sys/devices/system/cpu/possible file * feat: add /sys/devices/system/cpu/online
This commit is contained in:
11
kernel/crates/kprobe/Cargo.toml
Normal file
11
kernel/crates/kprobe/Cargo.toml
Normal file
@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "kprobe"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
log = "0.4.21"
|
||||
|
||||
[target.'cfg(target_arch = "x86_64")'.dependencies]
|
||||
yaxpeax-x86 = { version = "2", default-features = false, features = ["fmt"] }
|
||||
yaxpeax-arch = { version = "0", default-features = false }
|
112
kernel/crates/kprobe/src/arch/loongarch64/mod.rs
Normal file
112
kernel/crates/kprobe/src/arch/loongarch64/mod.rs
Normal file
@ -0,0 +1,112 @@
|
||||
use alloc::sync::Arc;
|
||||
use core::ops::{Deref, DerefMut};
|
||||
|
||||
use crate::{KprobeBasic, KprobeBuilder, KprobeOps};
|
||||
|
||||
const BRK_KPROBE_BP: u64 = 10;
|
||||
const BRK_KPROBE_SSTEPBP: u64 = 11;
|
||||
const EBREAK_INST: u32 = 0x002a0000;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Kprobe {
|
||||
basic: KprobeBasic,
|
||||
point: Arc<LA64KprobePoint>,
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub struct LA64KprobePoint {
|
||||
addr: usize,
|
||||
inst_tmp: [u8; 8],
|
||||
}
|
||||
|
||||
impl Deref for Kprobe {
|
||||
type Target = KprobeBasic;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.basic
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for Kprobe {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.basic
|
||||
}
|
||||
}
|
||||
|
||||
impl Kprobe {
|
||||
pub fn probe_point(&self) -> &Arc<LA64KprobePoint> {
|
||||
&self.point
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for LA64KprobePoint {
|
||||
fn drop(&mut self) {
|
||||
let address = self.addr;
|
||||
let inst_tmp_ptr = self.inst_tmp.as_ptr() as usize;
|
||||
let inst_32 = unsafe { core::ptr::read(inst_tmp_ptr as *const u32) };
|
||||
unsafe {
|
||||
core::ptr::write(address as *mut u32, inst_32);
|
||||
}
|
||||
log::trace!(
|
||||
"Kprobe::uninstall: address: {:#x}, old_instruction: {:?}",
|
||||
address,
|
||||
inst_32
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl KprobeBuilder {
|
||||
pub fn install(self) -> (Kprobe, Arc<LA64KprobePoint>) {
|
||||
let probe_point = match &self.probe_point {
|
||||
Some(point) => point.clone(),
|
||||
None => self.replace_inst(),
|
||||
};
|
||||
let kprobe = Kprobe {
|
||||
basic: KprobeBasic::from(self),
|
||||
point: probe_point.clone(),
|
||||
};
|
||||
(kprobe, probe_point)
|
||||
}
|
||||
/// # 安装kprobe
|
||||
///
|
||||
/// 不同的架构下需要保存原指令,然后替换为断点指令
|
||||
fn replace_inst(&self) -> Arc<LA64KprobePoint> {
|
||||
let address = self.symbol_addr + self.offset;
|
||||
let point = LA64KprobePoint {
|
||||
addr: address,
|
||||
inst_tmp: [0u8; 8],
|
||||
};
|
||||
let inst_tmp_ptr = point.inst_tmp.as_ptr() as usize;
|
||||
let inst_32 = unsafe { core::ptr::read(address as *const u32) };
|
||||
unsafe {
|
||||
core::ptr::write(address as *mut u32, EBREAK_INST);
|
||||
// inst_32 :0-32
|
||||
// ebreak :32-64
|
||||
core::ptr::write(inst_tmp_ptr as *mut u32, inst_32);
|
||||
core::ptr::write((inst_tmp_ptr + 4) as *mut u32, EBREAK_INST);
|
||||
}
|
||||
log::trace!(
|
||||
"Kprobe::install: address: {:#x}, func_name: {:?}, opcode: {:x?}",
|
||||
address,
|
||||
self.symbol,
|
||||
inst_32
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl KprobeOps for LA64KprobePoint {
|
||||
fn return_address(&self) -> usize {
|
||||
self.addr + 4
|
||||
}
|
||||
|
||||
fn single_step_address(&self) -> usize {
|
||||
self.inst_tmp.as_ptr() as usize
|
||||
}
|
||||
|
||||
fn debug_address(&self) -> usize {
|
||||
self.inst_tmp.as_ptr() as usize + 4
|
||||
}
|
||||
|
||||
fn break_address(&self) -> usize {
|
||||
self.addr
|
||||
}
|
||||
}
|
211
kernel/crates/kprobe/src/arch/mod.rs
Normal file
211
kernel/crates/kprobe/src/arch/mod.rs
Normal file
@ -0,0 +1,211 @@
|
||||
use alloc::boxed::Box;
|
||||
use alloc::string::String;
|
||||
use alloc::sync::Arc;
|
||||
use core::{any::Any, fmt::Debug};
|
||||
|
||||
#[cfg(target_arch = "loongarch64")]
|
||||
mod loongarch64;
|
||||
#[cfg(target_arch = "riscv64")]
|
||||
mod rv64;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
mod x86;
|
||||
|
||||
#[cfg(target_arch = "loongarch64")]
|
||||
pub use loongarch64::*;
|
||||
#[cfg(target_arch = "riscv64")]
|
||||
pub use rv64::*;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub use x86::*;
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub type KprobePoint = X86KprobePoint;
|
||||
#[cfg(target_arch = "riscv64")]
|
||||
pub type KprobePoint = Rv64KprobePoint;
|
||||
#[cfg(target_arch = "loongarch64")]
|
||||
pub type KprobePoint = LA64KprobePoint;
|
||||
|
||||
pub trait ProbeArgs: Send {
|
||||
/// 用于使用者转换到特定架构下的TrapFrame
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
/// 返回导致break异常的地址
|
||||
fn break_address(&self) -> usize;
|
||||
/// 返回导致单步执行异常的地址
|
||||
fn debug_address(&self) -> usize;
|
||||
}
|
||||
|
||||
pub trait KprobeOps: Send {
|
||||
/// # 返回探测点的下一条指令地址
|
||||
///
|
||||
/// 执行流需要回到正常的路径中,在执行完探测点的指令后,需要返回到下一条指令
|
||||
fn return_address(&self) -> usize;
|
||||
/// # 返回单步执行的指令地址
|
||||
///
|
||||
/// 通常探测点的处的原指令被保存在一个数组当中。根据架构的不同, 在保存的指令后面,可能会填充必要的指令。
|
||||
/// 例如x86架构下支持单步执行的特性, 而其它架构下通常没有,因此我们使用break异常来进行模拟,所以会填充
|
||||
/// 一条断点指令。
|
||||
fn single_step_address(&self) -> usize;
|
||||
/// # 返回单步执行指令触发异常的地址
|
||||
///
|
||||
/// 其值等于`single_step_address`的值加上探测点指令的长度
|
||||
fn debug_address(&self) -> usize;
|
||||
/// # 返回设置break断点的地址
|
||||
///
|
||||
/// 其值与探测点地址相等
|
||||
fn break_address(&self) -> usize;
|
||||
}
|
||||
|
||||
struct ProbeHandler {
|
||||
func: fn(&dyn ProbeArgs),
|
||||
}
|
||||
|
||||
impl ProbeHandler {
|
||||
pub fn new(func: fn(&dyn ProbeArgs)) -> Self {
|
||||
ProbeHandler { func }
|
||||
}
|
||||
/// 调用探测点处理函数
|
||||
pub fn call(&self, trap_frame: &dyn ProbeArgs) {
|
||||
(self.func)(trap_frame);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct KprobeBuilder {
|
||||
symbol: Option<String>,
|
||||
symbol_addr: usize,
|
||||
offset: usize,
|
||||
pre_handler: ProbeHandler,
|
||||
post_handler: ProbeHandler,
|
||||
fault_handler: Option<ProbeHandler>,
|
||||
event_callback: Option<Box<dyn CallBackFunc>>,
|
||||
probe_point: Option<Arc<KprobePoint>>,
|
||||
enable: bool,
|
||||
}
|
||||
|
||||
pub trait EventCallback: Send {
|
||||
fn call(&self, trap_frame: &dyn ProbeArgs);
|
||||
}
|
||||
|
||||
impl KprobeBuilder {
|
||||
pub fn new(
|
||||
symbol: Option<String>,
|
||||
symbol_addr: usize,
|
||||
offset: usize,
|
||||
pre_handler: fn(&dyn ProbeArgs),
|
||||
post_handler: fn(&dyn ProbeArgs),
|
||||
enable: bool,
|
||||
) -> Self {
|
||||
KprobeBuilder {
|
||||
symbol,
|
||||
symbol_addr,
|
||||
offset,
|
||||
pre_handler: ProbeHandler::new(pre_handler),
|
||||
post_handler: ProbeHandler::new(post_handler),
|
||||
event_callback: None,
|
||||
fault_handler: None,
|
||||
probe_point: None,
|
||||
enable,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_fault_handler(mut self, func: fn(&dyn ProbeArgs)) -> Self {
|
||||
self.fault_handler = Some(ProbeHandler::new(func));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_probe_point(mut self, point: Arc<KprobePoint>) -> Self {
|
||||
self.probe_point = Some(point);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_event_callback(mut self, event_callback: Box<dyn CallBackFunc>) -> Self {
|
||||
self.event_callback = Some(event_callback);
|
||||
self
|
||||
}
|
||||
|
||||
/// 获取探测点的地址
|
||||
///
|
||||
/// 探测点的地址 == break指令的地址
|
||||
pub fn probe_addr(&self) -> usize {
|
||||
self.symbol_addr + self.offset
|
||||
}
|
||||
}
|
||||
|
||||
pub struct KprobeBasic {
|
||||
symbol: Option<String>,
|
||||
symbol_addr: usize,
|
||||
offset: usize,
|
||||
pre_handler: ProbeHandler,
|
||||
post_handler: ProbeHandler,
|
||||
fault_handler: ProbeHandler,
|
||||
event_callback: Option<Box<dyn CallBackFunc>>,
|
||||
enable: bool,
|
||||
}
|
||||
|
||||
pub trait CallBackFunc: Send + Sync {
|
||||
fn call(&self, trap_frame: &dyn ProbeArgs);
|
||||
}
|
||||
|
||||
impl Debug for KprobeBasic {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
f.debug_struct("Kprobe")
|
||||
.field("symbol", &self.symbol)
|
||||
.field("symbol_addr", &self.symbol_addr)
|
||||
.field("offset", &self.offset)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl KprobeBasic {
|
||||
pub fn call_pre_handler(&self, trap_frame: &dyn ProbeArgs) {
|
||||
self.pre_handler.call(trap_frame);
|
||||
}
|
||||
|
||||
pub fn call_post_handler(&self, trap_frame: &dyn ProbeArgs) {
|
||||
self.post_handler.call(trap_frame);
|
||||
}
|
||||
|
||||
pub fn call_fault_handler(&self, trap_frame: &dyn ProbeArgs) {
|
||||
self.fault_handler.call(trap_frame);
|
||||
}
|
||||
|
||||
pub fn call_event_callback(&self, trap_frame: &dyn ProbeArgs) {
|
||||
if let Some(ref call_back) = self.event_callback {
|
||||
call_back.call(trap_frame);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_event_callback(&mut self, callback: Box<dyn CallBackFunc>) {
|
||||
self.event_callback = Some(callback);
|
||||
}
|
||||
|
||||
pub fn disable(&mut self) {
|
||||
self.enable = false;
|
||||
}
|
||||
|
||||
pub fn enable(&mut self) {
|
||||
self.enable = true;
|
||||
}
|
||||
|
||||
pub fn is_enabled(&self) -> bool {
|
||||
self.enable
|
||||
}
|
||||
/// 返回探测点的函数名称
|
||||
pub fn symbol(&self) -> Option<&str> {
|
||||
self.symbol.as_deref()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<KprobeBuilder> for KprobeBasic {
|
||||
fn from(value: KprobeBuilder) -> Self {
|
||||
let fault_handler = value.fault_handler.unwrap_or(ProbeHandler::new(|_| {}));
|
||||
KprobeBasic {
|
||||
symbol: value.symbol,
|
||||
symbol_addr: value.symbol_addr,
|
||||
offset: value.offset,
|
||||
pre_handler: value.pre_handler,
|
||||
post_handler: value.post_handler,
|
||||
event_callback: value.event_callback,
|
||||
fault_handler,
|
||||
enable: value.enable,
|
||||
}
|
||||
}
|
||||
}
|
157
kernel/crates/kprobe/src/arch/rv64/mod.rs
Normal file
157
kernel/crates/kprobe/src/arch/rv64/mod.rs
Normal file
@ -0,0 +1,157 @@
|
||||
use alloc::sync::Arc;
|
||||
use core::{
|
||||
arch::riscv64::sfence_vma_all,
|
||||
fmt::Debug,
|
||||
ops::{Deref, DerefMut},
|
||||
};
|
||||
|
||||
use crate::{KprobeBasic, KprobeBuilder, KprobeOps};
|
||||
const EBREAK_INST: u32 = 0x00100073; // ebreak
|
||||
const C_EBREAK_INST: u32 = 0x9002; // c.ebreak
|
||||
const INSN_LENGTH_MASK: u16 = 0x3;
|
||||
const INSN_LENGTH_32: u16 = 0x3;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Kprobe {
|
||||
basic: KprobeBasic,
|
||||
point: Arc<Rv64KprobePoint>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum OpcodeTy {
|
||||
Inst16(u16),
|
||||
Inst32(u32),
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub struct Rv64KprobePoint {
|
||||
addr: usize,
|
||||
old_instruction: OpcodeTy,
|
||||
inst_tmp: [u8; 8],
|
||||
}
|
||||
|
||||
impl Deref for Kprobe {
|
||||
type Target = KprobeBasic;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.basic
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for Kprobe {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.basic
|
||||
}
|
||||
}
|
||||
|
||||
impl Kprobe {
|
||||
pub fn probe_point(&self) -> &Arc<Rv64KprobePoint> {
|
||||
&self.point
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Rv64KprobePoint {
|
||||
fn drop(&mut self) {
|
||||
let address = self.addr;
|
||||
match self.old_instruction {
|
||||
OpcodeTy::Inst16(inst_16) => unsafe {
|
||||
core::ptr::write(address as *mut u16, inst_16);
|
||||
},
|
||||
OpcodeTy::Inst32(inst_32) => unsafe {
|
||||
core::ptr::write(address as *mut u32, inst_32);
|
||||
},
|
||||
}
|
||||
unsafe {
|
||||
sfence_vma_all();
|
||||
}
|
||||
log::trace!(
|
||||
"Kprobe::uninstall: address: {:#x}, old_instruction: {:?}",
|
||||
address,
|
||||
self.old_instruction
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl KprobeBuilder {
|
||||
pub fn install(self) -> (Kprobe, Arc<Rv64KprobePoint>) {
|
||||
let probe_point = match &self.probe_point {
|
||||
Some(point) => point.clone(),
|
||||
None => self.replace_inst(),
|
||||
};
|
||||
let kprobe = Kprobe {
|
||||
basic: KprobeBasic::from(self),
|
||||
point: probe_point.clone(),
|
||||
};
|
||||
(kprobe, probe_point)
|
||||
}
|
||||
/// # 安装kprobe
|
||||
///
|
||||
/// 不同的架构下需要保存原指令,然后替换为断点指令
|
||||
fn replace_inst(&self) -> Arc<Rv64KprobePoint> {
|
||||
let address = self.symbol_addr + self.offset;
|
||||
let inst_16 = unsafe { core::ptr::read(address as *const u16) };
|
||||
// See https://elixir.bootlin.com/linux/v6.10.2/source/arch/riscv/kernel/probes/kprobes.c#L68
|
||||
let is_inst_16 = if (inst_16 & INSN_LENGTH_MASK) == INSN_LENGTH_32 {
|
||||
false
|
||||
} else {
|
||||
true
|
||||
};
|
||||
let mut point = Rv64KprobePoint {
|
||||
old_instruction: OpcodeTy::Inst16(0),
|
||||
inst_tmp: [0; 8],
|
||||
addr: address,
|
||||
};
|
||||
let inst_tmp_ptr = point.inst_tmp.as_ptr() as usize;
|
||||
if is_inst_16 {
|
||||
point.old_instruction = OpcodeTy::Inst16(inst_16);
|
||||
unsafe {
|
||||
core::ptr::write(address as *mut u16, C_EBREAK_INST as u16);
|
||||
// inst_16 :0-16
|
||||
// c.ebreak:16-32
|
||||
core::ptr::write(inst_tmp_ptr as *mut u16, inst_16);
|
||||
core::ptr::write((inst_tmp_ptr + 2) as *mut u16, C_EBREAK_INST as u16);
|
||||
}
|
||||
} else {
|
||||
let inst_32 = unsafe { core::ptr::read(address as *const u32) };
|
||||
point.old_instruction = OpcodeTy::Inst32(inst_32);
|
||||
unsafe {
|
||||
core::ptr::write(address as *mut u32, EBREAK_INST);
|
||||
// inst_32 :0-32
|
||||
// ebreak :32-64
|
||||
core::ptr::write(inst_tmp_ptr as *mut u32, inst_32);
|
||||
core::ptr::write((inst_tmp_ptr + 4) as *mut u32, EBREAK_INST);
|
||||
}
|
||||
}
|
||||
unsafe {
|
||||
sfence_vma_all();
|
||||
}
|
||||
log::trace!(
|
||||
"Kprobe::install: address: {:#x}, func_name: {:?}, opcode: {:x?}",
|
||||
address,
|
||||
self.symbol,
|
||||
point.old_instruction
|
||||
);
|
||||
Arc::new(point)
|
||||
}
|
||||
}
|
||||
|
||||
impl KprobeOps for Rv64KprobePoint {
|
||||
fn return_address(&self) -> usize {
|
||||
let address = self.addr;
|
||||
match self.old_instruction {
|
||||
OpcodeTy::Inst16(_) => address + 2,
|
||||
OpcodeTy::Inst32(_) => address + 4,
|
||||
}
|
||||
}
|
||||
fn single_step_address(&self) -> usize {
|
||||
self.inst_tmp.as_ptr() as usize
|
||||
}
|
||||
fn debug_address(&self) -> usize {
|
||||
match self.old_instruction {
|
||||
OpcodeTy::Inst16(_) => self.inst_tmp.as_ptr() as usize + 2,
|
||||
OpcodeTy::Inst32(_) => self.inst_tmp.as_ptr() as usize + 4,
|
||||
}
|
||||
}
|
||||
fn break_address(&self) -> usize {
|
||||
self.addr
|
||||
}
|
||||
}
|
135
kernel/crates/kprobe/src/arch/x86/mod.rs
Normal file
135
kernel/crates/kprobe/src/arch/x86/mod.rs
Normal file
@ -0,0 +1,135 @@
|
||||
use crate::{KprobeBasic, KprobeBuilder, KprobeOps};
|
||||
use alloc::string::ToString;
|
||||
use alloc::sync::Arc;
|
||||
use core::{
|
||||
fmt::Debug,
|
||||
ops::{Deref, DerefMut},
|
||||
};
|
||||
use yaxpeax_arch::LengthedInstruction;
|
||||
|
||||
const EBREAK_INST: u8 = 0xcc; // x86_64: 0xcc
|
||||
const MAX_INSTRUCTION_SIZE: usize = 15; // x86_64 max instruction length
|
||||
|
||||
pub struct Kprobe {
|
||||
basic: KprobeBasic,
|
||||
point: Arc<X86KprobePoint>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct X86KprobePoint {
|
||||
addr: usize,
|
||||
old_instruction: [u8; MAX_INSTRUCTION_SIZE],
|
||||
old_instruction_len: usize,
|
||||
}
|
||||
|
||||
impl Drop for X86KprobePoint {
|
||||
fn drop(&mut self) {
|
||||
let address = self.addr;
|
||||
unsafe {
|
||||
core::ptr::copy(
|
||||
self.old_instruction.as_ptr(),
|
||||
address as *mut u8,
|
||||
self.old_instruction_len,
|
||||
);
|
||||
core::arch::x86_64::_mm_mfence();
|
||||
}
|
||||
let decoder = yaxpeax_x86::amd64::InstDecoder::default();
|
||||
let inst = decoder.decode_slice(&self.old_instruction).unwrap();
|
||||
log::trace!(
|
||||
"Kprobe::uninstall: address: {:#x}, old_instruction: {:?}",
|
||||
address,
|
||||
inst.to_string()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Kprobe {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
f.debug_struct("Kprobe")
|
||||
.field("basic", &self.basic)
|
||||
.field("point", &self.point)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Kprobe {
|
||||
type Target = KprobeBasic;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.basic
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for Kprobe {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.basic
|
||||
}
|
||||
}
|
||||
|
||||
impl KprobeBuilder {
|
||||
pub fn install(self) -> (Kprobe, Arc<X86KprobePoint>) {
|
||||
let probe_point = match &self.probe_point {
|
||||
Some(point) => point.clone(),
|
||||
None => self.replace_inst(),
|
||||
};
|
||||
let kprobe = Kprobe {
|
||||
basic: KprobeBasic::from(self),
|
||||
point: probe_point.clone(),
|
||||
};
|
||||
(kprobe, probe_point)
|
||||
}
|
||||
/// # 安装kprobe
|
||||
///
|
||||
/// 不同的架构下需要保存原指令,然后替换为断点指令
|
||||
fn replace_inst(&self) -> Arc<X86KprobePoint> {
|
||||
let address = self.symbol_addr + self.offset;
|
||||
let mut inst_tmp = [0u8; MAX_INSTRUCTION_SIZE];
|
||||
unsafe {
|
||||
core::ptr::copy(
|
||||
address as *const u8,
|
||||
inst_tmp.as_mut_ptr(),
|
||||
MAX_INSTRUCTION_SIZE,
|
||||
);
|
||||
}
|
||||
let decoder = yaxpeax_x86::amd64::InstDecoder::default();
|
||||
let inst = decoder.decode_slice(&inst_tmp).unwrap();
|
||||
let len = inst.len().to_const();
|
||||
log::trace!("inst: {:?}, len: {:?}", inst.to_string(), len);
|
||||
let point = Arc::new(X86KprobePoint {
|
||||
addr: address,
|
||||
old_instruction: inst_tmp,
|
||||
old_instruction_len: len as usize,
|
||||
});
|
||||
unsafe {
|
||||
core::ptr::write_volatile(address as *mut u8, EBREAK_INST);
|
||||
core::arch::x86_64::_mm_mfence();
|
||||
}
|
||||
log::trace!(
|
||||
"Kprobe::install: address: {:#x}, func_name: {:?}",
|
||||
address,
|
||||
self.symbol
|
||||
);
|
||||
point
|
||||
}
|
||||
}
|
||||
|
||||
impl Kprobe {
|
||||
pub fn probe_point(&self) -> &Arc<X86KprobePoint> {
|
||||
&self.point
|
||||
}
|
||||
}
|
||||
|
||||
impl KprobeOps for X86KprobePoint {
|
||||
fn return_address(&self) -> usize {
|
||||
self.addr + self.old_instruction_len
|
||||
}
|
||||
fn single_step_address(&self) -> usize {
|
||||
self.old_instruction.as_ptr() as usize
|
||||
}
|
||||
fn debug_address(&self) -> usize {
|
||||
self.old_instruction.as_ptr() as usize + self.old_instruction_len
|
||||
}
|
||||
fn break_address(&self) -> usize {
|
||||
self.addr
|
||||
}
|
||||
}
|
7
kernel/crates/kprobe/src/lib.rs
Normal file
7
kernel/crates/kprobe/src/lib.rs
Normal file
@ -0,0 +1,7 @@
|
||||
#![cfg_attr(target_arch = "riscv64", feature(riscv_ext_intrinsics))]
|
||||
#![no_std]
|
||||
extern crate alloc;
|
||||
|
||||
mod arch;
|
||||
|
||||
pub use arch::*;
|
21
kernel/crates/rbpf/.appveyor.yml
Normal file
21
kernel/crates/rbpf/.appveyor.yml
Normal file
@ -0,0 +1,21 @@
|
||||
version: 1.0.{build}
|
||||
branches:
|
||||
only:
|
||||
- main
|
||||
os:
|
||||
- Visual Studio 2015
|
||||
clone_depth: 1
|
||||
configuration:
|
||||
- Debug
|
||||
platform:
|
||||
- x64
|
||||
environment:
|
||||
matrix:
|
||||
- TOOLCHAIN_VERSION: 14.0
|
||||
RUST: 1.76.0
|
||||
- TOOLCHAIN_VERSION: 14.0
|
||||
RUST: beta
|
||||
- TOOLCHAIN_VERSION: 14.0
|
||||
RUST: nightly
|
||||
|
||||
build_script: mk/appveyor.bat
|
2
kernel/crates/rbpf/.gitignore
vendored
Normal file
2
kernel/crates/rbpf/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
target
|
||||
Cargo.lock
|
78
kernel/crates/rbpf/Cargo.toml
Normal file
78
kernel/crates/rbpf/Cargo.toml
Normal file
@ -0,0 +1,78 @@
|
||||
[package]
|
||||
|
||||
# Project metadata
|
||||
name = "rbpf"
|
||||
version = "0.2.0"
|
||||
authors = ["Quentin <quentin@isovalent.com>"]
|
||||
|
||||
# Additional metadata for packaging
|
||||
description = "Virtual machine and JIT compiler for eBPF programs"
|
||||
repository = "https://github.com/qmonnet/rbpf"
|
||||
readme = "README.md"
|
||||
keywords = ["BPF", "eBPF", "interpreter", "JIT", "filtering"]
|
||||
license = "Apache-2.0/MIT"
|
||||
edition = "2021"
|
||||
|
||||
# Packaging directives
|
||||
include = [
|
||||
"src/**",
|
||||
"examples/**",
|
||||
"tests/**",
|
||||
"bench/**",
|
||||
"LICENSE*",
|
||||
"Cargo.toml",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
|
||||
# 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 }
|
||||
combine = { version = "4.6", default-features = false }
|
||||
|
||||
# Optional Dependencies when using the standard library
|
||||
libc = { version = "0.2", optional = true }
|
||||
time = { version = "0.2", optional = true }
|
||||
|
||||
# Optional Dependencies for the CraneLift JIT
|
||||
cranelift-codegen = { version = "0.99", optional = true }
|
||||
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 }
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
elf = "0.0.10"
|
||||
json = "0.11"
|
||||
hex = "0.4.3"
|
||||
|
||||
[features]
|
||||
#default = ["std", "user", "cranelift"]
|
||||
cargo-clippy = []
|
||||
std = ["dep:time", "dep:libc", "combine/std"]
|
||||
cranelift = [
|
||||
"dep:cranelift-codegen",
|
||||
"dep:cranelift-frontend",
|
||||
"dep:cranelift-jit",
|
||||
"dep:cranelift-native",
|
||||
"dep:cranelift-module",
|
||||
]
|
||||
user = []
|
||||
|
||||
# Examples that depend on the standard library should be disabled when
|
||||
# testing the `no_std` configuration.
|
||||
[[example]]
|
||||
name = "disassemble"
|
||||
required-features = ["std"]
|
||||
|
||||
[[example]]
|
||||
name = "uptime"
|
||||
required-features = ["std"]
|
||||
|
||||
[[example]]
|
||||
name = "to_json"
|
||||
|
||||
[[example]]
|
||||
name = "rbpf_plugin"
|
202
kernel/crates/rbpf/LICENSE-APACHE
Normal file
202
kernel/crates/rbpf/LICENSE-APACHE
Normal file
@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
25
kernel/crates/rbpf/LICENSE-MIT
Normal file
25
kernel/crates/rbpf/LICENSE-MIT
Normal file
@ -0,0 +1,25 @@
|
||||
Copyright (c) 2016 6WIND S.A.
|
||||
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without
|
||||
limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software
|
||||
is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice
|
||||
shall be included in all copies or substantial portions
|
||||
of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
743
kernel/crates/rbpf/README.md
Normal file
743
kernel/crates/rbpf/README.md
Normal file
@ -0,0 +1,743 @@
|
||||
# rbpf
|
||||
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="misc/rbpf_256_border.png">
|
||||
<img src="misc/rbpf_256.png">
|
||||
</picture>
|
||||
|
||||
Rust (user-space) virtual machine for eBPF
|
||||
|
||||
[](https://github.com/qmonnet/rbpf/actions/workflows/test.yaml)
|
||||
[](https://ci.appveyor.com/project/qmonnet/rbpf/branch/main)
|
||||
[](https://coveralls.io/github/qmonnet/rbpf?branch=main)
|
||||
[](https://crates.io/crates/rbpf)
|
||||
|
||||
* [Description](#description)
|
||||
* [Link to the crate](#link-to-the-crate)
|
||||
* [API](#api)
|
||||
* [Example uses](#example-uses)
|
||||
* [Building eBPF programs](#building-ebpf-programs)
|
||||
* [Build Features](#build-features)
|
||||
* [Feedback welcome!](#feedback-welcome)
|
||||
* [Questions / Answers](#questions--answers)
|
||||
* [Caveats](#caveats)
|
||||
* [_To do_ list](#to-do-list)
|
||||
* [License](#license)
|
||||
* [Inspired by](#inspired-by)
|
||||
* [Other resources](#other-resources)
|
||||
|
||||
## Description
|
||||
|
||||
This crate contains a virtual machine for eBPF program execution. BPF, as in
|
||||
_Berkeley Packet Filter_, is an assembly-like language initially developed for
|
||||
BSD systems, in order to filter packets in the kernel with tools such as
|
||||
tcpdump so as to avoid useless copies to user-space. It was ported to Linux,
|
||||
where it evolved into eBPF (_extended_ BPF), a faster version with more
|
||||
features. While BPF programs are originally intended to run in the kernel, the
|
||||
virtual machine of this crate enables running it in user-space applications;
|
||||
it contains an interpreter, an x86_64 JIT-compiler for eBPF programs, as well as
|
||||
a disassembler.
|
||||
|
||||
It is based on Rich Lane's [uBPF software](https://github.com/iovisor/ubpf/),
|
||||
which does nearly the same, but is written in C.
|
||||
|
||||
The crate is supposed to compile and run on Linux, MacOS X, and Windows,
|
||||
although the JIT-compiler does not work with Windows at this time.
|
||||
|
||||
## Link to the crate
|
||||
|
||||
This crate is available from [crates.io](https://crates.io/crates/rbpf), so it
|
||||
should work out of the box by adding it as a dependency in your `Cargo.toml`
|
||||
file:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
rbpf = "0.2.0"
|
||||
```
|
||||
|
||||
You can also use the development version from this GitHub repository. This
|
||||
should be as simple as putting this inside your `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
rbpf = { git = "https://github.com/qmonnet/rbpf" }
|
||||
```
|
||||
|
||||
Of course, if you prefer, you can clone it locally, possibly hack the crate,
|
||||
and then indicate the path of your local version in `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
rbpf = { path = "path/to/rbpf" }
|
||||
```
|
||||
|
||||
Then indicate in your source code that you want to use the crate:
|
||||
|
||||
```rust,ignore
|
||||
extern crate rbpf;
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
The API is pretty well documented inside the source code. You should also be
|
||||
able to access [an online version of the documentation from
|
||||
here](https://docs.rs/rbpf/), automatically generated from the
|
||||
[crates.io](https://crates.io/crates/rbpf) version (may not be up-to-date with
|
||||
the main branch). [Examples](../../tree/main/examples) and [unit
|
||||
tests](../../tree/main/tests) should also prove helpful. Here is a summary of
|
||||
how to use the crate.
|
||||
|
||||
Here are the steps to follow to run an eBPF program with rbpf:
|
||||
|
||||
1. Create a virtual machine. There are several kinds of machines, we will come
|
||||
back on this later. When creating the VM, pass the eBPF program as an
|
||||
argument to the constructor.
|
||||
2. If you want to use some helper functions, register them into the virtual
|
||||
machine.
|
||||
3. If you want a JIT-compiled program, compile it.
|
||||
4. Execute your program: either run the interpreter or call the JIT-compiled
|
||||
function.
|
||||
|
||||
eBPF has been initially designed to filter packets (now it has some other hooks
|
||||
in the Linux kernel, such as kprobes, but this is not covered by rbpf). As a
|
||||
consequence, most of the load and store instructions of the program are
|
||||
performed on a memory area representing the packet data. However, in the Linux
|
||||
kernel, the eBPF program does not immediately access this data area: initially,
|
||||
it has access to a C `struct sk_buff` instead, which is a buffer containing
|
||||
metadata about the packet—including memory addresses of the beginning and of
|
||||
the end of the packet data area. So the program first loads those pointers from
|
||||
the `sk_buff`, and then can access the packet data.
|
||||
|
||||
This behavior can be replicated with rbpf, but it is not mandatory. For this
|
||||
reason, we have several structs representing different kinds of virtual
|
||||
machines:
|
||||
|
||||
* `struct EbpfVmMbuffer` mimics the kernel. When the program is run, the
|
||||
address provided to its first eBPF register will be the address of a metadata
|
||||
buffer provided by the user, and that is expected to contain pointers to the
|
||||
start and the end of the packet data memory area.
|
||||
|
||||
* `struct EbpfVmFixedMbuff` has one purpose: enabling the execution of programs
|
||||
created to be compatible with the kernel, while saving the effort to manually
|
||||
handle the metadata buffer for the user. In fact, this struct has a static
|
||||
internal buffer that is passed to the program. The user has to indicate the
|
||||
offset values at which the eBPF program expects to find the start and the end
|
||||
of packet data in the buffer. On calling the function that runs the program
|
||||
(JITted or not), the struct automatically updates the addresses in this
|
||||
static buffer, at the appointed offsets, for the start and the end of the
|
||||
packet data the program is called upon.
|
||||
|
||||
* `struct EbpfVmRaw` is for programs that want to run directly on packet data.
|
||||
No metadata buffer is involved, the eBPF program directly receives the
|
||||
address of the packet data in its first register. This is the behavior of
|
||||
uBPF.
|
||||
|
||||
* `struct EbpfVmNoData` does not take any data. The eBPF program takes no
|
||||
argument whatsoever and its return value is deterministic. Not so sure there
|
||||
is a valid use case for that, but if nothing else, this is very useful for
|
||||
unit tests.
|
||||
|
||||
All these structs implement the same public functions:
|
||||
|
||||
```rust,ignore
|
||||
// called with EbpfVmMbuff:: prefix
|
||||
pub fn new(prog: &'a [u8]) -> Result<EbpfVmMbuff<'a>, Error>
|
||||
|
||||
// called with EbpfVmFixedMbuff:: prefix
|
||||
pub fn new(prog: &'a [u8],
|
||||
data_offset: usize,
|
||||
data_end_offset: usize) -> Result<EbpfVmFixedMbuff<'a>, Error>
|
||||
|
||||
// called with EbpfVmRaw:: prefix
|
||||
pub fn new(prog: &'a [u8]) -> Result<EbpfVmRaw<'a>, Error>
|
||||
|
||||
// called with EbpfVmNoData:: prefix
|
||||
pub fn new(prog: &'a [u8]) -> Result<EbpfVmNoData<'a>, Error>
|
||||
```
|
||||
|
||||
This is used to create a new instance of a VM. The return type is dependent of
|
||||
the struct from which the function is called. For instance,
|
||||
`rbpf::EbpfVmRaw::new(Some(my_program))` would return an instance of `struct
|
||||
rbpf::EbpfVmRaw` (wrapped in a `Result`). When a program is loaded, it is
|
||||
checked with a very simple verifier (nothing close to the one for Linux
|
||||
kernel). Users are also able to replace it with a custom verifier.
|
||||
|
||||
For `struct EbpfVmFixedMbuff`, two additional arguments must be passed to the
|
||||
constructor: `data_offset` and `data_end_offset`. They are the offset (byte
|
||||
number) at which the pointers to the beginning and to the end, respectively, of
|
||||
the memory area of packet data are to be stored in the internal metadata buffer
|
||||
each time the program is executed. Other structs do not use this mechanism and
|
||||
do not need those offsets.
|
||||
|
||||
```rust,ignore
|
||||
// for struct EbpfVmMbuff, struct EbpfVmRaw and struct EbpfVmRawData
|
||||
pub fn set_program(&mut self, prog: &'a [u8]) -> Result<(), Error>
|
||||
|
||||
// for struct EbpfVmFixedMbuff
|
||||
pub fn set_program(&mut self, prog: &'a [u8],
|
||||
data_offset: usize,
|
||||
data_end_offset: usize) -> Result<(), Error>
|
||||
```
|
||||
|
||||
You can use for example `my_vm.set_program(my_program);` to change the loaded
|
||||
program after the VM instance creation. This program is checked with the
|
||||
verifier attached to the VM. The verifying function of the VM can be changed at
|
||||
any moment.
|
||||
|
||||
```rust,ignore
|
||||
pub type Verifier = fn(prog: &[u8]) -> Result<(), Error>;
|
||||
|
||||
pub fn set_verifier(&mut self,
|
||||
verifier: Verifier) -> Result<(), Error>
|
||||
```
|
||||
|
||||
Note that if a program has already been loaded into the VM, setting a new
|
||||
verifier also immediately runs it on the loaded program. However, the verifier
|
||||
is not run if no program has been loaded (if `None` was passed to the `new()`
|
||||
method when creating the VM).
|
||||
|
||||
```rust,ignore
|
||||
pub type Helper = fn (u64, u64, u64, u64, u64) -> u64;
|
||||
|
||||
pub fn register_helper(&mut self,
|
||||
key: u32,
|
||||
function: Helper) -> Result<(), Error>
|
||||
```
|
||||
|
||||
This function is used to register a helper function. The VM stores its
|
||||
registers in a hashmap, so the key can be any `u32` value you want. It may be
|
||||
useful for programs that should be compatible with the Linux kernel and
|
||||
therefore must use specific helper numbers.
|
||||
|
||||
```rust,ignore
|
||||
// for struct EbpfVmMbuff
|
||||
pub fn execute_program(&self,
|
||||
mem: &'a mut [u8],
|
||||
mbuff: &'a mut [u8]) -> Result<(u64), Error>
|
||||
|
||||
// for struct EbpfVmFixedMbuff and struct EbpfVmRaw
|
||||
pub fn execute_program(&self,
|
||||
mem: &'a mut [u8]) -> Result<(u64), Error>
|
||||
|
||||
// for struct EbpfVmNoData
|
||||
pub fn execute_program(&self) -> Result<(u64), Error>
|
||||
```
|
||||
|
||||
Interprets the loaded program. The function takes a reference to the packet
|
||||
data and the metadata buffer, or only to the packet data, or nothing at all,
|
||||
depending on the kind of the VM used. The value returned is the result of the
|
||||
eBPF program.
|
||||
|
||||
```rust,ignore
|
||||
pub fn jit_compile(&mut self) -> Result<(), Error>
|
||||
```
|
||||
|
||||
JIT-compile the loaded program, for x86_64 architecture. If the program is to
|
||||
use helper functions, they must be registered into the VM before this function
|
||||
is called. The generated assembly function is internally stored in the VM.
|
||||
|
||||
```rust,ignore
|
||||
// for struct EbpfVmMbuff
|
||||
pub unsafe fn execute_program_jit(&self, mem: &'a mut [u8],
|
||||
mbuff: &'a mut [u8]) -> Result<(u64), Error>
|
||||
|
||||
// for struct EbpfVmFixedMbuff and struct EbpfVmRaw
|
||||
pub unsafe fn execute_program_jit(&self, mem: &'a mut [u8]) -> Result<(u64), Error>
|
||||
|
||||
// for struct EbpfVmNoData
|
||||
pub unsafe fn execute_program_jit(&self) -> Result<(u64), Error>
|
||||
```
|
||||
|
||||
Calls the JIT-compiled program. The arguments to provide are the same as for
|
||||
`execute_program()`, again depending on the kind of VM that is used. The result of
|
||||
the JIT-compiled program should be the same as with the interpreter, but it
|
||||
should run faster. Note that if errors occur during the program execution, the
|
||||
JIT-compiled version does not handle it as well as the interpreter, and the
|
||||
program may crash. For this reason, the functions are marked as `unsafe`.
|
||||
|
||||
## Example uses
|
||||
|
||||
### Simple example
|
||||
|
||||
This comes from the unit test `test_vm_add`.
|
||||
|
||||
```rust
|
||||
extern crate rbpf;
|
||||
|
||||
fn main() {
|
||||
|
||||
// This is the eBPF program, in the form of bytecode instructions.
|
||||
let prog = &[
|
||||
0xb4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov32 r0, 0
|
||||
0xb4, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, // mov32 r1, 2
|
||||
0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, // add32 r0, 1
|
||||
0x0c, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // add32 r0, r1
|
||||
0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // exit
|
||||
];
|
||||
|
||||
// Instantiate a struct EbpfVmNoData. This is an eBPF VM for programs that
|
||||
// takes no packet data in argument.
|
||||
// The eBPF program is passed to the constructor.
|
||||
let vm = rbpf::EbpfVmNoData::new(Some(prog)).unwrap();
|
||||
|
||||
// Execute (interpret) the program. No argument required for this VM.
|
||||
assert_eq!(vm.execute_program().unwrap(), 0x3);
|
||||
}
|
||||
```
|
||||
|
||||
### With JIT, on packet data
|
||||
|
||||
This comes from the unit test `test_jit_ldxh`.
|
||||
|
||||
```rust
|
||||
extern crate rbpf;
|
||||
|
||||
fn main() {
|
||||
let prog = &[
|
||||
0x71, 0x10, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // ldxh r0, [r1+2]
|
||||
0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // exit
|
||||
];
|
||||
|
||||
// Let's use some data.
|
||||
let mem = &mut [
|
||||
0xaa, 0xbb, 0x11, 0xcc, 0xdd
|
||||
];
|
||||
|
||||
// This is an eBPF VM for programs reading from a given memory area (it
|
||||
// directly reads from packet data)
|
||||
let mut vm = rbpf::EbpfVmRaw::new(Some(prog)).unwrap();
|
||||
|
||||
#[cfg(any(windows, not(feature = "std")))] {
|
||||
assert_eq!(vm.execute_program(mem).unwrap(), 0x11);
|
||||
}
|
||||
#[cfg(all(not(windows), feature = "std"))] {
|
||||
// This time we JIT-compile the program.
|
||||
vm.jit_compile().unwrap();
|
||||
|
||||
// Then we execute it. For this kind of VM, a reference to the packet
|
||||
// data must be passed to the function that executes the program.
|
||||
unsafe { assert_eq!(vm.execute_program_jit(mem).unwrap(), 0x11); }
|
||||
}
|
||||
}
|
||||
```
|
||||
### Using a metadata buffer
|
||||
|
||||
This comes from the unit test `test_jit_mbuff` and derives from the unit test
|
||||
`test_jit_ldxh`.
|
||||
|
||||
```rust
|
||||
extern crate rbpf;
|
||||
|
||||
fn main() {
|
||||
let prog = &[
|
||||
// Load mem from mbuff at offset 8 into R1
|
||||
0x79, 0x11, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// ldhx r1[2], r0
|
||||
0x69, 0x10, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||
];
|
||||
let mem = &mut [
|
||||
0xaa, 0xbb, 0x11, 0x22, 0xcc, 0xdd
|
||||
];
|
||||
|
||||
// Just for the example we create our metadata buffer from scratch, and
|
||||
// we store the pointers to packet data start and end in it.
|
||||
let mut mbuff = &mut [0u8; 32];
|
||||
unsafe {
|
||||
let mut data = mbuff.as_ptr().offset(8) as *mut u64;
|
||||
let mut data_end = mbuff.as_ptr().offset(24) as *mut u64;
|
||||
*data = mem.as_ptr() as u64;
|
||||
*data_end = mem.as_ptr() as u64 + mem.len() as u64;
|
||||
}
|
||||
|
||||
// This eBPF VM is for program that use a metadata buffer.
|
||||
let mut vm = rbpf::EbpfVmMbuff::new(Some(prog)).unwrap();
|
||||
|
||||
#[cfg(any(windows, not(feature = "std")))] {
|
||||
assert_eq!(vm.execute_program(mem, mbuff).unwrap(), 0x2211);
|
||||
}
|
||||
#[cfg(all(not(windows), feature = "std"))] {
|
||||
// Here again we JIT-compile the program.
|
||||
vm.jit_compile().unwrap();
|
||||
|
||||
// Here we must provide both a reference to the packet data, and to the
|
||||
// metadata buffer we use.
|
||||
unsafe {
|
||||
assert_eq!(vm.execute_program_jit(mem, mbuff).unwrap(), 0x2211);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Loading code from an object file; and using a virtual metadata buffer
|
||||
|
||||
This comes from unit test `test_vm_block_port`.
|
||||
|
||||
This example requires the following additional crates, you may have to add them
|
||||
to your `Cargo.toml` file.
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
rbpf = "0.2.0"
|
||||
elf = "0.0.10"
|
||||
```
|
||||
|
||||
It also uses a kind of VM that uses an internal buffer used to simulate the
|
||||
`sk_buff` used by eBPF programs in the kernel, without having to manually
|
||||
create a new buffer for each packet. It may be useful for programs compiled for
|
||||
the kernel and that assumes the data they receive is a `sk_buff` pointing to
|
||||
the packet data start and end addresses. So here we just provide the offsets at
|
||||
which the eBPF program expects to find those pointers, and the VM handles the
|
||||
buffer update so that we only have to provide a reference to the packet data
|
||||
for each run of the program.
|
||||
|
||||
```rust
|
||||
extern crate elf;
|
||||
use std::path::PathBuf;
|
||||
|
||||
extern crate rbpf;
|
||||
use rbpf::helpers;
|
||||
|
||||
fn main() {
|
||||
// Load a program from an ELF file, e.g. compiled from C to eBPF with
|
||||
// clang/LLVM. Some minor modification to the bytecode may be required.
|
||||
let filename = "examples/load_elf__block_a_port.elf";
|
||||
|
||||
let path = PathBuf::from(filename);
|
||||
let file = match elf::File::open_path(&path) {
|
||||
Ok(f) => f,
|
||||
Err(e) => panic!("Error: {:?}", e),
|
||||
};
|
||||
|
||||
// Here we assume the eBPF program is in the ELF section called
|
||||
// ".classifier".
|
||||
let text_scn = match file.get_section(".classifier") {
|
||||
Some(s) => s,
|
||||
None => panic!("Failed to look up .classifier section"),
|
||||
};
|
||||
|
||||
let prog = &text_scn.data;
|
||||
|
||||
// This is our data: a real packet, starting with Ethernet header
|
||||
let packet = &mut [
|
||||
0x01, 0x23, 0x45, 0x67, 0x89, 0xab,
|
||||
0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54,
|
||||
0x08, 0x00, // ethertype
|
||||
0x45, 0x00, 0x00, 0x3b, // start ip_hdr
|
||||
0xa6, 0xab, 0x40, 0x00,
|
||||
0x40, 0x06, 0x96, 0x0f,
|
||||
0x7f, 0x00, 0x00, 0x01,
|
||||
0x7f, 0x00, 0x00, 0x01,
|
||||
0x99, 0x99, 0xc6, 0xcc, // start tcp_hdr
|
||||
0xd1, 0xe5, 0xc4, 0x9d,
|
||||
0xd4, 0x30, 0xb5, 0xd2,
|
||||
0x80, 0x18, 0x01, 0x56,
|
||||
0xfe, 0x2f, 0x00, 0x00,
|
||||
0x01, 0x01, 0x08, 0x0a, // start data
|
||||
0x00, 0x23, 0x75, 0x89,
|
||||
0x00, 0x23, 0x63, 0x2d,
|
||||
0x71, 0x64, 0x66, 0x73,
|
||||
0x64, 0x66, 0x0a
|
||||
];
|
||||
|
||||
// This is an eBPF VM for programs using a virtual metadata buffer, similar
|
||||
// to the sk_buff that eBPF programs use with tc and in Linux kernel.
|
||||
// We must provide the offsets at which the pointers to packet data start
|
||||
// and end must be stored: these are the offsets at which the program will
|
||||
// load the packet data from the metadata buffer.
|
||||
let mut vm = rbpf::EbpfVmFixedMbuff::new(Some(prog), 0x40, 0x50).unwrap();
|
||||
|
||||
// We register a helper function, that can be called by the program, into
|
||||
// the VM. The `bpf_trace_printf` is only available when we have access to
|
||||
// the standard library.
|
||||
#[cfg(feature = "std")] {
|
||||
vm.register_helper(helpers::BPF_TRACE_PRINTK_IDX,
|
||||
helpers::bpf_trace_printf).unwrap();
|
||||
}
|
||||
|
||||
// This kind of VM takes a reference to the packet data, but does not need
|
||||
// any reference to the metadata buffer: a fixed buffer is handled
|
||||
// internally by the VM.
|
||||
let res = vm.execute_program(packet).unwrap();
|
||||
println!("Program returned: {:?} ({:#x})", res, res);
|
||||
}
|
||||
```
|
||||
|
||||
## Building eBPF programs
|
||||
|
||||
Besides passing the raw hexadecimal codes for building eBPF programs, two other
|
||||
methods are available.
|
||||
|
||||
### Assembler
|
||||
|
||||
The first method consists in using the assembler provided by the crate.
|
||||
|
||||
```rust
|
||||
extern crate rbpf;
|
||||
use rbpf::assembler::assemble;
|
||||
|
||||
let prog = assemble("add64 r1, 0x605
|
||||
mov64 r2, 0x32
|
||||
mov64 r1, r0
|
||||
be16 r0
|
||||
neg64 r2
|
||||
exit").unwrap();
|
||||
|
||||
#[cfg(feature = "std")] {
|
||||
println!("{:?}", prog);
|
||||
}
|
||||
```
|
||||
|
||||
The above snippet will produce:
|
||||
|
||||
```rust,ignore
|
||||
Ok([0x07, 0x01, 0x00, 0x00, 0x05, 0x06, 0x00, 0x00,
|
||||
0xb7, 0x02, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00,
|
||||
0xbf, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0xdc, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
|
||||
0x87, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
|
||||
```
|
||||
|
||||
Conversely, a disassembler is also available to dump instruction names from
|
||||
bytecode in a human-friendly format.
|
||||
|
||||
```rust
|
||||
extern crate rbpf;
|
||||
use rbpf::disassembler::disassemble;
|
||||
|
||||
let prog = &[
|
||||
0x07, 0x01, 0x00, 0x00, 0x05, 0x06, 0x00, 0x00,
|
||||
0xb7, 0x02, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00,
|
||||
0xbf, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0xdc, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
|
||||
0x87, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||
];
|
||||
|
||||
disassemble(prog);
|
||||
```
|
||||
|
||||
This will produce the following output:
|
||||
|
||||
```txt
|
||||
add64 r1, 0x605
|
||||
mov64 r2, 0x32
|
||||
mov64 r1, r0
|
||||
be16 r0
|
||||
neg64 r2
|
||||
exit
|
||||
```
|
||||
|
||||
Please refer to [source code](src/assembler.rs) and [tests](tests/assembler.rs)
|
||||
for the syntax and the list of instruction names.
|
||||
|
||||
### Building API
|
||||
|
||||
The other way to build programs is to chain commands from the instruction
|
||||
builder API. It looks less like assembly, maybe more like high-level functions.
|
||||
What's sure is that the result is more verbose, but if you prefer to build
|
||||
programs this way, it works just as well. If we take again the same sample as
|
||||
above, it would be constructed as follows.
|
||||
|
||||
```rust
|
||||
extern crate rbpf;
|
||||
use rbpf::insn_builder::*;
|
||||
|
||||
let mut program = BpfCode::new();
|
||||
program.add(Source::Imm, Arch::X64).set_dst(1).set_imm(0x605).push()
|
||||
.mov(Source::Imm, Arch::X64).set_dst(2).set_imm(0x32).push()
|
||||
.mov(Source::Reg, Arch::X64).set_src(0).set_dst(1).push()
|
||||
.swap_bytes(Endian::Big).set_dst(0).set_imm(0x10).push()
|
||||
.negate(Arch::X64).set_dst(2).push()
|
||||
.exit().push();
|
||||
```
|
||||
|
||||
Again, please refer to [the source and related tests](src/insn_builder.rs) to
|
||||
get more information and examples on how to use it.
|
||||
|
||||
## Build features
|
||||
|
||||
### `no_std`
|
||||
|
||||
The `rbpf` crate has a Cargo feature named "std" that is enabled by default. To
|
||||
use `rbpf` in `no_std` environments this feature needs to be disabled. To do
|
||||
this, you need to modify your dependency on `rbpf` in Cargo.toml to disable the
|
||||
enabled-by-default features.
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
rbpf = { version = "1.0", default-features = false }
|
||||
```
|
||||
|
||||
Note that when using this crate in `no_std` environments, the `jit` module
|
||||
isn't available. This is because it depends on functions provided by `libc`
|
||||
(`libc::posix_memalign()`, `libc::mprotect()`) which aren't available on
|
||||
`no_std`.
|
||||
|
||||
The `assembler` module is available, albeit with reduced debugging features. It
|
||||
depends on the `combine` crate providing parser combinators. Under `no_std`
|
||||
this crate only provides simple parsers which generate less descriptive error
|
||||
messages.
|
||||
|
||||
## Feedback welcome!
|
||||
|
||||
This is the author's first try at writing Rust code. He learned a lot in the
|
||||
process, but there remains a feeling that this crate has a kind of C-ish style
|
||||
in some places instead of the Rusty look the author would like it to have. So
|
||||
feedback (or PRs) are welcome, including about ways you might see to take
|
||||
better advantage of Rust features.
|
||||
|
||||
Note that the project expects new commits to be covered by the
|
||||
[Developer's Certificate of Origin](https://wiki.linuxfoundation.org/dco).
|
||||
When contributing Pull Requests, please sign off your commits accordingly.
|
||||
|
||||
## Questions / Answers
|
||||
|
||||
### Why implementing an eBPF virtual machine in Rust?
|
||||
|
||||
As of this writing, there is no particular use case for this crate at the best
|
||||
of the author's knowledge. The author happens to work with BPF on Linux and to
|
||||
know how uBPF works, and he wanted to learn and experiment with Rust—no more
|
||||
than that.
|
||||
|
||||
### What are the differences with uBPF?
|
||||
|
||||
Other than the language, obviously? Well, there are some differences:
|
||||
|
||||
* Some constants, such as the maximum length for programs or the length for the
|
||||
stack, differs between uBPF and rbpf. The latter uses the same values as the
|
||||
Linux kernel, while uBPF has its own values.
|
||||
|
||||
* When an error occurs while a program is run by uBPF, the function running the
|
||||
program silently returns the maximum value as an error code, while rbpf
|
||||
returns Rust type `Error`.
|
||||
|
||||
* The registration of helper functions, that can be called from within an eBPF
|
||||
program, is not handled in the same way.
|
||||
|
||||
* The distinct structs permitting to run program either on packet data, or with
|
||||
a metadata buffer (simulated or not) is a specificity of rbpf.
|
||||
|
||||
* As for performance: theoretically the JITted programs are expected to run at
|
||||
the same speed, while the C interpreter of uBPF should go slightly faster
|
||||
than rbpf. But this has not been asserted yet. Benchmarking both programs
|
||||
would be an interesting thing to do.
|
||||
|
||||
### Can I use it with the “classic” BPF (a.k.a cBPF) version?
|
||||
|
||||
No. This crate only works with extended BPF (eBPF) programs. For cBPF programs,
|
||||
such as used by tcpdump (as of this writing) for example, you may be interested
|
||||
in the [bpfjit crate](https://crates.io/crates/bpfjit) written by Alexander
|
||||
Polakov instead.
|
||||
|
||||
### What functionalities are implemented?
|
||||
|
||||
Running and JIT-compiling eBPF programs work. There is also a mechanism to
|
||||
register user-defined helper functions. The eBPF implementation of the Linux
|
||||
kernel comes with [some additional
|
||||
features](https://github.com/iovisor/bcc/blob/master/docs/kernel-versions.md):
|
||||
a high number of helpers, several kinds of maps, tail calls.
|
||||
|
||||
* Additional helpers should be easy to add, but very few of the existing Linux
|
||||
helpers have been replicated in rbpf so far.
|
||||
|
||||
* Tail calls (“long jumps” from an eBPF program into another) are not
|
||||
implemented. This is probably not trivial to design and implement.
|
||||
|
||||
* The interaction with maps is done through the use of specific helpers, so
|
||||
this should not be difficult to add. The maps themselves can reuse the maps
|
||||
in the kernel (if on Linux), to communicate with in-kernel eBPF programs for
|
||||
instance; or they can be handled in user space. Rust has arrays and hashmaps,
|
||||
so their implementation should be pretty straightforward (and may be added to
|
||||
rbpf in the future).
|
||||
|
||||
### What about program validation?
|
||||
|
||||
The ”verifier” of this crate is very short and has nothing to do with the
|
||||
kernel verifier, which means that it accepts programs that may not be safe. On
|
||||
the other hand, you probably do not run this in a kernel here, so it will not
|
||||
crash your system. Implementing a verifier similar to the one in the kernel is
|
||||
not trivial, and we cannot “copy” it since it is under GPL license.
|
||||
|
||||
### What about safety then?
|
||||
|
||||
Rust has a strong emphasis on safety. Yet to have the eBPF VM work, some
|
||||
`unsafe` blocks of code are used. The VM, taken as an eBPF interpreter, can
|
||||
return an error but should not crash. Please file an issue otherwise.
|
||||
|
||||
As for the JIT-compiler, it is a different story, since runtime memory checks
|
||||
are more complicated to implement in assembly. It _will_ crash if your
|
||||
JIT-compiled program tries to perform unauthorized memory accesses. Usually, it
|
||||
could be a good idea to test your program with the interpreter first.
|
||||
|
||||
Oh, and if your program has infinite loops, even with the interpreter, you're
|
||||
on your own.
|
||||
|
||||
## Caveats
|
||||
|
||||
* This crate is **under development** and the API may be subject to change.
|
||||
|
||||
* The JIT compiler produces an unsafe program: memory access are not tested at
|
||||
runtime (yet). Use with caution.
|
||||
|
||||
* A small number of eBPF instructions have not been implemented yet. This
|
||||
should not be a problem for the majority of eBPF programs.
|
||||
|
||||
* Beware of turnips. Turnips are disgusting.
|
||||
|
||||
## _To do_ list
|
||||
|
||||
* Implement some traits (`Clone`, `Drop`, `Debug` are good candidates).
|
||||
* Provide built-in support for user-space array and hash BPF maps.
|
||||
* Improve safety of JIT-compiled programs with runtime memory checks.
|
||||
* Add helpers (some of those supported in the kernel, such as checksum update,
|
||||
could be helpful).
|
||||
* Improve verifier. Could we find a way to directly support programs compiled
|
||||
with clang?
|
||||
* Maybe one day, tail calls?
|
||||
* JIT-compilers for other architectures?
|
||||
* …
|
||||
|
||||
## License
|
||||
|
||||
Following the effort of the Rust language project itself in order to ease
|
||||
integration with other projects, the rbpf crate is distributed under the terms
|
||||
of both the MIT license and the Apache License (Version 2.0).
|
||||
|
||||
See
|
||||
[LICENSE-APACHE](https://github.com/qmonnet/rbpf/blob/main/LICENSE-APACHE)
|
||||
and [LICENSE-MIT](https://github.com/qmonnet/rbpf/blob/main/LICENSE-MIT) for
|
||||
details.
|
||||
|
||||
## Version
|
||||
[The last commit](https://github.com/qmonnet/rbpf/commit/fe7021b07b08a43b836743a77796d07ce1f4902e)
|
||||
|
||||
|
||||
## Inspired by
|
||||
|
||||
* [uBPF](https://github.com/iovisor/ubpf), a C user-space implementation of an
|
||||
eBPF virtual machine, with a JIT-compiler and disassembler (and also
|
||||
including the assembler from the human-readable form of the instructions,
|
||||
such as in `mov r0, 0x1337`), by Rich Lane for Big Switch Networks (2015)
|
||||
|
||||
* [_Building a simple JIT in
|
||||
Rust_](https://www.sophiajt.com/building-a-simple-jit-in-rust),
|
||||
by Sophia Turner (2015)
|
||||
|
||||
* [bpfjit](https://github.com/polachok/bpfjit) (also [on
|
||||
crates.io](https://crates.io/crates/bpfjit)), a Rust crate exporting the cBPF
|
||||
JIT compiler from FreeBSD 10 tree to Rust, by Alexander Polakov (2016)
|
||||
|
||||
## Other resources
|
||||
|
||||
* Cilium project documentation about BPF: [_BPF and XDP Reference
|
||||
Guide_](http://docs.cilium.io/en/latest/bpf/)
|
||||
|
||||
* [Kernel documentation about BPF](https://docs.kernel.org/bpf/)
|
||||
|
||||
* [_Dive into BPF: a list of reading
|
||||
material_](https://qmonnet.github.io/whirl-offload/2016/09/01/dive-into-bpf),
|
||||
a blog article listing documentation for BPF and related technologies (2016)
|
||||
|
||||
* [The Rust programming language](https://www.rust-lang.org)
|
1
kernel/crates/rbpf/clippy.toml
Normal file
1
kernel/crates/rbpf/clippy.toml
Normal file
@ -0,0 +1 @@
|
||||
doc-valid-idents = ["eBPF", "uBPF"]
|
26
kernel/crates/rbpf/examples/disassemble.rs
Normal file
26
kernel/crates/rbpf/examples/disassemble.rs
Normal file
@ -0,0 +1,26 @@
|
||||
// SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
// Copyright 2017 6WIND S.A. <quentin.monnet@6wind.com>
|
||||
|
||||
extern crate rbpf;
|
||||
use rbpf::disassembler;
|
||||
|
||||
// Simply disassemble a program into human-readable instructions.
|
||||
fn main() {
|
||||
let prog = &[
|
||||
0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79, 0x12, 0x50, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x79, 0x11, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbf, 0x13, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x07, 0x03, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x2d, 0x23, 0x12, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x69, 0x12, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x02, 0x10, 0x00,
|
||||
0x08, 0x00, 0x00, 0x00, 0x71, 0x12, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x02, 0x0e,
|
||||
0x00, 0x06, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79, 0x11, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbf,
|
||||
0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, 0x02, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00,
|
||||
0x15, 0x02, 0x08, 0x00, 0x99, 0x99, 0x00, 0x00, 0x18, 0x02, 0x00, 0x00, 0x00, 0x00, 0xff,
|
||||
0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5f, 0x21, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0xb7, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x18, 0x02, 0x00, 0x00, 0x00,
|
||||
0x00, 0x99, 0x99, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1d, 0x21, 0x01, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x95, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
];
|
||||
disassembler::disassemble(prog);
|
||||
}
|
3
kernel/crates/rbpf/examples/helper.rs
Normal file
3
kernel/crates/rbpf/examples/helper.rs
Normal file
@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
rbpf::helpers::show_helper();
|
||||
}
|
115
kernel/crates/rbpf/examples/load_elf.rs
Normal file
115
kernel/crates/rbpf/examples/load_elf.rs
Normal file
@ -0,0 +1,115 @@
|
||||
// SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
// Copyright 2016 6WIND S.A. <quentin.monnet@6wind.com>
|
||||
|
||||
#![allow(clippy::unreadable_literal)]
|
||||
|
||||
extern crate elf;
|
||||
use std::path::PathBuf;
|
||||
|
||||
extern crate rbpf;
|
||||
use rbpf::helpers;
|
||||
|
||||
// The following example uses an ELF file that has been compiled from the C program available in
|
||||
// `load_elf__block_a_port.c` in the same directory.
|
||||
//
|
||||
// It was compiled with the following command:
|
||||
//
|
||||
// ```bash
|
||||
// clang -O2 -emit-llvm -c load_elf__block_a_port.c -o - | \
|
||||
// llc -march=bpf -filetype=obj -o load_elf__block_a_port.o
|
||||
// ```
|
||||
//
|
||||
// Once compiled, this program can be injected into Linux kernel, with tc for instance. Sadly, we
|
||||
// need to bring some modifications to the generated bytecode in order to run it: the three
|
||||
// instructions with opcode 0x61 load data from a packet area as 4-byte words, where we need to
|
||||
// load it as 8-bytes double words (0x79). The kernel does the same kind of translation before
|
||||
// running the program, but rbpf does not implement this.
|
||||
//
|
||||
// In addition, the offset at which the pointer to the packet data is stored must be changed: since
|
||||
// we use 8 bytes instead of 4 for the start and end addresses of the data packet, we cannot use
|
||||
// the offsets produced by clang (0x4c and 0x50), the addresses would overlap. Instead we can use,
|
||||
// for example, 0x40 and 0x50.
|
||||
//
|
||||
// These change were applied with the following script:
|
||||
//
|
||||
// ```bash
|
||||
// xxd load_elf__block_a_port.o | sed '
|
||||
// s/6112 5000 0000 0000/7912 5000 0000 0000/ ;
|
||||
// s/6111 4c00 0000 0000/7911 4000 0000 0000/ ;
|
||||
// s/6111 2200 0000 0000/7911 2200 0000 0000/' | xxd -r > load_elf__block_a_port.tmp
|
||||
|
||||
// mv load_elf__block_a_port.tmp load_elf__block_a_port.o
|
||||
// ```
|
||||
//
|
||||
// The eBPF program was placed into the `.classifier` ELF section (see C code above), which means
|
||||
// that you can retrieve the raw bytecode with `readelf -x .classifier load_elf__block_a_port.o` or
|
||||
// with `objdump -s -j .classifier load_elf__block_a_port.o`.
|
||||
//
|
||||
// Once the bytecode has been edited, we can load the bytecode directly from the ELF object file.
|
||||
|
||||
fn main() {
|
||||
let filename = "examples/load_elf__block_a_port.elf";
|
||||
|
||||
let path = PathBuf::from(filename);
|
||||
let file = match elf::File::open_path(path) {
|
||||
Ok(f) => f,
|
||||
Err(e) => panic!("Error: {:?}", e),
|
||||
};
|
||||
|
||||
let text_scn = match file.get_section(".classifier") {
|
||||
Some(s) => s,
|
||||
None => panic!("Failed to look up .classifier section"),
|
||||
};
|
||||
|
||||
let prog = &text_scn.data;
|
||||
|
||||
let packet1 = &mut [
|
||||
0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x08,
|
||||
0x00, // ethertype
|
||||
0x45, 0x00, 0x00, 0x3b, // start ip_hdr
|
||||
0xa6, 0xab, 0x40, 0x00, 0x40, 0x06, 0x96, 0x0f, 0x7f, 0x00, 0x00, 0x01, 0x7f, 0x00, 0x00,
|
||||
0x01,
|
||||
// Program matches the next two bytes: 0x9999 returns 0xffffffff, else return 0.
|
||||
0x99, 0x99, 0xc6, 0xcc, // start tcp_hdr
|
||||
0xd1, 0xe5, 0xc4, 0x9d, 0xd4, 0x30, 0xb5, 0xd2, 0x80, 0x18, 0x01, 0x56, 0xfe, 0x2f, 0x00,
|
||||
0x00, 0x01, 0x01, 0x08, 0x0a, // start data
|
||||
0x00, 0x23, 0x75, 0x89, 0x00, 0x23, 0x63, 0x2d, 0x71, 0x64, 0x66, 0x73, 0x64, 0x66, 0x0au8,
|
||||
];
|
||||
|
||||
let packet2 = &mut [
|
||||
0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x08,
|
||||
0x00, // ethertype
|
||||
0x45, 0x00, 0x00, 0x3b, // start ip_hdr
|
||||
0xa6, 0xab, 0x40, 0x00, 0x40, 0x06, 0x96, 0x0f, 0x7f, 0x00, 0x00, 0x01, 0x7f, 0x00, 0x00,
|
||||
0x01,
|
||||
// Program matches the next two bytes: 0x9999 returns 0xffffffff, else return 0.
|
||||
0x98, 0x76, 0xc6, 0xcc, // start tcp_hdr
|
||||
0xd1, 0xe5, 0xc4, 0x9d, 0xd4, 0x30, 0xb5, 0xd2, 0x80, 0x18, 0x01, 0x56, 0xfe, 0x2f, 0x00,
|
||||
0x00, 0x01, 0x01, 0x08, 0x0a, // start data
|
||||
0x00, 0x23, 0x75, 0x89, 0x00, 0x23, 0x63, 0x2d, 0x71, 0x64, 0x66, 0x73, 0x64, 0x66, 0x0au8,
|
||||
];
|
||||
|
||||
let mut vm = rbpf::EbpfVmFixedMbuff::new(Some(prog), 0x40, 0x50).unwrap();
|
||||
vm.register_helper(helpers::BPF_TRACE_PRINTK_IDX, helpers::bpf_trace_printf)
|
||||
.unwrap();
|
||||
|
||||
let res = vm.execute_program(packet1).unwrap();
|
||||
println!("Packet #1, program returned: {res:?} ({res:#x})");
|
||||
assert_eq!(res, 0xffffffff);
|
||||
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
vm.jit_compile().unwrap();
|
||||
|
||||
let res = unsafe { vm.execute_program_jit(packet2).unwrap() };
|
||||
println!("Packet #2, program returned: {res:?} ({res:#x})");
|
||||
assert_eq!(res, 0);
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let res = vm.execute_program(packet2).unwrap();
|
||||
println!("Packet #2, program returned: {:?} ({:#x})", res, res);
|
||||
assert_eq!(res, 0);
|
||||
}
|
||||
}
|
43
kernel/crates/rbpf/examples/load_elf__block_a_port.c
Normal file
43
kernel/crates/rbpf/examples/load_elf__block_a_port.c
Normal file
@ -0,0 +1,43 @@
|
||||
// SPDX-License-Identifier: (APACHE-2.0 OR MIT)
|
||||
// Copyright 2016 6WIND S.A. <quentin.monnet@6wind.com>
|
||||
|
||||
// Block TCP packets on source or destination port 0x9999.
|
||||
|
||||
#include <linux/ip.h>
|
||||
#include <linux/in.h>
|
||||
#include <linux/tcp.h>
|
||||
#include <linux/bpf.h>
|
||||
|
||||
#define ETH_ALEN 6
|
||||
#define ETH_P_IP 0x0008 /* htons(0x0800) */
|
||||
#define TCP_HDR_LEN 20
|
||||
|
||||
#define BLOCKED_TCP_PORT 0x9999
|
||||
|
||||
struct eth_hdr {
|
||||
unsigned char h_dest[ETH_ALEN];
|
||||
unsigned char h_source[ETH_ALEN];
|
||||
unsigned short h_proto;
|
||||
};
|
||||
|
||||
#define SEC(NAME) __attribute__((section(NAME), used))
|
||||
SEC(".classifier")
|
||||
int handle_ingress(struct __sk_buff *skb)
|
||||
{
|
||||
void *data = (void *)(long)skb->data;
|
||||
void *data_end = (void *)(long)skb->data_end;
|
||||
struct eth_hdr *eth = data;
|
||||
struct iphdr *iph = data + sizeof(*eth);
|
||||
struct tcphdr *tcp = data + sizeof(*eth) + sizeof(*iph);
|
||||
|
||||
/* single length check */
|
||||
if (data + sizeof(*eth) + sizeof(*iph) + sizeof(*tcp) > data_end)
|
||||
return 0;
|
||||
if (eth->h_proto != ETH_P_IP)
|
||||
return 0;
|
||||
if (iph->protocol != IPPROTO_TCP)
|
||||
return 0;
|
||||
if (tcp->source == BLOCKED_TCP_PORT || tcp->dest == BLOCKED_TCP_PORT)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
126
kernel/crates/rbpf/examples/rbpf_plugin.rs
Normal file
126
kernel/crates/rbpf/examples/rbpf_plugin.rs
Normal file
@ -0,0 +1,126 @@
|
||||
// Copyright Microsoft Corporation
|
||||
// SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
|
||||
// Path: examples/rbpf_plugin.rs
|
||||
use std::io::Read;
|
||||
|
||||
// Helper function used by https://github.com/Alan-Jowett/bpf_conformance/blob/main/tests/call_unwind_fail.data
|
||||
fn _unwind(a: u64, _b: u64, _c: u64, _d: u64, _e: u64) -> u64 {
|
||||
a
|
||||
}
|
||||
|
||||
// This is a plugin for the bpf_conformance test suite (https://github.com/Alan-Jowett/bpf_conformance)
|
||||
// It accepts a single argument, the memory contents to pass to the VM.
|
||||
// It reads the program from stdin.
|
||||
fn main() {
|
||||
let mut args: Vec<String> = std::env::args().collect();
|
||||
#[allow(unused_mut)] // In no_std the jit variable isn't mutated.
|
||||
let mut jit: bool = false;
|
||||
let mut cranelift: bool = false;
|
||||
let mut program_text = String::new();
|
||||
let mut memory_text = String::new();
|
||||
|
||||
args.remove(0);
|
||||
|
||||
// Memory is always the first argument.
|
||||
if !args.is_empty() {
|
||||
memory_text.clone_from(&args[0]);
|
||||
// Strip whitespace
|
||||
memory_text.retain(|c| !c.is_whitespace());
|
||||
args.remove(0);
|
||||
}
|
||||
|
||||
// Process the rest of the arguments.
|
||||
while !args.is_empty() {
|
||||
match args[0].as_str() {
|
||||
"--help" => {
|
||||
println!("Usage: rbpf_plugin [memory] < program");
|
||||
return;
|
||||
}
|
||||
"--jit" => {
|
||||
#[cfg(any(windows, not(feature = "std")))]
|
||||
{
|
||||
println!("JIT not supported");
|
||||
return;
|
||||
}
|
||||
#[cfg(all(not(windows), feature = "std"))]
|
||||
{
|
||||
jit = true;
|
||||
}
|
||||
}
|
||||
"--cranelift" => {
|
||||
cranelift = true;
|
||||
|
||||
#[cfg(not(feature = "cranelift"))]
|
||||
{
|
||||
let _ = cranelift;
|
||||
println!("Cranelift is not enabled");
|
||||
return;
|
||||
}
|
||||
}
|
||||
"--program" => {
|
||||
if args.len() < 2 {
|
||||
println!("Missing argument to --program");
|
||||
return;
|
||||
}
|
||||
args.remove(0);
|
||||
if !args.is_empty() {
|
||||
program_text.clone_from(&args[0]);
|
||||
args.remove(0);
|
||||
}
|
||||
}
|
||||
_ => panic!("Unknown argument {}", args[0]),
|
||||
}
|
||||
args.remove(0);
|
||||
}
|
||||
|
||||
if program_text.is_empty() {
|
||||
// Read program text from stdin
|
||||
std::io::stdin().read_to_string(&mut program_text).unwrap();
|
||||
}
|
||||
|
||||
// Strip whitespace
|
||||
program_text.retain(|c| !c.is_whitespace());
|
||||
|
||||
// Convert program from hex to bytecode
|
||||
let bytecode = hex::decode(program_text).unwrap();
|
||||
|
||||
// Convert memory from hex to bytes
|
||||
let mut memory: Vec<u8> = hex::decode(memory_text).unwrap();
|
||||
|
||||
// Create rbpf vm
|
||||
let mut vm = rbpf::EbpfVmRaw::new(Some(&bytecode)).unwrap();
|
||||
|
||||
// Register the helper function used by call_unwind_fail.data test.
|
||||
vm.register_helper(5, _unwind).unwrap();
|
||||
|
||||
let result: u64;
|
||||
if jit {
|
||||
#[cfg(any(windows, not(feature = "std")))]
|
||||
{
|
||||
println!("JIT not supported");
|
||||
return;
|
||||
}
|
||||
#[cfg(all(not(windows), feature = "std"))]
|
||||
{
|
||||
unsafe {
|
||||
vm.jit_compile().unwrap();
|
||||
result = vm.execute_program_jit(&mut memory).unwrap();
|
||||
}
|
||||
}
|
||||
} else if cranelift {
|
||||
#[cfg(not(feature = "cranelift"))]
|
||||
{
|
||||
println!("Cranelift is not enabled");
|
||||
return;
|
||||
}
|
||||
#[cfg(feature = "cranelift")]
|
||||
{
|
||||
vm.cranelift_compile().unwrap();
|
||||
result = vm.execute_program_cranelift(&mut memory).unwrap();
|
||||
}
|
||||
} else {
|
||||
result = vm.execute_program(&mut memory).unwrap();
|
||||
}
|
||||
println!("{result:x}");
|
||||
}
|
74
kernel/crates/rbpf/examples/to_json.rs
Normal file
74
kernel/crates/rbpf/examples/to_json.rs
Normal file
@ -0,0 +1,74 @@
|
||||
// SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
// Copyright 2017 6WIND S.A. <quentin.monnet@6wind.com>
|
||||
|
||||
#[macro_use]
|
||||
extern crate json;
|
||||
|
||||
extern crate elf;
|
||||
use std::path::PathBuf;
|
||||
|
||||
extern crate rbpf;
|
||||
use rbpf::disassembler;
|
||||
|
||||
// Turn a program into a JSON string.
|
||||
//
|
||||
// Relies on `json` crate.
|
||||
//
|
||||
// You may copy this function and adapt it according to your needs. For instance, you may want to:
|
||||
//
|
||||
// * Remove the "desc" (description) attributes from the output.
|
||||
// * Print integers as integers, and not as strings containing their hexadecimal representation
|
||||
// (just replace the relevant `format!()` calls by the commented values.
|
||||
fn to_json(prog: &[u8]) -> String {
|
||||
// This call returns a high-level representation of the instructions, with the two parts of
|
||||
// `LD_DW_IMM` instructions merged, and name and descriptions of the instructions.
|
||||
// If you prefer to use a lower-level representation, use `ebpf::to_insn_vec()` function
|
||||
// instead.
|
||||
let insns = disassembler::to_insn_vec(prog);
|
||||
let mut json_insns = vec![];
|
||||
for insn in insns {
|
||||
json_insns.push(object!(
|
||||
"opc" => format!("{:#x}", insn.opc), // => insn.opc,
|
||||
"dst" => format!("{:#x}", insn.dst), // => insn.dst,
|
||||
"src" => format!("{:#x}", insn.src), // => insn.src,
|
||||
"off" => format!("{:#x}", insn.off), // => insn.off,
|
||||
// Warning: for imm we use a i64 instead of a i32 (to have correct values for
|
||||
// `lddw` operation. If we print a number in the JSON this is not a problem, the
|
||||
// internal i64 has the same value with extended sign on 32 most significant bytes.
|
||||
// If we print the hexadecimal value as a string however, we want to cast as a i32
|
||||
// to prevent all other instructions to print spurious `ffffffff` prefix if the
|
||||
// number is negative. When values takes more than 32 bits with `lddw`, the cast
|
||||
// has no effect and the complete value is printed anyway.
|
||||
"imm" => format!("{:#x}", insn.imm as i32), // => insn.imm,
|
||||
"desc" => insn.desc
|
||||
));
|
||||
}
|
||||
json::stringify_pretty(
|
||||
object!(
|
||||
"size" => json_insns.len(),
|
||||
"insns" => json_insns
|
||||
),
|
||||
4,
|
||||
)
|
||||
}
|
||||
|
||||
// Load a program from an object file, and prints it to standard output as a JSON string.
|
||||
fn main() {
|
||||
// Let's reuse this file from `load_elf/example`.
|
||||
let filename = "examples/load_elf__block_a_port.elf";
|
||||
|
||||
let path = PathBuf::from(filename);
|
||||
let file = match elf::File::open_path(path) {
|
||||
Ok(f) => f,
|
||||
Err(e) => panic!("Error: {:?}", e),
|
||||
};
|
||||
|
||||
let text_scn = match file.get_section(".classifier") {
|
||||
Some(s) => s,
|
||||
None => panic!("Failed to look up .classifier section"),
|
||||
};
|
||||
|
||||
let prog = &text_scn.data;
|
||||
|
||||
println!("{}", to_json(prog));
|
||||
}
|
78
kernel/crates/rbpf/examples/uptime.rs
Normal file
78
kernel/crates/rbpf/examples/uptime.rs
Normal file
@ -0,0 +1,78 @@
|
||||
// SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
// Copyright 2017 6WIND S.A. <quentin.monnet@6wind.com>
|
||||
|
||||
extern crate rbpf;
|
||||
use rbpf::helpers;
|
||||
|
||||
// The main objectives of this example is to show:
|
||||
//
|
||||
// * the use of EbpfVmNoData function,
|
||||
// * and the use of a helper.
|
||||
//
|
||||
// The two eBPF programs are independent and are not related to one another.
|
||||
fn main() {
|
||||
let prog1 = &[
|
||||
0xb4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov32 r0, 0
|
||||
0xb4, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, // mov32 r1, 2
|
||||
0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, // add32 r0, 1
|
||||
0x0c, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // add32 r0, r1
|
||||
0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // exit and return r0
|
||||
];
|
||||
|
||||
// We use helper `bpf_time_getns()`, which is similar to helper `bpf_ktime_getns()` from Linux
|
||||
// kernel. Hence rbpf::helpers module provides the index of this in-kernel helper as a
|
||||
// constant, so that we can remain compatible with programs for the kernel. Here we also cast
|
||||
// it to a u8 so as to use it directly in program instructions.
|
||||
let hkey = helpers::BPF_KTIME_GETNS_IDX as u8;
|
||||
let prog2 = &[
|
||||
0xb7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov64 r1, 0
|
||||
0xb7, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov64 r1, 0
|
||||
0xb7, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov64 r1, 0
|
||||
0xb7, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov64 r1, 0
|
||||
0xb7, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov64 r1, 0
|
||||
0x85, 0x00, 0x00, 0x00, hkey, 0x00, 0x00, 0x00, // call helper <hkey>
|
||||
0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // exit and return r0
|
||||
];
|
||||
|
||||
// Create a VM: this one takes no data. Load prog1 in it.
|
||||
let mut vm = rbpf::EbpfVmNoData::new(Some(prog1)).unwrap();
|
||||
// Execute prog1.
|
||||
assert_eq!(vm.execute_program().unwrap(), 0x3);
|
||||
|
||||
// As struct EbpfVmNoData does not takes any memory area, its return value is mostly
|
||||
// deterministic. So we know prog1 will always return 3. There is an exception: when it uses
|
||||
// helpers, the latter may have non-deterministic values, and all calls may not return the same
|
||||
// value.
|
||||
//
|
||||
// In the following example we use a helper to get the elapsed time since boot time: we
|
||||
// reimplement uptime in eBPF, in Rust. Because why not.
|
||||
|
||||
vm.set_program(prog2).unwrap();
|
||||
vm.register_helper(helpers::BPF_KTIME_GETNS_IDX, helpers::bpf_time_getns)
|
||||
.unwrap();
|
||||
|
||||
let time;
|
||||
|
||||
#[cfg(all(not(windows), feature = "std"))]
|
||||
{
|
||||
vm.jit_compile().unwrap();
|
||||
|
||||
time = unsafe { vm.execute_program_jit().unwrap() };
|
||||
}
|
||||
|
||||
#[cfg(any(windows, not(feature = "std")))]
|
||||
{
|
||||
time = vm.execute_program().unwrap();
|
||||
}
|
||||
|
||||
let days = time / 10u64.pow(9) / 60 / 60 / 24;
|
||||
let hours = (time / 10u64.pow(9) / 60 / 60) % 24;
|
||||
let minutes = (time / 10u64.pow(9) / 60) % 60;
|
||||
let seconds = (time / 10u64.pow(9)) % 60;
|
||||
let nanosec = time % 10u64.pow(9);
|
||||
|
||||
println!(
|
||||
"Uptime: {:#x} ns == {} days {:02}:{:02}:{:02}, {} ns",
|
||||
time, days, hours, minutes, seconds, nanosec
|
||||
);
|
||||
}
|
72
kernel/crates/rbpf/mk/appveyor.bat
Normal file
72
kernel/crates/rbpf/mk/appveyor.bat
Normal file
@ -0,0 +1,72 @@
|
||||
echo on
|
||||
SetLocal EnableDelayedExpansion
|
||||
|
||||
REM This is the recommended way to choose the toolchain version, according to
|
||||
REM Appveyor's documentation.
|
||||
SET PATH=C:\Program Files (x86)\MSBuild\%TOOLCHAIN_VERSION%\Bin;%PATH%
|
||||
|
||||
set VCVARSALL="C:\Program Files (x86)\Microsoft Visual Studio %TOOLCHAIN_VERSION%\VC\vcvarsall.bat"
|
||||
|
||||
if [%Platform%] NEQ [x64] goto win32
|
||||
set TARGET_ARCH=x86_64
|
||||
set TARGET_PROGRAM_FILES=%ProgramFiles%
|
||||
call %VCVARSALL% amd64
|
||||
if %ERRORLEVEL% NEQ 0 exit 1
|
||||
goto download
|
||||
|
||||
:win32
|
||||
echo on
|
||||
if [%Platform%] NEQ [Win32] exit 1
|
||||
set TARGET_ARCH=i686
|
||||
set TARGET_PROGRAM_FILES=%ProgramFiles(x86)%
|
||||
call %VCVARSALL% amd64_x86
|
||||
if %ERRORLEVEL% NEQ 0 exit 1
|
||||
goto download
|
||||
|
||||
:download
|
||||
REM vcvarsall turns echo off
|
||||
echo on
|
||||
|
||||
mkdir windows_build_tools
|
||||
mkdir windows_build_tools\
|
||||
echo Downloading Yasm...
|
||||
powershell -Command "(New-Object Net.WebClient).DownloadFile('http://www.tortall.net/projects/yasm/releases/yasm-1.3.0-win64.exe', 'windows_build_tools\yasm.exe')"
|
||||
if %ERRORLEVEL% NEQ 0 (
|
||||
echo ...downloading Yasm failed.
|
||||
exit 1
|
||||
)
|
||||
|
||||
set RUST_URL=https://static.rust-lang.org/dist/rust-%RUST%-%TARGET_ARCH%-pc-windows-msvc.msi
|
||||
echo Downloading %RUST_URL%...
|
||||
mkdir build
|
||||
powershell -Command "(New-Object Net.WebClient).DownloadFile('%RUST_URL%', 'build\rust-%RUST%-%TARGET_ARCH%-pc-windows-msvc.msi')"
|
||||
if %ERRORLEVEL% NEQ 0 (
|
||||
echo ...downloading Rust failed.
|
||||
exit 1
|
||||
)
|
||||
|
||||
start /wait msiexec /i build\rust-%RUST%-%TARGET_ARCH%-pc-windows-msvc.msi INSTALLDIR="%TARGET_PROGRAM_FILES%\Rust %RUST%" /quiet /qn /norestart
|
||||
if %ERRORLEVEL% NEQ 0 exit 1
|
||||
|
||||
set PATH="%TARGET_PROGRAM_FILES%\Rust %RUST%\bin";%cd%\windows_build_tools;%PATH%
|
||||
|
||||
if [%Configuration%] == [Release] set CARGO_MODE=--release
|
||||
|
||||
set
|
||||
|
||||
link /?
|
||||
cl /?
|
||||
rustc --version
|
||||
cargo --version
|
||||
|
||||
cargo test --all-features -vv %CARGO_MODE%
|
||||
if %ERRORLEVEL% NEQ 0 exit 1
|
||||
|
||||
REM Verify that `cargo build`, independent from `cargo test`, works; i.e.
|
||||
REM verify that non-test builds aren't trying to use test-only features.
|
||||
cargo build -vv %CARGO_MODE%
|
||||
if %ERRORLEVEL% NEQ 0 exit 1
|
||||
|
||||
REM Verify that we can build with all features
|
||||
cargo build --all-features -vv %CARGO_MODE%
|
||||
if %ERRORLEVEL% NEQ 0 exit 1
|
3
kernel/crates/rbpf/rustfmt.toml
Normal file
3
kernel/crates/rbpf/rustfmt.toml
Normal file
@ -0,0 +1,3 @@
|
||||
group_imports="StdExternalCrate"
|
||||
reorder_imports=true
|
||||
imports_granularity="Crate"
|
642
kernel/crates/rbpf/src/asm_parser.rs
Normal file
642
kernel/crates/rbpf/src/asm_parser.rs
Normal file
@ -0,0 +1,642 @@
|
||||
// SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
// Copyright 2017 Rich Lane <lanerl@gmail.com>
|
||||
|
||||
// Rust-doc comments were left in the module, but it is no longer publicly exposed from the root
|
||||
// file of the crate. Do not expect to find those comments in the documentation of the crate.
|
||||
|
||||
//! This module parses eBPF assembly language source code.
|
||||
|
||||
use alloc::{
|
||||
string::{String, ToString},
|
||||
vec::Vec,
|
||||
};
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
use combine::EasyParser;
|
||||
use combine::{
|
||||
attempt, between, eof, many, many1, one_of, optional,
|
||||
parser::char::{alpha_num, char, digit, hex_digit, spaces, string},
|
||||
sep_by,
|
||||
stream::position::{self},
|
||||
ParseError, Parser, Stream,
|
||||
};
|
||||
|
||||
/// Operand of an instruction.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum Operand {
|
||||
/// Register number.
|
||||
Register(i64),
|
||||
/// Jump offset or immediate.
|
||||
Integer(i64),
|
||||
/// Register number and offset.
|
||||
Memory(i64, i64),
|
||||
/// Used for pattern matching.
|
||||
Nil,
|
||||
}
|
||||
|
||||
/// Parsed instruction.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct Instruction {
|
||||
/// Instruction name.
|
||||
pub name: String,
|
||||
/// Operands.
|
||||
pub operands: Vec<Operand>,
|
||||
}
|
||||
|
||||
fn ident<I>() -> impl Parser<I, Output = String>
|
||||
where
|
||||
I: Stream<Token = char>,
|
||||
I::Error: ParseError<I::Token, I::Range, I::Position>,
|
||||
{
|
||||
many1(alpha_num())
|
||||
}
|
||||
|
||||
fn integer<I>() -> impl Parser<I, Output = i64>
|
||||
where
|
||||
I: Stream<Token = char>,
|
||||
I::Error: ParseError<I::Token, I::Range, I::Position>,
|
||||
{
|
||||
let sign = optional(one_of("-+".chars())).map(|x| match x {
|
||||
Some('-') => -1,
|
||||
_ => 1,
|
||||
});
|
||||
let hex = string("0x")
|
||||
.with(many1(hex_digit()))
|
||||
.map(|x: String| u64::from_str_radix(&x, 16).unwrap() as i64);
|
||||
let dec = many1(digit()).map(|x: String| x.parse::<i64>().unwrap());
|
||||
(sign, attempt(hex).or(dec)).map(|(s, x)| s * x)
|
||||
}
|
||||
|
||||
fn register<I>() -> impl Parser<I, Output = i64>
|
||||
where
|
||||
I: Stream<Token = char>,
|
||||
I::Error: ParseError<I::Token, I::Range, I::Position>,
|
||||
{
|
||||
char('r')
|
||||
.with(many1(digit()))
|
||||
.map(|x: String| x.parse::<i64>().unwrap())
|
||||
}
|
||||
|
||||
fn operand<I>() -> impl Parser<I, Output = Operand>
|
||||
where
|
||||
I: Stream<Token = char>,
|
||||
I::Error: ParseError<I::Token, I::Range, I::Position>,
|
||||
{
|
||||
let register_operand = register().map(Operand::Register);
|
||||
let immediate = integer().map(Operand::Integer);
|
||||
let memory = between(char('['), char(']'), (register(), optional(integer())))
|
||||
.map(|t| Operand::Memory(t.0, t.1.unwrap_or(0)));
|
||||
register_operand.or(immediate).or(memory)
|
||||
}
|
||||
|
||||
fn instruction<I>() -> impl Parser<I, Output = Instruction>
|
||||
where
|
||||
I: Stream<Token = char>,
|
||||
I::Error: ParseError<I::Token, I::Range, I::Position>,
|
||||
{
|
||||
let operands = sep_by(operand(), char(',').skip(spaces()));
|
||||
(ident().skip(spaces()), operands, spaces()).map(|t| Instruction {
|
||||
name: t.0,
|
||||
operands: t.1,
|
||||
})
|
||||
}
|
||||
|
||||
/// Parse a string into a list of instructions.
|
||||
///
|
||||
/// The instructions are not validated and may have invalid names and operand types.
|
||||
pub fn parse(input: &str) -> Result<Vec<Instruction>, String> {
|
||||
let mut with = spaces().with(many(instruction()).skip(eof()));
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
{
|
||||
match with.easy_parse(position::Stream::new(input)) {
|
||||
Ok((insts, _)) => Ok(insts),
|
||||
Err(err) => Err(err.to_string()),
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "std"))]
|
||||
{
|
||||
match with.parse(position::Stream::new(input)) {
|
||||
Ok((insts, _)) => Ok(insts),
|
||||
Err(err) => Err(err.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use alloc::{string::ToString, vec};
|
||||
|
||||
use combine::Parser;
|
||||
|
||||
use super::{ident, instruction, integer, operand, parse, register, Instruction, Operand};
|
||||
|
||||
// Unit tests for the different kinds of parsers.
|
||||
|
||||
#[test]
|
||||
fn test_ident() {
|
||||
assert_eq!(ident().parse("nop"), Ok(("nop".to_string(), "")));
|
||||
assert_eq!(ident().parse("add32"), Ok(("add32".to_string(), "")));
|
||||
assert_eq!(ident().parse("add32*"), Ok(("add32".to_string(), "*")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_integer() {
|
||||
assert_eq!(integer().parse("0"), Ok((0, "")));
|
||||
assert_eq!(integer().parse("42"), Ok((42, "")));
|
||||
assert_eq!(integer().parse("+42"), Ok((42, "")));
|
||||
assert_eq!(integer().parse("-42"), Ok((-42, "")));
|
||||
assert_eq!(integer().parse("0x0"), Ok((0, "")));
|
||||
assert_eq!(
|
||||
integer().parse("0x123456789abcdef0"),
|
||||
Ok((0x123456789abcdef0, ""))
|
||||
);
|
||||
assert_eq!(integer().parse("-0x1f"), Ok((-31, "")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_register() {
|
||||
assert_eq!(register().parse("r0"), Ok((0, "")));
|
||||
assert_eq!(register().parse("r15"), Ok((15, "")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_operand() {
|
||||
assert_eq!(operand().parse("r0"), Ok((Operand::Register(0), "")));
|
||||
assert_eq!(operand().parse("r15"), Ok((Operand::Register(15), "")));
|
||||
assert_eq!(operand().parse("0"), Ok((Operand::Integer(0), "")));
|
||||
assert_eq!(operand().parse("42"), Ok((Operand::Integer(42), "")));
|
||||
assert_eq!(operand().parse("[r1]"), Ok((Operand::Memory(1, 0), "")));
|
||||
assert_eq!(operand().parse("[r3+5]"), Ok((Operand::Memory(3, 5), "")));
|
||||
assert_eq!(
|
||||
operand().parse("[r3+0x1f]"),
|
||||
Ok((Operand::Memory(3, 31), ""))
|
||||
);
|
||||
assert_eq!(
|
||||
operand().parse("[r3-0x1f]"),
|
||||
Ok((Operand::Memory(3, -31), ""))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_instruction() {
|
||||
assert_eq!(
|
||||
instruction().parse("exit"),
|
||||
Ok((
|
||||
Instruction {
|
||||
name: "exit".to_string(),
|
||||
operands: vec![],
|
||||
},
|
||||
""
|
||||
))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
instruction().parse("call 2"),
|
||||
Ok((
|
||||
Instruction {
|
||||
name: "call".to_string(),
|
||||
operands: vec![Operand::Integer(2)],
|
||||
},
|
||||
""
|
||||
))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
instruction().parse("addi r1, 2"),
|
||||
Ok((
|
||||
Instruction {
|
||||
name: "addi".to_string(),
|
||||
operands: vec![Operand::Register(1), Operand::Integer(2)],
|
||||
},
|
||||
""
|
||||
))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
instruction().parse("ldxb r2, [r1+12]"),
|
||||
Ok((
|
||||
Instruction {
|
||||
name: "ldxb".to_string(),
|
||||
operands: vec![Operand::Register(2), Operand::Memory(1, 12)],
|
||||
},
|
||||
""
|
||||
))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
instruction().parse("lsh r3, 0x8"),
|
||||
Ok((
|
||||
Instruction {
|
||||
name: "lsh".to_string(),
|
||||
operands: vec![Operand::Register(3), Operand::Integer(8)],
|
||||
},
|
||||
""
|
||||
))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
instruction().parse("jne r3, 0x8, +37"),
|
||||
Ok((
|
||||
Instruction {
|
||||
name: "jne".to_string(),
|
||||
operands: vec![
|
||||
Operand::Register(3),
|
||||
Operand::Integer(8),
|
||||
Operand::Integer(37)
|
||||
],
|
||||
},
|
||||
""
|
||||
))
|
||||
);
|
||||
|
||||
// Whitespace between operands is optional.
|
||||
assert_eq!(
|
||||
instruction().parse("jne r3,0x8,+37"),
|
||||
Ok((
|
||||
Instruction {
|
||||
name: "jne".to_string(),
|
||||
operands: vec![
|
||||
Operand::Register(3),
|
||||
Operand::Integer(8),
|
||||
Operand::Integer(37)
|
||||
],
|
||||
},
|
||||
""
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
// Other unit tests: try to parse various set of instructions.
|
||||
|
||||
#[test]
|
||||
fn test_empty() {
|
||||
assert_eq!(parse(""), Ok(vec![]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_exit() {
|
||||
// No operands.
|
||||
assert_eq!(
|
||||
parse("exit"),
|
||||
Ok(vec![Instruction {
|
||||
name: "exit".to_string(),
|
||||
operands: vec![],
|
||||
}])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lsh() {
|
||||
// Register and immediate operands.
|
||||
assert_eq!(
|
||||
parse("lsh r3, 0x20"),
|
||||
Ok(vec![Instruction {
|
||||
name: "lsh".to_string(),
|
||||
operands: vec![Operand::Register(3), Operand::Integer(0x20)],
|
||||
}])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ja() {
|
||||
// Jump offset operand.
|
||||
assert_eq!(
|
||||
parse("ja +1"),
|
||||
Ok(vec![Instruction {
|
||||
name: "ja".to_string(),
|
||||
operands: vec![Operand::Integer(1)],
|
||||
}])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ldxh() {
|
||||
// Register and memory operands.
|
||||
assert_eq!(
|
||||
parse("ldxh r4, [r1+12]"),
|
||||
Ok(vec![Instruction {
|
||||
name: "ldxh".to_string(),
|
||||
operands: vec![Operand::Register(4), Operand::Memory(1, 12)],
|
||||
}])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tcp_sack() {
|
||||
// Sample program from ubpf.
|
||||
// We could technically indent the instructions since the parser support white spaces at
|
||||
// the beginning, but there is another test for that.
|
||||
let src = "\
|
||||
ldxb r2, [r1+12]
|
||||
ldxb r3, [r1+13]
|
||||
lsh r3, 0x8
|
||||
or r3, r2
|
||||
mov r0, 0x0
|
||||
jne r3, 0x8, +37
|
||||
ldxb r2, [r1+23]
|
||||
jne r2, 0x6, +35
|
||||
ldxb r2, [r1+14]
|
||||
add r1, 0xe
|
||||
and r2, 0xf
|
||||
lsh r2, 0x2
|
||||
add r1, r2
|
||||
mov r0, 0x0
|
||||
ldxh r4, [r1+12]
|
||||
add r1, 0x14
|
||||
rsh r4, 0x2
|
||||
and r4, 0x3c
|
||||
mov r2, r4
|
||||
add r2, 0xffffffec
|
||||
mov r5, 0x15
|
||||
mov r3, 0x0
|
||||
jgt r5, r4, +20
|
||||
mov r5, r3
|
||||
lsh r5, 0x20
|
||||
arsh r5, 0x20
|
||||
mov r4, r1
|
||||
add r4, r5
|
||||
ldxb r5, [r4]
|
||||
jeq r5, 0x1, +4
|
||||
jeq r5, 0x0, +12
|
||||
mov r6, r3
|
||||
jeq r5, 0x5, +9
|
||||
ja +2
|
||||
add r3, 0x1
|
||||
mov r6, r3
|
||||
ldxb r3, [r4+1]
|
||||
add r3, r6
|
||||
lsh r3, 0x20
|
||||
arsh r3, 0x20
|
||||
jsgt r2, r3, -18
|
||||
ja +1
|
||||
mov r0, 0x1
|
||||
exit
|
||||
";
|
||||
|
||||
assert_eq!(
|
||||
parse(src),
|
||||
Ok(vec![
|
||||
Instruction {
|
||||
name: "ldxb".to_string(),
|
||||
operands: vec![Operand::Register(2), Operand::Memory(1, 12)],
|
||||
},
|
||||
Instruction {
|
||||
name: "ldxb".to_string(),
|
||||
operands: vec![Operand::Register(3), Operand::Memory(1, 13)],
|
||||
},
|
||||
Instruction {
|
||||
name: "lsh".to_string(),
|
||||
operands: vec![Operand::Register(3), Operand::Integer(8)],
|
||||
},
|
||||
Instruction {
|
||||
name: "or".to_string(),
|
||||
operands: vec![Operand::Register(3), Operand::Register(2)],
|
||||
},
|
||||
Instruction {
|
||||
name: "mov".to_string(),
|
||||
operands: vec![Operand::Register(0), Operand::Integer(0)],
|
||||
},
|
||||
Instruction {
|
||||
name: "jne".to_string(),
|
||||
operands: vec![
|
||||
Operand::Register(3),
|
||||
Operand::Integer(8),
|
||||
Operand::Integer(37)
|
||||
],
|
||||
},
|
||||
Instruction {
|
||||
name: "ldxb".to_string(),
|
||||
operands: vec![Operand::Register(2), Operand::Memory(1, 23)],
|
||||
},
|
||||
Instruction {
|
||||
name: "jne".to_string(),
|
||||
operands: vec![
|
||||
Operand::Register(2),
|
||||
Operand::Integer(6),
|
||||
Operand::Integer(35)
|
||||
],
|
||||
},
|
||||
Instruction {
|
||||
name: "ldxb".to_string(),
|
||||
operands: vec![Operand::Register(2), Operand::Memory(1, 14)],
|
||||
},
|
||||
Instruction {
|
||||
name: "add".to_string(),
|
||||
operands: vec![Operand::Register(1), Operand::Integer(14)],
|
||||
},
|
||||
Instruction {
|
||||
name: "and".to_string(),
|
||||
operands: vec![Operand::Register(2), Operand::Integer(15)],
|
||||
},
|
||||
Instruction {
|
||||
name: "lsh".to_string(),
|
||||
operands: vec![Operand::Register(2), Operand::Integer(2)],
|
||||
},
|
||||
Instruction {
|
||||
name: "add".to_string(),
|
||||
operands: vec![Operand::Register(1), Operand::Register(2)],
|
||||
},
|
||||
Instruction {
|
||||
name: "mov".to_string(),
|
||||
operands: vec![Operand::Register(0), Operand::Integer(0)],
|
||||
},
|
||||
Instruction {
|
||||
name: "ldxh".to_string(),
|
||||
operands: vec![Operand::Register(4), Operand::Memory(1, 12)],
|
||||
},
|
||||
Instruction {
|
||||
name: "add".to_string(),
|
||||
operands: vec![Operand::Register(1), Operand::Integer(20)],
|
||||
},
|
||||
Instruction {
|
||||
name: "rsh".to_string(),
|
||||
operands: vec![Operand::Register(4), Operand::Integer(2)],
|
||||
},
|
||||
Instruction {
|
||||
name: "and".to_string(),
|
||||
operands: vec![Operand::Register(4), Operand::Integer(60)],
|
||||
},
|
||||
Instruction {
|
||||
name: "mov".to_string(),
|
||||
operands: vec![Operand::Register(2), Operand::Register(4)],
|
||||
},
|
||||
Instruction {
|
||||
name: "add".to_string(),
|
||||
operands: vec![Operand::Register(2), Operand::Integer(4294967276)],
|
||||
},
|
||||
Instruction {
|
||||
name: "mov".to_string(),
|
||||
operands: vec![Operand::Register(5), Operand::Integer(21)],
|
||||
},
|
||||
Instruction {
|
||||
name: "mov".to_string(),
|
||||
operands: vec![Operand::Register(3), Operand::Integer(0)],
|
||||
},
|
||||
Instruction {
|
||||
name: "jgt".to_string(),
|
||||
operands: vec![
|
||||
Operand::Register(5),
|
||||
Operand::Register(4),
|
||||
Operand::Integer(20)
|
||||
],
|
||||
},
|
||||
Instruction {
|
||||
name: "mov".to_string(),
|
||||
operands: vec![Operand::Register(5), Operand::Register(3)],
|
||||
},
|
||||
Instruction {
|
||||
name: "lsh".to_string(),
|
||||
operands: vec![Operand::Register(5), Operand::Integer(32)],
|
||||
},
|
||||
Instruction {
|
||||
name: "arsh".to_string(),
|
||||
operands: vec![Operand::Register(5), Operand::Integer(32)],
|
||||
},
|
||||
Instruction {
|
||||
name: "mov".to_string(),
|
||||
operands: vec![Operand::Register(4), Operand::Register(1)],
|
||||
},
|
||||
Instruction {
|
||||
name: "add".to_string(),
|
||||
operands: vec![Operand::Register(4), Operand::Register(5)],
|
||||
},
|
||||
Instruction {
|
||||
name: "ldxb".to_string(),
|
||||
operands: vec![Operand::Register(5), Operand::Memory(4, 0)],
|
||||
},
|
||||
Instruction {
|
||||
name: "jeq".to_string(),
|
||||
operands: vec![
|
||||
Operand::Register(5),
|
||||
Operand::Integer(1),
|
||||
Operand::Integer(4)
|
||||
],
|
||||
},
|
||||
Instruction {
|
||||
name: "jeq".to_string(),
|
||||
operands: vec![
|
||||
Operand::Register(5),
|
||||
Operand::Integer(0),
|
||||
Operand::Integer(12)
|
||||
],
|
||||
},
|
||||
Instruction {
|
||||
name: "mov".to_string(),
|
||||
operands: vec![Operand::Register(6), Operand::Register(3)],
|
||||
},
|
||||
Instruction {
|
||||
name: "jeq".to_string(),
|
||||
operands: vec![
|
||||
Operand::Register(5),
|
||||
Operand::Integer(5),
|
||||
Operand::Integer(9)
|
||||
],
|
||||
},
|
||||
Instruction {
|
||||
name: "ja".to_string(),
|
||||
operands: vec![Operand::Integer(2)],
|
||||
},
|
||||
Instruction {
|
||||
name: "add".to_string(),
|
||||
operands: vec![Operand::Register(3), Operand::Integer(1)],
|
||||
},
|
||||
Instruction {
|
||||
name: "mov".to_string(),
|
||||
operands: vec![Operand::Register(6), Operand::Register(3)],
|
||||
},
|
||||
Instruction {
|
||||
name: "ldxb".to_string(),
|
||||
operands: vec![Operand::Register(3), Operand::Memory(4, 1)],
|
||||
},
|
||||
Instruction {
|
||||
name: "add".to_string(),
|
||||
operands: vec![Operand::Register(3), Operand::Register(6)],
|
||||
},
|
||||
Instruction {
|
||||
name: "lsh".to_string(),
|
||||
operands: vec![Operand::Register(3), Operand::Integer(32)],
|
||||
},
|
||||
Instruction {
|
||||
name: "arsh".to_string(),
|
||||
operands: vec![Operand::Register(3), Operand::Integer(32)],
|
||||
},
|
||||
Instruction {
|
||||
name: "jsgt".to_string(),
|
||||
operands: vec![
|
||||
Operand::Register(2),
|
||||
Operand::Register(3),
|
||||
Operand::Integer(-18)
|
||||
],
|
||||
},
|
||||
Instruction {
|
||||
name: "ja".to_string(),
|
||||
operands: vec![Operand::Integer(1)],
|
||||
},
|
||||
Instruction {
|
||||
name: "mov".to_string(),
|
||||
operands: vec![Operand::Register(0), Operand::Integer(1)],
|
||||
},
|
||||
Instruction {
|
||||
name: "exit".to_string(),
|
||||
operands: vec![],
|
||||
}
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
/// When running without `std` the `EasyParser` provided by `combine`
|
||||
/// cannot be used. Because of this we need to use the `Parser` and the
|
||||
/// error messages are different.
|
||||
#[test]
|
||||
fn test_error_eof() {
|
||||
let expected_error;
|
||||
#[cfg(feature = "std")]
|
||||
{
|
||||
expected_error = Err(
|
||||
"Parse error at line: 1, column: 6\nUnexpected end of input\nExpected digit\n"
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
#[cfg(not(feature = "std"))]
|
||||
{
|
||||
expected_error = Err("unexpected parse".to_string());
|
||||
}
|
||||
// Unexpected end of input in a register name.
|
||||
assert_eq!(parse("lsh r"), expected_error);
|
||||
}
|
||||
|
||||
/// When running without `std` the `EasyParser` provided by `combine`
|
||||
/// cannot be used. Because of this we need to use the `Parser` and the
|
||||
/// error messages are different.
|
||||
#[test]
|
||||
fn test_error_unexpected_character() {
|
||||
let expected_error;
|
||||
#[cfg(feature = "std")]
|
||||
{
|
||||
expected_error = Err(
|
||||
"Parse error at line: 2, column: 1\nUnexpected `^`\nExpected letter or digit, whitespaces, `r`, `-`, `+`, `[` or end of input\n".to_string()
|
||||
);
|
||||
}
|
||||
#[cfg(not(feature = "std"))]
|
||||
{
|
||||
expected_error = Err("unexpected parse".to_string());
|
||||
}
|
||||
// Unexpected character at end of input.
|
||||
assert_eq!(parse("exit\n^"), expected_error);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_initial_whitespace() {
|
||||
assert_eq!(
|
||||
parse(
|
||||
"
|
||||
exit"
|
||||
),
|
||||
Ok(vec![Instruction {
|
||||
name: "exit".to_string(),
|
||||
operands: vec![],
|
||||
}])
|
||||
);
|
||||
}
|
||||
}
|
277
kernel/crates/rbpf/src/assembler.rs
Normal file
277
kernel/crates/rbpf/src/assembler.rs
Normal file
@ -0,0 +1,277 @@
|
||||
// SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
// Copyright 2017 Rich Lane <lanerl@gmail.com>
|
||||
|
||||
//! This module translates eBPF assembly language to binary.
|
||||
|
||||
use alloc::{
|
||||
collections::BTreeMap,
|
||||
format,
|
||||
string::{String, ToString},
|
||||
vec,
|
||||
vec::Vec,
|
||||
};
|
||||
|
||||
use self::InstructionType::{
|
||||
AluBinary, AluUnary, Call, Endian, JumpConditional, JumpUnconditional, LoadAbs, LoadImm,
|
||||
LoadInd, LoadReg, NoOperand, StoreImm, StoreReg,
|
||||
};
|
||||
use crate::{
|
||||
asm_parser::{
|
||||
parse, Instruction, Operand,
|
||||
Operand::{Integer, Memory, Nil, Register},
|
||||
},
|
||||
ebpf::{self, Insn},
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
enum InstructionType {
|
||||
AluBinary,
|
||||
AluUnary,
|
||||
LoadImm,
|
||||
LoadAbs,
|
||||
LoadInd,
|
||||
LoadReg,
|
||||
StoreImm,
|
||||
StoreReg,
|
||||
JumpUnconditional,
|
||||
JumpConditional,
|
||||
Call,
|
||||
Endian(i64),
|
||||
NoOperand,
|
||||
}
|
||||
|
||||
fn make_instruction_map() -> BTreeMap<String, (InstructionType, u8)> {
|
||||
let mut result = BTreeMap::new();
|
||||
|
||||
let alu_binary_ops = [
|
||||
("add", ebpf::BPF_ADD),
|
||||
("sub", ebpf::BPF_SUB),
|
||||
("mul", ebpf::BPF_MUL),
|
||||
("div", ebpf::BPF_DIV),
|
||||
("or", ebpf::BPF_OR),
|
||||
("and", ebpf::BPF_AND),
|
||||
("lsh", ebpf::BPF_LSH),
|
||||
("rsh", ebpf::BPF_RSH),
|
||||
("mod", ebpf::BPF_MOD),
|
||||
("xor", ebpf::BPF_XOR),
|
||||
("mov", ebpf::BPF_MOV),
|
||||
("arsh", ebpf::BPF_ARSH),
|
||||
];
|
||||
|
||||
let mem_sizes = [
|
||||
("w", ebpf::BPF_W),
|
||||
("h", ebpf::BPF_H),
|
||||
("b", ebpf::BPF_B),
|
||||
("dw", ebpf::BPF_DW),
|
||||
];
|
||||
|
||||
let jump_conditions = [
|
||||
("jeq", ebpf::BPF_JEQ),
|
||||
("jgt", ebpf::BPF_JGT),
|
||||
("jge", ebpf::BPF_JGE),
|
||||
("jlt", ebpf::BPF_JLT),
|
||||
("jle", ebpf::BPF_JLE),
|
||||
("jset", ebpf::BPF_JSET),
|
||||
("jne", ebpf::BPF_JNE),
|
||||
("jsgt", ebpf::BPF_JSGT),
|
||||
("jsge", ebpf::BPF_JSGE),
|
||||
("jslt", ebpf::BPF_JSLT),
|
||||
("jsle", ebpf::BPF_JSLE),
|
||||
];
|
||||
|
||||
{
|
||||
let mut entry = |name: &str, inst_type: InstructionType, opc: u8| {
|
||||
result.insert(name.to_string(), (inst_type, opc))
|
||||
};
|
||||
|
||||
// Miscellaneous.
|
||||
entry("exit", NoOperand, ebpf::EXIT);
|
||||
entry("ja", JumpUnconditional, ebpf::JA);
|
||||
entry("call", Call, ebpf::CALL);
|
||||
entry("lddw", LoadImm, ebpf::LD_DW_IMM);
|
||||
|
||||
// AluUnary.
|
||||
entry("neg", AluUnary, ebpf::NEG64);
|
||||
entry("neg32", AluUnary, ebpf::NEG32);
|
||||
entry("neg64", AluUnary, ebpf::NEG64);
|
||||
|
||||
// AluBinary.
|
||||
for &(name, opc) in &alu_binary_ops {
|
||||
entry(name, AluBinary, ebpf::BPF_ALU64 | opc);
|
||||
entry(&format!("{name}32"), AluBinary, ebpf::BPF_ALU | opc);
|
||||
entry(&format!("{name}64"), AluBinary, ebpf::BPF_ALU64 | opc);
|
||||
}
|
||||
|
||||
// LoadAbs, LoadInd, LoadReg, StoreImm, and StoreReg.
|
||||
for &(suffix, size) in &mem_sizes {
|
||||
entry(
|
||||
&format!("ldabs{suffix}"),
|
||||
LoadAbs,
|
||||
ebpf::BPF_ABS | ebpf::BPF_LD | size,
|
||||
);
|
||||
entry(
|
||||
&format!("ldind{suffix}"),
|
||||
LoadInd,
|
||||
ebpf::BPF_IND | ebpf::BPF_LD | size,
|
||||
);
|
||||
entry(
|
||||
&format!("ldx{suffix}"),
|
||||
LoadReg,
|
||||
ebpf::BPF_MEM | ebpf::BPF_LDX | size,
|
||||
);
|
||||
entry(
|
||||
&format!("st{suffix}"),
|
||||
StoreImm,
|
||||
ebpf::BPF_MEM | ebpf::BPF_ST | size,
|
||||
);
|
||||
entry(
|
||||
&format!("stx{suffix}"),
|
||||
StoreReg,
|
||||
ebpf::BPF_MEM | ebpf::BPF_STX | size,
|
||||
);
|
||||
}
|
||||
|
||||
// JumpConditional.
|
||||
for &(name, condition) in &jump_conditions {
|
||||
entry(name, JumpConditional, ebpf::BPF_JMP | condition);
|
||||
entry(
|
||||
&format!("{name}32"),
|
||||
JumpConditional,
|
||||
ebpf::BPF_JMP32 | condition,
|
||||
);
|
||||
}
|
||||
|
||||
// Endian.
|
||||
for &size in &[16, 32, 64] {
|
||||
entry(&format!("be{size}"), Endian(size), ebpf::BE);
|
||||
entry(&format!("le{size}"), Endian(size), ebpf::LE);
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn insn(opc: u8, dst: i64, src: i64, off: i64, imm: i64) -> Result<Insn, String> {
|
||||
if !(0..16).contains(&dst) {
|
||||
return Err(format!("Invalid destination register {dst}"));
|
||||
}
|
||||
if dst < 0 || src >= 16 {
|
||||
return Err(format!("Invalid source register {src}"));
|
||||
}
|
||||
if !(-32768..32768).contains(&off) {
|
||||
return Err(format!("Invalid offset {off}"));
|
||||
}
|
||||
if !(-2147483648..2147483648).contains(&imm) {
|
||||
return Err(format!("Invalid immediate {imm}"));
|
||||
}
|
||||
Ok(Insn {
|
||||
opc,
|
||||
dst: dst as u8,
|
||||
src: src as u8,
|
||||
off: off as i16,
|
||||
imm: imm as i32,
|
||||
})
|
||||
}
|
||||
|
||||
// TODO Use slice patterns when available and remove this function.
|
||||
fn operands_tuple(operands: &[Operand]) -> Result<(Operand, Operand, Operand), String> {
|
||||
match operands.len() {
|
||||
0 => Ok((Nil, Nil, Nil)),
|
||||
1 => Ok((operands[0], Nil, Nil)),
|
||||
2 => Ok((operands[0], operands[1], Nil)),
|
||||
3 => Ok((operands[0], operands[1], operands[2])),
|
||||
_ => Err("Too many operands".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
fn encode(inst_type: InstructionType, opc: u8, operands: &[Operand]) -> Result<Insn, String> {
|
||||
let (a, b, c) = (operands_tuple(operands))?;
|
||||
match (inst_type, a, b, c) {
|
||||
(AluBinary, Register(dst), Register(src), Nil) => insn(opc | ebpf::BPF_X, dst, src, 0, 0),
|
||||
(AluBinary, Register(dst), Integer(imm), Nil) => insn(opc | ebpf::BPF_K, dst, 0, 0, imm),
|
||||
(AluUnary, Register(dst), Nil, Nil) => insn(opc, dst, 0, 0, 0),
|
||||
(LoadAbs, Integer(imm), Nil, Nil) => insn(opc, 0, 0, 0, imm),
|
||||
(LoadInd, Register(src), Integer(imm), Nil) => insn(opc, 0, src, 0, imm),
|
||||
(LoadReg, Register(dst), Memory(src, off), Nil)
|
||||
| (StoreReg, Memory(dst, off), Register(src), Nil) => insn(opc, dst, src, off, 0),
|
||||
(StoreImm, Memory(dst, off), Integer(imm), Nil) => insn(opc, dst, 0, off, imm),
|
||||
(NoOperand, Nil, Nil, Nil) => insn(opc, 0, 0, 0, 0),
|
||||
(JumpUnconditional, Integer(off), Nil, Nil) => insn(opc, 0, 0, off, 0),
|
||||
(JumpConditional, Register(dst), Register(src), Integer(off)) => {
|
||||
insn(opc | ebpf::BPF_X, dst, src, off, 0)
|
||||
}
|
||||
(JumpConditional, Register(dst), Integer(imm), Integer(off)) => {
|
||||
insn(opc | ebpf::BPF_K, dst, 0, off, imm)
|
||||
}
|
||||
(Call, Integer(imm), Nil, Nil) => insn(opc, 0, 0, 0, imm),
|
||||
(Endian(size), Register(dst), Nil, Nil) => insn(opc, dst, 0, 0, size),
|
||||
(LoadImm, Register(dst), Integer(imm), Nil) => insn(opc, dst, 0, 0, (imm << 32) >> 32),
|
||||
_ => Err(format!("Unexpected operands: {operands:?}")),
|
||||
}
|
||||
}
|
||||
|
||||
fn assemble_internal(parsed: &[Instruction]) -> Result<Vec<Insn>, String> {
|
||||
let instruction_map = make_instruction_map();
|
||||
let mut result: Vec<Insn> = vec![];
|
||||
for instruction in parsed {
|
||||
let name = instruction.name.as_str();
|
||||
match instruction_map.get(name) {
|
||||
Some(&(inst_type, opc)) => {
|
||||
match encode(inst_type, opc, &instruction.operands) {
|
||||
Ok(insn) => result.push(insn),
|
||||
Err(msg) => return Err(format!("Failed to encode {name}: {msg}")),
|
||||
}
|
||||
// Special case for lddw.
|
||||
if let LoadImm = inst_type {
|
||||
if let Integer(imm) = instruction.operands[1] {
|
||||
result.push(insn(0, 0, 0, 0, imm >> 32).unwrap());
|
||||
}
|
||||
}
|
||||
}
|
||||
None => return Err(format!("Invalid instruction {name:?}")),
|
||||
}
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// Parse assembly source and translate to binary.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rbpf::assembler::assemble;
|
||||
/// let prog = assemble("add64 r1, 0x605
|
||||
/// mov64 r2, 0x32
|
||||
/// mov64 r1, r0
|
||||
/// be16 r0
|
||||
/// neg64 r2
|
||||
/// exit");
|
||||
/// println!("{:?}", prog);
|
||||
/// # assert_eq!(prog,
|
||||
/// # Ok(vec![0x07, 0x01, 0x00, 0x00, 0x05, 0x06, 0x00, 0x00,
|
||||
/// # 0xb7, 0x02, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00,
|
||||
/// # 0xbf, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
/// # 0xdc, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
|
||||
/// # 0x87, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
/// # 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]));
|
||||
/// ```
|
||||
///
|
||||
/// This will produce the following output:
|
||||
///
|
||||
/// ```test
|
||||
/// Ok([0x07, 0x01, 0x00, 0x00, 0x05, 0x06, 0x00, 0x00,
|
||||
/// 0xb7, 0x02, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00,
|
||||
/// 0xbf, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
/// 0xdc, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
|
||||
/// 0x87, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
/// 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
|
||||
/// ```
|
||||
pub fn assemble(src: &str) -> Result<Vec<u8>, String> {
|
||||
let parsed = (parse(src))?;
|
||||
let insns = (assemble_internal(&parsed))?;
|
||||
let mut result: Vec<u8> = vec![];
|
||||
for insn in insns {
|
||||
result.extend_from_slice(&insn.to_array());
|
||||
}
|
||||
Ok(result)
|
||||
}
|
1230
kernel/crates/rbpf/src/cranelift.rs
Normal file
1230
kernel/crates/rbpf/src/cranelift.rs
Normal file
File diff suppressed because it is too large
Load Diff
807
kernel/crates/rbpf/src/disassembler.rs
Normal file
807
kernel/crates/rbpf/src/disassembler.rs
Normal file
@ -0,0 +1,807 @@
|
||||
// SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
// Copyright 2017 6WIND S.A. <quentin.monnet@6wind.com>
|
||||
|
||||
//! Functions in this module are used to handle eBPF programs with a higher level representation,
|
||||
//! for example to disassemble the code into a human-readable format.
|
||||
|
||||
use alloc::{
|
||||
format,
|
||||
string::{String, ToString},
|
||||
vec,
|
||||
vec::Vec,
|
||||
};
|
||||
|
||||
use log::warn;
|
||||
|
||||
use crate::ebpf;
|
||||
|
||||
#[inline]
|
||||
fn alu_imm_str(name: &str, insn: &ebpf::Insn) -> String {
|
||||
format!("{name} r{}, {:#x}", insn.dst, insn.imm)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn alu_reg_str(name: &str, insn: &ebpf::Insn) -> String {
|
||||
format!("{name} r{}, r{}", insn.dst, insn.src)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn byteswap_str(name: &str, insn: &ebpf::Insn) -> String {
|
||||
match insn.imm {
|
||||
16 | 32 | 64 => {}
|
||||
_ => warn!("[Disassembler] Warning: Invalid offset value for {name} insn"),
|
||||
}
|
||||
format!("{name}{} r{}", insn.imm, insn.dst)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn ld_st_imm_str(name: &str, insn: &ebpf::Insn) -> String {
|
||||
if insn.off >= 0 {
|
||||
format!("{name} [r{}+{:#x}], {:#x}", insn.dst, insn.off, insn.imm)
|
||||
} else {
|
||||
format!(
|
||||
"{name} [r{}-{:#x}], {:#x}",
|
||||
insn.dst,
|
||||
-(insn.off as isize),
|
||||
insn.imm
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn ld_reg_str(name: &str, insn: &ebpf::Insn) -> String {
|
||||
if insn.off >= 0 {
|
||||
format!("{name} r{}, [r{}+{:#x}]", insn.dst, insn.src, insn.off)
|
||||
} else {
|
||||
format!(
|
||||
"{name} r{}, [r{}-{:#x}]",
|
||||
insn.dst,
|
||||
insn.src,
|
||||
-(insn.off as isize)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn st_reg_str(name: &str, insn: &ebpf::Insn) -> String {
|
||||
if insn.off >= 0 {
|
||||
format!("{name} [r{}+{:#x}], r{}", insn.dst, insn.off, insn.src)
|
||||
} else {
|
||||
format!(
|
||||
"{name} [r{}-{:#x}], r{}",
|
||||
insn.dst,
|
||||
-(insn.off as isize),
|
||||
insn.src
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn ldabs_str(name: &str, insn: &ebpf::Insn) -> String {
|
||||
format!("{name} {:#x}", insn.imm)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn ldind_str(name: &str, insn: &ebpf::Insn) -> String {
|
||||
format!("{name} r{}, {:#x}", insn.src, insn.imm)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn jmp_imm_str(name: &str, insn: &ebpf::Insn) -> String {
|
||||
if insn.off >= 0 {
|
||||
format!("{name} r{}, {:#x}, +{:#x}", insn.dst, insn.imm, insn.off)
|
||||
} else {
|
||||
format!(
|
||||
"{name} r{}, {:#x}, -{:#x}",
|
||||
insn.dst,
|
||||
insn.imm,
|
||||
-(insn.off as isize)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn jmp_reg_str(name: &str, insn: &ebpf::Insn) -> String {
|
||||
if insn.off >= 0 {
|
||||
format!("{name} r{}, r{}, +{:#x}", insn.dst, insn.src, insn.off)
|
||||
} else {
|
||||
format!(
|
||||
"{name} r{}, r{}, -{:#x}",
|
||||
insn.dst,
|
||||
insn.src,
|
||||
-(insn.off as isize)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// High-level representation of an eBPF instruction.
|
||||
///
|
||||
/// In addition to standard operation code and various operand, this struct has the following
|
||||
/// properties:
|
||||
///
|
||||
/// * It stores a name, corresponding to a mnemonic for the operation code.
|
||||
/// * It also stores a description, which is a mnemonic for the full instruction, using the actual
|
||||
/// values of the relevant operands, and that can be used for disassembling the eBPF program for
|
||||
/// example.
|
||||
/// * Immediate values are stored in an `i64` instead of a traditional i32, in order to merge the
|
||||
/// two parts of (otherwise double-length) `LD_DW_IMM` instructions.
|
||||
///
|
||||
/// See <https://www.kernel.org/doc/Documentation/networking/filter.txt> for the Linux kernel
|
||||
/// documentation about eBPF, or <https://github.com/iovisor/bpf-docs/blob/master/eBPF.md> for a
|
||||
/// more concise version.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct HLInsn {
|
||||
/// Operation code.
|
||||
pub opc: u8,
|
||||
/// Name (mnemonic). This name is not canon.
|
||||
pub name: String,
|
||||
/// Description of the instruction. This is not canon.
|
||||
pub desc: String,
|
||||
/// Destination register operand.
|
||||
pub dst: u8,
|
||||
/// Source register operand.
|
||||
pub src: u8,
|
||||
/// Offset operand.
|
||||
pub off: i16,
|
||||
/// Immediate value operand. For `LD_DW_IMM` instructions, contains the whole value merged from
|
||||
/// the two 8-bytes parts of the instruction.
|
||||
pub imm: i64,
|
||||
}
|
||||
|
||||
/// Return a vector of `struct HLInsn` built from an eBPF program.
|
||||
///
|
||||
/// This is made public to provide a way to manipulate a program as a vector of instructions, in a
|
||||
/// high-level format, for example for dumping the program instruction after instruction with a
|
||||
/// custom format.
|
||||
///
|
||||
/// Note that the two parts of `LD_DW_IMM` instructions (that have the size of two standard
|
||||
/// instructions) are considered as making a single immediate value. As a consequence, the number
|
||||
/// of instructions stored in the vector may not be equal to the size in bytes of the program
|
||||
/// divided by the length of an instructions.
|
||||
///
|
||||
/// To do so, the immediate value operand is stored as an `i64` instead as an i32, so be careful
|
||||
/// when you use it (see example `examples/to_json.rs`).
|
||||
///
|
||||
/// This is to oppose to `ebpf::to_insn_vec()` function, that treats instructions on a low-level
|
||||
/// ground and do not merge the parts of `LD_DW_IMM`. Also, the version in `ebpf` module does not
|
||||
/// use names or descriptions when storing the instructions.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rbpf::disassembler;
|
||||
///
|
||||
/// let prog = &[
|
||||
/// 0x18, 0x00, 0x00, 0x00, 0x88, 0x77, 0x66, 0x55,
|
||||
/// 0x00, 0x00, 0x00, 0x00, 0x44, 0x33, 0x22, 0x11,
|
||||
/// 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||
/// ];
|
||||
///
|
||||
/// let v = disassembler::to_insn_vec(prog);
|
||||
/// assert_eq!(v, vec![
|
||||
/// disassembler::HLInsn {
|
||||
/// opc: 0x18,
|
||||
/// name: "lddw".to_string(),
|
||||
/// desc: "lddw r0, 0x1122334455667788".to_string(),
|
||||
/// dst: 0,
|
||||
/// src: 0,
|
||||
/// off: 0,
|
||||
/// imm: 0x1122334455667788
|
||||
/// },
|
||||
/// disassembler::HLInsn {
|
||||
/// opc: 0x95,
|
||||
/// name: "exit".to_string(),
|
||||
/// desc: "exit".to_string(),
|
||||
/// dst: 0,
|
||||
/// src: 0,
|
||||
/// off: 0,
|
||||
/// imm: 0
|
||||
/// },
|
||||
/// ]);
|
||||
/// ```
|
||||
pub fn to_insn_vec(prog: &[u8]) -> Vec<HLInsn> {
|
||||
if prog.len() % ebpf::INSN_SIZE != 0 {
|
||||
panic!(
|
||||
"[Disassembler] Error: eBPF program length must be a multiple of {:?} octets",
|
||||
ebpf::INSN_SIZE
|
||||
);
|
||||
}
|
||||
if prog.is_empty() {
|
||||
return vec![];
|
||||
}
|
||||
|
||||
let mut res = vec![];
|
||||
let mut insn_ptr: usize = 0;
|
||||
|
||||
while insn_ptr * ebpf::INSN_SIZE < prog.len() {
|
||||
let insn = ebpf::get_insn(prog, insn_ptr);
|
||||
|
||||
let name;
|
||||
let desc;
|
||||
let mut imm = insn.imm as i64;
|
||||
match insn.opc {
|
||||
// BPF_LD class
|
||||
ebpf::LD_ABS_B => {
|
||||
name = "ldabsb";
|
||||
desc = ldabs_str(name, &insn);
|
||||
}
|
||||
ebpf::LD_ABS_H => {
|
||||
name = "ldabsh";
|
||||
desc = ldabs_str(name, &insn);
|
||||
}
|
||||
ebpf::LD_ABS_W => {
|
||||
name = "ldabsw";
|
||||
desc = ldabs_str(name, &insn);
|
||||
}
|
||||
ebpf::LD_ABS_DW => {
|
||||
name = "ldabsdw";
|
||||
desc = ldabs_str(name, &insn);
|
||||
}
|
||||
ebpf::LD_IND_B => {
|
||||
name = "ldindb";
|
||||
desc = ldind_str(name, &insn);
|
||||
}
|
||||
ebpf::LD_IND_H => {
|
||||
name = "ldindh";
|
||||
desc = ldind_str(name, &insn);
|
||||
}
|
||||
ebpf::LD_IND_W => {
|
||||
name = "ldindw";
|
||||
desc = ldind_str(name, &insn);
|
||||
}
|
||||
ebpf::LD_IND_DW => {
|
||||
name = "ldinddw";
|
||||
desc = ldind_str(name, &insn);
|
||||
}
|
||||
|
||||
ebpf::LD_DW_IMM => {
|
||||
insn_ptr += 1;
|
||||
let next_insn = ebpf::get_insn(prog, insn_ptr);
|
||||
imm = ((insn.imm as u32) as u64 + ((next_insn.imm as u64) << 32)) as i64;
|
||||
name = "lddw";
|
||||
desc = format!("{name} r{:}, {imm:#x}", insn.dst);
|
||||
}
|
||||
|
||||
// BPF_LDX class
|
||||
ebpf::LD_B_REG => {
|
||||
name = "ldxb";
|
||||
desc = ld_reg_str(name, &insn);
|
||||
}
|
||||
ebpf::LD_H_REG => {
|
||||
name = "ldxh";
|
||||
desc = ld_reg_str(name, &insn);
|
||||
}
|
||||
ebpf::LD_W_REG => {
|
||||
name = "ldxw";
|
||||
desc = ld_reg_str(name, &insn);
|
||||
}
|
||||
ebpf::LD_DW_REG => {
|
||||
name = "ldxdw";
|
||||
desc = ld_reg_str(name, &insn);
|
||||
}
|
||||
|
||||
// BPF_ST class
|
||||
ebpf::ST_B_IMM => {
|
||||
name = "stb";
|
||||
desc = ld_st_imm_str(name, &insn);
|
||||
}
|
||||
ebpf::ST_H_IMM => {
|
||||
name = "sth";
|
||||
desc = ld_st_imm_str(name, &insn);
|
||||
}
|
||||
ebpf::ST_W_IMM => {
|
||||
name = "stw";
|
||||
desc = ld_st_imm_str(name, &insn);
|
||||
}
|
||||
ebpf::ST_DW_IMM => {
|
||||
name = "stdw";
|
||||
desc = ld_st_imm_str(name, &insn);
|
||||
}
|
||||
|
||||
// BPF_STX class
|
||||
ebpf::ST_B_REG => {
|
||||
name = "stxb";
|
||||
desc = st_reg_str(name, &insn);
|
||||
}
|
||||
ebpf::ST_H_REG => {
|
||||
name = "stxh";
|
||||
desc = st_reg_str(name, &insn);
|
||||
}
|
||||
ebpf::ST_W_REG => {
|
||||
name = "stxw";
|
||||
desc = st_reg_str(name, &insn);
|
||||
}
|
||||
ebpf::ST_DW_REG => {
|
||||
name = "stxdw";
|
||||
desc = st_reg_str(name, &insn);
|
||||
}
|
||||
ebpf::ST_W_XADD => {
|
||||
name = "stxxaddw";
|
||||
desc = st_reg_str(name, &insn);
|
||||
}
|
||||
ebpf::ST_DW_XADD => {
|
||||
name = "stxxadddw";
|
||||
desc = st_reg_str(name, &insn);
|
||||
}
|
||||
|
||||
// BPF_ALU class
|
||||
ebpf::ADD32_IMM => {
|
||||
name = "add32";
|
||||
desc = alu_imm_str(name, &insn);
|
||||
}
|
||||
ebpf::ADD32_REG => {
|
||||
name = "add32";
|
||||
desc = alu_reg_str(name, &insn);
|
||||
}
|
||||
ebpf::SUB32_IMM => {
|
||||
name = "sub32";
|
||||
desc = alu_imm_str(name, &insn);
|
||||
}
|
||||
ebpf::SUB32_REG => {
|
||||
name = "sub32";
|
||||
desc = alu_reg_str(name, &insn);
|
||||
}
|
||||
ebpf::MUL32_IMM => {
|
||||
name = "mul32";
|
||||
desc = alu_imm_str(name, &insn);
|
||||
}
|
||||
ebpf::MUL32_REG => {
|
||||
name = "mul32";
|
||||
desc = alu_reg_str(name, &insn);
|
||||
}
|
||||
ebpf::DIV32_IMM => {
|
||||
name = "div32";
|
||||
desc = alu_imm_str(name, &insn);
|
||||
}
|
||||
ebpf::DIV32_REG => {
|
||||
name = "div32";
|
||||
desc = alu_reg_str(name, &insn);
|
||||
}
|
||||
ebpf::OR32_IMM => {
|
||||
name = "or32";
|
||||
desc = alu_imm_str(name, &insn);
|
||||
}
|
||||
ebpf::OR32_REG => {
|
||||
name = "or32";
|
||||
desc = alu_reg_str(name, &insn);
|
||||
}
|
||||
ebpf::AND32_IMM => {
|
||||
name = "and32";
|
||||
desc = alu_imm_str(name, &insn);
|
||||
}
|
||||
ebpf::AND32_REG => {
|
||||
name = "and32";
|
||||
desc = alu_reg_str(name, &insn);
|
||||
}
|
||||
ebpf::LSH32_IMM => {
|
||||
name = "lsh32";
|
||||
desc = alu_imm_str(name, &insn);
|
||||
}
|
||||
ebpf::LSH32_REG => {
|
||||
name = "lsh32";
|
||||
desc = alu_reg_str(name, &insn);
|
||||
}
|
||||
ebpf::RSH32_IMM => {
|
||||
name = "rsh32";
|
||||
desc = alu_imm_str(name, &insn);
|
||||
}
|
||||
ebpf::RSH32_REG => {
|
||||
name = "rsh32";
|
||||
desc = alu_reg_str(name, &insn);
|
||||
}
|
||||
ebpf::NEG32 => {
|
||||
name = "neg32";
|
||||
desc = format!("{name} r{:}", insn.dst);
|
||||
}
|
||||
ebpf::MOD32_IMM => {
|
||||
name = "mod32";
|
||||
desc = alu_imm_str(name, &insn);
|
||||
}
|
||||
ebpf::MOD32_REG => {
|
||||
name = "mod32";
|
||||
desc = alu_reg_str(name, &insn);
|
||||
}
|
||||
ebpf::XOR32_IMM => {
|
||||
name = "xor32";
|
||||
desc = alu_imm_str(name, &insn);
|
||||
}
|
||||
ebpf::XOR32_REG => {
|
||||
name = "xor32";
|
||||
desc = alu_reg_str(name, &insn);
|
||||
}
|
||||
ebpf::MOV32_IMM => {
|
||||
name = "mov32";
|
||||
desc = alu_imm_str(name, &insn);
|
||||
}
|
||||
ebpf::MOV32_REG => {
|
||||
name = "mov32";
|
||||
desc = alu_reg_str(name, &insn);
|
||||
}
|
||||
ebpf::ARSH32_IMM => {
|
||||
name = "arsh32";
|
||||
desc = alu_imm_str(name, &insn);
|
||||
}
|
||||
ebpf::ARSH32_REG => {
|
||||
name = "arsh32";
|
||||
desc = alu_reg_str(name, &insn);
|
||||
}
|
||||
ebpf::LE => {
|
||||
name = "le";
|
||||
desc = byteswap_str(name, &insn);
|
||||
}
|
||||
ebpf::BE => {
|
||||
name = "be";
|
||||
desc = byteswap_str(name, &insn);
|
||||
}
|
||||
|
||||
// BPF_ALU64 class
|
||||
ebpf::ADD64_IMM => {
|
||||
name = "add64";
|
||||
desc = alu_imm_str(name, &insn);
|
||||
}
|
||||
ebpf::ADD64_REG => {
|
||||
name = "add64";
|
||||
desc = alu_reg_str(name, &insn);
|
||||
}
|
||||
ebpf::SUB64_IMM => {
|
||||
name = "sub64";
|
||||
desc = alu_imm_str(name, &insn);
|
||||
}
|
||||
ebpf::SUB64_REG => {
|
||||
name = "sub64";
|
||||
desc = alu_reg_str(name, &insn);
|
||||
}
|
||||
ebpf::MUL64_IMM => {
|
||||
name = "mul64";
|
||||
desc = alu_imm_str(name, &insn);
|
||||
}
|
||||
ebpf::MUL64_REG => {
|
||||
name = "mul64";
|
||||
desc = alu_reg_str(name, &insn);
|
||||
}
|
||||
ebpf::DIV64_IMM => {
|
||||
name = "div64";
|
||||
desc = alu_imm_str(name, &insn);
|
||||
}
|
||||
ebpf::DIV64_REG => {
|
||||
name = "div64";
|
||||
desc = alu_reg_str(name, &insn);
|
||||
}
|
||||
ebpf::OR64_IMM => {
|
||||
name = "or64";
|
||||
desc = alu_imm_str(name, &insn);
|
||||
}
|
||||
ebpf::OR64_REG => {
|
||||
name = "or64";
|
||||
desc = alu_reg_str(name, &insn);
|
||||
}
|
||||
ebpf::AND64_IMM => {
|
||||
name = "and64";
|
||||
desc = alu_imm_str(name, &insn);
|
||||
}
|
||||
ebpf::AND64_REG => {
|
||||
name = "and64";
|
||||
desc = alu_reg_str(name, &insn);
|
||||
}
|
||||
ebpf::LSH64_IMM => {
|
||||
name = "lsh64";
|
||||
desc = alu_imm_str(name, &insn);
|
||||
}
|
||||
ebpf::LSH64_REG => {
|
||||
name = "lsh64";
|
||||
desc = alu_reg_str(name, &insn);
|
||||
}
|
||||
ebpf::RSH64_IMM => {
|
||||
name = "rsh64";
|
||||
desc = alu_imm_str(name, &insn);
|
||||
}
|
||||
ebpf::RSH64_REG => {
|
||||
name = "rsh64";
|
||||
desc = alu_reg_str(name, &insn);
|
||||
}
|
||||
ebpf::NEG64 => {
|
||||
name = "neg64";
|
||||
desc = format!("{name} r{:}", insn.dst);
|
||||
}
|
||||
ebpf::MOD64_IMM => {
|
||||
name = "mod64";
|
||||
desc = alu_imm_str(name, &insn);
|
||||
}
|
||||
ebpf::MOD64_REG => {
|
||||
name = "mod64";
|
||||
desc = alu_reg_str(name, &insn);
|
||||
}
|
||||
ebpf::XOR64_IMM => {
|
||||
name = "xor64";
|
||||
desc = alu_imm_str(name, &insn);
|
||||
}
|
||||
ebpf::XOR64_REG => {
|
||||
name = "xor64";
|
||||
desc = alu_reg_str(name, &insn);
|
||||
}
|
||||
ebpf::MOV64_IMM => {
|
||||
name = "mov64";
|
||||
desc = alu_imm_str(name, &insn);
|
||||
}
|
||||
ebpf::MOV64_REG => {
|
||||
name = "mov64";
|
||||
desc = alu_reg_str(name, &insn);
|
||||
}
|
||||
ebpf::ARSH64_IMM => {
|
||||
name = "arsh64";
|
||||
desc = alu_imm_str(name, &insn);
|
||||
}
|
||||
ebpf::ARSH64_REG => {
|
||||
name = "arsh64";
|
||||
desc = alu_reg_str(name, &insn);
|
||||
}
|
||||
|
||||
// BPF_JMP class
|
||||
ebpf::JA => {
|
||||
name = "ja";
|
||||
desc = if insn.off >= 0 {
|
||||
format!("{name} +{:#x}", insn.off)
|
||||
} else {
|
||||
format!("{name} -{:#x}", -insn.off)
|
||||
}
|
||||
}
|
||||
ebpf::JEQ_IMM => {
|
||||
name = "jeq";
|
||||
desc = jmp_imm_str(name, &insn);
|
||||
}
|
||||
ebpf::JEQ_REG => {
|
||||
name = "jeq";
|
||||
desc = jmp_reg_str(name, &insn);
|
||||
}
|
||||
ebpf::JGT_IMM => {
|
||||
name = "jgt";
|
||||
desc = jmp_imm_str(name, &insn);
|
||||
}
|
||||
ebpf::JGT_REG => {
|
||||
name = "jgt";
|
||||
desc = jmp_reg_str(name, &insn);
|
||||
}
|
||||
ebpf::JGE_IMM => {
|
||||
name = "jge";
|
||||
desc = jmp_imm_str(name, &insn);
|
||||
}
|
||||
ebpf::JGE_REG => {
|
||||
name = "jge";
|
||||
desc = jmp_reg_str(name, &insn);
|
||||
}
|
||||
ebpf::JLT_IMM => {
|
||||
name = "jlt";
|
||||
desc = jmp_imm_str(name, &insn);
|
||||
}
|
||||
ebpf::JLT_REG => {
|
||||
name = "jlt";
|
||||
desc = jmp_reg_str(name, &insn);
|
||||
}
|
||||
ebpf::JLE_IMM => {
|
||||
name = "jle";
|
||||
desc = jmp_imm_str(name, &insn);
|
||||
}
|
||||
ebpf::JLE_REG => {
|
||||
name = "jle";
|
||||
desc = jmp_reg_str(name, &insn);
|
||||
}
|
||||
ebpf::JSET_IMM => {
|
||||
name = "jset";
|
||||
desc = jmp_imm_str(name, &insn);
|
||||
}
|
||||
ebpf::JSET_REG => {
|
||||
name = "jset";
|
||||
desc = jmp_reg_str(name, &insn);
|
||||
}
|
||||
ebpf::JNE_IMM => {
|
||||
name = "jne";
|
||||
desc = jmp_imm_str(name, &insn);
|
||||
}
|
||||
ebpf::JNE_REG => {
|
||||
name = "jne";
|
||||
desc = jmp_reg_str(name, &insn);
|
||||
}
|
||||
ebpf::JSGT_IMM => {
|
||||
name = "jsgt";
|
||||
desc = jmp_imm_str(name, &insn);
|
||||
}
|
||||
ebpf::JSGT_REG => {
|
||||
name = "jsgt";
|
||||
desc = jmp_reg_str(name, &insn);
|
||||
}
|
||||
ebpf::JSGE_IMM => {
|
||||
name = "jsge";
|
||||
desc = jmp_imm_str(name, &insn);
|
||||
}
|
||||
ebpf::JSGE_REG => {
|
||||
name = "jsge";
|
||||
desc = jmp_reg_str(name, &insn);
|
||||
}
|
||||
ebpf::JSLT_IMM => {
|
||||
name = "jslt";
|
||||
desc = jmp_imm_str(name, &insn);
|
||||
}
|
||||
ebpf::JSLT_REG => {
|
||||
name = "jslt";
|
||||
desc = jmp_reg_str(name, &insn);
|
||||
}
|
||||
ebpf::JSLE_IMM => {
|
||||
name = "jsle";
|
||||
desc = jmp_imm_str(name, &insn);
|
||||
}
|
||||
ebpf::JSLE_REG => {
|
||||
name = "jsle";
|
||||
desc = jmp_reg_str(name, &insn);
|
||||
}
|
||||
ebpf::CALL => {
|
||||
name = "call";
|
||||
desc = format!("{name} {:#x}", insn.imm);
|
||||
}
|
||||
ebpf::TAIL_CALL => {
|
||||
name = "tail_call";
|
||||
desc = name.to_string();
|
||||
}
|
||||
ebpf::EXIT => {
|
||||
name = "exit";
|
||||
desc = name.to_string();
|
||||
}
|
||||
|
||||
// BPF_JMP32 class
|
||||
ebpf::JEQ_IMM32 => {
|
||||
name = "jeq32";
|
||||
desc = jmp_imm_str(name, &insn);
|
||||
}
|
||||
ebpf::JEQ_REG32 => {
|
||||
name = "jeq32";
|
||||
desc = jmp_reg_str(name, &insn);
|
||||
}
|
||||
ebpf::JGT_IMM32 => {
|
||||
name = "jgt32";
|
||||
desc = jmp_imm_str(name, &insn);
|
||||
}
|
||||
ebpf::JGT_REG32 => {
|
||||
name = "jgt32";
|
||||
desc = jmp_reg_str(name, &insn);
|
||||
}
|
||||
ebpf::JGE_IMM32 => {
|
||||
name = "jge32";
|
||||
desc = jmp_imm_str(name, &insn);
|
||||
}
|
||||
ebpf::JGE_REG32 => {
|
||||
name = "jge32";
|
||||
desc = jmp_reg_str(name, &insn);
|
||||
}
|
||||
ebpf::JLT_IMM32 => {
|
||||
name = "jlt32";
|
||||
desc = jmp_imm_str(name, &insn);
|
||||
}
|
||||
ebpf::JLT_REG32 => {
|
||||
name = "jlt32";
|
||||
desc = jmp_reg_str(name, &insn);
|
||||
}
|
||||
ebpf::JLE_IMM32 => {
|
||||
name = "jle32";
|
||||
desc = jmp_imm_str(name, &insn);
|
||||
}
|
||||
ebpf::JLE_REG32 => {
|
||||
name = "jle32";
|
||||
desc = jmp_reg_str(name, &insn);
|
||||
}
|
||||
ebpf::JSET_IMM32 => {
|
||||
name = "jset32";
|
||||
desc = jmp_imm_str(name, &insn);
|
||||
}
|
||||
ebpf::JSET_REG32 => {
|
||||
name = "jset32";
|
||||
desc = jmp_reg_str(name, &insn);
|
||||
}
|
||||
ebpf::JNE_IMM32 => {
|
||||
name = "jne32";
|
||||
desc = jmp_imm_str(name, &insn);
|
||||
}
|
||||
ebpf::JNE_REG32 => {
|
||||
name = "jne32";
|
||||
desc = jmp_reg_str(name, &insn);
|
||||
}
|
||||
ebpf::JSGT_IMM32 => {
|
||||
name = "jsgt32";
|
||||
desc = jmp_imm_str(name, &insn);
|
||||
}
|
||||
ebpf::JSGT_REG32 => {
|
||||
name = "jsgt32";
|
||||
desc = jmp_reg_str(name, &insn);
|
||||
}
|
||||
ebpf::JSGE_IMM32 => {
|
||||
name = "jsge32";
|
||||
desc = jmp_imm_str(name, &insn);
|
||||
}
|
||||
ebpf::JSGE_REG32 => {
|
||||
name = "jsge32";
|
||||
desc = jmp_reg_str(name, &insn);
|
||||
}
|
||||
ebpf::JSLT_IMM32 => {
|
||||
name = "jslt32";
|
||||
desc = jmp_imm_str(name, &insn);
|
||||
}
|
||||
ebpf::JSLT_REG32 => {
|
||||
name = "jslt32";
|
||||
desc = jmp_reg_str(name, &insn);
|
||||
}
|
||||
ebpf::JSLE_IMM32 => {
|
||||
name = "jsle32";
|
||||
desc = jmp_imm_str(name, &insn);
|
||||
}
|
||||
ebpf::JSLE_REG32 => {
|
||||
name = "jsle32";
|
||||
desc = jmp_reg_str(name, &insn);
|
||||
}
|
||||
|
||||
_ => {
|
||||
panic!(
|
||||
"[Disassembler] Error: unknown eBPF opcode {:#2x} (insn #{:?})",
|
||||
insn.opc, insn_ptr
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
let hl_insn = HLInsn {
|
||||
opc: insn.opc,
|
||||
name: name.to_string(),
|
||||
desc,
|
||||
dst: insn.dst,
|
||||
src: insn.src,
|
||||
off: insn.off,
|
||||
imm,
|
||||
};
|
||||
|
||||
res.push(hl_insn);
|
||||
|
||||
insn_ptr += 1;
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
/// Disassemble an eBPF program into human-readable instructions and prints it to standard output.
|
||||
///
|
||||
/// The program is not checked for errors or inconsistencies.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rbpf::disassembler;
|
||||
/// let prog = &[
|
||||
/// 0x07, 0x01, 0x00, 0x00, 0x05, 0x06, 0x00, 0x00,
|
||||
/// 0xb7, 0x02, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00,
|
||||
/// 0xbf, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
/// 0xdc, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
|
||||
/// 0x87, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
/// 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||
/// ];
|
||||
/// disassembler::disassemble(prog);
|
||||
/// # // "\nadd64 r1, 0x605\nmov64 r2, 0x32\nmov64 r1, r0\nbe16 r0\nneg64 r2\nexit"
|
||||
/// ```
|
||||
///
|
||||
/// This will produce the following output:
|
||||
///
|
||||
/// ```test
|
||||
/// add64 r1, 0x605
|
||||
/// mov64 r2, 0x32
|
||||
/// mov64 r1, r0
|
||||
/// be16 r0
|
||||
/// neg64 r2
|
||||
/// exit
|
||||
/// ```
|
||||
pub fn disassemble(prog: &[u8]) {
|
||||
#[cfg(feature = "std")]
|
||||
{
|
||||
for insn in to_insn_vec(prog) {
|
||||
println!("{}", insn.desc);
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "std"))]
|
||||
{
|
||||
for insn in to_insn_vec(prog) {
|
||||
log::info!("{}", insn.desc);
|
||||
}
|
||||
}
|
||||
}
|
635
kernel/crates/rbpf/src/ebpf.rs
Normal file
635
kernel/crates/rbpf/src/ebpf.rs
Normal file
@ -0,0 +1,635 @@
|
||||
// SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
// Copyright 2016 6WIND S.A. <quentin.monnet@6wind.com>
|
||||
|
||||
//! This module contains all the definitions related to eBPF, and some functions permitting to
|
||||
//! manipulate eBPF instructions.
|
||||
//!
|
||||
//! The number of bytes in an instruction, the maximum number of instructions in a program, and
|
||||
//! also all operation codes are defined here as constants.
|
||||
//!
|
||||
//! The structure for an instruction used by this crate, as well as the function to extract it from
|
||||
//! a program, is also defined in the module.
|
||||
//!
|
||||
//! To learn more about these instructions, see the Linux kernel documentation:
|
||||
//! <https://www.kernel.org/doc/Documentation/networking/filter.txt>, or for a shorter version of
|
||||
//! the list of the operation codes: <https://github.com/iovisor/bpf-docs/blob/master/eBPF.md>
|
||||
|
||||
use alloc::{vec, vec::Vec};
|
||||
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
|
||||
/// The maximum call depth is 8
|
||||
pub const RBPF_MAX_CALL_DEPTH: usize = 8;
|
||||
|
||||
/// Maximum number of instructions in an eBPF program.
|
||||
pub const PROG_MAX_INSNS: usize = 1000000;
|
||||
/// Size of an eBPF instructions, in bytes.
|
||||
pub const INSN_SIZE: usize = 8;
|
||||
/// Maximum size of an eBPF program, in bytes.
|
||||
pub const PROG_MAX_SIZE: usize = PROG_MAX_INSNS * INSN_SIZE;
|
||||
/// Stack for the eBPF stack, in bytes.
|
||||
pub const STACK_SIZE: usize = 512;
|
||||
|
||||
// eBPF op codes.
|
||||
// See also https://www.kernel.org/doc/Documentation/networking/filter.txt
|
||||
|
||||
// Three least significant bits are operation class:
|
||||
/// BPF operation class: load from immediate.
|
||||
pub const BPF_LD: u8 = 0x00;
|
||||
/// BPF operation class: load from register.
|
||||
pub const BPF_LDX: u8 = 0x01;
|
||||
/// BPF operation class: store immediate.
|
||||
pub const BPF_ST: u8 = 0x02;
|
||||
/// BPF operation class: store value from register.
|
||||
pub const BPF_STX: u8 = 0x03;
|
||||
/// BPF operation class: 32 bits arithmetic operation.
|
||||
pub const BPF_ALU: u8 = 0x04;
|
||||
/// BPF operation class: jump (64-bit wide operands for comparisons).
|
||||
pub const BPF_JMP: u8 = 0x05;
|
||||
/// BPF operation class: jump (32-bit wide operands for comparisons).
|
||||
pub const BPF_JMP32: u8 = 0x06;
|
||||
// [ class 6 unused, reserved for future use ]
|
||||
/// BPF operation class: 64 bits arithmetic operation.
|
||||
pub const BPF_ALU64: u8 = 0x07;
|
||||
|
||||
// For load and store instructions:
|
||||
// +------------+--------+------------+
|
||||
// | 3 bits | 2 bits | 3 bits |
|
||||
// | mode | size | insn class |
|
||||
// +------------+--------+------------+
|
||||
// (MSB) (LSB)
|
||||
|
||||
// Size modifiers:
|
||||
/// BPF size modifier: word (4 bytes).
|
||||
pub const BPF_W: u8 = 0x00;
|
||||
/// BPF size modifier: half-word (2 bytes).
|
||||
pub const BPF_H: u8 = 0x08;
|
||||
/// BPF size modifier: byte (1 byte).
|
||||
pub const BPF_B: u8 = 0x10;
|
||||
/// BPF size modifier: double word (8 bytes).
|
||||
pub const BPF_DW: u8 = 0x18;
|
||||
|
||||
// Mode modifiers:
|
||||
/// BPF mode modifier: immediate value.
|
||||
pub const BPF_IMM: u8 = 0x00;
|
||||
/// BPF mode modifier: absolute load.
|
||||
pub const BPF_ABS: u8 = 0x20;
|
||||
/// BPF mode modifier: indirect load.
|
||||
pub const BPF_IND: u8 = 0x40;
|
||||
/// BPF mode modifier: load from / store to memory.
|
||||
pub const BPF_MEM: u8 = 0x60;
|
||||
// [ 0x80 reserved ]
|
||||
// [ 0xa0 reserved ]
|
||||
/// BPF mode modifier: exclusive add.
|
||||
pub const BPF_XADD: u8 = 0xc0;
|
||||
|
||||
// For arithmetic (BPF_ALU/BPF_ALU64) and jump (BPF_JMP) instructions:
|
||||
// +----------------+--------+--------+
|
||||
// | 4 bits |1 b.| 3 bits |
|
||||
// | operation code | src| insn class |
|
||||
// +----------------+----+------------+
|
||||
// (MSB) (LSB)
|
||||
|
||||
// Source modifiers:
|
||||
/// BPF source operand modifier: 32-bit immediate value.
|
||||
pub const BPF_K: u8 = 0x00;
|
||||
/// BPF source operand modifier: `src` register.
|
||||
pub const BPF_X: u8 = 0x08;
|
||||
|
||||
// Operation codes -- BPF_ALU or BPF_ALU64 classes:
|
||||
/// BPF ALU/ALU64 operation code: addition.
|
||||
pub const BPF_ADD: u8 = 0x00;
|
||||
/// BPF ALU/ALU64 operation code: subtraction.
|
||||
pub const BPF_SUB: u8 = 0x10;
|
||||
/// BPF ALU/ALU64 operation code: multiplication.
|
||||
pub const BPF_MUL: u8 = 0x20;
|
||||
/// BPF ALU/ALU64 operation code: division.
|
||||
pub const BPF_DIV: u8 = 0x30;
|
||||
/// BPF ALU/ALU64 operation code: or.
|
||||
pub const BPF_OR: u8 = 0x40;
|
||||
/// BPF ALU/ALU64 operation code: and.
|
||||
pub const BPF_AND: u8 = 0x50;
|
||||
/// BPF ALU/ALU64 operation code: left shift.
|
||||
pub const BPF_LSH: u8 = 0x60;
|
||||
/// BPF ALU/ALU64 operation code: right shift.
|
||||
pub const BPF_RSH: u8 = 0x70;
|
||||
/// BPF ALU/ALU64 operation code: negation.
|
||||
pub const BPF_NEG: u8 = 0x80;
|
||||
/// BPF ALU/ALU64 operation code: modulus.
|
||||
pub const BPF_MOD: u8 = 0x90;
|
||||
/// BPF ALU/ALU64 operation code: exclusive or.
|
||||
pub const BPF_XOR: u8 = 0xa0;
|
||||
/// BPF ALU/ALU64 operation code: move.
|
||||
pub const BPF_MOV: u8 = 0xb0;
|
||||
/// BPF ALU/ALU64 operation code: sign extending right shift.
|
||||
pub const BPF_ARSH: u8 = 0xc0;
|
||||
/// BPF ALU/ALU64 operation code: endianness conversion.
|
||||
pub const BPF_END: u8 = 0xd0;
|
||||
|
||||
// Operation codes -- BPF_JMP or BPF_JMP32 classes:
|
||||
/// BPF JMP operation code: jump.
|
||||
pub const BPF_JA: u8 = 0x00;
|
||||
/// BPF JMP operation code: jump if equal.
|
||||
pub const BPF_JEQ: u8 = 0x10;
|
||||
/// BPF JMP operation code: jump if greater than.
|
||||
pub const BPF_JGT: u8 = 0x20;
|
||||
/// BPF JMP operation code: jump if greater or equal.
|
||||
pub const BPF_JGE: u8 = 0x30;
|
||||
/// BPF JMP operation code: jump if `src` & `reg`.
|
||||
pub const BPF_JSET: u8 = 0x40;
|
||||
/// BPF JMP operation code: jump if not equal.
|
||||
pub const BPF_JNE: u8 = 0x50;
|
||||
/// BPF JMP operation code: jump if greater than (signed).
|
||||
pub const BPF_JSGT: u8 = 0x60;
|
||||
/// BPF JMP operation code: jump if greater or equal (signed).
|
||||
pub const BPF_JSGE: u8 = 0x70;
|
||||
/// BPF JMP operation code: helper function call.
|
||||
pub const BPF_CALL: u8 = 0x80;
|
||||
/// BPF JMP operation code: return from program.
|
||||
pub const BPF_EXIT: u8 = 0x90;
|
||||
/// BPF JMP operation code: jump if lower than.
|
||||
pub const BPF_JLT: u8 = 0xa0;
|
||||
/// BPF JMP operation code: jump if lower or equal.
|
||||
pub const BPF_JLE: u8 = 0xb0;
|
||||
/// BPF JMP operation code: jump if lower than (signed).
|
||||
pub const BPF_JSLT: u8 = 0xc0;
|
||||
/// BPF JMP operation code: jump if lower or equal (signed).
|
||||
pub const BPF_JSLE: u8 = 0xd0;
|
||||
|
||||
// Op codes
|
||||
// (Following operation names are not “official”, but may be proper to rbpf; Linux kernel only
|
||||
// combines above flags and does not attribute a name per operation.)
|
||||
|
||||
/// BPF opcode: `ldabsb src, dst, imm`.
|
||||
pub const LD_ABS_B: u8 = BPF_LD | BPF_ABS | BPF_B;
|
||||
/// BPF opcode: `ldabsh src, dst, imm`.
|
||||
pub const LD_ABS_H: u8 = BPF_LD | BPF_ABS | BPF_H;
|
||||
/// BPF opcode: `ldabsw src, dst, imm`.
|
||||
pub const LD_ABS_W: u8 = BPF_LD | BPF_ABS | BPF_W;
|
||||
/// BPF opcode: `ldabsdw src, dst, imm`.
|
||||
pub const LD_ABS_DW: u8 = BPF_LD | BPF_ABS | BPF_DW;
|
||||
/// BPF opcode: `ldindb src, dst, imm`.
|
||||
pub const LD_IND_B: u8 = BPF_LD | BPF_IND | BPF_B;
|
||||
/// BPF opcode: `ldindh src, dst, imm`.
|
||||
pub const LD_IND_H: u8 = BPF_LD | BPF_IND | BPF_H;
|
||||
/// BPF opcode: `ldindw src, dst, imm`.
|
||||
pub const LD_IND_W: u8 = BPF_LD | BPF_IND | BPF_W;
|
||||
/// BPF opcode: `ldinddw src, dst, imm`.
|
||||
pub const LD_IND_DW: u8 = BPF_LD | BPF_IND | BPF_DW;
|
||||
|
||||
#[allow(unknown_lints)]
|
||||
#[allow(clippy::eq_op)]
|
||||
/// BPF opcode: `lddw dst, imm` /// `dst = imm`.
|
||||
pub const LD_DW_IMM: u8 = BPF_LD | BPF_IMM | BPF_DW;
|
||||
/// BPF opcode: `ldxb dst, [src + off]` /// `dst = (src + off) as u8`.
|
||||
pub const LD_B_REG: u8 = BPF_LDX | BPF_MEM | BPF_B;
|
||||
/// BPF opcode: `ldxh dst, [src + off]` /// `dst = (src + off) as u16`.
|
||||
pub const LD_H_REG: u8 = BPF_LDX | BPF_MEM | BPF_H;
|
||||
/// BPF opcode: `ldxw dst, [src + off]` /// `dst = (src + off) as u32`.
|
||||
pub const LD_W_REG: u8 = BPF_LDX | BPF_MEM | BPF_W;
|
||||
/// BPF opcode: `ldxdw dst, [src + off]` /// `dst = (src + off) as u64`.
|
||||
pub const LD_DW_REG: u8 = BPF_LDX | BPF_MEM | BPF_DW;
|
||||
/// BPF opcode: `stb [dst + off], imm` /// `(dst + offset) as u8 = imm`.
|
||||
pub const ST_B_IMM: u8 = BPF_ST | BPF_MEM | BPF_B;
|
||||
/// BPF opcode: `sth [dst + off], imm` /// `(dst + offset) as u16 = imm`.
|
||||
pub const ST_H_IMM: u8 = BPF_ST | BPF_MEM | BPF_H;
|
||||
/// BPF opcode: `stw [dst + off], imm` /// `(dst + offset) as u32 = imm`.
|
||||
pub const ST_W_IMM: u8 = BPF_ST | BPF_MEM | BPF_W;
|
||||
/// BPF opcode: `stdw [dst + off], imm` /// `(dst + offset) as u64 = imm`.
|
||||
pub const ST_DW_IMM: u8 = BPF_ST | BPF_MEM | BPF_DW;
|
||||
/// BPF opcode: `stxb [dst + off], src` /// `(dst + offset) as u8 = src`.
|
||||
pub const ST_B_REG: u8 = BPF_STX | BPF_MEM | BPF_B;
|
||||
/// BPF opcode: `stxh [dst + off], src` /// `(dst + offset) as u16 = src`.
|
||||
pub const ST_H_REG: u8 = BPF_STX | BPF_MEM | BPF_H;
|
||||
/// BPF opcode: `stxw [dst + off], src` /// `(dst + offset) as u32 = src`.
|
||||
pub const ST_W_REG: u8 = BPF_STX | BPF_MEM | BPF_W;
|
||||
/// BPF opcode: `stxdw [dst + off], src` /// `(dst + offset) as u64 = src`.
|
||||
pub const ST_DW_REG: u8 = BPF_STX | BPF_MEM | BPF_DW;
|
||||
|
||||
/// BPF opcode: `stxxaddw [dst + off], src`.
|
||||
pub const ST_W_XADD: u8 = BPF_STX | BPF_XADD | BPF_W;
|
||||
/// BPF opcode: `stxxadddw [dst + off], src`.
|
||||
pub const ST_DW_XADD: u8 = BPF_STX | BPF_XADD | BPF_DW;
|
||||
|
||||
/// BPF opcode: `add32 dst, imm` /// `dst += imm`.
|
||||
pub const ADD32_IMM: u8 = BPF_ALU | BPF_K | BPF_ADD;
|
||||
/// BPF opcode: `add32 dst, src` /// `dst += src`.
|
||||
pub const ADD32_REG: u8 = BPF_ALU | BPF_X | BPF_ADD;
|
||||
/// BPF opcode: `sub32 dst, imm` /// `dst -= imm`.
|
||||
pub const SUB32_IMM: u8 = BPF_ALU | BPF_K | BPF_SUB;
|
||||
/// BPF opcode: `sub32 dst, src` /// `dst -= src`.
|
||||
pub const SUB32_REG: u8 = BPF_ALU | BPF_X | BPF_SUB;
|
||||
/// BPF opcode: `mul32 dst, imm` /// `dst *= imm`.
|
||||
pub const MUL32_IMM: u8 = BPF_ALU | BPF_K | BPF_MUL;
|
||||
/// BPF opcode: `mul32 dst, src` /// `dst *= src`.
|
||||
pub const MUL32_REG: u8 = BPF_ALU | BPF_X | BPF_MUL;
|
||||
/// BPF opcode: `div32 dst, imm` /// `dst /= imm`.
|
||||
pub const DIV32_IMM: u8 = BPF_ALU | BPF_K | BPF_DIV;
|
||||
/// BPF opcode: `div32 dst, src` /// `dst /= src`.
|
||||
pub const DIV32_REG: u8 = BPF_ALU | BPF_X | BPF_DIV;
|
||||
/// BPF opcode: `or32 dst, imm` /// `dst |= imm`.
|
||||
pub const OR32_IMM: u8 = BPF_ALU | BPF_K | BPF_OR;
|
||||
/// BPF opcode: `or32 dst, src` /// `dst |= src`.
|
||||
pub const OR32_REG: u8 = BPF_ALU | BPF_X | BPF_OR;
|
||||
/// BPF opcode: `and32 dst, imm` /// `dst &= imm`.
|
||||
pub const AND32_IMM: u8 = BPF_ALU | BPF_K | BPF_AND;
|
||||
/// BPF opcode: `and32 dst, src` /// `dst &= src`.
|
||||
pub const AND32_REG: u8 = BPF_ALU | BPF_X | BPF_AND;
|
||||
/// BPF opcode: `lsh32 dst, imm` /// `dst <<= imm`.
|
||||
pub const LSH32_IMM: u8 = BPF_ALU | BPF_K | BPF_LSH;
|
||||
/// BPF opcode: `lsh32 dst, src` /// `dst <<= src`.
|
||||
pub const LSH32_REG: u8 = BPF_ALU | BPF_X | BPF_LSH;
|
||||
/// BPF opcode: `rsh32 dst, imm` /// `dst >>= imm`.
|
||||
pub const RSH32_IMM: u8 = BPF_ALU | BPF_K | BPF_RSH;
|
||||
/// BPF opcode: `rsh32 dst, src` /// `dst >>= src`.
|
||||
pub const RSH32_REG: u8 = BPF_ALU | BPF_X | BPF_RSH;
|
||||
/// BPF opcode: `neg32 dst` /// `dst = -dst`.
|
||||
pub const NEG32: u8 = BPF_ALU | BPF_NEG;
|
||||
/// BPF opcode: `mod32 dst, imm` /// `dst %= imm`.
|
||||
pub const MOD32_IMM: u8 = BPF_ALU | BPF_K | BPF_MOD;
|
||||
/// BPF opcode: `mod32 dst, src` /// `dst %= src`.
|
||||
pub const MOD32_REG: u8 = BPF_ALU | BPF_X | BPF_MOD;
|
||||
/// BPF opcode: `xor32 dst, imm` /// `dst ^= imm`.
|
||||
pub const XOR32_IMM: u8 = BPF_ALU | BPF_K | BPF_XOR;
|
||||
/// BPF opcode: `xor32 dst, src` /// `dst ^= src`.
|
||||
pub const XOR32_REG: u8 = BPF_ALU | BPF_X | BPF_XOR;
|
||||
/// BPF opcode: `mov32 dst, imm` /// `dst = imm`.
|
||||
pub const MOV32_IMM: u8 = BPF_ALU | BPF_K | BPF_MOV;
|
||||
/// BPF opcode: `mov32 dst, src` /// `dst = src`.
|
||||
pub const MOV32_REG: u8 = BPF_ALU | BPF_X | BPF_MOV;
|
||||
/// BPF opcode: `arsh32 dst, imm` /// `dst >>= imm (arithmetic)`.
|
||||
///
|
||||
/// <https://en.wikipedia.org/wiki/Arithmetic_shift>
|
||||
pub const ARSH32_IMM: u8 = BPF_ALU | BPF_K | BPF_ARSH;
|
||||
/// BPF opcode: `arsh32 dst, src` /// `dst >>= src (arithmetic)`.
|
||||
///
|
||||
/// <https://en.wikipedia.org/wiki/Arithmetic_shift>
|
||||
pub const ARSH32_REG: u8 = BPF_ALU | BPF_X | BPF_ARSH;
|
||||
|
||||
/// BPF opcode: `le dst` /// `dst = htole<imm>(dst), with imm in {16, 32, 64}`.
|
||||
pub const LE: u8 = BPF_ALU | BPF_K | BPF_END;
|
||||
/// BPF opcode: `be dst` /// `dst = htobe<imm>(dst), with imm in {16, 32, 64}`.
|
||||
pub const BE: u8 = BPF_ALU | BPF_X | BPF_END;
|
||||
|
||||
/// BPF opcode: `add64 dst, imm` /// `dst += imm`.
|
||||
pub const ADD64_IMM: u8 = BPF_ALU64 | BPF_K | BPF_ADD;
|
||||
/// BPF opcode: `add64 dst, src` /// `dst += src`.
|
||||
pub const ADD64_REG: u8 = BPF_ALU64 | BPF_X | BPF_ADD;
|
||||
/// BPF opcode: `sub64 dst, imm` /// `dst -= imm`.
|
||||
pub const SUB64_IMM: u8 = BPF_ALU64 | BPF_K | BPF_SUB;
|
||||
/// BPF opcode: `sub64 dst, src` /// `dst -= src`.
|
||||
pub const SUB64_REG: u8 = BPF_ALU64 | BPF_X | BPF_SUB;
|
||||
/// BPF opcode: `div64 dst, imm` /// `dst /= imm`.
|
||||
pub const MUL64_IMM: u8 = BPF_ALU64 | BPF_K | BPF_MUL;
|
||||
/// BPF opcode: `div64 dst, src` /// `dst /= src`.
|
||||
pub const MUL64_REG: u8 = BPF_ALU64 | BPF_X | BPF_MUL;
|
||||
/// BPF opcode: `div64 dst, imm` /// `dst /= imm`.
|
||||
pub const DIV64_IMM: u8 = BPF_ALU64 | BPF_K | BPF_DIV;
|
||||
/// BPF opcode: `div64 dst, src` /// `dst /= src`.
|
||||
pub const DIV64_REG: u8 = BPF_ALU64 | BPF_X | BPF_DIV;
|
||||
/// BPF opcode: `or64 dst, imm` /// `dst |= imm`.
|
||||
pub const OR64_IMM: u8 = BPF_ALU64 | BPF_K | BPF_OR;
|
||||
/// BPF opcode: `or64 dst, src` /// `dst |= src`.
|
||||
pub const OR64_REG: u8 = BPF_ALU64 | BPF_X | BPF_OR;
|
||||
/// BPF opcode: `and64 dst, imm` /// `dst &= imm`.
|
||||
pub const AND64_IMM: u8 = BPF_ALU64 | BPF_K | BPF_AND;
|
||||
/// BPF opcode: `and64 dst, src` /// `dst &= src`.
|
||||
pub const AND64_REG: u8 = BPF_ALU64 | BPF_X | BPF_AND;
|
||||
/// BPF opcode: `lsh64 dst, imm` /// `dst <<= imm`.
|
||||
pub const LSH64_IMM: u8 = BPF_ALU64 | BPF_K | BPF_LSH;
|
||||
/// BPF opcode: `lsh64 dst, src` /// `dst <<= src`.
|
||||
pub const LSH64_REG: u8 = BPF_ALU64 | BPF_X | BPF_LSH;
|
||||
/// BPF opcode: `rsh64 dst, imm` /// `dst >>= imm`.
|
||||
pub const RSH64_IMM: u8 = BPF_ALU64 | BPF_K | BPF_RSH;
|
||||
/// BPF opcode: `rsh64 dst, src` /// `dst >>= src`.
|
||||
pub const RSH64_REG: u8 = BPF_ALU64 | BPF_X | BPF_RSH;
|
||||
/// BPF opcode: `neg64 dst, imm` /// `dst = -dst`.
|
||||
pub const NEG64: u8 = BPF_ALU64 | BPF_NEG;
|
||||
/// BPF opcode: `mod64 dst, imm` /// `dst %= imm`.
|
||||
pub const MOD64_IMM: u8 = BPF_ALU64 | BPF_K | BPF_MOD;
|
||||
/// BPF opcode: `mod64 dst, src` /// `dst %= src`.
|
||||
pub const MOD64_REG: u8 = BPF_ALU64 | BPF_X | BPF_MOD;
|
||||
/// BPF opcode: `xor64 dst, imm` /// `dst ^= imm`.
|
||||
pub const XOR64_IMM: u8 = BPF_ALU64 | BPF_K | BPF_XOR;
|
||||
/// BPF opcode: `xor64 dst, src` /// `dst ^= src`.
|
||||
pub const XOR64_REG: u8 = BPF_ALU64 | BPF_X | BPF_XOR;
|
||||
/// BPF opcode: `mov64 dst, imm` /// `dst = imm`.
|
||||
pub const MOV64_IMM: u8 = BPF_ALU64 | BPF_K | BPF_MOV;
|
||||
/// BPF opcode: `mov64 dst, src` /// `dst = src`.
|
||||
pub const MOV64_REG: u8 = BPF_ALU64 | BPF_X | BPF_MOV;
|
||||
/// BPF opcode: `arsh64 dst, imm` /// `dst >>= imm (arithmetic)`.
|
||||
///
|
||||
/// <https://en.wikipedia.org/wiki/Arithmetic_shift>
|
||||
pub const ARSH64_IMM: u8 = BPF_ALU64 | BPF_K | BPF_ARSH;
|
||||
/// BPF opcode: `arsh64 dst, src` /// `dst >>= src (arithmetic)`.
|
||||
///
|
||||
/// <https://en.wikipedia.org/wiki/Arithmetic_shift>
|
||||
pub const ARSH64_REG: u8 = BPF_ALU64 | BPF_X | BPF_ARSH;
|
||||
|
||||
/// BPF opcode: `ja +off` /// `PC += off`.
|
||||
pub const JA: u8 = BPF_JMP | BPF_JA;
|
||||
/// BPF opcode: `jeq dst, imm, +off` /// `PC += off if dst == imm`.
|
||||
pub const JEQ_IMM: u8 = BPF_JMP | BPF_K | BPF_JEQ;
|
||||
/// BPF opcode: `jeq dst, src, +off` /// `PC += off if dst == src`.
|
||||
pub const JEQ_REG: u8 = BPF_JMP | BPF_X | BPF_JEQ;
|
||||
/// BPF opcode: `jgt dst, imm, +off` /// `PC += off if dst > imm`.
|
||||
pub const JGT_IMM: u8 = BPF_JMP | BPF_K | BPF_JGT;
|
||||
/// BPF opcode: `jgt dst, src, +off` /// `PC += off if dst > src`.
|
||||
pub const JGT_REG: u8 = BPF_JMP | BPF_X | BPF_JGT;
|
||||
/// BPF opcode: `jge dst, imm, +off` /// `PC += off if dst >= imm`.
|
||||
pub const JGE_IMM: u8 = BPF_JMP | BPF_K | BPF_JGE;
|
||||
/// BPF opcode: `jge dst, src, +off` /// `PC += off if dst >= src`.
|
||||
pub const JGE_REG: u8 = BPF_JMP | BPF_X | BPF_JGE;
|
||||
/// BPF opcode: `jlt dst, imm, +off` /// `PC += off if dst < imm`.
|
||||
pub const JLT_IMM: u8 = BPF_JMP | BPF_K | BPF_JLT;
|
||||
/// BPF opcode: `jlt dst, src, +off` /// `PC += off if dst < src`.
|
||||
pub const JLT_REG: u8 = BPF_JMP | BPF_X | BPF_JLT;
|
||||
/// BPF opcode: `jle dst, imm, +off` /// `PC += off if dst <= imm`.
|
||||
pub const JLE_IMM: u8 = BPF_JMP | BPF_K | BPF_JLE;
|
||||
/// BPF opcode: `jle dst, src, +off` /// `PC += off if dst <= src`.
|
||||
pub const JLE_REG: u8 = BPF_JMP | BPF_X | BPF_JLE;
|
||||
/// BPF opcode: `jset dst, imm, +off` /// `PC += off if dst & imm`.
|
||||
pub const JSET_IMM: u8 = BPF_JMP | BPF_K | BPF_JSET;
|
||||
/// BPF opcode: `jset dst, src, +off` /// `PC += off if dst & src`.
|
||||
pub const JSET_REG: u8 = BPF_JMP | BPF_X | BPF_JSET;
|
||||
/// BPF opcode: `jne dst, imm, +off` /// `PC += off if dst != imm`.
|
||||
pub const JNE_IMM: u8 = BPF_JMP | BPF_K | BPF_JNE;
|
||||
/// BPF opcode: `jne dst, src, +off` /// `PC += off if dst != src`.
|
||||
pub const JNE_REG: u8 = BPF_JMP | BPF_X | BPF_JNE;
|
||||
/// BPF opcode: `jsgt dst, imm, +off` /// `PC += off if dst > imm (signed)`.
|
||||
pub const JSGT_IMM: u8 = BPF_JMP | BPF_K | BPF_JSGT;
|
||||
/// BPF opcode: `jsgt dst, src, +off` /// `PC += off if dst > src (signed)`.
|
||||
pub const JSGT_REG: u8 = BPF_JMP | BPF_X | BPF_JSGT;
|
||||
/// BPF opcode: `jsge dst, imm, +off` /// `PC += off if dst >= imm (signed)`.
|
||||
pub const JSGE_IMM: u8 = BPF_JMP | BPF_K | BPF_JSGE;
|
||||
/// BPF opcode: `jsge dst, src, +off` /// `PC += off if dst >= src (signed)`.
|
||||
pub const JSGE_REG: u8 = BPF_JMP | BPF_X | BPF_JSGE;
|
||||
/// BPF opcode: `jslt dst, imm, +off` /// `PC += off if dst < imm (signed)`.
|
||||
pub const JSLT_IMM: u8 = BPF_JMP | BPF_K | BPF_JSLT;
|
||||
/// BPF opcode: `jslt dst, src, +off` /// `PC += off if dst < src (signed)`.
|
||||
pub const JSLT_REG: u8 = BPF_JMP | BPF_X | BPF_JSLT;
|
||||
/// BPF opcode: `jsle dst, imm, +off` /// `PC += off if dst <= imm (signed)`.
|
||||
pub const JSLE_IMM: u8 = BPF_JMP | BPF_K | BPF_JSLE;
|
||||
/// BPF opcode: `jsle dst, src, +off` /// `PC += off if dst <= src (signed)`.
|
||||
pub const JSLE_REG: u8 = BPF_JMP | BPF_X | BPF_JSLE;
|
||||
|
||||
/// BPF opcode: `jeq dst, imm, +off` /// `PC += off if (dst as u32) == imm`.
|
||||
pub const JEQ_IMM32: u8 = BPF_JMP32 | BPF_K | BPF_JEQ;
|
||||
/// BPF opcode: `jeq dst, src, +off` /// `PC += off if (dst as u32) == (src as u32)`.
|
||||
pub const JEQ_REG32: u8 = BPF_JMP32 | BPF_X | BPF_JEQ;
|
||||
/// BPF opcode: `jgt dst, imm, +off` /// `PC += off if (dst as u32) > imm`.
|
||||
pub const JGT_IMM32: u8 = BPF_JMP32 | BPF_K | BPF_JGT;
|
||||
/// BPF opcode: `jgt dst, src, +off` /// `PC += off if (dst as u32) > (src as u32)`.
|
||||
pub const JGT_REG32: u8 = BPF_JMP32 | BPF_X | BPF_JGT;
|
||||
/// BPF opcode: `jge dst, imm, +off` /// `PC += off if (dst as u32) >= imm`.
|
||||
pub const JGE_IMM32: u8 = BPF_JMP32 | BPF_K | BPF_JGE;
|
||||
/// BPF opcode: `jge dst, src, +off` /// `PC += off if (dst as u32) >= (src as u32)`.
|
||||
pub const JGE_REG32: u8 = BPF_JMP32 | BPF_X | BPF_JGE;
|
||||
/// BPF opcode: `jlt dst, imm, +off` /// `PC += off if (dst as u32) < imm`.
|
||||
pub const JLT_IMM32: u8 = BPF_JMP32 | BPF_K | BPF_JLT;
|
||||
/// BPF opcode: `jlt dst, src, +off` /// `PC += off if (dst as u32) < (src as u32)`.
|
||||
pub const JLT_REG32: u8 = BPF_JMP32 | BPF_X | BPF_JLT;
|
||||
/// BPF opcode: `jle dst, imm, +off` /// `PC += off if (dst as u32) <= imm`.
|
||||
pub const JLE_IMM32: u8 = BPF_JMP32 | BPF_K | BPF_JLE;
|
||||
/// BPF opcode: `jle dst, src, +off` /// `PC += off if (dst as u32) <= (src as u32)`.
|
||||
pub const JLE_REG32: u8 = BPF_JMP32 | BPF_X | BPF_JLE;
|
||||
/// BPF opcode: `jset dst, imm, +off` /// `PC += off if (dst as u32) & imm`.
|
||||
pub const JSET_IMM32: u8 = BPF_JMP32 | BPF_K | BPF_JSET;
|
||||
/// BPF opcode: `jset dst, src, +off` /// `PC += off if (dst as u32) & (src as u32)`.
|
||||
pub const JSET_REG32: u8 = BPF_JMP32 | BPF_X | BPF_JSET;
|
||||
/// BPF opcode: `jne dst, imm, +off` /// `PC += off if (dst as u32) != imm`.
|
||||
pub const JNE_IMM32: u8 = BPF_JMP32 | BPF_K | BPF_JNE;
|
||||
/// BPF opcode: `jne dst, src, +off` /// `PC += off if (dst as u32) != (src as u32)`.
|
||||
pub const JNE_REG32: u8 = BPF_JMP32 | BPF_X | BPF_JNE;
|
||||
/// BPF opcode: `jsgt dst, imm, +off` /// `PC += off if (dst as i32) > imm (signed)`.
|
||||
pub const JSGT_IMM32: u8 = BPF_JMP32 | BPF_K | BPF_JSGT;
|
||||
/// BPF opcode: `jsgt dst, src, +off` /// `PC += off if (dst as i32) > (src as i32) (signed)`.
|
||||
pub const JSGT_REG32: u8 = BPF_JMP32 | BPF_X | BPF_JSGT;
|
||||
/// BPF opcode: `jsge dst, imm, +off` /// `PC += off if (dst as i32) >= imm (signed)`.
|
||||
pub const JSGE_IMM32: u8 = BPF_JMP32 | BPF_K | BPF_JSGE;
|
||||
/// BPF opcode: `jsge dst, src, +off` /// `PC += off if (dst as i32) >= (src as i32) (signed)`.
|
||||
pub const JSGE_REG32: u8 = BPF_JMP32 | BPF_X | BPF_JSGE;
|
||||
/// BPF opcode: `jslt dst, imm, +off` /// `PC += off if (dst as i32) < imm (signed)`.
|
||||
pub const JSLT_IMM32: u8 = BPF_JMP32 | BPF_K | BPF_JSLT;
|
||||
/// BPF opcode: `jslt dst, src, +off` /// `PC += off if (dst as i32) < (src as i32) (signed)`.
|
||||
pub const JSLT_REG32: u8 = BPF_JMP32 | BPF_X | BPF_JSLT;
|
||||
/// BPF opcode: `jsle dst, imm, +off` /// `PC += off if (dst as i32) <= imm (signed)`.
|
||||
pub const JSLE_IMM32: u8 = BPF_JMP32 | BPF_K | BPF_JSLE;
|
||||
/// BPF opcode: `jsle dst, src, +off` /// `PC += off if (dst as i32) <= (src as i32) (signed)`.
|
||||
pub const JSLE_REG32: u8 = BPF_JMP32 | BPF_X | BPF_JSLE;
|
||||
|
||||
/// BPF opcode: `call imm` /// helper function call to helper with key `imm`.
|
||||
pub const CALL: u8 = BPF_JMP | BPF_CALL;
|
||||
/// BPF opcode: tail call.
|
||||
pub const TAIL_CALL: u8 = BPF_JMP | BPF_X | BPF_CALL;
|
||||
/// BPF opcode: `exit` /// `return r0`.
|
||||
pub const EXIT: u8 = BPF_JMP | BPF_EXIT;
|
||||
|
||||
// Used in JIT
|
||||
/// Mask to extract the operation class from an operation code.
|
||||
pub const BPF_CLS_MASK: u8 = 0x07;
|
||||
/// Mask to extract the arithmetic operation code from an instruction operation code.
|
||||
pub const BPF_ALU_OP_MASK: u8 = 0xf0;
|
||||
|
||||
/// Prototype of an eBPF helper function.
|
||||
pub type Helper = fn(u64, u64, u64, u64, u64) -> u64;
|
||||
|
||||
/// An eBPF instruction.
|
||||
///
|
||||
/// See <https://www.kernel.org/doc/Documentation/networking/filter.txt> for the Linux kernel
|
||||
/// documentation about eBPF, or <https://github.com/iovisor/bpf-docs/blob/master/eBPF.md> for a
|
||||
/// more concise version.
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub struct Insn {
|
||||
/// Operation code.
|
||||
pub opc: u8,
|
||||
/// Destination register operand.
|
||||
pub dst: u8,
|
||||
/// Source register operand.
|
||||
pub src: u8,
|
||||
/// Offset operand.
|
||||
pub off: i16,
|
||||
/// Immediate value operand.
|
||||
pub imm: i32,
|
||||
}
|
||||
|
||||
impl Insn {
|
||||
/// Turn an `Insn` back into an array of bytes.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rbpf::ebpf;
|
||||
///
|
||||
/// let prog: &[u8] = &[
|
||||
/// 0xb7, 0x12, 0x56, 0x34, 0xde, 0xbc, 0x9a, 0x78,
|
||||
/// ];
|
||||
/// let insn = ebpf::Insn {
|
||||
/// opc: 0xb7,
|
||||
/// dst: 2,
|
||||
/// src: 1,
|
||||
/// off: 0x3456,
|
||||
/// imm: 0x789abcde
|
||||
/// };
|
||||
/// assert_eq!(insn.to_array(), prog);
|
||||
/// ```
|
||||
pub fn to_array(&self) -> [u8; INSN_SIZE] {
|
||||
[
|
||||
self.opc,
|
||||
self.src.wrapping_shl(4) | self.dst,
|
||||
(self.off & 0xff) as u8,
|
||||
self.off.wrapping_shr(8) as u8,
|
||||
(self.imm & 0xff) as u8,
|
||||
(self.imm & 0xff_00).wrapping_shr(8) as u8,
|
||||
(self.imm as u32 & 0xff_00_00).wrapping_shr(16) as u8,
|
||||
(self.imm as u32 & 0xff_00_00_00).wrapping_shr(24) as u8,
|
||||
]
|
||||
}
|
||||
|
||||
/// Turn an `Insn` into an vector of bytes.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rbpf::ebpf;
|
||||
///
|
||||
/// let prog: Vec<u8> = vec![
|
||||
/// 0xb7, 0x12, 0x56, 0x34, 0xde, 0xbc, 0x9a, 0x78,
|
||||
/// ];
|
||||
/// let insn = ebpf::Insn {
|
||||
/// opc: 0xb7,
|
||||
/// dst: 2,
|
||||
/// src: 1,
|
||||
/// off: 0x3456,
|
||||
/// imm: 0x789abcde
|
||||
/// };
|
||||
/// assert_eq!(insn.to_vec(), prog);
|
||||
/// ```
|
||||
pub fn to_vec(&self) -> Vec<u8> {
|
||||
vec![
|
||||
self.opc,
|
||||
self.src.wrapping_shl(4) | self.dst,
|
||||
(self.off & 0xff) as u8,
|
||||
self.off.wrapping_shr(8) as u8,
|
||||
(self.imm & 0xff) as u8,
|
||||
(self.imm & 0xff_00).wrapping_shr(8) as u8,
|
||||
(self.imm as u32 & 0xff_00_00).wrapping_shr(16) as u8,
|
||||
(self.imm as u32 & 0xff_00_00_00).wrapping_shr(24) as u8,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the instruction at `idx` of an eBPF program. `idx` is the index (number) of the
|
||||
/// instruction (not a byte offset). The first instruction has index 0.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if it is not possible to get the instruction (if idx is too high, or last instruction is
|
||||
/// incomplete).
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rbpf::ebpf;
|
||||
///
|
||||
/// let prog = &[
|
||||
/// 0xb7, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
/// 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||
/// ];
|
||||
/// let insn = ebpf::get_insn(prog, 1);
|
||||
/// assert_eq!(insn.opc, 0x95);
|
||||
/// ```
|
||||
///
|
||||
/// The example below will panic, since the last instruction is not complete and cannot be loaded.
|
||||
///
|
||||
/// ```rust,should_panic
|
||||
/// use rbpf::ebpf;
|
||||
///
|
||||
/// let prog = &[
|
||||
/// 0xb7, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
/// 0x95, 0x00, 0x00, 0x00, 0x00, 0x00 // two bytes missing
|
||||
/// ];
|
||||
/// let insn = ebpf::get_insn(prog, 1);
|
||||
/// ```
|
||||
pub fn get_insn(prog: &[u8], idx: usize) -> Insn {
|
||||
// This guard should not be needed in most cases, since the verifier already checks the program
|
||||
// size, and indexes should be fine in the interpreter/JIT. But this function is publicly
|
||||
// available and user can call it with any `idx`, so we have to check anyway.
|
||||
if (idx + 1) * INSN_SIZE > prog.len() {
|
||||
panic!(
|
||||
"Error: cannot reach instruction at index {:?} in program containing {:?} bytes",
|
||||
idx,
|
||||
prog.len()
|
||||
);
|
||||
}
|
||||
Insn {
|
||||
opc: prog[INSN_SIZE * idx],
|
||||
dst: prog[INSN_SIZE * idx + 1] & 0x0f,
|
||||
src: (prog[INSN_SIZE * idx + 1] & 0xf0) >> 4,
|
||||
off: LittleEndian::read_i16(&prog[(INSN_SIZE * idx + 2)..]),
|
||||
imm: LittleEndian::read_i32(&prog[(INSN_SIZE * idx + 4)..]),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a vector of `struct Insn` built from a program.
|
||||
///
|
||||
/// This is provided as a convenience for users wishing to manipulate a vector of instructions, for
|
||||
/// example for dumping the program instruction after instruction with a custom format.
|
||||
///
|
||||
/// Note that the two parts of `LD_DW_IMM` instructions (spanning on 64 bits) are considered as two
|
||||
/// distinct instructions.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rbpf::ebpf;
|
||||
///
|
||||
/// let prog = &[
|
||||
/// 0x18, 0x00, 0x00, 0x00, 0x88, 0x77, 0x66, 0x55,
|
||||
/// 0x00, 0x00, 0x00, 0x00, 0x44, 0x33, 0x22, 0x11,
|
||||
/// 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||
/// ];
|
||||
///
|
||||
/// let v = ebpf::to_insn_vec(prog);
|
||||
/// assert_eq!(v, vec![
|
||||
/// ebpf::Insn {
|
||||
/// opc: 0x18,
|
||||
/// dst: 0,
|
||||
/// src: 0,
|
||||
/// off: 0,
|
||||
/// imm: 0x55667788
|
||||
/// },
|
||||
/// ebpf::Insn {
|
||||
/// opc: 0,
|
||||
/// dst: 0,
|
||||
/// src: 0,
|
||||
/// off: 0,
|
||||
/// imm: 0x11223344
|
||||
/// },
|
||||
/// ebpf::Insn {
|
||||
/// opc: 0x95,
|
||||
/// dst: 0,
|
||||
/// src: 0,
|
||||
/// off: 0,
|
||||
/// imm: 0
|
||||
/// },
|
||||
/// ]);
|
||||
/// ```
|
||||
pub fn to_insn_vec(prog: &[u8]) -> Vec<Insn> {
|
||||
if prog.len() % INSN_SIZE != 0 {
|
||||
panic!(
|
||||
"Error: eBPF program length must be a multiple of {:?} octets",
|
||||
INSN_SIZE
|
||||
);
|
||||
}
|
||||
|
||||
let mut res = vec![];
|
||||
let mut insn_ptr: usize = 0;
|
||||
|
||||
while insn_ptr * INSN_SIZE < prog.len() {
|
||||
let insn = get_insn(prog, insn_ptr);
|
||||
res.push(insn);
|
||||
insn_ptr += 1;
|
||||
}
|
||||
res
|
||||
}
|
488
kernel/crates/rbpf/src/helpers.rs
Normal file
488
kernel/crates/rbpf/src/helpers.rs
Normal file
@ -0,0 +1,488 @@
|
||||
// SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
// Copyright 2015 Big Switch Networks, Inc
|
||||
// (Algorithms for uBPF helpers, originally in C)
|
||||
// Copyright 2016 6WIND S.A. <quentin.monnet@6wind.com>
|
||||
// (Translation to Rust, other helpers)
|
||||
|
||||
//! This module implements some built-in helpers that can be called from within an eBPF program.
|
||||
//!
|
||||
//! These helpers may originate from several places:
|
||||
//!
|
||||
//! * Some of them mimic the helpers available in the Linux kernel.
|
||||
//! * Some of them were proposed as example helpers in uBPF and they were adapted here.
|
||||
//! * Other helpers may be specific to rbpf.
|
||||
//!
|
||||
//! The prototype for helpers is always the same: five `u64` as arguments, and a `u64` as a return
|
||||
//! value. Hence some helpers have unused arguments, or return a 0 value in all cases, in order to
|
||||
//! respect this convention.
|
||||
|
||||
// Helpers associated to kernel helpers
|
||||
// See also linux/include/uapi/linux/bpf.h in Linux kernel sources.
|
||||
|
||||
// bpf_ktime_getns()
|
||||
|
||||
/// Index of helper `bpf_ktime_getns()`, equivalent to `bpf_time_getns()`, in Linux kernel, see
|
||||
/// <https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/include/uapi/linux/bpf.h>.
|
||||
pub const BPF_KTIME_GETNS_IDX: u32 = 5;
|
||||
|
||||
/// Get monotonic time (since boot time) in nanoseconds. All arguments are unused.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rbpf::helpers;
|
||||
///
|
||||
/// let t = helpers::bpf_time_getns(0, 0, 0, 0, 0);
|
||||
/// let d = t / 10u64.pow(9) / 60 / 60 / 24;
|
||||
/// let h = (t / 10u64.pow(9) / 60 / 60) % 24;
|
||||
/// let m = (t / 10u64.pow(9) / 60 ) % 60;
|
||||
/// let s = (t / 10u64.pow(9)) % 60;
|
||||
/// let ns = t % 10u64.pow(9);
|
||||
/// println!("Uptime: {:#x} == {} days {}:{}:{}, {} ns", t, d, h, m, s, ns);
|
||||
/// ```
|
||||
#[allow(dead_code)]
|
||||
#[allow(unused_variables)]
|
||||
#[allow(deprecated)]
|
||||
#[cfg(feature = "std")]
|
||||
pub fn bpf_time_getns(unused1: u64, unused2: u64, unused3: u64, unused4: u64, unused5: u64) -> u64 {
|
||||
time::precise_time_ns()
|
||||
}
|
||||
|
||||
// bpf_trace_printk()
|
||||
|
||||
/// Index of helper `bpf_trace_printk()`, equivalent to `bpf_trace_printf()`, in Linux kernel, see
|
||||
/// <https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/include/uapi/linux/bpf.h>.
|
||||
pub const BPF_TRACE_PRINTK_IDX: u32 = 6;
|
||||
|
||||
/// Prints its **last three** arguments to standard output. The **first two** arguments are
|
||||
/// **unused**. Returns the number of bytes written.
|
||||
///
|
||||
/// By ignoring the first two arguments, it creates a helper that will have a behavior similar to
|
||||
/// the one of the equivalent helper `bpf_trace_printk()` from Linux kernel.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rbpf::helpers;
|
||||
///
|
||||
/// let res = helpers::bpf_trace_printf(0, 0, 1, 15, 32);
|
||||
/// assert_eq!(res as usize, "bpf_trace_printf: 0x1, 0xf, 0x20\n".len());
|
||||
/// ```
|
||||
///
|
||||
/// This will print `bpf_trace_printf: 0x1, 0xf, 0x20`.
|
||||
///
|
||||
/// The eBPF code needed to perform the call in this example would be nearly identical to the code
|
||||
/// obtained by compiling the following code from C to eBPF with clang:
|
||||
///
|
||||
/// ```c
|
||||
/// #include <linux/bpf.h>
|
||||
/// #include "path/to/linux/samples/bpf/bpf_helpers.h"
|
||||
///
|
||||
/// int main(struct __sk_buff *skb)
|
||||
/// {
|
||||
/// // Only %d %u %x %ld %lu %lx %lld %llu %llx %p %s conversion specifiers allowed.
|
||||
/// // See <https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/kernel/trace/bpf_trace.c>.
|
||||
/// char *fmt = "bpf_trace_printk %llx, %llx, %llx\n";
|
||||
/// return bpf_trace_printk(fmt, sizeof(fmt), 1, 15, 32);
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// This would equally print the three numbers in `/sys/kernel/debug/tracing` file each time the
|
||||
/// program is run.
|
||||
#[allow(dead_code)]
|
||||
#[allow(unused_variables)]
|
||||
#[cfg(feature = "std")]
|
||||
pub fn bpf_trace_printf(unused1: u64, unused2: u64, arg3: u64, arg4: u64, arg5: u64) -> u64 {
|
||||
println!("bpf_trace_printf: {arg3:#x}, {arg4:#x}, {arg5:#x}");
|
||||
let size_arg = |x| {
|
||||
if x == 0 {
|
||||
1
|
||||
} else {
|
||||
(x as f64).log(16.0).floor() as u64 + 1
|
||||
}
|
||||
};
|
||||
"bpf_trace_printf: 0x, 0x, 0x\n".len() as u64 + size_arg(arg3) + size_arg(arg4) + size_arg(arg5)
|
||||
}
|
||||
|
||||
// Helpers coming from uBPF <https://github.com/iovisor/ubpf/blob/master/vm/test.c>
|
||||
|
||||
/// The idea is to assemble five bytes into a single `u64`. For compatibility with the helpers API,
|
||||
/// each argument must be a `u64`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rbpf::helpers;
|
||||
///
|
||||
/// let gathered = helpers::gather_bytes(0x11, 0x22, 0x33, 0x44, 0x55);
|
||||
/// assert_eq!(gathered, 0x1122334455);
|
||||
/// ```
|
||||
pub fn gather_bytes(arg1: u64, arg2: u64, arg3: u64, arg4: u64, arg5: u64) -> u64 {
|
||||
arg1.wrapping_shl(32)
|
||||
| arg2.wrapping_shl(24)
|
||||
| arg3.wrapping_shl(16)
|
||||
| arg4.wrapping_shl(8)
|
||||
| arg5
|
||||
}
|
||||
|
||||
/// Same as `void *memfrob(void *s, size_t n);` in `string.h` in C. See the GNU manual page (in
|
||||
/// section 3) for `memfrob`. The memory is directly modified, and the helper returns 0 in all
|
||||
/// cases. Arguments 3 to 5 are unused.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rbpf::helpers;
|
||||
///
|
||||
/// let val: u64 = 0x112233;
|
||||
/// let val_ptr = &val as *const u64;
|
||||
///
|
||||
/// helpers::memfrob(val_ptr as u64, 8, 0, 0, 0);
|
||||
/// assert_eq!(val, 0x2a2a2a2a2a3b0819);
|
||||
/// helpers::memfrob(val_ptr as u64, 8, 0, 0, 0);
|
||||
/// assert_eq!(val, 0x112233);
|
||||
/// ```
|
||||
#[allow(unused_variables)]
|
||||
pub fn memfrob(ptr: u64, len: u64, unused3: u64, unused4: u64, unused5: u64) -> u64 {
|
||||
for i in 0..len {
|
||||
unsafe {
|
||||
let mut p = (ptr + i) as *mut u8;
|
||||
*p ^= 0b101010;
|
||||
}
|
||||
}
|
||||
0
|
||||
}
|
||||
|
||||
// TODO: Try again when asm!() is available in stable Rust.
|
||||
// #![feature(asm)]
|
||||
// #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
// #[allow(unused_variables)]
|
||||
// pub fn memfrob (ptr: u64, len: u64, arg3: u64, arg4: u64, arg5: u64) -> u64 {
|
||||
// unsafe {
|
||||
// asm!(
|
||||
// "mov $0xf0, %rax"
|
||||
// ::: "mov $0xf1, %rcx"
|
||||
// ::: "mov $0xf2, %rdx"
|
||||
// ::: "mov $0xf3, %rsi"
|
||||
// ::: "mov $0xf4, %rdi"
|
||||
// ::: "mov $0xf5, %r8"
|
||||
// ::: "mov $0xf6, %r9"
|
||||
// ::: "mov $0xf7, %r10"
|
||||
// ::: "mov $0xf8, %r11"
|
||||
// );
|
||||
// }
|
||||
// 0
|
||||
// }
|
||||
|
||||
/// Compute and return the square root of argument 1, cast as a float. Arguments 2 to 5 are
|
||||
/// unused.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rbpf::helpers;
|
||||
///
|
||||
/// let x = helpers::sqrti(9, 0, 0, 0, 0);
|
||||
/// assert_eq!(x, 3);
|
||||
/// ```
|
||||
#[allow(dead_code)]
|
||||
#[allow(unused_variables)]
|
||||
#[cfg(feature = "std")] // sqrt is only available when using `std`
|
||||
pub fn sqrti(arg1: u64, unused2: u64, unused3: u64, unused4: u64, unused5: u64) -> u64 {
|
||||
(arg1 as f64).sqrt() as u64
|
||||
}
|
||||
|
||||
/// C-like `strcmp`, return 0 if the strings are equal, and a non-null value otherwise.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rbpf::helpers;
|
||||
///
|
||||
/// let foo = "This is a string.\0".as_ptr() as u64;
|
||||
/// let bar = "This is another sting.\0".as_ptr() as u64;
|
||||
///
|
||||
/// assert!(helpers::strcmp(foo, foo, 0, 0, 0) == 0);
|
||||
/// assert!(helpers::strcmp(foo, bar, 0, 0, 0) != 0);
|
||||
/// ```
|
||||
#[allow(dead_code)]
|
||||
#[allow(unused_variables)]
|
||||
pub fn strcmp(arg1: u64, arg2: u64, arg3: u64, unused4: u64, unused5: u64) -> u64 {
|
||||
// C-like strcmp, maybe shorter than converting the bytes to string and comparing?
|
||||
if arg1 == 0 || arg2 == 0 {
|
||||
return u64::MAX;
|
||||
}
|
||||
let mut a = arg1;
|
||||
let mut b = arg2;
|
||||
unsafe {
|
||||
let mut a_val = *(a as *const u8);
|
||||
let mut b_val = *(b as *const u8);
|
||||
while a_val == b_val && a_val != 0 && b_val != 0 {
|
||||
a += 1;
|
||||
b += 1;
|
||||
a_val = *(a as *const u8);
|
||||
b_val = *(b as *const u8);
|
||||
}
|
||||
if a_val >= b_val {
|
||||
(a_val - b_val) as u64
|
||||
} else {
|
||||
(b_val - a_val) as u64
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Some additional helpers
|
||||
|
||||
/// Returns a random u64 value comprised between `min` and `max` values (inclusive). Arguments 3 to
|
||||
/// 5 are unused.
|
||||
///
|
||||
/// Relies on `rand()` function from libc, so `libc::srand()` should be called once before this
|
||||
/// helper is used.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// extern crate libc;
|
||||
/// extern crate rbpf;
|
||||
/// extern crate time;
|
||||
///
|
||||
/// unsafe {
|
||||
/// libc::srand(time::precise_time_ns() as u32)
|
||||
/// }
|
||||
///
|
||||
/// let n = rbpf::helpers::rand(3, 6, 0, 0, 0);
|
||||
/// assert!(3 <= n && n <= 6);
|
||||
/// ```
|
||||
#[allow(dead_code)]
|
||||
#[allow(unused_variables)]
|
||||
#[cfg(feature = "std")]
|
||||
pub fn rand(min: u64, max: u64, unused3: u64, unused4: u64, unused5: u64) -> u64 {
|
||||
let mut n = unsafe { (libc::rand() as u64).wrapping_shl(32) + libc::rand() as u64 };
|
||||
if min < max {
|
||||
n = n % (max + 1 - min) + min;
|
||||
};
|
||||
n
|
||||
}
|
||||
/// Prints the helper functions name and it's index.
|
||||
#[cfg(feature = "std")]
|
||||
pub fn show_helper() {
|
||||
for (index, name) in BPF_FUNC_MAPPER.iter().enumerate() {
|
||||
println!("{}:{}", index, name);
|
||||
}
|
||||
}
|
||||
|
||||
/// See https://github.com/torvalds/linux/blob/master/include/uapi/linux/bpf.h
|
||||
pub const BPF_FUNC_MAPPER: &[&str] = &[
|
||||
"unspec",
|
||||
"map_lookup_elem",
|
||||
"map_update_elem",
|
||||
"map_delete_elem",
|
||||
"probe_read",
|
||||
"ktime_get_ns",
|
||||
"trace_printk",
|
||||
"get_prandom_u32",
|
||||
"get_smp_processor_id",
|
||||
"skb_store_bytes",
|
||||
"l3_csum_replace",
|
||||
"l4_csum_replace",
|
||||
"tail_call",
|
||||
"clone_redirect",
|
||||
"get_current_pid_tgid",
|
||||
"get_current_uid_gid",
|
||||
"get_current_comm",
|
||||
"get_cgroup_classid",
|
||||
"skb_vlan_push",
|
||||
"skb_vlan_pop",
|
||||
"skb_get_tunnel_key",
|
||||
"skb_set_tunnel_key",
|
||||
"perf_event_read",
|
||||
"redirect",
|
||||
"get_route_realm",
|
||||
"perf_event_output",
|
||||
"skb_load_bytes",
|
||||
"get_stackid",
|
||||
"csum_diff",
|
||||
"skb_get_tunnel_opt",
|
||||
"skb_set_tunnel_opt",
|
||||
"skb_change_proto",
|
||||
"skb_change_type",
|
||||
"skb_under_cgroup",
|
||||
"get_hash_recalc",
|
||||
"get_current_task",
|
||||
"probe_write_user",
|
||||
"current_task_under_cgroup",
|
||||
"skb_change_tail",
|
||||
"skb_pull_data",
|
||||
"csum_update",
|
||||
"set_hash_invalid",
|
||||
"get_numa_node_id",
|
||||
"skb_change_head",
|
||||
"xdp_adjust_head",
|
||||
"probe_read_str",
|
||||
"get_socket_cookie",
|
||||
"get_socket_uid",
|
||||
"set_hash",
|
||||
"setsockopt",
|
||||
"skb_adjust_room",
|
||||
"redirect_map",
|
||||
"sk_redirect_map",
|
||||
"sock_map_update",
|
||||
"xdp_adjust_meta",
|
||||
"perf_event_read_value",
|
||||
"perf_prog_read_value",
|
||||
"getsockopt",
|
||||
"override_return",
|
||||
"sock_ops_cb_flags_set",
|
||||
"msg_redirect_map",
|
||||
"msg_apply_bytes",
|
||||
"msg_cork_bytes",
|
||||
"msg_pull_data",
|
||||
"bind",
|
||||
"xdp_adjust_tail",
|
||||
"skb_get_xfrm_state",
|
||||
"get_stack",
|
||||
"skb_load_bytes_relative",
|
||||
"fib_lookup",
|
||||
"sock_hash_update",
|
||||
"msg_redirect_hash",
|
||||
"sk_redirect_hash",
|
||||
"lwt_push_encap",
|
||||
"lwt_seg6_store_bytes",
|
||||
"lwt_seg6_adjust_srh",
|
||||
"lwt_seg6_action",
|
||||
"rc_repeat",
|
||||
"rc_keydown",
|
||||
"skb_cgroup_id",
|
||||
"get_current_cgroup_id",
|
||||
"get_local_storage",
|
||||
"sk_select_reuseport",
|
||||
"skb_ancestor_cgroup_id",
|
||||
"sk_lookup_tcp",
|
||||
"sk_lookup_udp",
|
||||
"sk_release",
|
||||
"map_push_elem",
|
||||
"map_pop_elem",
|
||||
"map_peek_elem",
|
||||
"msg_push_data",
|
||||
"msg_pop_data",
|
||||
"rc_pointer_rel",
|
||||
"spin_lock",
|
||||
"spin_unlock",
|
||||
"sk_fullsock",
|
||||
"tcp_sock",
|
||||
"skb_ecn_set_ce",
|
||||
"get_listener_sock",
|
||||
"skc_lookup_tcp",
|
||||
"tcp_check_syncookie",
|
||||
"sysctl_get_name",
|
||||
"sysctl_get_current_value",
|
||||
"sysctl_get_new_value",
|
||||
"sysctl_set_new_value",
|
||||
"strtol",
|
||||
"strtoul",
|
||||
"sk_storage_get",
|
||||
"sk_storage_delete",
|
||||
"send_signal",
|
||||
"tcp_gen_syncookie",
|
||||
"skb_output",
|
||||
"probe_read_user",
|
||||
"probe_read_kernel",
|
||||
"probe_read_user_str",
|
||||
"probe_read_kernel_str",
|
||||
"tcp_send_ack",
|
||||
"send_signal_thread",
|
||||
"jiffies64",
|
||||
"read_branch_records",
|
||||
"get_ns_current_pid_tgid",
|
||||
"xdp_output",
|
||||
"get_netns_cookie",
|
||||
"get_current_ancestor_cgroup_id",
|
||||
"sk_assign",
|
||||
"ktime_get_boot_ns",
|
||||
"seq_printf",
|
||||
"seq_write",
|
||||
"sk_cgroup_id",
|
||||
"sk_ancestor_cgroup_id",
|
||||
"ringbuf_output",
|
||||
"ringbuf_reserve",
|
||||
"ringbuf_submit",
|
||||
"ringbuf_discard",
|
||||
"ringbuf_query",
|
||||
"csum_level",
|
||||
"skc_to_tcp6_sock",
|
||||
"skc_to_tcp_sock",
|
||||
"skc_to_tcp_timewait_sock",
|
||||
"skc_to_tcp_request_sock",
|
||||
"skc_to_udp6_sock",
|
||||
"get_task_stack",
|
||||
"load_hdr_opt",
|
||||
"store_hdr_opt",
|
||||
"reserve_hdr_opt",
|
||||
"inode_storage_get",
|
||||
"inode_storage_delete",
|
||||
"d_path",
|
||||
"copy_from_user",
|
||||
"snprintf_btf",
|
||||
"seq_printf_btf",
|
||||
"skb_cgroup_classid",
|
||||
"redirect_neigh",
|
||||
"per_cpu_ptr",
|
||||
"this_cpu_ptr",
|
||||
"redirect_peer",
|
||||
"task_storage_get",
|
||||
"task_storage_delete",
|
||||
"get_current_task_btf",
|
||||
"bprm_opts_set",
|
||||
"ktime_get_coarse_ns",
|
||||
"ima_inode_hash",
|
||||
"sock_from_file",
|
||||
"check_mtu",
|
||||
"for_each_map_elem",
|
||||
"snprintf",
|
||||
"sys_bpf",
|
||||
"btf_find_by_name_kind",
|
||||
"sys_close",
|
||||
"timer_init",
|
||||
"timer_set_callback",
|
||||
"timer_start",
|
||||
"timer_cancel",
|
||||
"get_func_ip",
|
||||
"get_attach_cookie",
|
||||
"task_pt_regs",
|
||||
"get_branch_snapshot",
|
||||
"trace_vprintk",
|
||||
"skc_to_unix_sock",
|
||||
"kallsyms_lookup_name",
|
||||
"find_vma",
|
||||
"loop",
|
||||
"strncmp",
|
||||
"get_func_arg",
|
||||
"get_func_ret",
|
||||
"get_func_arg_cnt",
|
||||
"get_retval",
|
||||
"set_retval",
|
||||
"xdp_get_buff_len",
|
||||
"xdp_load_bytes",
|
||||
"xdp_store_bytes",
|
||||
"copy_from_user_task",
|
||||
"skb_set_tstamp",
|
||||
"ima_file_hash",
|
||||
"kptr_xchg",
|
||||
"map_lookup_percpu_elem",
|
||||
"skc_to_mptcp_sock",
|
||||
"dynptr_from_mem",
|
||||
"ringbuf_reserve_dynptr",
|
||||
"ringbuf_submit_dynptr",
|
||||
"ringbuf_discard_dynptr",
|
||||
"dynptr_read",
|
||||
"dynptr_write",
|
||||
"dynptr_data",
|
||||
"tcp_raw_gen_syncookie_ipv4",
|
||||
"tcp_raw_gen_syncookie_ipv6",
|
||||
"tcp_raw_check_syncookie_ipv4",
|
||||
"tcp_raw_check_syncookie_ipv6",
|
||||
"ktime_get_tai_ns",
|
||||
"user_ringbuf_drain",
|
||||
"cgrp_storage_get",
|
||||
"cgrp_storage_delete",
|
||||
];
|
2199
kernel/crates/rbpf/src/insn_builder.rs
Normal file
2199
kernel/crates/rbpf/src/insn_builder.rs
Normal file
File diff suppressed because it is too large
Load Diff
708
kernel/crates/rbpf/src/interpreter.rs
Normal file
708
kernel/crates/rbpf/src/interpreter.rs
Normal file
@ -0,0 +1,708 @@
|
||||
// SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
// Derived from uBPF <https://github.com/iovisor/ubpf>
|
||||
// Copyright 2015 Big Switch Networks, Inc
|
||||
// (uBPF: VM architecture, parts of the interpreter, originally in C)
|
||||
// Copyright 2016 6WIND S.A. <quentin.monnet@6wind.com>
|
||||
// (Translation to Rust, MetaBuff/multiple classes addition, hashmaps for helpers)
|
||||
|
||||
use crate::{
|
||||
ebpf::{self, Insn},
|
||||
helpers::BPF_FUNC_MAPPER,
|
||||
stack::StackFrame,
|
||||
*,
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "user"))]
|
||||
#[allow(unused)]
|
||||
fn check_mem(
|
||||
addr: u64,
|
||||
len: usize,
|
||||
access_type: &str,
|
||||
insn_ptr: usize,
|
||||
mbuff: &[u8],
|
||||
mem: &[u8],
|
||||
stack: &[u8],
|
||||
) -> Result<(), Error> {
|
||||
log::trace!(
|
||||
"check_mem: addr {:#x}, len {}, access_type {}, insn_ptr {}",
|
||||
addr,
|
||||
len,
|
||||
access_type,
|
||||
insn_ptr
|
||||
);
|
||||
log::trace!(
|
||||
"check_mem: mbuff: {:#x}/{:#x}, mem: {:#x}/{:#x}, stack: {:#x}/{:#x}",
|
||||
mbuff.as_ptr() as u64,
|
||||
mbuff.len(),
|
||||
mem.as_ptr() as u64,
|
||||
mem.len(),
|
||||
stack.as_ptr() as u64,
|
||||
stack.len()
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "user")]
|
||||
fn check_mem(
|
||||
addr: u64,
|
||||
len: usize,
|
||||
access_type: &str,
|
||||
insn_ptr: usize,
|
||||
mbuff: &[u8],
|
||||
mem: &[u8],
|
||||
stack: &[u8],
|
||||
) -> 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 {
|
||||
return Ok(());
|
||||
}
|
||||
if mem.as_ptr() as u64 <= addr && addr_end <= mem.as_ptr() as u64 + mem.len() as u64 {
|
||||
return Ok(());
|
||||
}
|
||||
if stack.as_ptr() as u64 <= addr && addr_end <= stack.as_ptr() as u64 + stack.len() as u64 {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
Err(Error::new(ErrorKind::Other, format!(
|
||||
"Error: out of bounds memory {} (insn #{:?}), addr {:#x}, size {:?}\nmbuff: {:#x}/{:#x}, mem: {:#x}/{:#x}, stack: {:#x}/{:#x}",
|
||||
access_type, insn_ptr, addr, len,
|
||||
mbuff.as_ptr() as u64, mbuff.len(),
|
||||
mem.as_ptr() as u64, mem.len(),
|
||||
stack.as_ptr() as u64, stack.len()
|
||||
)))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn do_jump(insn_ptr: &mut usize, insn: &Insn) {
|
||||
*insn_ptr = (*insn_ptr as i16 + insn.off) as usize;
|
||||
}
|
||||
|
||||
#[allow(unknown_lints)]
|
||||
#[allow(cyclomatic_complexity)]
|
||||
pub fn execute_program(
|
||||
prog_: Option<&[u8]>,
|
||||
mem: &[u8],
|
||||
mbuff: &[u8],
|
||||
helpers: &HashMap<u32, ebpf::Helper>,
|
||||
) -> Result<u64, Error> {
|
||||
const U32MAX: u64 = u32::MAX as u64;
|
||||
const SHIFT_MASK_64: u64 = 0x3f;
|
||||
|
||||
let prog = match prog_ {
|
||||
Some(prog) => prog,
|
||||
None => Err(Error::new(
|
||||
ErrorKind::Other,
|
||||
"Error: No program set, call prog_set() to load one",
|
||||
))?,
|
||||
};
|
||||
let mut stacks = Vec::new();
|
||||
let stack = StackFrame::new();
|
||||
// R1 points to beginning of memory area, R10 to stack
|
||||
let mut reg: [u64; 11] = [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
stack.as_ptr() as u64 + stack.len() as u64,
|
||||
];
|
||||
stacks.push(stack);
|
||||
if !mbuff.is_empty() {
|
||||
reg[1] = mbuff.as_ptr() as u64;
|
||||
} else if !mem.is_empty() {
|
||||
reg[1] = mem.as_ptr() as u64;
|
||||
}
|
||||
let check_mem_load =
|
||||
|stack: &[u8], addr: u64, len: usize, insn_ptr: usize| -> Result<(), Error> {
|
||||
check_mem(addr, len, "load", insn_ptr, mbuff, mem, stack)
|
||||
};
|
||||
let check_mem_store =
|
||||
|stack: &[u8], addr: u64, len: usize, insn_ptr: usize| -> Result<(), Error> {
|
||||
check_mem(addr, len, "store", insn_ptr, mbuff, mem, stack)
|
||||
};
|
||||
|
||||
// Loop on instructions
|
||||
let mut insn_ptr: usize = 0;
|
||||
while insn_ptr * ebpf::INSN_SIZE < prog.len() {
|
||||
let insn = ebpf::get_insn(prog, insn_ptr);
|
||||
insn_ptr += 1;
|
||||
let _dst = insn.dst as usize;
|
||||
let _src = insn.src as usize;
|
||||
|
||||
match insn.opc {
|
||||
// BPF_LD class
|
||||
// LD_ABS_* and LD_IND_* are supposed to load pointer to data from metadata buffer.
|
||||
// Since this pointer is constant, and since we already know it (mem), do not
|
||||
// bother re-fetching it, just use mem already.
|
||||
ebpf::LD_ABS_B => {
|
||||
reg[0] = unsafe {
|
||||
let x = (mem.as_ptr() as u64 + (insn.imm as u32) as u64) as *const u8;
|
||||
check_mem_load(stacks.last().unwrap().as_slice(), x as u64, 8, insn_ptr)?;
|
||||
x.read_unaligned() as u64
|
||||
}
|
||||
}
|
||||
ebpf::LD_ABS_H => {
|
||||
reg[0] = unsafe {
|
||||
let x = (mem.as_ptr() as u64 + (insn.imm as u32) as u64) as *const u16;
|
||||
check_mem_load(stacks.last().unwrap().as_slice(), x as u64, 8, insn_ptr)?;
|
||||
x.read_unaligned() as u64
|
||||
}
|
||||
}
|
||||
ebpf::LD_ABS_W => {
|
||||
reg[0] = unsafe {
|
||||
let x = (mem.as_ptr() as u64 + (insn.imm as u32) as u64) as *const u32;
|
||||
check_mem_load(stacks.last().unwrap().as_slice(), x as u64, 8, insn_ptr)?;
|
||||
x.read_unaligned() as u64
|
||||
}
|
||||
}
|
||||
ebpf::LD_ABS_DW => {
|
||||
log::info!("executing LD_ABS_DW, set reg[{}] to {:#x}", _dst, insn.imm);
|
||||
reg[0] = unsafe {
|
||||
let x = (mem.as_ptr() as u64 + (insn.imm as u32) as u64) as *const u64;
|
||||
check_mem_load(stacks.last().unwrap().as_slice(), x as u64, 8, insn_ptr)?;
|
||||
x.read_unaligned()
|
||||
}
|
||||
}
|
||||
ebpf::LD_IND_B => {
|
||||
reg[0] = unsafe {
|
||||
let x =
|
||||
(mem.as_ptr() as u64 + reg[_src] + (insn.imm as u32) as u64) as *const u8;
|
||||
check_mem_load(stacks.last().unwrap().as_slice(), x as u64, 8, insn_ptr)?;
|
||||
x.read_unaligned() as u64
|
||||
}
|
||||
}
|
||||
ebpf::LD_IND_H => {
|
||||
reg[0] = unsafe {
|
||||
let x =
|
||||
(mem.as_ptr() as u64 + reg[_src] + (insn.imm as u32) as u64) as *const u16;
|
||||
check_mem_load(stacks.last().unwrap().as_slice(), x as u64, 8, insn_ptr)?;
|
||||
x.read_unaligned() as u64
|
||||
}
|
||||
}
|
||||
ebpf::LD_IND_W => {
|
||||
reg[0] = unsafe {
|
||||
let x =
|
||||
(mem.as_ptr() as u64 + reg[_src] + (insn.imm as u32) as u64) as *const u32;
|
||||
check_mem_load(stacks.last().unwrap().as_slice(), x as u64, 8, insn_ptr)?;
|
||||
x.read_unaligned() as u64
|
||||
}
|
||||
}
|
||||
ebpf::LD_IND_DW => {
|
||||
reg[0] = unsafe {
|
||||
let x =
|
||||
(mem.as_ptr() as u64 + reg[_src] + (insn.imm as u32) as u64) as *const u64;
|
||||
check_mem_load(stacks.last().unwrap().as_slice(), x as u64, 8, insn_ptr)?;
|
||||
x.read_unaligned()
|
||||
}
|
||||
}
|
||||
|
||||
ebpf::LD_DW_IMM => {
|
||||
let next_insn = ebpf::get_insn(prog, insn_ptr);
|
||||
insn_ptr += 1;
|
||||
// log::warn!(
|
||||
// "executing LD_DW_IMM, set reg[{}] to {:#x}",
|
||||
// _dst,
|
||||
// ((insn.imm as u32) as u64) + ((next_insn.imm as u64) << 32)
|
||||
// );
|
||||
reg[_dst] = ((insn.imm as u32) as u64) + ((next_insn.imm as u64) << 32);
|
||||
}
|
||||
|
||||
// BPF_LDX class
|
||||
ebpf::LD_B_REG => {
|
||||
reg[_dst] = unsafe {
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
let x = (reg[_src] as *const u8).offset(insn.off as isize);
|
||||
check_mem_load(stacks.last().unwrap().as_slice(), x as u64, 1, insn_ptr)?;
|
||||
x.read_unaligned() as u64
|
||||
}
|
||||
}
|
||||
ebpf::LD_H_REG => {
|
||||
reg[_dst] = unsafe {
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
let x = (reg[_src] as *const u8).offset(insn.off as isize) as *const u16;
|
||||
check_mem_load(stacks.last().unwrap().as_slice(), x as u64, 2, insn_ptr)?;
|
||||
x.read_unaligned() as u64
|
||||
}
|
||||
}
|
||||
ebpf::LD_W_REG => {
|
||||
reg[_dst] = unsafe {
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
let x = (reg[_src] as *const u8).offset(insn.off as isize) as *const u32;
|
||||
check_mem_load(stacks.last().unwrap().as_slice(), x as u64, 4, insn_ptr)?;
|
||||
// log::warn!(
|
||||
// "executing LD_W_REG, the ptr is REG:{} -> [{:#x}] + {:#x}",
|
||||
// _src,
|
||||
// reg[_src],
|
||||
// insn.off
|
||||
// );
|
||||
x.read_unaligned() as u64
|
||||
}
|
||||
}
|
||||
ebpf::LD_DW_REG => {
|
||||
reg[_dst] = unsafe {
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
let x = (reg[_src] as *const u8).offset(insn.off as isize) as *const u64;
|
||||
check_mem_load(stacks.last().unwrap().as_slice(), x as u64, 8, insn_ptr)?;
|
||||
x.read_unaligned()
|
||||
}
|
||||
}
|
||||
|
||||
// BPF_ST class
|
||||
ebpf::ST_B_IMM => unsafe {
|
||||
let x = (reg[_dst] as *const u8).offset(insn.off as isize) as *mut u8;
|
||||
check_mem_store(stacks.last().unwrap().as_slice(), x as u64, 1, insn_ptr)?;
|
||||
x.write_unaligned(insn.imm as u8);
|
||||
},
|
||||
ebpf::ST_H_IMM => unsafe {
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
let x = (reg[_dst] as *const u8).offset(insn.off as isize) as *mut u16;
|
||||
check_mem_store(stacks.last().unwrap().as_slice(), x as u64, 2, insn_ptr)?;
|
||||
x.write_unaligned(insn.imm as u16);
|
||||
},
|
||||
ebpf::ST_W_IMM => unsafe {
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
let x = (reg[_dst] as *const u8).offset(insn.off as isize) as *mut u32;
|
||||
check_mem_store(stacks.last().unwrap().as_slice(), x as u64, 4, insn_ptr)?;
|
||||
x.write_unaligned(insn.imm as u32);
|
||||
},
|
||||
ebpf::ST_DW_IMM => unsafe {
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
let x = (reg[_dst] as *const u8).offset(insn.off as isize) as *mut u64;
|
||||
check_mem_store(stacks.last().unwrap().as_slice(), x as u64, 8, insn_ptr)?;
|
||||
x.write_unaligned(insn.imm as u64);
|
||||
},
|
||||
|
||||
// BPF_STX class
|
||||
ebpf::ST_B_REG => unsafe {
|
||||
let x = (reg[_dst] as *const u8).offset(insn.off as isize) as *mut u8;
|
||||
check_mem_store(stacks.last().unwrap().as_slice(), x as u64, 1, insn_ptr)?;
|
||||
x.write_unaligned(reg[_src] as u8);
|
||||
},
|
||||
ebpf::ST_H_REG => unsafe {
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
let x = (reg[_dst] as *const u8).offset(insn.off as isize) as *mut u16;
|
||||
check_mem_store(stacks.last().unwrap().as_slice(), x as u64, 2, insn_ptr)?;
|
||||
x.write_unaligned(reg[_src] as u16);
|
||||
},
|
||||
ebpf::ST_W_REG => unsafe {
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
let x = (reg[_dst] as *const u8).offset(insn.off as isize) as *mut u32;
|
||||
check_mem_store(stacks.last().unwrap().as_slice(), x as u64, 4, insn_ptr)?;
|
||||
x.write_unaligned(reg[_src] as u32);
|
||||
},
|
||||
ebpf::ST_DW_REG => unsafe {
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
let x = (reg[_dst] as *const u8).offset(insn.off as isize) as *mut u64;
|
||||
check_mem_store(stacks.last().unwrap().as_slice(), x as u64, 8, insn_ptr)?;
|
||||
x.write_unaligned(reg[_src]);
|
||||
},
|
||||
ebpf::ST_W_XADD => unimplemented!(),
|
||||
ebpf::ST_DW_XADD => unimplemented!(),
|
||||
|
||||
// BPF_ALU class
|
||||
// TODO Check how overflow works in kernel. Should we &= U32MAX all src register value
|
||||
// before we do the operation?
|
||||
// Cf ((0x11 << 32) - (0x1 << 32)) as u32 VS ((0x11 << 32) as u32 - (0x1 << 32) as u32
|
||||
ebpf::ADD32_IMM => reg[_dst] = (reg[_dst] as i32).wrapping_add(insn.imm) as u64, //((reg[_dst] & U32MAX) + insn.imm as u64) & U32MAX,
|
||||
ebpf::ADD32_REG => reg[_dst] = (reg[_dst] as i32).wrapping_add(reg[_src] as i32) as u64, //((reg[_dst] & U32MAX) + (reg[_src] & U32MAX)) & U32MAX,
|
||||
ebpf::SUB32_IMM => reg[_dst] = (reg[_dst] as i32).wrapping_sub(insn.imm) as u64,
|
||||
ebpf::SUB32_REG => reg[_dst] = (reg[_dst] as i32).wrapping_sub(reg[_src] as i32) as u64,
|
||||
ebpf::MUL32_IMM => reg[_dst] = (reg[_dst] as i32).wrapping_mul(insn.imm) as u64,
|
||||
ebpf::MUL32_REG => reg[_dst] = (reg[_dst] as i32).wrapping_mul(reg[_src] as i32) as u64,
|
||||
ebpf::DIV32_IMM if insn.imm as u32 == 0 => reg[_dst] = 0,
|
||||
ebpf::DIV32_IMM => reg[_dst] = (reg[_dst] as u32 / insn.imm as u32) as u64,
|
||||
ebpf::DIV32_REG if reg[_src] as u32 == 0 => reg[_dst] = 0,
|
||||
ebpf::DIV32_REG => reg[_dst] = (reg[_dst] as u32 / reg[_src] as u32) as u64,
|
||||
ebpf::OR32_IMM => reg[_dst] = (reg[_dst] as u32 | insn.imm as u32) as u64,
|
||||
ebpf::OR32_REG => reg[_dst] = (reg[_dst] as u32 | reg[_src] as u32) as u64,
|
||||
ebpf::AND32_IMM => reg[_dst] = (reg[_dst] as u32 & insn.imm as u32) as u64,
|
||||
ebpf::AND32_REG => reg[_dst] = (reg[_dst] as u32 & reg[_src] as u32) as u64,
|
||||
// As for the 64-bit version, we should mask the number of bits to shift with
|
||||
// 0x1f, but .wrappping_shr() already takes care of it for us.
|
||||
ebpf::LSH32_IMM => reg[_dst] = (reg[_dst] as u32).wrapping_shl(insn.imm as u32) as u64,
|
||||
ebpf::LSH32_REG => reg[_dst] = (reg[_dst] as u32).wrapping_shl(reg[_src] as u32) as u64,
|
||||
ebpf::RSH32_IMM => reg[_dst] = (reg[_dst] as u32).wrapping_shr(insn.imm as u32) as u64,
|
||||
ebpf::RSH32_REG => reg[_dst] = (reg[_dst] as u32).wrapping_shr(reg[_src] as u32) as u64,
|
||||
ebpf::NEG32 => {
|
||||
reg[_dst] = (reg[_dst] as i32).wrapping_neg() as u64;
|
||||
reg[_dst] &= U32MAX;
|
||||
}
|
||||
ebpf::MOD32_IMM if insn.imm as u32 == 0 => (),
|
||||
ebpf::MOD32_IMM => reg[_dst] = (reg[_dst] as u32 % insn.imm as u32) as u64,
|
||||
ebpf::MOD32_REG if reg[_src] as u32 == 0 => (),
|
||||
ebpf::MOD32_REG => reg[_dst] = (reg[_dst] as u32 % reg[_src] as u32) as u64,
|
||||
ebpf::XOR32_IMM => reg[_dst] = (reg[_dst] as u32 ^ insn.imm as u32) as u64,
|
||||
ebpf::XOR32_REG => reg[_dst] = (reg[_dst] as u32 ^ reg[_src] as u32) as u64,
|
||||
ebpf::MOV32_IMM => reg[_dst] = insn.imm as u32 as u64,
|
||||
ebpf::MOV32_REG => reg[_dst] = (reg[_src] as u32) as u64,
|
||||
// As for the 64-bit version, we should mask the number of bits to shift with
|
||||
// 0x1f, but .wrappping_shr() already takes care of it for us.
|
||||
ebpf::ARSH32_IMM => {
|
||||
reg[_dst] = (reg[_dst] as i32).wrapping_shr(insn.imm as u32) as u64;
|
||||
reg[_dst] &= U32MAX;
|
||||
}
|
||||
ebpf::ARSH32_REG => {
|
||||
reg[_dst] = (reg[_dst] as i32).wrapping_shr(reg[_src] as u32) as u64;
|
||||
reg[_dst] &= U32MAX;
|
||||
}
|
||||
ebpf::LE => {
|
||||
reg[_dst] = match insn.imm {
|
||||
16 => (reg[_dst] as u16).to_le() as u64,
|
||||
32 => (reg[_dst] as u32).to_le() as u64,
|
||||
64 => reg[_dst].to_le(),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
}
|
||||
ebpf::BE => {
|
||||
reg[_dst] = match insn.imm {
|
||||
16 => (reg[_dst] as u16).to_be() as u64,
|
||||
32 => (reg[_dst] as u32).to_be() as u64,
|
||||
64 => reg[_dst].to_be(),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
}
|
||||
|
||||
// BPF_ALU64 class
|
||||
ebpf::ADD64_IMM => reg[_dst] = reg[_dst].wrapping_add(insn.imm as u64),
|
||||
ebpf::ADD64_REG => reg[_dst] = reg[_dst].wrapping_add(reg[_src]),
|
||||
ebpf::SUB64_IMM => reg[_dst] = reg[_dst].wrapping_sub(insn.imm as u64),
|
||||
ebpf::SUB64_REG => reg[_dst] = reg[_dst].wrapping_sub(reg[_src]),
|
||||
ebpf::MUL64_IMM => reg[_dst] = reg[_dst].wrapping_mul(insn.imm as u64),
|
||||
ebpf::MUL64_REG => reg[_dst] = reg[_dst].wrapping_mul(reg[_src]),
|
||||
ebpf::DIV64_IMM if insn.imm == 0 => reg[_dst] = 0,
|
||||
ebpf::DIV64_IMM => reg[_dst] /= insn.imm as u64,
|
||||
ebpf::DIV64_REG if reg[_src] == 0 => reg[_dst] = 0,
|
||||
ebpf::DIV64_REG => reg[_dst] /= reg[_src],
|
||||
ebpf::OR64_IMM => reg[_dst] |= insn.imm as u64,
|
||||
ebpf::OR64_REG => reg[_dst] |= reg[_src],
|
||||
ebpf::AND64_IMM => reg[_dst] &= insn.imm as u64,
|
||||
ebpf::AND64_REG => reg[_dst] &= reg[_src],
|
||||
ebpf::LSH64_IMM => reg[_dst] <<= insn.imm as u64 & SHIFT_MASK_64,
|
||||
ebpf::LSH64_REG => reg[_dst] <<= reg[_src] & SHIFT_MASK_64,
|
||||
ebpf::RSH64_IMM => reg[_dst] >>= insn.imm as u64 & SHIFT_MASK_64,
|
||||
ebpf::RSH64_REG => reg[_dst] >>= reg[_src] & SHIFT_MASK_64,
|
||||
ebpf::NEG64 => reg[_dst] = -(reg[_dst] as i64) as u64,
|
||||
ebpf::MOD64_IMM if insn.imm == 0 => (),
|
||||
ebpf::MOD64_IMM => reg[_dst] %= insn.imm as u64,
|
||||
ebpf::MOD64_REG if reg[_src] == 0 => (),
|
||||
ebpf::MOD64_REG => reg[_dst] %= reg[_src],
|
||||
ebpf::XOR64_IMM => reg[_dst] ^= insn.imm as u64,
|
||||
ebpf::XOR64_REG => reg[_dst] ^= reg[_src],
|
||||
ebpf::MOV64_IMM => reg[_dst] = insn.imm as u64,
|
||||
ebpf::MOV64_REG => reg[_dst] = reg[_src],
|
||||
ebpf::ARSH64_IMM => {
|
||||
reg[_dst] = (reg[_dst] as i64 >> (insn.imm as u64 & SHIFT_MASK_64)) as u64
|
||||
}
|
||||
ebpf::ARSH64_REG => {
|
||||
reg[_dst] = (reg[_dst] as i64 >> (reg[_src] as u64 & SHIFT_MASK_64)) as u64
|
||||
}
|
||||
|
||||
// BPF_JMP class
|
||||
// TODO: check this actually works as expected for signed / unsigned ops
|
||||
ebpf::JA => do_jump(&mut insn_ptr, &insn),
|
||||
ebpf::JEQ_IMM => {
|
||||
if reg[_dst] == insn.imm as u64 {
|
||||
do_jump(&mut insn_ptr, &insn);
|
||||
}
|
||||
}
|
||||
ebpf::JEQ_REG => {
|
||||
if reg[_dst] == reg[_src] {
|
||||
do_jump(&mut insn_ptr, &insn);
|
||||
}
|
||||
}
|
||||
ebpf::JGT_IMM => {
|
||||
if reg[_dst] > insn.imm as u64 {
|
||||
do_jump(&mut insn_ptr, &insn);
|
||||
}
|
||||
}
|
||||
ebpf::JGT_REG => {
|
||||
if reg[_dst] > reg[_src] {
|
||||
do_jump(&mut insn_ptr, &insn);
|
||||
}
|
||||
}
|
||||
ebpf::JGE_IMM => {
|
||||
if reg[_dst] >= insn.imm as u64 {
|
||||
do_jump(&mut insn_ptr, &insn);
|
||||
}
|
||||
}
|
||||
ebpf::JGE_REG => {
|
||||
if reg[_dst] >= reg[_src] {
|
||||
do_jump(&mut insn_ptr, &insn);
|
||||
}
|
||||
}
|
||||
ebpf::JLT_IMM => {
|
||||
if reg[_dst] < insn.imm as u64 {
|
||||
do_jump(&mut insn_ptr, &insn);
|
||||
}
|
||||
}
|
||||
ebpf::JLT_REG => {
|
||||
if reg[_dst] < reg[_src] {
|
||||
do_jump(&mut insn_ptr, &insn);
|
||||
}
|
||||
}
|
||||
ebpf::JLE_IMM => {
|
||||
if reg[_dst] <= insn.imm as u64 {
|
||||
do_jump(&mut insn_ptr, &insn);
|
||||
}
|
||||
}
|
||||
ebpf::JLE_REG => {
|
||||
if reg[_dst] <= reg[_src] {
|
||||
do_jump(&mut insn_ptr, &insn);
|
||||
}
|
||||
}
|
||||
ebpf::JSET_IMM => {
|
||||
if reg[_dst] & insn.imm as u64 != 0 {
|
||||
do_jump(&mut insn_ptr, &insn);
|
||||
}
|
||||
}
|
||||
ebpf::JSET_REG => {
|
||||
if reg[_dst] & reg[_src] != 0 {
|
||||
do_jump(&mut insn_ptr, &insn);
|
||||
}
|
||||
}
|
||||
ebpf::JNE_IMM => {
|
||||
if reg[_dst] != insn.imm as u64 {
|
||||
do_jump(&mut insn_ptr, &insn);
|
||||
}
|
||||
}
|
||||
ebpf::JNE_REG => {
|
||||
if reg[_dst] != reg[_src] {
|
||||
do_jump(&mut insn_ptr, &insn);
|
||||
}
|
||||
}
|
||||
ebpf::JSGT_IMM => {
|
||||
if reg[_dst] as i64 > insn.imm as i64 {
|
||||
do_jump(&mut insn_ptr, &insn);
|
||||
}
|
||||
}
|
||||
ebpf::JSGT_REG => {
|
||||
if reg[_dst] as i64 > reg[_src] as i64 {
|
||||
do_jump(&mut insn_ptr, &insn);
|
||||
}
|
||||
}
|
||||
ebpf::JSGE_IMM => {
|
||||
if reg[_dst] as i64 >= insn.imm as i64 {
|
||||
do_jump(&mut insn_ptr, &insn);
|
||||
}
|
||||
}
|
||||
ebpf::JSGE_REG => {
|
||||
if reg[_dst] as i64 >= reg[_src] as i64 {
|
||||
do_jump(&mut insn_ptr, &insn);
|
||||
}
|
||||
}
|
||||
ebpf::JSLT_IMM => {
|
||||
if (reg[_dst] as i64) < insn.imm as i64 {
|
||||
do_jump(&mut insn_ptr, &insn);
|
||||
}
|
||||
}
|
||||
ebpf::JSLT_REG => {
|
||||
if (reg[_dst] as i64) < reg[_src] as i64 {
|
||||
do_jump(&mut insn_ptr, &insn);
|
||||
}
|
||||
}
|
||||
ebpf::JSLE_IMM => {
|
||||
if reg[_dst] as i64 <= insn.imm as i64 {
|
||||
do_jump(&mut insn_ptr, &insn);
|
||||
}
|
||||
}
|
||||
ebpf::JSLE_REG => {
|
||||
if reg[_dst] as i64 <= reg[_src] as i64 {
|
||||
do_jump(&mut insn_ptr, &insn);
|
||||
}
|
||||
}
|
||||
|
||||
// BPF_JMP32 class
|
||||
ebpf::JEQ_IMM32 => {
|
||||
if reg[_dst] as u32 == insn.imm as u32 {
|
||||
do_jump(&mut insn_ptr, &insn);
|
||||
}
|
||||
}
|
||||
ebpf::JEQ_REG32 => {
|
||||
if reg[_dst] as u32 == reg[_src] as u32 {
|
||||
do_jump(&mut insn_ptr, &insn);
|
||||
}
|
||||
}
|
||||
ebpf::JGT_IMM32 => {
|
||||
if reg[_dst] as u32 > insn.imm as u32 {
|
||||
do_jump(&mut insn_ptr, &insn);
|
||||
}
|
||||
}
|
||||
ebpf::JGT_REG32 => {
|
||||
if reg[_dst] as u32 > reg[_src] as u32 {
|
||||
do_jump(&mut insn_ptr, &insn);
|
||||
}
|
||||
}
|
||||
ebpf::JGE_IMM32 => {
|
||||
if reg[_dst] as u32 >= insn.imm as u32 {
|
||||
do_jump(&mut insn_ptr, &insn);
|
||||
}
|
||||
}
|
||||
ebpf::JGE_REG32 => {
|
||||
if reg[_dst] as u32 >= reg[_src] as u32 {
|
||||
do_jump(&mut insn_ptr, &insn);
|
||||
}
|
||||
}
|
||||
ebpf::JLT_IMM32 => {
|
||||
if (reg[_dst] as u32) < insn.imm as u32 {
|
||||
do_jump(&mut insn_ptr, &insn);
|
||||
}
|
||||
}
|
||||
ebpf::JLT_REG32 => {
|
||||
if (reg[_dst] as u32) < reg[_src] as u32 {
|
||||
do_jump(&mut insn_ptr, &insn);
|
||||
}
|
||||
}
|
||||
ebpf::JLE_IMM32 => {
|
||||
if reg[_dst] as u32 <= insn.imm as u32 {
|
||||
do_jump(&mut insn_ptr, &insn);
|
||||
}
|
||||
}
|
||||
ebpf::JLE_REG32 => {
|
||||
if reg[_dst] as u32 <= reg[_src] as u32 {
|
||||
do_jump(&mut insn_ptr, &insn);
|
||||
}
|
||||
}
|
||||
ebpf::JSET_IMM32 => {
|
||||
if reg[_dst] as u32 & insn.imm as u32 != 0 {
|
||||
do_jump(&mut insn_ptr, &insn);
|
||||
}
|
||||
}
|
||||
ebpf::JSET_REG32 => {
|
||||
if reg[_dst] as u32 & reg[_src] as u32 != 0 {
|
||||
do_jump(&mut insn_ptr, &insn);
|
||||
}
|
||||
}
|
||||
ebpf::JNE_IMM32 => {
|
||||
if reg[_dst] as u32 != insn.imm as u32 {
|
||||
do_jump(&mut insn_ptr, &insn);
|
||||
}
|
||||
}
|
||||
ebpf::JNE_REG32 => {
|
||||
if reg[_dst] as u32 != reg[_src] as u32 {
|
||||
do_jump(&mut insn_ptr, &insn);
|
||||
}
|
||||
}
|
||||
ebpf::JSGT_IMM32 => {
|
||||
if reg[_dst] as i32 > insn.imm {
|
||||
do_jump(&mut insn_ptr, &insn);
|
||||
}
|
||||
}
|
||||
ebpf::JSGT_REG32 => {
|
||||
if reg[_dst] as i32 > reg[_src] as i32 {
|
||||
do_jump(&mut insn_ptr, &insn);
|
||||
}
|
||||
}
|
||||
ebpf::JSGE_IMM32 => {
|
||||
if reg[_dst] as i32 >= insn.imm {
|
||||
do_jump(&mut insn_ptr, &insn);
|
||||
}
|
||||
}
|
||||
ebpf::JSGE_REG32 => {
|
||||
if reg[_dst] as i32 >= reg[_src] as i32 {
|
||||
do_jump(&mut insn_ptr, &insn);
|
||||
}
|
||||
}
|
||||
ebpf::JSLT_IMM32 => {
|
||||
if (reg[_dst] as i32) < insn.imm {
|
||||
do_jump(&mut insn_ptr, &insn);
|
||||
}
|
||||
}
|
||||
ebpf::JSLT_REG32 => {
|
||||
if (reg[_dst] as i32) < reg[_src] as i32 {
|
||||
do_jump(&mut insn_ptr, &insn);
|
||||
}
|
||||
}
|
||||
ebpf::JSLE_IMM32 => {
|
||||
if reg[_dst] as i32 <= insn.imm {
|
||||
do_jump(&mut insn_ptr, &insn);
|
||||
}
|
||||
}
|
||||
ebpf::JSLE_REG32 => {
|
||||
if reg[_dst] as i32 <= reg[_src] as i32 {
|
||||
do_jump(&mut insn_ptr, &insn);
|
||||
}
|
||||
}
|
||||
|
||||
// Do not delegate the check to the verifier, since registered functions can be
|
||||
// changed after the program has been verified.
|
||||
ebpf::CALL => {
|
||||
// See https://www.kernel.org/doc/html/latest/bpf/standardization/instruction-set.html#id16
|
||||
let src_reg = _src;
|
||||
let call_func_res = match src_reg {
|
||||
0 => {
|
||||
// Handle call by address to external function.
|
||||
if let Some(function) = helpers.get(&(insn.imm as u32)) {
|
||||
reg[0] = function(reg[1], reg[2], reg[3], reg[4], reg[5]);
|
||||
Ok(())
|
||||
}else {
|
||||
Err(format!(
|
||||
"Error: unknown helper function (id: {:#x}) [{}], (instruction #{})",
|
||||
insn.imm as u32,BPF_FUNC_MAPPER[insn.imm as usize],insn_ptr
|
||||
))
|
||||
}
|
||||
}
|
||||
1 => {
|
||||
// bpf to bpf call
|
||||
// The function is in the same program, so we can just jump to the address
|
||||
if stacks.len() >= ebpf::RBPF_MAX_CALL_DEPTH{
|
||||
Err(format!(
|
||||
"Error: bpf to bpf call stack limit reached (instruction #{}) max depth: {}",
|
||||
insn_ptr, ebpf::RBPF_MAX_CALL_DEPTH
|
||||
))
|
||||
}else {
|
||||
let mut pre_stack = stacks.last_mut().unwrap();
|
||||
// Save the callee saved registers
|
||||
pre_stack.save_registers(®[6..=9]);
|
||||
// Save the return address
|
||||
pre_stack.save_return_address(insn_ptr as u16);
|
||||
// save the stack pointer
|
||||
pre_stack.save_sp(reg[10] as u16);
|
||||
let mut stack = StackFrame::new();
|
||||
log::trace!("BPF TO BPF CALL: new pc: {} + {} = {}",insn_ptr ,insn.imm,insn_ptr + insn.imm as usize);
|
||||
reg[10] = stack.as_ptr() as u64 + stack.len() as u64;
|
||||
stacks.push(stack);
|
||||
insn_ptr += insn.imm as usize;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
_ =>{
|
||||
Err(format!(
|
||||
"Error: the function call type (id: {:#x}) [{}], (instruction #{}) not supported",
|
||||
insn.imm as u32,BPF_FUNC_MAPPER[insn.imm as usize],insn_ptr
|
||||
))
|
||||
}
|
||||
};
|
||||
if let Err(e) = call_func_res {
|
||||
Err(Error::new(ErrorKind::Other, e))?;
|
||||
}
|
||||
}
|
||||
ebpf::TAIL_CALL => unimplemented!(),
|
||||
ebpf::EXIT => {
|
||||
if stacks.len() == 1 {
|
||||
return Ok(reg[0]);
|
||||
} else {
|
||||
// Pop the stack
|
||||
stacks.pop();
|
||||
let stack = stacks.last().unwrap();
|
||||
// Restore the callee saved registers
|
||||
reg[6..=9].copy_from_slice(&stack.get_registers());
|
||||
// Restore the return address
|
||||
insn_ptr = stack.get_return_address() as usize;
|
||||
// Restore the stack pointer
|
||||
reg[10] = stack.get_sp() as u64;
|
||||
log::trace!("EXIT: new pc: {}", insn_ptr);
|
||||
}
|
||||
}
|
||||
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
unreachable!()
|
||||
}
|
1054
kernel/crates/rbpf/src/jit.rs
Normal file
1054
kernel/crates/rbpf/src/jit.rs
Normal file
File diff suppressed because it is too large
Load Diff
1782
kernel/crates/rbpf/src/lib.rs
Normal file
1782
kernel/crates/rbpf/src/lib.rs
Normal file
File diff suppressed because it is too large
Load Diff
41
kernel/crates/rbpf/src/no_std_error.rs
Normal file
41
kernel/crates/rbpf/src/no_std_error.rs
Normal file
@ -0,0 +1,41 @@
|
||||
// SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
|
||||
//! This module provides a simple implementation of the Error struct that is
|
||||
//! used as a drop-in replacement for `std::io::Error` when using `rbpf` in `no_std`.
|
||||
|
||||
use alloc::string::String;
|
||||
|
||||
/// Implementation of Error for no_std applications.
|
||||
/// Ensures that the existing code can use it with the same interface
|
||||
/// as the Error from std::io::Error.
|
||||
#[derive(Debug)]
|
||||
pub struct Error {
|
||||
#[allow(dead_code)]
|
||||
kind: ErrorKind,
|
||||
#[allow(dead_code)]
|
||||
error: String,
|
||||
}
|
||||
|
||||
impl Error {
|
||||
/// New function exposing the same signature as `std::io::Error::new`.
|
||||
#[allow(dead_code)]
|
||||
pub fn new<S: Into<String>>(kind: ErrorKind, error: S) -> Error {
|
||||
Error {
|
||||
kind,
|
||||
error: error.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The current version of `rbpf` only uses the [`Other`](ErrorKind::Other) variant
|
||||
/// from the [std::io::ErrorKind] enum. If a dependency on other variants were
|
||||
/// introduced in the future, this enum needs to be updated accordingly to maintain
|
||||
/// compatibility with the real `ErrorKind`. The reason all available variants
|
||||
/// aren't included in the first place is that [std::io::ErrorKind] exposes
|
||||
/// 40 variants, and not all of them are meaningful under `no_std`.
|
||||
#[derive(Debug)]
|
||||
pub enum ErrorKind {
|
||||
/// The no_std code only uses this variant.
|
||||
#[allow(dead_code)]
|
||||
Other,
|
||||
}
|
75
kernel/crates/rbpf/src/stack.rs
Normal file
75
kernel/crates/rbpf/src/stack.rs
Normal file
@ -0,0 +1,75 @@
|
||||
use crate::{ebpf::STACK_SIZE, vec, Vec};
|
||||
|
||||
pub struct StackFrame {
|
||||
return_address: u16,
|
||||
saved_registers: [u64; 4],
|
||||
sp: u16,
|
||||
frame: Vec<u8>,
|
||||
}
|
||||
|
||||
impl StackFrame {
|
||||
/// Create a new stack frame
|
||||
///
|
||||
/// The stack frame is created with a capacity of `STACK_SIZE` == 512 bytes
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
sp: 0,
|
||||
return_address: 0,
|
||||
saved_registers: [0; 4],
|
||||
frame: vec![0; STACK_SIZE],
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new stack frame with a given capacity
|
||||
#[allow(unused)]
|
||||
pub fn with_capacity(capacity: usize) -> Self {
|
||||
Self {
|
||||
sp: 0,
|
||||
return_address: 0,
|
||||
saved_registers: [0; 4],
|
||||
frame: vec![0; capacity],
|
||||
}
|
||||
}
|
||||
|
||||
/// The capacity of the stack frame
|
||||
pub fn len(&self) -> usize {
|
||||
self.frame.len()
|
||||
}
|
||||
|
||||
pub fn as_ptr(&self) -> *const u8 {
|
||||
self.frame.as_ptr()
|
||||
}
|
||||
|
||||
pub fn as_slice(&self) -> &[u8] {
|
||||
self.frame.as_slice()
|
||||
}
|
||||
/// Save the callee-saved registers
|
||||
pub fn save_registers(&mut self, regs: &[u64]) {
|
||||
self.saved_registers.copy_from_slice(regs);
|
||||
}
|
||||
|
||||
/// Get the callee-saved registers
|
||||
pub fn get_registers(&self) -> [u64; 4] {
|
||||
self.saved_registers
|
||||
}
|
||||
|
||||
/// Save the return address
|
||||
pub fn save_return_address(&mut self, address: u16) {
|
||||
self.return_address = address;
|
||||
}
|
||||
|
||||
/// Get the return address
|
||||
pub fn get_return_address(&self) -> u16 {
|
||||
self.return_address
|
||||
}
|
||||
|
||||
/// Save the stack pointer
|
||||
pub fn save_sp(&mut self, sp: u16) {
|
||||
self.sp = sp;
|
||||
}
|
||||
|
||||
/// Get the stack pointer
|
||||
pub fn get_sp(&self) -> u16 {
|
||||
self.sp
|
||||
}
|
||||
}
|
386
kernel/crates/rbpf/src/verifier.rs
Normal file
386
kernel/crates/rbpf/src/verifier.rs
Normal file
@ -0,0 +1,386 @@
|
||||
// SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
// Derived from uBPF <https://github.com/iovisor/ubpf>
|
||||
// Copyright 2015 Big Switch Networks, Inc
|
||||
// (uBPF: safety checks, originally in C)
|
||||
// Copyright 2016 6WIND S.A. <quentin.monnet@6wind.com>
|
||||
// (Translation to Rust)
|
||||
|
||||
// This “verifier” performs simple checks when the eBPF program is loaded into the VM (before it is
|
||||
// interpreted or JIT-compiled). It has nothing to do with the much more elaborated verifier inside
|
||||
// Linux kernel. There is no verification regarding the program flow control (should be a Direct
|
||||
// Acyclic Graph) or the consistency for registers usage (the verifier of the kernel assigns types
|
||||
// to the registers and is much stricter).
|
||||
//
|
||||
// On the other hand, rbpf is not expected to run in kernel space.
|
||||
//
|
||||
// Improving the verifier would be nice, but this is not trivial (and Linux kernel is under GPL
|
||||
// license, so we cannot copy it).
|
||||
//
|
||||
// Contrary to the verifier of the Linux kernel, this one does not modify the bytecode at all.
|
||||
|
||||
use alloc::format;
|
||||
|
||||
use crate::{ebpf, Error, ErrorKind};
|
||||
|
||||
fn reject<S: AsRef<str>>(msg: S) -> Result<(), Error> {
|
||||
let full_msg = format!("[Verifier] Error: {}", msg.as_ref());
|
||||
Err(Error::new(ErrorKind::Other, full_msg))
|
||||
}
|
||||
|
||||
fn check_prog_len(prog: &[u8]) -> Result<(), Error> {
|
||||
if prog.len() % ebpf::INSN_SIZE != 0 {
|
||||
reject(format!(
|
||||
"eBPF program length must be a multiple of {:?} octets",
|
||||
ebpf::INSN_SIZE
|
||||
))?;
|
||||
}
|
||||
if prog.len() > ebpf::PROG_MAX_SIZE {
|
||||
reject(format!(
|
||||
"eBPF program length limited to {:?}, here {:?}",
|
||||
ebpf::PROG_MAX_INSNS,
|
||||
prog.len() / ebpf::INSN_SIZE
|
||||
))?;
|
||||
}
|
||||
|
||||
if prog.is_empty() {
|
||||
reject("no program set, call set_program() to load one")?;
|
||||
}
|
||||
let last_opc = ebpf::get_insn(prog, (prog.len() / ebpf::INSN_SIZE) - 1).opc;
|
||||
if last_opc & ebpf::BPF_CLS_MASK != ebpf::BPF_JMP {
|
||||
reject("program does not end with “EXIT” instruction")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check_imm_endian(insn: &ebpf::Insn, insn_ptr: usize) -> Result<(), Error> {
|
||||
match insn.imm {
|
||||
16 | 32 | 64 => Ok(()),
|
||||
_ => reject(format!(
|
||||
"unsupported argument for LE/BE (insn #{insn_ptr:?})"
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn check_load_dw(prog: &[u8], insn_ptr: usize) -> Result<(), Error> {
|
||||
// We know we can reach next insn since we enforce an EXIT insn at the end of program, while
|
||||
// this function should be called only for LD_DW insn, that cannot be last in program.
|
||||
let next_insn = ebpf::get_insn(prog, insn_ptr + 1);
|
||||
if next_insn.opc != 0 {
|
||||
reject(format!("incomplete LD_DW instruction (insn #{insn_ptr:?})"))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check_jmp_offset(prog: &[u8], insn_ptr: usize) -> Result<(), Error> {
|
||||
let insn = ebpf::get_insn(prog, insn_ptr);
|
||||
if insn.off == -1 {
|
||||
reject(format!("infinite loop (insn #{insn_ptr:?})"))?;
|
||||
}
|
||||
|
||||
let dst_insn_ptr = insn_ptr as isize + 1 + insn.off as isize;
|
||||
if dst_insn_ptr < 0 || dst_insn_ptr as usize >= (prog.len() / ebpf::INSN_SIZE) {
|
||||
reject(format!(
|
||||
"jump out of code to #{dst_insn_ptr:?} (insn #{insn_ptr:?})"
|
||||
))?;
|
||||
}
|
||||
|
||||
let dst_insn = ebpf::get_insn(prog, dst_insn_ptr as usize);
|
||||
if dst_insn.opc == 0 {
|
||||
reject(format!(
|
||||
"jump to middle of LD_DW at #{dst_insn_ptr:?} (insn #{insn_ptr:?})"
|
||||
))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check_registers(insn: &ebpf::Insn, store: bool, insn_ptr: usize) -> Result<(), Error> {
|
||||
if insn.src > 10 {
|
||||
reject(format!("invalid source register (insn #{insn_ptr:?})"))?;
|
||||
}
|
||||
|
||||
match (insn.dst, store) {
|
||||
(0..=9, _) | (10, true) => Ok(()),
|
||||
(10, false) => reject(format!(
|
||||
"cannot write into register r10 (insn #{insn_ptr:?})"
|
||||
)),
|
||||
(_, _) => reject(format!("invalid destination register (insn #{insn_ptr:?})")),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check(prog: &[u8]) -> Result<(), Error> {
|
||||
check_prog_len(prog)?;
|
||||
|
||||
let mut insn_ptr: usize = 0;
|
||||
while insn_ptr * ebpf::INSN_SIZE < prog.len() {
|
||||
let insn = ebpf::get_insn(prog, insn_ptr);
|
||||
let mut store = false;
|
||||
|
||||
match insn.opc {
|
||||
// BPF_LD class
|
||||
ebpf::LD_ABS_B => {}
|
||||
ebpf::LD_ABS_H => {}
|
||||
ebpf::LD_ABS_W => {}
|
||||
ebpf::LD_ABS_DW => {}
|
||||
ebpf::LD_IND_B => {}
|
||||
ebpf::LD_IND_H => {}
|
||||
ebpf::LD_IND_W => {}
|
||||
ebpf::LD_IND_DW => {}
|
||||
|
||||
ebpf::LD_DW_IMM => {
|
||||
store = true;
|
||||
check_load_dw(prog, insn_ptr)?;
|
||||
insn_ptr += 1;
|
||||
}
|
||||
|
||||
// BPF_LDX class
|
||||
ebpf::LD_B_REG => {}
|
||||
ebpf::LD_H_REG => {}
|
||||
ebpf::LD_W_REG => {}
|
||||
ebpf::LD_DW_REG => {}
|
||||
|
||||
// BPF_ST class
|
||||
ebpf::ST_B_IMM => store = true,
|
||||
ebpf::ST_H_IMM => store = true,
|
||||
ebpf::ST_W_IMM => store = true,
|
||||
ebpf::ST_DW_IMM => store = true,
|
||||
|
||||
// BPF_STX class
|
||||
ebpf::ST_B_REG => store = true,
|
||||
ebpf::ST_H_REG => store = true,
|
||||
ebpf::ST_W_REG => store = true,
|
||||
ebpf::ST_DW_REG => store = true,
|
||||
ebpf::ST_W_XADD => {
|
||||
unimplemented!();
|
||||
}
|
||||
ebpf::ST_DW_XADD => {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
// BPF_ALU class
|
||||
ebpf::ADD32_IMM => {}
|
||||
ebpf::ADD32_REG => {}
|
||||
ebpf::SUB32_IMM => {}
|
||||
ebpf::SUB32_REG => {}
|
||||
ebpf::MUL32_IMM => {}
|
||||
ebpf::MUL32_REG => {}
|
||||
ebpf::DIV32_IMM => {}
|
||||
ebpf::DIV32_REG => {}
|
||||
ebpf::OR32_IMM => {}
|
||||
ebpf::OR32_REG => {}
|
||||
ebpf::AND32_IMM => {}
|
||||
ebpf::AND32_REG => {}
|
||||
ebpf::LSH32_IMM => {}
|
||||
ebpf::LSH32_REG => {}
|
||||
ebpf::RSH32_IMM => {}
|
||||
ebpf::RSH32_REG => {}
|
||||
ebpf::NEG32 => {}
|
||||
ebpf::MOD32_IMM => {}
|
||||
ebpf::MOD32_REG => {}
|
||||
ebpf::XOR32_IMM => {}
|
||||
ebpf::XOR32_REG => {}
|
||||
ebpf::MOV32_IMM => {}
|
||||
ebpf::MOV32_REG => {}
|
||||
ebpf::ARSH32_IMM => {}
|
||||
ebpf::ARSH32_REG => {}
|
||||
ebpf::LE => {
|
||||
check_imm_endian(&insn, insn_ptr)?;
|
||||
}
|
||||
ebpf::BE => {
|
||||
check_imm_endian(&insn, insn_ptr)?;
|
||||
}
|
||||
|
||||
// BPF_ALU64 class
|
||||
ebpf::ADD64_IMM => {}
|
||||
ebpf::ADD64_REG => {}
|
||||
ebpf::SUB64_IMM => {}
|
||||
ebpf::SUB64_REG => {}
|
||||
ebpf::MUL64_IMM => {}
|
||||
ebpf::MUL64_REG => {}
|
||||
ebpf::DIV64_IMM => {}
|
||||
ebpf::DIV64_REG => {}
|
||||
ebpf::OR64_IMM => {}
|
||||
ebpf::OR64_REG => {}
|
||||
ebpf::AND64_IMM => {}
|
||||
ebpf::AND64_REG => {}
|
||||
ebpf::LSH64_IMM => {}
|
||||
ebpf::LSH64_REG => {}
|
||||
ebpf::RSH64_IMM => {}
|
||||
ebpf::RSH64_REG => {}
|
||||
ebpf::NEG64 => {}
|
||||
ebpf::MOD64_IMM => {}
|
||||
ebpf::MOD64_REG => {}
|
||||
ebpf::XOR64_IMM => {}
|
||||
ebpf::XOR64_REG => {}
|
||||
ebpf::MOV64_IMM => {}
|
||||
ebpf::MOV64_REG => {}
|
||||
ebpf::ARSH64_IMM => {}
|
||||
ebpf::ARSH64_REG => {}
|
||||
|
||||
// BPF_JMP class
|
||||
ebpf::JA => {
|
||||
check_jmp_offset(prog, insn_ptr)?;
|
||||
}
|
||||
ebpf::JEQ_IMM => {
|
||||
check_jmp_offset(prog, insn_ptr)?;
|
||||
}
|
||||
ebpf::JEQ_REG => {
|
||||
check_jmp_offset(prog, insn_ptr)?;
|
||||
}
|
||||
ebpf::JGT_IMM => {
|
||||
check_jmp_offset(prog, insn_ptr)?;
|
||||
}
|
||||
ebpf::JGT_REG => {
|
||||
check_jmp_offset(prog, insn_ptr)?;
|
||||
}
|
||||
ebpf::JGE_IMM => {
|
||||
check_jmp_offset(prog, insn_ptr)?;
|
||||
}
|
||||
ebpf::JGE_REG => {
|
||||
check_jmp_offset(prog, insn_ptr)?;
|
||||
}
|
||||
ebpf::JLT_IMM => {
|
||||
check_jmp_offset(prog, insn_ptr)?;
|
||||
}
|
||||
ebpf::JLT_REG => {
|
||||
check_jmp_offset(prog, insn_ptr)?;
|
||||
}
|
||||
ebpf::JLE_IMM => {
|
||||
check_jmp_offset(prog, insn_ptr)?;
|
||||
}
|
||||
ebpf::JLE_REG => {
|
||||
check_jmp_offset(prog, insn_ptr)?;
|
||||
}
|
||||
ebpf::JSET_IMM => {
|
||||
check_jmp_offset(prog, insn_ptr)?;
|
||||
}
|
||||
ebpf::JSET_REG => {
|
||||
check_jmp_offset(prog, insn_ptr)?;
|
||||
}
|
||||
ebpf::JNE_IMM => {
|
||||
check_jmp_offset(prog, insn_ptr)?;
|
||||
}
|
||||
ebpf::JNE_REG => {
|
||||
check_jmp_offset(prog, insn_ptr)?;
|
||||
}
|
||||
ebpf::JSGT_IMM => {
|
||||
check_jmp_offset(prog, insn_ptr)?;
|
||||
}
|
||||
ebpf::JSGT_REG => {
|
||||
check_jmp_offset(prog, insn_ptr)?;
|
||||
}
|
||||
ebpf::JSGE_IMM => {
|
||||
check_jmp_offset(prog, insn_ptr)?;
|
||||
}
|
||||
ebpf::JSGE_REG => {
|
||||
check_jmp_offset(prog, insn_ptr)?;
|
||||
}
|
||||
ebpf::JSLT_IMM => {
|
||||
check_jmp_offset(prog, insn_ptr)?;
|
||||
}
|
||||
ebpf::JSLT_REG => {
|
||||
check_jmp_offset(prog, insn_ptr)?;
|
||||
}
|
||||
ebpf::JSLE_IMM => {
|
||||
check_jmp_offset(prog, insn_ptr)?;
|
||||
}
|
||||
ebpf::JSLE_REG => {
|
||||
check_jmp_offset(prog, insn_ptr)?;
|
||||
}
|
||||
|
||||
// BPF_JMP32 class
|
||||
ebpf::JEQ_IMM32 => {
|
||||
check_jmp_offset(prog, insn_ptr)?;
|
||||
}
|
||||
ebpf::JEQ_REG32 => {
|
||||
check_jmp_offset(prog, insn_ptr)?;
|
||||
}
|
||||
ebpf::JGT_IMM32 => {
|
||||
check_jmp_offset(prog, insn_ptr)?;
|
||||
}
|
||||
ebpf::JGT_REG32 => {
|
||||
check_jmp_offset(prog, insn_ptr)?;
|
||||
}
|
||||
ebpf::JGE_IMM32 => {
|
||||
check_jmp_offset(prog, insn_ptr)?;
|
||||
}
|
||||
ebpf::JGE_REG32 => {
|
||||
check_jmp_offset(prog, insn_ptr)?;
|
||||
}
|
||||
ebpf::JLT_IMM32 => {
|
||||
check_jmp_offset(prog, insn_ptr)?;
|
||||
}
|
||||
ebpf::JLT_REG32 => {
|
||||
check_jmp_offset(prog, insn_ptr)?;
|
||||
}
|
||||
ebpf::JLE_IMM32 => {
|
||||
check_jmp_offset(prog, insn_ptr)?;
|
||||
}
|
||||
ebpf::JLE_REG32 => {
|
||||
check_jmp_offset(prog, insn_ptr)?;
|
||||
}
|
||||
ebpf::JSET_IMM32 => {
|
||||
check_jmp_offset(prog, insn_ptr)?;
|
||||
}
|
||||
ebpf::JSET_REG32 => {
|
||||
check_jmp_offset(prog, insn_ptr)?;
|
||||
}
|
||||
ebpf::JNE_IMM32 => {
|
||||
check_jmp_offset(prog, insn_ptr)?;
|
||||
}
|
||||
ebpf::JNE_REG32 => {
|
||||
check_jmp_offset(prog, insn_ptr)?;
|
||||
}
|
||||
ebpf::JSGT_IMM32 => {
|
||||
check_jmp_offset(prog, insn_ptr)?;
|
||||
}
|
||||
ebpf::JSGT_REG32 => {
|
||||
check_jmp_offset(prog, insn_ptr)?;
|
||||
}
|
||||
ebpf::JSGE_IMM32 => {
|
||||
check_jmp_offset(prog, insn_ptr)?;
|
||||
}
|
||||
ebpf::JSGE_REG32 => {
|
||||
check_jmp_offset(prog, insn_ptr)?;
|
||||
}
|
||||
ebpf::JSLT_IMM32 => {
|
||||
check_jmp_offset(prog, insn_ptr)?;
|
||||
}
|
||||
ebpf::JSLT_REG32 => {
|
||||
check_jmp_offset(prog, insn_ptr)?;
|
||||
}
|
||||
ebpf::JSLE_IMM32 => {
|
||||
check_jmp_offset(prog, insn_ptr)?;
|
||||
}
|
||||
ebpf::JSLE_REG32 => {
|
||||
check_jmp_offset(prog, insn_ptr)?;
|
||||
}
|
||||
|
||||
ebpf::CALL => {}
|
||||
ebpf::TAIL_CALL => {
|
||||
unimplemented!()
|
||||
}
|
||||
ebpf::EXIT => {}
|
||||
|
||||
_ => {
|
||||
reject(format!(
|
||||
"unknown eBPF opcode {:#2x} (insn #{insn_ptr:?})",
|
||||
insn.opc
|
||||
))?;
|
||||
}
|
||||
}
|
||||
|
||||
check_registers(&insn, store, insn_ptr)?;
|
||||
|
||||
insn_ptr += 1;
|
||||
}
|
||||
|
||||
// insn_ptr should now be equal to number of instructions.
|
||||
if insn_ptr != prog.len() / ebpf::INSN_SIZE {
|
||||
reject(format!("jumped out of code to #{insn_ptr:?}"))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
655
kernel/crates/rbpf/tests/assembler.rs
Normal file
655
kernel/crates/rbpf/tests/assembler.rs
Normal file
@ -0,0 +1,655 @@
|
||||
// SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
// Copyright 2017 Rich Lane <lanerl@gmail.com>
|
||||
|
||||
#![allow(clippy::unreadable_literal)]
|
||||
|
||||
extern crate rbpf;
|
||||
mod common;
|
||||
|
||||
use common::{TCP_SACK_ASM, TCP_SACK_BIN};
|
||||
use rbpf::{assembler::assemble, ebpf};
|
||||
|
||||
fn asm(src: &str) -> Result<Vec<ebpf::Insn>, String> {
|
||||
Ok(ebpf::to_insn_vec(&(assemble(src))?))
|
||||
}
|
||||
|
||||
fn insn(opc: u8, dst: u8, src: u8, off: i16, imm: i32) -> ebpf::Insn {
|
||||
ebpf::Insn {
|
||||
opc,
|
||||
dst,
|
||||
src,
|
||||
off,
|
||||
imm,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_empty() {
|
||||
assert_eq!(asm(""), Ok(vec![]));
|
||||
}
|
||||
|
||||
// Example for InstructionType::NoOperand.
|
||||
#[test]
|
||||
fn test_exit() {
|
||||
assert_eq!(asm("exit"), Ok(vec![insn(ebpf::EXIT, 0, 0, 0, 0)]));
|
||||
}
|
||||
|
||||
// Example for InstructionType::AluBinary.
|
||||
#[test]
|
||||
fn test_add64() {
|
||||
assert_eq!(
|
||||
asm("add64 r1, r3"),
|
||||
Ok(vec![insn(ebpf::ADD64_REG, 1, 3, 0, 0)])
|
||||
);
|
||||
assert_eq!(
|
||||
asm("add64 r1, 5"),
|
||||
Ok(vec![insn(ebpf::ADD64_IMM, 1, 0, 0, 5)])
|
||||
);
|
||||
}
|
||||
|
||||
// Example for InstructionType::AluUnary.
|
||||
#[test]
|
||||
fn test_neg64() {
|
||||
assert_eq!(asm("neg64 r1"), Ok(vec![insn(ebpf::NEG64, 1, 0, 0, 0)]));
|
||||
}
|
||||
|
||||
// Example for InstructionType::LoadReg.
|
||||
#[test]
|
||||
fn test_ldxw() {
|
||||
assert_eq!(
|
||||
asm("ldxw r1, [r2+5]"),
|
||||
Ok(vec![insn(ebpf::LD_W_REG, 1, 2, 5, 0)])
|
||||
);
|
||||
}
|
||||
|
||||
// Example for InstructionType::StoreImm.
|
||||
#[test]
|
||||
fn test_stw() {
|
||||
assert_eq!(
|
||||
asm("stw [r2+5], 7"),
|
||||
Ok(vec![insn(ebpf::ST_W_IMM, 2, 0, 5, 7)])
|
||||
);
|
||||
}
|
||||
|
||||
// Example for InstructionType::StoreReg.
|
||||
#[test]
|
||||
fn test_stxw() {
|
||||
assert_eq!(
|
||||
asm("stxw [r2+5], r8"),
|
||||
Ok(vec![insn(ebpf::ST_W_REG, 2, 8, 5, 0)])
|
||||
);
|
||||
}
|
||||
|
||||
// Example for InstructionType::JumpUnconditional.
|
||||
#[test]
|
||||
fn test_ja() {
|
||||
assert_eq!(asm("ja +8"), Ok(vec![insn(ebpf::JA, 0, 0, 8, 0)]));
|
||||
assert_eq!(asm("ja -3"), Ok(vec![insn(ebpf::JA, 0, 0, -3, 0)]));
|
||||
}
|
||||
|
||||
// Example for InstructionType::JumpConditional.
|
||||
#[test]
|
||||
fn test_jeq() {
|
||||
assert_eq!(
|
||||
asm("jeq r1, 4, +8"),
|
||||
Ok(vec![insn(ebpf::JEQ_IMM, 1, 0, 8, 4)])
|
||||
);
|
||||
assert_eq!(
|
||||
asm("jeq r1, r3, +8"),
|
||||
Ok(vec![insn(ebpf::JEQ_REG, 1, 3, 8, 0)])
|
||||
);
|
||||
}
|
||||
|
||||
// Example for InstructionType::Call.
|
||||
#[test]
|
||||
fn test_call() {
|
||||
assert_eq!(asm("call 300"), Ok(vec![insn(ebpf::CALL, 0, 0, 0, 300)]));
|
||||
}
|
||||
|
||||
// Example for InstructionType::Endian.
|
||||
#[test]
|
||||
fn test_be32() {
|
||||
assert_eq!(asm("be32 r1"), Ok(vec![insn(ebpf::BE, 1, 0, 0, 32)]));
|
||||
}
|
||||
|
||||
// Example for InstructionType::LoadImm.
|
||||
#[test]
|
||||
fn test_lddw() {
|
||||
assert_eq!(
|
||||
asm("lddw r1, 0x1234abcd5678eeff"),
|
||||
Ok(vec![
|
||||
insn(ebpf::LD_DW_IMM, 1, 0, 0, 0x5678eeff),
|
||||
insn(0, 0, 0, 0, 0x1234abcd)
|
||||
])
|
||||
);
|
||||
assert_eq!(
|
||||
asm("lddw r1, 0xff11ee22dd33cc44"),
|
||||
Ok(vec![
|
||||
insn(ebpf::LD_DW_IMM, 1, 0, 0, 0xdd33cc44u32 as i32),
|
||||
insn(0, 0, 0, 0, 0xff11ee22u32 as i32)
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
// Example for InstructionType::LoadAbs.
|
||||
#[test]
|
||||
fn test_ldabsw() {
|
||||
assert_eq!(asm("ldabsw 1"), Ok(vec![insn(ebpf::LD_ABS_W, 0, 0, 0, 1)]));
|
||||
}
|
||||
|
||||
// Example for InstructionType::LoadInd.
|
||||
#[test]
|
||||
fn test_ldindw() {
|
||||
assert_eq!(
|
||||
asm("ldindw r1, 2"),
|
||||
Ok(vec![insn(ebpf::LD_IND_W, 0, 1, 0, 2)])
|
||||
);
|
||||
}
|
||||
|
||||
// Example for InstructionType::LoadReg.
|
||||
#[test]
|
||||
fn test_ldxdw() {
|
||||
assert_eq!(
|
||||
asm("ldxdw r1, [r2+3]"),
|
||||
Ok(vec![insn(ebpf::LD_DW_REG, 1, 2, 3, 0)])
|
||||
);
|
||||
}
|
||||
|
||||
// Example for InstructionType::StoreImm.
|
||||
#[test]
|
||||
fn test_sth() {
|
||||
assert_eq!(
|
||||
asm("sth [r1+2], 3"),
|
||||
Ok(vec![insn(ebpf::ST_H_IMM, 1, 0, 2, 3)])
|
||||
);
|
||||
}
|
||||
|
||||
// Example for InstructionType::StoreReg.
|
||||
#[test]
|
||||
fn test_stxh() {
|
||||
assert_eq!(
|
||||
asm("stxh [r1+2], r3"),
|
||||
Ok(vec![insn(ebpf::ST_H_REG, 1, 3, 2, 0)])
|
||||
);
|
||||
}
|
||||
|
||||
// Test all supported AluBinary mnemonics.
|
||||
#[test]
|
||||
fn test_alu_binary() {
|
||||
assert_eq!(
|
||||
asm("add r1, r2
|
||||
sub r1, r2
|
||||
mul r1, r2
|
||||
div r1, r2
|
||||
or r1, r2
|
||||
and r1, r2
|
||||
lsh r1, r2
|
||||
rsh r1, r2
|
||||
mod r1, r2
|
||||
xor r1, r2
|
||||
mov r1, r2
|
||||
arsh r1, r2"),
|
||||
Ok(vec![
|
||||
insn(ebpf::ADD64_REG, 1, 2, 0, 0),
|
||||
insn(ebpf::SUB64_REG, 1, 2, 0, 0),
|
||||
insn(ebpf::MUL64_REG, 1, 2, 0, 0),
|
||||
insn(ebpf::DIV64_REG, 1, 2, 0, 0),
|
||||
insn(ebpf::OR64_REG, 1, 2, 0, 0),
|
||||
insn(ebpf::AND64_REG, 1, 2, 0, 0),
|
||||
insn(ebpf::LSH64_REG, 1, 2, 0, 0),
|
||||
insn(ebpf::RSH64_REG, 1, 2, 0, 0),
|
||||
insn(ebpf::MOD64_REG, 1, 2, 0, 0),
|
||||
insn(ebpf::XOR64_REG, 1, 2, 0, 0),
|
||||
insn(ebpf::MOV64_REG, 1, 2, 0, 0),
|
||||
insn(ebpf::ARSH64_REG, 1, 2, 0, 0)
|
||||
])
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
asm("add r1, 2
|
||||
sub r1, 2
|
||||
mul r1, 2
|
||||
div r1, 2
|
||||
or r1, 2
|
||||
and r1, 2
|
||||
lsh r1, 2
|
||||
rsh r1, 2
|
||||
mod r1, 2
|
||||
xor r1, 2
|
||||
mov r1, 2
|
||||
arsh r1, 2"),
|
||||
Ok(vec![
|
||||
insn(ebpf::ADD64_IMM, 1, 0, 0, 2),
|
||||
insn(ebpf::SUB64_IMM, 1, 0, 0, 2),
|
||||
insn(ebpf::MUL64_IMM, 1, 0, 0, 2),
|
||||
insn(ebpf::DIV64_IMM, 1, 0, 0, 2),
|
||||
insn(ebpf::OR64_IMM, 1, 0, 0, 2),
|
||||
insn(ebpf::AND64_IMM, 1, 0, 0, 2),
|
||||
insn(ebpf::LSH64_IMM, 1, 0, 0, 2),
|
||||
insn(ebpf::RSH64_IMM, 1, 0, 0, 2),
|
||||
insn(ebpf::MOD64_IMM, 1, 0, 0, 2),
|
||||
insn(ebpf::XOR64_IMM, 1, 0, 0, 2),
|
||||
insn(ebpf::MOV64_IMM, 1, 0, 0, 2),
|
||||
insn(ebpf::ARSH64_IMM, 1, 0, 0, 2)
|
||||
])
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
asm("add64 r1, r2
|
||||
sub64 r1, r2
|
||||
mul64 r1, r2
|
||||
div64 r1, r2
|
||||
or64 r1, r2
|
||||
and64 r1, r2
|
||||
lsh64 r1, r2
|
||||
rsh64 r1, r2
|
||||
mod64 r1, r2
|
||||
xor64 r1, r2
|
||||
mov64 r1, r2
|
||||
arsh64 r1, r2"),
|
||||
Ok(vec![
|
||||
insn(ebpf::ADD64_REG, 1, 2, 0, 0),
|
||||
insn(ebpf::SUB64_REG, 1, 2, 0, 0),
|
||||
insn(ebpf::MUL64_REG, 1, 2, 0, 0),
|
||||
insn(ebpf::DIV64_REG, 1, 2, 0, 0),
|
||||
insn(ebpf::OR64_REG, 1, 2, 0, 0),
|
||||
insn(ebpf::AND64_REG, 1, 2, 0, 0),
|
||||
insn(ebpf::LSH64_REG, 1, 2, 0, 0),
|
||||
insn(ebpf::RSH64_REG, 1, 2, 0, 0),
|
||||
insn(ebpf::MOD64_REG, 1, 2, 0, 0),
|
||||
insn(ebpf::XOR64_REG, 1, 2, 0, 0),
|
||||
insn(ebpf::MOV64_REG, 1, 2, 0, 0),
|
||||
insn(ebpf::ARSH64_REG, 1, 2, 0, 0)
|
||||
])
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
asm("add64 r1, 2
|
||||
sub64 r1, 2
|
||||
mul64 r1, 2
|
||||
div64 r1, 2
|
||||
or64 r1, 2
|
||||
and64 r1, 2
|
||||
lsh64 r1, 2
|
||||
rsh64 r1, 2
|
||||
mod64 r1, 2
|
||||
xor64 r1, 2
|
||||
mov64 r1, 2
|
||||
arsh64 r1, 2"),
|
||||
Ok(vec![
|
||||
insn(ebpf::ADD64_IMM, 1, 0, 0, 2),
|
||||
insn(ebpf::SUB64_IMM, 1, 0, 0, 2),
|
||||
insn(ebpf::MUL64_IMM, 1, 0, 0, 2),
|
||||
insn(ebpf::DIV64_IMM, 1, 0, 0, 2),
|
||||
insn(ebpf::OR64_IMM, 1, 0, 0, 2),
|
||||
insn(ebpf::AND64_IMM, 1, 0, 0, 2),
|
||||
insn(ebpf::LSH64_IMM, 1, 0, 0, 2),
|
||||
insn(ebpf::RSH64_IMM, 1, 0, 0, 2),
|
||||
insn(ebpf::MOD64_IMM, 1, 0, 0, 2),
|
||||
insn(ebpf::XOR64_IMM, 1, 0, 0, 2),
|
||||
insn(ebpf::MOV64_IMM, 1, 0, 0, 2),
|
||||
insn(ebpf::ARSH64_IMM, 1, 0, 0, 2)
|
||||
])
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
asm("add32 r1, r2
|
||||
sub32 r1, r2
|
||||
mul32 r1, r2
|
||||
div32 r1, r2
|
||||
or32 r1, r2
|
||||
and32 r1, r2
|
||||
lsh32 r1, r2
|
||||
rsh32 r1, r2
|
||||
mod32 r1, r2
|
||||
xor32 r1, r2
|
||||
mov32 r1, r2
|
||||
arsh32 r1, r2"),
|
||||
Ok(vec![
|
||||
insn(ebpf::ADD32_REG, 1, 2, 0, 0),
|
||||
insn(ebpf::SUB32_REG, 1, 2, 0, 0),
|
||||
insn(ebpf::MUL32_REG, 1, 2, 0, 0),
|
||||
insn(ebpf::DIV32_REG, 1, 2, 0, 0),
|
||||
insn(ebpf::OR32_REG, 1, 2, 0, 0),
|
||||
insn(ebpf::AND32_REG, 1, 2, 0, 0),
|
||||
insn(ebpf::LSH32_REG, 1, 2, 0, 0),
|
||||
insn(ebpf::RSH32_REG, 1, 2, 0, 0),
|
||||
insn(ebpf::MOD32_REG, 1, 2, 0, 0),
|
||||
insn(ebpf::XOR32_REG, 1, 2, 0, 0),
|
||||
insn(ebpf::MOV32_REG, 1, 2, 0, 0),
|
||||
insn(ebpf::ARSH32_REG, 1, 2, 0, 0)
|
||||
])
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
asm("add32 r1, 2
|
||||
sub32 r1, 2
|
||||
mul32 r1, 2
|
||||
div32 r1, 2
|
||||
or32 r1, 2
|
||||
and32 r1, 2
|
||||
lsh32 r1, 2
|
||||
rsh32 r1, 2
|
||||
mod32 r1, 2
|
||||
xor32 r1, 2
|
||||
mov32 r1, 2
|
||||
arsh32 r1, 2"),
|
||||
Ok(vec![
|
||||
insn(ebpf::ADD32_IMM, 1, 0, 0, 2),
|
||||
insn(ebpf::SUB32_IMM, 1, 0, 0, 2),
|
||||
insn(ebpf::MUL32_IMM, 1, 0, 0, 2),
|
||||
insn(ebpf::DIV32_IMM, 1, 0, 0, 2),
|
||||
insn(ebpf::OR32_IMM, 1, 0, 0, 2),
|
||||
insn(ebpf::AND32_IMM, 1, 0, 0, 2),
|
||||
insn(ebpf::LSH32_IMM, 1, 0, 0, 2),
|
||||
insn(ebpf::RSH32_IMM, 1, 0, 0, 2),
|
||||
insn(ebpf::MOD32_IMM, 1, 0, 0, 2),
|
||||
insn(ebpf::XOR32_IMM, 1, 0, 0, 2),
|
||||
insn(ebpf::MOV32_IMM, 1, 0, 0, 2),
|
||||
insn(ebpf::ARSH32_IMM, 1, 0, 0, 2)
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
// Test all supported AluUnary mnemonics.
|
||||
#[test]
|
||||
fn test_alu_unary() {
|
||||
assert_eq!(
|
||||
asm("neg r1
|
||||
neg64 r1
|
||||
neg32 r1"),
|
||||
Ok(vec![
|
||||
insn(ebpf::NEG64, 1, 0, 0, 0),
|
||||
insn(ebpf::NEG64, 1, 0, 0, 0),
|
||||
insn(ebpf::NEG32, 1, 0, 0, 0)
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
// Test all supported LoadAbs mnemonics.
|
||||
#[test]
|
||||
fn test_load_abs() {
|
||||
assert_eq!(
|
||||
asm("ldabsw 1
|
||||
ldabsh 1
|
||||
ldabsb 1
|
||||
ldabsdw 1"),
|
||||
Ok(vec![
|
||||
insn(ebpf::LD_ABS_W, 0, 0, 0, 1),
|
||||
insn(ebpf::LD_ABS_H, 0, 0, 0, 1),
|
||||
insn(ebpf::LD_ABS_B, 0, 0, 0, 1),
|
||||
insn(ebpf::LD_ABS_DW, 0, 0, 0, 1)
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
// Test all supported LoadInd mnemonics.
|
||||
#[test]
|
||||
fn test_load_ind() {
|
||||
assert_eq!(
|
||||
asm("ldindw r1, 2
|
||||
ldindh r1, 2
|
||||
ldindb r1, 2
|
||||
ldinddw r1, 2"),
|
||||
Ok(vec![
|
||||
insn(ebpf::LD_IND_W, 0, 1, 0, 2),
|
||||
insn(ebpf::LD_IND_H, 0, 1, 0, 2),
|
||||
insn(ebpf::LD_IND_B, 0, 1, 0, 2),
|
||||
insn(ebpf::LD_IND_DW, 0, 1, 0, 2)
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
// Test all supported LoadReg mnemonics.
|
||||
#[test]
|
||||
fn test_load_reg() {
|
||||
assert_eq!(
|
||||
asm("ldxw r1, [r2+3]
|
||||
ldxh r1, [r2+3]
|
||||
ldxb r1, [r2+3]
|
||||
ldxdw r1, [r2+3]"),
|
||||
Ok(vec![
|
||||
insn(ebpf::LD_W_REG, 1, 2, 3, 0),
|
||||
insn(ebpf::LD_H_REG, 1, 2, 3, 0),
|
||||
insn(ebpf::LD_B_REG, 1, 2, 3, 0),
|
||||
insn(ebpf::LD_DW_REG, 1, 2, 3, 0)
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
// Test all supported StoreImm mnemonics.
|
||||
#[test]
|
||||
fn test_store_imm() {
|
||||
assert_eq!(
|
||||
asm("stw [r1+2], 3
|
||||
sth [r1+2], 3
|
||||
stb [r1+2], 3
|
||||
stdw [r1+2], 3"),
|
||||
Ok(vec![
|
||||
insn(ebpf::ST_W_IMM, 1, 0, 2, 3),
|
||||
insn(ebpf::ST_H_IMM, 1, 0, 2, 3),
|
||||
insn(ebpf::ST_B_IMM, 1, 0, 2, 3),
|
||||
insn(ebpf::ST_DW_IMM, 1, 0, 2, 3)
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
// Test all supported StoreReg mnemonics.
|
||||
#[test]
|
||||
fn test_store_reg() {
|
||||
assert_eq!(
|
||||
asm("stxw [r1+2], r3
|
||||
stxh [r1+2], r3
|
||||
stxb [r1+2], r3
|
||||
stxdw [r1+2], r3"),
|
||||
Ok(vec![
|
||||
insn(ebpf::ST_W_REG, 1, 3, 2, 0),
|
||||
insn(ebpf::ST_H_REG, 1, 3, 2, 0),
|
||||
insn(ebpf::ST_B_REG, 1, 3, 2, 0),
|
||||
insn(ebpf::ST_DW_REG, 1, 3, 2, 0)
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
// Test all supported JumpConditional mnemonics.
|
||||
#[test]
|
||||
fn test_jump_conditional() {
|
||||
assert_eq!(
|
||||
asm("jeq r1, r2, +3
|
||||
jgt r1, r2, +3
|
||||
jge r1, r2, +3
|
||||
jlt r1, r2, +3
|
||||
jle r1, r2, +3
|
||||
jset r1, r2, +3
|
||||
jne r1, r2, +3
|
||||
jsgt r1, r2, +3
|
||||
jsge r1, r2, +3
|
||||
jslt r1, r2, +3
|
||||
jsle r1, r2, +3"),
|
||||
Ok(vec![
|
||||
insn(ebpf::JEQ_REG, 1, 2, 3, 0),
|
||||
insn(ebpf::JGT_REG, 1, 2, 3, 0),
|
||||
insn(ebpf::JGE_REG, 1, 2, 3, 0),
|
||||
insn(ebpf::JLT_REG, 1, 2, 3, 0),
|
||||
insn(ebpf::JLE_REG, 1, 2, 3, 0),
|
||||
insn(ebpf::JSET_REG, 1, 2, 3, 0),
|
||||
insn(ebpf::JNE_REG, 1, 2, 3, 0),
|
||||
insn(ebpf::JSGT_REG, 1, 2, 3, 0),
|
||||
insn(ebpf::JSGE_REG, 1, 2, 3, 0),
|
||||
insn(ebpf::JSLT_REG, 1, 2, 3, 0),
|
||||
insn(ebpf::JSLE_REG, 1, 2, 3, 0)
|
||||
])
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
asm("jeq r1, 2, +3
|
||||
jgt r1, 2, +3
|
||||
jge r1, 2, +3
|
||||
jlt r1, 2, +3
|
||||
jle r1, 2, +3
|
||||
jset r1, 2, +3
|
||||
jne r1, 2, +3
|
||||
jsgt r1, 2, +3
|
||||
jsge r1, 2, +3
|
||||
jslt r1, 2, +3
|
||||
jsle r1, 2, +3"),
|
||||
Ok(vec![
|
||||
insn(ebpf::JEQ_IMM, 1, 0, 3, 2),
|
||||
insn(ebpf::JGT_IMM, 1, 0, 3, 2),
|
||||
insn(ebpf::JGE_IMM, 1, 0, 3, 2),
|
||||
insn(ebpf::JLT_IMM, 1, 0, 3, 2),
|
||||
insn(ebpf::JLE_IMM, 1, 0, 3, 2),
|
||||
insn(ebpf::JSET_IMM, 1, 0, 3, 2),
|
||||
insn(ebpf::JNE_IMM, 1, 0, 3, 2),
|
||||
insn(ebpf::JSGT_IMM, 1, 0, 3, 2),
|
||||
insn(ebpf::JSGE_IMM, 1, 0, 3, 2),
|
||||
insn(ebpf::JSLT_IMM, 1, 0, 3, 2),
|
||||
insn(ebpf::JSLE_IMM, 1, 0, 3, 2)
|
||||
])
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
asm("jeq32 r1, r2, +3
|
||||
jgt32 r1, r2, +3
|
||||
jge32 r1, r2, +3
|
||||
jlt32 r1, r2, +3
|
||||
jle32 r1, r2, +3
|
||||
jset32 r1, r2, +3
|
||||
jne32 r1, r2, +3
|
||||
jsgt32 r1, r2, +3
|
||||
jsge32 r1, r2, +3
|
||||
jslt32 r1, r2, +3
|
||||
jsle32 r1, r2, +3"),
|
||||
Ok(vec![
|
||||
insn(ebpf::JEQ_REG32, 1, 2, 3, 0),
|
||||
insn(ebpf::JGT_REG32, 1, 2, 3, 0),
|
||||
insn(ebpf::JGE_REG32, 1, 2, 3, 0),
|
||||
insn(ebpf::JLT_REG32, 1, 2, 3, 0),
|
||||
insn(ebpf::JLE_REG32, 1, 2, 3, 0),
|
||||
insn(ebpf::JSET_REG32, 1, 2, 3, 0),
|
||||
insn(ebpf::JNE_REG32, 1, 2, 3, 0),
|
||||
insn(ebpf::JSGT_REG32, 1, 2, 3, 0),
|
||||
insn(ebpf::JSGE_REG32, 1, 2, 3, 0),
|
||||
insn(ebpf::JSLT_REG32, 1, 2, 3, 0),
|
||||
insn(ebpf::JSLE_REG32, 1, 2, 3, 0)
|
||||
])
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
asm("jeq32 r1, 2, +3
|
||||
jgt32 r1, 2, +3
|
||||
jge32 r1, 2, +3
|
||||
jlt32 r1, 2, +3
|
||||
jle32 r1, 2, +3
|
||||
jset32 r1, 2, +3
|
||||
jne32 r1, 2, +3
|
||||
jsgt32 r1, 2, +3
|
||||
jsge32 r1, 2, +3
|
||||
jslt32 r1, 2, +3
|
||||
jsle32 r1, 2, +3"),
|
||||
Ok(vec![
|
||||
insn(ebpf::JEQ_IMM32, 1, 0, 3, 2),
|
||||
insn(ebpf::JGT_IMM32, 1, 0, 3, 2),
|
||||
insn(ebpf::JGE_IMM32, 1, 0, 3, 2),
|
||||
insn(ebpf::JLT_IMM32, 1, 0, 3, 2),
|
||||
insn(ebpf::JLE_IMM32, 1, 0, 3, 2),
|
||||
insn(ebpf::JSET_IMM32, 1, 0, 3, 2),
|
||||
insn(ebpf::JNE_IMM32, 1, 0, 3, 2),
|
||||
insn(ebpf::JSGT_IMM32, 1, 0, 3, 2),
|
||||
insn(ebpf::JSGE_IMM32, 1, 0, 3, 2),
|
||||
insn(ebpf::JSLT_IMM32, 1, 0, 3, 2),
|
||||
insn(ebpf::JSLE_IMM32, 1, 0, 3, 2)
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
// Test all supported Endian mnemonics.
|
||||
#[test]
|
||||
fn test_endian() {
|
||||
assert_eq!(
|
||||
asm("be16 r1
|
||||
be32 r1
|
||||
be64 r1
|
||||
le16 r1
|
||||
le32 r1
|
||||
le64 r1"),
|
||||
Ok(vec![
|
||||
insn(ebpf::BE, 1, 0, 0, 16),
|
||||
insn(ebpf::BE, 1, 0, 0, 32),
|
||||
insn(ebpf::BE, 1, 0, 0, 64),
|
||||
insn(ebpf::LE, 1, 0, 0, 16),
|
||||
insn(ebpf::LE, 1, 0, 0, 32),
|
||||
insn(ebpf::LE, 1, 0, 0, 64)
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_large_immediate() {
|
||||
assert_eq!(
|
||||
asm("add64 r1, 2147483647"),
|
||||
Ok(vec![insn(ebpf::ADD64_IMM, 1, 0, 0, 2147483647)])
|
||||
);
|
||||
assert_eq!(
|
||||
asm("add64 r1, -2147483648"),
|
||||
Ok(vec![insn(ebpf::ADD64_IMM, 1, 0, 0, -2147483648)])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tcp_sack() {
|
||||
assert_eq!(assemble(TCP_SACK_ASM), Ok(TCP_SACK_BIN.to_vec()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_error_invalid_instruction() {
|
||||
assert_eq!(asm("abcd"), Err("Invalid instruction \"abcd\"".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_error_unexpected_operands() {
|
||||
assert_eq!(
|
||||
asm("add 1, 2"),
|
||||
Err("Failed to encode add: Unexpected operands: [Integer(1), Integer(2)]".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_error_too_many_operands() {
|
||||
assert_eq!(
|
||||
asm("add 1, 2, 3, 4"),
|
||||
Err("Failed to encode add: Too many operands".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_error_operands_out_of_range() {
|
||||
assert_eq!(
|
||||
asm("add r16, r2"),
|
||||
Err("Failed to encode add: Invalid destination register 16".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
asm("add r1, r16"),
|
||||
Err("Failed to encode add: Invalid source register 16".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
asm("ja -32769"),
|
||||
Err("Failed to encode ja: Invalid offset -32769".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
asm("ja 32768"),
|
||||
Err("Failed to encode ja: Invalid offset 32768".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
asm("add r1, 4294967296"),
|
||||
Err("Failed to encode add: Invalid immediate 4294967296".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
asm("add r1, 2147483648"),
|
||||
Err("Failed to encode add: Invalid immediate 2147483648".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
asm("add r1, -2147483649"),
|
||||
Err("Failed to encode add: Invalid immediate -2147483649".to_string())
|
||||
);
|
||||
}
|
97
kernel/crates/rbpf/tests/common.rs
Normal file
97
kernel/crates/rbpf/tests/common.rs
Normal file
@ -0,0 +1,97 @@
|
||||
// SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
// Converted from the tests for uBPF <https://github.com/iovisor/ubpf>
|
||||
// Copyright 2015 Big Switch Networks, Inc
|
||||
// Copyright 2016 6WIND S.A. <quentin.monnet@6wind.com>
|
||||
|
||||
// Assembly code and data for tcp_sack testcases.
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub const TCP_SACK_ASM: &str = "
|
||||
ldxb r2, [r1+12]
|
||||
ldxb r3, [r1+13]
|
||||
lsh r3, 0x8
|
||||
or r3, r2
|
||||
mov r0, 0x0
|
||||
jne r3, 0x8, +37
|
||||
ldxb r2, [r1+23]
|
||||
jne r2, 0x6, +35
|
||||
ldxb r2, [r1+14]
|
||||
add r1, 0xe
|
||||
and r2, 0xf
|
||||
lsh r2, 0x2
|
||||
add r1, r2
|
||||
mov r0, 0x0
|
||||
ldxh r4, [r1+12]
|
||||
add r1, 0x14
|
||||
rsh r4, 0x2
|
||||
and r4, 0x3c
|
||||
mov r2, r4
|
||||
add r2, -20
|
||||
mov r5, 0x15
|
||||
mov r3, 0x0
|
||||
jgt r5, r4, +20
|
||||
mov r5, r3
|
||||
lsh r5, 0x20
|
||||
arsh r5, 0x20
|
||||
mov r4, r1
|
||||
add r4, r5
|
||||
ldxb r5, [r4]
|
||||
jeq r5, 0x1, +4
|
||||
jeq r5, 0x0, +12
|
||||
mov r6, r3
|
||||
jeq r5, 0x5, +9
|
||||
ja +2
|
||||
add r3, 0x1
|
||||
mov r6, r3
|
||||
ldxb r3, [r4+1]
|
||||
add r3, r6
|
||||
lsh r3, 0x20
|
||||
arsh r3, 0x20
|
||||
jsgt r2, r3, -18
|
||||
ja +1
|
||||
mov r0, 0x1
|
||||
exit";
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub const TCP_SACK_BIN: [u8; 352] = [
|
||||
0x71, 0x12, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x71, 0x13, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x67, 0x03, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x4f, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x03, 0x25, 0x00, 0x08, 0x00, 0x00, 0x00,
|
||||
0x71, 0x12, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x02, 0x23, 0x00, 0x06, 0x00, 0x00, 0x00,
|
||||
0x71, 0x12, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x01, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00,
|
||||
0x57, 0x02, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x67, 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
|
||||
0x0f, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x69, 0x14, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x01, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
|
||||
0x77, 0x04, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x57, 0x04, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00,
|
||||
0xbf, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x02, 0x00, 0x00, 0xec, 0xff, 0xff, 0xff,
|
||||
0xb7, 0x05, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0xb7, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x2d, 0x45, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbf, 0x35, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x67, 0x05, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0xc7, 0x05, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
|
||||
0xbf, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x71, 0x45, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0x05, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||
0x15, 0x05, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbf, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x15, 0x05, 0x09, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x07, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xbf, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x71, 0x43, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x67, 0x03, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0xc7, 0x03, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
|
||||
0x6d, 0x32, 0xee, 0xff, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0xb7, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
];
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub const TCP_SACK_MATCH: [u8; 78] = [
|
||||
0x00, 0x26, 0x62, 0x2f, 0x47, 0x87, 0x00, 0x1d, 0x60, 0xb3, 0x01, 0x84, 0x08, 0x00, 0x45, 0x00,
|
||||
0x00, 0x40, 0xa8, 0xde, 0x40, 0x00, 0x40, 0x06, 0x9d, 0x58, 0xc0, 0xa8, 0x01, 0x03, 0x3f, 0x74,
|
||||
0xf3, 0x61, 0xe5, 0xc0, 0x00, 0x50, 0xe5, 0x94, 0x3f, 0x77, 0xa3, 0xc4, 0xc4, 0x80, 0xb0, 0x10,
|
||||
0x01, 0x3e, 0x34, 0xb6, 0x00, 0x00, 0x01, 0x01, 0x08, 0x0a, 0x00, 0x17, 0x95, 0x6f, 0x8d, 0x9d,
|
||||
0x9e, 0x27, 0x01, 0x01, 0x05, 0x0a, 0xa3, 0xc4, 0xca, 0x28, 0xa3, 0xc4, 0xcf, 0xd0,
|
||||
];
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub const TCP_SACK_NOMATCH: [u8; 66] = [
|
||||
0x00, 0x26, 0x62, 0x2f, 0x47, 0x87, 0x00, 0x1d, 0x60, 0xb3, 0x01, 0x84, 0x08, 0x00, 0x45, 0x00,
|
||||
0x00, 0x40, 0xa8, 0xde, 0x40, 0x00, 0x40, 0x06, 0x9d, 0x58, 0xc0, 0xa8, 0x01, 0x03, 0x3f, 0x74,
|
||||
0xf3, 0x61, 0xe5, 0xc0, 0x00, 0x50, 0xe5, 0x94, 0x3f, 0x77, 0xa3, 0xc4, 0xc4, 0x80, 0x80, 0x10,
|
||||
0x01, 0x3e, 0x34, 0xb6, 0x00, 0x00, 0x01, 0x01, 0x08, 0x0a, 0x00, 0x17, 0x95, 0x6f, 0x8d, 0x9d,
|
||||
0x9e, 0x27,
|
||||
];
|
2257
kernel/crates/rbpf/tests/cranelift.rs
Normal file
2257
kernel/crates/rbpf/tests/cranelift.rs
Normal file
File diff suppressed because it is too large
Load Diff
377
kernel/crates/rbpf/tests/disassembler.rs
Normal file
377
kernel/crates/rbpf/tests/disassembler.rs
Normal file
@ -0,0 +1,377 @@
|
||||
// SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
// Copyright 2017 Jan-Erik Rediger <badboy@archlinux.us>
|
||||
//
|
||||
// Adopted from tests in `tests/assembler.rs`
|
||||
|
||||
extern crate rbpf;
|
||||
mod common;
|
||||
|
||||
use rbpf::{assembler::assemble, disassembler::to_insn_vec};
|
||||
|
||||
// Using a macro to keep actual line numbers in failure output
|
||||
macro_rules! disasm {
|
||||
($src:expr) => {{
|
||||
let src = $src;
|
||||
let asm = assemble(src).expect("Can't assemble from string");
|
||||
let insn = to_insn_vec(&asm);
|
||||
let reasm = insn
|
||||
.into_iter()
|
||||
.map(|ins| ins.desc)
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
|
||||
assert_eq!(src, reasm);
|
||||
}};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_empty() {
|
||||
disasm!("");
|
||||
}
|
||||
|
||||
// Example for InstructionType::NoOperand.
|
||||
#[test]
|
||||
fn test_exit() {
|
||||
disasm!("exit");
|
||||
}
|
||||
|
||||
// Example for InstructionType::AluBinary.
|
||||
#[test]
|
||||
fn test_add64() {
|
||||
disasm!("add64 r1, r3");
|
||||
disasm!("add64 r1, 0x5");
|
||||
}
|
||||
|
||||
// Example for InstructionType::AluUnary.
|
||||
#[test]
|
||||
fn test_neg64() {
|
||||
disasm!("neg64 r1");
|
||||
}
|
||||
|
||||
// Example for InstructionType::LoadReg.
|
||||
#[test]
|
||||
fn test_ldxw() {
|
||||
disasm!("ldxw r1, [r2+0x5]");
|
||||
}
|
||||
|
||||
// Example for InstructionType::StoreImm.
|
||||
#[test]
|
||||
fn test_stw() {
|
||||
disasm!("stw [r2+0x5], 0x7");
|
||||
}
|
||||
|
||||
// Example for InstructionType::StoreReg.
|
||||
#[test]
|
||||
fn test_stxw() {
|
||||
disasm!("stxw [r2+0x5], r8");
|
||||
}
|
||||
|
||||
// Example for InstructionType::JumpUnconditional.
|
||||
#[test]
|
||||
fn test_ja() {
|
||||
disasm!("ja +0x8");
|
||||
}
|
||||
|
||||
// Example for InstructionType::JumpConditional.
|
||||
#[test]
|
||||
fn test_jeq() {
|
||||
disasm!("jeq r1, 0x4, +0x8");
|
||||
disasm!("jeq r1, r3, +0x8");
|
||||
}
|
||||
|
||||
// Example for InstructionType::Call.
|
||||
#[test]
|
||||
fn test_call() {
|
||||
disasm!("call 0x3");
|
||||
}
|
||||
|
||||
// Example for InstructionType::Endian.
|
||||
#[test]
|
||||
fn test_be32() {
|
||||
disasm!("be32 r1");
|
||||
}
|
||||
|
||||
// Example for InstructionType::LoadImm.
|
||||
#[test]
|
||||
fn test_lddw() {
|
||||
disasm!("lddw r1, 0x1234abcd5678eeff");
|
||||
disasm!("lddw r1, 0xff11ee22dd33cc44");
|
||||
}
|
||||
|
||||
// Example for InstructionType::LoadAbs.
|
||||
#[test]
|
||||
fn test_ldabsw() {
|
||||
disasm!("ldabsw 0x1");
|
||||
}
|
||||
|
||||
// Example for InstructionType::LoadInd.
|
||||
#[test]
|
||||
fn test_ldindw() {
|
||||
disasm!("ldindw r1, 0x2");
|
||||
}
|
||||
|
||||
// Example for InstructionType::LoadReg.
|
||||
#[test]
|
||||
fn test_ldxdw() {
|
||||
disasm!("ldxdw r1, [r2+0x3]");
|
||||
}
|
||||
|
||||
// Example for InstructionType::StoreImm.
|
||||
#[test]
|
||||
fn test_sth() {
|
||||
disasm!("sth [r1+0x2], 0x3");
|
||||
}
|
||||
|
||||
// Example for InstructionType::StoreReg.
|
||||
#[test]
|
||||
fn test_stxh() {
|
||||
disasm!("stxh [r1+0x2], r3");
|
||||
}
|
||||
|
||||
// Test all supported AluBinary mnemonics.
|
||||
#[test]
|
||||
fn test_alu_binary() {
|
||||
disasm!(
|
||||
"add64 r1, r2
|
||||
sub64 r1, r2
|
||||
mul64 r1, r2
|
||||
div64 r1, r2
|
||||
or64 r1, r2
|
||||
and64 r1, r2
|
||||
lsh64 r1, r2
|
||||
rsh64 r1, r2
|
||||
mod64 r1, r2
|
||||
xor64 r1, r2
|
||||
mov64 r1, r2
|
||||
arsh64 r1, r2"
|
||||
);
|
||||
|
||||
disasm!(
|
||||
"add64 r1, 0x2
|
||||
sub64 r1, 0x2
|
||||
mul64 r1, 0x2
|
||||
div64 r1, 0x2
|
||||
or64 r1, 0x2
|
||||
and64 r1, 0x2
|
||||
lsh64 r1, 0x2
|
||||
rsh64 r1, 0x2
|
||||
mod64 r1, 0x2
|
||||
xor64 r1, 0x2
|
||||
mov64 r1, 0x2
|
||||
arsh64 r1, 0x2"
|
||||
);
|
||||
|
||||
disasm!(
|
||||
"add32 r1, r2
|
||||
sub32 r1, r2
|
||||
mul32 r1, r2
|
||||
div32 r1, r2
|
||||
or32 r1, r2
|
||||
and32 r1, r2
|
||||
lsh32 r1, r2
|
||||
rsh32 r1, r2
|
||||
mod32 r1, r2
|
||||
xor32 r1, r2
|
||||
mov32 r1, r2
|
||||
arsh32 r1, r2"
|
||||
);
|
||||
|
||||
disasm!(
|
||||
"add32 r1, 0x2
|
||||
sub32 r1, 0x2
|
||||
mul32 r1, 0x2
|
||||
div32 r1, 0x2
|
||||
or32 r1, 0x2
|
||||
and32 r1, 0x2
|
||||
lsh32 r1, 0x2
|
||||
rsh32 r1, 0x2
|
||||
mod32 r1, 0x2
|
||||
xor32 r1, 0x2
|
||||
mov32 r1, 0x2
|
||||
arsh32 r1, 0x2"
|
||||
);
|
||||
}
|
||||
|
||||
// Test all supported AluUnary mnemonics.
|
||||
#[test]
|
||||
fn test_alu_unary() {
|
||||
disasm!(
|
||||
"neg64 r1
|
||||
neg32 r1"
|
||||
);
|
||||
}
|
||||
|
||||
// Test all supported LoadAbs mnemonics.
|
||||
#[test]
|
||||
fn test_load_abs() {
|
||||
disasm!(
|
||||
"ldabsw 0x1
|
||||
ldabsh 0x1
|
||||
ldabsb 0x1
|
||||
ldabsdw 0x1"
|
||||
);
|
||||
}
|
||||
|
||||
// Test all supported LoadInd mnemonics.
|
||||
#[test]
|
||||
fn test_load_ind() {
|
||||
disasm!(
|
||||
"ldindw r1, 0x2
|
||||
ldindh r1, 0x2
|
||||
ldindb r1, 0x2
|
||||
ldinddw r1, 0x2"
|
||||
);
|
||||
}
|
||||
|
||||
// Test all supported LoadReg mnemonics.
|
||||
#[test]
|
||||
fn test_load_reg() {
|
||||
disasm!(
|
||||
r"ldxw r1, [r2+0x3]
|
||||
ldxh r1, [r2+0x3]
|
||||
ldxb r1, [r2+0x3]
|
||||
ldxdw r1, [r2+0x3]"
|
||||
);
|
||||
}
|
||||
|
||||
// Test all supported StoreImm mnemonics.
|
||||
#[test]
|
||||
fn test_store_imm() {
|
||||
disasm!(
|
||||
"stw [r1+0x2], 0x3
|
||||
sth [r1+0x2], 0x3
|
||||
stb [r1+0x2], 0x3
|
||||
stdw [r1+0x2], 0x3"
|
||||
);
|
||||
}
|
||||
|
||||
// Test all supported StoreReg mnemonics.
|
||||
#[test]
|
||||
fn test_store_reg() {
|
||||
disasm!(
|
||||
"stxw [r1+0x2], r3
|
||||
stxh [r1+0x2], r3
|
||||
stxb [r1+0x2], r3
|
||||
stxdw [r1+0x2], r3"
|
||||
);
|
||||
}
|
||||
|
||||
// Test all supported JumpConditional mnemonics.
|
||||
#[test]
|
||||
fn test_jump_conditional() {
|
||||
disasm!(
|
||||
"jeq r1, r2, +0x3
|
||||
jgt r1, r2, +0x3
|
||||
jge r1, r2, +0x3
|
||||
jlt r1, r2, +0x3
|
||||
jle r1, r2, +0x3
|
||||
jset r1, r2, +0x3
|
||||
jne r1, r2, +0x3
|
||||
jsgt r1, r2, +0x3
|
||||
jsge r1, r2, -0x3
|
||||
jslt r1, r2, +0x3
|
||||
jsle r1, r2, -0x3"
|
||||
);
|
||||
|
||||
disasm!(
|
||||
"jeq r1, 0x2, +0x3
|
||||
jgt r1, 0x2, +0x3
|
||||
jge r1, 0x2, +0x3
|
||||
jlt r1, 0x2, +0x3
|
||||
jle r1, 0x2, +0x3
|
||||
jset r1, 0x2, +0x3
|
||||
jne r1, 0x2, +0x3
|
||||
jsgt r1, 0x2, +0x3
|
||||
jsge r1, 0x2, -0x3
|
||||
jslt r1, 0x2, +0x3
|
||||
jsle r1, 0x2, -0x3"
|
||||
);
|
||||
|
||||
disasm!(
|
||||
"jeq32 r1, r2, +0x3
|
||||
jgt32 r1, r2, +0x3
|
||||
jge32 r1, r2, +0x3
|
||||
jlt32 r1, r2, +0x3
|
||||
jle32 r1, r2, +0x3
|
||||
jset32 r1, r2, +0x3
|
||||
jne32 r1, r2, +0x3
|
||||
jsgt32 r1, r2, +0x3
|
||||
jsge32 r1, r2, -0x3
|
||||
jslt32 r1, r2, +0x3
|
||||
jsle32 r1, r2, -0x3"
|
||||
);
|
||||
|
||||
disasm!(
|
||||
"jeq32 r1, 0x2, +0x3
|
||||
jgt32 r1, 0x2, +0x3
|
||||
jge32 r1, 0x2, +0x3
|
||||
jlt32 r1, 0x2, +0x3
|
||||
jle32 r1, 0x2, +0x3
|
||||
jset32 r1, 0x2, +0x3
|
||||
jne32 r1, 0x2, +0x3
|
||||
jsgt32 r1, 0x2, +0x3
|
||||
jsge32 r1, 0x2, -0x3
|
||||
jslt32 r1, 0x2, +0x3
|
||||
jsle32 r1, 0x2, -0x3"
|
||||
);
|
||||
}
|
||||
|
||||
// Test all supported Endian mnemonics.
|
||||
#[test]
|
||||
fn test_endian() {
|
||||
disasm!(
|
||||
"be16 r1
|
||||
be32 r1
|
||||
be64 r1
|
||||
le16 r1
|
||||
le32 r1
|
||||
le64 r1"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_large_immediate() {
|
||||
disasm!("add64 r1, 0x7fffffff");
|
||||
disasm!("add64 r1, 0x7fffffff");
|
||||
}
|
||||
|
||||
// Non-regression tests for overflow when trying to negate offset 0x8000i16.
|
||||
#[test]
|
||||
fn test_offset_overflow() {
|
||||
let insns = [
|
||||
0x62, 0x01, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, // stw
|
||||
0x6a, 0x01, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, // sth
|
||||
0x72, 0x01, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, // stb
|
||||
0x7a, 0x01, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, // stdw
|
||||
0x61, 0x01, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, // ldxw
|
||||
0x69, 0x01, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, // ldxh
|
||||
0x71, 0x01, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, // ldxb
|
||||
0x79, 0x01, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, // ldxdw
|
||||
0x15, 0x01, 0x00, 0x80, 0x02, 0x00, 0x00, 0x00, // jeq (imm)
|
||||
0x1d, 0x21, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, // jeq (reg)
|
||||
0x16, 0x01, 0x00, 0x80, 0x02, 0x00, 0x00, 0x00, // jeq32 (imm)
|
||||
0x1e, 0x21, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, // jeq32 (reg)
|
||||
];
|
||||
|
||||
let expected_output = "stw [r1-0x8000], 0x1
|
||||
sth [r1-0x8000], 0x1
|
||||
stb [r1-0x8000], 0x1
|
||||
stdw [r1-0x8000], 0x1
|
||||
ldxw r1, [r0-0x8000]
|
||||
ldxh r1, [r0-0x8000]
|
||||
ldxb r1, [r0-0x8000]
|
||||
ldxdw r1, [r0-0x8000]
|
||||
jeq r1, 0x2, -0x8000
|
||||
jeq r1, r2, -0x8000
|
||||
jeq32 r1, 0x2, -0x8000
|
||||
jeq32 r1, r2, -0x8000";
|
||||
|
||||
let prog = to_insn_vec(&insns);
|
||||
let asm = prog
|
||||
.into_iter()
|
||||
.map(|ins| ins.desc)
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
|
||||
assert_eq!(asm, expected_output);
|
||||
}
|
571
kernel/crates/rbpf/tests/misc.rs
Normal file
571
kernel/crates/rbpf/tests/misc.rs
Normal file
@ -0,0 +1,571 @@
|
||||
// SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
// Copyright 2016 6WIND S.A. <quentin.monnet@6wind.com>
|
||||
|
||||
// There are unused mut warnings due to unsafe code.
|
||||
#![allow(unused_mut)]
|
||||
#![allow(clippy::unreadable_literal)]
|
||||
|
||||
// This crate would be needed to load bytecode from a BPF-compiled object file. Since the crate
|
||||
// is not used anywhere else in the library, it is deactivated: we do not want to load and compile
|
||||
// it just for the tests. If you want to use it, do not forget to add the following
|
||||
// dependency to your Cargo.toml file:
|
||||
//
|
||||
// ---
|
||||
// elf = "0.0.10"
|
||||
// ---
|
||||
//
|
||||
// extern crate elf;
|
||||
// use std::path::PathBuf;
|
||||
|
||||
extern crate rbpf;
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
use rbpf::helpers;
|
||||
use rbpf::{assembler::assemble, Error, ErrorKind};
|
||||
|
||||
// The following two examples have been compiled from C with the following command:
|
||||
//
|
||||
// ```bash
|
||||
// clang -O2 -emit-llvm -c <file.c> -o - | llc -march=bpf -filetype=obj -o <file.o>
|
||||
// ```
|
||||
//
|
||||
// The C source code was the following:
|
||||
//
|
||||
// ```c
|
||||
// #include <linux/ip.h>
|
||||
// #include <linux/in.h>
|
||||
// #include <linux/tcp.h>
|
||||
// #include <linux/bpf.h>
|
||||
//
|
||||
// #define ETH_ALEN 6
|
||||
// #define ETH_P_IP 0x0008 /* htons(0x0800) */
|
||||
// #define TCP_HDR_LEN 20
|
||||
//
|
||||
// #define BLOCKED_TCP_PORT 0x9999
|
||||
//
|
||||
// struct eth_hdr {
|
||||
// unsigned char h_dest[ETH_ALEN];
|
||||
// unsigned char h_source[ETH_ALEN];
|
||||
// unsigned short h_proto;
|
||||
// };
|
||||
//
|
||||
// #define SEC(NAME) __attribute__((section(NAME), used))
|
||||
// SEC(".classifier")
|
||||
// int handle_ingress(struct __sk_buff *skb)
|
||||
// {
|
||||
// void *data = (void *)(long)skb->data;
|
||||
// void *data_end = (void *)(long)skb->data_end;
|
||||
// struct eth_hdr *eth = data;
|
||||
// struct iphdr *iph = data + sizeof(*eth);
|
||||
// struct tcphdr *tcp = data + sizeof(*eth) + sizeof(*iph);
|
||||
//
|
||||
// /* single length check */
|
||||
// if (data + sizeof(*eth) + sizeof(*iph) + sizeof(*tcp) > data_end)
|
||||
// return 0;
|
||||
// if (eth->h_proto != ETH_P_IP)
|
||||
// return 0;
|
||||
// if (iph->protocol != IPPROTO_TCP)
|
||||
// return 0;
|
||||
// if (tcp->source == BLOCKED_TCP_PORT || tcp->dest == BLOCKED_TCP_PORT)
|
||||
// return -1;
|
||||
// return 0;
|
||||
// }
|
||||
// char _license[] SEC(".license") = "GPL";
|
||||
// ```
|
||||
//
|
||||
// This program, once compiled, can be injected into Linux kernel, with tc for instance. Sadly, we
|
||||
// need to bring some modifications to the generated bytecode in order to run it: the three
|
||||
// instructions with opcode 0x61 load data from a packet area as 4-byte words, where we need to
|
||||
// load it as 8-bytes double words (0x79). The kernel does the same kind of translation before
|
||||
// running the program, but rbpf does not implement this.
|
||||
//
|
||||
// In addition, the offset at which the pointer to the packet data is stored must be changed: since
|
||||
// we use 8 bytes instead of 4 for the start and end addresses of the data packet, we cannot use
|
||||
// the offsets produced by clang (0x4c and 0x50), the addresses would overlap. Instead we can use,
|
||||
// for example, 0x40 and 0x50. See comments on the bytecode below to see the modifications.
|
||||
//
|
||||
// Once the bytecode has been (manually, in our case) edited, we can load the bytecode directly
|
||||
// from the ELF object file. This is easy to do, but requires the addition of two crates in the
|
||||
// Cargo.toml file (see comments above), so here we use just the hardcoded bytecode instructions
|
||||
// instead.
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "std")]
|
||||
fn test_vm_block_port() {
|
||||
// To load the bytecode from an object file instead of using the hardcoded instructions,
|
||||
// use the additional crates commented at the beginning of this file (and also add them to your
|
||||
// Cargo.toml). See comments above.
|
||||
//
|
||||
// ---
|
||||
// let filename = "my_ebpf_object_file.o";
|
||||
//
|
||||
// let path = PathBuf::from(filename);
|
||||
// let file = match elf::File::open_path(&path) {
|
||||
// Ok(f) => f,
|
||||
// Err(e) => panic!("Error: {:?}", e),
|
||||
// };
|
||||
//
|
||||
// let text_scn = match file.get_section(".classifier") {
|
||||
// Some(s) => s,
|
||||
// None => panic!("Failed to look up .classifier section"),
|
||||
// };
|
||||
//
|
||||
// let prog = &text_scn.data;
|
||||
// ---
|
||||
|
||||
let prog = &[
|
||||
0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79, 0x12, 0x50, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, // 0x79 instead of 0x61
|
||||
0x79, 0x11, 0x40, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, // 0x79 instead of 0x61, 0x40 i.o. 0x4c
|
||||
0xbf, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x03, 0x00, 0x00, 0x36, 0x00, 0x00,
|
||||
0x00, 0x2d, 0x23, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x69, 0x12, 0x0c, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x55, 0x02, 0x10, 0x00, 0x08, 0x00, 0x00, 0x00, 0x71, 0x12, 0x17, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x55, 0x02, 0x0e, 0x00, 0x06, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00,
|
||||
0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79, 0x11, 0x22,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, // 0x79 instead of 0x61
|
||||
0xbf, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, 0x02, 0x00, 0x00, 0xff, 0xff, 0x00,
|
||||
0x00, 0x15, 0x02, 0x08, 0x00, 0x99, 0x99, 0x00, 0x00, 0x18, 0x02, 0x00, 0x00, 0x00, 0x00,
|
||||
0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5f, 0x21, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0xb7, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x18, 0x02, 0x00, 0x00,
|
||||
0x00, 0x00, 0x99, 0x99, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1d, 0x21, 0x01,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x95, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
];
|
||||
|
||||
let packet = &mut [
|
||||
0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x08,
|
||||
0x00, // ethertype
|
||||
0x45, 0x00, 0x00, 0x3b, // start ip_hdr
|
||||
0xa6, 0xab, 0x40, 0x00, 0x40, 0x06, 0x96, 0x0f, 0x7f, 0x00, 0x00, 0x01, 0x7f, 0x00, 0x00,
|
||||
0x01,
|
||||
// Program matches the next two bytes: 0x9999 returns 0xffffffff, else return 0.
|
||||
0x99, 0x99, 0xc6, 0xcc, // start tcp_hdr
|
||||
0xd1, 0xe5, 0xc4, 0x9d, 0xd4, 0x30, 0xb5, 0xd2, 0x80, 0x18, 0x01, 0x56, 0xfe, 0x2f, 0x00,
|
||||
0x00, 0x01, 0x01, 0x08, 0x0a, // start data
|
||||
0x00, 0x23, 0x75, 0x89, 0x00, 0x23, 0x63, 0x2d, 0x71, 0x64, 0x66, 0x73, 0x64, 0x66, 0x0au8,
|
||||
];
|
||||
|
||||
let mut vm = rbpf::EbpfVmFixedMbuff::new(Some(prog), 0x40, 0x50).unwrap();
|
||||
vm.register_helper(helpers::BPF_TRACE_PRINTK_IDX, helpers::bpf_trace_printf)
|
||||
.unwrap();
|
||||
|
||||
let res = vm.execute_program(packet).unwrap();
|
||||
println!("Program returned: {res:?} ({res:#x})");
|
||||
assert_eq!(res, 0xffffffff);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(all(not(windows), feature = "std"))]
|
||||
fn test_jit_block_port() {
|
||||
// To load the bytecode from an object file instead of using the hardcoded instructions,
|
||||
// use the additional crates commented at the beginning of this file (and also add them to your
|
||||
// Cargo.toml). See comments above.
|
||||
//
|
||||
// ---
|
||||
// let filename = "my_ebpf_object_file.o";
|
||||
//
|
||||
// let path = PathBuf::from(filename);
|
||||
// let file = match elf::File::open_path(&path) {
|
||||
// Ok(f) => f,
|
||||
// Err(e) => panic!("Error: {:?}", e),
|
||||
// };
|
||||
//
|
||||
// let text_scn = match file.get_section(".classifier") {
|
||||
// Some(s) => s,
|
||||
// None => panic!("Failed to look up .classifier section"),
|
||||
// };
|
||||
//
|
||||
// let prog = &text_scn.data;
|
||||
// ---
|
||||
|
||||
let prog = &[
|
||||
0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79, 0x12, 0x50, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, // 0x79 instead of 0x61
|
||||
0x79, 0x11, 0x40, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, // 0x79 instead of 0x61, 0x40 i.o. 0x4c
|
||||
0xbf, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x03, 0x00, 0x00, 0x36, 0x00, 0x00,
|
||||
0x00, 0x2d, 0x23, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x69, 0x12, 0x0c, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x55, 0x02, 0x10, 0x00, 0x08, 0x00, 0x00, 0x00, 0x71, 0x12, 0x17, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x55, 0x02, 0x0e, 0x00, 0x06, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00,
|
||||
0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79, 0x11, 0x22,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, // 0x79 instead of 0x61
|
||||
0xbf, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, 0x02, 0x00, 0x00, 0xff, 0xff, 0x00,
|
||||
0x00, 0x15, 0x02, 0x08, 0x00, 0x99, 0x99, 0x00, 0x00, 0x18, 0x02, 0x00, 0x00, 0x00, 0x00,
|
||||
0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5f, 0x21, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0xb7, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x18, 0x02, 0x00, 0x00,
|
||||
0x00, 0x00, 0x99, 0x99, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1d, 0x21, 0x01,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x95, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
];
|
||||
|
||||
let packet = &mut [
|
||||
0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x08,
|
||||
0x00, // ethertype
|
||||
0x45, 0x00, 0x00, 0x3b, // start ip_hdr
|
||||
0xa6, 0xab, 0x40, 0x00, 0x40, 0x06, 0x96, 0x0f, 0x7f, 0x00, 0x00, 0x01, 0x7f, 0x00, 0x00,
|
||||
0x01,
|
||||
// Program matches the next two bytes: 0x9999 returns 0xffffffff, else return 0.
|
||||
0x99, 0x99, 0xc6, 0xcc, // start tcp_hdr
|
||||
0xd1, 0xe5, 0xc4, 0x9d, 0xd4, 0x30, 0xb5, 0xd2, 0x80, 0x18, 0x01, 0x56, 0xfe, 0x2f, 0x00,
|
||||
0x00, 0x01, 0x01, 0x08, 0x0a, // start data
|
||||
0x00, 0x23, 0x75, 0x89, 0x00, 0x23, 0x63, 0x2d, 0x71, 0x64, 0x66, 0x73, 0x64, 0x66, 0x0au8,
|
||||
];
|
||||
|
||||
let mut vm = rbpf::EbpfVmFixedMbuff::new(Some(prog), 0x40, 0x50).unwrap();
|
||||
vm.register_helper(helpers::BPF_TRACE_PRINTK_IDX, helpers::bpf_trace_printf)
|
||||
.unwrap();
|
||||
vm.jit_compile().unwrap();
|
||||
|
||||
unsafe {
|
||||
let res = vm.execute_program_jit(packet).unwrap();
|
||||
println!("Program returned: {res:?} ({res:#x})");
|
||||
assert_eq!(res, 0xffffffff);
|
||||
}
|
||||
}
|
||||
|
||||
// Program and memory come from uBPF test ldxh.
|
||||
#[test]
|
||||
fn test_vm_mbuff() {
|
||||
let prog = &[
|
||||
// Load mem from mbuff into R1
|
||||
0x79, 0x11, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, // ldhx r1[2], r0
|
||||
0x69, 0x10, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00,
|
||||
];
|
||||
let mem = &[0xaa, 0xbb, 0x11, 0x22, 0xcc, 0xdd];
|
||||
|
||||
let mbuff = [0u8; 32];
|
||||
unsafe {
|
||||
let mut data = mbuff.as_ptr().offset(8) as *mut u64;
|
||||
let mut data_end = mbuff.as_ptr().offset(24) as *mut u64;
|
||||
data.write_unaligned(mem.as_ptr() as u64);
|
||||
data_end.write_unaligned(mem.as_ptr() as u64 + mem.len() as u64);
|
||||
}
|
||||
|
||||
let vm = rbpf::EbpfVmMbuff::new(Some(prog)).unwrap();
|
||||
assert_eq!(vm.execute_program(mem, &mbuff).unwrap(), 0x2211);
|
||||
}
|
||||
|
||||
// Program and memory come from uBPF test ldxh.
|
||||
#[test]
|
||||
fn test_vm_mbuff_with_rust_api() {
|
||||
use rbpf::insn_builder::*;
|
||||
|
||||
let mut program = BpfCode::new();
|
||||
program
|
||||
.load_x(MemSize::DoubleWord)
|
||||
.set_dst(0x01)
|
||||
.set_src(0x01)
|
||||
.set_off(0x00_08)
|
||||
.push()
|
||||
.load_x(MemSize::HalfWord)
|
||||
.set_dst(0x00)
|
||||
.set_src(0x01)
|
||||
.set_off(0x00_02)
|
||||
.push()
|
||||
.exit()
|
||||
.push();
|
||||
|
||||
let mem = &[0xaa, 0xbb, 0x11, 0x22, 0xcc, 0xdd];
|
||||
|
||||
let mbuff = [0u8; 32];
|
||||
unsafe {
|
||||
let mut data = mbuff.as_ptr().offset(8) as *mut u64;
|
||||
let mut data_end = mbuff.as_ptr().offset(24) as *mut u64;
|
||||
*data = mem.as_ptr() as u64;
|
||||
*data_end = mem.as_ptr() as u64 + mem.len() as u64;
|
||||
}
|
||||
|
||||
let vm = rbpf::EbpfVmMbuff::new(Some(program.into_bytes())).unwrap();
|
||||
assert_eq!(vm.execute_program(mem, &mbuff).unwrap(), 0x2211);
|
||||
}
|
||||
|
||||
// Program and memory come from uBPF test ldxh.
|
||||
#[test]
|
||||
#[cfg(all(not(windows), feature = "std"))]
|
||||
fn test_jit_mbuff() {
|
||||
let prog = &[
|
||||
// Load mem from mbuff into R1
|
||||
0x79, 0x11, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, // ldhx r1[2], r0
|
||||
0x69, 0x10, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00,
|
||||
];
|
||||
let mem = &mut [0xaa, 0xbb, 0x11, 0x22, 0xcc, 0xdd];
|
||||
|
||||
let mut mbuff = [0u8; 32];
|
||||
unsafe {
|
||||
let mut data = mbuff.as_ptr().offset(8) as *mut u64;
|
||||
let mut data_end = mbuff.as_ptr().offset(24) as *mut u64;
|
||||
*data = mem.as_ptr() as u64;
|
||||
*data_end = mem.as_ptr() as u64 + mem.len() as u64;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let mut vm = rbpf::EbpfVmMbuff::new(Some(prog)).unwrap();
|
||||
vm.jit_compile().unwrap();
|
||||
assert_eq!(vm.execute_program_jit(mem, &mut mbuff).unwrap(), 0x2211);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(not(windows), feature = "std"))]
|
||||
#[test]
|
||||
fn test_vm_jit_ldabsb() {
|
||||
let prog = &[
|
||||
0x30, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00,
|
||||
];
|
||||
let mem = &mut [
|
||||
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee,
|
||||
0xff,
|
||||
];
|
||||
let mut vm = rbpf::EbpfVmRaw::new(Some(prog)).unwrap();
|
||||
assert_eq!(vm.execute_program(mem).unwrap(), 0x33);
|
||||
|
||||
vm.jit_compile().unwrap();
|
||||
unsafe {
|
||||
assert_eq!(vm.execute_program_jit(mem).unwrap(), 0x33);
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(all(not(windows), feature = "std"))]
|
||||
#[test]
|
||||
fn test_vm_jit_ldabsh() {
|
||||
let prog = &[
|
||||
0x28, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00,
|
||||
];
|
||||
let mem = &mut [
|
||||
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee,
|
||||
0xff,
|
||||
];
|
||||
let mut vm = rbpf::EbpfVmRaw::new(Some(prog)).unwrap();
|
||||
assert_eq!(vm.execute_program(mem).unwrap(), 0x4433);
|
||||
|
||||
vm.jit_compile().unwrap();
|
||||
unsafe {
|
||||
assert_eq!(vm.execute_program_jit(mem).unwrap(), 0x4433);
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(all(not(windows), feature = "std"))]
|
||||
#[test]
|
||||
fn test_vm_jit_ldabsw() {
|
||||
let prog = &[
|
||||
0x20, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00,
|
||||
];
|
||||
let mem = &mut [
|
||||
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee,
|
||||
0xff,
|
||||
];
|
||||
let mut vm = rbpf::EbpfVmRaw::new(Some(prog)).unwrap();
|
||||
assert_eq!(vm.execute_program(mem).unwrap(), 0x66554433);
|
||||
vm.jit_compile().unwrap();
|
||||
|
||||
unsafe {
|
||||
assert_eq!(vm.execute_program_jit(mem).unwrap(), 0x66554433);
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(all(not(windows), feature = "std"))]
|
||||
#[test]
|
||||
fn test_vm_jit_ldabsdw() {
|
||||
let prog = &[
|
||||
0x38, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00,
|
||||
];
|
||||
let mem = &mut [
|
||||
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee,
|
||||
0xff,
|
||||
];
|
||||
let mut vm = rbpf::EbpfVmRaw::new(Some(prog)).unwrap();
|
||||
assert_eq!(vm.execute_program(mem).unwrap(), 0xaa99887766554433);
|
||||
vm.jit_compile().unwrap();
|
||||
|
||||
unsafe {
|
||||
assert_eq!(vm.execute_program_jit(mem).unwrap(), 0xaa99887766554433);
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "Error: out of bounds memory load (insn #1),")]
|
||||
fn test_vm_err_ldabsb_oob() {
|
||||
let prog = &[
|
||||
0x38, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00,
|
||||
];
|
||||
let mem = &mut [
|
||||
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee,
|
||||
0xff,
|
||||
];
|
||||
let vm = rbpf::EbpfVmRaw::new(Some(prog)).unwrap();
|
||||
vm.execute_program(mem).unwrap();
|
||||
|
||||
// Memory check not implemented for JIT yet.
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "Error: out of bounds memory load (insn #1),")]
|
||||
fn test_vm_err_ldabsb_nomem() {
|
||||
let prog = &[
|
||||
0x38, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00,
|
||||
];
|
||||
let vm = rbpf::EbpfVmNoData::new(Some(prog)).unwrap();
|
||||
vm.execute_program().unwrap();
|
||||
|
||||
// Memory check not implemented for JIT yet.
|
||||
}
|
||||
|
||||
#[cfg(all(not(windows), feature = "std"))]
|
||||
#[test]
|
||||
fn test_vm_jit_ldindb() {
|
||||
let prog = &[
|
||||
0xb7, 0x01, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x50, 0x10, 0x00, 0x00, 0x03, 0x00, 0x00,
|
||||
0x00, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
];
|
||||
let mem = &mut [
|
||||
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee,
|
||||
0xff,
|
||||
];
|
||||
let mut vm = rbpf::EbpfVmRaw::new(Some(prog)).unwrap();
|
||||
assert_eq!(vm.execute_program(mem).unwrap(), 0x88);
|
||||
|
||||
vm.jit_compile().unwrap();
|
||||
unsafe {
|
||||
assert_eq!(vm.execute_program_jit(mem).unwrap(), 0x88);
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(all(not(windows), feature = "std"))]
|
||||
#[test]
|
||||
fn test_vm_jit_ldindh() {
|
||||
let prog = &[
|
||||
0xb7, 0x01, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x48, 0x10, 0x00, 0x00, 0x03, 0x00, 0x00,
|
||||
0x00, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
];
|
||||
let mem = &mut [
|
||||
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee,
|
||||
0xff,
|
||||
];
|
||||
let mut vm = rbpf::EbpfVmRaw::new(Some(prog)).unwrap();
|
||||
assert_eq!(vm.execute_program(mem).unwrap(), 0x9988);
|
||||
|
||||
vm.jit_compile().unwrap();
|
||||
unsafe {
|
||||
assert_eq!(vm.execute_program_jit(mem).unwrap(), 0x9988);
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(all(not(windows), feature = "std"))]
|
||||
#[test]
|
||||
fn test_vm_jit_ldindw() {
|
||||
let prog = &[
|
||||
0xb7, 0x01, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x40, 0x10, 0x00, 0x00, 0x01, 0x00, 0x00,
|
||||
0x00, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
];
|
||||
let mem = &mut [
|
||||
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee,
|
||||
0xff,
|
||||
];
|
||||
let mut vm = rbpf::EbpfVmRaw::new(Some(prog)).unwrap();
|
||||
assert_eq!(vm.execute_program(mem).unwrap(), 0x88776655);
|
||||
vm.jit_compile().unwrap();
|
||||
|
||||
unsafe {
|
||||
assert_eq!(vm.execute_program_jit(mem).unwrap(), 0x88776655);
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(all(not(windows), feature = "std"))]
|
||||
#[test]
|
||||
fn test_vm_jit_ldinddw() {
|
||||
let prog = &[
|
||||
0xb7, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x58, 0x10, 0x00, 0x00, 0x03, 0x00, 0x00,
|
||||
0x00, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
];
|
||||
let mem = &mut [
|
||||
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee,
|
||||
0xff,
|
||||
];
|
||||
let mut vm = rbpf::EbpfVmRaw::new(Some(prog)).unwrap();
|
||||
assert_eq!(vm.execute_program(mem).unwrap(), 0xccbbaa9988776655);
|
||||
vm.jit_compile().unwrap();
|
||||
|
||||
unsafe {
|
||||
assert_eq!(vm.execute_program_jit(mem).unwrap(), 0xccbbaa9988776655);
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "Error: out of bounds memory load (insn #2),")]
|
||||
fn test_vm_err_ldindb_oob() {
|
||||
let prog = &[
|
||||
0xb7, 0x01, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x38, 0x10, 0x00, 0x00, 0x33, 0x00, 0x00,
|
||||
0x00, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
];
|
||||
let mem = &mut [
|
||||
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee,
|
||||
0xff,
|
||||
];
|
||||
let vm = rbpf::EbpfVmRaw::new(Some(prog)).unwrap();
|
||||
vm.execute_program(mem).unwrap();
|
||||
|
||||
// Memory check not implemented for JIT yet.
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "Error: out of bounds memory load (insn #2),")]
|
||||
fn test_vm_err_ldindb_nomem() {
|
||||
let prog = &[
|
||||
0xb7, 0x01, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x38, 0x10, 0x00, 0x00, 0x03, 0x00, 0x00,
|
||||
0x00, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
];
|
||||
let vm = rbpf::EbpfVmNoData::new(Some(prog)).unwrap();
|
||||
vm.execute_program().unwrap();
|
||||
|
||||
// Memory check not implemented for JIT yet.
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "Error: No program set, call prog_set() to load one")]
|
||||
fn test_vm_exec_no_program() {
|
||||
let vm = rbpf::EbpfVmNoData::new(None).unwrap();
|
||||
assert_eq!(vm.execute_program().unwrap(), 0xBEE);
|
||||
}
|
||||
|
||||
fn verifier_success(_prog: &[u8]) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn verifier_fail(_prog: &[u8]) -> Result<(), Error> {
|
||||
Err(Error::new(ErrorKind::Other, "Gaggablaghblagh!"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_verifier_success() {
|
||||
let prog = assemble(
|
||||
"mov32 r0, 0xBEE
|
||||
exit",
|
||||
)
|
||||
.unwrap();
|
||||
let mut vm = rbpf::EbpfVmNoData::new(None).unwrap();
|
||||
vm.set_verifier(verifier_success).unwrap();
|
||||
vm.set_program(&prog).unwrap();
|
||||
assert_eq!(vm.execute_program().unwrap(), 0xBEE);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "Gaggablaghblagh!")]
|
||||
fn test_verifier_fail() {
|
||||
let prog = assemble(
|
||||
"mov32 r0, 0xBEE
|
||||
exit",
|
||||
)
|
||||
.unwrap();
|
||||
let mut vm = rbpf::EbpfVmNoData::new(None).unwrap();
|
||||
vm.set_verifier(verifier_fail).unwrap();
|
||||
vm.set_program(&prog).unwrap();
|
||||
assert_eq!(vm.execute_program().unwrap(), 0xBEE);
|
||||
}
|
2891
kernel/crates/rbpf/tests/ubpf_jit_x86_64.rs
Normal file
2891
kernel/crates/rbpf/tests/ubpf_jit_x86_64.rs
Normal file
File diff suppressed because it is too large
Load Diff
177
kernel/crates/rbpf/tests/ubpf_verifier.rs
Normal file
177
kernel/crates/rbpf/tests/ubpf_verifier.rs
Normal file
@ -0,0 +1,177 @@
|
||||
// SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
// Converted from the tests for uBPF <https://github.com/iovisor/ubpf>
|
||||
// Copyright 2015 Big Switch Networks, Inc
|
||||
// Copyright 2016 6WIND S.A. <quentin.monnet@6wind.com>
|
||||
|
||||
// The tests contained in this file are extracted from the unit tests of uBPF software. Each test
|
||||
// in this file has a name in the form `test_verifier_<name>`, and corresponds to the
|
||||
// (human-readable) code in `ubpf/tree/master/tests/<name>`, available at
|
||||
// <https://github.com/iovisor/ubpf/tree/master/tests> (hyphen had to be replaced with underscores
|
||||
// as Rust will not accept them in function names). It is strongly advised to refer to the uBPF
|
||||
// version to understand what these program do.
|
||||
//
|
||||
// Each program was assembled from the uBPF version with the assembler provided by uBPF itself, and
|
||||
// available at <https://github.com/iovisor/ubpf/tree/master/ubpf>.
|
||||
// The very few modifications that have been realized should be indicated.
|
||||
|
||||
// These are unit tests for the eBPF “verifier”.
|
||||
|
||||
extern crate rbpf;
|
||||
|
||||
use rbpf::{assembler::assemble, ebpf};
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "[Verifier] Error: unsupported argument for LE/BE (insn #0)")]
|
||||
fn test_verifier_err_endian_size() {
|
||||
let prog = &[
|
||||
0xdc, 0x01, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0xb7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
];
|
||||
let vm = rbpf::EbpfVmNoData::new(Some(prog)).unwrap();
|
||||
vm.execute_program().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "[Verifier] Error: incomplete LD_DW instruction (insn #0)")]
|
||||
fn test_verifier_err_incomplete_lddw() {
|
||||
// Note: ubpf has test-err-incomplete-lddw2, which is the same
|
||||
let prog = &[
|
||||
0x18, 0x00, 0x00, 0x00, 0x88, 0x77, 0x66, 0x55, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00,
|
||||
];
|
||||
let vm = rbpf::EbpfVmNoData::new(Some(prog)).unwrap();
|
||||
vm.execute_program().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "[Verifier] Error: infinite loop")]
|
||||
fn test_verifier_err_infinite_loop() {
|
||||
let prog = assemble(
|
||||
"
|
||||
ja -1
|
||||
exit",
|
||||
)
|
||||
.unwrap();
|
||||
let vm = rbpf::EbpfVmNoData::new(Some(&prog)).unwrap();
|
||||
vm.execute_program().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "[Verifier] Error: invalid destination register (insn #0)")]
|
||||
fn test_verifier_err_invalid_reg_dst() {
|
||||
let prog = assemble(
|
||||
"
|
||||
mov r11, 1
|
||||
exit",
|
||||
)
|
||||
.unwrap();
|
||||
let vm = rbpf::EbpfVmNoData::new(Some(&prog)).unwrap();
|
||||
vm.execute_program().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "[Verifier] Error: invalid source register (insn #0)")]
|
||||
fn test_verifier_err_invalid_reg_src() {
|
||||
let prog = assemble(
|
||||
"
|
||||
mov r0, r11
|
||||
exit",
|
||||
)
|
||||
.unwrap();
|
||||
let vm = rbpf::EbpfVmNoData::new(Some(&prog)).unwrap();
|
||||
vm.execute_program().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "[Verifier] Error: jump to middle of LD_DW at #2 (insn #0)")]
|
||||
fn test_verifier_err_jmp_lddw() {
|
||||
let prog = assemble(
|
||||
"
|
||||
ja +1
|
||||
lddw r0, 0x1122334455667788
|
||||
exit",
|
||||
)
|
||||
.unwrap();
|
||||
let vm = rbpf::EbpfVmNoData::new(Some(&prog)).unwrap();
|
||||
vm.execute_program().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "[Verifier] Error: jump out of code to #3 (insn #0)")]
|
||||
fn test_verifier_err_jmp_out() {
|
||||
let prog = assemble(
|
||||
"
|
||||
ja +2
|
||||
exit",
|
||||
)
|
||||
.unwrap();
|
||||
let vm = rbpf::EbpfVmNoData::new(Some(&prog)).unwrap();
|
||||
vm.execute_program().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "[Verifier] Error: program does not end with “EXIT” instruction")]
|
||||
fn test_verifier_err_no_exit() {
|
||||
let prog = assemble(
|
||||
"
|
||||
mov32 r0, 0",
|
||||
)
|
||||
.unwrap();
|
||||
let vm = rbpf::EbpfVmNoData::new(Some(&prog)).unwrap();
|
||||
vm.execute_program().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_verifier_err_no_exit_backward_jump() {
|
||||
let prog = assemble(
|
||||
"
|
||||
ja +1
|
||||
exit
|
||||
ja -2",
|
||||
)
|
||||
.unwrap();
|
||||
let vm = rbpf::EbpfVmNoData::new(Some(&prog)).unwrap();
|
||||
vm.execute_program().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "[Verifier] Error: eBPF program length limited to 1000000, here 1000001")]
|
||||
fn test_verifier_err_too_many_instructions() {
|
||||
// uBPF uses 65637 instructions, because it sets its limit at 65636.
|
||||
// We use the classic 4096 limit from kernel, so no need to produce as many instructions.
|
||||
let mut prog = (0..(1_000_000 * ebpf::INSN_SIZE))
|
||||
.map(|x| match x % 8 {
|
||||
0 => 0xb7,
|
||||
1 => 0x01,
|
||||
_ => 0,
|
||||
})
|
||||
.collect::<Vec<u8>>();
|
||||
prog.append(&mut vec![0x95, 0, 0, 0, 0, 0, 0, 0]);
|
||||
|
||||
let vm = rbpf::EbpfVmNoData::new(Some(&prog)).unwrap();
|
||||
vm.execute_program().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "[Verifier] Error: unknown eBPF opcode 0x6 (insn #0)")]
|
||||
fn test_verifier_err_unknown_opcode() {
|
||||
let prog = &[
|
||||
0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00,
|
||||
];
|
||||
let vm = rbpf::EbpfVmNoData::new(Some(prog)).unwrap();
|
||||
vm.execute_program().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "[Verifier] Error: cannot write into register r10 (insn #0)")]
|
||||
fn test_verifier_err_write_r10() {
|
||||
let prog = assemble(
|
||||
"
|
||||
mov r10, 1
|
||||
exit",
|
||||
)
|
||||
.unwrap();
|
||||
let vm = rbpf::EbpfVmNoData::new(Some(&prog)).unwrap();
|
||||
vm.execute_program().unwrap();
|
||||
}
|
2670
kernel/crates/rbpf/tests/ubpf_vm.rs
Normal file
2670
kernel/crates/rbpf/tests/ubpf_vm.rs
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user