Post

strace'ten eBPF'e Evrim: Canlı Sistemi Durdurmadan İzlemek

🇹🇷 Production ortamındaki bir veritabanını strace ile durdurmak bir intihar görevi olabilir. Peki, sistemi hiç yavaşlatmadan, kernel'ın her bir sinir ucunu nasıl dinleriz? Linux gözlemlenebilirliğinin (Observability) modern çağı eBPF'e, Kprobes ve Tracepoints dünyasına hoş geldiniz.

strace'ten eBPF'e Evrim: Canlı Sistemi Durdurmadan İzlemek

Bu blog serisinin ilk yazısında strace ile tanışmış, bir önceki yazımızda User Space Dedektifi ltrace ile kütüphanelerin derinliklerine inmiştik. Orada strace için kocaman, kırmızı bir uyarı levhası asmıştık: “Dikkat: Production ortamında kullanmak tehlikeli olabilir.”

Hatırlayalım: strace (ve temeli olan ptrace mekanizması), izlediği programı her bir sistem çağrısında (Syscall) durdurur, veriyi kopyalar ve tekrar çalıştırır. Saniyede 100.000 işlem yapan yoğun bir PostgreSQL sunucusuna strace ile bağlanmak, otobanda giden bir Ferrari’yi her 10 metrede bir durdurup “Ehliyet ruhsat lütfen” demeye benzer. Araba gitmeye devam eder belki ama artık hızı saatte 10 km’ye düşmüştür. Latency (Gecikme) tavan yapar, müşteriler isyan eder.

Peki, Ferrari’yi hiç durdurmadan, motorun iç sıcaklığını, pistonların devrini ve yakıt enjeksiyon milisaniyelerini canlı canlı nasıl izleriz? Hastanın karnını yarmadan (cerrahi müdahale yapmadan), iç organlarının 3 boyutlu haritasını nasıl çıkarırız?

Cevap: eBPF (Extended Berkeley Packet Filter).

Eğer strace bir neşter veya keşif amaçlı ameliyat ise; eBPF, Linux çekirdeğinin MRI (Manyetik Rezonans) cihazıdır. Sistemi durdurmaz. Kesip biçmez. Sadece manyetik alanlarla (Probe’lar) içeriyi tarar, veriyi toplar ve size sunar. Hasta (Production Sunucusu) hiçbir şey hissetmez.

Bu yazıda, “Debugger” zihniyetinden çıkıp “Observability” (Gözlemlenebilirlik) çağına adım atacağız. Kernel’ın içine güvenli sensörler yerleştirmeyi, kprobe ve tracepoint kavramlarını ve Linux’un süper gücü eBPF’i öğreneceğiz.

⚠️ İsimlendirme Uyarısı: “Packet Filter” adına aldanmayın. eBPF artık sadece paket filtrelemiyor. O, Linux çekirdeği içinde çalışan genel amaçlı, olay tabanlı bir sanal makinedir (Universal In-Kernel VM).

1. Evrim: cBPF’ten eBPF’e Geçiş

1992’de tcpdump için geliştirilen Classic BPF (cBPF), sadece ağ paketlerini filtreleyen basit bir mekanizmaydı (2 register, kısıtlı komut seti). 2014’te Alexei Starovoitov bunu Extended BPF (eBPF)‘e dönüştürdü:

  • Register Sayısı: 2’den 10’a çıktı. Bu registerlar modern işlemcilerin (x86_64) registerlarına (RDI, RSI, RDX…) birebir eşlenir. Bu sayede JIT Compiler kodu neredeyse hiç performans kaybı olmadan (Native Speed) çalıştırır.
  • Data Structures: Map yapıları eklendi (Kernel <-> User veri paylaşımı).
  • Tail Calls: Bir BPF programı diğerini çağırabilir hale geldi. Bu değişiklikle BPF, bir “Network Aracı” olmaktan çıkıp, Kernel’ın davranışını değiştirebilen bir Programlama Platformuna dönüştü.

⚠️ Ön Gereksinimler: Bu yazıda anlatılan modern özellikleri (CO-RE, BTF, Ring Buffer) denemek için Linux Kernel 5.8 veya üzeri gerekir. Ayrıca şu paketlerin kurulu olması şarttır: clang, llvm, bpftool, libbpf-dev (veya libbpf-devel).

