LLDB详解 一、引言 二、LLDB命令 三、使用LLDB 四、扩展LLDB 四、参考资料

一、引言

1.1 目的

程序员日常编写代码的时间远小于调试的时间,所以熟练掌握调试工具能够大幅降低调试所要花的时间,从而大幅提高工作效率。本文针对iOS开发过程中通过Xcode与/和LLDB进行调试进行的讲解,以提升对LLDB的掌握。如果同时能够帮助到广大网友,那么我讲十分开心。

1.2 日常调试

在日常工作中,大部分调试动作都是设置一些断点,在程序被暂停时查看执行逻辑、变量的值等,然后定位问题,并根据具体问题修改代码。这应该是大部分程序员的日常调试行为与状态。此种调试行为仅使用LLDB所提供的极少的能力。
除此之外,在大部分情况下程序员会借助在源代码里添加NSLog之类的日志代码,来进行辅助调试。

1.3 日常调试的弊端

  1. 效率低下
  • 比如,在某个循环中,需要在第N次循环才暂停,但是前N-1次也需要暂停与跳过
  • 比如,想查看某些UI效果,需要临时修改代码,重新运行
  • 比如,调试过程中的修改,需要反复多次,每次都重新编译与运行花费时间较多
  • 比如,在一个复杂的UI结构中,不知道某个按钮的点击事件被谁响应
  • 比如,某个元素布局错误,不知道谁引起了布局错误,花大力气进行查找
  1. 会在正常代码之外引入非必要代码
  • 添加的NSLog之类代码没有删除
  • 添加的其他有作用的调试代码遗漏删除(如,把测试过程中的数据请求地址发布到了证实版本)

二、LLDB命令

LLDB是开源、内置于XCode、具有REPL(read-eval-print-loop)特征的调试器,其提供C++、Python接口。在日常的开发和调试过程中给开发人员带来了非常多的帮助。在Xcode5.0的时候,LLDB全面取代了GDB,使调试工作更加高效、便捷。LLDB是LLVM的一部分。本节重点讲述LLDB的命令,这些命令大部分可以可通过Xcode提的GUI界面来完成,也可以使用Xcode提供的调试控制台以交互命令的方式执行。



2.1 命令概述

LLDB命令的格式格式如下,其中[]表示是可选部分。

<command> [<subcommand> [<subcommand>...]] <action> [-options [option-value]] [argument[argument...]]

  • command
    • 基于LLDB运行环境创建执上下文环境,最终的命令在此上下文环境中执行
  • subcommand
    • 对command创建的上下文环境进行修改,创建更具体的新的上下文环境。子命令以空格分隔,用于组织相关操作。
  • action
    • 在command、subcomand所形成的上下文环境中需要执行的动作
  • options
    • 动作所需要的选项、命令可能包含一个或多个选项 (option)
    • 选项以空格分隔,以双短划线 (--)开头,组合修改要执行的操作
    • 有些option还提供使用单个短划线 (-) 的简写形式。例如,第一次导致程序停止后自动删除breakpoint的breakpoint set --one-shot命令,可以写为breakpoint set -o。
    • 有些option还会传递值,option和值用空格分隔。例如,为breakpoint set命令传递--name option,选项值为函数、方法名称,这样就可以为指定函数、方法添加breakpoint。
    • 有些命令组合使用options。例如,breakpoint set命令组合使用--file和--line选项,并为其指定对应文件名称和行号。
    • Option values和arguments中的空格用双引号保护。如果参数中有双引号,则可以使用反斜杠()标志双引号。LLDB等效的使用单引号和双引号。例如:
      (lldb)command [subcommand] -option "some \"quoted\" string"
      也可以写为:
      (lldb) command [subcommand] -option 'some "quoted" string'
      这样的命令解析设计有助于LLDB命令语法在所有命令中保持统一。
    • 如果命令即接受options,又接受自由形式 (free form) 的arguments,则必须在最后一个option和第一个argument间放置以空格分隔的双短划线,如expression命令。这样能够确保以短划线开头的argument被解释为argument,而不会因其与option都以短划线开头而被错误解释。
  • arguement
    • 命令所需要参数
    • 命令可能需要一个或多个参数,参数以空格分隔,用于指示要执行的操作。例如,breakpoint disable命令参数传递要禁用的breakpoint。breakpoint disable 1 2表示禁用breakpoint ID为1和2的断点。

2.2.1 命令形式

