C语言的3种打桩方法总结
转自:C语言的3种打桩方法总结
前段时间项目组相对很久以前的工程做单元测试,由于比较久远内部逻辑复杂,外部依赖都比较多,所以想通过打桩的方式(不但要对外部接口还得可以对内部函数)进行函数级别的单元测试。调查了几个方法记录一下,以备以后参照。
google的cmockery
框架实现优点很多,请大家自行google。
但cmockery不能对内部函数进行打桩,所以不满足我们要求。
宏定义
利用C编译器预编译的特点,通过宏定义实现利用C编译器的预编译特点,通过宏定义替换需要打桩的函数。直接上代码。
main.c
- snippet.c
#include "test.h" // 把func1替换成func1_stub_行号 #define func1 func1_(__LINE__) #define func1_(line) func1__(line) #define func1__(line) func1_stub_ ## line // 把func2替换成func2_stub_行号 #define func2 func2_(__LINE__) #define func2_(line) func2__(line) #define func2__(line) func2_stub_ ## line //test函数中func1为第5行 void func1_stub_5(void) { printf("func1_stub\n"); } //test函数中func1为第6行 void func2_stub_6(void) { printf("func2_stub\n"); } int main() { test(); return 0; }
test.h
- snippet.c
#include <stdio.h> void test(void); void func1(void); void func2(void);
test.c
修改函数内存地址,通过Jump指令跳转到stub函数
第一种
demo.c
- snippet.c
#include <stdio.h> #include <stdint.h> #include <sys/mman.h> #include <unistd.h> void bar() { printf("function bar\n"); } void foo() { printf("function foo\n"); } void monkey_patch(void* old_function, void* new_function) { void* page; page = (void*)((uint64_t)old_function & ~(getpagesize()-1)); if(mprotect(page, getpagesize(), PROT_WRITE|PROT_READ|PROT_EXEC)) { perror("mprotect"); } ((uint8_t*)old_function)[0] = 0xeb; /* jmp */ /* * Number of bytes to jump relative to the eip which is 2 bytes past * the address of this instruction. Cannot jump more than 128 bytes. */ ((uint8_t*)old_function)[1] = new_function - old_function - 2; } int main() { foo(); monkey_patch(foo, bar); foo(); return 0; }
第二种
stub.h
- snippet.c
#ifndef __STUB_H__ #define __STUB_H__ #ifdef __cplusplus exern "C" { #endif #define CODESIZE 5U struct func_stub { void *fn; unsigned char code_buf[CODESIZE]; }; int stub_init(); void stub_set(struct func_stub *pstub, void *fn, void *fn_stub); void stub_reset(struct func_stub *pstub); #ifdef __cplusplus } #endif #endif
stub.c
- snippet.c
#include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <memory.h> #include <errno.h> #include <limits.h> #include <sys/mman.h> #include "stub.h" static long pagesize = -1; static inline void *pageof(const void* p) { return (void *)((unsigned long)p & ~(pagesize - 1)); } void stub_set(struct func_stub *pstub, void *fn, void *fn_stub) { pstub->fn = fn; memcpy(pstub->code_buf, fn, CODESIZE); if (-1 == mprotect(pageof(fn), pagesize * 2, PROT_READ | PROT_WRITE | PROT_EXEC)) { perror("mprotect to w+r+x faild"); exit(errno); } *(unsigned char *)fn = (unsigned char)0xE9; *(unsigned int *)((unsigned char *)fn + 1) = (unsigned char *)fn_stub - (unsigned char *)fn - CODESIZE; if (-1 == mprotect(pageof(fn), pagesize * 2, PROT_READ | PROT_EXEC)) { perror("mprotect to r+x failed"); exit(errno); } return; } void stub_reset(struct func_stub *pstub) { if (NULL == pstub->fn) { return; } if (-1 == mprotect(pageof(pstub->fn), pagesize * 2, PROT_READ | PROT_WRITE | PROT_EXEC)) { perror("mprotect to w+r+x faild"); exit(errno); } memcpy(pstub->fn, pstub->code_buf, CODESIZE); if (-1 == mprotect(pageof(pstub->fn), pagesize * 2, PROT_READ | PROT_EXEC)) { perror("mprotect to r+x failed"); exit(errno); } memset(pstub, 0, sizeof(struct func_stub)); return; } int stub_init(void) { int ret; pagesize = sysconf(_SC_PAGE_SIZE); ret = 0; if (pagesize < 0) { perror("get system _SC_PAGE_SIZE configure failed"); ret = -1; } return ret; }
main.c
- snippet.c
#include <stdlib.h> #include <stdio.h> #include <string.h> #include "stub.h" void f1() { printf("f1\n"); return; } void f2() { printf("f2\n"); return; } void *_memset(void *s, int ch, size_t n) { printf("-memset\n"); return s; } int main() { char ac[10] = {1}; struct func_stub stub; if (-1 == stub_init()) { printf("faild to init stub\n"); return 0; } stub_set(&stub, (void *)memset, (void *)_memset); memset(ac, 0, 10); stub_reset(&stub); memset(ac, 0, 10); printf("ac[0] = %hhu\n", ac[0]); stub_set(&stub, (void *)f1, (void *)f2); f1(); stub_reset(&stub); f1(); return 0; }
打赏作者以资鼓励: