lldb 的断点分类
lldb的断点类型:
-
软件断点:正在调试的二进制文件中的断点,在调试器中使用一种迫使CPU发出软件中断的操作码来停止程序的位置替换现有的操作码,命中断点并发送中断后,调试器将接收中断信号,将操作码替换为原始操作码并等待下一个指令。
-
硬件断点:使用专用硬件来观察CPU和停止执行程序的当前状态。
-
符号断点:直接绑定到符号
-
非符号断点:如果标记了一行代码以供调试器停止程序执行,代码中的位置会转化为调试器实际在其中放置断点的执行地址。
lldb的启动流程
lldb的启动过程其中有三个比较重要的节点:
lldb插件的调用Driver驱动的初始化Driver.mainloop的流程,此处可以类比理解为OBJC-runloop
lldb 插件如何调用
下面这张图描述了lldb插件启动的大概流程
lldb包装成了SBDebugger对象,我们可以使用它的API,图上有写到中间有过程会去系统指定的路径和用户自定义的路径下查找有无插件需要加载,那么lldb是如何调用插件的呢?
插件包装成动态库的样子,同时会在这个动态库的MachO文件中查找lldb::PluginInitialize(lldb::SBDebugger debugger)这个符号,如果有的话直接使用地址来调用这个函数,那么插件中就一定要有这个函数,以此为入口来使用插件。
加载插件的路径:
- 系统路径:
LLDB.framework/Resources/PlugIns - 用户自定义:
~/Library/Application Support/LLDB/PlugIns
Driver 驱动的初始化流程
Driver.mainloop
在进入这个mainloop后,就像在终端进入了lldb环境后一样,lldb一直处于一个等待的状态,这里是使用键盘IO来进行堵塞线程,有输入并回车后,命令会被命令解释器来处理,同时所有的命令都会被包装成CommandObject对象,真正处理的方法就是DoExecute。
SBAPI
LLDB API可以通过脚本桥接接口作为Python函数或者C++函数使用:
SBCommandInterpreter:用于处理解释lldb命令,SBDebugger:调试器对象,lldb.SBDebugger对象拥有在调试回话的命令解释器和所有目标SBTarget:当前选定目标,管理一个正在进行的进程以及该进程的使用的可执行文件和调试文件SBProcess:当前选定的Target所在的进程,lldb.SBProcess对象管理线程并控制访问存储的过程SBThread:当前选定的线程,管理该线程的堆栈帧SBFrame:当前选定的堆栈帧,管理当前堆栈信息和该堆栈使用到的寄存器组SBBreakpoint:创建和管理断点
lldbinit 文件
加载lldbinit文件的两种方式:
- 全局生效
.lldbinit文件:将.lldbinit文件放置于根目录下~/ - 指定目录生效:手动设置的
.lldbinit文件
查找规则:
- 先查找根目录下是否存在
.lldbinit-<当前应用名称>的文件,如果不存在再去查看是否有.lldbinit文件 - 如果使用了REPL,会去查找
.lldbinit-<language>-repl文件,比如使用了Swift则寻找.lldbinit-swift-repl - 最后回去查找工作目录的
.lldbinit文件
REPL:全称为Read-Eval-Print Loop(读取-求值-输出 循环),一个简单的交互式的编程环境,用于接收代码的输入并直接输出响应。
使用自定义的.lldbinit 文件
.lldbinit文件是可以预置一些命令的,比如给命令起一个别名,但是注意此时被起别名的命令应当使用全名而不是缩写
比如起别名Out命令来代替Po命令,command alias Out expression -O --此语句写入.lldbinit文件
使用命令lldb --local-lldbinit使在进入lldb环境时加载工作目录的.lldbinit文件
此时可以看出已经可以使用其命令
如果发现在进入lldb环境后命令并没有挂在上,可以使用command source来重新加载lldb.init文件
同样在Xcode中也有设置自定义lldb.init文件,如下图所示
lldb启动的整体流程
利用SBAPI对lldb进行调试
这段的目的是对照上图执行的语句来类比到SBAPI工程中来实现,从而来通过调用SBAPI来实现对lldb的调试
步骤:
- 创建
commandline工程 - 引入
lldb头文件 - 设置
HEADER_SEARCH_PATHS - 链接
lldb动态库 - 开始调试
搭建环境
首先在创建工程后,先引入lldb头文件,并且正确设置HEADER_SEARCH_PATHS = ${SRCROOT}/include
链接动态库和设置rpath OTHER_LDFLAGS = $(inherited) //${SRCROOT}/liblldb.15.0.0git.dylib LD_RUNPATH_SEARCH_PATHS = ${SRCROOT}
这时程序可以成功运行了,这时应该先创建SBTarget后设置断点然后接着运行。
SBDebugger::Initialize();
SBDebugger debugger = SBDebugger::Create();
debugger.SetAsync(false);
SBTarget target = debugger.CreateTargetWithFileAndArch("/Users/***/Desktop/989/SBAPI_Test/SBAPI_Test/test", "arm64");
//target.BreakpointCreateByLocation("test.m", 12);
target.BreakpointCreateByName("main");
SBProcess process = target.LaunchSimple(nil, nil, "/Users/***/Desktop/989/SBAPI_Test/SBAPI_Test");
NSLog(@"process.State: %d", process.GetState());
SBThread thread = process.GetThreadAtIndex(0);
NSLog(@"Thread: %d",thread.GetNumFrames());
**const** **char** *name = thread.GetFrameAtIndex(0).GetFunctionName();
NSLog(@"%s", name);
此段代码便是使用SBAPI操作从而对lldb进行调试的代码,但是这里会发现在获取线程的时候无论如何都无法获取,但是使用官方的lldb动态库都可以正常获取,这里是使用自己编译的动态库。
由debugserver引起的问题
根据上图的调试流程最终确认到的问题unable to locate debugserver,网上查找可以得到答案
问题应该是出在了LLDB_DEBUGSERVER_PATH这里,接下来可以从LLVM源码中查找相关信息
所以上述问题是因为没有配置LLDB_DEBUGSERVER_PATH环境变量造成的,查找后发现Xcode中debugServer文件
,路径为/Applications/Xcode.app/Contents/SharedFrameworks/LLDB.framework/Versions/A/Resources/debugserver
所以现在在工程中给它配置好后便大功告成