在之前的bcc代碼中我們知道其程式是分為兩部分的,一部分是C語言,另一部分是基于Python的。本篇是關于C語言部分的。
1.
事件和參數
1.1
kprobes
使用kprobe的文法是:
kprobe__kernel_function_name
其中kprobe__是字首,用于給核心函數建立一個kprobe(核心函數調用的動态跟蹤)。也可通過C語言函數定義一個C函數,然後使用python的BPF.attach_kprobe()來關聯到核心函數。
例如:
int kprobe__tcp_v4_connect(struct pt_regs *ctx, struct sock *sk)
其中參數struct
pt_regs *ctx是寄存器和BPF檔案
sock
*sk是tcp_v4_connect的第一個參數。
1.2
kretprobes
kretprobes動态跟蹤核心函數的傳回,文法如下:
kretprobe__kernel_function_name,字首是kretprobe__。也可以使用python的BPF.attach_kretprobe()來關聯C函數到核心函數。
int kretprobe__tcp_v4_connect(struct pt_regs *ctx)
{ int ret = PT_REGS_RC(ctx); [...]
}
傳回值儲存在ret中。
1.3
Tracepoints
文法如下:TRACEPOINT_PROBE(category,event)
TRACEPOINT_PROBE是一個宏, tracepiont定義的方式是categroy:event,
可以的參數是通過結構體args擷取的,擷取參數相關格式的方法是cat檔案:
/sys/kernel/debug/tracing/events/category/event/format
結構體args可以在函數中使用例如:
TRACEPOINT_PROBE(random, urandom_read) {
// args is
from /sys/kernel/debug/tracing/events/random/urandom_read/format
bpf_trace_printk("%d\\n", args->got_bits); return 0;
1.4
uprobes
通過python的BPF.attach_uprobe()可以将普通C函數關聯到uprobe探針。
參數可以通過PT_REGS_PARM宏來檢測。
程式本身名字使用宏PT_REGS_PARM1,第一個參數使用宏PT_REGS_PARM2。
1.5
uretprobes
同uprobes,隻不過該探針是在函數傳回時候觸發。
傳回的值通過PT_REGS_RC(ctx)擷取,例如:
BPF_HISTOGRAM(dist); int count(struct pt_regs *ctx) { dist.increment(PT_REGS_RC(ctx)); return 0;
在直方圖dist中,根據增加傳回鍵對應的值。
1.6
USDT probes
User
Statically-Defined Tracing使用者靜态定義的探針,會在應用和庫中定義用來提供使用者級别追蹤。BPF中提供對USDT的支援方法是enable_probe。
使用方法同其他probes,定義C函數,然後通過USDT.enable_probe()來關聯USDT probe。
參數可以通過bpf_usdt_readarg(index,ctx,&addr)來讀取。
int do_trace(struct pt_regs *ctx) {
uint64_t addr;
char path[128];
bpf_usdt_readarg(6, ctx, &addr);
bpf_probe_read(&path, sizeof(path), (void *)addr); bpf_trace_printk("path:%s\\n", path);
return 0;
};
1.7
彙總
事件 | C文法 | Python方式 | 參數擷取 |
kprobe__ | BPF.attach_kprobe() | struct pt_regs *ctx | |
kretprobe__ | BPF.attach_kretprobe() | PT_REGS_RC | |
tracepoints | TRACEPOINT_PROBE | BPF.attach_tracepoint() | struct args |
BPF.attach_uprobe() | PT_REGS_PARM | ||
BPF.attach_uretprobe() | |||
USDT.enable_probe | bpf_usdt_readarg |
2.
資料
上節是關于事件和參數的,這邊是看下如何擷取所監控函數中資料。
2.1
bpf_probe_read
文法:int
bpf_probe_read(void *dst, int size, const void *src)
如果成功傳回0.
該函數将内容複制到BPF棧中,用于後續使用。為了安全起見,所有記憶體讀取都通過bpf_probe_read函數。
2.2
bpf_probe_read_str
bpf_probe_read_str(void *dst, int size, const void *src)
複制NULL結尾的字元串到BPF棧,用于後續使用。
2.3
bpf_ktime_get_ns
文法:u64
bpf_ktime_get_ns(void)
擷取目前時間,以納秒方式。
2.4
bpf_get_current_pid_tgid
bpf_get_current_pid_tgid(void)
低32位為目前程序ID,高32位是組ID。
2.5
bpf_get_current_uid_gid
bpf_get_current_uid_gid(void)
傳回使用者ID群組ID.
2.6
bpf_get_current_common
文法:bpf_get_current_comm(char
*buf, int size_of_buf)
用目前程序名字填充第一個參數位址。
2.7
bpf_get_current_task
傳回指向目前task_struct對象的指針。可用于計算CPU線上時間,核心線程,運作隊列和其他相關資訊。
2.8
bpf_log2l
傳回提供值的log-2結果,經常用于建立直方圖索引建構直方圖。
2.9
bpf_get_prandom_u32
傳回一個無符号32位僞随機值。
3.
調試
3.1
bpf_override_return
bpf_override_return(struct pt_regs *, unsigned long rc)
用于關聯到函數入口的kprobe,忽略要執行的函數,傳回rc,用于目标的故障注入。
這個函數在允許故障注入的kprobe白名單中才能工作。白名單在核心代碼樹中會标記BPF_ALOW_ERROR_INJECTION()。
4.
輸出
4.1
bpf_trace_printk
bpf_trace_printk(const char *fmt, int fmt_size, ...)
簡單的核心printf函數,輸出到/sys/kernel/debug/tracing/trace_pipe。這個在之前有描述過,其最多3個參數,智能輸出%s,而且trace_pipe是全局共享的并發輸出會有問題,生産中工具使用BPF_PERF_OUTPUT()函數。
4.2
BPF_PERF_OUTPUT
通過perf ring buffer建立BPF表,将定義的事件資料輸出。這個是将資料推送到使用者态的建議方法。
例如:
struct data_t {
u32 pid;
u64 ts;
char comm[TASK_COMM_LEN];
BPF_PERF_OUTPUT(events);
int hello(struct pt_regs *ctx) {
struct data_t data = {};
data.pid = bpf_get_current_pid_tgid();
data.ts = bpf_ktime_get_ns();
bpf_get_current_comm(&data.comm, sizeof(data.comm));
events.perf_submit(ctx, &data, sizeof(data));
return 0;
代碼中的輸出表是events,資料通過events.perf_submit來推送。
4.3
perf_submit
perf_submit((void *)ctx, (void *)data, u32 data_size)
該函數是BPF_PERF_OUTPUT表的方法,将定義的事件資料推到使用者态。
5.
映射
映射的Maps是BPF資料儲存,是進階對象表、哈希表和直方圖的基礎。
5.1
BPF_TABLE
文法:BPF_TABLE(_table_type,
_key_type, _leaf_type, _name, _max_entries)
建立一個映射名字為_name。大多數時候會被高層宏使用,例如BPF_HASH,BPF_HIST等。
還有map.lookup(),
map.lookup_or_init(), map.delete(), map.update(), map.insert(),
map.increment().
5.2
BPF_HASH
文法BPF_HASH(name [, key_type
[, leaf_type [, size]]])
建立一個name哈希表,中括号中是可選參數。
預設:BPF_HASH(name,
key_type=u64, leaf_type=u64, size=10240)
相關函數:map.lookup(),
5.3
BPF_ARRAY
文法:BPF_ARRAY(name
[, leaf_type [, size]])
建立一個整數索引整列,用于快速查詢和更新。
預設參數如下:BPF_ARRAY(name,
leaf_type=u64, size=10240)
相關函數:map.lookup(),
map.update(), map.increment().
整列中資料是預配置設定的,不能删除,所有沒有删除操作了。
5.4
BPF_HISTOGRAM
文法:BPF_HISTOGRAM(name
[, key_type [, size ]])
建立一個直方圖,預設是由64整型桶索引。
相關函數:map.increment().
5.5
BPF_STACK_TACK
文法:BPF_STACK_TRACE(name,
max_entries)
建立一個棧跟蹤映射,叫做stack_traces。相關函數:map.get_stackid().
5.6
BPF_PERF_ARRAY
文法:BPF_PERF_ARRAY(name,
建立perf array,參數中max_entries保持和系統中CPU數量一緻。這個映射用于擷取硬體性能計數器.
text="""
BPF_PERF_ARRAY(cpu_cycles,
NUM_CPUS);
"""
b =
bcc.BPF(text=text, cflags=["-DNUM_CPUS=%d" % multiprocessing.cpu_count()])
b["cpu_cycles"].open_perf_event(b["cpu_cycles"].HW_CPU_CYCLES)
建立一個名字為cpu_cycles的perf array,入口和cpu數量一緻。
Array配置計數器HW_CPU_CYCLES,後續可以通過map.perf_read函數讀取到。每個表一次隻能配置一種硬體計數器。
5.7
BPF_PERCPU_ARRAY
文法:BPF_PERCPU_ARRAY(name
建立NUM_CPU個整數索引數組,優化了快速查找和更新。每個CPU的數組互相之間不用同步。
5.8
BPF_LPM_TRIE
文法:BPF_LPM_TRIE(name
[, key_type [, leaf_type [, size]]])
建立LPM trie映射。
5.9
BPF_PROG_ARRAY
文法:BPF_PROG_ARRAY(name,
size)
建立映射程式的數組,每個值要麼是一個檔案描述符指向bpf程式要麼是NULL.
這個數組可以作為跳轉表,可以同跳轉到其他的bpf程式。
相關函數:map.call()
5.10 Map.lookup
文法:*val
map.lookup(&key)
尋找map中鍵為key的值,如果存在則傳回指向該健值的指針。
5.11 Map.lookup_or_init
map.lookup_or_init(&key, &zero)
在map中尋找鍵,找到傳回健值的指針,找不到則初始化為第二個參數。
5.12 Map.delete
從map中删除某個健值。
5.13 Map.update
文法:map.update(&key,
&val)
更新健值。
5.14 Map.insert
插入健值。
5.15 Map.increment
增加指定鍵的值,用于直方圖。
5.16 Map.get_stackid
該函數從堆棧中尋找指定參數,傳回棧跟蹤的唯一ID。
5.17 Map.perf_read
從BPF_PERF_ARRAY中定義的數組傳回硬體性能計數。
5.18 Map.call
文法:void
map.call(void *ctx, int index)
調用bpf_tail_call來執行BPF_PROG_ARRAY數值中索引指向的程式,調用方式為tail-call表示不會傳回到原先調用的函數。
BPF_PROG_ARRAY(prog_array, 10);
int tail_call(void *ctx) {
bpf_trace_printk("Tail-call\n");
int do_tail_call(void *ctx) {
bpf_trace_printk("Original program\n");
prog_array.call(ctx, 2);
b = BPF(src_file="example.c")
tail_fn
= b.load_func("tail_call", BPF.KPROBE)
prog_array
= b.get_table("prog_array")
prog_array[c_int(2)] = c_int(tail_fn.fd)
b.attach_kprobe(event="some_kprobe_event", fn_name="do_tail_call")
tail_call()賦給了prog_array[2],在函數do_tail_call()中調用prog_array.call(ctx,2)來調用tail_call()并執行。