LLDBとかいう次世代高性能デバッガ

## LLDBとの出会い

LLVMを読むためのサポートとしてデバッガを使おう、と思い立ち調べていたら、GDBではなくLLDBという代物があるらしい。知らなかった。しかも結構前からあるみたい。

以下、[LLDBのページ][lldb]より抜粋したものを我的翻訳。

> LLDBとは？
> LLDBは次世代の高性能デバッガである。Clang expressionパーサとかLLVM Disassemblerのような、より大きなLLVMプロジェクトにおいて再利用しやすいコンポーネントとしてビルドされる。

今のところ何が嬉しいのかよくわかんねぇけど。
**LLDBは次世代の高性能デバッガである。**
この一文だけで十分なモチベーションになる。

以降、[LLDBチュートリアル][tutorial]をまとめていく。飽くまで筆者の理解であり、メモ程度。

## LLDBチュートリアル
### コマンドの構造

- LLDBのコマンドは、GDBより構造的な構文になっている。コマンドは、全て以下の様なフォームである。
	`<noun> <verb> [-options [option-value]] [argument [argument...]]`

- コマンド、オプション、引数はスペースで句切られる。スペースが必要なものには、ダブルクオーテーションが使用できる。バックスラッシュも想像の通り。
- コマンドに対するオプションはどこでも指定可能。ただし、引数 (argument) が'-'から始まる場合、LLDBにオプションの終了位置を'--'で教えてやる必要がある。例えば、`process launch`というコマンドを`--stop-at-entry`オプションと一緒にLLDBに与えるとき、かつコマンドの引数を`-program_arg value`で与えるとき、コマンドは以下のようになる。
	`(lldb) process launch --stop-at-entry -- -program_arg value # (lldb) はプロンプト`

#### Breakpoint
ここでは、特にBreakpointについてまとめる。Breakpointは次のように設定する。

- ファイルfoo.cの12行目にBreakpointを設定
	`(lldb) breakpoint set --file foo.c --line 12`
	または
	`(lldb) breakpoint set -f foo.c -l 12`
	
- 関数fooにBreakpointを設定
	`(lldb) breakpoint set --name foo`
	または
	`(lldb) breakpoint set -n foo`
	--nameオプションは1 breadpointコマンドに対して複数指定可。
	`(lldb) breakpoint set --name foo --name bar`

- メソッド名でも指定可能
	`(lldb) breakpoint set --method foo`
	または
	`(lldb) breakpoint set -M foo`
		
- Objective-Cのセレクタ名でも指定可能
	`(lldb) breakpoint set --selector alignLeftEdges:`
	または
	`(lldb) breakpoint set -S alignLeftEdges:`

- --shlibオプションで、Breakpointを実行可能ファイルに限定することもできる
	`(lldb) breakpoint set --shlib foo.dylib --name foo`
	または
	`(lldb) breakpoint set -s foo.dylib -n foo`
	--nameオプションのように、複数指定可能。

- gdbと同様に、lldbのコマンドインタプリタもコマンド名と最短のユニークな文字列をマッチさせる。良い日本語が見つからなかったので、以下のコマンド例で理解すること。
	`(lldb) breakpoint set -n "-[SKTGraphicView alignLeftEdges:]"`
	と
	`(lldb) br s -n "-[SKTGraphicView alignLeftEdges:]"`
	は同じ

#### コマンド補完
lldbでは、TABキーによるコマンド補完が使用可能。ソースファイル名・シンボル名・その他ファイル名を補完する。--fileオプション指定時にはファイル名を、--shlibオプション指定時にはロード済みの共有ライブラリ名を補完する。

#### Help
lldbはコマンドヘルプが超絶充実しているので、helpコマンドを活用すると良い。aproposコマンドでhelpテキストなどを検索可能。

以下のように、helpで出てくるコマンド例の引数の意味も調べられる。

