1. kprobes/kretprobes 介紹
1.1 kprobes 介紹
kprobes 主要用來對內核進行調試追蹤, 屬於比較輕量級的機制,,本質上是在指定的探測點(比如函數的某行, 函數的入口地址和出口地址,,或者內核的指定地址處)插入一組處理程序.。內核執行到這組處理程序的時候就可以獲取到當前正在執行的上下文信息, 比如當前的函數名, 函數處理的參數以及函數的返回值,,也可以獲取到寄存器甚至全局數據結構的信息。
1.2 kretprobes 介紹
kretprobes 在 kprobes 的機制上實現,主要用於返回點(比如內核函數或者系統調用的返回值)的探測以及函數執行耗時的計算。
2. bcc實例代碼
下面舉例來介紹 kprobe/kretprobes 在 BPF C 程序中的使用,本實例的功能爲獲取系統中的 IPV4 連接信息。
from __future__ import print_function
from bcc import BPF
# define BPF program
bpf_text = """
#include <uapi/linux/ptrace.h>
#include <net/sock.h>
#include <bcc/proto.h>
BPF_HASH(currsock, u32, struct sock *);
int kprobe__tcp_v4_connect(struct pt_regs *ctx, struct sock *sk)
{
u32 pid = bpf_get_current_pid_tgid();
// stash the sock ptr for lookup on return
currsock.update(&pid, &sk);
return 0;
};
int kretprobe__tcp_v4_connect(struct pt_regs *ctx)
{
int ret = PT_REGS_RC(ctx);
u32 pid = bpf_get_current_pid_tgid();
struct sock **skpp;
skpp = currsock.lookup(&pid);
if (skpp == 0) {
return 0; // missed entry
}
if (ret != 0) {
// failed to send SYNC packet, may not have populated
// socket __sk_common.{skc_rcv_saddr, ...}
currsock.delete(&pid);
return 0;
}
// pull in details
struct sock *skp = *skpp;
u32 saddr = 0, daddr = 0;
u16 dport = 0;
bpf_probe_read(&saddr, sizeof(saddr), &skp->__sk_common.skc_rcv_saddr);
bpf_probe_read(&daddr, sizeof(daddr), &skp->__sk_common.skc_daddr);
bpf_probe_read(&dport, sizeof(dport), &skp->__sk_common.skc_dport);
// output
bpf_trace_printk("trace_tcp4connect %x %x %d\\n", saddr, daddr, ntohs(dport));
currsock.delete(&pid);
return 0;
}
"""
# initialize BPF
b = BPF(text=bpf_text)
# header
print("%-6s %-12s %-16s %-16s %-4s" % ("PID", "COMM", "SADDR", "DADDR",
"DPORT"))
def inet_ntoa(addr):
dq = ''
for i in range(0, 4):
dq = dq + str(addr & 0xff)
if (i != 3):
dq = dq + '.'
addr = addr >> 8
return dq
# filter and format output
while 1:
# Read messages from kernel pipe
try:
(task, pid, cpu, flags, ts, msg) = b.trace_fields()
(_tag, saddr_hs, daddr_hs, dport_s) = msg.split(" ")
except ValueError:
# Ignore messages from other tracers
continue
# Ignore messages from other tracers
if _tag != "trace_tcp4connect":
continue
print("%-6d %-12.12s %-16s %-16s %-4s" % (pid, task,
inet_ntoa(int(saddr_hs, 16)),
inet_ntoa(int(daddr_hs, 16)),
dport_s))
3. 實例代碼分析
3.1 kprobes
語法格式:kprobe__kernel_function_name
其中kprobe__
是前綴,用於給內核函數創建一個kprobe(內核函數調用的動態跟蹤)。也可通過C語言函數定義一個C函數,然後使用 python 的BPF.attach_kprobe()
來關聯到內核函數。
本實例中使用 kprobes 定義了這樣一個函數:
int kprobe__tcp_v4_connect(struct pt_regs * ctx,struct sock * sk)
{
[...]
}
參數如下:
struct pt_regs *ctx
:寄存器和BPF文件;struct sock *sk
:tcp_v4_connect
內核函數的第一個參數。
tcp_v4_connect
在內核中的定義如下:
int tcp_v4_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len);
第一個參數總是 struct pt_regs *
,其餘的參數就是這個內核函數的參數(如果不使用的話,就不用寫了)。
3.2 kretprobes
kretprobes 用於動態跟蹤內核函數的返回,語法如下:
語法格式:kretprobe__kernel_function_name
其中kretprobe__
是前綴,用來創建 kretprobe 對內核函數返回的動態追蹤。也可通過C語言函數定義一個C函數,然後使用 python 的BPF.attach_kprobe()
來關聯到內核函數。
本實例中使用 kretprobes 定義了這樣一個函數:
int kretprobe__tcp_v4_connect(struct pt_regs *ctx)
{
int ret = PT_REGS_RC(ctx);
[...]
}
返回的參數可以在 BPF C 程序中使用PT_REGS_RC()
來獲得,返回值保存在了ret中。
4. 運行結果
使用如下命令運行實例bcc程序,運行結果如圖:
sudo python tcp4connect.py
可以看到 bcc 程序成功獲取到了系統 TCP IPv4連接信息,運行結果中標題解釋如下:
列標題 | 含義 |
---|---|
PID | 進程號 |
COMM | 進程命令行 |
SADDR | 源地址 |
DADDR | 目標地址 |
DPORT | 目標端口 |
參考鏈接:
https://lwn.net/Articles/132196/
https://github.com/iovisor/bcc/blob/master/docs/reference_guide.md