思考题

1. 增加了多少(10分)

增加了存储地址,操作数地址,指令地址和​​opcode​

2. 是什么类型(10分)

​opcode_entry​​​ 类型,​​set_width​​​ 表示操作数长度,​​make_DHelper​​​表示译码函数 ,​​make_EHelper​​ 表示执行函数

3.操作数结构体的实现(10分)

结构体/共同体中存储:操作数的类型,操作数的宽度,操作数的地址,寄存器操作数,立即操作数,寄存器和操作数指令

4.复现宏定义(30分)

展开结果如下:

make_EHelper(mov) //mov 指令的执行函数 

\#define make_EHelper(name) void concat(exec_, name) (vaddr_t *eip)

\#define concat(x, y) concat_temp(x, y)

\#define concat_temp(x, y)

make_EHelper(push) //push 指令的执行函数

\#define make_EHelper(name) void concat(exec_, name) (vaddr_t *eip)

\#define concat(x, y) concat_temp(x, y)

\#define concat_temp(x, y)

make_DHelper(I2r) //I2r 类型操作数的译码函数

\#define make_DHelper(name) void concat(decode_, name) (vaddr_t *eip)*

IDEX(I2a, cmp) //cmp 指令的 opcode_table 表项 #define IDEXW(id, ex, w)

{concat(decode_, id), concat(exec_, ex), w}

EX(nop) //nop 指令的 opcode_table 表项

\#define EX(ex) EXW(ex, 0)

make_rtl_arith_logic(and) //and 运算的 RTL 指令

\#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); \ }

5.立即数背后的故事(10分)

注意不同的计算机读取方式可能不同,有大端和小端的区别,读取数据时需要格外注意

6.神奇的 ​​eflags ​​(20分)

当超出表示范围时产生溢出,CF不能代替OF,并不是所有的进位情况都会超出表示范围。两个数相加

结果异号,OF为1,反之为0,两数相减,结果与减数相同,OF为1,反之为0。

7.​​git branch​​​ 和 ​​git log​​ 截图(10分)

git branch:

![](C:\Users\86178\Documents\计组\PA2.1\picture\git branch.JPG)

git log:


操作题

1.实现标志寄存器( 10 分)

在​​cpu/reg.h​​​ 中的寄存器结构体中定义​​eflags​​ 寄存器


在​​nemu/src/monitor/monitor.c​​​ 中的​​static inline void restart()​​​ 中设定​​eflags​​​ 寄存器的初值为​​0x2​​ 实现初值的设置。

2.实现所有 ​​RTL ​​指令( 30 分)

​mov​​​ 指令和​​not​​ 指令


  • 思路较为简单直接在变量基础上记性赋值或者按为取反再赋值即可。

static inline void rtl_mv(rtlreg_t* dest, const rtlreg_t *src1){ 
// dest <- src1
//TODO();
*dest=*src1;
} rtl_not 按位取反再赋给原变量。
static inline void rtl_not(rtlreg_t* dest) {
// dest <- ~dest
//TODO();
*dest=~*dest;
}

​sext​​ 指令


  • 实现符号扩展,因为寄存器是无符号整型数,所以先将寄存器强制转换为带符号整型数据,再通过长度判断移位数。

static inline void rtl_sext(rtlreg_t* dest, const rtlreg_t* src1, int width) { 
// dest <- signext(src1[(width * 8 - 1) .. 0])
//TODO();
int32_t t=(int32_t)* src1;
width=width<<3;
t=t>>width;
t=t<<width;
*dest=t;
}

​push​​​和​​pop​​指令


  • 读取寄存器实现进栈和出栈操作

static inline void rtl_push(const rtlreg_t* src1) { 
// esp <- esp - 4
// M[esp] <- src1
//TODO(); cpu.esp=cpu.esp-4;
rtl_sm(&cpu.esp,4,src1);
}
static inline void rtl_pop(rtlreg_t* dest) {
// dest <- M[esp]
// esp <- esp + 4
//TODO();
rtl_lm(dest,&cpu.esp,4);
cpu.esp=cpu.esp+4;
}

​eq0​​指令


  • 判断 ​​src1​​​ 是否为0,为0则 ​​*dest​​​ 为​​1​​​,反之 ​​*dest​​​ 为​​0​​。

static inline void rtl_eq0(rtlreg_t* dest, const rtlreg_t* src1) { 
// dest <- (src1 == 0 ? 1 : 0)
//TODO();
* dest=*src1==0?1:0;
}

​eqi​​指令


  • 判断​​ src1​​​ 是否等与 ​​imm ​​​,相等​​ *dest​​​ 为​​1​​​,不等 ​​*dest​​​ 为​​0​​。

static inline void rtl_eqi(rtlreg_t* dest, const rtlreg_t* src1, int imm) {
// dest <- (src1 == imm ? 1 : 0)
//TODO();
* dest=*src1==imm?1:0;
}

