Jako drugi projekt na zaliczenie przedmiotu Programowanie systemowe mam do napisania program przetwarzający dane z wybranego urządzenia. Ponieważ tak naprawdę chodzi o to, żeby nauczyć się programować z użyciem wątków, nie ważne jest skąd będą pochodziły. Jedyne warunki jakie program musi spełniać, to ciągła responsywność dla interfejsu użytkownika, nawet podczas wykonywania pomiarów. A ponieważ wszelkie dane od użytkownika najłatwiej pobiera się poprzez GUI, do realizacji programu użyłem biblioteki GTK+.
Pisząc w C zawsze obawiam się jednej rzeczy - Naruszenie ochrony pamięci . Informacja nie oznacza kompletnie nic, poza tym, ze to co napisałem nie działa. Prawie od samego początku, do programowania używałem Vim, więc nie uśmiechała mi się nauka jakiegoś graficznego IDE z debuggerem .
Niestety tym razem nie udało mi się samodzielnie znaleźć źródła błędu i nie pozostało mi nic innego, jak poznanie nowego narzędzia - GDB
Prosty przykład użycia GDB
Poniższy przykład pokazuje jak używać GDB , nie mając o nim prawie żadnego pojęcia. Przeczytałem parę stron dokumentacji, dwa tutoriale i mimo niewielkiej wiedzy jaką zdobyłem, byłem w stanie znaleźć błąd w kodzie.
Aby dostępne były dodatkowe informacje dla debuggera, program skompilować należy
podając dodatkową flagę -g.
Oto co robiłem żeby znaleźć miejsce gdzie zwolniłem niezarezerwowaną pamięć w mojej aplikacji:
macbook # gdb gui GNU gdb 6.8 Copyright (C) 2008 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "i686-pc-linux-gnu"... (gdb) # drukowanie fragmentu kodu (gdb) l 234 warning: Source file is more recent than executable. 229 "You have to choose device"); 230 gtk_dialog_run(GTK_DIALOG(dialog)); 231 gtk_widget_destroy(dialog); 232 return FALSE; 233 } 234 app = prs_app_new(app_conf->window, 235 gtk_combo_box_get_active_text(app_conf->input_device), 236 gtk_spin_button_get_value_as_int(app_conf->work_time)); 237 prs_app_start_measure(app); 238 prs_app_delete(app); (gdb) # ustawiam breakepoint na 234 lini (gdb) b 234 Breakpoint 1 at 0x80498e7: file gui.c, line 234. (gdb) # uruchamiam program, wyklikuję parametry w intefrejsie użytkownika (gdb) r Starting program: /home/piotrek/prs/src/gui [Thread debugging using libthread_db enabled] [New Thread 0xb7587970 (LWP 16632)] (gui:16632): GLib-GObject-WARNING **: invalid cast from `GtkButton' to `GtkLabel' [Switching to Thread 0xb7587970 (LWP 16632)] Breakpoint 1, on_run_button_clicked (button=0xa00b190, data=0xa00bf28) at gui.c:234 234 app = prs_app_new(app_conf->window, (gdb) # program doszedł do breakepointa i zatrzymał się. Można teraz # wykonać wszystkie niezbędne operacje, takie jak na przykład # drukowanie zmiennych (gdb) p app_conf $1 = (PrsAppConf *) 0xa00bf28 (gdb) p app $2 = (PrsApp *) 0xbfac5c18 (gdb) # można też wywoływać funkcje (gdb) p printf("\"%s\"\n", gtk_combo_box_get_active_text(app_conf->input_device)) "/dev/urandom" $3 = 15 (gdb) p gtk_spin_button_get_value_as_int(app_conf->work_time) $4 = 3 (gdb) # ponieważ nic mnie już tu nie interesuję, wykonuję kolejną linijkę # skompilowanego kodu (gdb) s prs_app_new (parent=0xa01a010, dev=0xa1a01f0 "/dev/urandom", work_time=3) at gui.c:180 180 app = (PrsApp *) g_malloc(sizeof(PrsApp)); (gdb) # jeśli nie wpiszę żadnego polecenia, powtórzona zostatnie ostatnia # instrukcja (gdb) 181 app->window = GTK_WINDOW(gtk_window_new(GTK_WINDOW_POPUP)); ... (gdb) 152 gtk_widget_destroy(GTK_WIDGET(app->box)); (gdb) 153 gtk_widget_destroy(GTK_WIDGET(app->window)); (gdb) Program received signal SIGSEGV, Segmentation fault. 0xb7a1a038 in g_type_check_instance_cast () from /usr/lib/libgobject-2.0.so.0 (gdb) Single stepping until exit from function g_type_check_instance_cast, which has no line number information. Program terminated with signal SIGSEGV, Segmentation fault.
Jak widać, w linii 153 zwalniałem pamięć, której nie zarezerwowałem. Dzięki
gdb mogę też w prosty sposób sprawdzić wartości zmiennych w danym stanie
programu, zmienić je na inne lub wykonać dowolną funkcję.
Wbudowana pomoc
Bardzo miłą niespodzianką w GDB jest polecenie help (lub w skrócie h), które
dokładnie opisuje każde polecenie debuggera. Zamiast co chwila szukać w manualu
jak działa komenda, wystarczy użyć wbudowanego systemu pomocy GDB.
GDB i Vim
Aby zintegrować GDB i Vim istnieje wiele skryptów , z których najbardziej zaawansowanym rozszerzeniem jest chyba clewn. Ostatnio zdałem sobie jednak sprawę z tego, że Vim to nie Emacs. Nie musi umieć wszystkiego, a właściwie nic poza edycją teksu.
Ponieważ nigdy nie żałowałem decyzji, które podejmowałem w zgodzie z regułami zawartymi w książce The Art of Unix Programming , postanowiłem nie łączyć tych dwóch narzędzi. Nie ukrywam też, że duży wpływ na tą decyzję miał post na forum Arch Linuksa napisany przez osobę która bez wątpienia jest lepszym programistą ode mnie.