DUICUO

Go プログラムをデバッグするには、Println の代わりに Delve を使用します。

Delve はデバッグを簡単にする多目的ツールキットです。

最後に新しいプログラミング言語を学ぼうとしたのはいつですか?粘り強く続けられましたか?新しいものがリリースされたらすぐに勇気を出して挑戦するタイプですか?いずれにせよ、新しい言語を学ぶことは非常に有益で、とても楽しいものです。

簡単な「Hello, world!」を書いてみて、サンプルコードを書いて実行し、少し修正を加えて、次の段階に進む。きっと、どんなテクノロジーを使っていても、誰もがこのような経験をしたことがあるでしょう。もしあなたがしばらく言語を試してみて、それをマスターしたいと思っているなら、成功への道のりを助けてくれるものがいくつかあります。

その一つがデバッガです。コード内の単純な「print」ステートメントを使ってデバッグすることを好む人もいますが、これはコード量が少ないシンプルなプログラムには適しています。しかし、複数の開発者が関わり、数千行に及ぶコードを扱う大規模プロジェクトの場合は、デバッガを使用する必要があります。

最近、Goプログラミング言語を学び始めました。この記事では、Delveというデバッガーについて解説します。DelveはGoプログラムのデバッグに特化したツールで、Goのサンプルコードを使ってその機能をいくつか理解していきます。ここで紹介するGoのサンプルコードについては心配しないでください。Goコードを書いたことがない方でも理解できるはずです。Goの目標の一つはシンプルさです。そのため、コードは常に理解しやすく、解釈しやすいものになっています。

Delveの紹介

Delve は GitHub でホストされているオープンソース プロジェクトです。

独自のドキュメントには次のように記載されています。

Delve は Go プログラミング言語用のデバッガーです。このプロジェクトは、Go 用のシンプルでフル機能のデバッグツールを提供することを目指しています。Delve は簡単に呼び出して簡単に使用できるものでなければなりません。デバッガーを使用すると、期待通りに動作しない場合があります。もしそう思うなら、Delve はあなたには向いていません。

詳しく見てみましょう。

私のテスト システムは Fedora Linux を実行しているラップトップで、Go コンパイラのバージョンは次のとおりです。

  1. $ cat / etc / fedora - release
  2. Fedora release 30 ( Thirty )
  3. $
  4. $ go version
  5. go version go1 . 12.17 linux / amd64
  6. $

Golangのインストール

Go がインストールされていない場合は、次のコマンドを実行することで、構成されたリポジトリから簡単に取得できます。

  1. $ dnf install golang . x86_64

または、インストール ページで、ご使用のオペレーティング システムに適した他のインストール バージョンを見つけることもできます。

始める前に、Goツールの以下のパスが正しく設定されていることを確認してください。これらのパスが設定されていない場合、一部のサンプルは正常に動作しない可能性があります。これらの環境変数はシェルのRCファイルで簡単に設定できます。私のマシンでは、 $HOME/bashrcファイルで設定されています。

  1. $ go env | grep GOPATH
  2. GOPATH = "/home/user/go"
  3. $
  4. $ go env | grep GOBIN
  5. GOBIN = "/home/user/go/gobin"
  6. $

Delveのインストール

Delveは、以下に示すように、シンプルなgo getコマンドを実行するだけでインストールできます。 go getは、Go言語で必要なパッケージを外部ソースからダウンロードしてインストールする方法です。インストール中に問題が発生した場合は、Delveのインストールチュートリアルを参照してください。

  1. $ go get - u github . com / go - delve / delve / cmd / dlv
  2. $

上記のコマンドを実行すると、Delveが$GOPATHにダウンロードされます。 $GOPATHに別の値を設定していない場合、デフォルトでは$GOPATH$HOME/goは同じパスになります。

go/ディレクトリに移動すると、 bin/ディレクトリ内のdlv確認できます。

  1. $ ls - l $HOME / go
  2. total 8
  3. drwxrwxr - x . 2 user user 4096 May 25 19 : 11 bin
  4. drwxrwxr - x . 4 user user 4096 May 25 19 : 21 src
  5. $
  6. $ ls - l ~ /go/ bin /
  7. total 19596
  8. - rwxrwxr - x . 1 user user 20062654 May 25 19 : 17 dlv
  9. $

