RISC-V assembly
Which registers contain arguments to functions? For example, which register holds 13 in main's call to printf?
- a2 寄存器,函数调用时,参数从左到右会依次保存在 a0, a1, a2, a3 寄存器,似乎是一直到寄存器 a7 的。
Where is the call to function f in the assembly code for main? Where is the call to g? (Hint: the compiler may inline functions.)
- 这里的调用都被内联了
At what address is the function printf located?
-
auipc
(Add Upper Immediate PC)指令是将一个立即数左移 $12$ 位加上当前指令的地址(pc)中,得到一个绝对地址。 -
例如
auipc a0, 0x0
就是将 $\text{0x0}$ 左移 $12$ 位加上当前指令的地址 (pc) 中(pc 的值我们一般认为是在指令执行完成时才发生递增,从而指向下一条指令,使得处理器能够按顺序顺利执行指令序列),因此,执行auipc a0, 0x0
时,加的就是当前指令的地址,即 $\text{0x28}$。 -
auipc
常常会与jalr
(Jump and Link Register) 指令组合在一起,在jalr 1526(ra)
中,它把 pc 设置为寄存器 ra 的值加上 offset,然后把原先的 $pc + 4$ 的值写入寄存器 ra,即 ra 的值就是函数的返回值,即正常序列里,jalr
的下一条指令,这里ra
的值应该是 $\text{0x38}$, pc 的值则为 $\text{0x626}$。
unsigned int i = 0x00646c72;
printf("H%x Wo%s", 57616, &i);
- 上面这段代码的输出结果为
He110 World
。
Backtrace
这里的关键在于弄懂 stack frame 的结构,参考这张图,其实基本原理与 x86 下 C 语言的 stack frame 是一致的,我们通过 uint64 fp = r_fp()
函数拿到的是栈基地址(联想 x86 中的 rbp),因此 fp + 8
就是 return address 的地址,因此 uint64 ra = *(uint64 *)(fp + 8);
,注意 fp + 8
是地址的地址,不是我们要输出的 ra!
我们需要遍历 stack frame,上一个 stack frame 的栈基地址就存放在 fp + 16
处,因此 fp = *(uint64 *)(fp + 16);
。
循环的终止条件,可以先计算出 fp
的上下界 PGROUNDDOWN(fp)
和 PGROUNDUP(fp)
(按照 PGSIZE 大小对齐),又或者当 PGROUNDUP(fp) != p->kstack + PGSIZE
时退出循环。
void backtrace(void) {
uint64 fp = r_fp();
printf("backtrace\n");
uint64 upb = PGROUNDUP(fp);
uint64 db = PGROUNDDOWN(fp);
// struct proc *p = myproc();
while (fp < upb && fp > db) {
uint64 ra = *(uint64 *)(fp - 8);
printf("%p\n", ra);
fp = *(uint64 *)(fp - 16);
}
}
Alarm
这个实验在了解了 trap 机制的整个流程,如何陷入到内核代码,又如何返回用户代码之后,其实并不难,但是还是有两个地方比较难想:
-
sigalarm
的调用其实只起到一个开关的作用,当开启sigalarm
之后,还是要在usertrap
的外部定时中断部分来执行定时响应函数,我们可以通过argaddr
函数来获取sigalarm
传入的作为函数地址的第二个参数,但是,我们并不能直接在usertrap
中把函数地址强制类型转化为函数指针来调用函数,这是因为kernel page table
与user page table
是不一样的,我们需要把p->trapframe->epc
修改为periodic
的地址,在返回用户态时,pc 会被更新为p->trapframe->epc
的值,这样返回到用户态时就会马上执行periodic
函数; -
经过上面的操作,我们返回时会直接执行
periodic
函数,但是执行完函数之后,机器就不知道接下来该执行哪一条指令来,因此提供了sigreturn
函数,使得可以恢复到发生定时器中断时的状态,这里提到了需要保存很多寄存器的值,因此直接在struct proc
中添加一个struct trapframe *save_reg;
字段,在usertrap
的定时中断响应中,利用memmove
将p->trapframe
复制到p->save_reg
,而在sys_return
中将p->save_reg
恢复到p->trapframe
。
几处关键的代码:
trap.c
中:
void usertrap(void) {
int which_dev = 0;
if ((r_sstatus() & SSTATUS_SPP) != 0)
panic("usertrap: not from user mode");
// send interrupts and exceptions to kerneltrap(),
// since we're now in the kernel.
w_stvec((uint64)kernelvec);
struct proc *p = myproc();
// save user program counter.
p->trapframe->epc = r_sepc();
if (r_scause() == 8) {
// system call
if (p->killed)
exit(-1);
// sepc points to the ecall instruction,
// but we want to return to the next instruction.
p->trapframe->epc += 4;
// an interrupt will change sstatus &c registers,
// so don't enable until done with those registers.
intr_on();
syscall();
} else if ((which_dev = devintr()) != 0) {
// ok
if (which_dev == 2 && p->interval != 0) {
// 说明启用了 alarm
++p->tick_num;
if (p->tick_num == p->interval) {
if (p->flag == 0) {
// typedef void (*Handler)();
// printf("p->handler: %p\n", p->handler);
memmove(p->save_reg, p->trapframe, PGSIZE);
p->flag = 1;
p->trapframe->epc = p->handler;
}
p->tick_num = 0;
}
}
} else {
printf("usertrap(): unexpected scause %p pid=%d\n", r_scause(), p->pid);
printf(" sepc=%p stval=%p\n", r_sepc(), r_stval());
p->killed = 1;
}
if (p->killed)
exit(-1);
// give up the CPU if this is a timer interrupt.
if (which_dev == 2) {
yield();
}
usertrapret();
}
sysproc.c
中:
uint64 sys_sigalarm() {
struct proc *p = myproc();
int interval;
if (argint(0, &interval) < 0) {
return -1;
}
uint64 addr;
if (argaddr(1, &addr) < 0) {
return -1;
}
p->interval = interval;
p->handler = addr;
return 0;
}
uint64 sys_sigreturn() {
struct proc *p = myproc();
memmove(p->trapframe, p->save_reg, PGSIZE);
p->flag = 0;
return 0;
}
同时注意在 proc.c
的 allocproc
中给 save_reg
分配空间,在 freeproc
中释放。
以及 kernel/param.h
中,FSSIZE
要修改为至少 $1088$,否则 alarmtest
能过,但是 usertests
过不了,会卡在 writebig 处,报错提示为 "test writebig: panic: balloc: out of blocks",不知道有没有大佬能解惑。