日曜日, 12月 27, 2015

LLDBでGoのデバッグをする その2 test編

その1ではgo buildしたLLDBでデバッグしてみましたが、その2ではgo testでのdebugの仕方です。

今回のデバッグ対象として以下のようなプログラム(main.go)とそのテスト(main_test.go)を書きました。

package main

import "fmt"

func RectArea(w, h int) int {
    return w * h
}
package main

import "testing"

func TestRectArea(t *testing.T) {
    s := RectArea(6, 8)
    if s != 48 {
        t.Error("error")
    }
}

testをビルドする

go test には -c というオプションがあり、これを使うとtestのバイナリをビルドできます。これと -gcflags を組み合わせてデバッグ可能なtestバイナリを作ります。

# -o は出力結果のファイル名を指定するオプション
$ go test -c -gcflags '-N -l' -o test
# 実行するとgo testと同じ結果が得られる
$ ./test
PASS

デバッグする

後は、前回同様lldbに、このtestバイナリを渡すだけです。

$ lldb test 
(lldb) target create "test"
Current executable set to 'test' (x86_64).
(lldb)

せっかくなのでプログラム本体とテスト両方にブレイクポイントを置いてみます。

(lldb) target create "test"
Current executable set to 'test' (x86_64).
(lldb) b main.go:4
Breakpoint 1: where = test`_/Users/kmtr/misc/c.RectArea + 9 at main.go:6, address = 0x000000000007dc09
(lldb) b main_test.go:7
Breakpoint 2: where = test`_/Users/kmtr/misc/c.TestRectArea + 58 at main_test.go:7, address = 0x000000000007dc5a
(lldb)

後は run するだけです。
main.go側で止まったところで、変数を確認。

   3    func RectArea(w, h int) int {
   4        return w * h
   5    }
(lldb) fr v
(long) w = 6
(long) h = 8
(long) ~r2 = 0
(lldb)

c で継続して、次はmain_test.goで止まるので、こちらでも変数を確認。
そして c で終了させます。

   4    
   5    func TestRectArea(t *testing.T) {
   6        s := RectArea(6, 8)
-> 7        if s != 48 {
   8            t.Error("error")
   9        }
   10   }
(lldb) fr v
(long) s = 48
(testing.T *) t = 0x000000c82009c000
(lldb) c
Process 25022 resuming
PASS
Process 25022 exited with status = 0 (0x00000000) 
(lldb)  

まとめ

-c オプションでtestバイナリを作るのがポイントで、後は前回と同じですね。

また続くかもしれない。

土曜日, 12月 26, 2015

LLDBでGoのデバッグをする その1

環境

環境は以下の通りです。

  • OS X El Capitan 10.11.2
  • lldb-340.4.119
  • go version go1.5 darwin/amd64

LLDBはxcodeについてきたものです。

やってみる

デバッグ対象のコードは、やっつけですがこんな感じです。ファイル名はmain.goとしました。

package main

import "fmt"

func main() {
   a := 10
   fmt.Printf("Add1(10) == %d\n", Add1(a))
}

// Add1 ...
func Add1(i int) int {
   return i + 1
}

LLDBでデバッグするために、-gcflags '-N -l' というオプションをつけてビルドします。

$ go build -gcflags '-N -l' main.go
$ ls
main    main.go

lldbコマンドに作成されたファイルを渡してみます。

$ lldb main
(lldb) target create "main"
Current executable set to 'main' (x86_64).
(lldb) 

ここから先はlldbの操作になります。とりあえず q と打つとlldbから抜けられることだけは覚えてください。

デバッグを開始するには run または r と打ちます。

(lldb) run
Process 22044 launched: '/Users/kmtr/misc/c/main' (x86_64)
Add1(10) == 11
Process 22044 exited with status = 0 (0x00000000) 
(lldb)  

無事実行されました。
これではデバッグにならないので、ブレイクポイントを打ちます。
試しに6行目と12行目に打ってみます。ブレイクポイントを打つ方法はいくつかありますが、行番号指定の場合 b ソースファイル名:行番号 が楽です。

(lldb) b main.go:6
Breakpoint 1: where = main`main.main + 31 at main.go:6, address = 0x000000000000205f
(lldb) b main.go:12
Breakpoint 2: where = main`main.Add1 + 9 at main.go:12, address = 0x0000000000002199
(lldb) 

br list もしくは b と打つと、現在のブレイクポイントの一覧が表示されます。
br delete ブレイクポイント番号 で対象のブレイクポイントを削除できます。
2番のブレイクポイントを消した後、再度ブレイクポイントを一覧表示して削除されることを確認してみてください。

ブレイクポイントを打った状態で再び run すると、こんな感じに止まります。

   3    import "fmt"
   4    
   5    func main() {
-> 6        a := 10
   7        fmt.Printf("Add1(10) == %d\n", Add1(a))
   8    }
   9    
(lldb)      

ステップオーバーは n です。打ってみるとカーソルが7行目に移ると思います。fr v 変数名 で変数の中身を表示できるので打ってみます。

(lldb) fr v a
(long) a = 10
(lldb) 

なぜかlongですが、とりあえずの確認には十分です。
現在のブレイクポイント周辺のソースを再表示する場合は f と打ちます。

ソースレベルでのステップインは s です。7行目で実行すると次のようにAdd1関数の中に入ると思います。

   9    
   10   // Add1 ...
   11   func Add1(i int) int {
-> 12       return i + 1
   13   }
(lldb) 

fr v と変数名を指定せずに打つと、現在のフレームの変数が全て表示されます。

(lldb) fr v
(long) i = 10
(long) ~r1 = 0
(lldb) 

(~r1 てなんだろう、、、)

c と打つと、次のブレイクポイント、もしくは終了するまで停止せずに実行されます。

(lldb) c
Process 22103 resuming
Add1(10) == 11
Process 22103 exited with status = 0 (0x00000000) 
(lldb) 

LLDBの使い方は、The LLDB Debugger のgdbとの比較がわかりやすいです。
watchが効かないという話も見かけましたが、少なくとも私の環境では問題ありませんでした。

感想

少ししか使っていないので、どこまでちゃんと動くのか、GDBとの差がどれだけあるかわからないけど、LLDBでもデバッグはできそうです。

続くかもしれない。