\documentclass{lxarticle} \usepackage{german} \usepackage[utf8]{inputenc} \usepackage{lxheaders} \usepackage{lxextras} \begin{document} \section*{Debugging} Beim Debugging geht es darum, Fehler in einem System zu finden. Es gibt zwei grundlegend verschiedene Fehlerarten: \begin{enumerate} \item die Anwendung beendet sich unerwartet \begin{description} \item[auf Grund von Fehlern bei der Speicherveraltung] z.B. durch freigeben eines Speicherbereichs, welcher zuvor nicht alloziert wurde: \begin{lstlisting} mem = malloc(5); free(((char*)mem)+6000); \end{lstlisting} Die Programmausf\"uhrung wird abgebrochen, die glibc liefert alle Informationen, welche n\"otig sind, um den Fehler zu decodieren: \begin{lstlisting} bash-4.0$ ./test *** glibc detected *** ./test: free(): invalid pointer: 0x083c9778 *** ======= Backtrace: ========= /lib/libc.so.6[0x57c231] ./test[0x804841e] /lib/libc.so.6(__libc_start_main+0xe6)[0x522a66] ./test[0x8048361] ======= Memory map: ======== 0048e000-0048f000 r-xp 0048e000 00:00 0 [vdso] 004e8000-00508000 r-xp 00000000 fd:00 66065 /lib/ld-2.10.1.so 00508000-00509000 r--p 0001f000 fd:00 66065 /lib/ld-2.10.1.so ... \end{lstlisting} Mit folgendem Befehl kann festgestellt werden, von wo aus der Fehler getriggered wurde: \cmd{addr2line -e test 0x804841e} /home/linutronix/test.c:16 \item[Speicherzugriffsfehler] z.B. durch Zugriff auf nicht allozierten Speicher. Die Programmausf\"uhrung wird abgebrochen: \begin{lstlisting} bash-4.0$ ./test Speicherzugriffsfehler \end{lstlisting} Solche Fehler lassen sich am Besten mit einer gdb Backtrace Ausgabe (siehe unten) finden. \end{description} \item die Anwendung verh\"alt sich anders als erwartet. In einem solchen Fall sollte man sich zu erst Gedanken dar\"uber machen, durch was das Fehlverhalten getriggered werden k\"onnte. Sofort mit einem Debugger durch den Code zu steppen ist kein guter Ansatz, da so evt. ein Fehler gefunden und behoben wird der aber nicht zwangsl\"aufig die Ursache f\"ur das Problem war. Dies kann dazu f\"uhren, dass die Fehlersuche eines sp\"ater entdeckten Bugs um so komplexer und schwieriger wird. Hat man sich Gedanken \"uber die Ursachen gemacht, kann man gezielt mit den nachfolgend beschriebenen Techniken dem Fehler auf den Grund gehen. \end{enumerate} \subsection*{Einfach aber wirkungsvoll: \cmd{printf}, \cmd{fprintf}} Da Ausgaben auf die \cmd{stdout} gecached werden, ist es zuverl\"assiger f\"ur Debugging \cmd{stderr} zu verwenden und nach jeder Ausgabe noch den cash flush zu triggern. Ein Debug konstrukt k\"onnte z.B. folgenderma\ss en aussehen: \begin{lstlisting} fprintf(stderr, "debug test\n"); fflush(stderr); \end{lstlisting} F\"ur gew\"ohnlich wird \cmd{(f)printf} beim Debuggen f\"ur drei Zwecke verwendet: \begin{enumerate} \item Tracing: \begin{lstlisting} fprintf(stderr, "%s, called\n", __FUNCTION__); fflush(stderr); \end{lstlisting} \item Ausgabe von Variablenwerten: \begin{lstlisting} fprintf(stderr, "Wert: %d, Wert (hex): %x\n", wert, wert); fflush(stderr); \end{lstlisting} \item Ausgabe von Adressen: \begin{lstlisting} fprintf(stderr, "Adresse: %p, Wert: %x\n", wert, wert); fflush(stderr); \end{lstlisting} \end{enumerate} Nat\"urlich k\"onnen die Methoden kombiniert werden. Soll das Debugging zur Compilezeit zu- und abschaltbar sein, kann z.B. folgendes Pr\"aprozessor konstrukt hilfreich sein: \begin{lstlisting} #ifdef DEBUG #define debug(...) fprintf(stderr, "%s:%d - ", __FILE__, __LINE__); \ fprintf(stderr, __VA_ARGS__); fflush(stderr) #else #define debug(...) ((void)0) #endif int main(int argc, char **argv) { int val = 3; debug("addr: %p - val: %d\n", &val, val); return 0; } \end{lstlisting} Resultat: \begin{lstlisting} bash-4.0$ gcc -o test test.c bash-4.0$ ./test bash-4.0$ bash-4.0$ gcc -o test -DDEBUG test.c bash-4.0$ ./test test.c:22 - test: 0xbfef8694 - val: 3 bash-4.0$ \end{lstlisting} \subsubsection*{deutlich flexibler: Logging Frameworks} Wird f\"ur Debugging oder Logging mehr Flexibilit\"at ben\"otigt, sollte ein Logging Framework verwendet werden. Prominente Vertretter f\"ur verschiedene Programmiersprachen sind zum Beispiel: \begin{itemize} \item Log for C++ (http://log4cpp.sourceforge.net) \item log4c (http://log4c.sourceforge.net) \item log4j (http://logging.apache.org/log4j) - f\"ur JAVA \end{itemize} Diese Frameworks bieten die M\"oglichkeit das Loggingverhalten zum Startpunkt der Anwendung, oder teilweise sogar, w\"ahrend der Laufzeit zu ver\"andern. Es wird beim Loggen mindestens zwischen den Kategorien Debug, Error und Info unteschieden, welche jeweils an verschiedene Ziele geleitet werden k\"onnen: Datei, syslog, \dots \subsubsection*{Backtraces ausgeben} Die glibc bietet auch die M\"oglichkeit an einer beliebigen Stelle in der Applikation die Ausgabe eines Function Call Traces (Backtrace) aus zu geben. Dies ist hilfreich, wenn man herausfinden will, \"uber welche Funktionsaufrufe die aktuelle Codezeile aufgerufen wurde. Beispiel: \begin{lstlisting} #include #include #define DEFAULT_BT_DEPTH 16 void btrace(int depth) { const void *trace[depth]; char **messages = (char **) NULL; unsigned int i = 0; int trace_size = backtrace(trace, depth); messages = backtrace_symbols(trace, trace_size); fprintf(stderr, "***BACKTRACE***\n"); for(; i < trace_size; i++) fprintf(stderr, "%s\n", messages[i]); fprintf(stderr, "***************\n"); fflush(stderr); } void pong() { #ifdef DEBUG btrace(DEFAULT_BT_DEPTH); #endif } void ping() { pong(); } int main(int argc, char **argv) { ping(); return 0; } \end{lstlisting} Anwendung: \begin{lstlisting} bash-4.0$ gcc -g -o test -DDEBUG test.c bash-4.0$ ./test ***BACKTRACE*** ./test [0x804859b] ./test [0x8048663] ./test [0x8048670] ./test [0x804867d] /lib/libc.so.6(__libc_start_main+0xe6) [0x522a66] ./test [0x8048441] *************** bash-4.0$ addr2line -e test 0x804867d /home/linutronix/test.c:27 bash-4.0$ addr2line -e test 0x8048670 /home/linutronix/test.c:24 \end{lstlisting} \subsection*{Tracing mit strace} Mit \cmd{strace} k\"onnen Systemaufrufe getraced werden. Es kann ermittelt werden welche Systemaufrufe von der Applikation zu welchem Zeitpunkt aufgerufen wurden und wieviel Zeit der Kernel ben\"otigt um den Aufruf ab zu arbeiten. Ein \cmd{strace} ist folgenderma\ss en zu interpretieren: \begin{lstlisting} bash-4.0$ strace -ftttT ./test 1245687387.873813 execve("./test", ["./test"], [/* 18 vars */]) = 0 <0.000233> ^ ^ ^ ^ ^ | | | | | | | +- Argumente | +- Dauer +- Zeitstempel +- syscall +- Rueckgabewert \end{lstlisting} Die Option \begin{itemize} \item \cmd{-f} sorgt daf\"ur, dass auch Kindprozesse mit getraced werden, \item \cmd{-ttt} blendet den pr\"azisen Zeitstempel ein, \item \cmd{-T} gibt die Dauer des Systemaufrufs mit an. \end{itemize} \subsection*{Debugging mit gdb} gdb ist ein textbasierter Debugger. Eine Applikation kann z.B mit dem Befehl \cmd{gdb ./test} in gdb geladen werden und anschliessend mit dem Befehl \cmd{run} ausgef\"uhrt werden. Die 'gdb Reference Card' listet viele n\"utzliche Befehle, z.B. zum Setzen von Breakpoints und zum Steppen durch die Applikation. Es gibt auch grafische Frontends f\"ur gdb, wie z.B. \cmd{ddd} (http://www.gnu.org/software/ddd). \subsubsection*{Core Dumps analysieren} \cmd{gdb} eignet sich au\ss erdem hervorragend zur Analyse von so genannten 'core dumps'. Ein 'core dump' entsteht, wenn eine Applikation unerwartet beendet wird. Gegebenenfalls mu\ss mit \cmd{ulimit -c unlimited} vor dem Applikationsstart das Limit f\"ur maximal zul\"assige core dumps erh\"oht werden. Beispiel: \begin{lstlisting} bash-4.0$ ulimit -c unlimited bash-4.0$ ./test Speicherzugriffsfehler (Speicherabzug geschrieben) bash-4.0$ gdb ./test core.12371 GNU gdb 6.8-debian Copyright (C) 2008 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later 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 "x86_64-linux-gnu"... Reading symbols from /lib/libc.so.6...done. Loaded symbols for /lib/libc.so.6 Reading symbols from /lib/ld-linux.so.2...done. Loaded symbols for /lib/ld-linux.so.2 Core was generated by `./test'. Program terminated with signal 11, Segmentation fault. [New process 12371] #0 0x08048661 in pong () at test.c:22 22 *null_ptr = 3; (gdb) (gdb) bt #0 0x08048661 in pong () at test.c:22 #1 0x08048671 in ping () at test.c:26 #2 0x0804867b in main (argc=1, argv=0xbf9921f4) at test.c:29 (gdb) \end{lstlisting} Der Befehl \cmd{bt} steht f\"ur Backtrace. Die zuletzt aufgerufene Funktion steht an Position \#0. Zeigt \#0 nicht auf die eigene Applikationen sondern z.B. in die glibc ist es eher warscheinlich, dass der Fehler durch eine nicht spezifiezierte Verwendung einer glibc Funktion getriggered wurde. Man sollte in diesem Fall zuerst untersuchen, ob die erste Funktion in der eigenen Software, einen korrekten glibc call absetzt, bevor die glibc gedebugged wird. %\subsubsection*{Remote-Debuggging mit gdbserver} %Text \end{document}