如何检测GDB当前进程是否正在运行?

ter*_*nus 57 c linux gdb posix

标准方式如下:

if (ptrace(PTRACE_TRACEME, 0, NULL, 0) == -1)
  printf("traced!\n");
Run Code Online (Sandbox Code Playgroud)

在这种情况下,如果跟踪当前进程(即使用gdb运行或附加到它),ptrace将返回错误.

但是这有一个严重的问题:如果调用成功返回,gdb可能以后不会附加到它.这是一个问题,因为我没有尝试实现反调试的东西.我的目的是在满足转义时发出'int 3'(即断言失败)并且gdb正在运行(否则我得到一个停止应用程序的SIGTRAP).

每次禁用SIGTRAP并发出'int 3'都不是一个很好的解决方案,因为我正在测试的应用程序可能正在使用SIGTRAP用于其他目的(在这种情况下我还是搞砸了,所以它没关系,但它是事情的原理:))

谢谢

Sam*_*iao 36

在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;
}
Run Code Online (Sandbox Code Playgroud)

  • @SamLiao:`buf [num_read] = 0;`如果从文件中读取`sizeof(buf)`字节,`写入`buf`的结尾.我建议你把`sizeof(buf) - 1`传递给`read`来解决这个问题? (2认同)

ter*_*nus 19

我最终使用的代码如下:

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;
}
Run Code Online (Sandbox Code Playgroud)

一些东西:

  • 当ptrace(PTRACE_ATTACH,...)成功时,跟踪的进程将停止并且必须继续.
  • 当gdb稍后附加时,这也适用.
  • 缺点是当经常使用时,会导致严重的减速.
  • 此外,此解决方案仅在Linux上确认可用.正如所提到的评论,它不适用于BSD.

无论如何,谢谢你的答案.

  • 你对ptrace的第二次调用缺少pid参数 (3认同)
  • 这很好,但是要工作,子级需要附加到其父级的权限(默认情况下是相反的:允许父级跟踪其子级),因此“#include &lt;sys/prctl.h&gt;”,然后某处 `prctl(PR_SET_PTRACER, (unsigned long)getpid(), 0, 0, 0);` (这允许来自我们自己和我们的后代的 ptrace - ia 来自您的解决方案的新子项)。 (2认同)

Hug*_*ugh 16

以前作为评论:你可以分叉一个孩子,它会尝试PTRACE_ATTACH其父母(然后在必要时分离)并传回结果.虽然看起来确实有些不雅.

如你所述,这是非常昂贵的.如果断言不规则地失败,我想这也不算太糟糕.也许保持一个长期运行的孩子这样做是值得的 - 在父母和孩子之间共享两个管道,孩子在读取一个字节时检查,然后发回一个带有状态的字节.


bad*_*eip 10

我有类似的需求,并提出了以下替代方案

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

如果调用,则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
Run Code Online (Sandbox Code Playgroud)


pes*_*ous 8

我发现,文件描述符"黑客"的修改版本由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;
}
Run Code Online (Sandbox Code Playgroud)


Emp*_*ian 6

如果您只是想知道应用程序是否正在运行以gdb进行调试,那么Linux上最简单的解决方案是readlink("/proc/<ppid>/exe"),并搜索结果"gdb".

  • 谢谢,但是当程序运行后gdb附加时它将无法运行. (3认同)

Arr*_*ell 6

这类似于 terminus 的答案,但使用管道进行通信:

#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;
    }
}
Run Code Online (Sandbox Code Playgroud)

在 OSX 下尝试原始代码,我发现 waitpid(在父级中)总是会返回 -1 并带有 EINTR(系统调用中断)。这是由补丁引起的,附加到父级并中断呼叫。

不清楚再次调用 waitpid 是否安全(这似乎在某些情况下可能表现不正确),所以我只是使用管道来进行通信。这是一些额外的代码,但可能会在更多平台上可靠地工作。

此代码已在 OSX 10.9.3、Ubuntu 14.04 (3.13.0-24-generic) 和 FreeBSD 10.0 上测试过。

对于实现进程能力的linux,这种方法只有在进程有CAP_SYS_PTRACE能力时才有效,这通常是在进程以root身份运行时设置的。

其他实用程序(gdblldb)也将此功能设置为其文件系统元数据的一部分。

您可以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;
}
Run Code Online (Sandbox Code Playgroud)