ics-pa

好久好久之前就想做的ics-pa,在毕业之前一定可以尝试一下!那么就从这个寒假开始吧
感谢南大

但是本篇记录的不是很完整,比较零碎

preparations

需要安装一个ubuntu22.04,算了,新装一个虚拟机吧。

vim相对行之间的跳转

1
使用比如 7j 和 7k 向下或者向上移动 7 行

PA1

RTFSC

(这个东西的意思时read the fxxk source code的意思,我还以为是什么基础设施的意思)

这个里面提到,我们的NEMU啊,是一个用来运行用户代码的程序,但是把客户代码读入NEMU,是通过所谓的monitor完成的。于是我们可以看看NEMU的monitor/monitor.c的源代码。下面是一些例子

  • rand.c。位于/nemu/src/utils/rand.c下
1
2
3
4
5
6
7
#include <common.h>                             
16 #ifndef CONFIG_TARGET_AM
17 #include <time.h>
18 #endif
20 void init_rand() {
21 srand(MUXDEF(CONFIG_TARGET_AM, 0, time(0)));
22 }
  • init_mem()初始化了一块malloc出来的内存,里面存放了一些随机数,并且用这些内容作为所谓的physical memory。位于nemu/src/memory/paddr.c下。

image-20221227161624376

  • ISA。关于ISA其实挺迷惑的。目前还没有说可以在哪里指定写x86或者别的指令集。在官网上也没有找到x86的??算了,就尝试一下riscv32吧,也算是跨越舒适区。在init_isa()中,代码非常简短

image-20221228110847355

根据文档里的说明,这里RESET_VECTOR是monitor直接把客户程序读入到一个固定的内存位置的地方,然后restart让cpu.pc指向这个RESET_VECTOR。这里做了一个memcpy,把一部分指令读入到RESET VECTOR的开头。暂时还不太清楚是什么意思。在官方文档上查到内容如下。

lui: load upper immediate,表示使用无符号数字形式构造一个常量。这里就是t0=0x80000

sw: store word, 注意:sw src, off(dst) => M[dst + off] = src[31:0]。所以这里也就是把0x80000的地方写入0。这里的地址映射是通过paddr.c中guest_to_host实现的。实现的非常简单。其实应该就是做加减之后在mmap出来的一部分内存中使用。

lw: load word。也就是读入a0=0。

ebreak: 可能是一个中断trap。

所以这段代码就是简单的测试了一下内容访问和读取,就转交控制权了。

顺便看一下这个模拟的CPU的格式。对于riscv32,只有一个pc,还有32个word大小的寄存器组合。

image-20221228112011498

之后,NEMU调用load_img()把指定的客户img读入内存,如下图。也是非常简单,相当于二进制读文件到RESET_VECTOR中。

image-20221228112652781

之后就make就行了。期间遇到这个错误

1
Makefile:18: *** NEMU_HOME= is not a NEMU repo.  Stop.

改正方式如下

1
export NEMU_HOME=/home/nicholas/Desktop/ics-pa/nemu

收获一个welcome()的截图

image-20221228114517414

我们来看看,关于这个(nemu)的提示,该怎么解释。根据官网提示,主要在sdb_mainloop()中,输出的hint,以及主循环部分。下面显然是读取标准输入,并解析和分割命令的部分。(但是好像没找到nemu提示符在哪里)

image-20221228114659340

之后如果输入”c”,NEMU开始进入指令执行的主循环cpu_exec()。一共有以下几种命令。

image-20221228121938972

很简单的,看到cpu_exec中,指令的含义。就是调用execute()执行代码,并且获取状态值。

image-20221228122223675

而execute()如下所示

image-20221228122315893

又调用了exec_once()这里调用isa_exec_once()估计才是真正让指令集工作的地方。然后应该是输出了一些状态信息。我们可以调试一下看看。

image-20221228122507451

这边传入-1是因为-1转为uint之后非常大,会让CPU一直执行下去。

这里还发现一个对于调试特别有用的,TUI工具。是gdb自带的。layout split即可使用。可以同时显示源代码和汇编。非常方便。

如果在make menuconfig中打开调试,则zuto.conf中就会出现CONFIG_CC_DEBUG=y

添加exit代码

在cmd_q中加一行输出,看看是怎么调用到cmd_q的。因为下面结构体中传入了函数指针。注意handler结构,是一个函数指针。

image-20221228132041843

而在下面比对的时候,发现找到一个名称一致的,就去把对应的handler拿出来执行,并判断返回值是否小于0。小于0就return。那么是谁调用了sbd_mainloop()呢?答案在nemu/src/engine/interpreter/init.c中

image-20221228132145170

image-20221228132628859

而main()调用engine start.(nemu-main.c)

image-20221228133818479

image-20221228134007776

然后我们只要去改掉NEMU_QUIT就可以了。使用`grep -rn “words” *可以在当前目录下递归搜索文件中的字符串。但是貌似这个只是执行代码的时候才会设置的。因此最好的办法是给engine_start加上一个返回值吧。改几个函数签名就行了。(直接exit(-1))也可以,但是最好不要这样。

image-20221228135632538

基础设施

这里需要实现三个:单步执行、打印寄存器、扫描内存三个调试用API。

注意可以用nemu/include/debug.h中的API进行调试

单步执行

image-20221228141605848

打印寄存器

这个只需要把对应寄存器内容打出来就可以了。主要是去改src/isa/riscv32下的reg.c文件。需要用到两个函数。第一个是把下标转换成名字。对于riscv,一共有32个寄存器。

image-20221228143417178

另一个是gpr(),直接获取了寄存器的值。

由于word_t是uint_16,因此输出内容用到的格式化字符串为hd,或者也可以十六进制打印,hx

1
2
3
4
5
6
7
8
9
10
11
12
#define PRIu8 "hu"
#define PRId8 "hd"
#define PRIx8 "hx"
#define PRIu16 "hu"
#define PRId16 "hd"
#define PRIx16 "hx"
#define PRIu32 "u"
#define PRId32 "d"
#define PRIx32 "x"
#define PRIu64 "llu" // or possibly "lu"
#define PRId64 "lld" // or possibly "ld"
#define PRIx64 "llx" // or possibly "lx"

扫描内存

命令格式如下

image-20221228145609605

首先需要知道内存存在什么位置,应该是上文pmem()。但是为了虚拟地址和真实地址转换,需要通过一些映射。也就是guest_to_hosthost_to_guest

用到两个最重要的交互函数

image-20221228154834007

image-20221228193026436

因此代码如下

image-20221228195745063

功能测试

image-20221228215357311

测试了一下上述三个功能,都还不错。第一部分完结啦

image-20221228195830109

PA2

decode_operand中用到了宏BITSSEXT, 它们均在nemu/include/macro.h中定义, 分别用于位抽取和符号扩展

decode_operand在nemu/src/isa/riscv32/inst.c中

INSTPAT等宏展开在nemu/include/cpu/decode.h中

decode-exec在src/isa/riscv32/inst.c中,但是好像无法正常解析

exec_once在nemu/src/cpu/cpu-exec.c中

inst_fetch在

一条指令在NEMU的执行过程

首先,指令被放在INITIAL_VECTOR中,首先进入execute(cpu-exec.c)里面。execute会调用exec_once函数,之后exec_once会设置PC和snpc,去调用isa_exec_once,并传入一个参数Decode*。

之后到了inst.c的isa_exec_once中,它会调用ifetch从INITIAL_VECTOR取出指令,这其实就是从INITIAL_VECTOR对应的虚拟地址映射到的物理地址上取出四个字节作为指令。之后会调用decode_exec()。在这里,指令将被解码和模拟执行。

使用INSTPAT()宏来模拟CPU对于一个32-bit指令的匹配,格式如下。其中?代表0或者1,其余的是确定的比特。这一部分其实是译码和执行部分加起来。

image-20230102094304705

其中INSTPAT宏的展开在nemu/include/cpu/decode.h中。尝试过gcc -E展开,但是发现效果并不好,遂放弃。实际上INSTPAT的意思就是通过对上述规则的每个字符进行匹配,然后根据RISCV的特点,找到源寄存器,目标寄存器一类的,再用C代码模拟执行这条指令。

image-20230102094622099

image-20230102140007605

image-20230102094549034

然后把保存下来的内容放在key、mask、shift里面。调用decode_operand完成c语言中执行的操作。

image-20230102130209094

我们要做的呢,实际上就是为INSTPAT()添加其他指令的对应匹配过程,并且在decode_operand中加上处理行为即可。使用的测试程序是dummy.c。反编译出来的内容如下。

image-20230102140107148

先对照原先就有的一些指令理解一下下面是来自riscv volumn1的第19页,关于lui指令格式的介绍。对照着NEMU中的格式(这里的空格好奇怪)

image-20230102142345923

image-20230102142255932

可以看到除了最后7个bit是固定的之外,其余都是不确定的。但是最后7个bit结合type指定了指令内容。如下所示

image-20230102193707988

现在尝试加一个addi进去,网上找到一张特别有用的图。地址。因此就按照这个改了。在volumn(1)156页也能找到。这个volumn1和volumn2应该就是指一个是user模式下,另一个是privileged模式。

addi的描述是下面这样的

image-20230102145930587

QQ图片20220821230349

li指令

这个指令上面查不到。在手册中查到如下。

image-20230102153700232

参考:cheetsheet:https://itnext.io/risc-v-instruction-set-cheatsheet-70961b4bbe8

同样的cheetsheet https://mark.theis.site/riscv/

发现真的是查不到这个指令。没办法,跟着网上博主的做法,实现了一个和addi一样的。

auipc

太nb了这个makefile,竟然能显示宏不展开的源文件,一定要研究一下,之前搞了好久。

image-20230102163648907

image-20230102163948116

jal

image-20230102192320315

同时,J也是这样的行为

image-20230103123759768

mv

image-20230102223445959

ret

还是不知道哪里能看伪指令。ret也是伪指令。

image-20230102224520084

image-20230102224458958

beq等条件跳转

image-20230103101241210

image-20230103102448659

image-20230103213239171

add等

image-20230103104955564

image-20230103105009618

image-20230103105138704

mul等

image-20230103144034176

image-20230103143957615

image-20230103154540106

image-20230103143925722

slti

image-20230103124827286

store相关

image-20230103130557076

load相关

image-20230103131257231

slli

image-20230103133159507

andi

image-20230103211342559

image-20230103211746944

p155开始是指令集

rem

这个看起来比较麻烦啊,是什么余数,其实就是mod

image-20230103212626030

image-20230103212554682

image-20230103212506910

sra

手酸

image-20230103221630781

image-20230103221616485

string 和hello string不用测

由于stringhello-str还需要实现额外的内容才能运行(具体在后续小节介绍), 目前可以先使用其它测试用例进行测试.

学习makefile

在monitor.c里面看到有一个batch模式,加上-b即可。于是修改home/nicholas/Desktop/ics-pa/abstract-machine/scripts/platform/nemu.mk(在/home/nicholas/Desktop/ics-pa/abstract-machine/Makefile里面用到了),在args里面加上-b。亲测可以

image-20230104152223300

gcc的所有参数

我个人觉得,RTFM是一种方法,但是碰到GNU的manual,还是算了。这是mit的

https://web.mit.edu/rhel-doc/3/rhel-gcc-en-3/invoking-gcc.html

添加ringbuf

在nemu/src/memory中,有一个out_of_memory函数,感觉在这里添加比较靠谱。同时需要查看一下nemu/src/utils/disasm.cc

还要参考一下exec_once里面对于disassemble的调用

image-20230111105248845

截止2023-1-4完成到PA2的一半,暂缓

文章目录
  1. 1. preparations
  2. 2. PA1
    1. 2.1. RTFSC
      1. 2.1.1. 添加exit代码
    2. 2.2. 基础设施
      1. 2.2.1. 单步执行
      2. 2.2.2. 打印寄存器
      3. 2.2.3. 扫描内存
      4. 2.2.4. 功能测试
  3. 3. PA2
    1. 3.1. 一条指令在NEMU的执行过程
      1. 3.1.1. li指令
      2. 3.1.2. auipc
      3. 3.1.3. jal
      4. 3.1.4. mv
      5. 3.1.5. ret
      6. 3.1.6. beq等条件跳转
      7. 3.1.7. add等
      8. 3.1.8. mul等
      9. 3.1.9. slti
      10. 3.1.10. store相关
      11. 3.1.11. load相关
      12. 3.1.12. slli
      13. 3.1.13. andi
      14. 3.1.14. p155开始是指令集
      15. 3.1.15. rem
      16. 3.1.16. sra
      17. 3.1.17. string 和hello string不用测
    2. 3.2. 学习makefile
    3. 3.3. gcc的所有参数
      1. 3.3.1. 添加ringbuf
|