跳转到内容

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


分析进程 ID

[编辑 | 编辑源代码]

一个脚本,它将为正在运行的进程生成一份基本报告和一个核心文件

#!/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()无法调用。


参考文献
[编辑 | 编辑源代码]
华夏公益教科书