BLOG.tass.io

入門LLDB

2015-01-10

Objective-Cの超良質なエッセイが連載されているobjc↑↓LLDBデバッガの話題があったので、私なりにまとめてみました。

LLDBとは

LLDBとは、オープンソースのデバッガです。 XCodeにも標準デバッガとして搭載されています。 REPL機能や、C++やPythonで拡張できるプラグイン機構も持っています。
今回詳しくはふれませんが、特に有名なプラグインでは、facebookが公開しているChiselがあります。 ChiselはiOSアプリのデバッグに便利なコマンドが多数用意されていますので、ぜひインストールしておきましょう。

LLDBの基本

ヘルプ

helpコマンドで、lldbのコマンドの一覧が出力されます。全てのコマンドが出力されるので、かなり膨大です。
特定のコマンドのヘルプが見たいときは、help <コマンド>を使うことができます。

# LLDBのヘルプを表示
(lldb) help

# `po`コマンドのヘルプを表示
(lldb) help po

変数の出力

printコマンドで、変数の値を出力することができます。
例えば NSInteger countという変数がスコープにある状態でブレークしている場合、print countコマンドでその値を出力できます。 printのエイリアスとして、pripを使うこともできます。

printするたびに、$数字という値が出力されていると思います。これは、LLDBの名前空間に自動的に紐付けされる変数名です。printの中で、この変数を使って結果を求めることもできます。例えば、print count$0 = 99が出力された後に、print $0 + 11とすれば、110が出力されます。

(lldb) print count
(NSInteger) $0 = 99
(lldb) print $0 + 11
(long) $1 = 110
(lldb) print count
(NSInteger) $2 = 99 ← 計算するだけなら、元の変数(count)の値は変化しない

式の実行

変数を出力させるだけでなく、変更も行いたい場合はexpressionコマンドを使います。エイリアスとして、expreコマンドも使えます。
eコマンドに続いて式を入力することで、デバッガ上で実行することができます。ここで変数値を更新した場合は、実行中のコード上でも値が更新されます。

(lldb) expression count = 100
(NSInteger) $0 = 100
(lldb) print count
(NSInteger) $1 = 100
(lldb) e count = count + 10
(NSInteger) $2 = 110
(lldb) print count
(NSInteger) $3 = 110

printとexpressionの違いは?

実はprint count = 100と実行した場合も、変数countの値は更新されます。help printhelp expressionを実行した場合、どちらもexpressionのヘルプが出力されます。
printexpressionの違いはなんでしょうか?

答えは、オプション引数をとるかどうかです。
help expressionを読めばわかりますが、expressionコマンドには沢山の引数が存在します。対して、printコマンドは引数を1つしか取りません。もとい、printコマンドはexpression --の省略形です。

expressionコマンドは、オプション引数を取らない場合は下記のようになります。

expression (count * 10)

オプション引数を取る場合は、式との境界を--で区切って指定します。例えば出力結果を2進数で出力するオプション引数-f binを指定した上で、式の前には--を指定して実行します。

expression -f bin -- (count * 10)

対して、printコマンドは前述の通りexpression --の省略形なので、オプション引数は指定することができません。expressionコマンドの簡略コマンドを思って下さい。

print (count * 10)
と
expression -- (count * 10)
は同じ

オブジェクトを出力するpoコマンド

なお、expressionコマンドのオプションに、--object-descriptionがあります。Objective-Cのオブジェクトとして出力するオプションですが、このエイリアスとしてpoコマンドがあります。

(lldb) po name
kuronekomichael
(lldb) expression --object-description -- name
kuronekomichael

po name
と
expression --object-description -- name
は同じ

