隨著Xcode 5的發布,LLDB調試器已經取代了GDB,成為了Xcode工程中默認的調試器。它與LLVM編譯器一起,帶給我們更豐富的流程控制和數據檢測的調試功能。LLDB為Xcode提供了底層調試環境,其中包括內嵌在Xcode IDE中的位于調試區域的控制面板,在這里我們可以直接調用LLDB命令.平時用Xcode運行程序,實際走的都是LLDB。熟練使用LLDB,可以讓你debug事半功倍。
我們以一個實際例子進行學習使用,為了方便,我們先寫一個Person類:
class Person: NSObject { var name:String var age:UInt32 init(name:String,age:UInt32) { self.name = name self.age = age } //重寫description是為了方便調試 override var description: String{ return "name:/(name),age:/(age)" }}進入 ViewController.swift添加一個測試函數,添加相應斷點,并在viewDidLoad()中調用,執行代碼到函數的最后,如下圖:
首先看一下打印命令p/po
在控制臺使用p和po來替代PRint,對于p和po,第一次看到可能不明白是啥,我們可以輸入help,在幫助信息中我們可以找到p和po部分如下:
(lldb) help
p -- ('expression --') Evaluate an expression (ObjC++ or Swift)in
the current program context, using user defined variables and
variables currentlyin scope.
po -- ('expression -O -- ') Evaluate an expression (ObjC++ or Swift)
in the current program context, using user defined variables and
variables currentlyin scope.
所以,p命令,本質是expression --;po命令,本質是expression -O —,其中-O的表示:-O ( --object-description ) Display using a language-specific description API, if possible.
注意打印輸出的最底部信息:For more information on any command, type'help '.我們通過輸入help和命令名得到更詳細的幫助。
1:p命令會打印出對象的類型和相關屬性.
(lldb) p person
(LLDBDemo.Person) $R0 = 0x00007f93fb4f4ae0 {
ObjectiveC.NSObject = {}
name = "Jack"
age = 24
}
2:po命令對于繼承自NSObject得對象,指示會打印出description中的內容,類似于print函數.
(lldb) po person
name:Jack,age:24
3:在看一個數組的小例子,直接console中輸入:
(lldb) po ["123","456"]
? 2 elements
- [0] : "123"
- [1] : "456"
(lldb) p ["123","456"]
([String]) $R3 = 2 values {
[0] = "123"
[1] = "456"
}
4:打印視圖的層級結構,該功能還是挺有用的
(lldb) (lldb) po view.performSelector("recursiveDescription")
? Unmanaged
- _value : >
| >
| <_UILayoutGuide: 0x7f93fb489720; frame = (0 0; 0 0); hidden = YES; layer = >
| <_UILayoutGuide: 0x7f93fb4ef7a0; frame = (0 0; 0 0); hidden = YES; layer = >
這里還有一個好玩的東西,那就是每個視圖中,都有一個16進制的字符串,代表這個視圖的ID,比如這個:UIView:0x7fd71ac66970;這個 ID的作用非常的強大,得到了這個ID,我們就可以通過這個命令來得到這個視圖的引用了:
(lldb) expression let $view = unsafeBitCast(0x7f93fb4dc930, UIView.self)
然后就可以對該視圖進行操作了,例如執行如下代碼:
(lldb)expression$view.backgroundColor = UIColor.redColor()
程序運行完成,整個視圖背景顏色變紅。ID還是很有用的,當然我們也可以直接操作當前視圖,如下:
(lldb)expressionself.view.backgroundColor = UIColor.redColor()
既然可以動態改變視圖背景,那么我們也可以動態添加相應的視圖,接下來我們添加一個layer到當前視圖上:
(lldb) (lldb) expression
Enter expressions, then terminate with an empty line to evaluate:
1 let layer = CALayer()
2 layer.backgroundColor = UIColor.yellowColor().CGColor
3 layer.bounds = CGRect(x:0, y:0, width:100, height:100)
4 layer.position = CGPoint(x:250, y:300)
5 layer.cornerRadius = 12.0
6 view.layer.addSublayer(layer)
7
效果如下圖:![]()
看起來不錯啊,但是如果接下來,我們還需要使用之前創建的layer,那該怎么辦?如果我們直接使用肯定不行,(lldb)expression layer,直接報錯,所以創建全局常量或者變量就好了,一起來看一下:
(lldb) expression let $layer = CALayer()
(lldb) expression
Enter expressions, then terminate with an empty line to evaluate:
1 $layer.backgroundColor = UIColor.yellowColor().CGColor
2 $layer.bounds = CGRect(x:0, y:0, width:100, height:100)
3 $layer.position = CGPoint(x:250, y:300)
4 $layer.cornerRadius = 12.0
5 view.layer.addSublayer($layer)
6
(lldb) po $layer
[ (kCGColorSpaceDeviceRGB)] ( 1 1 0 1 )>
5:打印視圖控制器的層級結構(lldb) po UIWindow.valueForKeyPath("keyWindow.rootViewController._printHierarchy")
? Optional
- Some : , state: appearing, view:
| , state: disappeared, view: not in the window
接下來看一下前面使用到的Expression命令
Expression命令很靈活,非常多,我們可以輸入help expression得到對應的幫助信息:Syntax: expression -- ,expression可以使用e或者expr代替。
1:expression命令可以幫助我們執行代碼,下面執行改變屬性name的值:
(lldb) p person
(LLDBDemo.Person) $R1 = 0x00007fd20a706be0 {
ObjectiveC.NSObject = {}
name = "Jack"
age = 24
}
(lldb) e person.name = "hua"
(lldb) p person
(LLDBDemo.Person) $R3 = 0x00007fd20a706be0 {
ObjectiveC.NSObject = {}
name = "hua"
age = 24
}
2:看一下格式化打印相關
(lldb) e -f bin -- person.age
(UInt32) $R4 = 0b00000000000000000000000000011000
(lldb) e -f oct -- person.age
(UInt32) $R5 = 030
(lldb) e -f hex -- person.age
(UInt32) $R6 = 0x00000018
上面是格式化打印二進制,八進制和十進制,命令的意思為:e是expression的縮寫,-f bin是格式化語法為:-f (format): -f ( --format ) Specify a format to be usedfor display.--分隔符,最后person.age是對需要的值進行格式化。
其實格式化打印可以更簡單:
(lldb) p/x person.age
(UInt32) $R10 =0x00000018
看到這里,可能會想,p/x是怎么回事?怎樣查找格式化命令符號所對應的含義?
(lldb) type format
The following subcommands are supported:
add -- Add a new formatting style for a type.
clear -- Delete all existing format styles.
delete -- Delete an existing formatting style for a type.
info -- This command evaluates the provided expression and shows which
format is applied to the resulting value (if any). This
command takes 'raw' input (no need to quote stuff).
list -- Show a list of current formats.
For more help on any particular subcommand, type 'help
'.
再輸入help format info,將看到所有格式化指令,下面是部分內容截圖:
![]()
上面劃線部分:d對應十進制,h對應十六進制,o對應八進制,再試試其他幾個:
(lldb) p/o person.age
(UInt32) $R2 = 030
(lldb) p/d person.age
(UInt32) $R3 = 24
3:打印Raw value,-R ( --raw-output ) Don't use formatting options.
(lldb) e -R -- person
(LLDBDemo.Person) $R7 = 0x00007fd20a706be0 {
ObjectiveC.NSObject = {}
name = {
_core = {
_baseAddress = {
_rawValue = 0x000000011321e4e0
}
_countAndFlags = {
_value = 3
}
_owner = None {
Some = {
instance_type = 0x0000000000000000
}
}
}
}
age = {
_value = 24
}
}
4:顯示變量類型, -T ( --show-types ) Show variable types when dumping values.
(lldb) e -T -- person
(LLDBDemo.Person) $R9 = 0x00007fd20a706be0 {
(NSObject) ObjectiveC.NSObject = {}
(String) name = "hua"
(UInt32) age = 24
}
5:顯示變量位置信息, -L ( --location ) Show variable location information.
(lldb) e -L -- person
scalar(0x00007fd20a706be0): (LLDBDemo.Person) $R10 = 0x00007fd20a706be0 {
scalar(0x00007fd20a706be0): ObjectiveC.NSObject = {}
0x00007fd20a706bf0: name = "hua"
0x00007fd20a706c08: age = 24
}
6:當然也可以組合多個部分一起查看,這里就是組合之前的R、T、L
(lldb) e -RTL -- person
scalar(0x00007fd20a706be0): (LLDBDemo.Person) $R11 = 0x00007fd20a706be0 {
scalar(0x00007fd20a706be0): (ObjectiveC.NSObject) ObjectiveC.NSObject = {}
0x00007fd20a706bf0: (Swift.String) name = {
0x00007fd20a706bf0: (Swift._StringCore) _core = {
0x00007fd20a706bf0: (Swift.COpaquePointer) _baseAddress = {
0x00007fd20a706bf0: (Builtin.RawPointer) _rawValue = 0x000000011321e4e0
}
0x00007fd20a706bf8: (Swift.UInt) _countAndFlags = {
0x00007fd20a706bf8: (Builtin.Int64) _value = 3
}
0x00007fd20a706c00: (Swift.Optional) _owner = None {
0x00007fd20a706c00: (AnyObject) Some = {
0x00007fd20a706c00: (Builtin.RawPointer) instance_type = 0x0000000000000000
}
}
}
}
0x00007fd20a706c08: (Swift.UInt32) age = {
0x00007fd20a706c08: (Builtin.Int32) _value = 24
}
}
7:多行表達式模式
(lldb) e
Enter expressions, then terminate with an empty line to evaluate:
1 struct Compass{var dirction = "N";var angle = 16.5}
2 var c = Compass()
3 print(c)
4
(Compass #1)(dirction: "N", angle: 16.5)
(lldb) e
Enter expressions, then terminate with an empty line to evaluate:
1 func add(a:Int,b:Int)->Int {return a+b }
2 let c = add(3,b:4)
3 print(c)
4
7
8:導入模塊框架
如果我們在調試的過程中,需要獲取某個框架的類,但是當前文件中并沒有導入,我們可以在console中調試過程中直接輸入import和框架名稱,好比如:我們需要MapKit中的類,直接:
(lldb) p import MapKit
9:LLDB變量
細心的朋友可能會發現輸出的信息中帶有$1、$2的字樣。實際上,我們每次查詢的結果會保存在一些持續變量中($[0-9]+),這樣你可以在后面的查詢中直接使用這些值。其實前面在使用layer的例子中已經提到了,在看個例子:
(lldb) e var $a = Person(name:"Jenny",age:24)
(lldb) p $a
(LLDBDemo.Person) $R12 = 0x00007fd20a512ba0 {
ObjectiveC.NSObject = {}
name = "Jenny"
age = 24
}
接下來看一下我們經常使用的斷點(breakpoint),斷點采用這個命令breakpoint,縮寫br,這里先列出文檔,看一下為我們提供了哪些功能:
(lldb) help breakpoint
The following subcommands are supported:
clear -- Clears a breakpoint or set of breakpoints in the executable.
command -- A set of commands for adding, removing and examining bits of
code to be executed when the breakpoint is hit (breakpoint
'commands').
delete -- Delete the specified breakpoint(s). If no breakpoints are
specified, delete them all.
disable -- Disable the specified breakpoint(s) without removing them. If
none are specified, disable all breakpoints.
enable -- Enable the specified disabled breakpoint(s). If no breakpoints
are specified, enable all of them.
list -- List some or all breakpoints at configurable levels of detail.
modify -- Modify the options on a breakpoint or set of breakpoints in
the executable. If no breakpoint is specified, acts on the
last created breakpoint. With the exception of -e, -d and -i,
passing an empty argument clears the modification.
name -- A set of commands to manage name tags for breakpoints
set -- Sets a breakpoint or set of breakpoints in the executable.
For more help on any particular subcommand, type 'help
'.
基本的使用:
1:使用list顯示所有斷點信息
當前工程只有一個斷點,所以只打印了一個斷點信息,并且可以知道斷點的相關信息,在當前文件的29行,而且可以通過左邊斷點顯示區域來對比結果。
2:禁用斷點即disable命令
根據上面內容,現在我們來玩一玩,現在在原有的代碼上再添加2個斷點,如下圖:
1)首先使前面兩個斷點失效
(lldb) br disable 1.*
1 breakpoints disabled.
(lldb) br disable 2.*
1 breakpoints disabled.
2)點擊運行程序,會跳到第三個斷點,中間斷點不在暫停,并且斷點信息也會不一樣,多了斷點失效部分的內容,如下圖:
3:我們也可以使用 enable恢復斷點
(lldb) br enable 1.*1 breakpoints enabled.
(lldb) br enable 2.*
1 breakpoints enabled.
4:刪除斷點,刪除第一個斷點
(lldb) br delete 1
1 breakpoints deleted; 0 breakpoint locations disabled.
(lldb) br list
Current breakpoints:
2: file = '/Users/longshihua/Desktop/LLDBDemo/LLDBDemo/ViewController.swift', line = 28, exact_match = 0, locations = 1, resolved = 1, hit count = 1
2.1: where = LLDBDemo`LLDBDemo.ViewController.testPerson () -> () + 12 at ViewController.swift:28, address = 0x000000010b4f3d5c, resolved, hit count = 1
3: file = '/Users/longshihua/Desktop/LLDBDemo/LLDBDemo/ViewController.swift', line = 30, exact_match = 0, locations = 1, resolved = 1, hit count = 1
3.1: where = LLDBDemo`LLDBDemo.ViewController.testPerson () -> () + 204 at ViewController.swift:30, address = 0x000000010b4f3e1c, resolved, hit count = 1
5:添加斷點
(lldb) br set -f ViewController.swift -l26
Breakpoint 4:where = LLDBDemo`LLDBDemo.ViewController.testPerson () -> () +12 at ViewController.swift:28, address =0x000000010b4f3d5c
命令中的-f ViewController.swift和-l26
-f ( --file ) 具體的文件
-l ( --line ) 文件的位置,即具體哪一行
6:為函數添加斷點,這里直接為我們的testPerson函數添加斷點,-F為函數名
(lldb) br set -F testPerson
Breakpoint 3:2 locations.
7:條件斷點
先添加一個函數,并加一個斷點,如下圖:當某種條件產生的時候觸發的斷點,現在這里實現停在1000次循環的第800次
當i為800時觸發斷點
(lldb) br modify -c i==800
(lldb) po i
800
指令說明:
-c ( --condition )
The breakpoint stops onlyifthis condition expression evaluates to
true.
記住一點,多利用help,查找命令和幫助信息,所以這里我們可以通過help br set來查看更多信息。
了解一下線程相關內容thread
1:threadreturn
Debug的時候,也許會因為各種原因,我們不想讓代碼執行某個方法,或者要直接返回一個想要的值。這時候就該thread return上場了。thread return可以接受一個表達式,調用命令之后直接從當前的堆棧中返回表達式的值。我們只需在方法的開始位置加一個斷點,當程序中斷的時候,輸入命令即可。
2:thread step-over單步執行,執行下一行代碼
thread step-in 進入函數體,單步執行
thread step-out 退出當前函數體
3:thread backtrace顯示堆棧信息
(lldb) thread backtrace
* thread #1: tid = 0x35a80, 0x0000000105b62d5c LLDBDemo`ViewController.testPerson(self=0x00007fab09d2bf00) -> () + 12 at ViewController.swift:28, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
* frame #0: 0x0000000105b62d5c LLDBDemo`ViewController.testPerson(self=0x00007fab09d2bf00) -> () + 12 at ViewController.swift:28
........................
Type類型查找
(lldb) help type
The following subcommands are supported:
category -- A set of commands for Operating on categories
filter -- A set of commands for operating on type filters
format -- A set of commands for editing variable value display options
lookup -- Lookup a type by name in the select target. This command
takes 'raw' input (no need to quote stuff).
summary -- A set of commands for editing variable summary display
options
synthetic -- A set of commands for operating on synthetic type
representations
For more help on any particular subcommand, type 'help
'.
拋出一個異常,不知道是啥的時候怎么辦?我們可以使用lookup命令,查找我們的類型信息:
(lldb) type lookup Person
@objc class Person : ObjectiveC.NSObject {
@objc var name: Swift.String
@objc var age: Swift.UInt32
@objc init(name: Swift.String, age: Swift.UInt32)
@objc override var description: Swift.String {
@objc override get {}
}
@objc func personException() throws
@objc deinit
@objc @objc init()
}
frame(幀)首先看一下與frame(幀)相關解釋:
作用:frame -- A set of commandsfor operating on the current thread's frames.
(lldb) help frame
The following subcommands are supported:
info -- List information about the currently selected frame in the
current thread.
select -- Select a frame by index from within the current thread and
make it the current frame.
variable -- Show frame variables. All argument and local variables that
are in scope will be shown when no arguments are given. If
any arguments are specified, they can be names of argument,
local, file static and file global variables. Children of
aggregate variables can be specified such as 'var->child.x'.
For more help on any particular subcommand, type 'help
'.
(lldb) frame variable person
(LLDBDemo.Person) person = 0x00007fab09e13790 {
ObjectiveC.NSObject = {}
name = "Jack"
age = 24
}
我們可以使用frame info查看當前frame的信息:
(lldb) frame info
frame #0:0x0000000105b62e1c LLDBDemo`ViewController.testPerson(self=0x00007fab09d2bf00) -> () +204 at ViewController.swift:30
target命令
對于target這個命令,我們用得最多的可能就是target modules lookup。由于LLDB給 target modules取了個別名image,所以這個命令我們又可以寫成image lookup.
1:當我們想查看一個類型的時候,可以使用image lookup --type,簡寫為image lookup -t,比如我們可以看看自己的Person類型信息:
(lldb) image lookup -t Person
2:當我們想查找一個方法或者符號的信息,比如所在文件位置等。我們可以使用image lookup --name,簡寫為 image lookup -n,這里我們就直接查找我們的testPerson函數,我們可以看到如下信息:
(lldb) image lookup -n testPerson
2 matches found in /Users/longshihua/Library/Developer/Xcode/DerivedData/LLDBDemo-fxvhyqfwhdszpwdegllmmmsjcohz/Build/Products/Debug-iphonesimulator/LLDBDemo.app/LLDBDemo:
Address: LLDBDemo[0x0000000100003d50] (LLDBDemo.__TEXT.__text + 9872)
Summary: LLDBDemo`LLDBDemo.ViewController.testPerson () -> () at ViewController.swift:26 Address: LLDBDemo[0x0000000100003e30] (LLDBDemo.__TEXT.__text + 10096)
Summary: LLDBDemo`@objc LLDBDemo.ViewController.testPerson () -> () at ViewController.swift
3:當我們有一個地址,想查找這個地址具體對應的文件位置,可以使用 image lookup --address,簡寫為image lookup -a.
新聞熱點
疑難解答