Q1 RISC-V assembly

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

生成的汇编代码:

000000000000001c <main>:

void main(void) {
  1c:	1141                	addi	sp,sp,-16 // 分配栈空间
  1e:	e406                	sd	ra,8(sp) // 保存main的返回地址,因为接下来要调用printf
  20:	e022                	sd	s0,0(sp) // 保存前一个函数的frame pointer
  22:	0800                	addi	s0,sp,16 // 现在frame pointer要增加16Bytes
  printf("%d %d\n", f(8)+1, 13);
  24:	4635                	li	a2,13
  26:	45b1                	li	a1,12
  28:	00000517          	auipc	a0,0x0
  2c:	7a050513          	addi	a0,a0,1952 # 7c8 <malloc+0xe8> // "%d %d\n"字符串地址
  30:	00000097          	auipc	ra,0x0 // ra=pc=0x30
  34:	5f8080e7          	jalr	1528(ra) # 628 <printf> // 0x30 + 0x5f8 = 0x628
  exit(0);
  38:	4501                	li	a0,0 // exit的参数,传入0
  3a:	00000097          	auipc	ra,0x0
  3e:	274080e7          	jalr	628(ra) # 2ae <exit>

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

由生成的汇编可知,13 保存在寄存器a2中,f(8)+1 即 12 保存在寄存器a1中,字符串"%d %d\n"地址被保存到a0


2.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.)

f(8)+1 在汇编中直接被展开成了 12。


3.At what address is the function printf located?

30:	00000097  auipc	ra,0x0
34:	5f8080e7  jalr	1528(ra) # 628 <printf>

ra=0x30, 0x30+1528=0x628。


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

执行 jalr 指令后,ra 寄存器被赋值为下一条指令,即 0x34+0x4=0x38 地址的指令:li a0,0。


5.Run the following code.

unsigned int i = 0x00646c72;
printf("H%x Wo%s", 57616, &i);

What is the output?

HE110, World.

第一个%x, 57616 对应十六进制 0xE110,第二个&i, 因为是小端序,解析出来的字符串排列顺序为0x72, 0x6c, 0x64, 0x00,查看 ascii 码表,对应字符串orld


6.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 打印出来是个随机数。

Q2 Backtrace

栈分布参考如图:

读出当前的 frame pointer. xv6 给栈分配的空间是一个 page, 当 fp 不指向该页的最高地址时, 说明不是调用的第一个函数.

读出当前函数的返回地址, 在 fp-8 的位置.
读出上一层函数的 fp 地址, 在 fp-16 的位置.

void backtrace(void)
{
	uint64 fp = r_fp();
	uint64 ra;

	printf("backtrace:\n");
	while (fp < PGROUNDUP(fp))
	{
		ra = *(uint64 *)(fp - 8);
		printf("%p\n", ra);
		fp = *(uint64 *)(fp - 16);
	}
}

make qemu 后执行 bttest, 得到函数调用过程:

$ bttest
backtrace:
0x0000000080002132
0x0000000080001fa4
0x0000000080001c8e

利用addr2line -e kernel/kernel

Q3 Alarm

添加一个新的sigalarm(interval, handler)系统调用。如果应用程序调用sigalarm(n, fn),那么在程序消耗的CPU时间的每n“ticks”之后,跳转应用程序函数fn。当fn返回时,应用程序应该resume where it left off。在xv6中,tick是一个相当任意的时间单位,由硬件计时器产生中断的频率决定。如果应用程序调用sigalarm(0,0),内核应该停止生成周期性的alarm调用。

首先在proc中添加alarm相关的成员,alarm_ticks用来跟踪应用程序设置的interval, ticks_passed用来跟踪应用程序消耗的ticks, alarm_saved_tf用来保存应用程序的trapframe, in_handler用来标记是否在处理alarm。

struct proc {
  //...
   int alarm_ticks;
   void (*alarm_handler)();
   int ticks_passed;
   struct trapframe *alarm_saved_tf;
   int in_handler;  
}

实现的系统调用:

uint64 sys_sigalarm(void) {
   int ticks;
   void (*handler)();
   struct proc *p = myproc();
 
   argint(0, &ticks); // 从userspace获取interval
   argaddr(1, (uint64 *)&handler); // 从userspace获取handler
 
   p->alarm_ticks = ticks;
   p->alarm_handler = handler;
 
   return 0;
 }
 
 // userspace调用sigreturn恢复调用sigalarm前的寄存器状态
 uint64 sys_sigreturn(void) {
   struct proc *p = myproc();
 
   memmove(p->trapframe, p->alarm_saved_tf, sizeof(struct trapframe));
   kfree(p->alarm_saved_tf);
   p->alarm_saved_tf = (void *)0;
   p->in_handler = 0;
 
   return p->trapframe->a0;
 }

在usertrap中添加alarm的处理:

void usertrap(void)
{
  //...
} else if ((which_dev = devintr()) != 0) { // timer interrupt
     if (which_dev == 2 && !p->in_handler) { // 是timer interrupt,并且不在处理alarm
       p->ticks_passed++;
       if (p->ticks_passed >= p->alarm_ticks) { // 当消耗的ticks大于等于interval
         p->alarm_saved_tf = (struct trapframe *)kalloc(); // 保存当前的trapframe
         memmove(p->alarm_saved_tf, p->trapframe, sizeof(struct trapframe));
         p->ticks_passed = 0;
         p->in_handler = 1;
         p->trapframe->epc = (uint64)p->alarm_handler; // 设置epc为handler的地址, 跳转
       }
     }
   }
  // ...  
}