(一) LLDB使用

1. Xcode断点

Xcode断点

arg1$rdi寄存器意思差不多,可以简单认为它是init调用时持有一个类的实例对象。

NSThread初始化

2. LLDB中常用的两个命令

image命令非常有用。最常用的有以下2个命令

  • image lookup -n

-n意思是让LLDB查询symbolfunction name

(lldb) image lookup -n "-[UIViewController viewDidLoad]"
//输出
1 match found in /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/PrivateFrameworks/UIKitCore.framework/UIKitCore:
        Address: UIKitCore[0x0000000000438886] (UIKitCore.__TEXT.__text + 4414950)
        Summary: UIKitCore`-[UIViewController viewDidLoad]
  • image lookup -rn
//这句话将执行区分大小写的正则搜索
//无论在任何地方、任何方法、任何模块中,都能匹配出来
(lldb) image lookup -rn test

-n来匹配你想精确匹配的结果,用-rn来进行正则搜索。

2.1 Objective-C的属性

@interface TestClass : NSObject
@property (nonatomic, strong) NSString *name;
@end

这个属性会生成gettersetter方法:

//getter
(lldb) image lookup -n "-[TestClass name]"
1 match found in /Users/ycpeng/Library/Developer/Xcode/DerivedData/Signals-bfxxbqqkqjqewpeuwhfjcbjbfjyt/Build/Products/Debug-iphonesimulator/Signals.app/Signals:
        Address: Signals[0x0000000100001720] (Signals.__TEXT.__text + 0)
        Summary: Signals`-[TestClass name] at TestClass.h:28

//setter
image lookup -n "-[TestClass setName:]"
1 match found in /Users/ycpeng/Library/Developer/Xcode/DerivedData/Signals-bfxxbqqkqjqewpeuwhfjcbjbfjyt/Build/Products/Debug-iphonesimulator/Signals.app/Signals:
        Address: Signals[0x0000000100001740] (Signals.__TEXT.__text + 32)
        Summary: Signals`-[TestClass setName:] at TestClass.h:28

2.2 Swift的属性

class SwiftTestClass: NSObject {
  var name: String!
}
//setter
(lldb) image lookup -rn Signals.SwiftTestClass.name.setter
1 match found in /Users/ycpeng/Library/Developer/Xcode/DerivedData/Signals-bfxxbqqkqjqewpeuwhfjcbjbfjyt/Build/Products/Debug-iphonesimulator/Signals.app/Signals:
        Address: Signals[0x000000010000c860] (Signals.__TEXT.__text + 45376)
        Summary: Signals`Signals.SwiftTestClass.name.setter : Swift.Optional at SwiftTestClass.swift:28

对比OC你会发现这个名字真的是太长了,如果你要设置一个断点,它将是这样的

 (lldb) b Signals.SwiftTestClass.name.setter : Swift.Optional

如果你想获得属性的所有方法:

(lldb) image lookup -rn Signals.SwiftTestClass.name

在结果中,你会找到它的gettersetter方法。

Signals.SwiftTestClass.name.getter
Signals.SwiftTestClass.name.setter

Swift的属性方法名的一般结构:

ModuleName.Classname.PropertyName.(getter|setter)

3. 设置断点

缩略写法

b: breakpoint
rb,rbreak: breakpoint set -r %1
  • Swift设置断点:
//全部拼写,比较麻烦
(lldb) b Breakpoints.SwiftTestClass.name.setter : Swift.ImplicitlyUnwrappedOptional

// rb,rbreak,将设置正则匹配的断点,输入更短
(lldb) rb SwiftTestClass.name.setter

//如果没有其他的name的set方法(否则可能匹配出一大堆)
(lldb) rb name\.setter
  • OC设置断点:
//会对UIViewController的所有方法设置断点
(lldb) rb '\-\[UIViewController\ '

别忘了还有Category里的方法:

//(-|+) [ClassName(categoryName) method]
//匹配UIViewController所有的方法,包括Category
(lldb) rb '\-\[UIViewController(\(\w+\))?\ '

3.1 rb, rbreak选项

你可以对匹配设置范围。

// . 对一切设置断点,包括:
//getters/setters, blocks/closures, 
//extensions/categories, functions/methods 
(lldb) rb .

