Go言語のデバッガはDelveとGDBが人気です。
GDBのドキュメントには以下のような記載があり、Delveの利用を推奨しています。
Note that Delve is a better alternative to GDB when debugging Go programs built with the standard toolchain.
(Delve は、標準ツールチェーンでビルドされた Go プログラムをデバッグする際に、GDB よりも優れた代替手段であることに注意してください。)
今回はDelveを利用したGo言語のデバッグ方法について紹介します。
目次
Delveの主なコマンド一覧
デバッグをする際によく利用するDelveのコマンドをピックアップしました。
内容 | コマンド | ショートコード |
---|---|---|
実行 | dlv debug [ファイル名] | N/A |
再実行 | restart | r |
終了 | exit quit |
q |
ブレークポイントの作成 | break [ファイル名]:[行数] | b [ファイル名]:[行数] |
ブレークポイントの確認 | breakpoints | bp |
次のブレークポイントへ移動 | continue | c |
次の行へ移動 | next | n |
ステップイン | step | s |
ステップアウト | stepout | N/A |
コードの表示 | ls [関数名]ls [パッケージ].[関数名] | N/A |
ローカル変数の表示 | locals | N/A |
変数の表示 | print [変数] | p [変数] |
変数の値の変更 | set [変数] = [値] | N/A |
スタックトレースの表示 | stack | bt |
スタックトレース上でデバッグを実行 | frame [番号] [コマンド] | N/A |
ヘルプの表示 | help | h |
Delveにはそのほかにも多数のコマンドが用意されています。
詳細はdelve/Documentation/cli/README.mdをご参考ください。
Delveのインストール方法
Delveのインストール手順はdelve/Documentation/installation/README.mdに記載されています。
$ go install github.com/go-delve/delve/cmd/dlv@latest
### dlvコマンドが存在していればOK
$ which dlv
/go/bin/dlv
Delveを利用した具体的なデバッグ方法
以下のコードを例に、Delveを利用した具体的なデバッグ方法について紹介します。
main.go
package main
import "fmt"
func greeting(name string) string {
beginning := "Hello! "
message := beginning + name
return message
}
func main() {
name := "Bob"
message := greeting(name)
fmt.Println(message)
}
Delveの起動と終了
dlv debug [ファイル名]
でデバッグモードになります。Delveを終了する場合はq
を入力します。
$ dlv debug main.go
(dlv)
(dlv)q
ブレークポイントを追加し、移動する
b
でブレークポイントを作成し、c
でブレークポイントへ移動できます。
r
でブレークポイントを残したままデバッグの再実行ができます。
$ dlv debug main.go
(dlv) b main.go:14
Breakpoint 1 set at 0x4b6964 for main.main() ./main.go:14
(dlv) c
9: return message
10: }
11:
12: func main() {
13: name := "Bob"
=> 14: message := greeting(name)
15:
16: fmt.Println(message)
17: }
(dlv) r
Process restarted with PID 40673
(dlv) c
> main.main() ./main.go:14 (hits goroutine(1):1 total:1) (PC: 0x10cb964)
9: return message
10: }
11:
12: func main() {
13: name := "Bob"
=> 14: message := greeting(name)
15:
16: fmt.Println(message)
17: }
デバッグ中にソースコードを確認する
ls
でデバッグ中にソースコードの確認ができます。
$ dlv debug main.go
(dlv) b main.go:14
(dlv) c
9: return message
10: }
11:
12: func main() {
13: name := "Bob"
=> 14: message := greeting(name)
15:
16: fmt.Println(message)
17: }
(dlv) ls main.greeting # greetingメソッドの表示
Showing /Users/301184/work/delve/main.go:5 (PC: 0x10cb873)
1: package main
2:
3: import "fmt"
4:
5: func greeting(name string) string {
6: beginning := "Hello! "
7: message := beginning + name
8:
9: return message
10: }
(dlv) ls greeting # greetingメソッドの表示
Showing /Users/301184/work/delve/main.go:5 (PC: 0x10cb873)
1: package main
2:
3: import "fmt"
4:
5: func greeting(name string) string {
6: beginning := "Hello! "
7: message := beginning + name
8:
9: return message
10: }
(dlv) ls # 現時点(ブレークポイント)のコードを表示
> main.main() ./main.go:16 (PC: 0x10cb98a)
11:
12: func main() {
13: name := "Bob"
14: message := greeting(name)
15:
=> 16: fmt.Println(message)
17: }
ブレークポイント時点での変数の中身を確認する
locals
で値の確認ができます。
たとえば、14行目ではname
の値が取得できます。
$ dlv debug main.go
(dlv) b main.go:14
(dlv) c
9: return message
10: }
11:
12: func main() {
13: name := "Bob"
=> 14: message := greeting(name)
15:
16: fmt.Println(message)
17: }
(dlv) locals
name = "Bob"
n
で次の行へ移動すると、name
と、14行目でセットされたmessage
が表示されます。
(dlv) n
> main.main() ./main.go:16 (PC: 0x10cb98a)
11:
12: func main() {
13: name := "Bob"
14: message := greeting(name)
15:
=> 16: fmt.Println(message)
17: }
(dlv) locals
name = "Bob"
message = "Hello! Bob"
(div) p name
"Bob"
ステップインとステップアウト
s
でステップインできます。
たとえば14行目でs
を実行すると、14行目で実行されているgreeting
メソッドのデバッグができます。
$ dlv debug main.go
(dlv) b main.go:14
(dlv) c
9: return message
10: }
11:
12: func main() {
13: name := "Bob"
=> 14: message := greeting(name)
15:
16: fmt.Println(message)
17: }
(dlv) s
> main.greeting() ./main.go:5 (PC: 0x10cb873)
1: package main
2:
3: import "fmt"
4:
=> 5: func greeting(name string) string {
6: beginning := "Hello! "
7: message := beginning + name
8:
9: return message
10: }
stepout
でステップアウトします。
(dlv) ls
> main.greeting() ./main.go:5 (PC: 0x10cb873)
1: package main
2:
3: import "fmt"
4:
=> 5: func greeting(name string) string {
6: beginning := "Hello! "
7: message := beginning + name
8:
9: return message
10: }
(dlv) stepout
> main.main() ./main.go:14 (PC: 0x10cb976)
Values returned:
~r1: "Hello! Bob"
9: return message
10: }
11:
12: func main() {
13: name := "Bob"
=> 14: message := greeting(name)
15:
16: fmt.Println(message)
17: }
ブレークポイントから次のブレークポイントへ移動する
c
でブレークポイントを移動できます。
(dlv) b main.go:14
(dlv) b main.go:16
(dlv) c
> main.main() ./main.go:14 (hits goroutine(1):1 total:1) (PC: 0x10cb964)
9: return message
10: }
11:
12: func main() {
13: name := "Bob"
=> 14: message := greeting(name)
15:
16: fmt.Println(message)
17: }
(dlv) c
> main.main() ./main.go:16 (hits goroutine(1):1 total:1) (PC: 0x10cb98a)
11:
12: func main() {
13: name := "Bob"
14: message := greeting(name)
15:
=> 16: fmt.Println(message)
17: }
スタックトレースを利用してデバッグする
bt
でスタックトレースを表示し、frame
で実行したいコマンドと位置を決定します。
$ dlv debug main.go
(dlv) b main.go:9
(dlv) c
> main.greeting() ./main.go:9 (hits goroutine(1):1 total:1) (PC: 0x10cb8cd)
4:
5: func greeting(name string) string {
6: beginning := "Hello! "
7: message := beginning + name
8:
=> 9: return message
10: }
11:
12: func main() {
13: name := "Bob"
14: message := greeting(name)
(dlv) bt
0 0x00000000010cb8cd in main.greeting
at ./main.go:9
1 0x00000000010cb976 in main.main
at ./main.go:14
2 0x000000000103b6e3 in runtime.main
at /usr/local/Cellar/go/1.16.3/libexec/src/runtime/proc.go:225
3 0x000000000106eb01 in runtime.goexit
at /usr/local/Cellar/go/1.16.3/libexec/src/runtime/asm_amd64.s:1371
(dlv) frame 1 locals # ./main.go:14 で localsを実行
name = "Bob"
さいごに
Twitter(@nishina555)やってます。フォローしてもらえるとうれしいです!