Как определить, запущен ли текущий процесс GDB? - PullRequest
55 голосов
/ 30 августа 2010

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

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

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

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

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

Спасибо

Ответы [ 7 ]

34 голосов
/ 26 июля 2014

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

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

Пример кода:

#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;
}
19 голосов
/ 30 августа 2010

Код, который я использовал в итоге, был следующий:

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.

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

16 голосов
/ 30 августа 2010

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

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

10 голосов
/ 15 ноября 2011

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

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
8 голосов
/ 11 июня 2012

Я обнаружил, что измененная версия дескриптора файла "hack" , описанная Silviocesare и , опубликованная в блоге 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 голосов
/ 30 августа 2010

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

4 голосов
/ 26 июня 2014

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

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

Пытаясь использовать оригинальный код в 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;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...