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:
linfeng
2024-10-25 15:59:57 +08:00
committed by GitHub
parent 80c9e8f8f0
commit fae6e9ade4
126 changed files with 29529 additions and 62 deletions

View 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 }

View 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
}
}

View 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,
}
}
}

View 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
}
}

View 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
}
}

View File

@ -0,0 +1,7 @@
#![cfg_attr(target_arch = "riscv64", feature(riscv_ext_intrinsics))]
#![no_std]
extern crate alloc;
mod arch;
pub use arch::*;