web-dev-qa-db-fra.com

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 en cours 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 cela: si l'appel revient avec succès, gdb risque de ne pas s'y attacher plus tard. Ce qui est un problème car je n'essaie pas d'implémenter des anti-débogage. Mon but est d'émettre un `` int 3 '' lorsqu'une contention est rencontrée (c'est-à-dire qu'une assertion é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 peut utiliser SIGTRAP à d'autres fins (auquel cas je suis toujours foutu, donc ça n'a pas d'importance mais c'est la principe de la chose :))

Merci

57
terminus

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

Comme vous l'avez mentionné, cela coûte assez cher. Je suppose que ce n'est pas trop mal si les affirmations échouent irrégulièrement. Peut-être que cela vaut la peine de garder un seul enfant de longue durée pour le faire - partager deux canaux entre le parent et l'enfant, l'enfant fait sa vérification lorsqu'il lit un octet, puis renvoie un octet avec le statut.

16
Hugh

Sur 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érifiez "/ proc/self/status" pour l'attribut "TracerPid".

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);
    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;
}
34
Sam Liao

Le code que j'ai fini par utiliser était 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 choses:

  • Lorsque ptrace (PTRACE_ATTACH, ...) réussit, le processus tracé s'arrête et doit être poursuivi.
  • Cela fonctionne également lorsque gdb se connecte plus tard.
  • Un inconvénient est que lorsqu'il est utilisé fréquemment, il entraînera un sérieux ralentissement.
  • De plus, cette solution n'est confirmée que pour fonctionner sur Linux. Comme les commentaires l'ont mentionné, cela ne fonctionnera pas sur BSD.

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

19
terminus

J'avais 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 n'interrompra que si un débogueur est attaché.

Si vous utilisez x86 et souhaitez un point d'arrêt qui interrompt dans l'appelant (pas dans raise), incluez simplement l'en-tête suivant et utilisez 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
10
badeip

J'ai trouvé qu'une version modifiée du descripteur de fichier "hack" décrit par Silviocesare et blogué par xorl fonctionnait bien 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;
}
8
pestophagous

Si vous voulez simplement savoir si l'application s'exécute sous gdb à des fins de débogage, la solution la plus simple sous Linux est de readlink("/proc/<ppid>/exe") et de rechercher le résultat pour "gdb".

6
Employed Russian

Ceci est similaire à la réponse de terminus, mais utilise des canaux de communication:

#include <unistd.h>
#include <stdint.h>
#include <sys/ptrace.h>
#include <sys/wait.h>

#if !defined(PTRACE_ATTACH) && defined(PT_ATTACH)
#  define PTRACE_ATTACH PT_ATTACH
#endif
#if !defined(PTRACE_DETACH) && defined(PT_DETACH)
#  define PTRACE_DETACH PT_DETACH
#endif

#ifdef __linux__
#  define _PTRACE(_x, _y) ptrace(_x, _y, NULL, NULL)
#else
#  define _PTRACE(_x, _y) ptrace(_x, _y, NULL, 0)
#endif

/** Determine if we're running under a debugger by attempting to attach using pattach
 *
 * @return 0 if we're not, 1 if we are, -1 if we can't tell.
 */
static int debugger_attached(void)
{
    int pid;

    int from_child[2] = {-1, -1};

    if (pipe(from_child) < 0) {
        fprintf(stderr, "Debugger check failed: Error opening internal pipe: %s", syserror(errno));
        return -1;
    }

    pid = fork();
    if (pid == -1) {
        fprintf(stderr, "Debugger check failed: Error forking: %s", syserror(errno));
        return -1;
    }

    /* Child */
    if (pid == 0) {
        uint8_t ret = 0;
        int ppid = getppid();

        /* Close parent's side */
        close(from_child[0]);

        if (_PTRACE(PTRACE_ATTACH, ppid) == 0) {
            /* Wait for the parent to stop */
            waitpid(ppid, NULL, 0);

            /* Tell the parent what happened */
            write(from_child[1], &ret, sizeof(ret));

            /* Detach */
            _PTRACE(PTRACE_DETACH, ppid);
            exit(0);
        }

        ret = 1;
        /* Tell the parent what happened */
        write(from_child[1], &ret, sizeof(ret));

        exit(0);
    /* Parent */
    } else {
        uint8_t ret = -1;

        /*
         *  The child writes a 1 if pattach failed else 0.
         *
         *  This read may be interrupted by pattach,
         *  which is why we need the loop.
         */
        while ((read(from_child[0], &ret, sizeof(ret)) < 0) && (errno == EINTR));

        /* Ret not updated */
        if (ret < 0) {
            fprintf(stderr, "Debugger check failed: Error getting status from child: %s", syserror(errno));
        }

        /* Close the pipes here, to avoid races with pattach (if we did it above) */
        close(from_child[1]);
        close(from_child[0]);

        /* Collect the status of the child */
        waitpid(pid, NULL, 0);

        return ret;
    }
}

En essayant le code d'origine sous OSX, j'ai trouvé que waitpid (dans le parent) retournerait toujours -1 avec un EINTR (appel système interrompu). Cela a été causé par pattach, l'attachement au parent et l'interruption de l'appel.

Il n'était pas clair s'il était sûr d'appeler à nouveau waitpid (il semblait que cela pourrait se comporter de manière incorrecte dans certaines situations), j'ai donc utilisé un tuyau pour faire la communication à la place. C'est un peu de code supplémentaire, mais fonctionnera probablement de manière fiable sur plus de plates-formes.

Ce code a été testé sur OSX 10.9.3, Ubuntu 14.04 (3.13.0-24-générique) et FreeBSD 10.0.

Pour linux, qui implémente les capacités de processus, cette méthode ne fonctionnera que si le processus a le CAP_SYS_PTRACE capacité, qui est généralement définie lorsque le processus est exécuté en tant que root.

D'autres utilitaires (gdb et lldb) ont également cette capacité définie dans le cadre de leurs métadonnées de système de fichiers.

Vous pouvez détecter si le processus est efficace CAP_SYS_PTRACE en liant contre -lcap,

#include <sys/capability.h>

cap_flag_value_t value;
cap_t current;

/*
 *  If we're running under linux, we first need to check if we have
 *  permission to to ptrace. We do that using the capabilities
 *  functions.
 */
current = cap_get_proc();
if (!current) {
    fprintf(stderr, "Failed getting process capabilities: %s\n", syserror(errno));
    return -1;
}

if (cap_get_flag(current, CAP_SYS_PTRACE, CAP_PERMITTED, &value) < 0) {
    fprintf(stderr, "Failed getting permitted ptrace capability state: %s\n", syserror(errno));
    cap_free(current);
    return -1;
}

if ((value == CAP_SET) && (cap_get_flag(current, CAP_SYS_PTRACE, CAP_EFFECTIVE, &value) < 0)) {
    fprintf(stderr, "Failed getting effective ptrace capability state: %s\n", syserror(errno));
    cap_free(current);
    return -1;
}
4
Arran Cudbard-Bell