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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/3596781/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-02 06:20:15  来源:igfitidea点击:

How to detect if the current process is being run by GDB?

clinuxgdbposix

提问by terminus

The standard way would be the following:

标准方法如下:

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

In this case ptrace returns an error if the current process is traced (i.e. running it with gdb or attaching to it).

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

But there is a serious problem with this: if the call returns successfully, gdb may not attach to it later. Which is a problem since I'm not trying to implement anti-debug stuff. My purpose is to emit an 'int 3' when a contition is met (i.e. an assert fails) and gdb is running (otherwise I get a SIGTRAP which stops the application).

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

Disabling SIGTRAP and emitting an 'int 3' every time is not a good sollution because the application I'm testing might be using SIGTRAP for some other purpose (in which case I'm still screwed, so it wouldn't matter but it's the principle of the thing :))

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

Thanks

谢谢

采纳答案by Hugh

Previously as a comment: you could fork a child which would try to PTRACE_ATTACHits parent (and then detach if necessary) and communicates the result back. It does seem a bit inelegant though.

以前作为评论:您可以分叉一个子项,该子项会尝试与PTRACE_ATTACH其父项(然后在必要时分离)并将结果返回。不过好像有点不雅观。

As you mention, this is quite costly. I guess it's not too bad if assertions fail irregularly. Perhaps it'd be worthwhile keeping a single long-running child around to do this - share two pipes between the parent and the child, child does its check when it reads a byte and then sends a byte back with the status.

正如你提到的,这是相当昂贵的。我想如果断言不规则地失败也不算太糟糕。也许值得保留一个长时间运行的孩子来做到这一点 - 在父母和孩子之间共享两个管道,孩子在读取一个字节时进行检查,然后将一个字节发送回状态。

回答by Sam Liao

On windows there is an API IsDebuggerPresent to check if process is under debugging. At linux, we can check this with another way (Not so efficient).

在 Windows 上有一个 API IsDebuggerPresent 来检查进程是否正在调试。在 linux 上,我们可以用另一种方式来检查(效率不高)。

Check "/proc/self/status" for "TracerPid" attribute.

检查“ /proc/self/status”的“ TracerPid”属性。

Example 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] = '
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;
}
'; 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; }

回答by terminus

The code I ended up using was the following:

我最终使用的代码如下:

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

A few things:

一些东西:

  • When ptrace(PTRACE_ATTACH, ...) is successful, the traced process will stop and has to be continued.
  • This also works when gdb is attaching later.
  • A drawback is that when used frequently, it will cause a serious slowdown.
  • Also, this solution is only confirmed to work on Linux. As the comments mentioned, it won't work on BSD.
  • 当 ptrace(PTRACE_ATTACH, ...) 成功时,被跟踪的进程将停止并且必须继续。
  • 这在 gdb 稍后附加时也有效。
  • 一个缺点是频繁使用会造成严重的减速。
  • 此外,此解决方案仅被确认适用于 Linux。正如评论中提到的,它不适用于 BSD。

Anyway, thanks for the answers.

无论如何,感谢您的回答。

回答by badeip

I had a similar need, and came up with the following alternatives

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

#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

If called, the debug_break function will only interrupt if a debugger is attached.

如果调用,则 debug_break 函数只会在附加调试器时中断。

If you are running on x86 and want a breakpoint which interrupts in the caller (not in raise), just include the following header, and use the debug_break macro:

如果您在 x86 上运行并想要一个在调用者中中断的断点(而不是在raise 中),只需包含以下标头,并使用 debug_break 宏:

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

回答by pestophagous

I found that a modified version of the file descriptor "hack" described by Silviocesareand blogged by xorlworked well for me.

我发现,文件描述符“黑客”的修改版本由Silviocesare描述通过xorl博客对我来说效果很好。

This is the modified code I use:

这是我使用的修改后的代码:

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

回答by Employed Russian

If you just want to know whether the app is running under gdbfor debugging purposes, the simplest solution on Linux is to readlink("/proc/<ppid>/exe"), and search the result for "gdb".

如果您只想知道应用程序是否gdb出于调试目的运行,Linux 上最简单的解决方案是readlink("/proc/<ppid>/exe"),然后在结果中搜索"gdb".

回答by Arran Cudbard-Bell

This is similar to terminus' answer, but uses pipes for communication:

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

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

Trying the original code under OSX, I found waitpid (in the parent) would always return -1 with an EINTR (System call interrupted). This was caused by pattach, attaching to the parent and interrupting the call.

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

It wasn't clear whether it was safe to just call waitpid again (that seemed like it might behave incorrectly in some situations) so I just used a pipe to do the communication instead. It's a bit of extra code, but will probably work reliably across more platforms.

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

This code has been tested on OSX 10.9.3, Ubuntu 14.04 (3.13.0-24-generic) and FreeBSD 10.0.

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

For linux, which implements process capabilities, this method will only work if the process has the CAP_SYS_PTRACEcapability, which is typically set when the process is run as root.

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

Other utilities (gdband lldb) also have this capability set as part of their filesystem metadata.

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

You can detect whether the process has effective CAP_SYS_PTRACEby linking against -lcap,

您可以CAP_SYS_PTRACE通过链接来检测流程是否有效-lcap

##代码##