Debugging 是否可以将gdb连接到崩溃的进程(又称“即时”调试)
当进程崩溃时,我希望能够在崩溃但未清理的状态下对其调用gdb(或类似的调试器)。通常,对内核转储进行事后检查会提供足够的信息,但有时我想进一步研究运行状态,可能会抑制直接故障并进一步运行。从一开始就在gdb下运行流程并不总是合适的(例如,调用很复杂或者bug对时间非常敏感) 我所描述的基本上是通过“AEDebug”注册表项在MS Windows上公开的实时调试功能:在执行诊断操作时暂停故障线程。在非开发人员的Windows PC上,这通常被设置为崩溃诊断机制(以前称为“Dr Watson”),Ubuntu的等效机制似乎就是这样Debugging 是否可以将gdb连接到崩溃的进程(又称“即时”调试),debugging,crash,gdb,Debugging,Crash,Gdb,当进程崩溃时,我希望能够在崩溃但未清理的状态下对其调用gdb(或类似的调试器)。通常,对内核转储进行事后检查会提供足够的信息,但有时我想进一步研究运行状态,可能会抑制直接故障并进一步运行。从一开始就在gdb下运行流程并不总是合适的(例如,调用很复杂或者bug对时间非常敏感) 我所描述的基本上是通过“AEDebug”注册表项在MS Windows上公开的实时调试功能:在执行诊断操作时暂停故障线程。在非开发人员的Windows PC上,这通常被设置为崩溃诊断机制(以前称为“Dr Watson”),U
我确实发现了一个问题,它提到了这个问题“不时出现”,所以它可能存在,但以一种逃避我搜索的方式进行了描述?我不知道是否存在这样的功能,但作为一个黑客,你可以预加载一些东西,在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)。对原始答案的真正改变是:
/*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);
}