前段时间项目组相对很久以前的工程做单元测试,由于比较久远内部逻辑复杂,外部依赖都比较多,所以想通过打桩的方式(不但要对外部接口还得可以对内部函数)进行函数级别的单元测试。调查了几个方法记录一下,以备以后参照。
- google的cmockery框架实现
优点很多,请大家自行google。
但cmockery不能对内部函数进行打桩,所以不满足我们要求。
- 利用C编译器预编译的特点,通过宏定义实现
利用C编译器的预编译特点,通过宏定义替换需要打桩的函数。直接上代码。
main.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
#include <stdio.h>
void test(void);
void func1(void);
void func2(void);
test.c
#include "test.h"
void test(void)
{
func1();
func2();
}
void func1(void)
{
printf("func1()\n");
}
void func2(void)
{
printf("func2()\n");
}
- 修改函数内存地址,通过Jump指令跳转到stub函数
第一种
demo.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
#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
#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
#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;
}
1233

被折叠的 条评论
为什么被折叠?



