Comprendre les appels système sous Linux avec strace

La traduction de l'article a été préparée spécialement pour les étudiants des cours de base et avancés Administrateur Linux.






L'appel système est le mécanisme par lequel les programmes utilisateur interagissent avec le noyau Linux, et strace est un outil puissant pour en garder la trace. Pour mieux comprendre le fonctionnement du système d'exploitation, il est utile de comprendre comment ils fonctionnent.



Le système d'exploitation peut être divisé en deux modes de fonctionnement:



  • Le mode noyau est le mode privilĂ©giĂ© utilisĂ© par le noyau du système d'exploitation.
  • Le mode utilisateur est le mode dans lequel la plupart des applications utilisateur s'exĂ©cutent.




Les utilisateurs utilisent généralement des utilitaires de ligne de commande et une interface graphique (GUI) pour leur travail quotidien. Dans le même temps, les appels système fonctionnent de manière invisible en arrière-plan, faisant référence au noyau pour effectuer le travail.



Les appels système sont très similaires aux appels de fonction en ce sens qu'ils reçoivent des arguments et des valeurs de retour. La seule différence est que les appels système fonctionnent au niveau du noyau, mais pas les fonctions. Le passage du mode utilisateur au mode noyau se fait à l'aide d'un mécanisme d' interruption spécial .



La plupart de ces détails sont cachés à l'utilisateur dans les bibliothèques système (glibc sur les systèmes Linux). Les appels système sont de nature générique, mais malgré cela, les mécanismes de leur exécution dépendent largement du matériel.



Cet article explore plusieurs exemples pratiques d'analyse des appels système à l'aide de strace. Les exemples utilisent Red Hat Enterprise Linux, mais toutes les commandes devraient également fonctionner sur d'autres distributions Linux:



[root@sandbox ~]# cat /etc/redhat-release
Red Hat Enterprise Linux Server release 7.7 (Maipo)
[root@sandbox ~]#
[root@sandbox ~]# uname -r
3.10.0-1062.el7.x86_64
[root@sandbox ~]#




Tout d'abord, assurez-vous que les outils nécessaires sont installés sur votre système. Vous stracepouvez vérifier s'il est installé à l'aide de la commande ci-dessous. Pour afficher la version, straceexécutez-la avec le paramètre -V:



[root@sandbox ~]# rpm -qa | grep -i strace
strace-4.12-9.el7.x86_64
[root@sandbox ~]#
[root@sandbox ~]# strace -V
strace -- version 4.12
[root@sandbox ~]#




S'il stracen'est pas installé, installez en exécutant:



yum install strace




Par exemple, créez un répertoire de test dans /tmpet deux fichiers à l'aide de la commande touch:



[root@sandbox ~]# cd /tmp/
[root@sandbox tmp]#
[root@sandbox tmp]# mkdir testdir
[root@sandbox tmp]#
[root@sandbox tmp]# touch testdir/file1
[root@sandbox tmp]# touch testdir/file2
[root@sandbox tmp]#




(J'utilise /tmpuniquement un répertoire car tout le monde y a accès, mais vous pouvez utiliser n'importe quel autre répertoire .) Vérifiez que les fichiers ont été créés dans le répertoire



Ă  l'aide de la commande :lstestdir



[root@sandbox tmp]# ls testdir/
file1  file2
[root@sandbox tmp]#




Vous utilisez probablement la commande lstous les jours sans vous rendre compte que les appels système sont exécutés sous le capot. C'est là que l'abstraction entre en jeu. Voici comment fonctionne cette commande:



   ->    (glibc) ->  




La commande lsappelle des fonctions à partir des bibliothèques système Linux (glibc). Ces bibliothèques, à leur tour, appellent des appels système, qui effectuent la majeure partie du travail.



Si vous voulez savoir quelles fonctions ont été appelées à partir de la bibliothèque glibc, utilisez la commande ltracesuivie de la commande ls testdir/:



ltrace ls testdir/




S'il ltracen'est pas installé, installez:



yum install ltrace




Il y aura beaucoup d'informations à l'écran, mais ne vous inquiétez pas - nous en parlerons plus tard. Voici quelques-unes des fonctions de bibliothèque importantes de la sortie ltrace:



opendir("testdir/")                                  = { 3 }
readdir({ 3 })                                       = { 101879119, "." }
readdir({ 3 })                                       = { 134, ".." }
readdir({ 3 })                                       = { 101879120, "file1" }
strlen("file1")                                      = 5
memcpy(0x1665be0, "file1\0", 6)                      = 0x1665be0
readdir({ 3 })                                       = { 101879122, "file2" }
strlen("file2")                                      = 5
memcpy(0x166dcb0, "file2\0", 6)                      = 0x166dcb0
readdir({ 3 })                                       = nil
closedir({ 3 })    




