LLDB六常用命令举例
创建符号断点
点击 Xcode 断点导航控制器页面(Breakpoint Navigator)左下角的 号按钮,可以创建不同类型的断点:
- Swift Error Breakpoint
- Exception Breakpoint…
- Symbolic Breakpoint…
- OpenGL ES Error Breakpoint
- Runtime Issue Breakpoint
- Constraint Error Breakpoint
- Test Failure Breakpoint
上面这 7 种断点,虽然应用场景各有不同,但是配置起来大同小异,以配置符号断点(Symbolic Breakpoint)为例:
各个字段的含义如下:
-
Name:断点名称。为了与断点编号区分开来,断点名称不能以数字开头,并且不能包含任何空格
-
Symbol:符号名称。填入要设置断点的方法或函数的名称(例如:
-[NSException raise]
,-
号代表对象方法, -
Module:模块名称。填入要设置断点的方法或函数所属的模块(例如:
libSystem.B.dylib
)。默认不填,以搜索进程中的所有模块 -
Condition:触发条件。填入的表达式的返回值必须为
bool
类型。例如:- 判断方法调用者是否为给定类的实例(
isMemberOfClass:
) - 判断方法调用者是否在给定类的继承体系中(
isKindOfClass:
) - 判断给定的对象是否能响应给定的方法(
respondsToSelector:
) - 判断给定的对象是否遵循给定的协议(
conformsToProtocol:
) - 获取方法实现的地址(
methodForSelector:
) - 或者其他返回值为
bool
类型的表达式 … …
- 判断方法调用者是否为给定类的实例(
-
Ignore:忽略断点的前 n 次命中。优先级在 Condition 之上,即先满足 Condition 再计算 Ignore
-
Action:断点行为。Xcode 为断点提供了以下 6 种行为:
- AppleScript,由苹果公司推出,内置在 macOS 中的一种功能强大的脚本语言
- Capture GPU Frame,捕获 GPU 帧
- Debugger Command,执行 LLDB 相关的命令
- Log Message,输出日志信息
- Shell Command,执行 Shell 命令
- Sound:播放声音
-
Options:断点选项。用于控制断点在执行完 Action 之后,是否自动继续运行程序。勾选
Automatically continue after evaluating actions
复选框,断点会在执行完 Action 之后,自动继续运行程序
在 Xcode 中对符号断点的如下配置:
等价于如下的 LLDB 命令:
# 创建断点
(lldb) breakpoint set --breakpoint-name DebugSaveGame --name saveGameFunction --shlib LLDBDemo --condition '(model == 1)' --ignore-count 5 --command 'frame variable' --auto-continue true
Breakpoint 1: where = LLDBDemo`saveGameFunction 24 at ViewController.m:39:5, address = 0x0000000100855048
# 查看断点时,输出的各个字段的含义如下
# name = 'saveGameFunction',符号名称为 saveGameFunction
# module = LLDBDemo,模块名称为 LLDBDemo
# locations = 1,根据断点的约束条件所匹配到的断点位置的数量(总共的断点数量)为 1
# resolved = 1,已解析的断点的数量(可用的断点数量)为 1
# hit count = 0,断点被命中的次数为 0
# Options: ignore: 5 enabled auto-continue,断点选项: 忽略该断点的前 5 次命中,并且执行完断点命令之后自动继续运行程序
# Breakpoint commands: frame variable,命中断点后,执行 frame variable 命令
# Condition: (model == 1),命中断点的条件为 model == 1
# Names: DebugSaveGame,断点的名称标识为 DebugSaveGame
(lldb) breakpoint list
Current breakpoints:
1: name = 'saveGameFunction', module = LLDBDemo, locations = 1, resolved = 1, hit count = 0 Options: ignore: 5 enabled auto-continue
Breakpoint commands:
frame variable
Condition: (model == 1)
Names:
DebugSaveGame
1.1: where = LLDBDemo`saveGameFunction 24 at ViewController.m:39:5, address = 0x0000000100855048, resolved, hit count = 0
hcg 注:
LLDB 总是会从你的约束中创建一个断点,即使 LLDB 没有找到任何与该约束匹配的代码位置(即,即使我们要打断点的代码位置未找到,LLDB 也会创建一个断点)。当你设置的断点无法被解析时,LLDB 会将断点报告为挂起(pending)
(lldb) breakpoint set --name nonFunction
Breakpoint 1: no locations (pending).
WARNING: Unable to resolve breakpoint to any actual locations.
关于 Xcode 底部的调试区域
程序在命中断点时会暂停运行,此时 Xcode 底部的调试区域会被打开,允许我们和 LLDB 交互
调试区域主要由 3 个部分组成:
-
顶部的(调试栏),包含如下按钮:
-
Hide the Debug area / Show the Debug area,隐藏调试区域 / 显示调试区域
-
Deactivate breakpoints / Activate breakpoints,禁用所有断点 / 启用所有断点
相当于 LLDB 命令breakpoint disable
/breakpoint enable
-
Continue program execution / Pause program execution,继续执行程序 / 暂停执行程序
相当于 LLDB 命令process continue
/process interrupt
-
Step over,源码级别的单步执行,会跨过函数或方法的调用,会单步执行所有线程
相当于 LLDB 命令thread step-over
Step over instruction (hold Control),汇编指令级别的单步执行,会跨过汇编函数的调用,会单步执行所有线程
相当于 LLDB 命令thread step-inst-over
Step over thread (hold Control-Shift),源码级别的单步执行,会跨过函数或方法的调用,仅单步执行当前线程
相当于 LLDB 命令thread step-over --run-mode this-thread
-
Step into,源码级别的单步执行,会进入函数或方法的调用,会单步执行所有线程
相当于 LLDB 命令thread step-in
Step into instruction (hold Control),汇编指令级别的单步执行,会进入汇编函数的调用,会单步执行所有线程
相当于 LLDB 命令thread step-inst
Step into thread (hold Control-Shift),源码级别的单步执行,会进入函数或方法的调用,仅单步执行当前线程
相当于 LLDB 命令thread step-in --run-mode this-thread
-
Step out,执行完当前栈帧,并在返回后暂停
相当于 LLDB 命令thread step-out
-
Debug View Hierarchy,查看视图层级
-
Debug Memory Graph,查看内存分配,可用于检查内存泄漏
-
Environment Overrides,环境重置,这里的环境指的是设备环境而不是环境变量
例如:设备外观是浅色模式(Light)还是深色模式(Dark),设备字体的大小,以及设备的其他可访问项 -
Simulate Location,位置模拟,用于调试有定位功能的应用
-
Choose stack frame (hold Command to show full backtrace),根据
process
-thread
-stack frame
的层级关系选择当前的栈帧
按住Command
键以显示当前线程的所有栈帧
-
-
左侧的(变量视图) 显示了可在代码中当前位置范围内检视的变量列表。 此列表是一个可公开的层次结构,可以通过逐步单击变量左侧的小三角形,以显示变量结构所有部分的值。此外,变量视图还具有以下功能:
-
Choose a scope option,选择要显示哪个作用域下的变量:
- Auto,显示最近访问的变量
- Local Variables,仅显示局部变量
- All Variables,Registers,Globals and Statics,显示所有局部变量、寄存器、全局变量和静态变量
-
Open Quick Look,针对选定的变量,打开快速查看页面
-
Print Description,针对选定的变量,打印其
Description
方法 -
Filter the results,结果过滤器
-
Hide the Variables View / Show the Variables View,隐藏变量视图 / 显示变量视图
-
-
右侧的(控制台) 包含了一个交互式终端的文本区域,可以使用它直接与 LLDB 进行交互。此外,控制台还具有以下功能:
-
Choose a output option,选择要显示那种类型的输出:
- All Output,显示所有类型的输出
- Debugger Output,仅显示调试器的输出
- Target Output,仅显示程序的输出
-
Filter the results,结果过滤器
-
Clear Console,清空控制台
-
Hide the Console / Show the Console,隐藏控制台 / 显示控制台
-
在使用 expression 命令执行方法时,需要将发送的消息强制转换为该方法的返回值类型
(lldb) expression NSArray* $array = @[@"Saturday", @"Sunday", @"Monday"]
(lldb) expression [$array count]
(NSUInteger) $0 = 3
# 有时候,LLDB 会因为无法识别方法,而导致 expression 命令执行失败
(lldb) expression [[$array objectAtIndex:0] uppercaseString]
error: <user expression 6>:1:27: no known method '-uppercaseString'; cast the message send to the method's return type
[[$array objectAtIndex:0] uppercaseString]
~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~
# 此时,需要将发送的消息强制转换为该方法的返回值类型
(lldb) expression (NSString *)[[$array objectAtIndex:0] uppercaseString]
(NSTaggedPointerString *) $1 = 0x9f0ca30e86dba9b3 @"SATURDAY"
关于 p 和 po 的输出格式
int value = 97;
char str[] = {'h', 'e', 'l', 'l', 'o', '\0'};
# hcg 注:
# 详情请参阅《通用参数类型详解》中的 gdb-format
# p 和 po 的输出格式,只能使用 gdb-format 中的格式字母(format letter),不能使用 gdb-format 中的重复次数(repeat count)和大小字母(size letter)
# 以默认的格式输出返回值
(lldb) p value
(int) $0 = 97
# /o : 以八进制的格式输出返回值(字母 o 表示 octal)
(lldb) p/o value
(int) $1 = 0141
# /x : 以十六进制的格式输出返回值(字母 x 表示 hexadecimal)
(lldb) p/x value
(int) $2 = 0x00000061
# /d : 以有符号十进制的格式输出返回值(字母 d 表示 decimal)
(lldb) p/d value
(int) $3 = 97
(lldb) p/d 'a'
(int) $4 = 97
# /u : 以无符号十进制的格式输出返回值(字母 u 表示 unsigned decimal)
(lldb) p/u value
(int) $5 = 97
# /t : 以二进制的格式输出返回值(字母 t 表示 binary)
(lldb) p/t value
(int) $6 = 0b00000000000000000000000001100001
# /f : 以浮点数的格式输出返回值(字母 f 表示 float)
(lldb) p/f value
(int) $7 = 1.35925951E-43
# /i : 以指令的格式输出返回值(字母 i 表示 instruction)
(lldb) p/i value
(int) $8 = 0x00000061 udf #0x61
# /c : 以字符常量的格式输出返回值(字母 c 表示 char)
(lldb) p/c value
(int) $9 = a\0\0\0
(lldb) p/c str
(char [6]) $10 = "hello"
# /s : 以字符串的格式输出返回值(所谓字符串,即以空字符 '\0' 结尾的字符数组)(字母 s 表示 string)
(lldb) p/s str
(char [6]) $11 = "hello"
# /a : 以地址的格式输出返回值(字母 a 表示 address)
(lldb) p/a value
(int) $12 = 0x00000061
(lldb) p/a str
(char [6]) $13 = "hello"
# /T : 以系统类型的格式输出返回值(字母 T 表示 OSType)
(lldb) p/T value
(int) $14 = '\0\0\0a'
# /A : 以十六进制浮点数的格式输出返回值(字母 A 表示 float as hex)
(lldb) p/A value
(int) $15 = 0x0.0000c2p-126
通过 expression 命令读取、设置、操作寄存器的值
在 LLDB 的命令空间中,所有的寄存器都以全局变量的形式存在,以 arm64 的 CPU 为例:x0
寄存器对应着全局变量 $x0
,x1
寄存器对应着全局变量 $x1
,
以此类推 … …
# 通过 expression 命令设置 x0 寄存器的值为 1
(lldb) expression $x0 = 1
(unsigned long) $0 = 1
# 通过 register read 命令读取 x0 寄存器的值,果然为 1
(lldb) register read x0
x0 = 0x0000000000000001
# 通过 register write 命令设置 x0 寄存器的值为 2
(lldb) register write x0 2
# 通过 expression 命令读取 x0 寄存器的值,果然为 2
(lldb) expression $x0
(unsigned long) $1 = 2
不仅可以通过 expression
命令读取和设置寄存器本身的值,而且可以通过 expression
命令操作寄存器指向的值
# 获取 x1 寄存器所指向的明文数据(这里假设 x1 寄存器指向了一个 NSData 类型的内存区域)
(lldb) expression [[NSString alloc] initWithData:(NSData *)$x1 encoding:NSUTF8StringEncoding]
(NSTaggedPointerString *) $5 = 0xe6d4a5b08059bc5e @"hcg"
# 调用 x2 寄存器所指向的 Person 对象的 sayHello 方法(这里假设 x2 寄存器指向了一个 Person 类型的内存区域)
(lldb) po [(Person *)$x2 sayHello]
2022-01-22 16:29:43.017872 0800 LLDBDemo[77517:18697227] hello, my name is hcg, I am 20 years old !
expression 命令只能在当前线程中执行表达式,expression 命令不能跨线程执行表达式
如下 Objective-C 代码:
...
int main_a = 10;
NSLog(@"%d", main_a);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
int local_variable = 11; // 在此处设置一个断点
NSLog(@"🎉🎉🎉 local_variable = %d", local_variable);
});
...
执行程序,命中断点。如果此时执行 po main_a
命令(相当于跨线程执行表达式),则会打印如下错误:
(lldb) p main_a
error: <user expression 0>:1:1: use of undeclared identifier 'main_a'
main_a
^
获取 UIButton 的 target 和 action
# 1.通过界面分析获取到 UIButton 对象的内存地址
(lldb) po self.view.subviews
<__NSArrayM 0x283c1b9c0>(
<UIButton: 0x104f15040; frame = (176 187; 62 30); opaque = NO; autoresize = RM BM; layer = <CALayer: 0x2832410e0>>,
<UIButton: 0x104f158f0; frame = (176 262; 62 30); opaque = NO; autoresize = RM BM; layer = <CALayer: 0x283240e20>>,
<UIButton: 0x104f15bc0; frame = (176 331; 62 30); opaque = NO; autoresize = RM BM; layer = <CALayer: 0x283240d40>>,
<_UILayoutGuide: 0x104f15e90; frame = (0 0; 0 44); hidden = YES; layer = <CALayer: 0x283240c60>>,
<_UILayoutGuide: 0x104f16420; frame = (0 862; 0 34); hidden = YES; layer = <CALayer: 0x283240b80>>
)
# 2.获取 UIButton 对象的所有 target
(lldb) po [(UIButton *)0x104f15040 allTargets]
{(
<ViewController: 0x104f0bcc0>
)}
# 3.获取 UIButton 对象指定 target 指定点击事件的 action
(lldb) po [(UIButton *)0x104f15040 actionsForTarget:(ViewController *)0x104f0bcc0 forControlEvent:0]
<__NSArrayM 0x283c2c630>(
saveGameBtnDidClick:
)
关于渲染服务(backboardd)
# 通过 expression 命令获取根控制器的 view,并将其背景颜色更改为橙色
# 一般情况下,只有在程序继续运行之后才会看到界面的变化。因为改变的内容必须被发送到渲染服务中,然后屏幕的显示才会被更新
(lldb) expression UIView* $currentView = [UIApplication sharedApplication].keyWindow.rootViewController.view
(lldb) expression (void)[$currentView setBackgroundColor:[UIColor orangeColor]]
# 渲染服务实际上是另外的一个进程(被称作 backboardd)
# 也就是说,即使我们正在调试的界面所在的进程被中断了,渲染服务所在的进程(backboardd)也还是继续运行着的
# 这意味着可以运行下面的命令,而不用继续运行程序,就能看到背景颜色的改变了:
# hcg 注: 这就是 Chisel 中 caflush 命令的实现原理
(lldb) expression (void)[CATransaction flush]
获取指定镜像当前的 ASLR 偏移量
# 打印第 0 个镜像(即主程序的 MachO)的 ASLR 偏移量
po _dyld_get_image_vmaddr_slide(0)
使用 watchpoint 命令监视对指定对象的指定成员变量的读写
假设我们有一个 Person
类型的对象,不知道为什么它的 _age
成员变量被重写了。因为这并不涉及到属性的访问器方法,所以不能使用符号断点。相反地,我们需要监视什么时候这个地址被写入了
方式一:
(lldb) expression Person* $aPerson = (Person *)0x00000002803a8300
# 获取成员变量 _age 相对于 $aPerson 对象的偏移量
(lldb) expression Ivar* $ivar_age = (Ivar *)class_getInstanceVariable([Person class], "_age")
(lldb) expression ptrdiff_t $offset_age = (ptrdiff_t)ivar_getOffset($ivar_age)
(lldb) po $offset_age
8
# 获取成员变量 _age 的大小
(lldb) po (const char *)ivar_getTypeEncoding($ivar_age)
"i"
(lldb) po sizeof(int)
4
# 通过 watchpoint set expression 命令监视对 $aPerson 对象 _age 成员变量的写入
(lldb) watchpoint set expression --size 4 -- (char *)$aPerson $offset_age
Watchpoint created: Watchpoint 1: addr = 0x2803a8308 size = 4 state = enabled type = w
new value: 0x0000000000000014
方式二:
# 通过 watchpoint set variable 命令监视对 p 对象 _age 成员变量的写入
(lldb) watchpoint set variable p->_age
Watchpoint created: Watchpoint 1: addr = 0x281210b48 size = 4 state = enabled type = w
declare @ '/Users/Airths/Desktop/Training/LLDBDemo/LLDBDemo/ViewController.m:110'
watchpoint spec = 'p->_age'
new value: 20
这篇好文章是转载于:学新通技术网
- 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
- 本站站名: 学新通技术网
- 本文地址: /boutique/detail/tanhfhkcgg
-
photoshop保存的图片太大微信发不了怎么办
PHP中文网 06-15 -
《学习通》视频自动暂停处理方法
HelloWorld317 07-05 -
Android 11 保存文件到外部存储,并分享文件
Luke 10-12 -
word里面弄一个表格后上面的标题会跑到下面怎么办
PHP中文网 06-20 -
photoshop扩展功能面板显示灰色怎么办
PHP中文网 06-14 -
微信公众号没有声音提示怎么办
PHP中文网 03-31 -
excel下划线不显示怎么办
PHP中文网 06-23 -
excel打印预览压线压字怎么办
PHP中文网 06-22 -
TikTok加速器哪个好免费的TK加速器推荐
TK小达人 10-01 -
怎样阻止微信小程序自动打开
PHP中文网 06-13