linux库打桩

本文介绍了三种桩函数技术,包括编译时、链接时和运行时打桩,以malloc为例详细阐述了如何替换系统函数,实现对函数调用的拦截和分析。编译时通过宏定义实现,链接时利用链接器的--wrap标志,运行时借助LD_PRELOAD环境变量和动态链接器。
部署运行你感兴趣的模型镜像

库打桩用于截获对共享库函数的调用,提供一个和库函数原型一致的桩函数,可以替换对原函数的调用,是性能分析工具使用的常见手段。打桩可以发生在编译时、链接时、运行时,这里以malloc为例展现桩函数的应用

1、编译时打桩

// main.c
#include <stdlib.h>
#include <stdio.h>
#include <malloc.h>

int main() {
    int *p = malloc(32);   // 这里被打桩,调用mymalloc.c中的桩函数mymalloc()
    free(p);
    exit(0);
}

//malloc.h
#define malloc(size) mymalloc(size)
void *mymalloc(size_t size);

//mymalloc.c
#include <stdio.h>
#include <malloc.h>

void *mymalloc(size_t size) {
    void *ptr = malloc(size);  // 这里调用的是glibc的malloc
    printf("malloc %u=%p\n", size, ptr);
    return ptr;
}

//编译命令
cc -c mymalloc.c
cc -I. main.c mymalloc.o

这里共有3个文件,一个本地的malloc.h文件通过宏定义的方式在预处理阶段打桩,mymalloc.o编译时使用glibc中的malloc.h,而main.c编译时通过-I参数指定本地目录,预处理阶段先在本地目录下搜索头文件,因此使用的是本地目录中的malloc.h,main.c中的malloc()被替换为mymalloc(),从而实现打桩

2、链接时打桩

linux的静态链接器支持--wrap func 标志进行链接时打桩,这个标志的作用是告诉链接器将对符号func的引用解析成__wrap_func,同时将__real_func的引用解析为func,通过符号替换的方式在链接阶段打桩,mymalloc.c中内容如下:

// mymalloc.c
#include <stdio.h>

void *__real_malloc(size_t size);

void *__wrap_malloc(size_t size) {
    void *ptr = __real_malloc(size);
    printf("malloc %u=%p\n", size, ptr);
    return ptr;
}

// 编译命令
cc8 -Wl,--wrap,malloc main.c mymalloc.c

3、运行时打桩

上面两种打桩方式都需要通过自定义的编译指令执行打桩,实际业务场景中往往只有可执行程序的obj文件,没有重新编译的机会,这时就需要运行时打桩机制。运行时打桩机制基于动态链接器的LD_PRELOAD环境变量,若LD_PRELOAD变量被设置为一个共享库路径名的列表,那么加载程序时需要解析为定义的引用时,先在LD_PRELOAD指定的库中查找,然后再查找系统库。malloc.c内容如下:

// mymalloc.c
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>

void *malloc(size_t size) {
    void *(*mallocp)(size_t size);
    char *error;
    mallocp = dlsym(RTLD_NEXT, "malloc");
    if ((error = dlerror()) != NULL) {
        fputs(error, stderr);
        exit(-1);
    }
    char *ptr = mallocp(size);
    printf("malloc %u=%p\n", size, ptr);
    return ptr;
}

// 编译命令
cc main.c
cc -shared -fPIC -o mymalloc.so mymalloc.c -ldl
LD_PRELOAD="./mymalloc.so" ./a.out

这里先将mymalloc编译为本地so文件,然后通过带LD_PRELOAD执行a.out

您可能感兴趣的与本文相关的镜像

Kotaemon

Kotaemon

AI应用

Kotaemon 是由Cinnamon 开发的开源项目,是一个RAG UI页面,主要面向DocQA的终端用户和构建自己RAG pipeline

