南京航空航天大学 PA2.1

PA 2.1

目录

文章目录

思考题

  1. 增加了多少?

    操作码

    源操作数1 或/和 源操作数2(立即数、寄存器编号、存储地址)

    目的操作数地址(寄存器编号、存储地址)

  2. 是什么类型?

    opcode_table每个表项的类型是opcode_entry

    opcode_entry.width记录了操作数长度信息

    opcode_entry.decode是一个函数指针,记录了译码函数

    opcode_entry.execute是一个函数指针,记录了执行函数

  3. 操作数结构体的实现

    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记录了要显示的指令信息

  4. 复现宏定义

    • 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); \
      }
    
  5. ⽴即数背后的故事

    要注意机器是大端机还是小端机。

    因为大小端机字节保存的顺序相反。

    使用前先判断大小端机,根据类型使用不同的字节读取方式。

  6. 神奇的 eflags

    溢出表示超过最大可表示范围。

    不能替换,进位不能代表溢出。

    OF=Cn⊕Cn-1。

  7. git branchgit log 截图(最新的,⼀张即可)

实验内容

实现标志寄存器


1.实现eflags

查询i386手册可知,eflags寄存器结构如下,根据讲义介绍,我们只需实现CFZFSFIFOF

通过位域与无名位域适当组合,来组织出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手册,可知subxor需要修改标志位

对于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 ZFOF=0CF=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-10这些位是否全为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手册可知需要实现callA表明操作数是一个立即数,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.regcpu.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

上一篇:vivado_hls demo LZ77


下一篇:jQuery编程小结