RISC-V assembly (easy)
问题
- 哪些寄存器保留函数的参数?例如,在
main
函数对printf
的调用中,哪个寄存器保留13? main
函数中哪里调用了汇编代码中的f
函数和g
函数?(编译器可能内联函数)printf
位于哪个地址?- 紧接着在
main
中跳转到printf
之后,寄存器ra
中保留的值是什么? 运行下列代码:
unsigned int i = 0x00646c72;printf("H%x Wo%s", 57616, &i);
输入什么?
输入取决于RISC-V是小端序(little-endian)的事实。若RISC-V是大端序(big-endian),则i
该当设置成什么能力给出雷同的输入?须要扭转57616
吗?下列代码中,
y=
之后将会输入什么?(非特定值)为何?printf("x=%d y=%d", 3);
答复
- a0 a1 a2(main函数只有这仨,实际上到a7); a2保留13
- 在0x26处,看到间接将12给寄存器a1,阐明f的调用被优化掉了
- 在0x34处,可知
jalr
跳转到1554 + ra
地址处。而依据0x30处,寄存器ra保留了0x30的地址,因而jalr
跳转到1554(dec)+0x30(hex)=0x642(hex)的指标地址,因而printf的地址是0x642,发现与正文雷同 - 0x38
输入:HE110 World
- 首先
%x
输入57616的十六进制模式 - 而后是值为0x00646c72的
i
,%s
实际上读取的是字符串,也就是一个又一个char
,所以i
这个int
类型实际上能够看作由4个char
类型组成,而后就是这4个char
怎么排列的问题 - 大端序即:0x12345678 -> 0x 12 34 56 78(从左到右地址序号增大)
- 小端序即:0x12345678 -> 0x 78 56 34 12(从左到右地址序号增大)
- 材料指出RISC-V采纳小端序,由此可知i在内存当中的贮存为:0x 72 6c 64 00
- 因而会先读取值为
0x72
的char
类型,在ascii中对应字母r
;而后是0x6c
,对应字母l
;0x64
对应d
;0x00
对应终止
因而若是大端序,则i应设置为:
i = 0x646c7200
。而57616
不须要变,因为没有产生扭转大小的类型转变。- 首先
- 实践上讲,因为传入两个参数,因而a0和a1寄存器都保留了确定的值,但在printf中因为应用了第三个参数,因而会应用a2的值作为
y=
后边的输入值;但a2寄存器中的值并不能确定,因而不确定输入后果。
但实际上,我尝试屡次都是1。可能是因为没有函数应用寄存器a2,a2就始终放弃值为1的状态吧
Backtrace (moderate)
要求
回溯对debug十分有用:在error产生时刻的堆栈上零碎调用的一个列表。为实现回溯,编译器生成机器码,在堆栈上保护以后调用链上每个函数对应的堆栈帧。每个堆栈帧由返回地址和一个指向调用者堆栈帧的帧指针组成。寄存器s0
包含一个指向以后堆栈帧的指针(理论指向堆栈上保留的返回地址+8).你的backtrace
该当应用帧指针遍历堆栈并打印堆栈帧上保留的返回地址。
在
kernel/printf.c
中实现backtrace()
。在sys_sleep
中调用此函数。运行命令bttest
,会调用sys_sleep
。输入格局该当如下所示:backtrace:0x0000000080002cda0x0000000080002bb60x0000000080002898
在终端运行命令
addr2line -e kernel/kernel
并将backtrace输入的地址复制粘贴过去就能失去以下输入:kernel/sysproc.c:74kernel/syscall.c:224kernel/trap.c:85
提醒
- 在
defs.h
中增加定义。 GCC编译器将以后函数的帧指针保留在寄存器
s0
中。在kernel/risv.h
中增加上面的函数:static inline uint64 r_fp() { uint64 x; asm volatile("mv %0, s0" : "=r" (x) ); return x;}
在
backtrace
中调用该函数可通过内联的机器码读取寄存器s0
堆栈帧布局示意图(如下)。留神,返回地址位于堆栈帧帧指针固定偏移-8的中央;以及保留的帧指针位于(以后)帧指针固定偏移-16的地位。
Stack . . +-> . | +-----------------+ | | | return address | | | | previous fp ------+ | | saved registers | | | local variables | | | ... | <-+ | +-----------------+ | | | return address | | +------ previous fp | | | saved registers | | | local variables | | +-> | ... | | | +-----------------+ | | | return address | | | | previous fp ------+ | | saved registers | | | local variables | | | ... | <-+ | +-----------------+ | | | return address | | +------ previous fp | | | saved registers | | | local variables | |$fp --> | ... | | +-----------------+ | | return address | | | previous fp ------+ | saved registers |$sp --> | local variables | +-----------------+
backtrace
须要辨认最初的栈帧以进行递归。一个有用的事实是,为每个内核堆栈调配的内存由单个对齐内存页组成,因而给定堆栈上的所有堆栈帧都在同一内存页上。你能够应用PGROUNDDOWN(fp)
辨认帧指针援用的内存页
如果backtrace
运行失常,能够在panic
中调用它。
实现
- 在
defs.h
中增加定义(略) - 将提醒给的
r_fp()
函数复制到risv.h
(略) backtrace
实现思路:- 首先还是能够确定,最简略的办法还是递归调用一个打印函数。
- 递归就须要确定递归终止条件,这次提醒中也给出了终止条件——堆栈帧都保留在同一内存页上,即帧指针都在同一内存页。由此可得递归终止条件:若以后堆栈帧中保留的前一个帧指针与以后帧指针不在同一内存页,则阐明保留的并非帧指针,就能够终止递归了。
- 而后就是返回地址获取办法:以后帧指针偏移-8后解援用
- 前一个帧指针获取办法:以后帧指针偏移-16后解援用
在
printf.c
中实现backtrace
void printFramePointer(uint64 fp) { uint64 pre_fp = *(uint64*)(fp - 16); printf("%p\n", *(uint64*)(fp - 8)); if (PGROUNDDOWN(fp) == PGROUNDDOWN(pre_fp)) { printFramePointer(pre_fp); }}void backtrace(void) { printFramePointer(r_fp());}
- 在
sys_sleep()
中增加backtrace()
(略)