Scroll to navigation

PTRACE(2) Manuel du programmeur Linux PTRACE(2)

NOM

ptrace - Suivre un processus

SYNOPSIS

#include <sys/ptrace.h>

long ptrace(enum __ptrace_request request, pid_t pid, 
            void *addr, void *data);

DESCRIPTION

L'appel système ptrace() fournit à un processus (l'« observateur ») un moyen d'observer et contrôler l'exécution d'un autre processus (l'« observé ») et d'examiner et éditer la mémoire et les registres de l'observé. L'utilisation principale de cette fonction est l'implémentation de points d'arrêt pour le débogage, et pour suivre les appels système.

Un observé doit d'abord être attaché à l'observateur. L'attachement et les commandes suivantes sont par thread : dans un processus multithreadé, chaque thread peut être attaché individuellement à un observateur (éventuellement différent), ou être laissé détaché et donc non débogué. Par conséquent, l'« observé » signifie toujours « (un) thread », jamais « un processus (éventuellement multithreadé) ». Les commandes ptrace sont toujours envoyées à un observé spécifique en utilisant un appel de la forme

ptrace(PTRACE_truc, pid, ...)

pid est l'identifiant de thread du thread Linux correspondant.

(Remarquez que dans cette page, un « processus multithreadé » signifie un groupe de threads constitué de threads créés en utilisant l'attribut CLONE_THREAD de clone(2).)

Un processus peut démarrer un suivi en appelant fork(2) et faire en sorte que le fils créé fasse un PTRACE_TRACEME, suivi (en général) par un execve(2). Autrement, un processus peut commencer à suivre un autre processus en utilisant PTRACE_ATTACH ou PTRACE_SEIZE.

L'observé s'arrêtera à chaque fois qu'un signal lui sera distribué, même si le signal est ignoré (à l'exception de SIGKILL qui a les effets habituels). L'observateur sera prévenu à son prochain appel de waitpid(2) (ou un des appels système liés à « wait ») ; cet appel renverra une valeur status contenant les renseignements indiquant la raison de l'arrêt de l'observé. Lorsque l'observé est arrêté, l'observateur peut utiliser plusieurs requêtes ptrace pour inspecter et modifier l'observé. L'observateur peut également laisser continuer l'exécution de l'observé, en ignorant éventuellement le signal ayant déclenché l'arrêt, ou même en envoyant un autre signal.

Si l'option PTRACE_O_TRACEEXEC n'est pas effective, tous les appels réussis d'execve(2) par le processus suivi déclencheront l'envoi d'un signal SIGTRAP, ce qui permet au père de reprendre le contrôle avant que le nouveau programme commence son exécution.

Quand l'observateur a fini le suivi, il peut forcer l'observé à continuer normalement, en mode non suivi, avec PTRACE_DETACH.

La valeur de l'argument request indique précisément l'action à entreprendre.

PTRACE_TRACEME
Le processus en cours va être suivi par son père. Un processus ne devrait sans doute pas envoyer cette requête si son père n'est pas prêt à le suivre. Dans cette requête pid, addr, et data sont ignorés.
La requête PTRACE_TRACEME ne sert qu'à l'observé. Les requêtes restantes ne servent qu'à l'observateur. Par la suite, pid précise l'identifiant de thread de l'observé sur lequel agir. Pour les requêtes différentes de PTRACE_ATTACH, PTRACE_SEIZE, PTRACE_INTERRUPT et PTRACE_KILL, l'observé doit être arrêté.
PTRACE_PEEKTEXT, PTRACE_PEEKDATA
Lire un mot à l'adresse addr dans l'espace mémoire de l'observé et le renvoyer en résultat de l'appel ptrace(). Linux ne sépare pas les espaces d'adressage de code et de données, donc ces deux requêtes sont équivalentes (data est ignoré, consultez la section NOTES).
PTRACE_PEEKUSER
Lire un mot à la position addr dans l'espace USER de l'observé, qui contient les registres et divers renseignements sur le processus (voir <sys/user.h>). La valeur est renvoyée en résultat de ptrace(). En principe, l'adresse doit être alignée sur une frontière de mots, bien que cela varie selon les architectures. Consultez la section NOTES. (data est ignoré, consultez la section NOTES).
PTRACE_POKETEXT, PTRACE_POKEDATA
Copier un mot depuis l'adresse data de la mémoire de l'observateur vers l'adresse addr de la mémoire de l'observé. Comme pour PTRACE_PEEKTEXT et PTRACE_PEEKDATA, ces deux requêtes sont équivalentes.
PTRACE_POKEUSER
Copier un mot depuis l'emplacement data de la mémoire de l'observateur vers l'emplacement addr dans l'espace USER de l'observé. Comme pour PTRACE_PEEKUSER, les emplacements doivent être alignés sur une frontière de mot. Pour maintenir l'intégrité du noyau, certaines modifications de la zone USER sont interdites.
PTRACE_GETREGS, PTRACE_GETFPREGS
Copier les registres généraux ou du processeur en virgule flottante de l'observé, vers l'adresse data de l'observateur. Consultez <sys/user.h> pour les détails sur le format de ces données (addr est ignoré). Remarquez que les systèmes SPARC ont la signification de data et addr inversée, c'est-à-dire que data est ignoré et les registres sont copiés vers l'adresse addr. PTRACE_GETREGS et PTRACE_GETFPREGS ne sont pas présents sur toutes les architectures.
PTRACE_GETREGSET (depuis Linux 2.6.34)
Lire les registres de l'observé. addr indique, de manière dépendante de l'architecture, le type de registres à lire. NT_PRSTATUS (avec une valeur numérique de 1) a pour conséquence habituelle la lecture de registres généraux. Si le processeur a, par exemple, des registres en virgule flottante ou en vecteur, ils peuvent être récupéré en configurant addr à la constante NT_foo correspondante. data pointe vers une struct iovec, qui décrit l'emplacement et la taille du tampon de destination. Le noyau modifie iov.len au retour pour indiquer le véritable nombre d'octets renvoyés.
PTRACE_SETREGS, PTRACE_SETFPREGS
Modifier les registres généraux ou du processeur en virgule flottante de l'observé, depuis l'adresse data de l'observateur. Comme pour PTRACE_POKEUSER, certaines modifications de registres généraux pourraient être interdites (addr est ignoré). Remarquez que les systèmes SPARC ont la signification de data et addr inversée, c'est-à-dire que data est ignoré et les registres sont copiés depuis l'adresse addr. PTRACE_SETREGS et PTRACE_SETFPREGS ne sont pas présents sur toutes les architectures.
PTRACE_SETREGSET (depuis Linux 2.6.34)
Modifier les registres de l'observé. La signification de addr et data est analogue à PTRACE_GETREGSET.
PTRACE_GETSIGINFO (depuis Linux 2.3.99-pre6)
Récupérer des renseignements sur le signal qui a provoqué l'arrêt. Pour ce faire, copier une structure siginfo_t (consultez sigaction(2)) de l'observé à l'adresse data de l'observateur (addr est ignoré).
PTRACE_SETSIGINFO (depuis Linux 2.3.99-pre6)
Définir les renseignements de signaux : copier une structure siginfo_t de l'adresse data de l'observateur vers l'observé. Cela n'affecte que les signaux qui auraient dû être distribués à l'observé et ont été interceptés à cause de ptrace(). Différencier ces signaux normaux des signaux créés par ptrace() lui-même peut être délicat (addr est ignoré).
PTRACE_PEEKSIGINFO (depuis Linux 3.10)
Récupérer les structures siginfo_t sans supprimer les signaux d’une file d’attente. addr pointe vers une structure ptrace_peeksiginfo_args qui indique la position ordinale à partir de laquelle la copie des signaux devrait commencer et le nombre de signaux à copier. Les structures siginfo_t sont copiées dans le tampon pointé par data. La valeur de retour contient le nombre de signaux copiés (zéro indique qu’il n’y a pas de signal correspondant à la position ordinale indiquée). Dans les structures siginfo renvoyées, le champ si_code contient des renseignements (__SI_CHLD, __SI_FAULT, etc.) qui ne sont sinon pas exposés à l’espace utilisateur.


struct ptrace_peeksiginfo_args {
    u64 off;    /* Position ordinale dans la file d’attente
                   où commencer la copie de signaux */
    u32 flags;  /* PTRACE_PEEKSIGINFO_SHARED ou 0 */
    s32 nr;     /* Nombre de signaux à copier */
};

Actuellement, seul l’attribut PTRACE_PEEKSIGINFO_SHARED permet de vider les signaux de la file de signaux par processus. Si cet attribut n’est pas défini, les signaux sont lus depuis la file par thread du thread indiqué.

PTRACE_GETSIGMASK (depuis Linux 3.11)
Placer une copie du masque des signaux bloqués (consultez sigprocmask(2)) dans le tampon pointé par data qui devrait être un pointeur vers un tampon de type sigset_t. L’argument addr contient la taille du tampon pointé par data (c’est-à-dire sizeof(sigset_t)).
PTRACE_SETSIGMASK (depuis Linux 3.11)
Modifier le masque des signaux bloqués (consultez sigprocmask(2)) à la valeur indiquée dans le tampon pointé par data qui devrait être un pointeur vers un tampon de type sigset_t. L’argument addr contient la taille du tampon pointé par data (c’est-à-dire sizeof(sigset_t)).
PTRACE_SETOPTIONS (depuis Linux 2.4.6, consultez les remarques de BOGUES)
Définir les options de ptrace à partir de l'adresse data (addr est ignoré). data est interprété comme un masque d'options, qui est construit à partir des attributs suivants.
PTRACE_O_EXITKILL (depuis Linux 3.8)
Si l'observateur définit cet attribut, un signal SIGKILL sera envoyé à tous les observés quand l'observateur se termine. Cet option est utile pour les gardiens ptrace qui veulent s'assurer que les observés ne peuvent jamais échapper au contrôle de l'observateur.
PTRACE_O_TRACECLONE (depuis Linux 2.5.46)
Arrêter l'observé au prochain clone(2) et commencer automatiquement à suivre le nouveau processus cloné, qui démarrera avec un signal SIGSTOP, ou PTRACE_EVENT_STOP si PTRACE_SEIZE est utilisé. Un waitpid(2) par l'observateur renverra une valeur status comme

  status>>8 == (SIGTRAP | (PTRACE_EVENT_CLONE<<8))
    

Le PID du nouveau processus peut être récupéré avec PTRACE_GETEVENTMSG.

Cette option peut ne pas intercepter tous les appels clone(2). Si l'observé appelle clone(2) avec l'attribut CLONE_VFORK, PTRACE_EVENT_VFORK sera envoyé si PTRACE_O_TRACEVFORK est utilisé. Sinon, si l'observé appelle clone(2) avec SIGCHLD comme signal de terminaison, PTRACE_EVENT_FORK sera envoyé si PTRACE_O_TRACEFORK est utilisé.
PTRACE_O_TRACEEXEC (depuis Linux 2.5.46)
Arrêter l'observé au prochain execve(2). Un waitpid(2) par l'observateur renverra une valeur status comme

  status>>8 == (SIGTRAP | (PTRACE_EVENT_EXEC<<8))
    

Si le thread en cours d'exécution n'est pas un leader de groupe de threads, l'identifiant de thread est réinitialisé à l'identifiant du leader de groupe de threads avant cet arrêt. Depuis Linux 3.0, le premier identifiant de thread peut être récupéré avec PTRACE_GETEVENTMSG.

PTRACE_O_TRACEEXIT (depuis Linux 2.5.60)
Arrêter l'observé à la terminaison. Un waitpid(2) par l'observateur renverra une valeur status comme

  status>>8 == (SIGTRAP | (PTRACE_EVENT_EXIT<<8))
    

L'état de fin de l'observé peut être récupéré avec PTRACE_GETEVENTMSG.

L'observé est arrêté tôt dans la terminaison du processus, alors que les registres sont toujours disponibles, ce qui permet au processus utilisant ptrace() de voir où la terminaison s'est produite, alors que la notification de terminaison normale a lieu à la fin de cette terminaison. Même si le contexte est disponible, l'observateur ne peut pas empêcher la terminaison à ce moment là.
PTRACE_O_TRACEFORK (depuis Linux 2.5.46)
Arrêter l'observé au prochain fork(2) et commencer automatiquement à suivre le nouveau processus créé, qui démarrera avec un signal SIGSTOP, ou PTRACE_EVENT_STOP si PTRACE_SEIZE est utilisé. Un waitpid(2) par l'observateur renverra une valeur status comme

  status>>8 == (SIGTRAP | (PTRACE_EVENT_FORK<<8))
    

Le PID du nouveau processus peut être récupéré avec PTRACE_GETEVENTMSG.

PTRACE_O_TRACESYSGOOD (depuis Linux 2.4.6)
Lors des interceptions d'appel système, mettre à 1 le bit 7 du numéro de signal (envoyer SIGTRAP | 0x80). Cela permet au processus utilisant ptrace() de faire la différence entre les interceptions normales et celles provoquées par un appel système. PTRACE_O_TRACESYSGOOD peut ne pas fonctionner sur toutes les architectures.
PTRACE_O_TRACEVFORK (depuis Linux 2.5.46)
Arrêter l'observé au prochain vfork(2) et commencer automatiquement à suivre le nouveau processus créé, qui démarrera avec un signal SIGSTOP, ou PTRACE_EVENT_STOP si PTRACE_SEIZE est utilisé. Un waitpid(2) par l'observateur renverra une valeur status comme

  status>>8 == (SIGTRAP | (PTRACE_EVENT_VFORK<<8))
    

Le PID du nouveau processus peut être récupéré avec PTRACE_GETEVENTMSG.

PTRACE_O_TRACEVFORKDONE (depuis Linux 2.5.60)
Arrêter l'observé à la fin du prochain vfork(2). Un waitpid(2) par l'observateur renverra une valeur status comme

  status>>8 == (SIGTRAP | (PTRACE_EVENT_VFORK_DONE<<8))
    

Le PID du nouveau processus peut (depuis Linux 2.6.18) être récupéré avec PTRACE_GETEVENTMSG.

