Возможно ли подключить gdb к аварийному процессу (отладка "a.k.a" точно в срок ")

Когда сбой процесса, я хочу, чтобы он вызывал gdb (или аналогичный отладчик) в этом состоянии с разбивкой, но не очищенным. Часто после вскрытия дампа ядра дает достаточно информации, но иногда я хочу продолжить изучение состояния работы, возможно, подавляя немедленную ошибку и работая немного дальше. Не всегда целесообразно запускать процесс под gdb с самого начала (например, когда вызов является сложным или ошибка является абсурдно чувствительной к времени)

То, что я описываю, - это в основном средство отладки в режиме "точно в срок", которое отображается в MS Windows с помощью раздела реестра "AEDebug": оставляя зависающую нить приостановленной, делая что-то диагностическое. На компьютерах с ОС, отличных от разработчиков, обычно устанавливается механизм диагностики сбоев (ранее "Dr Watson" ), для которого эквивалент Ubuntu выглядит "apport" .

Я нашел старый почтовый поток (2007), который ссылается на этот вопрос "время от времени появляющийся", поэтому, возможно, он существует, но описан в некотором роде что ускользает от моих поисков?

Ответ 1

Я не знаю, существует ли такая функция, но как хак, вы можете LD_PRELOAD что-то, что добавляет обработчик на SIGSEGV, который вызывает gdb:

cat >> handler.c << 'EOF'
#include <stdlib.h>
#include <signal.h>
void gdb(int sig) {
  system("exec xterm -e gdb -p \"$PPID\"");
  abort();
}

void _init() {
  signal(SIGSEGV, gdb);
}
EOF
gcc -g -fpic -shared -o handler.so -nostartfiles handler.c

И затем запустите свои приложения с помощью:

LD_PRELOAD=/path/to/handler.so your-application

Затем, после SEGV, он будет работать gdb в xterm. Если вы выполните bt, вы увидите что-то вроде:

(gdb) bt
#0  0x00007f8c58152cac in __libc_waitpid (pid=8294,
    [email protected]=0x7fffd6170e40, [email protected]=0)
    at ../sysdeps/unix/sysv/linux/waitpid.c:31
#1  0x00007f8c580df01b in do_system (line=<optimized out>)
    at ../sysdeps/posix/system.c:148
#2  0x00007f8c58445427 in gdb (sig=11) at ld.c:4
#3  <signal handler called>
#4  strlen () at ../sysdeps/x86_64/strlen.S:106
#5  0x00007f8c5810761c in _IO_puts (str=0x0) at ioputs.c:36
#6  0x000000000040051f in main (argc=1, argv=0x7fffd6171598) at a.c:2

Вместо запуска gdb вы также можете приостановить себя (kill(getpid(), SIGSTOP) или вызвать pause(), чтобы начать gdb самостоятельно в свободное время.

Этот подход не будет работать, если приложение установит сам обработчик SEGV или будет setuid/setgid...

Этот подход, используемый @yugr для своего libdebugme tool, который вы можете использовать здесь как:

DEBUGME_OPTIONS='xterm:handle_signals=1' \
  LD_PRELOAD=/path/to/libdebugme.so your-application

Ответ 2

Отвечая на мой собственный вопрос, чтобы включить код скрещенности, я получил от истинного ответа (@Stephane Chazelas выше). Только реальные изменения исходного ответа:

  • настройка PR_SET_PTRACER_ANY, позволяющая подключать gdb
  • немного больше (бесполезно?), пытаясь избежать кода libc в надежде на продолжение работы для (некоторых) сбоев кучи
  • включал SIGABRT, потому что некоторые из аварий были assert() s

Я использовал его с Linux Mint 16 (ядро 3.11.0-12-generic)

/* LD_PRELOAD library which launches gdb "just-in-time" in response to a process SIGSEGV-ing
 * Compile with:
 *
 * gcc -g -fpic -shared -nostartfiles -o jitdbg.so jitdbg.c
 * 
 * then put in LD_PRELOAD before running process, e.g.:
 * 
 * LD_PRELOAD=~/scripts/jitdbg.so defective_executable
 */

#include <unistd.h>
#include <signal.h>
#include <sys/prctl.h>


void gdb(int sig) {
  if(sig == SIGSEGV || sig == SIGABRT)
    {
      pid_t cpid = fork();
      if(cpid == -1)
        return;   // fork failed, we can't help, hope core dumps are enabled...
      else if(cpid != 0)
        {
          // Parent
          prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY, 0, 0, 0);  // allow any process to ptrace us
          raise(SIGSTOP);  // wait for child gdb invocation to pick us up
        }
      else
        {
          // Child - now try to exec gdb in our place attached to the parent

          // Avoiding using libc since that may already have been stomped, so building the
          // gdb args the hard way ("gdb dummy PID"), first copy
          char cmd[100];
          const char* stem = "gdb _dummy_process_name_                   ";  // 18 trailing spaces to allow for a 64 bit proc id
          const char*s = stem;
          char* d = cmd; 
          while(*s)
            {
            *d++ = *s++;
            }
          *d-- = '\0';
          char* hexppid = d;

          // now backfill the trailing space with the hex parent PID - not
          // using decimal for fear of libc maths helper functions being dragged in
          pid_t ppid = getppid();
          while(ppid)
            {
              *hexppid = ((ppid & 0xF) + '0');
              if(*hexppid > '9')
                *hexppid += 'a' - '0' - 10;
              --hexppid;
              ppid >>= 4;
            }
          *hexppid-- = 'x';   // prefix with 0x
          *hexppid = '0';
          // system() isn't listed as safe under async signals, nor is execlp, 
          // or getenv. So ideally we'd already have cached the gdb location, or we
          // hardcode the gdb path, or we accept the risk of re-entrancy/library woes
          // around the environment fetch...
          execlp("mate-terminal", "mate-terminal", "-e", cmd, (char*) NULL);
        }
    }
}

void _init() {
  signal(SIGSEGV, gdb);
  signal(SIGABRT, gdb);
}

Ответ 3

Если вы можете ожидать, что какая-то конкретная программа выйдет из строя, вы можете запустить ее под gdb.

gdb /usr/local/bin/foo
> run

Если программа выйдет из строя, gdb поймает ее и позволит продолжить расследование.

Если вы не можете предсказать, когда и какая программа выйдет из строя, тогда вы можете включить основные свалки системы в целом.

ulimit -c unlimited

Принудительный дамп процесса foo

/usr/local/sbin/foo
kill -11 `pidof foo` #kill -3 likely will also work

Должен быть создан основной файл, к которому вы можете подключить gdb к

gdb attach `which foo` -c some.core

Для систем RedHat иногда требуется дополнительная настройка, кроме ulimit, для включения дампов ядра.

http://www.akadia.com/services/ora_enable_core.html