实验描述及解答
理解一点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?
call.c中的代码如下:
#include "kernel/param.h"
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
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);
}
在Makefile中添加后(不知道怎么改xv-6的Makefile,可以参考第二章 实验:实现trace系统调用,这个实验过程基本包含了在xv-6下增加文件后,更改声明及编译规则的一般步骤),通过
make qemu
make fs.img
得到call.c对应的call.asm,在该文件的45行,(这里只列出相关的一部分,完整的call.asm见文章最后给出gitee仓库):
24: 4635 li a2,13
也就是a2
保存了13的值。
- main的汇编代码中对函数f的调用在哪里?对g的调用在哪里(提示:编译器可能会将函数内联)
在call.asm中,没有对应的汇编代码来调用。查了资料上因为g和f这两个函数都被内链进main里了。
- printf函数位于哪个地址?
这次我们不直接看call.asm文件了,汇编代码的可读性是真难受,通过gdb来查看。
运行
make CPUS=1 qemu-gdb
再开一个窗口运行
riscv64-unknown-elf-gdb
进入gdb后,
source .gdbinit
file user/_call //进入用户程序的调试
b main //在main处打一个断点
然后运行后,再在shell中执行call。gbd停在call进程的main里,接下来
b *0x34 //在调用printf的地址添加一个断点
运行到断点后,查看寄存器信息
info register //根据提示输入RET获得更多寄存器信息
第一问中的答案在这里也得到了印证。
然后
disas printf //得到printf的路口地址在
得到
0x65a <printf> addi sp,sp,-96
也就是printf的地址在0x65a
。
- 在main中printf的jalr之后的寄存器ra中有什么值?
重新执行一遍call程序,在0x34处打下断点,查看一下要跳转的地址在0x65a
,在这里打下断点后,执行,然后在gdb中查看寄存器ra(return address)
执行
i r ra
得到
ra 0x38 0x38 <main+28>
这一问就pass。
- 运行以下代码。
unsigned int i = 0x00646c72;
printf("H%x Wo%s", 57616, &i);
程序的输出是什么?
输出是
HE110 World
这是将字节映射到字符的ASCII码表。
输出取决于RISC-V小端存储的事实。如果RISC-V是大端存储,为了得到相同的输出,你会把i设置成什么?是否需要将57616更改为其他值?
i = 0x726c6400
57616的大小端序是一样的,不用更改。
这里有一个小端和大端存储的描述和一个更异想天开的描述。
6.在下面的代码中,“y=”之后将打印什么(注:答案不是一个特定的值)?为什么会发生这种情况?
printf("x=%d y=%d", 3);
打印
x=3 y=8229
第二个值是从a2寄存器读出来的,a2寄存器中是什么值打印出来的就是什么值。
实验所有代码见Lab4_assembly。