AIRobot

AIRobot quick note


  • 首页

  • 关于

  • 标签

  • 分类

  • 归档

  • 搜索

mock

发表于 2021-06-30
本文字数: 3.3k 阅读时长 ≈ 3 分钟

mock

mock测试就是在测试过程中,对于某些不容易构造或者不容易获取的对象,用一个虚拟的对象来创建以便测试的测试方法。

函数

CPU运行时实际主要是和寄存器打交道。运行函数时就是把对应代码区的指令搬到寄存器中,基于此我们可以在函数开头处替换掉部分指令,让CPU跳转到另外的执行地址以实现mock。

实现

替换指令只需要mov rax; jmp rax;

先把跳转地址放到rax寄存器,再跳转到对应地址。这里也可以把替换掉的指令保存一下,这样就也可以做到恢复原函数。

因为指令位于只读区,替换时还要改变内存属性。

优势

运行时替换,代码侵入性小,可以在单独的单元测试文件中实现。
可以mock没有源码的函数,比如c库中对系统调用的一些封装函数write等。
可以在mock函数中调用原函数
mock的优势,可以使单元测试的覆盖率接近100%。

劣势

因为实现过程用到了mprotect和memcpy,所以不能mock使用到的函数,但是可以通过单独实现所使用函数来避免这个问题
与架构相关,实现方式的指令与架构绑定。主要考虑了x86,x86_64,arm架构。

mock.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
#include <sys/mman.h>
#include <string.h>
#include "mock.h"
​
#define PAGE_SIZE (1<<12)
#define POINTER_LEN (sizeof(void *))
#define MOCK_CODE_LEN (2 + POINTER_LEN + 2)
#define MAX_MOCK_NUM (1024)
​
const char MOV[] = {'\x48', '\xb8'};
const char JMP[] = {'\xff', '\xe0'};
​
​
struct mock_info
{
char origin_code[MOCK_CODE_LEN];
void *addr;
};
​
struct mock_info origin_fns[MAX_MOCK_NUM] = {0};
unsigned int mock_fn_num = 0;
​
​
int mock(void *old_fn, void *new_fn)
{
struct mock_info *info;
int id = -1;
if (mock_fn_num == MAX_MOCK_NUM)
return -1;
for(int i = 0; i < MAX_MOCK_NUM; ++i)
{
if (!origin_fns[i].addr)
{
id = i;
break;
}
}
if (id < 0)
return -1;
info = &origin_fns[id];
info->addr = old_fn;
memcpy(info->origin_code, old_fn, MOCK_CODE_LEN);

unsigned int mod = (unsigned long)old_fn % PAGE_SIZE;

char *mem_start = (char *)old_fn - mod;
if (mprotect(mem_start, mod + MOCK_CODE_LEN, PROT_READ | PROT_WRITE | PROT_EXEC))
return -1;

memcpy(old_fn, MOV, sizeof(MOV));
memcpy(old_fn + sizeof(MOV), &new_fn, sizeof(void *));
memcpy(old_fn + sizeof(MOV) + sizeof(void *), JMP, sizeof(JMP));

if (mprotect(mem_start, mod + MOCK_CODE_LEN, PROT_READ | PROT_EXEC))
return -1;
++mock_fn_num;
return id;
}
​
int unmock(int id)
{
if (id < 0 || id >= MAX_MOCK_NUM)
return -1;
​
if (origin_fns[id].addr)
{
unsigned int mod = (unsigned long)origin_fns[id].addr % PAGE_SIZE;

char *mem_start = (char *)origin_fns[id].addr - mod;

if (mprotect(mem_start, mod + MOCK_CODE_LEN, PROT_READ | PROT_WRITE | PROT_EXEC))
return -1;

memcpy(origin_fns[id].addr, origin_fns[id].origin_code, MOCK_CODE_LEN);

if (mprotect(mem_start, mod + MOCK_CODE_LEN, PROT_READ | PROT_EXEC))
return -1;

origin_fns[id].addr = 0;
}
else
return -1;

return 0;
}
​
int mock_clear()
{
for (int i = 0; i < MAX_MOCK_NUM; ++i)
{
unmock(i);
}

return 0;
}

mock.h

1
2
3
4
5
6
7
8
9
10
#ifndef __MOCK_H
#define __MOCK_H
​
int mock(void *old_fn, void *new_fn);
​
int unmock(int id);
​
int mock_clear();
​
#endif

demo.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <stdio.h>
#include <unistd.h>
#include "mock.h"
​
void fun()
{
printf("fun\n");
}
​
void mock_fun()
{
printf("mock\n");
}
​
void fake_sleep(unsigned long s)
{
return;
}
​
​
int main()
{
int id;
fun();
id = mock(fun, mock_fun);
fun();
unmock(id);
fun();

sleep(2);
id = mock(sleep, fake_sleep);
unmock(id);
return 0;
}

运行结果

1
2
3
fun
mock
fun
cprofile
内联汇编
  • 文章目录
  • 站点概览
AIRobot

AIRobot

AIRobot quick note
130 日志
15 分类
23 标签
GitHub E-Mail
Creative Commons
  1. 1. mock
  2. 2. 函数
  3. 3. 实现
  4. 4. 优势
  5. 5. 劣势
0%
© 2023 AIRobot | 716k | 10:51