Fouten zoeken in code met ddd

ArticleCategory:

Software Development

AuthorImage:

[Foto van de Auteur]

TranslationInfo:

original in es Jose M. Laveda 

es to en Miguel A. Sepulveda & Ismael Ripoll

en to nl Tom Uijldert

AboutTheAuthor:

Abstract:

Doel van dit artikel is om gebruikers die nog nooit met een debugger (foutzoeker) hebben gewerkt, kennis te laten maken met de basis gedachten hierachter. Ook geschikt voor mensen die al wel met een debugger hebben gewerkt en op zoek zijn naar een betere grafische omgeving hiervoor. Er kan veel worden verteld over de mogelijkheden en stabiliteit van de hier beschreven debugger (gdb), we hebben echter besloten het om didactische redenen simpel te houden, zoals gewoonlijk :)

ArticleIllustration:

[Illustratie]

ArticleBody:

Wat is een debugger?

"Er was eens een programmeur wiens enige taak het was om, wanneer er een fout in de code was gevonden, het volgende te doen:
 
/*Code*/
(...)
loop
  change_a_variable;
  show_value_of_variable;
end_loop
(...)
Hij was gedwongen om op meerdere plaatsen dit soort code aan te brengen in het programma om zo de waarden van variabelen te kunnen achterhalen tijdens het uitvoeren van het programma. Het was een zware taak, vergalde zijn leven en het fouten zoeken nam twee keer zoveel tijd in beslag als het eigenlijke maken van de code (..)".

Wie heeft dit niet ervaren? Vaak is er een fout in het programma, we hebben al van alles geprobeerd en zijn er zo langzamerhand van overtuigd dat het een "fout in de compiler" betreft omdat de mogelijkheden uitgeput lijken� Welnu, op dit punt wordt het tijd voor software die helpt bij het fouten zoeken (debuggen).

Met een debugger kun je het programma stap voor stap uit laten voeren. Op die manier kun je eenvoudig de waarde van variabelen bekijken, onderzoeken wat er zou kunnen gebeuren onder bepaalde voorwaarden, etc. Dit alles kan steeds worden herhaald, terwijl de te onderzoeken code wordt uitgevoerd. Als je met deze definitie nog niet uit de voeten kan dan hoop ik gedurende dit artikel het begrip te verbeteren.

Wat zou er gebeuren als de programmeur in ons verhaal een debugger had gehad met de naam spy die hem de volgende mogelijkheid zou geven?:

$ spy my_program
en wat als hij vervolgens deze acties zou kunnen doen?:
 
spy> uitvoeren my_program "tot aan regelnummer 50"
spy> laat de waarde zien van variabele <X>
spy> wat is het type van variabele <X>
Hoogstwaarschijnlijk zou onze programmeur een gat in de lucht springen omdat hij eindelijk de oorzaak van de fout (bug) zou hebben gevonden.

Het moge duidelijk zijn dat het hulpmiddel "spy" zeer nuttig is gebleken want het stelde de programmeur in staat om daar waar hij wilde variabelen te bekijken tijdens de uitvoering van zijn programma. Dit is in essentie een debugger, simpel gesteld.

Let op! Debuggers kunnen alleen werken op programma's die zijn gecompileerd met de debug-optie, "-g" in het geval van de GNU gcc compiler.

Er is een debugging hulpmiddel beschikbaar voor alle Linux gebruikers (en op diverse systemen) genaamd GDB "GNU Source-Level Debugger". Het staat je ter beschikking voor dezelfde prijs en onder dezelfde licentie als het operating systeem waarop je dit artikel hoogstwaarschijnlijk aan het lezen bent; de GNU General Public License. Het kan fouten zoeken in C-code, C++, Modula-2 en assembler.

Waarschijnlijk zit het al in je huidige Linux distributie. Zo niet, neem dan een andere distributie of ga op zoek naar de broncode ergens op het net, waar het op ziljoenen plekken is te vinden ;).

