【DynamoRIO 入门教程】六:inline.c

一、功能说明

执行优化,使用自定义跟踪API将整个callees 内联到跟踪中。
这句话什么意思,应用程序里会有 call 指令,通过该指令进入 子函数后,又会通过return指令返回。
我们要做的优化就是将 从call 到 return 的过程 内联(存放)到一个 自定义 trace中。

二、主要数据结构 和 配套函数

typedef struct _hashtable_t {
    hash_entry_t **table;
    hash_type_t hashtype;
    bool str_dup;
    void *lock;
    uint table_bits;
    bool synch;
    void (*free_payload_func)(void *);
    uint (*hash_key_func)(void *);
    bool (*cmp_key_func)(void *, void *);
    uint entries;
    hashtable_config_t config;
    uint persist_count;
} hashtable_t;

hashtable_t 结构体 定义在 hashtable.h 头文件里,不需要我们自己定义。并且这应该是属于 Container Data Structures 的文件。

不过这各结构体里面的东西有点多,后面看到我们再说吧。

/****************************************************************************/
/* We use a hashtable to know if a particular tag is for a call trace or a * normal back branch trace. */

typedef struct _trace_head_entry_t {
    void *tag;
    bool is_trace_head;
	// has_ret 的意义是该basic block 含有 return 指令
    bool has_ret;
    /* We have to end at the next block after we see a return. */
    int end_next;
    /* Some callees are too large to inline, so we have a size limit. */
    uint size;
    /* We use a ref count so we know when to remove in the presence of * thread-private duplicated blocks. */
    uint refcount;
    struct _trace_head_entry_t *next;
} trace_head_entry_t;
static trace_head_entry_t *
create_trace_head_entry(void *tag)
{
    trace_head_entry_t *e = (trace_head_entry_t *)dr_global_alloc(sizeof(*e));
    e->tag = tag;
    e->end_next = 0;
    e->size = 0;
    e->has_ret = false;
    e->is_trace_head = false;
    e->refcount = 1;
    return e;
}
static void
free_trace_head_entry(void *entry)
{
    trace_head_entry_t *e = (trace_head_entry_t *)entry;
    dr_global_free(e, sizeof(*e));
}

DR主体函数

1、dr_client_main()

DR_EXPORT void
dr_client_main(client_id_t id, int argc, const char *argv[])
{
    if (!drmgr_init())
        DR_ASSERT(false);

    hashtable_init_ex(&head_table, HASH_BITS, HASH_INTPTR, false /*!strdup*/,
                      false /*synchronization is external*/, free_trace_head_entry, NULL,
                      NULL);

    dr_register_exit_event(event_exit);
    if (!drmgr_register_bb_instrumentation_event(event_analyze_bb, NULL, NULL))
        DR_ASSERT(false);
    dr_register_delete_event(event_fragment_deleted);
    dr_register_end_trace_event(query_end_trace);

    /* Make it easy to tell from the log file which client executed. */
    dr_log(NULL, DR_LOG_ALL, 1, "Client 'inline' initializing\n");
}

hashtable_init_ex() 使用来初始化 我们创建的全局 hash表 head_table 的。
另外注册了几个回调函数:
event_exit, event_analyze_bb 就不说了。
还有 dr_register_delete_event() 注册 代码段删除事件 回调函数 event_fragment_deleted。
dr_register_end_trace_event() 注册 end-trace event 回调函数 query_end_trace。
注意 end_trace 指的是: DR calls func before extending a trace with a new basic block 。在使用新basic block 扩展 trace 之前调用回调函数。

2、event_analyze_bb()

static dr_emit_flags_t
event_analyze_bb(void *drcontext, void *tag, instrlist_t *bb, bool for_trace,
                 bool translating, void **user_data)
{
    instr_t *instr;
    trace_head_entry_t *e = NULL;
    if (translating)
        return DR_EMIT_DEFAULT;
    for (instr = instrlist_first_app(bb); instr != NULL;
         instr = instr_get_next_app(instr)) {
        /* Blocks containing calls are trace heads. */
        if (instr_is_call(instr)) {
            dr_mark_trace_head(drcontext, tag);
            hashtable_lock(&head_table);
            e = hashtable_lookup(&head_table, tag);
            if (e == NULL) {
                e = create_trace_head_entry(tag);
                if (!hashtable_add(&head_table, tag, (void *)e))
                    DR_ASSERT(false);
            } else
                e->refcount++;
            e->is_trace_head = true;
            hashtable_unlock(&head_table);
#ifdef VERBOSE
            dr_log(drcontext, DR_LOG_ALL, 3,
                   "inline: marking bb " PFX " as call trace head\n", tag);
#endif
            /* Doesn't matter what's in rest of the bb. */
            return DR_EMIT_DEFAULT;
        } else if (instr_is_return(instr)) {
            hashtable_lock(&head_table);
            e = hashtable_lookup(&head_table, tag);
            if (e == NULL) {
                e = create_trace_head_entry(tag);
                if (!hashtable_add(&head_table, tag, (void *)e))
                    DR_ASSERT(false);
            } else
                e->refcount++;
            e->has_ret = true;
            hashtable_unlock(&head_table);
#ifdef VERBOSE
            dr_log(drcontext, DR_LOG_ALL, 3,
                   "inline: marking bb " PFX " as return trace head\n", tag);
#endif
        }
    }
    return DR_EMIT_DEFAULT;
}