Delve を$GOPATHにインストールしたので、通常のシェルコマンドのように実行できます。つまり、実行するたびにディレクトリに移動する必要はありません。version versionを使用して、 dlvが正しくインストールされていることを確認できます。この例ではバージョン 1.4.1 を使用しています。

  1. $ which dlv
  2. ~ /go/ bin / dlv
  3. $
  4. $ dlv version
  5. Delve Debugger
  6. Version : 1.4 . 1
  7. Build : $Id : bda606147ff48b58bde39e20b9e11378eaa4db46 $
  8. $

それでは、GoプログラムでDelveを使って、その機能と使い方を理解してみましょう。まずはHello, world!というメッセージを出力するhello.go作成しましょう。

これらのサンプル プログラムは$GOBINディレクトリに置いたことに注意してください。

  1. $ pwd
  2. / home / user / go / gobin
  3. $
  4. $ cat hello . go
  5. package main
  6. import "fmt"
  7. func main () {
  8. fmt . Println ( "Hello, world!" )
  9. }
  10. $

Goプログラムをコンパイルするには、 buildコマンドを実行します。入力は.go拡張子のファイルです。プログラムに構文エラーがない場合、Goコンパイラはそれをバイナリ実行ファイルにコンパイルします。このファイルは直接実行でき、画面にHello, world!メッセージが表示されます。

  1. $ go build hello . go
  2. $
  3. $ ls - l hello
  4. - rwxrwxr - x . 1 user user 1997284 May 26 12 : 13 hello
  5. $
  6. $ ./ hello
  7. Hello , world !
  8. $

Delveでプログラムを読み込む

Delve デバッガーにプログラムをロードする方法は 2 つあります。

ソース コードをバイナリ ファイルにコンパイルする前に、デバッグ パラメータを使用します。

最初の方法は、必要に応じてソースコードに対してdebugコマンドを使用することです。Delveは__debug_binという名前のバイナリファイルをコンパイルし、デバッガーに読み込みます。

この例では、 hello.goを含むディレクトリに移動し、 dlv debugコマンドを実行できます。ディレクトリ内に複数のソースファイルがあり、それぞれに main 関数が含まれている場合、Delve は単一のプログラムまたは単一のプロジェクトからビルドされた単一のバイナリを想定しているため、エラーをスローする可能性があります。このエラーが発生した場合は、以下に示す 2 番目の方法を使用してください。

  1. $ ls - l
  2. total 4
  3. - rw - rw - r --. 1 user user 74 Jun   4 11 : 48 hello . go
  4. $
  5. $ dlv debug
  6. Type 'help' for list of commands .
  7. ( dlv )

別のターミナルを開き、ディレクトリ内のファイルを一覧表示します。__ __debug_binバイナリファイルが追加されているはずです。これはソースコードから生成され、デバッガーに読み込まれます。これでdlvプロンプトに戻り、Delve を使い続けることができます。

  1. $ ls - l
  2. total 2036
  3. - rwxrwxr - x . 1 user user 2077085 Jun   4 11 : 48 __debug_bin
  4. - rw - rw - r --. 1 user user 74 Jun   4 11 : 48 hello . go
  5. $

execパラメータの使用

既にコンパイル済みのGoプログラムがある場合、またはgo buildコマンドを使用してコンパイル済みの場合、 __debug_binバイナリをDelveでコンパイルしたくない場合は、プログラムをDelveにロードする2番目の方法が便利です。上記のケースでは、 execコマンドを使用してディレクトリ全体をDelveデバッガーにロードできます。

  1. $ ls - l
  2. total 4
  3. - rw - rw - r --. 1 user user 74 Jun   4 11 : 48 hello . go
  4. $
  5. $ go build hello . go
  6. $
  7. $ ls - l
  8. total 1956
  9. - rwxrwxr - x . 1 user user 1997284 Jun   4 11 : 54 hello
  10. - rw - rw - r --. 1 user user 74 Jun   4 11 : 48 hello . go
  11. $
  12. $ dlv exec ./ hello
  13. Type 'help' for list of commands .
  14. ( dlv )

Delveのヘルプ情報を見る

dlvプロンプトでhelpを実行すると、Delve が提供する様々なヘルプオプションが表示されます。コマンドリストはかなり長いため、ここでは重要な機能の一部のみを紹介します。以下は Delve の機能の概要です。

  1. ( dlv ) help
  2. The following commands are available :
  3. Running the program :
  4. Manipulating breakpoints :
  5. Viewing program variables and memory :
  6. Listing and switching between threads and goroutines :
  7. Viewing the call stack and selecting frames :
  8. Other commands :
  9. Type help followed by a command for full documentation .
  10. ( dlv )

ブレークポイントを設定する

