第一部分 RISC-V assembly 阅读汇编
相关的C代码:
#include "kernel/param.h"
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
int g(int x) {
return x+3;
}
int f(int x) {
return g(x);
}
void main(void) {
printf("%d %d\n", f(8)+1, 13);
exit(0);
}
相关的汇编代码:
0000000000000000 <g>:
#include "kernel/param.h"
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
int g(int x) {
0: 1141 addi sp,sp,-16
2: e422 sd s0,8(sp)
4: 0800 addi s0,sp,16
return x+3;
}
6: 250d addiw a0,a0,3
8: 6422 ld s0,8(sp)
a: 0141 addi sp,sp,16
c: 8082 ret
000000000000000e <f>:
int f(int x) {
e: 1141 addi sp,sp,-16
10: e422 sd s0,8(sp)
12: 0800 addi s0,sp,16
return g(x);
}
14: 250d addiw a0,a0,3
16: 6422 ld s0,8(sp)
18: 0141 addi sp,sp,16
1a: 8082 ret
000000000000001c <main>:
void main(void) {
1c: 1141 addi sp,sp,-16
1e: e406 sd ra,8(sp)
20: e022 sd s0,0(sp)
22: 0800 addi s0,sp,16
printf("%d %d\n", f(8)+1, 13);
24: 4635 li a2,13
26: 45b1 li a1,12
28: 00000517 auipc a0,0x0
2c: 7b050513 addi a0,a0,1968 # 7d8 <malloc+0xea>
30: 00000097 auipc ra,0x0
34: 600080e7 jalr 1536(ra) # 630 <printf>
exit(0);
38: 4501 li a0,0
3a: 00000097 auipc ra,0x0
3e: 27e080e7 jalr 638(ra) # 2b8 <exit>
问题:
Which registers contain arguments to functions? For example, which register holds 13 in main’s call to printf?
从24: 4635 li a2,13
可以看出来13存储在a2寄存器,相应地,前两个参数分别寄存在a0和a1。
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.)
编译器把函数优化成inline
的,主函数中对f的调用直接被优化成了li a1,12
At what address is the function printf located?
jalr 1536(ra) # 630 <printf>
0000000000000630 <printf>:
Run the following code.
What is the output? Here’s an ASCII table that maps bytes to characters.
unsigned int i = 0x00646c72;
printf("H%x Wo%s", 57616, &i);
57616的16进制是e110
ascii码对照:
2进制 | 10进制 | 16进制 | 字符 |
---|---|---|---|
0111 0010 | 114 | 72 | r |
0110 1100 | 108 | 6c | l |
0110 0100 | 100 | 64 | d |
综上,打印结果为He110 World
In the following code, what is going to be printed after ‘y=’? (note: the answer is not a specific value.) Why does this happen?
printf("x=%d y=%d", 3);
%d
的取值取决于a2
寄存器的值,如果没有设置,该值可能是随机的。
第二部分 Backtrace 回溯
大概意思是回溯打印函数调用栈中的返回地址(即发生函数调用的地址)
Implement a backtrace() function in kernel/printf.c. Insert a call to this function in sys_sleep, and then run bttest, which calls sys_sleep. Your output should be as follows:
backtrace:
0x0000000080002cda
0x0000000080002bb6
0x0000000080002898
After
bttest
exit qemu. In your terminal: the addresses may be slightly different but if you runaddr2line -e kernel/kernel
(orriscv64-unknown-elf-addr2line -e kernel/kernel
) and cut-and-paste the above addresses as follows:
$ addr2line -e kernel/kernel
0x0000000080002de2
0x0000000080002f4a
0x0000000080002bfc
Ctrl-D
You should see something like this:
kernel/sysproc.c:74
kernel/syscall.c:224
kernel/trap.c:85
上代码,首先在risv.h
中添加从寄存器中读取当前fp
指针的函数
static inline uint64
r_fp()
{
uint64 x;
asm volatile("mv %0, s0" : "=r" (x) );
return x;
}
在printf.c
中添加backtrace
函数
void backtrace(void)
{
uint64 fp = r_fp();
uint64 upbound = PGROUNDUP(fp);
while (fp < upbound) {
printf("%p\n", *(uint64*)(fp - 8));
fp = *(uint64*)(fp - 16);
}
}
之后在defs.h
添加函数声明。最后在sys_sleep
函数(sysproc.c
)中调用backtrace
函数。即可编译运行测试。
hart 1 starting
hart 2 starting
init: starting sh
$ bttest
0x00000000800020cc
0x0000000080001fa6
0x0000000080001c90
第三部分 Alarm (hard)
In this exercise you’ll add a feature to xv6 that periodically alerts a process as it uses CPU time. This might be useful for compute-bound processes that want to limit how much CPU time they chew up, or for processes that want to compute but also want to take some periodic action. More generally, you’ll be implementing a primitive form of user-level interrupt/fault handlers; you could use something similar to handle page faults in the application, for example. Your solution is correct if it passes alarmtest and usertests.
首先编写系统调用
uint64 sys_sigalarm(void) {
int interval;
if (argint(0, &interval) < 0) {
return -1;
}
void (*fn) (void);
if (argaddr(1, (uint64*)&fn) < 0) {
return -1;
}
struct proc *p = myproc();
// 如果传入两个0,清空
if (interval == 0 && fn == 0) {
p->interval = -1;
p->tiktok = 0;
return 0;
}
// 保存间隔时间,保存函数指针,计时为0
p->interval = interval;
p->fn = fn;
p->tiktok = 0;
return 0;
}
// 恢复系统调用,恢复之前保存下来的寄存器(在trap.c中保存的)
uint64 sys_sigreturn(void) {
struct proc *p = myproc();
p->trapframe->epc = p->pc2;
p->trapframe->s0 = p->s0_2;
p->trapframe->ra = p->ra;
p->trapframe->sp = p->sp;
p->flag = 0; // 清空flag,指示当前可以重入handler函数
p->trapframe->a0 = p->a0;
p->trapframe->a1 = p->a1;
p->trapframe->a2 = p->a2;
p->trapframe->a3 = p->a3;
···
// printf("%p\n", p->trapframe->epc);
return 0;
};
修改proc.h中的结构体定义
// Per-process state
struct proc {
struct spinlock lock;
// p->lock must be held when using these:
enum procstate state; // Process state
void *chan; // If non-zero, sleeping on chan
int killed; // If non-zero, have been killed
int xstate; // Exit status to be returned to parent's wait
int pid; // Process ID
// wait_lock must be held when using this:
struct proc *parent; // Parent process
// these are private to the process, so p->lock need not be held.
uint64 kstack; // Virtual address of kernel stack
uint64 sz; // Size of process memory (bytes)
pagetable_t pagetable; // User page table
struct trapframe *trapframe; // data page for trampoline.S
struct context context; // swtch() here to run process
struct file *ofile[NOFILE]; // Open files
struct inode *cwd; // Current directory
char name[16]; // Process name (debugging)
// 需要保持的寄存器
int interval;
int tiktok;
uint64 pc2;
uint64 s0_2;
uint64 sp;
uint64 ra;
uint64 a0;
uint64 a1;
uint64 a2;
uint64 a3;
···
// 需要新增的字段
int flag; // 是否正在处理handler函数
void (*fn)(void); // handler函数指针
};
修改trap.c
void
usertrap(void)
{
···
// give up the CPU if this is a timer interrupt. 如果是tick中断
if(which_dev == 2) {
if (p->interval != -1) { // 如果计时器激活
p->tiktok += 1; // 计时加1
}
yield();
}
usertrapret();
}
//
// return to user space
//
void
usertrapret(void)
{
struct proc *p = myproc();
···
// set S Exception Program Counter to the saved user pc.
// 如果计时器到时,且flag为0(flag指示当时是否在handler过程中)
if (p->tiktok == p->interval && p->flag != 1) {
// printf("%p %p\n", p->fn, p->trapframe->ra);
// printf("%p %p\n", p->trapframe->epc, p->trapframe->ra);
// 保存寄存器
p->pc2 = p->trapframe->epc;
p->s0_2 = p->trapframe->s0;
p->ra = p->trapframe->ra;
p->sp = p->trapframe->sp;
p->a0 = p->trapframe->a0;
p->a1 = p->trapframe->a1;
p->a2 = p->trapframe->a2;
p->a3 = p->trapframe->a3;
···
p->flag = 1;
// printf("%p\n", p->trapframe->epc);
// printf("%p\n", p->pc2);
// 将epc指向fn函数的起始地址
p->trapframe->epc = (uint64)(p->fn);
// tiktok置零
p->tiktok = 0;
}
w_sepc(p->trapframe->epc);
// tell trampoline.S the user page table to switch to.
uint64 satp = MAKE_SATP(p->pagetable);
// jump to trampoline.S at the top of memory, which
// switches to the user page table, restores user registers,
// and switches to user mode with sret.
uint64 fn = TRAMPOLINE + (userret - trampoline);
((void (*)(uint64,uint64))fn)(TRAPFRAME, satp);
}
测试结果,全部通过
(base) zhaokanglun@GANBADEI:~/s081/xv6-labs-2021$ make qemu
qemu-system-riscv64 -machine virt -bios none -kernel kernel/kernel -m 128M -smp 3 -nographic -drive file=fs.img,if=none,format=raw,id=x0 -device virtio-blk-device,drive=x0,bus=virtio-mmio-bus.0
xv6 kernel is booting
hart 1 starting
hart 2 starting
init: starting sh
$ alarmtest
test0 start
.....................................alarm!
test0 passed
test1 start
......alarm!
.....alarm!
......alarm!
.....alarm!
....alarm!
.....alarm!
........alarm!
......alarm!
......alarm!
.....alarm!
.test1 passed
test2 start
..................................................................alarm!
test2 passed
$