eBPF筆記(五)—— eBPF Function Calls
Function Calls
????在早期,eBPF 程序不允許調用 helper function 以外的函數。為解決這個問題,可以考慮使用“always inline”
static __always_inline void my_function(void *ctx, int val)
????通過內聯函數的使用,也算是可以解決“can't jump”這個問題.但同時應當注意,在這其中存在一些限制:
????如果從多個位置調用該函數,則會導致該函數指令在已編譯的可執行文件中出現多個副本。有時編譯器可能會選擇內聯函數以進行優化,這也是可能無法將 kprobe 附加到某些內核函數的原因之一。
????從 Linux 4.16 和 LLVM 6.0 開始,取消了要求函數內聯的限制,以便 eBPF 程序員可以更自然地編寫函數調用。但是,這個稱為“BPF to BPF function calls”或“BPF subprograms”的功能目前不受 BCC 框架的支持(當然,如果函數是內聯的,則可以繼續使用 BCC 的函數。在 eBPF 中,還有另一種將復雜功能分解為更小部分的機制:Tail call
Tail Calls
????“tail calls can call and execute another eBPF program and replace the execution context, similar to how the execve() system call operates for regular processes.”
????也就是說,執行完Tail Calls之后,execution并不會返回給調用方。
????尾部調用可以避免在遞歸調用函數時一遍又一遍地向堆棧添加幀,這最終可能導致堆棧溢出錯誤。如果你可以安排你的代碼來調用遞歸函數作為它做的最后一件事,那么與調用函數關聯的堆棧幀實際上并沒有做任何有用的事情。尾部調用允許在不增加堆棧的情況下調用一系列函數。這在堆棧限制為 512 字節的 eBPF 中特別有用。
????尾部調用是使用 bpf_tail_call() 幫助程序函數進行的,函數原型如下:
long bpf_tail_call(void *ctx, struct bpf_map *prog_array_map, u32 index)
????三個參數有如下解釋:
- ctx 允許將上下文從調用 eBPF 程序傳遞給被調用方。
- prog_array_map 是 BPF_MAP_TYPE_PROG_ARRAY 類型的 eBPF 映射,其中包含一組用于標識 eBPF 程序的文件描述符。
- index 指示應調用該group中的哪一個eBPF程序。
????要注意的是,正如前面所說:“replace the execution context”。只要這個helper function執行成功,它就不會返回了。當前正在運行的 eBPF 程序在堆棧上將被正在調用的程序替換。但同時,如果這個函數調用失敗(比如prog_array_map中沒有所指ebpf程序),那么調用方將會繼續執行后續代碼。
????用戶空間代碼必須像往常一樣將所有 eBPF 程序加載到內核中,并且它還設置了prog_array_map。
以下為BCC下的示例代碼:
BPF_PROG_ARRAY(syscall, 300);
int hello(struct bpf_raw_tracepoint_args *ctx) {
int opcode = ctx->args[1];
syscall.call(ctx, opcode);
bpf_trace_printk("Another syscall: %d", opcode);
return 0;
}
int hello_exec(void *ctx) {
bpf_trace_printk("Executing a program");
return 0;
}
int hello_timer(struct bpf_raw_tracepoint_args *ctx) {
int opcode = ctx->args[1];
switch (opcode) {
case 222:
bpf_trace_printk("Creating a timer");
break;
case 226:
bpf_trace_printk("Deleting a timer");
break;
default:
bpf_trace_printk("Some other timer operation");
break;
}
return 0;
}
int ignore_opcode(void *ctx) {
return 0;
}
????首先使用宏BPF_PROG_ARRAY()創建syscall[300],在后續代碼中,index of array為opcode(operation code),the value代表的是函數指針。
????然后就是即將要綁定到 sys_enter tracepoint 的函數hello()。應當注意:傳遞給附加到 raw tracepoint (原始追蹤點)的eBPF程序的上下文采用此bpf_raw_tracepoint_args結構的形式。
????上下文cxt中的args[1]存儲這執行的操作碼,然后執行syscall.call(ctx, opcode);(注意:在將源代碼傳遞給編譯器之前,BCC 將重寫此代碼行以調用 bpf_tail_call() 幫助程序函數),如若有與opcode匹配的program則會完成tail call,并且不會執行bpf_trace_printk("Another syscall: %d", opcode);;否則調用失敗,hello()會繼續執行下去。
????其余函數的作用簡單明了。
????完整python代碼:
點擊查看代碼
#!/usr/bin/python3
from bcc import BPF
import ctypes as ct
program = r"""
BPF_PROG_ARRAY(syscall, 300);
int hello(struct bpf_raw_tracepoint_args *ctx) {
int opcode = ctx->args[1];
syscall.call(ctx, opcode);
bpf_trace_printk("Another syscall: %d", opcode);
return 0;
}
int hello_exec(void *ctx) {
bpf_trace_printk("Executing a program");
return 0;
}
int hello_timer(struct bpf_raw_tracepoint_args *ctx) {
int opcode = ctx->args[1];
switch (opcode) {
case 222:
bpf_trace_printk("Creating a timer");
break;
case 226:
bpf_trace_printk("Deleting a timer");
break;
default:
bpf_trace_printk("Some other timer operation");
break;
}
return 0;
}
int ignore_opcode(void *ctx) {
return 0;
}
"""
b = BPF(text=program)
b.attach_raw_tracepoint(tp="sys_enter", fn_name="hello")
ignore_fn = b.load_func("ignore_opcode", BPF.RAW_TRACEPOINT)
exec_fn = b.load_func("hello_exec", BPF.RAW_TRACEPOINT)
timer_fn = b.load_func("hello_timer", BPF.RAW_TRACEPOINT)
prog_array = b.get_table("syscall")
prog_array[ct.c_int(59)] = ct.c_int(exec_fn.fd)
prog_array[ct.c_int(222)] = ct.c_int(timer_fn.fd)
prog_array[ct.c_int(223)] = ct.c_int(timer_fn.fd)
prog_array[ct.c_int(224)] = ct.c_int(timer_fn.fd)
prog_array[ct.c_int(225)] = ct.c_int(timer_fn.fd)
prog_array[ct.c_int(226)] = ct.c_int(timer_fn.fd)
# Ignore some syscalls that come up a lot
prog_array[ct.c_int(21)] = ct.c_int(ignore_fn.fd)
prog_array[ct.c_int(22)] = ct.c_int(ignore_fn.fd)
prog_array[ct.c_int(25)] = ct.c_int(ignore_fn.fd)
prog_array[ct.c_int(29)] = ct.c_int(ignore_fn.fd)
prog_array[ct.c_int(56)] = ct.c_int(ignore_fn.fd)
prog_array[ct.c_int(57)] = ct.c_int(ignore_fn.fd)
prog_array[ct.c_int(63)] = ct.c_int(ignore_fn.fd)
prog_array[ct.c_int(64)] = ct.c_int(ignore_fn.fd)
prog_array[ct.c_int(66)] = ct.c_int(ignore_fn.fd)
prog_array[ct.c_int(72)] = ct.c_int(ignore_fn.fd)
prog_array[ct.c_int(73)] = ct.c_int(ignore_fn.fd)
prog_array[ct.c_int(79)] = ct.c_int(ignore_fn.fd)
prog_array[ct.c_int(98)] = ct.c_int(ignore_fn.fd)
prog_array[ct.c_int(101)] = ct.c_int(ignore_fn.fd)
prog_array[ct.c_int(115)] = ct.c_int(ignore_fn.fd)
prog_array[ct.c_int(131)] = ct.c_int(ignore_fn.fd)
prog_array[ct.c_int(134)] = ct.c_int(ignore_fn.fd)
prog_array[ct.c_int(135)] = ct.c_int(ignore_fn.fd)
prog_array[ct.c_int(139)] = ct.c_int(ignore_fn.fd)
prog_array[ct.c_int(172)] = ct.c_int(ignore_fn.fd)
prog_array[ct.c_int(233)] = ct.c_int(ignore_fn.fd)
prog_array[ct.c_int(280)] = ct.c_int(ignore_fn.fd)
prog_array[ct.c_int(291)] = ct.c_int(ignore_fn.fd)
b.trace_print()
這些對 b.load_func() 的調用返回每個尾部調用程序的文件描述符。請注意,尾部調用需要具有與其父級相同的程序類型------此例為BPF.RAW_TRACEPOINT。此外,值得指出的是,每個尾部調用程序本身就是一個 eBPF 程序。

浙公網安備 33010602011771號