除錯Go語言的核心轉儲(Core Dumps)

曼託斯發表於2019-02-16

翻譯原文連結 轉帖/轉載請註明出處

英文原文連結【Go, the unwritten parts】 發表於2017/05/22 作者JBD是Go語言開發小組成員

檢查程式的執行路徑和當前狀態是非常有用的除錯手段。核心檔案(core file)包含了一個執行程式的記憶體轉儲和狀態。它主要是用來作為事後除錯程式用的。它也可以被用來檢視一個執行中的程式的狀態。這兩個使用場景使除錯檔案轉儲成為一個非常好的診斷手段。我們可以用這個方法來做事後診斷和分析線上的服務(production services)。

在這篇文章中,我們將用一個簡單的hello world網站服務作為例子。在現實中,我們的程式很容易就會變得很複雜。分析核心轉儲給我們提供了一個機會去重構程式的狀態並且檢視只有在某些條件/環境下才能重現的案例。

作者注: 這個除錯流程只在Linux上可行。我不是很確定它是否在其它Unixs系統上工作。macOS對此還不支援。Windows現在也不支援。

在我們開始前,需要確保核心轉儲的ulimit設定在合適的範圍。它的預設值是0,意味著最大的核心檔案大小是0。我通常在我的開發機器上將它設定成unlimited。使用以下命令:

$ ulimit -c unlimited

接下來,你需要在你的機器上安裝delve

下面我們使用的main.go檔案。它註冊了一個簡單的請求處理函式(handler)然後啟動了HTTP服務。

$ 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
")
    })
    log.Fatal(http.ListenAndServe("localhost:7777", nil))
}

讓我們編譯並生產二進位制檔案。

$ go build .

現在讓我們假設,這個伺服器出了些問題,但是我們並不是很確定問題的根源。你可能已經在程式里加了很多輔助資訊,但還是無法從這些除錯資訊中找出線索。通常在這種情況下,當前程式的快照會非常有用。我們可以用這個快照深入檢視程式的當前狀態。

有幾個方式來獲取核心檔案。你可能已經熟悉了奔潰轉儲(crash dumps)。它們是在一個程式奔潰的時候寫入磁碟的核心轉儲。Go語言在預設設定下不會生產奔潰轉儲。但是當你把GOTRACEBACK環境變數設定成“crash”,你就可以用Ctrl+backslash才觸發奔潰轉儲。如下圖所示:

$ GOTRACEBACK=crash ./hello
(Ctrl+)

上面的操作會使程式終止,將堆疊跟蹤(stack trace)列印出來,並把核心轉儲檔案寫入磁碟。

另外個方法可以從一個執行的程式獲得核心轉儲而不需要終止相應的程式。gcore可以生產核心檔案而無需使執行中的程式退出。

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

根據上面的操作,我們獲得了轉儲而沒有終止對應的程式。下一步就是把核心檔案載入進delve並開始分析。

$ dlv core ./hello core.546

差不多就這些。delve的常用操作都可以使用。你可以backtrace,list,檢視變數等等。有些功能不可用因為我們使用的核心轉儲是一個快照而不是正在執行的程式。但是程式執行路徑和狀態全部可以訪問。

(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

 

相關文章