Laten we aannemen dat je de broncode ergens hebt opgevist en in de directory /usr/src hebt gezet. Ga nu naar de directory /usr/src/gdb-4.xxxx/gdb, tik in ./configure en ga vervolgens naar de directory doc. Hier kun je de documentatie voor gdb aanmaken middels het uitvoeren van make gdb.dvi; make refcard.dvi. Beide bestanden kunnen eenvoudig worden gelezen of afgedrukt op iedere Linux doos.

Wat is DDD?

In plaats van het tot in detail beschrijven van het gebruik van gdb en zijn commando's, is het veel nuttiger voor nieuwe gebruikers om bekend te raken met een veel vriendelijker omgeving genaamd "ddd", wat staat voor Display Data Debugger.

ddd geeft, in een notendop, een gebruiksvriendelijker en makkelijker te configureren omgeving voor het uitvoeren van een foutzoek-sessie. We moeten hierbij opmerken dat ddd alleen een grafische omgeving biedt boven op gdb, deze laatste is dus nodig voor het juiste gebruik van ddd. Feitelijk staat ddd het toe om direct gdb aan te sturen wanneer je dat wilt. Er zijn ook andere debuggers die je onder ddd kunt gebruiken zoals dbx en xdb.

Goede informatie over ddd wordt gegeven op http://www.cs.tu-bs.de/softech/ddd/, als je echter de RedHat distributie hebt, heb je de broncode al in .rpm formaat. Er kunnen twee versies van ddd aanwezig zijn. Eentje met de Motif routines dynamisch gelinked en eentje statisch. De statische versie is voor gebruikers die geen Motif bibliotheek bezitten.

