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

printk & dynamic_debug —
kernel loglama.

KERN_DEBUG'dan KERN_EMERG'e log seviyesi hiyerarşisi, dmesg ile ring buffer analizi ve üretim kernel'ında debug mesajlarını sıfır yeniden derlemeyle açan dynamic_debug altyapısı.

00 printk temelleri — log seviyeleri

printk, kernel'ın temel loglama fonksiyonudur. userspace'deki printf()'e benzer ama kernel'ın kendi ring buffer'ına yazar ve seviye (log level) bilgisi taşır.

Log seviyeleri

MakroDeğerAnlamıÖrnek durum
KERN_EMERG0Sistem kullanılamazKernel panic
KERN_ALERT1Derhal müdahale gerekliKritik donanım hatası
KERN_CRIT2Kritik durumRAID degrade
KERN_ERR3Hata durumlarıDriver initialization başarısız
KERN_WARNING4Uyarı — dikkat gerektirirKaynak azalması
KERN_NOTICE5Normal ama önemliAygıt bağlandı
KERN_INFO6Bilgi mesajıDriver yüklendi, parametre değerleri
KERN_DEBUG7Debug mesajlarıGeliştirici izleme bilgisi

printk kullanım biçimleri

C — kernel kodu
#include <linux/printk.h>

/* Eski biçim — seviye string olarak önek */
printk(KERN_ERR "i2c_init: donanım bulunamadı, hata=%d\n", ret);
printk(KERN_INFO "my_driver: v1.0 yüklendi, base=0x%08x\n", base_addr);

/* Modern biçim — pr_* makroları (önerilen) */
pr_emerg("kritik donanım hatası!\n");
pr_alert("acil müdahale gerekiyor\n");
pr_crit("kritik bölge hatası\n");
pr_err("başlatma başarısız: %d\n", ret);
pr_warn("kaynaklar azalıyor, kalan=%d\n", free_pages);
pr_notice("ağ arayüzü %s bağlandı\n", ifname);
pr_info("sürücü yüklendi: irq=%d, port=%d\n", irq, port);
pr_debug("__func__=%s, satır=%d, değer=%d\n", __func__, __LINE__, val);

pr_fmt — modül öneki

C — kernel kodu
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/printk.h>

/* Artık tüm pr_* çıktıları "my_driver: " önekiyle başlar */
pr_info("başlatıldı\n");
/* dmesg: [  12.345] my_driver: başlatıldı */

pr_err("donanım yanıt vermedi\n");
/* dmesg: [  12.567] my_driver: donanım yanıt vermedi */
console_loglevel

Konsola (seri port, framebuffer vb.) yalnızca console_loglevel'dan daha düşük seviyedeki mesajlar yazılır. Ring buffer'a ise tüm mesajlar gider. Varsayılan: 7 (INFO). Debug mesajlarını konsolda görmek için: echo 8 > /proc/sys/kernel/printk

01 dmesg ve journalctl — ring buffer okuma

dmesg, kernel log ring buffer'ını okuyan kullanıcı alanı aracıdır. Systemd kullanan sistemlerde journalctl de kernel mesajlarına erişim sağlar.

dmesg temel kullanım

bash
# tüm ring buffer içeriğini yazdır
dmesg

# insan okunabilir zaman damgasıyla
dmesg -T
# [Nis 12 14:32:15] usb 1-1: new high-speed USB device

# gerçek zamanlı izle (tail -f gibi)
dmesg -w

# sadece belirli seviyeleri göster
dmesg --level err,warn
dmesg --level crit,alert,emerg

# belirli kelimeyi ara
dmesg | grep -i 'i2c'

# boot'tan bu yana geçen süreyi sıfırla (yeni boot referansı)
dmesg -c   # okur ve temizler (root gerektirir)

journalctl — systemd entegrasyonu

bash
# sadece kernel mesajları
journalctl -k
journalctl --dmesg

# son boot'tan bu yana kernel mesajları
journalctl -k -b

# bir önceki boot (crash sonrası analiz için)
journalctl -k -b -1

# gerçek zamanlı izle
journalctl -k -f

# sadece hata seviyesi ve üzeri
journalctl -k -p err

# zaman aralığı filtresi
journalctl -k --since "2026-04-12 10:00" --until "2026-04-12 11:00"

# JSON formatında çıktı (otomasyon için)
journalctl -k -o json | python3 -m json.tool | grep MESSAGE
GÖMÜLÜ SISTEMLER

Birçok gömülü sistem systemd kullanmaz. Bu durumda dmesg yeterlidir. Minimal sistemlerde /proc/kmsg'den direkt okuma veya netconsole ile uzak log toplama tercih edilebilir.

02 printk ring buffer — yapı ve yapılandırma

printk, kernel mesajlarını döngüsel (ring) bir tampona yazar. Buffer dolduğunda en eski mesajların üzerine yazılır. Boyut ve davranış kernel konfigürasyonuyla ayarlanır.

Buffer boyutunu yapılandır

Kconfig
# Ring buffer boyutu: 2^CONFIG_LOG_BUF_SHIFT bayt
CONFIG_LOG_BUF_SHIFT=17    # 128 KB (varsayılan)
CONFIG_LOG_BUF_SHIFT=20    # 1 MB  (yoğun debug için önerilen)
CONFIG_LOG_BUF_SHIFT=21    # 2 MB
bash — çalışma zamanı
# mevcut ring buffer boyutunu öğren
dmesg --buffer-size
# 131072  (128 KB)

# /proc/kmsg — raw kernel log (bir process'in tutması gerekir)
cat /proc/kmsg &   # arka planda çalıştır
# [  12.345678] kernel: my_driver: başlatıldı

# /dev/kmsg — pozisyon takibi yapan versiyon
cat /dev/kmsg

netconsole — uzak log toplama

Flash alanı kısıtlı gömülü sistemlerde kernel mesajlarını ağ üzerinden uzak bir makineye göndermek mümkündür:

bash
# Hedef sistemde netconsole modülünü yükle
# format: src_port@src_ip/src_iface,dst_port@dst_ip/dst_mac
modprobe netconsole netconsole=6666@192.168.1.100/eth0,514@192.168.1.200/ff:ff:ff:ff:ff:ff

# Geliştirme makinesinde dinle
nc -ul 514
# veya
socat UDP-RECVFROM:514,fork STDOUT

printk zaman damgası formatı

Kconfig
# Mesajlara monotonic timestamp ekle (önerilen)
CONFIG_PRINTK_TIME=y

# Zaman damgası çözünürlüğü: nanosaniye (5.10+)
CONFIG_PRINTK_CALLER=y  # çağıran CPU ve görevi göster

03 dynamic_debug — kalıcı debug mesajları

dynamic_debug, kaynak koduna yerleştirilen pr_debug() ve dev_dbg() çağrılarını üretim kernelinde devre dışı bırakarak derler; ardından çalışma zamanında belirli dosya, fonksiyon veya satırlar için seçici olarak etkinleştirir.

Neden dynamic_debug?

  Geleneksel yaklaşım:
  debug için #ifdef DEBUG ekle → derle → test et → #ifdef kaldır → yeniden derle
  ↕  yavaş, hata yapımına açık, üretim kodu farklılaşır

  dynamic_debug:
  pr_debug() yaz → her zaman derlensin → üretimde NOP olarak çalışsın
  → sorun çıktığında çalışma zamanında: echo "file spi.c +p" > control
  → debug mesajları akar → sorun çözülünce: echo "file spi.c -p" > control
    

Gerekli konfigürasyon

Kconfig
CONFIG_DYNAMIC_DEBUG=y
CONFIG_DYNAMIC_DEBUG_CORE=y  # 5.1+ kernel'larda ayrı seçenek

