<?xml version="1.0" encoding="iso-8859-1"?>

<!DOCTYPE artikkel PUBLIC "-//infoweb.ntnu.no//DTD Infoweb 1.0//NO"
"http://infoweb.ntnu.no/infoweb.dtd">

<artikkel spraak="no">
  <tittel>Debugging under UNIX</tittel>
  <forfatter>glaserud</forfatter>
  <sammendrag>Debugging gjøres for å luke ut feil i programvare. Her er en oppskrift på hvordan dette kan gjøres under UNIX.</sammendrag>
  <hoveddel>

    <os2>Introduksjon</os2>
    
    <avsnitt>
      Debugging under UNIX er for mange ganske annerledes enn fra DOS/Windows, der debuggeren
      gjerne er integrert med kompilator og editor i et utviklingsmiljø. På UNIX-plattformen
      er ofte dette utviklingsmiljøet delt opp i flere enkeltprogrammer. Dette fører til at
      man ikke er bundet til en enkelt debugger, men kan velge og vrake i flere programmer
      som finnes tilgjengelig.
    </avsnitt>

    <os2>Kompilering av programmer for debugging</os2>

    <avsnitt>
      For at et program skal kunne debugges, må det kompileres slik at en del
      tilleggsinformasjon blir lagt med. Bruker man gcc, g++, f90 eller f77-kompilatoren,
      gjøres dette ved å gi opsjonen <uthev>-g</uthev> til kompilatoren. Merk at mange
      debuggere ikke fullt ut vil takle språk som ikke er så mye brukt som C.
    </avsnitt>
    
    <avsnitt>
      Dersom vi har et program <kommando>nalle</kommando>, vil vi vanligvis gjøre følgende
      for å kompilere det:
    </avsnitt>

    <avsnitt>
      <konsoll>
	gcc -o nalle.o -c nalle.c
	gcc -o nalle nalle.o</konsoll>
    </avsnitt>

    <avsnitt>
      For å få med debuggings-informasjon tar vi med -g:
    </avsnitt>

    <avsnitt>
      <konsoll>
	gcc -g -o nalle.o -c nalle.c
	gcc -g -o nalle nalle.o
      </konsoll>
    </avsnitt>

    <avsnitt>
      <uthev>Merk</uthev> at programfilen blir mye <uthev>større</uthev> når
      <uthev>debuggings</uthev>-informasjon kompileres med.
    </avsnitt>

    <os2>Debugging</os2>

    <avsnitt>
      Under har jeg kikket nærmere på en del av de programmene som finnes for debugging
      på NTNU. Det er ikke sikkert at alle vil være installert på den maskinen du jobber
      på. Min personlige favoritt er kombinasjon av emacs og gdb.
    </avsnitt>

    <os3>GDB - The GNU DeBugger</os3>

    <avsnitt>
      Informasjon om gdb finnes i GNU info tree. Dette aksesseres ved å utføre kommandoen
      <kommando>info gdb</kommando> eller med <kommando>C-h i</kommando> i emacs. GDB
      virker for C og C++, med begrenset støtte for Modula-2 og Fortran.
    </avsnitt>

    <avsnitt>
      GDB startes vanligvis som <kommando>gdb [program]</kommando> der program er
      den kjørbare filen til programmet som skal debugges. Det er også mulig å starte
      gdb slik at den knytter seg til en prosess som allerede kjører, eller å starte
      den slik at den ser på core-fila til et program som har terminert abnormalt.
    </avsnitt>

    <avsnitt>
      Når debugger kommer opp vil du få et prompt <kommando>(gdb)</kommando> der du kan
      skrive inn kommandoer. En nyttig kommando er <kommando>help</kommando> som gir en
      oversikt over ulike kategorier av kommandoer, der det er mulig å se nærmere på
      hver kategori. Hjelp om enkeltkommandoer fåes med <kommando>h [kommando]</kommando>
    </avsnitt>
    
    <avsnitt>
      Når et program er klart til å kjøres fra gdb, er en vanlig måte å sette igang
      debuggingen på å gi følgende to kommandoer:
    </avsnitt>

    <avsnitt>
      <konsoll>
	(gdb) b main
	(gdb) r</konsoll>
    </avsnitt>

    <avsnitt>
      Den første kommandoen setter et breakpoint i rutinen main, altså helt først i
      programmet. Den neste kommandoen kjører programmet helt til den kommer til start
      av main-prosedyren hvor den treffer et breakpoint og stopper.
    </avsnitt>
    
    <avsnitt>
      Det finnes flere måter å gå gjennom et program. En av de har vi allerede vist, ved 
      at det er mulig å sette breakpoints. Breakpoints kan bl.a. settes på disse måtene:
    </avsnitt>
    
    <ul>
      <li><kommando>(gdb) b [funksjon]</kommando></li>
      <li><kommando>(gdb) b [linjenummer]</kommando></li>
      <li><kommando>(gdb) b [kildekodefil]:[linjenr]</kommando></li>
    </ul>

    <avsnitt>
      Når et program har stoppet, kan du kjøre programmet videre fra der det ble stoppet
      med kommandoen <kommando>c</kommando>. Programmet vil da kjøre videre inntil en av
      følgende inntreffer:
    </avsnitt>

    <ol>
      <li>Programmet avslutter normalt</li>
      <li>Programmet avslutter abnormalt</li>
      <li>Debuggeren finner et breakpoint</li>
    </ol>

    <avsnitt>
      Når et breakpoint settes, assosieres dette med et nummer. Man kan da i ettertid skru
      et breakpoint på eller av med kommandoen <kommando>enable [nr]</kommando> og
      <kommando>disable [nr]</kommando>.
    </avsnitt>

    <avsnitt>
      To andre viktige kommandoer er <kommando>n</kommando> (next) og <kommando>s</kommando>
      (step). Next utfører en kommandolinje og stopper. Hvis det er et funksjonskall på
      linjen, utføres funksjonskallet før den stopper. Step gjør stort sett det samme,
      men stopper på første linje av funksjonen.
    </avsnitt>

    <avsnitt>
      For å se på variabler finnes kommandoene <kommando>print</kommando> og 
      <kommando>display</kommando> som begge tar variabelnavnet som argument. Den første
      skriver ut verdien av variabelen, mens den andre kontinuerlig viser verdien.
      Med display assosieres et nummer, s.a. man kan skru det av med
      <kommando>undisplay [nr]</kommando>.
    </avsnitt>

    <os3>Kombinasjonen GDB/emacs</os3>

    <avsnitt>
      GDB kan <uthev>startes fra emacs</uthev>, og dette er etter mitt syn en mer praktisk
      måte å jobbe på. GDB startes med kommandoen <kommando>M-x gdb</kommando>. Du får
      da et spørsmål om hvilket program som skal debugges, skriv så inn navnet på
      programfilen.
    </avsnitt>

    <avsnitt>
      Den store forskjellen oppleves når du begynner å debugge. emacs starter opp med
      gdb i ett vindu, mens kildekoden dukker opp i neste vindu. Etter hvert som 
      debuggingen går fremover, vises det med en pil hvor i kildekoden debuggeren
      befinner seg.
    </avsnitt>

    <os3>ddd - gdb/dbx wrapper</os3>

    <avsnitt>
      Dette er egentlig ikke en debugger i den forstand at programmet selv foretar
      debugging. ddd er derimot et overbygg til andre debuggere (gdb og dbx) og har et
      grafisk brukergrensesnitt mot disse. Programmet bruker i utgangspunktet gdb til
      debugging, men kan med opsjonen --dbx settes til å benytte denne istedenfor.
      Programmet startes med <kommando>ddd [programfil]</kommando>. Andre måter å starte
      ddd får du ved å skrive <kommando>man ddd</kommando>.
    </avsnitt>

    <avsnitt>
      ddd er pr. 10. februar 2002 kun installert på
      <uthev>solarislogin.stud.ntnu.no</uthev>.
    </avsnitt>

    <os3>dbx</os3>

    <avsnitt>
      Dette er en annen debugger som kanskje finnes på ditt system. Det startes med
      <kommando>dbx [programfil]</kommando> og er tekstbasert som gdb. Se
      <kommando>man dbx</kommando> for mer informasjon om programmet. Selv har jeg ikke
      hatt behov for å bruke programmet, men det kan hende at du bruker et språk som 
      gdb ikke forstår. dbx skjønner Pascal og assembler fra h.h.v. kompilatorene
      pc og as.
    </avsnitt>

    <os2>Andre nyttige verktøy</os2>

    <avsnitt>
      Programmet <uthev>strace</uthev> lister opp alle kall til kjernen
      som gjøres av programmet mens det kjører. På denne måten kan det være mulig å
      oppdage rare ting i programmet.
    </avsnitt>
    
    <avsnitt>
      Noen maskiner har også installert et program som heter <uthev>purify</uthev>, som kan
      brukes til å finne pekerfeil og minnelekkasjer til et program.
    </avsnitt>

    <os2>Debugging av prosesser som bruker fork()</os2>

    <avsnitt>
      Dette er et problemfelt, og inntil noen korrigerer meg, så vet jeg kun om en måte
      å løse dette problemet på, nemlig ved å gjøre større eller mindre forandringer i
      koden.
    </avsnitt>

    <avsnitt>
      En mulighet er at fork() ikke kalles ved debugging, dette kan fungere dersom ikke
      programmet er avhengig av og baserer seg på at det kjører fork(). Da kan fork()
      utelates, f.eks. med
    </avsnitt>

    <avsnitt>
      <konsoll>
	#ifndef DEBUG
	fork()
	#endif</konsoll>
    </avsnitt>

    <avsnitt>
      der DEBUG settes med argumentet -DDEBUG til kompilatoren.
    </avsnitt>

    <avsnitt>
      En annen mulighet (som jeg selv ikke har eksperimentert med) er at de to
      prosesstrådene settes til å sove permanent etter fork()-kommandoen, f.eks.
      ved en konstruksjon som
    </avsnitt>

    <avsnitt>
      <konsoll>
	int sove_videre = 1;
	parent_id = fork();
	while(sove_videre)
	sleep(1);</konsoll>
    </avsnitt>

    <avsnitt>
      Her vil begge trådene sove i en uendelig løkke som ikke er CPU-intensiv. Så kan det
      startes en debugger-sesjon for hver av de to trådene der debuggeren attacher rett
      til prosessen, sove_videre kan settes til 0 fra debuggeren, og trådene kan kjøres
      kontrollert videre. I teorien skulle dette virke veldig bra, men jeg har ikke prøvd
      dette selv. Dersom noen har bedre forslag til hvordan dette kan gjøres, tas de 
      imot med glede, og vil bli inkludert på denne siden.
    </avsnitt>
  </hoveddel>
</artikkel>