相同的命令可以用不同的形式实现,例如——以下命令表示同一操作:验证变量someVariable描述,并输出结果。

  • 规范形式(canonical form)expression --object-description -- someVariable。
  • 缩写形式 (abbreviated form)e -O -- someVariable。
  • 别名 (alias)po someVariable。可以为任何命令创建别名,以方便执行常见操作。

2.2.2 命令补全(Command Completion)

LLDB支持源文件名,符号名,文件名,等等的命令补全(Commmand Completion)。终端窗口中的补全是通过在命令行中输入一个制表符来初始化的。Xcode控制台中的补全与在源码编辑器中的补全方式是一样的:补全会在第三个字符被键入时自动弹出,或者通过Esc键手动取消弹出。

命令中的私有选项可以有不同的完成者(completers)。如breakpoint中的--file <path>选项作为源文件的完成者,--shlib <path>选项作为当前加载的库的完成者,等等。这些行为是特定的,例如,如果指定--shlib <path>,且以--file <path>结尾,则LLDB只会列出由--shlib <path>指定的共享类库。

2.2 命令详解

2.2.1 帮助类命令

2.2.1.1 help

help命令可以查看LLDB的帮助文档,方便进一步了解LLDB命令结构。
只输入help命令,将返回所有命令列表,如下是部分命令:

Debugger commands:
  apropos           -- List debugger commands related to a word or subject.
  breakpoint        -- Commands for operating on breakpoints (see 'help b' for
                       shorthand.)
  command           -- Commands for managing custom LLDB commands.
  disassemble       -- Disassemble specified instructions in the current
                       target.  Defaults to the current function for the
                       current thread and stack frame.
  expression        -- Evaluate an expression on the current thread.  Displays
                       any returned value with LLDB's default formatting.
  frame             -- Commands for selecting and examing the current thread's
                       stack frames.
  gdb-remote        -- Connect to a process via remote GDB server.
                       If no host is specifed, localhost is assumed.
                       gdb-remote is an abbreviation for 'process connect
                       --plugin gdb-remote connect://<hostname>:<port>'
  gui               -- Switch into the curses based GUI mode.

可以通过help command来查看命令的具体帮助信息

2.2.1.2 apropos

当我们只记得某个单词,但是不太确定属于某个命令、子命令等时,可以通过此命令进行查找,以获取更加详细的信息。

(lldb) apropos jump
The following commands may relate to 'jump':
  _regexp-jump -- Set the program counter to a new address.
  thread jump  -- Sets the program counter to a new address.
  jump         -- Set the program counter to a new address.

2.2.2 expression

此命令在当前线程执行表达式并把结果输出,输出结果是默认采用LLDB内置的格式化程序。此命令最重要的用途有两个:被用来输出结果(我们经常使用的是po、p,就是它的简写)、修改变量的值

2.2.2.1 po

po命令在当前线程上执行表达式,并输出表达式的计算结果。
此命令其实是expression -O --的简写形式,其中-O实际上是--object-description,此选项表示使用类型提供的描述来输出结果,对于OC语言来说就是descriprtion。

'po' is an abbreviation for 'expression -O  --'

-O ( --object-description )
            Display using a language-specific description API, if possible.

其使用非常简单,但是其背后的原理比较复杂。通过下图,我们可以看到其最少要经过两次编译与运行,所花的时间成本还是比较高的。


2.2.2.2 p

p命令在当前线程上执行表达式,并输出表达式的计算结果,此结果以LLDB内置的格式标识。此命令其实是expression --的简写形式。

'p' is an abbreviation for 'expression --'

其使用非常简单,但是其背后的原理比较复杂。与po命令相比,其第二次是采用动态类型解析、并使用LLDB内置的类型描述输出表达式的结果。


2.2.2.3 v

此命令不属于expression,但是其与po、p作用类似,所以一并在这里介绍。
命令的作用是:显示当前堆栈帧的变量,默认为所有参数和当前范围内的变量。
可以指定聚合变量的子项例如“var->child.x”。 'frame variable' 中的 -> 和 [] 运算符不调用运算符重载(如果存在),但直接访问指定的元素。

'v' is an abbreviation for 'frame variable'

其使用非常简单,但是其背后的原理比较复杂。与po、p相比,其背后是最简单的逻辑。


2.2.2.4 po-VS-p-VS-v

以下是po、p、v的对比


2.2.3 breakpoint

