maanantai 23. maaliskuuta 2009

Gdb watch pointit - pysähdy kun muistipaikassa oleva data muuttuu




Gdb watchpointit - Stoppaa suoritus kun data muuttuu


Oikeastaan kirjoitin tämän tekstin alunperin tuohon aiempaan blogitekstiini (debuggauksesta ylensä), mutta koska se paisui kuin pullataikina, päätin laittaa tästä ihan oman osion.

Usein ohjelmissa on bugeja, joissa kirjoitetaan yli allokoidun tilan siten, että kirjoitus menee kerta toisensa jälkeen samaan paikkaan. (Esim, ylikirjoitettaessa staattisesti varattu taulukko). Mikäli sattuu niin ikävästi, että ylikirjoitettu muisti on varattu ohjelman käyttöön, voi olla, ettei käyttöjärjestelmä huomaa mitään virhettä, vaan ohjelma kyykkää myöhemmin mitä kummallisimpiin vikoihin. Tosi jänniä ilmiöitä saa aikaiseksi muunmuassa ylikirjoiottamalla jonkin tallennetun callbackfunktion osoittimen -> prosessorille annetaan suoritettavaksi mitä erikoisempia käskyjä :) Tällaisten "viuhupointtereiden" jäljittäminen voi olla todella työlästä.

Tällaisissa tapauksissa usein toivoisi, että muistiosoitteen voisi merkitä "seurattavaksi", eli jonkin vahtimaan osoitetta ja pysäyttämään suorituksen osoitteen päivittyessä. Näin saisi viuhupointterin käyttäjän kiinni itse teosta.

gdb antaa tähän valmiudet watchpointtien avulla. Tämä kuitenkin vaatii pari riviä lisäselvitystä.

Watchpoint on siis periaatteeltaan samanlainen kuin breakpoint, mutta nyt suoritus pysäytetään tietyn muuttujan (tai muistipaikan) arvoa luettaessa/kirjoitettaessa.

Gdb:ssä on tuki kahdenlaisille watchpointeille, "rauta" ja "softa" watchpointeille. Näissä kuitenkin on muutamia eroja:

Rautawatchpointit hyödyntävät prosessoriin tehtyä watchpoint tukea (johon en tarkemmin nyt mene). Kuitenkaan kaikissa prosessoreissa ei ole tukea tälle, tai gdb ei pysty kaikkien prosessorien tarjoamaa tukea hyödyntämään. Perus x86, ppc, ym prosessorien kanssa tämä tuki tulisi kuitenkin olla saatavilla.

Defaulttina rautawatchpointit on käytössä, mikäli ne on tuettuina. Tähän on pari syytä. Ensinnäkin, rautawatchpointit ovat nopeita. Niiden käyttö ei vaadi ylimääräistä prosessointiaikaa, sillä varsinainen työ tehdään jo rautatasolla. Lisäksi rautawatchpointit mahdollistavat ohjelman pysäytyksen "vahdittavaksi" määritettävää muuttujaa/muistiosoitetta luettaessa.

Softawtchpointit tätä lukemisen havaitsemista ei voida toteuttaa, ilman että jokainen käsky tutkittaisiin debuggerin toimesta, ja lukupyyntö tutkittaisiin. Tämä hidastaisi ohjelman suoritusta toivottoman paljon. Jo pelkkä muistipaikkojen kirjoituksen tutkiminen hidastaa ohjelman ajoa melkoisesti (Ts. softawatchpoittien käyttö hidastaa ohjelmaa merkittävästi jo nyt).

Vielä yksi ero softa ja rautawatchpointtien välillä tulee esiin monisäikeisiä ohjelmia debugattaessa. Softawatchpointti pystyy havaitsemaan kirjoituksen vain sen säikeen kontextista, jossa watchpointti asetettiin. Rautawatchpointti taas pystyy havaitsemaan luvut ja kirjoitukset säikeestä riippumatta, tosin rautawatchpointin luonnissa voidaan täsmentää minkä säikeen kontekstissa tulevista luvuista/kirjoituksista ollaan kiinnostuneita.

Pieni tarkennus on kuitenkin vielä paikallaan. Mikäli käytät muuttujan nimeä watchpointtia asetettaessa, watchpoint pysyy validina vain niin kauan, kun ohjelman suoritus pysyy siinä (funktion/tiedoston)kontekstissa, jossa muuttuja on näkyvillä. Jos haluat tutkia esim. jotain keosta varattua paikallista muuttujaa läpi koko ohjelman ajon, tulee sinun asettaa watchpointti muistialueen osoitteen, ei muuttujan nimen perusteella.

Ja nyt watchpointin syntaksiin:
Watchpoint muuttujan int foo sisällölle nimen ja osoitteen perusteella:

watch foo [threadinnumero mikäli rautawatchpointti]
watch (int)0xB00BBABE [threadinnumero mikäli rautawatchpointti]

Eli jälkimmäisessä siis oletetaan, että muuttuja foo on talletettu osoitteeseen 0xB00BBABE. Oikean osoitteen saat selville käskyllä

print &foo


Huomaa siis, että mikäli haluat ohjelman suorituksen pysähtyvän kun johonkin muistiosoitteeseen kirjoitetaan jotain, tulee sinun suorittaa oikeanlainen castaus, jotta gdb tietää minkäkokoista muistikönttiä annetusta osoitteesta eteenpäin halutaan tarkkailla. Laitetaanpä viekä selvennykseksi toinen esimerkki. Olkoon osoitteessa 0x00BADDAD nyt taulukko long long int *foo[5], eli taulukollinen osoittimia jotka osoittavat long long int tyyppiseen dataan.

Mikäli haluaisimme seurata sitä arvoa, johon taulukon kolmas osoitin osoittaa, komentaisimme:

watch *((long long int *)0x00BADDAD+2) [threadinnumero]

Lisaa gdb:sta

Ei kommentteja: