Go言語のデバッグツールである「delve」の利用方法を確認します。「基本的な利用方法」「テストコードのデバッグ」「実行中プロセスのデバッグ」など取り上げます。
delveをインストール
delve をインストールします。
go install github.com/go-delve/delve/cmd/dlv@latest
インストールできたか確認します。
$ dlv version
Delve Debugger
Version: 1.8.3
Build: $Id: f92bb46b82b3b92d79ce59c4b55eeefbdd8d040c
( $GOPATH/bin
のパスが通ってない場合、$GOPATH/bin/dlv version
で確認できると思います。)
以下コマンドで、dlvの利用方法を確認できます。
dlv help
# dlv help [command]
dlv help debug
dlv help attach
動作確認用コード
以下、動作確認用のコードです。
./
├── go.mod
├── main.go
└── main_test.go
module debug_sample
go 1.18
main.go
には以下処理が実装されています。
package main
import (
"fmt"
)
var abc string
func init() {
abc = "wakuwaku bank"
}
func add(a, b int) int {
fmt.Printf("func add %v\n", abc)
return a + b
}
func sub(a, b int) int {
fmt.Printf("func sub %v\n", abc)
return a - b
}
func calculate(a, b int) (addValue, subValue int) {
addValue = add(a, b)
subValue = sub(a, b)
return addValue, subValue
}
func sampleFunc1() {
for i := 0; i < 10; i++ {
a := i + 10
b := i
addValue, subValue := calculate(a, b)
fmt.Printf("addValue: %v subValue: %v\n", addValue, subValue)
}
}
func main() {
sampleFunc1()
}
main_test.go
には以下処理が実装されています。
package main
import "testing"
func Test_add(t *testing.T) {
type args struct {
a int
b int
}
tests := []struct {
name string
args args
want int
}{{name: "Both positive values", args: args{10, 3}, want: 13}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := add(tt.args.a, tt.args.b); got != tt.want {
t.Errorf("add() = %v, want %v", got, tt.want)
}
})
}
}
func Test_sub(t *testing.T) {
type args struct {
a int
b int
}
tests := []struct {
name string
args args
want int
}{{name: "Both positive values", args: args{10, 3}, want: 7}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := sub(tt.args.a, tt.args.b); got != tt.want {
t.Errorf("sub() = %v, want %v", got, tt.want)
}
})
}
}
[デバッグ] dlv debug
デバッグ開始
$ dlv debug main.go
Type 'help' for list of commands.
(dlv)
関数・コードの確認
( funcs
list
)
funcs
で関数を確認できます。
(dlv) funcs main\..*
main.add
main.calculate
main.init.0
main.main
main.sampleFunc1
main.sub
runtime.main.func1
runtime.main.func2
list
でソースコードを確認できます。
(dlv) list main.main
Showing /debug_sample/main.go:38 (PC: 0x1029dda10)
33: addValue, subValue := calculate(a, b)
34: fmt.Printf("addValue: %v subValue: %v\n", addValue, subValue)
35: }
36: }
37:
38: func main() {
39: sampleFunc1()
40: }
(dlv) list main.sampleFunc1:3
Showing /debug_sample/main.go:32 (PC: 0x1029dd8c0)
27: }
28:
29: func sampleFunc1() {
30: for i := 0; i < 10; i++ {
31: a := i + 10
32: b := i
33: addValue, subValue := calculate(a, b)
34: fmt.Printf("addValue: %v subValue: %v\n", addValue, subValue)
35: }
36: }
37:
ブレイクポイントの設定・削除
( break
clear
)
break
でブレイクポイントを設定できます。
(dlv) break main.main
Breakpoint 1 set at 0x1029dda10 for main.main() ./main.go:38
(dlv) break main.sampleFunc1:3
Breakpoint 2 set at 0x1029dd8c0 for main.sampleFunc1() ./main.go:32
breakpoints
で設定されたブレイクポイントを確認できます。
(dlv) breakpoints
Breakpoint runtime-fatal-throw (enabled) at 0x10296c880 for runtime.throw() /Users/w/.gvm/gos/go1.18/src/runtime/panic.go:982 (0)
Breakpoint unrecovered-panic (enabled) at 0x10296cb90 for runtime.fatalpanic() /Users/w/.gvm/gos/go1.18/src/runtime/panic.go:1065 (0)
print runtime.curg._panic.arg
Breakpoint 1 (enabled) at 0x1029dda10 for main.main() ./main.go:38 (0)
Breakpoint 2 (enabled) at 0x1029dd8c0 for main.sampleFunc1() ./main.go:32 (0)
clear
で指定ブレイクポイントを削除できます。
(dlv) clear 1
Breakpoint 1 cleared at 0x1029dda10 for main.main() ./main.go:38
(dlv) breakpoints
Breakpoint runtime-fatal-throw (enabled) at 0x10296c880 for runtime.throw() /Users/w/.gvm/gos/go1.18/src/runtime/panic.go:982 (0)
Breakpoint unrecovered-panic (enabled) at 0x10296cb90 for runtime.fatalpanic() /Users/w/.gvm/gos/go1.18/src/runtime/panic.go:1065 (0)
print runtime.curg._panic.arg
Breakpoint 2 (enabled) at 0x1029dd8c0 for main.sampleFunc1() ./main.go:32 (0)
clearall
で全てのブレイクポイントを削除できます。
ブレイクポイントまで処理を進める
( continue
)
continue
で設定したブレイクポイントまで処理を進めてみます。
(dlv) continue
> main.sampleFunc1() ./main.go:32 (hits goroutine(1):1 total:1) (PC: 0x1029dd8c0)
27: }
28:
29: func sampleFunc1() {
30: for i := 0; i < 10; i++ {
31: a := i + 10
=> 32: b := i
33: addValue, subValue := calculate(a, b)
34: fmt.Printf("addValue: %v subValue: %v\n", addValue, subValue)
35: }
36: }
37:
continue
以外にも以下コマンドで処理を進めることができます。
next
- 1行処理を進める(ステップオーバー)。
step
- 1行処理を進める。
stepout
- 現在の関数から抜ける。
変数を確認・上書き
( locals
set
)
(dlv) locals
i = 0
a = 10
(dlv) vars main.abc
main.abc = "wakuwaku bank"
(dlv) print i
0
(dlv) print a
10
(dlv) set i=2
(dlv) print i
2
スタックトレース表示
( stack
frame
)
ます、動作確認のため、処理をadd関数まで進めます。
(dlv) break main.add
Breakpoint 3 set at 0x1029dd610 for main.add() ./main.go:13
(dlv) c
> main.add() ./main.go:13 (hits goroutine(1):1 total:1) (PC: 0x1029dd610)
8:
9: func init() {
10: abc = "wakuwaku bank"
11: }
12:
=> 13: func add(a, b int) int {
14: fmt.Printf("func add %v\n", abc)
15: return a + b
16: }
17:
18: func sub(a, b int) int {
スタックトレースを表示します。
(dlv) stack
0 0x00000001029dd610 in main.add
at ./main.go:13
1 0x00000001029dd838 in main.calculate
at ./main.go:24
2 0x00000001029dd8d0 in main.sampleFunc1
at ./main.go:33
3 0x00000001029dda20 in main.main
at ./main.go:39
4 0x000000010296ed64 in runtime.main
at /Users/w/.gvm/gos/go1.18/src/runtime/proc.go:250
5 0x00000001029985a4 in runtime.goexit
at /Users/w/.gvm/gos/go1.18/src/runtime/asm_arm64.s:1259
frame
でスタックトレースの番号を指定して、コードや変数を確認できます。
(dlv) frame 2 ls
Goroutine 1 frame 2 at /debug_sample/main.go:33 (PC: 0x1029dd8d0)
28:
29: func sampleFunc1() {
30: for i := 0; i < 10; i++ {
31: a := i + 10
32: b := i
=> 33: addValue, subValue := calculate(a, b)
34: fmt.Printf("addValue: %v subValue: %v\n", addValue, subValue)
35: }
36: }
37:
38: func main() {
(dlv) frame 2 locals
i = 2
a = 10
b = 2
条件指定
( condition
)
まずは、for文の処理内まで処理を進めます。
$ dlv debug main.go
Type 'help' for list of commands.
(dlv) b main.sampleFunc1:3
Breakpoint 1 set at 0x1050958c0 for main.sampleFunc1() ./main.go:32
(dlv) c
> main.sampleFunc1() ./main.go:32 (hits goroutine(1):1 total:1) (PC: 0x1050958c0)
27: }
28:
29: func sampleFunc1() {
30: for i := 0; i < 10; i++ {
31: a := i + 10
=> 32: b := i
33: addValue, subValue := calculate(a, b)
34: fmt.Printf("addValue: %v subValue: %v\n", addValue, subValue)
35: }
36: }
37:
(dlv) locals
i = 0
a = 10
condition
でブレークポイントの条件を設定できます。
i==5
のとき停止するように設定してみます。
(dlv) condition 1 i==5
(dlv) c
func add wakuwaku bank
func sub wakuwaku bank
addValue: 10 subValue: 10
func add wakuwaku bank
func sub wakuwaku bank
addValue: 12 subValue: 10
func add wakuwaku bank
func sub wakuwaku bank
addValue: 14 subValue: 10
func add wakuwaku bank
func sub wakuwaku bank
addValue: 16 subValue: 10
func add wakuwaku bank
func sub wakuwaku bank
addValue: 18 subValue: 10
> main.sampleFunc1() ./main.go:32 (hits goroutine(1):2 total:2) (PC: 0x1050958c0)
27: }
28:
29: func sampleFunc1() {
30: for i := 0; i < 10; i++ {
31: a := i + 10
=> 32: b := i
33: addValue, subValue := calculate(a, b)
34: fmt.Printf("addValue: %v subValue: %v\n", addValue, subValue)
35: }
36: }
37:
(dlv) locals
i = 5
a = 15
デバッグ終了
( exit
)
exit
でデバッグを終了できます。
(dlv) exit
[デバッグ] dlv test
( テストをデバッグ )
dlv test
でテストコードをデバッグできます。
$ dlv test
Type 'help' for list of commands.
(dlv) funcs debug_sample.Test*
debug_sample.Test_add
debug_sample.Test_add.func1
debug_sample.Test_sub
debug_sample.Test_sub.func1
(dlv) b debug_sample.Test_add
Breakpoint 1 set at 0x104891e90 for debug_sample.Test_add() ./main_test.go:5
(dlv) c
> debug_sample.Test_add() ./main_test.go:5 (hits goroutine(4):1 total:1) (PC: 0x104891e90)
1: package main
2:
3: import "testing"
4:
=> 5: func Test_add(t *testing.T) {
6: type args struct {
7: a int
8: b int
9: }
10: tests := []struct {
[デバッグ] dlv attach
( 実行中プロセスをデバッグ )
動作確認用コードを修正
以下のようにWebサーバーとして起動し続けるように修正します。
package main
import (
"fmt"
"net/http"
)
var abc string
func init() {
abc = "wakuwaku bank"
}
func add(a, b int) int {
fmt.Printf("func add %v\n", abc)
return a + b
}
func sub(a, b int) int {
fmt.Printf("func sub %v\n", abc)
return a - b
}
func calculate(a, b int) (addValue, subValue int) {
addValue = add(a, b)
subValue = sub(a, b)
return addValue, subValue
}
func sampleFunc1(w http.ResponseWriter, r *http.Request) {
for i := 0; i < 10; i++ {
a := i * 2
b := i
addValue, subValue := calculate(a, b)
fmt.Fprintf(w, "addValue: %v subValue: %v\n", addValue, subValue)
}
}
func main() {
http.HandleFunc("/sample_func_1", sampleFunc1)
http.ListenAndServe(":80", nil)
}
起動します。
$ go build
$ ./debug_sample
実行中プロセスをデバッグ
プロセスIDを確認します。
$ ps -ef | grep "debug_sample"
502 19185 16487 0 1:05PM ttys000 0:00.02 ./debug_sample
502 19210 18800 0 1:06PM ttys009 0:00.00 grep debug_sample
プロセスIDが19185のプロセス
をデバッグします。
$ dlv attach 19185
Type 'help' for list of commands.
(dlv)
ブレイクポイントを設定して処理が呼ばれるのを待ちます。
(dlv) b main.sampleFunc1
Breakpoint 1 set at 0x1010e8470 for main.sampleFunc1() ./main.go:30
(dlv) c
別コンソールでエンドポイントにリクエストしてみます。
$ curl http://localhost/sample_func_1
以下のように、設定したブレイクポイントで処理が止まりました。
(dlv) c
> main.sampleFunc1() ./main.go:30 (hits goroutine(21):1 total:1) (PC: 0x1010e8470)
Warning: debugging optimized function
25: addValue = add(a, b)
26: subValue = sub(a, b)
27: return addValue, subValue
28: }
29:
=> 30: func sampleFunc1(w http.ResponseWriter, r *http.Request) {
31: for i := 0; i < 10; i++ {
32: a := i * 2
33: b := i
34: addValue, subValue := calculate(a, b)
35: fmt.Fprintf(w, "addValue: %v subValue: %v\n", addValue, subValue)
コマンド説明
個人的に、利用頻度の高いコマンドを紹介します。
ブレイクポイント関連
コマンド | alias | 説明 |
---|---|---|
break | b | ブレイクポイントを設定する。 |
breakpoints | bp | アクティブブレイクポイントを表示する。 |
clear | ブレイクポイントを削除する。 | |
clearall | ブレイクポイントを全て削除する。 | |
condition | cond | ブレイクポイントの条件を設定する。 |
処理を進める
コマンド | alias | 説明 |
---|---|---|
continue | c | 「次のブレイクポイント」 or 「プログラム終了」 or 「指定箇所」まで処理を進める。 ( e.g. c main.add:1 ) |
next | n | 1行処理を進める(ステップオーバー)。 |
step | s | 1行処理を進める。 |
stepout | so | 現在の関数から抜ける。 |
restart | プロセスを再開する。 | |
exit | q | デバッガーを終了する。 |
その他
コマンド | alias | 説明 |
---|---|---|
funcs | 関数一覧を確認する。 ( e.g. funcs main\..* ) | |
list | l | コードを表示する。 ( e.g. l 10 l main.main:3 ) |
locals | ローカル変数を確認する。 | |
vars | パッケージ変数を確認する。 | |
print | p | 式を評価する。 |
set | 変数を上書きする。 | |
stack | bt | スタックトーレスを表示する。 |
その他コマンドの利用方法なども、デバッグ中に help
を実行することで確認できます。
(dlv) help
The following commands are available:
Running the program:
call ------------------------ Resumes process, injecting a function call (EXPERIMENTAL!!!)
continue (alias: c) --------- Run until breakpoint or program termination.
next (alias: n) ------------- Step over to next source line.
rebuild --------------------- Rebuild the target executable and restarts it. It does not work if the executable was not built by delve.
restart (alias: r) ---------- Restart process.
step (alias: s) ------------- Single step through program.
step-instruction (alias: si) Single step a single cpu instruction.
stepout (alias: so) --------- Step out of the current function.
Manipulating breakpoints:
break (alias: b) ------- Sets a breakpoint.
breakpoints (alias: bp) Print out info for active breakpoints.
clear ------------------ Deletes breakpoint.
clearall --------------- Deletes multiple breakpoints.
condition (alias: cond) Set breakpoint condition.
on --------------------- Executes a command when a breakpoint is hit.
toggle ----------------- Toggles on or off a breakpoint.
trace (alias: t) ------- Set tracepoint.
watch ------------------ Set watchpoint.
Viewing program variables and memory:
args ----------------- Print function arguments.
display -------------- Print value of an expression every time the program stops.
examinemem (alias: x) Examine raw memory at the given address.
locals --------------- Print local variables.
print (alias: p) ----- Evaluate an expression.
regs ----------------- Print contents of CPU registers.
set ------------------ Changes the value of a variable.
vars ----------------- Print package variables.
whatis --------------- Prints type of an expression.
Listing and switching between threads and goroutines:
goroutine (alias: gr) -- Shows or changes current goroutine
goroutines (alias: grs) List program goroutines.
thread (alias: tr) ----- Switch to the specified thread.
threads ---------------- Print out info for every traced thread.
Viewing the call stack and selecting frames:
deferred --------- Executes command in the context of a deferred call.
down ------------- Move the current frame down.
frame ------------ Set the current frame, or execute command on a different frame.
stack (alias: bt) Print stack trace.
up --------------- Move the current frame up.
Other commands:
config --------------------- Changes configuration parameters.
disassemble (alias: disass) Disassembler.
dump ----------------------- Creates a core dump from the current process state
edit (alias: ed) ----------- Open where you are in $DELVE_EDITOR or $EDITOR
exit (alias: quit | q) ----- Exit the debugger.
funcs ---------------------- Print list of functions.
help (alias: h) ------------ Prints the help message.
libraries ------------------ List loaded dynamic libraries
list (alias: ls | l) ------- Show source code.
source --------------------- Executes a file containing a list of delve commands
sources -------------------- Print list of source files.
transcript ----------------- Appends command output to a file.
types ---------------------- Print list of types
Type help followed by a command for full documentation.