• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

LLDB六常用命令举例

武飞扬头像
Airths
帮助5

创建符号断点

点击 Xcode 断点导航控制器页面(Breakpoint Navigator)左下角的 号按钮,可以创建不同类型的断点:

  1. Swift Error Breakpoint
  2. Exception Breakpoint…
  3. Symbolic Breakpoint…
  4. OpenGL ES Error Breakpoint
  5. Runtime Issue Breakpoint
  6. Constraint Error Breakpoint
  7. Test Failure Breakpoint

上面这 7 种断点,虽然应用场景各有不同,但是配置起来大同小异,以配置符号断点(Symbolic Breakpoint)为例:
学新通
各个字段的含义如下:

  • Name:断点名称。为了与断点编号区分开来,断点名称不能以数字开头,并且不能包含任何空格

  • Symbol:符号名称。填入要设置断点的方法或函数的名称(例如:-[NSException raise]- 号代表对象方法, 号代表类方法)

  • Module:模块名称。填入要设置断点的方法或函数所属的模块(例如:libSystem.B.dylib)。默认不填,以搜索进程中的所有模块

  • Condition:触发条件。填入的表达式的返回值必须为 bool 类型。例如:

    1. 判断方法调用者是否为给定类的实例(isMemberOfClass:
    2. 判断方法调用者是否在给定类的继承体系中(isKindOfClass:
    3. 判断给定的对象是否能响应给定的方法(respondsToSelector:
    4. 判断给定的对象是否遵循给定的协议(conformsToProtocol:
    5. 获取方法实现的地址(methodForSelector:
    6. 或者其他返回值为 bool 类型的表达式 … …
  • Ignore:忽略断点的前 n 次命中。优先级在 Condition 之上,即先满足 Condition 再计算 Ignore

  • Action:断点行为。Xcode 为断点提供了以下 6 种行为:

    1. AppleScript,由苹果公司推出,内置在 macOS 中的一种功能强大的脚本语言
    2. Capture GPU Frame,捕获 GPU 帧
    3. Debugger Command,执行 LLDB 相关的命令
    4. Log Message,输出日志信息
    5. Shell Command,执行 Shell 命令
    6. 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,选择要显示哪个作用域下的变量:

      1. Auto,显示最近访问的变量
      2. Local Variables,仅显示局部变量
      3. 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,选择要显示那种类型的输出:

      1. All Output,显示所有类型的输出
      2. Debugger Output,仅显示调试器的输出
      3. 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
系列文章
更多 icon
同类精品
更多 icon
继续加载