在一款完整iOS移動應用的開發中,代碼的調試和編寫佔着同等重要的地位。Xcode默認使用LLDB做爲代碼調試器,LLDB功能豐富且強大,恰當的使用它,能夠幫助開發者事半功倍的完成代碼調試的工做。express
關於LLDB調試器,最經常使用的指令應該是p與po了,開發者經常使用這兩個命令來進行對象的打印操做,p會打印出對象地址和類型,po則會額外打印出對象的值得內容,實際上,這兩個命令都是expression相關命令的簡寫。expression命令也並不是簡單的打印命令,實際上它是一個執行代碼命令,執行後將返回值進行打印,這個命令有一個十分強大的特色,它能夠真實改變程序運行中變量的值。例如在以下代碼中的int c = a+b 一行添加一個斷點,運行工程。sass
int a = 0; int b = 1; int c = a+b; NSLog(@"%d",c);
若是開發者不進行任何認爲操做,此時打印出的值應該是1,爲了測試,能夠在調試區輸入以下命令:app
(lldb) expression a=1
此後跳過斷點繼續運行程序,能夠看到打印的結果以下,c變成2。iphone
(lldb) expression a=1 (int) $0 = 1 2016-04-24 11:39:40.213 BreakPointTest[1010:79065] 2
經過上面的演示,咱們發現使用LLDB調試代碼十分方便的一個特色,當咱們知道程序某個地方可能會出現問題,爲了找到解決方法,不使用LLDB時咱們可能須要在代碼中添加大量的打印函數,而且屢次嘗試修改源代碼才能解決問題,若是使用LLDB的expression命令,咱們不只不須要添加額外的打印代碼,也不須要直接修改源代碼,在調試區進行屢次調試,直到找到正確的修改方法後再對源代碼修改一次便可。函數
當Xcode進入斷點調試或者遇到異常程序崩潰時,在Xcode左側的導航區都會將程序運行中的相關堆棧塊信息列舉出來,例如使用以下測試代碼,在text方法中的int c = a+b 一行添加一個斷點。工具
#import "ViewController.h" @interface ViewController () { int ab; } @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; ab = 1; [self test]; } -(void)test{ int a = 0; int b = 1; int c = a+b; NSLog(@"%d",c); } @end
當程序運行到斷點處斷開時,Xcode左側的堆棧塊以下圖所示:oop
從圖中能夠看出,程序當前處於激活狀態的線程有5個,程序目前斷在線程1中的test方法堆棧塊中,使用frame info指令能夠打印當前堆棧塊的信息,示例以下:學習
(lldb) frame info frame #0: 0x0000000102497905 BreakPointTest`-[ViewController test](self=0x00007fcd5b413320, _cmd="test") + 37 at ViewController.m:39
在打印的信息中,會有所在的文件名稱和函數名稱及堆棧塊標號和內存地址。測試
在實際代碼調試過程當中,程序運行的回溯是一個重要的方法,例如上面的代碼例子,雖然如今斷點斷在test方法中,開發者可能須要在viewDidLoad方法中進行相關調試,例如上面viewDidLoad方法中有一個變量ab,若是想查看ab變量的值,咱們就須要將當前選中調試的堆棧塊選擇爲viewDidLoad方法所在的堆棧塊,從Xcode左側導航區能夠看到,viewDidLoad方法堆棧塊的標號爲1,執行以下LLDB指令便可切換:ui
(lldb) frame select 1 frame #1: 0x00000001024978cb BreakPointTest`-[ViewController viewDidLoad](self=0x00007fcd5b413320, _cmd="viewDidLoad") + 91 at ViewController.m:31 28 int a = 0; 29 int b = 1; 30 int c = a+b; -> 31 NSLog(@"%d",c); 32 } 33 @end
從打印信息能夠看到,如今選中的調試堆棧塊已經切換到viewDidLoad方法,再使用expression指令時就能夠操做這個方法中的相關變量了。
在使用LLDB工具前,遇到這樣的狀況,我每每會採用打多個斷點,一步步追溯代碼的運行過程並檢查過程當中變量的值是否正確,調試起來並不十分方便,若是不當心錯過了某個斷點,又要從新開始,經過選擇調試的frame堆棧塊能夠十分方便的解決這個問題。
與frame相關的還有一個指令十分有用,下面的指令能夠打印出當前堆棧塊中全部對象的內容:
(lldb) frame variable (ViewController *) self = 0x00007fcd5b413320 (SEL) _cmd = "test" (int) a = 0 (int) b = 1 (int) c = 0
variable後面也能夠添加參數名來打印特定對象的內容:
(lldb) frame variable a (int) a = 0
上面提到過,程序運行中會有多個激活的線程,每一個線程中又有許多堆棧塊,frame相關指令用於綜合調試各個堆棧塊,thread指令則是用於綜合調試各個線程。首先Xcode左側導航區爲咱們列出的線程堆棧塊並非當前線程中的全部堆棧塊,使用以下命令能夠打印出當前線程的全部堆棧塊:
(lldb) thread backtrace * thread #1: tid = 0x152f8, 0x0000000102497905 BreakPointTest`-[ViewController test](self=0x00007fcd5b413320, _cmd="test") + 37 at ViewController.m:39, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1 * frame #0: 0x0000000102497905 BreakPointTest`-[ViewController test](self=0x00007fcd5b413320, _cmd="test") + 37 at ViewController.m:39 frame #1: 0x00000001024978cb BreakPointTest`-[ViewController viewDidLoad](self=0x00007fcd5b413320, _cmd="viewDidLoad") + 91 at ViewController.m:31 frame #2: 0x0000000103475984 UIKit`-[UIViewController loadViewIfRequired] + 1198 frame #3: 0x0000000103475cd3 UIKit`-[UIViewController view] + 27 frame #4: 0x000000010334bfb4 UIKit`-[UIWindow addRootViewControllerViewIfPossible] + 61 frame #5: 0x000000010334c69d UIKit`-[UIWindow _setHidden:forced:] + 282 frame #6: 0x000000010335e180 UIKit`-[UIWindow makeKeyAndVisible] + 42 frame #7: 0x00000001032d2ed9 UIKit`-[UIApplication _callInitializationDelegatesForMainScene:transitionContext:] + 4131 frame #8: 0x00000001032d9568 UIKit`-[UIApplication _runWithMainScene:transitionContext:completion:] + 1769 frame #9: 0x00000001032d6714 UIKit`-[UIApplication workspaceDidEndTransaction:] + 188 frame #10: 0x0000000105d438c8 FrontBoardServices`__FBSSERIALQUEUE_IS_CALLING_OUT_TO_A_BLOCK__ + 24 frame #11: 0x0000000105d43741 FrontBoardServices`-[FBSSerialQueue _performNext] + 178 frame #12: 0x0000000105d43aca FrontBoardServices`-[FBSSerialQueue _performNextFromRunLoopSource] + 45 frame #13: 0x0000000102e4a301 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17 frame #14: 0x0000000102e4022c CoreFoundation`__CFRunLoopDoSources0 + 556 frame #15: 0x0000000102e3f6e3 CoreFoundation`__CFRunLoopRun + 867 frame #16: 0x0000000102e3f0f8 CoreFoundation`CFRunLoopRunSpecific + 488 frame #17: 0x00000001032d5f21 UIKit`-[UIApplication _run] + 402 frame #18: 0x00000001032daf09 UIKit`UIApplicationMain + 171 frame #19: 0x0000000102497c3f BreakPointTest`main(argc=1, argv=0x00007fff5d768668) + 111 at main.m:14 frame #20: 0x00000001056fe92d libdyld.dylib`start + 1 frame #21: 0x00000001056fe92d libdyld.dylib`start + 1
thread list指令則能夠打印出當前全部激活的線程,以下:
(lldb) thread list Process 1049 stopped * thread #1: tid = 0x152f8, 0x0000000102497905 BreakPointTest`-[ViewController test](self=0x00007fcd5b413320, _cmd="test") + 37 at ViewController.m:39, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1 thread #2: tid = 0x1531b, 0x0000000105a43ee2 libsystem_kernel.dylib`kevent64 + 10, queue = 'com.apple.libdispatch-manager' thread #3: tid = 0x1531c, 0x0000000105a435e2 libsystem_kernel.dylib`__workq_kernreturn + 10 thread #4: tid = 0x15324, 0x0000000105a435e2 libsystem_kernel.dylib`__workq_kernreturn + 10 thread #5: tid = 0x15328, 0x0000000105a435e2 libsystem_kernel.dylib`__workq_kernreturn + 10
thread info能夠打印出當前選中調試的線程的信息:
(lldb) thread info thread #1: tid = 0x152f8, 0x0000000102497905 BreakPointTest`-[ViewController test](self=0x00007fcd5b413320, _cmd="test") + 37 at ViewController.m:39, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1
一樣也能夠使用thread select指令來切換選中調試的線程:
(lldb) thread select 2
thread continue指令用於繼續執行當前的線程:
(lldb) thread continue 2016-04-24 12:29:54.562 BreakPointTest[1049:86776] 1 Resuming thread 0x152f8 in process 1049 Process 1049 resuming
disassemble相關指令用於輸出某段程序的彙編代碼,執行disassemble指令將會反彙編當前函數:
BreakPointTest`-[ViewController test]: 0x10aab7940 <+0>: pushq %rbp 0x10aab7941 <+1>: movq %rsp, %rbp 0x10aab7944 <+4>: subq $0x20, %rsp 0x10aab7948 <+8>: leaq 0x1711(%rip), %rax ; @"%d" 0x10aab794f <+15>: movq %rdi, -0x8(%rbp) 0x10aab7953 <+19>: movq %rsi, -0x10(%rbp) 0x10aab7957 <+23>: movl $0x0, -0x14(%rbp) 0x10aab795e <+30>: movl $0x1, -0x18(%rbp) -> 0x10aab7965 <+37>: movl -0x14(%rbp), %ecx 0x10aab7968 <+40>: addl -0x18(%rbp), %ecx 0x10aab796b <+43>: movl %ecx, -0x1c(%rbp) 0x10aab796e <+46>: movl -0x1c(%rbp), %esi 0x10aab7971 <+49>: movq %rax, %rdi 0x10aab7974 <+52>: movb $0x0, %al 0x10aab7976 <+54>: callq 0x10aab7c80 ; symbol stub for: NSLog 0x10aab797b <+59>: addq $0x20, %rsp 0x10aab797f <+63>: popq %rbp 0x10aab7980 <+64>: retq
使用disassemble -b則會輸出帶字節信息的彙編代碼:
(lldb) disassemble -b BreakPointTest`-[ViewController test]: 0x10aab7940 <+0>: 55 pushq %rbp 0x10aab7941 <+1>: 48 89 e5 movq %rsp, %rbp 0x10aab7944 <+4>: 48 83 ec 20 subq $0x20, %rsp 0x10aab7948 <+8>: 48 8d 05 11 17 00 00 leaq 0x1711(%rip), %rax ; @"%d" 0x10aab794f <+15>: 48 89 7d f8 movq %rdi, -0x8(%rbp) 0x10aab7953 <+19>: 48 89 75 f0 movq %rsi, -0x10(%rbp) 0x10aab7957 <+23>: c7 45 ec 00 00 00 00 movl $0x0, -0x14(%rbp) 0x10aab795e <+30>: c7 45 e8 01 00 00 00 movl $0x1, -0x18(%rbp) -> 0x10aab7965 <+37>: 8b 4d ec movl -0x14(%rbp), %ecx 0x10aab7968 <+40>: 03 4d e8 addl -0x18(%rbp), %ecx 0x10aab796b <+43>: 89 4d e4 movl %ecx, -0x1c(%rbp) 0x10aab796e <+46>: 8b 75 e4 movl -0x1c(%rbp), %esi 0x10aab7971 <+49>: 48 89 c7 movq %rax, %rdi 0x10aab7974 <+52>: b0 00 movb $0x0, %al 0x10aab7976 <+54>: e8 05 03 00 00 callq 0x10aab7c80 ; symbol stub for: NSLog 0x10aab797b <+59>: 48 83 c4 20 addq $0x20, %rsp 0x10aab797f <+63>: 5d popq %rbp 0x10aab7980 <+64>: c3 retq
使用disassemble -c 指令能夠設置輸出彙編代碼的行數,以下:
(lldb) disassemble -c 5 BreakPointTest`-[ViewController test]: 0x10aab7940 <+0>: pushq %rbp 0x10aab7941 <+1>: movq %rsp, %rbp 0x10aab7944 <+4>: subq $0x20, %rsp 0x10aab7948 <+8>: leaq 0x1711(%rip), %rax ; @"%d" 0x10aab794f <+15>: movq %rdi, -0x8(%rbp)
使用disassemble -l只輸出當前斷點處彙編代碼:
BreakPointTest`-[ViewController test] + 37 at ViewController.m:30 29 int b = 1; -> 30 int c = a+b; 31 NSLog(@"%d",c); BreakPointTest`-[ViewController test]: -> 0x10aab7965 <+37>: movl -0x14(%rbp), %ecx
使用disassemble -m混合顯示彙編代碼:
(lldb) disassemble -m BreakPointTest`-[ViewController test] at ViewController.m:27 26 } 27 -(void)test{ 28 int a = 0; BreakPointTest`-[ViewController test]: 0x10aab7940 <+0>: pushq %rbp 0x10aab7941 <+1>: movq %rsp, %rbp 0x10aab7944 <+4>: subq $0x20, %rsp 0x10aab7948 <+8>: leaq 0x1711(%rip), %rax ; @"%d" 0x10aab794f <+15>: movq %rdi, -0x8(%rbp) 0x10aab7953 <+19>: movq %rsi, -0x10(%rbp) BreakPointTest`-[ViewController test] + 23 at ViewController.m:28 27 -(void)test{ 28 int a = 0; 29 int b = 1; 0x10aab7957 <+23>: movl $0x0, -0x14(%rbp) BreakPointTest`-[ViewController test] + 30 at ViewController.m:29 28 int a = 0; 29 int b = 1; 30 int c = a+b; 0x10aab795e <+30>: movl $0x1, -0x18(%rbp) BreakPointTest`-[ViewController test] + 37 at ViewController.m:30 29 int b = 1; -> 30 int c = a+b; 31 NSLog(@"%d",c); -> 0x10aab7965 <+37>: movl -0x14(%rbp), %ecx BreakPointTest`-[ViewController test] + 40 at ViewController.m:30 29 int b = 1; 30 int c = a+b; 31 NSLog(@"%d",c); 0x10aab7968 <+40>: addl -0x18(%rbp), %ecx BreakPointTest`-[ViewController test] + 43 at ViewController.m:30 29 int b = 1; 30 int c = a+b; 31 NSLog(@"%d",c); 0x10aab796b <+43>: movl %ecx, -0x1c(%rbp) BreakPointTest`-[ViewController test] + 46 at ViewController.m:31 30 int c = a+b; 31 NSLog(@"%d",c); 32 } 0x10aab796e <+46>: movl -0x1c(%rbp), %esi BreakPointTest`-[ViewController test] + 49 at ViewController.m:31 30 int c = a+b; 31 NSLog(@"%d",c); 32 } 0x10aab7971 <+49>: movq %rax, %rdi 0x10aab7974 <+52>: movb $0x0, %al 0x10aab7976 <+54>: callq 0x10aab7c80 ; symbol stub for: NSLog BreakPointTest`-[ViewController test] + 59 at ViewController.m:32 31 NSLog(@"%d",c); 32 } 33 @end 0x10aab797b <+59>: addq $0x20, %rsp 0x10aab797f <+63>: popq %rbp 0x10aab7980 <+64>: retq
使用disassemble -p進行當前行的彙編代碼輸出:
(lldb) disassemble -p BreakPointTest`-[ViewController test]: -> 0x10aab7965 <+37>: movl -0x14(%rbp), %ecx 0x10aab7968 <+40>: addl -0x18(%rbp), %ecx 0x10aab796b <+43>: movl %ecx, -0x1c(%rbp) 0x10aab796e <+46>: movl -0x1c(%rbp), %esi
bt指令用於打印當前線程全部堆棧塊信息:
(lldb) bt * thread #1: tid = 0x19f11, 0x000000010aab7965 BreakPointTest`-[ViewController test](self=0x00007fee9c11e330, _cmd="test") + 37 at ViewController.m:30, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1 * frame #0: 0x000000010aab7965 BreakPointTest`-[ViewController test](self=0x00007fee9c11e330, _cmd="test") + 37 at ViewController.m:30 frame #1: 0x000000010aab792b BreakPointTest`-[ViewController viewDidLoad](self=0x00007fee9c11e330, _cmd="viewDidLoad") + 91 at ViewController.m:22 frame #2: 0x000000010ba95984 UIKit`-[UIViewController loadViewIfRequired] + 1198 frame #3: 0x000000010ba95cd3 UIKit`-[UIViewController view] + 27 frame #4: 0x000000010b96bfb4 UIKit`-[UIWindow addRootViewControllerViewIfPossible] + 61 frame #5: 0x000000010b96c69d UIKit`-[UIWindow _setHidden:forced:] + 282 frame #6: 0x000000010b97e180 UIKit`-[UIWindow makeKeyAndVisible] + 42 frame #7: 0x000000010b8f2ed9 UIKit`-[UIApplication _callInitializationDelegatesForMainScene:transitionContext:] + 4131 frame #8: 0x000000010b8f9568 UIKit`-[UIApplication _runWithMainScene:transitionContext:completion:] + 1769 frame #9: 0x000000010b8f6714 UIKit`-[UIApplication workspaceDidEndTransaction:] + 188 frame #10: 0x000000010e3638c8 FrontBoardServices`__FBSSERIALQUEUE_IS_CALLING_OUT_TO_A_BLOCK__ + 24 frame #11: 0x000000010e363741 FrontBoardServices`-[FBSSerialQueue _performNext] + 178 frame #12: 0x000000010e363aca FrontBoardServices`-[FBSSerialQueue _performNextFromRunLoopSource] + 45 frame #13: 0x000000010b46a301 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17 frame #14: 0x000000010b46022c CoreFoundation`__CFRunLoopDoSources0 + 556 frame #15: 0x000000010b45f6e3 CoreFoundation`__CFRunLoopRun + 867 frame #16: 0x000000010b45f0f8 CoreFoundation`CFRunLoopRunSpecific + 488 frame #17: 0x000000010b8f5f21 UIKit`-[UIApplication _run] + 402 frame #18: 0x000000010b8faf09 UIKit`UIApplicationMain + 171 frame #19: 0x000000010aab7c5f BreakPointTest`main(argc=1, argv=0x00007fff55148668) + 111 at main.m:14 frame #20: 0x000000010dd1e92d libdyld.dylib`start + 1 frame #21: 0x000000010dd1e92d libdyld.dylib`start + 1
c指令繼續運行線程和process continue效果同樣。
call指令運行一個表達式,和 expression 效果同樣。
detach指令結束當前調試的線程。
di指令反彙編當前函數與disassemble相同。
exit指令退出lldb調試器。
finish指令完成當前堆棧塊的調試,程序會繼續運行。
n指令進行單步調試,與next做用同樣。
p指令與expression做用同樣。
print指令用於變量的打印。
r指令從新運行應用程序。
quit指令結束調試。
bugreport指令用於建立堆棧信息報告。
command history指令用於打印LLDB調試命令記錄。
help指令用於查詢LLDB相關調試指令的用法。
apropo指令用於查詢某些包含某些關鍵字的指令。
version指令用於查詢LLDB調試器的版本,以下:
(lldb) version lldb-350.0.21.3
image list命令用於打印工程中全部用到的庫文件。
image相關指令還有一個十分有用的命令,image lookup --address能夠查詢某個內存地址的內容,以下:
(lldb) image lookup --address 0x000000010373e885 Address: CoreFoundation[0x00000000000f4885] (CoreFoundation.__TEXT.__text + 996309) Summary: CoreFoundation`-[__NSArray0 objectAtIndex:] + 101
image lookup --type用於查詢某種類型中包含的屬性,以下:
(lldb) image lookup --type UILabel Best match found in /Users/vip/Library/Developer/Xcode/DerivedData/BreakPointTest-cearqrjqbntqcnfgiqzpxhyadewi/Build/Products/Debug-iphonesimulator/BreakPointTest.app/BreakPointTest: id = {0x000082c1}, name = "UILabel", byte-size = 8, decl = UILabel.h:18, compiler_type = "@interface UILabel : UIView @property ( getter = text,setter = setText:,readwrite,copy,nonatomic ) NSString * text; @property ( getter = font,setter = setFont:,readwrite,nonatomic ) UIFont * font; @property ( getter = textColor,setter = setTextColor:,readwrite,nonatomic ) UIColor * textColor; @property ( getter = shadowColor,setter = setShadowColor:,readwrite,nonatomic ) UIColor * shadowColor; @property ( getter = shadowOffset,setter = setShadowOffset:,assign,readwrite,nonatomic ) CGSize shadowOffset; @property ( getter = textAlignment,setter = setTextAlignment:,assign,readwrite,nonatomic ) NSTextAlignment textAlignment; @property ( getter = lineBreakMode,setter = setLineBreakMode:,assign,readwrite,nonatomic ) NSLineBreakMode lineBreakMode; @property ( getter = attributedText,setter = setAttributedText:,readwrite,copy,nonatomic ) NSAttributedString * attributedText; @property ( getter = highlightedTextColor,setter = setHighlightedTextColor:,readwrite,nonatomic ) UIColor * highlightedTextColor; @property ( getter = isHighlighted,setter = setHighlighted:,assign,readwrite,nonatomic ) BOOL highlighted; @property ( getter = isUserInteractionEnabled,setter = setUserInteractionEnabled:,assign,readwrite,nonatomic ) BOOL userInteractionEnabled; @property ( getter = isEnabled,setter = setEnabled:,assign,readwrite,nonatomic ) BOOL enabled; @property ( getter = numberOfLines,setter = setNumberOfLines:,assign,readwrite,nonatomic ) NSInteger numberOfLines; @property ( getter = adjustsFontSizeToFitWidth,setter = setAdjustsFontSizeToFitWidth:,assign,readwrite,nonatomic ) BOOL adjustsFontSizeToFitWidth; @property ( getter = baselineAdjustment,setter = setBaselineAdjustment:,assign,readwrite,nonatomic ) UIBaselineAdjustment baselineAdjustment; @property ( getter = minimumScaleFactor,setter = setMinimumScaleFactor:,assign,readwrite,nonatomic ) CGFloat minimumScaleFactor; @property ( getter = allowsDefaultTighteningForTruncation,setter = setAllowsDefaultTighteningForTruncation:,assign,readwrite,nonatomic ) BOOL allowsDefaultTighteningForTruncation; @property ( getter = preferredMaxLayoutWidth,setter = setPreferredMaxLayoutWidth:,assign,readwrite,nonatomic ) CGFloat preferredMaxLayoutWidth; @property ( getter = minimumFontSize,setter = setMinimumFontSize:,assign,readwrite,nonatomic ) CGFloat minimumFontSize; @property ( getter = adjustsLetterSpacingToFitWidth,setter = setAdjustsLetterSpacingToFitWidth:,assign,readwrite,nonatomic ) BOOL adjustsLetterSpacingToFitWidth; @end"
x指令能夠讀取某段內存的二進制數據:
(lldb) x 0x000000010373e885 0x10373e885: 66 66 2e 0f 1f 84 00 00 00 00 00 55 48 89 e5 48 ff.........UH..H 0x10373e895: 8d 3d 6d f2 28 00 e8 c0 d9 f0 ff 48 89 05 c1 58 .=m.(......H...X
LLDB的用法和技巧還有不少,它能夠大大提升咱們調試代碼的效率,有疏漏和錯誤之處,還望與志同道合的朋友共同窗習進步。
專一技術,熱愛生活,交流技術,也作朋友。
——琿少 QQ羣:203317592