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                addisp,sp,-16
    2:e422                sds0,8(sp)
    4:0800                addis0,sp,16
   return x+3;
 }
    6:250d                addiwa0,a0,3
    8:6422                lds0,8(sp)
    a:0141                addisp,sp,16
    c:8082                ret
 
 000000000000000e <f>:
 
 int f(int x) {
    e:1141                addisp,sp,-16
   10:e422                sds0,8(sp)
   12:0800                addis0,sp,16
   return g(x);
 }
   14:250d                addiwa0,a0,3
   16:6422                lds0,8(sp)
   18:0141                addisp,sp,16
   1a:8082                ret
 
 000000000000001c <main>:
 
 void main(void) {
   1c:1141                addisp,sp,-16
   1e:e406                sdra,8(sp)
   20:e022                sds0,0(sp)
   22:0800                addis0,sp,16
   printf("%d %d\n", f(8)+1, 13);
   24:4635                lia2,13
   26:45b1                lia1,12
   28:00000517          auipca0,0x0
   2c:7b050513          addia0,a0,1968 # 7d8 <malloc+0xea>
   30:00000097          auipcra,0x0
   34:600080e7          jalr1536(ra) # 630 <printf>
   exit(0);
   38:4501                lia0,0
   3a:00000097          auipcra,0x0
   3e:27e080e7          jalr638(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 寄存器的值,所以实际上应该是一个相当随机的数。

最后修改:2022 年 12 月 14 日
如果觉得我的文章对你有用,请随意赞赏