<think>我们正在讨论使用cppfreemock对动态进行打桩的方法。请注意,用户要求使用中文回答,并遵循LaTeX格式要求。 由于这是一个具体的编程问题,我们需要提供步骤和示例代码。同时,根据要求,我们需要在回答末尾生成相关问题。 首先,我们需要明确:cppfreemock是一个用于C++的mock框架,可以用于对动态函数进行打桩(即替换原始函数的行为)。 主要步骤包括: 1. 包含必要的头文件 2. 定义要mock的函数的原型(如果它们来自动态,我们通常需要知道确切的函数签名) 3. 使用MOCKER宏来设置mock函数的行为 4. 在测试中使用这些mock函数 注意:由于动态的函数是在运行时加载的,cppfreemock通过拦截函数调用来实现打桩。这通常需要链接时指定--wrap选项或者使用LD_PRELOAD,但cppfreemock提供了一种更简便的方式。 然而,cppfreemock的文档并不十分丰富,因此我们将根据常见的用法示例进行说明。 示例:假设我们有一个动态中的函数: int my_func(int a, int b); 我们想要在测试中mock这个函数。 步骤: 1. 在测试代码中包含头文件: #include "cppfreemock/cppfreemock.hpp" 2. 使用MOCKER宏来声明和定义mock函数: // 声明mock函数 MOCKER(my_func); // 这声明了一个mock函数,用于替换my_func 3. 在测试用例中设置期望和打桩行为: using namespace ::testing; TEST(TestSuite, TestCase) { // 设置期望:期望my_func被调用,参数任意,然后返回一个特定值 EXPECT_CALL(*MOCKER(my_func), MOCK_FUNCTION(_, _)) .Times(Exactly(1)) .WillOnce(Return(42)); // 调用被测试代码,其中会调用my_func // 此时,调用会被拦截并返回42 } 4. 注意:在链接测试时,需要链接cppfreemock,并且可能需要指定编译选项(如-fPIC)以及链接选项(如--wrap)。 但是,对于动态函数,我们通常需要确保在测试执行前,mock函数已经被加载并替换了原始函数。cppfreemock通过其初始化函数和拦截机制自动完成。 重要:cppfreemock目前主要支持Linux和macOS,并且要求使用Clang编译器(因为依赖于特定的编译器特性)。 由于动态函数的地址解析发生在运行时,cppfreemock使用了一个技巧:它通过覆盖动态函数在PLT(过程链接表)中的条目来实现拦截。因此,它只能拦截通过PLT调用的函数(即动态中导出的函数)。 详细步骤: 1. 在测试文件中,包含头文件: #include <cppfreemock/cppfreemock.hpp> 2. 声明要mock的函数(注意:这些函数必须是动态中存在的,并且已经在头文件中声明过)。在测试中,我们需要重新声明这些函数(如果它们没有被包含的话),但通常我们会包含它们的头文件。 3. 使用MOCKER宏来创建mock函数。这个宏会定义一个与原始函数同名的mock函数,并且会覆盖原始函数的调用。 4. 编写测试用例,使用EXPECT_CALL来设置期望。 5. 编译测试程序时,需要链接到被测动态以及cppfreemock。同时,需要设置编译器选项:使用Clang,并且开启C++14或更高版本。 示例代码: // 假设我们有一个动态中的函数:int add(int, int); // 我们有一个头文件 mathlib.h #include "mathlib.h" #include <cppfreemock/cppfreemock.hpp> #include <gtest/gtest.h> // 声明我们要mock的函数add // 注意:这里不需要重新声明,因为mathlib.h已经声明了。 // 使用MOCKER宏声明mock函数 // 注意:MOCKER宏会定义一个全局的mock对象,用于后续设置期望 MOCKER(add); TEST(MathTest, TestAdd) { // 设置期望:当add被调用,参数为(1,2)时,返回100 EXPECT_CALL(*MOCKER(add), MOCK_FUNCTION(1, 2)) .Times(1) .WillOnce(Return(100)); // 调用被测试的函数,该函数内部会调用add(1,2) int result = add(1, 2); ASSERT_EQ(result, 100); } int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } 编译命令示例(假设使用clang++): clang++ -std=c++14 -I/path/to/cppfreemock/include -I/path/to/gtest/include \ -L/path/to/cppfreemock/lib -lcppfreemock \ -L/path/to/gtest/lib -lgtest -lgtest_main \ -L/path/to/mathlib -lmath \ -o test test.cpp 注意:在运行测试之前,需要设置动态路径(如果动态不在标准路径): export LD_LIBRARY_PATH=/path/to/mathlib:$LD_LIBRARY_PATH 但是,由于cppfreemock的拦截机制,它可能会要求使用LD_PRELOAD加载自己的。具体请参考cppfreemock的文档。 此外,cppfreemock还提供了对全局函数、静态函数的重载打桩,但要注意,动态函数的打桩需要确保在测试执行时,mock函数被优先解析。 由于cppfreemock的实现依赖于动态链接器的特性,因此它可能无法在所有环境下工作(例如,静态链接的情况)。 总结:使用cppfreemock对动态打桩的关键是使用MOCKER宏声明要mock的函数,然后使用EXPECT_CALL设置期望。编译时需要链接cppfreemock,并确保动态在运行时可用。 生成相关问题: 1. cppfreemock与gmock有什么区别? 2. 如何对C++类的虚函数进行打桩? 3. 使用cppfreemock时遇到链接错误怎么办? 4. cppfreemock是否支持mock私有成员函数? 5. 如何mock动态中的全局变量? </think>### 使用 cppfreemock 对动态进行打桩的方法 cppfreemock 是一个强大的 C++ Mock 框架,用于对动态函数进行打桩(Stub/Mock)。以下是具体操作步骤: #### **1. 包含头文件与初始化** ```cpp #include <cppfreemock/cppfreemock.hpp> using namespace cppfreemock; // 声明要打桩的动态函数原型(假设目标有 funcA) extern "C" int funcA(int param); TEST(MyTestSuite, TestDynamicStub) { Initialize(); // 必须初始化框架 ... } ``` #### **2. 使用 MOCKER 宏打桩** ```cpp MOCKER(funcA) // 声明对 funcA 的 mock .defaults() .returns(100); // 默认返回 100 // 设置带条件的桩:当参数=5时返回 200 MOCKER(funcA).stubs() .with(5) .returns(200); ``` #### **3. 调用与验证** ```cpp // 调用动态函数(实际执行打桩逻辑) ASSERT_EQ(funcA(1), 100); // 触发默认桩 ASSERT_EQ(funcA(5), 200); // 触发条件桩 // 验证调用次数 MOCKER(funcA).verify().times(2); ``` #### **4. 编译与链接** 编译时需要链接 `cppfreemock` 并指定动态路径: ```bash g++ -std=c++11 test.cpp -I/path/to/cppfreemock \ -L/path/to/mocklib -lcppfreemock \ -ldl # 动态加载支持 ``` #### **关键原理** - **动态拦截**:通过 `LD_PRELOAD` 或编译时插桩(`--wrap` 符号)拦截动态函数[^1]。 - **PLT/GOT 重定向**:修改全局偏移表(GOT)实现函数替换[^2]。 - **桩函数注册**:`MOCKER` 宏将桩函数注入调用链。 #### **注意事项** 1. **符号可见性**:动态函数必须导出(非 `static`)。 2. **调用约定**:匹配 `extern "C"` 和原始调用约定。 3. **线程安全**:桩配置需考虑多线程场景。 4. **作用域**:通过 `MOCK_SCOPE` 控制桩的生命周期: ```cpp { MOCK_SCOPE(funcA) { MOCKER(funcA).returns(42); funcA(0); // 返回 42 } // 自动恢复原始函数 } ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值