4月20日に出たこの Go Developer Survey 2019 Results 眺めていて、 I depend upon the following techniques when developing in Go
が気になった。
これは Go で開発する時にどういう手法に頼ってるかみたいな内容で、一番は fmt.Print() or similar
で text log でした。
たしかに自分もよく text log だしてたりするなと思った。
後は Go は動かす前にコンパイルするから、最低限動くもののフィードバックはここで受け取って修正できているからあまり困っていないのかもしれない。
survey では text log の次は「 Delv や GDB のような debugger を local machine で使う」でした。
自分はどっちも使ったことなかった。
ということで試したので、そのメモをこの記事に書く。
GDB
GDB は GNU DeBugger の略らしい。
Go のためのやつかと思ったけど C, C++ だったり gcc でコンパイルされたプログラムのデバッグとかに使われるらしい。
sourceware.org
僕の普段の開発機は Mac なので今回も gdb を Mac で動かしていこうと思ったのですが、署名とかが必要になるっぽくて面倒だったので docker run -it --rm golang:1.14.3 bash
でコンテナ立ててそこで検証することにしました。
golang:1.14.3
の image を使った場合は apt-get update
, apt-get install gdb
すればとりあえずインストールはされる。
今回は下記のプログラムを使います。
package main import ( "fmt" ) type user struct { Name string Age int } func main() { fmt.Println("start") u := &user{ Name: "hatappi", Age: 28, } echo() fmt.Printf("name is %s, age is %d\n", u.Name, u.Age) fmt.Println("end") } func echo() { fmt.Println("echo~~") }
Go のドキュメントに書かれているように、Go は build 時に最適化が走るのでそれを無効にしつつビルドします。
さらに Go 1.11 以降はデバッグ情報が圧縮されるのでそれも無効にします。
$ go build \ -ldflags=-w \ -gcflags=all="-N -l" \ -ldflags=-compressdwarf=false \ -o main \ main.go
次に gdb main
として下記のようなものが出たらいったん ok です。
Go の extension が load されていない場合は source /usr/local/go/src/runtime/runtime-gdb.py
で go の extension を load します。
実行後は l
で source code を表示できて l main.echo
のようにすると指定した関数のソースコードを表示できます。
(gdb) l main.echo 13 echo() 14 15 fmt.Println("end") 16 } 17 18 func echo() { 19 fmt.Println("echo~~") 20 } (gdb)
l 10
を実行すると10行目を中心に前後の行を表示してくれます。
run
を実行するとプログラムを実行します。
しかしこのままだと処理が終わってしまいます。
そこで break
を使ってブレークポイントを設定します。
(gdb) b 20 Breakpoint 1 at 0x4afb8d: file /root/main.go, line 20.
設定したブレークポイントは info b
で確認できます。
run
を実行すればブレークポイントを設定したところで処理がとまってくれるます。
止まったところで様々なコマンドを実行することができます。
例えば定義されている変数を見る info locals
や p u.Name
とかで特定の変数の値を見ることができます。
(gdb) p u.Name $1 = 0x4de43d "hatappi"
それ以外だと info goroutines
で Go routine の状況もみられます。
さらに変数も変更することができます。
(gdb) p u.Age $2 = 28 (gdb) set variable u.Age=100 (gdb) p u.Age $3 = 100
処理を続行したい時は c
を入力すれば処理が継続されます。
Delve
GDB は Go 以外にも使用できていたけどこの Delve は Delve is a debugger for the Go programming language.
とあるように Go のためのデバッグツールです。
delve は xcode-select --install
して go get github.com/go-delve/delve/cmd/dlv
すれば使えます。
https://github.com/go-delve/delve/blob/master/Documentation/installation/osx/install.md
go get してくるだけなのでさくっと使えて良さそう。
今回も GDB の時と同じく下記のプログラムを使います。
package main import ( "fmt" ) type user struct { Name string Age int } func main() { fmt.Println("start") u := &user{ Name: "hatappi", Age: 28, } echo() fmt.Printf("name is %s, age is %d\n", u.Name, u.Age) fmt.Println("end") } func echo() { fmt.Println("echo~~") }
起動は main
パッケージと同じディレクトリにいれば dlv debug
で起動できます。
$ dlv debug Type 'help' for list of commands. (dlv)
どういったことができるかは起動後の help
でもみれますし下記からドキュメントを参照することもできます。
https://github.com/go-delve/delve/tree/master/Documentation/cli
ソースコードを表示する時は GDB と同じく l 40
とか l main.echo
で確認することができます。
ブレークポイントは break main.main
で設定して c
で実行すると main がよばれたところでとまります。
さらに main 関数内でとめたいところを break 20
で指定してもう一度 c
で処理を継続します。
ローカル変数をみたい時は locals
で 特定の変数を使いたい時は print
を使います。
(dlv) print u *main.user {Name: "hatappi", Age: 28}
変数は set u.Age = 100
のように set
を使うことで変更できます。
delve は他にも起動中のプロセスにattach できる dlv attach
があったりして便利そう。