代码之家  ›  专栏  ›  技术社区  ›  terminus

如何检测当前进程是否由gdb运行?

  •  55
  • terminus  · 技术社区  · 14 年前

    标准方法如下:

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

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

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

    每次禁用sigtrap并发出一个“in t 3”并不是一个好的解决方案,因为我测试的应用程序可能正在将sigtrap用于其他用途(在这种情况下,我仍然被拧紧,所以这并不重要,但这是事情的原则:)

    谢谢

    7 回复  |  直到 6 年前
        1
  •  16
  •   Hugh    14 年前

    以前的评论是:你可以用叉子叉一个孩子 PTRACE_ATTACH 它的父级(必要时分离)并将结果传回。不过,看起来确实有点不雅。

    正如你所说,这是相当昂贵的。我想,如果断言不规则地失败,那也不算太糟糕。也许让一个长时间运行的孩子来做这件事是值得的——在父代和子代之间共享两个管道,子代在读取一个字节时进行检查,然后将一个字节与状态一起发送回来。

        2
  •  34
  •   Violet Giraffe    6 年前

    在Windows上有一个API ISDebuggerPresent来检查进程是否正在调试中。在Linux上,我们可以用另一种方法来检查这一点(不是很有效)。

    “检查” /过程/自我/状态 “为了” 示踪 “属性。

    示例代码:

    #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;
    }
    
        3
  •  19
  •   terminus    10 年前

    我最终使用的代码如下:

    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
  •  10
  •   badeip    13 年前

    我也有类似的需求,并提出了以下备选方案

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

    如果调用,调试中断函数将仅在附加调试程序时中断。

    如果您在x86上运行,并且想要一个中断调用者的断点(不在 提升 ,只包括以下头,并使用调试中断宏:

    #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
  •  8
  •   pestophagous    12 年前

    我发现文件描述符“hack”的修改版本 described by 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
  •  6
  •   Employed Russian    14 年前

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

        7
  •  4
  •   Arran Cudbard-Bell    10 年前

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

    尝试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 功能,通常在进程作为根运行时设置。

    其他公用设施( gdb lldb )还可以将此功能设置为文件系统元数据的一部分。

    您可以检测流程是否有效 帽状痕迹 通过链接 -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;
    }