Nemu PA2

本文是 IS 2016課程的homework筆記,詳情請(qǐng)參考課程主頁和本人github主頁

  • cpu_exec是一個(gè)循環(huán)執(zhí)行exec(eip)函數(shù),execmake_helper宏定義
// nemu/src/cpu/exec/exec.c
make_helper(exec) {
    ops_decoded.opcode = instr_fetch(eip, 1);
    return opcode_table[ ops_decoded.opcode ](eip);
}

// nemu/include/cpu/helper.h
/* All function defined with 'make_helper' return the length of the operation. */
#define make_helper(name) int name(swaddr_t eip)
  • exec調(diào)用的是opcode_table對(duì)當(dāng)前eip指向的指令進(jìn)行dispatch,table和ops_decoded的定義為
// nemu/include/cpu/decode/operand.h
typedef struct {
    uint32_t type;
    size_t size;
    union {
        uint32_t reg;
        swaddr_t addr;
        uint32_t imm;
        int32_t simm;
    };
    uint32_t val;
    char str[OP_STR_SIZE];
} Operand;

typedef struct {
    uint32_t opcode;
    bool is_operand_size_16;
    Operand src, dest, src2;
} Operands;

// decode.c
Operands ops_decoded;
  • 66這個(gè)prefix決定了mov指令立即數(shù)的長度,在opcode_table里邊將其分發(fā)到函數(shù)operand_size,該函數(shù)將全局變量的域is_operand_size_16置位,然后再按照正常的邏輯執(zhí)行exec函數(shù),不過返回值加1字節(jié)。
make_helper(operand_size) {
    ops_decoded.is_operand_size_16 = true;
    int instr_len = exec(eip + 1);
    ops_decoded.is_operand_size_16 = false;
    return instr_len + 1;
}
  • 比如找到的是mov_i2r_v,那么它用make_helper聲明,用make_helper_v定義,v表示是變長的操作數(shù),可能是16/32位操作數(shù)。譯碼是通過operand的size來決定不同的實(shí)現(xiàn)的,沒有66的話就是32bit的。
// nemu/src/cpu/exec/data-mov/mov.c
/* for instruction encoding overloading */
make_helper_v(mov_i2r)
make_helper_v(mov_i2rm)
make_helper_v(mov_r2rm)
make_helper_v(mov_rm2r)
make_helper_v(mov_a2moffs)
make_helper_v(mov_moffs2a)

#define make_helper_v(name) \\
    make_helper(concat(name, _v)) { \\
        return (ops_decoded.is_operand_size_16 ? concat(name, _w) : concat(name, _l)) (eip); \\
    }

  • 后面的mov_i2r_wmov_i2r_l是模板,定義在mov.c里邊,用用模板引擎多次調(diào)用make_instr_helper實(shí)現(xiàn)。這里有四層的模板:
  • 最頂層的模板是template-start.htemplate-end.h,作用是提供操作數(shù)的類型和后綴名,用DATA_BYTE的大小決定。
  • 中間層是mov-template.h,提供move指令的指令名等信息,這里將instr定義為mov
  • 組合層在exec/helper.h中,用make_instr_helper實(shí)現(xiàn),這里用到了上層提供的類型,后綴名和指令名。組合層需要兩個(gè)函數(shù)指針“decode_type_suffix"和"do_type_suffix"。
  • 最后是實(shí)現(xiàn)層實(shí)現(xiàn)具體的解碼和執(zhí)行函數(shù)。

例如,mov_i2r_v的實(shí)現(xiàn)在mov.c中,在mov.c里邊就有3次"調(diào)用mov-template.h,三次調(diào)用傳遞了不同的數(shù)據(jù)類型和后綴名,mov-template中將instr宏定義為mov。然后一次實(shí)現(xiàn)mov_i2r_wmov_i2r_l,這里邊mov是instr,w/l是后綴,i2r是type。他們使使用了idex,該函數(shù)需要參數(shù)decode_i2r_wdo_i2r_w

