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这个命令啦。
稍微解释一下,这个函数相当于模块载入回调函数,debugger
和internal_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命令。
7. 脚本桥接的类图
7.1 Overview
基本的类:
-
lldb.SBDebugger
:在你的脚本中用来访问类的实例的类,非常中心,还处理LLDB命令的输入和输出 -
lldb.SBTarget
:与被调试的可执行文件有关(相关调试文件,磁盘上的文件)。
你可以用SBDebugger
的实例来获取到当前选择的SBTarget
。然后 你就可以通过SBTarget
访问大部分其余类。 -
lldb.SBProcess
:SBTarget
和SBProcess
是一对多的关系: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脚本啦。