GNU Debugger
Bu bölümde hata ayıklayıcı olarak, GNU sistemlerinde standart olan, GDB (GNU Debugger) uygulamasını inceleyeceğiz. gdb
bir komut satırı uygulaması olarak çalışmakta ve C, C++, Objective-C, assembly ve Java olmak üzere bir çok dili desteklemektedir. gdb ayrıca, Eclipse, Qt Creator, NetBeans gibi bir çok IDE ve GNU DDD (Data Display Debugger) grafik önyüz uygulaması üzerinden de kullanılabilir.
gdb ile bir programın içsel durumunu inceleyebilir, işleyişine müdahale edebiliriz. Tipik olarak, kodu adım adım işletebilir, değişken ve yazmaçların değerlerini gözleyip değiştirebilir, kod üzerinde kesme noktaları belirleyebilir, akışa müdahale edebilir ve fonksiyonların çağrılma sırasını takip edebiliriz. gdb çok sayıda komut ve seçeneği barındırmasına karşın çoğu durumda göreli olarak daha az bir komut setiyle bir çok işi yapabilmekteyiz. Bu bölümde temel kullanım senaryolarına değinmeye çalışacağız.
GDB Kullanımı
İlk olarak, programların debug amaçlı olarak nasıl derlendiğine ve ardından gdb ile nasıl çalıştırıldığına bakalım.
Debug Amaçlı Derleme
Bir programın içsel durumunu etkin bir şekilde inceleyebilmek için çalışabilir dosya içinde debug sembolleri bulunmalıdır. Bu sembollerin bulunmaması durumunda gdb aşağıdaki gibi bir uyarı verecektir.
Uygulama içerisine debug sembollerini eklemek için, derleme sürecinde tipik olarak -g
ve -ggdb
anahtarları kullanılmaktadır. -ggdb
anahtarı ile, -g
anahtarından farklı olarak, yalnız gdb'nin kullanabileceği özel bazı semboller de üretilmektedir. Bu anahtarların ayrıca, fazladan bilgi üreten, seviye gösteren kullanımları da mevcuttur. Örneğin önişlemci makro tanımları için -g3
veya -ggdb3
anahtarları kullanılmalıdır. Makro kullanımına ilerideki bölümlerde değineceğiz.
Uygulamanın debug modda derlenmesi, çalıştırılabilir dosya formatına yeni alanları eklediğinden, kodun bir miktar büyümesine neden olacaktır. Aşağıdaki gibi basit bir kodu önce normal, sonrasında debug modda derleyerek karşılaştıralım.
Kodu debug.c adıyla saklayıp sırasıyla aşağıdaki gibi derleyip, gcc tarafından üretilen dosyaları inceleyebilirsiniz.
Bu örnek için üretilen sembolik makina kodunun 384B'tan 2.5K'ya çıktığını ve çalışabilir dosya formatına yeni bölümlerin eklendiğini görmekteyiz.
Not: Linux altında, derleyicinin ürettiği amaç dosyalar, paylaşımlı kütüphaneler ve çalışabilir dosyalar ELF (Executable and Linkable Format) formatında saklanmaktadır. ELF formatı çok sayıda bölümden oluşmaktadır. readelf aracı ile ELF dosya formatını inceleyebilir, S anahtarı ile bölüm başlıklarını (section headers) listeleyebilirsiniz.
Programların GDB İle Çalıştırılması
İncelenecek olan programlar gdb ile çalıştırılabildiği gibi çalışan uygulamalar da, proses kimlikleri kullanılarak, gdb üzerinden incelenebilir. Uygulama isimlerini ve proses kimliklerini gdb'ye argüman olarak geçirebildiğimiz gibi aynı işlemleri gdb komut satırından da yapabiliriz. Tipik kullanımlar aşağıdaki gibidir.
Kullanım
$ gdb programismi
(gdb) file programismi
$ gdb -p proseskimliği
(gdb) attach proseskimliği
Program isminin belirtildiği, tablodaki ilk iki kullanım şeklinde, uygulama öncelikle gdb tarafından yüklenmektedir. Uygulamayı çalıştırmak için sonrasında run komutunu kullanabilirsiniz.
Not: gdb açılışta sahiplik bilgilerini de içeren bir karşılama mesajı basmaktadır. Komut satırından
-q
anahtarını kullanarak bu mesajın görüntülenmesini engelleyebilirsiniz.
Programlara Komut Satırı Argümanlarının Geçirilmesi
Komut satırı argümanlarını aşağıda gösterilen 3 farklı şekilde de geçirmek mümkündür.
Uygulamaya geçirilen argümanlar aşağıdaki gibi listelenebilir.
Uygulamanın Çalışmasının Sonlandırılması ve Askıya Alınması
kill komutu ile uygulama sonlandırabilir, Ctrl-C tuşlarıyla uygulamanın çalışmasını geçici olarak durdurabilirsiniz.
GDB'nin Sonlandırılması
gdb uygulamasından quit komutu veya Ctrl-D seçenekleriyle çıkabilirsiniz.
Yardım
Komut satırında help yazarak değişik seviyelerde yardım alabilirsiniz. Yalnız help yazarak genel yardım listesini alabilir, sonrasında ilgilendiğiniz komuta ulaşarak daha detaylı bilgi alabilirsiniz. Örnek bir kullanım aşağıdaki gibi olabilir.
breakpoints kullanımı ile ilgili olduğumuzu var sayalım.
Nihayetinde gerçek break komut ile ilgili yardım alabiliriz.
Çoğu durumda, bir gdb komutunun tamamını yazmaksızın, sadece diğer komutlardan ayrım yapılabilecek kadar olan kısmını yazarak, komutu kullanabilirsiniz. Ayrıca tab tuşana basarak gdb'nin kodu tamamlamasını veya adayları listelemesini sağlayabilirsiniz. Örneğin aşağıdaki komutların hepsi aynı sonucu üretecektir.
GDB Temel Özellikleri
Bu bölümde, gdb kullanarak, bir uygulama hakkında nasıl bilgi alabileceğimize ve işleyişine nasıl müdahale edebileceğimize bakacağız.
Kaynak Kodun Listelenmesi
Kaynak kod list komutu ile listelenebilir. Aldığı argümanlar ve temel kullanım şekilleri aşağıdaki gibidir.
Komut
Açıklama
list
Son listelenen noktadan ya da kodun başından itibaren 10 satırı görüntüler
list SATIRNUMARASI
İstenilen noktayı çevreleyen 10 satırı listeler
list BAŞLANGIÇSATIRI,BİTİŞSATIRI
Belirtilen başlangıç ve bitiş noktalarının arasını listeler
list FONKSİYONADI
Belirtilen fonksiyonu görüntüler
list DOSYAADI:SATIRNUMARASI
Projenin birden çok dosyadan oluşması durumunda satır numarasından önce dosya ismi belirtilebilir
list DOSYAADI:FONKSİYONADI
Projenin birden çok dosyadan oluşması durumunda fonksiyon adından önce dosya ismi belirtilebilir
Makina Kodlarının Listelenmesi
Sembolik ve karşılık geldikleri gerçek makina kodları disassemble komutu ile listelenebilir. disassemble argüman olarak bellek adresi almaktadır, örnek kullanımları aşağıdaki gibidir.
Komut
Açıklama
disas FONKSİYONADI
Belirtilen fonksiyona ait sembolik makina kodlarını listeler
disas /m FONKSİYONADI
Sembolik makina kodlarıyla beraber karşılık gelen kaynak kod satırları da listelenir
disas /r FONKSİYONADI
Sembolik makina kodlarıyla beraber gerçek makina kodları da listelenir
Program Akışının İzlenmesi
Programın akışı, kullanıcı tarafından Ctrl-C tuşlarına basılarak veya önceden belirlenen kesme noktalarıyla durdurulabilir. Kesme noktalarının kullanımına ilerleyen bölümlerde değineceğiz. Durdurulan program sonrasında kaldığı yerden, kullanıcı müdahalesi olmaksızın, yoluna devam edebileceği gibi kontrollü bir şekilde adım adım da çalıştırılabilir.
Akış komutları ve kullanım şekilleri aşağıdaki gibidir.
Komut
Açıklama
continue
Akış kaldığı yerden devam ettirilir
next [N]
1Aldığı argüman sayısınca, fonksiyon çağrılarını tek satır olarak ele alarak, kodu kaynak kod düzeyinde satır satır çalıştırır
step [N]
Aldığı argüman sayısınca, çağrılan fonksiyonların içine girerek, kodu kaynak kod düzeyinde satır satır çalıştırır
nexti [N]
Aldığı argüman sayısınca, fonksiyon çağrılarını tek satır olarak ele alarak, makina kodlarını adım adım çalıştırır
stepi [N]
Aldığı argüman sayısınca, çağrılan fonksiyonların içine girerek, makina kodlarını adım adım çalıştırır
next ve step komutları arasındaki, ister programın yazıldığı kaynak kod düzeyinde ister sembolik makina komutları düzeyinde olsun, fonksiyon çağrılarını ele alış biçimlerindeki farklılığa dikkat ediniz. next komutlarında fonksiyonlar tek hamlede işletilmekte buna karşın step komutlarında akış çağrı yapılan fonksiyon kodundan devam etmektedir. Aradaki farkı görmek için basit bir örnek yapalım. Aşağıdaki kodu debug.c adıyla saklayıp derleyebilirsiniz.
Şimdi gdb'yi uygulamamız için çalıştıralım.
Program akışı main fonksiyonunda kesilecek şekilde, break komutu ile, bir kesme noktası tanımlayalım ve uygulamayı çalıştıralım. break komutunun detaylarına ilerleyen bölümlerde bakacağız.
Akışın 11. satırda yani foo fonksiyonu çağrısında durduğunu görüyoruz. Daha detaylı inceleme yapabilmek için, disassemble komutuyla, sembolik makina kodlarına bakalım.
Sembolik makina kodlarındaki ok işareti, henüz işletilmemiş, sıradaki ilk komutu göstermektedir. nexti ile kodun işleyişini bir adım ilerletelim.
Not: Kesme noktasının gösterdiği makina komutunun fonksiyonun ilk makina komutu değil, derleyici tarafından yazılan başlangıç kodlarını (prologue) takip eden, foo fonksiyonu çağrı komutu olduğuna dikkat ediniz.
Tekrar sembolik makina kodlarına bakalım.
nexti komutuyla foo fonksiyonunun işletildiğini ve akışın main fonksiyonun bir sonraki komutundan devam ettirildiğini görüyoruz. Şimdi benzer işlemi stepi komutuyla yapalım. Öncesinde kill komutuyla uygulamayı sonlandırıp uygulamayı yeniden çalıştıralım.
Bu sefer stepi ile kodun işleyişini bir adım ilerletelim.
stepi komutu ile foo fonksiyonu tek hamlede çalıştırılmak yerine akış foo fonksiyonuna dallanmaktadır. Sembolik makina kodlarına baktığımızda bu durum daha açık şekilde gözükmektedir.
Fonksiyonların Çağrılması
Program kodundaki veya programa linklenmiş dinamik kütüphane içeriğindeki fonksiyonları call komutuyla çağırabilirsiniz. finish komutuyla da bir fonksiyonu sonlandırarak çağıran fonksiyona geri dönülebilir.
Program Verisinin İncelenmesi
Değişkenlerle temsil edilen veya direkt adres kullanılarak gösterilen bellek alanlarını, print ve x komutlarıyla inceleyebiliriz, ayrıca bu bölümde yazmaç değerlerini nasıl elde edebileceğimize de bakacağız. Tam olarak aynı işi yapmamalarına karşın, birbirinin yerine geçebilen kullanımları olan bu komutlara daha yakından bakalım.
Komut
Açıklama
print /FORMAT İFADE
Programın yazıldığı kaynak kod düzeyindeki anlamlı bir ifadeyi belirtilen formatta gösterir
print /FORMAT ADRESGÖSTERENDEĞER@N
İfadenin bir adres göstermesi durumunda, @ operatoru ile devam eden N-1 adet bellek bölgesi de, tür bilgisi gözetilerek, gösterilir
Bu noktada bir ifadeye ait tür bilgisinin whatis komutuyla öğrenebildiğini söyleyelim.
Şimdi print komutunun kullanımına bakalım, aşağıdaki örnek kodu debug.c adıyla saklayıp derleyebilirsiniz.
gdb'yi çalıştırdıktan sonra, ilgilendiğimiz yerel değişkenlerin sonrasına bir kesme noktası koyalım ve programı çalıştıralım. Bu sayede bu program sonlanmayacak ve incelemelerimize devam edebileceğiz.
Sırasıyla s, i ve arr yerel değişkenleri için bazı örnekler yapalım.
Not: Normal bir derleme sürecinde, değişmez adresleri olan statik ömürlü değişkenlerin aksine, konumları yazmaç göreli olarak değişen yerel değişken isimleri derleyici tarafından üretilen amaç kod (object code) içinde saklanmazlar. Debug hedefli derleme yaparak yerel değişken isimlerini kullanabildiğimize dikkat ediniz.
Şimdi belleği incelemek için kullanabileceğimiz bir diğer komut olan x (Examine memory) komutuna bakalım.
Komut
Açıklama
x /FORMAT ADRESİFADESİ
Belirtilen adresteki bellek bölgesinin değeri formatlı bir şekilde gösterilir
Şimdi x komutuyla bazı basit örnekler yapalım.
Yazmaç Değerlerinin Elde Edilmesi
Yazmaç değerlerini print ve info komutlarıyla almak mümkündür. Genel şekilleri ve örnekler aşağıdaki gibidir.
Komut
print /FORMAT $YAZMAÇ
info registers
Not: Format karakterlerinin çoğu, C dilinden aşina olduğumuz karakterlerden oluşmaktadır, tam liste için help komutundan faydalanabilirsiniz.
Program Verisinin Değiştirilmesi
Bellek alanlarının ve yazmaçların değerlerini set komutuyla değiştirebilirsiniz. set komutu çok sayıda alt komuta (subcommand) sahiptir, tam liste için help set şeklinde yardım alabilirsiniz. Genel kullanım şekli ve bir önceki kod için örnekler aşağıdaki gibidir.
Komut
set var GÜNCELLENECEKALAN=İFADE
Geçmiş Değerlerin Kullanımı
gdb, print komutunun ürettiği sonuçları, geriye dönük takip edebilmek ve yeniden kullanabilmek için, $NUM, şeklinde numaralandırdığı değişkenlerde saklamaktadır. show values komutuyla geçmiş değerleri listeleyebilirsiniz.
Macro İşlemleri
Önişlemci makrolarına ilişkin bilgileri de debug bilgisi olarak saklayabilmek için gcc'ye g3
veya ggdb3
anahtarlarından birini geçirmeliyiz. Aşağıdaki örnek üzerinden macro işlemlerini nasıl yapabileceğimize bakalım. Kodu debug.c olarak saklayıp derleyebilirsiniz.
Program çalışmıyor iken bile list komutuyla makro tanımlarının üzerinden geçip sonrasında makro değerlerlerine ulaşabiliriz.
Makroların karşılık geldiklerini değerleri öğrenmek için macro expand veya info macro komutlarını kullanabilirsiniz. Ayrıca macro define ve macro undef ile makro tanımlayabilir veya geçersiz kılabilirsiniz. help macro komutuyla makro işlemleriyle ilgili yardım alınabilir.
Tanımladığımız NUMBER makrosunun değerine iki farklı şekilde ulaşabiliriz.
Yığınının (Call Stack) İncelenmesi
Bir fonksiyon çağrıldığında, yığında fonksiyon için, yığın çerçevesi (call frame) olarak isimlendirilen ve fonksiyon sonlandığında geri verilen, yeni bir alan ayrılır. Fonksiyona geçirilen argümanlar, yerel değişkenler ve fonksiyonun geri dönüş adresi yığın çerçevesinde bulunmaktadır. Yığının, son girenin ilk çıktığı (LIFO) bir veri alanı olduğunu hatırlayınız. gdb yığın üzerinde işlem yapabilmek için gerekli komutları barındırmaktadır. Şimdi bu komutların genel kullanımına bakalım.
Komut
Açıklama
backtrace [N] [full]
Argümansız kullanılması durumunda tüm yığın çerçevelerini görüntüler. N değerinin pozitif olması durumunda en içteki N adet, negatif olması durumunda ise en dıştaki N adet çerçeve listelenir. full niteleyicisi geçirilmesi durumunda ise yerel değişken değerleri de listelenir
frame [N]
Argümansız kullanıması durumunda gündemdeki (current) yığın çerçevesine ait bilgileri görüntüler. Argümanlı kullanımda belirtilen çerçeveyi seçer
info frame [N]
Gündemdeki veya N argümanıyla belirtilen yığın çerçevesine ait bilgileri görüntüler
info locals
Gündemdeki yığın çerçevesine ait yerel değişken değerlerini görüntüler
İncelememize bir örnek üzerinden devam edelim, aşağıdaki örneği debug.c ismiyle saklayıp derleyebilirsiniz.
bar fonksiyonuna bir kesme noktası koyarak uygulamayı çalıştıralım ve yığın çerçevelerine bakalım.
backtrace komutunun, içten dışa doğru, yığın çerçevelerini numaralandırarak listelediğini görmekteyiz. # karakteriyle başlayan bu numaralar yığın çerçevelerini tanımlamak için kullanılmaktadır, daha sonra frame komutunda da bu numaraları kullanacağız. backtrace komutuna, ilk veya son yığın çerçevesinden başlayarak, listelemesini istediğimiz çerçeve sayısını argüman geçirebiliriz, aşağıdaki kullanımları inceleyiniz.
Şimdi frame ve info locals komutlarının kullanımlarına bakalım. frame ile isteğimiz bir çerçeveyi seçebilir ve info locals ile o çerçeveye ait yerel değişken değerlerine ulaşabiliriz.
Komut Dosyaları
Komut dosyaları, gdb komutlarından oluşan yazı dosyalarıdır. # ile başlayan satırlar açıklama olarak ele alınır. gdb çalıştırılırken, komut dosyası argüman olarak geçirilebildiği gibi sonrasında gdb komut satırından da gösterilebilir. Genel kullanımları aşağıdaki gibidir.
Kullanım
$ gdb -x KOMUTDOSYASI
(gdb) source KOMUTDOSYASI
KOMUTDOSYASI dosya ismi ve dizin yolundan oluşabilir. Şimdi, incelediğimiz debug programı için, basit bir örnek yapalım. Aşağıdaki komutları commands.txt dosyasında saklayabilir ve sonrasında gdb'yi aşağıdaki gibi çalıştırabilirsiniz.
Başlangıç Dosyaları
gdb ilk açılışta bazı öntanımlı dosyaları, bir öncelik sırasına göre, aramaktadır. Aradığı başlangıç dosyaları ve öncelikleri aşağıdaki gibidir.
Dosya
Konum
system.gdbinit
Kullanıcı veya çalışma dizininden bağımsız, sistem genelindeki başlangıç dosyasıdır. Bu özelliğin kullanılabilmesi için gdb --with-system-gdbinit seçeneği ile derlenmiş olmalıdır
~/.gdbinit
Kullanıcı dizinindeki başlangıç dosyasıdır
./.gdbinit
Çalışma dizinindeki başlangıç dosyasıdır
Daha önce komut dosyalarını incelerken kullandığımız örneği şimdi ana dizinde bir .gdbinit dosyası oluşturarak tekrarlayalım. Bu durumda gdb'yi çalıştırdığımızda aşağıdaki gibi başlayan bir uyarı güvenlik uyarısı alıyoruz.
Bu durumda kullanıcı dizinindeki .gdbinit dosyasına, uyarıda belirtilen add-auto-load-safe-path /home/serkan/embedded/gdb/.gdbinit komutunu ekleyip gdb'yi yeniden çalıştırtığımızda, çalışma dizinindeki .gdbinit dosyasının içeriğinin okunduğunu görmekteyiz.
Çalışma Modları
Komut satırı seçeneklerini kullanarak gdb'yi farklı modlarda çalıştırmak mümkündür. Desteklenen modlardan birkaçına bakalım.
Seçenek
Mod
-nx
Başlangıç dosyalarındaki komutlar çalıştırılmaz
-q
Başlangıç mesajları basılmaz
-batch
Kullanıcıyla etkileşime geçilmez, başlangıç dosyaları ve komut satırı seçenekleri işletildikten sonra gdb sonlanır
batch mod ile, istediğiniz komutları seçenek olarak geçirerek, gdb komut satırına düşmeksizin gdb'nin istediğiniz işleri yapmasını sağlayabilirsiniz. Çalışmasını istediğimiz komutları ex seçeneğiyle belirtebiliriz. Bu şekilde gdb'nin ürettiği çıktıyı bu şekilde bir dosyaya yönlendirebiliriz. Aşağıdaki örneği inceleyiniz.
Kabuk Kullanımı
gdb çalışıyorken, gdb'yi sonlandırmadan ya da askıya almadan, kabuk komutlarını shell KOMUT şeklinde çalıştırmak mümkündür. Örneğin, gdb ekranı temizlemek için bir komuta sahip olmadığından ekranı shell clear şeklinde temizleyebilirsiniz.
Kesme Noktaları Oluşturulması
Kesme noktaları, program akışının durdurulduğu ve kontrolün kullanıcıya geçtiği noktalardır. Bu durumda tipik kullanım, program verisini incelemek ve kodu, kontrollü bir şekilde, adım adım çalıştırmak şeklinde olmaktadır. Kesme noktaları program kodu üzerinde açık bir şekilde belirtilebildiği gibi data belleği üzerindeki alanlar da izlenebilir. Şimdi bu özelliğe daha yakından bakalım.
Kod Üzerinde Oluşturulan Kesme Noktaları (Breakpoints)
Program kodu üzerinde kesme noktası break KONUM şeklinde tanımlanmaktadır. Konum değeri aşağıdaki biçimlerde olabilmekte ayrıca bir koşul ifadesi de eklenebilmektedir.
Komut
Açıklama
break [DOSYAADI:]SATIRNUMARASI
Belirtilen satır kesme noktası olarak işaretlenir
break [DOSYAADI:]FONKSİYONADI
Belirtilen fonksiyon kesme noktası olarak işaretlenir
break ADRES
Belirtilen adres kesme noktası olarak işaretlenir
break KONUM if KOŞUL
Kesme noktasına koşul eklenir
Proje birden çok kaynak dosyadan oluşuyorsa DOSYAADI belirtilmedir. Şimdi bir örnek üzerinde koşul içeren bir kesme noktası oluşturalım. Döngü içerisinde indeks değerinin basıldığı 6. satırı kesme noktası olarak belirliyoruz. Bir koşul belirtmeseydik, döngünün her turunda akış kesilecekti. Koşulu indeks değerinin 5 olması olarak belirlediğimizden akış bu kesme noktasında durduğunda i değişkeni 5 değerine sahiptir.
Ayrıca, bir kesme noktasına sonradan da koşul ekleyebilirsiniz. Aynı örnek için 6 numaralı satıra kesme noktası ekleyip, sonrasında condition komutu ile bir koşul ifadesi ekleyebiliriz. condition komutunun genel hali aşağıdaki gibidir.
Komut
Açıklama
condition N KOŞUL
N kesme noktası numarasını göstermektedir
Tanımlanmış kesme numaralarını info breakpoints komutu ile öğrenebilirsiniz.
Bellek Üzerinde Oluşturulan İzleme Noktaları (Watchpoints)
Kod üzerinde tanımlanan kesme noktalarının aksine izleme noktaları veri belleği üzerindedir. Bu sayede, veri belleği üzerinde ilgilendiğimiz bir alanın değeri okunduğunda veya değiştirildiğinde akışın durmasını sağlayabiliriz. İzleme noktaları oluşturduğumuzda, kod üzerinde ilgilendiğimiz bellek alanının değerini kullanan kısımları bulma zorunluluğumuz ortadan kalkmaktadır.
İzleme noktası oluşturmak için kullanılan komutlar aşağıdaki gibidir.
Komut
Açıklama
watch İFADE
Argüman olarak geçirilen ifadenin değeri değiştirildiğinde etkin olur
rwatch İFADE
Argüman olarak geçirilen ifadenin değeri okunduğunda etkin olur
awatch İFADE
Argüman olarak geçirilen ifadenin değeri okunduğunda veya değiştiğinde etkin olur
Genel biçimde gösterilen ifade çoğunlukla bir değişken adı olmaktadır. Bu özelliğin kullanımına basit bir örnek yaparak daha yakından bakalım.
İlk olarak programı main fonskiyonuna kadar çalıştıralım, ardından i değişkenindeki değerin değişimini izlemek için watch i komutuyla bir izleme noktası oluşturduktan sonra continue ile programın çalışmasını devam ettirelim. Bu durumda programın i değerine ilişkin eski ve yeni değerleri vererek sonlandığını görüyoruz, disas ile i değişkenine (-0x8(%ebp)) 0 değerinin yazıldığını ve akışın sonraki makina komutunda durduğunu görüyoruz. Benzer testleri rwatch ve awatch komutlarıyla da yapabilirsiniz.
Burada bir noktaya dikkatinizi çekmek istiyoruz. İzleme noktası oluşturduğumuzda watch komutunun, Hardware watchpoint 2: i şeklinde bir bilgi mesaji verdiğini görmekteyiz. Mesajdaki Hardware ifadesi izleme noktasının donanım desteği alınarak oluşturulduğunu göstermektedir. Bir sonraki bölümde yazılımsal ve donanımsal olarak oluşturulan izleme noktalarına kısaca değineceğiz.
Yazılımsal ve Donanımsal Kesme Noktaları
Bir önceki bölümde, kod ve bellek üzerinde, kesme noktalarının nasıl kullanıldığını inceledik. Kod üzerinde belirlediğimiz noktalar işletilmeye çalışıldığında veya izlediğimiz değişkenler adreslendiğinde, akışın sonlandırıldığını ve kontrolun debugger programına, gdb, geçtiğini gördük. Şimdi bu özelliğin nasıl gerçekleştirildiğe daha yakından bakalım. Kesme noktaları yazılımsal ve donanımsal olmak üzere iki farklı şekilde oluşturulabilmektedir.
Yazılımsal Kesme Noktaları
Yazılımsal kesme noktaları, kod üzerinde oluşturduğumuz kesme noktalarını (breakpoints) oluşturmak için kullanılmaktadır. gdb uygulamasında bu noktaları break komutuyla oluşturmuştuk. Kod üzerinde yazılımsal kesme noktaları oluşturmanın birden çok yolu olmasına karşın burada gdb tarafından da kullanılan yöntemden bahsedeceğiz. Bu yöntemde, kesme noktasındaki makina komutu debugger tarafından değiştirilerek, işlemcinin bir istisna (exception) durumu oluşturması hedeflenmektedir. İstisna durumu oluştuğunda, işlemci normal akışını sonlandıracak ve işletim sistemi tarafından tanımlanan bir ele alım kodunu (interrup handler) çalıştıracaktır. Ele alım kodunun tipik davranışı ise bu duruma neden olan prosese bir sinyal göndermek şeklindedir. gdb, işletim sistemi tarafından gönderilen bu sinyalden haberdar olmakta ve kontrol gdb uygulamasına geçmektedir. gdb, alt proses olarak çalıştırdığı veya daha sonradan bağlandığı prosese gelen sinyalleri takip edebilmektedir. x86 mimarisinde, istisna durumu oluşturmak için, gerçek makina kodu 0xCC olan, int 3 sembolik makina kodu kullanılmaktadır. int sembolik makina kodu, yazılım yoluyla kesme (software interrupt) oluşturmak için kullanılmakta ve kesme numarasını operand olarak almaktadır. Normalde komutun kendisi (opcode) ve aldığı operand olmak üzere 2 byte olmasına karşın, int 3 komutu özel olarak bir byte ile gösterilmektedir. Bu durum, Intel Architecture Software Developer's Manual dokümanında aşağıdaki gibi ifade edilmiştir.
İşlemci, int 3 sembolik makina koduyla karşılaştığında normal akışından çıkacak ve işletim sisteminin debug ele alım kodunu çalıştıracaktır. Bu kodda, nihayetinde debugger tarafından alınacak, SIGTRAP sinyalini üretecektir. gdb uygulamasında, bir kesme noktası belirlendiğinde karşılık geldiği konumdaki makina komutunun ilk byte değerinin 0xCC değeri ile değiştirildiğini söyledik. Bu durumda incelediğiniz kod üzerinde bir kesme noktası belirleyip sonrasında disas ile makina kodlarına baktığınızda, kesme noktasında, 0xCC değerini görmeyi bekleyebilirsiniz. Fakat gdb, gerçekte bu işlemi yapmasına karşın, disas çıktısında kodun değişmemiş halini göstermektedir. Bu yüzden bu durumu gözleyebilmek için, kendi makina kodlarının bir kısmını basan, aşağıdaki örneği kullanacağız. Örnek kodu break.c adıyla saklayıp, debug sembolleri olmaksızın, aşağıdaki gibi derleyebilirsiniz.
İlk olarak programı, herhangi bir kesme noktası oluşturmaksızın, çalıştıralım. Çıktı olarak, main fonksiyonundan itibaren toplamda 32 byte'tan oluşan makina komutlarını görmekteyiz.
Sonrasında main fonksiyonunu kesme noktası olarak belirleyelim ve kodu main fonksiyonuna kadar işletelim.
Bu noktada sembolik ve gerçek makina kodlarına baktığımızda, main fonksiyonunun başlangıç kodlarından (prologue) sonraki 0x40058a adresindeki ilk komutunun kesme noktası olarak gösterildiğini görüyoruz. Kesme noktasındaki makina komutunun 48 ile başladığını görmekteyiz. Programın bir önceki çalışmasında da 5. sıradaki makina komutunun 48 olarak gösterildiğine dikkat ediniz. Bu noktadan sonra continue komutu ile programı çalıştırıyoruz.
Bu sefer 5. sıradaki makina komutunun, beklediğimiz üzere, 48 yerine cc ile gösterildiğini görmekteyiz. Buna karşın, disas çıktısında bu değer hala 48 ile gösterilmeye devam etmektedir.
Yazılımsal kesme noktaları bazı zorluklar barındırmaktadır. Kesme noktasından sonra akış devam ettirilmek istendiğinde, 0xCC makina kodu yerine gerçek makina kodu geri yazılmalı, akış devam ettirilmeli fakat kesme noktasının hala kullanılabilir olması için hemen ardından 0xCC kodu geri yazılmalıdır. Kesme noktasında program askıya alındığında komut göstericisi bir sonraki komutu göstermektedir. Akışın devam ettirilebilmesi için komut göstericisi tekrardan kesme noktasını gösterecek şekilde ayarlanmalı ve işlemci pipeline'ı temizlenmelidir.
Donanımsal Kesme Noktaları
Donamımsal kesme noktalarıyla, yazılımsal kesme noktalarının aksine, veri belleğini de, etkin bir şekilde izlemek mümkündür. Donanımsal kesme noktaları, işlemci bünyesinde, özel gereksinimlere ihtiyaç duymaktadır. x86 mimarisinde bu amaçla, DRx şeklinde isimlendirilen, 6 adet debug yazmacı bulunmaktadır. Bu yazmaçlardan 4 tanesi adres yolunu dinlemekte, iki tanesi ise kontrol ve durum bilgisi için kullanılmaktadır. Bu yazmaçlar, aktif olmaları durumunda, kendilerine yüklenen değerleri adres yolundaki değerlerle karşılaştırmaktadırlar. Adreslerin eşleşmesi durumunda, adres üzerinde, okuma, yazma veya çalıştırma işlemleri yapılmasına göre içsel bir kesmeye neden olmaktadırlar. Bu durumda akış durmakta, araya işletim sisteminin ele alım kodu girmekte ve nihayetinde, yazılımsal kesmelerde olduğu gibi, debugger bu durumdan haberdar olmaktadır. Bu özelliğin olmaması durumunda bellek izlenmek istendiğinde debugger her makina komutundan sonra izlenen bellek bölgesiyle ilgili işlem yapılıp yapılmadığına bakmalıdır. Donanımsal kesme noktaları, yazılımsal kesme noktalarının aksine, kısıtlı sayıda olmalarına karşın, değişkenlerin izlenmek istendiği ve kodun, FLASH bellek gibi, değiştirilemediği ortamlarda kod üzerinde de kesme noktaları oluşturabilmek için oldukça faydalıdırlar. gdb'nin kod üzerindeki kesme noktalarını yazılımsal, bellek üzerindeki izleme noktalarının ise donanımsal olarak gerçekleştirdiğini görmekteyiz.
Kesme Noktalarının Silinmesi ve Etkisizleştirilmesi
Kesme noktalarını delete ve disable komutlarıyla silebilir veya etkisizleştirebilirsiniz. Kod veya bellek üzerinde oluşturduğunuz kesme noktalarını info breakpoints ile listeleyebilir, kesme noktalarının durumlarına ve numaralarına ulaşabilirsiniz. disable ve delete komutları kesme numaralarını argüman olarak almaktadır, argüman geçirilmemesi durumunda tüm kesme noktaları üzerinde işlem yapılır. Aşağıdaki örnek kullanımı inceleyiniz.
Last updated
Was this helpful?