Nemu PA2

本文是 IS 2016课程的homework笔记,详情请参考课程主页和本人github主页

  • cpu_exec是一个循环执行exec(eip)函数,execmake_helper宏定义
// nemu/src/cpu/exec/exec.c
make_helper(exec) {
    ops_decoded.opcode = instr_fetch(eip, 1);
    return opcode_table[ ops_decoded.opcode ](eip);
}

// nemu/include/cpu/helper.h
/* All function defined with 'make_helper' return the length of the operation. */
#define make_helper(name) int name(swaddr_t eip)
  • exec调用的是opcode_table对当前eip指向的指令进行dispatch,table和ops_decoded的定义为
// nemu/include/cpu/decode/operand.h
typedef struct {
    uint32_t type;
    size_t size;
    union {
        uint32_t reg;
        swaddr_t addr;
        uint32_t imm;
        int32_t simm;
    };
    uint32_t val;
    char str[OP_STR_SIZE];
} Operand;

typedef struct {
    uint32_t opcode;
    bool is_operand_size_16;
    Operand src, dest, src2;
} Operands;

// decode.c
Operands ops_decoded;
  • 66这个prefix决定了mov指令立即数的长度,在opcode_table里边将其分发到函数operand_size,该函数将全局变量的域is_operand_size_16置位,然后再按照正常的逻辑执行exec函数,不过返回值加1字节。
make_helper(operand_size) {
    ops_decoded.is_operand_size_16 = true;
    int instr_len = exec(eip + 1);
    ops_decoded.is_operand_size_16 = false;
    return instr_len + 1;
}
  • 比如找到的是mov_i2r_v,那么它用make_helper声明,用make_helper_v定义,v表示是变长的操作数,可能是16/32位操作数。译码是通过operand的size来决定不同的实现的,没有66的话就是32bit的。
// nemu/src/cpu/exec/data-mov/mov.c
/* for instruction encoding overloading */
make_helper_v(mov_i2r)
make_helper_v(mov_i2rm)
make_helper_v(mov_r2rm)
make_helper_v(mov_rm2r)
make_helper_v(mov_a2moffs)
make_helper_v(mov_moffs2a)

#define make_helper_v(name) \\
    make_helper(concat(name, _v)) { \\
        return (ops_decoded.is_operand_size_16 ? concat(name, _w) : concat(name, _l)) (eip); \\
    }

  • 后面的mov_i2r_wmov_i2r_l是模板,定义在mov.c里边,用用模板引擎多次调用make_instr_helper实现。这里有四层的模板:
  • 最顶层的模板是template-start.htemplate-end.h,作用是提供操作数的类型和后缀名,用DATA_BYTE的大小决定。
  • 中间层是mov-template.h,提供move指令的指令名等信息,这里将instr定义为mov
  • 组合层在exec/helper.h中,用make_instr_helper实现,这里用到了上层提供的类型,后缀名和指令名。组合层需要两个函数指针“decode_type_suffix"和"do_type_suffix"。
  • 最后是实现层实现具体的解码和执行函数。

例如,mov_i2r_v的实现在mov.c中,在mov.c里边就有3次"调用mov-template.h,三次调用传递了不同的数据类型和后缀名,mov-template中将instr宏定义为mov。然后一次实现mov_i2r_wmov_i2r_l,这里边mov是instr,w/l是后缀,i2r是type。他们使使用了idex,该函数需要参数decode_i2r_wdo_i2r_w

// nemu/src/cpu/exec/data-move/move.c
#define DATA_BYTE 1
#include "mov-template.h"
#undef DATA_BYTE

#define DATA_BYTE 2
#include "mov-template.h" // mov_xxx_w
#undef DATA_BYTE

#define DATA_BYTE 4
#include "mov-template.h"
#undef DATA_BYTE

// nemu/include/cpu/exec/template-start.h
#if DATA_BYTE == 1

#define SUFFIX b
#define DATA_TYPE uint8_t
#define DATA_TYPE_S int8_t

#elif DATA_BYTE == 2

#define SUFFIX w
#define DATA_TYPE uint16_t
#define DATA_TYPE_S int16_t

#elif DATA_BYTE == 4

#define SUFFIX l
#define DATA_TYPE uint32_t
#define DATA_TYPE_S int32_t

#else

#error unknown DATA_BYTE

#endif


// nemu/src/cpu/exec/data-move/move-template.h
#define instr mov

make_instr_helper(i2r)
make_instr_helper(i2rm)
make_instr_helper(r2rm)
make_instr_helper(rm2r)

// nemu/include/cpu/exec/helper.h
#define make_instr_helper(type) \\
    make_helper(concat5(instr, _, type, _, SUFFIX)) { \\
        return idex(eip, concat4(decode_, type, _, SUFFIX), do_execute); \\
    }

// nemu/include/cpu/helper.h
/* Instruction Decode and EXecute */
static inline int idex(swaddr_t eip, int (*decode)(swaddr_t), void (*execute) (void)) {
    /* eip is pointing to the opcode */
    int len = decode(eip + 1);
    execute();
    return len + 1; // "1" for opcode
}

  • 传递给idex的函数有两个,一个是decode,一个是execute.

  • 这里的execute部分特别隐晦,在mov.c里边定义了一个do_execute的函数,在之前的头文件里边又定义了一个do_execute的宏,编译时,do_execute进行了文本替换,比如替换成了do_mov_b等等,这样才有了实际的执行体。

