• Ladění pomocí GDB. Základy ladicího programu WinDbg Příkazy ladicího programu gdb

    Cílem ladění programu je odstranit chyby v jeho kódu. K tomu budete s největší pravděpodobností muset prozkoumat stav proměnných během dodací lhůta, stejně jako samotný proces provádění (například pro sledování podmíněných skoků). Zde je debugger naším prvním pomocníkem. C má samozřejmě spoustu možností pro ladění bez přímého zastavení programu: od jednoduchého printf(3) po speciální síťové logovací systémy a syslog . V assembleru jsou takové metody také použitelné, ale možná budete muset sledovat stav registrů, image ( dump ) RAM a další věci, které je mnohem pohodlnější dělat v interaktivním debuggeru. Obecně platí, že pokud píšete v assembleru, pak se pravděpodobně neobejdete bez debuggeru.

    Ladění můžete začít definováním bodu přerušení ( breakpoint ), pokud již přibližně víte, kterou část kódu prozkoumat. Tato metoda se používá nejčastěji: nastavíme bod přerušení, spustíme program a krok za krokem procházíme jeho prováděním, přičemž současně dodržujeme potřebné proměnné a registry. Můžete také jen spustit program pod debuggerem a zachytit okamžik, kdy se zhroutí kvůli chybě segmentace – můžete tak zjistit, která instrukce se pokouší o přístup do paměti, podívat se blíže na proměnnou, která chybu způsobuje a podobně. Nyní můžete tento kód znovu prozkoumat, projít si jej a umístit bod přerušení těsně před okamžikem selhání.

    Začněme jednoduše. Vezměme program Hello world a zkompilujeme jej s ladicími informacemi pomocí přepínače kompilátoru -g:

    $ gcc -g ahoj.s -o ahoj $

    Spustit gdb:

    $ gdb ./hello GNU gdb 6.4.90-debian Copyright (C) 2006 Free Software Foundation, Inc. GDB je svobodný software, na který se vztahuje obecná veřejná licence GNU, a za určitých podmínek jej můžete měnit a/nebo distribuovat jeho kopie. Chcete-li zobrazit podmínky, zadejte „zobrazit kopírování“. Na GDB není absolutně žádná záruka. Podrobnosti zobrazíte zadáním „zobrazit záruku“. Tato GDB byla nakonfigurována jako "i486-linux-gnu"...Pomocí hostitelské knihovny libthread_db "/lib/tls/libthread_db.so.1". (gdb)

    GDB se spustilo, načetlo zkoumaný program, zobrazilo výzvu (gdb) a čeká na příkazy. Chceme projít programem "po krocích" ( jednokrokový režim ). Chcete-li to provést, musíte zadat příkaz, na kterém se má program zastavit. Můžete zadat podprogram - pak bude zastavení provedeno před začátkem provádění pokynů tohoto podprogramu. Můžete také zadat název souboru a číslo řádku.

    (gdb) b hlavní bod přerušení 1 na 0x8048324: soubor hello.s, řádek 17. (gdb)

    b je zkratka pro break . Všechny příkazy v GDB lze zkrátit, pokud nevytvářejí nejednoznačné přepisy. Program spustíme příkazem run. Stejný příkaz se používá k restartování dříve spuštěného programu.

    (gdb) r Spouštěcí program: /tmp/hello Bod zlomu 1, hlavní () at hello.s:17 17 movl $4, %eax /* put system call number write = 4 Aktuální jazyk: auto; aktuálně asm (gdb)

    GDB zastavil program a čeká na příkazy. Zobrazí se příkaz vašeho programu, který bude proveden jako další, název funkce, která se právě provádí, název souboru a číslo řádku. Pro krokování máme dva příkazy: step (zkratka pro s ) a next (zkratka pro n ). Příkaz step spustí program zadáním těl podprogramů. Další příkaz pouze prochází instrukcemi aktuálního podprogramu.

    (gdb) n 20 movl $1, %ebx /* první parametr do registru %ebx */ (gdb)

    Provede se tedy instrukce na řádku 17 a očekáváme, že v registru %eax bude číslo 4. Příkaz print (zkráceně p ) slouží k zobrazení různých výrazů na obrazovce. Na rozdíl od instrukcí assembleru používá GDB při zápisu registrů znak $ místo %. Podívejme se, co je v registru %eax:

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

    Opravdu 4! GDB čísluje všechny výstupní výrazy. Nyní vidíme první výraz ($1 ), který se rovná 4. Na tento výraz lze nyní odkazovat jeho jménem. Můžete také provádět jednoduché výpočty:

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

    Zatímco jsme si hráli s příkazem print, už jsme zapomněli, která instrukce se provede jako další. Příkaz info line zobrazí informace o zadaném řádku kódu. Bez argumentů zobrazí informace o aktuálním řádku.

    (gdb) informační řádek 20. řádek "hello.s" začíná na adrese 0x8048329 končí na 0x804832e . (gdb)

    Příkaz list (zkráceně l ) zobrazí zdrojový kód vašeho programu. Lze to předat jako argumenty:

    • číslo řádku- číslo řádku v aktuálním souboru;
    • soubor:číslo_řádku- číslo řádku v zadaném souboru;
    • název_funkce- název funkce, pokud neexistují žádné nejasnosti;
    • soubor:název_funkce- název funkce v zadaném souboru;
    • *adresa- adresa v paměti, kde se nachází požadovaná instrukce.

    Pokud předáte jeden argument, příkaz list vypíše 10 řádků zdrojového kódu kolem tohoto umístění. Předáním dvou argumentů určíte počáteční a koncový řetězec výpisu.

    (gdb) l main 12 mimo tento soubor */ 13 .type main, @function /* main je funkce (ne data) */ 14 15 16 main: 17 movl $4, %eax /* vložte číslo systémového volání 18 write = 4 do registru %eax */ 19 20 movl $1, %ebx1 vložte první parametr do registru /2 číslo deskriptoru souboru 22 stdout = 1 */ (gdb) l *$eip 0x8048329 je na adrese hello.s:20. 15 16 main: 17 movl $4, %eax /* vložte číslo systémového volání 18 zápis = 4 do registru %eax */ 19 20 movl $1, %ebx /* vložte první parametr do registru 21 %ebx; číslo deskriptoru souboru 22 stdout = 1 */ 23 movl $hello_str, %ecx /* vložte druhý parametr do registru 24 %ecx; ukazatel na řetězec */ (gdb) l 20, 25 20 movl $1, %ebx /* umístění prvního parametru do registru 21 %ebx; číslo deskriptoru souboru 22 stdout = 1 */ 23 movl $hello_str, %ecx /* vložte druhý parametr do registru 24 %ecx; ukazatel na řetězec */ 25 (gdb)

    Zapamatujte si tento příkaz: list *$eip . S ním můžete vždy zobrazit zdrojový kód kolem aktuálně prováděné instrukce. Spusťte náš program:

    (gdb) n 23 movl $hello_str, %ecx /* vložte druhý parametr do registru %ecx (gdb) n 26 movl $hello_str_length, %edx /* vložte třetí parametr do registru %edx (gdb)

    Není to únavné pokaždé stisknout n? Pokud pouze stisknete Enter , GDB zopakuje poslední příkaz:

    (gdb) 29 int $0x80 /* spouštěcí přerušení 0x80 */ (gdb) Ahoj světe! 31 movl $1, %eax /* ukončení systémového telefonního čísla = 1 */ (gdb)

    Dalším užitečným příkazem, o kterém stojí za to vědět, jsou informační registry. Samozřejmě to lze snížit na i r . Lze mu předat parametr – seznam registrů k tisku. Například, když dojde ke spuštění v chráněném režimu, je nepravděpodobné, že bychom se zajímali o hodnoty registrů segmentů.

    (gdb) info registry eax 0xe 14 ecx 0x804955c 134518108 edx 0xe 14 ebx 0x1 1 esp 0xbfabb55c 0xbfabb55c ebp 0xbfabb5a5 8aibes 0xbfabb5a5 8xbx0x0xbfabb5a8 0xbx0x0 0 –1208566592 eip 0x804833a 0x804833a elags 0x246 [ PF ZF IF ] cs 0x73 115 ss 0x7b 123 ds 0x7b 123 es 0x7b 123 fs 0x0 0 gs 0x33 51 (gdb) info registry eax ebigepesepesxed axe 0xe 14 ecx 0x804955c 134518108 edx 0xe 14 ebx 0x1 1 esp 0xbfabb55c 0xbfabb55c ebp 0xbfabb5a8 0xbfabb5af6 0xbfabb5af6 esi 506 esi -5006 9 2eip 0x804833a 0x804833a vlajky 0x246 [ PF ZF IF ] (gdb)

    Takže kromě registrů máme také

    gdb odkazuje na "chytré" debugger programy, tedy takové, které "rozumí" kódu a jsou schopny jej spouštět řádek po řádku, měnit hodnoty proměnných, nastavovat breakpointy a podmínky zastavení... Jedním slovem, udělejte vše pro to, aby si vývojář mohl zkontrolovat správnou činnost svého programu.

    gdb zasazené do mnoha UNIX podobné systémy a je schopen ladit několik programovacích jazyků. Si je jedním z nich.

    Zavolat gdb zadejte příkaz do terminálu

    Gdb [název_programu_chcete_ladit]

    K odchodu z gdb: zadejte příkaz

    Konec nebo C -d

    Další důležité příkazy GDB

    spustit [argumenty příkazového řádku programu] Spusťte program ke spuštění. break [číslo řádku / název funkce] Nastavte bod přerušení programu na konkrétním řádku nebo funkci. next Skok na další řádek bez přechodu do funkcí. krok Přejít na další řádek. Pokud je na lince volání funkce, jděte dovnitř. list Vytiskne část kódu programu (několik řádků kolem místa, kde je aktuálně nastaven bod) print [ proměnná] Vypíše hodnotu proměnné na obrazovku. info locals Tisk aktuálních hodnot všech lokálních proměnných v rámci cyklu, funkce atd. display [ variable ] Zobrazení hodnoty proměnné v každém kroku ladění. help Zobrazí seznam všech příkazů GDB.

    Podívejme se, jak pracovat s GDB pomocí ukázkového programu caesar.c, který jste pravděpodobně již psali minulý týden. Zkontrolujeme naši vlastní verzi, takže vaše výsledky se mohou mírně lišit v závislosti na implementaci.

    Přejděte tedy do složky pset2 (myslíme, že si již pamatujete, jak to udělat) v cs50 Virtual Lab nebo CS50 IDE. Zadáme příkaz:

    gdb. /Caesar

    Program caesar má jednu funkci, hlavní. Nastavte bod přerušení programu na hlavní funkci:

    přerušit hlavní

    Spusťte program caesar s argumentem "3":

    Běh 13

    Řekněme, že potřebujeme zkontrolovat hodnotu argc:

    Tisk argc

    Takto by to mělo vypadat v okně terminálu:

    Nyní provedeme program krok za krokem pomocí dalšího příkazu. Udělejme to několikrát.

    Zde je klíči proměnné přiřazena hodnota. Podívejme se, jakou hodnotu má pro tento řádek:

    Při prvním zavolání další je klíči přiřazena hodnota "0". Proč ano, když jsme zadali číslo 3? Jde o to, že příkaz ještě nebyl proveden. Když zadáte další ještě několikrát, program vás vyzve k zadání textu.

    Opětovným provedením dalšího příkazu se dostaneme do smyčky s podmínkou.

    Pojďme se bavit o debuggerech pro Microsoft Windows. Není jich málo, vzpomeňte si alespoň na všemi oblíbený OllyDbg, kdysi populární, ale dnes už téměř mrtvý SoftIce, stejně jako Sycer, Immunity Debugger, x64dbg a nespočet debuggerů zabudovaných v IDE. Podle mého pozorování ne každý má rád WinDbg. Myslím, že to většinou souvisí s příkazovým rozhraním debuggeru. Fanouškům Linuxu a FreeBSD by se to jistě líbilo. Otrlým uživatelům Windows to ale připadá divné a nepohodlné. Mezitím, pokud jde o funkčnost, WinDbg není v žádném případě horší než ostatní debuggery. Minimálně to rozhodně není horší než klasické GDB nebo tam LLDB. Co dnes s vámi uvidíme.

    Ve světě Windows je vše jako obvykle tak trochu přes zadnici. Oficiální instalátor WinDbg lze stáhnout z webu MS. Tento instalátor vám kromě WinDbg dodá také nejnovější verzi .NET Framework a bez ptaní restartuje systém. Po instalaci není jisté, že debugger bude vůbec fungovat, zvláště pod staršími verzemi Windows. Proto je lepší stáhnout si neoficiální sestavení WinDbg nebo . Silně Doporučuji vám použít jednu z těchto verzí - je to nejjednodušší a nejrychlejší způsob instalace WinDbg.

    Existují dvě verze WinDbg, x86 a x64. Chcete-li se vyhnout jakýmkoli problémům, laďte aplikace x86 pomocí ladicího programu x86 a aplikace x64 pomocí ladicího programu x64. Po prvním spuštění bude WinDbg vypadat dost chudě. Ale nedělejte si s tím starosti. Po pár minutách práce s WinDbg si ho upravíte pro sebe a bude to vypadat docela hezky. Například něco takového (klikací, 51 Kb, 1156 X 785):

    Tento snímek obrazovky ukazuje ladění programu z poznámky Získání seznamu běžících procesů na Windows API. Jak můžete vidět, WinDbg sebral zdrojový kód programu. Hodnoty lokálních proměnných jsou zobrazeny vpravo. V dolní části je okno pro zadávání příkazů, kde se pomocí příkazu kn zobrazil zásobník volání. V horní části ladicího programu jsou tlačítka, která můžete použít k provádění jednoduchých akcí, jako je „krok vpřed“, a také k otevření dalších oken. Pomocí těchto oken můžete zobrazit obsah paměti RAM, hodnoty registrů, výpis programu v disassembleru a mnoho dalších zajímavých věcí.

    Obecně se dá spousta věcí ve WinDbg dělat přes GUI. Například v okně zdrojového kódu můžete umístit kurzor na správné místo a kliknutím na ikonu dlaně tam vytvořit bod přerušení. Nebo spusťte běh na kurzor. Provádění příkazu lze také kdykoli zastavit pomocí tlačítka Přerušit na panelu v horní části. Tomu všemu se nebudeme podrobně věnovat. Ale mějte na paměti, že takové příležitosti existují a zaslouží si být prozkoumány!

    Než začnete ladit pomocí WinDbg, musíte udělat několik jednoduchých gest. Otevřete Soubor → Cesta k souboru symbolu a zadejte:

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

    Poté klikněte na Procházet a přejděte k souborům s informacemi o ladění (.pdb) vašeho projektu. Podobně v Soubor → Cesta ke zdrojovému souboru zadejte cestu ke zdrojovému adresáři. Pokud máte pochybnosti, zadejte cestu, kde se nachází soubor projektu sady Visual Studio, nemůžete udělat chybu. Potom řekněte Soubor → Uložit pracovní prostor, abyste nemuseli znovu zadávat všechny tyto cesty při každém spuštění WinDbg.

    Nyní, když je vše nastaveno, existuje několik způsobů, jak zahájit ladění. Můžete spustit nový proces pod ladicím programem, můžete se připojit k existujícímu, můžete otevřít výpis při selhání. To vše se provádí prostřednictvím nabídky Soubor. Zvláštní pozornost si zaslouží možnost vzdáleného ladění. Pokud například používáte WinDbg k ladění ovladačů, zdá se, že nemáte moc na výběr, než použít vzdálené ladění. Což není překvapivé, protože sebemenší chyba v kódu ovladače může vést k BSOD.

    Pokud již ladíte proces, ale chtěli byste to začít dělat na dálku, říkáme:

    Server tcp:port=3003

    Musíte zkontrolovat, zda je port otevřený, což platí zejména pro Windows Server. Na klientovi proveďte Soubor → Připojit ke vzdálené relaci, zadejte:

    tcp:Port=3003,Server=10.110.0.10

    Kromě toho můžete spustit server, který vám umožní ladit jakýkoli proces v systému:

    dbgsrv.exe -t tcp:port=3003

    Na klientovi se připojíme přes File → Connect to Remote Stub. Poté můžete prostřednictvím rozhraní jako obvykle vybrat proces ze seznamu nebo spustit nový. Na vzdáleném počítači budou fungovat pouze procesy.

    Nyní se konečně podíváme na základní příkazy WinDbg.

    Důležité! Někdy může provedení příkazů trvat velmi dlouho, například pokud se rozhodnete načíst všechny symboly ladění najednou. Pokud vás čekání omrzí, stačí stisknout Ctr+C ve vstupním poli příkazu a WinDbg okamžitě přestane dělat to, co nyní dělá.

    Pomoc
    příkaz .hh

    Vymazat výstup v okně příkazu:

    Přidejte cestu, kde bude WinDbg hledat symboly ladění (stejný příkaz bez znaménka plus přepíše všechny dříve registrované cesty):

    Sympath + c:\pdbs

    Znovu načíst symboly:

    Zobrazit seznam modulů:

    Hledáme symboly:

    x *!Ololo::Můj::Jmenný prostor::*

    Symboly zatížení pro modul:

    ld název_modulu

    Pokud z nějakého důvodu WinDbg nenalezne soubory .pdb a místo názvů souborů .c/.cpp s čísly řádků vidíte ve trasování zásobníku něco jako module+0x19bc, zkuste následující sekvenci příkazů – tímto způsobem získáte další informace o možných příčinách problému:

    Sym hlučný
    .znovu načtěte MyModule.dll

    Zadejte cestu ke zdrojovému adresáři:

    Nastavte bod přerušení:

    bp kernel32!CreateProcessA
    bp `mysorucefile.cpp:123`
    bp `MyModule!mysorucefile.cpp:123`
    bp @@(Full::Class:Name::metoda)

    Nastavte bod přerušení, který se spustí pouze jednou:

    Nastavte bod přerušení, který bude pátý po 4 průchodech:

    Při každém zásahu do bodu přerušení můžete automaticky spustit příkazy:

    bp kernel32!LoadLibraryA ".echo \"Proměnné:\n\"; dv"

    Na Hardware syntaxe bodu přerušení je:

    ba

    Kde režim je e, r nebo w - provádění, čtení, zápis. S mode = e může být parametr size pouze 1. Například:

    ba e 1 kernel32!LoadLibraryA

    Seznam bodů přerušení:

    Deaktivovat bod přerušení:

    Aktivovat bod přerušení:

    Úplné odstranění bodů přerušení:

    bc číslo
    před naším letopočtem.*

    Ukažte příkazy, které potřebujete zadat k obnovení aktuálních bodů přerušení:

    Zapsat protokol do souboru:

    Logoopen c:\1.txt

    Zastavit zápis protokolu do souboru:

    Spouštět příkazy ze souboru:

    Chcete-li uložit body přerušení do souboru, můžete provést (vyžadováno na jednom řádku!) následující příkazy:

    Logopen c:\1.txt; .bpcmds; .logzavřít

    Zobrazit výpis disassembleru:

    Zobrazit hodnotu registru:

    Zobrazit zásobník hovorů:

    Totéž s čísly rámů:

    Přesunout do rámečku:

    číslo rámu

    Zobrazit místní proměnné:

    Zobrazit strukturu:

    dt název_proměnné

    Zobrazit strukturu rekurzivně:

    dt -r název_proměnné

    Výpis paměti na:

    Výpis paměti jako word/dword/qword:

    dw adresu
    dd adresu
    dq adresa

    Bitový výpis:

    Dump ascii řetězec:

    Vypsat řetězec unicode:

    Editace paměti.

    Dnes uděláte v podnikání ještě jeden krok
    učení se systémů Linux. Budu mluvit o tom hlavním
    triky při práci s gdb. Po jejich zvládnutí můžete pochopit, jak funguje jakýkoli program, napsat své vlastní exploity.

    Pravděpodobně jste všichni slyšeli o takové věci jako debugger, gdb je debugger. gdb-gnu
    debugger. Jedná se o druh SoftICE pro Windows (pro ty, kteří nevědí - nejoblíbenější a podle mého názoru nejlepší debugger obecně), pouze pod
    Linuxové systémy. Faktem je, že na síti není tolik dokumentů, které demonstrují práci této věci, a najednou jsem to zvládl sám. Tak,
    dokument bude popisovat základní příkazy gdb. To vše bude ukázáno na příkladu. A jako příklad jsem se rozhodl vzít nepotřebný program ano. Pro ty, kteří to nevědí, tento program jednoduše zobrazuje znak 'y' do nekonečna, pro začátek jsem se rozhodl, že ho naučím zobrazovat ne tento znak, ale řetězec 'XAKEP', alespoň to bude zábavnější.

    Tak a teď je vše v pořádku. Samotný debugger začíná takto:

    Můžete ale zadat různé parametry, pro nás to bude cesta ke studovanému programu:

    # gdb /usr/bin/yes

    Můžete prozkoumat základní soubory, k tomu musíte zadat následující:

    # gdb /usr/bin/yes jádro

    K zobrazení obsahu registrů budete možná potřebovat také příkaz:

    (gdb) info registry

    nebo takhle (zkrácená verze)

    Nyní se podíváme na to, jak provádět odposlechy. Existovat
    body přerušení, body zavěšení a body sledování. Konkrétněji bych se rád zaměřil na body přerušení. Mohou být instalovány na:

    (gdb) break function - Zastavení před zadáním funkce
    (gdb) break *adress - Zastavení před provedením instrukce na adrese.

    Po nastavení si můžete prohlédnout všechny body k tomu, použijte příkaz:

    (gdb) info přestávka

    A pak můžete odstranit tyto body:

    (gdb) clear breakpoint - kde break je název bodu přerušení
    (např. funkce nebo adresa)

    Velmi potřebnou věcí je schopnost automaticky zobrazovat různé hodnoty, když je program spuštěn. K tomu existuje příkaz zobrazení:

    (gdb) display/format value , kde format je formát zobrazení a hodnota je samotný výraz, který se má zobrazit.

    Pro práci s displejem jsou přiřazeny následující příkazy:

    (gdb) info display - zobrazuje informace o displejích
    (gdb) delete num - kde num je odstranění prvků v indexu
    č

    Byl to malý příkazový průvodce pro získání základní představy.
    Dále na příkladu by bylo žádoucí to demonstrovat a je to trochu víc. A pamatujte - zde jsem uvedl jen velmi malou část všech možností gdb, ve skutečnosti jich má stokrát více, takže čtěte a učte se.
    Jak jsem slíbil, bereme zbytečný program ano. Cesta na vašem počítači se nemusí shodovat s mojí, vše závisí na operačním systému, který používáte, pokud něco, použijte vyhledávání (příkaz
    nalézt).

    # gdb /usr/bin/yes

    Po spuštění se zobrazí uvítací zpráva.

    GNU gdb 19991004




    Na GDB není absolutně žádná záruka. Podrobnosti zobrazíte zadáním „zobrazit záruku“.
    Tato GDB byla nakonfigurována jako "i386-redhat-linux"...
    (nenalezeny žádné symboly ladění)...

    Protože yes vypisuje nekonečné množství znaků, bylo by pro nás lepší, abychom je v ladicím programu neviděli a výstup
    programy lze přesměrovat na jinou konzoli. Otevřete nový terminál, napište kdo je i a získáte název konzole. Musím vypadnout
    něco takového:

    Teď se k tomu jen přivaž.

    (gdb) tty /dev/pts/1

    A nyní dáme zarážku na funkci puts () a aby to bylo jasnější, zde je man-help o funkci (příkaz man
    klade)

    #zahrnout
    int puts(const char*s);
    puts() zapíše řetězec s a koncový nový řádek do std
    ven.

    Jak vidíte, funkce posílá řetězec s do výstupního proudu. To je to, co potřebujeme. U toho se tedy prozatím zastavíme.

    (gdb) break klade
    Bod zlomu 1 na 0x8048698

    A spustíme samotný program, abychom počkali, až gdb zastaví jeho provádění při volání funkce.

    (gdb)r
    Spouštěcí program: /usr/bin/yes
    Bod přerušení 1 na 0x4006d585: soubor ioputs.c, řádek 32.

    Breakpoint 1, 0x4006d585 v _IO_puts (str=0x8048e59 "y") na ioputs.c:32
    32 ioputs.c: Žádný takový soubor nebo adresář.
    1: x/i $eip 0x4006d585<_IO_puts+21>: mov 0x8(%ebp),%esi

    Aha, tak se stal zázrak, bod zlomu fungoval. Co vidíme – a nevidíme nic jiného než parametr funkce, respektive adresu, na které leží. Co teď potřebuješ
    dělat? Správně, opravte údaje na této adrese. Zároveň přepíšeme několik dalších znaků svými vlastními.

    (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"

    No a teď se podívejme na náš výtvor. Co je v paměti:

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

    Nyní odstraníme náš bod přerušení:

    (gdb) info přestávka
    Číslo Typ Disp Enb Adresa Co
    1 bod přerušení ponechat y 0x4006d585 v _IO_puts na ioputs.c:32
    bod zlomu již zasáhl 1x
    (gdb) jasné klade
    Smazán bod přerušení 1

    A pokračujte v provádění, abyste si užili výsledek:

    To je vše. Odcházíme.

    (gdb)q
    Program běží. Přesto odejít? (y nebo n) y

    Tady praxe končí, zbytek si nastudujte sami a pamatujte, že hlavní věcí v tomto životě je UČENÍ.
    Zde je několik dalších příkladů práce:

    Připojení k běžícímu procesu:

    // spuštění gdb
    [e-mail chráněný]:~ > gdb
    GNU gdb 4.18
    Copyright 1998 Free Software Foundation, Inc.
    GDB je svobodný software, na který se vztahuje GNU General Public License, a vy jste
    vítáme vás při jeho změně a/nebo distribuci jeho kopií za určitých podmínek.
    Chcete-li zobrazit podmínky, zadejte „zobrazit kopírování“.
    Na GDB není absolutně žádná záruka. Zadejte "zobrazit záruku".
    podrobnosti.
    Tato GDB byla nakonfigurována jako „i386-suse-linux“.
    (gdb) připojte "pid"
    (gdb) připojit 1127 // příklad

    Hledat v paměti:

    (gdb) x/d nebo x "adresa" ukazují desetinné místo
    (gdb) x/100s "adresa" zobrazí dalších 100 desetinných míst
    (gdb) x 0x0804846c zobrazit desetinné číslo na 0x0804846c
    (gdb) x/s "adresa" zobrazuje řetězce na adrese
    (gdb) x/105 0x0804846c zobrazit 105 řetězců při 0x0804846c
    (gdb) x/x "adresa" zobrazuje hexadecimální adresu
    (gdb) x/10x 0x0804846c zobrazit 10 adres na 0x0804846c
    (gdb) x/b 0x0804846c zobrazit bajt na 0x0804846c
    (gdb) x/10b 0x0804846c-10 zobrazit bajt na 0x0804846c-10
    (gdb) x/10b 0x0804846c+20 zobrazit bajt na 0x0804846c+20
    (gdb) x/20i 0x0804846c zobrazit 20 instrukcí assembleru na adrese

    Seznam všech sekcí ve spustitelném souboru:

    (gdb) sekce informací o údržbě // nebo
    (gdb) Mai i s

    Spustitelný soubor:
    `/home/hack/homepage/challenge/buf/basic", typ souboru
    elf32-i386.
    0x080480f4->0x08048107 při 0x000000f4: .interp ALLOC

    0x08048108->0x08048128 na 0x00000108: .note.ABI-tag
    ALLOC LOAD DATA POUZE PRO ČTENÍ MAJÍ OBSAH
    0x08048128->0x08048158 na 0x00000128: .hash ALLOC
    NAČÍST DATA POUZE PRO ČTENÍ HAS_CONTENTS
    0x08048158->0x080481c8 na 0x00000158: .dynsym ALLOC
    NAČÍST DATA POUZE PRO ČTENÍ HAS_CONTENTS
    0x080481c8->0x08048242 na 0x000001c8: .dynstr ALLOC
    NAČÍST DATA POUZE PRO ČTENÍ HAS_CONTENTS
    0x08048242->0x08048250 na 0x00000242: .gnu.version
    ALLOC LOAD DATA POUZE PRO ČTENÍ
    MÁ OBSAH

    Bryaka na adresu:

    (gdb) rozebrat hlavní
    Výpis kódu assembleru pro funkci main:
    0x8048400

    : push % ebp
    0x8048401 : přesun %esp,%ebp
    0x8048403 : pod $0x408,%esp
    0x8048409 : přidat $0xffffffff8,%esp
    0x804840c : mov 0xc(%ebp),%eax
    0x804840f : přidat $0x4,%eax
    0x8048412 : mov(%eax),%edx
    0x8048414 : push %edx
    0x8048415 : lea 0xffffffc00(%ebp),%eax
    ...

    (gdb) break *0x8048414 // příklad
    Bod zlomu 1 na 0x8048414
    (gdb) break main // příklad
    Bod zlomu 2 na 0x8048409
    (gdb)

    Překlad článku Allana O'Donnella Learning C with GDB.

    Vzhledem k vlastnostem jazyků na vysoké úrovni, jako je Ruby, Scheme nebo Haskell, může být učení C skličujícím úkolem. Kromě překonání nízkoúrovňových funkcí C, jako je manuální správa paměti a ukazatele, se stále musíte obejít bez REPL. Jakmile si zvyknete na průzkumné programování REPL, bude pro vás práce s cyklem zápis-kompilace-běh trochu frustrující.

    Nedávno mě napadlo, že bych mohl použít GDB jako pseudo-REPL pro C. Experimentoval jsem s použitím GDB jako nástroje pro výuku jazyků spíše než jen pro ladění, a ukázalo se, že je to velká zábava.

    Účelem tohoto příspěvku je ukázat vám, že GDB je skvělý nástroj pro výuku jazyka C. Představím vám některé z mých oblíbených příkazů GDB a ukážu vám, jak můžete pomocí GDB porozumět jedné ze záludných částí jazyka C: rozdílu mezi poli a ukazateli.

    Úvod do GDB

    Začněme vytvořením následujícího malého programu C − minimální.c:

    int main() ( int i = 1337; návrat 0; )
    Upozorňujeme, že program nedělá absolutně nic a nemá ani jediný příkaz. printf. Nyní se pojďme ponořit do nového světa učení C pomocí GBD.

    Pojďme tento program zkompilovat s příznakem -G vygenerovat informace pro ladění, se kterými bude GDB pracovat, a poskytnout mu tyto informace:

    $ gcc -g minimal.c -o minimal $ gdb minimal
    Nyní byste měli být okamžitě na příkazovém řádku GDB. Slíbil jsem vám REPL, tak si získejte:

    (gdb) tisk 1 + 2 $ 1 = 3
    Úžasné! tisk je vestavěný příkaz GDB, který vyhodnocuje výsledek C-tého výrazu. Pokud nevíte, co konkrétní příkaz GDB dělá, použijte nápovědu - zadejte help name-of-the-command na příkazovém řádku GDB.

    Zde je pro vás zajímavější příklad:

    (GBD) tisk (int) 2147483648 $2 = -2147483648
    Chybí mi vysvětlit proč 2147483648 == -2147483648 . Hlavním bodem je, že i aritmetika může být v C složitá a GDB rozumí C aritmetice velmi dobře.

    Nyní do funkce vložíme bod přerušení hlavní a spusťte program:

    (gdb) break main (gdb) run
    Program se zastavil na třetím řádku, kde je proměnná inicializována i. Zajímavostí je, že ačkoli proměnná ještě není inicializována, pomocí příkazu již vidíme její hodnotu tisk:

    (gdb) tisk i $ 3 = 32767
    V C není hodnota lokální neinicializované proměnné definována, takže váš výsledek se může lišit.

    Pomocí příkazu můžeme spustit aktuální řádek kódu další:

    (gdb) další (gdb) tisk i $4 = 1337

    Zkoumání paměti pomocí příkazu X

    Proměnné v C jsou souvislé bloky paměti. V tomto případě je blok každé proměnné charakterizován dvěma čísly:

    1. Číselná adresa prvního bajtu v bloku.
    2. Velikost bloku v bajtech. Tato velikost je určena typem proměnné.

    Jedním z charakteristických rysů jazyka C je, že máte přímý přístup k paměťovému bloku proměnné. Operátor & nám dává adresu proměnné v paměti a velikost vypočítá velikost obsazenou proměnnou paměti.

    V GDB můžete hrát s oběma funkcemi:

    (gdb) tisk &i $5 = (int *) 0x7fff5fbff584 (gdb) velikost tiskuof(i) $6 = 4
    V normálním jazyce to znamená, že proměnná i nachází se na 0x7fff5fbff5b4 a zabírá 4 bajty v paměti.

    Již výše jsem zmínil, že velikost proměnné v paměti závisí na jejím typu a obecně na operátoru velikost může pracovat se samotnými datovými typy:

    (gdb) velikost tisku(int) $7 = 4 (gdb) velikost tisku(double) $8 = 8
    To znamená, že alespoň na mém počítači se proměnné líbí int zabírají čtyři bajty a typ dvojnásobek- osm bajtů.

    GDB má výkonný nástroj pro přímé zkoumání paměti - příkaz X. Tento příkaz kontroluje paměť začínající na konkrétní adrese. Má také řadu formátovacích příkazů, které vám poskytují jemnou kontrolu nad počtem bajtů, které chcete zkontrolovat, a nad tím, jak je chcete zobrazit. V případě jakýchkoli potíží vytočte pomoc x na příkazovém řádku GDB.

    Jak již víte, operátor & vypočítá adresu proměnné, což znamená, že ji můžete předat příkazu X význam &i a mít tak možnost podívat se na jednotlivé bajty za proměnnou i:

    (gdb) x/4xb &i 0x7fff5fbff584: 0x39 0x05 0x00 0x00
    Příznaky formátování označují, že chci získat čtyři ( 4 ) výstup hodnot v šestnáctkové soustavě (he X) ve formě jednoho bajtu ( b yte). Zadal jsem kontrolu pouze čtyř bajtů, protože tolik zabírá proměnná v paměti i. Výstup ukazuje bajtovou reprezentaci proměnné v paměti.

    S výstupem bajt po bajtu je však spojena jedna jemnost, kterou musíte mít neustále na paměti - na počítačích Intel jsou bajty uloženy v pořadí „od junior po senior“ (zprava doleva), na rozdíl od známějšího zápisu pro osobu, kde by nízký bajt musel být na konci (zleva doprava).

    Jedním ze způsobů, jak tento problém objasnit, je přiřazení proměnné i zajímavější hodnotu a znovu zkontrolujte tuto oblast paměti:

    (gdb) set var i = 0x12345678 (gdb) x/4xb &i 0x7fff5fbff584: 0x78 0x56 0x34 0x12

    Prozkoumejte paměť s týmem ptype

    tým ptype možná jeden z mých oblíbených. Ukazuje typ C-tého výrazu:

    (gdb) ptype i type = int (gdb) ptype &i type = int * (gdb) ptype main type = int (void)
    Typy v C mohou být komplikované, ale ptype umožňuje vám je interaktivně prozkoumat.

    Ukazatele a pole

    Pole jsou v C překvapivě jemný koncept. Smyslem tohoto bodu je napsat jednoduchý program a pak jej spouštět přes GDB, dokud pole nedávají smysl.

    Takže potřebujeme programový kód s polem pole.c:

    int main() ( int a = (1, 2, 3); return 0; )
    Sestavte jej s vlajkou -G, spustit v GDB as pomocí další přejděte na inicializační řádek:

    $ gcc -g pole.c -o pole $ pole gdb (gdb) přerušit hlavní (gdb) spustit (gdb) další
    V tomto okamžiku můžete zobrazit obsah proměnné a zjistit její typ:

    (gdb) print a $1 = (1, 2, 3) (gdb) ptype a type = int
    Nyní, když je náš program správně nastaven v GDB, první věc, kterou musíte udělat, je použít příkaz X abyste viděli, jak proměnná vypadá A"pod kapotou":

    (gdb) x/12xb &a 0x7fff5fbff56c: 0x01 0x00 0x00 0x00 0x02 0x00 0x00 0x00 0x7fff5fbff574: 0x03 0x00 0x00 00
    To znamená, že oblast paměti pro pole A začíná v 0x7fff5fbff56c. První čtyři bajty obsahují A, další čtyři A a poslední čtyři obchody A. Opravdu, můžete to zkontrolovat a ujistit se velikost to ví A zabírá přesně dvanáct bajtů v paměti:

    (gdb) velikost tisku(a) $2 = 12
    Až do tohoto okamžiku pole vypadají, jak by měla být. Mají odpovídající typy polí a ukládají všechny hodnoty do souvislých paměťových míst. V určitých situacích se však pole chovají velmi podobně jako ukazatele! Můžeme například použít aritmetické operace A:

    (gdb) print a + 1 $ 3 = (int *) 0x7fff5fbff570
    Normálně to znamená a+1 je ukazatelem na int, která má adresu 0x7fff5fbff570. Nyní byste měli reflexivně předávat ukazatele na příkaz X, tak se podívejme, co se stalo:

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

    Vezměte prosím na vědomí, že adresa 0x7fff5fbff570 přesně o čtyři jednotky více než 0x7fff5fbff56c, tedy adresa prvního bajtu pole A. Vzhledem k tomu, že typ int zabírá čtyři bajty v paměti, můžeme konstatovat, že a+1 ukazovat na A.

    Ve skutečnosti je indexování pole v C syntaktický cukr pro aritmetiku ukazatelů: a[i] ekvivalentní *(a+i). Můžete si to ověřit v GDB:

    (gdb) tisk a $4 = 1 (gdb) tisk *(a + 0) $5 = 1 (gdb) tisk a $6 = 2 (gdb) tisk *(a + 1) $7 = 2 (gdb) tisk a $8 = 3 (gdb) tisk *(a + 2) $9 = 3
    Takže jsme to v některých situacích viděli A se chová jako pole a v některých případech jako ukazatel na jeho první prvek. Co se děje?

    Odpověď zní, že když je ve výrazu C použit název pole, „rozpadne se“ do ukazatele na první prvek. Z tohoto pravidla existují pouze dvě výjimky: když je předán název pole velikost a když je název pole použit s operátorem adresy take & .

    Skutečnost, že jméno A při použití operátoru se nerozpadá na ukazatel na první prvek & , vyvolává zajímavou otázku: jaký je rozdíl mezi ukazatelem do kterého A A &A?

    Číselně oba představují stejnou adresu:

    (gdb) x/4xb a 0x7fff5fbff56c: 0x01 0x00 0x00 0x00 (gdb) x/4xb &a 0x7fff5fbff56c: 0x01 0x00 0x00 0x00
    Jejich typy jsou však různé. Jak jsme již viděli, název pole se rozpadá na ukazatel na jeho první prvek, a proto musí mít typ int*. Co se týče typu &A, pak se na to můžeme zeptat GDB:

    (gdb) ptype &a type = int (*)
    Zjednodušeně řečeno, &A je ukazatel na pole tří celých čísel. To dává smysl: A se při předání provozovateli nerozpadne & a je typu int.

    Můžete sledovat rozdíl mezi ukazatelem, který se rozpadá A a provoz &A na příkladu toho, jak se chovají s ohledem na aritmetiku ukazatele:

    (gdb) tisk a + 1 $ 10 = (int *) 0x7fff5fbff570 (gdb) tisk &a + 1 $ 11 = (int (*)) 0x7fff5fbff578
    Všimněte si, že přidání 1 do A zvýší adresu o čtyři jednotky a přidá 1 k &A přidá k adrese dvanáct.

    Ukazatel, na který se vlastně rozpadá A má formu &A:

    (gdb) tisk &a $11 = (int *) 0x7fff5fbff56c

    Závěr

    Doufám, že jsem vás přesvědčil, že GDB je šikovné výzkumné prostředí pro výuku C. Umožňuje vám tisknout hodnotu výrazů pomocí příkazu tisk, prozkoumejte paměť bajt po bajtu pomocí příkazu X a pracovat s typy pomocí příkazu ptype.

    1. Použijte GDB k práci na Ksplice Pointer Challenge.
    2. Pochopit, jak jsou struktury uloženy v paměti. Jak souvisí s poli?
    3. Pro lepší pochopení programování v jazyce symbolických instrukcí použijte příkazy pro demontáž GDB. Je obzvláště zábavné prozkoumat, jak funguje zásobník volání funkcí.
    4. Podívejte se na režim "TUI" GDB, který poskytuje grafický doplněk ncurses ke známé GDB. Na OS X budete pravděpodobně muset sestavit GDB ze zdroje.

    Od překladatele: Tradičně použijte k označení chyb PM. Budu rád za konstruktivní kritiku.