了解一点 RISC-V 汇编是很重要的,您在 6.004 中已经接触过。
在 xv6 仓库中有一个文件 user/call.c
。make fs.img
对它进行编译,并在 user/call.asm
中生成可读的程序汇编版本。
请阅读 call.asm
中的函数 g
、f
和 main
。RISC-V 的使用手册参见 参考页面。
下面是一些需要回答的问题(将答案存储在文件 answers-traps.txt
中):
-
哪些寄存器包含函数的参数?例如,在
main
调用printf
时,哪个寄存器保存了 13? -
在
main
的汇编代码中,对函数f
的调用在哪里?g
在哪里?(提示:编译器可以内联函数) -
函数
printf
位于什么地址? -
在
main
中的jalr
到printf
之后,ra
寄存器中的值是什么? -
运行下面的代码。
unsigned int i = 0x00646c72; printf("H%x Wo%s", 57616, &i);
输出是什么?这是一个 ASCII 表,它将字节映射到字符。
输出结果取决于 RISC-V 是小端序的。如果 RISC-V 是大端序的,你会将
i
设置为什么以产生相同的输出?你是否需要将57616
更改为其他值? -
在下面的代码中,
'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>
回答问题:
- 哪些寄存器包含函数的参数?例如,在
main
调用printf
时,哪个寄存器保存了 13?
寄存器 a0 - a7 包含函数的参数,这是我们在 syscall 实验中就已经知道的。寄存器 a2 保存了 13。
- 在
main
的汇编代码中,对函数f
的调用在哪里?g
在哪里?(提示:编译器可以内联函数)
main 函数中对 f、g 函数的调用被编译器优化成内联函数了,这是一种取消了函数的参数压栈,减少了调用的开销的编译器的优化方法。在这里 f(8)+1
应该是直接被编译器算出 12 的结果了,就免去了调用函数的过程。不过很明显像是 printf 和 exit 这样的函数就不一样了。
- 函数
printf
位于什么地址?
函数 printf
位于 0x630。
- 在
main
中的jalr
到printf
之后,ra
寄存器中的值是什么?
jalr offset(ra)
,ra 存储下一条汇编语句的地址,那么在这里就是 0x38。(jalr 格式不一样就不在这里细谈了)
- 运行下面的代码。输出是什么?输出结果取决于 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。
大端小端的问题容易弄混的地方是:内存从左到右是地址变大,而我们说的数据的字节是从右到左越来越 “高” 位。实际上大端模式是和我们阅读的习惯一致的。
- 在下面的代码中,
'y='
后面会打印什么?(注意:答案不是一个特定的值)为什么会这样?
printf("x=%d y=%d", 3);
打印 5223。答案确实不是一个特定的值(虽然重新运行、重新编译都不会导致结果不同)。推测是打印出了 a2 寄存器的值,所以实际上应该是一个相当随机的数。