iOS常用調(diào)試方法:LLDB命令
在iOS項目開發(fā)過程中,常用到靜態(tài)分析(Analyze)、斷點(BreakPoint)和控制臺(Console)進行代碼調(diào)試。本篇文章介紹Xcode常用調(diào)試方法之”LLDB命令“。
本文來自360奇舞團QiShare團隊投稿。
相關(guān)閱讀:
1.簡介
LLDB是新一代高性能調(diào)試器。它構(gòu)建為一組可重用的組件,可以高度利用較大的LLVM項目中的現(xiàn)有庫,例如Clang表達式解析器和LLVM反匯編程序。
LLDB是Mac OS X上Xcode的默認調(diào)試器,支持在桌面和iOS設(shè)備和模擬器上調(diào)試C,Objective-C和C ++。
LLDB項目中的所有代碼都是在標(biāo)準(zhǔn)LLVM許可證下提供的,這是一種開源的“BSD風(fēng)格”許可證。
2.幫助
LLDB命令格式如下:
- <命令名稱> <命令動作> [-可選項 [可選項的值]] [參數(shù)1 [參數(shù)2...]]
LLDB命令的各部分由空格分割,如果參數(shù)中包含空格,則需要使用雙引號括起參數(shù),如果參數(shù)中包含雙引號或者反斜杠,則需要使用反斜杠進行轉(zhuǎn)義。
LLDB命令有非常多的功能,完全背下來不太容易,也沒必要。開發(fā)者可以使用help命令查看相關(guān)命令的用法,甚至可以查看help命令的用法。
- (lldb) help help
- Show a list of all debugger commands, or give details about a specific
- command.
- Syntax: help [<cmd-name>]
- Command Options Usage:
- help [-ahu] [<cmd-name> [<cmd-name> [...]]]
- -a ( --hide-aliases )
- Hide aliases in the command list.
- -h ( --show-hidden-commands )
- Include commands prefixed with an underscore.
- -u ( --hide-user-commands )
- Hide user-defined commands from the list.
- This command takes options and free-form arguments. If your arguments
- resemble option specifiers (i.e., they start with a - or --), you must use
- ' -- ' between the end of the command options and the beginning of the
- arguments.
3. 執(zhí)行
在LLDB中,執(zhí)行命令expression是最基礎(chǔ)的命令,簡寫為expr或者e,語法為:expression
- //! 輸出count的值
- (lldb) expression count
- (NSUInteger) $0 = 10
- //! 執(zhí)行string的拼接字符串方法
- (lldb) expression [string stringByAppendingString:@"732"]
- (__NSCFString *) $1 = 0x00006000006ac870 @"QiShare732"
- //! 修改view的顏色
- (lldb) expression self.view.backgroundColor = [UIColor redColor]
- (UICachedDeviceRGBColor *) $2 = 0x0000600001d74780
- (lldb) expression [CATransaction flush]
- //!< 因為斷點會終止UI線程,執(zhí)行[CATransaction flush]命令可以渲染出修改后的界面
4. 打印
打印命令print是最常用的命令,簡寫為pri或者p,語法為:print
同樣常用的expression簡寫命令還有po和call。其中po表示print object,用來打印對象,call用來調(diào)用某個方法。示例如下:
- (lldb) expression -- self.view
- (UIView *) $4 = 0x00007f8ca8401690
- (lldb) e self.view
- (UIView *) $5 = 0x00007f8ca8401690
- (lldb) p self.view
- (UIView *) $6 = 0x00007f8ca8401690
- (lldb) po self.view
- <UIView: 0x7f8ca8401690; frame = (0 0; 375 812); autoresize = W+H; layer = <CALayer: 0x6000008a1dc0>>
- (lldb) call self.view
- (UIView *) $8 = 0x00007f8ca8401690
另外,開發(fā)者可以按照print/
- p/x //!< 以十六進制打印整數(shù)
- p/d //!< 以帶符號的十進制打印整數(shù)
- p/u //!< 以無符號的十進制打印整數(shù)
- p/o //!< 以八進制打印整數(shù)
- p/t //!< 以二進制打印整數(shù)
- p/a //!< 以十六進制打印地址
- p/c //!< 打印字符常量
- p/f //!< 打印浮點數(shù)
- p/s //!< 打印字符串
- p/r //!< 格式化打印
p/x //!< 以十六進制打印整數(shù)p/d //!< 以帶符號的十進制打印整數(shù)p/u //!< 以無符號的十進制打印整數(shù)p/o //!< 以八進制打印整數(shù)p/t //!< 以二進制打印整數(shù)p/a //!< 以十六進制打印地址p/c //!< 打印字符常量p/f //!< 打印浮點數(shù)p/s //!< 打印字符串p/r //!< 格式化打印
示例如下:
- (lldb) p count
- (NSUInteger) $0 = 10
- (lldb) p/t count
- (NSUInteger) $1 = 0b0000000000000000000000000000000000000000000000000000000000001010
- (lldb) p/o count
- (NSUInteger) $2 = 012
- (lldb) p/x count
- (NSUInteger) $3 = 0x000000000000000a
- (lldb) p/x string
- (__NSCFConstantString *) $4 = 0x000000010416a0b8 @"QiShare"
- (lldb) p/c string
- (__NSCFConstantString *) $5 = \xb8\xa0\x16\x04\x01\0\0\0 @"QiShare"
- (lldb) p/s string
- (__NSCFConstantString *) $6 = @"QiShare"
- (lldb) p/a string
- (__NSCFConstantString *) $7 = 0x000000010416a0b8 @"QiShare" @"QiShare"
5. 線程
thread是線程相關(guān)的命令,語法為thread
開發(fā)者可以使用thread list命令查看當(dāng)前的所有線程,示例如下:
- (lldb) thread list
- Process 4031 stopped
- * thread #1: tid = 0x25cac, 0x0000000104167e23 QiDebugDemo`-[QiConsoleViewController testLLDBCommands](self=0x00007f850df0bbf0, _cmd="testLLDBCommands") at QiConsoleViewController.m:34, queue = 'com.apple.main-thread', stop reason = breakpoint 4.1
- thread #2: tid = 0x25d2f, 0x00000001079ff28a libsystem_kernel.dylib`__workq_kernreturn + 10
- thread #3: tid = 0x25d30, 0x00000001079ff28a libsystem_kernel.dylib`__workq_kernreturn + 10
- thread #4: tid = 0x25d31, 0x00000001079ff28a libsystem_kernel.dylib`__workq_kernreturn + 10
- thread #5: tid = 0x25d32, 0x00000001079ff28a libsystem_kernel.dylib`__workq_kernreturn + 10
- thread #6: tid = 0x25d33, 0x00000001079ff28a libsystem_kernel.dylib`__workq_kernreturn + 10
- thread #7: tid = 0x25d3e, 0x00000001079f520a libsystem_kernel.dylib`mach_msg_trap + 10, name = 'com.apple.uikit.eventfetch-thread'
thread backtrace命令可以方便地供開發(fā)者查看線程堆棧信息,簡寫為bt。比如,當(dāng)程序崩潰的時候,開發(fā)者可以查看堆棧調(diào)用列表。示例如下:
- (lldb) thread backtrace
- * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1
- frame #0: 0x0000000104cc2705 libobjc.A.dylib`objc_exception_throw
- frame #1: 0x00000001056704ec CoreFoundation`_CFThrowFormattedException + 194
- frame #2: 0x00000001057a6b00 CoreFoundation`-[__NSArrayI objectAtIndexedSubscript:] + 96
- * frame #3: 0x00000001043a1df7 QiDebugDemo`-[QiConsoleViewController testLLDBCommands](self=0x00007fadc7c50400, _cmd="testLLDBCommands") at QiConsoleViewController.m:33
- frame #4: 0x00000001043a1d5a QiDebugDemo`-[QiConsoleViewController viewDidLoad](self=0x00007fadc7c50400, _cmd="viewDidLoad") at QiConsoleViewController.m:26
- ...
- frame #18: 0x00000001056830be CoreFoundation`__CFRunLoopDoObservers + 430
- frame #19: 0x0000000105683751 CoreFoundation`__CFRunLoopRun + 1537
- frame #20: 0x0000000105682e11 CoreFoundation`CFRunLoopRunSpecific + 625
- frame #21: 0x000000010ddd51dd GraphicsServices`GSEventRunModal + 62
- frame #22: 0x000000010a1db81d UIKitCore`UIApplicationMain + 140
- frame #23: 0x00000001043a2450 QiDebugDemo`main(argc=1, argv=0x00007ffeeb85df90) at main.m:7
- frame #24: 0x0000000107858575 libdyld.dylib`start + 1
在調(diào)試過程中,開發(fā)者可以使用thread return命令終端某個方法并返回一個想要的值。示例如下:
- (lldb) thread return string
- (lldb) continue
- 2019-02-27 17:22:47.323225+0800 QiDebugDemo[5071:222700] resultString: Qi_Share
6. 斷點
作者在iOS 調(diào)試方法:斷點這篇文章中介紹過斷點的用法。其實,可視化的斷點都可以使用LLDB語法來實現(xiàn)。比如下圖中的1、2、3、4、5都能用LLDB命令表達。
- 啟用/禁用斷點(breakpoint enable/disable)
- 繼續(xù)執(zhí)行程序(continue)
- 執(zhí)行下一步(next)
- 進入方法(step)
- 跳出方法(finish)
在斷點相關(guān)操作中,因為Xcode已經(jīng)集成了可視化的斷點操作工具,所以breakpoint命令并不被常用。但是,breakpoint命令擁有著十分強大的功能,語法為:breakpoint
- //! 查看所有斷點
- (lldb) breakpoint list
- //! 為所有類中的viewDidAppear:設(shè)置斷點
- (lldb) breakpoint set -n viewDidAppear:
- //! 為QiConsoleViewController.m文件中的testLLDBCommands方法設(shè)定斷點
- (lldb) breakpoint set -f QiConsoleViewController.m -n testLLDBCommands
- //! 為QiConsoleViewController.m文件中的第32行代碼設(shè)定斷點
- (lldb) breakpoint set -f QiConsoleViewController.m -l 32
- //! 為handleString:方法設(shè)定條件斷點,條件為string != nil
- (lldb) breakpoint set - handleString: -c string != nil
7. 觀察點
想比較于breakpoint是對方法生效的斷點,watchpoint則是對地址生效的斷點。watchpoint類似于KVO的工作原理,當(dāng)觀察到屬性地址里面的東西改變時,就讓程序中斷,其語法為:watchpoint
- (lldb) watchpoint set variable string
- Watchpoint created: Watchpoint 1: addr = 0x7ffeef497360 size = 8 state = enabled type = w
- declare @ '/Users/huangxianshuai/Desktop/Products/QiShare/QiDebugDemo/QiDebugDemo/QiConsoleViewController.m:33'
- watchpoint spec = 'string'
- new value: 0x00000001007670b8
- (lldb) next
- Watchpoint 1 hit:
- old value: 0x00000001007670b8
- new value: 0x0000000100767138
- (lldb) image lookup -a 0x00000001007670b8
- Address: QiDebugDemo[0x00000001000040b8] (QiDebugDemo.__DATA.__cfstring + 32)
- Summary: @"QiShare"
- (lldb) image lookup -a 0x0000000100767138
- Address: QiDebugDemo[0x0000000100004138] (QiDebugDemo.__DATA.__cfstring + 160)
- Summary: @"huang"
image lookup -a 是 image lookup -address 的簡寫,可以查看參數(shù)地址的內(nèi)容。
8. 總結(jié)
文章僅列舉了作者平常使用到的一些LLDB命令,這些命令的確能提高調(diào)試效率。而更多的LLDB命令可以從LLDB Homepage(http://lldb.llvm.org/)或者其他網(wǎng)絡(luò)資源中獲取。
【本文是51CTO專欄機構(gòu)360技術(shù)的原創(chuàng)文章,微信公眾號“360技術(shù)( id: qihoo_tech)”】