🧱 2. Zihniyet Devrimi: Context Switch vs In-Kernel VM

strace‘in neden yavaş olduğunu teknik olarak anlamadan eBPF’in değerini anlayamayız.

📚 Teknik Sözlük: User Space vs Kernel Space Linux iki katmandan oluşur. User Space (Halk): Uygulamaların (Chrome, Python, MySQL) çalıştığı, yetkileri kısıtlı alandır. Kernel Space (Devlet): Donanımı yöneten, sınırsız yetkiye sahip çekirdek alandır.

Eski Dünya: ptrace ve Context Switch Cehennemi

Bir program read() çağrısı yaptığında ve strace onu izliyorsa şu olur:

  1. Program: read() çağırır.
  2. Kernel: Programı durdurur (TRAP).

📚 Teknik Sözlük: Syscall (Sistem Çağrısı) Uygulamaların (User Space), donanıma erişmek için (dosya okuma, ağ paketi gönderme) Kernel’dan yardım istediği resmi kapıdır. read(), write(), open() birer syscall’dır.

  1. Context Switch: CPU, programdan strace sürecine geçer.
  2. strace: Veriyi okur, ekrana basar.
  3. Context Switch: CPU, strace‘ten tekrar Kernel’a geçer.
  4. Kernel: Programı tekrar çalıştırır.

Bu “dur-kalk” işlemi (Context Switch), işlemci için çok pahalı bir operasyondur. Bellek haritaları (TLB) temizlenir, önbellekler (Cache) soğur.

📉 Teknik Sözlük: Context Switch (Bağlam Değişimi) CPU’nun o an yaptığı işi (Process) bırakıp, başka bir işe geçmek için mevcut durumu kaydetmesi ve yeni durumu yüklemesi işlemidir.

  • Maliyet: Çok yüksektir. CPU önbellekleri (“sıcak veriler”) boşa çıkar.
  • strace: Her read() için 2 kez context switch yaptırır. Milyonlarca çağrıda sistem kilitlenir.
graph TD
    subgraph PTRACE ["Eski Dünya: ptrace (Yavaş)"]
        App["Uygulama"] -- "1. Syscall" --> Kernel
        Kernel -- "2. Dur!" --> App
        Kernel -- "3. Switch" --> Strace["strace Araç"]
        Strace -- "4. Veriyi Oku" --> Kernel
        Kernel -- "5. Switch" --> App
        
        style Strace stroke:#f44336,stroke-width:2px
    end
graph TD
    subgraph EBPF ["Yeni Dünya: eBPF (Hızlı)"]
        App2["Uygulama"] -- "1. Syscall" --> Kernel2["Kernel + eBPF Sandbox"]
        Kernel2 -- "2. Kaydet & Devam Et" --> App2
        
        style Kernel2 stroke:#4caf50,stroke-width:2px
    end

Yeni Dünya: eBPF ve Sandbox

eBPF yaklaşımı ise tamamen farklıdır. strace gibi dışarıdan bir ajan (Userspace Process) kullanmak yerine, Kernel’ın içine minik bir programcık (Bytecode) göndeririz.

  1. Biz (User space), C veya Python ile küçük bir script yazarız: “Her read çağrıldığında sadece byte sayısını topla.”
  2. Bu script derlenir ve Kernel’a yüklenir.
  3. Verifier (Doğrulayıcı): Kernel, bu kodun güvenli olup olmadığını, sonsuz döngüye girip girmeyeceğini kontrol eder. Güvenliyse kabul eder.
  4. JIT (Just-In-Time) Compiler: Bu kod, işlemcinin anlayacağı makine diline çevrilir.
  5. Çalışma Anı: Program read() çağırdığında, Kernel dışarı çıkmadan, kendi içinde bu minik kodu çalıştırır. Veriyi toplar (Map’e yazar) ve devam eder.

Teknik Sözlük: eBPF Maps Kernel ve User Space arasındaki “Ortak Posta Kutusu”dur. eBPF programı Kernel içinde çalışırken topladığı verileri (örn: byte sayısı, gecikme süresi) Map’e yazar. User Space’teki Python/Go programınız da Map’i okur. İletişim burada gerçekleşir.

