B.2.1. GDB Kullanım Örnekleri
#include <stdio.h> void bol (int a, int b) { printf("%d / %d = %d\n", a, b, a/b); } /* bol */ int main (void) { int x, y; for (x=170, y=6; x>-170; x-=30, y-=2) bol(x, y); } /* main */
Yukarıda değişik tamsayıları birbirine bölüp sonucu yazan basit bir program
verilmiştir. Ancak program çalıştırıldığında hata alıp sonlanmaktadır.
Problemi gdb
hata düzelticisi ile araştırmak için iki
seçeneğimiz bulunmaktadır: (1) Programı canlı olarak
gdb
altında çalıştırmak. (2) Programı çalıştırıp, hatayı alarak core dump
oluşturmak ve program öldükten
oluşan bu dosyayı gdb
ile incelemek.
Canlı İnceleme
İlk yöntem ile ilgili örnek bir oturumun ekran dökümü aşağıda verilmiştir:
$ gcc -std=c17 -pedantic -Wall -g prog.c ## Programı "-g" ile derle. $ gdb -silent ./a.out ## Programı "gdb" altında yükle. Reading symbols from ./a.out... (gdb) r ## Programı çalıştır. Starting program: …/a.out 170 / 6 = 28 140 / 4 = 35 110 / 2 = 55 Program received signal SIGFPE, Arithmetic exception. 0x000055555555515f in bol (a=80, b=0) at prog.c:5 5 printf("%d / %d = %d\n", a, b, a/b); (gdb) bt #0 0x000055555555515f in bol (a=80, b=0) at prog.c:5 #1 0x00005555555551ab in main () at prog.c:12 (gdb) p main::x $1 = 80 (gdb) p main::y $2 = 0 (gdb) c Continuing. Program terminated with signal SIGFPE, Arithmetic exception. The program no longer exists. (gdb) q
Yukarıdaki ekran dökümünün ilk satırında,
programın -g
seçeneği ile derlendiğine
dikkat edin. Eğer gdb
kullanarak herhangi bir programda hata
düzeltmesi yapılacaksa, derleme sonucunda oluşacak yürütülebilir dosyaya
(örneğin a.out
) uygun bilgileri dahil etmek ve incelemeyi mümkün
kılmak için program derleme komutunda mutlaka -g
seçeneği
kullanılmalıdır. İkinci satırda, derleme sonucunda bulunduğumuz dizinde
oluşan a.out
dosyasını gdb
komutuna
argüman olarak
veriyoruz. Bu şekilde program belleğe yüklenir. Çalıştırmak için dördüncü
satırda run
(veya r
)
komutunu giriyoruz. Program (kısmen) çalışarak bir miktar çıktı üretiyor;
ancak normal sonlanamadan, hata alıp duruyor.
Alınan hata mesajı 10. satırda gdb
tarafından görüntülenmektedir.
Hatanın alındığı bellek adresi programın 5. kaynak satırına denk
gelmektedir ve bu satır bol
fonksiyonunun içindedir. Bu esnada
bol
fonksiyonunun parametrelerinin sırası ile a=80
ve
b=0
olduğunu görüyoruz. gdb
program kaynak kodu
bilgilerine de sahip olduğu için, bu satırın kaynak kodu da ekrana
dökülmektedir.
Hata alındığı sırada, programda nerede olduğumuzu daha detaylı görebilmek
için (çok daha büyük ve karmaşık programlarda bu daha da önemli hale gelir),
geçerli fonksiyon yığınını geriye doğru döken
backtrace
(veya bt
)
komutunu giriyoruz ve bol
fonksiyonunun
main
fonksiyonu tarafından programın 12. kaynak kodu satırında
çağrıldığını görüyoruz. Bu esnada main
fonksiyonunda tanımlı
olan x
ve y
değişkenlerini de kontrol etmek için
print
(veya p
)
komutlarını giriyoruz. Aktif bir fonksiyona ait değişkenleri kullanmak için
fonksiyon_adı::değişken_adı
sözdizimini kullanırız. Eğer sadece değişken adı kullanılmış olsaydı,
sadece içinde bulunulan fonsiyon
(geçerli çerçeve
) olan
bol
tarafından bilinen değişkenler (a
ve
b
) hakkında bilgi alabilecektik.
Ölüm Sonrası İnceleme
İkinci yöntem için, programı komut satırında normal bir şekilde çalıştırıp,
hatayı alırız. Yürütme sırasında alınan hataların detaylı bir incelemesi
yapılabilsin diye, sistem hata alındığı anki programın bellek durumunu bir
dosyaya döker. Buna core dump (çekirdek dökümü) adı verilir.
Ancak, bu tip dosyaların
zamanla sistemde birikmesi disk alanını doldurabileceği ve genelde
sıradan kullanıcıların işine yaramayacağı düşünüldüğünden, birçok sistemde
(örneğin, güncel Ubuntu versiyonlarında) bu özellik kapalı tutulmaktadır.
Core dump oluşumunu mevcut oturum için, yani geçici olarak etkin hale
getirmek için, program çalıştırılmadan önce,
ulimit -c unlimited
işletim sistemi komutu
girilmelidir.
(Bazı sistemlerde, ayrıca kernel.core_pattern
isimli işletim
sistemi çekirdek parametresinin sysctl
komutu ile değiştirilmesi
gerekebilir.) Aşağıda örnek bir oturumun ekran dökümü verilmiştir:
$ gcc -std=c17 -pedantic -Wall -g prog.c ## Programı "-g" ile derle. $ ulimit -c unlimited ## Bu oturum için "core" dosyasına izin ver. $ rm core ## Daha önceden oluşmuş bir "core" dosyası varsa, sil. rm: cannot remove 'core': No such file or directory $ ./a.out ## Programı çalıştır. 170 / 6 = 28 140 / 4 = 35 110 / 2 = 55 Floating point exception (core dumped) $ ls -sh core ## "core" dosyası oluştu mu? 412K core $ gdb -silent ./a.out core ## Programı ve core dosyasını yükle. Reading symbols from ./a.out... [New LWP 31322] Core was generated by `./a.out'. Program terminated with signal SIGFPE, Arithmetic exception. #0 0x00005565d614215f in bol (a=80, b=0) at prog.c:5 5 printf("%d / %d = %d\n", a, b, a/b); (gdb) bt #0 0x00005565d614215f in bol (a=80, b=0) at prog.c:5 #1 0x00005565d61421ab in main () at prog.c:12 (gdb) f 1 #1 0x00005565d61421ab in main () at prog.c:12 12 bol(x, y); (gdb) p x $1 = 80 (gdb) p y $2 = 0 (gdb) q
Beşinci satırda görüldüğü gibi, a.out
programını normal
bir şekilde çalıştırıp, hatayı ve core
dosyasını oluşturuyoruz.
Program kısmen çıktı üretip hatalı bir şekilde sonlanıyor.
Onuncu satırda core
dosyasının oluştuğunu doğruluyoruz.
Onikinci satırda, ilk argüman olarak yine
programın yürütülebilir dosya adını, ikinci argüman olarak ise core
dump dosya adını vererek gdb
komutunu
çalıştırıyoruz.
Bu sefer programı gdb
üzerinde tekrar çalıştırmayıp, doğrudan
durum çözümlemesine geçiyoruz. Benzer komutlarla
(backtrace
, frame 1
,
print
)
çalışan fonksiyonlara ve değişken değerlerine bakıp, doğrudan
quit
(veya q
)
komutu ile gdb
oturumunu
sonlandırıyoruz.