用Python玩转LLDB

1. 为什么会有脚本桥接

在LLDB中有很多方式可以创建自定义命令。
第一种便是command alias,它为一个静态的命令创建了一个别名。虽然操作起来容易,但它局限于处理那些没有输入的命令(所谓静态)。

然后第二种便是command regex,它允许你通过指定一个正则表达式来捕获输入并将它传给一个命令。当你想提供输入给一个LLDB命令时这个方法表现得很好,但当执行多行命令它就显得很不方便了。另外,这个方法也只能提供一个输入参数。

最后,这个在便捷性和复杂性中间折中的方案便是LLDB的脚本桥接script bridging。有了它你几乎可以做任何事情。脚本桥接是一个Python接口,LLDB用它来扩展调试器来完成你高端的调试需求。
然而,这个脚本桥接接口是有代价的。它有着非常陡峭的学习曲线,并且它的文档简直弱爆了。幸运的是,这里可以帮助你学习和了解脚本桥接。一旦你掌握了LLDB的Python模块,你就可以做一些非常酷(或者恐怖)的事情。

2. 说在前面

在正式讨论脚本桥接之前,这里我想先谈一个曾让我脑袋爆炸的python脚本:

/Applications/Xcode.app/Contents/SharedFrameworks/LLDB.framework/Versions/A/Resources/Python/lldb/macosx/heap.py

正是这个脚本让我决定深入了解LLDB。它做了很多事情:找出所有的malloc的object的堆栈(malloc_info -s),获取NSObject一个特定子类的所有实例(obj_refs -0),找出所有引用某一内存位置的指针(ptr_refs),找出内存中的c字符串(cstr_ref)

你可以通过下面的LLDB命令加载这个脚本:
(lldb) command script import lldb.macosx.heap
不幸的是,这个脚本稍微有点小问题,因为编译器在更新而这个脚本没有怎么变过。但你读完这篇文章后,墙裂建议你读懂这个脚本的内容,从中你可以学习到很多东西。

3. Python 101

正如前面所述,LLDB的脚本桥接是调试器的一个Python接口。这意味着你可以在LLDB中加载和执行Python代码。在这些Python代码中,你可以引入lldb模块来与调试器交互来获取各种信息(比如命令的参数等)。

先看看LLDB用的是哪个版本的Python。打开一个命令行并输入:

$ lldb
(lldb) script import sys
(lldb) script print (sys.version)

script命令呼唤出LLDB的Python的解释器。如果你仅仅键入script不带参数,便会进入LLDB的Python REPL(Python交互的命令行)。

4. HelloWorld

从今往后,假定你把LLDB的Python脚本都放在了这个目录:~/lldb
在该目录下新建一个叫helloworld.py的文件,内容如下:

def your_first_command(debugger, command, result, internal_dict):
    print("hello world!")

在LLDB中如何使用这个Python代码呢?

(lldb) command script import ~/lldb/helloworld.py
(lldb) command script add -f helloworld.your_first_command yay
(lldb) yay
hello world!

# 顺便观察一下
(lldb) script helloworld
<module 'helloworld' from '/Users/gogleyin/lldb/helloworld.py'>
(lldb) script dir(helloworld)
 ['__builtins__', '__doc__', '__file__', '__name__', '__package__','your_first_command']
(lldb) script helloworld.your_first_command
<function your_first_command at 0x10bee0500>

5. 更有效率地导入Python脚本

我们当然不希望每次启动LLDB时都输入上面这些繁琐的命令。

幸运的是,LLDB有一个非常好用的函数叫lldb_init_module。一旦Python模块被加载到LLDB中时它就会被调用。这意味着你可以在这个函数里面把your_first_command安装到LLDB里面去.

在上述那个py文件中加入下面这个函数,然后在你的~/.lldbinit文件中import这个脚本:

# 文件:helloworld.py
...
def __lldb_init_module(debugger, internal_dict):
    debugger.HandleCommand('command script add -f helloworld.your_first_command yay')

