kprobes/kretprobes 在 bcc 程序中的使用

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 *sktcp_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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章