技巧一:運行時修改變量的值
你以前怎么驗證是不是某個變量的值導致整段程序不能正常工作?修改代碼中的變量的值,然后cmd+r重新啟動app?現在你不需要這么做了,只需要設置一個斷點,當程序在這進入調試模式后,使用expr命令即可在運行時修改變量的值。
假如有一個loginWithUsername:方法,需要兩個參數:username,password。
首先設置好斷點,如下圖所示:
運行app,進入斷點模式后,在(lldb)后輸入
1
2
|
expr username = @
"username"
expr password = @
"badpassword"
|
控制台會返回以下信息
1
2
|
(NSString *) $0 = 0x3d3504c4 @
"username"
(NSString *) $1 = 0x1d18ef60 @
"badpassword"
|
現在跳出斷點,執行斷點之后的兩條輸出語句,控制台會有以下輸出
1
2
|
(0x1c59aae0) A line
for
the breakpoint
(0x1c59aae0) Username and Password after: username:badpassword
|
看到看吧,我們在運行時修改了變量的值,事情還可以變的更簡單一些,我們可以編輯斷點,讓它自動填充需要的修改的變量的值,並且可以選擇在此斷點處不進入斷點模式,僅僅修改指定變量的值,然后自動執行后續代碼。
右擊斷點選擇“Edit Breakpoint...”(或者按住cmd+option,單擊斷點),然后如下圖所示設置斷點
注意選中了最后一行(“Automatically continue after evaluating”)的選擇框,這就保證運行到這個斷點的時,填充變量的值,然后繼續運行,並不在此處斷點進入調試模式。
運行app,你會得到和上述手動設置變量的值一樣的輸出。
接下來單擊斷點,使其處於禁用狀態,現在箭頭的顏色應該是淺藍色的,重新運行app,你會發現username和password的值沒有在運行時被改變了。
技巧二:設置斷點觸發條件
斷點的另外一個重要作用,是可以設置觸發斷點生效的條件,這樣我們就可以在運行時針對特定的數據進行分析,觀察app是否運行在正確的軌道上。如下圖:
上述截圖可以看到如下語句
1
|
(
BOOL
)[(NSString*)[item valueForKey:@
"ID"
] isEqualToString:@
"93306"
]
|
通過這行語句,我們告訴編譯器:當item中ID等於93306時,此斷點生效,進入斷點調試模式。
技巧三:格式化輸出數據
如果你厭倦了代碼里無窮無盡的NSLog,幸運的是我們可以在編輯斷點使其輸出格式化字符串就像平常編碼時一樣。不過有一點需要注意,平常編碼時可能會使用NSString‘s stringWithFormat:輸出格式化字符串,不過這個方法貌似在斷點中木有效果,你需要使用alloc/init形式的方法,如下:
1
|
po [[NSString alloc] initWithFormat:@
"Item index is: %d"
, index]
|
運行app,就能在控制台看到想要的輸出啦!
簡單!強大!這就是LLDB給你的選擇,從此代碼里可以不用再有NSLog滿天飛的情況了,代碼變得更干凈了,心情變得更愉悅了!
LLDB還有很多強大的地方,本教程只不過揭開了它的面紗,即便如此,仍讓我們沉醉不已。
你可能從未使用過LLDB,那讓我們先來熱熱身。 在調試器中最常用到的命令是p
(用於輸出基本類型)或者po
(用於輸出 Objective-C 對象)。如下,你可以通過輸入po 和 view 來輸出 view 的信息:
po [self view]
隨后調試器會輸出這個 object 的 description。在這個例子中可能是這樣的信息:
(UIView *) $1 = 0x0824c800 <UITableView: 0x824c800; frame = (0 20; 768 1004); clipsToBounds = YES; autoresize = W+H; gestureRecognizers = <NSArray: 0x74c3010>; layer = <CALayer: 0x74c2710>; contentOffset: {0, 0}>
什么?在什么地方可以輸入這個命令?OK,首先,我們需要先設置一個斷點。如下圖所示,我在viewDidLoad:
中設置了一個了一個斷點:
接下來運行程序,然后程序會停留在斷點處,從下圖你可以看到在什么地方輸入LLDB命令:
你可能需要的是 view 下 subview 的數量。由於 subview 的數量是一個 int 類型的值,所以我們使用命令p
:
p (int)[[[self view] subviews] count]
最后你看到的輸出會是:
(int) $2 = 2
是不是很簡單?
細心的朋友可能會發現輸出的信息中帶有$1
、$2
的字樣。實際上,我們每次查詢的結果會保存在一些持續變量中($[0-9]+),這樣你可以在后面的查詢中直接使用這些值。比如現在我接下來要重新取回$1
的值:
(lldb) po $1
(UIView *) $1 = 0x0824c800 <UITableView: 0x824c800; frame = (0 20; 768 1004); clipsToBounds = YES; autoresize = W+H; gestureRecognizers = <NSArray: 0x74c3010>; layer = <CALayer: 0x74c2710>; contentOffset: {0, 0}>
可以看到,我們依然可以取到之前[self view]的值。
LLDB命令還可以用在斷點上,詳細的使用可以參見這個文章
常用命令
下面補充說明其它一些常用的命令:
- expr
可以在調試時動態執行指定表達式,並將結果打印出來。常用於在調試過程中修改變量的值。
如圖設置斷點,然后運行程序。程序中斷后輸入下面的命令:
expr a=2
你會看到如下的輸出:
(int) $0 = 2
繼續運行程序,程序輸出的信息是:
實際值:2
很明顯可以看出,變量a的值被改變。 除此之外,還可以使用這個命令新聲明一個變量對象,如:
expr int $b=2
p $b
下面的命令用於輸出新聲明對象的值。(注意,對象名前要加$)
- call
call即是調用的意思。其實上述的po和p也有調用的功能。因此一般只在不需要顯示輸出,或是方法無返回值時使用call。 和上面的命令一樣,我們依然在viewDidLoad:
里面設置斷點,然后在程序中斷的時候輸入下面的命令:
call [self.view setBackgroundColor:[UIColor redColor]]
繼續運行程序,看看view的背景顏色是不是變成紅色的了!在調試的時候靈活運用call命令可以起到事半功倍的作用。
- bt
打印調用堆棧,加all可打印所有thread的堆棧。不詳細舉例說明,感興趣的朋友可以自己試試。
- image
image 命令可用於尋址,有多個組合命令。比較實用的用法是用於尋找棧地址對應的代碼位置。 下面我寫了一段代碼
NSArray *arr=[[NSArray alloc] initWithObjects:@"1",@"2", nil];
NSLog(@"%@",arr[2]);
這段代碼有明顯的錯誤,程序運行這段代碼后會拋出下面的異常:
- *** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayI objectAtIndex:]: index 2 beyond bounds [0 .. 1]'
- *** First throw call stack:
- (
- 0 CoreFoundation 0x0000000101951495 __exceptionPreprocess + 165
- 1 libobjc.A.dylib 0x00000001016b099e objc_exception_throw + 43
- 2 CoreFoundation 0x0000000101909e3f -[__NSArrayI objectAtIndex:] + 175
- 3 ControlStyleDemo 0x0000000100004af8 -[RootViewController viewDidLoad] + 312
- 4 UIKit 0x000000010035359e -[UIViewController loadViewIfRequired] + 562
- 5 UIKit 0x0000000100353777 -[UIViewController view] + 29
- 6 UIKit 0x000000010029396b -[UIWindow addRootViewControllerViewIfPossible] + 58
- 7 UIKit 0x0000000100293c70 -[UIWindow _setHidden:forced:] + 282
- 8 UIKit 0x000000010029cffa -[UIWindow makeKeyAndVisible] + 51
- 9 ControlStyleDemo 0x00000001000045e0 -[AppDelegate application:didFinishLaunchingWithOptions:] + 672
- 10 UIKit 0x00000001002583d9 -[UIApplication _handleDelegateCallbacksWithOptions:isSuspended:restoreState:] + 264
- 11 UIKit 0x0000000100258be1 -[UIApplication _callInitializationDelegatesForURL:payload:suspended:] + 1605
- 12 UIKit 0x000000010025ca0c -[UIApplication _runWithURL:payload:launchOrientation:statusBarStyle:statusBarHidden:] + 660
- 13 UIKit 0x000000010026dd4c -[UIApplication handleEvent:withNewEvent:] + 3189
- 14 UIKit 0x000000010026e216 -[UIApplication sendEvent:] + 79
- 15 UIKit 0x000000010025e086 _UIApplicationHandleEvent + 578
- 16 GraphicsServices 0x0000000103aca71a _PurpleEventCallback + 762
- 17 GraphicsServices 0x0000000103aca1e1 PurpleEventCallback + 35
- 18 CoreFoundation 0x00000001018d3679 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 41
- 19 CoreFoundation 0x00000001018d344e __CFRunLoopDoSource1 + 478
- 20 CoreFoundation 0x00000001018fc903 __CFRunLoopRun + 1939
- 21 CoreFoundation 0x00000001018fbd83 CFRunLoopRunSpecific + 467
- 22 UIKit 0x000000010025c2e1 -[UIApplication _run] + 609
- 23 UIKit 0x000000010025de33 UIApplicationMain + 1010
- 24 ControlStyleDemo 0x0000000100006b73 main + 115
- 25 libdyld.dylib 0x0000000101fe95fd start + 1
- 26 ??? 0x0000000000000001 0x0 + 1
- )
- libc++abi.dylib: terminating with uncaught exception of type NSException
現在,我們懷疑出錯的地址是0x0000000100004af8(可以根據執行文件名判斷,或者最小的棧地址)。為了進一步精確定位,我們可以輸入以下的命令:
image lookup --address 0x0000000100004af8
命令執行后返回:
Address: ControlStyleDemo[0x0000000100004af8] (ControlStyleDemo.__TEXT.__text + 13288)
Summary: ControlStyleDemo`-[RootViewController viewDidLoad] + 312 at RootViewController.m:53
我們可以看到,出錯的位置是RootViewController.m
的第53行。
更多的命令可以參見這個網址。
另外,facebook開源了他們擴展的LLDB命令庫,有興趣的朋友也可以安裝看看。
簡稱和別名
很多時候,LLDB完整的命令是很長的。比如前面所說的image lookup --address
這個組合命令。為了方便日常的使用,提高效率,LLDB命令也提供通過簡稱的方式調用命令。還是這個命令,我們用簡稱就可以寫為im loo -a
,是不是簡單多了。
如果你是從gdb時代就開始使用調試器的,你會發現,有些命令如p
、call
等命令和gdb下是一致的。其實這些命令是LLDB一些命令的別名,比如p
是frame variable
的別名,p view
實際上是frame variable view
。除了系統自建的LLDB別名,你也可以自定義別名。比如下面這個命令
command alias ioa image lookup --address %1
是將我前面所介紹過的一個命令image lookup --address
添加了一個ioa
的別名。然后執行下面的命令:
(lldb) ioa 0x0000000100004af8
Address: ControlStyleDemo[0x0000000100004af8] (ControlStyleDemo.__TEXT.__text + 13288)
Summary: ControlStyleDemo`-[RootViewController viewDidLoad] + 312 at RootViewController.m:53
可以看到,我們得到了我們想要的結果,而命令卻大大縮短。
這里我就不再詳細展開,有興趣的朋友可以查看這個網址。
常見問題
上面我們簡單的學習了如何使用LLDB命令。但有時我們在使用這些LLDB命令的時候,依然可能會遇到一些問題。比如下面這個命令。
(lldb) p NSLog(@"%@",[self.view viewWithTag:1001])
error: 'NSLog' has unknown return type; cast the call to its declared return type
error: 1 errors parsing expression
如果在使用LLDB命令中發現有 unknown type 的類似錯誤(多見於id類型,比如NSArray中某個值),那我們就必須顯式聲明類型。比如上面這個命令,我們得這么修改。
p (void)NSLog(@"%@",[self.view viewWithTag:1001])
這樣就能得到正確的結果了。
執行類命令集
LLDB | GDB |
Launch a process no arguments. | |
(lldb) process launch (lldb) run (lldb) r |
(gdb) run (gdb) r |
Launch a process with arguments <args> . |
|
(lldb) process launch — <args> (lldb) r <args> |
(gdb) run <args> (gdb) r <args> |
Launch a process for with arguments a.out 1 2 3 without having to supply the args every time. |
|
% lldb — a.out 1 2 3 (lldb) run … (lldb) run … |
% gdb –args a.out 1 2 3 (gdb) run … (gdb) run … |
Launch a process with arguments in new terminal window (Mac OS X only). | |
(lldb) process launch –tty — <args> (lldb) pro la -t — <args> |
|
Launch a process with arguments in existing terminal /dev/ttys006 (Mac OS X only). | |
(lldb) process launch –tty=/dev/ttys006 — <args> (lldb) pro la -t/dev/ttys006 — <args> |
|
Attach to a process with process ID 123. | |
(lldb) process attach –pid 123 (lldb) attach -p 123 |
(gdb) attach 123 |
Attach to a process named “a.out”. | |
(lldb) process attach –name a.out (lldb) pro at -n a.out |
(gdb) attach a.out |
Wait for a process named “a.out” to launch and attach. | |
(lldb) process attach –name a.out –waitfor (lldb) pro at -n a.out -w |
(gdb) attach -waitfor a.out |
Do a source level single step in the currently selected thread. | |
(lldb) thread step-in (lldb) step (lldb) s |
(gdb) step (gdb) s |
Do a source level single step over in the currently selected thread. | |
(lldb) thread step-over (lldb) next (lldb) n |
(gdb) next (gdb) n |
Do an instruction level single step in the currently selected thread. | |
(lldb) thread step-inst (lldb) si |
(gdb) stepi (gdb) si |
Do an instruction level single step over in the currently selected thread. | |
(lldb) thread step-inst-over (lldb) ni |
(gdb) nexti (gdb) ni |
Step out of the currently selected frame. | |
(lldb) thread step-out (lldb) finish |
(gdb) finish |
Backtrace and disassemble every time you stop. | |
(lldb) target stop-hook add Enter your stop hook command(s). Type ‘DONE’ to end. > bt > disassemble –pc > DONE Stop hook #1 added. |
斷點類命令集
LLDB | GDB |
Set a breakpoint at all functions named main. | |
(lldb) breakpoint set –name main (lldb) br s -n main (lldb) b main |
(gdb) break main |
Set a breakpoint in file test.c at line 12. | |
(lldb) breakpoint set –file test.c –line 12 (lldb) br s -f test.c -l 12 (lldb) b test.c:12 |
(gdb) break test.c:12 |
Set a breakpoint at all C++ methods whose basename is main. | |
(lldb) breakpoint set –method main (lldb) br s -M main |
(gdb) break main (Hope that there are no C funtions named main). |
Set a breakpoint at and object C function: -[NSString stringWithFormat:]. | |
(lldb) breakpoint set –name “-[NSString stringWithFormat:]” (lldb) b -[NSString stringWithFormat:] |
(gdb) break -[NSString stringWithFormat:] |
Set a breakpoint at all Objective C methods whose selector is count. | |
(lldb) breakpoint set –selector count (lldb) br s -S count |
(gdb) break count (Hope that there are no C or C++ funtions namedcount). |
List all breakpoints. | |
(lldb) breakpoint list (lldb) br l |
(gdb) info break |
Delete a breakpoint. | |
(lldb) breakpoint delete 1 (lldb) br del 1 |
(gdb) delete 1 |
監視點(WATCHPOINT)命令集
LLDB | GDB |
Set a watchpoint on a variable when it is written to. | |
(lldb) watchpoint set variable -w write global_var (lldb) watch set var -w write global_var |
(gdb) watch global_var |
Set a watchpoint on a memory location when it is written into. The size of the region to watch for defaults to the pointer size if no ‘-x byte_size’ is specified. This command takes raw input, evaluated as an expression returning an unsigned integer pointing to the start of the region, after the ‘–’ option terminator. | |
(lldb) watchpoint set expression -w write — my_ptr (lldb) watch set exp -w write — my_ptr |
(gdb) watch -location g_char_ptr |
Set a condition on a watchpoint. | |
(lldb) watch set var -w write global (lldb) watchpoint modify -c ‘(global==5)’ (lldb) c … (lldb) bt * thread #1: tid = 0x1c03, 0x0000000100000ef5 a.out modify + 21 at main.cpp:16, stop reason = watchpoint 1 modify + 21 at main.cpp:16frame #1: 0x0000000100000eac a.out main + 108 at main.cpp:25 start + 1(lldb) frame var global (int32_t) global = 5 |
|
List all watchpoints. | |
(lldb) watchpoint list (lldb) watch l |
(gdb) info break |
Delete a watchpoint. | |
(lldb) watchpoint delete 1 (lldb) watch del 1 |
(gdb) delete 1 |
檢查變量
LLDB | GDB |
Show the arguments and local variables for the current frame. | |
(lldb) frame variable | (gdb) info args and (gdb) info locals |
Show the local variables for the current frame. | |
(lldb) frame variable –no-args (lldb) fr v -a |
(gdb) info locals |
Show the contents of local variable “bar”. | |
(lldb) frame variable bar (lldb) fr v bar (lldb) p bar |
(gdb) p bar |
Show the contents of local variable “bar” formatted as hex. | |
(lldb) frame variable –format x bar (lldb) fr v -f x bar |
(gdb) p/x bar |
Show the contents of global variable “baz”. | |
(lldb) target variable baz (lldb) ta v baz |
(gdb) p baz |
Show the global/static variables defined in the current source file. | |
(lldb) target variable (lldb) ta v |
n/a |
Display a the variable “argc” and “argv” every time you stop. | |
(lldb) target stop-hook add –one-liner “frame variable argc argv” (lldb) ta st a -o “fr v argc argv” (lldb) display argc (lldb) display argv |
(gdb) display argc (gdb) display argv |
Display a the variable “argc” and “argv” only when you stop in the function named main. | |
(lldb) target stop-hook add –name main –one-liner “frame variable argc argv” (lldb) ta st a -n main -o “fr v argc argv” |
|
Display the variable “*this” only when you stop in c class named MyClass. | |
(lldb) target stop-hook add –classname MyClass –one-liner “frame variable *this” (lldb) ta st a -c MyClass -o “fr v *this” |
檢查線程狀態
LLDB | GDB |
Show the stack backtrace for the current thread. | |
(lldb) thread backtrace (lldb) bt |
(gdb) bt |
Show the stack backtraces for all threads. | |
(lldb) thread backtrace all (lldb) bt all |
(gdb) thread apply all bt |
Select a different stack frame by index for the current thread. | |
(lldb) frame select 12 | (gdb) frame 12 |
List information about the currently selected frame in the current thread. | |
(lldb) frame info | |
Select the stack frame that called the current stack frame. | |
(lldb) up (lldb) frame select –relative=1 |
(gdb) up |
Select the stack frame that is called by the current stack frame. | |
(lldb) down (lldb) frame select –relative=-1 (lldb) fr s -r-1 |
(gdb) down |
Select a different stack frame using a relative offset. | |
(lldb) frame select –relative 2 (lldb) fr s -r2
(lldb) frame select –relative -3 |
(gdb) up 2 (gdb) down 3 |
Show the general purpose registers for the current thread. | |
(lldb) register read | (gdb) info registers |
Show the general purpose registers for the current thread formatted as signed decimal. LLDB tries to use the same format characters as printf(3) when possible. Type “help format” to see the full list of format specifiers. | |
(lldb) register read –format i (lldb) re r -f i
LLDB now supports the GDB shorthand format syntax but there can’t be space after the command: |
|
Show all registers in all register sets for the current thread. | |
(lldb) register read –all (lldb) re r -a |
(gdb) info all-registers |
Show the values for the registers named “rax”, “rsp” and “rbp” in the current thread. | |
(lldb) register read rax rsp rbp | (gdb) info all-registers rax rsp rbp |
Show the values for the register named “rax” in the current thread formatted as binary. | |
(lldb) register read –format binary rax (lldb) re r -f b rax
LLDB now supports the GDB shorthand format syntax but there can’t be space after the command: |
(gdb) p/t $rax |
Read memory from address 0xbffff3c0 and show 4 hex uint32_t values. | |
(lldb) memory read –size 4 –format x –count 4 0xbffff3c0 (lldb) me r -s4 -fx -c4 0xbffff3c0 (lldb) x -s4 -fx -c4 0xbffff3c0
LLDB now supports the GDB shorthand format syntax but there can’t be space after the command: |
(gdb) x/4xw 0xbffff3c0 |
Read memory starting at the expression “argv[0]“. | |
(lldb) memory read argv[0] NOTE: any command can inline a scalar expression result (as long as the target is stopped) using backticks around any expression: (lldb) memory read –size sizeof(int) argv[0] |
(gdb) x argv[0] |
Read 512 bytes of memory from address 0xbffff3c0 and save results to a local file as text. | |
(lldb) memory read –outfile /tmp/mem.txt –count 512 0xbffff3c0 (lldb) me r -o/tmp/mem.txt -c512 0xbffff3c0 (lldb) x/512bx -o/tmp/mem.txt 0xbffff3c0 |
(gdb) set logging on (gdb) set logging file /tmp/mem.txt (gdb) x/512bx 0xbffff3c0 (gdb) set logging off |
Save binary memory data starting at 0×1000 and ending at 0×2000 to a file. | |
(lldb) memory read –outfile /tmp/mem.bin –binary 0×1000 0×1200 (lldb) me r -o /tmp/mem.bin -b 0×1000 0×1200 |
|
(gdb) dump memory /tmp/mem.bin 0×1000 0×2000 | |
Disassemble the current function for the current frame. | |
(lldb) disassemble –frame (lldb) di -f |
(gdb) disassemble |
Disassemble any functions named main. | |
(lldb) disassemble –name main (lldb) di -n main |
(gdb) disassemble main |
Disassemble an address range. | |
(lldb) disassemble –start-address 0x1eb8 –end-address 0x1ec3 (lldb) di -s 0x1eb8 -e 0x1ec3 |
(gdb) disassemble 0x1eb8 0x1ec3 |
Disassemble 20 instructions from a given address. | |
(lldb) disassemble –start-address 0x1eb8 –count 20 (lldb) di -s 0x1eb8 -c 20 |
(gdb) x/20i 0x1eb8 |
Show mixed source and disassembly for the current function for the current frame. | |
(lldb) disassemble –frame –mixed (lldb) di -f -m |
n/a |
Disassemble the current function for the current frame and show the opcode bytes. | |
(lldb) disassemble –frame –bytes (lldb) di -f -b |
n/a |
Disassemble the current source line for the current frame. | |
(lldb) disassemble –line (lldb) di -l |
n/a |
可執行文件和共享庫查詢命令
LLDB | GDB |
List the main executable and all dependent shared libraries. | |
(lldb) image list | (gdb) info shared |
Lookup information for a raw address in the executable or any shared libraries. | |
(lldb) image lookup –address 0x1ec4 (lldb) im loo -a 0x1ec4 |
(gdb) info symbol 0x1ec4 |
Lookup information for an address in a.out only. | |
(lldb) image lookup –address 0x1ec4 a.out (lldb) im loo -a 0x1ec4 a.out |
|
Lookup information for for a type Point by name. |
|
(lldb) image lookup –type Point (lldb) im loo -t Point |
(lldb) ptype Point |
Dump all sections from the main executable and any shared libraries. | |
(lldb) image dump sections | (gdb) maintenance info sections |
Dump all sections in the a.out module. | |
(lldb) image dump sections a.out | |
Dump all symbols from the main executable and any shared libraries. | |
(lldb) image dump symtab | |
Dump all symbols in a.out and liba.so. | |
(lldb) image dump symtab a.out liba.so |
雜項
LLDB | GDB |
Echo text to the screen. | |
(lldb) script print “Here is some text” | (gdb) echo Here is some text\n |