00 Remote debugging nedir
Gömülü hedefte tam GDB çalıştırmak kaynak ve depolama açısından çoğunlukla mümkün değildir; host GDB + target gdbserver modeli bu problemi çözer.
Gömülü debug problemi
Masaüstü Linux'ta bir programı debug etmek kolaydır: gdb ./myapp yeterlidir. Ancak gömülü hedefte durum farklıdır.
Remote debug modeli
Host (geliştirici makinesi) Target (ARM board) ┌──────────────────────────┐ ┌──────────────────────┐ │ arm-linux-gnueabihf-gdb │ │ gdbserver :1234 │ │ (debug sembolleri burada) │ │ │ │ │ │ │ │ ./myapp (çalışıyor) │ │ target remote │ │ │ │ 192.168.1.100:1234 │◄──TCP───►│ │ └──────────────────────────┘ └──────────────────────┘
gdbserver boyutu
Statik linkli gdbserver, ARM için yaklaşık 1-2 MB disk alanı kaplar. Dinamik linkli versiyon ~400 KB'dır. Hata ayıklama bittikten sonra kaldırılabilir veya yalnızca geliştirme image'larında bulunur.
Debug yöntemleri karşılaştırması
| Yöntem | Granülarite | Donanım | Kullanım |
|---|---|---|---|
| printf debugging | Manuel | Yok | Hızlı prototip, basit sorunlar |
| gdbserver (TCP) | Satır/sembol | Ağ bağlantısı | Genel Linux uygulama debug |
| gdbserver (serial) | Satır/sembol | UART | Ağ yoksa, erken boot |
| JTAG (OpenOCD) | Register/hafıza | JTAG probe | Bare-metal, kernel debug |
| Core dump | Crash anı | Yok | Post-mortem, production crash |
| perf / strace | Performans/syscall | Yok | Performans analizi |
Host gereksinimleri
# Cross GDB kurulumu (Ubuntu/Debian)
apt install gdb-multiarch
# Toolchain spesifik GDB
apt install gcc-arm-linux-gnueabihf
# gdb-multiarch: tek binary, tüm mimariler
gdb-multiarch -q ./myapp
# (gdb) set architecture aarch64
# Toolchain ile:
# arm-linux-gnueabihf-gdb (32-bit ARM)
# aarch64-linux-gnu-gdb (64-bit ARM)
Bu bölümde
- Gömülü debug problemi: kaynak kısıtı, stripped binary, headless
- Remote debug modeli: host GDB + target gdbserver TCP üzerinden
- gdbserver boyutu: ~1-2 MB statik linkli
- Yöntem karşılaştırması: printf, gdbserver, JTAG, core dump
- Host:
gdb-multiarchveya toolchain GDB
01 gdbserver kurulumu ve başlatma
gdbserver, hedef cihazda çalışan uygulamaya GDB Remote Serial Protocol üzerinden erişim sağlar; yeni process başlatabilir veya çalışan bir process'e bağlanabilir.
Kurulum
# Debian/Ubuntu tabanlı sistemlerde
apt install gdbserver
# Buildroot ile dahil etme
# make menuconfig → Target packages → Debugging → gdb → gdbserver
# BR2_PACKAGE_GDB_SERVER=y
# Yocto ile dahil etme
# IMAGE_INSTALL:append = " gdb gdbserver"
# veya: IMAGE_FEATURES += "tools-debug"
# Boyut kontrolü
ls -lh /usr/bin/gdbserver
# -rwxr-xr-x 1 root root 1.4M gdbserver
Yeni process başlatma
# TCP port 1234'te bekle, myapp'i başlat
gdbserver :1234 ./myapp
# Argümanlarla
gdbserver :1234 ./myapp --config /etc/myapp.conf --verbose
# Belirli arayüze bağla
gdbserver 192.168.1.100:1234 ./myapp
# Serial port üzerinden (ağ yokken)
gdbserver /dev/ttyUSB0 ./myapp
gdbserver /dev/ttyAMA0 ./myapp # Raspberry Pi UART
Çalışan process'e bağlanma
# Çalışan process PID'ini bul
pgrep myapp
# 1847
# PID'e attach et
gdbserver :1234 --attach 1847
# Veya inline
gdbserver :1234 --attach $(pgrep myapp)
Multi-process modu
# --multi: birden fazla bağlantıyı kabul eder
# GDB'den "target extended-remote" ile bağlanılır
gdbserver --multi :1234
# Arka planda çalıştır
gdbserver --multi :1234 &
# Extended remote modunda GDB'den yeni process başlat:
# (gdb) target extended-remote 192.168.1.100:1234
# (gdb) set remote exec-file /usr/bin/myapp
# (gdb) run
gdbserver'ı production image'larında bırakmak güvenlik riski oluşturur; herhangi bir GDB bağlantısı process'in tüm hafızasını okuyabilir ve kodu değiştirebilir. Yalnızca geliştirme image'larında kullanın ve firewall ile port 1234'ü kısıtlayın.
Bu bölümde
- Buildroot:
BR2_PACKAGE_GDB_SERVER=y; Yocto:IMAGE_FEATURES += "tools-debug" gdbserver :1234 ./myapp: yeni process başlatmagdbserver :1234 --attach <PID>: çalışan process'e bağlanmagdbserver --multi :1234: çoklu bağlantı modu- Serial port alternatifi:
gdbserver /dev/ttyAMA0 ./myapp
02 Host GDB ile bağlanma
Cross GDB'yi doğru sysroot ve solib yollarıyla yapılandırmak, shared library sembollerinin otomatik yüklenmesi için kritiktir.
Temel bağlantı
# Cross GDB'yi debug semboller içeren binary ile başlat
arm-linux-gnueabihf-gdb ./myapp
# Veya gdb-multiarch
gdb-multiarch ./myapp
# GDB içinde sysroot ve bağlantı
(gdb) set sysroot /path/to/buildroot/output/staging
(gdb) target remote 192.168.1.100:1234
# extended-remote (--multi mod için)
(gdb) target extended-remote 192.168.1.100:1234
sysroot ve solib-search-path
# sysroot: GDB, shared library'leri buradan yükler
# /lib/libc.so.6 → sysroot/lib/libc.so.6
(gdb) set sysroot /opt/buildroot/output/staging
# Alternatif: solib-search-path
(gdb) set solib-search-path /opt/buildroot/output/staging/lib:/opt/buildroot/output/staging/usr/lib
# Yüklenen shared library'leri listele
(gdb) info sharedlibrary
# From To Syms Shared Object Library
# 0xb6f5b000 0xb6f6b000 Yes /lib/ld-linux.so.3
# 0xb6e1a000 0xb6f52000 Yes /lib/libc.so.6
# Otomatik sembol yükleme
(gdb) set auto-solib-add on
.gdbinit dosyası ile otomatik setup
set sysroot /opt/buildroot/output/staging
set solib-search-path /opt/buildroot/output/staging/lib
set architecture arm
set print pretty on
set print array on
set pagination off
arm-linux-gnueabihf-gdb \
-ex "set sysroot /opt/buildroot/output/staging" \
-ex "target remote 192.168.1.100:1234" \
-ex "break main" \
-ex "continue" \
./myapp
Bu bölümde
arm-linux-gnueabihf-gdb ./myapp: cross GDB başlatmaset sysroot /path/to/staging: shared library sembol yolutarget remote host:portvetarget extended-remote- .gdbinit: sysroot ve sık kullanılan ayarların otomasyonu
gdb -ex "...": komut satırından tek adımda bağlanma
03 Debug sembolleri ve strip
Production binary'si strip edilmeli, debug sembolleri ayrı .dbg dosyasında saklanmalı; bu yaklaşım hem flash alanından tasarruf sağlar hem de debug imkânını korur.
Debug sembolü ile derleme
# Maksimum debug bilgisi (-g3: makro bilgileri dahil)
DEBUG_CFLAGS := -g3 -ggdb -O0 -fno-omit-frame-pointer
# Production: optimizasyonlu + debug bilgisi
RELEASE_CFLAGS := -O2 -g -fno-omit-frame-pointer
# -fno-omit-frame-pointer: güvenilir backtrace için önemli
Strip ve ayrı sembol dosyası
# Yöntem 1: objcopy ile sembol dosyası ayır
arm-linux-gnueabihf-objcopy --only-keep-debug myapp myapp.dbg
arm-linux-gnueabihf-strip --strip-debug myapp
arm-linux-gnueabihf-objcopy \
--add-gnu-debuglink=myapp.dbg myapp
# Yöntem 2: eu-strip (elfutils) — daha temiz
arm-linux-gnueabihf-eu-strip -f myapp.dbg myapp
# Sonuç kontrolü
file myapp
# myapp: ELF 32-bit LSB executable, ARM, stripped
file myapp.dbg
# myapp.dbg: ELF 32-bit shared object, ARM, not stripped, with debug_info
GDB'de sembol dosyası yükleme
# Stripped binary ile başlat, sembol dosyasını yükle
arm-linux-gnueabihf-gdb ./myapp
(gdb) symbol-file ./myapp.dbg
(gdb) target remote 192.168.1.100:1234
# Veya başlarken
arm-linux-gnueabihf-gdb -s myapp.dbg myapp
# Build ID eşleşmesini doğrula
eu-readelf -n myapp | grep Build-ID
eu-readelf -n myapp.dbg | grep Build-ID
# İkisi de aynı olmalı
Buildroot ve Yocto sembol ayarları
# Debug sembollerini dahil et (-g3)
BR2_ENABLE_DEBUG=y
BR2_DEBUG_3=y
# Strip stratejisi
BR2_STRIP_strip=y # varsayılan: strip --strip-unneeded
# Debug paketleri dahil et (.dbg alt paketleri)
IMAGE_FEATURES += "dbg-pkgs"
# Tüm paketleri debug sembolle derle
DEBUG_BUILD = "1"
Bu bölümde
- Derleme:
-g3 -ggdb -fno-omit-frame-pointer objcopy --only-keep-debug+strip --strip-debug: sembol ayırma--add-gnu-debuglink: binary ile .dbg arasında bağ- GDB'de
symbol-file myapp.dbgile sembol yükleme eu-readelf -nile Build ID eşleşme doğrulama
04 Temel GDB komutları
GDB'nin temel komut seti, breakpoint koyma, execution kontrolü ve durum inceleme işlemlerini kapsar.
Breakpoint'ler
# Fonksiyon başına breakpoint
(gdb) break main
(gdb) break handle_sensor_data
(gdb) break sensor.c:142 # dosya:satır
(gdb) break *0x00010f34 # adrese breakpoint
# Tüm breakpoint'leri listele
(gdb) info breakpoints
# Breakpoint sil / devre dışı bırak
(gdb) delete 2
(gdb) disable 1
(gdb) enable 1
Execution kontrolü
(gdb) run # yeni process başlat
(gdb) continue # breakpoint'ten devam
(gdb) step # bir satır ilerle (fonksiyona gir)
(gdb) next # bir satır ilerle (fonksiyona girme)
(gdb) finish # mevcut fonksiyondan çık
(gdb) step 5 # 5 satır ilerle
Değer inceleme
# Değişken değeri yazdır
(gdb) print temperature
(gdb) print sensor->value
(gdb) print *buffer@16 # buffer[0..15] array
(gdb) p/x flags # hex formatında
(gdb) p/t bitmask # binary
# Her durdurmada otomatik yazdır
(gdb) display temperature
(gdb) info display
(gdb) undisplay 1
# Watchpoint: değer değişince dur
(gdb) watch temperature
(gdb) watch *0xbffff230
(gdb) rwatch sensor_flag # read watchpoint
Backtrace ve bellek inceleme
# Call stack
(gdb) backtrace # kısa
(gdb) bt full # local değişkenlerle
# Frame geçişi
(gdb) frame 2
(gdb) info locals
(gdb) info args
# Ham bellek inceleme
(gdb) x/10x $sp # stack'ten 10 kelime hex
(gdb) x/20i $pc # PC'den 20 instruction
(gdb) x/s 0x400550 # string olarak oku
# Disassembly
(gdb) disassemble main
(gdb) disassemble /m handle_error # kaynak ile karışık
# Register durumu
(gdb) info registers
(gdb) p/x $r0
Bu bölümde
- Breakpoint:
break func,break file:line,break *addr - Execution:
continue,step,next,finish print/display/watchile değer izlemebt full,info locals,frame Nx/Nfmt addrile ham bellek inceleme
05 Gelişmiş debug teknikleri
Conditional breakpoint'ler, catchpoint'ler, Python scripting ve post-mortem core dump analizi üretim ortamındaki zor hataları çözmede hayat kurtarır.
Conditional breakpoint
# Yalnızca koşul doğruysa dur
(gdb) break sensor.c:87 if temperature > 500
(gdb) break process_packet if packet_id == 42
(gdb) break malloc if size > 1048576
# Mevcut breakpoint'e koşul ekle
(gdb) condition 3 errno != 0
# N kez geçildikten sonra dur
(gdb) ignore 2 100 # breakpoint 2'yi 100 kez atla
Catchpoint
# Sistem çağrısında dur
(gdb) catch syscall write
(gdb) catch syscall open close read
# C++ exception'da dur
(gdb) catch throw
(gdb) catch catch
# fork/exec'te dur
(gdb) catch fork
(gdb) catch exec
Core dump ve post-mortem analiz
# Core dump aktifleştir
ulimit -c unlimited
echo "/data/cores/core.%e.%p.%t" > /proc/sys/kernel/core_pattern
# Core'u host'a kopyala
scp root@192.168.1.100:/data/cores/core.myapp.1847.* .
# GDB ile core analizi
arm-linux-gnueabihf-gdb \
-ex "set sysroot /opt/buildroot/output/staging" \
./myapp.dbg \
core.myapp.1847.1700000000
(gdb) bt full # crash anındaki call stack
(gdb) info locals # local değişkenler
(gdb) print errno
# Batch modda otomatik analiz
arm-linux-gnueabihf-gdb --batch \
-ex "set sysroot /opt/buildroot/output/staging" \
-ex "bt" \
-ex "info locals" \
-ex "quit" \
./myapp.dbg core.myapp.1847.1700000000
Python GDB scripting
import gdb
class SensorDump(gdb.Command):
"""sensor_data yapısını biçimli yazdır."""
def __init__(self):
super().__init__("dump-sensor", gdb.COMMAND_USER)
def invoke(self, arg, from_tty):
frame = gdb.selected_frame()
sensor = frame.read_var("sensor")
print(f"Sıcaklık: {sensor['temperature']}")
print(f"Basınç: {sensor['pressure']}")
print(f"Durum: {sensor['status']}")
SensorDump()
# (gdb) source analyze_crash.py
# (gdb) dump-sensor
Bu bölümde
- Conditional breakpoint:
break file:line if condition - Catchpoint:
catch syscall,catch throw,catch fork - Core dump:
ulimit -c unlimited+core_pattern - Post-mortem:
gdb binary core+bt full - GDB batch modu ile otomatik crash analizi
- Python GDB scripting:
gdb.Commandile özel komut
06 Çoklu thread debugging
Çok thread'li gömülü uygulamalarda GDB, tüm thread'leri inceleyebilir, thread geçişi yapabilir ve deadlock senaryolarını tespit edebilir.
Thread listesi ve geçiş
# Tüm thread'leri listele
(gdb) info threads
# Id Target Id Frame
# 1 Thread 0xb6f99460 (LWP 1847) main () at main.c:42
# * 2 Thread 0xb6598460 (LWP 1848) sensor_read () at sensor.c:87
# 3 Thread 0xb6197460 (LWP 1849) write_log () at log.c:15
# Thread'e geç
(gdb) thread 1
(gdb) thread 3
# Tüm thread backtrace
(gdb) thread apply all backtrace
(gdb) thread apply all bt
# Belirli thread'lerde komut
(gdb) thread apply 1 2 info locals
Scheduler locking ve non-stop
# off: varsayılan — tüm thread'ler devam eder
(gdb) set scheduler-locking off
# on: sadece mevcut thread çalışır
(gdb) set scheduler-locking on
# step: sadece step/next sırasında lock
(gdb) set scheduler-locking step
# Non-stop: bir thread durunca diğerleri çalışmaya devam
(gdb) set non-stop on
(gdb) set pagination off
Deadlock tespiti
# Tüm thread'leri durdur
Ctrl+C
# Tüm thread stack'larini görüntüle
(gdb) thread apply all bt
# Deadlock belirtisi:
# Thread 1: __lll_lock_wait (mutex=0x601234) ← mutex A bekliyor
# Thread 2: __lll_lock_wait (mutex=0x601240) ← mutex B bekliyor
# (Thread 1 mutex B sahibi, Thread 2 mutex A sahibi)
# pthread_mutex_t durumu incele
(gdb) thread 1
(gdb) print mutex_a.__data.__owner # kim tutuyor
(gdb) print mutex_a.__data.__lock # kilitli mi
Bu bölümde
info threads: PID ve frame ile thread listesithread N: thread geçişithread apply all backtrace: tüm thread stack'larıset scheduler-locking on/off/step- Non-stop mode ve deadlock analizi: mutex owner tespiti
07 VS Code ile GDB entegrasyonu
VS Code'un C/C++ extension'ı, gdbserver'a uzaktan bağlanmak için launch.json konfigürasyonu ile GUI tabanlı debug deneyimi sunar.
launch.json konfigürasyonu
{
"version": "0.2.0",
"configurations": [
{
"name": "Remote Debug — Raspberry Pi",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/build/myapp.dbg",
"args": [],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"externalConsole": false,
"MIMode": "gdb",
"miDebuggerPath": "/usr/bin/arm-linux-gnueabihf-gdb",
"miDebuggerServerAddress": "192.168.1.100:1234",
"setupCommands": [
{
"description": "Sysroot ayarla",
"text": "set sysroot /opt/buildroot/output/staging",
"ignoreFailures": false
},
{
"description": "Pretty printing",
"text": "set print pretty on",
"ignoreFailures": true
},
{
"description": "Solib arama yolu",
"text": "set solib-search-path /opt/buildroot/output/staging/lib",
"ignoreFailures": true
}
],
"sourceFileMap": {
"/home/buildbot/build": "${workspaceFolder}"
}
}
]
}
tasks.json ile gdbserver otomasyonu
{
"version": "2.0.0",
"tasks": [
{
"label": "Deploy binary",
"type": "shell",
"command": "scp ${workspaceFolder}/build/myapp root@192.168.1.100:/usr/bin/myapp"
},
{
"label": "Start gdbserver",
"type": "shell",
"command": "ssh root@192.168.1.100 'gdbserver :1234 /usr/bin/myapp' &",
"dependsOn": ["Deploy binary"],
"presentation": { "reveal": "always", "panel": "new" }
}
]
}
sourceFileMap — build path eşleme
// Debug sembolleri farklı bir build path'e atıfta bulunuyorsa
"sourceFileMap": {
"/build/worker/yocto/workspace/sources/myapp":
"${workspaceFolder}/src",
"/build/worker/yocto/tmp/work/armv8a-poky-linux/myapp/1.0-r0/myapp-1.0":
"${workspaceFolder}"
}
Önerilen extension'lar
cppdbg debug provider ile gdbserver entegrasyonu.Bu bölümde
miDebuggerPath: cross GDB binary yolumiDebuggerServerAddress: gdbserver host:portsetupCommands: sysroot ve solib-search-pathsourceFileMap: build path ile workspace path eşleme- tasks.json ile deploy ve gdbserver başlatma otomasyonu
08 Pratik: crash tespiti ve core analizi
NULL pointer dereference, stack overflow ve use-after-free gibi yaygın crash senaryolarını GDB ile tespit etmeyi ve ASAN kombinasyonunu ele alıyoruz.
Test programı — kasıtlı crash'lar
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* Senaryo 1: NULL pointer dereference */
void crash_null_ptr(void)
{
int *p = NULL;
*p = 42; /* SIGSEGV */
}
/* Senaryo 2: Stack overflow */
void crash_stack(int n)
{
char buf[4096];
memset(buf, 'A', sizeof(buf));
crash_stack(n + 1); /* sonsuz recursion */
}
/* Senaryo 3: Use-after-free */
void crash_uaf(void)
{
char *buf = malloc(64);
strcpy(buf, "hello");
free(buf);
buf[0] = 'X'; /* use after free */
}
int main(int argc, char **argv)
{
int s = (argc > 1) ? atoi(argv[1]) : 1;
if (s == 1) crash_null_ptr();
if (s == 2) crash_stack(0);
if (s == 3) crash_uaf();
return 0;
}
Core dump ve GDB analizi
# Debug bilgisiyle derle
arm-linux-gnueabihf-gcc -g3 -fno-omit-frame-pointer \
crash_demo.c -o crash_demo
# Core dump aktifleştir
ulimit -c unlimited
echo "/tmp/core.%e.%p" > /proc/sys/kernel/core_pattern
# Crash 1: NULL pointer
./crash_demo 1
# Segmentation fault (core dumped)
scp root@192.168.1.100:/tmp/core.crash_demo.2001 .
arm-linux-gnueabihf-gdb \
-ex "set sysroot /opt/buildroot/output/staging" \
./crash_demo \
core.crash_demo.2001
(gdb) bt
# #0 crash_null_ptr () at crash_demo.c:9
# #1 main (argc=2, ...) at crash_demo.c:24
(gdb) frame 0
(gdb) info locals
# p = 0x0 ← NULL pointer tespit edildi
(gdb) print p
# $1 = (int *) 0x0
ASAN ile GDB kombinasyonu
# ASAN ile derle
arm-linux-gnueabihf-gcc -g3 -fsanitize=address \
-fno-omit-frame-pointer \
crash_demo.c -o crash_demo_asan
# ASAN hatada core dump üret
ASAN_OPTIONS=abort_on_error=1:disable_coredump=0 \
./crash_demo_asan 3
# ASAN çıktısı:
# ==2047==ERROR: AddressSanitizer: heap-use-after-free
# WRITE of size 1 at 0x602000000010
# #0 in crash_uaf crash_demo.c:21
# #1 in main crash_demo.c:27
Production core dump yapılandırması
[Coredump]
Storage=external
Compress=yes
ProcessSizeMax=256M
ExternalSizeMax=256M
MaxUse=1G
KeepFree=256M
[Manager]
DefaultLimitCORE=infinity
# Core listesi
coredumpctl list
# Son crash detayı
coredumpctl info
# Core dosyasını dışa aktar
coredumpctl dump myapp -o /tmp/myapp.core
# Direkt GDB'ye aç
coredumpctl gdb myapp
Core dump analizinde (No symbol table info available) hatası görülürse binary'nin stripped olduğundan emin olun ve symbol-file myapp.dbg ile sembol dosyasını elle yükleyin. Sembol dosyasının build ID'si binary ile eşleşmelidir: eu-readelf -n myapp | grep Build-ID ile karşılaştırın.
Bu bölümde
- NULL pointer, stack overflow, use-after-free: yaygın crash senaryoları
- Core dump aktifleştirme:
ulimit -c unlimited+core_pattern - Post-mortem:
bt full+info localsile crash noktası tespiti - ASAN:
ASAN_OPTIONS=abort_on_error=1ile core üretimi - Production:
coredump.conf+DefaultLimitCORE=infinity coredumpctl list/dump/gdbile systemd core yönetimi