Kaynak kodunda kullanım

C — kernel kodu
#include <linux/dynamic_debug.h>

/* pr_debug — dynamic_debug ile kontrol edilir */
pr_debug("register okuması: addr=0x%02x val=0x%02x\n", addr, val);

/* dev_dbg — cihaz bağlamıyla */
dev_dbg(&pdev->dev, "SPI transfer tamamlandı: %d byte\n", len);

/* print_hex_dump_debug — buffer dump */
print_hex_dump_debug("SPI TX: ", DUMP_PREFIX_OFFSET, 16, 1,
                     tx_buf, tx_len, true);
DERLEME ZAMANI DAVRANIŞI

CONFIG_DYNAMIC_DEBUG=y ile pr_debug() çağrıları derlenip özel bir ELF section'ına (__verbose) yerleştirilir. Çalışma zamanında bu section okunarak hangi çağrıların aktif olduğu takip edilir. CONFIG_DYNAMIC_DEBUG kapalıysa ve DEBUG makrosu da tanımlı değilse, tüm pr_debug() çağrıları derleme sırasında tamamen silinir.

04 dynamic_debug kontrol arayüzü

dynamic_debug'un tüm kontrolü /sys/kernel/debug/dynamic_debug/control dosyası üzerinden yapılır. Belirli bir kelime birleşimine uyan debug noktaları seçici olarak açılıp kapanabilir.

Kontrol dosyası formatı

bash
# mevcut tüm debug noktalarını listele
cat /sys/kernel/debug/dynamic_debug/control | head -20
# drivers/i2c/i2c-core-base.c:847 [i2c_core]i2c_transfer =_ "i2c transfer..."
# drivers/spi/spi.c:1023 [spi]spi_sync =_ "SPI transfer..."
# ^dosya:satır  [modül]fonksiyon  =durum "mesaj"
# durum: _ = kapalı, p = aktif

# Belirli dosyayı etkinleştir
echo 'file drivers/i2c/i2c-core-base.c +p' > /sys/kernel/debug/dynamic_debug/control

# Belirli modülü etkinleştir
echo 'module i2c_core +p' > /sys/kernel/debug/dynamic_debug/control

# Belirli fonksiyonu etkinleştir
echo 'func i2c_transfer +p' > /sys/kernel/debug/dynamic_debug/control

# Satır numarasıyla
echo 'file spi.c line 1023 +p' > /sys/kernel/debug/dynamic_debug/control

# Tüm kernel'da debug mesajlarını etkinleştir (dikkatli!)
echo 'module * +p' > /sys/kernel/debug/dynamic_debug/control

# Devre dışı bırak
echo 'module i2c_core -p' > /sys/kernel/debug/dynamic_debug/control

Bayraklar

+p / -p
Mesajı yazdır (print). En sık kullanılan bayrak.
+f / -f
Mesaja fonksiyon adı ekle.
+l / -l
Mesaja satır numarası ekle.
+m / -m
Mesaja modül adı ekle.
+t / -t
Mesaja thread ID ekle.
+_ / -_
Tüm bayrakları sıfırla.

Boot parametresiyle etkinleştirme

kernel cmdline
# /boot/grub/grub.cfg veya u-boot bootargs
# Boot sırasında i2c modülü debug'u etkinleştir
dyndbg="module i2c_core +p; module spi +p"

# Sadece belirli dosya
dyndbg="file drivers/spi/spi.c +p"

05 dev_dbg() ailesi — cihaza bağlı loglama

dev_* makroları, her mesaja struct device referansını ekler. Bu sayede log çıktısında hangi cihazdan geldiği bellidir ve dynamic_debug ile cihaz bazlı filtreleme yapılabilir.

dev_* makroları

C — kernel kodu
#include <linux/device.h>

struct device *dev = &pdev->dev;  /* ya da &client->dev, &spi->dev vb. */