🚀 Teknik Sözlük: JIT (Just-In-Time) Compiler Yazdığınız eBPF kodunu (Bytecode), işlemcinin doğrudan anlayacağı makine diline (Native Code) anında çevirir. Bu sayede eBPF scriptleri, Kernel’ın kendi C koduymuş gibi inanılmaz hızlı çalışır. Yorumlanmaz (Interpreted), doğrudan çalıştırılır (Executed).

graph TD
    User["SİZ (User Space)"] -- "C/Python Kodu" --> Verifier{"Verifier (Güvenlik Kontrolü)"}
    Verifier -- "Onay" --> JIT["JIT Compiler (Makine Dili)"]
    JIT --> Kernel["KERNEL (Sandbox)"]
    Kernel -- "Veri Yaz" --> Map[("eBPF Map (Posta Kutusu)")]
    User -- "Veri Oku" --> Map
    
    style Verifier stroke:#ff9800,stroke-width:2px
    style JIT stroke:#2196f3,stroke-width:2px
    style Map stroke:#4caf50,stroke-width:2px,stroke-dasharray: 5 5
    style Kernel stroke:#fff,stroke-width:2px

eBPF Maps: Veri Otobanı

Kernel ve User Space nasıl konuşur? Map yapıları sayesinde.

  • Hash Maps: Anahtar-Değer saklar (Örn: PID -> Process Adı).
  • Ring Buffer: Kernel’dan User Space’e ışık hızında veri akıtır (Örn: Her paket için log).
  • Perf Buffer: CPU başına buffer ayırır.
graph TD
    subgraph Kernel Space
        EbpfCode["eBPF Kodu"] -- "bpf_map_update_elem()" --> Map[("eBPF MAP (Hash/RingBuffer)")]
    end
    
    subgraph User Space
        App["Python/Go Uygulaması"] -- "bpf_map_lookup_elem()" --> Map
    end
    
    style Map stroke:#4caf50,stroke-width:2px

Sonuç: Sıfır Context Switch. Sıfır duraklama. İnanılmaz hız. eBPF, Linux Kernel’ını programlanabilir hale getirir. Kernel’ı yeniden derlemeden, sisteme reset atmadan, canlı yayında Kernel’ın mantığını değiştirebilir veya izleyebilirsiniz.

🧬 2. Sinir Uçlarına Dokunmak: Hooks, Probes ve Tracepoints

eBPF programcıkları havada asılı durmaz. Kernel’daki olaylara (Event) bağlanmaları gerekir. Buna Hooking denir. Tıpkı bir olta iğnesini akıntıya bırakmak gibi. Üç temel olta türümüz vardır:

A. Kprobes (Kernel Probes) - “Dinamik Ajanlar”

Kernel’daki herhangi bir fonksiyonu izleyebilirsiniz. do_sys_open, tcp_sendmsg, vfs_read… Kernel’ın kaynak kodunda gördüğünüz hemen her fonksiyonun başına (kprobe) veya sonuna (kretprobe) bir eBPF sensörü takabilirsiniz.

  • Avantajı: Sonsuz esneklik. Her şeyi izleyebilirsiniz.
  • Dezavantajı: Kernel sürümüne bağımlıdır. Linux 5.4’te adı tcp_sendmsg olan fonksiyon, 5.10’da tcp_sendmsg_locked olabilir. Scriptiniz çalışmayabilir (Unstable API).

B. Tracepoints - “Resmi Duraklar”

Kernel geliştiricileri, kodun içine “Buraya birileri bakmak isteyebilir” diye özel, sabit duraklar koymuştur. sched:sched_switch (Process değişimi), net:netif_receive_skb (Paket gelişi) gibi.

  • Avantajı: Çok kararlıdır (Stable API). Kernel sürümü değişse bile tracepoint isimleri genelde değişmez. Production için en güvenlisi budur.
  • Dezavantajı: Her yerde yoktur. Sadece Kernel geliştiricilerinin koyduğu yerleri izleyebilirsiniz.

