# ***Segv et Erreurs Valgrind*** :face_with_head_bandage: <br> ***LE*** plus gros problème rencontré en C est le fameux *Segmentation fault (core dumped)* et dans ce cours, vous allez apprendre à mieux le comprendre et comment le corriger avec ***Valgrind***. <br> ![](https://hackmd.io/_uploads/HyjxH8kA3.png) ## *Segmentation fault* Un Segv en fin de compte qu'est ce que c'est. Le segmentation fault ou Erreur de sementation en français est le ***signal*** envoyé par votre ordinateur pour stopper le processus en cours (votre programme). Ce signal là, ***SIGSEGV*** sera envoyé par votre OS au moment où votre programme tentera d'accéder à une zone mémoire ***qui ne lui est pas alloué***. Il y a énormément de signaux différents que linux utilisent pour fonctionner, le segfault n'en est qu'un parmis des 10aines. > SIGSEGV en fait c'est un videur qui jete votre process en dehors de la boîte de nuit parce qu'il est trop bourré et a, fun fact, sauté dans la fontaine. Heureusement pour vous, il y a plusieurs types de Segmentation Fault :smiley: , vous aurez besoin de les connaître pour taper un mega 100% au mini-shell2, mais on vous machera pas le travail à vous de chercher. Bref, avoir cette erreur n'est pas anodine et si vous l'avez c'est pour une raison ! ***Vous gérez mal votre mémoire...*** Mais dans la prochaine rubrique on vous expliquera comment vous en sortir et comprendre parfaitement le problème de votre segv. Et cet ouil c'est : <br> ## *Valgrind* Ouais nous aussi on trouvait cet ***or pur*** incompréhensible, mais sah c’est hyper utile et voici des explications : - ***Comprendre comment fonctionne Valgrind exactement*** Le logiciel valgrind vous permet de tracer la variable qui pose problème et de voir par où elle passe, où elle fait crasher votre programme et pourquoi. Plusieurs informations importantes peuvent être retenues dans un check-up valgrind : 1. les types d'erreurs (qu'on expliquera après car chacunes méritent une section) 3. le chemin (de fonctions en fonctions) qu'a prit la variable qui pose problème à partir de la root (du main) + le numéro des lignes des fonctions par lesquelles elle passe. 4. Les bytes qui ont été malloc + les bytes qui ont été free (ou non >:( (***FREE TES MALLOCS***)) 5. le type de la variable qui fait crasher le programme <br> --- - ***Conditional jump or move depends on uninitialized value(s)*** Cette erreur est la plus commune, vous l’aurez tout le temps surtout en CPE. Pourquoi ? Parce que si on la lit mot par mot, on comprend que ce sont ***des instructions ASM*** : Conditional jump veut dire que lorsque vous allez vous balader dans vos pointeurs, votre programme lira une adresse ***allouée*** que vous n’avez pas assigné (donc vide). Exemple : Imaginons qu'on ait rempli la string *str* manuellement mais qu'on a rien mit à l'index 6 : ```c char *str = "Hello §orld"; printf("La string est %s\n", str); $--> Hello orld ``` <br> Lorsque vous allez vous balader dans votre string et que vous allez tomber sur ***le caractère inconnu §***, une erreur valgrind apparaîtra. Mais plus que ça, si par exemple vous basez un if malencontreusement sur tous les charactères d'une string, comme par exemple une boucle, le charactère inconnu aura un comportement aléatoire et causera ***des problèmes*** (*"fallait pas qu'il rentre dans ce if fait chier" est une phrase qu'on peut souvent entendre venant d'un Conditional Jumper inconscient*) + vous aurez un valgrind de 80000 lignes parce qu'il marque l'erreur à chaque itération :skull:. Autre exemple pour que ce soit plus clair : ```c int main(void) { int i; if (i == 0) { my_printf("Hello\n"); } return (0); } ``` <br> Aucune ***valeur*** n'a été attribué à i (il prendra une valeur random d'ailleurs, genre 1902487). Alors, lorsque vous allez baser une itération dessus, vous aurez un Conditional Jump comme celui-ci : ```c ==28042== Conditional jump or move depends on uninitialised value(s) ==28042== at 0x4004E3: main (test.c:5) ``` <br> En gros, ça nous dit qu'on a un *Conditional Jump* dans le if. Alors heureusement dans 95% des cas, valgrind s’en sortira ***sans te faire segv*** car il passera plusieurs fois par cet endroit de la mémoire si vous éxecutez plusieurs fois le programe sans avoir ***make re*** (lisez le cours sur le Makefile pour bien comprendre) et saura trouver son chemin. Par contre faites gaffe à ça parce que de 1, c’est -5 points en duo stumper parce que c'est une erreur en plus, et ***surtout*** de 2 ça fait crasher ***le grille-pain qu’est la mouli*** <br> --- - ***Invalid Write of Size [insert size]*** Alors là c'est autre chose que si vous n'assignez pas de valeur à une place que vous avez alloué. C'est en fait exactement l'inverse, c'est quand vous assignez une valeur à un endroit dont vous n'avez pas accès (d'où l'intéret de savoir faire de bons ***malloc***). ***C'est bien plus grave d'ailleurs*** parce que là c'est segv sans broncher. Prenons un exemple : Imaginons que nous voulous remplir une string par des 'z', ça sert à rien mais c'est pour que vous compreniez : ```c char *fillString(void) { char *str = malloc(sizeof(char) * 10); int i = 0; while (i < 15) { str[i] = 'z'; i++; } return str; } ``` La taille allouée n'est pas assez grande et vous allez vous manger un bon gros segv des familles et voici l'erreur que vous allez avoir : ```c ==18332== Invalid write of size 1 ==18332== at 0x400553: main (test.c:7) ==18332== Address 0x521004a is 0 bytes after a block of size 10 alloc\'d ==18332== at 0x4C2EB6B: malloc (vg_replace_malloc.c:299) ==18332== by 0x400538: main (test.c:3) ``` Pour comprendre le 1 après *Invalid write of size*, souvenez vous de la taille des 3 variables que vous aurez majoritairement: - char = 1 byte - int = 4 bytes - pointeur = 8 bytes On comprend alors que le problème vient du fait qu'on a voulu écrire un char dans une zone invalide ! Ensuite, le ***Address 0x521004a is 0 bytes after a block of size 10 alloc\'d***, comment comprendre ce charabiat : Et bien c'est tout simple, cela veut dire que l'erreur se trouve après un bloc de ***10 allocations***, et donc qu'il y a 0 byte à l'intérieur. En d'autres termes, ***vous n'avez pas alloué assez*** ! <br> --- - ***Invalid Read of Size [insert size]*** Cette erreur est, comme vous pouvez le comprendre, reliée à celle d'au-dessus. ***Invalid Read*** veut dire qu'on a voulu lire (souvent à l'intérieur d'un pointeur (ex.: *str)) une valeur pour pouvoir en faire quelque chose mais que nous n'avons pas les droits d'aller voir. > En fait la RAM c'est comme un énorme bac à sable, on a pas le droit d'y aller pour jouer à par si on demande un endroit précis au C. Si on dépasse la limite le C nous expulse. Bref regardez cet exemple : ```c int main(void) { int *ptr = NULL; int i = *ptr; return 0; } ``` Ici on essaie de lire ce qu'il y a dans ptr pour l'assigner à i, sauf que NULL est inaccessible, renvoie à l'adresse 0x0 et contient la valeur '\0' qui est le rien absolu et donc -> ***Invalid read***. Alors si vous avez bien retenu ce cours, quel sera la valeur du read dans cet exemple ? Et ouais : ```c ==26212== Invalid read of size 4 ==26212== at 0x400497: main (test.c:8) ==26212== Address 0x0 is not stack\'d, malloc\'d or (recently) free\'d ``` Autre exemple: ```c int i = *(0x091873); ``` Pareil, cette adresse est totalement ***aléatoire*** et pas assignée -> ***Invalid read***. <br> ___ - ***Syscall param points to unadressable bytes*** Alors celle-là vous allez l'avoir moins de fois car un syscall c'est littéralement un appel système donc une fonction existante qui existe dans linux (il y en a plus de 380) Vous allez surtout l'avoir dans un readfile ou lorsque vous allez avoir le droit à de nouvelles fonctionnalités comme le mini-shell1, 2 ou 42sh. Voici un exemple : ```c int main(void) { int fd = open("test", O_RDONLY); char *buff = malloc(sizeof(char) * 3); free(buff); read(fd, buff, 2); } ``` <br> La fonction open (qui est un ***syscall***) va essayer de lire le fichier *"test"* pour le rediriger dans buff, donc lire le pointeur buff et placer les char dans ***les emplacements alloués***. Or nous avons free buff entre temps, donc toute la place n'est plus accessible : ```c ==32002== Syscall param read(buf) points to unaddressable byte(s) ==32002== at 0x4F3B410: __read_nocancel (in /usr/lib64/libc-2.25.so) ==32002== by 0x400605: main (test.c:11) ==32002== Address 0x5210040 is 0 bytes inside a block of size 3 free\'d ==32002== at 0x4C2FD18: free (vg_replace_malloc.c:530) ==32002== by 0x4005EF: main (test.c:10) ==32002== Block was alloc\'d at ==32002== at 0x4C2EB6B: malloc (vg_replace_malloc.c:299) ==32002== by 0x4005DF: main (test.c:8) ``` <br> Alors j'avoue que celle là elle soule à lire mais : - *Block was alloc'd at ... :* littéralement *"vous avez alloué dans cet adresse"* - *... is 0 bytes inside a block of size 3 free'd* bah ouais normal tu l'as free, il n'y a plus de place - *Syscall param read(buf) points to unaddressable byte(s)* bah là voilà mskn read a pas marché --- Il y a encore 3 erreurs différentes après mais vraiment tu les auras jamais pas besoin d'en faire des sections. ## ***Voilà j'espère que ça t'aura mieux fait comprendre le merveilleux outil qu'est valgrind !!!***