# 文件:~/.lldbinit
...
command script import ~/lldb/helloworld.py

现在开始每次启动LLDB你都可以使用yay这个命令啦。
稍微解释一下,这个函数相当于模块载入回调函数,debuggerinternal_dict都是它被调用时的上下文。其中debugger是个SBDebugger实例,它有个HandleCommand方法,这个方法的作用差不多等同于输入命令到LLDB中去。

你阔以通过Python的help命令来查看帮助文档:

(lldb) script help(lldb.SBDebugger.HandleCommand)
Help on method HandleCommand in module lldb:

HandleCommand(self, *args) unbound lldb.SBDebugger method
    HandleCommand(self, str command)

(截止在写这篇文章的时候)你会得到上面这个令人非常失望的帮助文档,所以说这玩意有非常陡峭的学习曲线。

6. 调试桥接脚本

在开发或者学习编写桥接脚本时,随着脚本规模的增长,避免不了会出现一些诡异或者预期之外的问题,所以学会调试是非常重要的。命令行下调试python那就是pdb了。他是Python的一个内建模块,有着基本的调试器的功能。
(注意:在Xcode中pdb是不能正常工作的。pdb调试python脚本必须在命令行下的lldb里面进行。)

6.1 使用pdb进行调试

# 文件:helloworld.py
...
def your_first_command(debugger, command, result, internal_dict):
    import pdb; pdb.set_trace()  # 添加这行,即添加断点,执行到下面一行时会停止
    print ("hello world")

# 回到LLDB中:
(lldb) command source ~/.lldbinit  # 因为helloworld.py内容改变需要重新加载
(lldb) yay zzz
> /Users/gogleyin/lldb/helloworld.py(3)your_first_command()
-> print("hello world!")
(Pdb)  # 进入了pdb的交互模式
# 用Python创建一个LLDB命令时,有3个预期参数:debugger,command和result。现在来探索一下
(Pdb) command  # 这个就是提供给自定义命令的参数
'zzz'
(Pdb) result  # SBCommandReturnObject的实例,用来判断一个LLDB命令是否执行成功
<lldb.SBCommandReturnObject; proxy of <Swig Object of type 'lldb::SBCommandReturnObject *' at 0x10be02a20> >
(Pdb) result.AppendMessage("2nd hello world!")  # 你可以追加文本用来显示(当命令执行完毕时)
(Pdb) debugger  # SBDebugger的实例
<lldb.SBDebugger; proxy of <Swig Object of type 'lldb::SBDebugger *' at 0x10be02b70> >
(Pdb) c  # 恢复执行
hello world!
2nd hello world!
(lldb)  # 回到了lldb交互模式

6.2 pdb的post mortem调试

何为post mortem?算后检查,脚本抛(未被捕获的)异常后来支持查看出问题的代码堆栈

不多说。先在~/lldb新建一个文件叫findclass.py,内容如下:

import lldb 

def __lldb_init_module(debugger, internal_dict):
    debugger.HandleCommand('command script add -f findclass.findclass findclass')


def findclass(debugger, command, result, internal_dict):
    """
    The findclass command will dump all the Objective-C runtime classes it knows about.
    Alternatively, if you supply an argument for it, it will do a case sensitive search
    looking only for the classes which contain the input. 

    Usage: findclass  # All Classes
    Usage: findclass UIViewController # Only classes that contain UIViewController in name
    """ 


    codeString = r'''
    @import Foundation;
    int numClasses;
    Class * classes = NULL;
    classes = NULL;
    numClasses = objc_getClassList(NULL, 0);
    NSMutableString *returnString = [NSMutableString string];
    classes = (__unsafe_unretained Class *)malloc(sizeof(Class) * numClasses);
    numClasses = objc_getClassList(classes, numClasses);

    for (int i = 0; i < numClasses; i++) {
      Class c = classes[i];
      [returnString appendFormat:@"%s,", class_getName(c)];
    }
    free(classes);
    
    returnString;
    '''

    res = lldb.SBCommandReturnObject()
    debugger.GetCommandInterpreter().HandleCommand("expression -lobjc -O -- " + codeString, res)
    if res.GetError(): 
        raise AssertionError("Uhoh... something went wrong, can you figure it out? :]")
    elif not res.HasResult():
        raise AssertionError("There's no result. Womp womp....")
        
    returnVal = res.GetOutput()
    resultArray = returnVal.split(",")
    if not command: # No input supplied 
        print returnVal.replace(",", "\n").replace("\n\n\n", "")
    else: 
        filteredArray = filter(lambda className: command in className, resultArray)
        filteredResult = "\n".join(filteredArray)
        result.AppendMessage(filteredResult)


