You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
226 lines
7.0 KiB
226 lines
7.0 KiB
\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 <stdlib.h> |
|
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 <stdio.h> |
|
#include <string.h> |
|
#include <unistd.h> |
|
#define __USE_GNU |
|
#include <dlfcn.h> |
|
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. |
|
|
|
% ============================================================== |
|
|
|
|
|
% ==============================================================
|
|
|