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: 0x0000000080002cda 0x0000000080002bb6 0x0000000080002898
在终端运行命令
addr2line -e kernel/kernel
并将 backtrace 输入的地址复制粘贴过去就能失去以下输入:kernel/sysproc.c:74 kernel/syscall.c:224 kernel/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()
(略)