En examinant cette sortie, vous pouvez probablement comprendre ce qui se passe. Le répertoire nommé est testdirouvert à l'aide d'une fonction de bibliothèque opendir, suivi d'appels aux fonctions readdirqui lisent le contenu du répertoire. À la fin, une fonction est appelée closedirqui ferme le répertoire précédemment ouvert. Pour l'instant, ignorez les autres fonctions telles que strlenet memcpy.



Comme vous pouvez le voir, il est facile de voir les fonctions de la bibliothèque appelées, mais dans cet article, nous nous concentrerons sur les appels système qui sont appelés par les fonctions de la bibliothèque système.



Pour afficher les appels système, utilisez stracela commande ls testdircomme indiqué ci-dessous. Et encore une fois, vous obtenez un tas d'informations incohérentes:



[root@sandbox tmp]# strace ls testdir/
execve("/usr/bin/ls", ["ls", "testdir/"], [/* 40 vars */]) = 0
brk(NULL)                               = 0x1f12000
<<< truncated strace output >>>
write(1, "file1  file2\n", 13file1  file2
)          = 13
close(1)                                = 0
munmap(0x7fd002c8d000, 4096)            = 0
close(2)                                = 0
exit_group(0)                           = ?
+++ exited with 0 +++
[root@sandbox tmp]#


À la suite de l'exécution, stracevous recevrez une liste des appels système exécutés pendant l'exécution de la commande ls. Tous les appels système peuvent être divisés dans les catégories suivantes:



  • La gestion des processus
  • Gestion de fichiers
  • Gestion des rĂ©pertoires et des systèmes de fichiers
  • Autre




Il existe un moyen pratique d'analyser les informations reçues - écrire la sortie dans un fichier à l'aide de l'option -o.



[root@sandbox tmp]# strace -o trace.log ls testdir/
file1  file2
[root@sandbox tmp]#




Cette fois, il n'y aura pas de données à l'écran - la commande lsfonctionnera comme prévu, affichant une liste de fichiers et écrivant toute la sortie stracedans un fichier trace.log. Pour une commande simple, le lsfichier contient près de 100 lignes:



[root@sandbox tmp]# ls -l trace.log
-rw-r--r--. 1 root root 7809 Oct 12 13:52 trace.log
[root@sandbox tmp]#
[root@sandbox tmp]# wc -l trace.log
114 trace.log
[root@sandbox tmp]#




Jetez un œil à la première ligne du fichier trace.log:



execve("/usr/bin/ls", ["ls", "testdir/"], [/* 40 vars */]) = 0




  • Au dĂ©but de la ligne se trouve le nom de l'appel système en cours d'exĂ©cution - execve.
  • Le texte entre parenthèses correspond aux arguments passĂ©s Ă  l'appel système.
  • Le nombre après le signe = (dans ce cas, 0) est la valeur renvoyĂ©e par l'appel système.




Maintenant, le résultat ne semble pas trop effrayant, n'est-ce pas? Et vous pouvez également appliquer la même logique aux autres lignes.



Faites attention à la seule commande que vous avez appelée - ls testdir. Vous connaissez le nom du répertoire utilisé par la commande ls, alors pourquoi ne pas utiliser grepfor testdirdans le fichier trace.loget voir ce qu'il trouve? Regardez attentivement le résultat:



[root@sandbox tmp]# grep testdir trace.log
execve("/usr/bin/ls", ["ls", "testdir/"], [/* 40 vars */]) = 0
stat("testdir/", {st_mode=S_IFDIR|0755, st_size=32, ...}) = 0
openat(AT_FDCWD, "testdir/", O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC) = 3
[root@sandbox tmp]#




En revenant à l'analyse ci-dessus execve, pouvez-vous dire ce que fait le prochain appel système?



execve("/usr/bin/ls", ["ls", "testdir/"], [/* 40 vars */]) = 0




Vous n'avez pas à vous souvenir de tous les appels système et de ce qu'ils font: tout est dans la documentation. Les pages man se précipitent à la rescousse! Assurez-vous que le package est installé avant d'exécuter la commande man man-pages:



[root@sandbox tmp]# rpm -qa | grep -i man-pages
man-pages-3.53-5.el7.noarch
[root@sandbox tmp]#




N'oubliez pas que vous devez ajouter "2" entre la commande manet le nom de l'appel système. Si vous lisez manabout man( man man), vous verrez que la section 2 est réservée aux appels système. De même, si vous voulez des informations sur les fonctions de bibliothèque, vous devez ajouter 3 entre manet le nom de la fonction de bibliothèque.



