写树莓派os时踩的一个坑

最近毕设树莓派上写os时碰到一个坑。一个简单的用于测试的led灯通过gpio端口点亮的程序中的一个函数在内联模式下能够正常让小灯间隔n(ms)点亮,在函数模式下小灯就没反应。卡了好几天终于解决了。这里总结一下。

首先函数如下

1
2
3
4
5
6
#[inline(never)]
fn spin_sleep_ms(ms: usize) {
    for _ in 0..(ms * 6000) {
        unsafe { asm!("nop" :::: "volatile"); }
    }
}

因为是裸板程序,没有其它依赖。一开始考虑是否是栈的问题,然而在汇编的bootloader中有如下代码的

1
2
3
4
5
6
7
8
2:
    // set the stack to start before our boot code
    adr     x1, _start
    mov     sp, x1

    // jump to kinit, which shouldn't return. halt if it does
    bl      kinit
    b       1b

注意到最终会把寄存器sp设置为_start。而在一个layout.ld文件中有

SECTIONS {
  . = 0x4000000; /* bootloader start; leave ~64MiB free */
  ...

可以看到有0x4000000,因此我一开始就排除了是栈的问题,转而考虑其它可能的情况。。。

然后就开始了各种配药过程。

1. 反汇编看看编译出来的代码是否有问题

1
cargo objdump -- -disassemble -no-show-raw-insn -print-imm-hex build/$(KERN).elf

失败,感觉没有问题。

2. 用qemu翻译指令

1
2
3
4
5
qemu-system-aarch64 \
    -nographic \
    -M raspi3 \
    -serial null -serial mon:stdio \
    -kernel build/blinky.elf -d in_asm

失败,翻译的代码块还是没有问题。

3. 用qemu模拟执行打印

1
2
3
4
5
qemu-system-aarch64 \
    -nographic \
    -M raspi3 \
    -serial null -serial mon:stdio \
    -kernel build/blinky.elf -d trace:memory_region*

失败,两种情况都对正确的位置写入数据。

4. 板子问题

。。。。是的,我以为有可能是板子坏了,cpu调用bl就炸,所以我又去买了一块板子。。。然后还是无法点亮。

5. 用jlink调试

这次方向对了,买了jlink,顺丰一天多后就到了,在内联模式下能够正常单步调试。然后在函数的时候,gdb里会直接显示指令未定义。

然后我在调用该函数之前的kinit()函数中用内联方式暂停5s(一开始配药发现这么做是ok),然后调用有问题的函数。趁5s的间隔,把jlink连接上了,这次gdb里指令显示没有问题。然后单步单步,终于找到了崩掉的那条指令如下

0000000004000060 blinky::spin_sleep_ms::h1f0194988446209e.llvm.10254504060701608934:
 4000060:       mov     w8, #0x27c0
 4000064:       movk    w8, #0x9, lsl #16
 4000068:       subs    x8, x8, #0x1
 400006c:       nop
 4000070:       b.ne    #-0x8 <blinky::spin_sleep_ms::h1f0194988446209e.llvm.10254504060701608934+0x8>
 4000074:       ret

0000000004000078 blinky::kmain::h1a7bcb40ef77b1ac:
 4000078:       str     x20, [sp, #-0x20]!
 400007c:       stp     x19, x30, [sp, #0x10]
 4000080:       mov     w19, #0x4
 4000084:       movk    w19, #0x3f20, lsl #16
 4000088:       orr     w8, wzr, #0x40000
 400008c:       orr     w20, wzr, #0x10000
 4000090:       str     w8, [x19]
 4000094:       str     w20, [x19, #0x18]
 4000098:       bl      #-0x38 <blinky::spin_sleep_ms::h1f0194988446209e.llvm.10254504060701608934>
 400009c:       str     w20, [x19, #0x24]
 40000a0:       bl      #-0x40 <blinky::spin_sleep_ms::h1f0194988446209e.llvm.10254504060701608934>
 40000a4:       b       #-0x10 <blinky::kmain::h1a7bcb40ef77b1ac+0x1c>

如上,0x4000078位置处str x20, [sp, #-0x20]!这条指令执行完之后,gdb里面显示的指令就又进入未定义状态。所以,还是sp栈的问题。。。。。

然后有一个奇怪的问题在于,用gdb调试时,指令的地址都是在0x60附近,甚至0x0也有指令。所以怀疑其实代码并没有被载入0x4000000的位置,其实被载入0x0

然后查到启动时用到的config.txt文件里面的参数old_kernel=1然后查官方文档,这个参数置1的话,内核会被载入0x0的位置。

然后改该文件的参数如下

1
2
3
4
5
arm_64bit=1
kernel=kernel8.img
disable_commandline_tags=1
enable_jtag_gpio=1
kernel_address=0x80000

再跑程序就正常了。