PA 2.1
目錄
@
思考題
-
增加了多少?
操作碼
源操作數1 或/和 源操作數2(立即數、寄存器編號、存儲地址)
目的操作數地址(寄存器編號、存儲地址)
-
是什么類型?
opcode_table
每個表項的類型是opcode_entry
opcode_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