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