此命令是与用于设置、取消、修改断点等操作。

2.2.3.1 设置断点

使用breakpoint 的子命令 set 设置断点
具体信息可以通过help breakpoint set 查询所有可选的选项(options)以及参数(arguments).

// 按方法名称设置--name(-n):
(lldb) breakpoint set -n greet
Breakpoint 2: where = Greeter`Greeter.Greeter.greet(personNamed: Swift.String) -> () + 127 at Greeter.swift:9:12, address = 0x00000001000034cf
// 按行数设置--line(-l):
(lldb) breakpoint set -l 18
Breakpoint 3: where = Greeter`main + 14 at Greeter.swift:18:15, address = 0x000000010000315e
按文件设置--file(-f):
(lldb) breakpoint set -f Greeter.swift -l 20
Breakpoint 6: where = Greeter`main + 34 at Greeter.swift:20:1, address = 0x0000000100003172
// 根据语言捕获异常--language-exception(-E):
(lldb) breakpoint set -E Objc
Breakpoint 7: no locations (pending).


// 可以通过--exception-typename(-O) 指定要捕获的异常类型:
(lldb)  breakpoint set -E Swift -O EnumErrorType
Breakpoint 8: no locations (pending).
// pending,说明某块还没有被加载

2.2.3.2 获取所有断点

此命令获取的是所有的断点,包括通过GUI设置的断点和通过Xcode 所提供的的LLDB交互窗口。

// 获取断点列表: breakpoint list
(lldb) breakpoint list
Current breakpoints:
1: name = 'sayHello', locations = 0 (pending)

2: name = 'greet', locations = 1
  2.1: where = Greeter`Greeter.Greeter.greet(personNamed: Swift.String) -> () + 127 at Greeter.swift:9:12, address = Greeter[0x00000001000034cf], unresolved, hit count = 0

3: file = '/Users/jackli/Desktop/Roborock/Greeter.swift', line = 18, exact_match = 0, locations = 1
  3.1: where = Greeter`main + 14 at Greeter.swift:18:15, address = Greeter[0x000000010000315e], unresolved, hit count = 0

4: file = 'main.swift', line = 20, exact_match = 0, locations = 0 (pending)

5: file = 'Greet.swift', line = 20, exact_match = 0, locations = 0 (pending)

6: file = 'Greeter.swift', line = 20, exact_match = 0, locations = 1
  6.1: where = Greeter`main + 34 at Greeter.swift:20:1, address = Greeter[0x0000000100003172], unresolved, hit count = 0

7: Exception breakpoint (catch: off throw: on) the correct runtime exception handler will be determined when you run

8: Swift Error breakpoint the correct runtime exception handler will be determined when you run
需要注意的是: 设置断点其实是设置了逻辑断点, 可能会对应一个或多个位置断点(location).
每个逻辑断点都有一个按照顺序从1开始分配的整型ID. 每一个位置断点又有一个自己的位置ID, 与逻辑断点的ID之间以.分隔, 用于定位位置断点. 示例如下:
2: name = 'greet', locations = 1
  2.1: where = Greeter`Greeter.Greeter.greet(personNamed: Swift.String) -> () + 127 at Greeter.swift:9:12, address = Greeter[0x00000001000034cf], unresolved, hit count = 0

// 逻辑断点是动态的, 如果程序载入了新的代码, 会自动载入新的位置断点.

2.2.3.3 修改断点

// 修改断点:
// breakpoint modify
// 使用这个命令修改逻辑断点或者位置断点, 需要传递逻辑断点或者位置断点的ID 作为参数, 具体参数可以通过help breakpoint modify 查看.

部分常用选项如下:
--condition(-c) 传入表达式参数, 只有表达式判断为true 才会执行断点.
ignore-count(-i) 指定跳过断点的次数, 在该断点处跳过指定次数后, 才会执行该断点.
--one-shot(-o) 执行断点一次后移除该断点.
--queue-name(-q) 指定队列名称, 该断点只有在指定队列上才会执行.
--thread-name(-T) 指定线程名称, 该断点只有在指定的线程上才会执行.
--thread-id(-t) 指定线程ID(TID), 该断点只有在指定的线程上才会执行.
--thread-index(-x) 指定线程index, 该断点只有在指定的线程上才会执行.

如下示例修改了断点ID为1的断点, 指定其在执行后,自动移除.
(lldb) breakpoint modify --one-shot 1
我们都知道, 当程序抵达断点处时, 程序会暂停执行, 此时可以执行lldb命令.
我们也可以通过执行breakpoint command add 命令, 让程序每次抵达断点处时, 自动执行我们添加的命令. 如:
在位置断点1.1处添加指令, 每行可以添加一个指令, Enter 跳转至下一指令, 输入DONE结束添加.
(lldb) breakpoint command add 1.1
Enter your debugger command(s). Type 'DONE' to end.
> thread backtrace
> DONE
可以将process continue 作为最后一个指令, 那么调试器就会在执行指令后, 自动继续执行程序, 这在记录Log的场景下使用是非常方便的.
(lldb) target create "Greeter"
Current executable set to (x86_64).
(lldb) breakpoint list
No breakpoints currently set.
(lldb) breakpoint set -n greet
Breakpoint 1: where = Greeter`Greeter.Greeter.greet(personNamed: Swift.String) -> () + 127 at Greeter.swift:9:12, address = 0x00000001000034cf
(lldb) breakpoint command add 1
Enter your debugger command(s).  Type 'DONE' to end.
> frame variable
> process continue
(lldb) process launch
Process 5997 launched: (x86_64)

