NJU ICS PA 一些笔记
Do you know
本人代码水平拙劣🥲,实现部分仅供参考
南京大学 计算机科学与技术系 计算机系统基础 课程实验 2024
PA1 - 开天辟地的篇章
RTFSC
文件:nemu/src/monitor/sdb/sdb.c
原因:退出时 nemu_state.state
不是 “正常” 的
解决办法:
static int cmd_q(char *args) {
nemu_state.state = NEMU_QUIT;
return -1;
}
监视点
基本框架
添加 w <expr>
和 d <index>
命令,来添加和删除监视点。
并且需要实现监视点池中链表的维护,监视点表达式的计算。
为了提高 NEMU 的性能,提供监视点功能的开关选项。
链表维护
监视点池中涉及监视点链表和空闲链表,通过 init_wp_pool()
来对其初始化。
void init_wp_pool() {
int i;
for (i = 0; i < NR_WP; i ++) {
wp_pool[i].NO = i;
wp_pool[i].next = (i == NR_WP - 1 ? NULL : &wp_pool[i + 1]);
}
head = NULL;
free_ = wp_pool;
}
接着,通过 new_wp()
和 free_wp()
实现监视点的管理
int new_wp(char* args){
WP *p=NULL,*q=NULL;
if (free_==NULL)
{
printf("Watchpoints Limit");
return -1;
}
p=free_;
bool success=1;
p->result = expr(args,&success);
if (!success)return -1;
p->expr = strdup(args);
free_=free_->next;
if (head!=NULL)
{
q=head;
while (q->next!=NULL)q=q->next;
q->next=p;
return 1;
}
head=p;
head->next=NULL;
return 1;
}
void free_wp(int no)
{
if (head==NULL)
{
printf("Watchpoint %d not found.\n",no);
return;
}
if (head->NO==no)
{
WP *wp=head;
head=wp->next;
wp->next=free_;
free_=wp;
return;
}
WP *p=NULL;
if (head!=NULL)
{
p=head;
while (1){
if (p->next->NO==no){
WP *wp=p->next;
p->next=wp->next;
wp->next=free_;
free_=wp;
return;
}
if (p->next==NULL)
{
printf("Watchpoint %d not found.\n",no);
return;
}
p=p->next;
}
}
}
监视点求值
为了判断监视点的值是否发生变化,还需要在结构体中添加一个成员来记录。然后通过 check_expr()
来求值和判断变化。
bool check_expr(){
bool changed=false;
if (head==NULL)return 0;
WP *p;
bool success=true;
p=head;
word_t result = expr(p->expr,&success);
if (result!=p->result && success)
{
printf("Watchpoint %d changed at 0x%x.\n",p->NO,cpu.pc);
changed=true;
}
if (changed)return 1;
return 0;
}
如何阅读手册
程序是个状态机
对于计算 1+2+...+100
的程序的状态机,它是确定性的。
(0, x, x) -> (1, 0, x) -> (2, 0, 0) -> (3, 0, 1) -> (4, 1, 1) -> (5, 1, 2) -> (6, 3, 2) -> ... -> (199,4851, 99) -> (200, 4950, 99) -> (201, 4950, 100) -> (202, 5050, 100)
理解基础设施
不必多说,使用过调试器的话肯定有所体会。
RTFM
riscv32 有哪几种指令格式?
There are four core instruction formats.1
Register-Type, Immediate-Type, Store-Type, Upper Immediate-Type.
LUI 指令的行为是什么?
LUI (load upper immediate) is used to build 32-bit constants and uses the U-type format. LUI places the 32-bit U-immediate value into the destination register rd, filling in the lowest 12 bits with zeros.

mstatus 寄存器的结构是怎么样的?
The mstatus (Machine Status) register is an MXLEN-bit read/write
register formatted as shown in figures below. It's a Control and
Status Register.
为什么要使用 -Wall
和 -Werror
?
At section 3.92, we found that:
-Werror
Turn all warnings into errors.
-Wall
This enables all the warnings about constructions
that some users consider questionable, and that are easy to avoid (or
modify to prevent the warning), even in conjunction with macros. This
also enables some language-specific warnings.
To add these options, we can leverage compilers to identify potential issues in our programs.
shell 命令
使用 find . -type f \( -name "*.c" -o -name "*.h" \) -print0 | xargs -0 wc -l
来统计行数
由于我环境经历了多次迁移,似乎把 git 弄坏了(
不过毕竟我没有提交作业的需求,就不注意这些细节了
PA2 - 简单复杂的机器
不停计算的机器
画出在 YEMU 上执行的加法程序的状态机
类似地,使用一个 6 元组来分别表示 PC, R [0], R [1], M [x], M [y], M [z].
(0, 0, 0, x, y ,0) -> (1, y, 0, x, y, 0) -> (2, y, y, x, y, 0) -> (3, x, y , x, y, 0) -> (4, x+y, y, x, y, 0) -> (5, x+y, y, x, y, x+y)
RTFSC(2)
立即数背后的故事
1. 假设我们需要将 NEMU 运行在 Motorola 68k 的机器上 (把 NEMU 的源代码编译成 Motorola 68k 的机器码)
此时读取的字节序列会被解释为大端序的,如果在二进制文件中以小端序存储,可能会导致问题。
2. 假设我们需要把 Motorola 68k 作为一个新的 ISA 加入到 NEMU 中
我们需要正确模拟大端序对应的存储结构与解释方式。
立即数背后的故事 (2)
在 RISC-V32 中,一般使用分部加载的方式:
通过 lui
加载高 20 位,addi
加载低 12 位
lui x10, 0x0D000
addi x10, x10, 0x721
auipc
的执行过程
QEMU 内建的第一条指令,正是 auipc
0x00000297, // auipc t0,0
在 QEMU 运行过程中,首先调用 exec_once()
来进入相应的处理流程。
static void exec_once(Decode *s, vaddr_t pc) {
s->pc = pc;
s->snpc = pc;
isa_exec_once(s);
cpu.pc = s->dnpc;
//some macros...
}
传入的 Decode
是一个包含与 PC 有关变量的结构体
typedef struct Decode {
vaddr_t pc;
vaddr_t snpc; // static next pc
vaddr_t dnpc; // dynamic next pc
ISADecodeInfo isa;
IFDEF(CONFIG_ITRACE, char logbuf[128]);
} Decode;
然后调用 isa_exec_once(s)
,对于不同的架构,具体的定义不同。
instruction fetch
在 risc-v32 的实现中,代码如下
int isa_exec_once(Decode *s) {
s->isa.inst = inst_fetch(&s->snpc, 4);
return decode_exec(s);
}
具体的过程又涉及到 vaddr_read()
和 paddr_read()
,处理 mmio 地址和 pmem 地址,物理内存上使用 host_read()
读取主机内存上的不同长度字节。
instruction decode
完成取指调用的一系列函数后,isa_exec_once()
会返回 decode_exec(s)
将指令与相应的模式匹配
static int decode_exec(Decode *s) {
s->dnpc = s->snpc;
INSTPAT_START();
INSTPAT("??????? ????? ????? ??? ????? 00101 11", auipc , U, R(rd) = s->pc + imm);
INSTPAT("??????? ????? ????? 100 ????? 00000 11", lbu , I, R(rd) = Mr(src1 + imm, 1));
INSTPAT("??????? ????? ????? 000 ????? 01000 11", sb , S, Mw(src1 + imm, 1, src2));
INSTPAT("0000000 00001 00000 000 00000 11100 11", ebreak , N, NEMUTRAP(s->pc, R(10))); // R(10) is $a0
INSTPAT("??????? ????? ????? ??? ????? ????? ??", inv , N, INV(s->pc));
INSTPAT_END();
R(0) = 0; // reset $zero to 0
return 0;
}
其中,auipc
对应的 (U-Type) 格式如下:

execute
QEMU 在宏中定义了 auipc
的具体行为:
INSTPAT("??????? ????? ????? ??? ????? 00101 11", auipc , U, R(rd) = s->pc + imm);
pattern_decode()
中的宏处理了格式字符串:
static inline void pattern_decode(const char *str, int len,
uint64_t *key, uint64_t *mask, uint64_t *shift) {
uint64_t __key = 0, __mask = 0, __shift = 0;
macro64(0);//宏展开,遍历了6位二进制数0b000000的任意取值
panic("pattern too long");
finish:
*key = __key >> __shift;
*mask = __mask >> __shift;
*shift = __shift;
}
运行第一个 C 程序
我们需要在此部分实现的指令有 lui
, addi
,
jal
, jalr
.