Linux c - 利用JMP指令打动态桩

代码开发时,本模块的代码经常会用到外部模块的某些函数,这些外部函数在进行单元测试时一般会进行打桩。

这种一般称之为静态打桩,比如:

void func()
{
    exfunc();  // 本模块的func函数调用了外部模块的exfunc函数
}
//在进行本模块的单元测试时 exfunc在编译时会提示未定义 所以一般会进行如下打桩

int exfunc()
{
    return 0; // 空函数直接返回 或 做某些特定的实现
}

对于一些函数,我们希望在正式版本中做一些处理,而在单元测试中做另外一些处理。

这时我们一般会使用编译宏,比如:

void function()
{
    #ifdef _UNIT_TEST
    call_unittest_func();
    #else
    call_normal_func();
    #endif
}

这种使用编译宏来区分单元测试版本和正式版本的方法很简单,但是会使代码中存在很多的编译宏,使得代码不容易阅读。

下面介绍一种利用JMP指令,打动态桩的方法。

所谓动态桩是指,代码中调用函数A时,不改变代码流程,却能够不执行A,而去执行B函数。

代码如下:

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>


#define JMP_OFFSET_LEN  5   //JMP指令的长度

//JMP相对跳转
void install_stub(void* src_func, void* dst_func)
{
    int pagesize = sysconf(_SC_PAGESIZE);                                          // 系统页大小
    unsigned long srcpage = (unsigned long)((unsigned long)src_func & 0xFFFFF000); // 计算原函数地址所在的页 的首地址
    mprotect((void*)srcpage, pagesize, PROT_READ | PROT_WRITE | PROT_EXEC);        // 使用mprotect函数使该页的内存可读可写可执行
    unsigned char jmpcmd[JMP_OFFSET_LEN] = {0};
    unsigned int offset = (unsigned long)dst_func - (unsigned long)src_func - JMP_OFFSET_LEN;
    jmpcmd[0] = 0xE9;                                                              // JMP指令
    memcpy(&jmpcmd[1], &offset, sizeof(offset));                                   // 偏移
    memcpy(src_func, jmpcmd, JMP_OFFSET_LEN);                                      // 将原函数的地址替换为JMP指令 跳转到目的函数
}

//JMP绝对跳转
void install_stub2(void* src_func, void* dst_func)
{
    int pagesize = sysconf(_SC_PAGESIZE);                                          // 系统页大小
    unsigned long srcpage = (unsigned long)((unsigned long)src_func & 0xFFFFF000); // 计算原函数地址所在的页 的首地址
    mprotect((void*)srcpage, pagesize, PROT_READ | PROT_WRITE | PROT_EXEC);        // 使用mprotect函数使该页的内存可读可写可执行
    unsigned char jmpcmd[14] = {0};                                                // JMP远跳只支持32位程序 64位程序地址占8个字节 寻址有问题
    jmpcmd[0] = 0xFF;                                                              // 当JMP指令为 FF 25 00 00 00 00时,会取下面的8个字节作为跳转地址
    jmpcmd[1] = 0x25;                                                              // 因此可以使用14个字节作为指令 (FF 25 00 00 00 00) + dstaddr
    jmpcmd[2] = 0x00;                                                              
    jmpcmd[3] = 0x00;
    jmpcmd[4] = 0x00;
    jmpcmd[5] = 0x00;
    unsigned long dstaddr = (unsigned long)dst_func;
    memcpy(&jmpcmd[6], &dstaddr, sizeof(dstaddr));                                 
    memcpy(src_func, jmpcmd, sizeof(jmpcmd));   
}

void test_funcA()
{
    printf("call funcA\n");
}

void test_funcB()
{
    printf("call funcB\n");
}

int main()
{
    install_stub2((void*)test_funcA, (void*)test_funcB);  // 添加动态桩 用B替换A
    test_funcA();                                        // 执行A函数
    return 0;
}

编译执行会发现,调用test_funcA函数,实际执行的是test_funcB。

这种不改变A函数的实现,不改变调用A函数的代码流程,通过JMP指令跳转到执行B函数,即称之为动态打桩。

 

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值