Go Wiki: CoreDumpDebugging

元々https://rakyll.org/coredumps/で公開されました。


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

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

この記事では、簡単な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 .

将来、このサーバーで何か問題が発生していて、それが何なのかよくわからないと仮定します。さまざまな方法でプログラムを計測しているかもしれませんが、既存の計測データから手がかりを得るには不十分な可能性があります。

基本的に、このような状況では、現在のプロセスのスナップショットを作成し、そのスナップショットを使用して、既存のデバッグツールでプログラムの現在の状態を詳しく調べることができると便利です。

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

$ 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の一部です。