// nemu/src/cpu/exec/data-move/move.c
#define DATA_BYTE 1
#include "mov-template.h"
#undef DATA_BYTE

#define DATA_BYTE 2
#include "mov-template.h" // mov_xxx_w
#undef DATA_BYTE

#define DATA_BYTE 4
#include "mov-template.h"
#undef DATA_BYTE

// nemu/include/cpu/exec/template-start.h
#if DATA_BYTE == 1

#define SUFFIX b
#define DATA_TYPE uint8_t
#define DATA_TYPE_S int8_t

#elif DATA_BYTE == 2

#define SUFFIX w
#define DATA_TYPE uint16_t
#define DATA_TYPE_S int16_t

#elif DATA_BYTE == 4

#define SUFFIX l
#define DATA_TYPE uint32_t
#define DATA_TYPE_S int32_t

#else

#error unknown DATA_BYTE

#endif


// nemu/src/cpu/exec/data-move/move-template.h
#define instr mov

make_instr_helper(i2r)
make_instr_helper(i2rm)
make_instr_helper(r2rm)
make_instr_helper(rm2r)

// nemu/include/cpu/exec/helper.h
#define make_instr_helper(type) \\
    make_helper(concat5(instr, _, type, _, SUFFIX)) { \\
        return idex(eip, concat4(decode_, type, _, SUFFIX), do_execute); \\
    }

// nemu/include/cpu/helper.h
/* Instruction Decode and EXecute */
static inline int idex(swaddr_t eip, int (*decode)(swaddr_t), void (*execute) (void)) {
    /* eip is pointing to the opcode */
    int len = decode(eip + 1);
    execute();
    return len + 1; // "1" for opcode
}

  • 傳遞給idex的函數(shù)有兩個(gè),一個(gè)是decode,一個(gè)是execute.

  • 這里的execute部分特別隱晦,在mov.c里邊定義了一個(gè)do_execute的函數(shù),在之前的頭文件里邊又定義了一個(gè)do_execute的宏,編譯時(shí),do_execute進(jìn)行了文本替換,比如替換成了do_mov_b等等,這樣才有了實(shí)際的執(zhí)行體。

// exec/helper.h
#define do_execute concat4(do_, instr, _, SUFFIX)

// mov-template.h
static void do_execute() {
    OPERAND_W(op_dest, op_src->val);
    print_asm_template2();
}

// template-start.h
#define OPERAND_W(op, src) concat(write_operand_, SUFFIX) (op, src)

// exec/helper.h
#define print_asm_template2() \
    print_asm(str(instr) str(SUFFIX) " %s,%s", op_src->str, op_dest->str)

  • write_operand_xx的實(shí)現(xiàn)在decode里邊,如果操作類型是寄存器就寫到目的寄存器,否則就寫到內(nèi)存里邊。
void concat(write_operand_, SUFFIX) (Operand *op, DATA_TYPE src) {
    if(op->type == OP_TYPE_REG) { REG(op->reg) = src; }
    else if(op->type == OP_TYPE_MEM) { swaddr_write(op->addr, op->size, src); }
    else { assert(0); }
}

  • print_asm_template2()。

  • 如果nemu是用面向?qū)ο蟮姆椒▽懣赡軙?huì)清晰很多,這里的各種奇奇怪怪的宏替代if/switch,出錯(cuò)調(diào)試起來各種亂七八糟的提示,感覺設(shè)計(jì)過度了。

  • 關(guān)于decode_type_suffix這個(gè)在decode模塊無疑,看一下mov的實(shí)現(xiàn)decode_i2r_w,

// decode-template.h
/* XX <- Ib 
 * eXX <- Iv 
 */
make_helper(concat(decode_i2r_, SUFFIX)) {
    decode_r_internal(eip, op_dest);
    return decode_i(eip);
}

#define decode_r_internal concat3(decode_r_, SUFFIX, _internal)

/* eXX: eAX, eCX, eDX, eBX, eSP, eBP, eSI, eDI */
static int concat3(decode_r_, SUFFIX, _internal) (swaddr_t eip, Operand *op) {
    op->type = OP_TYPE_REG;
    op->reg = ops_decoded.opcode & 0x7;
    op->val = REG(op->reg);

#ifdef DEBUG
    snprintf(op->str, OP_STR_SIZE, "%%%s", REG_NAME(op->reg));
#endif
    return 0;
}

  • 這里寫反了,先寫decode再寫execute比較好

實(shí)現(xiàn)一條call指令

  1. 查找call rel32對(duì)應(yīng)的opcode為e8,在exec.c里邊的opcode_table里邊增加一個(gè)條目,記為call_i_v。

  2. 聲明1中的函數(shù),在cpu/exec目錄里邊創(chuàng)建call目錄,該目錄下依次創(chuàng)建三個(gè)文件:call.h/call.c/call-template.h;在all-instrs.h目錄里邊增加"call/call.h"頭文件。

// call.h
#include "cpu/helper.h"

make_helper(call_i_v);

// call.c
#include "cpu/exec/helper.h"

#define DATA_BYTE 2
#include "calll-template.h"
#endif

#define DATA_BYTE 4
#include "call-template.h"
#endif

make_helpr_v(call_i)

// call-template.h
#include "cpu/exec/template-start.h"

#define instr call

static void do_execute() {
         // push(ip)
         // read operand address
         // eip += relative address
}

make_instr_helper(i)

#include "cpu/exec/template-end.h"
  1. 創(chuàng)建decode函數(shù),在decode/decode.h,這里應(yīng)該能夠直接使用decode_i_wdecode_i_l,這里src->type為立即數(shù),數(shù)據(jù)在imm里邊
make_helper(concat(decode_i_, SUFFIX)) {
    /* eip here is pointing to the immediate */
    op_src->type = OP_TYPE_IMM;
    op_src->imm = instr_fetch(eip, DATA_BYTE);
    op_src->val = op_src->imm;

#ifdef DEBUG
    snprintf(op_src->str, OP_STR_SIZE, "$0x%x", op_src->imm);
#endif
    return DATA_BYTE;
}

  1. 替換exec table中的inv為call_i_v即可

  2. 使用decode_i_w各種不適,因?yàn)閏all的操作數(shù)不是立即數(shù)?注意到call opcode的后面跟的是cw/cd,試著將i改為c,增加新的decode_c看看。

// decode.h
make_helper(decode_c_w);
make_helper(decode_c_l);

// operand.h
enum { OP_TYPE_REG, OP_TYPE_MEM, OP_TYPE_IMM, OP_TYPE_CALL };

// decode-template.h
/* cw/cd */
make_helper(concat(decode_c_, SUFFIX)) {
    op_src->type = OP_TYPE_CALL;
    op_src->val = instr_fetch(eip, DATA_BYTE);
#ifdef DEBUG
    sprintf(op_src->str, OP_STR_SIZE, "", op_src->val);
#endif
    return DATA_BYTE;
}

實(shí)現(xiàn)push指令

  • done, nothing special

實(shí)現(xiàn)test指令

  • done

實(shí)現(xiàn)je指令

  • done
    int32_t tmp = op_dest->val - op_src->val;
    cpu.AF = 0;
    cpu.OF = 0;
    cpu.CF = 0;
    cpu.SF = tmp & 0x7fffffff;
    cpu.ZF = tmp == 0 ? 1 : 0;
    cpu.PF = parity(tmp & 0xff);
  • push/pop/ret

  • 問題出在了sub指令,他的描述是

83 /5 ib SUB r/m16,imm8 2/7 Subtract sign-extended immediate byte from r/m word
83 /5 ib SUB r/m32,imm8 2/7 Subtract sign-extended immediate byte from r/m dword

它的目的操作數(shù)和源操作數(shù)長度不一致,計(jì)算的時(shí)候把源操作數(shù)要sign extended,但是問題是,框架中要保持目的和源操作數(shù)的類型一致。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容