2.2.3.4 修改断点状态

设置断点可用状态(Diable/Enable)
禁用断点: breakpoint disable
启用断点: breakpoint enable
以上命令都需要指定断点ID参数(支持通配符).
当逻辑断点设置为diable时, 它下方的所有位置断点都将不再生效.
(lldb) breakpoint disable 1
1 breakpoints disabled.
(lldb) breakpoint disable 2.1
1 breakpoints disabled.


(lldb) breakpoint enable 1
1 breakpoints enabled.
(lldb) breakpoint enable 2.1
1 breakpoints enabled.


还可用通配符*指定断点.
如下示例先是禁用了逻辑断点1下的所有位置断点, 而后启用了位置断点1.1
(lldb) breakpoint disable 1.*
2 breakpoint disabled*
(lldb) breakpoint enable 1.1
1 breakpoint enabled.

2.2.3.5 删除断点

删除断点
(lldb) breakpoint delete 1
1 breakpoints deleted; 2 breakpoint locations disabled.

2.2.4 watchpoint

其与breakpoint很类似,主要用于在感知到内存发生变化后触发断点。内存断点受硬件寄存器数量的限制。

// 针对变量设置内存断点
(lldb) watchpoint set variable places
Watchpoint created: Watchpoint 1: addr = 0x100004a40 size = 8 state = enabled type = w
    declare @ 'main.swift:7'
    watchpoint spec = 'places'
    new value: 1 value

// 针对表达式返回值设置内存断点
(lldb) watchpoint set expression -- (int *)$places + 8
Watchpoint created: Watchpoint 2: addr = 0x100005f33 size = 8 state = enabled type = w
    new value: 0x0000000000000000

// 默认情况下,内存断点监视对变量或地址的写访问。 通过传递值为 read、write 或 read_write 的 --watch (-w) 选项来指定对监视器的访问类型。

// 默认情况下,观察点使用目标机器的指针字节大小监视读取和写入。 通过传递值为 1、2、4 或 8 的 --size (-s) 选项,更改用于监视区域的字节数。

内存断点的删除、修改等操作同breakpoint

2.2.5 thread

  • n 下一行(next的简写)
  • c 继续(continue的简写)
  • s 进入函数(step into的简写)
  • finish 跳出当前函数
    上面是命令行的输入,对应下面的GUI按钮。


// 返回某值
thread return value 

// 跳过2条语句
thread jump --by 2

// 这两个命令也与流程控制类似,不过它们属于frame
up\down

2.2.6 image

// 列出所有依赖库

(lldb) image list
[  0] 69F6C476-555D-3987-B162-E62C9A1D718C 0x000000010008c000 /var/mobile/Containers/Bundle/Application/D6B51B8B-00F8-443F-9B8E-EF47C2330D2B/WhatsApp.app/WhatsApp (0x000000010008c000)
[  1] 3134CFB2-F722-310E-A2C7-42AE4DC131AB 0x00000001017ec000 /Library/MobileSubstrate/MobileSubstrate.dylib (0x00000001017ec000)
[  2] 4DB79D94-6764-3180-99C9-5615E29073E7 0x0000000196848000 /Users/piao/Library/Developer/Xcode/iOS DeviceSupport/9.0.2 (13A452)/Symbols/usr/lib/libSystem.B.dylib
// 查找ASLR偏移
(lldb) image list -f -o WhatsApp
[  0] /var/mobile/Containers/Bundle/Application/D6B51B8B-00F8-443F-9B8E-EF47C2330D2B/WhatsApp.app/WhatsApp 0x000000000008c000(0x000000010008c000)
ASLR偏移:0x000000000008c000
// 打印加载模块列表

