一、前言
1、简介
LLDB是新一代高性能调试器。它构建为一组可重用的组件,可以高度利用较大的LLVM项目中的现有库,例如Clang表达式解析器和LLVM反汇编程序。LLDB是Mac OS X上Xcode的默认调试器,支持在桌面和iOS设备和模拟器上调试C,Objective-C和C ++。LLDB项目中的所有代码都是在标准LLVM许可证下提供的,这是一种开源的“BSD风格”许可证。
对于开发测试同学来说,学习iOS代码调试LLDB命令,能更好地辅助我们通过各种手段如修改变量返回值创造实际难以模拟的环境进行测试,甚至能协助开发同学定位bug。下面小编来分享一些平时常用到的iOS代码调试LLDB命令,希望能对做iOS测试的同学有所帮助。
2、指令格式
LLDB命令的各部分由空格分割,如果参数中包含空格,则需要使用双引号括起参数,如果参数中包含双引号或者反斜杠,则需要使用反斜杠进行转义。
<command> [<subcommand> [<subcommand>...]] <action> [-options [option-
value]] [argument [argument...]]
- command:命令
- subcommand:子命令
- action:命令操作
- options:命令选项
- argument:命令参数
- 比如给test函数设置断点:
breakpoint set -n test
- breakpoint 是命令
- set 是命令操作
- -n 是命令选项
- test 是命令参数
二、常用指令
1、help(帮助)
LLDB命令有非常多的功能,完全背下来不太容易,也没必要。开发者可以使用help命令查看相关命令的用法,甚至可以查看help命令的用法。
所有命令可以通过help <command> [<subcommand>]
查看详细文档。比如help breakpoint
、help breakpoint set
(lldb) help breakpoint
Commands for operating on breakpoints (see 'help b' for shorthand.)
Syntax: breakpoint <subcommand> [<command-options>]
The following subcommands are supported:
clear -- Delete or disable breakpoints matching the specified source
file and line.
command -- Commands for adding, removing and listing LLDB commands
executed when a breakpoint is hit.
delete -- Delete the specified breakpoint(s). If no breakpoints are
specified, delete them all.
disable -- Disable the specified breakpoint(s) without deleting them. If
none are specified, disable all breakpoints.
enable -- Enable the specified disabled breakpoint(s). If no breakpoints
are specified, enable all of them.
list -- List some or all breakpoints at configurable levels of detail.
modify -- Modify the options on a breakpoint or set of breakpoints in
the executable. If no breakpoint is specified, acts on the
last created breakpoint. With the exception of -e, -d and -I,
passing an empty argument clears the modification.
name -- Commands to manage name tags for breakpoints
read -- Read and set the breakpoints previously saved to a file with
"breakpoint write".
set -- Sets a breakpoint or set of breakpoints in the executable.
write -- Write the breakpoints listed to a file that can be read in
with "breakpoint read". If given no arguments, writes all
breakpoints.
For more help on any particular subcommand, type 'help <command> <subcommand>'.
(lldb)
2、p和po(打印)
p
命令: print
命令的简写,使用p 命令可以查看基本数据类型的值;如果使用p命令查看的是对象,那么只会返回对象的指针地址。 p命令后面除了可以接变量、常量,还可以接表达式。
po
命令:print object
的缩写,可以理解为打印对象。功能与p命令类似,也可以打印常量、变量,打印表达式返回的对象等。p 和 po 的区别在于使用 po 只会输出对应的值,而 p 则会返回值的类型以及命令结果的引用名。
在输出结果中有类似于1这样的符号,它是指向对象的一个引用,在控制面板中可以直接使用这个符号来操作对应的对象,它们存在于LLDB的全名空间中,目的是为了辅助调试。$后面的数值是递增的,每打印一个与对象相关的命令,这个值都会加1。打印对象除了以上命令外,也可以在控制台左侧区域,点击变量右键点击 Print Deion of “xxx”,或者选中变量后,点击下边栏的i按钮,即可在控制台区看到打印结果。
另外,开发者可以按照print/的语法为print命令指定打印格式:
p //!< 默认打印十进制
p/x //!< 以十六进制打印整数
p/d //!< 以带符号的十进制打印整数
p/u //!< 以无符号的十进制打印整数
p/o //!< 以八进制打印整数
p/t //!< 以二进制打印整数
p/a //!< 以十六进制打印地址
p/c //!< 打印字符常量
p/f //!< 打印浮点数
p/s //!< 打印字符串
p/r //!< 格式化打印
3、expression(执行表达式)
expression
命令是调试过程中最有价值有命令了,既可以打印值也可以修改值,简写为expr
或者e
。它能够在调试时,动态的修改变量的值,同时打印出结果,在调试想要让应用执行异常路径(如执行某else情况)或者修改某些中间变量值如(如修改返回状态码以查看客户端相关响应等)非常有用,可以创造各种实际中难以遇到的测试环境辅助测试。用法举例如下图:
expression命令是动态修改变量的值,Xcode还支持动态调用函数。在控制台执行call命令,可以在不修改代码,不重新编译的情况下,在断点调用某个方法,并输出此方法的返回值。
expression、expression --和指令print、p、call的效果一样
expression -O --和指令po的效果一样
4、thread和frame(线程和栈帧)
在进程停止后,LLDB会选择一个当前线程和线程中当前帧(frame)。很多检测状态的命令可以用于这个线程或帧。
1)thread list (列出所有线程)
用于列出所有线程,如下所示,其中星号(*)表示thread#1
为当前线程。
(lldb) thread list
Process 18671 stopped
* thread #1: tid = 0x5fe79, 0x000000010a4f3e28 LLDBTest`-[ViewController viewDidLoad](self=0x00007fb21c1091f0, _cmd="viewDidLoad") at ViewController.m:21:18, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
thread #2: tid = 0x5ff6c, 0x00007fff5e73c458 libsystem_pthread.dylib`start_wqthread
thread #3: tid = 0x5ff6d, 0x00007fff5e70253e libsystem_kernel.dylib`__workq_kernreturn + 10
thread #4: tid = 0x5ff6e, 0x00007fff5e73c458 libsystem_pthread.dylib`start_wqthread
thread #5: tid = 0x5ff6f, 0x00007fff5e73c458 libsystem_pthread.dylib`start_wqthread
thread #6: tid = 0x5ff70, 0x00007fff5e700e7e libsystem_kernel.dylib`mach_msg_trap + 10, name = 'com.apple.uikit.eventfetch-thread'
thread #7: tid = 0x5ff71, 0x00007fff5e70253e libsystem_kernel.dylib`__workq_kernreturn + 10
thread #8: tid = 0x5ff72, 0x00007fff5e73c458 libsystem_pthread.dylib`start_wqthread
thread #9: tid = 0x5ff74, 0x00007fff5e70253e libsystem_kernel.dylib`__workq_kernreturn + 10
(lldb)
2)thread backtrace(堆栈打印,简写bt)
bt命令可以打印出线程的堆栈信息,bt命令是打印当前线程的堆栈信息,如下图所示。该信息比左侧的Debug Navigator 看到的还要详细一些。如果嫌堆栈打印太长,可以加一个值限制,如bt 10
。bt all
命令可以打印所有线程的堆栈信息。
3)thread return(跳出当前方法的执行)
thread return
主要用于控制程序流程,如想要直接跳过执行某个方法,或者直接让某方法返回一个想要的值,thread return后有一个可选参数,在执行时它会把可选参数加载进返回寄存器里,然后立刻执行返回命令,跳出当前栈帧(如:numberOfSectionsInTableView:方法直接thread return 10,就可以直接跳过方法执行,返回10)。
4)frame variable(查看帧变量)
frame variable
命令显示当前帧的变量,默认输出当前帧所有参数和所有局部变量,如下所示:
(lldb) frame variable
(ViewController *) self = 0x00007fd09f405ed0
(SEL) _cmd = "viewDidLoad"
(__NSArrayI *) temArr = 0x0000600002094ff0 @"3 elements"
(lldb)
5)frame variable(流程控制)
- thread continue̵/continue̵/c: 程序继续运行
- thread step-over/next/n:单步运行(源码),不会进入子函数
- thread step-in/step/s: 单步运行(源码),会进入子函数
- thread step-out/finish: 直接执行完当前函数的所有代码,返回到调用的地方
- thread return [value]/thread r:让当前函数直接返回,不执行当前断点后面代码
- thread step-inst-over/nexti/ni:单步运行(汇编),不会进入子函数
- thread step-inst/stepi/si:单步运行(汇编),会进入子函数
前面四个对应Xcode工具栏的前四个:
5、breakpoint(断点)
1)breakpoint set(设置断点,可以简写为br
)
breakpoint set -a 函数地址
breakpoint set -n 函数符号
(lldb) breakpoint set -a 0x000000010189bde7
Breakpoint 3: where = LLDBTest`-[ViewController test] + 23 at ViewController.m:26:5, address = 0x000000010189bde7
(lldb) breakpoint set -n "-[ViewController viewDidLoad]"
Breakpoint 4: where = LLDBTest`-[ViewController viewDidLoad] + 30 at ViewController.m:17:5, address = 0x000000010189bcfe
(lldb) breakpoint set -n touchesBegan:withEvent:
Breakpoint 5: 97 locations.
(lldb)
2)breakpoint list(列出所有的断点)
下面有4个断点,每个断点都有一个整数编号
(lldb) breakpoint list
Current breakpoints:
1: file = '/Users/zhangjh48/Desktop/LLDBTest/LLDBTest/ViewController.m', line = 21, exact_match = 0, locations = 1, resolved = 1, hit count = 1
1.1: where = LLDBTest`-[ViewController viewDidLoad] + 152 at ViewController.m:21:18, address = 0x000000010189bd78, resolved, hit count = 1
3: address = LLDBTest[0x0000000100001de7], locations = 1, resolved = 1, hit count = 0
3.1: where = LLDBTest`-[ViewController test] + 23 at ViewController.m:26:5, address = 0x000000010189bde7, resolved, hit count = 0
4: name = '-[ViewController viewDidLoad]', locations = 1, resolved = 1, hit count = 0
4.1: where = LLDBTest`-[ViewController viewDidLoad] + 30 at ViewController.m:17:5, address = 0x000000010189bcfe, resolved, hit count = 0
6: file = '/Users/zhangjh48/Desktop/LLDBTest/LLDBTest/ViewController.m', line = 30, exact_match = 0, locations = 1, resolved = 1, hit count = 1
6.1: where = LLDBTest`-[ViewController touchesBegan:withEvent:] + 70 at ViewController.m:30:6, address = 0x000000010189be46, resolved, hit count = 1
(lldb)
3)设置断点状态
-
breakpoint disable 断点编号
: 禁用断点 -
breakpoint enable 断点编号
: 启用断点 -
breakpoint delete 断点编号
: 删除断点
(lldb) breakpoint disable 1
1 breakpoints disabled.
(lldb) breakpoint enable 1
1 breakpoints enabled.
(lldb) breakpoint delete 3
1 breakpoints deleted; 0 breakpoint locations disabled.
(lldb)
4)断点命令
-
breakpoint command add 断点编号
: 给断点预先设置需要执行的命令,当触发断点时候,就会执行
(lldb) breakpoint set -n "-[ViewController test]"
Breakpoint 3: where = LLDBTest`-[ViewController test] + 23 at ViewController.m:26:5, address = 0x0000000100cf8de7
(lldb) breakpoint command add 3
Enter your debugger command(s). Type 'DONE' to end.
> print "断点命中了"
> print "断点参数为"
> po self
> DONE
2021-03-31 08:54:15.029736+0800 LLDBTest[2178:59045] (
"\U5317\U4eac",
"\U4e0a\U6d77",
"\U5e7f\U5dde"
)
print "断点命中了"
(const char [16]) $0 = "断点命中了"
print "断点参数为"
(const char [16]) $1 = "断点参数为"
po self
<ViewController: 0x7fe7f3107810>
(lldb)
-
breakpoint command list 断点编号
: 列出断点的绑定的命令 -
breakpoint command delete 断点编号
: 删除断点绑定的命令
(lldb) breakpoint command list 3
Breakpoint 3:
Breakpoint commands:
print "断点命中了"
print "断点参数为"
po self
(lldb) breakpoint command delete 3
(lldb) breakpoint command list 3
Breakpoint 3 does not have an associated command.
(lldb)
6、watchpoint(内存断点)
相比较于breakpoint
是对方法生效的断点,watchpoint
则是对地址生效的断点。watchpoint
类似于KVO
的工作原理,当观察到属性地址里面的东西改变时,就让程序中断。
watchpoint set variable 变量
watchpoint set expression 地址
watchpoint list
watchpoint diable 断点编号
watchpoint enable 断点编号
watchpoint delete 断点编号
watchpoint command add 断点编号
watchpoint command list 断点编号
watchpoint command delete 断点编号
7、image(镜像寻址)
image
命令主要用于寻址,一般是通过各种组合命令实现不同功能。
1)image list(列出所有模块信息)
image list -o -f
打印出模块的偏移地址、全路径。可以使用grep
过滤
(lldb) image list
[ 0] D87C3A17-73C3-3DFE-8A60-B423A0E92269 0x00000001059bf000 /Users/zhangjh48/Library/Developer/Xcode/DerivedData/LLDBTest-fkdakpzatewwmvdojsurqlcdeomb/Build/Products/Debug-iphonesimulator/LLDBTest.app/LLDBTest
[ 1] DEA51514-B4E8-3368-979B-89D0F8397ABC 0x000000010b7ba000 /usr/lib/dyld
(lldb) image list -o -f
[ 0] 0x00000000059bf000 /Users/zhangjh48/Library/Developer/Xcode/DerivedData/LLDBTest-fkdakpzatewwmvdojsurqlcdeomb/Build/Products/Debug-iphonesimulator/LLDBTest.app/LLDBTest
[ 1] 0x000000010b7ba000 /usr/lib/dyld
(lldb) image list -o -f | grep LLDBTest
[ 0] 0x00000000059bf000 /Users/zhangjh48/Library/Developer/Xcode/DerivedData/LLDBTest-fkdakpzatewwmvdojsurqlcdeomb/Build/Products/Debug-iphonesimulator/LLDBTest.app/LLDBTest
(lldb)
可以使用image list
命令查看ASLR
偏移地址,上面表示LLDBTest
文件的偏移地址为0x00000000059bf000,MachO的TEXT段的偏移地址为0x00000000059bf000(ASLR+PAGEZERO)
2)image lookup(查找模块)
-
image lookup -t 类型
:查找某个类型的信息 -
image lookup -a 地址
:根据内存地址查找在模块中的位置 -
image lookup -n 符号或函数名
:查找某个符号或者函数的位置
(lldb) image lookup -t AppDelegate
Best match found in /Users/zhangjh48/Library/Developer/Xcode/DerivedData/LLDBTest-fkdakpzatewwmvdojsurqlcdeomb/Build/Products/Debug-iphonesimulator/LLDBTest.app/LLDBTest:
id = {0x200000033}, name = "AppDelegate", byte-size = 8, decl = AppDelegate.h:10, compiler_type = "@interface AppDelegate : UIResponder
@end"
(lldb) image lookup -n "-[ViewController test]"
1 match found in /Users/zhangjh48/Library/Developer/Xcode/DerivedData/LLDBTest-fkdakpzatewwmvdojsurqlcdeomb/Build/Products/Debug-iphonesimulator/LLDBTest.app/LLDBTest:
Address: LLDBTest[0x0000000100001df0] (LLDBTest.__TEXT.__text + 144)
Summary: LLDBTest`-[ViewController test] at ViewController.m:25
(lldb) image lookup -a 0x0000000100a68e07
Address: LLDBTest[0x0000000100001e07] (LLDBTest.__TEXT.__text + 167)
Summary: LLDBTest`-[ViewController test] + 23 at ViewController.m:26:5
(lldb)
其中image lookup -a 地址
经常用于查询崩溃位置,通过地址查询崩溃的地方位于哪个模块哪个文件哪个位置
8、register(寄存器)
register
指令能够获取和修改各个寄存器的信息
1)register read (读取所有寄存器信息)
注意:真机调试和模拟器调试,打印的值可能不一样,因为他们寄存器不一样
(lldb) register read
General Purpose Registers:
x0 = 0x0000000105109d20
x1 = 0x0000000104bbe5f0 "testWithA:b:"
x2 = 0x0000000000000003
x3 = 0x0000000000000004
x4 = 0x000000016b2459c8
x5 = 0x0000000000000040
x6 = 0x0000000000000044
x7 = 0x0000000000000800
x8 = 0x0000000104bc52b8 "testWithA:b:"
x9 = 0x0000000000000303
x10 = 0x000000010681ddb0
x11 = 0x000000000007b700
x12 = 0x000000000000003b
x13 = 0x0000000000000000
x14 = 0x0000000000000000
x15 = 0xffffffffffffffff
x16 = 0x0000000104bc52f2 (void *)0xf3f00000000104bc
x17 = 0x0000000104bbdd18 LLDBTest`-[ViewController testWithA:b:] at ViewController.m:27
x18 = 0x0000000000000000
x19 = 0x0000000105109d20
x20 = 0x0000000000000000
x21 = 0x00000001e5f70000 (void *)0x00000001d5623100
x22 = 0x00000001d1d341fc
x23 = 0x0000000000000001
x24 = 0x0000000000000001
x25 = 0x00000001dbd44000 UIKitCore`_UIPreviewInteractionController._currentCommitTransition
x26 = 0x000000028301ff80
x27 = 0x00000001e1d384a8 CoreFoundation`__NSArray0__struct
x28 = 0x00000001d24cdffc
fp = 0x000000016b245b30
lr = 0x0000000104bbdcfc LLDBTest`-[ViewController viewDidLoad] + 192 at ViewController.m:24:5
sp = 0x000000016b245b00
pc = 0x0000000104bbdd34 LLDBTest`-[ViewController testWithA:b:] + 28 at ViewController.m:28:43
cpsr = 0x40000000
(lldb)
2)register read/格式 寄存器名称
- po $x0:打印方法调用者
- x/s $x1:打印方法名
- po $x2:打印参数(以此类推,x3、x4也可能是参数)
- 如果是非arm64,寄存器就是r0、r1、r2
3)register write 寄存器名称 数值
如下,修改寄存器$x3
的值为5
(lldb) register read $x3
x3 = 0x0000000000000004
(lldb) register write $x3 5
(lldb) register read $x3
x3 = 0x0000000000000005
(lldb) po $x3
5
(lldb)
9、缩写
lldb大部分命令都支持缩写:
- 命令:expression 缩写:exp
- 命令:thread backtrace 缩写:bt
- 命令:breakpoint list 缩写:br l
- 命令:process continue 缩写:continue, c
- 命令:thread step-over 缩写:next, n
- 命令:thread step-in 缩写:step, s
- 命令:thread step-out 缩写:finish, f
- 命令:thread step-inst-over 缩写:nexti, ni
- 命令:thread step-inst 缩写:stepi, si
更多命令可参考:https://lldb.llvm.org/use/map.html
参考链接:
https://www.dllhook.com/post/51.html#toc_11
https://blog.bombox.org/2019-08-06/ios-lldb/
https://www.sohu.com/a/301595611_216613
https://zhuanlan.51cto.com/art/201904/595593.htm