// -s 对模块的一切设置断点
(lldb) rb . -s UIKit

// -f 对某个文件中的一切设置断点
(lldb) rb . -f DetailViewController.swift

3.2 其他有用的配置

  • 指定语言
// -L 可以指定断点的语言
//下面的意思是对Commons模块中一切swift代码设置断点
(lldb) breakpoint set -L swift -r . -s Commons
  • 指定匹配的表达式

比如我想给所有使用if let的地方都设置断点。

// -A 表示在工程中所有源文件中搜索
// -p 后面写要搜索的正则表达式
(lldb) breakpoint set -A -p "if let"

//如果只想在某些文件中搜索,使用-f指定搜索范围
(lldb) breakpoint set -p "if let" -f MasterViewController.swift -f DetailViewController.swift

// -s限制模块
(lldb) breakpoint set -p "if let" -s Signals -A
  • 设置匹配条件

我想给-[UIView setTintColor:]设置断点,但我只关心我们项目中使用到的地方,要怎么办呢?这时候就需要-c出马了。

首先我们需要知道范围:

//让LLDB打印出Signals模块中Mach-O文件的segments和sections
(lldb) image dump sections Signals

找到__TEXT片段,这个范围就是我们可执行代码的地址范围了。

可执行代码的地址范围

比如上图中这个范围是0x0000000104225000-0x0000000104236000
那我们就可以这么设置断点:

 (lldb) breakpoint set -n "-[UIView setTintColor:]" -c "*(uintptr_t*)$rsp >= 0x0000000104225000 && *(uintptr_t*)$rsp <= 0x0000000104236000 "

这个用到了x86_64的调用约定,一个函数被调用的时候栈指针寄存器的工作原理(只会在64位的iOS模拟器上生效)

3.3 移除断点

比如我们设置一个main断点,会发现这个断点有107个地方。

(lldb) b main
Breakpoint 1: 107 locations.
  • 列出断点信息