```bash:lldbコマンドライン
(lldb) help command alias
...(中略)
Syntax: command alias <alias-name> <cmd-name> [<options-for-aliased-command>]
(lldb) help <alias-name>
  <alias-name> -- The name of an abbreviation (alias) for a debugger command.
```


#### Alias
長いコマンドの短縮形もlldbに教えることができる。
例えば、以下の様な breakpointコマンドは
`(lldb) breakpoint set --file foo.c --line 12`
aliasで以下のようにできる。
`(lldb) command alias bfl breakpoint set -f %1 -l %2`
`(lldb) bfl foo.c 12`
シェルのように引数を与えられるようだ。

lldbは立ち上がり時に~/.lldbinitファイルを読みに行くので、そのファイル内にaliasコマンドを書き連ねておけば、いちいちlldb起動時にaliasを登録し直すといった面倒な作業は発生しなくなる。ユーザが定義したaliasもlldbのhelpで参照可能であるので、自分で定義したaliasの意味を忘れてしまった時も安心。

```bash:lldbコマンドライン
(lldb) command alias tasukete help
(lldb) tasukete
... (中略)
  tasukete  -- ('help')  Show a list of all debugger commands, or give details about specific commands.
```

gdbでいうところのbreakコマンドも実装しているが、動作はgdbのそれとは少し異なる。デフォルトで、breakコマンドはaliasコマンドによってbにマップされている。

#### "raw" commands
lldbインタプリタは、"raw"コマンドをサポートしている。コマンドオプション以降は、全てそのままコマンドへ渡される。つまり’\’によるエスケープが必要なくなる。ただし'-'はダメ、詳しくは本節冒頭部を参照。

#### Pythonインタプリタ
lldbはPythonインタプリタを持っている。以下のように使用可能。

```bash:lldbコマンドライン
(lldb) script print('Hello, LLDB !')
Hello, LLDB !
(lldb) script import commands
(lldb) script commands.getoutput('uname')
'Darwin'
```
これもよく使うパッケージは~/.lldbinitにてimportしてたりすると良いかもしれない。
これについては、LLDBでPythonの力を借りたくなった時に、さらに調べてみようと思う。

### LLDBへプログラムをロードする
コマンドライン上で直接引数として指定するか、lldb起動後fileコマンドで指定する。以下、[チュートリアル][lldb]から引用。

> ```
$ lldb /Projects/Sketch/build/Debug/Sketch.app 
Current executable set to '/Projects/Sketch/build/Debug/Sketch.app' (x86_64).
```

> or you can specify it after the fact with the "file" command:

>```
$ lldb 
(lldb) file /Projects/Sketch/build/Debug/Sketch.app 
Current executable set to '/Projects/Sketch/build/Debug/Sketch.app' (x86_64).
```

### サンプルプログラム
以降、実践のため自前でサンプルプログラムを用意する。[チュートリアルページ][tutorial]でも某かを用いて説明しているが、（僕にとって）わかりづらかったので。

```cpp:sample.c
#include <stdio.h>

void show_msg (char *msg)
{
        int i;
        for (i=0; msg[i]!='\0'; i++) {
                putchar(msg[i]);
        }
}

int main (int argc, char *argv[])
{
        char *msg = "Hello, World !\n";
        char *msg2 = "Hello, LLDB !\n";
        show_msg(msg);
        show_msg(msg2);

        return 0;
}
```

```bash:実行結果
% clang sample.c -g -O0 -m32
% ./a.out
Hello, World !
Hello, LLDB !
```

### Breakpointの設定
Breakpointの作り方は前述のとおり。実際にサンプルプログラムにBreakpointを作ってみた。

```bash:lldbコマンドライン
% lldb a.out
(lldb) target create "a.out"
Current executable set to 'a.out' (i386).
(lldb) breakpoint set --name main
Breakpoint 1: where = a.out`main + 44 at sample.c:13, address = 0x00001f4c
(lldb) breakpoint set --name show_msg
Breakpoint 2: where = a.out`show_msg + 12 at sample.c:6, address = 0x00001ecc
(lldb) breakpoint list
Current breakpoints:
1: name = 'main', locations = 1
  1.1: where = a.out`main + 44 at sample.c:13, address = a.out[0x00001f4c], unresolved, hit count = 0

