目录
思考题
由于md图片的显示问题,具体可以联系qq 邮箱1528882125@qq.com获取本实验全套的代码和报告
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:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tmtxfVth-1649584585743)(C:\Users\86178\Documents\计组\PA2.1\picture\git branch.JPG)]
git log:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z8F5wAln-1649584585745)(C:\Users\86178\Documents\计组\PA2.1\picture\gitlog.JPG)]
操作题
1.实现标志寄存器( 10 分)
在cpu/reg.h
中的寄存器结构体中定义eflags
寄存器
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iPpIAxUi-1649584585745)(C:\Users\86178\Documents\计组\PA2.1\picture\1.1.png)]
在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
的信息,说明功能实现成功,如图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8r8crIhI-1649584585747)(C:\Users\86178\Documents\计组\PA2.1\picture\dummy.JPG)]
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
的内容之后,可以对指令进行更加深入的了解,对理论课的学习复习都有帮助。
备注
助教真帅,解决了我实验中遇到的问题
明天会更好,实验继续加油
遇到的问题及解决方法
1.遇到问题:在把每条x86
指令实现完成之后运行nemu
仍然显示没有定义函数
解决方法:首先排除函数没有实现的缘故,因为绝大部分TODO
部分都已经实现完成了,经过排查是没有在all-instr.h
中声明所有的指令造成的错误,之后全 部进行定义就解决了问题。
实验心得
个人感觉本次实验难度较大,对之前的寄存器的知识也有一部分的考察,对指令的内容,汇编反汇编的内容都融合在一起进行综合的应用因而提升了实验的难度但是在结束PA2.1
的内容之后,可以对指令进行更加深入的了解,对理论课的学习复习都有帮助。
备注
助教真帅,解决了我实验中遇到的问题
明天会更好,实验继续加油