首先分析translate的主流程:translate_all
int cur_pos = cpu->dyncom_engine->functions;
vector<addr_t>::iterator i = cpu->dyncom_engine->startbb[cur_pos].begin();
for(; i < cpu->dyncom_engine->startbb[cur_pos].end(); i++){
create_basicblock(cpu, *i, cpu->dyncom_engine->cur_func, BB_TYPE_NORMAL);
LOG("create bb 0x%x\n", *i);
bbs ++;
}
这里是创建基本块,这些基本块的信息已经由前面打tag的时候给标识好了。
BasicBlock* bb_dispatch = BasicBlock::Create(_CTX(), "dispatch", cpu->dyncom_engine->cur_func, 0);
SwitchInst* sw;
if(is_user_mode(cpu)){
#ifdef OPT_LOCAL_REGISTERS
Value *v_pc;
/* Now only for arm platform */
if(cpu->info.pc_index_in_gpr != -1)
v_pc = arch_get_reg(cpu, cpu->info.pc_index_in_gpr, 32, bb_dispatch);
else
v_pc = new LoadInst(cpu->ptr_PHYS_PC, "", false, bb_dispatch);
#else
Value *v_pc = new LoadInst(cpu->ptr_PHYS_PC, "", false, bb_dispatch);
#endif
sw = SwitchInst::Create(v_pc, bb_ret, bbs, bb_dispatch);
} else {
BasicBlock* bb_real_dispatch = BasicBlock::Create(_CTX(), "real_dispatch", cpu->dyncom_engine->cur_func, 0);
LoadInst* v_icount = new LoadInst(cpu->ptr_ICOUNTER, "", false, bb_dispatch);
LoadInst* v_old_icount = new LoadInst(cpu->ptr_OLD_ICOUNTER, "", false, bb_dispatch);
Value *cycles = BinaryOperator::Create(Instruction::Sub, v_icount, v_old_icount, "", bb_dispatch);
Value *gout = new ICmpInst(*bb_dispatch, ICmpInst::ICMP_UGT, cycles, CONST(TIMEOUT_THRESHOLD), "");
BranchInst::Create(bb_timeout, bb_real_dispatch, gout, bb_dispatch);
// create dispatch basicblock
Value *v_pc = new LoadInst(cpu->ptr_PHYS_PC, "", false, bb_real_dispatch);
sw = SwitchInst::Create(v_pc, bb_ret, bbs, bb_real_dispatch);
}
创建一个dispatch基本块,当基本块碰到未知块时就跳到这里进行分发。在系统模拟中增加了超时处理,也就是当执行的指令超过某个阀值的时候就进入超时处理,否则进入正常的分发。这样做的目的是防止指令执行时间过长,没有时间检查外设中断活动等。
在正常的分发中,会根据当前pc进行分发。接下来将对每个基本块进行翻译。
or_tag(cpu, pc, TAG_TRANSLATED);
LOG("basicblock: L%08llx\n", (unsigned long long)pc);
// Add dispatch switch case for basic block.
ConstantInt* c = ConstantInt::get(getIntegerType(cpu->info.address_size), pc);
sw->addCase(c, cur_bb);
将pc对应的tag标识为已经翻译,将对应的case加到switch中去。
cpu->f.tag_instr(cpu, pc, &dummy1, &new_pc, &next_pc);
/* get target basic block */
if (tag & TAG_RET)
bb_target = bb_dispatch;
if (tag & (TAG_CALL|TAG_BRANCH|TAG_POSTCOND)) {
if (new_pc == NEW_PC_NONE) /* translate_instr() will set PC */
bb_target = bb_dispatch;
else {
bb_target = (BasicBlock*)lookup_basicblock(cpu, cpu->dyncom_engine->cur_func, new_pc, bb_ret, BB_TYPE_NORMAL);
/* Avoid dead loop in a process */
if(!is_user_mode(cpu)){
dummy1 = get_tag(cpu, new_pc);
if (dummy1 & TAG_TRANSLATED)
bb_target = bb_dispatch;
}
}
}
获取pc的tag信息,如果是返回指令,将bb_target设置为dispatch块,通过它最终跳转至最外面的ret块。如果是转移指令并且新的跳转地址未知,那么target设置为分发,由dispatch决定去哪。否则找到那个目标块,如果是已经翻译过的,就直接进入到dispatch,防止再次进入翻译。
if(is_user_mode(cpu)){
if (tag & (TAG_CONDITIONAL | TAG_POSTCOND | TAG_LAST_INST ))
bb_next = (BasicBlock*)lookup_basicblock(cpu, cpu->dyncom_engine->cur_func, next_pc, bb_ret, BB_TYPE_NORMAL);
}
else{
if (tag & (TAG_CONDITIONAL | TAG_POSTCOND | TAG_LAST_INST | (TAG_MEMORY)))
bb_next = (BasicBlock*)lookup_basicblock(cpu, cpu->dyncom_engine->cur_func, next_pc, bb_ret, BB_TYPE_NORMAL);
}
对应条件执行等,意味着这个块要结束了,那么接下来的就是一个新的块。
if(save_pc_for_all_insn(cpu)){
emit_store_pc(cpu, cur_bb, pc);
}
else{
if(!(tag & TAG_CONTINUE))
//update pc
emit_store_pc(cpu, cur_bb, pc);
}
这里根据需要设置pc值。这里也存在两种策略,一种是”实时“更新,这个会影响性能,一般debug的使用,更多的是根据需要再更新pc值,如根据pc偏移访问数据等。
#if ENABLE_ICOUNTER
arch_inc_icounter(cpu, cur_bb);
#endif
// Only for debug all the execution instructions
#if ENABLE_DEBUG_ME
cur_bb = arch_debug_me(cpu, cur_bb, bb_trap);
#endif
在使能执行指令计数的情况下增加,已经调试功能。然后调用translate_instr,对指令进行翻译。
if (tag & TAG_CONDITIONAL)
bb_cond = create_basicblock(cpu, pc, cpu->dyncom_engine->cur_func, BB_TYPE_COND);
if ((tag & TAG_DELAY_SLOT) && (tag & TAG_CONDITIONAL))
bb_delay = create_basicblock(cpu, pc, cpu->dyncom_engine->cur_func, BB_TYPE_DELAY);
首先根据是否是条件执行创建一个条件块,即条件成立的时候进入执行。类似的创建延迟槽块。
if (tag & TAG_DELAY_SLOT) {
if (tag & TAG_CONDITIONAL) {
addr_t delay_pc;
// cur_bb: if (cond) goto b_cond; else goto bb_delay;
Value *c = cpu->f.translate_cond(cpu, pc, cur_bb);
if((tag & TAG_END_PAGE) && !is_user_mode(cpu)){
emit_store_pc_cond(cpu, tag, c, cur_bb, next_pc);
BranchInst::Create(bb_cond, bb_ret, c, cur_bb);
}
else
BranchInst::Create(bb_cond, bb_delay, c, cur_bb);
// bb_cond: instr; delay; goto bb_target;
pc += cpu->f.translate_instr(cpu, pc, bb_cond);
delay_pc = pc;
cpu->f.translate_instr(cpu, pc, bb_cond);
BranchInst::Create(bb_target, bb_cond);
// bb_cond: delay; goto bb_next;
cpu->f.translate_instr(cpu, delay_pc, bb_delay);
BranchInst::Create(bb_next, bb_delay);
} else {
// cur_bb: instr; delay; goto bb_target;
pc += cpu->f.translate_instr(cpu, pc, cur_bb);
cpu->f.translate_instr(cpu, pc, cur_bb);
BranchInst::Create(bb_target, cur_bb);
}
return NULL; /* don't link */
}
这里对延迟槽指令进行了特殊处理,delay_slot 的意思是该指令紧挨着的指令也必须一起执行,这个跟一般的指令执行是不一样的。如果是条件执行延迟槽指令的话,先翻译条件,然后根据条件进行跳转。如果条件成立,将跳转至条件块,否则跳至延迟槽块(即延迟槽指令的下一条指令)。条件块中,主要是翻译延迟槽指令和接下来的那条指令,然后跳转。延迟槽块中的翻译延迟槽接下来的那条指令,然后跳转到在下一条。注意这里翻译条件块是跳转的地址是target,应该针对的是跳转这种情况,其实在mips中间还有ld,st延迟槽,这里好像是没有考虑这个情况?另外如果延迟槽指令是一个跳转指令,如果翻译的时候跳转就发生了,那么延迟槽之后的那条指令不是得不到执行了?这样的话跟延迟槽的语义是不一致的,这也是一个疑问。如果不是条件延迟槽的话,直接翻译延迟槽指令和接下来的那条指令即可。
if (tag & TAG_CONDITIONAL) {
// cur_bb: if (cond) goto b_cond; else goto bb_next;
Value *c = cpu->f.translate_cond(cpu, pc, cur_bb);
if((tag & TAG_END_PAGE) && !is_user_mode(cpu)){
emit_store_pc_cond(cpu, tag, c, cur_bb, next_pc);
BranchInst::Create(bb_cond, bb_ret, c, cur_bb);
}
else{
if (tag & TAG_BEFORE_SYSCALL){
emit_store_pc(cpu, cur_bb, next_pc);
BranchInst::Create(bb_cond, bb_trap, c, cur_bb);
}
else
BranchInst::Create(bb_cond, bb_next, c, cur_bb);
}
cur_bb = bb_cond;
}
对于条件执行指令,首先要翻译条件,对于一个页尾的指令,我们需要更新pc,然后就是需要返回,这个跟skyeye的内存管理有关系,skyeye是按照页的大小来翻译的,所以碰到页尾,就不再继续了。
if ((tag & TAG_MEMORY) && !is_user_mode(cpu)) { //&& !(tag & TAG_BRANCH)) {
#if 0
uint32_t instr;
bus_read(32, pc, &instr);
cur_bb = arch_check_mm(cpu, instr, cur_bb, bb_next, bb_trap);
#endif
cpu->dyncom_engine->bb_trap = bb_trap;
}
对于内存访问的情况,我们将主函数的trap块告诉cpu->dyncom_engine->bb_trap,这样在接下来的翻译过程中如果出现内存访问异常等问题,可以跳转到主函数的异常块。参考代码如下:
BasicBlock * arch_check_mm(cpu_t *cpu, BasicBlock *bb, Value* addr, int count, int read, BasicBlock *exit_bb)
{
if(is_user_mode(cpu)){
cpu->dyncom_engine->bb_load_store = bb;
return bb;
}
#if 1
if (cpu->dyncom_engine->ptr_func_check_mm == NULL) {
printf("No check mm\n");
return bb;
}
#endif
if(count == 0){
printf("count=0\n");
abort();
exit(-1);
}
Type const *intptr_type = cpu->dyncom_engine->exec_engine->getTargetData()->getIntPtrType(_CTX());
Constant *v_cpu = ConstantInt::get(intptr_type, (uintptr_t)cpu);
Value *v_cpu_ptr = ConstantExpr::getIntToPtr(v_cpu, PointerType::getUnqual(intptr_type));
std::vector<Value *> params;
params.push_back(v_cpu_ptr);
params.push_back(addr);
params.push_back(CONST(count));
params.push_back(CONST(read));
// XXX synchronize cpu context!
Value *exit_val = CallInst::Create(cpu->dyncom_engine->ptr_func_check_mm, params.begin(), params.end(), "", bb);
// return bb;
Value *cond = ICMP_EQ(exit_val, CONST(0));
BasicBlock *load_store_bb = BasicBlock::Create(_CTX(), "load_store", cpu->dyncom_engine->cur_func, 0);
cpu->dyncom_engine->bb_load_store = load_store_bb;
arch_branch(1, load_store_bb, exit_bb, cond, bb);
return load_store_bb;
}
这个函数在访问内存操作的指令的翻译中会调用这个对内存访问合法性进行检查。该函数主要是调用check_mm对地址合法性进行检查,然后根据函数返回值,决定到底是跳到执行该指令还是转到异常处理。这里的exit_bb就是前面那个bb_trap。
cpu->f.translate_instr(cpu, pc, cur_bb);
这个将执行具体指令的翻译工作。
if ((tag & TAG_MEMORY) && !is_user_mode(cpu)) { //&& !(tag & TAG_BRANCH)) {
#if 0
uint32_t instr;
bus_read(32, pc, &instr);
cur_bb = arch_check_mm(cpu, instr, cur_bb, bb_next, bb_trap);
#endif
cur_bb = cpu->dyncom_engine->bb_load_store;
}
如果是内存访问指令,前面的函数分析当中在检查内存函数里面创建了一个新的基本块,也就是正常访问的块。然后接下来真正的翻译工作将在这个块进行(可以参考ld,st指令的翻译)。这里其实从概念上来说还可以进一步拆成两个basic_block,一个是正常的访问指令,另一个是这条指令之后的那些指令组成一个基本块。当然也可以将后面的那个基本块的指令直接加在正常访问块之后。
if ((tag & TAG_NEED_PC) && !is_user_mode(cpu)) {
BasicBlock *bb = cur_bb;
Value *vpc = new LoadInst(cpu->ptr_PC, "", false, bb);
new StoreInst(ADD(vpc, CONST(instr_length)), cpu->ptr_PC, bb);
}
如果需要更新pc指令,那个就更新pc
if (tag & TAG_POSTCOND) {
Value *c = cpu->f.translate_cond(cpu, pc, cur_bb);
BranchInst::Create(bb_target, bb_next, c, cur_bb);
}
对postcond进行处理。
if ((tag & (TAG_END_PAGE | TAG_EXCEPTION)) && !is_user_mode(cpu))
BranchInst::Create(bb_ret, cur_bb);
else if (tag & (TAG_BRANCH | TAG_CALL | TAG_RET))
BranchInst::Create(bb_target, cur_bb);
else if (tag & TAG_TRAP)
BranchInst::Create(bb_trap, cur_bb);
else if (tag & TAG_CONDITIONAL) {/* Add terminator instruction 'br' for conditional instruction */
BranchInst::Create(bb_next, cur_bb);
}
这个是对于结束符的处理,决定跳转到那个基本块。如果是页尾或者异常,我们就跳转到ret,结束整个执行;如果是跳转指令,那么就转到目的基本块;如果异常,就去到异常块;如果是条件执行,那么就接着往下走。
if (tag & TAG_CONTINUE)
return cur_bb;
else
return NULL;
如果是连续执行的返回当前基本块,如过不是返回NULL。如果是返回值不是NULL,则说明还没有到结束符,上面会进行进行翻译,否则意味着碰到了结束符,一个基本块的翻译就完成了。