4. praktikum - Silumine (debugger)
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
Sisukord
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.