hello.go プログラムを Delve デバッガーにロードしたので、後で検証するために main 関数にブレークポイントを設定します。Go では、メインプログラムはmain.mainから実行を開始するため、この名前に対してbreakコマンドを指定する必要があります。その後、 breakpointsコマンドを使用して、ブレークポイントが正しく設定されているかどうかを確認できます。

コマンドの省略形も使用できることを忘れないでください。`break break main.mainの代わりにb main.mainを使用できます。どちらも同じ効果があります。`bp` とbreakpointsについても同様です。必要なコマンドの省略形はbp helpコマンドを実行してヘルプ情報を表示することで確認できます。

  1. ( dlv ) break main . main
  2. Breakpoint 1 set at 0x4a228f for main . main () ./ hello . go : 5
  3. ( dlv ) breakpoints
  4. Breakpoint runtime - fatal - throw at 0x42c410 for runtime . fatalthrow () / usr / lib / golang / src / runtime / panic . go : 663 ( 0 )
  5. Breakpoint unrecovered - panic at 0x42c480 for runtime . fatalpanic () / usr / lib / golang / src / runtime / panic . go : 690 ( 0 )
  6.         print runtime . curg . _panic . arg
  7. Breakpoint 1 at 0x4a228f for main . main () ./ hello . go : 5 ( 0 )
  8. ( dlv )

プログラムは実行を継続します。

ここで、 continueを使ってプログラムの実行を継続します。ブレークポイントで停止します。この例では、main 関数のmain.mainで停止します。ここからnextコマンドを使ってプログラムを1行ずつ実行できます。`fmt.Println fmt.Println("Hello, world!")に到達すると、まだデバッガー内ではありますが、画面にHello, world!と表示されているのがわかるでしょう。

  1. ( dlv ) continue
  2. > main . main () ./ hello . go : 5 ( hits goroutine ( 1 ): 1 total : 1 ) ( PC : 0x4a228f )
  3.       1 : package main
  4.       2 :
  5.       3 : import "fmt"
  6.       4 :
  7. =>   5 : func main () {
  8.       6 : fmt . Println ( "Hello, world!" )
  9.       7 : }
  10. ( dlv ) next
  11. > main . main () ./ hello . go : 6 ( PC : 0x4a229d )
  12.       1 : package main
  13.       2 :
  14.       3 : import "fmt"
  15.       4 :
  16.       5 : func main () {
  17. =>   6 : fmt . Println ( "Hello, world!" )
  18.       7 : }
  19. ( dlv ) next
  20. Hello , world !
  21. > main . main () ./ hello . go : 7 ( PC : 0x4a22ff )
  22.       2 :
  23.       3 : import "fmt"
  24.       4 :
  25.       5 : func main () {
  26.       6 : fmt . Println ( "Hello, world!" )
  27. =>   7 :       }
  28. ( dlv )

Delveを出る

いつでもquitコマンドを実行してデバッガーを終了できます。その後、シェルプロンプトに戻ります。とても簡単ですよね?

  1. ( dlv ) quit
  2. $

Delveのその他の機能

Delveの他の機能を他のGoプログラムを使って試してみましょう。今回はGolangチュートリアルからプログラムを1つ選びました。Goを学習するなら、まずはGolangチュートリアルから始めるのがおすすめです。

以下のプログラムfunctions.go 、Go プログラムにおける関数の定義と呼び出し方法を簡単に示しています。ここでは、2 つの数値を加算してその合計を返す単純なadd()関数を使用しています。このプログラムは以下のようにビルドして実行できます。

  1. $ cat functions . go
  2. package main
  3. import "fmt"
  4. func add ( x int , y int ) int {
  5.         return x + y
  6. }
  7. func main () {
  8. fmt . Println ( add ( 42 , 13 ))
  9. }
  10. $

以下に示すようにプログラムをビルドして実行できます。

  1. $ go build functions . go && ./ functions
  2. 55
  3. $

関数を入力する

