RISC-V assembly (easy)

问题

  1. 哪些寄存器保留函数的参数?例如,在main函数对printf的调用中,哪个寄存器保留13?
  2. main函数中哪里调用了汇编代码中的f函数和g函数?(编译器可能内联函数)
  3. printf位于哪个地址?
  4. 紧接着在main中跳转到printf之后,寄存器ra中保留的值是什么?
  5. 运行下列代码:

    unsigned int i = 0x00646c72;printf("H%x Wo%s", 57616, &i);

    输入什么?
    输入取决于RISC-V是小端序(little-endian)的事实。若RISC-V是大端序(big-endian),则i该当设置成什么能力给出雷同的输入?须要扭转57616吗?

  6. 下列代码中,y=之后将会输入什么?(非特定值)为何?

    printf("x=%d y=%d", 3);

答复

  1. a0 a1 a2(main函数只有这仨,实际上到a7); a2保留13
  2. 在0x26处,看到间接将12给寄存器a1,阐明f的调用被优化掉了
  3. 在0x34处,可知jalr跳转到1554 + ra地址处。而依据0x30处,寄存器ra保留了0x30的地址,因而jalr跳转到1554(dec)+0x30(hex)=0x642(hex)的指标地址,因而printf的地址是0x642,发现与正文雷同
  4. 0x38
  5. 输入: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
    • 因而会先读取值为0x72char类型,在ascii中对应字母r;而后是0x6c,对应字母l0x64对应d0x00对应终止

    因而若是大端序,则i应设置为:i = 0x646c7200。而57616不须要变,因为没有产生扭转大小的类型转变。

  6. 实践上讲,因为传入两个参数,因而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中调用它。

实现

  1. defs.h中增加定义(略)
  2. 将提醒给的r_fp()函数复制到risv.h(略)
  3. backtrace实现思路:

    • 首先还是能够确定,最简略的办法还是递归调用一个打印函数。
    • 递归就须要确定递归终止条件,这次提醒中也给出了终止条件——堆栈帧都保留在同一内存页上,即帧指针都在同一内存页。由此可得递归终止条件:若以后堆栈帧中保留的前一个帧指针与以后帧指针不在同一内存页,则阐明保留的并非帧指针,就能够终止递归了。
    • 而后就是返回地址获取办法:以后帧指针偏移-8后解援用
    • 前一个帧指针获取办法:以后帧指针偏移-16后解援用
  4. 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());}
  5. sys_sleep()中增加backtrace()(略)

后果

运行后果

测试后果