So erkennen Sie, ob der aktuelle Prozess von GDB ausgeführt wird

Lesezeit: 10 Minuten

Benutzeravatar von Terminus
Endstation

Der Standardweg wäre folgender:

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

In diesem Fall gibt ptrace einen Fehler zurück, wenn der aktuelle Prozess verfolgt wird (z. B. wenn er mit GDB ausgeführt oder daran angehängt wird).

Dabei gibt es jedoch ein ernsthaftes Problem: Wenn der Aufruf erfolgreich zurückkehrt, wird GDB später möglicherweise nicht daran angehängt. Was ein Problem ist, da ich nicht versuche, Anti-Debug-Sachen zu implementieren. Mein Zweck ist es, ein ‘int 3’ auszugeben, wenn eine Bedingung erfüllt ist (z. B. ein Assert fehlschlägt) und GDB ausgeführt wird (andernfalls erhalte ich ein SIGTRAP, das die Anwendung stoppt).

SIGTRAP zu deaktivieren und jedes Mal ein ‘int 3’ auszugeben, ist keine gute Lösung, da die Anwendung, die ich teste, SIGTRAP möglicherweise für einen anderen Zweck verwendet (in diesem Fall bin ich immer noch am Arsch, also wäre es egal, aber es ist das prinzip der sache :))

  • Sie müssen etwas wie IsDebuggerPresent auf POSIX finden

    – Svisstack

    29. August 2010 um 22:03 Uhr

  • @Svisstack: Ja, meine Frage ist ungefähr, was dieser POSIX-Aufruf / diese Methode wäre.

    – Endstation

    29. August 2010 um 22:15 Uhr

  • Sie könnten ein Kind gabeln, das es versuchen würde PTRACE_ATTACH sein Elternteil (und dann gegebenenfalls abtrennen) und das Ergebnis zurückmelden. Es wirkt allerdings etwas unelegant.

    – Hugo

    29. August 2010 um 22:41 Uhr

  • @Huw: Das hat funktioniert, danke. Wenn Sie eine Antwort schreiben, werde ich sie akzeptieren. Aber die Gabelung macht es ziemlich kostspielig.

    – Endstation

    29. August 2010 um 23:02 Uhr


Benutzeravatar von Sam Liao
Sam Liao

Unter Windows gibt es eine API, IsDebuggerPresent, um zu prüfen, ob der Prozess gerade debuggt wird. Bei Linux können wir dies auf andere Weise überprüfen (nicht so effizient).

Prüfen “/proc/self/status” zum “TracerPid“Attribut.

Beispielcode:

#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);
    ::close(status_fd);

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

  • Ist das Feld “TracerPid”. /proc/self/status auf jeder Version des Linux-Kernels verfügbar?

    – osgx

    28. Juli 2014 um 0:13 Uhr

  • @osgx. Habe kurz nachgesehen, die „TracerPid“ wird mindestens seit 2005 oder sogar früher von Linux Procfs hinzugefügt. Außerdem ist mir aufgefallen, dass gdb & google perftools dieselbe Methode verwenden, um die Tracer-PID abzurufen oder zu überprüfen.

    – Sam Liao

    28. Juli 2014 um 8:50 Uhr

  • @SamLiao: buf[num_read] = 0; schreibt über das Ende hinaus buf wenn sizeof(buf) Bytes werden aus der Datei gelesen. Darf ich vorschlagen, dass Sie bestehen? sizeof(buf) - 1 zum Aufruf an read um das zu beheben?

    – Richard Koch

    24. Januar 2017 um 22:46 Uhr


  • Unter Linux ist auch der obige Code erforderlich, einschließlich (lesen) und (atoi).

    – michael

    12. Juni 2018 um 23:56 Uhr

  • Ich schlage Upping vor buf‘s Größe auf 4096 statt 1024, nur um auf der sicheren Seite zu sein. Die grosse von "/proc/self/status" ist 1401 in meinem Fall, und wer soll das sagen TracerPid ist garantiert in den oberen 1024 Symbolen? Ich schlage auch die folgende Optimierung vor: brauchen wir nicht atoimüssen wir nur überprüfen, ob das erste Nicht-Leerzeichen-Symbol der PID eine Ziffer ist und nicht '0'. Ich habe die Antwort mit meinen Vorschlägen bearbeitet. Und ich habe die fehlende Header-Datei eingefügt.

    – Violette Giraffe

    4. August 2018 um 14:04 Uhr


Benutzeravatar von Terminus
Endstation

Der Code, den ich am Ende verwendet habe, war der folgende:

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

Ein paar Dinge:

  • Wenn ptrace(PTRACE_ATTACH, …) erfolgreich ist, wird der verfolgte Prozess beendet und muss fortgesetzt werden.
  • Dies funktioniert auch, wenn GDB später angehängt wird.
  • Ein Nachteil ist, dass es bei häufiger Verwendung zu einer ernsthaften Verlangsamung kommt.
  • Außerdem wurde bestätigt, dass diese Lösung nur unter Linux funktioniert. Wie in den Kommentaren erwähnt, wird es nicht funktionieren BSD.

  • Bei Ihrem zweiten Aufruf von ptrace fehlt der PID-Parameter

    – Nevelis

    26. September 2012 um 22:24 Uhr

  • Verwenden Sie besser _Exit anstelle von exit, oder ein Exit-Handler kann Chaos verursachen.

    – BeniBela

    13. Dezember 2012 um 21:10 Uhr

  • Dies funktioniert nicht unter OSX (und vermutlich BSD), da waitpid mit EINTR unterbrochen wird. Sehen Sie unten eine korrigierte fehlerfreie Version …

    – Arran Cudbard-Bell

    25. Juni 2014 um 22:12 Uhr

  • Bestätigt, dass es nicht auf BSD funktioniert. PTRACE_CONT ist auch überflüssig, das Abhängen bewirkt automatisch, dass der verfolgte Prozess fortgesetzt wird.

    – Arran Cudbard-Bell

    27. Juni 2014 um 10:06 Uhr

  • @ArranCudbard-Bell: Danke für die Info, ich habe die Antwort aktualisiert. OTOH Ich habe PTRACE_CONT drin gelassen, ich finde es besser, diese Dinge explizit zu machen.

    – Endstation

    2. Juli 2014 um 6:38 Uhr