/* Seviye hiyerarşisi */
dev_emerg(dev, "kritik donanım arızası\n");
dev_alert(dev, "acil durum\n");
dev_crit(dev, "ciddi hata\n");
dev_err(dev, "başlatma hatası: %d\n", ret);
dev_warn(dev, "beklenmeyen durum: state=%d\n", state);
dev_notice(dev, "bağlantı kuruldu\n");
dev_info(dev, "başlatıldı: irq=%d, base=0x%lx\n", irq, base);
dev_dbg(dev, "register okundu: 0x%02x = 0x%02x\n", reg, val);

Örnek dmesg çıktısı

dmesg çıktısı
[   12.345678] i2c i2c-1: bme280 0x76: başlatıldı: irq=32, base=0x0
[   12.345912] spi spi0.0: flash@0: başlatıldı: irq=45, base=0xfe200000
[   12.346100] platform my-uart: başlatıldı: irq=57, base=0xfe201000
# cihaz adı (bus:address) otomatik ön ek olarak eklenir

dev_dbg ile dynamic_debug

bash
# belirli bir cihazın debug mesajlarını etkinleştir
# (dynamic_debug cihaz adına göre filtreleme yapar)
echo 'func bme280_probe +p' > /sys/kernel/debug/dynamic_debug/control

# veya modül bazında
echo 'module bme280 +p' > /sys/kernel/debug/dynamic_debug/control

# dmesg'de görünür:
# [  145.678] bme280 1-0076: register 0xD0 = 0x60 (chip_id OK)
dev_err_probe()

Probe fonksiyonunda -EPROBE_DEFER döndürülmesi durumunu sessizce yutmak ve diğer hataları dev_err ile loglamak için kullanılır: return dev_err_probe(dev, ret, "irq isteği başarısız\n");

06 Ratelimited loglama — burst koruması

Hızla tekrar eden bir hata durumunda printk, ring buffer'ı tek bir hata mesajıyla doldurabilir ve diğer kritik mesajların kaybolmasına yol açabilir. Ratelimited varyantlar bunu önler.

Ratelimited makrolar

C — kernel kodu
#include <linux/ratelimit.h>

/* Varsayılan ratelimit: 5 saniyede en fazla 10 mesaj */
pr_err_ratelimited("SPI transfer hatası: %d\n", ret);
pr_warn_ratelimited("düşük bellek, kalan=%d KB\n", free_kb);
dev_err_ratelimited(dev, "I2C NAK alındı, adr=0x%02x\n", addr);

/* Özel limit tanımla */
static DEFINE_RATELIMIT_STATE(my_rs, 10 * HZ, 5);
/* 10 saniyede en fazla 5 mesaj */

if (__ratelimit(&my_rs))
    pr_err("özel ratelimit: CRC hatası blok=%llu\n", block_nr);

Ratelimit davranışı

dmesg çıktısı
[  100.001] SPI transfer hatası: -110
[  100.002] SPI transfer hatası: -110
[  100.003] SPI transfer hatası: -110
[  100.004] SPI transfer hatası: -110
[  100.005] SPI transfer hatası: -110
[  100.006] SPI transfer hatası: -110
[  100.007] SPI transfer hatası: -110
[  100.008] SPI transfer hatası: -110
[  100.009] SPI transfer hatası: -110
[  100.010] SPI transfer hatası: -110
[  100.011] printk: 847 messages suppressed.
[  105.001] SPI transfer hatası: -110
# 5 saniye sonra tekrar başlar
DİKKAT — ratelimit ve debug

Ratelimit mekanizması üretim için doğru yaklaşımdır, ancak debug sırasında mesajların bastırılması hatanın gizlenmesine yol açabilir. Debug modunda pr_err() (ratelimitsiz) kullan, sorun çözüldükten sonra pr_err_ratelimited()'a geç.

printk_once ve printk_deferred