Voici les numéros de section man:



1.       .
2.   (,  ).
3.   (  ).
4.   (    /dev).




Pour afficher la documentation d'un appel système, exécutez man avec le nom de cet appel système.



man 2 execve




Selon la documentation, un appel système execveexécute un programme qui lui est passé en paramètres (dans ce cas, c'est le cas ls). Des paramètres supplémentaires pour ls lui sont également transmis. Dans cet exemple, c'est le cas testdir. Par conséquent, cet appel système s'exécute simplement lsavec testdircomme paramètre:



'execve - execute program'

'DESCRIPTION
       execve()  executes  the  program  pointed to by filename'




Le prochain appel système statreçoit un paramètre testdir:



stat("testdir/", {st_mode=S_IFDIR|0755, st_size=32, ...}) = 0




Pour afficher la documentation, utilisez man 2 stat. L'appel système stat renvoie des informations sur le fichier spécifié. N'oubliez pas que tout dans Linux est un fichier, y compris les répertoires.



Ensuite, l'appel système openats'ouvre testdir. Notez que la valeur de retour est 3. Il s'agit du descripteur de fichier qui sera utilisé lors des appels système suivants:



openat(AT_FDCWD, "testdir/", O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC) = 3




Ouvrez maintenant le fichier
trace.log
et remarquez la ligne suivant l'appel système openat. Vous verrez un appel système getdentsqui effectue la plupart du travail requis pour exécuter la commande ls testdir. Maintenant, exécutons grep getdentspour le fichier trace.log:



[root@sandbox tmp]# grep getdents trace.log
getdents(3, /* 4 entries */, 32768)     = 112
getdents(3, /* 0 entries */, 32768)     = 0
[root@sandbox tmp]#




La documentation ( man getdents) dit qu'elle getdentslit les entrées du répertoire, ce dont nous avons réellement besoin. Notez que l'argument pour getdent3 est le descripteur de fichier obtenu précédemment à partir de l'appel système openat.



Maintenant que le contenu du répertoire est reçu, nous avons besoin d'un moyen d'afficher les informations dans le terminal. Donc, nous faisons greppour un autre appel système write, qui est utilisé pour sortir vers le terminal:



[root@sandbox tmp]# grep write trace.log
write(1, "file1  file2\n", 13)          = 13
[root@sandbox tmp]#




Dans les arguments, vous pouvez voir les noms des fichiers à sortir: file1et file2. Pour le premier argument (1), rappelez-vous que sous Linux, trois descripteurs de fichiers sont ouverts par défaut pour tout processus:



  • 0 - flux d'entrĂ©e standard
  • 1 - flux de sortie standard
  • 2 - flux d'erreur standard




Ainsi, l'appel système writeprend file1et file2la sortie standard, qui est un terminal, désigne le numéro 1.



Maintenant que vous savez ce que le système appelle, a fait la plupart du travail pour l'équipe ls testdir/. Mais qu'en est-il des 100 autres appels système du fichier trace.log?



Le système d'exploitation fait beaucoup de choses pour démarrer le processus, donc une grande partie de ce que vous voyez dans le fichier trace.loginitialise et nettoie le processus. Jetez un œil complet au fichier trace.log et essayez de comprendre ce qui se passe lorsque la commande est exécutée ls.



Vous pouvez désormais analyser les appels système pour n'importe quel programme. L'utilitaire strace fournit également de nombreuses options de ligne de commande utiles, dont certaines sont décrites ci-dessous.



Par défaut strace, il n'affiche pas toutes les informations sur les appels système. Cependant, il dispose d'une option -v verbosequi affichera des informations supplémentaires sur chaque appel système:



strace -v ls testdir




Il est recommandé d'utiliser un paramètre -fpour suivre les processus enfants créés par un processus en cours d'exécution:



strace -f ls testdir




Que faire si vous voulez uniquement les noms des appels système, le nombre de fois qu'ils sont exécutés et le pourcentage de temps passé à les exécuter? Vous pouvez utiliser l'option -cpour obtenir ces statistiques:



strace -c ls testdir/




Si vous souhaitez tracer un certain appel système, par exemple, openet en ignorer les autres, vous pouvez utiliser l'option -eavec le nom de l'appel système:



[root@sandbox tmp]# strace -e open ls testdir
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libselinux.so.1", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libcap.so.2", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libacl.so.1", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libpcre.so.1", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libdl.so.2", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libattr.so.1", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libpthread.so.0", O_RDONLY|O_CLOEXEC) = 3
open("/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC) = 3
file1  file2
+++ exited with 0 +++
[root@sandbox tmp]#




Que faire si vous devez filtrer par plusieurs appels système? Ne vous inquiétez pas, vous pouvez utiliser la même option -eet séparer les appels système requis par une virgule. Par exemple, pour writeet getdent:



[root@sandbox tmp]# strace -e write,getdents ls testdir
getdents(3, /* 4 entries */, 32768)     = 112
getdents(3, /* 0 entries */, 32768)     = 0
write(1, "file1  file2\n", 13file1  file2
)          = 13
+++ exited with 0 +++
[root@sandbox tmp]#




Jusqu'à présent, nous n'avons suivi que les exécutions de commandes explicites. Mais qu'en est-il des commandes exécutées plus tôt? Et si vous voulez traquer les démons? Pour cela, vous stracedisposez d'une option spéciale -pà laquelle vous pouvez transmettre l'ID de processus.



Nous ne lancerons pas le démon, mais utiliserons une commande catqui affiche le contenu du fichier qui lui est passé en argument. Mais si vous ne spécifiez pas d'argument, la commande catattendra simplement l'entrée de l'utilisateur. Après avoir saisi le texte, il affichera le texte saisi à l'écran. Et ainsi de suite jusqu'à ce que l'utilisateur clique Ctrl+Cpour quitter.



Exécutez la commande catsur un terminal.



[root@sandbox tmp]# cat




Sur un autre terminal, recherchez l'identifiant du processus (PID) avec la commande ps:



[root@sandbox ~]# ps -ef | grep cat
root      22443  20164  0 14:19 pts/0    00:00:00 cat
root      22482  20300  0 14:20 pts/1    00:00:00 grep --color=auto cat
[root@sandbox ~]#




Commencez maintenant straceavec l'option -pet le PID que vous avez trouvés ps. Après le démarrage, il straceaffichera des informations sur le processus auquel il s'est connecté, ainsi que son PID. straceSurveille maintenant les appels système effectués par la commande cat. Le premier appel système que vous verrez est lu, en attente de l'entrée du thread 0, c'est-à-dire de l'entrée standard, qui est maintenant le terminal sur lequel la commande s'exécute cat:



[root@sandbox ~]# strace -p 22443
strace: Process 22443 attached
read(0,




Revenez maintenant au terminal où vous avez laissé la commande en cours d'exécution catet entrez du texte. Pour démonstration, je suis entré x0x0. Veuillez noter que j'ai catsimplement répété ce que j'ai entré et que x0x0l'écran apparaîtra deux fois.



[root@sandbox tmp]# cat
x0x0
x0x0




Revenez au terminal où vous vous êtes straceconnecté au processus cat. Vous voyez maintenant deux nouveaux appels système: le précédent read, qui a maintenant lu x0x0, et un autre pour l'écriture write, qui réécrit x0x0sur le terminal, et encore un nouveau read, qui attend une lecture du terminal. Notez que l'entrée standard (0) et la sortie standard (1) sont sur la même borne:



[root@sandbox ~]# strace -p 22443
strace: Process 22443 attached
read(0, "x0x0\n", 65536)                = 5
write(1, "x0x0\n", 5)                   = 5
read(0,




Imaginez les avantages du lancement stracepour les démons: vous pouvez voir tout ce qui se passe en arrière-plan. Terminez la commande
cat
en cliquant
Ctrl+C
... Cela mettra Ă©galement fin Ă  la session
strace
depuis la fin du processus surveillé.



Pour afficher les horodatages des appels système, utilisez l'option -t:



[root@sandbox ~]#strace -t ls testdir/

14:24:47 execve("/usr/bin/ls", ["ls", "testdir/"], [/* 40 vars */]) = 0
14:24:47 brk(NULL)                      = 0x1f07000
14:24:47 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f2530bc8000
14:24:47 access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
14:24:47 open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3




Que faire si vous souhaitez connaître le temps passé entre les appels système? Il existe une option pratique -rqui indique le temps nécessaire pour terminer chaque appel système. Assez utile, n'est-ce pas?



[root@sandbox ~]#strace -r ls testdir/

0.000000 execve("/usr/bin/ls", ["ls", "testdir/"], [/* 40 vars */]) = 0
0.000368 brk(NULL)                 = 0x1966000
0.000073 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fb6b1155000
0.000047 access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
0.000119 open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3




Conclusion



L'utilitaire est stracetrès pratique pour apprendre les appels système sous Linux. Pour d'autres options de ligne de commande, voir man et la documentation en ligne.






All Articles