前言
LLDB是我们平时调试中使用最多的工具之一,
p
或者po
是使用最多的指令。除了p
和po
之外,还有什么指令可以使用呢?今天再次来提升一下对LLDB的使用吧。
一、LLDB
LLDB是个开源的内置于XCode的具有REPL(read-eval-print-loop)特征的Debugger,其可以安装C++或者Python插件。在日常的开发和调试过程中给开发人员带来了非常多的帮助。
二、设置断点(breakPoint set)
# breakpoint 设置断点指令
# set 是子指令,表示设置
# -n 是选项,是name的缩写。
# XXX 方法名称
(lldb) breakpoint set -n “XXX” “XXX”
# 举个栗子🌰
# 可以这么用,给名字为“viewDidLoad”的方法添加断点
(lldb) breakpoint set -n "viewDidLoad"
# 还可以这么用,给控制器“ViewController”中的“viewDidLoad”方法添加断点
(lldb) breakpoint set -n "-[ViewController viewDidLoad]"
试试
(lldb) breakpoint set -n "viewDidLoad" #设置断点
Breakpoint 1: 104 locations. #成功设置一组104个断点
(lldb) breakpoint list #查看所有断点
Current breakpoints: #这里打印了所有有关viewDidLoad的断点。
1: name = 'viewDidLoad', locations = 104, resolved = 104, hit count = 0
1.1: where = LLDB`-[ViewController viewDidLoad] + 20 at ViewController.m:18, address = 0x000000010028b764, resolved, hit count = 0
1.2: where = DocumentManager`-[UIDocumentBrowserViewController viewDidLoad], address = 0x0000000103921e4d, resolved, hit count = 0
1.3: where = DocumentManager`-[DOCInfoViewController viewDidLoad], address = 0x000000010392c7da, resolved, hit count = 0
....
# 给控制器“ViewController”中的“viewDidLoad”方法添加断点
(lldb) breakpoint set -n "-[ViewController viewDidLoad]"
# 成功设置断点 位置是ViewController viewDidLoad 中 第18行的位置
Breakpoint 1: where = LLDB`-[ViewController viewDidLoad] + 20 at ViewController.m:18, address = 0x00000001024c7764
# 查看所有断点信息
(lldb) breakpoint list
Current breakpoints:
1: name = '-[ViewController viewDidLoad]', locations = 1, resolved = 1, hit count = 0
1.1: where = LLDB`-[ViewController viewDidLoad] + 20 at ViewController.m:18, address = 0x00000001024c7764, resolved, hit count = 0
还有没有其他设置断点的方式呢?列举几个常用的。
- 给所有名为xx的函数设置一个断点
# breakpoint 设置断点指令
# set 是子指令,表示设置
# -name 是选项,名称的意思
# xx 方法名称
(lldb) breakpoint set -name xx #完整写法
(lldb) br s -n xx #简单缩写
(lldb) b xx #变态缩写
- 在文件F指定行L设置断点
# breakpoint 设置断点指令
# set 是子指令,表示设置
# -file 是子指令,表示文件
# F 表示文件的名称
# -line 表示行号
(lldb) breakpoint set -file F -line L #完整写法
(lldb) br s -f F -l L #简单缩写
(lldb) b F:L #变态缩写
- 给所有名为xx的C++函数设置一个断点(希望没有同名的C函数)
# breakpoint 设置断点指令
# set 是子指令,表示设置
# -method 是子指令,表示方法名
# xx 方法名称
(lldb) breakpoint set -method xx #完整写法
(lldb) br s -M xx #简单缩写 没有变态缩写
- 给一个OC函数[objc msgSend:]设置一个断点
# breakpoint 设置断点指令
# set 是子指令,表示设置
# -name 是子指令,表示文件
# objc 对象
# msgSend 方法名称
(lldb) breakpoint set -name “[objc msgSend:]” #完整写法
(lldb) b -n “[objc msgSend:]” #简单缩写
- 给所有名为xx的OC方法设置一个断点(希望没有名为xx的C或者C++函数)
# breakpoint 设置断点指令
# set 是子指令,表示设置
# -selector 是子指令,表示选择器
# xx 表示方法名称
(lldb) breakpoint set -selector xx
(lldb) br s -S count
- 给所有函数名正则匹配成功的函数设置一个断点
(lldb) breakpoint set --func-regex regular-expression
(lldb) br s -r regular-expression
- 给指定函数地址func_addr的位置设置一个断点
(lldb) br set -a func_addr
- 断点查看
(lldb) breakpoint list
(lldb) br l
- 断点删除
(lldb) breakpoint delete index
(lldb) br del index
- 禁用断点
# index 为断点编号,可以通过 ‘breakpoint list’查看编号
(lldb)breakpoint disable index
- 开启断点
# index 为断点编号,可以通过 ‘breakpoint list’查看编号
(lldb)breakpoint enable index
二、断点的流程控制
设置好断点后怎么控制前进、下一步呢?
Xcode已经为我们提供了可视化的工具,但是如果你习惯了命令行操作不希望双手离开键盘降低你的效率,了解一下也是很有帮助的。
- 继续
(lldb) process continue # 完整写法
(lldb) continue # 简单缩写
(lldb) c # 变态缩写
- 下一步
(lldb) thread step-over # 完整写法
(lldb) next # 简单缩写
(lldb) n # 变态缩写
- 进入 ,进入主要是进入函数中查看相关代码或者汇编代码
(lldb) thread step-in # 完整写法
(lldb) step # 简单缩写
(lldb) s # 变态缩写
- 跳出 和进入相反
(lldb) thread step-out
(lldb) finish
(lldb) f
三、hook概念(stop-hook )
-
target stop-hook
我们知道,用LLDB debug,大多数时候需要让程序stop。
target stop-hook
命令就是让你可以在每次stop的时候去执行一些命令
target stop-hook
只对breakpoint和watchpoint的程序stop生效,直接点击Xcode上的pause或者debug view hierarchy不会生效
假如我们想在每次程序stop的时候,都用命令打印当前view。我们可以添加一个stop-hook:
(lldb) target stop-hook add -o "frame variable"
Stop hook #4 added.
-
target stop-hook add & display
target stop-hook add
表示添加stop-hook,-o的全称是--one-liner,表示添加一条命令。
- Hook 1 (frame variable)
(ViewController *) self = 0x00007fd55b12e380
(SEL) _cmd = "viewDidLoad"
(NSMutableURLRequest *) request = 0x00007fd55b1010c0
在程序stop的时候,他会自动执行frame variable
,打印出了所有的变量。
e.g: 下面2行代码效果相同
(lldb) target stop-hook add -o "p self.view"
(lldb) display self.view
-
target stop-hook list
当添加完stop-hook之后,我们想看当前所有的stop-hook怎么办呢?使用stop-hook list
(lldb) target stop-hook list
Hook: 4
State: enabled
Commands:
frame variable
Hook: 5
State: enabled
Commands:
expression self.view
Hook: 6
State: enabled
Commands:
expr -- self.view
我们可以看到,我们添加了4个stop-hook,每个stop-hook都有一个id,他们分别是4,5,6
-
target stop-hook delete & undisplay
有添加的命令,当然也就有删除的命令。使用target stop-hook delete可以删除stop-hook,如果你觉得这个命令有点长,懒得敲。你也可以用undisplay
(lldb) target stop-hook delete 4
(lldb) undisplay 5
我们用target stop-hook delete和undisplay分别删除了id为4和5的stop-hook
-
target stop-hook disable/enable
当我们暂时想让某个stop-hook失效的时候,可以使用target stop-hook disable
(lldb) target stop-hook disable 8
如果我们想让所有的stop-hook失效,只需不传入stop-hookid即可:
(lldb) target stop-hook disable
有disable就有enable,我们又想让stop-hook生效了。可以使用target stop-hook enable
(lldb) target stop-hook enable 8
同理,不传入参数表示让所有stop-hook生效
(lldb) target stop-hook enable
四、image
当我们有一个地址,想查找这个地址具体对应的文件位置,可以使用image lookup --address,简写为image lookup -a e.g: 当我们发生一个crash。
2015-12-17 14:51:06.301 TLLDB[25086:246169] *** Terminating app due to uncaught exception ‘NSRangeException‘, reason: ‘*** -[__NSArray0 objectAtIndex:]: index 1 beyond bounds for empty NSArray‘
*** First throw call stack:
(
0 CoreFoundation 0x000000010accde65 __exceptionPreprocess + 165
1 libobjc.A.dylib 0x000000010a746deb objc_exception_throw + 48
2 CoreFoundation 0x000000010ac7c395 -[__NSArray0 objectAtIndex:] + 101
3 TLLDB 0x000000010a1c3e36 -[ViewController viewDidLoad] + 86
4 UIKit 0x000000010b210f98 -[UIViewController loadViewIfRequired] + 1198
5 UIKit 0x000000010b2112e7 -[UIViewController view] + 27
我们可以看到是由于-[__NSArray0 objectAtIndex:]:超出边界而导致的crash,但是objectAtIndex:的代码到底在哪儿呢?
# -a是-address的缩写
(lldb) image lookup -a 0x000000010a1c3e36
Address: TLLDB[0x0000000100000e36] (TLLDB.__TEXT.__text + 246)
Summary: TLLDB`-[ViewController viewDidLoad] + 86 at ViewController.m:32
根据0x000000010a1c3e36 -[ViewController viewDidLoad]里面的地址,使用image lookup -address查找,我们可以看到代码位置在ViewController.m里面的32行。
- 还可以通过image查看类信息
(lldb) image lookup -t Persen
1 match found in /Users/chian/Library/Developer/Xcode/DerivedData/LLDB-culbvnuqlgmqubfafrwfnblamkly/Build/Products/Debug-iphonesimulator/LLDB.app/LLDB:
id = {0x10000002b}, name = "Persen", byte-size = 24, decl = ViewController.h:11, compiler_type = "@interface Persen : NSObject{
int _age;
NSString * _name;
}
@property ( getter = name,setter = setName:,readwrite,nonatomic ) NSString * name;
@property ( getter = age,setter = setAge:,assign,readwrite,nonatomic ) int age;
@end
- 还可以通过
(lldb) image lookup -t "method name"
来定位方法位置。
(lldb) image lookup -n test1
1 match found in /Users/chian/Library/Developer/Xcode/DerivedData/LLDB-culbvnuqlgmqubfafrwfnblamkly/Build/Products/Debug-iphonesimulator/LLDB.app/LLDB:
Address: LLDB[0x00000001000014e0] (LLDB.__TEXT.__text + 304)
Summary: LLDB`-[ViewController test1] at ViewController.m:25
可以看到LLDB定位到test1在第25行
通过以上的命令完全可以看不出image到底是干嘛的!😝
所以暂时只能记得其拥有特殊功效。
- 通过help可以看到更多命令,描述很详细的解释了各个命令的功效。想要了解每个功能,还需要平时多加练习和记忆。
(lldb) help image
# 用于访问一个或多个目标模块信息的命令。
Commands for accessing information for one or more target modules.
# 语法:形象
Syntax: target modules <sub-command> ...
# “image”是“target modules”的缩写
The following subcommands are supported:
add -- Add a new module to the current target's modules.
dump -- Commands for dumping information about one or more target
modules.
list -- List current executable and dependent shared library
images.
load -- Set the load addresses for one or more sections in a
target module.
lookup -- Look up information within executable and dependent
shared library images.
search-paths -- Commands for managing module search paths for a target.
show-unwind -- Show synthesized unwind instructions for a function.
For more help on any particular subcommand, type 'help <command> <subcommand>'.
五、register
register
指令能够获取和修改各个寄存器的信息。
官方的解释是这样的:
# 访问当前线程和堆栈帧寄存器的命令。
Commands to access registers for the current thread and stack frame.
# 语法:寄存器[读|写]…
Syntax: register [read|write] ...
# 支持下列子命令:
The following subcommands are supported:
# 从当前帧。如果没有指定寄存器,则将它们全部转储。
read -- Dump the contents of one or more register values from the
current frame. If no register is specified, dumps them all.
# 修改单个寄存器值。
write -- Modify a single register value.
# 要获得任何特定子命令的更多帮助,请键入“help <command> <subcommand>”。
For more help on any particular subcommand, type 'help <command> <subcommand>'.
我们可以通过register read
来获取当前断点中寄存器中的内容:
(lldb) register read
General Purpose Registers:
rax = 0x0000000108340cd8 (void *)0x0000000108340d28: __NSSetM
rbx = 0x000000010763e010 libobjc.A.dylib`objc_release
rcx = 0x82c1b42ba17f0088
rdx = 0x82c1b42ba17f0088
rdi = 0x00006000035be140
rsi = 0x0000000000000001
rbp = 0x00007ffee8ef0120
rsp = 0x00007ffee8ef0120
r8 = 0x000000000000003f
r9 = 0x00006000035be140
r10 = 0x00000000000007fb
r11 = 0x00007f82b08240f0
r12 = 0x000000010b48d44d "touchesBegan:withEvent:"
r13 = 0x00006000035be140
r14 = 0x00007f82b050bf90
r15 = 0x00007ffee8ef0130
rip = 0x000000010763ed06 libobjc.A.dylib`objc_object::sidetable_release(bool) + 4
rflags = 0x0000000000000202
cs = 0x000000000000002b
fs = 0x0000000000000000
gs = 0x0000000000000000
具体怎么用我还没学到😆。见谅见谅
六、expression 指令
expression
命令是执行一个表达式,并将表达式返回的结果输出,是LLDB调试命令中最重要的命令,也是我们常用的p
和 po
命令的 鼻祖。
他主要有2个功能:
- 执行表达式
(lldb) expression self.view.backgroundColor = UIColor.yellowColor
(UICachedDeviceRGBColor *) $2 = 0x000060000208c700 // 修改值
(lldb) expression [CATransaction flush] // 直接刷新界面
- 输出返回值
(lldb) expression self.view
(UIView *) $3 = 0x00007f82b040c670
- 衍生出的扩展命令
p
print
e
call
po
其实都是expression的别名,甚至我们还可以自己设置别名。这里不展开描述了。
七、frame 参数检查
-
frame
- frame 官方解释
(lldb) help frame
# 用于选择和检查当前线程堆栈帧的命令。
Commands for selecting and examing the current thread's stack frames.
# 语法:frame <subcommand> [<subcommand-options>]
Syntax: frame <subcommand> [<subcommand-options>]
The following subcommands are supported:
支持下列子命令:
# info——列出关于当前堆栈框架的信息线程的索引
info -- List information about the current stack frame in the current
thread.
# 选择——选择当前堆栈帧当前线程(参见“线程回溯”)。
select -- Select the current stack frame by index from within the
current thread (see 'thread backtrace'.)
# 变量——显示当前堆栈框架的变量。默认为所有作用域中的参数
和局部变量。参数的名字,可以是本地、文件静态和文件全局变量指定。
可以指定聚合变量的子变量如“var - > child.x”。
variable -- Show variables for the current stack frame. Defaults to all
arguments and local variables in scope. Names of argument,
local, file static and file global variables can be
specified. Children of aggregate variables can be specified
such as 'var->child.x'.
For more help on any particular subcommand, type 'help <command> <subcommand>'.
frame 框架的意思,但它应解释成(帧)它能显示堆栈信息,
我们在控制台上输入命令bt,可以打印出来所有的frame。如果仔细观察,这些frame和左边红框里的堆栈是一致的。平时我们看到的左边的堆栈就是frame。
-
frame variable
平时Debug的时候我们经常做的事就是查看变量的值,通过frame variable命令,可以打印出当前frame的所有变量
(lldb) frame variable
(ViewController *) self = 0x00007fa158526e60
(SEL) _cmd = "text:"
(BOOL) ret = YES
(int) a = 3
可以看到,他将self,_cmd,ret,a等本地变量都打印了出来
如果我们要需要打印指定变量,也可以给frame variable传入参数:
(lldb) frame variable self->_string
(NSString *) self->_string = nil
不过frame variable只接受变量作为参数,不接受表达式,也就是说我们无法使用frame variable self.string,因为self.string是调用string的getter方法。所以一般打印指定变量,我更喜欢用p或者po。
-
frame info
frame info: 查看当前frame的信息
(lldb) frame info
frame #0: 0x0000000101bf87d5 TLLDB`-[ViewController text:](self=0x00007fa158526e60, _cmd="text:", ret=YES) + 37 at
frame select: 选择某个frame
(lldb) frame select 1
frame #1: 0x0000000101bf872e TLLDB`-[ViewController viewDidLoad](self=0x00007fa158526e60, _cmd="viewDidLoad") + 78 at ViewController.m:23
20
21 - (void)viewDidLoad {
22 [super viewDidLoad];
-> 23 [self text:YES];
24 NSLog(@"1");
25 NSLog(@"2");
26 NSLog(@"3");
当我们选择frame 1的时候,他会把frame1的信息和代码打印出来。不过一般我都是直接在Xcode左边点击某个frame,这样更方便
八、watchpoint
-
watchpoint
breakpoint有一个孪生兄弟watchpoint。如果说breakpoint是对方法生效的断点,watchpoint就是对地址生效的断点。
官方解释:
(lldb) help watchpoint
Commands for operating on watchpoints.
Syntax: watchpoint <subcommand> [<command-options>]
The following subcommands are supported:
command -- Commands for adding, removing and examining LLDB commands
executed when the watchpoint is hit (watchpoint 'commmands').
delete -- Delete the specified watchpoint(s). If no watchpoints are
specified, delete them all.
disable -- Disable the specified watchpoint(s) without removing it/them.
If no watchpoints are specified, disable them all.
enable -- Enable the specified disabled watchpoint(s). If no watchpoints
are specified, enable all of them.
ignore -- Set ignore count on the specified watchpoint(s). If no
watchpoints are specified, set them all.
list -- List all watchpoints at configurable levels of detail.
modify -- Modify the options on a watchpoint or set of watchpoints in
the executable. If no watchpoint is specified, act on the
last created watchpoint. Passing an empty argument clears the
modification.
set -- Commands for setting a watchpoint.
For more help on any particular subcommand, type 'help <command> <subcommand>'.
如果我们想要知道某个属性什么时候被篡改了,我们该怎么办呢?有人可能会说对setter方法打个断点不就行了么?但是如果更改的时候没调用setter方法呢? 这时候最好的办法就是用watchpoint。我们可以用他观察这个属性的地址。如果地址里面的东西改变了,就让程序中断。
watchpoint set命令用于添加一个watchpoint。只要这个地址中的内容变化了,程序就会中断。
- watchpoint set variable
通过这个命令可以为array对象设置观察点
(lldb) watchpoint set variable array
Watchpoint created: Watchpoint 1: addr = 0x7ffee04500e0 size = 8 state = enabled type = w
declare @ '/Users/chian/Desktop/LLDB/LLDB/ViewController.m:30'
watchpoint spec = 'array'
new value: 0x0000600002474540
watchpoint set variable传入的是变量名。需要注意的是,这里不接受方法。
设置成功后可以通过watchpoint list
可以查看到所有被标记的watchpoint。
当我执行p array = @[@"a",@"b"]
修改array后,watchpoint打印出了我旧值和新值。
# 查看所有观察点watchpoint
(lldb) watchpoint list
Number of supported hardware watchpoints: 4
Current watchpoints:
Watchpoint 1: addr = 0x7ffee04500e0 size = 8 state = enabled type = w
declare @ '/Users/chian/Desktop/LLDB/LLDB/ViewController.m:30'
watchpoint spec = 'array'
new value: 0x0000600002474540
# 修改变量值
(lldb) p array = @[@"a",@"b"]
Watchpoint 1 hit:
old value: 0x0000600002474540
new value: 0x000060000247fb40
error: Execution was interrupted, reason: watchpoint 1.
The process has been returned to the state before expression evaluation.
# 打印地址看到真实数据
(lldb) po 0x000060000247fb40
<__NSArrayI 0x60000247fb40>(
a,
b
)
除了以上指令之外,watchpoint当然也有和breakpoint一样的指令。
例如:
watchpoint disable
watchpoint enable
watchpoint delete
九 、thread
-
thread backtrace & bt
有时候我们想要了解线程堆栈信息,可以使用thread backtrace
thread backtrace
作用是将线程的堆栈打印出来。
e.g: 当发生crash的时候,我们可以使用thread backtrace
(线程回溯)查看堆栈调用 。
bt
则是 backtrace 的缩写,输入bt
等同于thread backtrace
-
thread return
Debug的时候,也许会因为各种原因,我们不想让代码执行某个方法,或者要直接返回一个想要的值。这时候就该thread return上场了
thread return可以接受一个表达式,调用命令之后直接从当前的frame返回表达式的值。
结语
以上篇幅介绍的只是冰山一角。希望这篇文章能够给大家一些帮助,来更多的了解LLDB