LLDB
LLDB 是一个有着 REPL 的特性和 C++ ,Python 插件的开源调试器。LLDB 绑定在 Xcode 内部,存在于主窗口底部的控制台中。调试器允许你在程序运行的特定时暂停它,你可以查看变量的值,执行自定的指令,并且按照你所认为合适的步骤来操作程序的进展
基本语法
<command> [<subcommand> [<subcommand>...]] <action> [-options [option-value]] [argument [argument...]]
<command>
(命令)和<subcommand>
(子命令):LLDB调试命令的名称。命令和子命令按层级结构来排列:一个命令对象为跟随其的子命令对象创建一个上下文,子命令又为其子命令创建一个上下文,依此类推。<action>
执行命令的操作<options>
命令选项<arguement>
命令的参数[]
表示命令是可选的,可以有也可以没有
GDB to LLDB 参考是一个非常好的调试器可用命令的总览。也可以安装 Chisel,它是一个开源的 LLDB 插件合辑,这会使调试变得更加有趣
例: breakpoint set -n main
这个命令对应到上面的语法就是:
command: breakpoint 表示断点命令
action: set 表示设置断点
option: -n 表示根据方法name设置断点
arguement: mian 表示方法名为mian
快捷键
快捷键功能 | 命令 |
---|---|
暂停/继续 | cmd + ctrl + Y |
控制台显示/隐藏 | cmd + Y |
光标切换到控制台 | cmd + shift + C |
清空控制台 | cmd + K |
step over | fn6 |
step into | fn7 |
step out | fn8 |
唯一匹配原则
LLDB有个很省事的特性,如果输入的字母已经能匹配到某个命令,就可以直接执行,等于输入了完整的命令
help
最简单命令是 help,它会列举出所有的命令。如果你忘记了一个命令是做什么的,或者想知道更多的话,你可以通过 help <command>
来了解更多细节,例如 help print
或者 help thread
。如果你甚至忘记了 help
命令是做什么的,你可以试试 help help
。不过你如果知道这么做,那就说明你大概还没有忘光这个命令
apropos
有的时候,可能并不能完全记得某个命令,如果只记得命令中的某个关键字。这时候可以使用apropos搜索相关命令信息
(lldb) apropos stop-hook
The following built-in commands may relate to 'stop-hook':
_regexp-display -- Add an expression evaluation stop-hook.
_regexp-undisplay -- Remove an expression evaluation stop-hook.
target stop-hook -- A set of commands for operating on debugger
target stop-hooks.
target stop-hook add -- Add a hook to be executed when the target stops.
target stop-hook delete -- Delete a stop-hook.
target stop-hook disable -- Disable a stop-hook.
target stop-hook enable -- Enable a stop-hook.
target stop-hook list -- List all stop-hooks.
$
任何以美元符开头的东西都是存在于 LLDB 的命名空间的,它们是为了帮助你进行调试而存在的
p、po、print、call
print: 打印某个东西,可以是变量和表达式 p: 可以看做是print的简写 call: 调用某个方法
查询变量一般用p与po命令。
po (print object 的缩写)的作用为打印对象,事实上,我们可以通过help po得知,po是expression -O --的简写,我们可以通过它打印出对象,而不是打印对象的指针。而值得一提的是,在 help expression 返回的帮助信息中,我们可以知道,po命令会尝试调用对象的 description 方法来取得对象信息,因此我们也可以重载某个对象的description方法,使我们调试的时候能获得可读性更强,更全面的信息
expression
expression 可简写为e,作用为执行一个表达式,可以用来查询当前堆栈变量的值
打印变量
默认的格式
(lldb) p 16
16
十六进制:
(lldb) p/x 16
0x10
二进制 (t 代表 two):
(lldb) p/t 16
0b00000000000000000000000000010000
(lldb) p/t (char)16
0b00010000
也可以使用 p/c 打印字符,或者 p/s 打印以空终止的字符串 (译者注:以 '\0' 结尾的字符串)
可以在 C 语言中用 int a = 0 来声明一个变量一样,也可以在 LLDB 中做同样的事情。不过为了能使用声明的变量,变量必须以美元$
符开头
(lldb) e int $a = 2
(lldb) p $a * 19
38
(lldb) e NSArray *$array = @[ @"Saturday", @"Sunday", @"Monday" ]
(lldb) p [$array count]
2
(lldb) po [[$array objectAtIndex:0] uppercaseString]
SATURDAY
(lldb) p [[$array objectAtIndex:$a] characterAtIndex:0]
error: no known method '-characterAtIndex:'; cast the message send to the method's return type
error: 1 errors parsing expression
breakpoint
一般对breakpoint命令使用得不多,而是在XCode的GUI界面中直接添加断点。除了直接触发程序暂停供调试外,可以进行进一步的配置
断点创建
(lldb) breakpoint set -f main.m -l 16
Breakpoint 1: where = DebuggerDance`main + 27 at main.m:16, address = 0x000000010a3f6cab
也可以使用缩写形式 br。虽然 b 是一个完全不同的命令 (_regexp-break 的缩写),但恰好也可以实现和上面同样的效果
watchpoint
有时候会关心类的某个属性什么时候被人修改了,最简单的方法当然就是在setter的方法打断点,或者在@property的属性生命行打上断点。这样当对象的setter方法被调用时,就会触发这个断点,当然这么做是有缺点的,对于直接访问内存地址的修改,setter方法的断点并没有办法监控得到,因此需要用到watchpoint命令
watchpoint命令在XCode的GUI中也可以直接使用,当程序暂停时,我们能对当前程序栈中的变量设置watchpoint。值得注意的是,watchpoint是直接设置到该变量所在的内存地址上的,所以当这个变量释放了后,watchpoint仍然是对这个地址的内存生效的
watchpoint set self->testVar //为该变量地址设置watchpoint
watchpoint set expression 0x00007fb27b4969e0 //为该内存地址设置watchpoint,内存地址可从前文提及的`p`命令获取
watchpoint command add -o 'frame info' 1 //为watchpoint 1号加上子命令 `frame info`
watchpoint list //列出所有watchpoint
watchpoint delete // 删除所有watchpoint
thread、bt
thread backtrace [-c <count>] [-s <frame-index>] [-e <boolean>]
-c:设置打印堆栈的帧数(frame) -s:设置从哪个帧(frame)开始打印 -e:是否显示额外的回溯
bt即是thread backtrace,作用是打印出当前线程的堆栈信息。当程序发生了crash后,我们可以用该命令打印出发生crash的当前的程序堆栈,查询出发生crash的调用路径。由于比较常用,所以LLDB直接给它一个特殊的bt别名。
thread另一个比较常用的用法是 thread return,调试的时候,我们希望在当前执行的程序堆栈直接返回一个自己想要的值,可以执行该命令直接返回。
thread return <expr>
在这个断点中,我们可以执行 thread return NO让该函数调用直接返回NO ,在调试中轻松覆盖任何函数的返回路径。
c、n、s、finish
- c/ continue/ thread continue: 这三个命令效果都等同于上图中第一个按钮的。表示程序继续运行
- n/ next/ thread step-over: 这三个命令效果等同于上图第二个按钮。表示单步运行
- s/ step/ thread step-in: 这三个命令效果等同于上图第三个按钮。表示进入某个方法
- finish/ step-out: 这两个命令效果等同于第四个按钮。表示直接走完当前方法,返回到上层frame
thread其他不常用的命令
thread jump: 直接让程序跳到某一行。由于ARC下编译器实际插入了不少retain,release命令。跳过一些代码不执行很可能会造成对象内存混乱发生crash。 thread list: 列出所有的线程 thread select: 选择某个线程 thread until: 传入一个line的参数,让程序执行到这行的时候暂停 thread info: 输出当前线程的信息
frame
frame即是帧,其实就是当前的程序堆栈,我们输入bt命令,打印出来的其实是当前线程frame。
frame info
查看当前frame的信息
在调试中,一般我们比较关心当前堆栈的变量值,我们可以使用frame variable
来获取全部变量值。当然也可以输入特定变量名,来获取单独的变量值,如frame v self-> testVar来获取testVar的值
(lldb) frame info
frame #0: 0x0000000103a11210 Test`-[AppDelegate application:didFinishLaunchingWithOptions:](self=0x000060000002d8c0, _cmd="application:didFinishLaunchingWithOptions:", application=0x00007f8d2e100160, launchOptions=0x0000000000000000) at AppDelegate.m:21
(lldb) frame variable
(AppDelegate *) self = 0x000060000002d8c0
(SEL) _cmd = "application:didFinishLaunchingWithOptions:"
(UIApplication *) application = 0x00007f8d2e100160
(NSDictionary *) launchOptions = nil
target
target modules lookup(image lookup)
对于target这个命令,我们用得最多的可能就是target modules lookup。由于LLDB给target modules取了个别名image,所以这个命令我们又可以写成image lookup。
image lookup –address
当有一个地址,想查找这个地址具体对应的文件位置,可以使用image lookup --address,简写为image lookup -a
e.g: 当我们发生一个crash
(lldb) image lookup -a 0x000000010a1c3e36
image lookup –name
当想查找一个方法或者符号的信息,比如所在文件位置等。我们可以使用image lookup --name,简写为image lookup -n
(lldb) image lookup -n dictionaryWithXMLString:
image lookup –type
当我们想查看一个类型的时候,可以使用image lookup --type,简写为image lookup -t:
查看Model的类型:
(lldb) image lookup -t Model
target stop-hook
target stop-hook命令就是让在每次stop的时候去执行一些命令
target stop-hook只对breakpoint和watchpoint的程序stop生效,直接点击Xcode上的pause或者debug view hierarchy不会生效
target stop-hook add & display
假如想在每次程序stop的时候,都用命令打印当前frame的所有变量。我们可以添加一个stop-hook:
(lldb) target stop-hook add -o "frame variable"
Stop hook #4 added.
target stop-hook add表示添加stop-hook,-o的全称是--one-liner,表示添加一条命令
大多情况下,在stop的时候可能想要做的是打印一个东西。正常情况需要用target stop-hook add -o "p xxx",LLDB提供了一个更简便的命令display, 下面2行代码效果相同
(lldb) target stop-hook add -o "p self.view"
(lldb) display self.view
target stop-hook list
查看当前所有的stop-hook
target stop-hook delete & undisplay
有添加的命令,当然也就有删除的命令。使用target stop-hook delete可以删除stop-hook,也可以用undispla
(lldb) target stop-hook delete 4
(lldb) undisplay 5
target stop-hook disable/enable 暂时想让某个stop-hook失效的时候,可以使用target stop-hook disable
(lldb) target stop-hook disable 8
让stop-hook生效
(lldb) target stop-hook enable 8
想让所有的stop-hook失效/生效,只需不传入stop-hookid即可
target symbols add(add-dsym)
add-dsym ~/.../XXX.dSYM
说这个命令之前,先简单解释一下dSYM文件。程序运行的时候,都会编译成二进制文件。因为计算机只识别二进制文件,在编译的时候Xcode会生成dSYM文件,dSYM文件记录了哪行代码对应着哪些二进制,对代码打断点就会对应到二进制上
当Xcode找不着dSYM文件的时候,就无法对代码打断点,进行调试。target symbols add命令的作用就是可以手动的将dSYM文件添加上去。LLBD对这个命令起了一个别名: add-dsym