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 的一部分。