Как определить, выполняется ли текущий процесс GDB?
стандартный способ будет следующим:
if (ptrace(PTRACE_TRACEME, 0, NULL, 0) == -1)
printf("traced!n");
в этом случае ptrace возвращает ошибку, если текущий процесс отслеживается (т. е. выполняется с gdb или присоединяется к нему).
но есть серьезная проблема с этим: если вызов возвращается успешно, gdb не может подключиться к нему позже. Что является проблемой, так как я не пытаюсь реализовать анти-отладочные вещи. Моя цель состоит в том, чтобы испустить "int 3", Когда выполняется contition (т. е. assert терпит неудачу), и gdb работает (в противном случае я получаю SIGTRAP, который останавливает приложение).
отключение SIGTRAP и испускание "int 3" каждый раз не является хорошим решением, потому что приложение, которое я тестирую, может использовать SIGTRAP для какой-то другой цели (в этом случае я все еще ввернут, поэтому это не имеет значения, но это принцип вещи :))
спасибо
7 ответов:
ранее в качестве комментария: вы можете раскошелиться на ребенка, который попытается
PTRACE_ATTACHего родитель (а затем отсоединить при необходимости) и передает результат обратно. Хотя это кажется немного неэлегантным.Как вы упомянули, это довольно дорого. Я думаю, это не так уж плохо, если утверждения терпят неудачу нерегулярно. Возможно, было бы целесообразно сохранить один продолжительный ребенок вокруг, чтобы сделать это-разделить два канала между родителем и ребенком, ребенок делает свою проверку, когда он читает байт, а затем отправляет байт обратно со статусом.
в 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] = ''; 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; }несколько вещей:
- когда ptrace(PTRACE_ATTACH,...) успешно, прослеженный процесс остановится и должен быть продолжен.
- это также работает, когда gdb присоединяется позже.
- недостатком является то, что при частом использовании, это вызовет серьезное замедление.
- кроме того, это решение подтверждено только для работы на Linux. Как уже упоминалось в комментариях, он не будет работать БСД.
в любом случае, спасибо за ответы.
у меня была аналогичная потребность, и я придумал следующие альтернативы
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 и хотите точку останова, которая прерывает вызывающего абонента (не в поднять), просто включите следующий заголовок и используйте макрос 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
Я обнаружил, что модифицированная версия файлового дескриптора "hack" описано 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".
это похоже на ответ 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 (системный вызов прерван). Это было вызвано тем, что паттах подключился к родителю и прервал вызов.
было неясно, безопасно ли снова вызывать 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; }
Comments