1. 自定义LLDB命令
我们已经学了一些基础的LLDB
命令。现在是时候吧这些只是组合起来创造一些强力的复杂调试脚本了。LLDB
允许你通过Python
来进行大部分调试,辅助你解开那些隐藏在背后的秘密。
1.1 脚本桥接
LLDB
有几种方法可以自定的命令。之前我们学习了command alias
和command regex
。下面,权衡了便利与复杂的就是脚本桥接(script bridging
)。用它你可以做到几乎你所有想做的事情。它是一个Python
到LLDB
调试器的接口,用来扩展调试功能,完成更复杂的调试需求。
首先必须要提到一个脚本:
/Applications/Xcode.app/Contents/SharedFrameworks/LLDB.framework/Versions/A/Resources/Python/lldb/macosx/heap.py
这个脚本做了这些事情:可以找到调用栈中所有malloc
的对象(malloc_info -s
);可以获取所有NSObject
特定子类的所有示例(obj_refs -O
);可以找到所有指向特定内存地址的指针(ptr_refs
);找到内存中的所有C字符串(cstr_ref
)。
你可以通过下面的方式来加载这个脚本。
command script import lldb.macosx.heap
然而,这个脚本因为编译器改变而代码没有改变,导致它有一些功能没法使用了。
Python 101
LLDB
脚本桥接是Python
到调试器的接口。你可以在LLDB
中加载并执行Python
脚本。在这些Python
脚本中,你需要导入lldb
模块来和调试器进行交互。
我们先来看看LLDB
的Python
版本。
~> lldb
(lldb) script import sys
(lldb) script print (sys.version)
3.7.3 (default, Dec 13 2019, 19:58:14)
[Clang 11.0.0 (clang-1100.0.33.17)]
~> python3 --version
Python 3.7.3
在Python
中玩一玩
~> python3
Python 3.7.3 (default, Dec 13 2019, 19:58:14)
[Clang 11.0.0 (clang-1100.0.33.17)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> h = "hellow world"
>>> h
'hellow world'
>>> h.split(" ")
['hellow', 'world']
如果你想查看Python
类的类型,加上.__class__
就行了。
>>> h.split(" ").__class__
>>> h.__class__
如果你想查看帮助文档,利用help
命令就可以了。
>>> help (str)
>>> help (str.split)
如果你想定义一个函数怎么做呢?
>>> def test(a):
...
省略号表示你开始创建一个函数了。输入两个空格,然后输入print(a + " world!")
。Python
是通过缩进来判断作用域的,如果你的缩进不对,Python
的函数是会报错的。再次点击回车来退出函数的编写。
>>> def test(a):
... print(a + " world!")
...
>>> test("hello")
hello world!
创建你的第一个LLDB Python脚本
首先我们创建一个文件夹~/lldb
。
~> mkdir ~/lldb
你喜欢用什么编辑器都可以,创建~/lldb/helloworld.py
。
def your_first_command(debugger, command, result, internal_dict):
print ("hello world!")
函数中的参数,你可以先不管,就是由LLDB
传过来的参数。
~> lldb
(lldb) command script import ~/lldb/helloworld.py
我们在LLDB
中导入这个文件,如果没有问题的话,什么输出都不会有。因为它只是把文件桥接过来了。如果你要调用里面函数怎么办呢?你首先需要导入对应的模块。
(lldb) script import helloworld
你可以通过列出模块汇总所有函数来验证是否导入成功。
(lldb) script dir(helloworld)
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'your_first_command']
在里面看到了我们定义的your_first_command
方法。
那怎么在LLDB
中调用这个函数呢?
(lldb) command script add -f helloworld.your_first_command yay
(lldb) yay
hello world!
我们通过
command script add
把helloworld
模块中的your_first_command
方法定义为LLDB
命令yay
了。-f
表示你要添加的是一个python
方法。
更有效地设置命令
如果你写了很多自定义脚本了,你肯定不希望每次LLDB
启动的时候,都靠自己来进行导入。幸好,LLDB
有一个叫__lldb_init_module
的模块,来帮助你进行加载。
我们在helloworld.py
追加一下代码。
def your_first_command(debugger, command, result, internal_dict):
print ("hello world!")
#LLDB加载时会自动执行
def __lldb_init_module(debugger, internal_dict):
debugger.HandleCommand('command script add -f helloworld.your_first_command yay')
你传入了一个debugger
就是SBDebugger
的一个实例,然后你调用了它的HandleCommand
方法。这个方法和在LLDB
进行输入的效果差不多。
保存helloworld.py
。然后在~/.lldbinit
中添加导入代码。
command script import ~/lldb/helloworld.py
然后在终端新的tab中启动lldb
。
~> lldb
(lldb) yay
hello world!
1.2 调试脚本桥接
用pdb调试你的调试脚本
在helloworld.py
脚本your_first_command
改成下面这样。
def your_first_command(debugger, command, result, internal_dict):
import pdb; pdb.set_trace()
print ("hello world!")
然后启动LLDB
。
~> lldb
(lldb) yay woot
> /Users/xxx/lldb/helloworld.py(3)your_first_command()
-> print ("hello world!")
(Pdb)
我们可以看到pdb
已经断在了your_first_command
中print
这一行。当用Python
来创建一个LLDB
命令时,会传入几个特别的参数:debugger
、command
和result
。
我们先来试试command
。这个命令会列出你传给yay
命令的所有命令。因为这里没有处理任何命令的逻辑,所以yay
会自动忽略这些输入。
-> print ("hello world!")
(Pdb) command
'woot'
我们再来看看result
。输出是一个SBCommandReturnObject
实例对象。你可以通过它知道代码在LLDB
命令中执行是否成功。另外,你可以添加一些信息。这些信息会在命令执行完毕时显示出来。
(Pdb) result
>
result.AppendMessage("2nd hello world!")
(Pdb) result.AppendMessage("2nd hello world!")
我们先还是保持断住的。先保持这样,我们来看看debugger
。输出是一个SBDebugger
的实例对象。
(Pdb) debugger
>
输入c
让pdb
恢复运行。
(Pdb) c
hello world!
2nd hello world!
pdb死后调试
根据错误类型,pdb
有一个很吸引人的选项,让你可以探究问题发生时的调用栈,但它仅在发生异常时有效。
这里我们重新建一个python文件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
调试Photos
。
~> lldb -n Photos
(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
Traceback (most recent call last):
File "/Users/ycpeng/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/ycpeng/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/ycpeng/lldb/findclass.py(40)findclass()
-> raise AssertionError("Uhoh... something went wrong, can you figure it out? :]")
我们甚至可以在pdb
中查看源代码。
# 表示列出1~50行代码
(Pdb) l 1, 50
其中18~35行是一个长字符串,就是这个命令的核心逻辑。
# 直接打印,可能不太好看
(Pdb) codeString
'\n @import Foundation;\n int numClasses;\n Class * classes = NULL;\n classes = NULL;\n numClasses = objc_getClassList(NULL, 0);\n NSMutableString *returnString = [NSMutableString string];\n classes = (__unsafe_unretained Class *)malloc(sizeof(Class) * numClasses);\n numClasses = objc_getClassList(classes, numClasses);\n\n for (int i = 0; i < numClasses; i++) {\n Class c = classes[i];\n [returnString appendFormat:@"%s,", class_getName(c)];\n }\n free(classes);\n \n returnString;\n '
# 我们用print打印,就会好看很多
(Pdb) print(codeString)
@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;
我们可以看到这是一段OC代码。通过运行时获取所有的类。
我们来看一下报错的地方。我们还能看到40行的断点->
。
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....")
我们来看看这里res.GetError()
到底报了什么错。
(Pdb) print(res.GetError())
error: warning: got name from symbols: classes
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
这个错误看起来就就像平时LLDB
打印出来错误的样子了。我们可以看到objc_getClassList
和class_getName
返回了未知类型。
我们查看一下文档:
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;
'''
保存代码之后,Ctrl + D
退出pdb
。
(lldb) command script import ~/lldb/findclass.py
(lldb) findclass
//打印了很多OC类
我们也可以限制一下我们关心的类型。比如下面我们打印包含ViewController
的类。
(lldb) findclass ViewController
NSServiceViewControllerUnifyingProxy
IPXFeedViewControllerSpec
IPXFeedViewControllerMacSpec
...
expression的调试选项
expression
有个调试选项--debug
或者-g
。
在findclass.py
中38行
debugger.GetCommandInterpreter().HandleCommand("expression -lobjc -O -- " + codeString, res)
加入调试选项-g
debugger.GetCommandInterpreter().HandleCommand("expression -lobjc -g -O -- " + codeString, res)
然后再执行一下
(lldb) command script import ~/lldb/findclass.py
(lldb) findclass
Traceback (most recent call last):
File "/Users/ycpeng/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 14327 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = signal 2147483647
frame #0: 0x000000010b342000 $__lldb_expr1`$__lldb_expr($__lldb_arg=0x0000000000000000) at lldb-566369.expr:42
39
40 void
41 $__lldb_expr(void *$__lldb_arg)
-> 42 {
43 ;
44 /*LLDB_BODY_START*/
45
Target 0: (Photos) stopped.
现在你就可以用LLDB
进行调试了。你需要查看源代码需要用到命令source list
,或者list
,甚至更简单l
。
(lldb) l
46 @import Foundation;
47 int numClasses;
48 Class * classes = NULL;
49 classes = NULL;
50 numClasses = (int)objc_getClassList(NULL, 0);
51 NSMutableString *returnString = [NSMutableString string];
52 classes = (__unsafe_unretained Class *)malloc(sizeof(Class) * numClasses);
或者你还可以使用LLDB
的gui
命令。
(lldb) gui
接下来你就可以点击N
键进行单步调试,用S
键进行step into
了。如果你调试完毕,记得把-g
删掉。
1.3 lldb模块中重要的类
-
lldb.SBDebugger
:你需要用这个类来访问你在调试脚本中创建的其他类。一个函数中一定会有一个对这个类实例对象的引用。这个类负责处理输入到LLDB
中的命令,而且可以控制在哪儿或者怎么展示输出。 -
lldb.SBTarget
:负责在内存中调试的可执行文件、调试文件和驻留在磁盘上的可执行文件的物理文件。调试的时候,你要用SBDebugger
的实例来选择SBTarget
实例,之后你就可以通过它访问其他类了。 -
lldb.SBProcess
:SBTarget
与SBProcess
有一对多关系:SBTarget
管理一个或多个SBProcess
实例。SBProcess
负责内存访问以及进程中的多线程。 -
lldb.SBThread
:管理该特定线程中的栈帧SBFrames
,还管理单步执行的控制逻辑。 -
lldb.SBFrame
:管理本地变量(通过调试信息给出)以及冻结在该特定帧上的任何寄存器。 -
lldb.SBModule
:表示特定的可执行文件。模块可以包含主可执行文件或任何动态加载的代码(如基础框架)。可以使用image list
命令获得加载到可执行文件中的模块的完整列表。 -
lldb.SBFunction
:表示一个加载到内存中的泛型函数。这个类与SBFrame
类有一对一的关系。
通过LLDB来探索lldb
模块
我们每次修改~/.lldbinit
都要输入command source ~/.lldbinit
比较麻烦。我们在~/.lldbinit
添加一个命令。
command alias reload_script command source ~/.lldbinit
我们新建一个tvOS
的项目Meh
,语言选Swift
。设置一个断点,并输入lldb.debugger
。
LLDB
有几个可以方便访问的全局变量:
lldb.SBDebugge -> lldb.debugger
lldb.SBTarget -> lldb.target
lldb.SBProcess -> lldb.process
lldb.SBThread -> lldb.thread
lldb.SBFrame -> lldb.frame
我们可以看看我们当前的target
//直接打印这个类可能看不出什么
(lldb) script lldb.target
>
//我们来print一下
(lldb) script print(lldb.target)
Meh
print
命令可以打印一个实例的概况。就像po
命令调用了OC中NSObject
的description
方法。
再看看其他几个命令的打印效果。
// 打印当前进程的信息
(lldb) script print(lldb.process)
SBProcess: pid = 14955, state = stopped, threads = 8, executable = Meh
// 打印当前线程的信息,还有我们的断点
(lldb) script print(lldb.thread)
thread #1: tid = 0xc525e, 0x000000010e81fad0 Meh`ViewController.viewDidLoad(self=0x00007fe7dfe04b10) at ViewController.swift:12, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
// 打印当前栈帧的信息
(lldb) script print(lldb.frame)
frame #0: 0x000000010e81fad0 Meh`ViewController.viewDidLoad(self=0x00007fe7dfe04b10) at ViewController.swift:12
学习和查看脚本桥接类的文档
通过help
命令可以查看帮助文档。
(lldb) script help(lldb.target)
(lldb) script help(lldb.SBTarget)
为了方便阅读,我们可以在~/.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")/'
输入我们要查询的东西,然后直接就可以跳转到对应的网址了
(lldb) gdocumentation SBTarget
创建breakAfterRegex命令
如何设计一个命令,在函数之后立即停止,打印出返回值,然后继续?
我们来创建一个~/lldb/BreakAfterRegex.py
。
- 使用LLDB创建正则断点。
- 添加一个断点操作动作执行到当前帧完成。
- 利用寄存器的知识打印出正确的寄存器中的返回值。
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):
print ("yay. basic script setup with input: {}".format(command))
这里添加了一个名为bar
的命令。该命令由模块BreakAfterRegex
中的breakAfterRegex
实现。
在~/.lldbinit
添加。
command script import ~/lldb/BreakAfterRegex.py
然后我们在项目中,输入
(lldb) reload_script
(lldb) bar UIViewController test -a -b
yay. basic script setup with input: UIViewController test -a -b
新LLDB
脚本中的输出是提供给它的参数。基本骨架已经准备好了。现在是编写基于输入创建断点的代码的时候了。返回BreakAfterRegex.py
并找到def breakAfterRegex(debugger, command, result, internal_dict):
。删除print语句并将其替换为以下逻辑:
def breakAfterRegex(debugger, command, result, internal_dict):
#1 使用传入的正则参数创建断点。这个断点对象将是SBBreakpoint类型。
target = debugger.GetSelectedTarget()
breakpoint = target.BreakpointCreateByRegex(command)
#2 如果断点创建失败,脚本将警告您它找不到任何可以中断的地方。否则,将打印出断点对象。
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))
#3 设置断点,以便每当断点命中时的回调函数。
breakpoint.SetScriptCallbackFunction("BreakAfterRegex.breakpointHandler")
def breakpointHandler(frame, bp_loc, dict):
# 获取函数名,并打印
function_name = frame.GetFunctionName()
print("stopped in: {}".format(function_name))
return True
注意在函数的末尾返回
True
。返回True
将导致程序停止执行。返回False
,甚至省略return
语句都会导致程序在执行此方法后继续运行。
为断点创建回调函数时,要实现的方法签名不同。刚刚的回调就包括SBFrame
、SBBreakpointLocation
和Python
字典。
SBFrame
表示已停在其中的栈帧。SBBreakpointLocation
是在SBBreakpoint
中找到一个断点的实例。这是非常有意义的。因为对于一个断点,可能有很多点击。特别是如果尝试中断一个经常实现的函数,例如main
,或者使用了会匹配很多结果的正则表达式。
下面显示了当在特定函数上停止时,类的简化交互:
在断点回调函数中,SBFrame
和SBBreakpointLocation
是大多数重要lldb
类的线索。可以通过SBFrame
或SBFrame
对SBModule
的引用来获取所有主要类实例。
请记住,不要在脚本中使用
lldb.frame
或其他全局变量。因为它们在脚本中执行时,信息可能比较落后。因此必须遍历以frame
或bc_loc
开头的变量才能获取到所需类的实例。
我们来试一下。
(lldb) reload_script
(lldb) bar NSObject.init\]
SBBreakpoint: id = 3, regex = 'NSObject.init\]', locations = 2
继续执行,并使用tvOS
模拟器的远程遥控器点击一下触发断点。如果在触发断点时遇到问题,一种可靠的方法是导航到模拟器的主屏幕(⌘ + Shift + H)。
我们已经成功地创建了一个正则断点命令。现在,我们已经停在了NSObject
其中一个init
方法,可能是类方法或实例方法。而且这很可能是NSObject
的一个子类。我们将使用LLDB
在Python
脚本中手动地重现这个操作。
我们结束执行这个方法。因为我们使用的是tvOS
模拟器,它的架构是x64
,所以我们需要使用RAX
寄存器打印出LLDB
中NSObject
的init
的返回值。
(lldb) finish
(lldb) po $rax
打开BreakAfterRegex.py
并重写breakpointHandler
函数。
def breakpointHandler(frame, bp_loc, dict):
#1 文档信息
'''The function called when the regularexpression breakpoint gets triggered'''
#2 从SBFrame出发,通过引用获取到SBDebugger和SBThread的实例
thread = frame.GetThread()
process = thread.GetProcess()
debugger = process.GetTarget().GetDebugger()
#3 获取父函数的名称
function_name = frame.GetFunctionName()
#4 让调试器不要异步执行
debugger.SetAsync(False)
#5 退出这个方法,将不再处于当前的栈帧中
thread.StepOut()
#6 调用evaluateReturnedObject方法获取适当的输出信息
output = evaluateReturnedObject(debugger, thread, function_name)
if output is not None:
print(output)
return False
下面我们来实现evaluateReturnedObject
方法。
def evaluateReturnedObject(debugger, thread, function_name):
'''Grabs the reference from the return register and returns a string from the evaluated value.
TODO ObjC only
'''
#1 实例化SBCommandReturnObject
res = lldb.SBCommandReturnObject()
#2 获取一些后面要用的实例
interpreter = debugger.GetCommandInterpreter()
target = debugger.GetSelectedTarget()
frame = thread.GetSelectedFrame()
parent_function_name = frame.GetFunctionName()
#3 创建要执行的表达式,该表达式将输出返回值
expression = 'expression -lobjc -O -- {}'.format(getRegisterString(target))
#4 通过SBCommandInterpreter执行表达式
# 它允许我们控制输出的位置,而不是立即将其传递到stderr或stdout。
interpreter.HandleCommand(expression, res)
#5 查看执行表达式之后是否有返回值
if res.HasResult():
#6 把断住的函数名、寄存器获取的对象和前一帧的函数名,格式化为字符串并返回该字符串。
output = '{}\nbreakpoint: {}\nobject: {}\nstopped: {}'.format(
'*' * 80,
function_name,
res.GetOutput().replace('\n', ''),
parent_function_name)
return output
else:
#7 如果不需要输出,返回None
return None
还剩寄存器读取没有完成。
# 根据架构返回需要读取的寄存器名
def getRegisterString(target):
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')
我们现在来试试!reload_script
重新加载脚本,再删除现在的所有断点,重新执行bar NSObject.init\]
。然后continue
几次,可以看到一些有用的信息了。
(lldb) reload_script
(lldb) br del
About to delete all breakpoints, do you want to do that?: [Y/n]
All breakpoints removed. (3 breakpoints)
(lldb) bar NSObject.init\]
SBBreakpoint: id = 4, regex = 'NSObject.init\]', locations = 2
********************************************************************************
breakpoint: -[NSObject init]
object:
stopped: -[RBSXPCMessageReply _initWithMessage:]
********************************************************************************
breakpoint: -[NSObject init]
object: 105553124551936
stopped: -[BSXPCCoder initWithMessage:]
********************************************************************************
breakpoint: -[NSObject init]
object: 105553124551936
stopped: objc_object::sidetable_retain()