Skip to content

Latest commit

 

History

History
131 lines (90 loc) · 5.49 KB

lab4_RISCV.md

File metadata and controls

131 lines (90 loc) · 5.49 KB

MIT 6.S081 - Lab Traps - RISC-V assembly

题面

了解一点 RISC-V 汇编是很重要的,您在 6.004 中已经接触过。

在 xv6 仓库中有一个文件 user/call.cmake fs.img 对它进行编译,并在 user/call.asm 中生成可读的程序汇编版本。

请阅读 call.asm 中的函数 gfmain。RISC-V 的使用手册参见 参考页面

下面是一些需要回答的问题(将答案存储在文件 answers-traps.txt 中):

  • 哪些寄存器包含函数的参数?例如,在 main 调用 printf 时,哪个寄存器保存了 13?

  • main 的汇编代码中,对函数 f 的调用在哪里?g 在哪里?(提示:编译器可以内联函数)

  • 函数 printf 位于什么地址?

  • main 中的 jalrprintf 之后,ra 寄存器中的值是什么?

  • 运行下面的代码。

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

    输出是什么?这是一个 ASCII 表,它将字节映射到字符。

    输出结果取决于 RISC-V 是小端序的。如果 RISC-V 是大端序的,你会将 i 设置为什么以产生相同的输出?你是否需要将 57616 更改为其他值?

    这是对 little- endian 和 big-endian 的描述一个更古怪的描述

  • 在下面的代码中,'y=' 后面会打印什么?(注意:答案不是一个特定的值)为什么会这样?

    printf("x=%d y=%d", 3);

讲解

这个 part 就是来复习计算机组成原理的。

先上一段汇编。

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>

回答问题

  1. 哪些寄存器包含函数的参数?例如,在 main 调用 printf 时,哪个寄存器保存了 13?

寄存器 a0 - a7 包含函数的参数,这是我们在 syscall 实验中就已经知道的。寄存器 a2 保存了 13。

  1. main 的汇编代码中,对函数 f 的调用在哪里?g 在哪里?(提示:编译器可以内联函数)

main 函数中对 f、g 函数的调用被编译器优化成内联函数了,这是一种取消了函数的参数压栈,减少了调用的开销的编译器的优化方法。在这里 f(8)+1 应该是直接被编译器算出 12 的结果了,就免去了调用函数的过程。不过很明显像是 printf 和 exit 这样的函数就不一样了。

  1. 函数 printf 位于什么地址?

函数 printf 位于 0x630。

  1. main 中的 jalrprintf 之后,ra 寄存器中的值是什么?

jalr offset(ra),ra 存储下一条汇编语句的地址,那么在这里就是 0x38。(jalr 格式不一样就不在这里细谈了)

  1. 运行下面的代码。输出是什么?输出结果取决于 RISC-V 是小端序的。如果 RISC-V 是大端序的,你会将 i 设置为什么以产生相同的输出?你是否需要将 57616 更改为其他值?
unsigned int i = 0x00646c72;
printf("H%x Wo%s", 57616, &i);

输出是 “HE110 World”。

  • 57616 按照 16 进制转换为 e110,这个不需要进行更改;
  • rld:其 ASCII 码是 72、6c、64(16 进制);如果要显示 r l d 的话,那么在内存中的布局就应该是 72 6c 64 00。
    • 如果使用小端的话,那么内存中的存储的方式应该是低字节的数据存在低地址,那么就应该写实际字节的写法就是 0x00646c72,这和题目相符;
    • 如果使用大端的话,那么内存中的存储的方式应该是低字节的数据存在高地址,那么就应该写实际字节的写法就是 0x726c6400。

大端小端的问题容易弄混的地方是:内存从左到右是地址变大,而我们说的数据的字节是从右到左越来越 “高” 位。实际上大端模式是和我们阅读的习惯一致的。

  1. 在下面的代码中,'y=' 后面会打印什么?(注意:答案不是一个特定的值)为什么会这样?
printf("x=%d y=%d", 3);

打印 5223。答案确实不是一个特定的值(虽然重新运行、重新编译都不会导致结果不同)。推测是打印出了 a2 寄存器的值,所以实际上应该是一个相当随机的数。