PTRACE_GETEVENTMSG (depuis Linux 2.5.46)
Récupérer un message (dans un unsigned long) concernant l'événement ptrace qui vient d'arriver, en le plaçant à l'adresse data de l'observateur. Pour PTRACE_EVENT_EXIT, il s'agit du code de retour de l'observé. Pour PTRACE_EVENT_FORK, PTRACE_EVENT_VFORK, PTRACE_EVENT_VFORK_DONE et PTRACE_EVENT_CLONE, il s'agit du PID du nouveau processus (addr est ignoré).
PTRACE_CONT
Redémarrer l'observé arrêté. Si data est non nul, il est interprété comme un numéro de signal à distribuer à l'observé ; sinon aucun signal n'est distribué. L'observateur peut ainsi contrôler si un signal envoyé à l'observé doit lui être distribué ou non (addr est ignoré).
PTRACE_SYSCALL, PTRACE_SINGLESTEP
Redémarrer l'observé arrêté comme pour PTRACE_CONT, mais en s'arrangeant pour qu'il soit arrêté à la prochaine entrée ou sortie d'un appel système, ou après la prochaine instruction, respectivement (l'observé sera aussi arrêté par l'arrivée d'un signal). Du point de vue de l'observateur, l'observé semblera être arrêté par SIGTRAP. Ainsi, pour PTRACE_SYSCALL l'idée est d'inspecter les arguments de l'appel système au premier arrêt puis de faire un autre PTRACE_SYSCALL et d'inspecter la valeur de retour au second arrêt. Le paramètre data est interprété comme pour PTRACE_CONT (addr est ignoré).
PTRACE_SYSEMU, PTRACE_SYSEMU_SINGLESTEP (depuis Linux 2.6.14)
Pour PTRACE_SYSEMU, continuer puis s'arrêter lors du prochain appel système, qui ne sera pas exécuté. Pour PTRACE_SYSEMU_SINGLESTEP, faire la même chose, mais exécuter pas à pas s'il ne s'agit pas d'un appel système. Cette fonction est utilisée par des programmes comme User Mode Linux, qui veulent émuler tous les appels système de l'observé. Le paramètre data est interprété comme pour PTRACE_CONT. L'argument addr est ignoré. Ces requêtes ne sont pour l'instant disponibles que sur x86.
PTRACE_LISTEN (depuis Linux 3.4)
Redémarrer l'observé arrêté, mais en l'empêchant de s'exécuter. L'état résultant de l'observé est similaire a celui d'un processus qui a été arrêté par un SIGSTOP (ou autre signal d'arrêt). Consultez la sous-section Arrêt-groupe pour des renseignements supplémentaires. PTRACE_LISTEN ne fonctionne que sur les observés attachés par PTRACE_SEIZE.
PTRACE_KILL
Envoyer à l'observé un signal SIGKILL pour le terminer (addr et data sont ignorés).
Cette opération est obsolète, ne l'utilisez pas. À la place, envoyez un SIGKILL directement en utilisant kill(2) ou tgkill(2). Le problème avec PTRACE_KILL est qu'il nécessite que l'observé soit en arrêt-distribution-signal, sinon cela risque de ne pas fonctionner (c'est-à-dire risque de se terminer avec succès sans tuer l'observé). En revanche, envoyer SIGKILL directement n'est pas concerné par cette limite.
PTRACE_INTERRUPT (depuis Linux 3.4)
Arrêter un observé. Si l’observé est en cours d’exécution ou en sommeil dans l’espace utilisateur et que PTRACE_SYSCALL est effectif, l’appel système est interrompu et l'arrêt-sortie-appel-système est signalé (l’appel système interrompu est redémarré quand l’observé est redémarré). Si l’observé avait déjà été arrêté par un signal et que PTRACE_LISTEN lui avait été envoyé, l’observé s’arrête avec PTRACE_EVENT_STOP et WSTOPSIG(status) renvoie le signal d’arrêt. Si n’importe quel autre arrêt-ptrace est créé en même temps (par exemple, si un signal est envoyé à l’observé), cet arrêt-ptrace arrive. Si rien de ce qui précède ne s’applique (par exemple si l’observé est en cours d’exécution en espace utilisateur), il s’arrête avec PTRACE_EVENT_STOP avec WSTOPSIG(status) == SIGTRAP. PTRACE_INTERRUPT ne fonctionne que sur les observés attachés par PTRACE_SEIZE.
PTRACE_ATTACH
Attacher le processus numéro pid, pour le suivre. L'observé va recevoir un SIGSTOP, mais il ne sera peut-être pas arrêté tout de suite, utilisez waitid(2) pour attendre son arrêt. Consultez la sous-section Attachement et détachement pour obtenir de plus amples renseignements (addr et data sont ignorés).
PTRACE_SEIZE (depuis Linux 3.4)
Attacher au processus indiqué par pid, faisant de lui l'observé du processus appelant. Contrairement à PTRACE_ATTACH, PTRACE_SEIZE n'arrête pas le processus. Seul un processus sous PTRACE_SEIZE peut accepter les commandes PTRACE_INTERRUPT et PTRACE_LISTEN. addr doit être zéro. data contient un masque d'options de ptrace à activer immédiatement.
PTRACE_DETACH
Relancer l'observé arrêté comme avec PTRACE_CONT, mais en commençant par s'en détacher. Sous Linux un observé peut être détaché ainsi quelque soit la méthode employée pour démarrer le suivi (addr est ignoré).

Mort sous ptrace

Quand un processus (éventuellement multithreadé) reçoit un signal pour le tuer (un dont la disposition est configurée à SIG_DFL et dont l'action par défaut est de tuer le processus), tous les threads se terminent. Chaque observé signale sa mort à son ou ses observateurs. La notification de cet événement est distribuée par waitpid(2).

Remarquez que le signal tueur provoquera d'abord un arrêt-distribution-signal (sur un seul observé) et, seulement après être injecté par l'observateur (ou après être envoyé à un thread qui n'est pas suivi), la mort du signal arrivera sur tous les observés d'un processus multithreadé (le terme « arrêt-distribution-signal » est expliqué plus bas).

SIGKILL ne génère pas d'arrêt-distribution-signal et l'observateur ne peut par conséquent pas le supprimer. SIGKILL tue même à l'intérieur des appels systèmes (arrêt-sortie-appel-système n'est pas créé avant la mort par SIGKILL). L'effet direct est que SIGKILL tue toujours le processus (tout ses threads), même si certains threads du processus sont suivis avec ptrace.

Quand l'observé appelle _exit(2), il signale sa mort à son observateur. Les autres threads ne sont pas concernés.

Quand n'importe quel thread exécute exit_group(2), tous les observés de son groupe de threads signalent leur mort à leur observateur.

Si l'option PTRACE_O_TRACEEXIT est active, PTRACE_EVENT_EXIT arrivera avant la mort réelle. Cela s'applique aux terminaisons avec exit(2), exit_group(2) et aux morts de signal (sauf SIGKILL), et lorsque les threads sont démolis par execve(2) dans un processus multithreadé.

L'observateur ne peut pas assumer que l'observé arrêté-ptrace existe. L'observé risque de mourir avant d'être arrêté dans plusieurs cas (comme avec SIGKILL). Par conséquent, le tracé doit être préparé pour traiter une erreur ESRCH sur n'importe quel opération ptrace. Malheureusement, la même erreur est renvoyée si l'observé existe mais n'est pas arrêté-ptrace (pour les commandes qui nécessitent un observé arrêté), ou s'il n'est pas suivi par le processus qui a envoyé l'appel ptrace. L'observateur doit garder une trace de l'état arrêté ou en fonctionnement de l'observé, et interpréter ESRCH comme « l'observé s'est achevé de manière inattendue » seulement s'il sait que l'observé est effectivement entré en arrêt-ptrace. Remarquez qu'il n'est pas garanti que waitpid(WNOHANG) signale de façon fiable l'état de mort de l'observé si une opération ptrace renvoie ESRCH. waitpid(WNOHANG) pourrait plutôt renvoyer 0. Autrement dit, l'observé pourrait « ne pas être encore mort », mais déjà refuser des requêtes ptrace.

L'observateur ne peut pas assumer que l'observé finit toujours sa vie en signalant WIFEXITED(status) ou WIFSIGNALED(status) ; dans certains cas ça n'arrive pas. Par exemple si un thread différent du leader de groupe de threads fait un execve(2), il disparaît ; son PID ne sera plus jamais vu, tous les arrêts suivants de ptrace seront signalés sous le PID du leader de groupe de threads.

États arrêtés

Deux états existent pour un observé : en cours d'exécution ou à l'arrêt. Du point de vue de ptrace, un observé qui est bloqué dans un appel système (comme read(2), pause(2), etc.) est néanmoins considéré en cours d’exécution, même si l’observé est bloqué depuis longtemps. L’état de l’observé après PTRACE_LISTEN est en quelque sorte une zone d’ombre : il n’est dans aucun arrêt-ptrace (les commandes ptrace n’auront aucun effet sur lui et il distribuera des notifications waitpid(2)), mais il pourrait aussi être considéré « arrêté » parce qu’il n’est pas en train d’exécuter des instructions (pas de programmation) et, s’il était en arrêt-groupe avant PTRACE_LISTEN, il ne répondra pas aux signaux avant de recevoir SIGCONT.

De nombreuses sortes d'états sont possibles quand l'observé est arrêté, et les discussions dans ptrace sont souvent confondues. Par conséquent, l'utilisation de termes précis est importante.

Dans cette page de manuel, tous les états d'arrêt dans lesquels l'observé est prêt à accepter des commandes ptrace de l'observateur sont appelés arrêts-ptrace. Les arrêts-ptrace peuvent ensuite être sous-divisés en arrêt-distribution-signal, arrêt-groupe, arrêt-appel-système, etc. Ces états d'arrêt sons décrits en détail ci-dessous.

Lorsque l'observé en cours d'exécution entre en arrêt-ptrace, il avise son observateur en utilisant waitpid(2) (ou un des autres appels système « wait »). La plupart de cette page de manuel suppose que l'observateur attend avec :

pid = waitpid(pid_ou_moins_1, &status, __WALL);

Les observés arrêtés-ptrace sont signalés comme renvoyés avec un pid strictement positif et WIFSTOPPED(status) vrai.

L'attribut __WALL ne contient pas les attributs WSTOPPED et WEXITED, mais implique leur fonctionnalité.

La configuration de l'attribut WCONTINUED en appelant waitpid(2) n'est pas conseillée : l'état « exécuté » est relatif au processus et l'utiliser peut embrouiller le vrai père de l'observé.

Utiliser l'attribut WNOHANG pourrait forcer waitpid(2) à renvoyer 0 (« aucun résultat d'attente encore disponible ») même si l'observateur sait qu'il devrait y avoir une notification. Exemple :

    errno = 0;
    ptrace(PTRACE_CONT, pid, 0L, 0L);
    if (errno == ESRCH) {
        /* l'observé est mort */
        r = waitpid(observé, &status, __WALL | WNOHANG);
        /* r peut encore être 0 ici ! */
    }

Les sortes d'arrêts-ptrace suivants existent : arrêts-distribution-signal, arrêts-groupe, arrêts PTRACE_EVENT et arrêts-appel-système. Ils sont signalés par waitpid(2) avec WIFSTOPPED(status) vrai. Ils peuvent être distingués en examinant la valeur status>>8, et en cas d'ambiguïté dans cette valeur, en faisant une requête PTRACE_GETSIGINFO (remarque : la macro WSTOPSIG(status) ne peut pas être utilisée pour réaliser cet examen, car elle renvoie la valeur (status>>8) & 0xff.)

Arrêt-distribution-signal

Quand un processus (éventuellement multithreadé) reçoit n'importe quel signal sauf SIGKILL, le noyau choisi un thread arbitraire pour traiter le signal (si le signal est créé avec tgill(2), le thread cible peut être explicitement choisi par l'appelant). Si le thread choisi est suivi, il entre en arrêt-distribution-signal. À ce moment là, le signal n'est pas encore distribué au processus, et peut être supprimé par l'observateur. Si l'observateur ne supprime pas le signal, il passe le signal à l'observé lors de la requête suivante de redémarrage de ptrace. Cette deuxième étape de distribution de signal est appelée injection de signal dans cette page de manuel. Remarquez que si le signal est bloqué, l'arrêt-distribution-signal n'arrive pas avant que le signal soit débloqué, à l'exception habituelle que SIGSTOP ne peut pas être bloqué.

L'arrêt-distribution-signal est respecté par l'observateur tant que waitpid(2) retourne avec WIFSTOPPED(status) vrai, avec le signal renvoyé par WSTOPSIG(status). Si le signal est SIGTRAP, cela pourrait être un arrêt-ptrace de nature différente ; consultez les sections Arrêts-appel-système et execve(2) sous ptrace plus bas pour obtenir de plus amples précisions. Si WSTOPSIG(status) renvoie un signal d'arrêt, cela pourrait être un arrêt-groupe ; voir ci-dessous.

Injection et suppression de signal

Après un arrêt-distribution-signal respecté par l'observateur, l'observateur devrait redémarrer l'observé avec l'appel

ptrace(PTRACE_redémarrer, pid, 0, sig)

PTRACE_redémarrer est une des requêtes ptrace de redémarrage. Si sig est 0, alors aucun signal n'est distribué. Sinon, le signal sig est distribué. Cette opération est appelée injection de signal dans cette page de manuel, pour la distinguer de l'arrêt-distribution-signal.

La valeur de sig peut être différente de celle de WSTOPSIG(status) : l'observateur peut provoquer l'injection d'un autre signal.

Remarquez qu'un signal supprimé provoque toujours un retour prématuré des appels système. Dans ce cas, les appels système seront redémarrés : l'observateur forcera l'observé à réexécuter l'appel système interrompu (ou l'appel système restart_syscall(2) pour les quelques appels système qui utilisent un autre mécanisme de redémarrage) si l'observateur utilise PTRACE_SYSCALL. Même les appels système (comme poll(2)) qui ne sont pas redémarrables après signal sont redémarré après la suppression du signal ; cependant, des bogues du noyau existent et certains appels système échouent avec EINTR même si aucun signal observable n'est injecté dans l'observé.

Lors du redémarrage des commandes ptrace émises dans d'autres arrêts-ptrace qu'arrêt-distribution-signal, l'injection de signal n'est pas garantie, même si sig est non nul. Aucune erreur n'est signalée ; un sig non nul risque simplement d'être ignoré. Les utilisateurs de ptrace ne devraient pas essayer de « créer un nouveau signal » de cette façon : utilisez plutôt tgkill(2).

Le fait que des requêtes d'injection de signal puissent être ignorées lors du redémarrage de l'observé après des arrêts ptrace qui ne sont pas des arrêts-distribution-signal est une source de confusion pour les utilisateurs de ptrace. Un scénario typique est que l'observateur remarque un arrêt-groupe, le confonde avec un arrêt-distribution-signal, et redémarre l'observé avec

ptrace(PTRACE_restart, pid, 0, stopsig)

dans le but d'injecter stopsig, alors que stopsig sera ignoré et que l'observé continuera de fonctionner.

Le signal SIGCONT a pour effet de bord de réveiller (tous les threads d'un processus arrêté-groupe. Cet effet de bord arrive avant un arrêt-distribution-signal. L'observateur ne peut pas supprimer cet effet de bord (il ne peut que supprimer l'injection de signal, qui force seulement le gestionnaire de SIGCONT à ne pas être exécuté dans l'observé, si un gestionnaire de ce type est installé). En fait, le réveil depuis un arrêt-groupe pourrait être suivi par un arrêt-distribution-signal pour le ou les signaux différents de SIGCONT, s'ils étaient en attente quand SIGCONT a été distribué. Autrement dit, SIGCONT pourrait ne pas être le premier signal remarqué par l'observé après avoir été envoyé.

L'arrêt de signaux force (tous les threads d')un processus à entrer en arrêt-groupe. Cet effet de bord arrive après une injection de signal, et peut par conséquent être supprimé par l'observateur.

Sous Linux 2.4 et les versions précédentes, le signal SIGSTOP ne pouvait pas être injecté.

PTRACE_GETSIGINFO peut être utilisé pour récupérer une structure siginfo_t qui correspond au signal distribué. PTRACE_SETSIGINFO pourrait être utilisé pour le modifier. Si PTRACE_SETSIGINFO a été utilisé pour modifier siginfo_t, le champ si_signo et le paramètre sig de la commande de redémarrage doivent correspondre, sinon le résultat est indéfini.

Arrêt-groupe

Quand un processus (éventuellement multithreadé) reçoit un signal d'arrêt, tous les threads s'arrêtent. Si certains threads sont suivis, ils entrent en arrêt-groupe. Remarquez que le signal d'arrêt provoquera d'abord un arrêt-distribution-signal (sur un seul observé) et, seulement après avoir été injecté par l'observateur (ou après avoir été envoyé à un thread qui n'est pas suivi), l'arrêt-groupe sera initié sur tous les observés d'un processus multithreadé. Comme d'habitude, tous les observés signalent leur arrêt-groupe séparément à l'observateur correspondant.

L'arrêt-groupe est respecté par l'observateur tant que waitpid(2) retourne avec WIFSTOPPED(status) vrai, avec le signal d'arrêt disponible par l'intermédiaire de WSTOPSIG(status). Le même résultat est renvoyé par d'autres classes d'arrêts-ptrace, par conséquent la méthode conseillée est de réaliser l'appel

ptrace(PTRACE_GETSIGINFO, pid, 0, &siginfo)

L'appel peut être évité si le signal n'est pas SIGSTOP, SIGTSTP, SIGTTIN ou SIGTTOU ; seuls ces quatre signaux sont des signaux d'arrêt. Si l'observateur voit autre chose, ce ne peut pas être un arrêt-groupe. Sinon, l'observateur doit appeler PTRACE_GETSIGINFO. Si PTRACE_GETSIGINFO échoue avec EINVAL, alors c'est définitivement un arrêt-groupe (d'autres codes d'échec sont possibles, comme ESRCH (« pas de processus de ce type ») si un SIGKILL a tué l'observé).

Si l’observé était attaché en utilisant PTRACE_SEIZE, un arrêt-groupe est indiqué par PTRACE_EVENT_STOP : status>>16 == PTRACE_EVENT_STOP. Cela permet la détection d’arrêts-groupe sans nécessiter d’appel PTRACE_GETSIGINFO supplémentaire.

Depuis Linux 2.6.38, après que l'observateur a vu l'arrêt-ptrace de l'observé et jusqu'à ce qu'il le redémarre ou le tue, l'observé ne fonctionnera pas, et n'enverra pas de notification (sauf mort par SIGKILL) à l'observateur, même si l'observateur entre dans un autre appel waitpid(2).

Le comportement du noyau décrit dans le paragraphe précédent pose un problème avec la gestion transparente de signaux d'arrêt. Si l'observateur redémarre l'observé après un arrêt-groupe, le signal d'arrêt est effectivement ignoré — l'observé ne reste pas arrêté, il fonctionne. Si l'observateur ne redémarre pas l'observé avant d'entrer dans le prochain waitpid(2), les signaux SIGCONT suivants ne seront pas signalés à l'observateur ; cela pourrait forcer des signaux SIGCONT à être sans effet sur l'observé.

Depuis Linux 3.4, une méthode permet d'éviter ce problème : à la place de PTRACE_CONT, une commande PTRACE_LISTEN peut être utilisée pour redémarrer un observé de façon à ce qu'il ne s'exécute pas, mais attende un nouvel événement qu'il peut signaler à l'aide de waitpid(2) (comme s'il était redémarré par un SIGCONT).

Arrêts PTRACE_EVENT

Si l'observateur configure des options PTRACE_O_TRACE_*, l'observé entrera en arrêts-ptrace appelés arrêts PTRACE_EVENT.

Les arrêts PTRACE_EVENT sont respectés par l'observateur tant que waitpid(2) retourne avec WIFSTOPPED(status), et que WSTOPSIG(status) renvoie SIGTRAP. Un bit supplémentaire est configuré dans l'octet le plus haut du mot d'état : la valeur status>>8 sera

(SIGTRAP | PTRACE_EVENT_truc << 8).

Les événements suivants existent.

PTRACE_EVENT_VFORK
Arrêt avant de revenir de vfork(2) ou clone(2) avec l'attribut CLONE_VFORK. Quand l'observé est continué après cet arrêt, il attendra une sortie ou exécution du fils avant de continuer son exécution (autrement dit, le comportement normal avec vfork(2)).
PTRACE_EVENT_FORK
Arrêt avant de revenir de fork(2) ou clone(2) avec le signal de sortie configuré à SIGCHLD.
PTRACE_EVENT_CLONE
Arrêt avant de revenir de clone(2).
PTRACE_EVENT_VFORK_DONE
Arrêt avant de revenir de vfork(2) ou clone(2) avec l'attribut CLONE_VFORK, mais après que le fils a débloqué son observé par sortie ou exécution.

Pour les quatre arrêts décrits ci-dessus, l'arrêt arrive dans le père (c'est-à-dire l'observé), pas dans le nouveau thread créé. PTRACE_GETEVENTMSG permet de récupérer l'identifiant du nouveau thread.

PTRACE_EVENT_EXEC
Arrêt avant le retour d'execve(2). Depuis Linux 3.0, PTRACE_GETEVENTMSG renvoie le premier identifiant de thread.
PTRACE_EVENT_EXIT
Arrêt avant la sortie (y compris la mort depuis exit_group(2)), la mort du signal ou la sortie provoquée par execve(2) dans un processus multithreadé. PTRACE_GETEVENTMSG renvoie l'état de sortie. Les registres peuvent être examinés (contrairement à quand une « vraie » sortie arrive). L'observé est toujours actif ; il a besoin de PTRACE_CONT ou PTRACE_DETACH pour terminer sa sortie.
PTRACE_EVENT_STOP
Arrêt causé par la commande PTRACE_INTERRUPT, ou arrêt-groupe, ou arrêt-ptrace initial quand un nouveau fils est attaché (seulement s’il est attaché en utilisant PTRACE_SEIZE), ou PTRACE_EVENT_STOP si PTRACE_SEIZE était utilisé.

PTRACE_GETSIGINFO sur les arrêts PTRACE_EVENT renvoie SIGTRAP dans si_signo, avec si_code configuré à (event<<8) | SIGTRAP.

Arrêts-appel-système

Si l'observé était redémarré par PTRACE_SYSCALL, l'observé entre en arrêt-entrée-appel-système juste avant d'entrer dans n'importe quel appel système. Si l'observateur redémarre l'observé avec PTRACE_SYSCALL, l'observé entre en arrêt-sortie-appel-système quand l'appel système est terminé, ou s'il est interrompu par un signal (c'est-à-dire qu'un arrêt-distribution-signal n'arrive jamais entre un arrêt-entrée-appel-système et un arrêt-sortie-appel-système : il arrive après l'arrêt-sortie-appel-système).

D'autres possibilités sont que l'observé pourrait s'arrêter dans un arrêt PTRACE_EVENT, sortir (s'il est entré en _exit(2) ou exit_group(2)), être tué par SIGKILL ou mourir silencieusement (s'il s'agit d'un leader de groupe de threads, que l'execve(2) est arrivé dans un autre thread et que ce thread n'est pas suivi par le même observateur ; cette situation sera abordée plus tard).

Les arrêt-entrée-appel-système et arrêt-sortie-appel-système sont respectés par l'observateur tant que waitpid(2) retourne avec WIFSTOPPED(status) vrai, et que WSTOPSIG(status) donne SIGTRAP. Si l'option PTRACE_O_TRACESYSGOOD était configurée par l'observateur, alors WSTOPSIG(status) donnera la valeur (SIGTRAP | 0x80).

Les arrêts-appel-système peuvent être distingués d'un arrêt-distribution-signal avec SIGTRAP en demandant PTRACE_GETSIGINFO pour les cas suivants.

si_code <= 0
SIGTRAP a été distribué comme résultat d'une action en espace utilisateur, par exemple, un appel système (tgkill(2), kill(2), sigqueue(3), etc.), l'expiration d'un minuteur POSIX, la modification d'état sur une file de messages POSIX où la fin d'une requête d'E/S asynchrone.
si_code == SI_KERNEL (0x80)
SIGTRAP a été envoyé par le noyau.
si_code == SIGTRAP ou si_code == (SIGTRAP|0x80)
C'est un arrêt-appel-système.

Cependant, les arrêts-appel-système arrivent très souvent (deux fois par appel système) et réaliser PTRACE_GETSIGINFO pour chaque arrêt-appel-système pourrait être assez coûteux.

Certaines architectures permettent de distinguer ces cas en examinant les registres. Par exemple, sur x86, rax == -ENOSYS en arrêt-entrée-appel-système. Puisque SIGTRAP (comme tout autre signal) arrive toujours après l'arrêt-sortie-appel-système et que rax ne contient à ce moment presque jamais -ENOSYS, le SIGTRAP ressemble à un « arrêt-appel-système qui n'est pas un arrêt-entrée-appel-système » ; autrement dit, il ressemble à un « arrêt-sortie-appel-système perdu » et peut être détecté de cette façon. Une telle détection est néanmoins fragile, elle est donc a éviter.

L'utilisation de l'option PTRACE_O_TRACESYSGOOD est la méthode conseillée pour distinguer les arrêts-appel-système des autres sortes d'arrêts-ptrace, puisqu'il est fiable et n'induit pas de perte de performances.

Les arrêt-entrée-appel-système et arrêt-sortie-appel-système ne sont pas différentiables l'un de l'autre. L'observateur doit garder une trace de la suite d'arrêts-ptrace afin de ne pas mal interpréter un arrêt-entrée-appel-système comme un arrêt-sortie-appel-système ou vice versa. La règle est que l'arrêt-entrée-appel-système est toujours suivi par un arrêt-sortie-appel-système, un arrêt PTRACE_EVENT ou la mort de l'observé ; aucune autre sorte d'arrêt-ptrace ne peut arriver entre-deux.

Si suite à un arrêt-entrée-appel-système, l'observateur utilise une commande de redémarrage différente de PTRACE_SYSCALL, l'arrêt-sortie-appel-système n'est pas créé.

PTRACE_GETSIGINFO sur les arrêts-appel-système renvoie SIGTRAP dans si_signo, avec si_code configuré à SIGTRAP ou (SIGTRAP | 0x80).

Arrêts PTRACE_SINGLESTEP, PTRACE_SYSEMU, PTRACE_SYSEMU_SINGLESTEP

[Les précisions sur ces types d'arrêts sont encore à documenter.]

Commandes ptrace d'information et de redémarrage

La plupart des commandes ptrace (toutes sauf PTRACE_ATTACH, PTRACE_SEIZE, PTRACE_TRACEME, PTRACE_INTERRUPT et PTRACE_KILL) nécessitent que l'observé soit en arrêt-ptrace, sinon il échoue avec ESRCH.

Quand l'observé est en arrêt-ptrace, l'observateur peut lire et écrire les donnés sur l'observé en utilisant les commandes d'information. Ces commandes laissent l'observé en état arrêté-ptrace :

    ptrace(PTRACE_PEEKTEXT/PEEKDATA/PEEKUSER, pid, addr, 0);
    ptrace(PTRACE_POKETEXT/POKEDATA/POKEUSER, pid, addr, long_val);
    ptrace(PTRACE_GETREGS/GETFPREGS, pid, 0, &struct);
    ptrace(PTRACE_SETREGS/SETFPREGS, pid, 0, &struct);
    ptrace(PTRACE_GETREGSET, pid, NT_foo, &iov);
    ptrace(PTRACE_SETREGSET, pid, NT_foo, &iov);
    ptrace(PTRACE_GETSIGINFO, pid, 0, &siginfo);
    ptrace(PTRACE_SETSIGINFO, pid, 0, &siginfo);
    ptrace(PTRACE_GETEVENTMSG, pid, 0, &long_var);
    ptrace(PTRACE_SETOPTIONS, pid, 0, PTRACE_O_flags);

Remarquez que certaines erreurs ne sont pas signalées. Par exemple, la configuration d'informations de signal (siginfo) pourrait être sans effet pour certains arrêts-ptrace, alors que l'appel pourrait-être réussi (en renvoyant 0 et sans définir errno) ; la demande de PTRACE_GETEVENTMSG pourrait réussir et renvoyer une quelconque valeur aléatoire si l'arrêt-ptrace actuel n'est pas documenté comme renvoyant un message d'événement significatif.

L'appel

ptrace(PTRACE_SETOPTIONS, pid, 0, PTRACE_O_flags);

ne concerne qu'un observé. Les attributs actuels de l'observé sont remplacés. Les attributs sont hérités par les nouveaux observés créés et « attachés automatiquement » à l'aide d'options PTRACE_O_TRACEFORK, PTRACE_O_TRACEVFORK ou PTRACE_O_TRACECLONE actives.

Un autre groupe de commandes peut redémarrer l'observé arrêté ptrace. Ils sont de la forme :

ptrace(cmd, pid, 0, sig);

cmd est PTRACE_CONT, PTRACE_LISTEN, PTRACE_DETACH, PTRACE_SYSCALL, PTRACE_SINGLESTEP, PTRACE_SYSEMU ou PTRACE_SYSEMU_SINGLESTEP. Si l'observé est en arrêt-distribution-signal, sig est le signal à injecter (s'il est non nul). Sinon, sig pourrait être ignoré (lors du redémarrage d'un observé depuis un arrêt-ptrace différent d'un arrêt-distribution-signal, il est conseillé de toujours passer 0 à sig).

Attachement et détachement

Un thread peut être attaché à l'observateur en utilisant l'appel

ptrace(PTRACE_ATTACH, pid, 0, 0);

ou

ptrace(PTRACE_SEIZE, pid, 0, PTRACE_O_flags);

PTRACE_ATTACH envoie aussi SIGSTOP à ce thread. Si l'observateur veut que SIGSTOP soit sans effet, il doit le supprimer. Remarquez que si d'autres signaux sont envoyés en même temps à ce thread pendant l'attachement, l'observateur pourrait voir l'observé entrer en arrêt-distribution-signal avec d'autres signaux d'abord ! Ces signaux sont d'habitude réinjectés jusqu'à ce que SIGSTOP soit vu, puis l'injection SIGSTOP est supprimée. Le bogue de conception ici est qu'un attachement ptrace et un SIGSTOP distribués en même temps peuvent entrer en compétition et le SIGSTOP risque d'être perdu.

Puisque l'attachement envoie SIGSTOP et que l'observateur le supprime normalement, cela risque de forcer le retour d'un EINTR perdu de l'appel système en cours d'exécution dans l'observé, tel que c'est décrit dans la section Injection et suppression de signal.

Depuis Linux 3.4, PTRACE_SEIZE peut être utilisé à la place de PTRACE_ATTACH. PTRACE_SEIZE n'arrête pas le processus attaché. Si vous devez l'arrêter après attachement (ou à n'importe quel autre moment) sans lui envoyer de signal du tout, utilisez la commande PTRACE_INTERRUPT.

La requête

ptrace(PTRACE_TRACEME, 0, 0, 0);

transforme le thread appelant en observé. L'appel continue d'être exécuté (n'entre pas en arrêt-ptrace). PTRACE_TRACEME est habituellement suivi avec

raise(SIGSTOP);

et permet au père (qui est maintenant l'observateur) de respecter l'arrêt-distribution-signal.

Si les options PTRACE_O_TRACEFORK, PTRACE_O_TRACEVFORK ou PTRACE_O_TRACECLONE font effet, alors les fils respectivement créés par vfork(2) ou clone(2) avec l'attribut CLONE_VFORK, vfork(2) ou clone(2) avec le signal de sortie configuré à SIGCHLD, et d'autres sortes de clone(2), sont automatiquement attachés au même observateur qui à suivi leur père. SIGSTOP est distribué aux fils, les forçant à entrer en arrêt-distribution-signal après être sortis de l'appel système qu'ils ont créé.

Le détachement de l'observé est réalisé par :

ptrace(PTRACE_DETACH, pid, 0, sig);

PTRACE_DETACH est une opération de redémarrage ; par conséquent elle nécessite que l'observé soit en arrêt-ptrace. Si l'observé est en arrêt-distribution-signal, un signal peut être injecté. Sinon, le paramètre sig pourrait être silencieusement ignoré.

Si l'observé est en cours d'exécution quand l'observateur veut le détacher, la solution habituelle est d'envoyer SIGSTOP (en utilisant tgkill(2), pour s'assurer qu'il va au bon thread), d'attendre que l'observé s'arrête en arrêt-distribution-signal pour SIGSTOP et ensuite de le détacher (en supprimant l'injection SIGSTOP). Un bogue de conception est que l'observé pourrait entrer dans d'autres arrêts-ptrace et avoir besoin d'être redémarré et attendre encore, jusqu'à ce que SIGSTOP soit vu. Encore une autre complication est de s'assurer que l'observé n'est pas déjà arrêté-ptrace, parce qu'aucune distribution de signal n'arrive tant qu'il l'est — pas même SIGSTOP.

Si l'observateur meurt, tous les observés sont automatiquement détachés et redémarrés, sauf s'il étaient en arrêt-groupe. Le gestion de redémarrage depuis un arrêt-groupe est en ce moment dysfonctionnelle, mais le comportement « prévu » est de laisser les observés arrêtés et d'attendre un SIGCONT. Si l'observé est redémarré depuis un arrêt-distribution-signal, le signal en attente est injecté.

execve(2) sous ptrace

Quand un thread de processus multithreadé appelle execve(2), le noyau détruit tous les autres threads du processus, et réinitialise l'identifiant de thread du thread exécuté à l'identifiant de groupe de threads (PID) (ou, pour le présenter autrement, quand un processus multithreadé fait un execve(2), à la fin de l'appel, il apparaît comme si l'execve(2) s'était appliqué au leader de groupe de threads, quelque soit le thread qui a fait execve(2)). Cette réinitialisation de l'identifiant de thread semble est très déroutante pour les observateurs.
  • Tous les autres threads s'arrêtent en arrêt PTRACE_EVENT_EXIT, si l'option PTRACE_O_TRACEEXIT était activée. Alors tous les autres threads sauf le leader de groupe de threads signalent leur mort comme s'il s'étaient terminés par l'intermédiaire de _exit(2) avec un code de retour 0.
  • L'observé en cours d'exécution modifie son identifiant de thread pendant qu'il est dans l'execve(2) (rappelez-vous que, sous ptrace, le « pid » renvoyé par waitpid(2) ou fourni dans les appels ptrace, est l'identifiant de thread de l'observé). Ainsi, l'identifiant de thread de l'observé est réinitialisé pour être le même que son identifiant de processus (PID), qui est le même que l'identifiant de thread du leader de groupe de threads.
  • Ensuite un arrêt PTRACE_EVENT_EXEC arrive, si l'option PTRACE_O_TRACEEXEC était activée.
  • Si le leader de groupe de threads a signalé son arrêt PTRACE_EVENT_EXIT pendant ce temps, le leader de thread mort à l'air de « revenir de nulle part » du point de vue de l'observateur (remarque : le leader de groupe de threads ne signale pas sa mort à l'aide de WIFEXITED(status) tant qu'au moins un autre thread est en vie. Cela enlève la possibilité à l'observateur de le voir mourir puis réapparaître). Si le leader de groupe de threads était encore en vie, cela pourrait être vu par l'observateur comme si le leader de groupe revenait d'un autre appel système que celui dans lequel il était entré, ou même « revenait d'un appel système même s'il n'y avait pas d'appel système ». Si le leader de groupe de threads n'était pas suivi (ou était suivi par un autre observateur), alors pendant execve(2) il apparaîtra comme s'il était devenu un observé de l'observateur de l'observé en cours d'exécution.

Tous les effets précédents sont des artifices de la modification d'identifiant de thread de l'observé.

L'option PTRACE_O_TRACEEXEC est l'outil conseillé pour s'occuper de cette situation. D'abord, elle active l'arrêt PTRACE_EVENT_EXEC, qui arrive avant le retour d'execve(2). Dans cet arrêt, l'observateur peut utiliser PTRACE_GETEVENTMSG pour récupérer l'ancien identifiant de thread de l'observé (cette fonctionnalité a été introduite avec Linux 3.0). Ensuite, l'option PTRACE_O_TRACEEXEC désactive la création obsolète de SIGTRAP dans execve(2).

Quand l'observé reçoit une notification d'arrêt PTRACE_EVENT_EXEC, il est garanti qu'à part cet observé et le leader de groupe de threads, aucun autre thread du processus n'est en vie.

Lors de la réception d'une notification d'arrêt PTRACE_EVENT_EXEC, l'observateur devrait nettoyer toutes ses structures de données internes décrivant les threads de ce processus et ne garder qu'une seule structure de données — celle qui décrit l'unique observé en cours d'exécution, avec

identifiant de thread == identifiant de groupe de threads == identifiant de processus.

Par exemple, soient deux threads qui appellent execve(2) en même temps :

*** arrêt-entrée-appel-système obtenu dans le thread 1 : **
PID1 execve("/bin/truc", "truc" <pas terminé…>
*** PTRACE_SYSCALL émis pour le thread 1 **
*** arrêt-entrée-appel-système obtenu dans le thread 2 : **
PID2 execve("/bin/bidule", "bidule" <pas terminé…>
*** PTRACE_SYSCALL émis pour le thread 2 **
*** PTRACE_EVENT_EXEC obtenu pour PID0, PTRACE_SYSCALL émis **
*** arrêt-sortie-appel-système obtenu pour PID0 : **
PID0 <… retour d'execve> )             = 0

Si l'option PTRACE_O_TRACEEXEC n'est pas effective pour l'observé en cours d'exécution, le noyau distribue un SIGTRAP supplémentaire à l'observé après le retour d'execve(2). C'est un signal normal (similaire à celui qui peut être créé par kill -TRAP), pas une sorte spéciale d'arrêt-ptrace. L'utilisation de PTRACE_GETSIGINFO pour ce signal renvoie si_code configuré à 0 (SI_USER). Ce signal pourrait être bloqué par un masque de signal, et pourrait ainsi être distribué (bien) plus tard.

Normalement, l'observé (par exemple strace(1)) ne voudrait pas montrer ce signal SIGTRAP post-execve supplémentaire à l'utilisateur, et voudrait supprimer sa distribution à l'observé (si SIGTRAP est configuré à SIG_DFL, s'est un signal tueur). Cependant, déterminer quel est le SIGTRAP à supprimer n'est pas simple. La configuration de l'option PTRACE_O_TRACEEXEC et par conséquent la suppression du SIGTRAP supplémentaire est l'approche conseillée.

Vrai père

L'interface de programmation de ptrace utilise (parfois mal) la norme UNIX de signalement de père ou fils par l'intermédiaire de waitpid(2). Cela a régulièrement forcé le vrai père du processus à arrêter de recevoir plusieurs sortes de notifications de waitpid(2) quand le processus fils est suivi par un autre processus.

Nombreux bogues de ce type ont été corrigés, mais il en reste encore beaucoup dans Linux 2.6.38. Consultez la section BOGUES ci dessous.

Depuis Linux 2.6.38, ce qui suit est censé fonctionner correctement :

*
l'exécution ou la mort par signal sont d'abord signalées à l'observateur, puis, quand l'observateur consomme le résultat de waitpid(2), au vrai père (au vrai père seulement quand l'intégralité du processus multithreadé se termine). Si l'observateur et le vrai père sont le même processus, le signalement n'est envoyé qu'une fois.

VALEUR RENVOYÉE

En cas de succès, la requête PTRACE_PEEK* renvoie les données demandées (mais consultez les NOTES), alors que les autres requêtes renvoient zéro.

ptrace() renvoie -1 en cas d'échec en remplissant errno avec le code d'erreur. Comme la valeur renvoyée par une requête PTRACE_PEEK* peut légitimement être -1, l'appelant doit effacer errno avant l'appel, et ensuite le vérifier pour savoir si une erreur s'est produite.

ERREURS

EBUSY
(i386 seulement) Une erreur est survenue lors de l'allocation ou de la libération d'un registre de débogage.
EFAULT
Tentative de lire ou écrire dans une zone mémoire non valable de l'observateur ou de l'observé, probablement parce que la zone n'était pas projetée ou accessible. Malheureusement sous Linux, certaines variantes de cette erreur déclencheront EIO ou EFAULT plus ou moins arbitrairement.
EINVAL
Tentative d'utiliser une option non valable.
EIO
La requête request n'est pas valable ou une tentative de lecture ou d'écriture dans une zone non valable de mémoire de l'observateur ou de l'observé a eu lieu. Un problème d'alignement a aussi pu survenir sur une frontière de mot, ou une tentative de redémarrage en envoyant un signal non valable.
EPERM
Le processus indiqué ne peut pas être suivi. Cela peut être dû à un manque de privilège de l'observateur (la capacité nécessaire est CAP_SYS_PTRACE). Les processus non privilégiés ne peuvent pas suivre les processus auxquels ils ne peuvent envoyer de signal, ou ceux qui s'exécutent Set-UID/Set-GID. En outre, le processus visé peut être déjà suivi, ou (sur les noyaux antérieurs à 2.6.26) être init(8) (le processus numéro 1).
ESRCH
Le processus indiqué n'existe pas, ou n'est pas suivi par l'appelant, ou n'est pas arrêté (pour les requêtes qui ont besoin d'un observé arrêté).

CONFORMITÉ

SVr4, BSD 4.3.

NOTES

Bien que les arguments de ptrace() soient interprétés comme dans le prototype donné, la bibliothèque glibc déclare ptrace comme une fonction variadique où seul l'argument request est corrigé. Il vaut mieux toujours fournir quatre arguments, même si l'opération demandée ne les utilise pas, en configurant les arguments non utilisés ou ignorés à 0L ou (void *) 0.

L'interface de programmation de l'appel système est différente pour les requêtes PTRACE_PEEKTEXT, PTRACE_PEEKDATA et PTRACE_PEEKUSER : elles stockent le résultat à l’adresse indiquée par le paramètre data, et la valeur de retour est l’attribut d’erreur. La fonction glibc encapsulant cet appel fournit une interface détaillée dans la section DESCRIPTION ci-dessus, et le résultat qu'elle renvoie est le résultat de l'appel système.

Dans les noyaux antérieurs à 2.6.26, init(8), le processus numéro 1, ne peut pas être suivi.

La disposition du contenu de la mémoire et de la zone USER dépendent du système d'exploitation et de l'architecture. Le décalage fourni et les données renvoyées peuvent ne pas correspondre entièrement avec la définition d'une structure struct user.

La taille d'un mot (« word ») est déterminée par la version du système d'exploitation (par exemple 32 bits pour Linux 32 bits).

Cette page documente le fonctionnement actuel de ptrace() sous Linux. Celui-ci peut varier sensiblement sur d'autres types d'UNIX. De toute façon, l'utilisation de ptrace() dépend fortement de l'architecture et du système d'exploitation.

BOGUES

Sur les machines ayant des en-têtes du noyau 2.6, PTRACE_SETOPTIONS est déclaré avec une valeur différente de celle du noyau 2.4. De ce fait, les applications compilées avec des en-têtes du noyau 2.6 ne peuvent pas s'exécuter sous des noyaux 2.4. Il est possible de contourner cette difficulté en redéfinissant PTRACE_SETOPTIONS à PTRACE_OLDSETOPTIONS, si cette dernière constante est définie.

Les notifications d'arrêt-groupe sont envoyées à l'observateur, mais pas au vrai père. C'était encore vrai en 2.6.38.6.

Si un leader de groupe de threads est suivi et existe en appelant _exit(2), un arrêt PTRACE_EVENT_EXIT lui arrivera (si réclamé), mais la notification WIFEXITED suivante ne sera pas distribuée avant la fin de tous les autres threads. Comme expliqué précédemment, si un des autres threads appelle execve(2), la mort du leader de groupe de threads ne sera jamais signalée. Si le thread exécuté n'est pas suivi par cet observateur, l'observé ne saura jamais qu'execve(2) est arrivé. Un contournement possible est de PTRACE_DETACHer le leader de groupe de threads au lieu de le redémarrer dans ce cas. C'était encore vrai en 2.6.38.6.

Un signal SIGKILL pourrait encore provoquer un arrêt PTRACE_EVENT_EXIT avant une véritable mort du signal. Cela pourrait évoluer à l'avenir. SIGKILL est supposé tuer immédiatement les tâches même sous ptrace. C'était encore vrai en 2.6.38.6.

Certains appels système renvoient EINTR si un signal a été envoyé à l'observé, mais que la distribution a été supprimée par l'observateur (c'est une opération tout à fait caractéristique : elle est normalement réalisée par les débogueurs sur tous les attachements, afin de ne pas introduire de SIGSTOP défectueux). Depuis Linux 3.2.9, les appels système suivants sont concernés (cette liste est sans doute incomplète) : epoll_wait(2) et read(2) depuis un descripteur de fichier inotify(7). Le symptôme classique de ce bogue est qu'en attachant à un processus quiescent avec la commande

strace -p <ID-processus>

alors, au lieu de la ligne de sortie habituelle attendue comme

    restart_syscall(<... retour de l'appel interrompu ...>_
ou
    select(6, [5], NULL, [5], NULL_
(« _ » indique la position du curseur), plusieurs lignes sont affichées. Par exemple :
    clock_gettime(CLOCK_MONOTONIC, {15370, 690928118}) = 0
    epoll_wait(4,_
Ce qui n'est pas visible ici est que le processus a été bloqué dans epoll_wait(2) avant que strace(1) ne s'y soit attaché. L'attachement a forcé epoll_wait(2) à revenir dans l'espace utilisateur avec l'erreur EINTR. Dans ce cas particulier, le programme a réagit à EINTR en vérifiant l'heure actuelle et en exécutant encore epoll_wait(2) (les programmes qui ne s'attendent pas à de telles erreurs EINTR « perdue » risquent de se comporter de façon inattendue sur une attache strace(1)).

VOIR AUSSI

gdb(1), strace(1), clone(2), execve(2), fork(2), gettid(2), sigaction(2), tgkill(2), vfork(2), waitpid(2), exec(3), capabilities(7), signal(7)

COLOPHON

Cette page fait partie de la publication 3.65 du projet man-pages Linux. Une description du projet et des instructions pour signaler des anomalies peuvent être trouvées à l'adresse http://www.kernel.org/doc/man-pages/.

TRADUCTION

Depuis 2010, cette traduction est maintenue à l'aide de l'outil po4a <http://po4a.alioth.debian.org/> par l'équipe de traduction francophone au sein du projet perkamon <http://perkamon.alioth.debian.org/>.

Christophe Blaess <http://www.blaess.fr/christophe/> (1996-2003), Alain Portal <http://manpagesfr.free.fr/> (2003-2006). Julien Cristau et l'équipe francophone de traduction de Debian (2006-2009).

Veuillez signaler toute erreur de traduction en écrivant à <debian-l10n-french@lists.debian.org> ou par un rapport de bogue sur le paquet manpages-fr.

Vous pouvez toujours avoir accès à la version anglaise de ce document en utilisant la commande « man -L C <section> <page_de_man> ».

20 février 2014 Linux