执行优化,使用自定义跟踪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_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 之前调用回调函数。
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 呢? 我也不知道。
/* 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 事件里?
/* 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。