C — kernel kodu
# Yalnızca bir kez yazdır (tüm sistem ömrü boyunca)
pr_err_once("bu uyarı yalnızca bir kez görünür\n");
dev_warn_once(dev, "eski API kullanımı tespit edildi\n");

# Ertelenmiş yazdırma — atomic bağlamdan güvenli çağrı
printk_deferred(KERN_ERR "atomic context'ten mesaj\n");

07 Pratik: driver debug workflow

Gerçek dünya senaryosu: bir SPI flash driver, probe sırasında başarısız oluyor ama hata mesajı yeterince ayrıntılı değil. Sıfır yeniden derlemeyle debug workflow'u.

Başlangıç durumu — yetersiz hata mesajı

dmesg çıktısı
[    2.345678] spi-nor spi0.0: unrecognized JEDEC id bytes: ff, ff, ff
[    2.345900] spi-nor: probe of spi0.0 failed with error -22

Adım 1 — dynamic_debug ile spi-nor modülünü aç

bash
# spi-nor modülündeki tüm pr_debug çağrılarını etkinleştir
echo 'module spi_nor +p' > /sys/kernel/debug/dynamic_debug/control

# modülü yeniden yükle (veya cihazı bağla)
modprobe -r spi_nor && modprobe spi_nor

# ya da sysfs üzerinden aygıtı yeniden probe et
echo spi0.0 > /sys/bus/spi/drivers/spi-nor/unbind 2>/dev/null
echo spi0.0 > /sys/bus/spi/drivers/spi-nor/bind

Adım 2 — Ayrıntılı debug çıktısını incele

dmesg çıktısı
[    5.123456] spi spi0.0: spi_nor_read_id: CS assert
[    5.123512] spi spi0.0: spi_nor_read_id: RDID cmd=0x9F sent
[    5.123534] spi spi0.0: spi_nor_read_id: response: ff ff ff ff
[    5.123556] spi spi0.0: SPI clock divider=4, actual=12.5 MHz
# ff ff ff → MISO hattı sürekli yüksek → CS veya MISO bağlantı sorunu

Adım 3 — Elektriksel sorun teyidi ve çözüm

Tüm yanıtların 0xFF olması, MISO hattının her zaman yüksek (pull-up) olduğunu gösteriyor. Sürücü debugu bizi doğrudan fiziksel katmana yönlendirdi. Bağlantı düzeltildikten sonra:

dmesg çıktısı — düzeltme sonrası
[    2.345678] spi spi0.0: spi_nor_read_id: response: ef 40 18
[    2.345700] spi-nor spi0.0: w25q128jv (16384 Kbytes)
[    2.346000] spi-nor spi0.0: mtd .name = spi0.0, .size = 0x01000000

Adım 4 — Dynamic debug'u kapat

bash
# Sorun çözüldü, debug mesajlarını kapat
echo 'module spi_nor -p' > /sys/kernel/debug/dynamic_debug/control

# Teyit et
grep 'spi_nor' /sys/kernel/debug/dynamic_debug/control | grep -c '=p'
# 0  (hiçbir mesaj aktif değil)

Hatırlanacaklar

  • pr_err(), pr_warn(), pr_info(), pr_debug() — modern pr_* makroları, printk(KERN_*)'ten tercih edilir
  • #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt — her mesaja modül öneki ekler
  • dmesg -T okunabilir zaman, dmesg --level err,warn seviye filtresi
  • CONFIG_DYNAMIC_DEBUG=y ile pr_debug() mesajları çalışma zamanında açılabilir
  • echo 'module X +p' > .../dynamic_debug/control — modül bazlı etkinleştirme
  • dev_err(), dev_dbg() — mesajlara cihaz adı ekler, sürücü loglaması için standart
  • pr_err_ratelimited() — tekrarlayan hatalarda ring buffer'ı korur

Bir sonraki adım: KASAN & sanitizer rehberi — memory hataları ve undefined behavior tespiti.