Skip to main content

Gatech CS3210 学习笔记 - lab1

· 6 min read

lab1 的内容非常简单,就是一个简单的点灯实验。我在刚开始接触课程时就尝试了,系列开篇也写了我烧录了 lab1 的代码并且成功了,但直到最近我才完成了 lab1。时间拉得这么长,除了因为其他原因搁置了 CS3210 的学习,还遇到了一个比较麻烦的问题。

实验内容#

要做的事非常简单,将一个 U 盘格式化,写十行代码,编译烧录看结果。格式化 U 盘没什么难度,单分区 FAT32 格式就可以了。然而我每次用 fdisk 格式化都忘记改分区类型标志导致 rpi 无法引导,要是用 GUI 还不会遇到这种差错。 代码方面,初始化配置、寄存器地址之类的代码都已经给好了,只需要分别用两种语言编写 GPIO 配置和死循环闪烁的代码。我写好 C 语言部分烧录上电也确实没问题,但是到了 Rust 部分就怎么也没有反应,先给出代码。

int kmain(void) {  // FIXME: STEP 1: Set GPIO Pin 16 as output.  *GPIO_FSEL1 = 1<<18;  // FIXME: STEP 2: Continuously set and clear GPIO 16.  while(1){    *GPIO_SET0 = 1<<16;    spin_sleep_ms(1000);    *GPIO_CLR0 = 1<<16;    spin_sleep_ms(1000);  }}
unsafe fn kmain() -> ! {    // FIXME: STEP 1: Set GPIO Pin 16 as output.    GPIO_FSEL1.write_volatile(1<<18);    // FIXME: STEP 2: Continuously set and clear GPIO 16.    loop {        GPIO_SET0.write_volatile(1<<16);        spin_sleep_ms(1000);        GPIO_CLR0.write_volatile(1<<16);        spin_sleep_ms(1000);    }}

我对着代码调试了两天都没有看出来为什么只有 Rust 编译出的文件无法正常工作,后来我决定从汇编入手,于是开始了汇编的学习...

解决问题#

从最表面的现象看不出任何问题:上面给出的两块代码语法都是正确的,上电时 rpi 上的指示灯表现也是一致的,用示波器测过 GPIO 电平确定了不是因为闪烁太快没观察到。而反编译出的汇编代码并不是一看就看得懂的,前几天我和另一个同学一起调试了两个小时才最终确定问题出在课程给出的初始化部分代码上。用了这么久才找出问题除了我没有汇编基础还因为我对课程资料太过信任。相对的,不知道代码来源的同学从头到尾检查了所有内容才得以顺利解决了问题。 这个问题还是比较有意思的。负责初始化配置的汇编代码将栈指针(register sp)指向了控制代码的开头导致栈处于无法使用的状态。使用 C 语言实现的程序没有用到栈所以正常执行了,使用 Rust 语言的程序也没有显式使用栈,但编译器得到的 kmain 函数中使用了 x20 寄存器(register x20)。这个寄存器在 arm64 的函数调用约定中用于数据存储而非参数传递,同时由于 Rust 部分代码是由汇编代码调用执行的,所以在 kmain 函数开头由于保存 x20 寄存器的值造成了入栈操作,引发了程序崩溃。下面给出代码。

//初始化部分.section .text.init
.global _start
//省略部分代码    // set the stack to start before our boot code    //adr     x1, _start    ldr     x1, =0x40000200    mov     sp, x1
    // jump to kinit, which shouldn't return. halt if it does    bl      kinit    b       1b//kinit用于清除bss及调用kmain//kmain反编译结果 4000080:       str     x20, [sp, #-0x20]! 4000084:       stp     x19, x30, [sp, #0x10] 4000088:       mov     w19, #0x4 400008c:       movk    w19, #0x3f20, lsl #16 4000090:       orr     w8, wzr, #0x40000 4000094:       orr     w20, wzr, #0x10000

大多数情况下树莓派都使用满递减堆栈,所以栈指针应处于内存最高位。Stanford CS107e 的相关代码同样是设置在最高位。对于这个 lab,将栈指针设置在任意较高的位置都可以正常执行。 对于为什么要如此配置栈指针我尝试 Email 了课程页面上的联系方式但还没有得到回信。