programing

현재 프로세스가 GDB에서 실행 중인지 탐지하는 방법

prostudy 2022. 5. 20. 21:36
반응형

현재 프로세스가 GDB에서 실행 중인지 탐지하는 방법

표준 방법은 다음과 같다.

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

이 경우 ptrace는 현재 프로세스를 추적할 경우(예: GDB로 실행하거나 ptrace에 부착) 오류를 반환한다.

그러나 여기에는 심각한 문제가 있다: 만약 통화가 성공적으로 돌아온다면, GDB는 나중에 그것에 연결되지 않을 수도 있다.그것은 내가 반디버그적인 것을 실행하려고 하지 않기 때문에 문제다.내 목적은 조건이 충족되고(예: 어설션 실패) GDB가 실행 중일 때(그렇지 않으면 응용 프로그램을 중지하는 SIGTRAP를 얻음) 'int 3'을 방출하는 것이다.

테스트 중인 응용 프로그램이 다른 목적으로 SIGTRAP를 사용할 수 있기 때문에 매번 SIGTRAP를 비활성화하고 'int 3'을 방출하는 것은 좋은 해결책이 아니다(이 경우 나는 여전히 망쳐져 있으므로 문제가 되지 않지만 사물의 원리 :).

Windows에는 프로세스가 디버깅 중인지 확인하기 위한 API IsDebuggerPresent가 있다.리눅스에서는 이것을 다른 방법(효율적이지 않음)으로 확인할 수 있다.

"/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);
    ::close(status_fd);

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

나도 비슷한 욕구를 가지고 있었고, 다음과 같은 대안을 생각해 냈다.

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

호출될 경우 디버그_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

이는 종단부의 대답과 유사하지만, 통신을 위해 파이프를 사용한다.

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

OS X에 따라 원래 코드를 시도하다 보니 waitfid (부모의 경우)가 항상 EINTR(시스템 호출 중단)으로 -1을 반환한다는 것을 알았다.이것은 팻택이 부모에게 달라붙어 통화를 방해했기 때문에 생긴 것이다.

그냥 미지근하다고 다시 전화하는 것이 안전한지 확실하지 않아서(어떤 상황에서는 잘못 행동할지도 모르는 것 같았다) 나는 대신 파이프를 사용하여 통신을 했다.그것은 약간의 추가 코드지만, 아마도 더 많은 플랫폼에서 안정적으로 작동할 것이다.

이 코드는 OS X v10.9.3 (Mavericks), Ubuntu 14.04 (Trusty Tahr) (3.13.0-24-generic) 및 FreeB에서 테스트되었다.SD 10.0.

프로세스 기능을 구현하는 Linux의 경우 이 방법은 프로세스에 다음이 있을 경우에만 작동된다.CAP_SYS_PTRACE일반적으로 프로세스가 루트로 실행될 때 설정되는 기능.

기타 유틸리티(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;
}

당신은 아이를 갈퀴로 갈 수 있다.PTRACE_ATTACH부모(그리고 필요한 경우 분리)를 수행하고 결과를 다시 전달한다.하지만 그것은 약간 무례하게 보인다.

네가 언급했듯이, 이것은 꽤 비용이 많이 든다.주장이 불규칙적으로 실패해도 그리 나쁘지 않은 것 같아.아마도 오랫동안 단 한 명의 아이가 이것을 하기 위해 주변에 두는 것이 가치 있는 일일 것이다 - 부모와 자식 사이에 두 개의 파이프를 공유하고, 아이가 바이트를 읽을 때 체크하고, 그리고 나서 그 상태와 함께 바이트를 다시 보낸다.

내가 사용하게 된 코드는 다음과 같았다.

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가 부착될 때도 작동한다.
  • 단점은 자주 사용하면 심각한 둔화를 유발한다는 점이다.
  • 또한 이 솔루션은 리눅스에서만 작동하는 것으로 확인된다.코멘트가 언급했듯이 BSD에서는 효과가 없을 것이다.

애플리케이션이 디버깅을 위해 GDB에서 실행되는지 여부를 알고자 한다면 Linux에서 가장 간단한 솔루션은readlink("/proc/<ppid>/exe"), 그리고 결과를 검색한다."gdb".

나는 실비아에 의해 기술되고 xorl에 의해 블로그된 파일 설명자 "hack"의 수정된 버전이 나에게 잘 작동한다는 것을 발견했다.

이것은 내가 사용하는 수정된 코드다.

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

Sam Liao의 대답 C++ 버전(리눅스에만 해당):

// Detect if the application is running inside a debugger.
bool being_traced()
{
  std::ifstream sf("/proc/self/status");
  std::string s;
  while (sf >> s)
  {
    if (s == "TracerPid:")
    {
      int pid;
      sf >> pid;
      return pid != 0;
    }
    std::getline(sf, s);
  }

  return false;
}

참조URL: https://stackoverflow.com/questions/3596781/how-to-detect-if-the-current-process-is-being-run-by-gdb

반응형