(lldb) image list -f -o // 通常带这两个参数使用
[  0] 40E417A4-F966-3DB4-B028-B0272DC016A7 0x000a0000 /Users/piao/Library/Developer/Xcode/DerivedData/debug-bdkhskdqykkoqmhjedilckzvpuls/Build/Products/Debug-iphoneos/debug.app/debug 
      /Users/piao/Library/Developer/Xcode/DerivedData/debug-bdkhskdqykkoqmhjedilckzvpuls/Build/Products/Debug-iphoneos/debug.app.dSYM/Contents/Resources/DWARF/debug
[  1] 011601C0-F561-3306-846B-94A7C8C841DA 0x2d9e6000 /Users/piao/Library/Developer/Xcode/iOS DeviceSupport/7.1.2 (11D257)/Symbols/System/Library/Frameworks/CoreGraphics.framework/CoreGraphics
查找原始地址信息
在可执行文件或任何共享库中查找原始地址的信息。
// image lookup -a 表达式
例子:
(lldb) image lookup -a $pc
Address: debug[0x0000b236] (debug.__TEXT.__text + 1254)
Summary: debug`main + 58 at main.m:16
//查找类信息
image lookup --type UIViewController
image lookup -t UIViewController

//查找符号
image lookup --name viewDidLoad
image lookup -n viewDidLoad

//查找地址
image lookup --address 0x000000018ff60490
image lookup -a 0x000000018ff60490

三、使用LLDB

3.1 使用LLDB的方式

日常,我们使用LLDB主要是通过Xcode提供的breakpoint navigator、Debug Area(variables view、console)来完成的。其中,在程序已经暂停的情况下,可以使用console来通过交互式命令来完整使用LLDB提供的能力。
从广义的角度来说,第四节 扩展LLDB也属于使用LLDB的方式,不过其与本章的使用具有较大的差异,所以另起一小节来介绍。
作为日常使用,可以仔细看一下下面图中的各个选项、文本框应该填写什么,来更加充分的使用LLDB提供的能力。


下面,介绍更多的日常实际场景的例子。

3.2 实际使用LLDB

3.2.1 修改返回值

- (int)rtInt {
    NSLog(@"rtInt");
    // 在下面一行设置断点,并添加如下图所示内容(方法1)
    int n = 10;
    // 在下面一行设置断点,并添加如下图所示内容(方法2)
    return n;
}

- (NSDictionary *)rtDict {
    NSLog(@"rtDict");
    NSDictionary *dict= @{@"key":@"value"};
    // 设置断点,然后控制台输入,并在LLDB的环境里定义变量,并把变量赋值给我们需要的对象
    // e NSDictionary *$otherDict = @{@"key1":@"value1"}
    // e dict = $otherDict
    // 在LLDB里可以定义新的变量,变量必须以$开头
    return dict;
}

3.2.2 输出布局信息

(lldb) po [self.view recursiveDescription]
<UIView: 0x7fb5b6909b50; frame = (0 0; 393 852); autoresize = W+H; backgroundColor = <UIDynamicSystemColor: 0x6000020e6b40; name = systemBackgroundColor>; layer = <CALayer: 0x6000035cb6a0>>
   | <UIButton: 0x7fb5b3f04f60; frame = (10 10; 200 80); opaque = NO; layer = <CALayer: 0x6000035fe260>>
   |    | <UIButtonLabel: 0x7fb5b3b157b0; frame = (81.6667 29; 37 22); text = '点我'; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x6000016d08c0>>

以上只是简单的UI调试,可以使用Xcode提供的Debug View Hierarchy来进行更深入的排查。
[图片上传失败...(image-b0acbc-1670762937746)]

通过这些UI所提供的的调试能力,可以查看被截断的UI、布局以及创建布局的调用堆栈、视图层次、大小等信息。

3.3 修改UI并实时看到

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    UIButton *btn = [[UIButton alloc] initWithFrame:CGRectMake(10, 10, 200, 80)];
    [self.view addSubview:btn];
    [btn addTarget:self action:@selector(click:) forControlEvents:UIControlEventTouchUpInside];
    [btn setTitleColor:UIColor.redColor forState:UIControlStateNormal];
    [btn setTitle:@"点我"forState:UIControlStateNormal];
}

- (void)click:(UIButton *)btn {
    // 在这里设断定,并输入如下命令
    // e (void)[self.view setBackgroundColor:[UIColor grayColor]] 修改颜色
    // e (void)[CATransaction flush]  // 把颜色的修改通过渲染更新到界面
    NSLog(@"here");
}

注意
以上命令并不总是能成功,在viewDidLoad中设置断点然后进行操作就不行

3.4 注意返回类型

在上面例子中,如果输入
e [self.view setBackgroundColor:[UIColor grayColor]]
则会报这个错误
error: expression failed to parse:
error: <user expression 0>:1:12: no known method '-setBackgroundColor:'; cast the message send to the method's return type
[self.view setBackgroundColor:[UIColor grayColor]]
~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

// 上面的错误信息告诉LLDB不知道返回类型,所以需要强制类型转换
// 这是在使用LLDB过程中经常碰到的问题

3.5 跳过某些语句

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    NSLog(@"%d", __LINE__); // 假设此行是41
    NSLog(@"%d", __LINE__); // 在此行设置断点,在debug command中输入:thread jump --by 1
    NSLog(@"%d", __LINE__);
    NSLog(@"%d", __LINE__);
    NSLog(@"%d", __LINE__);
    NSLog(@"%d", __LINE__);

    // 以下是输出,可以看出跳过了42行
    baseStudy[33708:346268] 41
    baseStudy[33708:346268] 43
    baseStudy[33708:346268] 44
    baseStudy[33708:346268] 45
    baseStudy[33708:346268] 46
}

3.6 命令后执行

// 这是上面设置的命令
// -C 表示后面可以添加新的命令
// -G true标识断点不停止,直接运行
br set -l 43 -f ViewController.m  -C 'po @"onthway"' -G true


// 可以看到断点其实被设置3个(因为循环了3次)
5: file = 'ViewController.m', line = 43, exact_match = 0, locations = 1, resolved = 1, hit count = 2 Options: enabled auto-continue 
    Breakpoint commands:
      po @"onthway"

  5.1: where = baseStudy`-[ViewController viewDidLoad] + 90 at ViewController.m:43:9, address = 0x000000010d564cba, resolved, hit count = 2 

