跳轉到內容

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()無法呼叫。


參考文獻
[編輯 | 編輯原始碼]
華夏公益教科書