标准方式如下:
if (ptrace(PTRACE_TRACEME, 0, NULL, 0) == -1)
printf("traced!\n");
在这种情况下,如果跟踪当前进程(即使用gdb运行或附加到它),ptrace将返回错误。
但是这有一个严重的问题:如果调用成功返回,gdb可能以后不会附加到它。这是一个问题,因为我没有尝试实现反调试的东西。我的目的是在满足一个转义时发出'int 3'(即一个断言失败)并且gdb正在运行(否则我得到一个停止应用程序的SIGTRAP)。
每次禁用SIGTRAP并发出'int 3'都不是一个很好的解决方案,因为我正在测试的应用程序可能正在使用SIGTRAP用于其他目的(在这种情况下我还是搞砸了,所以没关系但这是事情的原则:))
由于
答案 0 :(得分:34)
在Windows上有一个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;
}
答案 1 :(得分:19)
我最终使用的代码如下:
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;
}
一些事情:
无论如何,谢谢你的答案。
答案 2 :(得分:16)
以前作为评论:您可以分叉一个孩子,它会尝试PTRACE_ATTACH
其父母(然后在必要时分离)并传回结果。它看起来确实有点不雅。
如你所述,这是非常昂贵的。如果断言不规则地失败,我想这也不算太糟糕。也许保持一个长期运行的孩子这样做是值得的 - 在父母和孩子之间共享两个管道,孩子在读取一个字节时检查,然后发回一个带有状态的字节。
答案 3 :(得分:10)
我有类似的需求,并想出了以下替代方案
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
答案 4 :(得分:8)
我发现文件描述符“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;
}
答案 5 :(得分:6)
如果您只是想知道应用程序是否在gdb
下运行以进行调试,那么Linux上最简单的解决方案是readlink("/proc/<ppid>/exe")
,并搜索"gdb"
的结果。
答案 6 :(得分:4)
这类似于终点'答案,但使用管道进行沟通:
#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;
}