1. Xcode断点
Xcode断点NSThread初始化arg1和$rdi寄存器意思差不多,可以简单认为它是
init
调用时持有一个类的实例对象。
2. LLDB中常用的两个命令
image
命令非常有用。最常用的有以下2个命令
image lookup -n
-n
意思是让LLDB
查询symbol
或function 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
这个属性会生成getter
和setter
方法:
//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<Swift.String> at SwiftTestClass.swift:28
对比OC你会发现这个名字真的是太长了,如果你要设置一个断点,它将是这样的
(lldb) b Signals.SwiftTestClass.name.setter : Swift.Optional<Swift.String>
如果你想获得属性的所有方法:
(lldb) image lookup -rn Signals.SwiftTestClass.name
在结果中,你会找到它的getter
和setter
方法。
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<Swift.String>
// 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: <EXPR>: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
<UIApplication: 0x7fb072d012d0>
(lldb) expression -l objc -O -- [UIApplication sharedApplication]
<UIApplication: 0x7fb072d012d0>
需要注意的是,如果在程序运行过程中,突然断住,默认使用的是Objective-C调试上下文。
5. 创建自定义变量
我们在程序运行过程中,点击⏸暂停
按钮。创建一个临时变量test
,我们需要用到$
符号。
//突然暂停,默认是OC调试上下文
(lldb) po id $test = [NSObject new]
(lldb) po $test
<NSObject: 0x6000012e8290>
(lldb) expression -l swift -O -- $test
<NSObject: 0x6000012e8290>
- 我们试试Xcode自动创建的变量
-
在xcode中创建一个符号断点
符号断点
// viewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
title = "Quarterback"
}
- 执行
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
- 下面我们来设置一下这个值,再继续运行。
(lldb) expression -l swift -- $R0.title = "Enjoy"
title
- 我们再暂停,执行一下:
expression -l swift -O -- $R0.viewDidLoad()
什么都没有发生?实际上这个方法已经执行了。如果你断开断点就会发现,title已经变回去了。
title
- 为什么没有进入我们设置的断点呢?
默认情况下,LLDB在执行命令时会忽略任何的断点。
- 你可以通过
-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
方法里面了。
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
我们仔细看每一条打印信息会发现:
- 模块的
UUID
会在最前面打印出来。这个就是模块的唯一标识,在查询符号信息的时候非常重要。比如上面56E47800-2CCB-3B7D-B94B-CCF5F13D6BCF 0x00007fff256b8000
就是Foundation
模块的唯一标识。 -
UUID
后面的是加载地址。这个指明了Foundation
模块加载到app中的位置。 - 最后是模块在硬盘上的完整地址。
如果你想仔细看看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 = <read memory from 0x41 failed (0 of 4 bytes read)>
(siginfo_t *) siginfo = <read memory from 0x39 failed (0 of 8 bytes read)>
(UnixSignalHandler *const) self = <read memory from 0x31 failed (0 of 8 bytes read)>
我们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\]
这里就不贴出来了,有很多。但我们可以发现UIKit
的NSObject
中有一些叫IvarDescription
的Category
。
(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中。
-
~/.lldbinit-[context]
。如果你要用Xcode调试,那么这个[context]就是Xcode;如果你要用终端调试,那么这[context]就是lldb。 -
如果你要在Xcode和lldb中都使用某些指令,那么你可以把这些内容写在~/.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 regex
和command alias
类似,它的输入语法类似于这样
s/<regex>/<subst>/
这是一个普通的正则表达式。以s/开头,指定输入使用替换指令。<regex>是将要被替代的部分,<subst>部分是用来替代的部分。
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]
<UIDevice: 0x600000955a00>
(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
网友评论