​neq0​​指令


  • 判断 ​​src1 ​​​是否为​​1​​​,为​​1​​​则 ​​*dest​​​ 为​​1​​​,反之 ​​*dest​​​ 为​​0​​。

static inline void rtl_neq0(rtlreg_t* dest, const rtlreg_t* src1) { 
// dest <- (src1 != 0 ? 1 : 0)
//TODO();
*dest=*src1!=0?1:0;
}

​msb​​指令


  • 将 ​​*src1 ​​​指向的数的符号位赋给 ​​*dest ​​。

static inline void rtl_msb(rtlreg_t dest, const rtlreg_t* src1, int width) { 
`// dest <- src1[width * 8 - 1]
//TODO();
rtl_shr(dest,src1,width*8-1);
}

​update_ZF​​指令


  • 更新​​ZF​​​位,先移位,再判断是否为​​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])
//TODO();
rtlreg_t t;
rtl_shli(&t,result,32-width*8);
if(t==0) cpu.eflags.ZF=1;
else
cpu.eflags.ZF=0;
}

​update_SF​​指令


  • 更新​​SF​​​位,调用 ​​rtl_msb​​​ 将 ​​*result ​​​指向数的符号位赋给​​SF​​位。

*static inline void rtl_update_SF(const rtlreg_t* result, int width) { 
// eflags.SF <- is_sign(result[width * 8 - 1 .. 0])
//TODO();
rtlreg_t t;
rtl_msb(&t,result,width);
cpu.eflags.SF=t;
}

3.实现 6 条 ​​x86​​ 指令( 30 分)

首先需要在​​all-instr.h​​中声明所有的指令

如下:

#include "cpu/exec.h"

make_EHelper(mov);

make_EHelper(operand_size);

make_EHelper(inv);
make_EHelper(nemu_trap);
make_EHelper(call);
make_EHelper(push);
make_EHelper(pop);
make_EHelper(sub);
make_EHelper(xor);
make_EHelper(ret);
make_EHelper(nop);
make_EHelper(lea);
make_EHelper(and);
make_EHelper(jmp);
make_EHelper(add);
make_EHelper(cmp);
make_EHelper(leave);
make_EHelper(setcc);
make_EHelper(movzx);
make_EHelper(call_rm);
make_EHelper(jmp_rm);
make_EHelper(inc);
make_EHelper(dec);
make_EHelper(jcc);
make_EHelper(adc);
make_EHelper(or);
make_EHelper(test);
make_EHelper(shl);
make_EHelper(shr);
make_EHelper(sar);
make_EHelper(not);
make_EHelper(div);
make_EHelper(idiv);
make_EHelper(mul);
make_EHelper(imul1);
make_EHelper(imul2);
make_EHelper(cwtl);
make_EHelper(cltd);
make_EHelper(movsx);
make_EHelper(sbb);
make_EHelper(in);
make_EHelper(out);
make_EHelper(neg);
make_EHelper(rol);
make_EHelper(lidt);
make_EHelper(int);
make_EHelper(pusha);
make_EHelper(popa);
make_EHelper(iret);
make_EHelper(mov_r2cr);
make_EHelper(mov_cr2r);

​call​​指令

根据视频里面的提示,可以很快完成 call。先填表,转译函数选择​​ make_DHelper(I)​​​,根据 ​​i386 ​​​手 册的代码框架,补充​​opcode​​ 数组

/* 0xe8 */  IDEX(I,call), IDEX(J,jmp), EMPTY, IDEXW(J,jmp,1),

编写在 ​​control.c​​​中的​​make_EHelper(call)​​。

make_EHelper(call) {
// the target address is calculated at the decode stage
// TODO();
rtl_push(eip);//将eip压栈
decoding.is_jmp=1;//设置跳转标志
rtl_add(&decoding.jmp_eip,eip,&id_dest->val);//将两个相加赋给
print_asm("call %x", decoding.jmp_eip);
}

运行结果:

10000a:   e8 0f 00 00 00                        call 10001e

​push​​指令

到指令​​55​​​ 停止,查找对应​​push​​​ 指令填写​​opcode ​​ 数组

/* 0x50 */  IDEX(r,push),IDEX(r,push),IDEX(r,push),IDEX(r,push),
/* 0x54 */ IDEX(r,push),IDEX(r,push),IDEX(r,push),IDEX(r,push),

之后在​​data-mov.c​​ 中补充指令

make_EHelper(push) {
// TODO();
rtl_push(&id_dest->val);//将值压栈
print_asm_template1(push);
}

运行结果:

10001e:   55                                    pushl %ebp

​sub​​指令

运行到​​83​​​停止,对应​​sub​​​操作,修改​​grp1​​​数组,​​sub​​指令在第六个的位置

/* 0x80, 0x81, 0x83 */
make_group(gp1,
EX(add), EX(or), EX(adc),EX(sbb),
EX(and), EX(sub), EX(xor), EX(cmp))

在​​arith.c​​​ 中补充​​sub​​指令

make_EHelper(sub) {
//TODO();
//参考sbb
rtl_sub(&t2, &id_dest->val, &id_src->val);
rtl_sltu(&t3, &id_dest->val, &t2);
operand_write(id_dest, &t2);
//设置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);
print_asm_template2(sub);
}

运行结果:

100021:   83 ec 18                              subl $0x18,%esp

​nop​​指令

对应90,填写opcode表格即可

/* 0x90 */  EX(nop), EMPTY, EMPTY, EMPTY,

运行结果:

100012:   90                                    nop

​pop​​指令

再运行到5d停止,参考反汇编,对应 pop ,参考 push,填表

编写在​​data-mov.c​​​中的 ​​make_EHelper(pop) ​

make_EHelper(pop) {  //TODO();  rtl_pop(&t0);//寄存器出栈    if(id_dest->type==OP_TYPE_REG)//寄存器操作数,写入寄存器  {   rtl_sr(id_dest->reg,id_dest->width,&t0);  }   else if(id_dest->type==OP_TYPE_MEM)//内存,写入内存  {   rtl_sm(&id_dest->addr,id_dest->width,&t0);  }   else {assert(0);}   print_asm_template1(pop);}

运行结果:

100013:   5d                                    popl %ebp

​ret​​指令

再运行到c3停止,参考反汇编,对应ret 操作,无译码函数,直接执行

/* 0xc0 */  IDEXW(gp2_Ib2E, gp2, 1), IDEX(gp2_Ib2E, gp2), IDEXW(I,ret,2), EX(ret),

编写在​​control.c​​​中的​​ make_EHelper(ret)​​​,参考 ​​make_EHelper(call)​​​,将 ​​eip​​退栈,并设置跳转标志。

make_EHelper(ret) {  //TODO();  rtl_pop(&decoding.jmp_eip);//将eip出栈 if(decoding.opcode==0xc2)//0xc2处,esp+dest {   cpu.esp+=id_dest->val;  } decoding.is_jmp=1;//设置跳转标志    print_asm("ret");}

运行结果:

100014:   c3                                    ret

4.成功运行 ​​dummy​​(10 分)

在​​nexus-am/tests/cputest​​ 目录下输入指令

make ARCH=x86-nemu ALL=dummy run

输入​​si30​​​ 输出后出现​​HIT GOOD TRAP​​ 的信息,说明功能实现成功,如图:


5.实现 ​​Diff-test​​( 20 分)

首先需要在在 ​​nemu/include/common.h​​​ 中定义宏 ​​DIFF_TEST​​ ,取消注释即可

然后在 ​​nemu/src/monitor/difftest/diff-test.c​​​ 中编写​​difftest_step()​​​ 函数比较寄存器的值,若不一样设为​​ture​​​一样的话设为​​false​​。

代码如图所示

if(r.eip!=cpu.eip||r.eax!=cpu.eax||r.ebx!=cpu.ebx||r.ecx!=cpu.ecx||r.edx!=cpu.edx||r.ebp!=cpu.ebp||r.esp!=cpu.esp||r.edi!=cpu.edi||r.esi!=cpu.esi)  {   diff=true;    printf("r.eip:%#x,cpu.eip:%#x\n",r.eip,cpu.eip);      printf("r.eax:%#x,cpu.eax:%#x\n",r.eax,cpu.eax);    printf("r.ebx:%#x,cpu.ebx:%#x\n",r.ebx,cpu.ebx);    printf("r.ecx:%#x,cpu.ecx:%#x\n",r.ecx,cpu.ecx);    printf("r.edx:%#x,cpu.edx:%#x\n",r.edx,cpu.edx);    printf("r.ebp:%#x,cpu.ebp:%#x\n",r.ebp,cpu.ebp);    printf("r.esp:%#x,cpu.esp:%#x\n",r.esp,cpu.esp);    printf("r.edi:%#x,cpu.edi:%#x\n",r.edi,cpu.edi);    printf("r.esi:%#x,cpu.esi:%#x\n",r.esi,cpu.esi);  }

遇到的问题及解决方法

1.遇到问题:在把每条​​x86​​​ 指令实现完成之后运行​​nemu​​ 仍然显示没有定义函数

​ 解决方法:首先排除函数没有实现的缘故,因为绝大部分​​TODO​​​ 部分都已经实现完成了,经过排查是没有在​​all-instr.h​​中声明所有的指令造成的错误,之后全 部进行定义就解决了问题。

实验心得

个人感觉本次实验难度较大,对之前的寄存器的知识也有一部分的考察,对指令的内容,汇编反汇编的内容都融合在一起进行综合的应用因而提升了实验的难度但是在结束​​PA2.1​​ 的内容之后,可以对指令进行更加深入的了解,对理论课的学习复习都有帮助。

备注

助教真帅,解决了我实验中遇到的问题

明天会更好,实验继续加油