前言
LLDB 简介
LLDB 是集成于 Xcode 的默认调试器,支持在桌面、iOS设备和模拟器上调试C、Objective-C和C++程序。它是一套开源调试器,提供诸如读取DWARF(一种调试信息格式)、分步执行、回溯跟踪等功能。掌握 LLDB 的使用可以极大的提高我们的调试效率
LLDB 与 GDB
在 Xcode4.2 以前 Xcode 编译器还是 GCC(前端) + LLVM(后端) 架构,此时调试器还是 GDB 的时代。在 Xcode4.2 Clang(前端) + LLVM3.0(后端) 成为了默认编译器,之后从 Xcode4.3 开始 LLDB 成为了 Xcode 的默认调试器。LLDB 官网有LLDB 与 GDB命令对照表
LLDB 支持平台
macOS desktop user space debugging for i386 and x86_64
iOS simulator debugging on i386 and x86_64
iOS device debugging on ARM and AArch64
Linux local user-space debugging for i386, x86_64 and PPC64le
FreeBSD local user-space debugging for i386 and x86_64
Windows local user-space debugging for i386 (*)
mac 环境以外使用 LLDB 需要配置环境,mac 环境下安装 Xcode 即可使用。本文仅探讨 Xcode 环境下 LLDB 的使用
语法
基本格式
<noun> <verb> [-options [value]] [argument [argument...]]
即:<关键字> <动作> [-可选项 [可选项的值]] [参数1 [参数2···]]
特别说明
-
关键字
、动作
、可选项
、参数
都是通过空格
进行分隔。参数
如果带有空格需要使用""
包裹参数 - 如果参数中包含有
\
或""
,那么使用\
对它们进行转义 - 如果参数以
-
开始,需要在可选项和参数之间使用--
加以分割 - 可选项顺序可以不是固定的
示例
(lldb) process launch --stop-at-entry -- -program_arg_1 value -program_arg_2 value
关键字:process
动作:launch
可选项:-stop-at-entry
参数:-program_arg_1 value、-program_arg_2 value
作用:启动程序并传入 `-program_arg_1 value、-program_arg_2 value 作为参数
(lldb) breakpoint set --file foo.c --line 12
关键字:breakpoint
动作:set
可选项:-file、-line
可选项的值:foo.c、12
作用:在文件 `foo.c` 的 `12` 行设置断点
(lldb) breakpoint set --name foo
关键字:breakpoint
动作:set
可选项:-name
可选项的值:foo
作用:为函数名为 `foo` 的函数设置断点
变量打印
大部分 iOS Coder 在实际开发中用得最多的 LLDB 命令可能就是 po
。po
的全称是print object
,它的作用是调用 NSObject 对象的 debugDescription 方法。而另一个常用命令 p
全称是 print
,作用是打印 NSObject 对象的地址或基本数据类型的值,并生成一个临时变量
(lldb) p testInt
(NSInteger) $9 = 5
(lldb) po testInt
5
// 另一个与 p 相同作用命令:
(lldb) expression -- testInt
(NSInteger) $7 = 5
(lldb) expression -o -- testInt
5
- 以特定格式打印变量:x(16进制)、c(字符)、t(二进制)
(lldb) p/x testInt
(NSInteger) $11 = 0x0000000000000005
(lldb) p/c testInt
(NSInteger) $12 = \x05\0\0\0\0\0\0\0
(lldb) p/t testInt
(NSInteger) $14 = 0b0000000000000000000000000000000000000000000000000000000000000101
- 声明变量
- 变量名必须以
$
符号开头
(lldb) expression NSString *$str = @"Hello World"
(lldb) po $str
Hello World
- 修改变量
(lldb) p testInt
(NSInteger) $15 = 5
(lldb) expression testInt = 100
(NSInteger) $16 = 100
(lldb) p testInt
(NSInteger) $17 = 100
断点调试
- 通过函数名设置断点
(lldb) breakpoint set --name "-[ViewController testMethod]"
(lldb) br s -n "-[ViewController testMethod]"
(lldb) b -[ViewController testMethod]
// 成功后:
Breakpoint 3: where = lldbTest2`-[ViewController testMethod] + 12 at ViewController.m:30:1, address = 0x000000010638161c
- 通过地址设置断点
(lldb) breakpoint set --address 0x0000000106216070
(lldb) br s -a 0x0000000106216070
// 成功后:
Breakpoint 2: where = lldbTest2`lldbTest2[0x0000000100003070], address = 0x000000010342e070
- 列出所有断点
(lldb) breakpoint list
(lldb) br l
- 删除断点
(lldb) breakpoint delete 1
(lldb) br del 1
流程控制
在调试过程中我们最常用的就是 Xcode 提供的调试工具,除断点控制以外,从左到右四个按钮分别是:continue
、step over
、step in
、step out
,在 LLDB 中存在命令与之对应
- continue
(lldb) process continue
(lldb) continue
(lldb) c
// 会取消程序的暂停,允许程序正常执行 (要么一直执行下去,要么到达下一个断点)
- step over
(lldb) thread step-over
(lldb) next
(lldb) n
// 会以黑盒的方式执行一行代码。如果所在这行代码是一个函数调用,那么就不会跳进这个函数,而是会执行这个函数,然后继续
- step in
(lldb) thread step-in
(lldb) step
(lldb) s
// 每执行一次上述命令断点会跳转到下一行源代码位置,如果下一行是一个函数调用,会进入函数调用内部
// 注:当前行不是函数调用时,next 和 step 效果是一样的
- step out
(lldb) thread step-out
(lldb) finish
// 如果你不小心跳进一个函数,但实际上你想跳过它,常见的反应是重复的运行 n 直到函数返回。但其实这种情况 step out 可以解决。它会继续执行到下一个返回语句 (直到一个堆栈帧结束) 然后再次停止
- 除此以外还有一个常用命令 thread return
thread return <Value>
// thread return 可以用来控制程序流程。它有一个可选参数,在执行时它会把可选参数加载进返回寄存器里,然后立刻执行返回命令,跳出当前栈帧
// 注:这会给ARC的引用计数造成一些问题,或者使函数内的清理部分失效。但是如果在函数的开头执行这个命令,可以非常好的隔离这个函数,伪造返回值
调试信息
- 查看当前调用方法和行数
- frame info / fr i
(lldb) frame info
frame #0: 0x000000010a560522 lldbTest2`-[ViewController viewDidLoad](self=0x00007fb9ca42b940, _cmd="viewDidLoad") at ViewController.m:25:21
- 查看当前方法和所有变量
- frame variable / fr v
(lldb) frame variable
(ViewController *) self = 0x00007fb9ca42b940
(SEL) _cmd = "viewDidLoad"
(NSInteger) testInt = 5
- 查看当前调试线程、行数、和源码信息
- thread info
(lldb) thread info
thread #1: tid = 0x6f66ea, 0x000000010a560522 lldbTest2`-[ViewController viewDidLoad](self=0x00007fb9ca42b940, _cmd="viewDidLoad") at ViewController.m:25:21, queue = 'com.apple.main-thread', stop reason = step out
- 查看当前所有线程状态
- thread list
(lldb) thread list
Process 81659 stopped
* thread #1: tid = 0x6f66ea, 0x000000010a560522 lldbTest2`-[ViewController viewDidLoad](self=0x00007fb9ca42b940, _cmd="viewDidLoad") at ViewController.m:25:21, queue = 'com.apple.main-thread', stop reason = step out
thread #3: tid = 0x6f6710, 0x000000010d48fbfe libsystem_kernel.dylib`__workq_kernreturn + 10
thread #4: tid = 0x6f6712, 0x000000010d48fbfe libsystem_kernel.dylib`__workq_kernreturn + 10
thread #6: tid = 0x6f6717, 0x000000010d48e22a libsystem_kernel.dylib`mach_msg_trap + 10, name = 'com.apple.uikit.eventfetch-thread'
- 当前线程堆栈回溯信息
- thread backtrace / bt
(lldb) thread backtrace
* thread #1, queue = 'com.apple.main-thread', stop reason = step out
* frame #0: 0x000000010a560522 lldbTest2`-[ViewController viewDidLoad](self=0x00007fb9ca42b940, _cmd="viewDidLoad") at ViewController.m:25:21
frame #1: 0x000000010dffd43b UIKitCore`-[UIViewController loadViewIfRequired] + 1183
frame #2: 0x000000010dffd868 UIKitCore`-[UIViewController view] + 27
frame #3: 0x000000010e635c33 UIKitCore`-[UIWindow addRootViewControllerViewIfPossible] + 122
frame #4: 0x000000010e636327 UIKitCore`-[UIWindow _setHidden:forced:] + 289
frame #5: 0x000000012795fbbd UIKit`-[UIWindowAccessibility _orderFrontWithoutMakingKey] + 86
……
- 列出当前所有可执行文件
- image list / im li
(lldb) image list
……
- 查找函数
- image lookup --name <function-or-symbol> / im loo -r -n <function-or-symbol>
(lldb) image lookup --name testMethod
1 match found in /Users/ryanyuan/Library/Developer/Xcode/DerivedData/lldbTest2-fpaxelkuicxzoafqzpwnnjimxgbk/Build/Products/Debug-iphonesimulator/lldbTest2.app/lldbTest2:
Address: lldbTest2[0x00000001000015c0] (lldbTest2.__TEXT.__text + 272)
Summary: lldbTest2`-[ViewController testMethod] at ViewController.m:28
- 根据地址查找函数
- image lookup --address <address-expression> / im loo -a <address-expression>
(lldb) image lookup --address 0x0000000105212536
Address: lldbTest2[0x0000000100001536] (lldbTest2.__TEXT.__text + 582)
Summary: lldbTest2`-[ViewController testMethod] + 310 at ViewController.m:30:5
// 该方法可以用来从 crash log 中定位函数,后续博客会更新相关内容,待续...
观察者
在调试过程中有时没办法判断断点的具体位置,需要在某个值发生变化时触发断点调试,这个时候就需要用到 LLDB 中的 watchpoint
- 设置观察者,当变量被修改时触发
- watchpoint set variable <Value> / wa s v <Value>
(lldb) watchpoint set variable testInt
Watchpoint created: Watchpoint 1: addr = 0x7ffee7f94878 size = 8 state = enabled type = w
declare @ '/Users/ryanyuan/Desktop/lldbTest2/lldbTest2/ViewController.m:23'
watchpoint spec = 'testInt'
new value: 5
// 拦截到变化后
Watchpoint 1 hit:
old value: 5
new value: 0
- 根据内存地址设置观察者
- watchpoint set expression -- <Address> / wa s e -- <Address>
(lldb) watchpoint set expression -- 0x000000010f46c070
Watchpoint created: Watchpoint 1: addr = 0x10f46c070 size = 8 state = enabled type = w
new value: 4574177224
- 当前所有观察者列表
- watchpoint list / watch l
(lldb) watchpoint list
Number of supported hardware watchpoints: 4
Current watchpoints:
Watchpoint 1: addr = 0x7ffeeabfa878 size = 8 state = enabled type = w
declare @ '/Users/ryanyuan/Desktop/lldbTest2/lldbTest2/ViewController.m:23'
watchpoint spec = 'testInt'
new value: 0
Watchpoint 2: addr = 0x10f46c070 size = 8 state = enabled type = w
new value: -620567661233521176
condition = 'testInt == 2'
- 设置带有条件的观察者,当被修改的变量满足条件时触发
- watchpoint modify -c '<condition>'
(lldb) watchpoint modify -c 'testInt == 2'
(lldb) watchpoint list
Number of supported hardware watchpoints: 4
Current watchpoints:
Watchpoint 1: addr = 0x7ffeeabfa878 size = 8 state = enabled type = w
declare @ '/Users/ryanyuan/Desktop/lldbTest2/lldbTest2/ViewController.m:23'
watchpoint spec = 'testInt'
new value: 0
Watchpoint 2: addr = 0x10f46c070 size = 8 state = enabled type = w
new value: -620567661233521176
condition = 'testInt == 2'
- 删除观察者
- watchpoint delete <WatchpointNumber> / watch del <WatchpointNumber>
(lldb) watchpoint delete 2
1 watchpoints deleted.
帮助
在调试过程中最常用的指令应该是 help
,对于任何指令我们都可以使用 help <关键字> <动作>
查看详细的描述和参数,经常使用 help
可以让我们了解更多的调试方式,对于 LLDB 的使用也会更加灵活