赞
踩
点击 Xcode 断点导航控制器页面(Breakpoint Navigator)左下角的 + 号按钮,可以创建不同类型的断点:
上面这 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 种行为:
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 底部的调试区域会被打开,允许我们和 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,选择要显示哪个作用域下的变量:
Open Quick Look,针对选定的变量,打开快速查看页面
Print Description,针对选定的变量,打印其 Description
方法
Filter the results,结果过滤器
Hide the Variables View / Show the Variables View,隐藏变量视图 / 显示变量视图
右侧的(控制台) 包含了一个交互式终端的文本区域,可以使用它直接与 LLDB 进行交互。此外,控制台还具有以下功能:
Choose a output option,选择要显示那种类型的输出:
Filter the results,结果过滤器
Clear Console,清空控制台
Hide the Console / Show the Console,隐藏控制台 / 显示控制台
(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"
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
在 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 !
如下 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(@" 本文内容由网友自发贡献,转载请注明出处:https://www.wpsshop.cn/w/Monodyee/article/detail/288992
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。