LLDBとの出会い
LLVMを読むためのサポートとしてデバッガを使おう、と思い立ち調べていたら、GDBではなくLLDBという代物があるらしい。知らなかった。しかも結構前からあるみたい。
以下、LLDBのページより抜粋したものを我的翻訳。
LLDBとは?
LLDBは次世代の高性能デバッガである。Clang expressionパーサとかLLVM Disassemblerのような、より大きなLLVMプロジェクトにおいて再利用しやすいコンポーネントとしてビルドされる。
今のところ何が嬉しいのかよくわかんねぇけど。
LLDBは次世代の高性能デバッガである。
この一文だけで十分なモチベーションになる。
以降、LLDBチュートリアルをまとめていく。飽くまで筆者の理解であり、メモ程度。
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で出てくるコマンド例の引数の意味も調べられる。
(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の意味を忘れてしまった時も安心。
(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インタプリタを持っている。以下のように使用可能。
(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 /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).
サンプルプログラム
以降、実践のため自前でサンプルプログラムを用意する。チュートリアルページでも某かを用いて説明しているが、(僕にとって)わかりづらかったので。
#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;
}
% clang sample.c -g -O0 -m32
% ./a.out
Hello, World !
Hello, LLDB !
Breakpointの設定
Breakpointの作り方は前述のとおり。実際にサンプルプログラムにBreakpointを作ってみた。
% 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で詳細を確認して欲しい。
(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)が実行される。
(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も使用できる。
次のように使ってみた。
- show_msg()にbreakpointを設定
- int iをwatchpointに設定
- i==10の時、pythonから
print 'PYTHON-MESSAGE: i==10'
を実行
#====================================
# 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とのコマンド対比
参考
- The LLDB Debugger http://lldb.llvm.org
- The LLDB Debugger Tutorial http://lldb.llvm.org/tutorial.html
あとがき
LLVM読み全然進んでないや…
LLDBの続きはまた今度。