27
Odrobaczanie (debugging) programu Zasada van Tassela: Żaden program nie działa od razu. Bądź szczególnie podejrzliwy, jeżeli za pierwszym razem dostałeś poprawną odpowiedź. Pojęcie “robaka” (bug) w elektronicznej technice obliczeniowej przypisuje się admirał U.S. Navy, Grace Murray Hopper, twórczyni COBOLu, która opowiadała historię o techniku, który naprawił komputer Harvard II usuwając ćmę z obwodów. Cała historia jest opisana w Annals of the History of Computing, 3 285-286 (1981). Później uogólniono to pojęcie na złe funkcjonowanie programów. Jeszcze wcześniej mianem “bug” określano defekty urządzeń elektrycznych ("Hawkin's New Catechism of Electricity", T. Audel & Co., 1896) albo przyczyny szumu na liniach telefonicznych lub telegraficznych. Słowa “bug” w sensie nagłego rujnującego zdarzenia używano już w czasach Shakespeare’a. Samo słowo w tym znaczeniu prawdopodobnie pochodzi z walijskiego (zanglicyzowanego) “bugbear”, oznaczającego różne

Odrobaczanie (debugging) programu

  • Upload
    kalei

  • View
    48

  • Download
    0

Embed Size (px)

DESCRIPTION

Odrobaczanie (debugging) programu. Zasada van Tassela: Żaden program nie działa od razu. Bądź szczególnie podejrzliwy, jeżeli za pierwszym razem dostałeś poprawną odpowiedź. - PowerPoint PPT Presentation

Citation preview

Page 1: Odrobaczanie (debugging) programu

Odrobaczanie (debugging) programu

Zasada van Tassela: Żaden program nie działa od razu. Bądź szczególnie podejrzliwy, jeżeli za pierwszym razem dostałeś poprawną odpowiedź.

Pojęcie “robaka” (bug) w elektronicznej technice obliczeniowej przypisuje się admirał U.S. Navy, Grace Murray Hopper, twórczyni COBOLu, która opowiadała historię o techniku, który naprawił komputer Harvard II usuwając ćmę z obwodów. Cała historia jest opisana w Annals of the History of Computing, 3 285-286 (1981). Później uogólniono to pojęcie na złe funkcjonowanie programów. Jeszcze wcześniej mianem “bug” określano defekty urządzeń elektrycznych ("Hawkin's New Catechism of Electricity", T. Audel & Co., 1896) albo przyczyny szumu na liniach telefonicznych lub telegraficznych.

Słowa “bug” w sensie nagłego rujnującego zdarzenia używano już w czasach Shakespeare’a. Samo słowo w tym znaczeniu prawdopodobnie pochodzi z walijskiego (zanglicyzowanego) “bugbear”, oznaczającego różne mityczne potwory.

Page 2: Odrobaczanie (debugging) programu

Admirał Grace Murray Hopper (1896-1992)

Strona z dziennika komputera Harvard II z opisem usunięcia ćmy z obwodów wraz z przyklejonym owadem.

Page 3: Odrobaczanie (debugging) programu

