0%

DynamoRIO sample instrace源码分析

分析DynamoRIO的SampleClient源代码

入口

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
// TLS的id,用于查找TLS的位置
static int tls_idx;
// client_id_t是uint格式
static client_id_t client_id;
// 用于支持多线程
static void *mutex;

// reg_id_t是byte类型,用于包含一个DR_REG_枚举值
static reg_id_t tls_seg;
static uint tls_offs;

DR_EXPORT void
dr_client_main(client_id_t id, int argc, const char *argv[])
{
// 需要1个eflags和2个单独的寄存器
// drreg_options_t : drreg初始化的时候的参数放在一起的一个struct
drreg_options_t ops = { sizeof(ops), 3, false };
dr_set_client_name("DynamoRIO Sample Client 'instrace'",
"http://dynamorio.org/issues");

// 1. 初始化drmgr和drreg
// drmgr用于组合协调多个插桩代码同时插桩时的调度
// drreg在多个插桩发生时协调Context Switch的寄存器调度
// 它们都属于原DynamoRIO的扩展功能
if (!drmgr_init() || drreg_init(&ops) != DRREG_SUCCESS)
DR_ASSERT(false);

// 2. 注册event(回调函数)
// 2.1 退出event
dr_register_exit_event(event_exit);
// 2.2 线程初始化event
if (!drmgr_register_thread_init_event(event_thread_init) ||
// 2.3 线程退出event
!drmgr_register_thread_exit_event(event_thread_exit) ||
// 2.4 该方法可以注册两个阶段的event
// 参数1为分析阶段event,该阶段为即使分析,仅可添加标签指令,本方法设置为null
// 参数2为插桩阶段event,传递单条指令给回调函数,并允许回调函数在待插桩的指令前插入指令,如果遇到其他插桩的指令需要避开该指令
!drmgr_register_bb_instrumentation_event(NULL, event_app_instruction, NULL))
DR_ASSERT(false);

client_id = id;
// 锁
mutex = dr_mutex_create();

// 3. 以下几行代码用于分配TLS(Thread-Local Storage)为原始TLS
// drmgr用于管理TLS的一个方法,返回该槽的地址,如果为-1说明预留槽已经用完了
tls_idx = drmgr_register_tls_field();
DR_ASSERT(tls_idx != -1);
// DR 提供的 TLS 字段无法直接从CodeCache中访问。为了提高性能分配了原始 TLS,这样只需一条指令就能直接访问和更新它。
if (!dr_raw_tls_calloc(&tls_seg, &tls_offs, INSTRACE_TLS_COUNT, 0))
DR_ASSERT(false);

// 4. 日志
dr_log(NULL, DR_LOG_ALL, 1, "Client 'instrace' initializing\n");
}

在主函数中共定义了以下几个event:

  • event_exit:退出event
  • event_thread_init:线程初始化event
  • event_thread_exit:线程退出event
  • event_app_instruction:指令级插桩event

我们一个个看

event_exit

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
// 全局指令计数
static uint64 num_refs;

static void
event_exit(void)
{
dr_log(NULL, DR_LOG_ALL, 1, "Client 'instrace' num refs seen: " SZFMT "\n", num_refs);

// 1. 释放使用dr_raw_tls_calloc()分配的、从偏移量tls_offs开始的原始线程本地存储槽 num_slots。
if (!dr_raw_tls_cfree(tls_offs, INSTRACE_TLS_COUNT))
DR_ASSERT(false);

// 2. 取消注册tls_field,该注册初始化在main函数的第3步
if (!drmgr_unregister_tls_field(tls_idx) ||
// 3. 取消注册线程初始化event
!drmgr_unregister_thread_init_event(event_thread_init) ||
// 4. 取消注册线程退出event
!drmgr_unregister_thread_exit_event(event_thread_exit) ||
// 5. 取消注册插桩event
!drmgr_unregister_bb_insertion_event(event_app_instruction) ||
// 6. 退出drreg
drreg_exit() != DRREG_SUCCESS)
DR_ASSERT(false);

// 7. 销毁锁
dr_mutex_destroy(mutex);

// 8. 退出drmgr
drmgr_exit();
}

