\chapter{Debug}\index{Debug} \label{chap:debug} Quand plus rien ne marche, reste-il encore un espoir ? Il existe bien entendu des outils \textsl{mainstream} tels que le classique \texttt{gdb}\index{gdb}, mais il en existe une foultitude d'autres, injustement méconnus. Nous allons en voir quelques-uns en essayant de nous baser sur des cas réels. % ============================================================== \section{Gdb}\index{gdb} GDB is a source-level debugger, capable of breaking programs at any specific line, displaying variable values, and determining where errors occurred. Currently, gdb supports C, C++, D, Objective-C, Fortran, Java, OpenCL C, Pascal, assembly, Modula-2, Go, and Ada. \textsc{A must-have for any serious programmer}. \subsection{Clickaconvi} \textbf{DDD} is a graphical front-end for GDB and other command-line debuggers. Using DDD, you can see what is going on “inside” another program while it executes—or what another program was doing at the moment it crashed. \textbf{xxgdb} is a simple but powerful graphical interface to the GNU debugger gdb. A more powerful (but slower and much bigger) interface is available in the ddd package. % -------------------------------------------------- % novembre 2020, panique dans le kernel de la Fedora % \subsection{Un cas réel} J'ai un programme, écrit en C, qui est assez agressif sur le calcul flottant et les accès disque, et qui se fait tuer en cours de route, avec un message inquiétant du kernel \texttt{[95335.731943] fonderie: Corrupted page table at address 7fffe82d6000} qui me laisse perplexe. \begin{verbatim} [tth@laserbox Sauvageonnes]$ gdb ~/Devel/FloatImg/Fonderie/fonderie GNU gdb (GDB) Fedora 10.1-2.fc33 This GDB was configured as "x86_64-redhat-linux-gnu". Reading symbols from /home/tth/Devel/FloatImg/Fonderie/fonderie... (gdb) run -I 'G/?????.fimg' -O 'Png' -w 0 -x 0 -T 80 Starting program: /home/tth/Devel/FloatImg/Fonderie/fonderie -I 'G/?????.fimg' -O 'Png' -w 0 -x 0 -T 80 Missing separate debuginfos, use: dnf debuginfo-install glibc-2.32-2.fc33.x86_64 *** /home/tth/Devel/FloatImg/Fonderie/fonderie : compiled by tTh, Nov 25 2020 10:19:10 pid 1949 \end{verbatim} Là, on attend vingt minutes que le logiciel tripote plein d'images en virgule flottante. C'est long. \begin{verbatim} 54 / 1784 Program terminated with signal SIGKILL, Killed. The program no longer exists. Missing separate debuginfos, use: dnf debuginfo-install pnglite-0.1.17-1.fc33.21.x86_64 zlib-1.2.11-22.fc33.x86_64 (gdb) \end{verbatim} On est donc bien avancé, \texttt{SIGKILL}, dans ce cas-là, c'est le noyau qui flingue\index{bfg9000} le processus. Mais pour quelle raison, et à quel endroit dans le programme ? Avec un peu de chance, gdb a conservé une trace des dernières microscondes de la fonderie. \begin{verbatim} (gdb) backtrace No stack. (gdb) \end{verbatim} Bah non, il nous avait bien prévenu : \textit{The program no longer exists}, point suivant. % ============================================================== \section{Valgrind} Reprenons le premier exemple précédemment traité avec Gdb, et tentons de résoudre l'énigme avec Valgrind. \begin{verbatim} (gdb) quit [tth@laserbox]$ man valgrind [tth@laserbox]$ valgrind fonderie -I 'G/?????.fimg' -O 'Png' \ -w 0 -x 0 -T 80 ==2388== Memcheck, a memory error detector ==2388== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al. ==2388== Using Valgrind-3.16.1 and LibVEX; rerun with -h for copyright info \end{verbatim} Là, on attend vingt heures que le logiciel tripote plein d'images en virgule flottante. C'est très long. Je me demande même si ce n'est pas un roblock total. % ============================================================== \section{Strace}\index{strace} Strace permet de tracer les appels systèmes d'un processus. Comme vous le savez tous, un appel système (aka syscall\index{syscall}) est \textbf{le} moyen de communication qu'utilise un process utilisateur pôur demander un service au noyau. \lstinputlisting[language=C]{code/hello.c} Un exemple canonique, n'est-il pas ? Ce bout de code affichant quelque chose à l'écran, il doit bien y avoir un appel au noyau qui traine par là pour écrire vers la sortie standard. Nous allons donc le chercher% \footnote{En trichant un peu, je l'avoue, je connais son nom.} \begin{verbatim} $ gcc hello.c $ strace -o foo ./a.out $ grep write foo write(1, "Hello world.\n", 13) = 13 $ \end{verbatim} On peut réaliser la même opération en utilisant le filtrage interne de strace, ce qui évite le passage par un fichier intermédiaire, mais le résultat est moins lisible, puisque a.out écrit sur stdout, et strace sur stderr~: \begin{verbatim} $ strace -e write ./a.out write(1, "Hello world.\n", 13Hello world. ) = 13 +++ exited with 0 +++ $ \end{verbatim} % ============================================================== \section{LD\_PRELOAD}\index{LD\_PRELOAD} D'accord, aves \texttt{strace} nous pouvons voir passer les appels systèmes, mais dans la vie d'un process, certaines opérations ne sortent pas de la \texttt{libc}\index{libc}. L'une d'entre elles, \texttt{getenv(3)}, va nous servir d'exemple. \begin{verbatim} NAME getenv - get an environment variable SYNOPSIS #include char *getenv(const char *name); DESCRIPTION The getenv() function searches the environment list to find the envi‐ ronment variable name, and returns a pointer to the corresponding value string. \end{verbatim} Cette fonction est utilisée par un logiciel pour avoir accès à son contexte extérieur, son environnement, dans lequel on peut trouver (entre autres) la variable \texttt{\$LOGNAME} qui, oh surprise, contient votre nom de login. Et justement, vous avez un programme sous la main que vous suspecter d'avoir un problème relationnel avec cette variable. Il nius faut donc remplacer le getenv de la libc par notre propre version qui va écouter et exfiltrer l'utilisation de cette fonction. \begin{lstlisting}[language=C] /* spy_getenv.so: spy_getenv.c Makefile gcc -Wall -shared -fPIC $< -ldl -o $@ */ #include #include #include #define __USE_GNU #include typedef char * (*original_getenv)(const char *envname); char *getenv(char *envname) { static char *arrow = "--getenv--> "; static char *wtf = " --> WTF ?"; char *content; original_getenv orig_getenv; orig_getenv = (original_getenv)dlsym(RTLD_NEXT, "getenv"); write(STDERR_FILENO, arrow, strlen(arrow)); write(STDERR_FILENO, envname, strlen(envname)); content = orig_getenv(envname); if (NULL != content) { write(STDERR_FILENO, "=", 1); write(STDERR_FILENO, content, strlen(content)); } else { write(STDERR_FILENO, wtf, strlen(wtf)); } write(STDERR_FILENO, "\n", 1); return content; } \end{lstlisting} Simple et efficace. % ============================================================== % ==============================================================