C. Uprobes (User Probes) - “Uygulamanın İçine Sızmak”

eBPF sadece Kernel’ı değil, User Space’teki uygulamaları da izleyebilir! MySQL’in içindeki dispatch_command fonksiyonunu veya kendi yazdığınız Go/C++ programındaki hesapla() fonksiyonunu izleyebilirsiniz. GDB gibi ama durdurmadan.

graph TD
    Kernel["KERNEL ÇEKİRDEĞİ"]
    Event1("Tracepoint: Sabit Durak") --> Kernel
    Event2("Kprobe: Dinamik Kanca") --> Kernel
    
    subgraph Hooks ["eBPF Kancaları"]
        Event1
        Event2
    end
    
    style Event1 stroke:#4caf50,stroke-width:2px
    style Event2 stroke:#ff9800,stroke-width:2px

🛠️ 3. Alet Çantası: BCC ve bpftrace

🛠️ 3. Alet Çantası: BCC vs CO-RE (Modern Dönem)

eBPF kodunu (C ile) yazıp Kernel’a yüklemek eskiden tam bir işkenceydi.

1. Eski Okul: BCC (Runtime Compilation)

Yazının önceki kısımlarında bahsettiğimiz biosnoop gibi araçlar BCC (BPF Compiler Collection) kullanır.

  • Çalışma Mantığı: Scripti çalıştırdığınız anda, arka planda C kodunu derler.
  • Sorun: Hedef sunucuda kernel-headers ve LLVM/Clang (yüzlerce MB) kurulu olmalıdır. Production sunucusuna compiler kurmak? Asla.

2. Modern Standart: CO-RE (Compile Once - Run Everywhere)

2020 sonrası endüstri standardı budur. libbpf kütüphanesi ve BTF (BPF Type Format) sayesinde:

  • Kodu kendi makinenizde 1 kere derlersiniz.
  • Ortaya çıkan minik binary’i Production sunucusuna atarsınız.
  • Hedef sunucuda compiler olmasına gerek yoktur. Kernel uyumluluğunu BTF halleder.

Analoji: Mobilya Fabrikası vs IKEA BCC, evinize marangoz atölyesi kurmak gibidir. Bir sandalye lazımsa, ustanın gelip, odunları kesip, evinizin içinde talaş çıkartarak (Compiler) sandalyeyi yapması gerekir. CO-RE ise, IKEA’dan kutu alıp eve gelmek gibidir. Fabrika (Compiler) geliştiricinin bilgisayarındadır. Siz sadece hazır paketi (Binary) alır ve “tak” diye kurarsınız (Relocation). Çok daha temiz ve hızlıdır.

📚 Teknik Sözlük: BTF (BPF Type Format) Kernel veri yapılarının (structs) haritasını çıkaran bir “Metadata” formatıdır. Bu sayede eBPF programı, hangi Kernel sürümünde çalıştığını ve aradığı verinin (örn: pid) bellekte tam olarak nerede olduğunu bilir.

🔧 Kaputun Altı (Relocation): Sizin makinenizde struct task_struct içinde pid alanı 100. offset’te olabilir. Production kernel’ında 104. offset’te olabilir. libbpf, program yüklenirken bu farkı algılar ve koddaki adresleri dinamik olarak kaydırır (Field Relocation). Büyü buradadır.

💡 Tavsiye: Hızlı analiz için bpftrace, profesyonel/production agent yazmak için libbpf + CO-RE kullanın.

💻 4. Kaputun Altı: Gerçek eBPF Kodu (C)

“Script yazıyoruz” dedik ama arka planda aslında C kodu çalışıyor. bpf_printk kullanmak kolaydır ama yavaştır (trace_pipe kullanır). Production ortamında Ring Buffer (Halka Tampon) kullanılır. İşte gerçek bir mühendislik örneği:

🚅 Analoji: Neden Ring Buffer? Veriyi dışarı aktarmak için neden print kullanmıyoruz? Çünkü print (veya trace_pipe) tek şeritli bir gişe gibidir; trafik yoğunlaşınca kuyruk oluşur ve sistem tıkanır. Ring Buffer ise, havalimanındaki bagaj bandı gibidir. Kernel valizi banda koyar, User Space diğer uçtan alır. Duraksama yoktur, kilitlenme (Lock) yoktur, sürekli akan bir şerit vardır.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// hello.bpf.c
#include "vmlinux.h" // Kernel Veri Yapıları
#include <bpf/bpf_helpers.h>

