Déploiement de logiciels de débogage avec strace

Déploiement de logiciels de débogage avec strace

Mon travail quotidien consiste principalement à déployer des logiciels, ce qui signifie que je passe beaucoup de temps à essayer de répondre à des questions telles que :

  • Ce logiciel fonctionne pour le développeur, mais pas pour moi. Pourquoi?
  • Hier, ce logiciel fonctionnait pour moi, mais aujourd’hui non. Pourquoi?

Il s'agit d'une sorte de débogage légèrement différente du débogage logiciel classique. Le débogage régulier concerne la logique du code, mais le débogage de déploiement concerne l'interaction entre le code et l'environnement. Même si la racine du problème est une erreur logique, le fait que tout fonctionne sur une machine et pas sur une autre signifie que le problème vient d’une manière ou d’une autre de l’environnement.

Ainsi, au lieu des outils de débogage habituels comme gdb J'ai un ensemble différent d'outils pour le déploiement de débogage. Et mon outil préféré pour résoudre le problème, comme « Pourquoi ce logiciel ne fonctionne-t-il pas pour moi ? » appelé strass.

Qu'est-ce que la strace ?

strass est un outil de « traçage des appels système ». Il a été créé à l'origine pour Linux, mais les mêmes astuces de débogage peuvent être réalisées avec des outils pour d'autres systèmes (DTrace ou ktrace).

L'application de base est très simple. Il vous suffit d'exécuter strace avec n'importe quelle commande et tous les appels système seront supprimés (même si vous devrez probablement d'abord l'installer vous-même). strass):

$ strace echo Hello
...Snip lots of stuff...
write(1, "Hellon", 6)                  = 6
close(1)                                = 0
close(2)                                = 0
exit_group(0)                           = ?
+++ exited with 0 +++

Quels sont ces appels système ? C'est quelque chose comme une API pour le noyau du système d'exploitation. Autrefois, les logiciels avaient un accès direct au matériel sur lequel ils fonctionnaient. Si, par exemple, il fallait afficher quelque chose à l'écran, il jouait avec des ports ou des registres mappés en mémoire pour les appareils vidéo. Lorsque les systèmes informatiques multitâches sont devenus populaires, le chaos régnait alors que diverses applications se disputaient le matériel. Des erreurs dans une application pourraient en faire tomber d’autres, voire l’ensemble du système. Puis des modes privilèges (ou « protection en anneau ») sont apparus dans le CPU. Le noyau est devenu le plus privilégié : il a reçu un accès complet au matériel, engendrant des applications moins privilégiées qui devaient déjà demander l'accès au noyau pour interagir avec le matériel via des appels système.

Au niveau binaire, un appel système est légèrement différent d'un simple appel de fonction, mais la plupart des programmes utilisent un wrapper dans la bibliothèque standard. Ceux. la bibliothèque standard POSIX C contient un appel de fonction écrire (), qui contient tout le code spécifique à l'architecture pour l'appel système écrire.

Déploiement de logiciels de débogage avec strace

En bref, toute interaction entre une application et son environnement (les systèmes informatiques) s'effectue via des appels système. Par conséquent, lorsqu’un logiciel fonctionne sur une machine mais pas sur une autre, il serait bon d’examiner les résultats du suivi des appels système. Plus précisément, voici une liste de points typiques pouvant être analysés à l’aide d’une trace d’appel système :

  • E/S de la console
  • E/S réseau
  • Accès au système de fichiers et E/S de fichiers
  • Gestion de la durée de vie d'un thread de processus
  • Gestion de la mémoire de bas niveau
  • Accès à des pilotes de périphériques spécifiques

Quand utiliser le strace ?

En théorie, strass utilisé avec n'importe quel programme dans l'espace utilisateur, car tout programme dans l'espace utilisateur doit effectuer des appels système. Il fonctionne plus efficacement avec les programmes compilés de bas niveau, mais il fonctionne également avec des langages de haut niveau comme Python si vous pouvez éliminer le bruit supplémentaire du moteur d'exécution et de l'interpréteur.

