Loading

MIT 6.S081 操作系统 LAB4:Traps

Lab: traps

RISC-V assembly

有关RISC-V的汇编,分析call.c中的代码

call.c:

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);
}

call.asm:

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 :

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

:

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: 7c050513 addi a0,a0,1984 # 7e8 <malloc+0xea>
30: 00000097 auipc ra,0x0
34: 610080e7 jalr 1552(ra) # 640
exit(0);
38: 4501 li a0,0
3a: 00000097 auipc ra,0x0
3e: 27e080e7 jalr 638(ra) # 2b8

Which registers contain arguments to functions? For example, which register holds 13 in main's call to printf?

a0保存返回值,a1保存参数12,a2保存参数13

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?

0x640
30: 00000097 auipc ra,0x0将pc的值放入ra,此时ra的值为0x30
34: 610080e7 jalr 1552(ra) # 640 <printf>相对寻址,1552+48=1600=0x640

What value is in the register ra just after the jalr to printf in main?

ra保存返回地址,故是调用printf后的下一条指令地址:0x38

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);

RISC-V是小端,低地址保存低位数据
57616=0xe110
在ASCII中,72对应r,6c对应l,64对应d
故输出为He110 World
如果RISC-V是大端,则i的值应为0x726c6400

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);

y的输出是未定义的,取决于定义寄存器的值

Backtrace

For debugging it is often useful to have a backtrace: a list of the function calls on the stack above the point at which the error occurred.

取出fp栈帧指针,不断往前读取,因为xv6分配栈是按页对齐的,所以读到页对齐循环结束
栈帧布局如下:

fp-8的地址为return address,fp-16为上一个栈帧的地址

void backtrace()
{
  printf("backtrace:\n");
  uint64 fp=r_fp();
  while(PGROUNDDOWN(fp)!=fp)
  {
    uint64 addr=*((uint64*)(fp-8));
    printf("%p\n",addr);
    fp=*((uint64*)(fp-16));
  }
}

Alarm

实现一个定时器,每隔一定数量的cpu时钟,执行handler函数

修改proc结构体

//alarm
  int interval;
  void (*fn) ();//handler
  int count;
  int handling;//表示是否正在执行handler函数
  struct trapframe save_info;//保存信息

系统调用

sigalarm设置定时器

uint64
sys_sigalarm(void)
{
  struct proc *p = myproc();
  int interval;
  void(*fn)();
  if(argint(0, &interval) < 0)
    return -1;
  if(argaddr(1, (uint64*)&fn) < 0)
    return -1;
  p->interval=interval;
  p->fn=fn;
  p->count=interval;
  return 0;
}

sigreturn将保存下来的trapframe还原回去,还原寄存器现场

uint64
sys_sigreturn(void)
{
  struct proc *p = myproc();
  p->handling=0;
  *(p->trapframe)=p->save_info;
  return 0;
}

处理时钟中断

每个时钟中断,进行相应的处理

// give up the CPU if this is a timer interrupt.
  if(which_dev == 2)
  {
    if(p->interval!=0&&!p->handling)
    {
      if(--p->count==0)
      {
        p->count=p->interval;
        p->handling=1;
        p->save_info=*(p->trapframe);
        p->trapframe->epc=(uint64)p->fn;
      }
    }
    yield();
  }

思路有以下:

  • 通过判断interval是否==0决定是否设置了定时器
  • 判断p->handling,如果正在执行handler则不改变闹钟时间
  • 进入handler前要保存当前trapframe,方便在sigreturn中还原
  • 改变pc的值为handler的地址,执行处理函数

test

posted @ 2021-12-27 23:43  traver  阅读(211)  评论(0)    收藏  举报