是否可以将gdb附加到崩溃的进程(a.k.a"及时"调试)

时间:2014-03-18 14:06:01

标签: debugging crash gdb

当进程崩溃时,我希望有可能在崩溃但未清理的状态下对它调用gdb(或类似的调试器)。经常对核心转储进行后期处理可以提供足够的信息,但有时我想进一步探索运行状态,可能会抑制即时故障并进一步运行。从一开始就在gdb下运行进程并不总是合适的(例如,调用很复杂或者错误是时间敏感的)

我所描述的基本上是通过" AEDebug"在MS Windows上公开的即时调试工具。注册表项:在执行某些诊断时保留故障线程。在非开发人员的Windows PC上,这通常设置为崩溃诊断机制(以前称为#34; Dr Watson"),其Ubuntu等价物似乎是"apport"

我确实找到一个old mail thread (2007)引用了这个问题"偶尔弹出",所以它可能存在但是以一种逃避我搜索的方式描述?

3 个答案:

答案 0 :(得分:18)

我不知道是否存在这样的功能,但作为一个黑客,你可以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,
    stat_loc=stat_loc@entry=0x7fffd6170e40, options=options@entry=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

答案 1 :(得分:6)

回答我自己的问题,包括我从真实答案中得出的充实代码(上面的@Stephane Chazelas)。只有对原始答案的真正更改是:

  1. 设置PR_SET_PTRACER_ANY以允许gdb附加
  2. 多一点(徒劳无功?)试图避免使用libc代码以期仍在工作 (某些)堆损坏
  3. 包括SIGABRT,因为一些崩溃是assert()s
  4. 我一直在使用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's 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);
    }
    

答案 2 :(得分:2)

如果您能够预料到特定程序会崩溃,您可以在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