当进程崩溃时,我希望有可能在崩溃但未清理的状态下对它调用gdb(或类似的调试器)。经常对核心转储进行后期处理可以提供足够的信息,但有时我想进一步探索运行状态,可能会抑制即时故障并进一步运行。从一开始就在gdb下运行进程并不总是合适的(例如,调用很复杂或者错误是时间敏感的)
我所描述的基本上是通过" AEDebug"在MS Windows上公开的即时调试工具。注册表项:在执行某些诊断时保留故障线程。在非开发人员的Windows PC上,这通常设置为崩溃诊断机制(以前称为#34; Dr Watson"),其Ubuntu等价物似乎是"apport"。
我确实找到一个old mail thread (2007)引用了这个问题"偶尔弹出",所以它可能存在但是以一种逃避我搜索的方式描述?
答案 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)。只有对原始答案的真正更改是:
我一直在使用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之外的其他配置才能启用核心转储。