//列出所有断点
//(lldb) breakpoint list
//列出ID为1下的所有断点
(lldb) breakpoint list 1
1: name = 'main', locations = 107, resolved = 107, hit count = 0
  1.1: where = Signals`main + 15 at AppDelegate.swift:28:7, address = 0x0000000106d1924f, resolved, hit count = 0 
  1.2: where = Foundation`-[NSDirectoryTraversalOperation main], address = 0x00007fff256fd2ff, resolved, hit count = 0 
  1.3: where = Foundation`-[NSFilesystemItemRemoveOperation main], address = 0x00007fff256febcc, resolved, hit count = 0 
  1.4: where = Foundation`-[NSFilesystemItemMoveOperation main], address = 0x00007fff256ff0ba, resolved, hit count = 0 
  1.5: where = Foundation`-[NSOperation main], address = 0x00007fff25751b68, resolved, hit count = 0 
  1.6: where = Foundation`-[NSBlockOperation main], address = 0x00007fff25752be7, resolved, hit count = 0 
  1.7: where = Foundation`-[NSInvocationOperation main], address = 0x00007fff257530fa, resolved, hit count = 0 
  1.8: where = Foundation`-[_NSBarrierOperation main], address = 0x00007fff2575348b, resolved, hit count = 0 
  1.9: where = Foundation`-[NSThread main], address = 0x00007fff25781825, resolved, hit count = 0
...
  • 简报:
(lldb) breakpoint list 1 -b
1: name = 'main', locations = 107, resolved = 107, hit count = 0
  • 指定多个断点ID或者范围:
(lldb) breakpoint list 1 3
(lldb) breakpoint list 1-3
  • 删除断点
//删除ID为1下的所有断点
(lldb) breakpoint delete 1
//删除ID为1下的1号断点
(lldb) breakpoint delete 1.1
//删除所有断点
(lldb) breakpoint delete

4. Swift vs Objective-C调试上下文

如果你在Swift文件中设置了一个断点,然后输入

//Swift调试上下文
(lldb) po [UIApplication sharedApplication]
error: :3:16: error: expected ',' separator
[UIApplication sharedApplication]
               ^
              ,

这时,你处于Swift调试上下文中,所以没法使用OC方法进行调用,使用Swift的方法调用是可以的;相应的你在OC上下文中,调用Swift的方法是不行的。

//OC调试上下文
(lldb) po UIApplication.shared
error: property 'shared' not found on object of type 'UIApplication'
  • 如果你想使用另一个上下文进行调试怎么办呢?
    expression可以用-l设置解释的语言(objc/swift)
    比如在上面的例子中,在Swift调试上下文中,使用OC方法调用。
//Swift调试上下文
(lldb) po UIApplication.shared


(lldb) expression -l objc -O -- [UIApplication sharedApplication]

需要注意的是,如果在程序运行过程中,突然断住,默认使用的是Objective-C调试上下文

5. 创建自定义变量

我们在程序运行过程中,点击⏸暂停按钮。创建一个临时变量test,我们需要用到$符号。

//突然暂停,默认是OC调试上下文
(lldb) po id $test = [NSObject new]
(lldb) po $test


(lldb) expression -l swift -O -- $test

  • 我们试试Xcode自动创建的变量
  1. 在xcode中创建一个符号断点


    符号断点
// viewDidLoad
override func viewDidLoad() {
  super.viewDidLoad()
  title = "Quarterback"
}
  1. 执行p self
(lldb) p self
(Signals.MasterContainerViewController) $R0 = 0x00007fa827708b90 {
  ...
}

这时,我们便拿到了对VC的引用变量$R0。让程序继续运行,之后再点击暂停按钮。

(lldb) po $R0.title
error: use of undeclared identifier '$R0'

你可能会???。因为你突然断住了,所以默认是OC调试上下文。我们要重新设置一下解析语言。

(lldb) expression -l swift -- $R0.title
(String?) $R2 = "Quarterback"
title
  1. 下面我们来设置一下这个值,再继续运行。
(lldb) expression -l swift -- $R0.title = "Enjoy"
title
  1. 我们再暂停,执行一下:
expression -l swift -O -- $R0.viewDidLoad()

什么都没有发生?实际上这个方法已经执行了。如果你断开断点就会发现,title已经变回去了。


title
  • 为什么没有进入我们设置的断点呢?
    默认情况下,LLDB在执行命令时会忽略任何的断点。
  1. 你可以通过-i选项进行设置。
(lldb) expression -l swift -O -i 0 -- $R0.viewDidLoad()
//输出
error: Execution was interrupted, reason: breakpoint 1.1.
The process has been left at the point where it was interrupted, use "thread return -x" to return to the state before expression evaluation.

忽略错误信息,你会发现LLDB已经断在viewDidLoad方法里面了。

image.png

6. 格式化输出

在LLDB中输入

(lldb) expression -G x -- 10
(Int) $R0 = 0x000000000000000a

其中-G表示GDB格式说明符,x表示以16进制

6.1 GDB格式化输出

LLDB允许你使用更短的语法来进行打印。

(lldb) p/x 10
(int) $0 = 0x0000000a
(lldb) p/t 10
(int) $1 = 0b00000000000000000000000000001010
(lldb) p/t -10
(int) $2 = 0b11111111111111111111111111110110
(lldb) p/t 10.0
(double) $3 = 0b0100000000100100000000000000000000000000000000000000000000000000
(lldb) p/d 'D'
(char) $4 = 68
(lldb) p/c 1430672467
(int) $5 = STFU
  • 输出格式支持
• x: 十六进制
• d: 十进制
• u: 无符号十进制 
• o: 八进制
• t: 二进制
• a: 地址
• c: char
• f: float
• s: string

6.2 LLDB额外的输出格式

(lldb) expression -f Y -- 1430672467
(int) $0 = 53 54 46 55             STFU
  • 输出格式支持
• B: boolean
• b: binary
• y: bytes
• Y: bytes with ASCII
• c: character
• C: printable character
• F: complex float
• s: c-string
• i: decimal
• E: enumeration
• x: hex
• f: float
• o: octal
• O: OSType
• U: unicode16
• u: unsigned decimal
• p: pointer

7. Image命令

image命令是target modules的缩写命令,专门用来查询modules的相关信息。

(lldb) image list
[  0] 932A7C03-F5CA-3C72-A92B-6BF9C891B398 0x000000010dd61000 /Users/xxx/Library/Developer/Xcode/DerivedData/Signals-bfxxbqqkqjqewpeuwhfjcbjbfjyt/Build/Products/Debug-iphonesimulator/Signals.app/Signals 
[  1] EBC07CB6-870A-3A8E-B48A-67F62EA161F3 0x000000010e1fd000 /usr/lib/dyld 
[  2] 75369F31-702D-364A-95C3-8AFA9DD4B3A2 0x000000010dd96000 /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/usr/lib/dyld_sim 
[  3] DE911A6B-0A08-35E8-9946-AE52E5D32042 0x000000010e0a5000 /Users/xxx/Library/Developer/Xcode/DerivedData/Signals-bfxxbqqkqjqewpeuwhfjcbjbfjyt/Build/Products/Debug-iphonesimulator/Signals.app/Frameworks/Commons.framework/Commons 
[  4] 56E47800-2CCB-3B7D-B94B-CCF5F13D6BCF 0x00007fff256b8000 /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/Foundation.framework/Foundation 
...
[405]                                                         __lldb_apple_objc_v2_get_dynamic_class_info 
[406]                                                         __lldb_caller_function 
[407]                                                         __lldb_apple_objc_v2_get_shared_cache_class_info 
[408]                                                         __lldb_caller_function 

第一个模块是我们app的,第二个和第三个模块是动态链接dyld的。

如果你想只查看某个模块,可以用:

(lldb) image list Foundation
[  0] 56E47800-2CCB-3B7D-B94B-CCF5F13D6BCF 0x00007fff256b8000 /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/Foundation.framework/Foundation 

我们仔细看每一条打印信息会发现:

  1. 模块的UUID会在最前面打印出来。这个就是模块的唯一标识,在查询符号信息的时候非常重要。比如上面56E47800-2CCB-3B7D-B94B-CCF5F13D6BCF 0x00007fff256b8000就是Foundation模块的唯一标识。
  2. UUID后面的是加载地址。这个指明了Foundation模块加载到app中的位置。
  3. 最后是模块在硬盘上的完整地址。

如果你想仔细看看UIKit模块:

image dump symtab UIKit -s address
ymtab, file = /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/UIKit.framework/UIKit, num_symbols = 3 (sorted by address):
               Debug symbol
               |Synthetic symbol
               ||Externally Visible
               |||
Index   UserID DSX Type            File Address/Value Load Address       Size               Flags      Name
------- ------ --- --------------- ------------------ ------------------ ------------------ ---------- ----------------------------------
[    0]      0     Data            0x0000000000000fd0 0x00007fff2c7dafd0 0x0000000000000028 0x001e0000 UIKitVersionString
[    1]      1     Data            0x0000000000000ff8 0x00007fff2c7daff8 0x0000000000000008 0x001e0000 UIKitVersionNumber


Symtab, file = /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/AccessibilityBundles/UIKit.axbundle/UIKit, num_symbols = 7021 (sorted by address):
               Debug symbol
               |Synthetic symbol
               ||Externally Visible
               |||
Index   UserID DSX Type            File Address/Value Load Address       Size               Flags      Name
------- ------ --- --------------- ------------------ ------------------ ------------------ ---------- ----------------------------------
[    0]      0     Code            0x00000000000022c8 0x00000001109e52c8 0x000000000000001f 0x000e0000 +[_UIStatusBarStringViewAccessibility(SafeCategory) safeCategoryTargetClassName]
[    1]      1     Code            0x00000000000022e7 0x00000001109e52e7 0x0000000000000011 0x000e0000 +[_UIStatusBarStringViewAccessibility(SafeCategory) safeCategoryBaseClass]
[    2]      2     Code            0x00000000000022f8 0x00000001109e52f8 0x000000000000007f 0x000e0000 +[_UIStatusBarStringViewAccessibility _accessibilityPerformValidations:]
[    3]      3     Code            0x0000000000002377 0x00000001109e5377 0x0000000000000267 0x000e0000 -[_UIStatusBarStringViewAccessibility accessibilityLabel]
[    4]      4     Code            0x00000000000025de 0x00000001109e55de 0x000000000000007b 0x000e0000 -[_UIStatusBarStringViewAccessibility accessibilityTraits]
[    5]      5     Code            0x0000000000002659 0x00000001109e5659 0x0000000000000008 0x000e0000 -[_UIStatusBarStringViewAccessibility _accessibilitySupportsActivateAction]
[    6]      6     Code            0x0000000000002661 0x00000001109e5661 0x000000000000008b 0x000e0000 -[_UIStatusBarStringViewAccessibility accessibilityActivate]
[    7]      7     Code            0x00000000000026ec 0x00000001109e56ec 0x000000000000000a 0x000e0000 -[_UIStatusBarStringViewAccessibility accessibilityHint]
[    8]      8     Code            0x00000000000026f6 0x00000001109e56f6 0x00000000000000d8 0x000e0000 -[_UIStatusBarStringViewAccessibility canBecomeFocused]
[    9]      9     Code            0x00000000000027ce 0x00000001109e57ce 0x0000000000000011 0x000e0000 +[AXUIKitGlue sharedGlueObjectIfAvailable]
[   10]     10     Code            0x00000000000027df 0x00000001109e57df 0x00000000000000e8 0x000e0000 +[AXUIKitGlue _accessibilityInitializeSubclassRuntimeOverrides]
...

如果你想看某一个信息,可以参考第2部分。

(lldb) image lookup -n "-[UIViewController viewDidLoad]"
(lldb) image lookup -rn UIViewController
(lldb) image lookup -rn '\[UIViewController\ '
(lldb) image lookup -rn \[UIViewController\s
(lldb) image lookup -rn '\[UIViewController\(\w+\)\ '

7.1代码断点

我们在UnixSignalHandler.m设置一个断点,如下图所示。

代码断点
 (lldb) frame info
frame #0: 0x000000010e8e51d0 Commons`__34+[UnixSignalHandler sharedHandler]_block_invoke(.block_descriptor=0x000000010e8ec268) at UnixSignalHandler.m:68:28