Jeden z “najsławniejszych” błędów w FORTRANie (historia opisana przez Freda Webba na alt.folklore.computers w roku 1990:

I worked at Nasa during the summer of 1963. The group I was working in was doing preliminary work on the Mission Control Center computer systems and programs. My office mate had the job of testing out an orbit computation program which had been used during the Mercury flights. Running some test data with known answers through it, he was getting answers that were close, but not accurate enough. So, he started looking for numerical problems in the algorithm, checking to make sure his tests data was really correct, etc. After a couple of weeks with no results, he came across a DO statement, in the form:

DO 10 I=1.10

This statement was interpreted by the compiler (correctly) as:

DO10I = 1.10

The programmer had clearly intended:

DO 10 I = 1, 10

Page 4: Odrobaczanie (debugging) programu

Literówki w nazwach i nie tylko (np. JI zamiast IJ, “1” zamiast “I” lub “O” zamiast “0” jako indeks tablicy albo w wyrażeniach

Rezygnacja z reguły pierwszej litery (zawsze dajemy IMPLICIT NONE). Używanie małych a nie dużych liter (trudno pomylić z cyframi)

Pomyłkowe wstawienie zmiennych lub stałych całkowitych aby uzyskać wyrażenie rzeczywiste (np. X=1/6)

Czasami pomaga wymóg deklarowania zmiennych

Niewspółmierność bloków COMMON w różnych segmentach

Bloki COMMON zapisać w plikach I używać instrukcji INCLUDE

Niezgodność rozmiarów tablic w różnych segmentach

Deklarować dyrektywą PARAMETER stałe używane w wymiarowaniu tablic i zapisać deklaracje w plikach włączanych do źródła przez INCLUDE

Wyjście poza 72-gą kolumnę Używać edytorów tekstów z licznikiem kolumn

Użycie tablicy w kontekście zmiennej prostej lub odwrotnie

Niezgodność listy parametrów formalnych i aktualnych

Najczęstsze błędy podczas programowania w FORTRANie i sposoby zapobiegania im

Page 5: Odrobaczanie (debugging) programu

Sposoby odrobaczania programu

Analiza kodu źródłowego

Analiza wyników działania programu

Diagnostyka użytkownika (drukowanie wyników pośrednich)

Użycie opcji kompilatora generujących informację diagnostyczną oraz sprawdzających przekroczenie granic tablic.

Użycie odpowiednego programu narzędziowego, debuggera.

Page 6: Odrobaczanie (debugging) programu

Sześć zasad odrobaczania.

1. Doprowadź do tego, żeby błąd był powtarzalny.

2. Zawęż problem do najprostszych przebiegów z najmniejszym zestawem danych, najlepiej takich gdzie wszystkie operacje możesz przeprowadzić na piechotę.

3. Zlokalizuj najmniejszą jednostkę programu, gdzie błąd występuje.

4. Przeprowadź odpowiednio zaplanowane eksperymenty (np., jeżeli program ma obliczyć kąt między trzema atomami rozsądnie jest wybrać najpierw współrzędne które dadzą kąt prosty, potem pełny, potem dowolny.

5. Używaj doświadczenia własnego i innych.

6. Nigdy nie dopuszczaj myśli, że problemu nie rozwiążesz albo że do jego rozwiązania jest potrzebna wiedza magiczna.

Więcej w artykule Terence Parr “Learn the Essentials of Debugging”

Page 7: Odrobaczanie (debugging) programu

gdb (Linux, darmowy)

dbx (AIX i inne systemy; przychodzi z kompilatorem)

xdb (AIX; okienkowy)

totalview (AIX, UNICOS; okienkowy)

Debuggery do kodów FORTRANowskich

Page 8: Odrobaczanie (debugging) programu

Praca z debuggerem gdb

Kompilacja z opcją –g (dodaje informację diagnostyczną do kodu wynikowego)

Przykład:

f77 –g program.f –o program

Warto dodać sprawdzanie rozmiarów tablic (opcja fbounds-check), np:

f77 –g –fbounds-check program.f –o program

Tak skompilowany program można uruchomić z poziomu gdb:

gdb program

Page 9: Odrobaczanie (debugging) programu

Podstawowe komendy debuggera

break plik.f:nr_linii ustawienie punktu zatrzymania w linii nr_linii pliku źródłowego plik.f

clear plik.f:nr_linii usunięcie danego punktu zatrzymania

run wykonanie programu do końca lub pierwszego zatrzymania

cont wznowienie wykonywania programu po napotkaniu punktu zatrzymania

next [liczba_linii] przejście do następnej linii lub wykonanie następnych liczba_linii kodu danego podprogramu bez wliczania linii wywoływanych popdrogramów lub funkcji.

step [liczba_linii] przejście do następnej instrukcji lub wykonanie następnych liczba_linii instrukcji programu, włączając linie podprogramów lub funkcji wywoływanych

print zmienna drukowanie wartości danej zmiennej.

quit wyjście z debuggera.

Page 10: Odrobaczanie (debugging) programu

Trochę bardziej zaawansowane ale użyteczne komendy

cd katalog zmiana katalogu roboczego na katalog

pwd pokazuje katalog roboczy

file prog ładowanie programu prog do debuggera

set args arg wprowadzanie argumentów arg do linii polecenia programu

list [nr1,nr2] listowanie 10 linii kodu scentrowanych na danej linii (uwaga: kolejne list kontytuuje) albo od linii nr1 do linii nr2.

list +[-] listowanie następnych (+) albo poprzednich (-) linii kodu źródłowego.

list plik.f:linia listowanie kodu źródłowego z pliku plik.f począwszy od linii linia.

info kategoria informacja na temat danej kategorii; np. info break podaje informację o punktach zatrzymania

where pokazuje w którym miejscu programu się znajdujemybacktrace

where full pokazuje gdzie się znajdujemy oraz drukuje wartości wszystkich backtrace full zmiennych w danym segmencie oraz segmentach wywołujących

Page 11: Odrobaczanie (debugging) programu

set z=wartość nadawanie nowej wartości zmiennej z występującej w aktualnie analizowanym module programu

set env zm=wyr Nadawanie wartości wyr zmiennej środowiskowej zm

unset env zm anulowanie zmiennej środowiskowej zm

show env pokazuje zmienne środowiskowe

show path pokazuje ścieżki dostępu

where full pokazuje gdzie się znajdujemy oraz drukuje wartości wszystkich zmiennych w danym segmencie oraz segmentach wywołujących

watch wyrażenie zatrzymuje program jeżeli wartość wyrażenia wyrażenie zmienia się i drukuje nową wartość (wyrażenie może być nazwą zmiennej)

awatch wyrażenie zatrzymuje program jeżeli wyrażenie jest pisane lub czytane

rwatch wyrażenie zatrzymuje program, jeżeli wyrażenie jest czytane

Pełen opis gdb: http://www.delorie.com/gnu/docs/gdb/gdb.html

Page 12: Odrobaczanie (debugging) programu

Przykład użycia debuggera do śledzenia wykonywania programu titr obliczającego krzywą miareczkowania mocnego kwasu mocną zasadą

Źródła FORTRANowskie

titr.f czyt_dane.f oblicz_krzywa.f oblicz_ph.f pisz_wyniki.f DIMENSIONS

Dane

krzywa_dane

Wyniki

krzywa_wyniki

Plik Makefile

Page 13: Odrobaczanie (debugging) programu

Przykład sesji debuggera

etoh:~/FORTRAN/DEBUG> gdb titr(gdb) break titr.f:15Breakpoint 1 at 0x804887b: file titr.f, line 15.(gdb) runStarting program: /big/staff/adam/FORTRAN/DEBUG/titrBreakpoint 1, MAIN__ () at titr.f:1515 CALL OBLICZ_KRZYWA(IOUT,CKW,CZ,VKW)Current language: auto; currently fortran(gdb) print ckw$1 = 0.10000000000000001(gdb) next17 CALL PISZ_WYNIKI(IOUT)(gdb) steppisz_wyniki__ (wyjscie=0x804a050) at pisz_wyniki.f:77 OPEN(WYJSCIE,FILE='krzywa.wyniki',STATUS='UNKNOWN')(gdb) step 311 WRITE(WYJSCIE,'(I3,2x,F5.2,F8.3)') I,OBJZ(I),PH(I)(gdb) contContinuing.Program exited normally.(gdb)

Page 14: Odrobaczanie (debugging) programu

Zmieniamy w pliku DIMENSIONS maksymalną liczbę punktów ze 100 na 10

PARAMETER (MAXPUNKT=10)

Kompilujemy bez debuggingu

etoh:~/FORTRAN/DEBUG> makef77 -c titr.ff77 -c czyt_dane.ff77 -c oblicz_krzywa.ff77 -c oblicz_ph.ff77 -c pisz_wyniki.ff77 -o titr titr.o czyt_dane.o oblicz_krzywa.o oblicz_ph.o pisz_wyniki.o

etoh:~/FORTRAN/DEBUG> ./titr

Program pozornie się wykonał ale plik krzywa.wyniki zawiera tylko nagłówek

etoh:~/FORTRAN/DEBUG> more krzywa.wynikiPunkt VZ pH

Sprawdzanie przekroczenia rozmiarów tablic

Page 15: Odrobaczanie (debugging) programu

Teraz kompilujemy z –fbounds-check –g

etoh:~/FORTRAN/DEBUG> make -f Makefile-debugf77 -c -fbounds-check -g titr.ff77 -c -fbounds-check -g czyt_dane.ff77 -c -fbounds-check -g oblicz_krzywa.ff77 -c -fbounds-check -g oblicz_ph.ff77 -c -fbounds-check -g pisz_wyniki.ff77 -o titr -fbounds-check -g titr.o czyt_dane.o oblicz_krzywa.o oblicz_ph.o pisz_wyniki.o

Po uruchomieniu od razu widać, gdzie jest problem

etoh:~/FORTRAN/DEBUG> ./titrSubscript out of range on file line 12, procedure czyt.Attempt to access the 21-th element of variable vz.Abort

Page 16: Odrobaczanie (debugging) programu

SUBROUTINE CZYT_DANE(WEJSCIE,STKW,STZ,OBJKW)C Czytanie danych wejsciowych IMPLICIT NONE INCLUDE 'DIMENSIONS' INTEGER WEJSCIE,I,NPUNKT REAL*8 STKW,STZ,OBJKW REAL*8 VZ(MAXPUNKT),PH(MAXPUNKT) COMMON /KRZYWA/ VZ,PH,NPUNKT

OPEN(WEJSCIE,FILE='krzywa.dane',STATUS='OLD') READ(WEJSCIE,*) NPUNKT,STKW,STZ,OBJKW READ(WEJSCIE,*) (VZ(I),I=1,NPUNKT)

CLOSE(WEJSCIE)

RETURN END

Zatrzymanie

Page 17: Odrobaczanie (debugging) programu

Podobne informacje dostajemy uruchamiając program z poziomu debuggera

etoh:~/FORTRAN/DEBUG> gdb titr(gdb) runStarting program: /big/staff/adam/FORTRAN/DEBUG/titrSubscript out of range on file line 12, procedure czyt.Attempt to access the 21-th element of variable vz.

Program received signal SIGABRT, Aborted.0x4009283b in raise () from /lib/tls/libc.so.6

Możemy wtedy zapytać dokładnie gdzie błąd nastąpił

(gdb) where#0 0x4009283b in raise () from /lib/tls/libc.so.6#1 0x40093fa2 in abort () from /lib/tls/libc.so.6#2 0x4002846f in sig_die () from /usr/lib/libg2c.so.0#3 0x40027c94 in s_rnge () from /usr/lib/libg2c.so.0#4 0x080489ea in czyt_dane__ (wejscie=0x804a04c, stkw=0xbffffc10, stz=0xbffffc08, objkw=0xbffffc00) at czyt_dane.f:12#5 0x0804887b in MAIN__ () at titr.f:12#6 0x08048dd6 in main ()

Page 18: Odrobaczanie (debugging) programu

Można też uzyskać pełną informację, łącznie z wartościami zmiennych

(gdb) where full#0 0x4009283b in raise () from /lib/tls/libc.so.6No symbol table info available.#1 0x40093fa2 in abort () from /lib/tls/libc.so.6No symbol table info available.#2 0x4002846f in sig_die () from /usr/lib/libg2c.so.0No symbol table info available.#3 0x40027c94 in s_rnge () from /usr/lib/libg2c.so.0No symbol table info available.#4 0x080489ea in czyt_dane__ (wejscie=0x804a04c, stkw=0xbffffc10, stz=0xbffffc08, objkw=0xbffffc00) at czyt_dane.f:12 __g77_impdo_0 = 10 __g77_cilist_1 = {err = 0, unit = 1, end = 0, format = 0x0, rec = 0}

Page 19: Odrobaczanie (debugging) programu

i = 21 npunkt = 31 vz = {1, 2, 3, 4, 5, 6, 7, 8, 9, 9.1999999999999993, 9.4000000000000004, 9.5999999999999996, 9.8000000000000007, 9.9000000000000004, 9.9499999999999993, 9.9900000000000002, 10, 10.01, 10.050000000000001, 10.1} ph = {0 <repeats 20 times>}#5 0x0804887b in MAIN__ () at titr.f:12 n = 31 inp = 1 iout = 2ckw = 0.10000000000000001 cz = 0.10000000000000001 vkw = 10 vz = {1, 2, 3, 4, 5, 6, 7, 8, 9, 9.1999999999999993, 9.4000000000000004, 9.5999999999999996, 9.8000000000000007, 9.9000000000000004, 9.9499999999999993, 9.9900000000000002, 10, 10.01, 10.050000000000001, 10.1} ph = {0 <repeats 20 times>}#6 0x08048dd6 in main ()No symbol table info available.

Page 20: Odrobaczanie (debugging) programu

Dlaczego błąd nie był sygnalizowany bez opcji –fbounds-check, natomiast program nie wydrukował wyników ?

Kompilujemy tylko z opcją –g

etoh:~/FORTRAN/DEBUG> make -f Makefile-debug1f77 -c -g titr.ff77 -c -g czyt_dane.ff77 -c -g oblicz_krzywa.ff77 -c -g oblicz_ph.ff77 -c -g pisz_wyniki.ff77 -o titr -g titr.o czyt_dane.o oblicz_krzywa.o oblicz_ph.o pisz_wyniki.o

Page 21: Odrobaczanie (debugging) programu

Kompilujemy tylko z opcją –g

etoh:~/FORTRAN/DEBUG> make -f Makefile-debug1f77 -c -g titr.ff77 -c -g czyt_dane.ff77 -c -g oblicz_krzywa.ff77 -c -g oblicz_ph.ff77 -c -g pisz_wyniki.ff77 -o titr -g titr.o czyt_dane.o oblicz_krzywa.o oblicz_ph.o pisz_wyniki.o

Uruchamiamy z gdb ustawiając zatrzymanie w pisz_wyniki.f

etoh:~/FORTRAN/DEBUG> gdb titr

(gdb) break pisz_wyniki.f:1Breakpoint 1 at 0x8048aec: file pisz_wyniki.f, line 1.(gdb) runStarting program: /big/staff/adam/FORTRAN/DEBUG/titr

Breakpoint 1, pisz_wyniki__ (wyjscie=0x804864c) at pisz_wyniki.f:11 SUBROUTINE PISZ_WYNIKI(WYJSCIE)Current language: auto; currently fortran

Page 22: Odrobaczanie (debugging) programu

Drukujemy pełną informację

(gdb) where full#0 pisz_wyniki__ (wyjscie=0x804864c) at pisz_wyniki.f:1 i = 1069128089 npunkt = -2112311744 objz = (1, 2, 3, 4, 5, 6, 7, 8, 9, 9.1999999999999993, 9.4000000000000004, 9.5999999999999996, 9.8000000000000007, 9.9000000000000004, 9.9499999999999993, 9.9900000000000002, 10, 10.01, 10.050000000000001, 10.1) ph = (1.0871501757189002, 1.1760912590556813, 1.26884531229258, 1.3679767852945943, 1.4771212547196624, 1.6020599913279623, 1.7533276666586115, 1.9542425094393248, 2.2787536009528289, 2.3802112417116055, 2.5096504795465826, 2.6901960800285134, 2.9956351945975515, 3.2988530764097082, 3.6009728956867422, 4.3008127941181264, 7, 9.6987529113637798, 10.396855627379825, 10.696803942579511)

Page 23: Odrobaczanie (debugging) programu

#1 0x08048868 in MAIN__ () at titr.f:16 n = -2112311744 inp = 1 iout = 2 ckw = 0.10000000000000001 cz = 0.10000000000000001 vkw = 10 vz = (1, 2, 3, 4, 5, 6, 7, 8, 9, 9.1999999999999993, 9.4000000000000004, 9.5999999999999996, 9.8000000000000007, 9.9000000000000004, 9.9499999999999993, 9.9900000000000002, 10, 10.01, 10.050000000000001, 10.1) ph = (1.0871501757189002, 1.1760912590556813, 1.26884531229258, 1.3679767852945943, 1.4771212547196624, 1.6020599913279623, 1.7533276666586115, 1.9542425094393248, 2.2787536009528289, 2.3802112417116055, 2.5096504795465826, 2.6901960800285134, 2.9956351945975515, 3.2988530764097082, 3.6009728956867422, 4.3008127941181264, 7, 9.6987529113637798, 10.396855627379825, 10.696803942579511)#2 0x08048c76 in main ()No symbol table info available.

Page 24: Odrobaczanie (debugging) programu

Taka wartość zmiennej NPUNKT (w pisz_wyniki.f) lub N (w titr.f) jest wynikiem nadpisania jej przez pierwszy element tablicy PH wykraczający poza zadeklarowany rozmiar tablicy. Zmienna NPUNKT lub N sąsiadje z tablicą PH w obszarze wspólnym KRZYWA:

REAL*8 VZ(MAXPUNKT),PH(MAXPUNKT)COMMON /KRZYWA/ VZ,PH,NPUNKT

Błąd ten nastąpił w podprogramie oblicz_krzywa.f, gdzie jest wypełniana tablica PH.

Jeżeli podejrzewamy, że problem jest ze zmienną NPUNKT, możemy wydrukować jej wartość bez zrzucania całego zestawu zmiennych w danym punkcie programu.

(gdb) print npunkt$1 = -2112311744

Page 25: Odrobaczanie (debugging) programu

Zmianę wartości n możemy też śledzić przy pomocy komendy watch

(gdb) break titr.f:1Breakpoint 1 at 0x8048814: file titr.f, line 1.(gdb) runStarting program: /big/staff/adam/FORTRAN/DEBUG/titr

Breakpoint 1, MAIN__ () at titr.f:11 PROGRAM TITRCurrent language: auto; currently fortran

Page 26: Odrobaczanie (debugging) programu

(gdb) watch nHardware watchpoint 2: n(gdb) contContinuing.Hardware watchpoint 2: n

Old value = 0New value = 310x4002ffc7 in l_read () from /usr/lib/libg2c.so.0(gdb) contContinuing.Hardware watchpoint 2: n

Old value = 31New value = -2112311744oblicz_krzywa__ (wyjscie=0x8049010, stkw=0xbffffc10, stz=0xbffffc08, objkw=0xbffffc00) at oblicz_krzywa.f:1313 ENDDO

Page 27: Odrobaczanie (debugging) programu

Użyteczne warianty wywoływania gdb

Analiza zrzutu pamięci (coredump) programu który się “wywalił”

Zwykle zrzut jest zapisany w pliku core. Przykładowo po wywołaniu

gdb prog core

gdb zachowa się tak, jakby program prog był uruchomiony z poziomu debuggera i zatrzymał się wskutek błędu, zatem można prowadzić analizę używając where, print, itp. Kolejność argumentów nie obowiązuje jeżeli prog poprzedzimu –e a core –c.

Debugging już uruchomionego programu

gdb prog nr_procesu

Gdzie prog jest nazwą pliku wykonywalnego a nr_procesu numerem procesu odpowiadającego “chodzącemu” programowi.

Specyfikowanie katalogów, gdzie znajdują się pliki źródłowe

gdb –d katalog prog