// User Space ile paylaşılacak Map
struct {
    __uint(type, BPF_MAP_TYPE_RINGBUF);
    __uint(max_entries, 256 * 1024);
} rb SEC(".maps");

SEC("kprobe/sys_execve")
int BPF_KPROBE(hello_exec, const char *filename) {
    int *e;
    
    // Ring Buffer'dan yer ayır
    e = bpf_ringbuf_reserve(&rb, sizeof(int), 0);
    if (!e) return 0;

    *e = 42; // Veriyi yaz (Basitlik için sayı yazdık)
    
    // User Space'e gönder
    bpf_ringbuf_submit(e, 0);
    return 0;
}

char LICENSE[] SEC("license") = "GPL";

Bu Kod Nasıl Çalışır?

  1. vmlinux.h: Bu dosya sihirli değildir. bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h komutuyla, o anki Kernel’ın tüm struct yapısını içeren devasa bir başlık dosyası üretilir. Kodunuz bu sayede Kernel’ı tanır.
  2. Loader (Yükleyici): Bu C kodu kendi kendine çalışmaz. User Space’te (Python veya C) bir Loader gerekir. hello_bpf__open_and_load() gibi fonksiyonlar libbpf‘te standart değildir. Bunlar, Skeleton adı verilen otomatik üretilmiş bir header’dan gelir: bpftool gen skeleton hello.bpf.o > hello.skel.h Bu komut, derlenmiş eBPF kodunu (Object file) alır ve onu yönetebileceğiniz bir C header dosyasına (Wrapper) dönüştürür.

User Space tarafı şöyle görünür (Basitleştirilmiş):

1
2
3
4
5
6
7
8
// Loader (User Space)
#include "hello.skel.h" // Skeleton dosyasını ekle

skel = hello_bpf__open_and_load(); // Kodu Yükle
hello_bpf__attach(skel);           // Kancayı Tak
while(1) {
    bpf_ringbuf_consume(rb, process_event, NULL); // Veriyi Çek
}

☁️ Cloud Native İpucu: Yukarıdaki kod tüm process’leri izler. Kubernetes dolu bir node’da bu çok gürültü yaratır. Sadece belirli bir Pod’u izlemek için kodunuza bpf_get_current_pid_tgid() yanına Cgroup ID veya PID Namespace kontrolü ekleyin. Modern eBPF ajanları (Tetragon, Cilium) böyle çalışır.

⚙️ Nasıl Derlenir? Bu mimariyi çalıştırmak için zincirleme bir derleme işlemi gerekir:

  1. eBPF Kodunu Derle (Clang): clang -g -O2 -target bpf -D__TARGET_ARCH_x86 -c hello.bpf.c -o hello.bpf.o
  2. Skeleton Oluştur: bpftool gen skeleton hello.bpf.o > hello.skel.h
  3. Loader’ı Derle ve Linkle (GCC): gcc loader.c -o hello -lbpf -lelf
  4. Çalıştır (Root ile): sudo ./hello

🔍 Debug İpucu: Yazdığınız programın Kernel’a yüklendiğinden emin olmak için bpftool kullanabilirsiniz:

1
2
sudo bpftool prog show
# Çıktıda "hello_exec" ismini ve id'sini görmelisiniz.

Ayrıca map’lerin içini görmek için: sudo bpftool map dump name rb

🛡️ 5. Sadece İzlemek Değil, Yönetmek: XDP (eXpress Data Path)

Linux kernel’ı normalde bir ağ paketini işlemek için onlarca katmandan (Driver -> IP -> TCP -> Socket) geçirir. Bu yavaştır. XDP, paketi daha Kernel’a girmeden, Driver seviyesinde yakalar.

  • DDoS Koruması: Saniyede 20 Milyon paketi DROP edebilirsiniz (Cloudflare bunu kullanır).
  • Load Balancing: Paketi Kernel stack’e sokmadan başka bir sunucuya sektirebilirsiniz (Cilium/Facebook Katran).

