测试打桩的方法
测试打桩常用有两种方法:
- 通过宏定义来实现
- 通过
jmp指令来实现
通过 jmp 实现打桩
实现代码如下:
#define JMP_LEN 5#define JMP_INS 0xe9 // 该jmp指令的操作数是相对地址typedef char backup_t[JMP_LEN];/*** 在运行期间打桩* @param [in]old_func 旧的函数* @param [in]new_func 新的函数* @param [out]backup 用于备份旧的函数的入口指令*/void install_stub(void *old_func, void *new_func, backup_t *backup){char jmp_cmd[JMP_LEN] = {0};unsigned offset = 0; // 新旧函数相对地址void *page_addr;int ret = 0;/* 将函数入口所在页设为可读可写可执行 */page_addr = (void*)((uint64_t)old_func & ~(getpagesize() - 1));ret = mprotect(page_addr, getpagesize(), PROT_WRITE | PROT_READ | PROT_EXEC);if (ret) {fprintf(stderr, "mprotect failed.\n");}/* 替换入口指令 */jmp_cmd[0] = JMP_INS;offset = (unsigned long)new_func - (unsigned long)old_func - JMP_LEN;memcpy(&jmp_cmd[1], &offset, sizeof(offset));memcpy(*backup, old_func, JMP_LEN);memcpy(old_func, jmp_cmd, JMP_LEN);}
运行时打桩是否破坏调用关系
结论是:不会
首先看下简单的调用关系。对于如下代码:
int foo(int a) {...}int main() {int res = foo(7);}
汇编代码如下:( gcc -S -O0 -m32 )
foo:pushl %ebpmovl %esp, %ebpsubl $16, %esp # 申请栈空间...leave # 释放栈空间,等价于:movl %ebp, %esp; pop %ebpret # 等价于:pop %eipmain:pushl $7 # 传参call foo # 等价于:pushl %eip, %esp; movl $foo, %eip
从上面的汇编代码可以得知:
函数调用者将返回地址写到栈里面;函数被调者在返回时只需要将返回地址从栈中拿出即可
因此,通过 jmp 进行打桩,不会对函数的调用关系产生影响