6: file = 'ViewController.m', line = 43, exact_match = 0, locations = 1, resolved = 1, hit count = 1 Options: enabled auto-continue 
    Breakpoint commands:
      po @"onthway"

  6.1: where = baseStudy`-[ViewController viewDidLoad] + 90 at ViewController.m:43:9, address = 0x000000010d564cba, resolved, hit count = 1 

7: file = 'ViewController.m', line = 43, exact_match = 0, locations = 1, resolved = 1, hit count = 0 Options: enabled auto-continue 
    Breakpoint commands:
      po @"onthway"

  7.1: where = baseStudy`-[ViewController viewDidLoad] + 90 at ViewController.m:43:9, address = 0x000000010d564cba, resolved, hit count = 0 

3.7 执行一次的断点

// 把上面的断点语句添加--one-shot true
br set --one-shot true -l 43 -f ViewController.m   -C 'po @"onthway"' -G true

// 输出3次  ontheway
3: file = '/Users/wangwenjun04/Desktop/myfolder/studyCode/baseStudy/baseStudy/ViewController.m', line = 43, exact_match = 0, locations = 1, resolved = 1, hit count = 3

  3.1: where = baseStudy`-[ViewController viewDidLoad] + 90 at ViewController.m:43:9, address = 0x0000000103ea0cba, resolved, hit count = 3 

4: file = '/Users/wangwenjun04/Desktop/myfolder/studyCode/baseStudy/baseStudy/ViewController.m', line = 50, exact_match = 0, locations = 1, resolved = 1, hit count = 1

  4.1: where = baseStudy`-[ViewController viewDidLoad] + 199 at ViewController.m:50:22, address = 0x0000000103ea0d27, resolved, hit count = 1 

