標準的な方法は次のようになります。
if (ptrace(PTRACE_TRACEME, 0, NULL, 0) == -1)
printf("traced!\n");
この場合、現在のプロセスがトレースされている(つまり、gdbで実行されている、またはアタッチされている)場合、ptraceはエラーを返します。
しかし、これには深刻な問題があります。呼び出しが正常に戻った場合、gdbは後で接続できない可能性があります。デバッグ用のものを実装しようとしていないので、これは問題です。私の目的は、条件が満たされ(つまり、アサートが失敗)、gdbが実行されている場合(そうでない場合、アプリケーションを停止するSIGTRAPを取得する)、「int 3」を発行することです。
SIGTRAPを無効にして「int 3」を毎回出力するのは良い解決策ではありません。テストしているアプリケーションが他の目的でSIGTRAPを使用している可能性があるためです(その場合、私はまだねじ込まれているため、問題ではありませんが、それは事の原則:))
ありがとう
以前はコメントとして:PTRACE_ATTACH
その親(そして必要に応じてデタッチ)し、結果を返します。しかし、それは少し洗練されていないように見えます。
あなたが言うように、これはかなり高価です。アサーションが不規則に失敗したとしても、それほど悪くはないと思います。おそらく、これを行うには、実行時間の長い単一の子を保持する価値があります。親と子の間で2つのパイプを共有します。子は、バイトを読み取るときにチェックを行い、ステータスとともにバイトを送り返します。
ウィンドウには、プロセスがデバッグ中であるかどうかを確認するための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;
}
最終的に使用したコードは次のとおりです。
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;
}
いくつかのこと:
とにかく、答えてくれてありがとう。
私は同様のニーズを持っていて、次の代替案を考え出しました
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
ファイル記述子「ハック」の変更されたバージョン 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;
}
アプリがデバッグのためにgdb
で実行されているかどうかを知りたいだけの場合、Linuxでの最も簡単な解決策はreadlink("/proc/<ppid>/exe")
を実行し、"gdb"
を検索することです。
これは端末の回答に似ていますが、通信にパイプを使用します。
#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;
}