Go Wiki: CoreDumpDebugging

元の記事は https://rakyll.org/coredumps/ で公開されました。


デバッグは、実行フローを調査し、プログラムの現在の状態を理解するために非常に役立ちます。

コアファイルは、実行中のプロセスのメモリダンプとプロセスステータスを含むファイルです。主にプログラムの事後デバッグに使用されるほか、プログラムがまだ実行中の状態を理解するためにも使用されます。これら2つのケースにより、コアダンプのデバッグは、本番サービスを事後分析するための優れた診断補助となります。

この記事ではシンプルな「hello world」Webサーバーを使用しますが、現実のプログラムは非常に複雑になることがあります。コアダンプ分析を利用できることで、特定の時点のスナップショットからプログラムを復元し、特定の条件/環境でのみ再現される可能性のあるケースを調査する機会が得られます。

: このフローは現時点では Linux でのみエンドツーエンドで動作します。他の Unix についてはよくわかりませんが、macOS ではまだサポートされていません。Windows は現時点ではサポートされていません。

始める前に、コアダンプの ulimit が適切なレベルであることを確認する必要があります。デフォルトでは 0 であり、これはコアファイルの最大サイズがゼロにしかならないことを意味します。私は通常、開発マシンで次のように入力して無制限に設定します。

$ 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 .

将来、このサーバーで何か厄介なことが起こっていると仮定しましょう。しかし、それが何であるか確信が持てません。プログラムをさまざまな方法で計測しているかもしれませんが、既存の計測データから手がかりを得るには不十分かもしれません。

基本的に、このような状況では、現在のプロセスのスナップショットを取得し、そのスナップショットを使用して既存のデバッグツールでプログラムの現在の状態を深く掘り下げることができれば素晴らしいでしょう。

コアファイルを取得する方法はいくつかあります。クラッシュダンプにはすでに慣れているかもしれません。これらは基本的に、プログラムがクラッシュしたときにディスクに書き込まれるコアダンプです。Go はデフォルトではクラッシュダンプを有効にしませんが、GOTRACEBACK 環境変数が「crash」に設定されている場合、Ctrl+バックスラッシュでこのオプションを提供します。

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

スタックトレースが出力され、プログラムがクラッシュし、コアダンプファイルが書き込まれます。

もう1つのオプションは、プロセスを強制終了することなく、実行中のプロセスからコアダンプを取得することです。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の一部です。