Klasik iptables, saniyede 1-2 Milyon paket işlerken, XDP ile 20-25 Milyona çıkarsınız. Çünkü XDP, pakete “Pasaport Kontrolü” sırasında değil, “Uçaktan İner İnmez” müdahale eder.

graph LR
    NIC["Network Kartı"] --> XDP{"XDP Hook (Erken Müdahale)"}
    XDP -- "DROP / REDIRECT" --> Cop("Çöp / Başka Sunucu")
    XDP -- "PASS" --> TC["Traffic Control (tc)"]
    TC --> Netfilter["Netfilter / Iptables (Yavaş Bölge)"]
    Netfilter --> App["Uygulama"]
    
    style XDP stroke:#ff9800,stroke-width:3px
    style Netfilter stroke:#f44336,stroke-width:1px,stroke-dasharray: 5 5

🕵️‍♂️ 6. Vaka Analizi 1: “Disk Yavaş mı Yoksa Program mı Tembel?”

MySQL sunucusu yavaş. Disk I/O’dan şüpheleniyorsunuz. iostat size diskin doluluk oranını (%util) verir ama gecikmenin (latency) ne kadar olduğunu söylemez. Ortalama bir değer verir. strace açsanız sunucu daha da yavaşlayacak.

Çözüm: biosnoop (BCC aracı). Bu araç, Kernel’ın Block I/O katmanına (disk sürücüsü ile dosya sistemi arasına) kanca atar.

1
sudo biosnoop

Çıktı akar:

1
2
3
TIME(s)     COMM           PID    DISK    T  SECTOR    BYTES   LAT(ms)
0.000000    mysqld         1234   sda     R  2048      4096    0.15
0.000400    mysqld         1234   sda     W  4096      8192    12.50

Sağdaki LAT(ms) sütunu, o spesifik I/O işleminin kaç milisaniye sürdüğünü gösterir. Eğer burada sürekli 0.1ms görüyorsanız disk harikadır. Ama arada 100ms’ler görüyorsanız, diskiniz (veya kiraladığınız EBS volume) can çekişiyordur. Bunu strace ile yapsaydınız, o 0.1ms’lik işlem, ptrace overhead’i yüzünden 0.5ms görünecek ve ölçümünüzü sapırttıracaktı. eBPF ile ölçüm, olayı değiştirmez (Heisenberg mutlu).

🕵️‍♂️ 7. Vaka Analizi 2: “TCP Bağlantıları Neden Kopuyor?”

Uygulamanız arada bir “Connection Reset” hatası alıyor. Ama tcpdump açtığınızda o hatayı bir türlü yakalayamıyorsunuz çünkü log dosyaları GB’larca veriyle doluyor. Bize “Sadece kapanan bağlantıları” gösterecek akıllı bir filtre lazım.

Çözüm: tcplife.

1
sudo tcplife

Çıktı:

1
2
PID   COMM       LADDR           LPORT RADDR           RPORT TX_KB RX_KB MS
8542  curl       10.0.0.5        4562  192.168.1.10    80    1     15    25.40

Bu araç, Kernel’ın TCP durum makinesini (State Machine) izler. Bir bağlantı ESTABLISHED durumundan CLOSED durumuna geçtiği an (Kernel içinde), o anı yakalar ve özet bilgiyi (Ne kadar veri gitti, ne kadar sürdü) ekrana basar. Sıfır ek yük ile tüm TCP trafiğinin bir muhasebesini tutarsınız.

🕵️‍♂️ 8. Vaka Analizi 3: “Dosyayı Kim Siliyor?”

Bir log dosyası aniden yok oluyor. rm komutu history’de yok. Kimin sildiğini bulamıyorsunuz. auditd kurabilirsiniz ama ayarlaması zahmetli.

Çözüm: opensnoop veya tek satırlık bpftrace.

1
2
# unlink (dosya silme) syscall'ını izle ve yapanı yaz
sudo bpftrace -e 'tracepoint:syscalls:sys_enter_unlink { printf("%s (%d) siliyor: %s\n", comm, pid, str(args->pathname)); }'