Hughs Benutzeravatar
Hugo

Sie könnten ein Kind gabeln, das es versuchen würde PTRACE_ATTACH sein Elternteil (und dann gegebenenfalls abtrennen) und das Ergebnis zurückmelden. Es wirkt allerdings etwas unelegant.

Wie Sie bereits erwähnt haben, ist dies ziemlich kostspielig. Ich denke, es ist nicht so schlimm, wenn Behauptungen unregelmäßig scheitern. Vielleicht würde es sich lohnen, dafür ein einziges Kind mit langer Laufzeit zu haben – teilen Sie zwei Pipes zwischen dem Elternteil und dem Kind, das Kind prüft, wenn es ein Byte liest, und sendet dann ein Byte mit dem Status zurück.

Benutzeravatar von badeip
badeip

Ich hatte ein ähnliches Bedürfnis und kam auf die folgenden Alternativen

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

Wenn sie aufgerufen wird, unterbricht die debug_break-Funktion nur, wenn ein Debugger angeschlossen ist.

Wenn Sie auf x86 laufen und einen Haltepunkt wollen, der den Anrufer unterbricht (nicht in heben), fügen Sie einfach den folgenden Header ein und verwenden Sie das Makro 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

Ich habe festgestellt, dass eine modifizierte Version des Dateideskriptors “hack” von Silviocesare beschrieben und gebloggt von xorl hat bei mir gut funktioniert.

Dies ist der modifizierte Code, den ich verwende:

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

  • @franz1 interessant. danke für den Kommentar. Ich habe kürzlich GDB 7.10 zu Hause erstellt, also werde ich testen und Code lesen und versuchen, dies zu aktualisieren. Danke schön.

    – pestophag

    11. November 2015 um 16:09 Uhr

  • Dies hat wahrscheinlich zu viele Fehlalarme. Dateideskriptoren können aus vielen Gründen durchgesickert sein. Sogar bekannte Open-Source-Programme tun dies manchmal (z bugs.launchpad.net/ubuntu/+source/kde4libs/+bug/385999 )

    – Chys

    25. März 2021 um 13:27 Uhr

Benutzeravatar von Peter Mortensen
Peter Mortensen

Wenn Sie nur wissen möchten, ob die Anwendung zu Debugging-Zwecken unter GDB läuft, ist dies die einfachste Lösung unter Linux readlink("/proc/<ppid>/exe")und suchen Sie das Ergebnis nach "gdb".

  • @franz1 interessant. danke für den Kommentar. Ich habe kürzlich GDB 7.10 zu Hause erstellt, also werde ich testen und Code lesen und versuchen, dies zu aktualisieren. Danke schön.

    – pestophag

    11. November 2015 um 16:09 Uhr

  • Dies hat wahrscheinlich zu viele Fehlalarme. Dateideskriptoren können aus vielen Gründen durchgesickert sein. Sogar bekannte Open-Source-Programme tun dies manchmal (z bugs.launchpad.net/ubuntu/+source/kde4libs/+bug/385999 )

    – Chys

    25. März 2021 um 13:27 Uhr

Dies ähnelt der Antwort von Terminus, verwendet jedoch Pipes für die Kommunikation:

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

Beim Ausprobieren des ursprünglichen Codes unter OS X stellte ich fest, dass waitpid (im übergeordneten Element) immer -1 mit einem EINTR (Systemaufruf unterbrochen) zurückgab. Dies wurde durch Pattach verursacht, das Anhängen an das übergeordnete Element und das Unterbrechen des Anrufs.

Es war nicht klar, ob es sicher war, waitpid einfach erneut aufzurufen (das schien sich in einigen Situationen falsch zu verhalten), also habe ich stattdessen einfach eine Pipe verwendet, um die Kommunikation durchzuführen. Es ist ein bisschen zusätzlicher Code, wird aber wahrscheinlich auf mehr Plattformen zuverlässig funktionieren.

Dieser Code wurde getestet OS X v10.9.3 (Mavericks), Ubuntu 14.04 (Trusty Tahr) (3.13.0-24-generisch) und FreeBSD 10.0.

Für Linux, das Prozessfähigkeiten implementiert, funktioniert diese Methode nur, wenn der Prozess die hat CAP_SYS_PTRACE Capability, die normalerweise gesetzt wird, wenn der Prozess als Root ausgeführt wird.

Andere Dienstprogramme (gdb und lldb) haben diese Funktion ebenfalls als Teil ihrer Dateisystem-Metadaten festgelegt.

Sie können erkennen, ob der Prozess wirksam ist CAP_SYS_PTRACE durch Verlinkung gegen -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;
}

1416280cookie-checkSo erkennen Sie, ob der aktuelle Prozess von GDB ausgeführt wird

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy