PA1 无代码仅思路 出现的代码是框架原有的

PA1.1

1.实现用于模拟寄存器的结构体 CPU_state

在 nemu\include\cpu\reg.h 中。

我们都知道,cpu的寄存器是公用内存的,所以要用到union(这个我记得网上有答案,所以不讲了)

实现后:

PA1 无代码仅思路 出现的代码是框架原有的

2.思考题:在 cmd_c() 函数中, 调用 cpu_exec() 的时候传入了参数 -1 , 你知道这是什么意思吗?

cput_exec()在nemu\src\monitor\cpu-exec.c中。我们会发现:

PA1 无代码仅思路 出现的代码是框架原有的

n是无符号整型,-1表示最大的数。所以for 循环可以执行最大次数的循环,而ecex_wrapper()函数就是执行%eip 指向的当前指令并更新%eip。最终就可以执行完所有指令。

 

PA1 无代码仅思路 出现的代码是框架原有的

3.单步执行 si N

先了解这三个函数的用途readline(),strtok(),sscanf()。

在nemu\src\monitor\debug\ui.c中。

PA1 无代码仅思路 出现的代码是框架原有的

PA1 无代码仅思路 出现的代码是框架原有的

可以知道是用 readline 读取我们输入的命令之后,用 strtok()分解第一个字符串(以空格分开),然后与cmd_table[]中的 name 比较,执行对应的函数。

在cmd_table中添加si;在单步执行的函数中,应当再用strtok()分解一次字符,然后用sscanf转化为数字,执行相应的次数。

4.打印寄存器

和1.3类似,printf出寄存器。

regsl[i],32 位寄存器的名字,regsw[i],16 位,regsb[i],8 位。

在前面的寄存器的结构体 CPU_state中我们可以知道cpu.gpr[i]._32放了32位寄存器的值,16位8位类似。

5.扫描内存

讲义前面提到过:内存通过在 nemu\src\memory\memory.c 中定义的大数组 pmem 来模拟. 在客户程序运行的过程中, 总是使用 vaddr_read() 和 vaddr_write() 访问模拟的内存. vaddr, paddr分别代表虚拟地址和物理地址. 

PA1 无代码仅思路 出现的代码是框架原有的

vaddr_read 函数调用 paddr_read,传入两个参数:起始地址,扫描长度。所以我们通过 strtok 分别获得字符串型的地址和扫描长度,用 sscanf 转换为要求的形式,调用 vaddr_read 函数扫描内存。

PA1.2

1首先需要我们了解一下正则表达式。

2然后我们要去为token添加规则。

需要添加空格 == ( ) * / + - != && || ! 十六进制 十进制 寄存器 变量等16种规则。了解正则表达式之后我们知道+*/|都需要转义,因为正则表达式元字符的存在。前三个需要转义两次,一次转义\,\再转义+*/。有的例如(可以直接用ascii码,有的例如||需要在enum中赋值。十进制十六进制寄存器变量的形式用正则表达式表示,举个小例子:{"\\$[a-dA- D][hlHL]|\\$[eE]?(ax|dx|cx|bx|bp|si|di|sp)",TK_register}是寄存器。{"\\|\\|",TK_logical_OR}是||。注意:要将!=放在!前面,防止被识别为!和=。

3接下来我们要完善make_token()函数。

函数目的是为了识别出表达式中的每一个token。在for循环中,用regexec()函数匹配目标文本串和前面定义的rules[i]中的正则表达式比较,pmatch.rm_so==0表示匹配串在目标串中的第一个位置,pmatch.rm_eo表示结束位置,position和substr_len表示读取完后的位置和读取长度。成功识别得到该字符或者字符串的对应规则后,我们需要用switch语句将表达式中每一个部分用对应的数字表示type,将==、十进制数等复制到tokens[nr_token].str中,会用到strcpy或者strncpy函数。

4检查左右括号是否匹配。

我的思路是,在check_parentheses() 函数中,设置了两个变量left=0,flag=0,若tokens[p]为左括号,则判断从tokens[p+1]到tokens[q]。遇到了左括号则left++;遇到右括号则left--,且判断left是否等于0且当前位置是否到了末尾,若left为0且当前位置不在末位,flag赋值1,说明两侧括号不匹配,若left小于0则assert(0),因为说明出现了讲义中提到的类似(4+3))*((2+1)的情况。最后若(left==0)&&(tokens[q]为) )&&flag!=1,即该表达式仅有一对括号,位于两端,互相匹配,则返回1;若left!=0则assert(0),说明可能出现了不匹配的括号;其他情况都返回0;

