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

发表于 2021-05-21
本文字数: 305 阅读时长 ≈ 1 分钟
1
2
3
4
5
6
python -m cProfile -o profile.prof run.py
python -c "import pstats; p=pstats.Stats('profile.prof'); p.sort_stats('time').print_stats()"
# calls, cumulative, file, line, module, name, nfl, pcalls, stdname, time
# apt install graphviz
# pip install gprof2dot
gprof2dot -f pstats del.out | dot -Tpng -o output.png

sympy

发表于 2021-05-19
本文字数: 120 阅读时长 ≈ 1 分钟

https://zhuanlan.zhihu.com/p/337412103
http://sparkandshine.net/prime-primer-primality-tests-generating-factorization/#5

macos dd iso

发表于 2021-05-06
本文字数: 86 阅读时长 ≈ 1 分钟
1
2
3
diskutil list
diskutil umountDisk /dev/disk2
sudo dd if=./linux.iso of=/dev/rdisk2 bs=1m

vsyscall,vdso

发表于 2021-03-14
本文字数: 1.5k 阅读时长 ≈ 1 分钟

vsyscall是kernel提供一个快速调用的方式,其实是牺牲了一定安全性的。
vsyscall.h

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
#ifndef _ASM_X86_VSYSCALL_H
#define _ASM_X86_VSYSCALL_H

enum vsyscall_num {
__NR_vgettimeofday,
__NR_vtime,
__NR_vgetcpu,
};

#define VSYSCALL_START (-10UL << 20)
#define VSYSCALL_SIZE 1024
#define VSYSCALL_END (-2UL << 20)
#define VSYSCALL_MAPPED_PAGES 1
#define VSYSCALL_ADDR(vsyscall_nr) (VSYSCALL_START+VSYSCALL_SIZE*(vsyscall_nr))

#ifdef __KERNEL__
#include <linux/seqlock.h>

#define __section_vgetcpu_mode __attribute__ ((unused, __section__ (".vgetcpu_mode"), aligned(16)))
#define __section_jiffies __attribute__ ((unused, __section__ (".jiffies"), aligned(16)))

/* Definitions for CONFIG_GENERIC_TIME definitions */
#define __section_vsyscall_gtod_data __attribute__ \
((unused, __section__ (".vsyscall_gtod_data"),aligned(16)))
#define __section_vsyscall_clock __attribute__ \
((unused, __section__ (".vsyscall_clock"),aligned(16)))
#define __vsyscall_fn \
__attribute__ ((unused, __section__(".vsyscall_fn"))) notrace

#define VGETCPU_RDTSCP 1
#define VGETCPU_LSL 2

extern int __vgetcpu_mode;
extern volatile unsigned long __jiffies;

/* kernel space (writeable) */
extern int vgetcpu_mode;
extern struct timezone sys_tz;

extern void map_vsyscall(void);

#endif /* __KERNEL__ */

#endif /* _ASM_X86_VSYSCALL_H */

可以看到gettimeofday,time,getcpu这三个系统调用给了固定地址,起始地址是-10UL << 20,依次是三个函数的地址。

getcpu() was added in kernel 2.6.19 for x86-64 and i386. Library support was added in glibc 2.29.

vdso地址随机化,增强了安全性。

1…345…26
AIRobot

AIRobot

AIRobot quick note
130 日志
15 分类
23 标签
GitHub E-Mail
Creative Commons
0%
© 2023 AIRobot | 716k | 10:51