第一个for 循环,遍历了 basic block 里的所有指令。
通过 **instr_is_call()**来判断指令是否为 call 指令。
如果是,则通过 dr_mark_trace_head()将 当前basic block 的tag标记为 trace head。这就是所谓的自定义 trace。
dr_mark_trace_head 的效果: DR将一个计数器与这个 trace head 相关联,一旦计数器超过了 -hot_threshold 参数,DR就开始构建 trace。在将每个片段(basic block) 添加到 trace 之前,DR会调用客户端注册的 end_trace event 的回调函数 来确定是否要
结束
跟中。(无论是 DR里标准的trace 还是客户端自定义的trace ,都会调用这个 end_trace event 回调)。所谓的end_trace evnet 就是我们上面再 client_main 里注册的。

回到代码:当确定要把一个 call所在的basic block 作为 自定义trace head ,我们就需要吧这个trace head的信息记录在 一个trace_head_entry结构体里 ,并把这个结构体作为 value 链接到 hash 表 head_table 里。
所以可以看到:先用hashtable_lookup()来判断当前 basic block 的tag 是否存储在 hash 表里(trace head 的 tag 与 trace 的tag是一致的,或者是有关联的?)
如果不在,就用
create_trace_head_entry()
来创建一个 trace_head_entry结构体,然后用hashtable_add() 链接到 hash 表 head_table 里(使用tag作为key, 指针e作为 value)。
如果在,就将其refcount 属性加1。关于refcount ,注释里说:我们使用 ref 计数, 以便我们知道何时在存在 线程私有复制块 的情况下删除。没明白啥意思!

is_trace_head 属性设为 true,以表示 这个trace head 当前正在使用。
完事返回: DR_EMIT_DEFAULT

如果当前的 instr 是一条 return 指令(通过instr_is_return 来判断),同样在 hash 表里查找 这个trace head。如果hash 表里找不到,就创建trace_head_entry,然后加到 hash 表里。
如果找到了,那就修改 refcount 加1。
然后再将 has_ret 属性设为true。

通过这段代码我们可以知道,无论是遇到call指令还是 return 指令,都会生成一个 trace_head_entry 结构体表示当前 basic block 是一个 trace head,然后将这个结构体放到hash表里。但是,遇到call 时,我们是使用了dr_mark_trace_head() 来保证 call 所在的basic block是一个 trace head ,但是如何保证 return 所在的 basic block 也一定是一个 trace head 呢? 我也不知道。

3、event_fragment_deleted()

/* To keep the size of our hashtable down. */
static void
event_fragment_deleted(void *drcontext, void *tag)
{
    trace_head_entry_t *e;
    hashtable_lock(&head_table);
    e = hashtable_lookup(&head_table, tag);
    if (e != NULL) {
        e->refcount--;
        if (e->refcount == 0)
            hashtable_remove(&head_table, tag);
    }
    hashtable_unlock(&head_table);
}

这个函数内容就简单了,当要删除一个 fragment 时,先判断它是否在 hash 表里, 如果在,根据 refcount 将其refcount 减1,或者直接删掉这个节点。

但有一点我不明白,为什么同一个 basic block 会多次出现在 basick block creation 事件里?

4、query_end_trace()

