web-dev-qa-db-ja.com

現在のプロセスがGDBによって実行されているかどうかを検出するにはどうすればよいですか?

標準的な方法は次のようになります。

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

この場合、現在のプロセスがトレースされている(つまり、gdbで実行されている、またはアタッチされている)場合、ptraceはエラーを返します。

しかし、これには深刻な問題があります。呼び出しが正常に戻った場合、gdbは後で接続できない可能性があります。デバッグ用のものを実装しようとしていないので、これは問題です。私の目的は、条件が満たされ(つまり、アサートが失敗)、gdbが実行されている場合(そうでない場合、アプリケーションを停止するSIGTRAPを取得する)、「int 3」を発行することです。

SIGTRAPを無効にして「int 3」を毎回出力するのは良い解決策ではありません。テストしているアプリケーションが他の目的でSIGTRAPを使用している可能性があるためです(その場合、私はまだねじ込まれているため、問題ではありませんが、それは事の原則:))

ありがとう

57
terminus

以前はコメントとして:PTRACE_ATTACHその親(そして必要に応じてデタッチ)し、結果を返します。しかし、それは少し洗練されていないように見えます。

あなたが言うように、これはかなり高価です。アサーションが不規則に失敗したとしても、それほど悪くはないと思います。おそらく、これを行うには、実行時間の長い単一の子を保持する価値があります。親と子の間で2つのパイプを共有します。子は、バイトを読み取るときにチェックを行い、ステータスとともにバイトを送り返します。

16
Hugh

ウィンドウには、プロセスがデバッグ中であるかどうかを確認するための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;
}
34
Sam Liao

最終的に使用したコードは次のとおりです。

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では動作しません。

とにかく、答えてくれてありがとう。

19
terminus

私は同様のニーズを持っていて、次の代替案を考え出しました

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で実行していて、(raiseではなく)呼び出し元で中断するブレークポイントが必要な場合は、次のヘッダーを含めて、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
10
badeip

ファイル記述子「ハック」の変更されたバージョン 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;
}
8
pestophagous

アプリがデバッグのためにgdbで実行されているかどうかを知りたいだけの場合、Linuxでの最も簡単な解決策はreadlink("/proc/<ppid>/exe")を実行し、"gdb"を検索することです。

6

これは端末の回答に似ていますが、通信にパイプを使用します。

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