[轉] Xcode 高級調試技巧


在蘋果的官方文檔中列出了我們在調試中能用到的一些命令,我們在這重點講一些常用的命令
調試本地文件方法(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.視圖層次
打印視圖層次 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.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 中。
三、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
 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM