PA 2.1
目錄
@
思考題
-
增加了多少?
操作碼
源操作數1 或/和 源操作數2(立即數、寄存器編號、存儲地址)
目的操作數地址(寄存器編號、存儲地址)
-
是什么類型?
opcode_table每個表項的類型是opcode_entryopcode_entry.width記錄了操作數長度信息opcode_entry.decode是一個函數指針,記錄了譯碼函數opcode_entry.execute是一個函數指針,記錄了執行函數 -
操作數結構體的實現
typedef struct { uint32_t type; //操作數的類型 int width; //操作數的寬度 union { uint32_t reg; //寄存器編號 rtlreg_t addr; //操作數的地址 uint32_t imm; //立即數 int32_t simm; //帶符號立即數 }; rtlreg_t val;//操作數的值 char str[OP_STR_SIZE];//指令的解釋 } Operand;一般在譯碼過程中,都會根據操作碼的類型先行標記操作數的類型,再根據操作數類型對共同體內的對應變量賦值。
val一般存儲該操作數的值,str記錄了要顯示的指令信息 -
復現宏定義
-
void exec_mov (vaddr_t *eip)#define make_EHelper(name) void concat(exec_, name) (vaddr_t *eip)concat連接為exec_name的形式 -
void exec_push (vaddr_t *eip)同上
-
void decode_I2r (vaddr_t *eip)#define make_DHelper(name) void concat(decode_, name) (vaddr_t *eip)concat連接為decode_name的形式 -
{decode_I2a, exec_cmp, 0}#define IDEXW(id, ex, w) {concat(decode_, id), concat(exec_, ex), w} #define IDEX(id, ex) IDEXW(id, ex, 0) -
{((void *)0), exec_nop, 0}#define NULL ((void *)0) #define EXW(ex, w) {NULL, concat(exec_, ex), w} #define EX(ex) EXW(ex, 0)
static inline void rtl_and (rtlreg_t* dest, const rtlreg_t* src1, const rtlreg_t* src2) { *dest = ((*src1) & (*src2)); } static inline void rtl_andi (rtlreg_t* dest, const rtlreg_t* src1, int imm) { *dest = ((*src1) & (imm)); }#define make_rtl_arith_logic(name) \ static inline void concat(rtl_, name) (rtlreg_t* dest, const rtlreg_t* src1, const rtlreg_t* src2) { \ *dest = concat(c_, name) (*src1, *src2); \ } \ static inline void concat3(rtl_, name, i) (rtlreg_t* dest, const rtlreg_t* src1, int imm) { \ *dest = concat(c_, name) (*src1, imm); \ } -
-
⽴即數背后的故事
要注意機器是大端機還是小端機。
因為大小端機字節保存的順序相反。
使用前先判斷大小端機,根據類型使用不同的字節讀取方式。
-
神奇的
eflags溢出表示超過最大可表示范圍。
不能替換,進位不能代表溢出。
OF=Cn⊕Cn-1。
-
git branch和git log截圖(最新的,⼀張即可)
實驗內容
實現標志寄存器
1.實現eflags
查詢i386手冊可知,eflags寄存器結構如下,根據講義介紹,我們只需實現CF、ZF、SF、IF、OF。
通過位域與無名位域適當組合,來組織出eflags的結構
其中value是用來給eflags賦初值時使用
union {
uint32_t value;//用於賦初值
struct {
uint32_t CF:1;
uint32_t :5;//無名位域
uint32_t ZF:1;
uint32_t SF:1;
uint32_t :1;
uint32_t IF:1;
uint32_t :1;
uint32_t OF:1;
};
}eflags;
2.eflags設置初值
查詢i386手冊,發現Chapter 10 Initialization第十章與初始化相關。通過閱讀可知需要把eflags初始化為0x2
在ics2020\nemu\src\monitor\monitor.c找到restart()函數,並在其中添加
cpu.eflags.value=0x2;//eflags賦初始值0x2
3.實現所有指令對標志位的設置
已有6條指令中,通過查詢i386手冊,可知sub與xor需要修改標志位
對於sub,模仿sbb,除了不需要減去CF外,其余的均相同
//更新ZF,SF rtl_update_ZFSF(&t2, id_dest->width); //更新CF rtl_sltu(&t0, &id_dest->val, &t2); rtl_or(&t0, &t3, &t0); rtl_set_CF(&t0); //更新OF rtl_xor(&t0, &id_dest->val, &id_src->val); rtl_xor(&t1, &id_dest->val, &t2); rtl_and(&t0, &t0, &t1); rtl_msb(&t0, &t0, id_dest->width); rtl_set_OF(&t0);
對於xor,需要更新SF ZF,OF=0、CF=0
rtl_li(&t0,0); rtl_set_CF(&t0);//CF=0 rtl_set_OF(&t0);//OF=0 rtl_update_ZFSF(&id_dest->val,id_dest->width);//更新ZF、SF
實現所有RTL指令
1.rtl_mv
按照注釋,簡單賦值即可
static inline void rtl_mv(rtlreg_t* dest, const rtlreg_t *src1)
// dest <- src1
*dest = *src1;
}
2.rtl_not
按照注釋,簡單取非即可
static inline void rtl_not(rtlreg_t* dest)
{
// dest <- ~dest
*dest = ~ (*dest);
}
3.rtl_sext
函數參數中有width,考慮要對不同大小的數進行符號擴展。我們可以通過帶符號整數的算數右移來實現符號擴展,則先左移一定位數使得要求符號處於最高位,再算數右移回原位置,即可實現符號擴展。
static inline void rtl_sext(rtlreg_t* dest, const rtlreg_t* src1, int width) {
// dest <- signext(src1[(width * 8 - 1) .. 0])
int32_t t = *src1;//轉為帶符號整數
if (width == 1) {//若為1字節
*dest = (t<<24)>>24;//符號擴展
}
else if (width == 2) {//2字節
*dest = (t<<16)>>16;
}
else if (width ==4) {//4字節
*dest = t;
}
else {
assert(0);
}
}
4.rtl_push
根據注釋,使用rtl實現取寄存器值、減法、存儲等功能
static inline void rtl_push(const rtlreg_t* src1) {
// esp <- esp - 4
// M[esp] <- src1
rtl_lr(&t0,R_ESP,4);//獲取寄存器ESP的值
rtl_subi(&t0,&t0,4);//ESP-4
rtl_sr(R_ESP,4,&t0);//存儲ESP
rtl_sm(&t0,4,src1);//存儲要push的內容
}
5.rtl_pop
實現思路與rtl_push基本相同
static inline void rtl_pop(rtlreg_t* dest) {
// dest <- M[esp]
// esp <- esp + 4
rtl_lr(&t0,R_ESP,4);//獲取寄存器ESP的值
rtl_lm(dest,&t0,4);//獲取ESP指定位置的內存的值
rtl_addi(&t0,&t0,4);//ESP+4
rtl_sr(R_ESP,4,&t0);//存儲ESP
}
6.rtl_eq0
判斷src1是否為0,若是,則給dest賦值1,否則賦值0
static inline void rtl_eq0(rtlreg_t* dest, const rtlreg_t* src1) {
// dest <- (src1 == 0 ? 1 : 0)
*dest = *src1 == 0 ? 1 : 0;
}
7.rtl_eqi
判斷操作數src1與立即數imm是否相等。若相等,則給dest賦值1,否則賦值0
static inline void rtl_eqi(rtlreg_t* dest, const rtlreg_t* src1, int imm) {
// dest <- (src1 == imm ? 1 : 0)
*dest = *src1 == imm ? 1 : 0;
}
8.rtl_neq0
判斷操作數是否為0
static inline void rtl_neq0(rtlreg_t* dest, const rtlreg_t* src1) {
// dest <- (src1 != 0 ? 1 : 0)
*dest = *src1 != 0 ? 1 : 0;
}
9.rtl_msb
獲取最高有效位,因為src1是無符號整數,可以通過邏輯右移獲取其最高有效位
static inline void rtl_msb(rtlreg_t* dest, const rtlreg_t* src1, int width) {
// dest <- src1[width * 8 - 1]
rtl_shri(dest, src1, width*8 - 1);//邏輯右移
}
10.make_rtl_setget_eflags
此宏定義實現了rtl_set_XF()與rtl_get_XF()的功能,我們只需要實現對eflags寄存器相應位置的存取即可
#define make_rtl_setget_eflags(f) \
static inline void concat(rtl_set_, f) (const rtlreg_t* src) { \
cpu.eflags.f = *src; \
} \
static inline void concat(rtl_get_, f) (rtlreg_t* dest) { \
*dest = cpu.eflags.f; \
}
11.rtl_update_ZF
更新ZF,根據注釋,我們只需判斷從width*8-1...0這些位是否全為0。我們可以通過邏輯左移把原來width*8-1移到最高位,末尾補0。若移位后的數值為0,則ZF=1否則ZF=0
static inline void rtl_update_ZF(const rtlreg_t* result, int width) {
// eflags.ZF <- is_zero(result[width * 8 - 1 .. 0])
t0 = *result;
rtl_shli(&t0,&t0,32-width*8);//邏輯左移,去掉無用位
if (t0 == 0)//若整體為0,即所有位都是0
t1=1;
else
t1=0;
rtl_set_ZF(&t1);//設置ZF
}
12.rtl_update_SF
更新SF,獲取result符號即可。
static inline void rtl_update_SF(const rtlreg_t* result, int width) {
// eflags.SF <- is_sign(result[width * 8 - 1 .. 0])
rtl_msb(&t0, result, width);//獲取result符號
rtl_set_SF(&t0);//設置SF
}
實現6條x86指令
1.call
運行dummy發現e8指令沒有實現,查詢i386手冊可知需要實現call。A表明操作數是一個立即數,v表明操作數大小可能為2或4字節
據此,我們可以選定譯碼函數make_DHelper(J),但在此之前,我們還需要實現make_DopHelper(SI),其功能是譯碼一個帶符號立即數。
op->simm=instr_fetch(eip,op->width);//讀取指定長度信息
譯碼函數make_DHelper(J)功能是獲取一個帶符號立即數,並decoding.jmp_eip 指向id_dest->simm + *eip的位置
實現執行函數make_EHelper(call),由於譯碼函數已經完成了跳轉位置的設定,這里我們只需標志跳轉並入棧eip即可
make_EHelper(call) {
decoding.is_jmp=1;//標識跳轉
rtl_push(eip);//入棧eip
print_asm("call %x", decoding.jmp_eip);
}
最后填寫opcode_table
IDEXW(J,call,4)
執行結果如下
2.push
運行dummy發現55指令沒有實現,查詢i386手冊可知需要實現push。
選取譯碼函數make_DHelper(r),其功能是讀取操作碼中的寄存器信息。
選取執行函數make_EHelper(push)
使用rtl_push指令把取到的操作數入棧
make_EHelper(push) {
rtl_push(&id_dest->val);
print_asm_template1(push);
}
最后填寫opcode_table
IDEX(r,push)
執行結果如下
3.pop
運行dummy發現5d指令沒有實現,查詢i386手冊可知需要實現pop。
選取譯碼函數make_DHelper(r),其功能是讀取操作碼中的寄存器信息。
選取執行函數make_EHelper(pop)
使用rtl_pop指令把棧頂內容存入id_dest
make_EHelper(pop) {
rtl_pop(&id_dest->val);
operand_write(id_dest, &id_dest->val);
print_asm_template1(pop);
}
最后填寫opcode_table
IDEX(r,pop)
執行結果如下
4.sub
運行dummy發現83指令沒有實現,查詢反匯編結果可知需要實現sub。
發現opcode_table中要使用了grp1,查表可知grp1[5]應填寫執行函數make_EHelper(sub)
選取執行函數make_EHelper(sub),若src的位數少於dest,則需要位擴展,相減之后的結果存入dest即可
make_EHelper(sub) {
rtl_sub(&t2, &id_dest->val, &id_src->val);
rtl_sltu(&t3, &id_dest->val, &t2);//t3記錄是否借位,0表示借位
operand_write(id_dest, &t2);//最終結果寫入對應寄存器或內存
rtl_update_ZFSF(&t2, id_dest->width);//更新ZF,SF
rtl_sltu(&t0, &id_dest->val, &t2);//與減去借位后再比
rtl_or(&t0, &t3, &t0);
rtl_set_CF(&t0);
rtl_xor(&t0, &id_dest->val, &id_src->val);
rtl_xor(&t1, &id_dest->val, &t2);
rtl_and(&t0, &t0, &t1);
rtl_msb(&t0, &t0, id_dest->width);
rtl_set_OF(&t0);
print_asm_template2(sub);
}
填寫gpr1
make_group(gp1,
EMPTY, EMPTY, EMPTY, EMPTY,
EMPTY, EX(sub), EMPTY, EMPTY)
執行結果如下
5.xor
運行dummy發現31指令沒有實現,查詢i386手冊可知需要實現xor。
選取譯碼函數make_DHelper(G2E) Ev <- Gv
選取執行函數make_EHelper(xor),只需要把兩個源操作數異或后的結果存入目的操作數即可。
make_EHelper(xor) {
rtl_xor(&id_dest->val,&id_src->val,&id_src2->val);//異或
operand_write(id_dest,&id_dest->val);//賦值
rtl_li(&t0,0);
rtl_set_CF(&t0);//CF=0
rtl_set_OF(&t0);//OF=0
rtl_update_ZFSF(&id_dest->val,id_dest->width);//更新ZF、SF
print_asm_template2(xor);
}
最后填寫opcode_table
IDEX(G2E,xor)
執行結果如下
6.ret
運行dummy發現c3指令沒有實現,查詢i386手冊可知需要實現ret。
由上圖可知ret無譯碼函數
選取make_EHelper(ret)為執行函數,由於需要跳轉回上次call指令的下條指令處,需要設置跳轉標志,並出把要跳轉的位置出棧賦給decoding.jmp_eip
make_EHelper(ret) {
decoding.is_jmp=1;//設置跳轉
rtl_pop(&decoding.jmp_eip);//獲取跳轉位置
print_asm("ret");
}
最后填寫opcode_table
EX(ret)
執行結果如下
成功運行dummy
實現Diff-test
在nemu/include/common.h中定義宏
#define DIFF_TEST
在nemu/src/monitor/diff-test/diff-test.c中修改difftest_step(),實現寄存器的對比。
先定義一個宏定義,用來判斷r.reg與cpu.reg的內容是否相等,若不相等,則令diff=false
#define test_reg(reg) \
if (r.reg != cpu.reg) { \
diff = true; \
Log("reg error NEMU.reg=0x%08x QEMU.reg=0x%08x\n",cpu.reg,r.reg); \
}
在difftest_step()添加下面代碼,實現對8個通用寄存器以及eip的檢測
test_reg(eax);
test_reg(ecx);
test_reg(edx);
test_reg(ebx);
test_reg(ebp);
test_reg(esp);
test_reg(edi);
test_reg(esi);
test_reg(eip);
開啟diff-test,成功運行dummy
