好久好久之前就想做的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 |
|
- init_mem()初始化了一块malloc出来的内存,里面存放了一些随机数,并且用这些内容作为所谓的physical memory。位于
nemu/src/memory/paddr.c
下。
- ISA。关于ISA其实挺迷惑的。目前还没有说可以在哪里指定写x86或者别的指令集。在官网上也没有找到x86的??算了,就尝试一下riscv32吧,也算是跨越舒适区。在
init_isa()
中,代码非常简短
根据文档里的说明,这里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大小的寄存器组合。
之后,NEMU调用load_img()把指定的客户img读入内存,如下图。也是非常简单,相当于二进制读文件到RESET_VECTOR中。
之后就make就行了。期间遇到这个错误
1 | Makefile:18: *** NEMU_HOME= is not a NEMU repo. Stop. |
改正方式如下
1 | export NEMU_HOME=/home/nicholas/Desktop/ics-pa/nemu |
收获一个welcome()的截图
我们来看看,关于这个(nemu)的提示,该怎么解释。根据官网提示,主要在sdb_mainloop()中,输出的hint,以及主循环部分。下面显然是读取标准输入,并解析和分割命令的部分。(但是好像没找到nemu提示符在哪里)
之后如果输入”c”,NEMU开始进入指令执行的主循环cpu_exec()
。一共有以下几种命令。
很简单的,看到cpu_exec中,指令的含义。就是调用execute()执行代码,并且获取状态值。
而execute()如下所示
又调用了exec_once()这里调用isa_exec_once()估计才是真正让指令集工作的地方。然后应该是输出了一些状态信息。我们可以调试一下看看。
这边传入-1是因为-1转为uint之后非常大,会让CPU一直执行下去。
这里还发现一个对于调试特别有用的,TUI工具。是gdb自带的。layout split
即可使用。可以同时显示源代码和汇编。非常方便。
如果在make menuconfig中打开调试,则zuto.conf中就会出现CONFIG_CC_DEBUG=y
添加exit代码
在cmd_q中加一行输出,看看是怎么调用到cmd_q的。因为下面结构体中传入了函数指针。注意handler结构,是一个函数指针。
而在下面比对的时候,发现找到一个名称一致的,就去把对应的handler拿出来执行,并判断返回值是否小于0。小于0就return。那么是谁调用了sbd_mainloop()呢?答案在nemu/src/engine/interpreter/init.c中
而main()调用engine start.(nemu-main.c)
然后我们只要去改掉NEMU_QUIT就可以了。使用`grep -rn “words” *可以在当前目录下递归搜索文件中的字符串。但是貌似这个只是执行代码的时候才会设置的。因此最好的办法是给engine_start加上一个返回值吧。改几个函数签名就行了。(直接exit(-1))也可以,但是最好不要这样。
基础设施
这里需要实现三个:单步执行、打印寄存器、扫描内存三个调试用API。
注意可以用nemu/include/debug.h中的API进行调试
单步执行
打印寄存器
这个只需要把对应寄存器内容打出来就可以了。主要是去改src/isa/riscv32下的reg.c文件。需要用到两个函数。第一个是把下标转换成名字。对于riscv,一共有32个寄存器。
另一个是gpr(),直接获取了寄存器的值。
由于word_t是uint_16,因此输出内容用到的格式化字符串为hd,或者也可以十六进制打印,hx
1 |
扫描内存
命令格式如下
首先需要知道内存存在什么位置,应该是上文pmem()。但是为了虚拟地址和真实地址转换,需要通过一些映射。也就是guest_to_host
和host_to_guest
。
用到两个最重要的交互函数
因此代码如下
功能测试
测试了一下上述三个功能,都还不错。第一部分完结啦
PA2
decode_operand
中用到了宏BITS
和SEXT
, 它们均在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,其余的是确定的比特。这一部分其实是译码和执行部分加起来。
其中INSTPAT宏的展开在nemu/include/cpu/decode.h中。尝试过gcc -E展开,但是发现效果并不好,遂放弃。实际上INSTPAT的意思就是通过对上述规则的每个字符进行匹配,然后根据RISCV的特点,找到源寄存器,目标寄存器一类的,再用C代码模拟执行这条指令。
然后把保存下来的内容放在key、mask、shift里面。调用decode_operand完成c语言中执行的操作。
我们要做的呢,实际上就是为INSTPAT()添加其他指令的对应匹配过程,并且在decode_operand中加上处理行为即可。使用的测试程序是dummy.c。反编译出来的内容如下。
先对照原先就有的一些指令理解一下下面是来自riscv volumn1的第19页,关于lui指令格式的介绍。对照着NEMU中的格式(这里的空格好奇怪)
可以看到除了最后7个bit是固定的之外,其余都是不确定的。但是最后7个bit结合type指定了指令内容。如下所示
现在尝试加一个addi进去,网上找到一张特别有用的图。地址。因此就按照这个改了。在volumn(1)156页也能找到。这个volumn1和volumn2应该就是指一个是user模式下,另一个是privileged模式。
addi的描述是下面这样的
li指令
这个指令上面查不到。在手册中查到如下。
参考:cheetsheet:https://itnext.io/risc-v-instruction-set-cheatsheet-70961b4bbe8
同样的cheetsheet https://mark.theis.site/riscv/
发现真的是查不到这个指令。没办法,跟着网上博主的做法,实现了一个和addi一样的。
auipc
太nb了这个makefile,竟然能显示宏不展开的源文件,一定要研究一下,之前搞了好久。
jal
同时,J也是这样的行为
mv
ret
还是不知道哪里能看伪指令。ret也是伪指令。
beq等条件跳转
add等
mul等
slti
store相关
load相关
slli
andi
p155开始是指令集
rem
这个看起来比较麻烦啊,是什么余数,其实就是mod
sra
手酸
string 和hello string不用测
由于string
和hello-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。亲测可以
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的调用
截止2023-1-4完成到PA2的一半,暂缓