フォーマット出力するp/*コマンド

通常は十進数で出力されますが、フォーマットを指定してprintすることができます。

  • p/xコマンドで、16進数で出力されます。
    (lldb) p/x 32
    (int) $25 = 0x00000020
  • p/tコマンドで、2進数で出力されます。
    (lldb) p/t 32
    (int) $26 = 0b00000000000000000000000000100000
  • p/cでcharacterとして出力されます。
  • p/sで文字列として出力されます。

変数の利用

LLDB上で変数を定義した後、継続してデバッガ上でその変数を利用したい場合は接頭辞に「$」を付ける必要があります。

以下の例では、valueは定義できても参照できません。

(lldb) e long value = 10
(lldb) p value
error: use of undeclared identifier 'value'
error: 1 errors parsing expression

以下の例では、$valueを定義して参照もできています。

(lldb) e long $value = 10
(lldb) p $value
(long) $value = 10

さらに、変数を定義しつつ式を実行してメソッドを呼び出す事もできます。

(lldb) e int $index = 2
(lldb) p $index * 19
(int) $33 = 38
(lldb) e NSArray *$array = @[ @"Saturday", @"Sunday", @"Monday" ]
(lldb) p [$array count]
(NSUInteger) $34 = 3
(lldb) po [[$array objectAtIndex:0] uppercaseString]
SATURDAY
(lldb) p [[$array objectAtIndex:$index] characterAtIndex:0]
error: no known method '-characterAtIndex:'; cast the message send to the method's return type
error: 1 errors parsing expression

おおう、最後のpコマンドでエラーになってしまいました。 characterAtIndexメソッドの戻り値の型が自動判別できないようです。キャストしてあげれば、正しく出力できます。

(lldb) p (char)[[$array objectAtIndex:$index] characterAtIndex:0]
(char) $36 = 'M'

フローコントロール

XCodeデバッガの上部に表示されているツールバーに「コンティニュー」「ステップオーバー」「ステップイン」「ステップアウト」があります。XCodeならGUIで操作することもできますし、lldbでコマンドラインから操作することもできます。

  • 停止地点から再開 continue もしくは c
  • ステップオーバー next もしくは n
  • ステップイン step もしくは s
  • ステップアウト finish

また、frame infoコマンドでは、現在停止中のスタックの状態を出力できます。

# main関数でbreakpointを貼って、スタックの状態を取得した例。main関数の引数も確認できる
(lldb) frame info
frame #0: 0x000000010101a49c ToDoSample`main(argc=1, argv=0x00007fff5ebe6348) + 28 at main.m:16

# isEvenという関数でbreakpointを貼って、スタックの状態を取得した例。引数に101が渡ってきていることが確認できる
(lldb) frame info
frame #0: 0x000000010101a500 ToDoSample`isEven(i=101) + 16 at main.m:5

関数を実行中に、途中で処理を終えて任意の戻り値を返すこともできます。 thread return <式>コマンドを使うことで、現在停止中のフレームで処理を終えて、指定した戻り値を返すことができます。

ブレークポイント

XCodeで行の左端をクリックすることでブレークポイントを設定することができますが、lldbからコマンドラインで設定・削除することもできます。breakpointもしくはbrコマンドを利用します。

  • breakpoint list - 現在のブレークポイントの一覧を表示
  • breakpoint disable <ブレークポイントの番号> 指定したブレークポイントを無効にする
  • breakpoint enable <ブレークポイントの番号> 指定したブレークポイントを有効にする
  • breakpoint delete <ブレークポイントの番号> 指定したブレークポイントを削除する
  • breakpoint set -F 関数名 指定した関数名にブレークポイントを設定する

breakpoint setの例)

# 関数でもOK
breakpoint set -F isEven
# インスタンスメソッドやクラスメソッドにも設定できる
breakpoint set -F "-[NSArray objectAtIndex:]"
breakpoint set -F "+[NSSet setWithObject:]"

breakpointは、もっと複雑な条件を指定したり、ブレークポイント到達時に任意のlldbコマンドを実行することもできます。

XCodeのブレークポイントの選択メニューから「Edit Breakpoint」を選択すると、詳細な設定ができるダイアログが表示されます。

例えば下記の例では、常に停止するのではなく、変数countの値が99の時だけ停止するように設定しています。さらに、停止した際に自動的にlldbコマンド p countを実行するように設定してあります。
なお、このダイアログの下部にあるチェックボックス「Automantically continue after evaluating actions」にチェックを入れると、ブレークポイントに設定した条件に合致して指定したコマンドが実行された後、そのまま実行が再開されるようになります。これを応用すると、わざわざコード上にログ出力のコードを埋め込むことなく、変数値をデバッグウィンドウに出力させたりといった事ができるようになります。

このコードを実行した結果が下記となります。p countによって、デバッグウィンドウにcountの値が出力されていることがわかります。

より高度な実行

lldbデバッガ上では、C/C++/Objective-C/Swiftのコードを実行することが出来ます。 さすがに新しいクラスや関数を作成することはできませんが、例えばmallocを呼び出してメモリ空間を作成して操作するといったことは可能です。

# 8バイトのメモリ空間を作成
(lldb) expression char *$str = (char*) malloc(8)
# メモリ空間に文字列をコピー
(lldb) expression (void)strcpy($str, "munkeys")
(lldb) print $str
(char *) $str = 0x00007fc640d00090 "munkeys"
# 2文字目を'o'に置き換え
(lldb) expression $str[1] = 'o'
(lldb) print $str
(char *) $str = 0x00007fc640d00090 "monkeys"

さらに、memory readコマンドを使うことで、メモリをより詳しく見ることができます。なお、memory readのエイリアスとしてxも使えます。

 (lldb) memory read $str
 0x7fc640d00090: 6d 6f 6e 6b 65 79 73 00 c4 00 0d 64 fc 07 00 70  monkeys....d...p
 0x7fc640d000a0: 00 00 00 00 00 00 00 20 c4 00 0d 64 fc 07 00 70  ....... ...d...p

デフォルトでは32バイト分が表示されますが、-cオプションを使うことでより絞って表示することもできます。

(lldb) memory read -c 8 $str
0x7fc640d00090: 6d 6f 6e 6b 65 79 73 00                          monkeys.

最後に、先ほどmallocしたメモリ空間を解放する場合は、free関数を呼び出せばokです。

(lldb) expression (void)free($str)

Michael Kuroneko

Written by Michael Kuroneko. Follow me on twitter, github.