LLDB调试技巧
LLDB调试技巧
从XCode5之后,所有XCode的项目,调试器自动配置为lldb(LLDB Quick Start Guide),替代了原来的GDB。LLDB本身作为一个功能完整的调试器,既可以在通过XCode的GUI调试模块来工作,也可以单独工作。
如果能够了解LLDB本身的常用命令,那么在XCode的环境中使用它或者在XCode的debug console中使用命令都会更加得心应手。
LLDB命令基本语法
如果有GDB的经验的话,会发现LLDB的命令很相似。所有的LLDB命令都是以下的结构:
<command> [<subcommand> [<subcommand>...]] <action> [-options [option-value]] [argument [argument...]]
具体示例:
(lldb) breakpoint set --file test.c --line 12
<command>即上图中breakpoint部分。使用help命令可以看到LLDB定义的基本命令集。subcommand通常是由动词构成,它表示一个command内的一类相关操作。比如上例中的command,也就是breakpoint还有delete、disable等subcommand。使用help <command>就能查看相关command的subcommand。argument一个command或者command [subcommand]组合可能需要跟不同参数(argument)。针对不同的option还可以跟不同的参数。比如上例中test.c和12就是跟在breakpoint set不同的option之后的参数。option用来修饰需要进行的操作。常常在其起前面用--分割,一些option也提供简约表达形式使用-开头。比如上例中,--file这个option的简约表达式为-f。
为命令设置别名(command alias)
如果觉得命令的输入十分冗长、不方便,可以为常用的命令设置便于记住的别名。这样就方便使用,而不用每次输入长段的命令。比如:
(lldb) command alias bfl breakpoint set -f %1 -l %2
将在某个文件(-f 之后的参数)某一行(-l 之后的参数)设置一个断点的命令用别名bfl代替。在使用:
(lldb) bfl foo.c 12
实际上就是在执行:
(lldb) breakpoint set --file foo.c --line 12
常见的一些如next、step和continue都是LLDB初始配置的命令别名。想要查看所有的命令,包括自定义的命令别名,在现版本的LLDB(lldb-370.0.37)中,只需要键入help命令:
... /* 命令 */
Current command abbreviations (type 'help command alias' for more info):
... /* 命令别名 */
第二段就列出了所有的命令别名。
如果想要去掉自己设置的或者是LLDB预设的命令别名,只需要执行下列命令就可以了:
(lldb) command unalias <command-alias>
使用LLDB的help功能
- 使用
help help查看help命令的使用; - 使用
help <command>查看某个command的使用; - 使用
help <command> [subcommand]查看某个command下的子命令的使用;
命令不同形式
以在XCode中最常用的po命令来举例。LLDB的命令是存在多种形式的,实际在上面已经零散提到。
| 命令形式 | 命令内容 |
|---|---|
| 标准形式 | expression –object-description – someVariable |
| 简写形式 | e -O – someVariable |
| 别名形式 | po someVariable |
需要解释一下的是这里--object-description这个option之后用--分割了argument部分。文档解释:
Commands that accept options as well as freeform arguments, such as the expression command, must place a space-delimited double dash (–) between the last option and the first argument. This ensures that arguments resembling an option by starting with a dash (-) are interpreted as an argument.
独立使用LLDB流程
绝大部分时候,都是通过XCode间接地使用LLDB的调试功能。理解如何独立使用LLDB调试程序的流程,也能更好地理解在XCode中调试程序的技巧。
大体上,使用LLDB可以分为下面几个步骤(大致有先后顺序):
- 确定调试的程序
- 设置调试断点
- 控制程序运行
- 输出线程和
stack frame信息。
在这里将文档实例照搬至此。
假设有一下的一个名为Greeter.swift的文件:
class Greeter {
private var acquaintances: Set<String> = []
func hasMet(personNamed name: String) -> Bool {
return acquaintances.contains(name)
}
func greet(personNamed name: String) {
if hasMet(personNamed: name) {
print("Hello again, \(name)!")
} else {
acquaintances.insert(name)
print("Hello, \(name). Nice to meet you!")
}
}
}
let greeter = Greeter()
greeter.greet(personNamed: "Anton")
greeter.greet(personNamed: "Mei")
greeter.greet(personNamed: "Anton")
在使用swiftc命令编译之后会产生一个名为Greeter的可执行文件:
$ swiftc -g Greeter.swift
$ ls
Greeter.dSYM
Greeter.swift
Greeter*
确定调试程序
首先,使用lldb来启动程序。
$ lldb Greeter
(lldb) target create "Greeter"
Current executable set to 'Greeter' (x86_64).
该命令就会进入lldb的调试session,能够让你输入不同的lldb命令与被调试的程序交互。
设置调试断点
在确定了调试的程序之后,通常需要设置调试断点。断点一般设置在自己需要特别关心的代码块上。
(lldb) breakpoint set --line 18
Breakpoint 1: where = Greeter`main + 70 at Greeter.swift:18, address = 0x0000000100001996
运行程序
使用process launch来运行需要调试的程序。前面lldb <program>只是开启了针对program的调试session,并没有启动被调试的程序进程。
(lldb) process launch
Process 97209 launched: 'Greeter' (x86_64)
Process 97209 stopped
* thread #1: tid = 0x1288be3, 0x0000000100001996 Greeter`main + 70 at Greeter.swift:18, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
frame #0: 0x0000000100001996 Greeter`main + 70 at Greeter.swift:18
15 }
16 }
18
-> 18 let greeter = Greeter()
19
20 greeter.greet(personNamed: "Anton")
21 greeter.greet(personNamed: "Mei")
控制程序运行
使用thread step-over跳过断点所在行,调至下一行。
(lldb) thread step-over
Process 97209 stopped
* thread #1: tid = 0x1288be3, 0x00000001000019bd Greeter`main + 109 at Greeter.swift:20, queue = 'com.apple.main-thread', stop reason = step over
frame #0: 0x00000001000019bd Greeter`main + 109 at Greeter.swift:20
17
18 let greeter = Greeter()
19
-> 20 greeter.greet(personNamed: "Anton")
21 greeter.greet(personNamed: "Mei")
22 greeter.greet(personNamed: "Anton")
使用thread step-in,如果断点在函数(或者方法)处,可以进入函数(或者方法)内部;如果不在,其效果跟thread step-over一样。
输出信息
使用thread backtrace输出程序执行至目前断点处之前的栈帧顺序。
(lldb) thread backtrace
* thread #1: tid = 0x1288be3, 0x0000000100001a98 Greeter`Greeter.hasMet(name="Anton", self=0x0000000101200190) -> Bool + 24 at Greeter.swift:5, queue = 'com.apple.main-thread', stop reason = step in
frame #0: 0x0000000100001a98 Greeter`Greeter.hasMet(name="Anton", self=0x0000000101200190) -> Bool + 24 at Greeter.swift:5
* frame #1: 0x0000000100001be4 Greeter`Greeter.greet(name="Anton", self=0x0000000101200190) -> () + 84 at Greeter.swift:9
frame #2: 0x00000001000019eb Greeter`main + 155 at Greeter.swift:20
frame #3: 0x00007fff949d05ad libdyld.dylib`start + 1
frame #4: 0x00007fff949d05ad libdyld.dylib`start + 1
使用frame variable输出当前栈帧上的所有变量。
(lldb) frame variable
(String) name = "Anton"
(Greeter.Greeter) self = 0x0000000100502920 {
acquaintances = ([0] = "Anton")
}