Debugging 是否可以将gdb连接到崩溃的进程(又称“即时”调试)

Debugging 是否可以将gdb连接到崩溃的进程(又称“即时”调试),debugging,crash,gdb,Debugging,Crash,Gdb,当进程崩溃时,我希望能够在崩溃但未清理的状态下对其调用gdb(或类似的调试器)。通常,对内核转储进行事后检查会提供足够的信息,但有时我想进一步研究运行状态,可能会抑制直接故障并进一步运行。从一开始就在gdb下运行流程并不总是合适的(例如,调用很复杂或者bug对时间非常敏感) 我所描述的基本上是通过“AEDebug”注册表项在MS Windows上公开的实时调试功能:在执行诊断操作时暂停故障线程。在非开发人员的Windows PC上,这通常被设置为崩溃诊断机制(以前称为“Dr Watson”),U

当进程崩溃时,我希望能够在崩溃但未清理的状态下对其调用gdb(或类似的调试器)。通常,对内核转储进行事后检查会提供足够的信息,但有时我想进一步研究运行状态,可能会抑制直接故障并进一步运行。从一开始就在gdb下运行流程并不总是合适的(例如,调用很复杂或者bug对时间非常敏感)

我所描述的基本上是通过“AEDebug”注册表项在MS Windows上公开的实时调试功能:在执行诊断操作时暂停故障线程。在非开发人员的Windows PC上,这通常被设置为崩溃诊断机制(以前称为“Dr Watson”),Ubuntu的等效机制似乎就是这样


我确实发现了一个问题,它提到了这个问题“不时出现”,所以它可能存在,但以一种逃避我搜索的方式进行了描述?

我不知道是否存在这样的功能,但作为一个黑客,你可以预加载一些东西,在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
然后,在SEGV上,它将在
xterm
中运行
gdb
。如果您在那里执行
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下启动它

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之外的其他配置来启用核心转储


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

  • 设置PR_SET_PTRACER_ANY以允许gdb连接
  • 还有一点(徒劳?)试图避免使用libc代码,希望仍能工作 对于(某些)堆损坏
  • 包括SIGABRT,因为一些崩溃是assert()的
  • 我一直在Linux Mint 16(内核3.11.0-12-generic)中使用它

    /*LD_预加载库,它启动gdb“及时”响应进程信号
    *编译时使用:
    *
    *gcc-g-fpic-shared-nostartfiles-o jitdbg.so jitdbg.c
    * 
    *然后在运行流程之前,输入LD_预加载,例如:
    * 
    *LD\u PRELOAD=~/scripts/jitdbg.so有缺陷的\u可执行文件
    */
    #包括
    #包括
    #包括
    无效gdb(内部信号){
    如果(sig==SIGSEGV | | sig==SIGABRT)
    {
    pid_t cpid=fork();
    如果(cpid==-1)
    return;//fork失败,我们无能为力,希望核心转储已启用。。。
    否则如果(cpid!=0)
    {
    //母公司
    prctl(PR_SET_PTRACER,PR_SET_PTRACER_ANY,0,0);//允许任何进程跟踪我们
    raise(SIGSTOP);//等待孩子的gdb调用来接我们
    }
    其他的
    {
    //Child-现在尝试在连接到父级的位置执行gdb
    //避免使用libc,因为这可能已经被践踏了,所以构建
    //gdb参数的硬方法(“gdb伪PID”),第一个副本
    char-cmd[100];
    const char*stem=“gdb\u dummy\u process\u name”;//18个尾随空格以允许64位进程id
    const char*s=茎;
    char*d=cmd;
    而(*s)
    {
    *d++=*s++;
    }
    *d--='\0';
    char*hexppid=d;
    //现在用十六进制父PID回填尾部空间-不
    //使用decimal以防libc数学助手函数被拖入
    pid_t ppid=getppid();
    while(ppid)
    {
    *hexppid=((ppid&0xF)+“0”);
    如果(*hexppid>“9”)
    *hexppid+='a'-'0'-10;
    --己二酸;
    ppid>>=4;
    }
    *hexppid--='x';//前缀为0x
    *hexppid='0';
    //system()在异步信号下未列为安全,execlp也未列为安全,
    //所以理想情况下我们已经缓存了gdb位置,或者
    //硬编码gdb路径,否则我们接受重新进入/库灾难的风险
    //围绕环境获取。。。
    execlp(“配对端子”、“配对端子”、“-e”、cmd、(char*)NULL);
    }
    }
    }
    void _init(){
    信号(SIGSEGV,gdb);
    信号(SIGABRT,gdb);
    }
    
    对于您的应用程序,您总是可以在SIGSEGV上添加一个信号处理程序。@StephaneChazelas-true,但不幸的是,我继承了大量测试可执行文件,这些文件都是通过一个有点难以理解的perl脚本编写的(它喜欢创建符号链接,有时是递归的:-))。总的来说,我认为它将是一个有用的工具,可以添加到我的工具箱中。您可能想看看
    valgrind
    的功能,因为我相信它可以在某些事件中调用gdb。@Stephanehazelas感谢valgrind的指导;我认为这个协会提供了一个。因此,这不是我所要寻找的特定的魔法子弹,而是一个更好的子弹。这当然是令人愉快的接近,因为(我认为!)它应该继承任何涉及到的子进程树。一个补充问题:似乎system()在信号处理程序()中并不一定是安全的-它通常是安全的并且被广泛使用的习惯用法,还是在它向等待的线程发送信号时出于偏执的安全考虑。还要感谢SIGSTOP/pause建议-让故障平静地休眠可能是浸泡测试的一个好默认值(让它在一夜之间敲打,在早上进行一小套尸检)@TomGoodfellow你可以直接做fork/exec,而不是清单上的那两个。我会
    /usr/local/sbin/foo
    kill -11 `pidof foo` #kill -3 likely will also work
    
    gdb attach `which foo` -c some.core
    
    /* 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);
    }