Valgrind

Dokumentti luotu: 31.3.2004, Pekka Paalanen
Kommentteja ja korjauksia saa lähettää osoitteeseen paalanen@lut.fi

Sisällysluettelo

Sivu 1:
Esittely
Valgrind LTY:ssä
Käyttö
Virhehavaintojen tulkintoja

Sivu 2 (lähinnä lisätietoa kiinnostuneille):
Tekniset vaatimukset
Pitkä esimerkkiajo
Vastaantulleita kummallisia tilanteita

Esittely

Valgrind on näppärä muistidebuggeri x86/Linux ympäristöön. Sen käyttö on helppoa, eikä se vaadi testattavalta ohjelma tavallisesti mitään erikoista. Valgrind havaitsee monia sellaisia virheitä ohjelmista, jotka muuten olisivat jääneet kokonaan huomaamatta tai niiden löytäminen olisi hyvinkin työlästä.

Valgind havaitsee seuraavanlaisia virheitä (lainaus):

Valgrind on komentoriviohjelma, joka ajaa (tavallaan tulkkaa) ajettavan ohjelman ja raportoi virheistä. Valgrind osaa myös muita temppuja kuin mitä tässä dokumentissa on esitetty, mutta tässä käsitellään vain Valgrindin muistinkäsittelyn tarkkailua (memcheck) C-kielellä kirjoitetuissa ohjelmissa.

Valgrind on suunniteltu lähinnä C ja C++ ohjelmille, mutta sillä voi tutkia millä tahansa kielellä tehtyjä ohjelmia, kunhan ohjelma on muutenkin ajettavissa. Lisäksi Valgrind ei tutki ainoastaan juuri sitä ajettavaa ohjelmaa, vaan myös kaikkia valmiita kirjastofunktioita, joita ohjelma käyttää. Ohjelmaa ei (välttämättä) tarvitse erikseen kääntää Valgrindiä varten tai linkittää johonkin erityiseen kirjastoon.

Valgrindin huono puoli on mm. se, että sen alla ohjelmat suoritetaan huomattavasti hitaammin. Oletuksena käytössä olevan muistinkäsittelyn tarkkailijan kanssa 10-30 kertaa hitaammin kuin normaalisti.

Valgrind LTY:ssä

Valgrind löytyy Pentinkulman koneista, luokka 6303. Tavallisia C-kielellä koodattuja harkkatöitä voi ajaa suoraan Valgrindillä, ja se myös kannattaa.

Käyttö

Valgrindia käytetään käännettyyn ohjelmaan (binääriin), ei lähdekoodiin. C-kielinen ohjelma kannattaa kääntää -g optiolla, esimerkiksi:

gcc -W -Wall -g -o ohjelma koodi.c
Valgrind osaa lukea mukaan kääntyvää debug-informaatiota ja kertoo selkeämmin, missä kohtaa koodia virhe tulee.

Kovemman tason optimoinnit saattavat joskus sekottaa muistidebuggeria, joten optimointi kannattaa jättää pois tai käyttää korkeintaan -O1 tasoa.

Itse Valgrindin käyttäminen on yksinkertaista:

valgrind ./ohjelma
Valgrind ajaa ohjelman ja tulostelee samalla raporttiaan. Oletuksena Valgrindin ilmoitukset menevät standardiin virhevirtaan eli stderr:iin, siis normaalisti ruudulle.

Valgrindille on myös optioita olemassa, joista saa listan komennolla valgrind --help, mutta man- tai info-sivua sillä ei ole. Dokumentaatiota löytyy Valgrindin kotisivulta englanniksi. Yleisimmät optiot ovat:

Virhehavaintojen tulkintoja

Seuraavassa on esitetty pätkiä Valgrindin tulosteista kertoen, mitä ne tarkoittavat.

Invalid write

==3863== Invalid write of size 1
==3863==    at 0x40021F47: strcpy (in /usr/lib/valgrind/vgskin_memcheck.so)
==3863==    by 0x8048807: kasittely (korjattu.c:129)
==3863==    by 0x804856D: main (korjattu.c:23)
==3863==    by 0x40263DCB: __libc_start_main (in /lib/libc-2.3.2.so)
==3863==    Address 0x410BC6B0 is 0 bytes after a block of size 4 alloc'd
==3863==    at 0x4002AA4D: malloc (in /usr/lib/valgrind/vgskin_memcheck.so)
==3863==    by 0x80487C7: kasittely (korjattu.c:123)
==3863==    by 0x804856D: main (korjattu.c:23)
==3863==    by 0x40263DCB: __libc_start_main (in /lib/libc-2.3.2.so)
Ohjelma on yrittänyt kirjoittaa yhden tavun väärään paikkaan muistia. Monesti tällainen johtaisi segmentation fault:iin, mutta se ei ole mitenkään taattua. Normaalisti virhe olisi jäänyt havaitsematta ja tuottanut hyvin kummallisia tuloksia.

