70 votes

Comment détecter si le processus en cours est exécuté par GDB ?

La méthode standard serait la suivante :

if (ptrace(PTRACE_TRACEME, 0, NULL, 0) == -1)
  printf("traced!\n");

Dans ce cas, ptrace renvoie une erreur si le processus actuel est tracé (c'est-à-dire en l'exécutant avec gdb ou en s'y attachant).

Mais il y a un sérieux problème avec ceci : si l'appel retourne avec succès, gdb peut ne pas s'y attacher plus tard. Ce qui est un problème puisque je n'essaie pas d'implémenter un système anti-débogage. Mon but est d'émettre un 'int 3' lorsqu'une condition est remplie (c'est-à-dire qu'une assert échoue) et que gdb est en cours d'exécution (sinon j'obtiens un SIGTRAP qui arrête l'application).

Désactiver SIGTRAP et émettre un 'int 3' à chaque fois n'est pas une bonne solution car l'application que je teste pourrait utiliser SIGTRAP dans un autre but (auquel cas je suis toujours dans la merde, donc ça n'aurait pas d'importance mais c'est le principe de la chose :)).

Gracias

1 votes

Vous devez trouver quelque chose comme IsDebuggerPresent sur POSIX

2 votes

@Svisstack : Oui, ma question est à peu près ce que serait cet appel/méthode POSIX.

6 votes

Vous pourriez bifurquer un enfant qui essaierait de PTRACE_ATTACH son parent (puis le détacher si nécessaire) et communique le résultat en retour. Cela semble cependant un peu inélégant.

49voto

arsane Points 6500

Sous Windows, il existe une API IsDebuggerPresent pour vérifier si le processus est en cours de débogage. Sous linux, nous pouvons vérifier cela d'une autre manière (pas si efficace).

Vérifier " /proc/self/status "pour " TracerPid Attribut ".

Exemple de code :

#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <ctype.h>

bool debuggerIsAttached()
{
    char buf[4096];

    const int status_fd = ::open("/proc/self/status", O_RDONLY);
    if (status_fd == -1)
        return false;

    const ssize_t num_read = ::read(status_fd, buf, sizeof(buf) - 1);
    ::close(status_fd);

    if (num_read <= 0)
        return false;

    buf[num_read] = '\0';
    constexpr char tracerPidString[] = "TracerPid:";
    const auto tracer_pid_ptr = ::strstr(buf, tracerPidString);
    if (!tracer_pid_ptr)
        return false;

    for (const char* characterPtr = tracer_pid_ptr + sizeof(tracerPidString) - 1; characterPtr <= buf + num_read; ++characterPtr)
    {
        if (::isspace(*characterPtr))
            continue;
        else
            return ::isdigit(*characterPtr) != 0 && *characterPtr != '0';
    }

    return false;
}

0 votes

Le champ "TracerPid" de /proc/self/status disponible sur toutes les versions du noyau Linux ?

1 votes

@osgx. J'ai fait quelques vérifications rapides, le "TracerPid" est ajouté par linux procfs au moins depuis 2005 ou même avant. De plus, j'ai remarqué que gdb et google perftools utilisent la même méthode pour obtenir ou vérifier le pid du traceur.

2 votes

@SamLiao : buf[num_read] = 0; écrit après la fin de buf si sizeof(buf) octets sont lus dans le fichier. Je vous suggère de passer sizeof(buf) - 1 à l'appel à read pour régler ce problème ?

23voto

terminus Points 3966

Le code que j'ai fini par utiliser est le suivant :

int
gdb_check()
{
  int pid = fork();
  int status;
  int res;

  if (pid == -1)
    {
      perror("fork");
      return -1;
    }

  if (pid == 0)
    {
      int ppid = getppid();

      /* Child */
      if (ptrace(PTRACE_ATTACH, ppid, NULL, NULL) == 0)
        {
          /* Wait for the parent to stop and continue it */
          waitpid(ppid, NULL, 0);
          ptrace(PTRACE_CONT, NULL, NULL);

          /* Detach */
          ptrace(PTRACE_DETACH, getppid(), NULL, NULL);

          /* We were the tracers, so gdb is not present */
          res = 0;
        }
      else
        {
          /* Trace failed so gdb is present */
          res = 1;
        }
      exit(res);
    }
  else
    {
      waitpid(pid, &status, 0);
      res = WEXITSTATUS(status);
    }
  return res;
}

Quelques trucs :

  • Lorsque ptrace(PTRACE_ATTACH, ...) réussit, le processus tracé s'arrête et doit être poursuivi.
  • Cela fonctionne également lorsque gdb s'attache ultérieurement.
  • L'inconvénient est que, lorsqu'il est utilisé fréquemment, il provoque un sérieux ralentissement.
  • De plus, il a été confirmé que cette solution ne fonctionne que sur Linux. Comme le mentionnent les commentaires, elle ne fonctionnera pas sur BSD.

Quoi qu'il en soit, merci pour les réponses.

3 votes

Votre deuxième appel à ptrace manque le paramètre pid

1 votes

Il vaut mieux utiliser _Exit au lieu de exit, ou un gestionnaire de sortie pourrait provoquer le chaos.

0 votes

Cela ne fonctionne pas sous OSX (et probablement BSD) à cause de waitpid qui est interrompu par EINTR. Voir une version corrigée sans fautes de frappe ci-dessous...

19voto

Huw Points 4070

Auparavant, en guise de commentaire : vous pourriez bifurquer vers un enfant qui essaierait de PTRACE_ATTACH son parent (puis le détacher si nécessaire) et communique le résultat en retour. Cela semble cependant un peu inélégant.

Comme vous le mentionnez, cela est assez coûteux. Je suppose que ce n'est pas trop grave si les assertions échouent de façon irrégulière. Peut-être que cela vaudrait la peine de garder un seul enfant qui tourne longtemps pour faire cela - partager deux tuyaux entre le parent et l'enfant, l'enfant fait sa vérification quand il lit un octet et renvoie ensuite un octet avec le statut.

14voto

badeip Points 76

J'ai eu un besoin similaire, et j'ai trouvé les alternatives suivantes

static int _debugger_present = -1;
static void _sigtrap_handler(int signum)
{
    _debugger_present = 0;
    signal(SIGTRAP, SIG_DFL);
}

void debug_break(void)
{
    if (-1 == _debugger_present) {
        _debugger_present = 1;
        signal(SIGTRAP, _sigtrap_handler);
        raise(SIGTRAP);
    }
}

Si elle est appelée, la fonction debug_break ne s'interrompra que si un débogueur est attaché.

Si vous exécutez sur x86 et que vous voulez un point d'arrêt qui s'interrompt dans l'appelant (et non pas dans soulever ), il suffit d'inclure l'en-tête suivant et d'utiliser la macro debug_break :

#ifndef BREAK_H
#define BREAK_H

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>

int _debugger_present = -1;
static void _sigtrap_handler(int signum)
{
    _debugger_present = 0;
    signal(SIGTRAP, SIG_DFL);
}

#define debug_break()                       \
do {                                        \
    if (-1 == _debugger_present) {          \
        _debugger_present = 1;              \
        signal(SIGTRAP, _sigtrap_handler);  \
        __asm__("int3");                    \
    }                                       \
} while(0)

#endif

8voto

que que Points 1435

J'ai découvert qu'une version modifiée du "hack" du descripteur de fichier décrit par Silviocesare y blogué par xorl a bien fonctionné pour moi.

Voici le code modifié que j'utilise :

#include <stdio.h>
#include <unistd.h>

// gdb apparently opens FD(s) 3,4,5 (whereas a typical prog uses only stdin=0, stdout=1,stderr=2)
int detect_gdb(void)
{
    int rc = 0;
    FILE *fd = fopen("/tmp", "r");

    if (fileno(fd) > 5)
    {
        rc = 1;
    }

    fclose(fd);
    return rc;
}

1 votes

@franz1 intéressant. merci pour le commentaire. je viens de construire GDB 7.10 à la maison récemment, donc je vais tester et lire le code et essayer de mettre à jour ceci. merci beaucoup.

1 votes

Il y a probablement trop de faux positifs. Les descripteurs de fichiers peuvent être divulgués pour de nombreuses raisons. Même des programmes open-source bien connus le font parfois (par ex. bugs.launchpad.net/ubuntu/+source/kde4libs/+bug/385999 )

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X