概要
Goでデバッグをする際にdelveというツールがオススメです。
環境
- golang v1.8.3
- delve 1.0.0-rc.1
インストール
brew
でもインストールできますが、単体テストの実行の際に上手く動かないことがあったのでgo get
の方をオススメします。
$ xcode-select --install $ go get -u github.com/derekparker/delve/cmd/dlv
通常の使い方
以下のコードに対して実行するとします。
func main() { a := Hoge("main") fmt.Println(a) } func Hoge(m string) string { hoge := "hoge" fuga := m + hoge + "fuga" return fuga }
起動
$ dlv debug hoge.go (dlv)
ソースの確認
ls <func>
やls <pkg>.<func>
の形式で表示できます。
関数指定の場合
(dlv) ls Hoge 10: fmt.Println(a) 11: 12: fmt.Println("end") 13: } 14: 15: func Hoge(m string) string { 16: hoge := "hoge" 17: fuga := m + hoge + "fuga" 18: 19: return fuga 20: }
パッケージ指定の場合
(dlv) ls main.main 1: package main 2: 3: import "fmt" 4: 5: func main() { 6: fmt.Println("start") 7: 8: a := Hoge("main") 9: 10: fmt.Println(a)
ブレークポイント設定
b <func>
やb <filename>:<line>
で設定できます。オススメは後者です。
(dlv) b hoge.go:6 Breakpoint 1 set at 0x1087223 for main.Hoge() ./hoge.go:11
開始
(dlv) c > main.main() ./hoge.go:6 (hits goroutine(1):1 total:1) (PC: 0x1087122) 1: package main 2: 3: import "fmt" 4: 5: func main() { => 6: fmt.Println("start") 7: 8: a := Hoge("main") 9: 10: fmt.Println(a) 11:
ちゃんとブレークポイントで止まります。
1行ずつ実行
n
を入力します。
(dlv) n start > main.main() ./hoge.go:8 (PC: 0x10871e5) 3: import "fmt" 4: 5: func main() { 6: fmt.Println("start") 7: => 8: a := Hoge("main") 9: 10: fmt.Println(a) 11: 12: fmt.Println("end") 13: }
1つずつ進みます。
ステップイン
関数の中に入りたい時はs
を入力
(dlv) s > main.Hoge() ./hoge.go:15 (PC: 0x10873c3) 10: fmt.Println(a) 11: 12: fmt.Println("end") 13: } 14: => 15: func Hoge(m string) string { 16: hoge := "hoge" 17: fuga := m + hoge + "fuga" 18: 19: return fuga 20: }
すると定義元まで行ってくれます。
変数の確認
locals
で表示できます。これができればもうプリントデバッグはしなくて済みますね。
(dlv) locals hoge = (unreadable could not read string at 0x1 due to protocol error E08 during memory read for packet $m1,6) fuga = ""
今の行数だとまだ代入されてないですが、処理を進めてから実行してみます。
(dlv) n > main.Hoge() ./hoge.go:17 (PC: 0x10873fe) 12: fmt.Println("end") 13: } 14: 15: func Hoge(m string) string { 16: hoge := "hoge" => 17: fuga := m + hoge + "fuga" 18: 19: return fuga 20: } (dlv) locals hoge = "hoge" fuga = ""
hoge :=
の処理が終わった後は中身が代入されてますし、
(dlv) n > main.Hoge() ./hoge.go:19 (PC: 0x108745f) 14: 15: func Hoge(m string) string { 16: hoge := "hoge" 17: fuga := m + hoge + "fuga" 18: => 19: return fuga 20: } (dlv) locals hoge = "hoge" fuga = "mainhogefuga"
fuga
の方も代入後はちゃんと変数に値が入っています。
再実行
ブレークポイントを残したままもう一度処理を再実行したい場合はrestart
です。
(dlv) restart Process restarted with PID 55704 (dlv) c > main.main() ./hoge.go:6 (hits goroutine(1):1 total:1) (PC: 0x1087122) 1: package main 2: 3: import "fmt" 4: 5: func main() { => 6: fmt.Println("start") 7: 8: a := Hoge("main") 9: 10: fmt.Println(a) 11:
単体テストでの使い方
小さなプログラムであれば、問題の箇所にブレークポイントをセットしてそこまで実行させればいいですが、大きなプログラムだとそれは大変です。
なので単体テストでチェックします。
対象のテスト
func TestHoge(t *testing.T) { tests := []struct { in string out string }{ {"1", "1hogefuga"}, {"tmp", "tmphogefuga"}, } for _, v := range tests { out := Hoge(v.in) if out != v.out { t.Errorf("input %s\n, get %s\n, want %s\n", v.in, out, v.out) } } }
実行方法
dlv test
を使います。また、通常単体テストを指定する時にgo test -run TestHoge
と実行するように、-test.run
オプションを後ろにつけます。
$ dlv test -- -test.run TestHoge (dlv)
関数にブレークポイントをセットします。
(dlv) b hoge.go:15 Breakpoint 1 set at 0x10efd13 for _/Users/a13156.Hoge() ./hoge.go:15
実行してみます。
(dlv) c > _/Users/jun06t.Hoge() ./hoge.go:15 (hits goroutine(5):1 total:1) (PC: 0x10efd13) 10: fmt.Println(a) 11: 12: fmt.Println("end") 13: } 14: => 15: func Hoge(m string) string { 16: hoge := "hoge" 17: fuga := m + hoge + "fuga" 18: 19: return fuga 20: }
変数を確認してみると、ちゃんとテストの値が入っていることが分かります。
(dlv) n > _/Users/jun06t.Hoge() ./hoge.go:19 (PC: 0x10efdaf) 14: 15: func Hoge(m string) string { 16: hoge := "hoge" 17: fuga := m + hoge + "fuga" 18: => 19: return fuga 20: } (dlv) locals hoge = "hoge" fuga = "1hogefuga"
その他ハマった点
lldb-serverが実行できない
could not launch process: exec: "lldb-server": executable file not found in $PATH
というエラーです。以下を実行したら僕は直りました。
$ xcode-select --install
getsockopt: connection refused
could not launch process: dial tcp :49754: getsockopt: connection refused
というエラーです。githubのissueを見たところ、僕の環境では結局brew
をやめてgo get
で入れたら直りました。
バイナリに対して実行したら変数が表示されない
delveはdlv exec <binary>
と実行すればバイナリに対してもデバッグできます。
しかしgoのビルドは通常だと最適化され、実行された関数や変数の値はレジスタへ保存されるため、そのままだとdelveで変数などが上手く表示できません。
(dlv) locals (no locals)
なのでビルド時には最適化を無効化するために以下のオプションを付けます。
$ go build -gcflags '-N -l' sample.go
こうして作ったバイナリであれば変数もきちんと表示されました。