我们可以看到完整的方法名是__34+[UnixSignalHandler sharedHandler]_block_invoke,其中_block_invoke能帮助你快速定位OC中的block。

我们也可以看到这个方法是位于Commons这个模块的,我们就可以在Commons搜索到所有的block了。

(lldb) image lookup -rn _block_invoke Commons
6 matches found in /Users/ycpeng/Library/Developer/Xcode/DerivedData/Signals-bfxxbqqkqjqewpeuwhfjcbjbfjyt/Build/Products/Debug-iphonesimulator/Signals.app/Frameworks/Commons.framework/Commons:
        Address: Commons[0x00000000000015d0] (Commons.__TEXT.__text + 1168)
        Summary: Commons`__32-[UnixSignalHandler initPrivate]_block_invoke at UnixSignalHandler.m:78        Address: Commons[0x00000000000011c0] (Commons.__TEXT.__text + 128)
        Summary: Commons`__34+[UnixSignalHandler sharedHandler]_block_invoke at UnixSignalHandler.m:67        Address: Commons[0x0000000000001940] (Commons.__TEXT.__text + 2048)
        Summary: Commons`__38-[UnixSignalHandler appendSignal:sig:]_block_invoke at UnixSignalHandler.m:119        Address: Commons[0x0000000000001980] (Commons.__TEXT.__text + 2112)
        Summary: Commons`__38-[UnixSignalHandler appendSignal:sig:]_block_invoke_2 at UnixSignalHandler.m:123        Address: Commons[0x0000000000001cf0] (Commons.__TEXT.__text + 2992)
        Summary: Commons`__38-[UnixSignalHandler appendSignal:sig:]_block_invoke_3 at UnixSignalHandler.m:135        Address: Commons[0x00000000000017f0] (Commons.__TEXT.__text + 1712)
        Summary: Commons`__32-[UnixSignalHandler initPrivate]_block_invoke.24 at UnixSignalHandler.m:105

