一. lldb自定义插件模版
打开Xcode,Cmd + Shift + N 选择如下,创建LGCatAddress动态库
![](https://img.haomeiwen.com/i1212147/50d3b84eb57f3e1a.png)
![](https://img.haomeiwen.com/i1212147/cf73091862fab19b.png)
// Debug.Config.xcconfig内容如下
#include "Common.Config.xcconfig"
// Release.Config.xcconfig内容如下
#include "Common.Config.xcconfig"
// Common.Config.xcconfig内容如下
HEADER_SEARCH_PATHS = ${SRCROOT}/include
删除LGCatAddress.h头文件,把LGCatAddress.m修改为LGCatAddress.mm,现在工程结构如下
![](https://img.haomeiwen.com/i1212147/444ac3099748e5ab.png)
lldb加载插件的时候,去动态库插件的macho中扫描lldb::PluginInitialize符号,扫描到之后调用此方法,此时就在LGCatAddress动态库中定义这样一个函数
// 自定义插件模版
#import <lldb/API/LLDB.h>
#import <Foundation/Foundation.h>
// 动态库插件的mach-o扫描符号 lldb::PluginInitialize
namespace lldb {
//SBDebugger: 当前的调试器
bool PluginInitialize(SBDebugger debugger) {
return true;
}
}
// 定义一个插件还需要继承SBCommandPluginInterface
// 继承SBCommandPluginInterface
namespace LG {
class CatAddressCommand: public
lldb::SBCommandPluginInterface {
public:
CatAddressCommand(NSString *message) :
m_message(message) {}
bool DoExecute(lldb::SBDebugger /*SBDebugger*/, char
** /*command*/, lldb::SBCommandReturnObject & /*result*/) override;
private:
NSString *m_message;
};
}
// 重新实现DoExecute
namespace LG {
bool CatAddressCommand::DoExecute(lldb::SBDebugger /*debugger*/, char ** /*command*/, lldb::SBCommandReturnObject & /*result*/) {
return true;
}
}
小结:自定义插件
- 在插件里面实现lldb::PluginInitialize
- 自定义一个command: (继承自) lldb::SBCommandPluginInterface
- 重新实现DoExecute方法
二. lldb插件注册
// PluginInitialize作用:挂载自定义command
namespace lldb {
bool PluginInitialize(SBDebugger debugger) {
// SBCommandInterpreter:用于处理/解释lldb的命令。
// 通过SBCommandInterpreter把我们定义的lldb命令传递给lldb,告诉lldb这个是内置的命令,并把它放入命令解释器中
// 获取命令解释器,
lldb::SBCommandInterpreter interpreter = debugger.GetCommandInterpreter();
// 接下来定义命令
// cat address <地址> :地址所在的空间:mach-o、堆上、栈上
// AddMultiwordCommand方法第二个参数用于输出内容,比如自定义的cat命令,lldb中输入 help cat 就会输出第二个参数定义的内容
lldb::SBCommand cmd = interpreter.AddMultiwordCommand("cat", NULL);
// 添加下一个关键字
// help cat address就会输出cat address ---- 🦅
cmd.AddCommand("address", new LG::CatAddressCommand(@"Cat 🦅!"), "cat address");
return true;
}
}
namespace LG {
class CatAddressCommand: public
lldb::SBCommandPluginInterface {
public:
CatAddressCommand(NSString *message) :
m_message(message) {}
// 执行当前的命令调用这个函数
bool DoExecute(lldb::SBDebugger /*SBDebugger*/, char
** /*command*/, lldb::SBCommandReturnObject & /*result*/) override;
private:
NSString *m_message;
};
}
// char** 命令传递的参数
namespace LG {
bool CatAddressCommand::DoExecute(lldb::SBDebugger /*debugger*/, char **command /*command*/, lldb::SBCommandReturnObject & /*result*/) {
NSLog(@"%s", *command);
// SBCommandReturnObject ->说明-》结果
return true;
}
}
三. lldb插件测试工程搭建
LGCatAddress动态库TARGETS左下角点击+号,添加单元测试框架LGCatAddressTests,这里选择OC语言
![](https://img.haomeiwen.com/i1212147/ddabfd6275e25ff7.png)
![](https://img.haomeiwen.com/i1212147/68f22bacc55c9c03.png)
此时需要把单元测试框架LGCatAddressTests与动态库LGCatAddress进行关联
![](https://img.haomeiwen.com/i1212147/fb162937c39a46ae.png)
![](https://img.haomeiwen.com/i1212147/45feba49dab92613.png)
Cmd + U 运行单元测试,报错如下
![](https://img.haomeiwen.com/i1212147/9e39c9c6833d17f3.png)
原因:只引入了lldb头文件,没有引入lldb动态库
// 查看lldb路径
$ lldb -P
/Applications/Xcode.app/Contents/SharedFrameworks/LLDB.framework/Resources/Python3
// 打开查看lldb动态库
$ open /Applications/Xcode.app/Contents/SharedFrameworks/LLDB.framework/Resources/Python3
![](https://img.haomeiwen.com/i1212147/6d299ab8cd4d97dd.png)
此时Cmd + B 编译成功,Cmd + U,单元测试也运行成功
接下来就是在单元测试工程中测试LGCatAddress工程,此时动态库LGCatAddress需要对单元测试提供测试接口,所以还需要添加LGCatAddress.h文件,把测试接口放入
// LGCatAddress.h内容如下
#import <lldb/API/LLDB.h>
#import <Foundation/Foundation.h>
// 继承SBCommandPluginInterface
namespace LG {
class CatAddressCommand: public
lldb::SBCommandPluginInterface {
public:
CatAddressCommand(NSString *message) :
m_message(message) {}
// 执行当前的命令调用这个函数
bool DoExecute(lldb::SBDebugger /*SBDebugger*/, char
** /*command*/, lldb::SBCommandReturnObject & /*result*/) override;
private:
NSString *m_message;
};
}
// LGCatAddress.m中把 LGCatAddress.h 引入
#import "LGCatAddress.h"
动态库LGCatAddress需要把LGCatAddress.h文件暴露出来
![](https://img.haomeiwen.com/i1212147/d39fb8a6fb66173a.png)
单元测试文件LGCatAddressTests.m中,需要把LGCatAddress.h引入
Cmd + U 运行单元测试工程,报错如下
![](https://img.haomeiwen.com/i1212147/c2ef15e6dd7f3048.png)
报错原因:xcconfig只配置了动态库LGCatAddress,没有配置单元测试工程LGCatAddressTests
创建xcconfig文件为Tests.Config.xcconfig,并进行相应配置
// Tests.Config.xcconfig内容如下
#include "Common.Config.xcconfig"
创建了这么多xcconfig文件,为什么不配置一个Common.Config.xcconfig呢?
release下动态库脱符号是不一样的,脱的non-global符号,创建多个xcconfig文件是为了不同scheme环境配置
// Release.Config.xcconfig内容如下
#include "Common.Config.xcconfig"
DEPLOYMENT_POSTPROCESSING = YES
STRIP_STYLE = non-global
编译之后,发现动态库LGCatAddress中,Build Settings中也进行了相应配置
接下来验证我们的插件
把可执行文件放入 LGCatAddress目录
![](https://img.haomeiwen.com/i1212147/c40e2af88b88d5a4.png)
// test.c文件内容如下
#include <stdlib.h>
void func1() {
}
int main() {
//mach-o
void *m = &func1;
int a = 0;
//堆上
int *p = (int *)malloc(4);
//栈上
int *b = &a;
return 0;
}
// 单元测试文件LGCatAddressTests.mm内容如下
#import <XCTest/XCTest.h>
#import "LGCatAddress.h"
@interface LGCatAddressTests : XCTestCase
{
lldb::SBDebugger _debugger;
lldb::SBTarget _target;
lldb::SBProcess _process;
}
@end
@implementation LGCatAddressTests
- (void)setUp {
// 初始化SBDebugger -》加载一个可执行文件-〉验证我们的插件
lldb::SBDebugger::Initialize();
_debugger = lldb::SBDebugger::Create();
_debugger.SetAsync(false);
// 1. 初始化target
// 传入可执行文件地址
_target = _debugger.CreateTargetWithFileAndArch("/Users/wangning/Desktop/LGCatAddress/TestExec/a.out", "x86_64");
_target.BreakpointCreateByLocation("test.c", 12);
}
- (void)testExample {
_process = _target.LaunchSimple(nil, nil, "/Users/wangning/Desktop/LGCatAddress/TestExec");
XCTAssertTrue(_process.GetNumThreads() > 0);
lldb::SBThread thread = _process.GetThreadAtIndex(0);
const char * name = thread.GetFrameAtIndex(0).GetFunctionName();
NSLog(@"%s", name);
}
@end
只要方法testExample中能打印出 main,就说明单元测试成功,Cmd + U运行单元测试,报错如下
![](https://img.haomeiwen.com/i1212147/40177509a48c601b.png)
报错原因:单元测试LGCatAddressTests框架没有引用到LLDB动态库
// 修改Tests.Config.xcconfig文件内容如下,Cmd + U 测试成功
#include "Common.Config.xcconfig"
OTHER_LDFLAGS = $(inherited) -F"/Applications/Xcode.app/Contents/SharedFrameworks" -framework "LLDB"
Cmd + U 运行成功
思考如下问题,为什么测试框架LGCatAddressTests中,关联的Tests.Config.xcconfig文件需要配置OTHER_LDFLAGS动态库?而动态库LGCatAddress只需要把LLDB拖入即可?
这是ld链接器功能,当把一个动态库作为文件拖进来之后,它不会配置OTHER_LDFLAGS参数,底层会把动态库形成一个路径。lldb会直接把这个路径代表的文件当作动态库加载进来。
当把一个动态库拖进来,实际上执行的链接器是这样的 ld //Applications/Xcode.app/Contents/SharedFrameworks/LLDB.framework/Resources/LLDB,下面进行验证
删除LLDB动态库
![](https://img.haomeiwen.com/i1212147/82a9db350d8d28eb.png)
删除Frameworks目录
![](https://img.haomeiwen.com/i1212147/4ed88f5d5f635350.png)
// 修改Common.Config.xcconfig文件内容如下
HEADER_SEARCH_PATHS = ${SRCROOT}/include
SLASH=/
// 路径 -》动态库所在的位置
OTHER_LDFLAGS = $(inherited) ${SLASH}/Applications/Xcode.app/Contents/SharedFrameworks/LLDB.framework/Versions/A/LLDB
Cmd + U 运行成功
四. stack address扫描
下面让我们一起来验证插件是否起作用?
// 修改LGCatAddressTests.mm内容如下
#import <XCTest/XCTest.h>
#import "LGCatAddress.h"
@interface LGCatAddressTests : XCTestCase
{
lldb::SBDebugger _debugger;
lldb::SBTarget _target;
lldb::SBProcess _process;
lldb::SBCommandInterpreter _interp;
}
@end
@implementation LGCatAddressTests
- (void)setUp {
// 初始化SBDebugger -》加载一个可执行文件-〉验证我们的插件
lldb::SBDebugger::Initialize();
_debugger = lldb::SBDebugger::Create();
_debugger.SetAsync(false);
// 1. 初始化target
// 传入可执行文件地址
_target = _debugger.CreateTargetWithFileAndArch("/Users/wangning/Desktop/LGCatAddress/TestExec/a.out", "x86_64");
_target.BreakpointCreateByLocation("test.c", 12);
_interp = _debugger.GetCommandInterpreter();
}
- (void)testExample {
_process = _target.LaunchSimple(nil, nil, "/Users/wangning/Desktop/LGCatAddress/TestExec");
XCTAssertTrue(_process.GetNumThreads() > 0);
lldb::SBThread thread = _process.GetThreadAtIndex(0);
const char * name = thread.GetFrameAtIndex(0).GetFunctionName();
// 测试代码
// 手动的命令解释器+CatAddressCommand
LG::CatAddressCommand lgCmd(@"zhin笨");
lldb::SBCommand cmd = _interp.AddMultiwordCommand("cat", NULL);
cmd.AddCommand("address", &lgCmd, "cat address");
//调用我的cat address
lldb::SBCommandReturnObject result;
_interp.HandleCommand("cat address 0x10000", result);
NSLog(@"%s", result.GetOutput());
}
@end
// LGCatAddress.mm文件,修改namespace LG如下
namespace LG {
bool CatAddressCommand::DoExecute(lldb::SBDebugger /*debugger*/, char **command, lldb::SBCommandReturnObject & result) {
NSLog(@"****%s", *command);
// SBCommandReturnObject ->说明-》结果
result.AppendMessage("Cat 🦅");
return true;
}
}
// Cmd + U,运行单元测试框架,namespace LG {} 中的打印内容为单元测试testExample方法中配置的 0x10000地址,testExample方法中打印的内容为Cat 🦅
// 执行成功如下
2021-03-07 11:44:36.067969+0800 xctest[24666:2377189] ****0x10000
2021-03-07 11:44:37.947290+0800 xctest[24666:2377189] Cat 🦅
这里有一个问题,为什么不能直接调用PluginInitialize方法?
要想直接调用PluginInitialize方法,需要在LGCatAddressTests.mm文件testExample方法中加载libLGCatAddress.dylib动态库,现在我们来尝试一下
// 修改LGCatAddressTests.mm文件testExample方法内容如下
- (void)testExample {
_process = _target.LaunchSimple(nil, nil, "/Users/wangning/Desktop/LGCatAddress/TestExec");
XCTAssertTrue(_process.GetNumThreads() > 0);
lldb::SBThread thread = _process.GetThreadAtIndex(0);
const char * name = thread.GetFrameAtIndex(0).GetFunctionName();
// 测试代码
//调用我的cat address
lldb::SBCommandReturnObject result;
_interp.HandleCommand("plugin load /Users/wangning/Library/Developer/Xcode/DerivedData/LGCatAddress-chptclbvjhxgaxciyugwgzjxelmt/Build/Products/Release/libLGCatAddress.dylib", result);
NSLog(@"%s", result.GetOutput());
_interp.HandleCommand("cat address 0x10000", result);
NSLog(@"%s", result.GetOutput());
}
// 测试成功
2021-03-07 12:00:57.177993+0800 xctest[24760:2390894] ****0x10000
2021-03-07 12:01:08.025570+0800 xctest[24760:2390894] Cat 🦅
接下来我们来获取test.c文件中m的地址,查看m在mcho的什么地方?
// 修改LGCatAddressTests.mm文件内setUp方法的断点行数为17
_target.BreakpointCreateByLocation("test.c", 17);
// 修改LGCatAddressTests.mm文件内testExample方法如下
- (void)testExample {
_process = _target.LaunchSimple(nil, nil, "/Users/wangning/Desktop/LGCatAddress/TestExec");
XCTAssertTrue(_process.GetNumThreads() > 0);
lldb::SBThread thread = _process.GetThreadAtIndex(0);
const char * name = thread.GetFrameAtIndex(0).GetFunctionName();
// 测试代码
//调用我的cat address
lldb::SBCommandReturnObject result;
_interp.HandleCommand("plugin load /Users/wangning/Library/Developer/Xcode/DerivedData/LGCatAddress-chptclbvjhxgaxciyugwgzjxelmt/Build/Products/Release/libLGCatAddress.dylib", result);
NSLog(@"%s", result.GetOutput());
_interp.HandleCommand("cat address 0x10000", result);
NSLog(@"%s", result.GetOutput());
// po m
_interp.HandleCommand("po m", result);
lldb::SBStream stream;
// cat address (po m)地址
stream.Printf("cat address %s", result.GetOutput());
_interp.HandleCommand(stream.GetData(), result);
}
// 打印出地址如下
2021-03-07 12:22:01.286431+0800 xctest[24915:2407536] Cat 🦅
2021-03-07 12:22:01.914933+0800 xctest[24915:2407536] ****0x0000000100003f40
// 查看a.out可执行文件内存地址
$ objdump --macho -d /Users/wangning/Desktop/LGCatAddress/TestExec/a.out
/Users/wangning/Desktop/LGCatAddress/TestExec/a.out:
(__TEXT,__text) section
_func1:
100003f40: 55 pushq %rbp
// 地址相同都为100003f40
// LGCatAddressTests.mm文件内,_interp.HandleCommand打断点
(lldb) plugin load /Users/wangning/Library/Developer/Xcode/DerivedData/LGCatAddress-chptclbvjhxgaxciyugwgzjxelmt/Build/Products/Release/libLGCatAddress.dylib
(lldb) help cat address
cat address
Syntax: address
五. 扫描Mach-O文件
// 在当前macho中搜索地址
(lldb) image lookup -a 100003f40
Address: xctest[0x0000000100003f40] (xctest.__TEXT.__info_plist + 1072)
Summary:
扫描macho分为三步
- 扫描Mach-O
- 扫描stack
- 扫描堆
// 扫描macho 打印堆地址
// LGCatAddress.mm内容如下
#import "LGCatAddress.h"
// char** 命令传递的参数
namespace LG {
using lldb::eReturnStatusFailed;
using lldb::eReturnStatusSuccessFinishResult;
using lldb::SBCommandInterpreter;
using lldb::SBCommandReturnObject;
using lldb::SBDebugger;
using lldb::SBError;
using lldb::SBExpressionOptions;
using lldb::SBFrame;
using lldb::SBStream;
using lldb::SBSymbol;
using lldb::SBTarget;
using lldb::SBThread;
using lldb::SBValue;
using lldb::SBAddress;
using lldb::SBSection;
using lldb::SBModule;
std::tuple<bool, const char *> tryStackAddress(SBAddress addr, SBTarget target) {
// frame -> thread
SBThread thread = target.GetProcess().GetSelectedThread();
uint32_t num_frames = thread.GetNumFrames();
SBStream stream;
for (UInt32 i = 0; i < num_frames; i++) {
const uint64_t address = addr.GetLoadAddress(target);
SBFrame frame = thread.GetFrameAtIndex(i);
const uint64_t fp = frame.GetFP();
const uint64_t sp = frame.GetSP();
if (address >= sp && address <= fp) {
stream.Printf("stack address (SP: 0x%llx FP: 0x%llx) %s", sp, fp, frame.GetFunctionName());
return std::make_tuple(true, [NSString stringWithUTF8String:stream.GetData()].UTF8String);
}
}
return std::make_tuple(false, nullptr);
}
bool CatAddressCommand::DoExecute(lldb::SBDebugger debugger, char **command, lldb::SBCommandReturnObject & result) {
SBTarget target = debugger.GetSelectedTarget();
SBThread thread = target.GetProcess().GetSelectedThread();
if (!thread.IsValid()) {
result.SetError("Expects an address");
//false:命令处理失败
return false;
}
// 0x -> 地址 -》SBAddress
// 0x10000
NSString *cmd = [NSString stringWithCString:*command encoding:NSUTF8StringEncoding];
if ( !cmd || cmd.length < 1) {
result.SetError("Expects an address");
return false;
}
long address = 0;
char* end = nullptr;
if ([cmd.lowercaseString hasPrefix:@"0x"]) {
// 按16进制扫描
address = strtol(*command, &end, 16);
} else {
// 按10进制扫描
address = strtol(*command, &end, 10);
}
bool foundAddress = false;
const char *returnDescription = nullptr;
// 把address包装成SBAddress
SBAddress addr = target.ResolveLoadAddress(address);
// 获取 mach-o 所在的Section,获取到的话说明addr在macho中
// 扫描Mach-O
// 扫描stack
if (returnDescription == nullptr) {
std::tuple<bool, const char *> tuple = tryStackAddress(addr, target);
foundAddress = std::get<0>(tuple);
returnDescription = std::get<1>(tuple);
}
// 扫描堆
SBStream stream = SBStream();
if (foundAddress) {
stream.Printf("address:%s, %s", *command, returnDescription);
result.AppendMessage(stream.GetData());
//true:命令处理成功
return true;
} else {
stream.Printf("Couldn't find address, reverting to \"image lookup -a %s\"", *command);
result.AppendMessage(stream.GetData());
return false;
}
}
}
// 动态库插件的mach-o扫描符号 lldb::PluginInitialize
namespace lldb {
//SBDebugger: 当前的调试器
bool PluginInitialize(SBDebugger debugger) {
// 获取命令解释器
lldb::SBCommandInterpreter interpreter = debugger.GetCommandInterpreter();
// 接下来定义命令
// AddMultiwordCommand方法第二个参数用于输出内容,比如自定义的cat命令,lldb中输入 help cat 就会输出第二个参数定义的内容
lldb::SBCommand cmd = interpreter.AddMultiwordCommand("cat", NULL);
// 添加下一个关键字
// help cat address就会输出cat address ---- 🦅
cmd.AddCommand("address", new LG::CatAddressCommand(@"Cat 🦅!"), "cat address ---- 🦅");
return true;
}
}
// 修改LGCatAddressTests.mm文件testExample方法内容如下
- (void)testExample {
_process = _target.LaunchSimple(nil, nil, "/Users/wangning/Desktop/LGCatAddress/TestExec");
XCTAssertTrue(_process.GetNumThreads() > 0);
lldb::SBThread thread = _process.GetThreadAtIndex(0);
const char * name = thread.GetFrameAtIndex(0).GetFunctionName();
// 测试代码
//调用我的cat address
lldb::SBCommandReturnObject result;
_interp.HandleCommand("plugin load /Users/wangning/Library/Developer/Xcode/DerivedData/LGCatAddress-chptclbvjhxgaxciyugwgzjxelmt/Build/Products/Release/libLGCatAddress.dylib", result);
NSLog(@"%s", result.GetOutput());
_interp.HandleCommand("cat address 0x10000", result);
NSLog(@"%s", result.GetOutput());
// po m
lldb::SBStream stream;
_interp.HandleCommand("po m", result);
// cat address (po m)地址
stream.Printf("cat address %s", result.GetOutput());
_interp.HandleCommand(stream.GetData(), result);
NSLog(@"%s", result.GetOutput());
// cat address (po b)地址
_interp.HandleCommand("po b", result);
lldb::SBStream bstream;
bstream.Printf("cat address %s", result.GetOutput());
_interp.HandleCommand(bstream.GetData(), result);
NSLog(@"%s", result.GetOutput());
}
// Cmd + U,单元测试运行成功打印
2021-03-07 22:50:58.298961+0800 xctest[26457:2582788] Couldn't find address, reverting to "image lookup -a 0x10000"
2021-03-07 22:51:01.902436+0800 xctest[26457:2582788] Couldn't find address, reverting to "image lookup -a 0x0000000100003f40"
2021-03-07 22:51:04.082132+0800 xctest[26457:2582788] address:0x00007ffeefbff4dc, stack address (SP: 0x7ffeefbff4c0 FP: 0x7ffeefbff4f0) main
网友评论