4. praktikum - Silumine (debugger)

Allikas: Lambda
Süsteemprogrammeerimine keeles C

Praktikumid

Laborid

Labor on saadaval ka inglise keeles

See labor näitab kuidas saame programmi silumisprogrammi abil tema töö ajal lähemalt uurida. Ühtlasi püüame pöördkomipleerida programmiga objdump.

debug - silumine
decompilation - pöördkompileerimine

Kompileerimine

Kuna kompilaator tahab vaikimisi ehitada kõige väiksema võimaliku käivituva faili (sellele lisaaega kulutamata), eemaldab ta failist info funktsioonide ja muutujate nimede kohta. (seda nimetatakse sümboltabeliks (symbol table). Sümboltabeli saame soovi korral faili alles jätta, kui kompileerimisel -g võtit kasutame.

gcc -g filename.c -o filename

Siit ka vajadus ehitada edasiste kompileerimiste kiirendamiseks makefile. (Võtke selleks tõesti hetk, sest hoiate sellega tulevikus kokku aega ja närve)

Miks vajame sümboltabelit? Debugger suudab selle abil silumise ajal väärtuslikku lisainfot pakkuda.

Lühidalt: kui silud, kasuta -g võtit.

Siluja

Kasutame käsureaprogrammi gdb - GNU Debugger. Tollel on ka graafiline liides, kuid selle üle X käima saamine on veidi keerulisem ja sinna me täna ei lähe. Oma arvutis võite sellega eksperimenteerida. GDB on väga võimas ja tegelikult ka üllatavalt lihtne kasutada.

  • GDB manuaal
  • Abi saamiseks võid gdb-s kirjutada "help" või "help käsunimi". See meenutab käske ja nende süntaksit. Käske on väga palju, aga sellest ehmatada ei tasu, sest reaalselt kasutad neist nagunii väga väikest hulka.

Kompileeritud programmi jooksutamiseks kirjuta:

gdb programminimi

Avaneb käsurida (gdb) ja midagi muud ei juhtugi. Programm ootab, et määraksid katkestuspunktid (breakpoint) ja vaatlejad (watches) ja kirjutaksid käsu "run".

Programmist väljumiseks kirjuta:

quit

Programmi jooksutamine

Ometi ei taha me väljuda. Programmi gdb-s käimalaskmiseks kirjutame käsu:

run

Programm jookseb kuni katkestad selle CTRL-C abil (mispuhul seiskub programm kusiganes ta parajasti oli ning saad tema mälupilti sellel ajahetkel uurida), programm lõpetab või jookseb kokku (Segmentation fault). Kokkujooksmine on selgelt kõige huvitavam, sest saad uurida kus see juhtus ning tutvuda stacki sisuga.

Pärast "run" käsku võid lisada ka programmi argumendid.

Ülesanne: Jooksuta gdb abil mingi programm, siis jooksuta programm, mis jookseb kokku ja vaata mis juhtub.

Peatuspunktid

Programmi teatud kohas peatamiseks võid sellele enne käivitamist katkestuspunkte lisada. Nendeks võivad olla funktsiooninimed, reanumbrid või koguni mõne kindla faili kindlad read. Vajadusel võid oma programmi peatada isegi siis kui too püüab mõnel kindlal mäluaadressil olevat instruktsiooni jooksutada.

  • break function
  • break linenumber
  • break file:function
  • break file:linenumber
  • break *address

Katkestuspunktide nägemiseks kirjuta:

info break

Kui vigu teed, saad neid kustutada (punktide numbrid saad käsuga "info break"):

delete breakpoint number

Ülesanne: Lisa katkestuspunkt, jooksuta programmi kuni see kätte jõuab.

Programmi uudistamine

Sul peaks olema nüüd peatunud programm. Debugger on mälu vaatlemise tööriist: saad lugeda (ja muuta) muutujate sisu ja stacki.

Kõige lihtsam viis sisu vaadelda on print:

print variablename
print array[2]
print $eax

Võid ka pointerite sisuga tutvuda kui *. märki kasutad. Print on nii sage käsk, et võid seda lühendada kirjutades lihtsalt 'p'.

p $eax

Vihje: Võid mälu x käsuga (eXamine) uurida. Kõigi registrite vaatlemiseks kirjuta 'info registers'.

Ülesanne: Vaatle oma programmi muutujaid. Lisa programmi mõni struct ja püüa aru saada kuidas ja millisel moel selle andmeid hoitakse.

Mälu muutmine

Muutujate väärtust on väga lihtne muuta. Võid selleks kasutada 'print' käsku =-märgiga.

print x = 888
p c = '\0'

Kui muutuja väärtust näha ei soovi, võid kasutada ka 'set' käsku:

set var x = 99
set var c = EOF

Stack

Stack esitatakse backtrace käsu puhul: see on inimloetav ning saad tulemuseks ridade numbrid ja iga stackikihi funktsiooninime:

backtrace

Kehtiva frame'i infot saad kirjutades:

frame

Ülesanne Jooksuta programmi mille tulemuseks on segmentation fault (ehita üks selline). Jooksuta seda kuni ta kokku jookseb ja vaata kus ta seda tegi.

Vihje: Mõne teise frame'i kohalike muutujate uurimiseks võid kasutada käsku 'up'. See liigub mööda stacki kihte kõrgemale. Vaata mis juhtub kui kirjutad 'up' ja siis 'frame'.

Liikumine

Seisatud programmi saab jooksutada ka sammhaaval. Selleks on kaks käsku: 'next' ja 'step'.

  • next - käivitab järgmise koodirea
  • step - käivitab järgmise koodirea, aga astub sisse ka erinevatesse funktsioonidesse
  • continue - jätkab tavapärast käivitumist

Kui tahad teada millisel real viibid:

list

Käsule list võid lisada ka rea numbri; siis näed tollel konkreetsel real olevat koodi.

Vihje: võid ka ühe protsessorikäsu (instruction) jooksutada; selleks on käsud: 'stepi' ja 'nexti'.

Ülesanne: Jooksuta peatuspunktidega programmi: püüa tuvastada mis erinevus on käskudel 'step' ja 'next'.

Watchpoint

Programmi saab seisata ka siis kui mõne muutuja väärtus muutub. Neid nimetatakse watchpointideks (nalja pärast võite öelda näiteks jõllik):

watch variablename

Ülesanne: Lisa muutujale watchpoint ja vaata kuidas see rakendub. Käivitumise jätkamiseks kasuta käsku 'continue'

Pöördkompileerimine

Saad muuta protsessorisse söödetavad numbrid loetavaks assemblerkoodiks:

disas
disas functionname

Ülesanne: püüa pöördkompileerida mõnd lihtsat aritmeetikat tegevat funktsiooni. Vaata kuidas kasutatakse stacki enne arvutust ning kuidas seda funktsioonist lahkumise eel töödeldakse.

Pöördkompileerida saad ka programmi 'objdump' abil.

Ülesanne: Pöörkompileeri programm: kuidas erineb tulemus gdb "disas" käsust? Testi objdump erinevaid võtmeid, mida "man objdump" pakub.

Järeldused

Kui mõistad kuidas stacki hallatakse, muutub assembler oluliselt arusaadavamaks. Võid suvalise kompileeritud programmi tagasi assembleriks muuta, kuid tulemus on ilma lähteteksti ja sümboltabelita üsna kole (proovi näiteks ls käsu puhul)

Võid proovida kirjutada funktsiooni, milles on kaks funktsiooni first väljub ja second teeb midagi huvitavat. Võid muuta "main" funktsiooni nii, et selle 'call' instruktsioon kutsub first asemel välja funktsiooni second. Sul ei pea selleks olema lähtekoodi. Samuti võid otsida mõne 'jmp' käsu ning vahetada selle aadressi sinna kus teine funktsioon välja kutsutaks. 4 baidi muutmine käivituvas failis võib palju muuta.

Loodetavasti olid eksperimendid valgustava iseloomuga.