Go Wiki: CoreDumpDebugging

最初發佈於 https://rakyll.org/coredumps/


除錯對於檢查執行流程和理解程式當前狀態非常有益。

核心檔案(core file)是一個包含正在執行程序的記憶體轉儲及其程序狀態的檔案。它主要用於程式的事後除錯,以及在程式仍在執行時瞭解其狀態。這兩種情況都使得對核心轉儲的除錯成為事後分析生產服務的良好診斷輔助手段。

在這篇文章中,我將使用一個簡單的“hello world” Web 伺服器,但在實際生活中,我們的程式可能會很容易變得非常複雜。核心轉儲分析的可用性讓您有機會從特定的快照中“復活”程式,並深入研究那些可能只在特定條件/環境中才能重現的案例。

注意:目前,此流程端到端僅在 Linux 上有效。我對其他 Unix 系統不太確定,但 macOS 尚未支援。Windows 目前也不支援。

在我們開始之前,您需要確保您的 ulimit 對核心轉儲的設定在一個合理的水平。預設情況下它是 0,這意味著最大的核心檔案大小隻能為零。我通常會在我的開發機器上將其設定為 unlimited,方法是輸入:

$ ulimit -c unlimited

然後,請確保您已在機器上安裝了 delve

這是一個包含簡單處理程式並啟動 HTTP 伺服器的 main.go 檔案。

$ cat main.go
package main

import (
    "fmt"
    "log"
    "net/http"
)

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprint(w, "hello world\n")
    })
    log.Fatal(http.ListenAndServe("localhost:7777", nil))
}

讓我們構建這個程式並生成一個二進位制檔案。

$ go build .

假設將來這個伺服器出現了什麼混亂的問題,但您不確定它到底是什麼。您可能已經以各種方式對程式進行了插樁,但這可能不足以從現有的插樁資料中獲得任何線索。

基本上,在這種情況下,最好能獲得當前程序的快照,然後使用該快照透過您現有的除錯工具深入瞭解程式的當前狀態。

有幾種方法可以獲取核心檔案。您可能已經熟悉崩潰轉儲(crash dumps),這些基本上是程式崩潰時寫入磁碟的核心轉儲。Go 預設不啟用崩潰轉儲,但在設定了 GOTRACEBACK 環境變數為“crash”的情況下,可以透過 Ctrl+backslash 選項進行生成。

$ GOTRACEBACK=crash ./hello
(Ctrl+\)

這將導致程式崩潰,並列印堆疊跟蹤,同時會寫入核心轉儲檔案。

另一種選擇是在不殺死程序的情況下從正在執行的程序中檢索核心轉儲。使用 gcore,可以在不導致程序崩潰的情況下獲取核心檔案。讓我們再次啟動伺服器:

$ ./hello &
$ gcore 546 # 546 is the PID of hello.

我們獲得了一個沒有讓程序崩潰的核心轉儲。下一步是載入核心檔案到 delve 並開始分析。

$ dlv core ./hello core.546

好了,就是這樣!這與典型的 delve 互動式除錯沒有區別。您可以進行回溯、列表、檢視變數等等。由於核心轉儲是一個快照而不是一個正在執行的程序,一些功能將被停用,但執行流程和程式狀態將完全可訪問。

(dlv) bt
 0  0x0000000000457774 in runtime.raise
    at /usr/lib/go/src/runtime/sys_linux_amd64.s:110
 1  0x000000000043f7fb in runtime.dieFromSignal
    at /usr/lib/go/src/runtime/signal_unix.go:323
 2  0x000000000043f9a1 in runtime.crash
    at /usr/lib/go/src/runtime/signal_unix.go:409
 3  0x000000000043e982 in runtime.sighandler
    at /usr/lib/go/src/runtime/signal_sighandler.go:129
 4  0x000000000043f2d1 in runtime.sigtrampgo
    at /usr/lib/go/src/runtime/signal_unix.go:257
 5  0x00000000004579d3 in runtime.sigtramp
    at /usr/lib/go/src/runtime/sys_linux_amd64.s:262
 6  0x00007ff68afec330 in (nil)
    at :0
 7  0x000000000040f2d6 in runtime.notetsleep
    at /usr/lib/go/src/runtime/lock_futex.go:209
 8  0x0000000000435be5 in runtime.sysmon
    at /usr/lib/go/src/runtime/proc.go:3866
 9  0x000000000042ee2e in runtime.mstart1
    at /usr/lib/go/src/runtime/proc.go:1182
10  0x000000000042ed04 in runtime.mstart
    at /usr/lib/go/src/runtime/proc.go:1152

(dlv) ls
> runtime.raise() /usr/lib/go/src/runtime/sys_linux_amd64.s:110 (PC: 0x457774)
   105:     SYSCALL
   106:     MOVL    AX, DI  // arg 1 tid
   107:     MOVL    sig+0(FP), SI   // arg 2
   108:     MOVL    $200, AX    // syscall - tkill
   109:     SYSCALL
=> 110:     RET
   111:
   112: TEXT runtime·raiseproc(SB),NOSPLIT,$0
   113:     MOVL    $39, AX // syscall - getpid
   114:     SYSCALL
   115:     MOVL    AX, DI  // arg 1 pid

此內容是 Go Wiki 的一部分。