7.2 block断点

我们来给appendSignal方法中的block设置断点。

(lldb) rb appendSignal.*_block_invoke -s Commons
Breakpoint 1: 3 locations.

然后在Terminal终端中输入

pkill -SIGIO Signals
appendSignal

我们可以看到这个block的名字是__38-[UnixSignalHandler appendSignal:sig:]_block_invoke_2。当一个方法中有多个block时,系统会自动为他们添加序号。

这时我们查看当前的参数,我们可能比较懵

frame variable
(int) sig = 
(siginfo_t *) siginfo = 
(UnixSignalHandler *const) self = 

我们step over一下,再来看,就是我们熟悉的信息了。

(lldb) frame variable
(int) sig = 23
(siginfo_t *) siginfo = 0x00007ffee17e2cd8
(UnixSignalHandler *) self = 0x0000600000f83ec0
(UnixSignal *) unixSignal = 0x000000010f2076b9

step over实际上是,让block完成了自己的init逻辑。

参考资料上提到这时的打印应该有block的类型__block_literal_5,但我这里确实没有输出。书上的输出
(__block_literal_5 *) = 0x0000608000275e80
(int) sig = 23
(siginfo_t *) siginfo = 0x00007fff587525e8
(UnixSignalHandler *)self = 0x000061800007d440
(UnixSignal *) unixSignal = 0x000000010bd9eebe

