生產環境核心問題往往難以重現,傳統 printk 重編譯核心週期長、風險高。Linux 核心提供 kprobe(動態插桩)機制,配合 SystemTap 高階腳本語言,實現無重啟、無模組重載的動態觀測。
| 類型 | 觸發點 | 適用場景 | 存取上下文 |
|---|---|---|---|
| kprobe | 函數入口 (entry) | 參數檢查、呼叫追蹤、條件觸發 | 函數參數、暫存器 |
| kretprobe | 函數返回 (return) | 返回值檢查、執行時間測量、錯誤碼分析 | 返回值、函數參數 (需 entry handler 保存) |
| jprobe | 函數入口 (鏡像堆疊) | 完整參數結構體存取、複雜邏輯注入 | 完整函數參數結構 (需相同原型) |
原始碼展示 arp_process、ip_route_input_slow、fib_lookup、fib_validate_source 的進出追蹤:
/* 關鍵結構體定義 */
struct kprobe {
struct kprobe_ops *ops;
kprobe_opcode_t *addr;
const char *symbol_name; // 以符號名定位,不依賴位址
unsigned int offset;
kprobe_pre_handler_t pre_handler; // entry
kprobe_post_handler_t post_handler; // return (舊式)
kprobe_fault_handler_t fault_handler;
};
struct kretprobe {
struct kprobe kp;
kretprobe_handler_t handler; // return handler
kretprobe_handler_t entry_handler; // entry handler (可選)
int maxactive; // 並發探針實例上限
int data_size; // entry/return 共享資料大小
};
struct jprobe {
struct kprobe kp;
kprobe_opcode_t *entry; // 鏡像處理函數
// 需 jprobe_return() 返回
};
int register_kprobe(struct kprobe *kp);
int register_kretprobe(struct kretprobe *rp);
int register_jprobe(struct jprobe *jp);
void unregister_kprobe(struct kprobe *kp);
void unregister_kretprobe(struct kretprobe *rp);
void unregister_jprobe(struct jprobe *jp);
// entry handler 保存指標
static int entry_handler(struct kretprobe_instance *ri, struct pt_regs *regs) {
struct fib_result *resp = (struct fib_result *)regs->dx; // 參數在 dx
*((struct fib_result **)ri->data) = resp; // 存入共享區
return 0;
}
// return handler 取用
static int return_handler(struct kretprobe_instance *ri, struct pt_regs *regs) {
struct fib_result *resp = *((struct fib_result **)ri->data);
int ret = regs->ax & 0xffff;
printk("fib_lookup returned %d, type=%d\n", ret, resp->type);
return 0;
}
// 註冊時指定 data_size
static struct kretprobe rp = {
.kp.symbol_name = "fib_lookup",
.entry_handler = entry_handler,
.handler = return_handler,
.data_size = sizeof(struct fib_result *),
};
SystemTap 將探針邏輯編譯為核心模組,提供類 C/Awk 語法:
# stap -e 'probe kernel.function("sys_open").call { printf("%s(%d) open %s\n", execname(), pid(), user_string($filename)) }'
# 完整腳本範例: trace_open.stp
probe kernel.function("sys_open").call {
if (pid() == target())
printf("%s(%d) open(%s, %x, %x)\n", execname(), pid(), user_string($filename), $flags, $mode)
}
probe kernel.function("sys_open").return {
if (pid() == target())
printf(" -> fd = %d\n", $return)
}
# 執行
stap -x $(pgrep nginx) trace_open.stp
kernel.function("name") / .return — 函數進出kernel.trace("tracepoint_name") — 靜態追蹤點syscall.name — 系統呼叫封裝process.begin/end — 程式啟動/結束timer.ms(100) / timer.profile — 定時/效能取樣| 問題 | kprobe 方案 | SystemTap 方案 |
|---|---|---|
| 特定檔案被誰開啟 | kprobe vfs_open + 檔名比對 | probe kernel.function("vfs_open").call { if (user_string($filename) ~ /target/) ... } |
| TCP 連線建立延遲 | kretprobe tcp_v4_connect + 時間戳 | probe kernel.function("tcp_v4_connect").return { @lat[pid()] <<< gettimeofday_us() - @entry[pid()] } |
| 記憶體洩漏 (kmalloc/kfree 不配對) | kprobe kmalloc/kfree 追蹤 call_site | probe kernel.function("kmalloc").return { @alloc[caller] <<< $return } probe kernel.function("kfree") { delete @alloc[caller] } |
| 檔案系統延遲 | kretprobe vfs_read/write + 區塊層追蹤 | probe ioblock.request { @start[dev, sector] = gettimeofday_us() } probe ioblock.end { @lat[dev] <<< gettimeofday_us() - @start[dev, sector] } |
schedule, do_syscall_64) 慎用maxactive 預設根據 CPU 核心數,高併發路徑需調大CAP_SYS_ADMIN 或 kernel.kptr_restrict=0,生產環境建議啟用 lockdown 模式限制Linux 4.1+ 引入 eBPF,提供更安全、可程式化、效能更佳的觀測框架:
# bpftrace 一行指令追蹤 openat
bpftrace -e 'tracepoint:syscalls:sys_enter_openat { printf("%s(%d) open %s\n", comm, pid, str(args->filename)); }'
# 追蹤 TCP 重傳
bpftrace -e 'kretprobe:tcp_retransmit_skb { @[comm] = count(); } interval:s:5 { print(@); clear(@); }'
# 工具生態
bcc-tools (opensnoop, execsnoop, biolatency, tcplife, runqlat)
bpftrace (腳本語言)
cilium/ebpf (Go 庫)
systemtap 4.0+ 可選用 eBPF 後端
kprobe/SystemTap 是核心工程師的「X 光機」,但門檻高、風險大。現代生產環境優先採用 eBPF/bpftrace/bcc:無需核心模組編譯、安全沙箱、豐富工具生態、支援 Kubernetes 叢集級觀測。保留 kprobe 技能用於:核心開發除錯、舊核心 (pre-4.1) 環境、極度底層路徑 (eBPF 尚不支援)。
Shell 重導向深度解析:為什麼要寫 2>&1 以及順序的重要性
Nginx 支援 .htaccess 實現偽靜態:Include 引入機制、Rewrite 規則轉換與生產級避坑
Tomcat 內存參數調優:Linux/Windows 環境完整配置指南
CentOS 5/6/7 完整安裝配置 VNC Server 教學(含防火牆、多用戶、系統服務)
Linux 將 Socket 綁定到指定網路介面 (SO_BINDTODEVICE) 實作
Linux 端口監聽實戰指南:netstat 與 nmap 核心用法詳解