Как определить, выполняется ли текущий процесс GDB?

Стандартным способом будет следующее:

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

В этом случае ptrace возвращает ошибку, если текущий процесс отслеживается (т.е. запускает его с помощью gdb или присоединяется к нему).

Но есть серьезные проблемы с этим: если вызов возвращается успешно, gdb может не присоединяться к нему позже. Это проблема, поскольку я не пытаюсь внедрять анти-отладочные материалы. Моя цель состоит в том, чтобы испускать "int 3", когда встречается конфликт (т.е. Утверждение терпит неудачу) и работает gdb (в противном случае я получаю SIGTRAP, который останавливает приложение).

Отключение SIGTRAP и испускание "int 3" каждый раз не является хорошим решением, потому что приложение, которое я тестирую, может использовать SIGTRAP для какой-то другой цели (в этом случае я все еще вкручен, так что это не имеет значения но это принцип вещи:))

Спасибо

Ответ 1

Ранее в качестве комментария: вы можете разветкить ребенка, который попытается PTRACE_ATTACH его родительский (а затем отсоединить при необходимости) и сообщит результат обратно. Это кажется немного неэлегантным, хотя.

Как вы говорите, это довольно дорого. Я думаю, это не так уж плохо, если утверждения не будут выполняться нерегулярно. Возможно, было бы полезно сохранить одного длинного ребенка вокруг, чтобы сделать это - поделитесь двумя трубами между родителем и дочерним элементом, ребенок выполняет свою проверку, когда он читает байт, а затем отправляет байт обратно со статусом.

Ответ 2

В окнах есть API IsDebuggerPresent, чтобы проверить, находится ли процесс в отладке. В linux мы можем проверить это другим способом (не так эффективно).

Проверьте "/proc/self/status" для атрибута TracerPid.

Пример кода:

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

int IsDebuggerPresent(void)
{
    char buf[1024];
    int debugger_present = 0;

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

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

    if (num_read > 0)
    {
        static const char TracerPid[] = "TracerPid:";
        char *tracer_pid;

        buf[num_read] = 0;
        tracer_pid    = strstr(buf, TracerPid);
        if (tracer_pid)
            debugger_present = !!atoi(tracer_pid + sizeof(TracerPid) - 1);
    }

    return debugger_present;
}

Ответ 3

В результате я использовал следующий код:

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;
}

Несколько вещей:

  • Когда ptrace (PTRACE_ATTACH,...) будет успешным, отслеживаемый процесс остановится и должен быть продолжен.
  • Это также работает, когда gdb подключается позже.
  • Недостатком является то, что при частом использовании это приведет к серьезному замедлению.
  • Кроме того, это решение подтверждено только для работы в Linux. Как упоминалось в комментариях, он не будет работать на BSD.

В любом случае, спасибо за ответы.

Ответ 4

У меня была аналогичная потребность, и я придумал следующие альтернативы

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);
    }
}

Если вызов вызван, функция debug_break будет прервана только при подключении отладчика.

Если вы работаете на x86 и хотите, чтобы точка прерывания прерывалась в вызывающем (не в рейзе), просто включите следующий заголовок и используйте макрос 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

Ответ 5

Я обнаружил, что модифицированная версия дескриптора файла "hack" описанная Silviocesare и blogged by xorl работал хорошо для меня.

Это модифицированный код, который я использую:

#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;
}

Ответ 6

Если вы просто хотите узнать, работает ли приложение под gdb для целей отладки, самое простое решение для Linux - readlink("/proc/<ppid>/exe") и поиск результата для "gdb".

Ответ 7

Это похоже на ответ конечной точки, но использует каналы для связи:

#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 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;
    }
}

Попробовав исходный код под OSX, я обнаружил, что waitpid (в родительском) всегда будет возвращать -1 с EINTR (системный вызов прерывается). Это было вызвано pattach, присоединением к родительскому объекту и прерыванием вызова.

Неясно, можно ли просто снова вызвать waitpid (похоже, в некоторых ситуациях это может привести к некорректной ошибке), поэтому я просто использовал канал для обмена сообщениями. Это немного дополнительного кода, но, вероятно, будет надежно работать на разных платформах.

Этот код был протестирован на OSX 10.9.3, Ubuntu 14.04 (3.13.0-24-generic) и FreeBSD 10.0.

Для linux, который реализует возможности процесса, этот метод будет работать, только если процесс имеет возможность CAP_SYS_PTRACE, которая обычно устанавливается, когда процесс запускается как root.

Другие утилиты (gdb и lldb) также обладают этой возможностью, установленной как часть метаданных их файловой системы.

Вы можете определить, эффективен ли процесс CAP_SYS_PTRACE, связав его с -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;
}