// exec/helper.h
#define do_execute concat4(do_, instr, _, SUFFIX)

// mov-template.h
static void do_execute() {
    OPERAND_W(op_dest, op_src->val);
    print_asm_template2();
}

// template-start.h
#define OPERAND_W(op, src) concat(write_operand_, SUFFIX) (op, src)

// exec/helper.h
#define print_asm_template2() \
    print_asm(str(instr) str(SUFFIX) " %s,%s", op_src->str, op_dest->str)

  • write_operand_xx的实现在decode里边,如果操作类型是寄存器就写到目的寄存器,否则就写到内存里边。
void concat(write_operand_, SUFFIX) (Operand *op, DATA_TYPE src) {
    if(op->type == OP_TYPE_REG) { REG(op->reg) = src; }
    else if(op->type == OP_TYPE_MEM) { swaddr_write(op->addr, op->size, src); }
    else { assert(0); }
}

  • print_asm_template2()。

  • 如果nemu是用面向对象的方法写可能会清晰很多,这里的各种奇奇怪怪的宏替代if/switch,出错调试起来各种乱七八糟的提示,感觉设计过度了。

  • 关于decode_type_suffix这个在decode模块无疑,看一下mov的实现decode_i2r_w

// decode-template.h
/* XX <- Ib 
 * eXX <- Iv 
 */
make_helper(concat(decode_i2r_, SUFFIX)) {
    decode_r_internal(eip, op_dest);
    return decode_i(eip);
}

#define decode_r_internal concat3(decode_r_, SUFFIX, _internal)

/* eXX: eAX, eCX, eDX, eBX, eSP, eBP, eSI, eDI */
static int concat3(decode_r_, SUFFIX, _internal) (swaddr_t eip, Operand *op) {
    op->type = OP_TYPE_REG;
    op->reg = ops_decoded.opcode & 0x7;
    op->val = REG(op->reg);

#ifdef DEBUG
    snprintf(op->str, OP_STR_SIZE, "%%%s", REG_NAME(op->reg));
#endif
    return 0;
}

  • 这里写反了,先写decode再写execute比较好

实现一条call指令

  1. 查找call rel32对应的opcode为e8,在exec.c里边的opcode_table里边增加一个条目,记为call_i_v

  2. 声明1中的函数,在cpu/exec目录里边创建call目录,该目录下依次创建三个文件:call.h/call.c/call-template.h;在all-instrs.h目录里边增加"call/call.h"头文件。

// call.h
#include "cpu/helper.h"

make_helper(call_i_v);

// call.c
#include "cpu/exec/helper.h"

#define DATA_BYTE 2
#include "calll-template.h"
#endif

#define DATA_BYTE 4
#include "call-template.h"
#endif

make_helpr_v(call_i)

// call-template.h
#include "cpu/exec/template-start.h"

#define instr call

static void do_execute() {
         // push(ip)
         // read operand address
         // eip += relative address
}

make_instr_helper(i)

#include "cpu/exec/template-end.h"
  1. 创建decode函数,在decode/decode.h,这里应该能够直接使用decode_i_wdecode_i_l,这里src->type为立即数,数据在imm里边
make_helper(concat(decode_i_, SUFFIX)) {
    /* eip here is pointing to the immediate */
    op_src->type = OP_TYPE_IMM;
    op_src->imm = instr_fetch(eip, DATA_BYTE);
    op_src->val = op_src->imm;

#ifdef DEBUG
    snprintf(op_src->str, OP_STR_SIZE, "$0x%x", op_src->imm);
#endif
    return DATA_BYTE;
}

  1. 替换exec table中的inv为call_i_v即可

  2. 使用decode_i_w各种不适,因为call的操作数不是立即数?注意到call opcode的后面跟的是cw/cd,试着将i改为c,增加新的decode_c看看。

// decode.h
make_helper(decode_c_w);
make_helper(decode_c_l);

// operand.h
enum { OP_TYPE_REG, OP_TYPE_MEM, OP_TYPE_IMM, OP_TYPE_CALL };

// decode-template.h
/* cw/cd */
make_helper(concat(decode_c_, SUFFIX)) {
    op_src->type = OP_TYPE_CALL;
    op_src->val = instr_fetch(eip, DATA_BYTE);
#ifdef DEBUG
    sprintf(op_src->str, OP_STR_SIZE, "", op_src->val);
#endif
    return DATA_BYTE;
}

实现push指令

  • done, nothing special

实现test指令

  • done

实现je指令

  • done
    int32_t tmp = op_dest->val - op_src->val;
    cpu.AF = 0;
    cpu.OF = 0;
    cpu.CF = 0;
    cpu.SF = tmp & 0x7fffffff;
    cpu.ZF = tmp == 0 ? 1 : 0;
    cpu.PF = parity(tmp & 0xff);
  • push/pop/ret

  • 问题出在了sub指令,他的描述是

83 /5 ib SUB r/m16,imm8 2/7 Subtract sign-extended immediate byte from r/m word
83 /5 ib SUB r/m32,imm8 2/7 Subtract sign-extended immediate byte from r/m dword

它的目的操作数和源操作数长度不一致,计算的时候把源操作数要sign extended,但是问题是,框架中要保持目的和源操作数的类型一致。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容