delveによるデバッグ方法(実行中プロセスのデバッグなど)

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

動作確認用コード

以下、動作確認用のコードです。

$ tree
.
├── 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.

参考

【エンジニア向け】仕事を見つける方法

転職する

転職エージェントを活用する

転職サイトの場合、自身でサイト上から企業を探す必要があります。 一方「レバテックキャリア」 などの転職エージェントの場合、エージェントが企業を紹介してくれます。エージェントが間に入ることにより、日程調整や、条件交渉などもサポートしてくれます。

転職ドラフトを活用する

転職ドラフト」は、 企業がITエンジニアをドラフトという形で指名するサービスです。年収が最初に提示されるなどのメリットがあります。 ただ、初回登録時にレジュメ作成が必要で、すでにエンジニア経験が豊富にあるエンジニア向けのサービスかと思います。 レジュメ作成が手間ですが、自身のキャリアを見直す機会になり、他の仕事探しにも役立つはずです。

エンジニア転職保証のあるスクールを活用する

ある程度、開発経験のあるかたであれば、独学で必要なスキルを身につけることができるはずです。ただ、別業種からエンジニアに転職したい場合など、1から独学で学ぶのはハードルが高いです。そういった方は、スクールの活用を検討しても良いと思います。 「TechAcademy」は、エンジニア転職保証コースを提供しています。給付金制度の対象講座として認定されているため、金銭面の負担も抑えることができます。

フリーランスとして活動する

レバテックフリーランス」「ITプロパートナーズ」「ギークスジョブ」は、フリーランスエージェントサービスです。 エージェントによって、支払いサイトなど細かい違いはありますが、まずは良い案件を見つけることが重要です。 登録自体は無料なので、複数エージェントに登録して、より多くの案件を紹介してもらうのがおすすめです。

logo
わくわくBank.
技術系の記事を中心に、役に立つと思ったこと、整理したい情報などを掲載しています。