141
127

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2016-01-05

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の続きはまた今度。

141
127
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
141
127

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?