讲义
2.1 第一个程序
源码:
GitHub - yym68686/ics2022: NUAA PA ics2022
下载源码后,编译前:
cd ics2022 && git checkout remotes/origin/pa2 && ./init.sh && source ~/.bashrc
思考题
-
增加了多少 我们说在执行完下一条指令前需要更新 eip 的值。设某指令执行前 eip 值为 x1,该指令执行后 eip 值为 x2,那么 x2 - x1 的这个差值都包括了一条指令的哪些组成成分?
- 操作码,立即数值,源操作数地址,目的操作数,不同的指令有不同的结构。
-
是什么类型 opcode_table 数组中存放了所有指令的信息,请问表中每个表项是什么类型?NEMU 又是如何通过这个表项得知操作数长度、应该使用哪个译码函数、哪个执行函数等信息的?
opcode_entry 类型- 通过查找指令表元素的第三项来获得操作数宽度
- 通过查找指令表元素的第一项来获得译码函数指针
- 通过查找指令表元素的第二项来获得执行函数指针
-
操作数结构体的实现 阅读 decode.c 和 decode.h ,思考一下操作数结构体/共同体中都包括哪些成员,分别存储什么信息?他们是如何实现协同工作的?
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;
4.复现宏定义
我们在学习 C 语言时就已经接触过宏定义了,现在我们需要复习一下,便于你更好地理解项目中所使用的宏定义。下面请你算出下列宏定义最终展开以后的样子,即类似于 type func_name(type arg1, type arg2, ...) 的我们最常见到的 C 语言函数定义的形式,在展开时,希望你能写出计算步骤,不要直接写出结果。如果你现在无法算出下列的宏定义,你可以先阅读完整个本节内容,再回来思考这个题目。
- make_EHelper(mov) //mov 指令的执行函数
- make_EHelper(mov) → void exec_mov(vaddr_t *eip)
- make_EHelper(push) //push 指令的执行函数
- make_EHelper(mov) → void exec_push(vaddr_t *eip)
- make_DHelper(I2r) //I2r 类型操作数的译码函数
- make_DHelper(I2r) → void decode_I2r(vaddr_t *eip)
- IDEX(I2a, cmp) //cmp 指令的 opcode_table 表项
- IDEX(I2a, cmp) → IDEXW(I2a, cmp, 0) → {decode_I2a, exec_cmp, 0}
- EX(nop) //nop 指令的 opcode_table 表项
- EX(nop) → EXW(nop, 0) → {NULL, exec_nop, 0}
- make_rtl_arith_logic(and) //and 运算的 RTL 指令
make_rtl_arith_logic(and) →
static inline void rtl_and(rtlreg_t* dest, const rtlreg_t* src1, const rtlreg_t* src2) {
*dest = c_and(*src1, *src2);
}
static inline void rtl_andi(rtlreg_t* dest, const rtlreg_t* src1, int imm) {
*dest = c_and(*src1, imm);
}
5.?即数背后的故事
Motorola 68k 系列的处理器都是大端架构的. 现在问题来了, 考虑以下两种情况:
- 假设我们需要将NEMU运行在Motorola 68k的机器上(把NEMU的源代码编译成Motorola 68k的机器码)
- 假设我们需要编写一个新的模拟器NEMU-Motorola-68k, 模拟器本身运行在x86架构中, 但它模拟的是Motorola 68k程序的执行
在这两种情况下, 你需要注意些什么问题? 为什么会产生这些问题? 怎么解决它们?
- nemu是小端序,在Motorola 68k的机器上读指令会出错
- 因为Motorola 68k是大端序
- nemu把小端序解释为大端序
6.神奇的 eflags
OF 的作用是判断是否有溢出, 这里的“溢出”是什么意思呢? 如果只用仅为标志位 CF 可不可以代替 OF 的功能呢?为什么?
- 溢出的意思是被减数与结果符号不一样,减的时候超过了数字的范围
- 不可以
- 因为OF是有符号数下的标志位,CF是对于无符号数而言的,有符号数下没有意义,不能代替OF
- 在运算的过程中如何获得
OF 的值?
- 当(负数-正数=正数)和(正数-负数=负数)时,判断为为溢出,OF=1
实验内容
实现标志寄存器
根据讲义所给 eflags 寄存器结构:
31 23 15 7 0
+-------------------+-------------------+-------+-+-+-+-+-+-+-------+-+-+
| |O| |I| |S|Z| | |C|
| X | |X| |X| | | X |1| |
| |F| |F| |F|F| | |F|
+-------------------+-------------------+-------+-+-+-+-+-+-+-------+-+-+
- 在1比特的位置置一,与下文 eflags 初值为2(0x10)有关,暗示 eflags 与各标志寄存器共享内存空间。
用到了位域,wiki 百科的例子是:
struct CHAR
{
unsigned int ch : 8;
unsigned int font : 6;
unsigned int size : 18;
};
struct CHAR ch1;
无名位域定义为:如果位域的定义没有给出标识符名字,那么这是无名位域,无法被初始化。无名位域用于填充(padding)内存布局。只有无名位域的比特数可以为0。这种占0比特的无名位域,用于强迫下一个位域在内存分配边界对齐。
Reference:位段 - 维基百科,自由的百科全书 (wikipedia.org)
所以修改include/cpu/reg.h:
typedef struct {
union{
struct {
union{
uint32_t _32;
uint16_t _16;
uint8_t _8[2];
};
} gpr[8];
struct {
rtlreg_t eax, ecx, edx, ebx, esp, ebp, esi, edi;
};
};
/* Do NOT change the order of the GPRs' definitions. */
/* In NEMU, rtlreg_t is exactly uint32_t. This makes RTL instructions
* in PA2 able to directly access these registers.
*/
+ union {
+ 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;
+ uint32_t : 20;
+ }
+ rtlreg_t value;
+ } eflags;
vaddr_t eip;
} CPU_state;
- value与标志寄存器共享内存空间,给eflags赋初值就是给这32位空间整体赋初值。根据i386手册第174页,eflags 初值为0x2。
在src/monitor/monitor.c 中增加 eflags 初始化:
static inline void restart() {
cpu.eip = ENTRY_START;
cpu.eflags.value = 0x2;
#ifdef DIFF_TEST
init_qemu_reg();
#endif
}
实现所有 RTL 指令
在include/cpu/rtl.h中完善函数:
static inline void rtl_mv(rtlreg_t* dest, const rtlreg_t *src1) {
*dest = *src1;
}
static inline void rtl_not(rtlreg_t* dest) {
*dest = ~(*dest);
}
static inline void rtl_sext(rtlreg_t* dest, const rtlreg_t* src1, int width) {
int32_t result = (int32_t)*src1;
switch(width){
case 1:
result <<= 24;
result >>= 24;
break;
case 2:
result <<= 16;
result >>= 16;
break;
case 4:
break;
default:
assert(0);
}
*dest = result;
}
static inline void rtl_push(const rtlreg_t* src1) {
cpu.esp -= 4;
rtl_sm(&cpu.esp, 4, *src1);
}
static inline void rtl_push(const rtlreg_t* src1) {
cpu.esp -= 4;
rtl_sm(&cpu.esp, 4, src1);
}
static inline void rtl_pop(rtlreg_t* dest) {
rtl_lm(dest, &cpu.esp, 4);
cpu.esp += 4;
}
static inline void rtl_eq0(rtlreg_t* dest, const rtlreg_t* src1) {
*dest = *src1 ? 0 : 1;
}
static inline void rtl_eqi(rtlreg_t* dest, const rtlreg_t* src1, int imm) {
*dest = *src1 == imm ? 1 : 0;
}
static inline void rtl_neq0(rtlreg_t* dest, const rtlreg_t* src1) {
*dest = *src1 != 0 ? 1 : 0;
}
static inline void rtl_msb(rtlreg_t* dest, const rtlreg_t* src1, int width) {
*dest = *src1 >> (width * 8 - 1) & 0x1;
}
static inline void rtl_update_ZF(const rtlreg_t* result, int width) {
if (*result == 0)
t0 = 1;
else t0 = 0;
rtl_set_ZF(&t0);
}
static inline void rtl_update_SF(const rtlreg_t* result, int width) {
t0 = *result >> (width * 8 - 1) & 0x1;
rtl_set_SF(&t0);
}
注意点:
- 基本根据注释写代码
- 各个标志寄存器作用:
- SF:结果为负,即符号位为1时SF=1,否则为0.
- ZF:结果为0,ZF=1,否则为0
- CF:最高位进位,CF=1,不进位为0
- OF:溢出,OF=1
- rtl_sext函数要根据不同的字长进行不同的处理
实现 6 条 x86 指令
本部分需要实现call , push , pop , sub , nop , ret 六个指令。
call 指令
先运行一次测试程序,看看有哪个指令没有完成,输入命令:
clear && cd /home/username/ics2022/nexus-am/tests/cputest && make clean && make ARCH=x86-nemu ALL=dummy run
查看汇编文件nexus-am/tests/cputest/build/dummy-x86-nemu.txt :
00100000 <_start>:
100000: bd 00 00 00 00 mov $0x0,%ebp
100005: bc 00 7c 00 00 mov $0x7c00,%esp
10000a: e8 0f 00 00 00 call 10001e <_trm_init>
0010000f <serial_init>:
10000f: 55 push %ebp
100010: 89 e5 mov %esp,%ebp
100012: 90 nop
100013: 5d pop %ebp
100014: c3 ret
00100015 <_halt>:
100015: 55 push %ebp
100016: 89 e5 mov %esp,%ebp
100018: 8b 45 08 mov 0x8(%ebp),%eax
10001b: d6 (bad)
10001c: eb fe jmp 10001c <_halt+0x7>
0010001e <_trm_init>:
10001e: 55 push %ebp
10001f: 89 e5 mov %esp,%ebp
100021: 83 ec 18 sub $0x18,%esp
100024: e8 e6 ff ff ff call 10000f <serial_init>
100029: e8 14 00 00 00 call 100042 <main>
10002e: 89 45 f4 mov %eax,-0xc(%ebp)
100031: 83 ec 0c sub $0xc,%esp
100034: ff 75 f4 pushl -0xc(%ebp)
100037: e8 d9 ff ff ff call 100015 <_halt>
10003c: 83 c4 10 add $0x10,%esp
10003f: 90 nop
100040: c9 leave
100041: c3 ret
00100042 <main>:
100042: 55 push %ebp
100043: 89 e5 mov %esp,%ebp
100045: b8 00 00 00 00 mov $0x0,%eax
10004a: 5d pop %ebp
10004b: c3 ret
- 第一次遇到call指令为
10000a: e8 0f 00 00 00 call 10001e <_trm_init> ,call的操作数是立即数
查阅i386手册第275页,call 操作码为0xE8 ,call 指令部分的伪代码:
IF rel16 or rel32 type of call
THEN (* near relative call *)
IF OperandSize = 16
THEN
Push(IP);
EIP ← (EIP + rel16) AND 0000FFFFH;
ELSE (* OperandSize = 32 *)
Push(EIP);
EIP ← EIP + rel32;
FI;
FI;
- call指令先将下一个eip压栈
- 然后eip赋值为跳转地址
首先在操作码表格中加入call指令:
IDEXW(I, call, 4), EMPTY, EMPTY, EMPTY,
- 宏定义展开后为
{decode_I, exec_call, 4} decode_I 作用是解析call 指令后的无符号立即数- 设置操作数宽度为4
- 当调用
decode_I 函数时,即调用src/cpu/decode/decode.c 中的函数:
make_DHelper(I) {
decode_op_I(eip, id_dest, true);
}
宏定义展开后为:
decode_I(vaddr_t *eip) {
decode_op_I(eip, op, true){
op->type = OP_TYPE_IMM;
op->imm = instr_fetch(eip, op->width);
rtl_li(&op->val, op->imm);
#ifdef DEBUG
snprintf(op->str, OP_STR_SIZE, "$0x%x", op->imm);
#endif
}
}
op->imm = instr_fetch(eip, op->width); 这里就是把立即数取出来,这里eip指向立即数,而不是指向整条指令的开始,是因为在exec_real(vaddr_t* eip)函数里执行了一次instr_fetch,把call指令取了出来,这里的eip是exec_real(vaddr_t* eip)传进来的decoding.seq_eip,所以decoding.seq_eip在取完call后自增一,指向call后面的立即数。- 但是cpu.eip并没有变,一开始cpu.eip就赋值给了decoding.seq_eip, 所以整个取指过程中,cpu.eip一直指向的是指令的开始字节,只有在指令执行结束后,通过update_eip函数才指向下一条指令。
- 取值译码完成后就要执行指令了
在src/cpu/exec/control.c 文件中补全call执行流程的指令:
make_EHelper(call) {
decoding.is_jmp = 1;
rtl_push(eip);
rtl_add(&decoding.jmp_eip, eip, &id_dest->val);
print_asm("call %x", decoding.jmp_eip);
}
- exec_real和decode_op_I(eip, op, true)分别进行了一次instr_fetch操作,所以eip,即decoding.seq_eip已经指向了下一个指令的开始字节,这里可以直接把eip push进寄存器。
在src/cpu/exec/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);
再次编译程序:
clear && cd /home/username/ics2022/nexus-am/tests/cputest && make clean && make ARCH=x86-nemu ALL=dummy run
- 成功执行
call 指令 - 提示
0x55 指令,即push 指令没有实现
push 指令
查看i386手册,第367页,push指令伪代码:
IF StackAddrSize = 16
THEN
IF OperandSize = 16 THEN
SP ← SP - 2;
(SS:SP) ← (SOURCE); (* word assignment *)
ELSE
SP ← SP - 4;
(SS:SP) ← (SOURCE); (* dword assignment *)
FI;
ELSE (* StackAddrSize = 32 *)
IF OperandSize = 16
THEN
ESP ← ESP - 2;
(SS:ESP) ← (SOURCE); (* word assignment *)
ELSE
ESP ← ESP - 4;
(SS:ESP) ← (SOURCE); (* dword assignment *)
FI;
FI;
手册第414页,0x50到0x57都是PUSH指令。我们只需要实现32位,在操作码表中补全指令:
IDEXW(r, push, 4), IDEXW(r, push, 4), IDEXW(r, push, 4), IDEXW(r, push, 4),
IDEXW(r, push, 4), IDEXW(r, push, 4), IDEXW(r, push, 4), IDEXW(r, push, 4),
- 宏定义展开后为
{decode_r, exec_push, 4} decode_r 作用是解析push 指令后的无符号立即数- 设置操作数宽度为4
在src/cpu/exec/data-mov.c中补全push指令:
make_EHelper(push) {
rtl_push(&(id_dest->val));
print_asm_template1(push);
}
在src/cpu/exec/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);
再次编译程序:
clear && cd /home/username/ics2022/nexus-am/tests/cputest && make clean && make ARCH=x86-nemu ALL=dummy run
出错,显示decode_op_SI: Assertion '0' failed ,说明decode_op_SI没有实现。仔细查看汇编文件:
10001e: 55 push %ebp
10001f: 89 e5 mov %esp,%ebp
100021: 83 ec 18 sub $0x18,%esp
此时push 已经执行完毕了。执行到0x100021 ,遇到0x83指令,查看src/cpu/exec/exec.c 中的操作码表:
IDEXW(I2E, gp1, 1), IDEX(I2E, gp1), EMPTY, IDEX(SI2E, gp1),
跟踪decode_SI2E 到src/cpu/decode/decode.c 中的make_DHelper(SI2E) 函数:
make_DHelper(SI2E) {
puts("***");
assert(id_dest->width == 2 || id_dest->width == 4);
decode_op_rm(eip, id_dest, true, NULL, false);
id_src->width = 1;
decode_op_SI(eip, id_src, true);
if (id_dest->width == 2) {
id_src->val &= 0xffff;
}
}
果然0x83调用了decode_op_SI 函数。src/cpu/decode/decode.c 里补全decode_op_SI 函数:
static inline make_DopHelper(SI) {
assert(op->width == 1 || op->width == 4);
op->type = OP_TYPE_IMM;
op->simm = instr_fetch(eip, op->width);
rtl_li(&op->val, op->simm);
#ifdef DEBUG
snprintf(op->str, OP_STR_SIZE, "$0x%x", op->simm);
#endif
}
- 跟
decode_op_I 函数差不多,只要把imm 换成simm 就行了
保存后再次编译程序:
clear && cd /home/username/ics2022/nexus-am/tests/cputest && make clean && make ARCH=x86-nemu ALL=dummy run
- 成功执行push函数
- 提示
0x83 指令,查看汇编代码,发现是sub 指令没有实现
sub 指令
在i386手册第414页找到0x83指令,发现是Grp1 Ev, Iv ,v表示不能确定字长。查看第416页,找到第一个指令组: 发现SUB指令在第六个位置。所以在src/cpu/exec/exec.c 中修改指令组第六个元素:
make_group(gp1,
EMPTY, EMPTY, EMPTY, EMPTY,
EMPTY, EX(sub), EMPTY, EMPTY)
- 指令可以确定字长用EXW,否则用EX
- 不需要译码函数
在手册第404页,找到sub指令的伪代码:
IF SRC is a byte and DEST is a word or dword
THEN DEST = DEST - SignExtend(SRC);
ELSE DEST ← DEST - SRC;
FI;
在src/cpu/exec/arith.c 中补全减法逻辑:
make_EHelper(sub) {
rtl_sub(&t0, &id_dest->val, &id_src->val);
operand_write(id_dest, &t0);
rtl_update_ZFSF(&t0, id_dest->width);
rtl_sltu(&t1, &id_dest->val, &t0);
rtl_set_CF(&t1);
rtl_xor(&t1, &id_dest->val, &id_src->val);
rtl_xor(&t2, &id_dest->val, &t0);
rtl_and(&t0, &t1, &t2);
rtl_msb(&t0, &t0, id_dest->width);
rtl_set_OF(&t0);
print_asm_template2(sub);
}
在src/cpu/exec/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(sub);
保存后再次编译程序:
clear && cd /home/username/ics2022/nexus-am/tests/cputest && make clean && make ARCH=x86-nemu ALL=dummy run
- 成功执行 sub 函数
- 提示
0x90 指令,即nop 指令没有实现
nop 指令
翻到手册第355页,NOP不需要任何操作。使用vim全文搜索nop指令:
:vim /nop/ /home/username/ics2022/nemu/**
发现make_EHelper(nop) 在src/cpu/exec/special.c 中,不需要额外增加内容。在src/cpu/exec/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(sub);
+make_EHelper(nop);
保存后再次编译程序:
clear && cd /home/username/ics2022/nexus-am/tests/cputest && make clean && make ARCH=x86-nemu ALL=dummy run
- 成功执行 nop 函数
- 提示
0x5d 指令,即pop 指令没有实现
pop 指令
查阅手册第414页,发现0x58到0x5f都是pop指令,补全操作码表:
IDEXW(r, pop, 4), IDEXW(r, pop, 4), IDEXW(r, pop, 4), IDEXW(r, pop, 4),
IDEXW(r, pop, 4), IDEXW(r, pop, 4), IDEXW(r, pop, 4), IDEXW(r, pop, 4),
在src/cpu/exec/data-mov.c 中完善执行函数逻辑:
make_EHelper(pop) {
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);
}
在src/cpu/exec/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(sub);
make_EHelper(nop);
+make_EHelper(pop);
保存后再次编译程序:
clear && cd /home/username/ics2022/nexus-am/tests/cputest && make clean && make ARCH=x86-nemu ALL=dummy run
- 成功执行 pop 函数
- 提示
0xc3 指令,即ret 指令没有实现
ret 指令
查阅手册第414页,0xc3 的位置是空白,没有译码函数,直接执行。补全操作码表:
IDEXW(gp2_Ib2E, gp2, 1), IDEX(gp2_Ib2E, gp2), EMPTY, EX(ret),
查阅手册第378页,查看ret的执行逻辑是:
IF instruction = near RET
THEN;
IF OperandSize = 16
THEN
IP ← Pop();
EIP ← EIP AND 0000FFFFH;
ELSE (* OperandSize = 32 *)
EIP ← Pop();
FI;
IF instruction has immediate operand THEN eSP ← eSP + imm16; FI;
FI;
在src/cpu/exec/control.c 补全ret指令的执行逻辑:
make_EHelper(ret) {
rtl_pop(&decoding.jmp_eip);
decoding.is_jmp = 1;
print_asm("ret");
}
在src/cpu/exec/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(sub);
make_EHelper(nop);
make_EHelper(pop);
+make_EHelper(ret);
保存后再次编译程序:
clear && cd /home/username/ics2022/nexus-am/tests/cputest && make clean && make ARCH=x86-nemu ALL=dummy run
- 成功执行 ret 函数
- 提示
0xff 指令,即pushl 指令没有实现
pushl 指令
在i386手册第414页找到0xff指令,发现是Indirct Grp5 。查看第416页,找到第五个指令组,发现pushl指令在第7个位置。所以在src/cpu/exec/exec.c 中修改指令组第七个元素:
make_group(gp5,
EMPTY, EMPTY, EMPTY, EMPTY,
EMPTY, EMPTY, EX(push), EMPTY)
- 指令可以确定字长用EXW,否则用EX
- 不需要译码函数
保存后再次编译程序:
clear && cd /home/username/ics2022/nexus-am/tests/cputest && make clean && make ARCH=x86-nemu ALL=dummy run
成功运? dummy
实现 Diff-test
根据讲义,在include/common.h 中删掉#define DIFF_TEST 注释。在src/monitor/diff-test/diff-test.c 中补全寄存器对比测试代码:
if (r.eip != cpu.eip || r.eax != cpu.eax || r.ebx != cpu.ebx || r.ecx != cpu.ecx || \
r.edx != cpu.edx || r.esp != cpu.esp || r.ebp != cpu.ebp || r.esi != cpu.esi || r.edi != cpu.edi){
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.esp:%#x, cpu.esp:%#x\n", r.esp, cpu.esp);
printf("r.ebp:%#x, cpu.ebp:%#x\n", r.ebp, cpu.ebp);
printf("r.esi:%#x, cpu.esi:%#x\n", r.esi, cpu.esi);
printf("r.edi:%#x, cpu.edi:%#x\n", r.edi, cpu.edi);
}
if (diff) {
nemu_state = NEMU_END;
}
保存后再次编译程序:
clear && cd /home/username/ics2022/nexus-am/tests/cputest && make clean && make ARCH=x86-nemu ALL=dummy run
- 在开启 diff-test 的条件下顺利运行得到
HIT GOOD TRAP 的结果
遇到的问题及解决办法
-
遇到问题:ret 返回地址总是出错。 解决方案:调试发现pop指令里面的pop写成了push。
实验心得
经过本次的实验,我学会了编写底层的逻辑代码,理清了计算机执行指令的过程,自己实践写了几个指令。其中遇到了各种问题,不过我都解决了,很开心。
References
指令的执行流程
南京大学 计算机系统基础 课程实验 2018(PA2)
指令表
coder32 edition | X86 Opcode and Instruction Reference 1.12
pa2.1
PA2.1 - 代码天地
|