前述のように、前述のオプションのいずれかを使用してバイナリファイルをDelveデバッガーに読み込み、 main.mainにブレークポイントを設定し、ブレークポイントまでプログラムを実行させます。次に、 next fmt.Println(add(42, 13))まで実行します。ここでadd()関数を呼び出します。Delveのstepコマンドを使用して、 main関数からadd()関数に入ることができます(以下を参照)。

  1. $ dlv debug
  2. Type 'help' for list of commands .
  3. ( dlv ) break main . main
  4. Breakpoint 1 set at 0x4a22b3 for main . main () ./ functions . go : 9
  5. ( dlv ) c
  6. > main . main () ./ functions . go : 9 ( hits goroutine ( 1 ): 1 total : 1 ) ( PC : 0x4a22b3 )
  7.       4 :
  8.       5 : func add ( x int , y int ) int {
  9.       6 :         return x + y
  10.       7 : }
  11.       8 :
  12. =>   9 : func main () {
  13.     10 : fmt . Println ( add ( 42 , 13 ))
  14.     11 : }
  15. ( dlv ) next
  16. > main . main () ./ functions . go : 10 ( PC : 0x4a22c1 )
  17.       5 : func add ( x int , y int ) int {
  18.       6 :         return x + y
  19.       7 : }
  20.       8 :
  21.       9 : func main () {
  22. =>   10 : fmt . Println ( add ( 42 , 13 ))
  23.     11 : }
  24. ( dlv ) step
  25. > main . add () ./ functions . go : 5 ( PC : 0x4a2280 )
  26.       1 : package main
  27.       2 :
  28.       3 : import "fmt"
  29.       4 :
  30. =>   5 : func add ( x int , y int ) int {
  31.       6 :         return x + y
  32.       7 : }
  33.       8 :
  34.       9 : func main () {
  35.     10 : fmt . Println ( add ( 42 , 13 ))
  36. ( dlv )

ブレークポイントを設定するには、文件名:行号を使用します。

上記の例では、 main関数を経由してadd()関数に入りましたが、ブレークポイントを追加したい場所に「ファイル名:行番号」の組み合わせを直接使用することもできます。以下は、 add()関数の先頭にブレークポイントを追加する別の方法です。

  1. ( dlv ) break functions . go : 5
  2. Breakpoint 1 set at 0x4a2280 for main . add () ./ functions . go : 5
  3. ( dlv ) continue
  4. > main . add () ./ functions . go : 5 ( hits goroutine ( 1 ): 1 total : 1 ) ( PC : 0x4a2280 )
  5.       1 : package main
  6.       2 :
  7.       3 : import "fmt"
  8.       4 :
  9. =>   5 : func add ( x int , y int ) int {
  10.       6 :         return x + y
  11.       7 : }
  12.       8 :
  13.       9 : func main () {
  14.     10 : fmt . Println ( add ( 42 , 13 ))
  15. ( dlv )

現在のスタック情報を表示する

これでadd() ` 関数に到達しました。`Delve` のstackコマンドを使用して、現在のスタックの内容を表示できます。ここでは、位置0はスタックの先頭にある関数add()を示しており、次の位置1は ` add() ` を呼び出すmain.mainを示していますmain.mainより下の関数は Go ランタイムに属し、プログラムの読み込みと実行を処理するために使用されます。

  1. ( dlv ) stack
  2. 0   0x00000000004a2280 in main . add
  3. at ./ functions . go : 5
  4. 1   0x00000000004a22d7 in main . main
  5. at ./ functions . go : 10
  6. 2   0x000000000042dd1f in runtime . main
  7. at / usr / lib / golang / src / runtime / proc . go : 200
  8. 3   0x0000000000458171 in runtime . goexit
  9. at / usr / lib / golang / src / runtime / asm_amd64 . s : 1337
  10. ( dlv )

フレーム間をジャンプ

Delveでは、 frameコマンドを使ってフレーム間を移動できます。以下の例では、 frameを使ってadd()フレームからmain.mainフレームへ移動しています。

  1. ( dlv ) frame 0
  2. > main . add () ./ functions . go : 5 ( hits goroutine ( 1 ): 1 total : 1 ) ( PC : 0x4a2280 )
  3. Frame 0 : ./ functions . go : 5 ( PC : 4a2280 )
  4.       1 : package main
  5.       2 :
  6.       3 : import "fmt"
  7.       4 :
  8. =>   5 : func add ( x int , y int ) int {
  9.       6 :         return x + y
  10.       7 : }
  11.       8 :
  12.       9 : func main () {
  13.     10 : fmt . Println ( add ( 42 , 13 ))
  14. ( dlv ) frame 1
  15. > main . add () ./ functions . go : 5 ( hits goroutine ( 1 ): 1 total : 1 ) ( PC : 0x4a2280 )
  16. Frame 1 : ./ functions . go : 10 ( PC : 4a22d7 )
  17.       5 : func add ( x int , y int ) int {
  18.       6 :         return x + y
  19.       7 : }
  20.       8 :
  21.       9 : func main () {
  22. =>   10 : fmt . Println ( add ( 42 , 13 ))
  23.     11 : }
  24. ( dlv )

印刷関数のパラメータ

関数は通常、複数の引数を受け取ります。`add add() `関数では、入力パラメータは2つの整数です。Delveには、コマンドラインから関数に渡された引数を出力できる便利なargsコマンドがあります。

  1. ( dlv ) args
  2. x = 42
  3. y = 13
  4. ~ r2 = 824633786832
  5. ( dlv )

逆コードを表示

コンパイルされたバイナリファイルをデバッグするため、コンパイラによって生成されたアセンブリ言語命令を表示できると非常に便利です。Delveには、これらの命令を表示するためのdisassembleコマンドが用意されています。次の例では、このコマンドを使用してadd()関数のアセンブリ命令を表示しています。

  1. ( dlv ) step
  2. > main . add () ./ functions . go : 5 ( PC : 0x4a2280 )
  3.       1 : package main
  4.       2 :
  5.       3 : import "fmt"
  6.       4 :
  7. =>   5 : func add ( x int , y int ) int {
  8.       6 :         return x + y
  9.       7 : }
  10.       8 :
  11.       9 : func main () {
  12.     10 : fmt . Println ( add ( 42 , 13 ))
  13. ( dlv ) disassemble
  14. TEXT main . add ( SB ) / home / user / go / gobin / functions . go
  15. => functions . go : 5   0x4a2280   48c744241800000000 mov qword ptr [ rsp + 0x18 ], 0x0
  16. functions . go : 6   0x4a2289   488b442408 mov rax , qword ptr [ rsp + 0x8 ]
  17. functions . go : 6   0x4a228e   4803442410 add rax , qword ptr [ rsp + 0x10 ]
  18. functions . go : 6   0x4a2293   4889442418 mov qword ptr [ rsp + 0x18 ], rax
  19. functions . go : 6   0x4a2298 c3 ret
  20. ( dlv )

シングルステップ終了機能

もう一つの機能はstepoutです。これを使うと、関数が呼び出された時点まで戻ることができます。この例では、 main.main関数に戻りたい場合、 stepoutコマンドを実行するだけで元に戻ります。この機能は、大規模なコードベースをデバッグする際に非常に便利なツールです。

  1. ( dlv ) stepout
  2. > main . main () ./ functions . go : 10 ( PC : 0x4a22d7 )
  3. Values returned :
  4.         ~ r2 : 55
  5.       5 : func add ( x int , y int ) int {
  6.       6 :         return x + y
  7.       7 : }
  8.       8 :
  9.       9 : func main () {
  10. =>   10 : fmt . Println ( add ( 42 , 13 ))
  11.     11 : }
  12. ( dlv )

変数情報を印刷する

Goチュートリアルの別のサンプルプログラムを見て、DelveがGoの変数をどのように処理するかを見てみましょう。以下のサンプルプログラムは、いくつかの異なる型の変数を定義し、初期化します。このプログラムをビルドして実行できます。

  1. $ cat variables . go
  2. package main
  3. import "fmt"
  4. var i , j int = 1 , 2
  5. func main () {
  6.         var c , python , java = true , false , "no!"
  7. fmt . Println ( i , j , c , python , java )
  8. }
  9. $
  10. $ go build variables . go &&; ./ variables
  11. 1 2 true false no !
  12. $

前述の通り、 delve debugを使用してプログラムをデバッガーに読み込みます。`delve` コマンドのprintコマンドを使用すると、変数の現在の値を名前で表示できます。

  1. ( dlv ) print c
  2. true
  3. ( dlv ) print java
  4. "no!"
  5. ( dlv )

あるいは、 localsコマンドを使用して関数内のすべてのローカル変数を印刷することもできます。

  1. ( dlv ) locals
  2. python = false
  3. c = true
  4. java = "no!"
  5. ( dlv )

変数の型がわからない場合は、 whatisコマンドを使用して変数名で型を出力できます。

  1. ( dlv ) whatis python
  2. bool
  3. ( dlv ) whatis c
  4. bool
  5. ( dlv ) whatis java
  6. string
  7. ( dlv )

要約

Delve の機能はまだほんの一部しかご紹介していません。ヘルプドキュメントをご覧いただき、他のコマンドもぜひお試しください。また、Delve を(デーモンとして)実行中の Go プログラムにバインドすることもできます。Go のソースコードがインストールされている場合は、Delve を使って Go ライブラリ内の情報をエクスポートすることもできます。ぜひお試しください!