不过现在知道了,也可以强行打印一下

(lldb) image lookup -t __block_literal_5
Best match found in /Users/xxx/Library/Developer/Xcode/DerivedData/Signals-bfxxbqqkqjqewpeuwhfjcbjbfjyt/Build/Products/Debug-iphonesimulator/Signals.app/Frameworks/Commons.framework/Commons:
id = {0x100000c05}, name = "__block_literal_5", byte-size = 52, decl = UnixSignalHandler.m:123, compiler_type = "struct __block_literal_5 {
    void *__isa;
    int __flags;
    int __reserved;
    void (*__FuncPtr)();
    __block_descriptor_withcopydispose *__descriptor;
    UnixSignalHandler *const self;
    siginfo_t *siginfo;
    int sig;
}"

这个对象定义了block。

参考资料直接对这个对象进行了打印,发现他就是个__NSMallocBlock__对象。

(lldb) po ((__block_literal_5 *)0x0000618000070200)
<__NSMallocBlock__: 0x0000618000070200>
...

如果我们想继续深入打印,我们又不能像书上直接拿到地址怎么办呢?
我们通过RDI寄存器一样可以拿到。

(lldb) po $rdi
<__NSMallocBlock__: 0x600000fa22c0>
 ...

现在我们就可以打印__block_literal_5结构体的成员了。