Dans toute sa brillance strass se manifeste lors du débogage d'un logiciel qui fonctionne bien sur une machine, mais cesse soudainement de fonctionner sur une autre, produisant de vagues messages sur les fichiers, les autorisations ou des tentatives infructueuses d'exécution de certaines commandes ou autre chose... C'est dommage, mais ce n'est pas le cas. se combinent si bien avec des problèmes de haut niveau tels que les erreurs de vérification des certificats. Cela nécessite généralement une combinaison strassquelquefois trace et des outils de niveau supérieur (comme l'outil de ligne de commande openssl pour déboguer le certificat).

Nous utiliserons un serveur autonome comme exemple, mais le suivi des appels système peut souvent être effectué sur des plates-formes de déploiement plus complexes. Il vous suffit de choisir les bons outils.

Exemple de débogage simple

Disons que vous souhaitez exécuter l'incroyable application serveur foo, et voici ce que vous obtenez :

$ foo
Error opening configuration file: No such file or directory

Apparemment, il n'a pas pu trouver le fichier de configuration que vous avez écrit. Cela se produit car parfois, lorsque les gestionnaires de packages compilent une application, ils remplacent les emplacements de fichiers attendus. Et si vous suivez le guide d'installation d'une distribution, dans une autre vous trouverez des fichiers complètement différents de ceux auxquels vous vous attendiez. Le problème pourrait être résolu en quelques secondes si le message d’erreur indiquait où chercher le fichier de configuration, mais ce n’est pas le cas. Alors où chercher ?

Si vous avez accès au code source, vous pouvez le lire et tout savoir. Un bon plan de sauvegarde, mais pas la solution la plus rapide. Vous pouvez recourir à un débogueur étape par étape comme gdb et voyez ce que fait le programme, mais il est beaucoup plus efficace d'utiliser un outil spécialement conçu pour montrer l'interaction avec l'environnement : strass.

conclusion strass peut sembler redondant, mais la bonne nouvelle est que la plupart peuvent être ignorées en toute sécurité. Il est souvent utile d'utiliser l'opérateur -o pour enregistrer les résultats de trace dans un fichier séparé :

$ strace -o /tmp/trace foo
Error opening configuration file: No such file or directory
$ cat /tmp/trace
execve("foo", ["foo"], 0x7ffce98dc010 /* 16 vars */) = 0
brk(NULL)                               = 0x56363b3fb000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=25186, ...}) = 0
mmap(NULL, 25186, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f2f12cf1000
close(3)                                = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "177ELF2113 3 > 1 260A2 "..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=1824496, ...}) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f2f12cef000
mmap(NULL, 1837056, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f2f12b2e000
mprotect(0x7f2f12b50000, 1658880, PROT_NONE) = 0
mmap(0x7f2f12b50000, 1343488, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x22000) = 0x7f2f12b50000
mmap(0x7f2f12c98000, 311296, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x16a000) = 0x7f2f12c98000
mmap(0x7f2f12ce5000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1b6000) = 0x7f2f12ce5000
mmap(0x7f2f12ceb000, 14336, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f2f12ceb000
close(3)                                = 0
arch_prctl(ARCH_SET_FS, 0x7f2f12cf0500) = 0
mprotect(0x7f2f12ce5000, 16384, PROT_READ) = 0
mprotect(0x56363b08b000, 4096, PROT_READ) = 0
mprotect(0x7f2f12d1f000, 4096, PROT_READ) = 0
munmap(0x7f2f12cf1000, 25186)           = 0
openat(AT_FDCWD, "/etc/foo/config.json", O_RDONLY) = -1 ENOENT (No such file or directory)
dup(2)                                  = 3
fcntl(3, F_GETFL)                       = 0x2 (flags O_RDWR)
brk(NULL)                               = 0x56363b3fb000
brk(0x56363b41c000)                     = 0x56363b41c000
fstat(3, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0x8), ...}) = 0
write(3, "Error opening configuration file"..., 60) = 60
close(3)                                = 0
exit_group(1)                           = ?
+++ exited with 1 +++

