进行PA的正确姿势(鸡汤)
- 多思考为什么
- 从问题开始着手理解系统也是个不错的方法
- 独立解决问题
- 即使是调一个很弱智的bug, "顺带"能学到的东西也比你想象中多得多
- 换句话说, 如果你选择抱大腿, 你失去的机会也比你想象中多得多
- 尝试尽可能理解每一处细节
- 将来调bug的时候, 这些细节就是你手中强有力的工具
- 换句话说, 当你在调bug的时候感到无从下手, 一定是你不了解其中的细节
- 用正确的工具做事情
- 这才是节省时间的科学方法, 而不是偷懒
- 多读讲义, 彩蛋很多
- 讲义中特地设置了不少"不合时宜"的提示, 有的彩蛋要多次阅读才能明白其中的奥妙
- 多看一道蓝框题, 也许能少调几天bug
- 按时完成, 拒绝拖延
- 这样S你才有时间做到上面几点
make多线程编译文件
- 使用
lscpu
查看cpu的个数;
- 在运行make时添加
-j?
参数,其中?
为查询到的cpu的数量,如果是n,则可以创建n个进程;
ccache帮助提高编译速度
- 在
PATH
变量的前面添加/usr/lib/ccaceh
;
- 使用
which gcc
发现输出/usr/lib/ccache/gcc
;
看待程序的两个角度
- 从源码的角度;
- 从状态机的角度;
了解配置系统kconfig
目前只需要关心两点即可:
- 根据配置,生成文件
nemu/include/generated/autoconf.h
,用于定义C宏;
- 根据配置,生成文件
nemu/include/config/auto.conf
,用于定义makefile变量;
看懂makefile文件
- 利用
make -nB
实现只输出命令但不执行的效果,根据输出的字符串,反推一些变量的值;
nemu-main.c
存在一个检测宏
CONFIG_TARGET_AM
的条件编译,该宏的含义是什么?init_monitor
考虑没有定义该宏的情况,剖析函数
init_monitor(argc, argv);
:该函数执行了多个函数:
- parse_args(argc, argv):该函数起到一个解析命令行参数的作用,可以先暂时不用关注;
- 实例化了一个
struct option数组
;
2023.11.17
- while循环条件中调用了函数getopt_long():用于解析命令行参数;
11.18
- init_rand():播种随机数;
- void init_log(const char *log_file): 实现了一个log打印的功能,变量log_fp为目的地,默认为stdout,否则为log_file指定的文件指针;其中
FILE *log_fp = NULL;
在log.c中被定义;
- void init_mem():内存初始化,分配一段内存,如果定义了某个宏,则对其进行随机初始化并最终输出内存的范围:
- void init_isa() : 初始化isa相关:
- load_img(): Load the image to memory.
- init_difftest();
- init_sdb();
第一项工作,将客户程序读入到内存当中
- 客户程序是什么:内置在nemu/src/isa/$ISA/init.c文件中的一段ricv-V指令;
- 内存如何模拟:使用一个uint8_t类型的数组进行模拟,其定义在nemu/src/memory/paddr.c中;
- 将客户程序读入到内存中的什么位置?约定让monitor直接将程序读入到一个固定的位置: RESET_VECTOR。其值在nemu/include/memory/paddr.h中定义;
- 对RESET_VECTOR的定义:
#define RESET_VECTOR (PMEM_LEFT + CONFIG_PC_RESET_OFFSET)
- 对PMEM_LEFT的定义:
#define PMEM_LEFT ((paddr_t)CONFIG_MBASE)
- 对CONFIG_MBASE的定义如下:
#define CONFIG_MBASE 0x8000000
- 对CONFIG_PC_RESET_OFFSET的定义如下:
#define CONFIG_PC_RESET_OFFSET 0x0
11.19
初始化完毕后内存布局如下:
PS:什么是镜像文件?disk image
11.19
engine_start
如果定义了
CONFIG_TARGET_AM
变量,则会执行cpu_exec(-1);
否则执行sdb_mainloop();
函数;- sdb_mainloop()函数:
- 首先判断是否为批处理模式
- 其次,进入一个for循环,并且每次for循环都会先获取一个str字符串,如果不是NULL则对其进行处理,否则退出;
- 在执行q命令时,makefile会报错,猜测可能时程序检测异常退出;
- 在执行cmd_q时,改变nemu_state.state = NEMU_QUIT,问题解决;
PS: paddr代表物理地址 vaddr代表虚拟地址
min 11.19
engine_start
如果定义了
CONFIG_TARGET_AM
变量,则会执行cpu_exec(-1);
否则执行sdb_mainloop();
函数;- sdb_mainloop()函数:
- 首先判断是否为批处理模式
- 其次,进入一个for循环,并且每次for循环都会先获取一个str字符串,如果不是NULL则对其进行处理,否则退出;
- 在执行q命令时,makefile会报错,猜测可能时程序检测异常退出;
- 在执行cmd_q时,改变nemu_state.state = NEMU_QUIT,问题解决;
PS: paddr代表物理地址 vaddr代表虚拟地址
11.19
cpu_exec
函数原型:
- 第一行是:
g_print_step = (n < MAX_INST_TO_PRINT);
- 首先检验nemu_state.state的状态:
- 如果是NEMU_END、NEMU_ABORT:打印说明程序已经执行完毕,并退出该函数;
- 否则,令
nemu_state.state = NEMU_RUNNING
,然后模拟cpu运行; - uint64_t timer_start = get_time();应该是获取开始时间;
- execute(n);
- 该函数接受了一个uint64_t类型的参数n,是什么意思?n是程序指令的条数;
- 为了弄清楚上个问题,先查看execute()函数的意思;
execute
- 声明Decode类型的变量s;
- 随后以n作为循环索引变量进行循环,可以猜测n是指令的条数;
- 随后调用了函数exec_once执行一条指令;
- 随后对全局变量g_nr_guest_inst加一;
- 调用trace_and_difftest函数;
exec_once
该函数就涵盖了一条指令完整的执行过程;
- 好像需要利用Decode类型的变量s进行译码;先查看Decode;
- Decode类型:
- 将 s中的pc, snpc都 赋值为传入的参数pc,随后调用isa_exec_once(s), 然后将cpu.pc更新为
s->dnpc
;
- 先不管对
s->logbuff
变量的处理,查看isa_exec_once函数的用法;
- CONFIG_ITRACE条件编译代码:
- 将
s->logbuf
赋给p; - 查看snprintf函数原型:
int snprintf(char *str, size_t size, const char *format, ...);
将格式化字符创写入str中; - 明白了,p仅仅是一个索引,将当前执行的pc值格式化打印到logbuf中,再将所执行的命令逐个字节打印到logbuf中;
- 添加一个空格,再最后添加一个\0方便打印输出;
11.21
isa_exec_once
这个函数好简单:取指令,并返回decode_exec(s)的值;
inst_fetch
vaddr_ifetch
paddr_read
likely
先放放,应该是一种条件判断;
in_pmem
PS: 妈的这些的代码真逆天啊,日,今天就到此为止吧11.20
11.19
decode_exec
该函数的功能是译码并执行;
这个还是比较复杂的,它牵扯到很多的宏定义和其他函数的调用,共同实现了译码的功能,要想看懂真的好难啊感觉,打开指令集手册,开干;
- 声明了rd、src1、src2、imm来作为源寄存器地址、目的寄存器地址以及立即数;
- 随后进行模式匹配!
- 调用了INSTPAT_START()宏,该宏有些奇怪之处:
- 调用了多个INSTPAT宏:
- 执行U类型代码时使用了gpr()宏如下(为什么用小写啊!!!!)
- 基本就是一个译码+执行的过程,暂且一放吧;
代码:
trace_and_difftest
将当前执行了的指令的logbuf打印:
基础设施:简易调试器
11.19
common.h
- stdint.h;
stdint.h是C标准函数库中的头文件,定义了具有特定位宽的整型,以及对应的宏;还列出了在其他标准头文件中定义的整型的极限。
- inttypes.h;
宏定义语法
- #define MAC(x,y) x##y: 将x与y两个字符串连接起来;
- 带多个参数的宏
...
与__VA_ARGS__;
- 基本理解了macro.h文件中MUXDEF、IFDEF等宏的实现原理
11.20
函数cmd_help(sdb.c)
char *arg = strtok(NULL, " ");
这一句话难道不会报段错误吗?
- 对于上述问题,我又查了以下strtok函数的具体用法:
11.20
cmd_c
执行了cpu_exec(-1);
跑去查看该函数;
- 这里的-1被解释为无符号数,因此非常大,所以可以执行很多指令,直至cpu trap;
11.20
11.21 不会用gdb,光眼瞪法实在是太难受了!!!!!根本不知道nemu_state.state的状态是在哪里被改变的啊啊啊!!!!我要恶心坏了!!!!!
通过使用gdb进行调试,终于明白了原来nemu_state是被指令所改变的,在decode_exec函数中!!!!
关于INSTPAT_START宏的解释如下

