
Avec cette interface en C, toute application écrite en C peut écrire sur le log du système. Les autres langages ont des APIs similaires. Même les scripts shell peuvent écrire dans le log avec la commande :
|
|
# strace /usr/sbin/uucico execve("/usr/sbin/uucico", ["/usr/sbin/uucico", "-S", "uucpssh", "-X", "11"], [/* 36 vars */]) = 0 uname({sys="Linux", node="brain", ...}) = 0 brk(0) = 0x8085e34 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x40014000 open("/etc/ld.so.preload", O_RDONLY) = -1 ENOENT (No such file or directory) etc............................ |
Que voyez-vous ? Attardons notre regard par exemple sur les lignes suivantes :
open("/etc/ld.so.preload", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY) = 3
Le programme essaye de lire /etc/ld.so.preload et échoue. Ensuite, il continue et lit /etc/ld.so.cache. Ici, il réussit et obtient un file descriptor 3. Maintenant, l'erreur de lecture de /etc/ld.so.preload peut ne pas être un problème du tout car le programme peut juste essayer de lire ceci et l'utiliser si possible. En d'autre mots, ce n'est pas obligatoirement un problème si un programme échoue lors de la lecture d'un fichier. Tout dépend de la façon dont le programme a été élaboré. Jetons un coup d'oeil à tous les appels open dans les résultats de strace :
|
open("/usr/conf/uucp/config", O_RDONLY)= -1 ENOENT (No such file or directory) open("/etc/nsswitch.conf", O_RDONLY) = 3 open("/etc/ld.so.cache", O_RDONLY) = 3 open("/lib/libnss_compat.so.2", O_RDONLY) = 3 open("/etc/passwd", O_RDONLY) = 3 open("/usr/conf/uucp/sys", O_RDONLY) = -1 ENOENT (No such file or directory) open("/var/log/uucp/Debug", O_WRONLY|O_APPEND|O_CREAT|O_NOCTTY, 0600) = 3 open("/var/log/uucp/Log", O_WRONLY|O_APPEND|O_CREAT|O_NOCTTY, 0644) = 4 open("/etc/ld.so.preload", O_RDONLY) = -1 ENOENT (No such file or directory) open("/etc/ld.so.cache", O_RDONLY) = 3 |
Le programme essaye maintenant de lire /usr/conf/uucp/config. Oh! C'est étrange, j'ai le fichier de configuration dans /etc/uucp/config ! ... et donc il n'y a pas de ligne où le programme essaye d'ouvrir /etc/uucp/config. Voilà l'erreur. Le programme a donc manifestement été configuré au moment de la compilation avec une localisation erronée du fichier de configuration.
Comme vous pouvez voir, strace peut être très utile. Le problème est que cela nécessite un peu d'expérience de programmation en C pour comprendre l'ensemble des résultats de strace mais normalement, vous ne devriez pas avoir besoin d'en arriver là.
Parfois, il arrive qu'un programme se termine avec ce message empreint de dépit : "Segmentation fault (core dumped)". Celà signifie que le programme a essayé (suite à une erreur de programmation) d'écrire au delà de la zone de mémoire qui lui était allouée. C'est la cas spécialement quand un programme écrit ne serait-ce que quelques octets de trop, ce quii peut survenir en de rares occasions. C'est dû à la mémoire qui est allouée en morceaux et il arrive parfois qu'accidentellement, il n'y ait plus de place suffisante pour les octets supplémentaires.
Lorsque ce "Segmentation fault" apparaît, un fichier core est créé dans le répertoire de travail actuel du programme (normalement, votre répertoire personnel). Ce fichier core est une copie du contenu de la mémoire au moment où l'erreur est survenue. Certains shells fournissent des possibilités de contrôler lorsque les fichiers core sont écrits. Sous bash, par exemple, le comportement par défaut est de ne pas écrire du tout de fichier core. Pour activer les fichiers core, vous devez utiliser cette commande :
# ulimit -c unlimited
# ./lshref -i index.html,index.htm test.html
Segmentation fault (core dumped)
Exit 139
Le fichier core peut maintenant être utilisé avec le débugeur gdb pour trouver ce qui n'a pas fonctionné. Avant de démarrer gdb, vous pouvez vérifier que vous cherchez bien dans le bon fichier core :
# file core.16897
core.16897: ELF 32-bit LSB core file Intel 80386, version 1 (SYSV), SVR4-style,
from 'lshref'
OK, lshref est le programme qui a crashé, donc chargeons le dans gdb. Pour appeler gdb en vue de l'utiliser avec le fichier core, vous ne devez pas uniquement spécifier le nom du fichier core mais aussi le nom de l'exécutable qui va avec le fichier core.
# gdb ./lshref core.23061 GNU gdb Linux (5.2.1-4)
Copyright 2002 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
Core was generated by `./lshref -i index.html,index.htm test.html'.
Program terminated with signal 11, Segmentation fault.
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
#0 0x40095e9d in strcpy () from /lib/libc.so.6
(gdb)
Ainsi, nous voyons que le programme crashe lorsqu'il essaye de faire un strcpy. Le problème est que cela peut se situer à beaucoup d'endroits dans le code, partout où strcpy est utilisé.
En général, il y a maintenant 2 possibilités pour trouver exactement dans quelle partie du code cela ne fonctionne plus.
Recompiler le code avec l'information pour le débugage (gcc option -g)
Réaliser une analyse de la pile dans gdb
Le problème dans notre cas est que strcpy est une fonction de bibliothèque et que même si nous recompilions absolument tout le code (en incluant le libc) cela nous dirait toujours que cela échoue à une certaine ligne de la bibliothèque C.
Ce dont nous avons besoin est une analyse de la pile trace qui va nous dire quelle fonction a été appelée avant que strcpy ne soit exécuté. La commande pour réaliser une analyse de la pile dans gdb est "backtrace". Cela ne va cependant pas fonctionner uniquement avec le fichier core. Nous devons relancer la commande dans gdb (reproduire l'erreur) :
|
gdb ./lshref core.23061 GNU gdb Linux (5.2.1-4) Copyright 2002 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. Core was generated by `./lshref -i index.html,index.htm test.html'. Program terminated with signal 11, Segmentation fault. 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 #0 0x40095e9d in strcpy () from /lib/libc.so.6 (gdb) backtrace #0 0x40095e9d in strcpy () from /lib/libc.so.6 Cannot access memory at address 0xbfffeb38 (gdb) run ./lshref -i index.html,index.htm test.html Starting program: /home/guido/lshref ./lshref -i index.html,index.htm test.html Program received signal SIGSEGV, Segmentation fault. 0x40095e9d in strcpy () from /lib/libc.so.6 (gdb) backtrace #0 0x40095e9d in strcpy () from /lib/libc.so.6 #1 0x08048d09 in string_to_list () #2 0x080494c8 in main () #3 0x400374ed in __libc_start_main () from /lib/libc.so.6 (gdb) |
Maintenant, nous pouvons voir que la fonction main() a appelé string_to_list() et de string_to_list strcpy() est appelé. Nous allons nous tourner vers string_to_list() et regarder le code :
|
char **string_to_list(char *string){ char *dat; char *chptr; char **array; int i=0; dat=(char *)malloc(strlen(string))+5000; array=(char **)malloc(sizeof(char *)*51); strcpy(dat,string); |
Cette ligne malloc semble contenir une faute de positionnement typographique. Probablement, cela aurait dû être :
dat=(char *)malloc(strlen(string)+5000);
Nous modifions cela, recompilons et ... hourra ... cela fonctionne.
Regardons un deuxième exemple où l'erreur n'est pas détectée dans une librairie mais dans le code de l'application. Dans un tel cas, l'application peut être compilée avec le drapeau "gcc -g" et gdb sera capable de montrer la ligne exacte où l'erreur a été détectée.
|
Voici un exemple simple. #include #include int add(int *p,int a,int b) { *p=a+b; return(*p); } int main(void) { int i; int *p = 0; /* a null pointer */ printf("result is %d\n", add(p,2,3)); return(0); } |
Nous le compilons :
gcc -Wall -g -o exmp exmp.c
Lancons le ...
# ./exmp
Segmentation fault (core dumped)
Exit 139
|
gdb exmp core.5302 GNU gdb Linux (5.2.1-4) Copyright 2002 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. Core was generated by `./exmp'. Program terminated with signal 11, Segmentation fault. 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 #0 0x08048334 in add (p=Cannot access memory at address 0xbfffe020 ) at exmp.c:6 6 *p=a+b; |
gdb nous dit que l'erreur a été détectée à la ligne 6 et que le pointeur "p" pointe vers une mémoire qui ne peut pas être atteinte.
Nous regardons le code ci-dessus et c'est bien-sûr un bête exemple où p est un pointeur nul et vous ne pouvez pas conserver une donnée dans un pointeur nul. Facile à réparer ...
Nous avons vu des cas où vous pouvez vraiment trouver la cause de l'erreur sans connaître grand chose sur les méandres du fonctionnement d'un programme.
Je n'ai pas abordé le cas des erreurs fonctionnelles, par ex. un bouton dans une GUI qui est dans une mauvaise position mais qui fonctionne quand même. Dans ces cas, vous devrez en apprendre plus sur le fonctionnement interne du programme. Cela prend généralement plus de temps et donc, il n'y a pas de recette préfabriquée sur la méthode à appliquer.
Cependant, les techniques simples pour trouver les erreurs peuvent être quand même appliquées dans la plupart des cas.
Bonne chasse !