XcodeのデバッガであるLLDBはPythonをサポートしていて、コマンドが作れたり、ブレークポイントで関数が実行できたりする。
同僚がトレース結果を逆順(最新が一番下)に表示したいと言っていたので、NSExceptionが発生したとき、トレースとNSExceptionの種類を表示するようにしてみた。
homeディレクトリに以下のような .lldbinit を作成しておく。
(Xcodeの動作時だけ作動させたいなら ~/.lldbinit-Xcode を作成する)
command script import ~/hoge.py
以下の内容で hoge.py を作成する。(別の名前にした場合、hoge.show_framesの部分も変更する)
#!/usr/bin/env python
# -*- coding: utf-8
import lldb
import commands
import os
def show_frames(frame, bp_loc, dict):
thread = frame.GetThread()
for i in range(thread.GetNumFrames() - 1, 0, -1):
print(str(thread.GetFrameAtIndex(i)))
print("UNCAUGHT NSEXCEPTION:")
debugger = thread.GetProcess().GetTarget().GetDebugger()
debugger.HandleCommand("po [$arg1 name]")
debugger.HandleCommand("po [$arg1 reason]")
return True
def __lldb_init_module(debugger, internal_dict):
debugger.HandleCommand("br set --name objc_exception_throw")
debugger.HandleCommand("br comm add -F hoge.show_frames")
これでデバッガの起動時に自分の関数が登録されるようになるはずである。
試しに以下のようにNSExceptionを投げると、
[[NSException exceptionWithName:@"hoge" reason:@"fuga" userInfo:nil] raise];
以下のような結果が得られた。
frame #22: 0x00000001028b465d libdyld.dylib`start + 1
frame #21: 0x00000001028b465d libdyld.dylib`start + 1
frame #20: 0x000000010076545f SimpleShare`main(argc=1, argv=0x00007fff5f49b5d8) at main.m:14
frame #19: 0x0000000102a9a0d4 UIKit`UIApplicationMain + 159
frame #18: 0x0000000102a9402f UIKit`-[UIApplication _run] + 468
frame #17: 0x0000000101306016 CoreFoundation`CFRunLoopRunSpecific + 406
frame #16: 0x00000001013065ff CoreFoundation`__CFRunLoopRun + 911
frame #15: 0x00000001013070cf CoreFoundation`__CFRunLoopDoSources0 + 527
frame #14: 0x0000000101321c01 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
frame #13: 0x00000001049367f6 FrontBoardServices`-[FBSSerialQueue _performNextFromRunLoopSource] + 45
frame #12: 0x000000010493646d FrontBoardServices`-[FBSSerialQueue _performNext] + 186
frame #11: 0x00000001049365f6 FrontBoardServices`__FBSSERIALQUEUE_IS_CALLING_OUT_TO_A_BLOCK__ + 24
frame #10: 0x0000000102a95793 UIKit`-[UIApplication workspaceDidEndTransaction:] + 182
frame #9: 0x0000000102a98584 UIKit`-[UIApplication _runWithMainScene:transitionContext:completion:] + 1709
frame #8: 0x0000000102a9231f UIKit`-[UIApplication _callInitializationDelegatesForMainScene:transitionContext:] + 4346
frame #7: 0x0000000102b18b6e UIKit`-[UIWindow makeKeyAndVisible] + 42
frame #6: 0x0000000102b05d20 UIKit`-[UIWindow _setHidden:forced:] + 294
frame #5: 0x0000000102b0563a UIKit`-[UIWindow addRootViewControllerViewIfPossible] + 65
frame #4: 0x0000000102c3d10a UIKit`-[UIViewController view] + 27
frame #3: 0x0000000102c3ccca UIKit`-[UIViewController loadViewIfRequired] + 1235
frame #2: 0x000000010076594e SimpleShare`-[ViewController viewDidLoad](self=0x00007fb1cd6051d0, _cmd="viewDidLoad") at ViewController.m:90
frame #1: 0x000000010137ba59 CoreFoundation`-[NSException raise] + 9
UNCAUGHT NSEXCEPTION:
hoge
fuga
ブレークポイントで呼ばれる関数の引数にはdebuggerがないが、frameからたどれるようになっているので、内蔵コマンドも実行できる。
ちなみに break command add
で番号を与えないときは、最後に作成したもの(ここではobjc_exception_throw)が対象になる。
また、コマンドを自作して、 br comm add -o print_frame
のようにしてみたが、それはうまくいかなかった(Xcodeが固まった)。
参考
- [LLDB] Pythonで独自コマンドを作ってみる
- 続・Debugger の Tips
- Introduction to LLDB Python scripting. | Fabian Guerra
- LLDB Python scripting in Xcode - Stack Overflow
- LLDB Example - Python Scripting to Debug a Problem
- LLDB Python examples