// 此断点只其一次作用
7: file = 'ViewController.m', line = 43, exact_match = 0, locations = 1, resolved = 1, hit count = 0 Options: enabled one-shot auto-continue 
    Breakpoint commands:
      po @"onthway"

  7.1: where = baseStudy`-[ViewController viewDidLoad] + 90 at ViewController.m:43:9, address = 0x0000000103ea0cba, resolved, hit count = 0 

3.8 忽略特定次数

假如有以下代码,当i的值是9的时候会出现问题。那么按照上述基本的调试方法,在if这一行设置断点,然后要通过8次的暂停才能在我们想要的地方停住。

NSInteger count = array.count;
for (NSInteger i = 0; i < count; i++) {
    if (i % 2) {
        // 做一些事情
    } else {
        // 做另外一些事情
    }
}

通过设置跳过前8次执行,LLDB自动在我们想要的时候暂停程序的执行。通过设置ingore的次数,我们的调率得到了提升。

3.9 条件断点

假如我们有如下代码:

void printName(NSString *name) {
    NSLog(@"name=%@", name);
}

int main(int argc, const char * argv[]) {    
    printName(@"jzm");
    printName(@"xjp");
    printName(@"hjt");
}

我们的需求是在name是xjp时暂停,那么按照简单的调试方法,只能在NSLog那一行来设置断点,然后再每次断点停住的时候检查name的值是否是xjp。我们可以更家搞笑,通过设置条件断点的方式。


3.10 记录日志

很多时候,我们是需要记录程序执行过程的路径、变量的值、次数等来发现问题,可以通过如下的方式来记录日志。
[图片上传失败...(image-e28259-1670762937746)]

除此方式外,还可以通过expression表达时候来记录日志。
[图片上传失败...(image-fb046a-1670762937746)]

3.11 过滤日志

有些情况下,我们添加的日志、程序的输出过多,会淹没我们想要看到的内容,可以通过过滤日志来实现。



还可以通过mac提供的控制台程序来实现多字段的过滤。


四、扩展LLDB

LLDB、Xcode提供了便捷的方式进行扩展,这些扩展都基于LLDB提供的接口。

3.1 工程级扩展

我们可以专门针对某个工程进行扩展,其方法很简单。新建一个用于扩展LLDB的python文件,然后选择edit scheme进行配置。

setting set prompt [wwj-lldb]$

command alias wflush e (void)[CATransaction flush]
# 修改提示语
setting set prompt [wwj-lldb]$
# 新建一个命令别名,用于刷新UI
command alias wflush e (void)[CATransaction flush]

3.2 系统级扩展

在~/.lldbinit文件中添加自定义命令,如果没有此文件需要新建。
FaceBook开源的chisel就是采用的此种方式。
其提供了许多扩展命令。

Command Description iOS OS X
pviews Print the recursive view description for the key window. Yes Yes
pvc Print the recursive view controller description for the key window. Yes No
visualize Open a UIImage, CGImageRef, UIView, CALayer, NSData (of an image), UIColor, CIColor, or CGColorRef in Preview.app on your Mac. Yes No
fv Find a view in the hierarchy whose class name matches the provided regex. Yes No
fvc Find a view controller in the hierarchy whose class name matches the provided regex. Yes No
show/hide Show or hide the given view or layer. You don't even have to continue the process to see the changes! Yes Yes
mask/unmask Overlay a view or layer with a transparent rectangle to visualize where it is. Yes No
border/unborder Add a border to a view or layer to visualize where it is. Yes Yes
caflush Flush the render server (equivalent to a "repaint" if no animations are in-flight). Yes Yes
bmessage Set a symbolic breakpoint on the method of a class or the method of an instance without worrying which class in the hierarchy actually implements the method. Yes Yes
wivar Set a watchpoint on an instance variable of an object. Yes Yes
presponder Print the responder chain starting from the given object. Yes Yes
... ... and many more!

其在~/.lldbinit中添加了如下的内容

# ~/.lldbinit

command script import /usr/local/opt/chisel/libexec/fbchisellldb.py

对此感兴趣的读者可以对chisel进行深入研究。

3.3 高阶扩展

通过LLVM工程自己编译LLDB代码(难度高,有兴趣的高手可以尝试并分享给大家)。

四、参考资料

  1. 全面剖析 LLDB 调试器
  2. LLDB的使用
  3. LLDB调试器使用简介
  4. LLDB: Beyond "po"
  5. 与调试器共舞 - LLDB 的华尔兹
  6. Advanced Debugging with Xcode and LLDB
  7. 自定义LLDB命令实战
  8. LLDB基础
  9. LLDB Debugging Guide
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章