Ik ga even voorbij aan de huidige situatie van ddd met betrekking tot lesstif (http://www.lesstif.org), ik ben niet bekend met lesstif, een freeware-implementatie van de Motif grafische bibliotheek. Recent compileerde ddd alleen met lesstif dankzij een patch (reparatie); ik heb het gebruikt onder kernel 1.2.13 met lesstif 0.75 (geloof ik ;). Bekijk de webpagina van het lesstif project om meer te weten te komen over de status hiervan.

Nu even ter zake, na het opstarten van ddd krijgen we:


Figuur 1. Hoofdscherm van ddd

Er zijn drie verschillende manieren om ddd op te starten; degene die we al hebben genoemd en de volgende twee:

$ ddd <programma> core
$ ddd <programma> <process_id>
Het bestand met de naam core wordt iedere keer dat een programma de fout in gaat (crasht) aangemaakt en bevat bruikbare informatie over de status van het programma ten tijde van de crash. Als jou systeem geen core bestanden (dumps) genereert, inspecteer dan eens de instellingen van het programma ($ ulimit -a laat ze allemaal zien. Gebruik $ ulimit -c <waarde> voor het instellen van een relevante maximum waarde).

process_id geeft ons de mogelijkheid het programma te bekijken tijdens uitvoering.

De grafische omgeving van ddd geeft meestal meerdere mogelijkheden om bepaalde taken uit te voeren; ik ga ze hier niet allemaal beschrijven, alleen de eenvoudigste en meer directe taken. Merk ook op dat het onderste scherm van het ddd window een log laat zien van alle transacties die door ddd zijn uitgevoerd. Het log window kan zeer behulpzaam zijn voor het leren van de bediening van gdb via de commandoregel.

Grafische omgeving

Figuur 1 laat zien dat het hoofdwindow is onderverdeeld in drie schermen. Het onderste is het "zuivere" debugger console (gdb in ons geval). Hier kunnen we direct gdb commando's invoeren alsof we helemaal geen ddd koppeling hebben. Het middelste scherm laat de broncode van het programma zien. Het bovenste scherm tenslotte is de grafische koppeling met de variabelen en andere objecten van het programma. Als laatste is er de toolbar (hulpmiddelen), uitgevoerd als drijvend window voor sturing en uitvoering van ddd commando's.

Naast dit hoofdwindow is er een uitvoerings-window voor het proces en een window voor de broncode van het programma. Beide zijn optioneel.

ddd heeft diverse help-hulpmiddelen om gebruikers van de nodige informatie te voorzien op ieder gewenst moment van de foutzoek-sessie. Een dialoogvenster bijvoorbeeld, verschijnt altijd als de cursor over een variabele beweegt of over een knop van de koppeling; hierin verschijnt relevante informatie over het onderliggende object. Ook hebben we nog het onderste scherm met de statusregel die laat zien welk commando er op dat moment wordt uitgevoerd en de uitvoer daarvan. Rechts vinden we nog een help-menu met daarin alle beschikbare informatie.
Nog meer hulp is te verkrijgen met het indrukken van de F1-toets en het vervolgens kiezen van een onderwerp in het drijvende menu dat verschijnt. Als laatste kan men nog help intikken in het gdb console (onderste scherm) om zo algemene help- informatie te verkrijgen over de gebruikte debugger en zijn commando's.

Normaal gesproken zal ddd na opstart één window laten zien met daarin drie schermen. Via het Preferences menu kan men ddd echter instrueren om met drie verschillende windows op te starten.


Figuur 2. Helpscherm voor het "File" menu

Van onderaf beginnen

Het "DDD:Debugger Console" is het startpunt voor het leren omgaan met ddd. Gebruikers met ervaring in gdb kunnen van hieruit eenvoudig ddd besturen. Mijn ervaring is dat het de moeite loont om te kijken wat er gebeurt in het console als je commando's uitvoert via de grafische koppeling. De optie Commands->Command History laat in een apart window zien welke commando's tot nu toe zijn uitgevoerd.

Om meer over de mogelijkheden van ddd te leren kun je het beste direct de originele documentatie raadplegen. Ik zal in ieder geval een aantal eenvoudige taken beschrijven die je direct op de debugger uit kunt voeren.

Algemene opdrachten

De broncode voor een sessie kun je inladen via de ddd opdrachtregel of via de menu-optie File; de inhoud ervan wordt weergegeven op de daarvoor bestemde plaats. Vanaf dit moment kunnen we al door de broncode lopen, waarde en type van een variabele bekijken en naar keuze delen van het programma uitvoeren...

De uitvoer van een programma kun je nazien in het execution window (Options->Run in Execution Window), of bekijken in het console (dat laatste zal niet werken als het programma ontworpen is om onder Motif of een andere grafische omgeving te werken).

Probeer de cursor op één van de variabelen in de boncode te plaatsen en de huidige waarde zal op de statusregel van ddd verschijnen. Als je nu de rechter muisknop indrukt zal het volgende menu verschijnen:

Dit menu geeft ons de mogelijkheid om de waarde van variabele fname in het onderste scherm te tonen, in het bovenste scherm ("drawing area"), of het een variabele is of slechts een pointer (een variabele die het adres bevat van een andere variabele, niet de waarde). Verder laat "What is" de structuur of het type van de variabele zien; Lookup gaat zoeken naar andere voorkomens van variabelen met dezelfde naam. "Break at" en "Clear at" tenslotte, geven de mogelijkheid om met breakpoints te werken, waarover straks meer.

Nog meer opties staan in de regel onder de broncode, tik eenvoudig de parameter in en kies de bijbehorende opdracht.

Een breakpoint (onderbreking) laat het programma uitvoeren tot aan het opgegeven punt (een regelnummer in het programma); op dat moment wordt de uitvoering gestopt en de gebruiker kan op zijn gemak waardes van variabelen bekijken op dat moment, verder gaan met het stap voor stap uitvoeren van het programma, status van threads bekijken, etc. Merk op dat normaal gesproken, bij het ontbreken van een breakpoint, het programma doorloopt tot aan het einde of crasht op de fout die er eventueel nog in zat. Op dat moment is het te laat om de status van het programma te bekijken, fouten zoeken moet gedurende de uitvoer van het programma gebeuren.

Doe het volgende voor het aanbrengen van een onderbreking:

In de figuur hierboven zie je twee onderbrekingen staan, op regels 70 en 71. Het bijbehorende symbool moge duidelijk zijn.

Het volgende menu is voor het plaatsen/verwijderen van onderbrekingen:

Met de optie Conditional kunnen voorwaardelijke onderbrekingen worden aangebracht. In dat geval zal het programma alleen worden onderbroken als aan de gestelde voorwaarde wordt voldaan op het moment van uitvoeren van die specifieke regel van het programma. Een andere mogelijke voorwaarde is de "Ignore Count", het programma wordt pas onderbroken nadat de betreffende regel <n> keer is uitgevoerd. Het programma kan dan bijvoorbeeld worden onderbroken na de vijftiende uitvoering van een bepaalde programmalus.

Als het programma is gestopt bij de onderbreking kunnen we de waarden van variabelen bekijken met behulp van de opties in het betreffende menu. Laten we niet vergeten dat deze functionaliteit in het hoofdmenu is te vinden (oftewel menu Data).
Voor sturing van de uitvoering van het programma zullen we het speciaal hiervoor aangemaakte knoppenmenu gebruiken. Deze is rechtsboven in het hoofd window te vinden.

Let op de overeenkomsten tussen knoppenmenu en hoofdmenu.

We kunnen het programma starten en stoppen. Als we het hoofdmenu gebruiken dan is het mogelijk om parameters aan het programma mee te geven via een apart dialoogvenster. Step voert de volgende programmaregel uit (stap voor stap). Als deze regel een functie-aanroep bevat dan zal het programma naar het begin van die functie springen en wachten op het volgende Step commando. Daartegenover staat de instructie Next, die de aanroep (regel) als atomaire eenheid ziet en de aanroep in zijn geheel als één instructie uit zal voeren. Hij stopt dus pas nadat de gehele functie is uitgevoerd.

Continue zal het programma verder uitvoeren na de onderbreking. Kill, Interrupt en Abort kan men verder gebruiken voor het handmatig onderbreken van het programma.

Waarschijnlijk het meest spectaculaire aan dit grafische programma is het data-scherm (bovenste scherm in het window). Hierin wordt op grafische wijze de structuur en inhoud van data weergegeven, alsook de afhankelijkheden hierin. Het volgende voorbeeld laat een array zien (Arguments) en vier van zijn elementen.

Dit scherm kan een veelheid aan informatie weergeven, kijk alleen maar eens naar de keuzes in het menu Data->More Status Displays, alwaar je alles in kunt stellen wat je wilt zien in het data-scherm. Vanuit het vorige voorbeeld kunnen we ook de inhoud van de registers van de processor laten zien, de benodigde dynamische programmabibliotheken (libraries) en de toestand van het programma in uitvoering:

Tenslotte

De instellingen van de ddd omgeving kunnen worden veranderd vanuit het menu Options->Preferences, alsook via de traditionele Motif resources (in het bestand $HOME/.dddinit). Het is niet de bedoeling van dit artikel om al deze mogelijkheden te behandelen.

Het is zeer aan te bevelen om de handleiding te lezen die bij ddd wordt geleverd (ddd.ps) en de handleiding van de gebruikte debugger ("Debugging with GDB"). Het zal de doorsnee nieuwsgierige gebruiker echter weinig moeite kosten om hier bekend mee te raken, het enige dat je nodig hebt is een stuk bekende code die je door de debugger laat bekijken om zo alle mogelijkheden te ontdekken.

Tot slot op voorhand mijn excuses voor eventuele fouten in dit artikel :-)

Meer informatie