(lldb) p/x ((__block_literal_5 *)0x600000fa22c0)->__FuncPtr
(void (*)()) $1 = 0x000000010e75f980 (Commons`__38-[UnixSignalHandler appendSignal:sig:]_block_invoke_2 at UnixSignalHandler.m:123)
// 验证这个函数地址
(lldb) image lookup -a 0x000000010e75f980
      Address: Commons[0x0000000000001980] (Commons.__TEXT.__text + 2112)
      Summary: Commons`__38-[UnixSignalHandler appendSignal:sig:]_block_invoke_2 at UnixSignalHandler.m:123
// 打印其他属性
(lldb) po ((__block_literal_5 *)0x600000fa22c0)->sig
23

我们再进一步:

(lldb) po 0x600000fa22c0
<__NSMallocBlock__: 0x600000fa22c0>
...
//查询__NSMallocBlock__什么输出都没有
(lldb) image lookup -rn __NSMallocBlock__
//__NSMallocBlock__的父类是__NSMallocBlock
(lldb) po [__NSMallocBlock__ superclass]
__NSMallocBlock
//__NSMallocBlock主要是负责内存管理的
(lldb) image lookup -rn __NSMallocBlock
5 matches found in /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation:
        Address: CoreFoundation[0x0000000000197d60] (CoreFoundation.__TEXT.__text + 1664208)
        Summary: CoreFoundation`-[__NSMallocBlock retain]        Address: CoreFoundation[0x0000000000197d80] (CoreFoundation.__TEXT.__text + 1664240)
        Summary: CoreFoundation`-[__NSMallocBlock release]        Address: CoreFoundation[0x0000000000197d90] (CoreFoundation.__TEXT.__text + 1664256)
        Summary: CoreFoundation`-[__NSMallocBlock retainCount]        Address: CoreFoundation[0x0000000000197da0] (CoreFoundation.__TEXT.__text + 1664272)
        Summary: CoreFoundation`-[__NSMallocBlock _tryRetain]        Address: CoreFoundation[0x0000000000197db0] (CoreFoundation.__TEXT.__text + 1664288)
        Summary: CoreFoundation`-[__NSMallocBlock _isDeallocating]
//__NSMallocBlock的父类是NSBlock
(lldb) po [__NSMallocBlock superclass]
NSBlock
//查看NSBlock的方法
(lldb) image lookup -rn 'NSBlock\ '
7 matches found in /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation:
        Address: CoreFoundation[0x0000000000197a40] (CoreFoundation.__TEXT.__text + 1663408)
        Summary: CoreFoundation`+[NSBlock allocWithZone:]        Address: CoreFoundation[0x0000000000197a60] (CoreFoundation.__TEXT.__text + 1663440)
        Summary: CoreFoundation`+[NSBlock alloc]        Address: CoreFoundation[0x0000000000197a80] (CoreFoundation.__TEXT.__text + 1663472)
        Summary: CoreFoundation`-[NSBlock copy]        Address: CoreFoundation[0x0000000000197a90] (CoreFoundation.__TEXT.__text + 1663488)
        Summary: CoreFoundation`-[NSBlock copyWithZone:]        Address: CoreFoundation[0x0000000000197aa0] (CoreFoundation.__TEXT.__text + 1663504)
        Summary: CoreFoundation`-[NSBlock invoke]        Address: CoreFoundation[0x0000000000197ab0] (CoreFoundation.__TEXT.__text + 1663520)
        Summary: CoreFoundation`-[NSBlock performAfterDelay:]        Address: CoreFoundation[0x0000000000197b40] (CoreFoundation.__TEXT.__text + 1663664)
        Summary: CoreFoundation`-[NSBlock debugDescription]

//在方法列表中我们发现可以invoke,那么我们在LLDB中直接调用
(lldb) po id $block = (id)0x600000fa22c0
(lldb) po [$block retain]
(lldb) po [$block invoke]
//成功调用
2020-03-04 17:34:13.786816+0800 Signals[29511:755040] Appending new signal: SIGIO
 nil

7.3 私有调试方法

我们来看看OC的私有方法。我们知道一个方法以_开头一般都是一个私有方法。

(lldb) image lookup -rn (?i)\ _\w+description\]

这里就不贴出来了,有很多。但我们可以发现UIKitNSObject中有一些叫IvarDescriptionCategory

