Appearance
6.1810 使用的工具与RISC-V入门
6.1810 使用的工具
本课程需要 RISC-V 版本的 QEMU 7.2+、GDB 8.3+、GCC 和 Binutils。
如果您在设置过程中遇到问题,请到 office hours 或在 Piazza 上发帖。我们很乐意提供帮助!
Debian 或 Ubuntu
bash
sudo apt-get install git build-essential gdb-multiarch qemu-system-misc gcc-riscv64-linux-gnu binutils-riscv64-linux-gnu您可能需要运行 Ubuntu 24 (或更高版本) 才能通过 apt-get 安装足够新的 qemu。
Arch Linux
bash
sudo pacman -S riscv64-linux-gnu-binutils riscv64-linux-gnu-gcc riscv64-linux-gnu-gdb qemu-emulators-full在 Windows 上安装
我们鼓励运行 Windows 的学生在本地机器上安装 Linux 或使用 WSL 2 (Windows Subsystem for Linux 2)。
我们也鼓励学生安装 Windows Terminal 工具来代替 Powershell/Command Prompt。
要使用 WSL 2,首先请确保您已安装 Windows Subsystem for Linux。然后从 Microsoft Store 添加 Ubuntu 24.04。之后您应该能够启动 Ubuntu 并与机器交互。
重要提示:请确保您运行的是 WSL 版本 2。WSL 1 不适用于 6.1810 实验。要检查,请在 Windows 终端中运行 wsl -l -v 以确认已安装 WSL 2 和正确的 Ubuntu 版本。
要安装本课程所需的所有软件,请运行:
bash
$ sudo apt-get update && sudo apt-get upgrade
$ sudo apt-get install git build-essential gdb-multiarch qemu-system-misc gcc-riscv64-linux-gnu binutils-riscv64-linux-gnu在 Windows 中,您可以在 \\wsl$\\ 目录下访问所有 WSL 文件。例如,Ubuntu 20.04 安装的主目录应该在 \\wsl$\\Ubuntu-20.04\\home\\<username>\\。
在 macOS 上安装
首先,安装开发者工具:
bash
$ xcode-select --install接下来,安装 Homebrew,一个 macOS 的包管理器:
bash
$ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"接下来,安装 RISC-V 编译器工具链:
bash
$ brew tap riscv/riscv
$ brew install riscv-toolsbrew 公式可能不会链接到 /usr/local。您需要更新 shell 的 rc 文件 (例如 ~/.bashrc) 以将适当的目录添加到 $PATH。
bash
PATH=$PATH:/usr/local/opt/riscv-gnu-toolchain/bin最后,安装 QEMU:
bash
brew install qemuRISC-V 调用约定、栈帧和 GDB
C代码被编译成机器指令。理解这层转换对于编写无法用C表达的代码(例如,在系统调用实验中)至关重要。
RISC-V 抽象机器
- 基础: 程序计数器 (PC), 32个通用寄存器 (x0-x31)。
- 寄存器约定:
ra(x1): 返回地址 (由调用者保存)。sp(x2): 栈指针 (由被调用者保存)。a0-a7(x10-x17): 函数参数和返回值。s0-s11(x8-x9, x18-x27): 被调用者保存的寄存器。如果一个函数要使用它们,必须在函数开头将它们保存在栈上,并在返回前恢复。t0-t6(x5-x7, x28-x31): 调用者保存的临时寄存器。函数可以自由使用它们,无需保存。
函数调用
call指令:call实际上是一个伪指令,它做两件事:- 将返回地址(即
call指令的下一条指令的地址)存入ra寄存器。 - 无条件跳转到目标函数的标签处。
- 将返回地址(即
ret指令:ret也是一个伪指令,它跳转到ra寄存器中保存的地址。
问题: 如果函数 f 调用了函数 g,g 又调用了 h,会发生什么?
g调用h时,会覆盖ra寄存器,导致g无法正确返回到f。- 解决方案: 在函数(非叶子函数)的开头(称为函数序言, prologue),必须在栈上为自己创建一个栈帧 (stack frame),并将
ra和其他需要保留的s寄存器保存在栈帧中。在函数返回前(称为函数尾声, epilogue),再从栈帧中恢复这些寄存器。
栈帧 (Stack Frame)
栈是向下增长的。一个典型的栈帧结构如下:
(高地址)
+-----------------+
| 返回地址 (ra) |
+-----------------+
| 上一个栈帧的 fp |
+-----------------+
| 保存的 s 寄存器 |
+-----------------+
| 局部变量 |
+-----------------+
(低地址) <- spsp(栈指针) 指向当前栈帧的顶部。fp(帧指针,s0) 指向当前栈帧的底部。通过fp可以方便地访问局部变量和参数。
GDB 调试技巧
layout split: 同时显示代码、汇编和寄存器,非常方便。stepi/si: 执行单条汇编指令。nexti/ni: 执行单条汇编指令,但会跳过函数调用。info registers: 显示所有寄存器的值。p $reg: 打印特定寄存器reg的值。x/Nfu addr: 检查内存。例如x/10i $pc表示显示从PC开始的10条指令;x/4g $sp表示以8字节(giant word)为单位显示从sp开始的4个值。backtrace/bt: 显示函数调用栈。info frame: 显示当前栈帧的详细信息。frame N: 切换到编号为N的栈帧。