(一) LLDB使用

作者: 收纳箱 | 来源:发表于2020-03-04 23:07 被阅读0次

    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<Swift.String> at SwiftTestClass.swift:28
    

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

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

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

    (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<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自动创建的变量
    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 = <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\]
    

    这里就不贴出来了,有很多。但我们可以发现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/<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
    

    相关文章

      网友评论

        本文标题:(一) LLDB使用

        本文链接:https://www.haomeiwen.com/subject/wudplhtx.html