00 kprobes nedir
kprobes, çalışan bir Linux kernel'ında herhangi bir talimata dinamik olarak izleme noktası yerleştirmeye olanak tanıyan bir altyapıdır. ftrace'in compile-time Mcount instrumentation'ından farklı olarak, kprobes çalışma zamanında breakpoint (INT3 / BRK talimatı) kullanır.
Çalışma mekanizması
Hedef fonksiyon: do_sys_open()
┌────────────────────────────────────┐
│ 1. register_kprobe() çağrılır │
│ 2. Kernel, hedef adresteki │
│ talimatı kopyalar │
│ 3. Orijinal talimatı INT3 ile │
│ değiştirir (x86) / BRK (ARM) │
└────────────────────────────────────┘
↓ CPU hedef adrese geldiğinde
┌────────────────────────────────────┐
│ 4. Breakpoint exception oluşur │
│ 5. kprobe handler çağrılır │
│ (pre_handler) │
│ 6. Orijinal talimat çalıştırılır │
│ (single-step veya trampoline) │
│ 7. post_handler çağrılır │
└────────────────────────────────────┘
Gerekli kernel konfigürasyonu
CONFIG_KPROBES=y
CONFIG_KPROBES_ON_FTRACE=y # ftrace ile optimize edilmiş kprobe
CONFIG_KRETPROBES=y # return probe desteği
CONFIG_KALLSYMS=y # sembol adından adres çözümlemesi için
CONFIG_KALLSYMS_ALL=y # tüm semboller (statik olanlar dahil)
CONFIG_KPROBES_ON_FTRACE=y ile, ftrace'in Mcount NOP'larına sahip fonksiyonlarda kprobe, breakpoint yerine ftrace trampoline'ini kullanır. Bu, breakpoint gecikmesini ortadan kaldırır ve overhead'i önemli ölçüde azaltır.
01 kprobe handler — pre/post/fault
Bir kprobe kaydederken üç adet handler fonksiyonu atayabilirsin. Her biri farklı bir noktada çağrılır.
NULL bırakmak güvenlidir.struct kprobe
struct kprobe {
/* Hedef: sembol adı veya adres, biri yeterli */
const char *symbol_name; /* "do_sys_open" */
kprobe_opcode_t *addr; /* ya da doğrudan adres */
int offset; /* fonksiyon başından ofset (byte) */
/* Handler'lar */
kprobe_pre_handler_t pre_handler;
kprobe_post_handler_t post_handler;
/* Dahili — kullanıcı doldurmaz */
kprobe_opcode_t opcode;
struct arch_specific_insn ainsn;
u32 flags;
};
Register/unregister API
#include <linux/kprobes.h>
/* kayıt: 0 başarı, negatif hata kodu */
int register_kprobe(struct kprobe *kp);
/* kayıt silme */
void unregister_kprobe(struct kprobe *kp);
/* dizi hâlinde kayıt */
int register_kprobes(struct kprobe **kps, int num);
void unregister_kprobes(struct kprobe **kps, int num);
02 kretprobe — return value ve latency ölçümü
kretprobe, bir fonksiyonun dönüş noktasına probe ekler. Return value'yu okumak ve fonksiyon süresi (latency) ölçmek için kullanılır.
struct kretprobe
struct kretprobe {
struct kprobe kp; /* temel kprobe — symbol_name buraya */
kretprobe_handler_t handler; /* dönüşte çağrılır */
kretprobe_handler_t entry_handler; /* girişte çağrılır (opsiyonel) */
int data_size; /* instance başına özel veri boyutu */
int maxactive; /* eş zamanlı aktif instance sayısı */
};
API
int register_kretprobe(struct kretprobe *rp);
void unregister_kretprobe(struct kretprobe *rp);
/* handler içinde return değerini oku */
regs_return_value(regs); /* arch bağımsız makro */
/* instance başına veri alanına eriş */
void *get_kretprobe_data(struct kretprobe_instance *ri);
Latency ölçümü için entry_handler'da ktime_get() ile başlangıç zamanını instance veriye yaz; handler'da oku ve farkı hesapla.
maxactive, eş zamanlı olarak izlenebilecek fonksiyon çağrısı sayısını belirler. 0 bırakılırsa kernel NR_CPUS * 2 kullanır. Çok sık çağrılan fonksiyonlarda bu değerin yetersiz kalması durumunda kretprobe bazı çağrıları atlayabilir — nmissed alanı sayar.
03 /sys/kernel/debug/kprobes/ — yönetim arayüzü
Kayıtlı kprobe'ları ve kretprobe'ları sysfs/debugfs üzerinden izleyip yönetebilirsin.
ls /sys/kernel/debug/kprobes/
# blacklist enabled list
# tüm kayıtlı kprobe'ları listele
cat /sys/kernel/debug/kprobes/list
# ffffffff812a4567 k do_sys_open+0x0 [my_kprobe_module]
# ffffffff812a4567 r do_sys_open+0x0 [my_kprobe_module]
# k = kprobe, r = kretprobe
# tüm kprobe'ları devre dışı bırak (etkinleştir için echo 1)
echo 0 > /sys/kernel/debug/kprobes/enabled
# probe edilemeyen (blacklist) fonksiyonlar
cat /sys/kernel/debug/kprobes/blacklist
# ffffffff810a1234 kprobes_jump_mask_area
# ffffffff810b5678 __kprobes_text_start
Kayıtlı kprobe'ların hangi modüle ait olduğu köşeli parantez içinde gösterilir. Modül kaldırıldığında kprobe'lar otomatik olarak silinir — unregister_kprobe() çağrısı module_exit'te yeterlidir.
04 Kprobe kernel modülü — tam C örneği
Aşağıdaki örnek, do_sys_open fonksiyonunun girişini ve çıkışını izleyen, dmesg'e yazan bir kprobe + kretprobe modülüdür.
// SPDX-License-Identifier: GPL-2.0
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/kprobes.h>
#include <linux/ktime.h>
#define TARGET_FUNC "do_sys_open"
/* ── kprobe: fonksiyon girişi ─────────────────────── */
static int pre_handler(struct kprobe *p, struct pt_regs *regs)
{
/* do_sys_open(int dfd, const char __user *filename,
* int flags, umode_t mode)
* x86-64 SysV ABI: rdi=dfd, rsi=filename, rdx=flags */
pr_info("kprobe: %s giriş — flags=0x%lx\n",
TARGET_FUNC, regs->dx);
return 0;
}
static void post_handler(struct kprobe *p, struct pt_regs *regs,
unsigned long flags)
{
pr_info("kprobe: %s sonrası — normal akış\n", TARGET_FUNC);
}
static struct kprobe kp = {
.symbol_name = TARGET_FUNC,
.pre_handler = pre_handler,
.post_handler = post_handler,
};
/* ── kretprobe: dönüş değeri + latency ───────────── */
struct my_data {
ktime_t entry_time;
};
static int entry_handler(struct kretprobe_instance *ri,
struct pt_regs *regs)
{
struct my_data *data = get_kretprobe_data(ri);
data->entry_time = ktime_get();
return 0;
}
static int ret_handler(struct kretprobe_instance *ri,
struct pt_regs *regs)
{
struct my_data *data = get_kretprobe_data(ri);
long retval = regs_return_value(regs);
ktime_t delta = ktime_sub(ktime_get(), data->entry_time);
pr_info("kretprobe: %s döndü — fd=%ld, süre=%lld ns\n",
TARGET_FUNC, retval, ktime_to_ns(delta));
return 0;
}
static struct kretprobe rp = {
.kp.symbol_name = TARGET_FUNC,
.entry_handler = entry_handler,
.handler = ret_handler,
.data_size = sizeof(struct my_data),
.maxactive = 20,
};
/* ── init / exit ─────────────────────────────────── */
static int __init kprobe_demo_init(void)
{
int ret;
ret = register_kprobe(&kp);
if (ret < 0) {
pr_err("register_kprobe başarısız: %d\n", ret);
return ret;
}
ret = register_kretprobe(&rp);
if (ret < 0) {
pr_err("register_kretprobe başarısız: %d\n", ret);
unregister_kprobe(&kp);
return ret;
}
pr_info("kprobe_demo: %s üzerine probe eklendi\n", TARGET_FUNC);
return 0;
}
static void __exit kprobe_demo_exit(void)
{
unregister_kprobe(&kp);
unregister_kretprobe(&rp);
pr_info("kprobe_demo: probe'lar kaldırıldı. nmissed=%d\n",
rp.nmissed);
}
module_init(kprobe_demo_init);
module_exit(kprobe_demo_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Emirhan Pehlevan");
MODULE_DESCRIPTION("kprobe + kretprobe demo — do_sys_open izleme");
Makefile
obj-m := kprobe_demo.o
KDIR ?= /lib/modules/$(shell uname -r)/build
all:
$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KDIR) M=$(PWD) clean
Derleme ve yükleme
make
insmod kprobe_demo.ko
# bir dosya aç ve dmesg çıktısına bak
ls /tmp
dmesg | tail -10
# [12345.678] kprobe: do_sys_open giriş — flags=0x8000
# [12345.678] kretprobe: do_sys_open döndü — fd=5, süre=4231 ns
# [12345.678] kprobe: do_sys_open sonrası — normal akış
rmmod kprobe_demo
05 Trampoline ve blacklist
Her kernel fonksiyonu kprobe ile izlenemez. Bazı fonksiyonlar kprobe altyapısının kendisi tarafından kullanılır ve burada bir probe oluşturmak sonsuz özyinelemeye yol açar.
Neden bazı fonksiyonlar probe edilemez?
kprobe_handler(), do_int3(), do_debug() — bu fonksiyonlara probe eklemek sonsuz döngü oluşturur.__kprobes veya nokprobe_inline ile işaretlenen fonksiyonlar. Blacklist'e otomatik eklenir.Blacklist kontrolü
cat /sys/kernel/debug/kprobes/blacklist
# ffffffff81001234 text_poke
# ffffffff81002345 kprobes_jump_mask_area
# register_kprobe() fonksiyon blacklist'teyse -EINVAL döner
# dmesg'de: "kprobe: Failed to register probe of..."
Bazı sistemlerde kernel parametresi kprobes.enabled=0 ile tüm kprobe'lar devre dışı bırakılabilir. Güvenlik odaklı sistemlerde (CONFIG_SECURITY_LOCKDOWN) kprobe desteği kilitli modda tamamen devre dışı kalabilir.
06 uprobe — userspace fonksiyon probe'u
uprobe, kprobe'un userspace uygulamalarına karşılığıdır. Bir kullanıcı alanı programının belirli bir fonksiyonuna (veya ofsetine) probe ekleyerek kernel'dan izleyebilirsin.
uprobe_events arayüzü
cd /sys/kernel/debug/tracing
# uretprobe (return probe) ekle: bash'in readline fonksiyonu
# format: p:<olay_adı> <binary>:<ofset>
echo 'p:readline_entry /bin/bash:0x4a3f20' >> uprobe_events
echo 'r:readline_ret /bin/bash:0x4a3f20' >> uprobe_events
# sembol adından ofset bul (objdump ile)
objdump -d /bin/bash | grep -A2 '<readline>:'
# 4a3f20: ... (ilk talimat adresi)
# uprobe'u etkinleştir
echo 1 > events/uprobes/readline_entry/enable
echo 1 > tracing_on
# bash başlat, readline tetiklenince kayıt oluşur
cat trace | grep readline
bpftrace ile uprobe
# bpftrace ile basit userspace probe
bpftrace -e 'uprobe:/bin/bash:readline { printf("readline çağrıldı, PID=%d\n", pid); }'
# return değerini oku
bpftrace -e 'uretprobe:/bin/bash:readline { printf("dönüş değeri: %s\n", str(retval)); }'
Position Independent Executable (PIE) olarak derlenen binary'lerde ASLR devredeyse her çalıştırmada adresler değişir. /proc/<pid>/maps veya bpftrace'in sembol çözümleme desteği kullanılarak doğru ofset bulunabilir.
07 eBPF ile kprobe — modern yaklaşım
eBPF (extended Berkeley Packet Filter), kprobe altyapısını güvenli bir sanal makine üzerinden kullanarak kernel modülü yazmadan gelişmiş izleme programları çalıştırmana olanak tanır.
bpftrace ile kprobe
# tcp_connect çağrıldığında PID ve hedef adresi yazdır
bpftrace -e '
kprobe:tcp_connect {
$sk = (struct sock *)arg0;
printf("tcp_connect: PID=%d, daddr=%s\n",
pid,
ntop(AF_INET, $sk->__sk_common.skc_daddr));
}'
# kretprobe: vfs_read'in dönüş değerini izle
bpftrace -e '
kretprobe:vfs_read {
@read_sizes = hist(retval);
}'
# do_sys_open çağrılarında dosya adını yazdır
bpftrace -e '
kprobe:do_sys_open {
printf("open: PID=%d comm=%s file=%s\n",
pid, comm, str(arg1));
}'
BCC (BPF Compiler Collection) ile Python
#!/usr/bin/env python3
from bcc import BPF
prog = r"""
#include
BPF_HASH(latency, u32, u64);
int kprobe__i2c_transfer(struct pt_regs *ctx) {
u32 pid = bpf_get_current_pid_tgid() >> 32;
u64 ts = bpf_ktime_get_ns();
latency.update(&pid, &ts);
return 0;
}
int kretprobe__i2c_transfer(struct pt_regs *ctx) {
u32 pid = bpf_get_current_pid_tgid() >> 32;
u64 *tsp = latency.lookup(&pid);
if (tsp) {
u64 delta = bpf_ktime_get_ns() - *tsp;
bpf_trace_printk("i2c_transfer latency: %llu ns\\n", delta);
latency.delete(&pid);
}
return 0;
}
"""
b = BPF(text=prog)
print("I2C transfer latency izleniyor... (Ctrl+C ile dur)")
b.trace_print()
eBPF programları kernel'ın güvenlik doğrulayıcısından (verifier) geçmek zorundadır — sonsuz döngü, geçersiz bellek erişimi gibi tehlikeli operasyonlar derleme/yükleme aşamasında reddedilir. Kernel modülü kadar güçlü değil ama üretim sistemlerinde kullanmak çok daha güvenlidir.
08 Pratik: network packet drop'u bul
Gerçek dünya senaryosu: UDP paketleri kaybolduğu bildiriliyor ama uygulama log'larında hiçbir şey görünmüyor. kprobe + kretprobe kombinasyonuyla kernel seviyesinde nereden drop olduğunu bul.
Adım 1 — Drop noktalarını belirle
Linux ağ yığınında paket drop'u birkaç farklı noktada gerçekleşebilir. Önce adayları listeleyelim:
# kfree_skb — paket serbest bırakma (drop sinyali)
grep 'kfree_skb' /sys/kernel/debug/tracing/available_filter_functions
# kfree_skb
# kfree_skb_reason
# skb:kfree_skb tracepoint'i var mı?
ls /sys/kernel/debug/tracing/events/skb/
# kfree_skb skb_copy_datagram_iovec
Adım 2 — bpftrace ile kfree_skb izle
# kfree_skb çağrıldığında stack trace yazdır
bpftrace -e '
kprobe:kfree_skb {
@drops[kstack] = count();
}
END {
print(@drops);
}'
Adım 3 — Tam kprobe modülü: UDP drop sayacı
// SPDX-License-Identifier: GPL-2.0
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/kprobes.h>
#include <linux/skbuff.h>
#include <linux/udp.h>
#include <linux/ip.h>
#include <linux/atomic.h>
static atomic64_t drop_count = ATOMIC64_INIT(0);
static int pre_kfree_skb(struct kprobe *p, struct pt_regs *regs)
{
struct sk_buff *skb = (struct sk_buff *)regs->di; /* x86-64: rdi=arg0 */
if (!skb || skb->protocol != htons(ETH_P_IP))
return 0;
if (ip_hdr(skb)->protocol == IPPROTO_UDP) {
long count = atomic64_inc_return(&drop_count);
if (count % 100 == 0) /* her 100'de bir log */
pr_warn("UDP drop #%ld tespit edildi\n", count);
}
return 0;
}
static struct kprobe kp_drop = {
.symbol_name = "kfree_skb",
.pre_handler = pre_kfree_skb,
};
/* kretprobe: udp_rcv'nin başarısız olduğu durumları izle */
static int ret_udp_rcv(struct kretprobe_instance *ri,
struct pt_regs *regs)
{
long ret = regs_return_value(regs);
if (ret != 0)
pr_info("udp_rcv hata: %ld\n", ret);
return 0;
}
static struct kretprobe rp_udp = {
.kp.symbol_name = "udp_rcv",
.handler = ret_udp_rcv,
.maxactive = 32,
};
static int __init udp_drop_init(void)
{
int ret;
ret = register_kprobe(&kp_drop);
if (ret < 0) { pr_err("kfree_skb probe kaydı başarısız: %d\n", ret); return ret; }
ret = register_kretprobe(&rp_udp);
if (ret < 0) {
pr_err("udp_rcv retprobe kaydı başarısız: %d\n", ret);
unregister_kprobe(&kp_drop);
return ret;
}
pr_info("UDP drop izleme başladı\n");
return 0;
}
static void __exit udp_drop_exit(void)
{
unregister_kprobe(&kp_drop);
unregister_kretprobe(&rp_udp);
pr_info("Toplam UDP drop: %lld\n", atomic64_read(&drop_count));
}
module_init(udp_drop_init);
module_exit(udp_drop_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("UDP drop sayacı — kprobe demo");
Çalıştırma ve yorumlama
insmod udp_drop_kprobe.ko
# UDP trafik üret (başka bir terminalde)
iperf3 -c 192.168.1.100 -u -b 100M -t 10
# drop'ları izle
dmesg -W | grep -E 'UDP drop|udp_rcv'
# [ 142.345] UDP drop #100 tespit edildi
# [ 142.891] UDP drop #200 tespit edildi
# [ 143.234] udp_rcv hata: -12 (ENOMEM — socket buffer dolu)
rmmod udp_drop_kprobe
dmesg | tail -2
# [ 155.000] Toplam UDP drop: 347
udp_rcv dönüş değeri -12 (ENOMEM) ise sorun receive buffer boyutundadır. /proc/sys/net/core/rmem_max ve rmem_default değerlerini artırmak çözüm olabilir.
Hatırlanacaklar
- kprobe,
pre_handlervepost_handlerile fonksiyon girişini izler;struct pt_regs'den argümanları okur - kretprobe,
entry_handler+handlerçiftiyle giriş zamanını saklar, dönüşte latency ve return value hesaplar register_kprobe()-EINVALdöndürürse fonksiyon blacklist'tedir/sys/kernel/debug/kprobes/list— aktif kprobe'ları gösterir- eBPF/bpftrace ile kernel modülü yazmadan aynı sonuca ulaşmak mümkün — üretim için daha güvenli
- uprobe ile userspace fonksiyonlarını kernel seviyesinde izleyebilirsin
Bir sonraki adım: printk & dynamic_debug rehberi — kernel loglama ve debug mesaj yönetimi.