(lldb) image lookup -rn NSObject\(IvarDescription\)
7 matches found in /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/PrivateFrameworks/UIKitCore.framework/UIKitCore:
        Address: UIKitCore[0x0000000000e3c010] (UIKitCore.__TEXT.__text + 14914928)
        Summary: UIKitCore`-[NSObject(IvarDescription) __ivarDescriptionForClass:]        Address: UIKitCore[0x0000000000e3c1bb] (UIKitCore.__TEXT.__text + 14915355)
        Summary: UIKitCore`-[NSObject(IvarDescription) _ivarDescription]        Address: UIKitCore[0x0000000000e3c29e] (UIKitCore.__TEXT.__text + 14915582)
        Summary: UIKitCore`-[NSObject(IvarDescription) __propertyDescriptionForClass:]        Address: UIKitCore[0x0000000000e3c748] (UIKitCore.__TEXT.__text + 14916776)
        Summary: UIKitCore`-[NSObject(IvarDescription) _propertyDescription]        Address: UIKitCore[0x0000000000e3c816] (UIKitCore.__TEXT.__text + 14916982)
        Summary: UIKitCore`-[NSObject(IvarDescription) __methodDescriptionForClass:]        Address: UIKitCore[0x0000000000e3cd20] (UIKitCore.__TEXT.__text + 14918272)
        Summary: UIKitCore`-[NSObject(IvarDescription) _methodDescription]        Address: UIKitCore[0x0000000000e3cdee] (UIKitCore.__TEXT.__text + 14918478)
        Summary: UIKitCore`-[NSObject(IvarDescription) _shortMethodDescription]

这几个方法有意思了。

_ivarDescription
_propertyDescription
_methodDescription
_shortMethodDescription

我们在LLDB中执行:

(lldb) po [[UIApplication sharedApplication] _ivarDescription]

会发现UIApplication背后还隐藏了那么多实例变量。

8. 持久化自定义配置

LLDB在启动时会加载一些初始化文件。如果找到了匹配的文件,则文件内容会被加载到LLDB中。

  1. ~/.lldbinit-[context]。如果你要用Xcode调试,那么这个[context]就是Xcode;如果你要用终端调试,那么这[context]就是lldb

  2. 如果你要在Xcodelldb中都使用某些指令,那么你可以把这些内容写在~/.lldbinit中。

//Xcode中生效
~/.lldbinit-Xcode
//终端中生效
~/.lldbinit-lldb
//Xcode和终端中均生效
~/.lldbinit

下面我们创建一个文件。

touch ~/.lldbinit

然后用你喜欢的编辑工具,在其中写入

command alias -- Yay_Autolayout expression -l objc -O -- [[[[[UIApplication sharedApplication] keyWindow] rootViewController] view] recursiveDescription]

Xcode重新运行起来,点击暂停,输入

(lldb) Yay_Autolayout

这个指令会打印出所有视图,由于比较多,这里就不展示了。

你还可以添加一个指令:

command alias cpo expression -l objc -O --

这个指令和普通po指令差不多,但会使用OC上下文。

9. command regex

command regexcommand alias类似,它的输入语法类似于这样

s///

这是一个普通的正则表达式。以s/开头,指定输入使用替换指令。是将要被替代的部分,部分是用来替代的部分。

command regex rlook 's/(.+)/image lookup -rn %1/'

(.+)表示匹配一个或多个字符,%1表示匹配出来的内容。那么这个指令就可以理解为,用rlook后面输入参数,用(.+)匹配出来的内容,来替换image lookup -rn %1中的%1的内容。例如:

//等价于image lookup -rn FOO
rlook FOO

当然,这个指令你也可以添加到~/.lldbinit文件中,方便使用。

  • 更加复杂的指令
command regex -- tv 's/(.+)/expression -l objc -O -- @import QuartzCore; [%1 setHidden:!(BOOL)[%1 isHidden]]; (void)[CATransaction flush];/'

这个指令有3步。

  • @import QuartzCore; 导入QuartzCore框架,确保LLDB知道你后面的代码可以执行。
  • [%1 setHidden:!(BOOL)[%1 isHidden]]; 设置是否隐藏。
  • [CATransaction flush]; 刷新CATransaction队列。更新LLDB中执行的结果,不需要continue就能看到变化。
tv [[[UIApp keyWindow] rootViewController] view]
隐藏视图
  • 链式正则输入

在处理对在内存和寄存器中的对象时,OC的调试上下文非常必要。而且,以[``和@开头的表达式基本可以肯定是OC。Swift调试内存太麻烦了,又不让访问寄存器,它也不是以[``和@开头的表达式。

下面我们创建一个新指令getcls

command regex getcls 's/(([0-9]|\$|\@|\[).*)/cpo [%1 class]/'

有了前面的基础,我们知道这是调用getcls后面匹配成功的参数,替换%1,并利用OC上下文po出对应的类。

(lldb) getcls @"hello world"
__NSCFString
(lldb) getcls @[@"hello world"]
__NSSingleObjectArrayI
(lldb) getcls [UIDevice currentDevice]
UIDevice
(lldb) cpo [UIDevice currentDevice]

(lldb) getcls 0x600000955a00
UIDevice

但如果我们在Swift调试上下文中

(lldb) getcls self
error: getcls

这是因为正则表达式没有匹配到你输入的内容,因为它不是以[``和@开头,也不是0x`这种地址形式。我们需要升级一下这个匹配:

command regex getcls 's/(([0-9]|\$|\@|\[).*)/cpo [%1 class]/' 's/(.+)/expression -l swift -O -- type(of: %1)/'

在后面追加了任何匹配,并将匹配通过type(of:)获取到它的类型。我们再试一次。

(lldb) getcls self
Signals.MasterContainerViewController

你可能感兴趣的:((一) LLDB使用)