event_thread_init

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
// 每个 ins_ref_t 描述一条已执行的指令
typedef struct _ins_ref_t {
app_pc pc;
int opcode;
} ins_ref_t;

// 线程私有的日志文件和计数器
typedef struct {
// seg base执行到的指令的位置
byte *seg_base;
// 已执行的指令的地址
ins_ref_t *buf_base;
file_t log;
FILE *logf;
uint64 num_refs;
} per_thread_t;

// 最大指令数
#define MAX_NUM_INS_REFS 8192
// 最大指令内存
#define MEM_BUF_SIZE (sizeof(ins_ref_t) * MAX_NUM_INS_REFS)

// 已分配的TLS slots的偏移量
enum {
INSTRACE_TLS_OFFS_BUF_PTR,
INSTRACE_TLS_COUNT, // 分配的TLS slots总量
};

// 该方法返回一个指针,先把tls_base转换为byte指针,再加上tls_offs和enum_val代表的偏移量,最终返回TLS_SLOT位置
#define TLS_SLOT(tls_base, enum_val) (void **)((byte *)(tls_base) + tls_offs + (enum_val))
// BUF_PTR指的是指向指令执行地址的指针,调用了上面定义的方法
#define BUF_PTR(tls_base) *(ins_ref_t **)TLS_SLOT(tls_base, INSTRACE_TLS_OFFS_BUF_PTR)


static void
event_thread_init(void *drcontext)
{
// 1. dr_thread_alloc用于分配内存并与drcontext关联
per_thread_t *data = dr_thread_alloc(drcontext, sizeof(per_thread_t));
DR_ASSERT(data != NULL);

// 2. 把per_thread_t放入TLS中
drmgr_set_tls_field(drcontext, tls_idx, data);

/* 3. dr_get_dr_segment_base用于获取由dr_raw_tls_calloc获取的TLS地址
让per_thread_t保留TLS地址本身,这样可以在Thread-local中获取TLS的全局地址
*/
data->seg_base = dr_get_dr_segment_base(tls_seg);

// 4. dr_raw_mem_alloc方法申请内存用于CodeCache
// 第二个参数为protection,DR规定了几个ENUM(DR_MEMPROT_READ、DR_MEMPROT_WRITE 和 DR_MEMPROT_EXEC)用于写在这里
data->buf_base =
dr_raw_mem_alloc(MEM_BUF_SIZE, DR_MEMPROT_READ | DR_MEMPROT_WRITE, NULL);
DR_ASSERT(data->seg_base != NULL && data->buf_base != NULL);
// BUF_PTR指向code cache的开头
BUF_PTR(data->seg_base) = data->buf_base;

// 已执行的指令数初始化为0
data->num_refs = 0;

/* 5. 打开一个log file,
client_id 用于获取client的library path
drcontext用于指定当前线程
NULL表示log放在默认位置,即client library path
instrace是logfile的命名
最后一个enum参数是文件打开方式,WINDOWS和LINUX不同
*/
data->log =
log_file_open(client_id, drcontext, NULL, "instrace",
#ifndef WINDOWS
DR_FILE_CLOSE_ON_FORK |
#endif
DR_FILE_ALLOW_LARGE);

// logf是FILE数据流
data->logf = log_stream_from_file(data->log);
fprintf(data->logf, "Format: <instr address>,<opcode>\n");
}

event_thread_exit

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
static void
event_thread_exit(void *drcontext)
{
per_thread_t *data;
// 1. 转储缓冲区的内容
instrace(drcontext);

// 2. 获取TLS中tls_idx对应位置的per_thread_t
data = drmgr_get_tls_field(drcontext, tls_idx);

// 3. num_refs 是全局指令计数器,加锁自增
dr_mutex_lock(mutex);
num_refs += data->num_refs;
dr_mutex_unlock(mutex);

// 4. 关闭日志流
log_stream_close(data->logf); /* closes fd too */

// 5. 释放dr_raw_mem_alloc()申请的内存.
dr_raw_mem_free(data->buf_base, MEM_BUF_SIZE);

// 6. 释放由by dr_thread_alloc()申请的针对某个线程的内存.
dr_thread_free(drcontext, data, sizeof(per_thread_t));
}

