##讲义中提到的问题:立即数背后的故事
Motorola 68k系列的处理器都是大端架构的, 现在问题来了, 考虑以下两种情况:
- 假设我们需要将NEMU运行在Motorola 68k的机器上(把NEMU的源代码编译成Motorola 68k的机器码)
- 假设我们需要编写一个新的模拟器NEMU-Motorola-68k, 模拟器本身运行在x86架构中, 但它模拟的是Motorola 68k程序的执行
在这两种情况下, 你需要注意些什么问题? 为什么会产生这些问题? 怎么解决它们?
###答案
- 需要注意的问题:大小端的兼容问题
- 产生这个问题的原因:大端和小端机器的字节码存放顺序(原则)是相反的
- 解决方案: 写一个专门处理大小端字节转换的函数
##实现新的指令
###在opcode_table
中填写正确的译码函数,执行函数和操作数宽度
这里主要通过查阅i386手册P414页,查看指令所对应的地址以及译码函数,或者查看反汇编文件dummy-x86-nemu.txt
来查看指令对应地址,主要修改两部分:
####make_group
/* 0x80, 0x81, 0x83 */
make_group(gp1,
EMPTY, EMPTY, EMPTY, EMPTY,
EMPTY, EXW(sub,1), EMPTY, EMPTY)
####opcode_entry opcode_table [512]
/* 0x30 */ IDEX(G2E, xor), IDEX(G2E, xor), IDEX(E2G, xor), IDEX(E2G, xor),
/* 0x34 */ IDEX(I2a, xor), IDEX(I2r, xor), EMPTY, EMPTY,
/* 0x50 */ IDEXW(r,push,4), IDEXW(r,push,4), IDEXW(r,push,4), IDEXW(r,push,4),
/* 0x54 */ IDEXW(r,push,4), IDEXW(r,push,4), IDEXW(r,push,4), IDEXW(r,push,4),
/* 0x58 */ IDEXW(r,pop,4), IDEXW(r,pop,4), IDEXW(r,pop,4), IDEXW(r,pop,4),
/* 0x5c */ IDEXW(r,pop,4), IDEXW(r,pop,4), IDEXW(r,pop,4), IDEXW(r,pop,4),
/* 0xc0 */ IDEXW(gp2_Ib2E, gp2, 1), IDEX(gp2_Ib2E, gp2), EMPTY, EX(ret),
/* 0xe8 */ IDEX(I, call), EMPTY, EMPTY, EMPTY,
###使用RTL实现正确的执行函数
首先进入rtl.h
,需要实现的框架函数都已经给出,并且内部都有注释,只需要根据注释填充对应内容即可,一些读取和写入操作需要使用rtl.h
内部已经定义写好的rtl
函数,具体实现如下:
####将传入的形参赋给cpu结构体中的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;
}
利用rtl_subi和rtl_sm实现减法和寄存器写入
static inline void rtl_push(const rtlreg_t* src1) {
// esp <- esp - 4
// M[esp] <- src1
rtl_subi(&cpu.esp, &cpu.esp, 4);
rtl_sm(&cpu.esp, 4, src1);
}
####利用rtl_addi和rtl_lm实现加法和寄存器读取
static inline void rtl_pop(rtlreg_t* dest) {
// dest <- M[esp]
// esp <- esp + 4
rtl_lm(dest, &cpu.esp, 4);
rtl_addi(&cpu.esp, &cpu.esp, 4);
}
####三元表达式判断src1的值然后赋值给dest
static inline void rtl_eq0(rtlreg_t* dest, const rtlreg_t* src1) {
// dest <- (src1 == 0 ? 1 : 0)
*dest = *src1 == 0 ? 1 : 0;
}
// 用定义好的rtl_li读入立即数赋值给dest
static inline void rtl_msb(rtlreg_t* dest, const rtlreg_t* src1, int width) {
// dest <- src1[width * 8 - 1]
rtl_li(dest, src1[width * 8 - 1]);
}
####利用rtl_eq0赋值给&t1,然后用写好的定义来设置ZF位
static inline void rtl_update_ZF(const rtlreg_t* result, int width) {
// eflags.ZF <- is_zero(result[width * 8 - 1 .. 0])
rtl_eq0(&t1, result);
rtl_set_ZF(&t1);
}
####利用rtl_msb赋值给&t1,然后用写好的定义来设置SF位
static inline void rtl_update_SF(const rtlreg_t* result, int width) {
// eflags.SF <- is_sign(result[width * 8 - 1 .. 0])
rtl_msb(&t1, result, width);
rtl_set_SF(&t1);
}
##运行第一个C程序
按照讲义的提示首先执行:
make ARCH=x86-nemu ALL=dummy run
执行完之后可以直接进入nemu,然后执行c可以看到类似讲义上的提示,相关截图如下:
###遇到的问题
####找不到头文件
报错信息如下:
/usr/include/features.h:367:25: fatal error: sys/cdefs.h: 没有那个文件或目录
分析了一下缺少的头文件路径在user/include下,说明不是pa代码缺少的问题,那只可能是系统缺少相应的c库,google找到cdefs.h所属的包为gcc-multilib,命令行安装即可:
####找不到Makefile文件
报错信息如下:
这个error很常见,一般十有八九是环境变量的问题,按照提示的error路径找到Makefile文件找到其用到的环境变量然后命令行无脑导入即可:
###完成代码
首先在nemu/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(pop);
make_EHelper(xor);
make_EHelper(ret);
在nemu/sec/cpu/exec/exec.c中实现:
####call
-
代码所在位置:nemu/sec/cpu/exec/control.c
-
实现思路:读取要压栈的
eip
值——>rtl_push
进栈——>更新jmp_eip
——>设置decoding.is_jmp
为1
进行跳转make_EHelper(call) {
// the target address is calculated at the decode stage
rtl_push(eip);
rtl_addi(&decoding.jmp_eip, eip, id_dest->val);
decoding.is_jmp = 1;
print_asm(“call %x”, decoding.jmp_eip);
}
####push&pop
-
代码所在位置:nemu/src/cpu/exec/data-mov.c
-
实现思路:
push
和pop
利用rtl
函数完成,使用operand_write
将值写回:make_EHelper(push) {
rtl_push(&t2);
operand_write(id_dest, &t2);
print_asm_template1(push);
}make_EHelper(pop) {
rtl_pop(&t2);
operand_write(id_dest, &t2);
print_asm_template1(pop);
}
####sub -
代码所在位置:nemu/src/cpu/exec/arith.c
-
实现思路:在实现
sub
之前根据讲义给出的结构图完成EFLAGS
寄存器,编辑 nemu/include/cpu/reg.h中的CPU_state增加如下代码:union {
uint32_t val;
struct {
uint32_t CF:1;
unsigned : 5;
uint32_t ZF:1;
uint32_t SF:1;
unsigned : 1;
uint32_t IF:1;
unsigned : 1;
uint32_t OF:1;
unsigned:20;
};
}eflags;
定义之后在src/monitor/monitor.c
中的restart
中依据I386手册要求赋初值0x00000002
:
static inline void restart() {
/* Set the initial instruction pointer. */
cpu.eip = ENTRY_START;
/* Set the initial EFLAGS */
cpu.eflags.val = 0x00000002;
#ifdef DIFF_TEST
init_qemu_reg();
#endif
}
实现sub:
make_EHelper(sub) {
rtl_sub(&t2, &id_dest->val, &id_src->val);
rtl_sltu(&t3, &id_dest->val, &t2);
operand_write(id_dest, &t2);
rtl_update_ZFSF(&t2, id_dest->width);
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);
}
####xor
-
代码所在位置:nemu/src/cpu/exec/logic.c
-
实现思路:
xor
指令通过查询i386手册明白是对src
和dest
通过rtl_xor
函数进行异或运算并写入操作数,用rtl_set_
函数将标志位设为0
:make_EHelper(xor) {
rtl_xor(&t2, &id_dest->val, &id_src->val);
operand_write(id_dest, &t2);
rtl_set_CF(&tzero);
rtl_set_OF(&tzero);
print_asm_template2(xor);
}
####ret
-
代码所在路径:nemu/src/cpu/exec/control.c
-
实现思路: 从栈顶弹出
eip
然后设置跳转即可make_EHelper(ret) {
rtl_pop(&decoding.jmp_eip);
decoding.is_jmp = 1;
print_asm(“ret”);
}
然后大概代码就完成了,但是运行时又遇到了以下错误:
意思是说static inline make_DopHelper(SI)
这个函数没实现,进去代码文件按照提示实现即可:
static inline make_DopHelper(SI) {
assert(op->width == 1 || op->width == 4);
op->type = OP_TYPE_IMM;
/* TODO: Use instr_fetch() to read `op->width' bytes of memory
* pointed by `eip'. Interpret the result as a signed immediate,
* and assign it to op->simm.
*
op->simm = ???
*/
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
}
然后就可以正确执行:
##git log截图