Kernel Debug Araçları
TEKNİK REHBER KERNEL DEBUG DYNAMIC INSTRUMENTATION 2026

kprobes & kretprobes —
dynamic instrumentation.

Çalışan kernel'ı durdurmadan, herhangi bir fonksiyonun girişine ve çıkışına probe ekle. Kaynak değişikliği yok, yeniden derleme yok — sadece bir kernel modülü.

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

Kconfig
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)
KPROBES_ON_FTRACE

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.

pre_handler
Probe edilen talimat çalışmadan önce çağrılır. Argümanları, register değerlerini ve yığın içeriğini burada okuyabilirsin. 0 döndürürse normal akış devam eder.
post_handler
Probe edilen talimat başarıyla çalıştıktan sonra çağrılır. Single-step sonrası yapılacak işlemler buraya gider.
fault_handler
Single-step sırasında bir memory fault oluşursa çağrılır. Çoğu kprobe bu handler'ı kullanmaz; NULL bırakmak güvenlidir.

struct kprobe

C — linux/kprobes.h
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

C
#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

C — linux/kprobes.h
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

C
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

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.

bash
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
MODÜL BAĞLAMINDA

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.

kprobe_demo.c
// 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

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

bash
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 altyapı fonksiyonları
kprobe_handler(), do_int3(), do_debug() — bu fonksiyonlara probe eklemek sonsuz döngü oluşturur.
__kprobes notrace niteliği
Kernel kaynak kodunda __kprobes veya nokprobe_inline ile işaretlenen fonksiyonlar. Blacklist'e otomatik eklenir.
Interrupt/exception handler'ları
Bazı düşük seviyeli interrupt handler'ları probe edilemez çünkü kprobe'un kendisi de interrupt mekanizmasını kullanır.
Satır içi (inline) fonksiyonlar
Derleyici inline etmişse fonksiyon bağımsız bir adrese sahip değildir; sembole göre probe eklenemez.

Blacklist kontrolü

bash
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..."
DİKKAT — güvenlik kritik fonksiyonlar

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ü

bash
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

bash
# 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)); }'
ASLR

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

bash
# 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

Python — BCC
#!/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 vs Kernel Modülü

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:

bash
# 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

bash
# 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ı

udp_drop_kprobe.c
// 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

bash
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_handler ve post_handler ile 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() -EINVAL dö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.