在蘋果的官方文檔中列出了咱們在調試中能用到的一些命令,咱們在這重點講一些經常使用的命令
調試本地文件方法(Mac OS X):(lldb) target create "/Users/piaoyun/Desktop/xx.app/Contents/MacOS/xxxx"
遠程調試方法:
設備端運行:
附加進程:
./debugserver *:1234 -a "YourAPPName"
直接啓動進程:
debugserver -x backboard *:1234 /path/to/app/executable
例:
debugserver -x backboard *:1234 /Applications/MobileNotes.app/MobileNotes
此命令會啓動記事本,並斷在dyld的第一條指令上
在Mac終端運行lldb命令後,輸入如下2條命令:
platform select remote-iOS
process connect connect://你的設備IP地址:1234
用USB鏈接方法:
////////////////////////////////////////////////////////////////////////////////////////
wget http://cgit.sukimashita.com/usbmuxd.git/snapshot/usbmuxd-1.0.8.tar.bz2
tar xjfv usbmuxd-1.0.8.tar.bz2
cd usbmuxd-1.0.8/Python-client/
python tcprelay.py -t 1234:1234
在Mac終端運行lldb命令後,輸入如下2條命令:
platform select remote-ios
process connect connect://localhost:1234
////////////////////////////////////////////////////////////////////////////////////////
-(void)loginWithUserName:(NSString *)username password:(NSString *)password
{
NSLog(@"login.... username:%@ password:%@", username, password); // 假設咱們在此下斷點
}
1、基本操做
1.1.視圖層次
打印視圖層次 po [self.contentView recursiveDescription]
1.2.改變某個取值
1.int a = 1;
2.//Console expr a=2
3.NSLog(@"實際值: %d", a);
1.3.call 改變view的背景色
call [self.view setBackgroundColor:[UIColor redColor]]
1.4.聲明變量
1.expr int $b=2 或者 e int $b=2
2.//輸出 po $b
print (type)表達式
例子:
print (int)$r6
print username
1.5.打印堆棧
ios中打印堆棧方法是 NSThread callStackSymbols,這裏調試的時候有個簡單的方法以下
bt 或者 bt all
1.6.更改方法返回值
thread return NO/YES
1.-(BOOL) returnYES
2.{
3. //thread return NO ,能夠更改函數返回值
4. return YES;
5.}//方法返回結果爲NO
1.7.多線程異常後查看歷史對象的malloc分配歷史
先打開Enable Zombie Objects 和 Malloc Stack
1.(lldb) command script import lldb.macosx.heap
2.(lldb) malloc_info -S 0x7ff7206c3a70
1.8.寄存器查找對象
曾經遇到過一個問題,[self.tableview reloadData]直接奔潰。這時候tableview其實沒有問題,咱們要怎麼去找問題呢?
如上圖所示,最後咱們經過malloc_info -S 0x00007fe99a629560來查看對象分配在堆裏面的具體的地址,隨後在左側打開table的全部變量,輸入這個地址即可以看到這個是什麼成員。
1.9. c
繼續執行
7.s
源碼級別單步執行,遇到子函數則進入
8.si
單步執行,遇到子函數則進入
9.n
源碼級別單步執行,遇到子函數不進入,直接步過
10.ni
單步執行,遇到子函數不進入,直接步過
11.finish/f
退出子函數
12.thread list
打印線程列表
13.image lookup -a 表達式、image list
例子:
image lookup -a $pc
返回以下:
Address: debug[0x0000b236] (debug.__TEXT.__text + 1254)
Summary: debug`main + 58 at main.m:16
打印加載模塊列表
image list [-f -o 一般帶這兩個參數使用]
返回以下:
[ 0] 40E417A4-F966-3DB4-B028-B0272DC016A7 0x000a0000 /Users/piao/Library/Developer/Xcode/DerivedData/debug-bdkhskdqykkoqmhjedilckzvpuls/Build/Products/Debug-iphoneos/debug.app/debug
/Users/piao/Library/Developer/Xcode/DerivedData/debug-bdkhskdqykkoqmhjedilckzvpuls/Build/Products/Debug-iphoneos/debug.app.dSYM/Contents/Resources/DWARF/debug
[ 1] 011601C0-F561-3306-846B-94A7C8C841DA 0x2d9e6000 /Users/piao/Library/Developer/Xcode/iOS DeviceSupport/7.1.2 (11D257)/Symbols/System/Library/Frameworks/CoreGraphics.framework/CoreGraphics
查找某個函數:
對於有調試符號的這樣使用
image lookup -r -n <FUNC_REGEX>
對於無調試符號的這樣使用:
image lookup -r -s <FUNC_REGEX>
14.disassemble -a 地址
例子:
dis -a $pc
debug`main at main.m:14:
0xa71fc: push {r7, lr}
0xa71fe: mov r7, sp
0xa7200: sub sp, #0x24
0xa7202: movs r2, #0x0
0xa7204: movt r2, #0x0
0xa7208: str r2, [sp, #0x20]
0xa720a: str r0, [sp, #0x1c]
0xa720c: str r1, [sp, #0x18]
0xa720e: blx 0xa7fe0 ; symbol stub for:
.
.
.
2015-04-29 添加
disassemble -A thumb
可選:
thumbv4t
thumbv5
thumbv5e
thumbv6
thumbv6m
thumbv7
thumbv7f
thumbv7s
thumbv7k
thumbv7m
thumbv7em
///////////////////////////////////////////////
15.memory read [起始地址 結束地址]/寄存器 -outfile 輸出路徑
例子:
memory read $pc
0x00035ebe: 0e 98 07 99 09 68 08 9a 90 47 0c 99 03 90 08 46 .....h...G.....F
0x00035ece: 03 99 01 f0 80 e8 02 22 c0 f2 00 02 41 f2 52 10 ......."....A.R.
memory read 0x35f1c 0x35f46 -outfile /tmp/test.txt // 將內存區域保存到文件
2015-04-29添加:
默認狀況下,memory read 只能讀取 1024字節數據
例如:
就會報錯
error: Normally, 'memory read' will not read over 1024 bytes of data.
解決方法:加-force參數
memory read 0x1000 0x3000 -outfile /tmp/test.txt -force
或者:
memory read 0x1000 -outfile /tmp/test.txt -count 0x2000 -force
memory read $x0(寄存器) -outfile /tmp/test.txt -count 0x2000 -force
--binary // 二進制輸出
例:
memory read 0x1000 0x3000 -outfile /tmp/test.bin --binary -force
寫內存:
memory write $rip 0xc3
memory write $rip+1 0x90
16.register read/格式、register write 寄存器名稱 數值
例子:
register read/x
返回以下:
General Purpose Registers:
r0 = 0x1599e028
r1 = 0x38131621 libobjc.A.dylib`objc_msgSend + 1
r2 = 0x000a85cc "class"
r3 = 0x000a85d4 (void *)0x000a8618: AppDelegate
r4 = 0x00000000
r5 = 0x000a71fd debug`main + 1 at main.m:14
r6 = 0x00000000
r7 = 0x27d63c80
r8 = 0x27d63c98
r9 = 0x00000002
r10 = 0x00000000
r11 = 0x00000000
r12 = 0x3a3ff1c8 (void *)0x3875cc19: _Unwind_SjLj_Unregister + 1
sp = 0x27d63c5c
lr = 0x38136eaf libobjc.A.dylib`objc_autoreleasePoolPush + 311
pc = 0x000a7236 debug`main + 58 at main.m:16
cpsr = 0x20000030
// 改寫r9寄存器例子:
register write r9 2
17.display 表達式 undisplay 序號
例子:
display $R0
undisplay 1
18 內存斷點 watchpoint set expression 地址 / watchpoint set variable 變量名稱 -- (源碼調試用到,略過)
例子:
watchpoint set expression 0x1457fa70
命中後獲得結果:
Watchpoint 3 hit:
old value: 3
new value: 4
18.2 內存訪問斷點 watchpoint set expression -w read -- 內存地址
watchpoint set expression -w read -- 0x16b9dd91
18.2 內存寫入斷點 watchpoint set expression -w write -- 內存地址
watchpoint set expression -w read -- 0x16b9dd91
18.3 條件斷點 watchpoint modify -c 表達式
例子:
watchpoint modify -c '*(int *)0x1457fa70 == 20'
命中後獲得結果:
Watchpoint 3 hit:
old value: 15
new value: 20
19.找按鈕事件 po [按鈕名稱/內存地址 allTargets]
例子:
(lldb) po [[self btnTest] allTargets]
{(
<ViewController: 0x166af1f0>
)}
(lldb) po [[self btnTest] actionsForTarget:(id)0x166af1f0 forControlEvent:0]
<__NSArrayM 0x165b8950>(
testAction:
)
Bash
1.// 在機器上實戰一下:
2.(lldb) po [[[UIApplication sharedApplication] keyWindow] recursiveDescription]
3.<UIWindow:
4. 0x15e771c0; frame = (0 0; 320 568); gestureRecognizers = <NSArray:
5.0x15e96210>; layer = <UIWindowLayer: 0x15e988e0>>
6. | <UIView: 0x15eb4180; frame = (0 0; 320 568); autoresize = W+H; layer = <CALayer: 0x15eb4300>>
7.
8. | | <UIButton: 0x15eb32d0; frame = (277 285; 46 30); opaque = NO;
9. autoresize = RM+BM; layer = <CALayer: 0x15eb2e30>>
10. |
11.| | <UIButtonLabel: 0x15db5220; frame = (0 6; 46 18); text =
12.'Button'; opaque = NO; userInteractionEnabled = NO; layer =
13.<_UILabelLayer: 0x15db5410>>
14. | | <_UILayoutGuide: 0x15eb4360; frame = (0 0; 0 20); hidden = YES; layer = <CALayer: 0x15eb4540>>
15. | | <_UILayoutGuide: 0x15eb4af0; frame = (0 568; 0 0); hidden = YES; layer = <CALayer: 0x15eb4b70>>
16.
17.(lldb) po [(UIButton *)0x15eb32d0 allTargets]
18.{(
19. <ViewController: 0x15e93250>
20.)}
21.
22.(lldb) po [(UIButton *)0x15eb32d0 allTargets]
23.{(
24. <ViewController: 0x15e93250>
25.)}
26.
27.(lldb) po [(UIButton *)0x15eb32d0 actionsForTarget:(id)0x15e93250 forControlEvent:0]
28.<__NSArrayM 0x15dbfc50>(
29.testAction:
30.)
31.// 調用--
32.(lldb) po [0x15e93250 testAction:nil]
33.0x00210c18
// 再來一發,對按鈕屬性操做
Bash
1.(lldb) po [[[UIApplication sharedApplication] keyWindow] recursiveDescription]
2.<UIWindow: 0x15e771c0; frame = (0 0; 320 568); gestureRecognizers = <NSArray: 0x15e96210>; layer = <UIWindowLayer: 0x15e988e0>>
3. | <UIView: 0x15eb4180; frame = (0 0; 320 568); autoresize = W+H; layer = <CALayer: 0x15eb4300>>
4. | | <UIButton: 0x15eb32d0; frame = (277 285; 46 30); opaque = NO; autoresize = RM+BM; layer = <CALayer: 0x15eb2e30>>
5. | | | <UIButtonLabel: 0x15db5220; frame = (0 6; 46 18); text = 'Button'; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x15db5410>>
6. | | <_UILayoutGuide: 0x15eb4360; frame = (0 0; 0 20); hidden = YES; layer = <CALayer: 0x15eb4540>>
7. | | <_UILayoutGuide: 0x15eb4af0; frame = (0 568; 0 0); hidden = YES; layer = <CALayer: 0x15eb4b70>>
8.
9.(lldb) expression UIButton *$btn = (UIButton *)0x15eb32d0
10.(lldb) expression [$btn setHidden:YES]
帶參數運行:
Bash
1.(lldb) target create "/bin/ls"
2.Current executable set to '/bin/ls' (x86_64).
3.(lldb) set set target.run-args $(python \-c 'print "a"*200')
4.(lldb) run
5.Process 40752 launched: '/bin/ls' (x86_64)
6.ls: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa: No such file or directory
7.Process 40752 exited with status = 1 (0x00000001)
8.(lldb)
std::string 讀取方式:從內存+8的地方開始 64bit自行變通
例:
Bash
1.(lldb) x $r0+8
2.0x02db9248: 20 82 e3 14 71 00 00 00 61 00 00 00 c0 82 d3 14 .惝q...a...喇贏
3.0x02db9258: 71 00 00 00 2b 00 00 00 20 f9 e6 14 61 00 00 00 q...+... .a...
4.(lldb) x/s 0x14e38220
5.0x14e38220: "hello!piaoyun"
常見問題-打印無效
上面咱們簡單的學習瞭如何使用LLDB命令。但有時咱們在使用這些LLDB命令的時候,依然可能會遇到一些問題。不明類型或者類型不匹配
1.p (void)NSLog(@"%@",[self.view viewWithTag:1001]) //記住要加void
2.p (CGRect)[self.view frame] //記住不能寫成 self.view.frame,lldb的bug
2、調試進階
2.1.監聽某個方法的調用
若是是自定義的view,好比QQView,想監聽frame變化,直接[QQView setFrame:]便可 系統方法就要如圖所示,x86_64系統中,rdi表示第一個參數,具體其餘平臺可看inspecting-obj-c-parameters-in-gdb,裏面有詳細的說明
1.$rdi ➡ arg0 (self)
2.$rsi ➡ arg1 (_cmd)
3.$rdx ➡ arg2
4.$rcx ➡ arg3
5.$r8 ➡ arg4
6.$r9 ➡ arg5
2.2.image尋址,找到崩潰行
此時會調用以下代碼會崩潰
1.NSArray *arr=[[NSArray alloc] initWithObjects:@"1",@"2", nil];
2.NSLog(@"%@",arr[2]);
3.
4.2015-06-30 14:47:59.280 QQLLDB[6656:138560] *** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayI objectAtIndex:]: index 2 beyond bounds [0 .. 1]'
5.*** First throw call stack:
6.(
7.0 CoreFoundation 0x000000010b2d8c65 __exceptionPreprocess + 165
8.1 libobjc.A.dylib 0x000000010af71bb7 objc_exception_throw + 45
9.2 CoreFoundation 0x000000010b1cf17e -[__NSArrayI objectAtIndex:] + 190
10.3 QQLLDB 0x000000010aa404f6 -[ViewController viewDidLoad] + 1030
11.4 UIKit 0x000000010ba75210 -[UIViewController loadViewIfRequired] + 738
12.5 UIKit 0x000000010ba7540e -[UIViewController view] + 27
13.6 UIKit 0x000000010b9902c9 -[UIWindow
此時咱們要直接找到崩潰的行數,很簡單。先找到非系統bug的崩潰地址,以下圖所示,顯然是第三行QQLLDB,右邊對應的地址是0x000000010aa404f6,而後輸入image lookup --address 0x000000010aa404f6,便可看到是60行出了bug
1.image lookup --address 0x000000010aa404f6
2.Address: QQLLDB[0x00000001000014f6] (QQLLDB.__TEXT.__text + 1030)
3.Summary: QQLLDB`-[ViewController viewDidLoad] + 1030 at ViewController.m:60
2.3.crash日誌分析,讀取符號表
1.要知道如何讀取符號表,咱們得先僞造一份符號表的數據文件出來,代碼以下
1.NSArray *arr=[[NSArray alloc] initWithObjects:@"1",@"2", nil];
2.NSLog(@"%@",arr[2]);
僞造步驟:
1.編譯到真機
2.而後進入xcode將這個打開,找到QQLLDB.app.dSYM這個文件,偷偷拷貝一份到桌面
3.而後在product中clean一下工程
4.在真機上打開一個編譯進去的程序,會出現圖
步驟以下圖所示:
以後在命令行操做
1. atos -o /Users/tomxiang/Desktop/符號表/QQLLDB.app.dSYM/Contents/Resources/DWARF/QQLLDB -l0x1000e8000 0x1000eebd4
2. //此時獲得結果,告訴咱們是ViewController的viewDidLoad的第62行崩潰
3.-[ViewController viewDidLoad] (in QQLLDB) (ViewController.m:62)
2.4觀察實例變量的變化
假設你有一個 UIView,不知道爲何它的 _layer 實例變量被重寫了 (糟糕)。由於有可能並不涉及到方法,咱們不能使用符號斷點。相反的,咱們想監視何時這個地址被寫入。
首先,咱們須要找到 _layer 這個變量在對象上的相對位置:
1.(lldb) p (ptrdiff_t)ivar_getOffset((struct Ivar*)class_getInstanceVariable([MyView class], "_layer"))
2.(ptrdiff_t) $0 = 8
如今咱們知道 ($myView + 8) 是被寫入的內存地址:
1.(lldb) watchpoint set expression -- (int *)$myView + 8
2.Watchpoint created: Watchpoint 3: addr = 0x7fa554231340 size = 8 state = enabled type = w
3.new value: 0x0000000000000000
這被以 wivar $myView _layer 加入到 Chisel 中。
3、Chisel-facebook開源插件
1.安裝方法:
1.git clone https://github.com/facebook/chisel.git ~/.chisel
2.echo "command script import ~/.chisel/fblldb.py">>~/.lldbinit
安裝好後,打開xcode就能夠運行調試了
2.基本命令
2.1.預覽圖片
1.UIImage *image = [UIImage imageNamed:@"clear"];
2.UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
3.[viewB addSubview:imageView];
4.
5.//此時執行命令以後,圖片會被蘋果用自帶的預覽工具顯示出來
6.visualize image
2.2.邊框/內容着色
以下所示,打印獲得imageView地址以後,而後用border命令將其邊框着色unborder取消着色
1.(lldb) p imageView
2.(UIImageView *) $2 = 0x00007fb8c9b15910
3.(lldb) border 0x00007fb8c9b15910 -c green -w 2
同理,mask是給內容着色 unmask
mask imageView -c green
2.3.關係鏈的繼承
1.(lldb) pclass image
2.UIImage
3. | NSObject
2.4.打印全部屬性
1.`pinternals`這個命令就是打印出來的一個控件(id)類型的內部結構,詳細到使人髮指!甚至是你自定義的控件中的類型,譬如這個styleView就是我自定義的,內部有個iconView的屬性,其中的值它也會打印出來。
2.
3.(lldb) pinternals image
4.(UIImage) $8 = {
5. NSObject = {
6. isa = UIImage
7.}
8._imageRef = 0x00007fc1f3330780
9._scale = 2
10._imageFlags = {
11. named = 1
12. imageOrientation = 0
13. cached = 0
14. hasPattern = 0
15. isCIImage = 0
16. renderingMode = 0
17. suppressesAccessibilityHairlineThickening = 0
18. hasDecompressionInfo = 0
19. }
20.}
2.5.bmessage
若是Chisel
ViewController沒有設置viewWillDisappear這個方法,此時我想用斷點斷下來,能夠這樣
1.(lldb) bmessage -[ChiselViewController viewWillDisappear:]
2.
3.Setting a breakpoint at -[UIViewController viewWillDisappear:] with condition (void*)object_getClass((id)$rdi) == 0x0000000100c47060
4.Breakpoint 2: where = UIKit`-[UIViewController viewWillDisappear:], address = 0x0000000101a0b566
Breakpoints
BreakPoint分類
breakpoint也是有分類的,我這裏的文章內大體按使用的方式分爲了
•Normal Breakpoint,
•Exception Breakpoint,
•OpenGL ES Error breakpoint,
•Symbolic Breakpoint,
•Test Failure Breakpoint,
•WatchPoints。
能夠按具體的情景使用不一樣類型的breakpoint,解決問題爲根本。
Normal Breakpoint
添加普通斷點就很少說了,在源代碼的右側點擊一下便可。或者,使用快捷鍵:command + \ 來添加和刪除。這兩種方式添加的breakpoints在Xcode上面是能夠經過UI看到的。
還有能夠經過下面兩個LLDB命令直接在運行時添加斷點,可是這種方式須要注意的是一方面沒法經過UI直接看到斷點,另一方面只存在於本次運行,下一次啓動模擬器從新運行的時候,這些斷點就不生效了。
如上圖,經過「br li」命令打印全部的breakpoint,能夠看到一共有3個breakpoint,第一個是經過Xcode的UI添加的,後面兩個分別是經過下面兩個命令添加的:
「breakpoint set -f XXX.m -l XX」 和 「b XXX.m:XX」。
breakpoint set -n write -c "(*(char**) ($esp + 8))[0]==0x17 && (*(char**) ($esp + 8))[1]==0x03 && (*(char**) ($esp + 8))[2]==0x03 && (*(char**) ($esp + 8))[3]==0x00 && (*(char**) ($esp + 8))[4]==0x28"
Exception Breakpoint
能夠經過下圖中Xcode的UI添加Exception Breakpoint。有時候,好比數組越界或者設置一個空對象等問題,都會拋出一個異常,可是這種類型的錯誤很是難以定位,這個時候就可使用Exception Breakpoint來進行調試,在異常發生時能夠捕捉到並中止程序的執行。OC中的異常是一個常被忽略的地方,但實際上系統框架內這個使用很是普遍,大部分這種錯誤信息,系統框架都會以異常的形式throw出來,因此善用這種breakpoint的話,咱們能大大減小查找錯誤的時間。
例如,當咱們添加以下Exception Breakpoint以後(bt 命令後文中會講解,這個命令的做用是在斷點觸發時,打印回調棧信息):
相似下面這樣的數組越界的問題,咱們能夠很容易就定位到問題所在,不用再毫無頭緒找來找去了:
當斷點暫停執行時,咱們能夠經過Xcode的UI中查看調用棧信息:
或者查看bt命令打印的調用棧信息:
還有相似以下的錯誤能夠經過這種斷點很容易定位到:
,不過這種問題,能夠經過使用setValue:forKey:代替來避免。
OpenGL ES Error Breakpoint
同上圖中,在Xcode的breakpoint navigator的下部添加按鈕,選擇」Add OpenGL ES Error Breakpoint」便可。這個breakpoint主要是用來在OpenGL ES發生錯誤時中止程序的運行。
Symbolic Breakpoint
經過Xcode的UI添加symbolic breakpoint的方式同exception breakpoint,彈出框以下:
Symbolic breakpoints 在某個特定的函數或者方法開始執行的時候,暫停程序的執行,經過這種方式添加斷點,咱們就不須要知道在源文件中添加,也不須要知道斷點設置在文件的第幾行。
上圖中,最主要的設置是Symbol的內容,能夠有以下幾種:
•1. A method name,方法名稱,例如 pathsMatchingExtensions: 這樣的方法名稱,會對全部類的這個方法都起做用。
•2. A method of a particular class. 特定類的某個方法。例如 ,[SKLine drawHandlesInView],或者 people::Person::name()
•3. A function name。函數名稱。例如 ,_objc_msgForward 這樣C函數。
另外,也能夠經過命令行的方式添加 Symbolic breakpoints。對C函數添加斷點:
對OC的方法添加斷點:
經常使用的這個類型的斷點有,objc_exception_throw能夠用來代替 Exception Breakpoint,還有一個-[NSObject doesNotRecognizeSelector:] 也比較經常使用,用於檢測方法調用失敗。
下斷:
breakpoint set -a 函數地址 --常規斷點
breakpoint set --func-regex 函數關鍵字 --飄雲提示:這個很是有用!我也是最近才研究發現的-雖然官方文檔一直有,可是沒重視
這樣下斷的優點:
好比再某動態庫中有 testA函數,那麼常規作法是先 image list -o -f 查看模塊基址 而後 image lookup -r -n 函數關鍵字找到偏移 而後再 br s -a 基址+偏移!
用上面這個命令下端就簡潔方便了!!!lldb會自動幫你下斷全部匹配特徵字的斷點,能夠模糊匹配哦
再來一個對動態庫函數下斷的:
breakpoint set --shlib foo.dylib --name foo
這個也很是有用,能夠進行斷點過程當中的一些自動化處理:
breakpoint command add 斷點序號
這個也很是有用,對C函數下斷很是好 / 貌似是模糊匹配
breakpoint set -F isTest / 能夠簡寫爲 b isTest
Test Failure Breakpoint
經過Xcode的UI添加方法同上。這個類型的break point 會在 test assertion 失敗的時候暫停程序的執行。
Watchpoints
Watuchpoints是一個用來監聽變量的值的變化或者內存地址的變化的工具,發生變化時會在debugger中觸發一個暫停。對於那些不知道如何準確跟蹤的狀態問題,能夠利用這個工具來解決。要設置watchpoint的話,在程序運行到stack frame包含有你想觀察的變量時,讓debugger暫停運行,這個時候變量在當前stack frame的scope內,這個時候才能對該變量設置watchpoint。
你能夠在Xcode的GUI中設置watchpoint,在xcode的 Variables View中,把你想觀察的變量保留出來,而後右鍵設置「Watch XXX」。例以下圖,觀察self的title變量,點擊 Watch 「_button1ClickCount」 便可。
命令行
或者也能夠經過命令行來設置watchpoint:watch set variable _button1ClickCount,詳細命令能夠參考:http://lldb.llvm.org/lldb-gdb.html,有好幾種命令能夠達到一樣的效果。
上面是對變量進行觀察,實際上咱們能夠對任意內存地址進行觀察,命令以下:watchpoint set expression — 0x123456,參考:http://stackoverflow.com/questions/21063995/watch-points-on-memory-address
須要注意的是,watchpoint是分類型的,包括read,write或者read_write類型,這個很是容易理解,在讀,寫或者讀寫變量或內存的時候,watchpoint是否被觸發。read,write或read_write跟着-w參數後面表示類型。另外,命令行中,watchpoint還有一些簡寫,set簡寫爲s,watch簡寫爲wa,variable簡寫爲v。
下面的示例是來自 http://www.dreamingwish.com/article/lldb-usage-a.html 網站的幾個命令:
第一個命令是監聽_abc4變量的內存地址write的變化,第二個是監聽_abc4變量read的變化,第三個是監聽_abc3變量read_write的變化。
須要注意的是,經過Xcode的GUI添加的watchpoint爲默認類型,即write類型,若是想要添加讀寫都watch的watchpoint,則只能經過命令行工具進行添加了。
使用watchpoint modify -c ‘(XXX==XX)’,則修改watchpoint以後在某個值的時候纔會監聽。
編輯選項
BreakPoint Condition
當咱們經過Xcode對breakpoint進行編輯時,能夠發現normal breakpoint和symbolic breakpoint都有一個」Condition」輸入選項,這個的做用很容易理解,只有在設置的condition表達式爲YES的狀況下這些斷點纔會起做用。
例如,下圖中的breakpoint在判斷字符串相等的時候纔會中止運行:
能夠注意到這裏使用stringWithUTF8Stirng:方法,緣由在於lldb的expression parser有一個bug,不兼容非ASCII字符,須要處理一下才行,不然會報錯「An Objective-C constant string's string initializer is not an array」,參考:http://stackoverflow.com/questions/17192505/error-in-breakpoint-condition
更加簡單一些的例子就不說了,好比 i == 99之類的簡單比較,只要表達式的結果爲BOOL類型便可。
Breakpoint Actions
能夠看到上面的每種breakpoint編輯選項中基本上都有「Add Action」選項,當breakpoint被觸發時,都首先會執行咱們設置的這些action,而後咱們才能獲得控制權,即Xcode上面纔會顯示程序中止執行的UI。這個Action經過例子比較好理解,咱們經過上面那個setObject:forKey:的異常來講明。代碼以下:
設置Breakpoint:
能夠看到上圖中,咱們一共設置了3個action。第一個action,用來打印exception的詳細信息,用法參考:http://stackoverflow.com/questions/17238673/xcode-exception-breakpoint-doesnt-print-details-of-the-exception-being-thrown。
第二個action,咱們使用shell命令「say」,讓電腦發聲,把一段文字讀出來。
第三個action,咱們使用「bt」命令來打印調用棧信息
設置完成以後,當異常發生時,咱們會聽到電腦發聲念上圖中的英文,而後在log中能夠看到以下信息,第一行是Exception的描述信息,下面是調用堆棧:
Continuing after Evaluation
看一下breakpoint的編輯彈窗,咱們能夠發現有一個 「Automatically continue after evaluation actions」 checkbox選項。當咱們勾選這個checkbox以後,debugger會執行breakpoint中添加的全部的actions,而後繼續執行程序。對於咱們來講,除了觸發一大堆command而且執行時間很長的狀況以外,程序會很快跳過這個breakpoint,因此咱們可能根本不會注意到這個breakpoint的存在。因此,這個選項的功能至關於在執行的最後一個action以後,直接輸入continue命令繼續執行。
有了這個很強大的功能,咱們能夠直接經過breakpoints來單獨對咱們的程序進行修改。在某行代碼時中止執行,使用」expression」命令來直接修改程序的某個變量設置直接修改UI,而後繼續執行。expression / call 配合這個選項的時候,會很是強大,能夠很方便實現不少很強大的功能。
例如,咱們實現一個以下的功能,把tableview的第一個cell的selectBackgroundView的背景色改成紅色:
action的內容爲「expression [[cell selectedBackgroundView] setBackgroundColor:[UIColor redColor]]」,這裏的表達式先不用關心,咱們後面LLDB章節會講到,修改以後,當咱們點擊cell的時候,cell的背景就會以下圖同樣變紅:
使用這種方式,咱們在不須要修改一行代碼的狀況下,只須要經過修改breakpoint,就能夠實現對UI的各類調試效果。
前言
你是否嘔心瀝血的嘗試去理解代碼和打印出來的變量內容?
NSLog(@"%@", whatIsInsideThisThing);
或是漏過函數調用來就簡化工程行爲?
NSNumber *n = @7; // theFunctionThatShouldReallyBeCalled();
或者短路的檢查邏輯?
if (1 || theBooleanAtStake) { ... }
亦或者是函數的僞實現?
1.int calculateTheTrickyValue {
2. return 9;
3.
4. /*
5. Figure this out later.
6. ...
7.}
8.
那是否是要不斷的重編譯,而後又開始新的輪迴?
構建軟件是複雜的並且BUG無處不藏。一個正常的修正過程是修改代碼,編譯,再次運行,而後祈禱上帝。
彷佛也不用墨守成規。你能夠用調試器啊!假設你已經知道怎麼檢視變量值,這裏有更多你須要掌握的東西。
這篇文章的目的是挑戰你的調試知識,把你可能知道得基礎知識點解析的更透徹,而後向你展現了一系列有趣的栗子。開始吧!
LLDB
LLDB是個開源調試器,REPL特性,自帶C++以及Python插件。它與Xcode綁定而且駐在控制檯界面化於窗口的下端。
調試器容許你在一個特定執行時刻暫停程序,檢視變量值,執行自定義命令,以及按你認爲合適得步驟進行程序步驟操控。(調試器主要功能戳這裏)
你之前使用調試器的部分極可能僅限於Xcode的UI上打個斷點。可是這有些技巧,你能夠作一些更酷比的事情。經過GDB與LLDB之間對比是針對全部支持的命令行的一個很好鳥瞰式的學習法,你還可能想要去安裝Chisel,一套開源的LLDB插件讓你的調試更加有趣。
與此同時,讓咱們開始如何使用調試器打印變量值的旅程吧。
基礎
這裏有一個簡單短小的程序來打印字符串。注意到斷點被添加到了第八行:
程序到此會停下來而後打開控制檯,讓咱們能與調試器進行交互。此時咱們應該輸入什麼呢?
幫助
最簡單得命令是鍵入help,你能夠獲取一個命令行列表。若是你忘記一個命令或者想知道該命令更細緻的使用方法,那麼你能夠經過調用help <command>,好比help print或help thread。若是你甚至忘記了命令自己,你能夠嘗試使用help help,可是若是你懂得足夠多,你可能已經完全不要這個命令了。
打印
打印值很容易,只要試着鍵入print命令:
LLDB實際上支持前綴命令判斷,因此你一樣可使用prin, pri或者p。可是你不能使用pr,由於LLDB不能分辨出你是不是想執行process命令。(吐槽幸虧p沒有歧義,暴露屬性)
你同時也注意到告終果帶一個$0。實際上你能夠用這個來引用變量!試着鍵入$0 + 7而後你會看到106。任何帶美圓符號是LLDB的命名空間,其存在是爲了爲你提供幫助。
表達式
若是你想修改一個值?修改,你說的算?好吧,修改!下面來一個簡單得表達式命令行:
這並不修改調試器中的值。實際上修改的是程序中的值!若是你繼續程序,它很神奇地會打印出42紅氣球(上下文)。
從如今開始注意一點,咱們爲了方便用p與e代替print和expression。
什麼是打印命令?
這裏有一個有意思的表達式來考慮下:p count = 18。若是咱們執行命令而後打印count的內容,咱們會看到它確實至關於執行了表達式count = 18。
這二者的區別是print命令不帶參數,這點與expression不一樣。考慮e -h +17。在選擇是否要進行輸入源爲+17,帶-h標誌的操做,仍是選擇是否要進行計算區分17和h操做,在這兩個選擇上面是不明確的。調試器認爲連字符致使了混淆,你可能得不到想要的結果。
幸運的是,這個解決方法十分簡單。使用--來表示表示符號的結束以及輸入源的開始。此時若是你想要用-h標誌,你可使用e -h -- +17,若是你想要進行區分,則你能夠執行e -- -h +17。不帶標誌則是十分普通,它(e --)有一個別名print。
若是你鍵入help print而且往下拖拽,你會看到:
'print' is an abbreviation for 'expression --'.
打印對象
若是咱們嘗試鍵入
p objects
那輸出會有點冗繁:
(NSString *) $7 = 0x0000000104da4040 @"red balloons"
當嘗試打印一個更加複雜的數據結構時候會狀況會更糟:
1.(lldb) p @[ @"foo", @"bar" ]
2.
3.(NSArray *) $8 = 0x00007fdb9b71b3e0 @"2 objects"
好吧,咱們想看下對象的description方法。咱們須要告訴expression命令做爲對象來打印這個結果,使用-O標誌(這不是0):
1.(lldb) e -O -- $8
2.<__NSArrayI 0x7fdb9b71b3e0>(
3.foo,
4.bar
5.)
很走運,e -O --也有別名,其別名爲po,咱們能夠只要這樣使用:
1.(lldb) po $8
2.<__NSArrayI 0x7fdb9b71b3e0>(
3.foo,
4.bar
5.)
6.(lldb) po @"lunar"
7.lunar
8.(lldb) p @"lunar"
9.(NSString *) $13 = 0x00007fdb9d0003b0 @"lunar"
打印變量
print命令有許多種不一樣的格式能夠由你來指定。它們以命令格式爲print/<fmt>或者更簡單p/<fmt>。接下來舉個栗子。
默認的格式:
1.(lldb) p 16
2.16
16進制格式:
1.(lldb) p/x 16
2.0x10
二進制格式(t表明tow):
1.(lldb) p/t 16
2.0b00000000000000000000000000010000
3.(lldb) p/t (char)16
4.0b00010000
你還可使用p/c打印字符,或者是p/s打印一個非終止類型的字符串char *。完整列表戳這裏。
變量
至此你能夠打印對象跟簡單得類型,並能夠在調試器中使用expression命令更改它們的值,讓咱們使用一些變量來減小咱們輸入工做。你能夠聲明一個變量C來表示int a = 0,一樣你能夠在LLDB中作一樣的事情。而後,變量必須以美圓符號做爲開頭:
1.(lldb) e int $a = 2
2.(lldb) p $a * 19
3.38
4.(lldb) e NSArray *$array = @[ @"Saturday", @"Sunday", @"Monday" ]
5.(lldb) p [$array count]
6.2
7.(lldb) po [[$array objectAtIndex:0] uppercaseString]
8.SATURDAY
9.(lldb) p [[$array objectAtIndex:$a] characterAtIndex:0]
10.error: no known method '-characterAtIndex:'; cast the message send to the method's return type
11.error: 1 errors parsing expression
噢。LLDB不能識別出所牽扯的變量類型。不時會遇到,咱們能夠給一點提示:
1.(lldb) p (char)[[$array objectAtIndex:$a] characterAtIndex:0]
2.'M'
3.(lldb) p/d (char)[[$array objectAtIndex:$a] characterAtIndex:0]
4.77
變量特性讓調試器更容易被使用,你這麼認爲嗎?
流程控制
你的程序會在你打上斷點的位置停下來。
此時你看到在調試工具欄有四個按鈕,經過使用它們你能夠控制程序的執行流程:
這四個按鈕從左到右依次爲:繼續,單步,跳入,跳出。
首先,繼續按鈕將會讓你得程序繼續正常執行(可能一直運行或者遇到下一個斷點)。在LLDB中,你可使用process continue來繼續執行,別名爲c。
其次,單步執行將會將單行代碼當作黑盒同樣執行。若是那行你調用了函數,那將不會進入這個函數,而是直接執行這個函數後繼續運行。LLDB中相對應的命令是thread step-over,next,或者 n。
若是你想進入一個函數調用來檢查調試該函數的執行,你可使用第三個按鈕,跳入,LLDB一樣提供了thread step-in,step, 和s。注意到next與step在當前行代碼不涉及函數調用的時候效果是同樣的。
大部分知道c,n,s。可是還有第四個按鈕,跳出。若是你不當心跳入了一個函數而你本意是想跳過它,通常反應是不斷的按n知道函數返回。跳出幫你節省時間。它會執行到return語句(知道執行了出棧操做),而後會停下來。
舉個栗子
來看下以下的代碼片斷:
代碼停在斷點,而後咱們執行以下的命令行:
1.p i
2.n
3.s
4.p i
5.finish
6.p i
7.frame info
這裏,frame info將會告訴你當前行以及源文件是啥,能夠經過鍵入help frame,help thread,以及help process獲取更多信息。那麼輸出什麼呢?先思考以前的描述想下答案!
1.(lldb) p i
2.(int) $0 = 99
3.(lldb) n
4.2014-11-22 10:49:26.445 DebuggerDance[60182:4832768] 101 is odd!
5.(lldb) s
6.(lldb) p i
7.(int) $2 = 110
8.(lldb) finish
9.2014-11-22 10:49:35.978 DebuggerDance[60182:4832768] 110 is even!
10.(lldb) p i
11.(int) $4 = 99
12.(lldb) frame info
13.frame #0: 0x000000010a53bcd4 DebuggerDance`main + 68 at main.m:17
仍在17行的緣由是finish命令會讓程序運行直到isEven()函數返回,而後立刻中止。可是請注意,17行已經執行完了。
線程返回
還有一個特別幫的功能是你在調試的時候能夠用thread return來控制程序流程。它使用可選參數,將這個參數載入寄存器,單後立刻執行返回命令,而後函數出棧。這意味着剩下函數沒有被執行。這樣由於ARC的引用計數/記錄出現問題,或者遺漏一些清除操做。但在一個函數的開頭執行這個命令是一個很是棒得函數打樁而且反悔了一個僞結果。
讓咱們來對上述相同的代碼段跑以下的指令:
1.p i
2.s
3.thread return NO
4.n
5.p even0
6.frame info
在看答案以前鄉下結果,答案以下:
1.(lldb) p i
2.(int) $0 = 99
3.(lldb) s
4.(lldb) thread return NO
5.(lldb) n
6.(lldb) p even0
7.(BOOL) $2 = NO
8.(lldb) frame info
9.frame #0: 0x00000001009a5cc4 DebuggerDance`main + 52 at main.m:17
斷點
咱們一直都使用斷點來讓程序中止,檢視當前狀態從而捕獲BUG。可是若是咱們轉變對斷點的理解,咱們能夠得到更多可能。
A breakpoint allows you to instruct a program when to stop, and then allows the running of commands.
考慮在函數剛開始處打一個斷點,使用thread return來重寫函數行爲,而後繼續。如今想象下自動實現這種處理。是否是聽起來很牛X,不是麼?
斷點管理
Xcode提供了一套工具來建立和操做斷點。咱們將會逐一過一遍而且進行描述與之對應的LLDB命令行。
在Xcode的左面板上,有一堆按鈕集合。有一個長得很像斷點。點擊打開斷點導航欄,進去以後你一眼看到你所操做的全部斷點:
這裏你能夠看到全部的斷點 - 對應LLDB中的breakpoint list或者是br li。你能夠點擊單個斷點進行打開或者關閉 - 對應LLDB中的breakpoint enable <breakpointID>和breakpoint disable <breakpointID>:
1.(lldb) br li
2.Current breakpoints:
3.1: file = '/Users/arig/Desktop/DebuggerDance/DebuggerDance/main.m', line = 16, locations = 1, resolved = 1, hit count = 1
4.
5. 1.1: where = DebuggerDance`main + 27 at main.m:16, address = 0x000000010a3f6cab, resolved, hit count = 1
6.
7.(lldb) br dis 1
8.1 breakpoints disabled.
9.(lldb) br li
10.Current breakpoints:
11.1: file = '/Users/arig/Desktop/DebuggerDance/DebuggerDance/main.m', line = 16, locations = 1 Options: disabled
12.
13. 1.1: where = DebuggerDance`main + 27 at main.m:16, address = 0x000000010a3f6cab, unresolved, hit count = 1
14.
15.(lldb) br del 1
16.1 breakpoints deleted; 0 breakpoint locations disabled.
17.(lldb) br li
18.No breakpoints currently set.
建立斷點
(UI建立略了。。。是人都會吧。。)
在調試器中打斷點,使用breakpoint set命令:
1.(lldb) breakpoint set -f main.m -l 16
2.Breakpoint 1: where = DebuggerDance`main + 27 at main.m:16, address = 0x
縮寫能夠用br。b是另一個徹底不一樣的命令,是_regexp-break的別名,可是它足夠健壯來進行建立上述命令同樣效果的斷點:
1.(lldb) b main.m:17
2.Breakpoint 2: where = DebuggerDance`main + 52 at main.m:17, address = 0x
你也能夠防止一個斷點在一個符號(C語言函數),而不用指定行數:
1.(lldb) b isEven
2.Breakpoint 3: where = DebuggerDance`isEven + 16 at main.m:4, address = 0x000000010a3f6d00
3.(lldb) br s -F isEven
4.Breakpoint 4: where = DebuggerDance`isEven + 16 at main.m:4, address
5.
如今這些斷點會中止正在將要執行的函數,一樣適用與OC方法:
1.(lldb) breakpoint set -F "-[NSArray objectAtIndex:]"
2.Breakpoint 5: where = CoreFoundation`-[NSArray objectAtIndex:], address = 0x000000010ac7a950
3.(lldb) b -[NSArray objectAtIndex:]
4.Breakpoint 6: where = CoreFoundation`-[NSArray objectAtIndex:], address = 0x000000010ac7a950
5.(lldb) breakpoint set -F "+[NSSet setWithObject:]"
6.Breakpoint 7: where = CoreFoundation`+[NSSet setWithObject:], address = 0x000000010abd3820
7.(lldb) b +[NSSet setWithObject:]
8.Breakpoint 8: where = CoreFoundation`+[NSSet setWithObject:], address = 0x000000010abd3820
若是你想經過UI來建立象徵性斷點,你能夠點擊左下端斷點導航欄的+號:
而後選擇第三個選項:
此時出現彈出框讓你輸入好比-[NSArray objectAtIndex:]的符號,而後程序在這個函數調用的時候即可以中止下來,無論是你的代碼或者仍是大蘋果的代碼!
若是咱們看下其餘選項,咱們能夠發現一些有意思的選項,一樣提供了各類條件觸發的鍛鍊只要你點擊了Xcode的UI而且選擇了「Edit Breakpoint」選項:
如上圖,斷點只有在i爲99的時候纔會中止程序。你能夠一樣設置「 ignore」選項來告訴斷點在前n次調用的時候不用中止程序(條件爲真)。
這裏還有一個「Add Action」按鈕。。。
斷點動做
可能上面斷點的栗子中,你想知道每次斷點時候i值是多少。咱們可使用動做p i,而後當斷點觸發的時候咱們進入調試器,它會預先執行這個命令在將控制流程交給你以前:
你也能夠加多重動做,能夠是調試器指令,shell指令或者更健壯的打印信息:
如上你能夠看到打印出i值,還有強調語句,打印出自定義的表達式。
下面是上述功能用純LLDB命令代替Xcode的UI:
1.(lldb) breakpoint set -F isEven
2.Breakpoint 1: where = DebuggerDance`isEven + 16 at main.m:4, address = 0x00000001083b5d00
3.(lldb) breakpoint modify -c 'i == 99' 1
4.(lldb) breakpoint command add 1
5.Enter your debugger command(s). Type 'DONE' to end.
6.> p i
7.> DONE
8.(lldb) br li 1
9.1: name = 'isEven', locations = 1, resolved = 1, hit count = 0
10. Breakpoint commands:
11. p i
12.
13.Condition: i == 99
14.
15. 1.1: where = DebuggerDance`isEven + 16 at main.m:4, address = 0x00000001083b5d00, resolved, hit count = 0
自動化,咱們來了!
計算值以後繼續
若是視線停留在斷點彈出框的底端,你會額外看到一個選項:「Automatically continue after evaluation actions(計算動做後自動執行)。」它只是一個勾選框,可是它卻有強大的能力。若是你勾選上了,調試器將會蘋果你全部的命令而後繼續執行程序。表面上看上跟斷點沒有打住同樣(除非你斷點太多了,拖慢了程序進度)。
這個勾選框功能與最後一個動做斷點繼續執行效果同樣,可是有勾選框更加容易點。對應調試器的指令以下:
1.(lldb) breakpoint set -F isEven
2.Breakpoint 1: where = DebuggerDance`isEven + 16 at main.m:4, address = 0x00000001083b5d00
3.(lldb) breakpoint command add 1
4.Enter your debugger command(s). Type 'DONE' to end.
5.> continue
6.> DONE
7.(lldb) br li 1
8.1: name = 'isEven', locations = 1, resolved = 1, hit count = 0
9. Breakpoint commands:
10. continue
11.
12. 1.1: where = DebuggerDance`isEven + 16 at main.m:4, address = 0x00000001083b5d00, resolved, hit count = 0
計算後自動繼續運行讓你能夠單獨經過使用斷點來修改你的程序!你能夠中止在單行,運行一個expression命令來改變變量,而後繼續。
舉個栗子
考慮下簡陋殘酷的「打印式調試」技術。不是用:
NSLog(@"%@", whatIsInsideThisThing);
而是用斷點處設置打印變量值替代吊打印日誌打印語句而後繼續。
不是用:
1. return 9;
2. /*
3. ...
4.}
5.
而是用斷點處調用thread return 9而後繼續執行。
帶動做的象徵斷點確實真的很強大。你也能夠添加這些斷點到你朋友的Xcode工程而且讓動做將全部信息細緻展現出來。接下來看看要耗時多久來進行計算以及會發生什麼吧。
調試器完整操做
在起舞以前還有一點須要咱們注意。你真的能夠在調試器中執行任何的C/OC/C++/Swift命令。比較弱的是咱們不能建立一個新的函數。。。這意味着沒有新的類,塊,函數,帶虛方法的C++類等等。除了這個,調試器什麼都能知足!
咱們能夠分配一些字節:
1.(lldb) e char *$str = (char *)malloc(8)
2.(lldb) e (void)strcpy($str, "munkeys")
3.(lldb) e $str[1] = 'o'
4.(char) $0 = 'o'
5.(lldb) p $str
6.(char *) $str = 0x00007fd04a900040 "monkeys"
或者咱們能夠檢查一些內存(使用x命令)來看咱們新數組的4個字節:
1.(lldb) x/4c $str
2.0x7fd04a900040: monk
咱們還能夠後三個字節:
1.(lldb) x/1w `$str + 3`
2.0x7fd04a900043: keys
當你所要的活結束的時候別忘記了釋放內存避免形成內存泄露:
(lldb) e (void)free($str)
跳舞吧,騷年!
如今咱們已經清楚基礎步驟,是時候來整一些比較瘋狂的東西了。我過去曾寫過一篇博客(你們本身收藏。。。)發表在looking at the internals of NSArray。當時用了大量的NSLog語句,後來全用調試器搞定了。它是一個很好的調試器使用練習。
暢通無阻(無斷點模式)
當你的應用在跑的時候,Xcode中的調試工具欄展現一箇中止按鈕而非繼續狀態的按鈕:
選中這個按鈕的時候,應用遇到斷點將會中止(就像輸入了process interrupt)。這時候將會讓你進入調試器。
這裏有一個有趣的地方。若是你運行一個iOS應用,你能夠嘗試這個(全局變量可提供)
1.(lldb) po [[[UIApplication sharedApplication] keyWindow] recursiveDescription]
2.<UIWindow: 0x7f82b1fa8140; frame = (0 0; 320 568); gestureRecognizers = <NSArray: 0x7f82b1fa92d0>; layer = <UIWindowLayer: 0x7f82b1fa8400>>
3. | <UIView: 0x7f82b1d01fd0; frame = (0 0; 320 568); autoresize = W+H; layer = <CALayer: 0x7f82b1e2e0a0>>
能夠看到整個層級!Chisel(上文說起)用pviews來實現。
更新UI
而後,經過上述的輸出,咱們能夠看到隱藏的視圖:
(lldb) e id $myView = (id)0x7f82b1d01fd0
而後在調試器中修改它的背景色:
(lldb) e (void)[$myView setBackgroundColor:[UIColor blueColor]]
在你下次繼續運行這個程序的時候你纔會看到變化。這由於這個變化須要傳遞給渲染服務而後視圖展現纔會被更新。
渲染服務其實是另外一個進程(稱做後臺),而且甚至咱們調試進程被中止了,這個後臺也不會被中止!
這意味着不經過繼續,你能夠執行:
(lldb) e (void)[CATransaction flush]
在模擬器中或者設備中的UI會進行刷新而你還在調試器中!Chisel提供了一個別名函數叫作caflush,而且它被用來實現其它捷徑像hide <view>,show <view>還有其餘許多許多。全部的Chisel命令都有對應的文檔,因此就在安裝它以後鍵入help來爲所欲爲的獲取更多的信息吧。
壓入視圖控制器
想象一個簡單的應用有一個UINavigationController做爲根視圖控制器。你能夠在調試器中至關簡易的執行以下操做:
(lldb) e id $nvc = [[[UIApplication sharedApplication] keyWindow] rootViewController]
而後壓入子視圖控制器:
1.(lldb) e id $vc = [UIViewController new]
2.(lldb) e (void)[[$vc view] setBackgroundColor:[UIColor yellowColor]]
3.(lldb) e (void)[$vc setTitle:@"Yay!"]
4.(lldb) e (void)[$nvc pushViewContoller:$vc animated:YES]
最後執行:
(lldb) caflush // e (void)[CATransaction flush]
你會看到立刻壓入了一個視圖控制器。
找到按鈕的目標
想象下你調試器中有一個變量,$myButton,你想要去建立它,並從UI中抓取它,或者簡單地只是你想在斷點停下來的時候將它做爲個局部變量。你可能想知道當你點擊它的時候是誰接收了這個動做。這裏展現達到這點有多麼的簡單:
1.(lldb) po [$myButton allTargets]
2.{(
3. <MagicEventListener: 0x7fb58bd2e240>
4.)}
5.(lldb) po [$myButton actionsForTarget:(id)0x7fb58bd2e240 forControlEvent:0]
6.<__NSArrayM 0x7fb58bd2aa40>(
7._handleTap:
8.)
如今你可能想在事件發生的時候添加一個斷點。只要在LLDB或者Xcode設置象徵性斷點在-[MyEventListener _handleTap:]。and you are all set to Go!
觀察實例變量值變化
想象一個假設的場景你有一個UIView且它的_layer實例變量被重寫了。由於這裏可能不涉及方法,咱們不能使用象徵性斷點。取而代之的是咱們想觀察一個內存地址何時被寫入了。
首先咱們須要找到_layer對象在那裏:
1.(ptrdiff_t) $0 = 8
2.
如今咱們知道($myView + 8)這個內存地址被寫入了:
1.(lldb) watchpoint set expression -- (int *)$myView + 8
2.Watchpoint created: Watchpoint 3: addr = 0x7fa554231340 size = 8 state = enabled type = w
3. new value: 0x0000000000000000
對應Chisel裏面的wivar $myView _layer。
在非重寫方法上的象徵性斷點
想象你想知道何時-[MyViewController viewDidAppear:]被調用了。若是MyViewController實際上沒有實現這個方法,可是父類實現了呢?咱們能夠設置一個斷點來看看具體狀況:
1.(lldb) b -[MyViewController viewDidAppear:]
2.Breakpoint 1: no locations (pending).
3.WARNING: Unable to resolve breakpoint to any actual locations.
由於LLDB根據符號搜索,它找不到該方法,因此你的斷點將不會被觸發。你所須要作的是設置一個條件,[self isKindofClass:[MyViewController class]],而後見這個斷點設在UIViewController上。通常來講,設置一個這樣的條件是有效的,可是,這裏無效是由於咱們沒有父類該方法的實現。
viewDidAppear:是大蘋果寫的,因此沒有對應的符號;在方法內部也沒有self。若是你想要使用在象徵性斷點內使用self,你須要知道它在那裏(可能在寄存器也可能在棧上;在x86你可能在$esp+4找到它)。這是個經過的歷程,由於你知道已經知道有四種體系架構了。吐槽略。。幸運的是,Chisel已經完成了這些封裝,你能夠調用bmessage:
1.(lldb) bmessage -[MyViewController viewDidAppear:]
2.Setting a breakpoint at -[UIViewController viewDidAppear:] with condition (void*)object_getClass((id)$rdi) == 0x000000010e2f4d28
3.Breakpoint 1: where = UIKit`-[UIViewController viewDidAppear:], address = 0x000000010e11533c
LLDB與Python
LLDB有完整的內置Python支持。若是你在LLDB上輸入腳本,它會打開一個Python REPL。若是你在LLDB中鍵入script,它會打開一個Python REPL。你能夠傳入一行Python語句到script命令來不進入REPL的狀況下進行執行腳本:
1.(lldb) script import os
2.(lldb) script os.system("open http://www.objc.io/")
這容許你建立各類各樣的酷比命令。將這個丟入文件,~/myCommands.py:
1. debugger.HandleCommand("e (void)[CATransaction flush]")
2.
而後在LLDB中運行以下:
command script import ~/myCommands.py
或者,將這行代碼放置於/.lldbinit讓LLDB每次運行的時候都執行一次。Chisel不過就是一堆Python腳本用來組合字符串,而後告訴LLDB來執行這些字符串。聽起來很簡單吧!呃?
參考連接:
1.當異常出現時
2.日誌記錄CocoaLumberjack
3.在Xcode中調試程序
4.南峯子的技術博客
5.與調試器共舞 - LLDB 的華爾茲
6.官方調試技巧文檔
7.inspecting-obj-c-parameters-in-gdb