配合LLDB调试器进行iOS代码调试
配合LLDB调试器进行iOS代码调试
在一款完整iOS移动应用的开发中,代码的调试和编写占着同等重要的地位。Xcode默认使用LLDB作为代码调试器,LLDB功能丰富且强大,恰当的使用它,可以帮助开发者事半功倍的完成代码调试的工作。
1.expression代码执行指令
关于LLDB调试器,最常用的指令应该是p与po了,开发者常用这两个命令来进行对象的打印操作,p会打印出对象地址和类型,po则会额外打印出对象的值得内容,实际上,这两个命令都是expression相关命令的简写。expression命令也并非简单的打印命令,实际上它是一个执行代码命令,执行后将返回值进行打印,这个命令有一个十分强大的特点,它可以真实改变程序运行中变量的值。例如在如下代码中的int c = a+b 一行添加一个断点,运行工程。
1 | int a = 0; |
如果开发者不进行任何认为操作,此时打印出的值应该是1,为了测试,可以在调试区输入如下命令:
1 | (lldb) expression a=1 |
此后跳过断点继续运行程序,可以看到打印的结果如下,c变成2。
1 | (lldb) expression a=1 |
通过上面的演示,我们发现使用LLDB调试代码十分方便的一个特点,当我们知道程序某个地方可能会出现问题,为了找到解决方法,不使用LLDB时我们可能需要在代码中添加大量的打印函数,并且多次尝试修改源代码才能解决问题,如果使用LLDB的expression命令,我们不仅不需要添加额外的打印代码,也不需要直接修改源代码,在调试区进行多次调试,直到找到正确的修改方法后再对源代码修改一次即可。
2.frame代码堆栈块信息相关指令
当Xcode进入断点调试或者遇到异常程序崩溃时,在Xcode左侧的导航区都会将程序运行中的相关堆栈块信息列举出来,例如使用如下测试代码,在text方法中的int c = a+b 一行添加一个断点。
1 | #import "ViewController.h" |
当程序运行到断点处断开时,Xcode左侧的堆栈块如下图所示:
从图中可以看出,程序当前处于激活状态的线程有5个,程序目前断在线程1中的test方法堆栈块中,使用frame info指令可以打印当前堆栈块的信息,示例如下:
1 | (lldb) frame info |
在打印的信息中,会有所在的文件名称和函数名称及堆栈块标号和内存地址。
在实际代码调试过程中,程序运行的回溯是一个重要的方法,例如上面的代码例子,虽然现在断点断在test方法中,开发者可能需要在viewDidLoad方法中进行相关调试,例如上面viewDidLoad方法中有一个变量ab,如果想查看ab变量的值,我们就需要将当前选中调试的堆栈块选择为viewDidLoad方法所在的堆栈块,从Xcode左侧导航区可以看到,viewDidLoad方法堆栈块的标号为1,执行如下LLDB指令即可切换:
1 | (lldb) frame select 1 |
从打印信息可以看到,现在选中的调试堆栈块已经切换到viewDidLoad方法,再使用expression指令时就可以操作这个方法中的相关变量了。
在使用LLDB工具前,遇到这样的情况,我往往会采用打多个断点,一步步追溯代码的运行过程并检查过程中变量的值是否正确,调试起来并不十分方便,如果不小心错过了某个断点,又要重新开始,通过选择调试的frame堆栈块可以十分方便的解决这个问题。
与frame相关的还有一个指令十分有用,下面的指令可以打印出当前堆栈块中所有对象的内容:
1 | (lldb) frame variable |
variable后面也可以添加参数名来打印特定对象的内容:
1 | (lldb) frame variable a |
3.thread线程操作相关指令
上面提到过,程序运行中会有多个激活的线程,每个线程中又有许多堆栈块,frame相关指令用于综合调试各个堆栈块,thread指令则是用于综合调试各个线程。首先Xcode左侧导航区为我们列出的线程堆栈块并不是当前线程中的所有堆栈块,使用如下命令可以打印出当前线程的所有堆栈块:
1 | (lldb) thread backtrace |
thread list指令则可以打印出当前所有激活的线程,如下:
1 | (lldb) thread list |
thread info可以打印出当前选中调试的线程的信息:
1 | (lldb) thread info |
同样也可以使用thread select指令来切换选中调试的线程:
1 | (lldb) thread select 2 |
thread continue指令用于继续执行当前的线程:
1 | (lldb) thread continue |
4.disassemble代码反汇编相关指令
disassemble相关指令用于输出某段程序的汇编代码,执行disassemble指令将会反汇编当前函数:
1 | BreakPointTest`-[ViewController test]: |
使用disassemble -b则会输出带字节信息的汇编代码:
1 | (lldb) disassemble -b |
使用disassemble -c 指令可以设置输出汇编代码的行数,如下:
1 | (lldb) disassemble -c 5 |
使用disassemble -l只输出当前断点处汇编代码:
1 | BreakPointTest`-[ViewController test] + 37 at ViewController.m:30 |
使用disassemble -m混合显示汇编代码:
1 | (lldb) disassemble -m |
使用disassemble -p进行当前行的汇编代码输出:
1 | (lldb) disassemble -p |
5.其他LLDB常用指令
bt指令用于打印当前线程所有堆栈块信息:
1 | (lldb) bt |
c指令**继续运行线程和process continue**效果一样。
call指令**运行一个表达式,**和 expression 效果一样。
detach指令**结束当前调试的线程。**
di指令**反汇编当前函数与disassemble**相同。
exit指令**退出lldb调试器。**
finish指令**完成当前堆栈块的调试,**程序会继续运行。
n指令**进行单步调试,与next**作用一样。
p指令**与expression作用一样。**
print指令用于变量的打印。
r指令**重新运行应用程序。**
quit指令结束调试。
bugreport指令用于创建堆栈信息报告。
command history指令用于打印LLDB调试命令记录。
help指令用于查询LLDB相关调试指令的用法。
apropo指令用于查询某些包含某些关键字的指令。
version指令用于查询LLDB调试器的版本,如下:
1 | (lldb) version |
image list命令用于打印工程中所有用到的库文件。
image相关指令还有一个十分有用的命令,image lookup --address可以查询某个内存地址的内容,如下:
1 | (lldb) image lookup --address 0x000000010373e885 |
image lookup --type用于查询某种类型中包含的属性,如下:
1 | (lldb) image lookup --type UILabel |
x指令可以读取某段内存的二进制数据:
1 | (lldb) x 0x000000010373e885 |
LLDB的用法和技巧还有很多,它可以大大提高我们调试代码的效率,有疏漏和错误之处,还望与志同道合的朋友共同学习进步。
专注技术,热爱生活,交流技术,也做朋友。
——珲少 QQ群:203317592