Lab4 Traps

开学!

RISC-V assembly

user/call.c
1
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.asm
1
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.c
1
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.h
1
2
3
4
5
6
7
8
9
struct proc {

...

int ticks;
int alarm_interval;
uint64 alarm_handler;
struct trapframe *alarm_state;
}
  • ticks
    • 保存计时器中断次数,每中断一次,ticks++
  • alarm_state
    • 直接用 struct trapframe 结构体保存原状态(笔者是个懒人
    • 在每次调用 handler 前,将其指向 p->trapframe + 1,sigreturn 后置零

初始化

kernel/proc.c
1
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.h
1
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.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void
usertrap(void)
{
...

// give up the CPU if this is a timer interrupt.
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

作者

Humoooor

发布于

2022-10-31

更新于

2024-01-04

许可协议

评论