先不用研究这段代码,进入命令行实战:

➜  ~ lldb -n MPlayerX   # 随便找个Mac上的App的进程
(lldb) command script import ~/lldb/findclass.py
(lldb) help findclass
     For more information run 'help findclass'  Expects 'raw' input (see 'help raw-input'.)

Syntax: findclass

    The findclass command will dump all the Objective-C runtime classes it knows about.
    Alternatively, if you supply an argument for it, it will do a case sensitive search
    looking only for the classes which contain the input.

    Usage: findclass  # All Classes
    Usage: findclass UIViewController # Only classes that contain UIViewController in name
(lldb) findclass
error: libarclite_macosx.a(arclite.o) failed to load objfile for /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/arc/libarclite_macosx.a
error: libarclite_macosx.a(arclite.o) failed to load objfile for /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/arc/libarclite_macosx.a
Traceback (most recent call last):
  File "/Users/gogleyin/lldb/findclass.py", line 40, in findclass
    raise AssertionError("Uhoh... something went wrong, can you figure it out? :]")
AssertionError: Uhoh... something went wrong, can you figure it out? :]

# 出错啦!而且也没有明确信息指出出错原因是啥。没关心,掏出pdb干它~!

(lldb) script import pdb
(lldb) findclass
Traceback (most recent call last):
  File "/Users/gogleyin/lldb/findclass.py", line 40, in findclass
    raise AssertionError("Uhoh... something went wrong, can you figure it out? :]")
AssertionError: Uhoh... something went wrong, can you figure it out? :]
(lldb) script pdb.pm()
> /Users/gogleyin/lldb/findclass.py(40)findclass()
-> raise AssertionError("Uhoh... something went wrong, can you figure it out? :]")
(Pdb) print codeString  # 这个东西包含了一段oc代码,用oc runtime来找出runtime的所有类
    @import Foundation;
    int numClasses;
    Class * classes = NULL;
    classes = NULL;
    numClasses = objc_getClassList(NULL, 0);
    NSMutableString *returnString = [NSMutableString string];
    classes = (__unsafe_unretained Class *)malloc(sizeof(Class) * numClasses);
    numClasses = objc_getClassList(classes, numClasses);

    for (int i = 0; i < numClasses; i++) {
      Class c = classes[i];
      [returnString appendFormat:@"%s,", class_getName(c)];
    }
    free(classes);

    returnString;  # 返回returnString的值给Python脚本