// 该方法转储缓冲区的内容
static void
instrace(void *drcontext)
{
per_thread_t *data;
ins_ref_t *ins_ref, *buf_ptr;

// 返回指定索引的、用户控制的本地线程地址,该地址由 drmgr_register_tls_field()返回。
// 要在代码缓存中生成读取 drcontext 字段的指令序列,请使用 drmgr_insert_read_tls_field()。
data = drmgr_get_tls_field(drcontext, tls_idx);
buf_ptr = BUF_PTR(data->seg_base);
/* Example of dumped file content:
* 0x7f59c2d002d3: call
* 0x7ffeacab0ec8: mov
*/
// 使用libc的fpringtf有缓冲区,比dr_fprintf更快
for (ins_ref = (ins_ref_t *)data->buf_base; ins_ref < buf_ptr; ins_ref++) {
// 使用PIFX前缀可以避免第一个字符为0的情况,并且使生成的文件更小
fprintf(data->logf, PIFX ",%s\n", (ptr_uint_t)ins_ref->pc,
decode_opcode_name(ins_ref->opcode));
data->num_refs++;
}
BUF_PTR(data->seg_base) = data->buf_base;
}

event_app_instruction

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
// 插桩 drmgr.h
static dr_emit_flags_t
event_app_instruction(void *drcontext, void *tag, instrlist_t *bb, instr_t *instr, bool for_trace, bool translating, void *user_data)
{
// 1. 不自动预测任何插桩
drmgr_disable_auto_predication(drcontext, bb);

// 2. 检查当前指令是否为应用程序指令(相对meta intructions)
if (!instr_is_app(instr))
return DR_EMIT_DEFAULT;

// 3. 指令级插桩
instrument_instr(drcontext, bb, instr);

// 4. 每个 bb 插入一次代码,调用 clean_call 处理缓冲区
if (drmgr_is_first_instr(drcontext, instr)
// 下面的注释是原代码中的注释,解释了AARCHXX和X86的区别
/* XXX i#1698: there are constraints for code between ldrex/strex pairs,
* so we minimize the instrumentation in between by skipping the clean call.
* We're relying a bit on the typical code sequence with either ldrex..strex
* in the same bb, in which case our call at the start of the bb is fine,
* or with a branch in between and the strex at the start of the next bb.
* However, there is still a chance that the instrumentation code may clear the
* exclusive monitor state.
* Using a fault to handle a full buffer should be more robust, and the
* forthcoming buffer filling API (i#513) will provide that.
*/
IF_AARCHXX(&&!instr_is_exclusive_store(instr)))
//
dr_insert_clean_call(drcontext, bb, instr, (void *)clean_call, false, 0);

return DR_EMIT_DEFAULT;
}

// 向CodeCache插入指令
static void
instrument_instr(void *drcontext, instrlist_t *ilist, instr_t *where)
{
// 需要两个寄存器
reg_id_t reg_ptr, reg_tmp;

// 1. 加载CodeCache指针到reg_ptr寄存器
if (drreg_reserve_register(drcontext, ilist, where, NULL, &reg_ptr) !=
DRREG_SUCCESS ||
drreg_reserve_register(drcontext, ilist, where, NULL, &reg_tmp) !=
DRREG_SUCCESS) {
DR_ASSERT(false); /* cannot recover */
return;
}

// 2. 以下几个方法都是在本文件中定义的
// 2.1 insert_load_buf_ptr 加载TLS缓冲区指针到reg_ptr寄存器
insert_load_buf_ptr(drcontext, ilist, where, reg_ptr);

// 2.2 insert_save_pc 更新reg_ptr的值并存储到TLS
insert_save_pc(drcontext, ilist, where, reg_ptr, reg_tmp, instr_get_app_pc(where));

// 2.3 insert_save_opcode 把操作码加载到临时寄存器中并存储到TLS
insert_save_opcode(drcontext, ilist, where, reg_ptr, reg_tmp,
instr_get_opcode(where));

// 2.4 insert_update_buf_ptr 把当前指令的pc加载到寄存器中并存储到TLS
insert_update_buf_ptr(drcontext, ilist, where, reg_ptr, sizeof(ins_ref_t));

// 3. 释放临时寄存器
if (drreg_unreserve_register(drcontext, ilist, where, reg_ptr) != DRREG_SUCCESS ||
drreg_unreserve_register(drcontext, ilist, where, reg_tmp) != DRREG_SUCCESS)
DR_ASSERT(false);
}

