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
strace
pouvez vérifier s'il est installé à l'aide de la commande ci-dessous. Pour afficher la version, strace
exé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
strace
n'est pas installé, installez en exécutant:
yum install strace
Par exemple, créez un répertoire de test dans
/tmp
et 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
/tmp
uniquement 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 :
ls
testdir
[root@sandbox tmp]# ls testdir/
file1 file2
[root@sandbox tmp]#
Vous utilisez probablement la commande
ls
tous 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
ls
appelle 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
ltrace
suivie de la commande ls testdir/
:
ltrace ls testdir/
S'il
ltrace
n'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
testdir
ouvert à l'aide d'une fonction de bibliothèque opendir
, suivi d'appels aux fonctions readdir
qui lisent le contenu du répertoire. À la fin, une fonction est appelée closedir
qui ferme le répertoire précédemment ouvert. Pour l'instant, ignorez les autres fonctions telles que strlen
et 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
strace
la commande ls testdir
comme 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,
strace
vous 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
ls
fonctionnera comme prévu, affichant une liste de fichiers et écrivant toute la sortie strace
dans un fichier trace.log
. Pour une commande simple, le ls
fichier 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 grep
for testdir
dans le fichier trace.log
et 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
man
et le nom de l'appel système. Si vous lisez man
about 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 man
et 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
execve
exé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 ls
avec testdir
comme paramètre:
'execve - execute program'
'DESCRIPTION
execve() executes the program pointed to by filename'
Le prochain appel système
stat
reç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
openat
s'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 getdents
qui effectue la plupart du travail requis pour exécuter la commande ls testdir
. Maintenant, exécutons grep getdents
pour 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 getdents
lit les entrées du répertoire, ce dont nous avons réellement besoin. Notez que l'argument pour getdent
3 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
grep
pour 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:
file1
et 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
write
prend file1
et file2
la 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.log
initialise 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 verbose
qui affichera des informations supplémentaires sur chaque appel système:
strace -v ls testdir
Il est recommandé d'utiliser un paramètre
-f
pour 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
-c
pour obtenir ces statistiques:
strace -c ls testdir/
Si vous souhaitez tracer un certain appel système, par exemple,
open
et en ignorer les autres, vous pouvez utiliser l'option -e
avec 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
-e
et séparer les appels système requis par une virgule. Par exemple, pour write
et 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
strace
disposez 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
cat
qui affiche le contenu du fichier qui lui est passé en argument. Mais si vous ne spécifiez pas d'argument, la commande cat
attendra 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+C
pour quitter.
Exécutez la commande
cat
sur 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
strace
avec l'option -p
et le PID que vous avez trouvés ps
. Après le démarrage, il strace
affichera des informations sur le processus auquel il s'est connecté, ainsi que son PID. strace
Surveille 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
cat
et entrez du texte. Pour démonstration, je suis entré x0x0
. Veuillez noter que j'ai cat
simplement répété ce que j'ai entré et que x0x0
l'écran apparaîtra deux fois.
[root@sandbox tmp]# cat
x0x0
x0x0
Revenez au terminal oĂą vous vous ĂŞtes
strace
connecté 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 x0x0
sur 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
strace
pour les démons: vous pouvez voir tout ce qui se passe en arrière-plan. Terminez la commandecat
en cliquant Ctrl+C
... Cela mettra Ă©galement fin Ă la sessionstrace
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
-r
qui 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
strace
trè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.