Haluaisin oppia debuggaamaan avoimen lähdekoodin ohjelmia. Kokeilin huvikseni käynnistää geditin ja sulkea se heti käynnistyttyä X-painikkeesta. Ajoin ohjelman komennolla
valgrind --tool=memcheck --leak-check=full --show-reachable=yes -v gedit
Sain läjäpäin tulostetta, ja viimeiset rivit olivat
==23173== by 0x8098CC6: ??? (in /usr/bin/gedit) ==23173== by 0x809CD47: ??? (in /usr/bin/gedit) ==23173== by 0x47029C1: g_type_create_instance (in /usr/lib/libgobject-2.0.so.0.2400.1) ==23173== by 0x46E6A17: ??? (in /usr/lib/libgobject-2.0.so.0.2400.1) ==23173== by 0x46E7C4B: g_object_newv (in /usr/lib/libgobject-2.0.so.0.2400.1) ==23173== ==23173== 80,384 bytes in 1,256 blocks are possibly lost in loss record 8,380 of 8,380 ==23173== at 0x4025016: realloc (vg_replace_malloc.c:525) ==23173== by 0x476500E: g_realloc (in /lib/libglib-2.0.so.0.2400.1) ==23173== by 0x478023E: ??? (in /lib/libglib-2.0.so.0.2400.1) ==23173== by 0x4780DB7: g_string_insert_len (in /lib/libglib-2.0.so.0.2400.1) ==23173== by 0x4781157: g_string_append_len (in /lib/libglib-2.0.so.0.2400.1) ==23173== by 0x474B9BC: ??? (in /lib/libglib-2.0.so.0.2400.1) ==23173== by 0x474BBD3: g_build_filename (in /lib/libglib-2.0.so.0.2400.1) ==23173== by 0x41DC275: ??? (in /usr/lib/libgtk-x11-2.0.so.0.2000.1) ==23173== by 0x41DD679: ??? (in /usr/lib/libgtk-x11-2.0.so.0.2000.1) ==23173== by 0x41DF174: gtk_icon_theme_has_icon (in /usr/lib/libgtk-x11-2.0.so.0.2000.1) ==23173== by 0x4142294: gtk_action_group_add_actions_full (in /usr/lib/libgtk-x11-2.0.so.0.2000.1) ==23173== by 0x414239D: gtk_action_group_add_actions (in /usr/lib/libgtk-x11-2.0.so.0.2000.1) ==23173== ==23173== LEAK SUMMARY: ==23173== definitely lost: 3,655 bytes in 15 blocks ==23173== indirectly lost: 13,210 bytes in 659 blocks ==23173== possibly lost: 1,320,690 bytes in 14,286 blocks ==23173== still reachable: 521,667 bytes in 8,011 blocks ==23173== suppressed: 0 bytes in 0 blocks ==23173== ==23173== ERROR SUMMARY: 3034 errors from 3034 contexts (suppressed: 239 from 13) --23173-- --23173-- used_suppression: 239 dl-hack3-cond-1 ==23173== ==23173== ERROR SUMMARY: 3034 errors from 3034 contexts (suppressed: 239 from 13)
Miten noita muistivuotoja kannattaa lähteä korjaamaan, kun melko minimaalinen kokeilukin antoi 3034 virhettä?
Ja toisaalta luin Valgrind-oppaasta, että ennen muistivuotojen paikallistamista ohjelma ja sen käyttämät kirjastot tulee kääntää -g option kanssa. Miten tämä tapahtuu, kun asensin geditin vain komennolla
sudo apt-get install gedit
? Ilmeisesti ensiksi pitäisi kääntää uudestaan geditin ja GTK+:n lähdekoodit vai kuinka?
Kyllä. Valgrindissa ja GDB:ssä (muiden muassa) näet rivinumerot niistä ohjelmista ja kirjastoista, jotka on käännetty g-lipun kanssa. Voit tehdä tämän lataamalla netistä ohjelmien lähdekoodit ja lukemalla näiden dokumentaatiosta, miten g-lipun saa päälle ohjelmaa käännettäessä ja kääntämällä ohjelmat sitten ohjeiden mukaisesti - sikäli, kun mitään ymmärrettäviä ohjeita löytyy. Joistakin kirjastoista löytyy myös paketinhallinnasta g-lipulla käännetty eli debug-symbolit sisältävä versio valmiina. Debian-pohjaisissa järjestelmissä (kuten Ubuntu) voit kokeilla myös tätä.
Etenkin isot kirjastot aiheuttavat Valgrindissa usein paljon virheilmoituksia, jotka saattavat olla tai saattavat olla olematta oikeita korjaamisen tarpeessa olevia muistivuotoja. Näiden ilmoitusten syyn paikallistaminen vaatii todennäköisesti joka tapauksessa syvällistä ymmärrystä korjattavien sovellusten ja/tai niiden käyttämien kirjastojen sisäisestä toiminnasta, joten en suosittelisi uppoamaan tähän suohon, jos et ole erityisen kiinnostunut kyseisistä ohjelmista.
EDIT: ihan ensimmäisenä kannattaa tietenkin tarkistaa, että käytät ohjelman uusinta versiota ja että joku muu ei ole jo korjannut löytämiäsi virheitä.
Kannattaa ehdottomasti muistaa, että valgrind ja muut vastaavat ohjelmat ovat työkaluja, jotka toimivan vain tietyissä rajoissa ja enintään niin hyvin, kuin niitä osaa käyttää. Jos ne antaisivat selkeää ja kiistatonta tietoa, kaikki maailman muistivuodot olisi varmaan jo korjattu – näin ei kuitenkaan ole.
Minusta järkevämpi lähestymistapa olisi ensin etsiä ongelma ja vasta sitten ratkaisu. Mainitsit katsoneesi ohjelmaa gedit. Oletko havainnut tässä jonkin muistivuodon, joka syö resursseja? Älä yritä korjata, jos ongelmaa ei ole. Entä oletko paikallistanut, millaiseen käyttöön tai jopa mihin tiettyyn toimintoon muistivuoto liittyy? Tämäkin kannattaa selvittää, jotta voi käyttää työkaluja kohdennetusti ja jotta voi suunnitella testitapauksia, joissa kyseistä ominaisuutta rasitetaan oikein olan takaa niin, että muistivuoto näkyy paremmin.
Koetin avata sillä tekstitiedostoa, jossa on yhdellä rivillä noin 100000 merkkiä. Tiedosto avautuu hitaasti ja merkit näyttäisivät menevän päällekäin. Epäilen muistivuotoa, mutta enpä ole asiasta varma. Täytyisi opetella kääntämään GTK+ ja gedit siten, että näkisin mitä funktioita ohjelma kutsuu.
Käytän geditin versiota 2.30.3 (hmm, sivun http://projects.gnome.org/gedit/ mukaan viimeisin vakaa versio on 2.30.2). En saa näkymään oikein tiedostoa example.txt, joka on luotu seuraavalla koodilla:
#include <iostream> #include <fstream> using namespace std; int main () { ofstream myfile; myfile.open ("example.txt"); for (long i=0;i<200000;++i) { myfile << "123"; } myfile.close(); return 0; }
Gedit latoo merkit liian lähekkäin. Ja jos tuon 200000 muuttaa luvuksi 20000000, niin tiedoston avaaminen kestää tolkuttoman kauan, ja en jaksa odottaa tiedoston avautumista. Luulen, että ohjelma koettaa lukea koko riviä kerralla ja tallentaa se liian pieneen muuttujaan, vaikka järkevämpää olisi lukea kerrallaan korkeintaan jokin vakiomäärä kirjaimia.
Ajattelin, että virhettä olisi helpompi paikallistaa, jos voisin nähdä jollakin debuggerilla, mitä koodiriviä kulloinkin kutsutaan kun gedit käynnistyy. En kuitenkaan tiedä miten voisin ajaa ohjelmaa ja samaan aikaan selata koodia rivi kerrallaan.
Miksi "muistivuoto" on monelle ensimmäinen sana, joka kaikenlaisista virheistä ja hidasteluista tulee mieleen? Noin ison tiedoston kanssa on lukuisia parempiakin selityksiä ongelmille, vaikkapa hidas algoritmi automaattiseen rivitykseen (monet tekstieditorit jopa varoittavat toiminnon hitaudesta) tai ruudun ulkopuolelle jäävän tekstin aggressiivinen esirenderöinti, joka syö kohtuuttomasti muistia (mutta ei suinkaan välttämättä vuoda!).
Jos kyseessä olisi todella muistivuoto, ongelman pitäisi paheta merkittävästi aina, kun suljet tiedoston (mutta et geditiä) ja avaat sen uudestaan. Lisäksi voit seurata hyvin yksinkertaisilla välineillä (vaikka komentorivillä top-ohjelmalla tai muulla prosessinhallintaohjelmalla), kuinka paljon muistia ohjelma käyttää; tästähän sen vuodon suoraan näkee!
Totta. Ajattelin, että kun gedit avaa pelkästään tyhjän tiedoston ja valgrid sanoo muistia hukkautuvan, että ongelma johtuu vuodosta. Pitäisi varmaankin selvittää, mitä funktioita gedit kutsuu kun example.txt avataan.
Näköjään tunnettu bugi: https://bugzilla.gnome.org/show_bug.cgi?id=127840 . Seuraavaksi pitäisi varmaankin löytää joku ohjelma, jolla lähdekoodia voi selata ohjelman suorituksen aikana. Mistäköhän löytyisi joku helppokäyttöinen debuggeri tähän hommaan?
Jaska kirjoitti:
Näköjään tunnettu bugi: https://bugzilla.gnome.org/show_bug.cgi?id=127840 . Seuraavaksi pitäisi varmaankin löytää joku ohjelma, jolla lähdekoodia voi selata ohjelman suorituksen aikana. Mistäköhän löytyisi joku helppokäyttöinen debuggeri tähän hommaan?
GDB ja DDD on helppokäyttöinen yhdistelmä. (gedit tulee kääntää g-lipun kanssa)
Ahaa. Mitenkäs nuo viimeisimmät lähdekoodit geditistä ja gtktextviewistä saa koneelle ja miten tuollainen kahden projektin paketti käännetään g-lipun kanssa?
Koska viesteistäsi jäi kuva, että olet yhä ehdottomasti muistivuodon kannalla, toistan vielä aiemman sanomani: mikään tässä esitetty ei todista, että kyseessä olisi suoranaisesti bugi, vaan kyse voi hyvin olla vain huonosta toteutuksesta (tai toteutuksesta, joka on yleensä hyvä mutta tässä erikoistapauksessa huono).
Eihän esimerkiksi seuraavassa C-koodissakaan ole muistivuotoa eikä muutakaan varsinaista bugia, vaikka se syö aivan turhaan gigan muistia:
#include <stdlib.h> #include <stdio.h> int main() { int i; char* data; data = malloc(1024 * 1024 * 1024); /* Giga muistia */ for (i = 0; i < 1024 * 1024 * 1024; ++i) { data[i] = i % 2; } printf("Anna sananen: "); if (scanf("%s", data) == 1) { printf("Annoit sanan %s.\n", data); } free(data); return 0; }
Tajusin kyllä, että kyse ei ole välttämättä muistivuodosta. Koitan nyt opetella debuggerin käyttöä seuraavaksi, jotta näen, missä vika piilee. Ilmeisesti algoritmi on huono. Tätä varten opettelen seuraavaksi debuggerin käyttöä.
On hyvä tietää, että on olemassa kahdenlaisia muistivuotoja: ajasta riippuva ja vakio ajasta riippumaton. Ajasta riippuva vuoto on kriittinen ja korjattava heti. Helppo esimerkki on peli, joka lataa jokaisen kentän alussa uudet spritet muttei koskaan vapauta tarpeettomia, jolloin sen muistinkäyttö kasvaa. Sanomattakin on selvää, ettei pelin kuulu kuluttaa muistia tällä tavoin. Se vähemmän kriittinen muistivuoto on vakio eli ajasta riippumaton vuoto, joka ohjelman alusta loppuun on sama. Tämän tyypin muistivuotoja ei välttämättä tarvitse korjata, eikä se ole aina mahdollistakaan. Jos (laadukkaissa) kolmannen osapuolen kirjastoissa on muistivuotoja, ne yleensä juurikin ovat vakiomuistivuotoja.
Jtm kirjoitti:
Tämän tyypin muistivuotoja ei välttämättä tarvitse korjata, eikä se ole aina mahdollistakaan.
Voitko antaa esimerkin muistivuodosta, joka on mahdoton korjata?
Aihe on jo aika vanha, joten et voi enää vastata siihen.