c linux gdb posix

¿Cómo detectar si el proceso actual está siendo ejecutado por GDB?



linux posix (7)

Anteriormente, como un comentario: podría bifurcar a un hijo que intentaría PTRACE_ATTACH su padre (y luego separarlo si fuera necesario) y comunica el resultado de nuevo. Sin embargo, parece un poco poco elegante.

Como mencionas, esto es bastante costoso. Supongo que no es tan malo si las afirmaciones fallan de manera irregular. Tal vez valdría la pena mantener a un solo niño de larga duración para hacer esto: compartir dos conexiones entre el padre y el niño, el niño hace su comprobación cuando lee un byte y luego envía un byte con el estado.

La forma estándar sería la siguiente:

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

En este caso, ptrace devuelve un error si se rastrea el proceso actual (es decir, ejecutándolo con gdb o adjuntándolo).

Pero hay un problema grave con esto: si la llamada se realiza con éxito, es posible que gdb no se adjunte más adelante. Lo cual es un problema ya que no estoy tratando de implementar cosas anti-depuración. Mi propósito es emitir un ''int 3'' cuando se cumple una contienda (es decir, falla una aserción) y gdb se está ejecutando (de lo contrario obtengo un SIGTRAP que detiene la aplicación).

Deshabilitar SIGTRAP y emitir un ''int 3'' cada vez no es una buena solución porque la aplicación que estoy probando podría estar usando SIGTRAP para algún otro propósito (en cuyo caso todavía estoy atornillado, por lo que no importa, pero es el principio de la cosa :))

Gracias


El código que terminé usando fue el siguiente:

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

Unas pocas cosas:

  • Cuando ptrace (PTRACE_ATTACH, ...) tenga éxito, el proceso de rastreo se detendrá y debe continuar.
  • Esto también funciona cuando gdb se adjunta más tarde.
  • Un inconveniente es que cuando se usa con frecuencia, causará una desaceleración grave.
  • Además, esta solución solo está confirmada para funcionar en Linux. Como se mencionó en los comentarios, no funcionará en BSD.

De todos modos, gracias por las respuestas.


En Windows hay una API IsDebuggerPresent para verificar si el proceso se encuentra bajo depuración. En Linux, podemos verificar esto de otra manera (No es tan eficiente).

Marque " / proc / self / status " para el atributo " TracerPid ".

Código de ejemplo:

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


Encontré que una versión modificada del descriptor de archivo "hack" descrito por Silviocesare y blogeado por xorl funcionó bien para mí.

Este es el código modificado que uso:

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


Esto es similar a la respuesta de terminus, pero usa tuberías para la comunicación:

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

Al probar el código original bajo OSX, encontré que waitpid (en el padre) siempre devolvería -1 con un EINTR (llamada del sistema interrumpida). Esto fue causado por un parche, adjuntando al padre e interrumpiendo la llamada.

No estaba claro si era seguro volver a llamar a waitpid (lo que parecía podría comportarse de manera incorrecta en algunas situaciones), así que solo usé una canalización para hacer la comunicación. Es un poco de código adicional, pero probablemente funcionará de manera confiable en más plataformas.

Este código ha sido probado en OSX 10.9.3, Ubuntu 14.04 (3.13.0-24-generic) y FreeBSD 10.0.

Para Linux, que implementa capacidades de proceso, este método solo funcionará si el proceso tiene la capacidad CAP_SYS_PTRACE , que normalmente se establece cuando el proceso se ejecuta como root.

Otras utilidades ( gdb y lldb ) también tienen esta capacidad establecida como parte de sus metadatos del sistema de archivos.

Puede detectar si el proceso tiene un CAP_SYS_PTRACE efectivo al vincular con -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; }


Si solo desea saber si la aplicación se ejecuta bajo gdb para fines de depuración, la solución más sencilla en Linux es readlink("/proc/<ppid>/exe") , y buscar el resultado para "gdb" .


Tuve una necesidad similar, y se me ocurrieron las siguientes alternativas

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

Si se llama, la función debug_break solo se interrumpirá si se adjunta un depurador.

Si está ejecutando en x86 y desea un punto de interrupción que interrumpa en la persona que llama (no en el aumento ), solo incluya el siguiente encabezado y use la macro 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