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