/* Ask whether to end trace prior to adding next_tag fragment. * Return values: * CUSTOM_TRACE_DR_DECIDES = use standard termination criteria * CUSTOM_TRACE_END_NOW = end trace * CUSTOM_TRACE_CONTINUE = do not end trace */
static dr_custom_trace_action_t
query_end_trace(void *drcontext, void *trace_tag, void *next_tag)
{
    /* If this is a call trace, only end on the block after a block with return * (need to get the return inlined!). * If this is a standard back branch trace, end it if we see a * block with a call (so that we'll go into the call trace). * otherwise return 0 and let DynamoRIO determine whether to * terminate the trace now. */
    trace_head_entry_t *e;
    hashtable_lock(&head_table);
    e = hashtable_lookup(&head_table, trace_tag);
    if (e == NULL || !e->is_trace_head) {	// 本地trace 不是call trace,而是 standard back branch trace
        e = hashtable_lookup(&head_table, next_tag);
        if (e == NULL || !e->is_trace_head) {	// 目标 basic block 不是 call trace
            hashtable_unlock(&head_table);
			// 让 DR来决定是否要结束 trace 
            return CUSTOM_TRACE_DR_DECIDES;
        } else {
			// 目标 basic block 是 call trace
            /* We've found a call: end this trace now so it won't keep going and * end up never entering the call trace. */
#ifdef VERBOSE
            dr_log(drcontext, DR_LOG_ALL, 3,
                   "inline: ending trace " PFX " before block " PFX " containing call\n",
                   trace_tag, next_tag);
#endif

            num_traces++;

            hashtable_unlock(&head_table);
			// 直接结束
            return CUSTOM_TRACE_END_NOW;
        }
    } else if (e->end_next > 0) {
		// 当前 trace 是 call trace
        e->end_next--;
        if (e->end_next == 0) {
			// 这说明上一个basic block 遇到了 return。
#ifdef VERBOSE
            dr_log(drcontext, DR_LOG_ALL, 3,
                   "inline: ending trace " PFX " before " PFX "\n", trace_tag, next_tag);
#endif

			// 这种情况意味着一次 inline
            num_complete_inlines++;
			// 结束意味着完成了一个 trace
            num_traces++;

            hashtable_unlock(&head_table);
            return CUSTOM_TRACE_END_NOW;
        }
    } else {	// 当前 trace 是 call trace,并且之前没有遇到过 return
        trace_head_entry_t *nxte = hashtable_lookup(&head_table, next_tag);
        uint size = dr_fragment_size(drcontext, next_tag);
        e->size += size;
        if (e->size > INLINE_SIZE_LIMIT) {
			// 如果加上去 大小超限,则直接结束
#ifdef VERBOSE
            dr_log(drcontext, DR_LOG_ALL, 3,
                   "inline: ending trace " PFX " before " PFX
                   " because reached size limit\n",
                   trace_tag, next_tag);
#endif

            num_traces++;

            hashtable_unlock(&head_table);

            return CUSTOM_TRACE_END_NOW;
        }	
		// 如果这个 basic block 不超限,但是内部含有return指令
        if (nxte != NULL && nxte->has_ret && !nxte->is_trace_head) {
            /* End trace after NEXT block */
			// 用end_next 来表明,下一个basic block应该结束了。
			//为 2 表示,这个trace 结尾是一个包含return 的bb 和这个bb之后的另一个bb
            e->end_next = 2;
#ifdef VERBOSE
            dr_log(drcontext, DR_LOG_ALL, 3,
                   "inline: going to be ending trace " PFX " after " PFX "\n", trace_tag,
                   next_tag);
#endif
            hashtable_unlock(&head_table);
			// 这个trace 要继续下去
            return CUSTOM_TRACE_CONTINUE;
        }
    }
    /* Do not end trace */
#ifdef VERBOSE
    dr_log(drcontext, DR_LOG_ALL, 3, "inline: NOT ending trace " PFX " after " PFX "\n",
           trace_tag, next_tag);
#endif
    hashtable_unlock(&head_table);
	// trace 要继续下去。
    return CUSTOM_TRACE_CONTINUE;
}

注意开头注释里的内容,当我们将要把一个 新的 basic block 链接到 trace 里时,也是判断是否要结束 trace 的时刻,所以会产生 end_trace event。

这个函数根据 当前trace 和目标 basic block 的类型分出了多种情况。由于if分支较多,因此我在代码里写了注释,方便介绍。
这个函数把当前 trace 分为 call trace 和 standard back branch trac。
把目标basic block 分为 一般待加入bb、call trace 的trace head 、含有return指令的bb、after return 的bb(也就是说,trace里上一个bb是含有return的)。

当 当前trace 是 standard back branch trac 时:
如果 目标bb 不是 call trace,则由 DR做决定。
如果 目标bb 是 call trace,则直接结束。

当 当前 trace 是call trace 时:
如果当前trace 刚遇到过含有return的bb,则当前bb作为结束bb。
如果 当前 trace 加上 目标 bb 总大小超过限制,则结束。
如果 目标bb 含有 return ,则将trace 的 end_next 置为2,与上上那种情况形成自洽。

但是有一个问题,如果当前 trace 是 call trace ,而目标 trace 也是 call trace 时会怎么样,会不会出现这种情况?

稀里糊涂,算是分析完了,但是感觉还是有很多不懂得地方。总结一下:
所谓自定义trace 内联 call 来进行优化,就是把call 调用以及子函数运行的过程放入一个自定义 trace里。
这个例子主要介绍了自定义 trace 的用法,自定义 trace ,我们要自己设定合适开始 作为 trace head,
也要设置合适结束 trace。结束的方法则 end_trace event 的返回值里。
其中的关键函数就是 dr_mark_trace_head() 函数,用来设置 自定义trace head。

你可能感兴趣的:(DynamoRIO)