(Pdb) l 35, 45  # 会列出35到45行代码,注意40行的 -> 表示当前pdb停在的位置
 35         '''
 36
 37         res = lldb.SBCommandReturnObject()
 38         debugger.GetCommandInterpreter().HandleCommand("expression -lobjc -O -- " + codeString, res)
 39         if res.GetError():
 40  ->         raise AssertionError("Uhoh... something went wrong, can you figure it out? :]")
 41         elif not res.HasResult():
 42             raise AssertionError("There's no result. Womp womp....")
 43
 44         returnVal = res.GetOutput()
 45         resultArray = returnVal.split(",")
# 嗯,似乎res.GetError()看起来更加有趣,玩一下先
(Pdb) print res.GetError()
error: 'objc_getClassList' has unknown return type; cast the call to its declared return type
error: 'objc_getClassList' has unknown return type; cast the call to its declared return type
error: 'class_getName' has unknown return type; cast the call to its declared return type
# 到这里可以看到,问题是codeString里面的代码让LLDB迷惑了。
# 实际这种错误在LLDB里面是非常常见的。你需要告诉LLDB一个函数的返回类型,因为它无法知道那是啥。
# 在这个case下,objc_getClassList和class_getName都有未知的返回类型
# Google一下便知这两个函数的签名如下:
 int objc_getClassList(Class *buffer, int bufferCount);
const char * class_getName(Class cls);
# 所有我们需要做的事转换返回类型到正确的值就可以啦。如下:

codeString = r'''
@import Foundation;
int numClasses;
Class * classes = NULL;
classes = NULL;
numClasses = (int)objc_getClassList(NULL, 0);
NSMutableString *returnString = [NSMutableString string];
classes = (__unsafe_unretained Class *)malloc(sizeof(Class) *
numClasses);
numClasses = (int)objc_getClassList(classes, numClasses);
for (int i = 0; i < numClasses; i++) {
  Class c = classes[i];
  [returnString appendFormat:@"%s,", (char *)class_getName(c)];
}
free(classes);
returnString;
'''
# 保存然后回到LLDB命令行中来,Ctrl+D跳出pdb环境:
(Pdb) ^D
(lldb) command script import ~/lldb/findclass.py  # 重新加载
(lldb) findclass
WKObject
WKNSURLRequest
WKNSURLAuthenticationChallenge
WKNSURL
... (太多内容省略)
(lldb) findclass ViewController
NSServiceViewControllerUnifyingProxy
PDFViewController
PDFViewControllerPrivate
NSRemoteViewControllerAuxiliary
NSServiceViewControllerAuxiliary
... (太多内容省略)

至此,你成功的用pdb来排查了一个脚本中的问题啦!

6.3 expression的debug选项

LLDB的expression命令有一大堆的选项。这里要隆重介绍的,就是其中的--debug选项啦(或者-g

如果你带上这个参数,�LLDB会计算这个表达式,但表达是会被写到一个文件上。
废话不多说来实操吧。

# 打开findclass.py并跳到38行,把-g插进去:
...
 debugger.GetCommandInterpreter().HandleCommand("expression -lobjc -O -- " + codeString, res)
...

# 跳回到lldb命令行
(lldb) findclass  # 执行会停止在JIT(just in time)编译器创建的一个方法上,以允许你调式你的代码
Traceback (most recent call last):
  File "/Users/gogleyin/lldb/findclass.py", line 40, in findclass
    raise AssertionError("Uhoh... something went wrong, can you figure it out? :]")
AssertionError: Uhoh... something went wrong, can you figure it out? :]
Process 34194 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = signal 2147483647
    frame #0: 0x0000000112020c20 $__lldb_expr1`$__lldb_expr($__lldb_arg=0x00007fffecca5642) at expr1.cpp:9508
   9505
   9506 void
   9507 $__lldb_expr(void *$__lldb_arg)
-> 9508 {
   9509     ;
   9510     /*LLDB_BODY_START*/
   9511     @import Foundation;

# 这个脚本现在会抛出一个错误因为带上了--debug选项。如果用pdb来观察res.GetError,你会发现它的信息如下:
 Execution was halted at the first instruction of the expression function because "debug" was requested...
# 这个是正常的并不是你的错误放心。不过需要注意的是此时这个脚本不会有返回值

# 现在你可以观察,进入甚至修改参数等
(lldb) l  # 等同于source list,可重复执行,打印完所有代码行后便不再有输出了
   9512     int numClasses;
   9513     Class * classes = NULL;
   9514     classes = NULL;
   9515     numClasses = (int)objc_getClassList(NULL, 0);
   9516     NSMutableString *returnString = [NSMutableString string];
   9517     classes = (__unsafe_unretained Class *)malloc(sizeof(Class) * numClasses);
   9518     numClasses = (int)objc_getClassList(classes, numClasses);
(lldb) gui  # 这是个最近引入的非常强大的命令,在命令行下模拟出了一个GUI调试界面。
# 在这个gui里,你可以用N来step over,用s来step into,Fn+F1来呼出菜单,X来退出返回到命令行

使用--debug选项是定位JIT代码中的问题的非常好的手段。而且用xcode比命令行更好哦。(见图2)

注意:在调式JIT代码时使用po命令偶尔会出错,如果你遇到了,可以选择使用frame variable命令。

图1.png
图2.png

7. 脚本桥接的类图

7.1 Overview

基本的类:

  • lldb.SBDebugger:在你的脚本中用来访问类的实例的类,非常中心,还处理LLDB命令的输入和输出
  • lldb.SBTarget:与被调试的可执行文件有关(相关调试文件,磁盘上的文件)。
    你可以用SBDebugger的实例来获取到当前选择的SBTarget。然后 你就可以通过SBTarget访问大部分其余类。
  • lldb.SBProcessSBTargetSBProcess是一对多的关系:SBTarget管理者一个或多个SBProcess实例。SBProcess处理内存读写还有它自己的线程。
  • lldb.SBThread:管理对应线程的栈帧和stepping的控制逻辑
  • lldb.SBFrame:管理局部变量(debug信息有提供的)和当时的寄存器快照
  • lldb.SBModule:代表着一个可执行文件。
  • lldb.SBFunction:这代表着一个加载到内存中的函数(或者对应代码),它与SBFrame是一对一的关系。

一图胜过千言万语:



上图是一个简化版的演示了LLDB Python主要的几个类之间的相互关系。

7.2 用lldb来探索lldb模块

暂时停一下前进的步伐,把下面这行加到你的~/.lldbinit文件中:
command alias reload_script command source ~/.lldbinit
它的作用是用添加另一个叫reload_script的LLDB命令,来简化重新加载lldbinit文件的过程。(因为lldbinit文件里面有加载对应py脚本的命令,所以重新加载lldbinit文件就可以了)。

另一方面,这还是能够保证lldbinit文件里面的东西是ok没问题的。LLDB启动时lldbinit文件里面的错误是不会被察觉,因为LLDB启动时没有访问stderr的权限。但在LLDB加载后重新加载init文件会在控制台里提示你脚本文件存在的任何语法错误。

新建一个Single View Application叫Meh,注意语言选择为Swift。然后打开ViewController.swift在viewDidLoad()方法的开头处添加一个GUI断点,接着构建运行,断点命中,进入到lldb中来,开启我们的探索之旅吧:

(lldb) script lldb.debugger  # 这些都是全局变量
<lldb.SBDebugger; proxy of <Swig Object of type 'lldb::SBDebugger *' at 0x10d1b04b0> >
(lldb) script lldb.target
<lldb.SBTarget; proxy of <Swig Object of type 'lldb::SBTarget *' at 0x10d5e7990> >
(lldb) script print lldb.target
Meh
(lldb) script print lldb.process
SBProcess: pid = 69880, state = stopped, threads = 7, executable = Meh
(lldb) script print lldb.thread
thread #1: tid = 0x15822cc, 0x000000010353ea70 Meh`ViewController.viewDidLoad(self=0x00007fa9d9905670) -> () at ViewController.swift:13, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
(lldb) script print lldb.frame  # 当前frame
frame #0: 0x000000010353ea70 Meh`ViewController.viewDidLoad(self=0x00007fa9d9905670) -> () at ViewController.swift:13
(lldb) script help(lldb.target)  # 会有非常详细的说明!
...

可以看到想获取一个实例的summary时print会更加有用点,
就好像对一个对象调用po就会返回NSObject的description一样在oc中。
如果不使用print命令,你就必须得通过属性变量才能获取到对应的信息。

注意!!在这些一行命令中使用这些Python的全局变量是ok的,
但是在脚本中千万别这么做。因为你会改变一些状态(比如step out一个函数),
并且这些全局变量直到你的脚本结束时才会更新。

正确的引用这些实例的方式是从SBDebugger开始(被传进到你的函数当中了),
然后层层访问获取到对应的实例。

7.3 查找帮助文档的正确姿势

官方文档

官方API文档才是最根本的:LLDB Python API

花式打开官方文档

这里,我有一个命令分享一下(添加到你的lldbinit文件中吧):
command regex gdocumentation 's/(.+)/script import os; os.system("open https:" + unichr(47) + unichr(47) + "lldb.llvm.org" + unichr(47) + "python_reference" + unichr(47) + "lldb.%1-class.html")/'
确保上面是一行的哦。

想查看SBTarget的文档时,只要(lldb) gdocumentation SBTarget浏览器就会弹出来并自动打开这个类对应的官方文档网页。是不是很酷?

如果你是认真的

如果你真想透彻掌握lldb的Python模块,或者准备写一个与lldb交互的商业应用,是时候换一个更加方便的搜索文档的姿势了。

因为目前官网没有搜索的功能,你需要一个能够简单地对一个查询查找所有类的方法。

一个变态的方法是把整个官网文档下载下来,可以使用这个httrack做到。之后就可以通过命令行命令来进行查找。

比如你把整个站点下载到了目录:~/websites/lldb,你就可以进行搜索:
mdfind SBProcess -onlyin ~/websites/lldb

把lldb的邮件列表下下来也是一个不错的想法。你可以在那里得到很多启发和进一步一些概念的解释。

最后一种方式是使用Google的对特定网站的过滤查询功能。比如我想查找所有包含SBTarget的邮件归档,我可以如下进行Google搜索:
SBTarget site:http://lists.llvm.org/pipermail/lldb-dev/

7.4 小试牛刀

现在我们来实现些有趣的东西:在一个函数执行完毕后停下来,打印出返回值,然后继续执行。

废话少说,直接撸代码。在~/lldb下创建BreakAfterRegex.py内容如下。
并在~/.lldbinit文件中添加这样一行:command script import ~/lldb/BreakAfterRegex.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import lldb

def __lldb_init_module(debugger, internal_dict):
    debugger.HandleCommand(
        'command script add -f BreakAfterRegex.breakAfterRegex bar')


def breakAfterRegex(debugger, command, result, internal_dict):
    '''Creates a regular expression breakpoint and adds it.
    Once the breakpoint is hit, control will step out of the current
    function and print the return value. Useful for stopping on
    getter/accessor/initialization methods
    '''

    target = debugger.GetSelectedTarget()
    breakpoint = target.BreakpointCreateByRegex(command)

    if not breakpoint.IsValid() or breakpoint.num_locations == 0:
        result.AppendWarning("Breakpoint isn't valid or hasn't found any hits")
    else:
        result.AppendMessage("{}".format(breakpoint))

    # 设置断点命中回调函数
    breakpoint.SetScriptCallbackFunction("BreakAfterRegex.breakpointHandler")


def breakpointHandler(frame, bp_loc, dict):
    '''The function called when the breakpoint 
    gets triggered
    '''
    thread = frame.GetThread()
    process = thread.GetProcess()
    debugger = process.GetTarget().GetDebugger()
    function_name = frame.GetFunctionName()
    
    # 在脚本里面欲改变被调试程序控制流程时,调试器**控制被调试程序**和**执行脚本**是异步的。
    # 所以你需要设置它为同步等待直到stepOut结束才恢复继续执行脚本
    debugger.SetAsync(False)
    thread.StepOut()

    output = evaluateReturnedObject(debugger,
                                    thread, function_name)
    if output is not None:
        print(output)
    # 返回False或者不返回,这个函数结束后被调试程序就会继续执行。返回True则停止执行。
    return False


def evaluateReturnedObject(debugger, thread, function_name):
    '''Grabs the reference from the return register
    and returns a string from the evaluated value. TODO ObjC only
    '''
    # 初始化一个新的SBCommandReturnObject实例,来完全修改输出内容
    res = lldb.SBCommandReturnObject()
    # 你可以用它来控制命令往哪里输出,而不是立刻输出到stderr或者stdout
    interpreter = debugger.GetCommandInterpreter()
    target = debugger.GetSelectedTarget()
    frame = thread.GetSelectedFrame()
    parent_function_name = frame.GetFunctionName()

    expression = 'expression -lobjc -O -- {}'.format(
        getRegisterString(target))
    interpreter.HandleCommand(expression, res)

    if res.HasResult():
        output = '{}\nbreakpoint: '\
            '{}\nobject: {}\nstopped: {}'.format(
                '*' * 80,
                function_name,
                res.GetOutput().replace('\n', ''),
                parent_function_name)
        return output
    else:
        return None


def getRegisterString(target):
    '''Gets the return register as a string for lldb
    based upon the hardware
    '''
    triple_name = target.GetTriple()
    if 'x86_64' in triple_name:
        return '$rax'
    elif 'i386' in triple_name:
        return '$eax'
    elif 'arm64' in triple_name:
        return '$x0'
    elif 'arm' in triple_name:
        return '$r0'
    raise Exception('Unknown hardware. Womp womp')

有个细微的点需要注意的是,当创建断点回调函数时,这个函数的签名很特别:包括一个SBFrame、SBBreakpointLocation和Python字典。SBBreakpointLocation是SBBreakpoint的某一个breakpoint的实例(比如一个正则断点会有几处命中)。

下面这个类图展示了暂停在某函数时几个类的交互:

同样,在断点回调函数里,你应该使用SBFrame以及SBBreakpointLocation并根据以上类图来获取其他类的实例,而不是直接使用lldb.frame或者其他全局变量,因为它们里面可能保存着一个过时的状态信息(在脚本执行期间)。

来体验一下。创建一个Swift的Single View Application,在ViewController.swift的override func viewDidLoad()处下个断点,构建运行,断点命中后:

(lldb) command source ~/.lldbinit
(lldb) bar NSObject.init\]
SBBreakpoint: id = 2, regex = 'NSObject.init\]', locations = 2
(lldb) c
Process 86748 resuming
********************************************************************************
breakpoint: -[NSObject init]
object: <CALayerArray 0x60800004f5d0>()
stopped: CA::Layer::insert_sublayer(CA::Transaction*, CALayer*, unsigned long)
********************************************************************************
breakpoint: -[NSObject init]
object: <NSISEngine: 0x60800019a000>{ Rows:  Constraints:  Integralization Adjustments:(none)  Statistics:  0 rows. Variable counts:}
stopped: -[NSISEngine init]
********************************************************************************
breakpoint: -[NSObject init]
object: <orphaned without delegate (bug!):0x61000004d2f0>{id: 0}
stopped: -[NSISVariable init]
********************************************************************************
breakpoint: -[NSObject init]
object: 107202383930720
stopped: -[NSISObjectiveLinearExpression init]
... (持续不段的内容会进来)

到此,你成功的实现了一个高级的lldb的Python脚本啦。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容

  • 转载 与调试器共舞 - LLDB 的华尔兹: https://objccn.io/issue-19-2/ 推荐:i...
    F麦子阅读 3,325评论 0 10
  • 与调试器共舞 - LLDB 的华尔兹 nangege 2014/12/19 你是否曾经苦恼于理解你的代码,而去尝试...
    McDan阅读 881评论 0 0
  • 你是否曾经苦恼于理解你的代码,而去尝试打印一个变量的值? NSLog(@"%@", whatIsInsideThi...
    木易林1阅读 954评论 0 4
  • 你是否曾经苦恼于理解你的代码,而去尝试打印一个变量的值? NSLog(@"%@", whatIsInsideThi...
    paraneaeee阅读 1,181评论 0 7
  • 随着Xcode 5的发布,LLDB调试器已经取代了GDB,成为了Xcode工程中默认的调试器。它与LLVM编译器一...
    随风飘荡的小逗逼阅读 1,398评论 0 0