当前位置:   article > 正文

LLDB(六):常用命令举例_lldb常用调试命令

lldb常用调试命令

创建符号断点

点击 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)为例:
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 中对符号断点的如下配置:
Symbolic Breakpoint Demo
等价于如下的 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 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

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.
  • 1
  • 2
  • 3

关于 Xcode 底部的调试区域

程序在命中断点时会暂停运行,此时 Xcode 底部的调试区域会被打开,允许我们和 LLDB 交互
Debug Area
调试区域主要由 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"
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

关于 p 和 po 的输出格式

int value = 97;
char str[] = {'h', 'e', 'l', 'l', 'o', '\0'};
  • 1
  • 2
# 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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61

通过 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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

不仅可以通过 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 !
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

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(@"       本文内容由网友自发贡献,转载请注明出处:https://www.wpsshop.cn/w/Monodyee/article/detail/288992
推荐阅读