Korte handleiding Unix debugging met GDB/DDD ir drs E.J Boks Hogeschool van Arhem en Nijmegen Inhoudsopgave Inleiding...1 Beschikbaarheid...2 Begrippen...2 Debugging mogelijk maken...2 Een lokale debugging sessie...3 Inspectie van processor en variabelen...7 Datastructuren via extern grafisch programma...10 Onderbreking programmaverloop...11 Processen aanhaken...11 Threads aanhaken...13 Programmaverloop...13 Een remote debugging sessie...14 Inleiding Dit document geeft een korte introductie van het gebruik van de Gnu Debugger (GDB) in combinatie met de Data Display Debugger (DDD). Alhoewel het bij de uitleg gebruikte platform draait onder FreeBSD, is de methodiek bruikbaar op vrijwel alle Unix/Linux platforms, alsmede Mac OS X en de Cygwin omgeving onder MS-Windows. GDB is een programma voor het opsporen van fouten in programmatuur. Deze programmatuur kan geschreven zijn in een groot aantal hogere program meertalen. Daarnaast is het niet noodzakelijk dat het te testen programma op dezelfde computer draait als GDB. Ook is het niet noodzakelijk dat deze te testen computer met dezelfde processorarchitectuur is uitgerust als de computer waarop GDB draait. Met zogenaamd Remote Debugging kan een programma op een andere computer/andere architectuur worden getest alsof het programma op de eigen computer draait. GDB is een commando gestuurd programma. Alle acties binnen GDB worden met een commando begonnen. Om een gemakkelijker verloop van de debugging sessie mogelijk te maken, kan naast/om GDB een grafische schil worden gebruikt. DDD is zo een schil. Met DDD kunnen
complexe datastructuren uit het te testen program ma worden afgebeeld. Indien het gewenst is kan DDD/GDB op een andere computer draaien waar de gebruiker aanwezig is. Communicatie tussen de twee systemen geschiedt dan via het X Windows systeem. Beschikbaarheid DDD en GDB zijn vaak al geinstalleerd op veel Unix/Linux platforms. Als dat niet zo is, dan kan men kostenloos de broncode van GDB en DD D op internet vinden, downloaden en installeren. Meer informatie hierover op : http://www.gnu.org/software/gdb en http://www.gnu.org/software/ddd Begrippen De volgende begrippen worden gebruikt in de GDB/DDD context: host. Dit is de computer waarop GDB/DDD draait. target. Dit is de computer waarop de te testen software draait. Debugging mogelijk maken Om debugging met GDB/DDD mogelijk te maken, moet de broncode gecompileerd worden met debugging informatie. Deze informatie wordt in een debug formaat aan het uitvoerbare (ELF) bestand toegevoegd. Bij de GNU compiler (gcc) gebeurt dit met toevoeging van een van de volgende switches aan gcc: gcc -gstabs+... (BSD DBX formaat). of gcc -gdwarf +... (System V Release 4 formaat ) of gcc -ggdb... (GNU formaat ) Het is raadzaam maar niet verplicht om optimalisatie uit te schakelen
bij debugging. Een lokale debugging sessie In dit hoofdstuk voeren we een debugging sessie uit met ddd op een filterdemonstratieprogram ma. Het programma is in C+ + geschreven. Om te beginnen met een debugging sessie zorgen we ervoor dat we een vers uitvoerbaar bestand hebben. Daarna starten we de debugger met toevoeging van de naam van het uitvoerbaar bestand:
Figuur 1: ddd opstarten Na opstart van ddd zijn er drie enkele velden te observeren, die al dan niet aan of uitgezet kunnen worden: Het broncode veld, waarin de code van het programma te zien is. Het assembly veld, waarin de corresponderende assembly te zien is. Het commandovenster, waarin de gdb commando's staan die
worden uitgevoerd door ddd namens de gebruiker. Het datastructurenveld, waarin tijdens programmauitvoering data wordt afgebeeld. Daarnaast is er en los commando console venster, waarin de belangrijkste commando's staan vermeld. Persoonlijk vind ik dit een onding en heb ik via DDD voorkeuren aangegeven dat dit deel wordt vn het DDD hoofdvenster (zie figuur 1). Bij een lokaal target draait gdb/ddd onder controle van het Operating System. Programmauitvoering staat uit bij start. Om te beginnen met de uitvoering moet in de commandoconsole op 'Run' worden gedrukt, of in het commandovenster run worden ingegeven. Dit dualisme is op alle mogelijkheden in DDD van toepassing; alle GDB commando's kunenn zelf worden ingetoetst, of uitgevoerd via de DDD grafsiche schil. Als het programma wordt uitgevoerd, dan loop het alsof de debugger niet aanwezig is. Er volgt pas een onderbreking als: De gebruiker ergens in de broncode (hetzij hogere taal/ hetzij assembly) een breakpunt heeft aangebracht. Het program ma door een fout stopt of eindigt. Een breakpuntwordt wordt aangebracht door de cursor op een programmaregel te brengen en dan dmv de rechter muisknop aan te geven dat er een (tijdelijk of permanent) breakpunt moet komen. Op het moment dat de programmaexecutie bij het breakpunt aankomt, zal de uitvoering stoppen en alle controle over het programma worden overgegeven aan GDB. Hieronder is zo'n moment aangegeven:
dmv de pijl in het C+ + venster is te zien waar de Program Counter (PC) van de processor zich nu bevindt. Via een extra scherm (dat op te vragen is via de menuoptie 'Status/Backtrace' geeft de debugger aan via welke funktieaanroepen we zijn aangeland in de huidige funktie. Door een van de regels in het backtrace venster aan te klikken, krijgen we desgewenst ook de broncode van die plek te zien.
Inspectie van processor en variabele n We kunnen nu alles inspecteren wat van belang is bij programmaverloop: lokale (stack) variabelen. Funktie aanroep variabelen (via stack of registers) Globale (heap) variabelen processorregisters geheugenlokaties Unix signalen De gewenste informatie kan bij DDD op drie verschillende manieren worden gepresenteerd: via de GDB command shell via het grafische data venster via een extern grafisch programma. Als we bijvoorbeeld via de GDB command shell informatie willen printen, dan wordt het info commando gebruikt. Een uitgebreide lijst van info geeft de volgende mogelijkheden: info address -- Describe where symbol SYM is stored info all- registers -- List of all registers and their contents info args -- Argument variables of current stack frame info auxv -- Display the inferior's auxiliary vector info breakpoints -- Status of user- settable breakpoints info catch -- Exceptions that can be caught in the current stack frame info classes -- All Objective- C classes info common -- Print out the values contained in a Fortran COMMON block info copying -- Conditions for redistributing copies of GDB info dcache -- Print information on the dcache performance info display -- Expressions to display when program stops info extensions -- All filename extensions associated with a source language
info files -- Names of targets and files being debugged info float -- Print the status of the floating point unit info frame -- All about selected stack frame info functions -- All function names info handle -- What debugger does when program gets various signals info line -- Core addresses of the code for a source line info locals -- Local variables of current stack frame info macro -- Show the definition of MACRO info mem -- Memory region attributes info program -- Execution status of the program info registers -- List of integer registers and their contents info scope -- List the variables local to a scope info selectors -- All Objective- C selectors info set -- Show all GDB settings info sharedlibrary -- Status of loaded shared object libraries info signals -- What debugger does when program gets various signals info source -- Information about the current source file info sources -- Source files in the program info stack -- Backtrace of the stack info symbol -- Describe what symbol is at location ADDR info target -- Names of targets and files being debugged info terminal -- Print inferior's saved terminal status info threads -- IDs of currently known threads info tracepoints -- Status of tracepoints info types -- All type names info udot -- Print contents of kernel ``struct user'' for current child info variables -- All global and static variable names info vector -- Print the status of the vector unit info warranty -- Various kinds of warranty you do not have info watchpoints -- Synonym for ``info breakpoints'' info win -- List of all displayed windows
Om het DDD data display venster te gebruiken, klikken we op een variabelenaam in het hogere taalvenster en vervolgens op Display in de commando console. Speciale gevallen zoals alle lokale variabelen kunnen automatisch worden weergegeven via de menuoptie 'data/local variables'. In het display venster kan bij pointer variabelen ook de plaats, waarnaar de pointer wijst, worden weergegeven. De link tussen pointer en lokatie wordt ook aangegeven met een pointer (pijl). Structuren en Unions in C/C+ + worden weergegeven met alle velden. Onderstaand voorbeeld geeft een aantal mogelijkheden weer:
In bovenstaande afbeelding wordt de lokatie waar de arg variabele filter naar wijst apart afgebeeld (met label *filter). Daarnaast wordt ook de inhoud waar de pointer variabele filterteller naar wijst apart afgebeeld. Ook is te zien wat de waarde is van alle AMD Athlon registers op mijn computer. Dit is via de menuoptie 'status/registers/ aangezet. Datas tr uc tu r e n via exte rn grafisch progra m m a DDD kan informatie uit het programma doorgeven aan een extern grafisch programma. De meest geschikte kandidaat daarvoor is gnuplot. In menuoptie 'preferences/helpers' moet dit correct aangeven zijn. Door selectie van een variabele en vervolgens keuze van de knop 'plot' wordt de data naar gnuplot gestuurd. Een voorbeeld is het volgende, waarin het gefilterde signaal uit het filterprogram ma via gnuplot wordt gevisualiseerd. Dit is erg handig bij grote hoeveelheden meetdata, omdat dan verbanden veel sneller kunnen worden geobserveerd. Bij remote targets is het helemaal handig, omdat data op een remote target vaak lastig op te slaan is.
Onderbr e ki ng progra m m a v e rl oop GDB kan op een lokale target op de volgende manieren met programmauitvoering omgaan: aanhaken bij de start van een programma (zoals hiervoor in het voorbeeld). Aanhaken bij een reeds lopend programma. Binnen een programma aanhaken bij verschillende threads. Processen aanhak e n Als ons programma los draait, dan heeft het een eigen proces nummer in Unix. Hieronder is te zien hoe het filterprogram ma apart draait (is opgestart uit de shell). Kijk vooral goed naar de procesnummers die bij mijn login horen :
Nu geven we gdb opdracht om aan te haken bij proces 1564. Dit gaat als volgt: Eerst laden we het ELF bestand in DDD waarin de debug broncode aanwezig is (menu: File/Open Program). Vervolgens haken we aan het process aan (menu 'File/Attach' optie).
Daarna kunnen op precies dezelfde manier met debugging te werk gaan als hierboven al beschreven. Houd er rekening mee dat bij Unix processen die via fork() tot stand gekomen zijn vaak ook alle geheugenadressen dezelfde waarden zullen hebben. In ieder geval gebeurt dit in de Linux kernel. Denk echter niet dat dit daadwerkelijk dezelfde adressen zijn. Het getoonde adres is een virtueel adres dat bestaat binnen de geheugenruimte van het proces. Binnen de kernel van het operating system wordt dit naar een MMU gestuurd, die dit mapt naar een fysiek RAM adres. Threads aanhak e n Binnen DDD kan men een thread (een zogenaamd light weight process) selecteren via de menuoptie 'status/threads'. Breakpunten en data kunnen dan per thread worden gezet/geevalueerd. Progra m m a v e rloop Binnen een proces of thread kan men het program maverloop op de volgende manieren laten gebeuren: op volle snelheid tot een breakpunt. Dit wordt met het run of cont commando bewerkstelligd. Na stop door een breakpunt, stappend per hogere taalinstructie door de code. Eventuele subroutines onderweg worden ingegaan. Dit wordt met het step commando gerealiseerd. Na stop door een breakpunt, stappend per hogere taalinstructie door de code. Eventuele subroutines onderweg worden uitgevoerd
maar niet ingegaan. Dit wordt met het next commando gerealiseerd. Na stop door een breakpunt, stappend per assemblyinstructie door de code. Eventuele subroutines onderweg worden ingegaan. Dit wordt met het stepi commando gerealiseerd. Na stop door een breakpunt, stappend per assemblyinstructie door de code. Eventuele subroutines onderweg worden uitgevoerd maar niet ingegaan. Dit wordt met het nexti commando gerealiseerd. Een voorbeeld van stepi (assembly is aangezet via menuoptie view/machine code window) : met stepi verspringt de PC in het assembly venster en daarnaast onregelmatig de PC in het C+ + venster. Een remote debugging sessie Een remote debugging sessie gaat in grote lijnen op dezelfde manier als een lokale debugging sessie. We moeten echter er voor zorgen dat GDB kan communiceren met het remote target. Hiervoor is in de meeste gevallen extra hardware nodig. Voorbeelden: De Atmel AVR kan met behulp van de JTAG debugger vanaf een werkstation worden bestuurd. Een ARM processor heeft In Circuit Emulation ondersteuning (ICE) aan boord, die dmv JTAG wordt bestuurd.
Als voorbeeld wordt hier een Atmel AT91 SAM7S ontwikkelbord gebruikt, dat dmv een ICE debugger aan het FreeBSD werkstation hangt. De set- up is dan als volgt: FreeBSD GDB socket socket ICE jtag AT91 jtag De ICE wordt apart bestuurd, in dit geval dmv een telnet sessie. Dit wordt hier verder buiten beschouwing gelaten. Er moet worden gezorgd dat: het uitvoerbaarbestand voor de target klaar is met debugging informatie. DDD wordt opgestart met koppeling aan een remote versie van de debugger, in plaats van de debugger voor de host. In dit voorbeeld heet GDB arm- elf- gdb in plaats van gdb. arm- elf- gdb is vanaf broncode gecompileerdmet als optie dat het met arm elf instructies moet kunnen omgaan. GDB moet de internetadres aangegeven krijgen waarop het kan communiceren met de ICE. GDB kan worden geinstrueerd dmv het remote <adres > commando waar het target is. In onderstaand voorbeeld is dat op IP adres 192.168.84.5:4242.
er is nu een remote debugging sessie opgezet. Deze is vrijwel gelijk aan een host gebaseerde sessie. Verschillen zijn onder andere: Op een remote target loopt het programma al bij begin van de sessie. Het run commando kan daarom niet worden gebruik. Willen we vanaf een HW reset verder gaan (zoals hierboven, vanaf adres 0x00000000) dan volstaat het cont commando. het aantal breakpunten wordt beperkt door de hardware. In het geval van de ARM ICE worden, bij debugging van code in flash,
slechts twee breakpunten ondersteund. Bij debugging in sram vervalt deze beperking. De snelheid waarmee door het programma kan worden gelopen hangt in grote mate af van de verbinding tussen host en target. Een serieele verbinding, zoals tussen Atmel AVR JTAG en werkstation, is traag! Het inladen van grote structuren of data kost vele seconden. De ICE debugger van de ARM werkt met een ethernet of USB verbindinding en is veel comfortabeler. Als laatste wordt hierboven de ARM sessie afgebeeld met een
backtrace venster en de ARM 7TMDI registers afgebeeld. Deze verschillen natuurlijk compleet van de AMD registers zoals afgebeeld bij de lokale sessie eerder.