5寻找dominant operator。

可以在rules中再设置一个变量用来记录运算符优先级。思路是,不考虑十进制十六进制寄存器和非,将op值设为起始位置(若表达式为!2,则返回op为!的位置),然后判断当前位置的运算符,(若遇到左括号,用r来记录括号的对数,然后从左右括号包含的范围之后的一位再判断运算符优先级,跳过括号内的运算符),与op位置的运算符比较优先级,优先级小于等于op位置,则op=当前位置。其中,-和*需要再判断它是否是第一个位置或者前面一位是否为符号,右括号除外,为了避免将指针与乘和减号与负号错认。

6完善求值函数eval()。

eval(p,q)函数大体上就是先判断表达式的首尾地址是否合理,不合理assert(0);

再判断pq是否相等,若相等,它要么是十进制或十六进制或寄存器,若是寄存器,利用regsl等函数判断出它是32位或16位,并判断出位置是第几个,然后求值。8位寄存器一个个判断。不管如何,它最终必然是一个数值并返回该值;

然后运用check_parentheses函数判断该表达式是否被一对匹配的括号包围着,若是则递归求值括号包围的那个表达式。

若以上判断都是否,则用find_dominant_operator函数找出最后一步运行的运算符所在位置op,而后递归调用eval函数,求出op左右两端表达式的值val1,val2,再根据op的运算符进行对应的操作并返回结果。判断*,要么是*0x100000 要么是*(0x10000+4)两种情况,进行判断,然后用eval得到整数,用vaddr_read()读取内存。判断负号,负号是不会被视作op的,所以它只能最后被分解为最小的表达式,例如3--2,最后会eval(2,3),即-2。判断!,要么是!2要么是!(1-1)这两种情况,进行判断。剩余几个判断不说了。

PA1.3

1监视点的结构体

在原有的基础上结构体还要加上新值、旧值、类型(判断是断点or监视点)、是否开启、监视的表达式。

2实现监视点池的管理

new_wp是从free链表中取一个结点给head链表,且将表达式、值赋给它,修改开关,并输出该节点的编号。运用正则表达式判断是否为断点,若是则type为b,否则为w。

正则表达式的使用可以仿照这个来https://www.jb51.net/article/119725.htm

free_wp函数是遍历head链表直到找出对应NO的结点,从head中删除,添加到free链表中。同时修改类型、表达式、值、开关。

3实现监视点

如讲义中所言,每当cpu_exec()执行完一条指令,调用函数judge_wp对所有表达式求值判断是否变化,若变化则返回-1,暂停,输出提示并返回。在cpu_exec.c中修改,并且需要声明judge_wp()函数。其中的judge_wp()函数在watchpoint.c中写。

使用info w来打印监视点信息。这里在cmd_info中调用了函数print_wp()。

删除监视点。在cmd_d中调用free_wp函数即可。

4断点

就是2所说的,运用正则表达式判断是否为断点格式:$eip==0x16进制数字,regcomp()函数编译正则表达式,执行成功返回0,则type为b,否则为w。

5

find . -name "*[.h|.cpp]" | xargs wc -l   可用于计算.c .h文件有多少行。

find . -name "*[.cpp| .h]" | xargs grep "^."| wc -l 用于计算.c .h文件除去空格有多少行。