2: name = 'show_msg', locations = 1
  2.1: where = a.out`show_msg + 12 at sample.c:6, address = a.out[0x00001ecc], unresolved, hit count = 0
```

`(lldb) breakpoint list`
で設定したBreakpoint一覧を出力させられる。

Breakpointの設定は、 ***logical*** breakpointであり、1箇所以上の場所にbreakpointが作られる。例えば、今回のサンプルプログラムとは全く関係ないのだが、あるメソッドを指定してbreakpointを設定した時を考えると、全てのクラス内のメソッドにbreakpointが作られる。ファイルや行指定でbreakpointを設定した時も同様に、インライン展開された時などは、複数箇所にbreakpointが作られることになる。
logical breakpointはそれぞれ整数IDを持つ。"1:"が親breakpointのID、"1.1:"が子breakpointのIDであるようだ。

breakpoint listで見られる情報は、他に *unresolved* かどうかである。が、これについてはあまり理解できなかった。実際に動作させたもののunresolvedとresolvedの切り替えポイントは捕まえられず。

breakpointに達した時にあるコマンドを実行させることもできる。
breakpoint command add 1.1とすると、breakpoint 1.1に到達した時にbt (_regexp-bt: backtrace)が実行される。btについては(lldb) help btで詳細を確認して欲しい。

```bash:lldbコマンドライン
(lldb) breakpoint command add 1.1
Enter your debugger command(s).  Type 'DONE' to end.
> bt
> DONE
(lldb) breakpoint list
Current breakpoints:
1: name = 'main', locations = 1
  1.1: where = a.out`main + 44 at sample.c:13, address = a.out[0x00001f4c], unresolved, hit count = 0
    Breakpoint commands:
      bt


2: name = 'show_msg', locations = 1
  2.1: where = a.out`show_msg + 12 at sample.c:6, address = a.out[0x00001ecc], unresolved, hit count = 0
```

下記のように、breakpoint command add 1.1に到達した時に、bt (_regexp-bt: backtrace)が実行される。

```java:lldbコマンドライン
(lldb) r
Process 98709 launched: '~/sample000/a.out' (i386)
(lldb)  bt
* thread #1: tid = 0x86c2f3, 0x00001f4c a.out`main(argc=1, argv=0xbffffbdc) + 44 at sample.c:13, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
  * frame #0: 0x00001f4c a.out`main(argc=1, argv=0xbffffbdc) + 44 at sample.c:13
    frame #1: 0x987cf6ad libdyld.dylib`start + 1