insert_load_buf_ptr

1
2
3
4
5
6
7
static void
insert_load_buf_ptr(void *drcontext, instrlist_t *ilist, instr_t *where, reg_id_t reg_ptr)
{
// 在ilist的where指令之前插入读取TLS的指令,从以tls_seg为基的tls_offs + INSTRACE_TLS_OFFS_BUF_PTR偏移量处读取,并把结果存储到reg_ptr中
dr_insert_read_raw_tls(drcontext, ilist, where, tls_seg,
tls_offs + INSTRACE_TLS_OFFS_BUF_PTR, reg_ptr);
}

insert_save_pc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// MINSERT:在ilist中的where指令之前插入第三个参数的创造的指令作为元数据指令
#define MINSERT instrlist_meta_preinsert

static void
insert_save_pc(void *drcontext, instrlist_t *ilist, instr_t *where, reg_id_t base,
reg_id_t scratch, app_pc pc)
{
// 创建一个指令,该指令用于把pc转换为ptr_int_t并存储到scratch寄存器中,并把该指令插入ilist中的where指令之前
instrlist_insert_mov_immed_ptrsz(drcontext, (ptr_int_t)pc, opnd_create_reg(scratch),
ilist, where, NULL, NULL);

// 把scratch转存到TLS中
MINSERT(ilist, where,
XINST_CREATE_store(drcontext,
OPND_CREATE_MEMPTR(base, offsetof(ins_ref_t, pc)),
opnd_create_reg(scratch)));
}

insert_save_opcode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static void
insert_save_opcode(void *drcontext, instrlist_t *ilist, instr_t *where, reg_id_t base,
reg_id_t scratch, int opcode)
{
// 调整scratch寄存器为2byte
scratch = reg_resize_to_opsz(scratch, OPSZ_2);

// 在ilist的where指令之前插入以下两条指令
// 这一条把操作数放入了scratch
MINSERT(ilist, where,
XINST_CREATE_load_int(drcontext, opnd_create_reg(scratch),
OPND_CREATE_INT16(opcode)));
// 这一条把操作数从scratch放入TLS指定位置中
MINSERT(ilist, where,
XINST_CREATE_store_2bytes(
drcontext, OPND_CREATE_MEM16(base, offsetof(ins_ref_t, opcode)),
opnd_create_reg(scratch)));
}

insert_update_buf_ptr

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static void
insert_update_buf_ptr(void *drcontext, instrlist_t *ilist, instr_t *where,
reg_id_t reg_ptr, int adjust)
{
// 将 reg_ptr 寄存器中的值与 adjust 相加,并将结果保存回 reg_ptr 寄存器
// 把这条指令插入到ilist中的where指令之前
MINSERT(
ilist, where,
XINST_CREATE_add(drcontext, opnd_create_reg(reg_ptr), OPND_CREATE_INT16(adjust)));

// 插入一条写入 TLS 的指令,将 reg_ptr 寄存器中的值写入 TLS 区域的 tls_offs + INSTRACE_TLS_OFFS_BUF_PTR 偏移位置。
dr_insert_write_raw_tls(drcontext, ilist, where, tls_seg,
tls_offs + INSTRACE_TLS_OFFS_BUF_PTR, reg_ptr);
}