Posted at

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

More than 3 years have passed since last update.


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コマンドライン

(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コマンドライン

(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コマンドライン

(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).


サンプルプログラム

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


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;
}



実行結果

% clang sample.c -g -O0 -m32

% ./a.out
Hello, World !
Hello, LLDB !


Breakpointの設定

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


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で詳細を確認して欲しい。


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)が実行される。


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'を実行


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とのコマンド対比

一覧表


参考


あとがき

LLVM読み全然進んでないや…

LLDBの続きはまた今度。