Bu komutu çalıştırıp bekleyin. Dosya silindiği an ekrana düşecektir: logrotate (4521) siliyor: /var/log/myapp.log.1

Faili meçhul cinayet aydınlandı. Logrotate konfigürasyonu hatalıymış.

🛡️ 9. Güvenlik ve Riskler: Kernel Panic Geçirir miyiz?

Linux çekirdeğinin içine kod yüklemek kulağa korkutucu gelir. “Ya hatalı bir kod yazarsam ve Kernel çökerse?” Linux geliştiricileri bunu düşündü. eBPF’in kalbinde Verifier (Doğrulayıcı) vardır.

Verifier şunları kontrol eder:

  1. Sonsuz Döngü Yok: Kodunuz mutlaka bitmelidir. (Aslında modern Kernel’larda sınırlı döngülere izin verilir ama çok sıkı denetlenir).
  2. Bellek Güvenliği: Rastgele bir Kernel belleğini okuyamazsınız. Sadece izin verilen bağlamı (Context) okuyabilirsiniz.
  3. Crash Koruması: Null pointer hatası yaratacak işlemler engellenir.

🚫 Verifier Neden Kızar? (Somut Örnek) Kernel’a sonsuz bir döngü sokarsanız CPU kilitlenir. Bu yüzden Verifier, limit değeri belli olmayan döngülere izin vermez.

  • for (int i=0; i<100; i++): Kabul (Verifier döngünün biteceğini bilir).
  • for (int i=0; i<x; i++): Red (X çok büyük olabilir, sistem kilitlenebilir).

🛡️ Teknik Detay: bpf_probe_read() Normalde bir Kernel modülü, herhangi bir bellek adresini *ptr diyerek okuyabilir. Eğer o adres o an geçersizse, Kernel çöker (Panic). eBPF ise, hafıza okumak için özel bpf_probe_read() fonksiyonunu kullanmak zorundadır. Bu fonksiyon, adresi okumadan önce “Güvenli mi?” diye kontrol eder. Güvenli değilse hata döner, sistemi çökertmez.

👮‍♂️ Analoji: Bodyguard (Verifier) Kernel, çok özel bir gece kulübüdür. İçeride “Sistem İstikrarı” partisi vardır. Verifier, kapıdaki bodyguard’dır. Sizin gönderdiğiniz kodu (müşteri) tepeden tırnağa arar. Üzerinde kesici alet (Sonsuz Döngü) veya yasadışı madde (Invalid Memory Access) varsa, “Kusura bakma, giremezsin” der. İçeri sadece temiz müşteriler girebilir.

Eğer yazdığınız (veya BCC ile gelen) script bu kurallara uymuyorsa, Kernel “Ben bunu yüklemem” der ve reject eder. Bu sayede eBPF, Kernel Modülü (LKM) yüklemekten çok daha güvenlidir. Kernel Modülü, hatasında tüm sistemi (Kernel Panic) çökertebilir. eBPF ise en fazla kendi hata verip çalışmaz.

👋 10. Son Söz: Sisteminizi “Dinleyin”

strace, bir sorunu anlamak için sistemi “dürtmek”ti. eBPF ise sistemi “duymak”tır.

Artık Linux kernel’ı kapalı bir kutu değil. İçinde ne olup bittiğini, paketlerin neden düştüğünü, diskin neden yavaşladığını, hangi process’in hangi dosyaya dokunduğunu mikrosaniye hassasiyetinde, sistemi yormadan izleyebilirsiniz.

Bu blog serisinde, en temel dosya izinlerinden başladık, ağ katmanlarına çıktık, User Space kütüphanelerine daldık ve şimdi Kernel’ın en modern teknolojisiyle zirveye ulaştık.

Artık elinizde bir neşter (strace), bir mikroskop (ltrace) ve bir MRI cihazı (eBPF) var. Hangi hastaya hangisini uygulayacağınız, sizin mühendislik yeteneğinize kalmış.

Sistemi izleyin, ama izlerken değiştirmeyin. Gözlemlenebilirlik (Observability) bir araç değil, bir kültürdür.

Sorunsuz Kernel’lar, temiz trace’ler dilerim.

This post is licensed under CC BY 4.0 by the author.