天天看點

eBPF監控工具bcc系列八BPF C 1.   事件和參數 2.   資料 3.   調試 4.   輸出 5.   映射

在之前的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()并執行。