golang 使用 core dump 分析定位程序崩潰問題

一、前言

core dump 是一個包含着意外終止的程序其內存快照的文件。這個文件可以被用來事後調試(debugging)以瞭解爲什麼會發生崩潰,同時瞭解其中涉及到的變量。通過 GOTRACEBACK,Go 提供了一個環境變量用於控制程序崩潰時生成的輸出信息。這個變量同樣可以強制生成 core dump,從而使調試成爲可能。

1.1 GOTRACEBACK

GOTRACEBACK 控制程序崩潰時輸出的詳細程度。它可以採用不同的值:

二、功能設置

2.1 系統設置

開啓 core dump 功能:

ulimit -c unlimited

設置 core file size = unlimited,core dump file 的文件的大小無限制。也可以根據自己的需要把 core file 設置爲特定的大小如:

ulimit -c 1024

需要注意的是 core file size 的單位是 block, block = 512 字節。因通過 ulimit 設置 core file size 只對當前的終端起作用,當你關閉終端或者重啓系統設置會失效。爲了避免每次都設置的麻煩,可以在~/.profile 最後添加:

echo "ulimit -c unlimited" >> ~/.profile

設置 core dump 文件的位置

*kernel.core_pattern=/var/core/core%t%p_%e*
%t: 生成的文件的時間戳
%p:發生斷言的進程的id
%e:發生斷言的的代碼文件

執行:

sysctl -p /etc/sysctl.conf

2.2 GO 設置

設置 Go 環境變量

export GOTRACEBACK=crash

需要注意的是和 ulmit 命令一樣,在關閉終端和重啓系統後,設置的 GOTRACEBACK 變量會消失,所以要將’export GOTRACEBACK=crash’ 加入到 .profile 文件中

echo "export GOTRACEBACK=crash " >> ~/.profile

三、案例分析

GOTRACEBAK 變量可以控制程序在崩潰時,stack 的輸出情況。下面結合具體地程序來分析。

package main
import (
  "time"
  "github.com/astaxie/beego/logs"
)
func main() {
  logs.Info("Start...")
  defer logs.Info("exit.")
  i := 0
  c := make(chan int, 1)
  for {
    go func(i int) {
      mem := make([]int, 100*1024*1024)
      logs.Info("i=%d,mem:%p", i, mem)
      mem[0] = <-c
    }(i)
    i++
    time.Sleep(200 * time.Microsecond)
  }
}

該程序將很快崩潰,產生如下報錯:

goroutine 279 [running]:
  goroutine running on other thread; stack unavailable
created by main.main
  /opt/gopath/src/test/coredump_test/testcoredump.go:15 +0xdf
goroutine 290 [running]:
  goroutine running on other thread; stack unavailable
created by main.main
  /opt/gopath/src/test/coredump_test/testcoredump.go:15 +0xdf
Aborted (core dumped)

gdb 可以進行調試,查看程序運行的詳細情況:

 gdb testcoredump core.15956
GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-110.el7
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
...
(gdb) start
Temporary breakpoint 1 at 0x618c50: file /opt/gopath/src/test/coredump_test/testcoredump.go, line 9.
Starting program: /opt/gopath/src/test/coredump_test/testcoredump
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
[New Thread 0x7ffff77f1700 (LWP 15980)]
[New Thread 0x7ffff6ff0700 (LWP 15981)]
[New Thread 0x7ffff5fee700 (LWP 15983)]
[New Thread 0x7ffff67ef700 (LWP 15982)]
[New Thread 0x7ffff57ed700 (LWP 15984)]
Temporary breakpoint 1, main.main () at /opt/gopath/src/test/coredump_test/testcoredump.go:9
9  func main() {
(gdb)

gdb 常用命令:

start    //開始調試
n    //一條一條執行
step/s    //執行下一條,如果函數進入函數
backtrace/bt    //查看函數調用棧幀
info/i locals    //查看當前棧幀局部變量
frame/f    //選擇棧幀,再查看局部變量
print/p    //打印變量的值
finish    //運行到當前函數返回
set var sum=0    //修改變量值
list/l 行號或函數名    //列出源碼
display/undisplay sum    //每次停下顯示變量的值/取消跟蹤
break/b  行號或函數名    //設置斷點
continue/c    //連續運行
info/i breakpoints    //查看已經設置的斷點
delete breakpoints 2    //刪除某個斷點
disable/enable breakpoints 3    //禁用/啓用某個斷點
break 7 if ok == true    //滿足條件才激活斷點
run/r    //重新從程序開頭連續執行
watch input[7]    //設置觀察點
info/i watchpoints    //查看設置的觀察點
x/7b input    //打印存儲器內容,b--每個字節一組,7--7組
disassemble    //反彙編當前函數或指定函數
 si    // 一條指令一條指令調試 而 s 是一行一行代碼
 info registers    // 顯示所有寄存器的當前值
x/20 $esp    //查看內存中開始的20個數

四、總結

程序崩潰可以通過 coredump 詳細地查看程序調用棧的相關信息,可以更迅速的定位到程序的問題,特別是引起程序崩潰的 bug:內存泄漏,一些 panic 等,當然在寫程序時儘量多些 log 更方便調試。golang 自帶的 pprof 在涉及到 c 庫的調用時,會監測不到,這時 coredump 結合 gdb 進行調試會比較有用。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/YiONLLc614BneyCUsHE6pw