Environ la totalité de la première page de sortie strass - Il s'agit généralement d'une préparation de bas niveau pour le lancement. (Beaucoup d'appels mmap, protéger, brk pour des choses comme détecter la mémoire de bas niveau et afficher des bibliothèques dynamiques.) En fait, lors du débogage, la sortie strass Il vaut mieux lire depuis la toute fin. Il y aura un défi ci-dessous écrire, qui affiche un message d'erreur. Nous regardons ci-dessus et voyons le premier appel système erroné - l'appel ouvrir, ce qui génère une erreur ÉNONCÉ (« fichier ou répertoire introuvable ») en essayant d'ouvrir /etc/foo/config.json. C'est ici que devrait se trouver le fichier de configuration.

Ce n'était qu'un exemple, mais je dirais que 90 % du temps que j'utilise strass, il n’y a rien de plus difficile à faire que cela. Vous trouverez ci-dessous un guide de débogage complet étape par étape :

  • Soyez contrarié à cause d'un vague message concernant une erreur système provenant d'un programme
  • Redémarrez le programme avec strass
  • Recherchez le message d'erreur dans les résultats de la trace
  • Allez plus haut jusqu'à ce que vous atteigniez le premier appel système échoué

Il est très probable que l'appel système de l'étape 4 révèle ce qui n'a pas fonctionné.

conseils

Avant de vous montrer un exemple de débogage plus complexe, je vais vous montrer quelques astuces pour une utilisation efficace strass:

l'homme est ton ami

Sur de nombreux systèmes * nix, une liste complète des appels système au noyau peut être obtenue en exécutant homme appelle. Vous verrez des choses comme frein(2), ce qui signifie que plus d'informations peuvent être obtenues en exécutant homme 2 points.

Petit râteau : homme 2 fourchette me montre la page du shell fourchette() в libc GNU, qui s'avère être implémenté en appelant cloner(). Sémantique des appels fourche reste le même si vous écrivez un programme en utilisant fourchette(), et lancez une trace - je ne trouverai aucun appel fourche, à leur place il y aura cloner(). De tels râteaux ne vous embrouillent que si vous commencez à comparer la source avec la sortie strass.

Utilisez -o pour enregistrer la sortie dans un fichier

strass peut générer des résultats volumineux, il est donc souvent utile de stocker les résultats de trace dans des fichiers séparés (comme dans l'exemple ci-dessus). Cela permet également d'éviter de confondre la sortie du programme avec la sortie strass dans la console.

Utilisez -s pour afficher plus de données d'argument

Vous avez peut-être remarqué que la seconde moitié du message d'erreur n'est pas affichée dans l'exemple de trace ci-dessus. C'est parce que strass par défaut, affiche uniquement les 32 premiers octets de l'argument de chaîne. Si vous voulez en voir plus, ajoutez quelque chose comme -s 128 XNUMX à l'appel strass.

-y facilite le suivi des fichiers, des sockets, etc.

"Tout est fichier" signifie que les systèmes *nix effectuent toutes les E/S à l'aide de descripteurs de fichiers, que cela s'applique à un fichier, à un réseau ou à des canaux interprocessus. C'est pratique pour la programmation, mais il est difficile de suivre ce qui se passe réellement lorsque vous voyez des lire и écrire dans les résultats de la trace des appels système.

En ajoutant un opérateur y, tu forceras strass annotez chaque descripteur de fichier dans la sortie avec une note de ce vers quoi il pointe.

Attacher à un processus déjà en cours d'exécution avec -p**

Comme vous le verrez dans l'exemple ci-dessous, vous devez parfois tracer un programme déjà en cours d'exécution. Si l'on sait qu'il s'exécute en tant que processus 1337 (par exemple, à partir de la sortie ps), alors vous pouvez le tracer comme ceci :

$ strace -p 1337
...system call trace output...

Vous aurez peut-être besoin des droits root.

Utilisez -f pour surveiller les processus enfants

strass Par défaut, il ne trace qu'un seul processus. Si ce processus génère des processus enfants, alors l'appel système pour générer le processus enfant peut être vu, mais les appels système du processus enfant ne seront pas affichés.

Si vous pensez que l'erreur provient d'un processus enfant, utilisez l'instruction -f, cela permettra son traçage. L’inconvénient est que le résultat vous confondra encore plus. Quand strass trace un processus ou un thread, il affiche un seul flux d'événements d'appel. Lorsqu'il trace plusieurs processus à la fois, vous pouvez voir le début d'un appel interrompu par un message , puis - un tas d'appels pour d'autres branches d'exécution, et seulement alors - la fin de la première <…reprise de l'appel>. Ou divisez tous les résultats de trace dans différents fichiers, également en utilisant l'opérateur -ff (détails dans guide sur strass).

Filtrer les traces en utilisant -e

Comme vous pouvez le constater, le résultat de la trace est un véritable tas de tous les appels système possibles. Drapeau -e Vous pouvez filtrer la trace (voir руководство sur strass). Le principal avantage est qu'il est plus rapide d'exécuter une trace filtrée que d'effectuer une trace complète, puis grep`à. Pour être honnête, je m’en fiche presque toujours.

Toutes les erreurs ne sont pas mauvaises

Un exemple simple et courant est un programme recherchant un fichier à plusieurs endroits à la fois, comme un shell recherchant un répertoire contenant un fichier exécutable :

$ strace sh -c uname
...
stat("/home/user/bin/uname", 0x7ffceb817820) = -1 ENOENT (No such file or directory)
stat("/usr/local/bin/uname", 0x7ffceb817820) = -1 ENOENT (No such file or directory)
stat("/usr/bin/uname", {st_mode=S_IFREG|0755, st_size=39584, ...}) = 0
...

Les heuristiques telles que « dernière requête ayant échoué avant de signaler une erreur » sont efficaces pour trouver les erreurs pertinentes. Quoi qu’il en soit, il est logique de commencer par la toute fin.

Les didacticiels de programmation C peuvent vous aider à comprendre les appels système.

Les appels standard aux bibliothèques C ne sont pas des appels système, mais seulement une fine couche de surface. Ainsi, si vous comprenez au moins un peu comment et quoi faire en C, il vous sera plus facile de comprendre les résultats de la trace des appels système. Par exemple, vous avez du mal à déboguer les appels vers les systèmes réseau, regardez le même classique Guide de Bija sur la programmation réseau.

Un exemple de débogage plus complexe

J'ai déjà dit que l'exemple du débogage simple est un exemple de ce à quoi je dois principalement faire face lorsque je travaille avec strass. Cependant, une véritable enquête est parfois nécessaire. Voici donc un exemple concret de débogage plus avancé.

bcron - planificateur de traitement de tâches, une autre implémentation du démon *nix cron. Il est installé sur le serveur, mais lorsque quelqu'un essaie de modifier le planning, voici ce qui se passe :

# crontab -e -u logs
bcrontab: Fatal: Could not create temporary file

Ok, ça veut dire bcron a essayé d'écrire un certain fichier, mais cela n'a pas fonctionné et il n'admettra pas pourquoi. Découvrir strass:

# strace -o /tmp/trace crontab -e -u logs
bcrontab: Fatal: Could not create temporary file
# cat /tmp/trace
...
openat(AT_FDCWD, "bcrontab.14779.1573691864.847933", O_RDONLY) = 3
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f82049b4000
read(3, "#Ansible: logsaggn20 14 * * * lo"..., 8192) = 150
read(3, "", 8192)                       = 0
munmap(0x7f82049b4000, 8192)            = 0
close(3)                                = 0
socket(AF_UNIX, SOCK_STREAM, 0)         = 3
connect(3, {sa_family=AF_UNIX, sun_path="/var/run/bcron-spool"}, 110) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f82049b4000
write(3, "156:Slogs #Ansible: logsaggn20 1"..., 161) = 161
read(3, "32:ZCould not create temporary f"..., 8192) = 36
munmap(0x7f82049b4000, 8192)            = 0
close(3)                                = 0
write(2, "bcrontab: Fatal: Could not creat"..., 49) = 49
unlink("bcrontab.14779.1573691864.847933") = 0
exit_group(111)                         = ?
+++ exited with 111 +++

Il y a un message d'erreur vers la toute fin écrire, mais cette fois, quelque chose est différent. Premièrement, il n’y a pas d’erreur d’appel système pertinente, qui se produit généralement avant cela. Deuxièmement, il est clair que quelqu'un a déjà lu le message d'erreur quelque part. Il semble que le vrai problème soit ailleurs, et bcrontab lit simplement le message.

Si vous regardez homme 2 lire, vous pouvez voir que le premier argument (3) est un descripteur de fichier, que *nix utilise pour tous les traitements d'E/S. Comment puis-je savoir ce que représente le descripteur de fichier 3 ? Dans ce cas particulier, vous pouvez exécuter strass avec l'opérateur y (voir ci-dessus) et il vous le dira automatiquement, mais pour comprendre des choses comme celle-ci, il est utile de savoir comment lire et analyser les résultats de trace.

La source d'un descripteur de fichier peut être l'un des nombreux appels système (tout dépend de l'utilité du descripteur - une console, une socket réseau, le fichier lui-même ou autre chose), mais quoi qu'il en soit, nous recherchons appelle en renvoyant 3 (c'est-à-dire que nous recherchons « = 3 » dans les résultats de traçage). Dans ce résultat il y en a 2 : ouvrir tout en haut et douille Au milieu. ouvrir ouvre le fichier mais close(3) montrera alors qu'il se referme. (Rake : les descripteurs de fichiers peuvent être réutilisés lors de leur ouverture et de leur fermeture). Appel socket () convient car c'est le dernier avant lis(), et il s'avère que bcrontab fonctionne avec quelque chose via un socket. La ligne suivante montre que le descripteur de fichier est associé à socket de domaine unix le long du chemin /var/run/bcron-spool.

Nous devons donc trouver le processus associé à socket unix d'un autre côté. Il existe quelques astuces intéressantes à cet effet, toutes deux utiles pour déboguer les déploiements de serveurs. La première consiste à utiliser netstat Ou plus récent ss (état de la prise). Les deux commandes affichent les connexions réseau actives du système et prennent la déclaration -l pour décrire les prises d'écoute, ainsi que l'opérateur -p pour afficher les programmes connectés au socket en tant que client. (Il existe de nombreuses autres options utiles, mais ces deux-là suffisent pour cette tâche.)

# ss -pl | grep /var/run/bcron-spool
u_str LISTEN 0   128   /var/run/bcron-spool 1466637   * 0   users:(("unixserver",pid=20629,fd=3))

Cela suggère que l'auditeur est la commande serveur inix, exécuté avec l'ID de processus 20629. (Et, par coïncidence, il utilise le descripteur de fichier 3 comme socket.)

Le deuxième outil vraiment utile pour trouver la même information s'appelle lsof. Il répertorie tous les fichiers ouverts (ou descripteurs de fichiers) sur le système. Ou vous pouvez obtenir des informations sur un fichier spécifique :

# lsof /var/run/bcron-spool
COMMAND   PID   USER  FD  TYPE  DEVICE              SIZE/OFF  NODE    NAME
unixserve 20629 cron  3u  unix  0x000000005ac4bd83  0t0       1466637 /var/run/bcron-spool type=STREAM

Le processus 20629 est un serveur de longue durée, vous pouvez donc le connecter à strass en utilisant quelque chose comme strace -o /tmp/trace -p 20629. Si vous modifiez une tâche cron dans un autre terminal, vous recevrez une sortie de trace avec une erreur. Et voici le résultat:

accept(3, NULL, NULL)                   = 4
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7faa47c44810) = 21181
close(4)                                = 0
accept(3, NULL, NULL)                   = ? ERESTARTSYS (To be restarted if SA_RESTART is set)
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=21181, si_uid=998, si_status=0, si_utime=0, si_stime=0} ---
wait4(0, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], WNOHANG|WSTOPPED, NULL) = 21181
wait4(0, 0x7ffe6bc36764, WNOHANG|WSTOPPED, NULL) = -1 ECHILD (No child processes)
rt_sigaction(SIGCHLD, {sa_handler=0x55d244bdb690, sa_mask=[CHLD], sa_flags=SA_RESTORER|SA_RESTART, sa_restorer=0x7faa47ab9840}, {sa_handler=0x55d244bdb690, sa_mask=[CHLD], sa_flags=SA_RESTORER|SA_RESTART, sa_restorer=0x7faa47ab9840}, 8) = 0
rt_sigreturn({mask=[]})                 = 43
accept(3, NULL, NULL)                   = 4
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7faa47c44810) = 21200
close(4)                                = 0
accept(3, NULL, NULL)                   = ? ERESTARTSYS (To be restarted if SA_RESTART is set)
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=21200, si_uid=998, si_status=111, si_utime=0, si_stime=0} ---
wait4(0, [{WIFEXITED(s) && WEXITSTATUS(s) == 111}], WNOHANG|WSTOPPED, NULL) = 21200
wait4(0, 0x7ffe6bc36764, WNOHANG|WSTOPPED, NULL) = -1 ECHILD (No child processes)
rt_sigaction(SIGCHLD, {sa_handler=0x55d244bdb690, sa_mask=[CHLD], sa_flags=SA_RESTORER|SA_RESTART, sa_restorer=0x7faa47ab9840}, {sa_handler=0x55d244bdb690, sa_mask=[CHLD], sa_flags=SA_RESTORER|SA_RESTART, sa_restorer=0x7faa47ab9840}, 8) = 0
rt_sigreturn({mask=[]})                 = 43
accept(3, NULL, NULL

(Dernier J'accepte() ne sera pas terminé lors du traçage.) Encore une fois, malheureusement, ce résultat ne contient pas l’erreur que nous recherchons. Nous ne voyons aucun message envoyé ou reçu par bcrontag du socket. Au lieu de cela, contrôlez complètement le processus (cloner, wait4, SIGCHLD etc.) Ce processus engendre un processus enfant qui, comme vous pouvez le deviner, fait le vrai travail. Et si vous avez besoin de retrouver sa trace, ajoutez à l'appel strace-f. C'est ce que nous trouverons en recherchant le message d'erreur dans le nouveau résultat avec strace -f -o /tmp/trace -p 20629:

21470 openat(AT_FDCWD, "tmp/spool.21470.1573692319.854640", O_RDWR|O_CREAT|O_EXCL, 0600) = -1 EACCES (Permission denied) 
21470 write(1, "32:ZCould not create temporary f"..., 36) = 36
21470 write(2, "bcron-spool[21470]: Fatal: logs:"..., 84) = 84
21470 unlink("tmp/spool.21470.1573692319.854640") = -1 ENOENT (No such file or directory)
21470 exit_group(111)                   = ?
21470 +++ exited with 111 +++

Maintenant, c'est quelque chose. Le processus 21470 reçoit une erreur « accès refusé » lors de la tentative de création d'un fichier au chemin tmp/bobine.21470.1573692319.854640 (relatif au répertoire de travail actuel). Si nous connaissions simplement le répertoire de travail actuel, nous connaîtrions également le chemin complet et pourrions comprendre pourquoi le processus ne peut pas y créer son fichier temporaire. Malheureusement, le processus est déjà terminé, vous ne pouvez donc pas simplement utiliser lsof-p 21470 afin de trouver le répertoire actuel, mais vous pouvez travailler dans la direction opposée - recherchez les appels système PID 21470 qui modifient le répertoire. (S'il n'y en a pas, le PID 21470 doit en avoir hérité de son parent, et cela est déjà passé par lsof -p ne peut pas être trouvé.) Cet appel système est chdir (ce qui est facile à découvrir à l’aide des moteurs de recherche en ligne modernes). Et voici le résultat des recherches inversées basées sur les résultats de trace, jusqu'au serveur PID 20629 :

20629 clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7faa47c44810) = 21470
...
21470 execve("/usr/sbin/bcron-spool", ["bcron-spool"], 0x55d2460807e0 /* 27 vars */) = 0
...
21470 chdir("/var/spool/cron")          = 0
...
21470 openat(AT_FDCWD, "tmp/spool.21470.1573692319.854640", O_RDWR|O_CREAT|O_EXCL, 0600) = -1 EACCES (Permission denied) 
21470 write(1, "32:ZCould not create temporary f"..., 36) = 36
21470 write(2, "bcron-spool[21470]: Fatal: logs:"..., 84) = 84
21470 unlink("tmp/spool.21470.1573692319.854640") = -1 ENOENT (No such file or directory)
21470 exit_group(111)                   = ?
21470 +++ exited with 111 +++

(Si vous êtes perdu, vous voudrez peut-être lire mon message précédent à propos de la gestion des processus *nix et des shells.) Ainsi, le serveur PID 20629 n'a pas reçu l'autorisation de créer un fichier sur le chemin /var/spool/cron/tmp/spool.21470.1573692319.854640. Très probablement, la raison en est les paramètres d'autorisation classiques du système de fichiers. Allons vérifier:

# ls -ld /var/spool/cron/tmp/
drwxr-xr-x 2 root root 4096 Nov  6 05:33 /var/spool/cron/tmp/
# ps u -p 20629
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
cron     20629  0.0  0.0   2276   752 ?        Ss   Nov14   0:00 unixserver -U /var/run/bcron-spool -- bcron-spool

C'est là que le chien est enterré ! Le serveur s'exécute en tant qu'utilisateur cron, mais seul root a l'autorisation d'écrire dans le répertoire /var/spool/cron/tmp/. Commande simple chown cron /var/spool/cron/tmp/ fera bcron fonctionne correctement. (Si ce n'était pas le problème, alors le prochain suspect le plus probable est un module de sécurité du noyau comme SELinux ou AppArmor, je vérifierais donc le journal des messages du noyau avec dmesg.)

En tout

Les traces d'appels système peuvent être fastidieuses pour un débutant, mais j'espère avoir montré qu'elles constituent un moyen rapide de déboguer toute une classe de problèmes de déploiement courants. Imaginez essayer de déboguer un multiprocessus bcronen utilisant un débogueur étape par étape.

L'analyse des résultats de trace à rebours tout au long d'une chaîne d'appels système nécessite des compétences, mais comme je l'ai dit, presque toujours, en utilisant strass, j'obtiens juste le résultat de la trace et recherche les erreurs en commençant par la fin. De toute façon, strass m'aide à gagner beaucoup de temps sur le débogage. J'espère que cela vous sera utile aussi.

Source: habr.com

Ajouter un commentaire