Virhe sattui funktiossa strcpy, joka on standardissa C-kirjastossa (tai tässä tapauksessa se on Valgrindin omassa kirjastossa). Funktiota kutsuttiin funktiosta kasittely riviltä 129 tiedostossa korjattu.c. Tätä taas kutsuttiin funktiosta main riviltä 23 tiedostossa korjattu.c, jne. Kääntäjän -g optio mahdollistaa näin tarkan paikan ilmoittamisen ohjelmassamme, mutta koska strcpy:ä ei ole käännetty -g:n kanssa, ei siitä nähdä kuin muistiosoite, mikä ei paljoa lohduta.
Virhe on kuitenkin todennäköisimmin omassa koodissamme, joten ei hätää, nämä tiedot yleensä riittävät virheen paikantamiseen.

Samassa yhteydessä Valgrind raportoi myös muistiavaruudessa lähinnä olevan laillisesti varatun muistialueen; missä kohtaa koodia se muistialue on varattu, sekä miten kaukana käsitelty "väärä muistipaikka" on laillisesta alueesta. "0 bytes after a block" kertoo, että "väärä muistipaikka" on heti laillisen muistialueen perässä. Tästä voisi vetää johtopäätöksen, että on varattu 4 tavua pitkä taulukko, jota on sitten käyty läpi, mutta juostu taulukon lopun ohi.

Invalid read

==3863== Invalid read of size 1
==3863==    at 0x40021F18: strlen (in /usr/lib/valgrind/vgskin_memcheck.so)
==3863==    by 0x4029655E: _IO_vfprintf (in /lib/libc-2.3.2.so)
==3863==    by 0x4029D131: _IO_printf (in /lib/libc-2.3.2.so)
==3863==    by 0x8048831: kasittely (korjattu.c:133)
==3863==    Address 0x410BC6B0 is 0 bytes after a block of size 4 alloc'd
==3863==    at 0x4002AA4D: malloc (in /usr/lib/valgrind/vgskin_memcheck.so)
==3863==    by 0x80487C7: kasittely (korjattu.c:123)
==3863==    by 0x804856D: main (korjattu.c:23)
==3863==    by 0x40263DCB: __libc_start_main (in /lib/libc-2.3.2.so)
Tässä taas on luettu yksi tavu väärästä paikasta, muuten samantapainen tilanne kuin edellisessä. Funktiosta kasittely on kutsuttu printf funktiota (joka näkyy tässä nimellä _IO_printf). Koska virhe tapahtui funktiossa strlen, voisi arvella, että on yritetty tulostaa merkkijonoa, joka ei ole nul-terminoitu.

...depends on uninitialised value(s)

==3826== Conditional jump or move depends on uninitialised value(s)
==3826==    at 0x804899C: parser (koodi2.c:140)
==3826==    by 0x80490F2: main (koodi2.c:371)
==3826==    by 0x40263DCB: __libc_start_main (in /lib/libc-2.3.2.so)
==3826==    by 0x8048420: (within /home/tester/desk/ht1)
Funktiossa parser tiedoston koodi2.c rivillä 140 on jokin ehtolauseke, jonka tulos riippuu alustamattoman muuttujan (muistipaikan) arvosta. Paikallisia muuttujia tai malloc:lla varattuja muistialueita ei automaattisesti alusteta, vaan ne sisältävät mitä sattuu. Ohjelman toiminta ei saa riippua alustamattomista muuttujista, koska tulos voi olla mitä tahansa.

Use of uninitialised value

==22678== Use of uninitialised value of size 4
==22678==    at 0x80487C1: laske_luvut (koodi.c:120)
==22678==    by 0x8048E65: main (koodi.c:264)
==22678==    by 0x40263DCB: __libc_start_main (in /lib/libc-2.3.2.so)
==22678==    by 0x80484C0: (within /home/tester/desk/ht1)
Vähän sama tilanne kuin edellisessä, ohjelma on käyttänyt alustamatonta muuttujaa esimerkiksi jossakin laskuoperaatiossa. Muuttujan koko on neljä tavua, joten se on luultavasti int tyyppiä.



Seuraava sivu