• GDB ile hata ayıklama. WinDbg hata ayıklayıcısını kullanmanın temelleri Debugger gdb komutları

    Bir programda hata ayıklamanın amacı, kodundaki hataları ortadan kaldırmaktır. Bunu yapmak için büyük olasılıkla değişkenlerin durumunu incelemeniz gerekecektir. kurşun zamanı, yanı sıra yürütme sürecinin kendisi (örneğin, koşullu dalların izlenmesi). Burada hata ayıklayıcı bizim ilk yardımcımızdır. Elbette C'nin programı doğrudan durdurmadan birçok hata ayıklama seçeneği vardır: basit printf(3)'ten özel ağ kayıt sistemlerine ve sistem günlüğüne kadar. Çeviricide bu tür yöntemler de uygulanabilir, ancak kayıtların durumunu izlemeniz, RAM görüntülerini dökmeniz ve etkileşimli bir hata ayıklayıcıda yapılması çok daha kolay olan diğer şeyleri yapmanız gerekebilir. Genel olarak, montaj dilinde yazıyorsanız, hata ayıklayıcı olmadan yapmanız pek mümkün değildir.

    Kodun yaklaşık olarak hangi bölümünü incelemeniz gerektiğini zaten biliyorsanız, bir kesme noktası tanımlayarak hata ayıklamaya başlayabilirsiniz. Bu yöntem en sık kullanılır: bir kesme noktası belirleriz, programı çalıştırırız ve gerekli değişkenleri ve kayıtları eşzamanlı olarak gözlemleyerek adım adım yürütülmesini sağlarız. Ayrıca programı bir hata ayıklayıcı altında çalıştırabilir ve bir segmentasyon hatası nedeniyle çöktüğü anı yakalayabilirsiniz - bu şekilde hangi talimatın belleğe erişmeye çalıştığını bulabilir, hataya neden olan değişkene daha yakından bakabilir ve böylece Açık. Artık bu kodu tekrar inceleyebilir, adım adım ilerleyebilir, başarısızlık anından biraz önce bir kesme noktası belirleyebilirsiniz.

    Basit bir şeyle başlayalım. Merhaba dünya programını alalım ve -g derleyici anahtarını kullanarak hata ayıklama bilgileriyle derleyelim:

    $ gcc -g merhaba.s -o merhaba $

    gdb'yi başlatın:

    $ gdb ./merhaba GNU gdb 6.4.90-debian Telif Hakkı (C) 2006 Free Software Foundation, Inc. GDB, GNU Genel Kamu Lisansı kapsamındaki ücretsiz bir yazılımdır ve belirli koşullar altında onu değiştirebilir ve/veya kopyalarını dağıtabilirsiniz. Koşulları görmek için "kopyalamayı göster" yazın. GDB'nin kesinlikle garantisi yoktur. Ayrıntılar için "garantiyi göster" yazın. Bu GDB "i486-linux-gnu" olarak yapılandırılmıştır... Ana bilgisayar libthread_db kütüphanesi "/lib/tls/libthread_db.so.1" kullanılarak. (gdb)

    GDB başladı, incelenmekte olan programı yükledi, bir istem (gdb) görüntüledi ve komutları bekliyor. Programın "adım adım" (tek adımlı mod) üzerinden geçmek istiyoruz. Bunu yapmak için programın durması gereken komutu belirtmeniz gerekir. Bir alt program belirleyebilirsiniz - daha sonra durdurma, bu alt programın talimatlarının yürütülmesi başlamadan önce gerçekleştirilecektir. Dosya adını ve satır numarasını da belirtebilirsiniz.

    (gdb) b 0x8048324'teki ana Kesme Noktası 1: hello.s dosyası, satır 17. (gdb)

    b mola kelimesinin kısaltmasıdır. GDB'deki tüm komutlar belirsizlik yaratmadığı sürece kısaltılabilir. Run komutu ile programı başlatıyoruz. Aynı komut daha önce çalışan bir programı yeniden başlatmak için kullanılır.

    (gdb) r Programın başlatılması: /tmp/hello Kesme noktası 1, main () hello.s:17'de 17 movl $4, %eax /* put sistem çağrı numarası write = 4 Mevcut dil: auto; şu anda asm (gdb)

    GDB programı durdurdu ve komutları bekliyor. Programınızda bundan sonra yürütülecek komutu, o anda yürütülen işlevin adını, dosya adını ve satır numarasını görürsünüz. Adım adım uygulama için iki komutumuz var: step (s olarak kısaltılır) ve next (n olarak kısaltılır). Step komutu, altprogramların gövdelerine girerek programı yürütür. Bir sonraki komut yalnızca geçerli alt programın talimatlarını adım adım gerçekleştirir.

    (gdb) n 20 movl $1, %ebx /* ilk parametre %ebx yazmacına gider */ (gdb)

    Böylece 17. satırdaki komut yürütülür ve 4 sayısının %eax yazmacında olmasını bekleriz.Çeşitli ifadeleri ekrana yazdırmak için print komutunu (kısaltılmış p ) kullanırız. Çevirici komutlarından farklı olarak GDB, kayıtları yazarken % yerine $ işaretini kullanır. %eax kaydında ne olduğuna bakalım:

    (gdb) p $eax $1 = 4 (gdb)

    Gerçekten 4! GDB tüm çıktı ifadelerini numaralandırır. Şimdi 4'e eşit olan ilk ifadeyi ($1) görüyoruz. Artık bu ifadeye isimle ulaşılabilir. Ayrıca basit hesaplamalar da yapabilirsiniz:

    (gdb) p $1 $2 = 4 (gdb) p $1 + 10 $3 = 14 (gdb) p 0x10 + 0x1f $4 = 47 (gdb)

    Print komutuyla oynarken bir sonraki komutun hangi komutun yürütüleceğini çoktan unutmuştuk. Bilgi satırı komutu, belirtilen kod satırıyla ilgili bilgileri görüntüler. Bağımsız değişkenler olmadan geçerli satırla ilgili bilgileri yazdırır.

    (gdb) bilgi satırı "hello.s"nin 20. satırı 0x8048329 adresinde başlar ve 0x804832e'de bitiyor . (gdb)

    List komutu (kısaltılmış l ) programınızın kaynak kodunu görüntüler. Bunu argüman olarak iletebilirsiniz:

    • satır_numarası- geçerli dosyadaki satır numarası;
    • dosya:satır_numarası- belirtilen dosyadaki satır numarası;
    • fonksiyon adı- belirsizlik yoksa işlev adı;
    • dosya:işlev_adı- belirtilen dosyadaki işlevin adı;
    • *adres- gerekli talimatın bulunduğu hafızadaki adres.

    Bir argüman iletirseniz, list komutu bu konumun etrafına 10 satır kaynak kodu yazdıracaktır. İki argüman ileterek listenin başlangıç ​​satırını ve bitiş satırını belirtirsiniz.

    (gdb) l main 12 bu dosyanın dışında */ 13 .type main, @function /* main - function (data değil) */ 14 15 16 main: 17 movl $4, %eax /* sistem çağrı numarasını koy 18 write = %eax kaydında 4 */ 19 20 movl $1, %ebx /* ilk parametreyi 21 %ebx kaydına yerleştir; dosya tanımlayıcı numarası 22 stdout = 1 */ (gdb) l *$eip 0x8048329 hello.s:20 konumunda. 15 16 main: 17 movl $4, %eax /* sistem çağrı numarası 18'i yazma = 4'ü %eax kaydına yerleştirin */ 19 20 movl $1, %ebx /* ilk parametreyi 21 %ebx kaydına yerleştirin; dosya tanıtıcı numarası 22 stdout = 1 */ 23 movl $hello_str, %ecx /* ikinci parametreyi 24 %ecx yazmacına yerleştir; bir dizge işaretçisi */ (gdb) l 20, 25 20 movl $1, %ebx /* ilk parametreyi 21 %ebx yazmacına yerleştir; dosya tanıtıcı numarası 22 stdout = 1 */ 23 movl $hello_str, %ecx /* ikinci parametreyi 24 %ecx yazmacına yerleştir; dize işaretçisi */ 25 (gdb)

    Bu komutu unutmayın: list *$eip . Onun yardımıyla, o anda yürütülmekte olan talimatın kaynak kodunu her zaman görüntüleyebilirsiniz. Programımızı daha da çalıştıralım:

    (gdb) n 23 movl $hello_str, %ecx /* ikinci parametreyi %ecx kaydına yerleştir (gdb) n 26 movl $hello_str_length, %edx /* üçüncü parametreyi %edx kaydına yerleştir (gdb)

    Her seferinde n tuşuna basmak sıkıcı değil mi? Sadece Enter tuşuna basarsanız GDB son komutu tekrarlayacaktır:

    (gdb) 29 int $0x80 /* çağrı kesmesi 0x80 */ (gdb) Merhaba dünya! 31 hareket $1, %eax /* sistem çağrı numarası çıkışı = 1 */ (gdb)

    Bilmeye değer bir başka kullanışlı komut da bilgi kayıtlarıdır. Elbette i r olarak kısaltılabilir. Ona bir parametre (yazdırılması gereken kayıtların listesi) iletebilirsiniz. Örneğin, korumalı modda yürütme gerçekleştiğinde, segment kayıtlarının değerleriyle ilgilenmemiz pek olası değildir.

    (gdb) bilgi kayıtları eax 0xe 14 ecx 0x804955c 134518108 edx 0xe 14 ebx 0x1 1 esp 0xbfabb55c 0xbfabb55c ebp 0xbfabb5a8 0xbfabb5a8 esi 0x0 0 edi 0xb7f6bcc0 - 1208566592 eip 0x804833a 0x804833a eflags 0x246 [ PF ZF IF ] cs 0x73 115 ss 0x7b 123 ds 0x7b 123 es 0x7b 123 fs 0x0 0 gs 0x33 51 (gdb) bilgi kayıtları eax ecx edx ebx esp ebp esi edi eip eflags eax 0 x e 14 ecx 0x804955c 134518108 edx 0xe 14 ebx 0x1 1 esp 0xbfabb55c 0xbfabb55c ebp 0xbfabb5a8 0xbfabb5a8 esi 0x0 0 edi 0xb7f6bcc0 -1208566592 eip 0x804833a 0x804833a bayraklar 0x246 [PF ZF IF] (gdb)

    Yani, kayıtlara ek olarak, ayrıca

    G.D.B."akıllı" hata ayıklayıcı programları, yani kodu "anlayan" ve onu satır satır yürütebilen, değişkenlerin değerlerini değiştirebilen, kesme noktalarını ve durdurma koşullarını ayarlayabilen programları ifade eder... Tek kelimeyle, her şeyi yapın böylece geliştirici, programının doğru çalışıp çalışmadığını kontrol edebilir.

    G.D.B. birçoğunun içine yerleştirilmiş UNIX-sistemlere benzer ve çeşitli programlama dillerinde hata ayıklayabilir. Xi de bunların arasında.

    Aramak G.D.B. komutu terminale girin

    Gdb [hata ayıklamak istediğiniz programın adı]

    Çıkış yapmak için G.D.B.: komutu girin

    Çık veya C–d

    Diğer önemli GDB komutları

    çalıştır [program komut satırı argümanları] Programı yürütmek için çalıştırın. break [satır numarası/işlev adı] Belirli bir satıra veya işleve program kesme noktası ayarlayın. next Fonksiyonların içine girmeden bir sonraki satıra geçin. adım Sonraki satıra gidin. Hatta bir işlev çağrısı varsa içeri girin. list Program kodunun bir parçasını yazdırır (noktanın halihazırda ayarlandığı yerin etrafında birkaç satır) print [ değişken] Değişkenin değerini ekrana yazdırır. info locals Bir döngü, fonksiyon vb. içindeki tüm yerel değişkenlerin mevcut değerlerini yazdırır. display [değişken] Her hata ayıklama adımında bir değişkenin değerini görüntüleyin. yardım Tüm GDB komutlarının listesini gösterir.

    Büyük olasılıkla geçen hafta yazdığınız caesar.c programını kullanarak GDB ile nasıl çalışılacağına bakalım. Bunu kendi sürümümüzde test edeceğiz, dolayısıyla sonuçlarınız uygulamaya bağlı olarak biraz farklılık gösterebilir.

    Bu nedenle, “cs50 Sanal Laboratuvarı” veya CS50 IDE'deki pset2 klasörüne gidin (bunu nasıl yapacağınızı zaten hatırladığınızı düşünüyoruz). Komutu girin:

    Gdb. /Sezar

    Sezar programının tek bir işlevi vardır; ana. Programın kesme noktasını ana fonksiyona ayarlayalım:

    ana mola

    Sezar programını "3" argümanıyla çalıştıralım:

    13'ü çalıştır

    Diyelim ki argc değerini kontrol etmemiz gerekiyor:

    argc'yi yazdır

    Bir terminal penceresinde şöyle görünmesi gerekir:

    Şimdi bir sonraki komutu kullanarak programı adım adım çalıştırıyoruz. Bunu birkaç kez yapalım.

    Burada anahtar değişkene bir değer atanır. Bu satırın ne anlama geldiğini kontrol edelim:

    Sonraki ilk çağrıldığında anahtar değişken "0" olarak ayarlanır. Eğer 3 sayısını girdiysek neden böyle oluyor? Önemli olan, komutun henüz yürütülmemiş olmasıdır. Sonraki birkaç kez daha girdiğinizde program sizden metin girmenizi ister.

    Bir sonraki komutu tekrar çalıştırarak koşullu döngünün içine gireceğiz.

    Microsoft Windows için hata ayıklayıcılar hakkında konuşalım. Bunlardan oldukça fazla var, sadece herkesin en sevdiği OllyDbg'yi, bir zamanlar popüler olan ama artık neredeyse ölü SoftIce'ın yanı sıra Sycer, Immunity Debugger, x64dbg ve IDE'de yerleşik sayısız hata ayıklayıcıyı hatırlayın. Gözlemlerime göre herkes WinDbg'yi sevmiyor. Bunun esas olarak hata ayıklayıcı komut arayüzünden kaynaklandığını düşünüyorum. Linux ve FreeBSD hayranları şüphesiz bundan hoşlanacaktır. Ancak sıkı Windows kullanıcıları bunu garip ve rahatsız edici buluyor. Bu arada işlevsellik açısından WinDbg hiçbir şekilde diğer hata ayıklayıcılardan daha aşağı değildir. En azından klasik GDB veya LLDB'den kesinlikle daha kötü değil. Bugün göreceğimiz şey budur.

    Windows dünyasında her şey her zamanki gibi biraz karışık. Resmi WinDbg yükleyicisi MS web sitesinden indirilebilir. Bu yükleyici, WinDbg'nin yanı sıra .NET Framework'ün en son sürümünü de yükleyecek ve sormadan sistemi yeniden başlatacaktır. Kurulumdan sonra, özellikle Windows'un eski sürümlerinde hata ayıklayıcının hiç çalışmayacağı bir gerçek değil. Bu nedenle, resmi olmayan bir WinDbg veya . Acilen Bu sürümlerden birini kullanmanızı tavsiye ederim; bu, WinDbg'yi kurmanın en kolay ve hızlı yoludur.

    WinDbg'nin iki sürümü vardır: x86 ve x64. Herhangi bir sorunu önlemek için, x86 hata ayıklayıcısını kullanarak x86 uygulamalarında ve x64 hata ayıklayıcısını kullanarak x64 uygulamalarında hata ayıklayın. İlk lansmandan sonra WinDbg oldukça kötü görünecek. Ama endişelenmeyin. WinDbg ile sadece birkaç dakika çalıştıktan sonra onu kendinize göre özelleştireceksiniz ve oldukça hoş görünecektir. Örneğin şunun gibi bir şey (tıklanabilir, 51 KB, 1156) X 785):

    Bu ekran görüntüsü, Windows API üzerinde çalışan işlemlerin listesini alma notundan programda hata ayıklamayı gösterir. Gördüğünüz gibi WinDbg programın kaynak kodunu aldı. Yerel değişkenlerin değerleri sağda görüntülenir. Altta, kn komutu kullanılarak çağrı yığınının görüntülendiği komutları girmek için bir pencere vardır. Hata ayıklayıcının üst kısmında "ileri adım atma" gibi basit eylemleri gerçekleştirmenize ve ayrıca ek pencereler açmanıza olanak tanıyan düğmeler bulunur. Bu pencereleri kullanarak RAM içeriğini, kayıt değerlerini, programın sökücü listesini ve diğer birçok ilginç şeyi görüntüleyebilirsiniz.

    Genel olarak WinDbg'deki pek çok şey GUI aracılığıyla yapılabilir. Örneğin, kaynak kodu penceresinde imleci istediğiniz konuma yerleştirebilir ve avuç içi simgesine tıklayarak orada bir kesme noktası oluşturabilirsiniz. Veya imlece çalıştırmayı yürütün. Ayrıca üstteki panelde bulunan Ara düğmesini kullanarak da komutun yürütülmesini istediğiniz zaman durdurabilirsiniz. Bütün bunlar üzerinde ayrıntılı olarak durmayacağız. Ancak bu tür fırsatların mevcut olduğunu ve keşfedilmeye değer olduğunu unutmayın!

    WinDbg'yi kullanarak hata ayıklamaya başlamadan önce birkaç basit adımı uygulamanız gerekir. Dosya → Sembol Dosyası Yolunu açın ve şunu girin:

    SRV*C:\symbols*http://msdl.microsoft.com/download/symbols

    Ardından Gözat'a tıklayın ve projenizin hata ayıklama bilgileri dosyalarının (.pdb) yolunu belirtin. Benzer şekilde, Dosya → Kaynak Dosya Yolu'nda kaynak dizinin yolunu belirtin. Şüpheniz varsa, Visual Studio proje dosyasının bulunduğu yolu belirtin, yanlış gidemezsiniz. Daha sonra Dosya → Çalışma Alanını Kaydet deyin, böylece WinDbg'yi her başlattığınızda tüm bu yolların yeniden belirtilmesine gerek kalmaz.

    Artık her şey ayarlandığına göre hata ayıklamaya başlamanın birkaç yolu var. Hata ayıklayıcının altında yeni bir işlem başlatabilir, mevcut bir işleme bağlanabilirsiniz, bir kilitlenme dökümü açabilirsiniz. Bütün bunlar Dosya menüsü aracılığıyla yapılır. Uzaktan hata ayıklama olasılığı özel ilgiyi hak ediyor. Örneğin, WinDbg'yi kullanarak sürücülerde hata ayıklıyorsanız, uzaktan hata ayıklamayı kullanmaktan başka seçeneğiniz kalmaz. Bu şaşırtıcı değil, çünkü sürücü kodundaki en ufak bir hata BSOD'a yol açabilir.

    İşlemde zaten hata ayıklama yapıyorsanız ancak bunu uzaktan yapmaya başlamak istiyorsanız şunu söylüyoruz:

    Sunucu tcp:port=3003

    Bağlantı noktasının açık olup olmadığını kontrol etmeniz gerekir; bu özellikle Windows Server'da önemlidir. İstemcide Dosya → Uzak Oturuma Bağlan yapın, şunu girin:

    tcp:Bağlantı Noktası=3003,Sunucu=10.110.0.10

    Ayrıca sistemdeki herhangi bir işlemin hatalarını ayıklamanıza olanak tanıyan bir sunucu başlatabilirsiniz:

    dbgsrv.exe -t tcp:port=3003

    İstemcide Dosya → Uzak Stub'a Bağlan aracılığıyla bağlanıyoruz. Bundan sonra arayüz aracılığıyla her zamanki gibi listeden bir süreç seçebilir veya yeni bir süreç başlatabilirsiniz. Uzak makinede yalnızca işlemler çalışacaktır.

    Şimdi son olarak temel WinDbg komutlarına bakalım.

    Önemli! Bazen komutların tamamlanması çok uzun zaman alabilir; örneğin tüm hata ayıklama sembollerini aynı anda yüklemeye karar verirseniz. Beklemekten yorulursanız komut giriş alanında Ctr+C tuşlarına basmanız yeterlidir; WinDbg o anda yapmakta olduğu şeyi hemen durduracaktır.

    Yardım
    .hh komutu

    Komut penceresinde çıktıyı temizle:

    WinDbg'nin hata ayıklama sembollerini arayacağı yolu ekleyin (artı işareti olmayan aynı komut, önceden girilen tüm yolların üzerine yazacaktır):

    Sympath+ c:\pdbs

    Sembolleri yeniden yükle:

    Modül listesini göster:

    Semboller arıyoruz:

    x *!Ololo::Benim::Ad Alanım::*

    Modül için sembolleri yükleyin:

    ld modül_adı

    WinDbg herhangi bir nedenden dolayı .pdb dosyalarını bulamazsa ve yığın izlerinde satır numaralı .c/.cpp dosyalarının adları yerine module+0x19bc gibi bir şey görürseniz, aşağıdaki komut dizisini çalıştırmayı deneyin - bu size daha fazlasını verecektir sorunun olası nedenleri hakkında bilgi:

    Sym gürültülü
    .MyModule.dll dosyasını yeniden yükle

    Kaynak dizinin yolunu belirtin:

    Bir kesme noktası belirleyin:

    bp kernel32!CreateProcessA
    bp 'mysorucefile.cpp:123'
    bp `MyModule!mysorucefile.cpp:123`
    bp @@(Tam::Sınıf:Ad::yöntem)

    Yalnızca bir kez çalışacak bir kesme noktası belirleyin:

    4 geçişten sonra 5. kez çalışacak bir kesme noktası belirleyin:

    Bir kesme noktasına her ulaşıldığında komutları otomatik olarak çalıştırabilirsiniz:

    bp kernel32!LoadLibraryA ".echo \"Değişkenler:\n\"; dv"

    sen donanım kesme noktası sözdizimi şöyledir:

    ba

    Modun e, r veya w olduğu yerde - yürütme, okuma, yazma. mode = e olduğunda size parametresi yalnızca 1 olabilir. Örneğin:

    ba e 1 kernel32!LoadLibraryA

    Kesme noktalarının listesi:

    Kesme noktasını devre dışı bırak:

    Bir kesme noktasını etkinleştirin:

    Kesme noktalarını tamamen kaldırmak:

    BC numarası
    M.Ö*

    Mevcut kesme noktalarını geri yüklemek için girmeniz gereken komutları gösterin:

    Günlüğü dosyaya yaz:

    Oturumu aç c:\1.txt

    Günlüğü dosyaya yazmayı durdurun:

    Komutları dosyadan yürütün:

    Kesme noktalarını bir dosyaya kaydetmek için aşağıdaki komutları (mutlaka tek satırda!) çalıştırabilirsiniz:

    Oturumu aç c:\1.txt; .bpcmds; .logclose

    Demonte edici listesini göster:

    Kayıt değerlerini göster:

    Çağrı yığınını göster:

    Çerçeve numaralarıyla aynı şey:

    Çerçeveye taşı:

    Çerçeve numarası

    Yerel değişkenleri göster:

    Yapıyı göster:

    dt değişken_adı

    Yapıyı yinelemeli olarak göster:

    dt -r değişken_adı

    Bellek dökümü şu adreste:

    Word/dword/qword biçimindeki bellek dökümü:

    dw adresi
    gg adresi
    dq adresi

    Bit şeklinde dökümü:

    Ascii dizesini boşalt:

    Unicode dize dökümü:

    Bellek düzenleme.

    Bugün iş hayatında bir adım daha atacaksınız
    Linux sistemlerini incelemek. Size ana olanları anlatacağım
    gdb ile çalışma teknikleri. Bunlarda uzmanlaştıktan sonra herhangi bir programın nasıl çalıştığını anlayabilecek ve kendi istismarlarınızı yazabileceksiniz.

    Muhtemelen hepiniz hata ayıklayıcı diye bir şey duymuşsunuzdur; gdb bir hata ayıklayıcıdır. GDB-GNU
    Hata ayıklayıcı. Bu, Windows için bir tür SoftICE'tır (bilmeyenler için, en popüler olanıdır ve bence genellikle en iyi hata ayıklayıcıdır), yalnızca
    Linux sistemleri. Gerçek şu ki, ağda bu şeyin işleyişini gösteren çok fazla belge yok ve bir zamanlar bu konuda kendim ustalaştım. Bu yüzden,
    Belge temel gdb komutlarını kapsayacaktır. Bütün bunlar bir örnekle gösterilecektir. Örnek olarak gereksiz programı evet almaya karar verdim. Bilmeyenler için bu program sadece 'y' karakterini sonsuza kadar basıyor.Başlangıç ​​olarak bu karakteri değil 'XAKEP' stringini yazdırmayı öğretmeye karar verdim, en azından daha eğlenceli olur.

    Artık her şey yolunda. Hata ayıklayıcının kendisi şu şekilde başlar:

    Ancak çeşitli parametreler girebilirsiniz, bizim için bu, incelenen programa giden yol olacaktır:

    # gdb /usr/bin/evet

    Çekirdek dosyaları inceleyebilirsiniz, bunun için aşağıdakileri girmeniz gerekir:

    # gdb /usr/bin/yes çekirdeği

    Kayıtların içeriğini görüntülemek için de bir komuta ihtiyacınız olabilir:

    (gdb) bilgi kayıtları

    veya bunun gibi (kısa versiyon)

    Şimdi nasıl müdahale yapılacağına bakalım. Var olmak
    kesme noktaları, müdahale noktaları ve izleme noktaları. Daha spesifik olarak kırılma noktalarından bahsetmek istiyorum. Şunlara kurulabilirler:

    (gdb) break işlevi - Bir işleve girmeden önce durun
    (gdb) break *adres - Adres talimatını yürütmeden önce durdurun.

    Ayarlardan sonra tüm noktaları görüntüleyebilir, şu komutu kullanabilirsiniz:

    (gdb) bilgi molası

    Daha sonra şu noktaları silebilirsiniz:

    (gdb) kesme noktasını temizle - burada kesme kesme noktasının adıdır
    (örneğin işlev veya adres)

    Çok gerekli bir şey, bir programı çalıştırırken farklı değerleri otomatik olarak görüntüleme yeteneğidir. Bunun için bir görüntüleme komutu var:

    (gdb) display/format value ; burada format, görüntüleme formatıdır ve değer, görüntülenmesi gereken ifadenin kendisidir.

    Ekranla çalışmak için aşağıdaki komutlar sağlanmıştır:

    (gdb) bilgi ekranı - ekranlar hakkındaki bilgileri görüntüler
    (gdb) delete num - burada num – indeksli öğeleri sil
    sayı

    Bu, temel fikri elde etmek için komutlara kısa bir referanstı.
    Şimdi bunu ve biraz daha fazlasını bir örnekle göstermek istiyorum. Ve unutmayın - burada gdb'nin tüm yeteneklerinin yalnızca çok küçük bir kısmını verdim, aslında yüzlerce kat daha fazlasına sahip, o yüzden okuyun ve öğrenin.
    Söz verdiğim gibi gereksiz evet programını alıyoruz. Makinenizdeki yol benimkiyle çakışmayabilir, bu tamamen kullandığınız işletim sistemine bağlıdır, eğer varsa aramayı kullanın (komut
    bulmak).

    # gdb /usr/bin/evet

    Başlatıldığında bir hoş geldiniz mesajı yazıyor.

    GNU gdb 19991004




    GDB'nin kesinlikle garantisi yoktur. Ayrıntılar için "garantiyi göster" yazın.
    Bu GDB "i386-redhat-linux" olarak yapılandırılmıştır...
    (hata ayıklama sembolü bulunamadı)...

    Evet sonsuz sayıda sembolün çıktısını aldığından, bunları hata ayıklayıcıda değil çıktıyı görmemiz daha iyi olur.
    programlar başka bir konsola yönlendirilebilir. Yeni bir terminal açın, kim olduğumu yazın, konsol adını alacaksınız. Dışarı çıkmalı
    bunun gibi bir şey:

    Şimdi onu buna bağlıyoruz.

    (gdb) tty /dev/pts/1

    Şimdi puts() fonksiyonuna bir kesme noktası belirledik ve bunu daha açık hale getirmek için burada fonksiyonla ilgili bir man yardımı (man komutu)
    koyar)

    #katmak
    int puts(const char *s);
    puts() s dizesini ve sonundaki yeni satırı std'ye yazar
    dışarı.

    Gördüğünüz gibi fonksiyon s dizesini çıkış akışına gönderiyor. İhtiyacımız olan şey bu. Şimdilik orada duracağız.

    (gdb) mola koyar
    0x8048698'de kesme noktası 1

    Ve gdb'nin işlev çağrısında yürütülmesini durdurmasını beklemek için programın kendisini başlatırız.

    (gdb)r
    Programı başlatma: /usr/bin/yes
    0x4006d585'te kesme noktası 1: ioputs.c dosyası, satır 32.

    Kesme noktası 1, ioputs.c:32'de _IO_puts'ta (str=0x8048e59 "y") 0x4006d585
    32 ioputs.c: Böyle bir dosya veya dizin yok.
    1: x/i $eip 0x4006d585<_IO_puts+21>: mov 0x8(%ebp),%esi

    Bir mucize oldu, kırılma noktası işe yaradı. Gördüğümüz şey - ve bir işlev parametresinden veya daha doğrusu bulunduğu adresten başka bir şey görmüyoruz. Şimdi neye ihtiyacın var?
    Yapmak? Aynen öyle, bu adresteki verileri düzeltin. Aynı zamanda kendi sembolümüzü birkaç sembolün üzerine yazacağız.

    (gdb) set (char)0x8048e59 = "X"
    (gdb) set (char)0x8048e5a="A"
    (gdb) set (char)0x8048e5b="K"
    (gdb) set (char)0x8048e5c="E"
    (gdb) set (char)0x8048e5d="P"

    Şimdi yaratılışımıza bakalım. Bellekte ne yatıyor:

    (gdb) x/3sw 0x8048e59
    0x8048e59<_IO_stdin_used+437>: "XAKEP\004\b"
    0x8048e61<_IO_stdin_used+445>: ""
    0x8048e62<_IO_stdin_used+446>: ""

    Şimdi kesme noktamızı kaldıralım:

    (gdb) bilgi molası
    Sayı Türü Disp Enb Adresi Ne
    1 kesme noktası, y 0x4006d585'i ioputs.c:32 adresindeki _IO_puts'ta tutar
    kesme noktası zaten 1 kez vuruldu
    (gdb) net koyar
    Silinen kesme noktası 1

    Sonucun tadını çıkarmak için uygulamaya devam edelim:

    Bu kadar. Haydi dışarı çıkalım.

    (gdb)q
    Program çalışıyor. Yine de çıkılsın mı? (y veya n)y

    Uygulamanın bittiği yer burasıdır, gerisini kendiniz inceleyin ve bu hayattaki asıl şeyin ÖĞRENME olduğunu unutmayın.
    İşte birkaç çalışma örneği daha:

    Çalışan bir işleme ekleme:

    // gdb'yi başlat
    hack@exploit:~ > gdb
    GNU gdb 4.18
    Telif Hakkı 1998 Özgür Yazılım Vakfı, Inc.
    GDB, GNU Genel Kamu Lisansı kapsamındaki ücretsiz bir yazılımdır ve
    belirli koşullar altında onu değiştirebilir ve/veya kopyalarını dağıtabilirsiniz.
    Koşulları görmek için "kopyalamayı göster" yazın.
    GDB'nin kesinlikle garantisi yoktur. İçin "garantiyi göster" yazın
    detaylar.
    Bu GDB "i386-suse-linux" olarak yapılandırılmıştır.
    (gdb) "pid"i ekle
    (gdb) 1127 // örnek ekleyin

    Bellekte ara:

    (gdb) x/d veya x "adres" ondalık sayıyı gösterir
    (gdb) x/100s "adres" sonraki 100 ondalık rakamı gösterir
    (gdb) x 0x0804846c, 0x0804846c'de ondalık sayıyı gösterir
    (gdb) x/s "adres" adresteki dizeleri gösterir
    (gdb) x/105 0x0804846c, 0x0804846c'de 105 dize gösterir
    (gdb) x/x "adres" onaltılık adresi gösterir
    (gdb) x/10x 0x0804846c, 0x0804846c'de 10 adresi gösterir
    (gdb) x/b 0x0804846c 0x0804846c'de baytı göster
    (gdb) x/10b 0x0804846c-10 0x0804846c-10'da baytı gösterir
    (gdb) x/10b 0x0804846c+20 0x0804846c+20'de baytı gösterir
    (gdb) x/20i 0x0804846c adresinde 20 montajcı talimatını gösterir

    Yürütülebilir dosyadaki tüm bölümlerin listesi:

    (gdb) bakım bilgisi bölümleri // veya
    (gdb) mai s

    Çalıştırılabilir dosya:
    `/home/hack/homepage/challenge/buf/basic", dosya türü
    elf32-i386.
    0x080480f4->0x08048107, 0x000000f4'te: .interp ALLOC

    0x08048108->0x08048128, 0x00000108'de: .note.ABI etiketi
    ALLOC YÜKLEME SADECE OKUNABİLİR VERİ HAS_CONTENTS
    0x08048128->0x08048158, 0x00000128'de: .hash ALLOC
    SADECE OKUNABİLİR VERİ HAS_CONTENTS YÜKLE
    0x08048158->0x080481c8, 0x00000158'de: .dynsym ALLOC
    SADECE OKUNABİLİR VERİ HAS_CONTENTS YÜKLE
    0x080481c8->0x08048242, 0x000001c8'de: .dynstr ALLOC
    SADECE OKUNABİLİR VERİ HAS_CONTENTS YÜKLE
    0x08048242->0x08048250, 0x00000242'de: .gnu.version
    ALLOC SADECE OKUNABİLİR VERİYİ YÜKLE
    HAS_CONTENTS

    Adreste kırılma noktası:

    (gdb) ana parçayı sökün
    Ana işlev için montajcı kodunun dökümü:
    0x8048400

    : %ebp'ye basın
    0x8048401 : hareket %esp,%ebp
    0x8048403 : alt $0x408,%esp
    0x8048409 : $0xfffffff8,%esp ekle
    0x804840c : mov 0xc(%ebp),%eax
    0x804840f : $0x4,%eax ekle
    0x8048412 : hareket (%eax),%edx
    0x8048414 : %edx'e basın
    0x8048415 : lea 0xffffffc00(%ebp),%eax
    ...

    (gdb) break *0x8048414 // örnek
    0x8048414'te kesme noktası 1
    (gdb) ana sonu // örnek
    0x8048409'da kesme noktası 2
    (gdb)

    Allan O'Donnell'in GDB ile C Öğrenmek başlıklı makalesinin çevirisi.

    Ruby, Scheme veya Haskell gibi üst düzey dillerin doğası göz önüne alındığında C'yi öğrenmek zor olabilir. Manuel bellek yönetimi ve işaretçiler gibi düşük seviyeli C özelliklerinin üstesinden gelmenin yanı sıra, REPL olmadan da idare etmeniz gerekir. Bir REPL'de keşif amaçlı programlamaya alıştığınızda, yazma-derleme-çalıştırma döngüsüyle uğraşmak biraz sinir bozucu olabilir.

    Son zamanlarda GDB'yi C için sahte REPL olarak kullanabileceğim aklıma geldi. GDB'yi sadece hata ayıklamak yerine dili öğrenmek için bir araç olarak kullanmayı denedim ve çok eğlenceli olduğu ortaya çıktı.

    Bu yazının amacı size GDB'nin C öğrenmek için harika bir araç olduğunu göstermektir. Size GDB'deki favori komutlarımdan birkaçını tanıtacağım ve C dilinin zor kısımlarından birini anlamak için GDB'yi nasıl kullanabileceğinizi göstereceğim. C dili: diziler ve işaretçiler arasındaki fark.

    GDB'ye giriş

    Aşağıdaki küçük C programını oluşturarak başlayalım: minimum.c:

    Int main() ( int i = 1337; return 0; )
    Lütfen programın kesinlikle hiçbir şey yapmadığını ve tek bir komutun bile olmadığını unutmayın. baskı. Şimdi GBD kullanarak C öğrenmenin yeni dünyasına dalalım.

    Bu programı flag ile derleyelim -G GDB'nin birlikte çalışacağı hata ayıklama bilgilerini oluşturmak ve ona şu bilgileri vermek için:

    $ gcc -g minimum.c -o minimum $ gdb minimum
    Artık bir anda GDB komut isteminde olmalısınız. Size bir REPL sözü verdim, işte alacağınız şey:

    (gdb) yazdır 1 + 2 $1 = 3
    Muhteşem! Yazdır bir C ifadesinin sonucunu değerlendiren yerleşik bir GDB komutudur. Bir GDB komutunun tam olarak ne işe yaradığını bilmiyorsanız, yardım almanız yeterli - yazın yardım-komut adı GDB komut satırında.

    İşte daha ilginç bir örnek:

    (gbd) yazdır (int) 2147483648 $2 = -2147483648
    Nedenini açıklamayı özleyeceğim 2147483648 == -2147483648 . Buradaki asıl nokta, C'de aritmetiğin bile yanıltıcı olabileceği ve GDB'nin C aritmetiğini mükemmel bir şekilde anladığıdır.

    Şimdi fonksiyona bir kesme noktası koyalım ana ve programı çalıştırın:

    (gdb) ana (gdb) çalıştırmayı sonlandır
    Program üçüncü satırda, değişkenin başlatıldığı yerde durdu Ben. İlginç olan şu ki, değişken henüz başlatılmamış olsa da, değerini zaten komutu kullanarak görebiliyoruz. Yazdır:

    (gdb) yazdır i $3 = 32767
    C'de yerel başlatılmamış değişkenin değeri tanımsızdır, dolayısıyla elde ettiğiniz sonuç değişebilir.

    Komutu kullanarak mevcut kod satırını çalıştırabiliriz. Sonraki:

    (gdb) sonraki (gdb) yazdır i $4 = 1337

    Komutu kullanarak hafızayı keşfediyoruz X

    C'deki değişkenler bitişik bellek bloklarıdır. Bu durumda her değişkenin bloğu iki sayıyla karakterize edilir:

    1. Bloktaki ilk baytın sayısal adresi.
    2. Bayt cinsinden blok boyutu. Bu boyut değişkenin türüne göre belirlenir.

    C dilinin ayırt edici özelliklerinden biri, bir değişkenin bellek bloğuna doğrudan erişiminizin olmasıdır. Şebeke & bize bellekteki değişkenin adresini verir ve boyutu Bir bellek değişkeninin kapladığı boyutu hesaplar.

    GDB'de her iki olasılıkla da oynayabilirsiniz:

    (gdb) print &i $5 = (int *) 0x7fff5fbff584 (gdb) print sizeof(i) $6 = 4
    Normal dilde bu, değişkenin şu anlama geldiği anlamına gelir: Ben da yerleşmiş 0x7fff5fbff5b4 ve hafızada 4 byte yer kaplar.

    Bellekteki bir değişkenin boyutunun türüne ve genel olarak operatöre bağlı olduğunu yukarıda belirtmiştim. boyutu veri türlerinin kendisiyle çalışabilir:

    (gdb) baskı boyutu(int) $7 = 4 (gdb) baskı boyutu(double) $8 = 8
    Bu, en azından benim makinemde aşağıdaki gibi değişkenlerin olduğu anlamına gelir: int dört bayt kaplar ve yazın çift– sekiz bayt.

    GDB'nin belleği doğrudan incelemek için güçlü bir aracı vardır - komut X. Bu komut, belirli bir adresten başlayarak belleği test eder. Ayrıca, kaç baytı kontrol etmek istediğiniz ve bunları nasıl görüntülemek istediğiniz konusunda size kesin kontrol sağlayan bir dizi biçimlendirme komutu da vardır. Herhangi bir zorluk durumunda arayın yardım et x GDB komut satırında.

    Bildiğiniz gibi operatör & bir değişkenin adresini hesaplar; bu, onun komuta aktarılabileceği anlamına gelir X Anlam &Ben ve böylece değişkenin arkasında saklanan bireysel baytlara bakma fırsatını yakalayın Ben:

    (gdb) x/4xb &i 0x7fff5fbff584: 0x39 0x05 0x00 0x00
    Biçimlendirme bayrakları dört tane istediğimi gösteriyor ( 4 ) çıktıyı onaltılık olarak değerlendirir (o X) bir bayt biçiminde ( B Evet). Yalnızca dört baytı kontrol etmeyi belirttim, çünkü bu, değişkenin bellekte kapladığı miktardır Ben. Çıktı, bellekteki değişkenin bayt bayt temsilini gösterir.

    Ancak bayt-bayt çıkışıyla ilgili olarak sürekli akılda tutulması gereken bir incelik vardır: Intel makinelerde baytlar, daha tanıdık kayıt yönteminin aksine, "en az önemliden en yükseğe" (sağdan sola) doğru sırayla depolanır. insanlar için, en düşük baytın sonunda olması gerekir (soldan sağa).

    Bu konuyu açıklığa kavuşturmanın bir yolu bir değişken atamaktır Ben daha ilginç bir değer ve bu hafıza alanını tekrar kontrol edin:

    (gdb) var i = 0x12345678'i ayarlayın (gdb) x/4xb &i 0x7fff5fbff584: 0x78 0x56 0x34 0x12

    Ekiple hafızayı keşfetmek ptipi

    Takım ptipi belki de favorilerimden biri. C ifadesinin türünü gösterir:

    (gdb) ptype i türü = int (gdb) ptype &i türü = int * (gdb) ptype ana türü = int (void)
    C'deki türler karmaşık olabilir ancak ptipi bunları etkileşimli olarak keşfetmenize olanak tanır.

    İşaretçiler ve Diziler

    Diziler C'de şaşırtıcı derecede incelikli bir kavramdır. Bu noktanın amacı basit bir program yazmak ve ardından onu diziler bir anlam ifade edene kadar GDB'de çalıştırmaktır.

    Bu yüzden dizili program koduna ihtiyacımız var dizi.c:

    Int main() ( int a = (1, 2, 3); return 0; )
    Bayrakla derleyin -G, GDB'de çalıştırın ve yardımla Sonraki başlatma satırına gidin:

    $ gcc -g diziler.c -o diziler $ gdb diziler (gdb) ara ana (gdb) çalıştır (gdb) sonraki
    Bu noktada değişkenin içeriğini görüntüleyebilir ve türünü öğrenebilirsiniz:

    (gdb) bir $1 yazdır = (1, 2, 3) (gdb) ptype a type = int
    Artık programımız GDB'de düzgün şekilde yapılandırıldığına göre yapılacak ilk şey şu komutu kullanmaktır: X değişkenin neye benzediğini görmek için A“kaputun altında”:

    (gdb) x/12xb &a 0x7fff5fbff56c: 0x01 0x00 0x00 0x00 0x02 0x00 0x00 0x00 0x7fff5fbff574: 0x03 0x00 0x00 0x00
    Bu, dizinin bellek alanının A adreste başlar 0x7fff5fbff56c. İlk dört bayt şunları içerir: A, sonraki dördü A ve son dört mağaza A. Gerçekten, kontrol edip emin olabilirsiniz boyutu Bunu biliyor A Bellekte tam olarak on iki bayt yer kaplar:

    (gdb) baskı boyutu(a) $2 = 12
    Bu noktaya kadar diziler olması gerektiği gibi görünüyor. Karşılık gelen dizi türlerine sahiptirler ve tüm değerleri bitişik bellek konumlarında saklarlar. Ancak bazı durumlarda diziler işaretçilere çok benzer davranır! Örneğin aritmetik işlemleri uygulayabiliriz. A:

    (gdb) print a + 1 $3 = (int *) 0x7fff5fbff570
    Normal bir deyişle, bu şu anlama gelir: a+1 bir işaretçidir int adresi olan 0x7fff5fbff570. Bu noktada refleks olarak işaretçileri komuta iletiyor olmalısınız X, bakalım ne oldu:

    (gdb) x/4xb a + 1 0x7fff5fbff570: 0x02 0x00 0x00 0x00

    Lütfen adresin 0x7fff5fbff570 tam olarak dört birimden fazla 0x7fff5fbff56c yani dizinin ilk baytının adresi A. tipi göz önüne alındığında int Bellekte dört bayt yer kaplıyor, şu sonuca varabiliriz: a+1 noktalar A.

    Aslında, C'deki dizi indeksleme, işaretçi aritmetiği için sözdizimsel şekerdir: bir[i] eş değer *(a + i). Bunu GDB'de kontrol edebilirsiniz:

    (gdb) $4 = 1 yazdır (gdb) yazdır *(a + 0) $5 = 1 (gdb) $6 = 2 yazdır (gdb) yazdır *(a + 1) $7 = 2 (gdb) $8 = 3 yazdır (gdb) yazdır *(a + 2) $9 = 3
    Yani bazı durumlarda bunu gördük. A bir dizi gibi davranır ve bazı durumlarda ilk elemanına işaretçi gibi davranır. Neler oluyor?

    Cevap şu: C'deki bir ifadede bir dizi adı kullanıldığında, ilk öğeye işaret eden bir işaretçiye "bozunur". Bu kuralın yalnızca iki istisnası vardır: dizi adı aktarıldığında boyutu ve dizi adı adres alma operatörüyle birlikte kullanıldığında & .

    İsmin gerçeği A operatörü kullanırken ilk öğeye yönelik bir işaretçiye dönüşmez & , ilginç bir soruyu gündeme getiriyor: İşaretin bulunduğu işaretçi arasındaki fark nedir? A Ve &A?

    Sayısal olarak ikisi de aynı adresi temsil eder:

    (gdb) x/4xb a 0x7fff5fbff56c: 0x01 0x00 0x00 0x00 (gdb) x/4xb &a 0x7fff5fbff56c: 0x01 0x00 0x00 0x00
    Ancak türleri farklıdır. Daha önce de gördüğümüz gibi, bir dizinin adı onun ilk elemanına yönelik bir işaretçiye çözümlenir ve bu nedenle türden olmalıdır. dahili *. Türüne gelince &A, o zaman GDB'ye bunu sorabiliriz:

    (gdb) ptype &a type = int (*)
    Basitçe söylemek gerekirse, &Aüç tam sayıdan oluşan bir dizinin işaretçisidir. Mantıklı: A Operatöre aktarıldığında parçalanmaz & ve bir türü var int.

    Bölünen işaretçi arasındaki farkı izleyebilirsiniz. A ve ameliyat &Aİşaretçi aritmetiğine göre nasıl davrandıklarına dair bir örnek:

    (gdb) yazdır a + 1 $10 = (int *) 0x7fff5fbff570 (gdb) yazdır &a + 1 $11 = (int (*)) 0x7fff5fbff578
    1 eklemeyi unutmayın A adresi dört birim artırırken 1 ekler &A adrese on iki ekler.

    Aslında çürüyen işaretçi A benziyor &A:

    (gdb) yazdır &a $11 = (int *) 0x7fff5fbff56c

    Çözüm

    Umarım sizi GDB'nin C öğrenmek için zarif bir keşif ortamı olduğuna ikna etmişimdir. Komutu kullanarak ifadelerin anlamlarını yazdırmanıza olanak tanır. Yazdır, şu komutla belleği bayt bayt keşfedin X ve komutu kullanarak türlerle çalışın ptipi.

    1. Ksplice Pointer Challenge üzerinde çalışmak için GDB'yi kullanın.
    2. Yapıların bellekte nasıl saklandığını anlayın. Dizilerle nasıl ilişkilidirler?
    3. Assembly dili programlamasını daha iyi anlamak için GDB disassembler komutlarını kullanın. İşlev çağrı yığınının nasıl çalıştığını keşfetmek özellikle eğlencelidir.
    4. Her zamanki GDB'nin üzerine grafiksel bir ncurses eklentisi sağlayan GDB'nin “TUI” moduna göz atın. OS X'te muhtemelen GDB'yi kaynaktan oluşturmanız gerekecektir.

    Çevirmenden: Geleneksel olarak hataları belirtmek için LAN'ı kullanın. Yapıcı eleştiri almaktan memnuniyet duyacağım.