第乙個eBPF程式 md

2021-10-21 14:14:48 字數 4110 閱讀 4395

了解bpf的童鞋都應該知道,bpf程式是可以attach到不同的probe點上來做核心級別的trace,那麼對於剛入門的人來說,如何來編寫乙個初級的bpf程式呢?這就是本篇博文想要介紹的內容。

bpf程式我們知道它需要先使用llvm進行編譯,完成後載入到核心中去執行,那麼也就是說對於bpf程式來說,它應該包含有兩部分:

在編寫核心程式時,有乙個固定的格式,假如我們需要新增一些邏輯到乙個probe點上,那麼需要定義乙個入口函式,這個入口函式的格式應該是怎樣的呢?它應該帶有什麼樣的引數呢?

這個格式實際是固定的,它只接受乙個引數struct pt_regs *ctx,而我們想要trace的函式,比如系統呼叫sys_execve,它是有多個引數的:

asmlinkage long sys_execve(const char __user *filename,

const char __user *const __user *ar**,

const char __user *const __user *envp);

那麼我們如何從ctx中來獲取這些引數值呢?想要獲取乙個函式的入口引數,那麼需要的probe型別就要是kprobe因為這種型別會在進入函式後立刻執行我們的bpf函式,那麼引數值上下文都在暫存器中會直接傳入到我們的入口bpf函式中,可以通過如下的函式來獲取引數。核心已經預定義了bpf helper函式來獲取這些引數資訊。

#define pt_regs_parm1(x) ((x)->di)

#define pt_regs_parm2(x) ((x)->si)

#define pt_regs_parm3(x) ((x)->dx)

#define pt_regs_parm4(x) ((x)->cx)

#define pt_regs_parm5(x) ((x)->r8)

#define pt_regs_ret(x) ((x)->sp)

#define pt_regs_fp(x) ((x)->bp)

#define pt_regs_rc(x) ((x)->ax)

#define pt_regs_sp(x) ((x)->sp)

#define pt_regs_ip(x) ((x)->ip)

那麼另外一種情況,如果probe點是乙個tracepoint的話,我們怎麼知道入口函式中能獲取什麼資訊呢?這裡劃重點!!!

比如,sys_enter_execve是核心預定義的乙個tracepoint,那麼如果我們bpf程式被attach到這個位置上,如何知道ctx中都儲存了什麼資訊呢?其實可以通過:

/sys/kernel/debug/tracing/events/syscalls/sys_enter_execve/format
來檢視:

# cat  /sys/kernel/debug/tracing/events/syscalls/sys_enter_execve/format

name: sys_enter_execve

id: 663

format:

field:unsigned short common_type; offset:0; size:2; signed:0;

field:unsigned char common_flags; offset:2; size:1; signed:0;

field:unsigned char common_preempt_count; offset:3; size:1; signed:0;

field:int common_pid; offset:4; size:4; signed:1;

field:int __syscall_nr; offset:8; size:4; signed:1;

field:const char * filename; offset:16; size:8; signed:0;

field:const char *const * ar**; offset:24; size:8; signed:0;

field:const char *const * envp; offset:32; size:8; signed:0;

print fmt: "filename: 0x%08lx, ar**: 0x%08lx, envp: 0x%08lx", ((unsigned long)(rec->filename)), ((unsigned long)(rec->ar**)), ((unsigned long)(rec->envp))

最後的輸出:

field:int __syscall_nr; offset:8;       size:4; signed:1;

field:const char * filename; offset:16; size:8; signed:0;

field:const char *const * ar**; offset:24; size:8; signed:0;

field:const char *const * envp; offset:32; size:8; signed:0;

就是這個tracepoint位置上的引數列表。可以直接定義類似的資料結構來代替struct pt_regs *ctx傳參到入口函式中,實際上是進行了一次強制型別轉換。比如:

struct syscall_execve_args
核心中有另外乙個例子:

借用bcc前端來寫bpf程式,和純c語言寫bpf是由些許差異的,首先對於bcc來說,它把bpf程式的兩個模組,核心程式和使用者態程式合二為一了。

bcc前端可選python語言編寫,這極大提公升了開發效率。那麼python它作為使用者態語言,核心態程式要怎麼寫呢?

其實在python中,是以字串型別來寫核心bpf c語言**,在執行時,會去做編譯和載入的過程。擷取我寫的示例:

#!/usr/bin/python

from __future__ import print_function

from bcc import bpf

import time

# define bpf program

bpf_text = """

#include #include #include #include int syscall__execve(struct pt_regs *ctx,

const char __user *filename,

const char __user *const __user *__ar**,

const char __user *const __user *__envp)

"""# initialize bpf

b = bpf(text=bpf_text)

execve_fnname = b.get_syscall_fnname("execve")

b.attach_kprobe(event=execve_fnname, fn_name="syscall__execve")

start_ts = time.time()

# loop with callback to print_event

while 1:

try:

time.sleep(1);

b.trace_print()

except keyboardinterrupt:

exit()

從上面的片段可知,核心bpf**直接作為了python中的一段string來處理的。

到這裡有些人可能存在疑問了,前面不是說對應的入口函式只能有乙個引數ctx嗎,這裡為什麼傳了3個引數???

實際上這是bcc庫做的一些改造,為了更方便開發者使用syscall的入口引數,對於kprobe來說,bcc允許按照syscall的入口引數來傳參,而在bcc底層處理時,會對這個bpf_text**做轉換操作,詳細資訊可以參考:

今天的關鍵點都記錄到了,先寫到這裡吧。

第乙個視窗程式

程式截圖 程式 include lresult callback wndproc hwnd,uint,wparam,lparam int winapi winmain hinstance hinstance,hinstance hprevinstance,pstr szcmdline,int icm...

第乙個popcap 程式

一 tips ddimage mmapimg ddimage 上f12,定位到ddimage.h,在vs2008 中ddimage.h tab標籤上alt o 定位到ddimage.cpp 二 vs2008 中c c code generation runtime library 設成multi t...

第乙個python 程式

有人在論壇 上問 將日誌格式化的方法,剛好學python,就拿這個練手了 09 55 54 error1 tmp error log.3 50 times mon jun 28 00 00 53 2009 09 55 54 error1 tmp error log.3 50 times 09 56 ...