commit de7422c4837904cfa9158f38d13e1cc21e766d3b Author: Théophile Bastian Date: Mon Nov 22 15:04:41 2021 +0100 poly: first version diff --git a/poly/.gitignore b/poly/.gitignore new file mode 100644 index 0000000..93f8674 --- /dev/null +++ b/poly/.gitignore @@ -0,0 +1,9 @@ +*.aux +*.dvi +*.fls +*.fdb* +*.log +*.ps +*.xdv +poly.tex +*.pdf diff --git a/poly/Makefile b/poly/Makefile new file mode 100644 index 0000000..5d7465a --- /dev/null +++ b/poly/Makefile @@ -0,0 +1,16 @@ +TARGET=poly +PANDOC_OPTS=\ + --include-in-header=header.tex \ + --highlight-style tango \ + --syntax-definition=syntax/gdb-bt.xml \ + --syntax-definition=syntax/gdb.xml +PDFDEPS= +MDDEPS=meta.yml + +all: $(TARGET).pdf + +%.pdf: %.tex $(PDFDEPS) $(MDDEPS) + latexmk -xelatex $< + +%.tex: %.md $(MDDEPS) + pandoc -s $(PANDOC_OPTS) -o "$@" "$<" meta.yml diff --git a/poly/header.tex b/poly/header.tex new file mode 100644 index 0000000..8be2e07 --- /dev/null +++ b/poly/header.tex @@ -0,0 +1,16 @@ +\usepackage[most]{tcolorbox} +\renewenvironment{Shaded}% + {\begin{tcolorbox}}% + {\end{tcolorbox}} + +% Thanks https://github.com/jgm/pandoc/issues/2453#issuecomment-219233316 +\newcommand{\hideFromPandoc}[1]{#1} +\hideFromPandoc{ + \let\Begin\begin + \let\End\end +} + +\definecolor{codeboxbg}{RGB}{255,255,230} +\tcbset{colback=codeboxbg} +\tcbset{boxrule=1pt} +\tcbset{size=title} diff --git a/poly/meta.yml b/poly/meta.yml new file mode 100644 index 0000000..6733750 --- /dev/null +++ b/poly/meta.yml @@ -0,0 +1,13 @@ +--- +title: "INF301 -- Debug de segfaults" +author: "Théophile Bastian \\texttt{}" +date: "\\vspace{-1em}" +lang: fr +geometry: + - top=2cm + - bottom=2cm + - left=2cm + - right=2cm +pagestyle: empty +papersize: a4 +--- diff --git a/poly/poly.md b/poly/poly.md new file mode 100644 index 0000000..bc76b42 --- /dev/null +++ b/poly/poly.md @@ -0,0 +1,182 @@ +\thispagestyle{empty} + +Une *segfault* -- *segmentation fault*, ou *erreur de segmentation* -- apparaît +quand un programme essaye d'accéder à une zone mémoire qui ne lui est pas +allouée. C'est une erreur fréquente dès lors qu'on manipule des pointeurs, et +elle est plus difficile à corriger qu'un bug classique -- en tout cas quand on +n'a pas les bons réflexes ! + +Elle arrive par exemple dans les cas suivants : + +\Begin{minipage}{0.3\textwidth} +```c +int *test = NULL; +*test = 42; +``` +\End{minipage} \hfill \Begin{minipage}{0.3\textwidth} +```c +int *test = NULL; +printf("%d\n", *test); +``` +\End{minipage} \hfill \Begin{minipage}{0.3\textwidth} +```c +int *test = 215342565; +*test = 42; +``` +\End{minipage} + +Dans le premier cas, on essaye d'assigner une valeur à l'adresse mémoire `NULL` +-- c'est-à-dire, l'adresse 0, ce qui est une erreur. De même, dans le +second cas, on essaye de lire cette valeur -- c'est tout autant interdit. Dans +le troisième cas, on fait de même, mais à une adresse au hasard -- les chances +que cette adresse corresponde à de la mémoire qui nous est réservée sont +infimes ! + +Prenons l'exemple suivant : + +\Begin{minipage}{0.39\textwidth} +```c +struct liste { + struct liste* suivant; + int valeur; +}; +typedef struct liste liste_t; + +// Alloue une nouvelle cellule de +// valeur `val` +liste_t* alloc_cell(int val) { + liste_t* cell = + malloc(sizeof(liste_t)); + cell->valeur = val; + cell->suivant = NULL; + return cell; +} +``` +\End{minipage}\hfill\Begin{minipage}{0.58\textwidth} +```c +// Affiche les `taille` 1ères valeurs de `liste` +void afficher_liste(liste_t* liste, int taille) { + liste_t* cur = liste; + for(int pos=0; pos < taille; ++pos) { + printf("%d\n", cur->valeur); + cur = cur->suivant; + } +} +int main(int argc, char** argv) { + int n = atoi(argv[1]); // premier argument + liste_t* tete = alloc_cell(1); + tete->suivant = alloc_cell(2); + afficher_liste(tete, n); + return 0; +} +``` +\End{minipage} + +Ce programme n'est pas correct si on lui passe `n > 2` : on tente d'afficher +plus de valeurs de la liste qu'il n'y en a. Et en effet : + +```GDB +$ ./test 3 +1 +2 +Segmentation fault (core dumped) +``` + +# Premier réflexe : `valgrind` + +[Valgrind](https://fr.wikipedia.org/wiki/Valgrind) est un logiciel analysant +les accès mémoire d'un programme. Il peut servir de débuggeur rudimentaire, +mais analyse également les *fuites mémoire*. Il s'utilise très facilement : il +suffit de rajouter `valgrind` en tête de sa ligne de commande. + +```GDB +$ valgrind ./test 3 +[...] +==862386== Process terminating with default action of signal 11 (SIGSEGV): dumping core +==862386== Access not within mapped region at address 0x8 +==862386== at 0x1091CE: afficher_liste (exemple.c:20) +==862386== by 0x109253: main (exemple.c:29) +[...] +``` + +Valgrind commence par nous rappeler qu'il s'agit d'une *segfault* : `SIGSEGV` +est encore un de ses autres noms\footnote{C'est en réalité plutôt le nom de la +réaction de Linux face à une segfault -- \textit{cf.} les \textit{signaux +UNIX} si vous êtes curieux·se.}. + +Dans les lignes *avant* ce message (éludées ici), valgrind liste les erreurs +non-critiques rencontrées. Bien souvent, ces erreurs correspondent se +traduisent par des problèmes incompréhensibles plus tard, et mieux vaut les +inspecter de plus près ! + +Pour une *segfault*, la partie la plus importante est celle recopiée ici. Elle +nous indique, de bas en haut, que : + +* dans la fonction `main`, fichier `exemple.c`, ligne 29, … +* on appelle la fonction `afficher_liste`, fichier `exemple.c`, et ligne 20… +* on crash (segfault) + +La première ligne nous indique la position du bug, les lignes suivantes nous +indiquent d'où on vient dans le programme. + +C'est certes peu d'informations (la ligne de l'erreur seulement -- pas la +variable concernée, etc), mais c'est déjà mieux que juste `Segmentation fault +(core dumped)`, et souvent suffisant. + +# Si c'est plus compliqué : gdb + +Le debugger `gdb` est plus compliqué à utiliser, mais plus puissant. Lorsqu'on +l'appelle + +```GDB +$ gdb ./test +[...] +(gdb) +``` + +on entre en *mode interactif* : gdb attend des commandes. Notez qu'on ne +**passe pas nos arguments à gdb** : `gdb ./test` et non `gdb ./test 3`. + +Il faut tout d'abord dire à gdb de lancer le programme (c'est ici qu'on passe le 3 !) + +```GDB +(gdb) run 3 +[...] +Program received signal SIGSEGV, Segmentation fault. +0x00005555555551ce in afficher_liste (tete=0x5555555592a0, taille=3) at exemple.c:20 +20 printf("%d\n", cur->valeur); +(gdb) +``` + +`gdb` nous dit qu'on a une *segfault* dans `afficher_liste`, ligne 20 de +`exemple.c` -- rien de nouveau. Il nous donne les valeurs des arguments de +cette fonction (`taille=3`, et une adresse pour `tete`). Mais surtout, *on +reste en mode interactif* ! On peut lui demander la même chose qu'à `valgrind`, +savoir d'où on vient : + +```GDB +(gdb) backtrace +#0 0x00005555555551ce in afficher_liste (tete=0x5555555592a0, taille=3) at exemple.c:20 +#1 0x0000555555555254 in main (argc=2, argv=0x7fffffffe4b8) at exemple.c:29 +``` + +On peut lui demander d'afficher des valeurs *au moment du crash* : +```GDB +(gdb) print pos +2 +(gdb) print cur +(liste_t *) 0x0 +``` + +...ou la même chose, mais dans `main`, au moment de l'appel de fonction : +```GDB +(gdb) frame 1 # le même 1 qu'en début de ligne de `backtrace` +(gdb) print n +3 +(gdb) frame 0 # et de retour dans `afficher_liste` +``` + +Le debugger `gdb` est *beaucoup* plus puissant que ça, avec des dizaines de +fonctionnalités : renseignez-vous sur les *breakpoints* par exemple. Il est +également très pratique pour débugger des erreurs classiques, et pas seulement +des *segfaults*. diff --git a/poly/src/a.out b/poly/src/a.out new file mode 100755 index 0000000..90ef2a7 Binary files /dev/null and b/poly/src/a.out differ diff --git a/poly/src/exemple.c b/poly/src/exemple.c new file mode 100644 index 0000000..2397a5e --- /dev/null +++ b/poly/src/exemple.c @@ -0,0 +1,31 @@ +#include +#include + +struct liste { + struct liste* suivant; + int valeur; +}; +typedef struct liste liste_t; + +liste_t* alloc_cell(int val) { + liste_t* cell = malloc(sizeof(liste_t)); + cell->valeur = val; + cell->suivant = NULL; + return cell; +} + +void afficher_liste(liste_t* tete, int taille) { + liste_t* cur = tete; + for(int pos=0; pos < taille; ++pos) { + printf("%d\n", cur->valeur); + cur = cur->suivant; + } +} + +int main(int argc, char** argv) { + int n = atoi(argv[1]); + liste_t* tete = alloc_cell(1); + tete->suivant = alloc_cell(2); + afficher_liste(tete, n); + return 0; +} diff --git a/poly/src/test b/poly/src/test new file mode 100755 index 0000000..44d730f Binary files /dev/null and b/poly/src/test differ diff --git a/poly/src/vgcore.859687 b/poly/src/vgcore.859687 new file mode 100644 index 0000000..9fa80ff Binary files /dev/null and b/poly/src/vgcore.859687 differ diff --git a/poly/src/vgcore.862386 b/poly/src/vgcore.862386 new file mode 100644 index 0000000..b28b1cf Binary files /dev/null and b/poly/src/vgcore.862386 differ diff --git a/poly/syntax/gdb-bt.xml b/poly/syntax/gdb-bt.xml new file mode 100644 index 0000000..5caeaaf --- /dev/null +++ b/poly/syntax/gdb-bt.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/poly/syntax/gdb.xml b/poly/syntax/gdb.xml new file mode 100644 index 0000000..27d9cc0 --- /dev/null +++ b/poly/syntax/gdb.xml @@ -0,0 +1,97 @@ + + + + + + + + break + rbreak + run + continue + backtrace + detach + quit + up + down + frame + where + info + ptype + print + call + catch + condition + command + set + watch + awatch + thread + list + dprintf + target + end + source + next + nexti + step + stepi + finish + start + reverse-continue + reverse-next + reverse-step + reverse-nexti + reverse-stepi + reverse-finish + checkpoint + restart + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +