开学!
RISC-V assembly
user/call.c1 2 3 4 5 6 7 8 9 10 11 12
| 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); }
|
阅读 user/call.asm 回答问题~
main.asm1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 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: 7c850513 addi a0,a0,1992 # 7f0 <malloc+0xee> 30: 00000097 auipc ra,0x0 34: 614080e7 jalr 1556(ra) # 644 <printf> exit(0) 38: 4501 li a0,0 3a: 00000097 auipc ra,0x0 3e: 290080e7 jalr 656(ra) # 2ca <exit> }
|
传给函数的参数保存在哪些寄存器中?例如 main 函数中的调用 printf 的参数 13 保存在哪个寄存器中?
a0 ~ a7 保存函数参数,更多的参数放在栈中
main 调用 printf 的参数 13 在 a2 中
main 函数中调用 f 函数的汇编代码在哪?调用 g 函数的代码在哪?(提示:编译器可能内联函数)
真的有调用吗。。。感觉编译器优化了,直接把 f(8)+1 的结果计算出来为 12,传给 a1 寄存器了。
printf 函数的地址是多少?
看注释,在 0x644
在 main 函数中,在执行 jalr 跳转到 printf 后,ra 寄存器的值时多少?
0x38
jalr 会将下一条指令的地址存到括号中的寄存器中
运行下面的代码,输出什么?
unsigned int i = 0x00646c72;
printf(“H%x” Wo%s”, 57616, &i);
He110 World
下面的代码,会打印出 ‘y=’ 什么?
printf(“x=%d y=%d”, 3);
按照 RISC-V 的函数调用约定,会打印出 a2 寄存器的值
Backtrace
对内核的函数调用进行回溯,比较简单
根据 RISC-V 的函数调用约定,ra 位于 fp - 0x8 的位置,Prev.fp 位于 fp - 0x10 的位置
在内核栈中,最后一个栈帧指针位于页面的首地址,根据这个可以判断何时退出循环
可以通过 gdb 进行调试,0x3ffffff9fc0 -> 0x3ffffffe0 -> 0x3ffffffa000
1 2 3 4 5 6 7 8 9 10 11
| (gdb) x/20gx $fp-0x10 0x3fffff9f70: 0x0000003fffff9fc0 0x00000000800021aa 0x3fffff9f80: 0x0000003fffff9fc0 0x00000001ffff9fa0 0x3fffff9f90: 0x0000003fffff9fc0 0x0000000000000020 0x3fffff9fa0: 0x0000000087f70000 0x0000000080009030 0x3fffff9fb0: 0x0000003fffff9fe0 0x000000008000201c 0x3fffff9fc0: 0x0000000000000063 0x0000000080009030 0x3fffff9fd0: 0x0000003fffffa000 0x0000000080001d12 0x3fffff9fe0: 0x0000000000000063 0x0000000000014f50 0x3fffff9ff0: 0x0000000000003fd0 0x0000000000000012 0x3fffffa000: Cannot access memory at address 0x3fffffa000
|
但是在用户栈中,最后一个栈帧指针是页面的首地址 - 0x10,就很怪。。。
比如在 sh 打印 $
时,查看用户栈,0x4fd0 -> 0x4fe0 -> 0x4ff0,最后一个指针是 0x4ff0
1 2 3 4 5 6 7 8 9 10
| (gdb) x/20gx $fp-0x10 0x4f80: 0x0000000000004fd0 0x0000000000000ade 0x4f90: 0x0000000000000000 0x0505050505050505 0x4fa0: 0x0505050505050505 0x0505050505050505 0x4fb0: 0x00000000000008a8 0x0000000000000000 0x4fc0: 0x0000000000004fe0 0x0000000000000b66 0x4fd0: 0x0000000000003fd0 0x00000000000000de 0x4fe0: 0x0000000000004ff0 0x0000000000000000 0x4ff0: 0x0000000000006873 0x0000000000000000 0x5000: Cannot access memory at address 0x5000
|
算了,不管这么多了,反正也只用回溯内核栈
把 backtrace
贴到 kernel/printf.c 中,在 kernel/defs.h 中添加声明,然后在 sys_sleep
调用就好了
kernel/printf.c1 2 3 4 5 6 7 8
| void backtrace(void) { printf("backtrace:\n"); for(uint64 fp = r_fp(); fp != PGROUNDUP(fp); fp = *(uint64*)(fp-0x10)){ printf("%p\n", *(uint64*)(fp-0x8)); } }
|
获得的地址可以通过 addr2line 得到对应的程序代码的位置,便于调试
如 addr2line -e kernel/kernel
放到 panic
函数中,可以更好地方便内核崩溃原因
Alarm
添加一个用户级的定时器中断,也就是 sigalarm(interval, handler)
和 sigreturn()
每 n 次硬件计时器中断,就会调用一次 handler,在 handler 中要有 sigreturn
保证还原到原本的状态
笔者天真地以为保存 p->trapframe->epc 就行了,wsfw(还有通用寄存器要进行保存)
在 proc 结构体添加变量
kernel/proc.h1 2 3 4 5 6 7 8 9
| struct proc {
...
int ticks; int alarm_interval; uint64 alarm_handler; struct trapframe *alarm_state; }
|
- ticks
- alarm_state
- 直接用
struct trapframe
结构体保存原状态(笔者是个懒人
- 在每次调用 handler 前,将其指向 p->trapframe + 1,
sigreturn
后置零
初始化
kernel/proc.c1 2 3 4 5 6 7 8 9 10 11 12 13
| static struct proc* allocproc(void) {
...
p->ticks = 0; p->alarm_interval = 0; p->alarm_handler = 0; p->alarm_state = 0;
return p; }
|
添加系统调用
kernel/sysproc.h1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| uint64 sys_sigalarm(void) { int ticks; uint64 handler; struct proc *p = myproc();
argint(0, &ticks); argaddr(1, &handler);
p->alarm_interval = ticks; p->alarm_handler = handler; return 0; }
uint64 sys_sigreturn(void) { struct proc *p = myproc(); uint64 a0 = p->alarm_state->a0;
memmove(p->trapframe, p->alarm_state, sizeof(struct trapframe)); memset(p->alarm_state, 0, sizeof(struct trapframe)); p->alarm_state = 0; return a0; }
|
计时器中断时判断是否执行 handler
kernel/trap.c1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| void usertrap(void) { ...
if(which_dev == 2) { yield(); p->ticks++; if(p->alarm_interval && !p->alarm_state && p->ticks % p->alarm_interval == 0) { p->alarm_state = p->trapframe + 1; memmove(p->alarm_state, p->trapframe, sizeof(struct trapframe)); p->trapframe->epc = p->alarm_handler; } } }
|
最后添加一些声明即可
Option challenge exercises
backtrace 打印函数名和行号
#todo