Linux 应用程序调试技术/核心文件
核心转储是程序内存的快照,包括程序计数器和堆栈指针在内的处理器寄存器以及其他操作系统和内存管理信息,在特定时间点捕获。因此,它们对于捕获罕见的竞争条件和异常情况至关重要。
更重要的是,这种罕见的情况通常会在使用率很高的生产或 QA 机器上出现,而gdb不可用,访问机器也不容易。更糟糕的是,使用率最高的通常是最大的客户(从金钱角度)。因此,获取尽可能多的取证数据并为此做好计划非常重要。
可以从程序内部或外部强制在选定的时刻转储核心文件。核心文件无法告诉应用程序是如何进入该状态的:核心文件无法替代良好的日志。详细的日志和核心文件相辅相成。
为了使进程能够转储核心文件,必须满足一些先决条件
- 核心文件大小限制应该允许它(请参阅 ulimit 的手册页)。例如ulimit -c unlimited. 它也可以从程序内部设置。
- 转储核心文件的进程应该对核心文件要转储到的文件夹具有写入权限(通常是进程的当前工作目录)
通常,核心文件转储到进程的当前工作目录。但操作系统可以配置为其他方式
# cat /proc/sys/kernel/core_pattern
%h-%e-%p.core
# sysctl -w "kernel.core_pattern=/var/cores/%h-%e-%p.core"
一种可能性是使用 gdb(如果可用)。这将使程序继续运行
(gdb) attach <pid>
(gdb) generate-core-file <optional-filename>
(gdb) detach
另一种可能性是向进程发送信号。这将终止它,假设该信号没有被自定义信号处理程序捕获
kill -s SIGABRT <pid>
同样,也有两种可能性:转储核心文件并终止程序,或转储并继续
void dump_core_and_terminate(void)
{
/*
* Alternative:
* char *p = NULL; *p = 0;
*/
abort();
}
void dump_core_and_continue(void)
{
pid_t child = fork();
if (child < 0) {
/*Parent: error*/
}
else if (child == 0) {
dump_core_and_terminate(); /*Child*/
}
else {
/*Parent: continue*/
}
}
注意:使用dump_core_and_continue()要谨慎:在多线程程序中,派生的子进程将只拥有调用父线程的克隆fork()[Butenhof Ch5; 关于线程和 fork]。这有许多影响,特别是对于互斥锁,但这里重点是子进程转储的核心文件将只包含一个线程的信息。如果您需要转储包含所有线程的核心文件,而又不终止进程,请尝试使用 谷歌核心转储库,即使它多年未维护。
为了获得良好的调用栈,重要的是 gdb 加载与生成核心文件的程序加载的相同的库。如果分析核心文件的机器上的库与转储核心文件的机器上的库不同(或位于不同位置),则将库复制到分析机器上,并镜像转储机器。例如
$ tree .
.
|-- juggler-29964.core
|-- lib64
| |-- ld-linux-x86-64.so.2
| |-- libc.so.6
| |-- libm.so.6
| |-- libpthread.so.0
| `-- librt.so.1
...
在 gdb 提示符下
(gdb) set solib-absolute-prefix ./
(gdb) set solib-search-path .
(gdb) file ../../../../../threadpool/bin.v2/libs/threadpool/example/juggler/gcc-4.1.2/debug/link-static/threading-multi/juggler
Reading symbols from /home/aurelian_melinte/threadpool/threadpool-0_2_5-src/threadpool/bin.v2/libs/threadpool/example/juggler/gcc-4.1.2/debug/link-static/threading-multi/juggler...done.
(gdb) core-file juggler-29964.core
Reading symbols from ./lib64/librt.so.1...(no debugging symbols found)...done.
Loaded symbols for ./lib64/librt.so.1
Reading symbols from ./lib64/libm.so.6...(no debugging symbols found)...done.
Loaded symbols for ./lib64/libm.so.6
Reading symbols from ./lib64/libpthread.so.0...(no debugging symbols found)...done.
Loaded symbols for ./lib64/libpthread.so.0
Reading symbols from ./lib64/libc.so.6...(no debugging symbols found)...done.
Loaded symbols for ./lib64/libc.so.6
Reading symbols from ./lib64/ld-linux-x86-64.so.2...(no debugging symbols found)...done.
Loaded symbols for ./lib64/ld-linux-x86-64.so.2
Core was generated by `../../../../bin.v2/libs/threadpool/example/juggler/gcc-4.1.2/debug/link-static/'.
Program terminated with signal 6, Aborted.
#0 0x0000003684030265 in raise () from ./lib64/libc.so.6
(gdb) frame 2
#2 0x0000000000404ae1 in dump_core_and_terminate () at juggler.cpp:30
将调试器指向源文件
(gdb) set substitute-path /from/path1 /to/path1
(gdb) set substitute-path /from/path2 /to/path2
以下脚本将为每个核心文件生成一份基本报告。当核心文件像雨点一样降落在您身上时,这很有用
#!/bin/bash
#
# A script to extract core-file informations
#
if [ $# -ne 1 ]
then
echo "Usage: `basename $0` <for-binary-image>"
exit -1
else
binimg=$1
fi
# Today and yesterdays cores
cores=`find . -name '*.core' -mtime -1`
#cores=`find . -name '*.core'`
for core in $cores
do
gdblogfile="$core-gdb.log"
rm $gdblogfile
bininfo=`ls -l $binimg`
coreinfo=`ls -l $core`
gdb -batch \
-ex "set logging file $gdblogfile" \
-ex "set logging on" \
-ex "set pagination off" \
-ex "printf \"**\n** Process info for $binimg - $core \n** Generated `date`\n\"" \
-ex "printf \"**\n** $bininfo \n** $coreinfo\n**\n\"" \
-ex "file $binimg" \
-ex "core-file $core" \
-ex "bt" \
-ex "info proc" \
-ex "printf \"*\n* Libraries \n*\n\"" \
-ex "info sharedlib" \
-ex "printf \"*\n* Memory map \n*\n\"" \
-ex "info target" \
-ex "printf \"*\n* Registers \n*\n\"" \
-ex "info registers" \
-ex "printf \"*\n* Current instructions \n*\n\"" -ex "x/16i \$pc" \
-ex "printf \"*\n* Threads (full) \n*\n\"" \
-ex "info threads" \
-ex "bt" \
-ex "thread apply all bt full" \
-ex "printf \"*\n* Threads (basic) \n*\n\"" \
-ex "info threads" \
-ex "thread apply all bt" \
-ex "printf \"*\n* Done \n*\n\"" \
-ex "quit"
done
另一个值得探索的替代方案是 btparser.
相同的报告功能可以为 gdb 预定义
define procinfo
printf "**\n** Process Info: \n**\n"
info proc
printf "*\n* Libraries \n*\n"
info sharedlib
printf "*\n* Memory Map \n*\n"
info target
printf "*\n* Registers \n*\n"
info registers
printf "*\n* Current Instructions \n*\n"
x/16i $pc
printf "*\n* Threads (basic) \n*\n"
info threads
thread apply all bt
end
document procinfo
Infos about the debugee.
end
define analyze
procinfo
printf "*\n* Threads (full) \n*\n"
info threads
bt
thread apply all bt full
end
一个脚本,它将为正在运行的进程生成一份基本报告和一个核心文件
#!/bin/bash
#
# A script to generate a core and a status report for a running process.
#
if [ $# -ne 1 ]
then
echo "Usage: `basename $0` <PID>"
exit -1
else
pid=$1
fi
gdblogfile="analyze-$pid.log"
rm $gdblogfile
corefile="core-$pid.core"
gdb -batch \
-ex "set logging file $gdblogfile" \
-ex "set logging on" \
-ex "set pagination off" \
-ex "printf \"**\n** Process info for PID=$pid \n** Generated `date`\n\"" \
-ex "printf \"**\n** Core: $corefile \n**\n\"" \
-ex "attach $pid" \
-ex "bt" \
-ex "info proc" \
-ex "printf \"*\n* Libraries \n*\n\"" \
-ex "info sharedlib" \
-ex "printf \"*\n* Memory map \n*\n\"" \
-ex "info target" \
-ex "printf \"*\n* Registers \n*\n\"" \
-ex "info registers" \
-ex "printf \"*\n* Current instructions \n*\n\"" -ex "x/16i \$pc" \
-ex "printf \"*\n* Threads (full) \n*\n\"" \
-ex "info threads" \
-ex "bt" \
-ex "thread apply all bt full" \
-ex "printf \"*\n* Threads (basic) \n*\n\"" \
-ex "info threads" \
-ex "thread apply all bt" \
-ex "printf \"*\n* Done \n*\n\"" \
-ex "generate-core-file $corefile" \
-ex "detach" \
-ex "quit"
TLS 数据在核心文件中很难使用 gdb 访问,并且__tls_get_addr()无法调用。