Bellek Kaçaklarının Peşinde Koşuşturmacalar

Glib'le uğraşmaya devam ediyorum [1]. 2.6 sürümünde kitaplık arayüzüne bir komut satırı ayrıştırıcısı eklendiğini Enver'in bilgilendirmesiyle öğrenmiştim. O konuyla bilâhare ilgileneceğim. Ondan önce bellek kaçaklarını yakalamak için kitaplığın ne tür imkanlar sağladığını veya kaynağı yeniden derlemeyi gerektirmeyen Valgrind ile Glib'in arasının nasıl olduğunu anlamak istedim. Basit bir deneme:

$ cat >foo.c <<EOF
#include <glib.h>

int
main()
{
        GList *foo = NULL;

        foo = g_list_append(foo, "foo");
        g_list_free(foo);

        exit(0);
}
EOF

$ gcc -o foo foo.c -I/usr/include/glib-2.0 \
                   -I/usr/lib/glib-2.0/include \
                   -lglib-2.0

$ valgrind ./foo
...
==15671== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 15 from 1)
==15671== malloc/free: in use at exit: 2149 bytes in 4 blocks.
==15671== malloc/free: 4 allocs, 0 frees, 2149 bytes allocated.
==15671== For counts of detected errors, rerun with: -v
==15671== searching for pointers to 4 not-freed blocks.
==15671== checked 84068 bytes.
==15671==
==15671== LEAK SUMMARY:
==15671==    definitely lost: 0 bytes in 0 blocks.
==15671==      possibly lost: 0 bytes in 0 blocks.
==15671==    still reachable: 2149 bytes in 4 blocks.
==15671==         suppressed: 0 bytes in 0 blocks.
==15671== Reachable blocks (those to which a pointer was found) are not shown.
==15671== To see them, rerun with: --show-reachable=yes

Hmm… Ayrılan bütün belleği serbest bıraktığımız halde “ulaşılabilir durumda” olduğu bildirilen birkaç blok görülüyor. Tahmin edilebileceği gibi zararsız bir durum söz konusu. Kitaplık kendi içinde bazı işler çeviriyor. Glib'in mem-pool özelliği bu. Kendisine başvurulduğu anda Glib, bellek havuzundan hazır birkaç parça koparıyor ve nasıl olsa süreç sona erdiğinde sisteme iade edilecek belleği serbest bırakmaya çalışmıyor. Tablo basitçe bu sanıyorum. Doğru mu anladım?

Zaten yeterince kirli durumda olan Valgrind çıktısında en azından şu özet kısımlarını temiz görmek isterdim. Google aramalarında bu hususta açık öneriler bulamadım. Belki kitaplığın hata ayıklama sürümünde bu işin icabına bakmışlardır düşüncesiyle -dbg paketini kurdum: sonuç yok. Hata ayıklama sürümü sadece kitaplık simgelerini içeriyor, yani unstripped bir sürüm. Program çökmeleri için faydalı olabilir.

Kaynağı yakından inceleyelim:

$ apt-get source libglib2.0-0

Glib'in inşa seçeneklerini incelediğimde çözümün ne olduğunu farkettim. Seçenekler arasında disable-mem-pools gibi bir şey var. Bu seçeneği kullanan -full-dbg gibi bir paket hazırlamak isterdim doğrusu. Fakat kitaplık paketleri hazırlamak her zaman müşkildir. Üstelik DBS kullanan bu paket yeterince de karmaşık. Bunun yerine debian/rules‘da inşa seçeneklerine şunları ekledim:

  --disable-mem-pools --disable-threads

İkinci seçenek full-dbg hatırına… Paketi yeniden derlediğimizde (devscripts/debuild) oluşan yeni libglib2.0-0'daki /usr/lib/* içeriğini mesela /usr/local/lib/debug gibi bir dizine koyuyoruz ve klasik bir numarayla sonuç:

$ LD_LIBRARY_PATH=/usr/local/lib/debug valgrind ./foo
...
==16035== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 15 from 1)
==16035== malloc/free: in use at exit: 0 bytes in 0 blocks.
==16035== malloc/free: 1 allocs, 1 frees, 12 bytes allocated.
==16035== For counts of detected errors, rerun with: -v
==16035== No malloc'd blocks -- no leaks are possible.

KDE ekosisteminden çıkan Valgrind çok yararlı bir araç. Hararetle öneriyorum. Bunların dışında araçlar da var: mesela memprof. Fakat motor tamirini böyle grafik ortamda icra etmeyi pek sevmiyorum. Valgrind için de bir grafik arayüz var: alleyoop. Lütfen bu tür arayüzlerden uzak duralım :-) Belirtmeden geçmeyeyim, Glib'in kendi araçları da gayet güzel. Mesela şu:

extern GMemVTable *glib_mem_profiler_table;
g_mem_set_vtable(glib_mem_profiler_table);

...

g_mem_profile();

Ama Glib'in bulaştığı bir davanın salâhiyeti için soruşturma tamamlanıncaya kadar kendisini açığa almamızda fayda var :-)