实现单步执行
这个好容易
实现打印寄存器的值
更容易了
扫描内存
只要实现了将一个十六进制数字符串转换为整数,就可以了
11.21
研究pmem的特点:
- 调用虚拟内存访问接口函数,即可访问静态变量pmem;
成功实现!!!
11.21
表达式求值
学会使用正则表达式识别token
- 要正确的识别token:
- 必须要有一个正确的正则表达式与其相匹配;
- 要有一个数据类型代表这个token;
- 以上两条就构成了一条规则,用于匹配识别所有token;
- init_regex():该函数在模拟器初始化时会将所有的规则编译为内部的pattern匹配信息;
init_regex
- 查看regcomp函数的用法:
Token结构体
该结构体用于记录每一个token的信息,空格串不需要记录;
make_token
该函数用于将表达式中的所有token识别、存储起来;
11.22
1.regex等调用、正则表达式元字符
实现算数表达式的词法分析
成功实现!

11.23
实现递归求值
- 首先更改了上一问题的bug,将空格字符丢弃;
- 尝试实现递归求值,未果。
11.24
解决check_parentheses函数括号匹配的bug,成功实现!
11.24
实现表达式生成器,并对自己的代码进行测试
gen-expr.c
- 播种随机数生成函数;
- 将第二个参数赋值为一个整数,作为获取测试样例的个数;
- 开始循环,逐个生成测试样例:
- 调用gen_rand_expr()获取生成的测试样例到buf中;
- 将buf输入到code_buf中;
- 打开/tmp/.code.c文件到fp;
- 将code_buf中的内容输出到fp中,关闭fp;
- 调用system执行gcc编译.code.c文件,这里利用system的返回值进行了判断;
- 打开/tmp/.expr文件,读入其中的内容到fp中;
- 将fp中的内容输入到result变量当中,关闭fp;
- 打印result和buf变量;
11.25基本实现表达式生成和自动求值
尝试解决表达式过长导致buf溢出的问题
初步的想法是,当检测到buf溢出的时候,直接舍弃掉当前表达式。
- 在main函数中,会判断system调用的gcc命令的返回状态,如果不为0,即编译失败,则直接continue,并不会基础处理该结果;
- 因此,只要检测表达式是否溢出就可以了,可以在判断buf满后不再做任何事情;
尝试解决分母为0的bug问题
这个bug解决了一天,都没有解决掉!!!
- 在计算器中会报除0错误;
- 用C去算则不会,有点逆天;
C不会产生core dump:
精简以下,C不会产生除0错误,:
排了一天的错误,结果发现了一个逆天bug:
- 当b作为一个变量嵌入到a中时,程序会产生浮点数错误,即除0错误;
- 当将b换成一个宏,粘贴到a中,一块计算a时,则不会产生除0错误;
发现一个有趣的现象
同一个表达式,在不同的编译器下进行编译计算,所得到的结果不同:
- 用gcc进行编译:会产生
Floating point exception (core dumped)
- 用clang进行编译,则可以计算
终于解决了上面这个现象有关的bug
原因:C语言整数运算默认为有符号型!!!!!
这个bug调了两天,问了下老师终于明白了;
11.26
检查源码,尝试排除浮点异常错误
- 解决表达式生成器缓冲区溢出bug;
- 尝试解决除0bug,未果,手足无措
11.27
历时三四天终于决定进行第三阶段,第二阶段所语调的除0错误,我怀疑应该是gcc编译器或者c语言本身的一些特性所导致的,我的算法实现应该没有问题,因此现将这个bug放一放,不要影响了自己的心态,看看能不能在C语言手册中找到答案;
监视点
扩展表达式求值的功能
11.28 基本实现扩展功能,通过测试,但是有一个bug未能解决
修复十进制和十六进制匹配冲突bug
如何保证十六进制前面的0不会被十进制规则匹配?
11.28 实现监视点链表的管理功能(新建和释放), 解决上述bug(通过将十进制和十六进制的匹配规则的顺序置换以下,提高十六进制数匹配的优先级)
11.29 基本实现监测点暂停程序。
11.29 实现监测点暂停,进行部分测试。
1h 11.29 pa1基本完工。