Process 98709 stopped
* thread #1: tid = 0x86c2f3, 0x00001f4c a.out`main(argc=1, argv=0xbffffbdc) + 44 at sample.c:13, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
    frame #0: 0x00001f4c a.out`main(argc=1, argv=0xbffffbdc) + 44 at sample.c:13
   10
   11   int main (int argc, char *argv[])
   12   {
-> 13           char *msg = "Hello, World !\n";
   14           char *msg2 = "Hello, LLDB !\n";
   15           show_msg(msg);
   16           show_msg(msg2);
```

### Watchpointの設定
breakpointに加えて、watchopintも使用できる。

次のように使ってみた。

1. show_msg()にbreakpointを設定
2. int iをwatchpointに設定
3. i==10の時、pythonから`print 'PYTHON-MESSAGE: i==10'`を実行

```bash:lldbコマンドライン
#====================================
# 1. show_msg()にbreakpointを設定
#====================================
(lldb) breakpoint set --name show_msg
Breakpoint 1: where = a.out`show_msg + 12 at sample.c:8, address = 0x00001ecc
#------------------------------------
# Run
#------------------------------------
(lldb) r
Process 45781 launched: '~/sample000/a.out' (i386)
Process 45781 stopped
* thread #1: tid = 0x87f8d6, 0x00001ecc a.out`show_msg(msg="Hello, World !\n") + 12 at sample.c:8, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
    frame #0: 0x00001ecc a.out`show_msg(msg="Hello, World !\n") + 12 at sample.c:8
   5    void show_msg (char *msg)
   6    {
   7            int i;
-> 8            for (i=0; msg[i]!='\0'; i++) {
   9                    putchar(msg[i]);
   10           }
   11   }
#====================================
# 2. int iをwatchpointに設定
#====================================
(lldb) watchpoint set variable i
Watchpoint created: Watchpoint 1: addr = 0xbffffb50 size = 4 state = enabled type = w
    declare @ '~/sample000/sample.c:7'
    watchpoint spec = 'i'
    new value: -1073742904
#------------------------------------
# i==10の時だけストップする条件を設定
#------------------------------------
(lldb) watchpoint modify -c 'i==10' 1
1 watchpoints modified.
#====================================
# 3. i==10の時、pythonから
#    `print 'PYTHON-MESSAGE: i==10'`
#    を実行
#====================================
(lldb) watchpoint command add -s python 1
Enter your Python command(s). Type 'DONE' to end.
    print 'PYTHON-MESSAGE: i==10'
    DONE
#------------------------------------
# 設定したwatchpointを確認
#------------------------------------
(lldb) watchpoint list
Number of supported hardware watchpoints: 4
Current watchpoints:
Watchpoint 1: addr = 0xbffffb50 size = 4 state = enabled type = w
    declare @ '~/sample000/sample.c:7'
    watchpoint spec = 'i'
    new value: -1073742904
    condition = 'i==10'
    watchpoint commands:
      print 'PYTHON-MESSAGE: i==10'
(lldb) c
Process 45781 resuming
PYTHON-MESSAGE: i==10 # ← pythonスクリプトが実行されている！

Watchpoint 1 hit:
old value: -1073742904
new value: 10         # i==10の時だけストップ (?)
Process 45781 stopped
* thread #1: tid = 0x87f8d6, 0x00001f08 a.out`show_msg(msg="Hello, World !\n") + 72 at sample.c:8, queue = 'com.apple.main-thread', stop reason = watchpoint 1
    frame #0: 0x00001f08 a.out`show_msg(msg="Hello, World !\n") + 72 at sample.c:8
   5    void show_msg (char *msg)
   6    {
   7            int i;
-> 8            for (i=0; msg[i]!='\0'; i++) {
   9                    putchar(msg[i]);
   10           }
   11   }
(lldb) p i
(int) $11 = 10
```

- watchpointは、rしてからでないとうまくできなかった。
- プログラム内の変数を引数に取れたりするのだろうか。

### プログラムの開始・アタッチ
rとrunとprocess launchは同じコマンドなようだ。

process attachというコマンドは、lldbの外部プロセスに対してデバッガを走らせることができるみたい。サンプルプログラムでは、うまく捕まえられなかったが。

`(lldb) process attach [プロセス名] --waitfor`
とすると、プロセス名で指定されたプロセスが走りだしたらlldbがそのプロセスを捕まえてくれるような感じ。どういう時に使うんだろう。

### プログラムの制御
gdbでいうところの、sだったりn, c, ...は全てthreadコマンド以下に存在する。ここでも少し紹介するが、gdbとの対応は本稿の最後の方を参照。

## GDBとのコマンド対比
[一覧表][gdb_vs_lldb]

## 参考
[lldb]: http://lldb.llvm.org "The LLDB Debugger"
[tutorial]: http://lldb.llvm.org/tutorial.html "The LLDB Debugger Tutorial"
[gdb_vs_lldb]: http://lldb.llvm.org/lldb-gdb.html

- The LLDB Debugger http://lldb.llvm.org
- The LLDB Debugger Tutorial http://lldb.llvm.org/tutorial.html

## あとがき
LLVM読み全然進んでないや…
LLDBの続きはまた今度。