美文网首页
LLDB插件(三)

LLDB插件(三)

作者: 浅墨入画 | 来源:发表于2021-03-07 22:57 被阅读0次

一. lldb自定义插件模版

打开Xcode,Cmd + Shift + N 选择如下,创建LGCatAddress动态库

image.png 接下来把lldb头文件(llvm中获取)放入 LGCatAddress目录 image.png 创建xcconfig文件,分别为Debug.Config.xcconfig Release.Config.xcconfig Common.Config.xcconfig,并进行相应配置
// 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,现在工程结构如下 image.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语言

image.png image.png

此时需要把单元测试框架LGCatAddressTests与动态库LGCatAddress进行关联

image.png image.png

Cmd + U 运行单元测试,报错如下

image.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
image.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文件暴露出来 image.png

单元测试文件LGCatAddressTests.m中,需要把LGCatAddress.h引入
Cmd + U 运行单元测试工程,报错如下

image.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目录

image.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运行单元测试,报错如下

image.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动态库

image.png

删除Frameworks目录

image.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

相关文章

网友评论

      本文标题:LLDB插件(三)

      本文链接:https://www.haomeiwen.com/subject/eyjjqltx.html