我已经开始了裸机嵌入式编程之旅,我正在尝试运行我的“Hello, world!”使用 Qemu 模拟树莓派 2 的代码示例。
根据不同来源的文档和教程,我成功地用 Rust 编译了
armv7a-none-eabi
的代码,将代码的开头放在 .elf
文件中的地址 0x8000 处,因为我读到树莓派的代码开始于这个地址。我的代码初始化 UART mini、UART mini 的 GPIO 14 和 15,并将符号发送到 UART mini 数据寄存器。
由于没有任何反应,我尝试使用 GDB 进行调试。然而,当用GDB连接到QEMU时,代码指针位于0x0000而不是0x8000,这让我觉得确实出了问题。
我像这样启动 Qemu:
qemu-system-arm -M raspi2b -kernel target/binary/my_custom_code.img -nographic -s -S
然后,我启动GDB:
gdb target/armv7a-none-eabi/debug/my_custom_code.elf
我连接到 Qemu 进程:
(gdb) target remote localhost:1234
Remote debugging using localhost:1234
0x00000000 in ?? ()
此后,什么也没有发生。任何
step
、next
都会抛出“无法找到当前函数的边界”错误,并且 continue
会挂起。我可以放置断点,但我猜这是因为我在 gdb 中加载了我的文件,而不是因为我连接到了正确运行的程序。
此外,当我尝试运行
info reg
时,我没有获得 BCM2837-ARM
的标准寄存器名称,这让我更加认为 qemu 没有正确运行树莓派或我的程序。
最后,我尝试使用迷你 UART 从树莓派向控制台显示文本,但由于它不起作用,我开始使用 GDB 进行调试,现在看起来没有任何东西运行正常。
我在
NixOS v24.05.20231209.666fc80
上完成所有这些工作,使用的 shell 使用包 gcc-arm-embedded
、qemu
gdb
。
我的树莓派模拟或者我的GDB进程有问题吗?为什么代码指针不在 0x8000 处?我是否错过了有关将迷你 UART 连接到 Qemu 控制台的其他内容?
如果需要,我很乐意提供更多信息。
QEMU 的树莓派模型启动二进制文件的方式与固件在真实硬件 pi 上的启动方式不完全相同。你有两个选择:
(1) 根据内核记录的要求(包括能够在任意地址加载之类的内容),使您的二进制文件期望像 Linux 内核一样启动,并将二进制文件传递给 -kernel 选项作为原始二进制文件、zImage 或类似文件
(2) 使您的二进制文件成为 ELF 文件,将二进制文件加载到正确的位置,并将 ELF 文件传递给 -kernel 或“通用加载器”。 QEMU 将遵循 A-profile Arm ELF 文件的 ELF 入口点。请注意,所有 CPU 将同时在同一位置启动,如果您不关心辅助 CPU,则您的二进制文件必须将它们整理出来并将其放入暂存笔中!
无论哪种方式,您都需要确保您的映像包含重置向量和其他中断/异常入口点处的代码,并具有一些汇编代码,以便在进入非汇编之前根据 Rust 的任何要求设置 CPU代码(例如,它可能期望启用 FPU)。如果您能找到有关如何执行此操作的现有教程,或者至少改编一个已知的工作 C 示例,而不是尝试通过自己遇到的问题来解决所有问题,您会发现速度要快得多。
对于 gdb,您需要使用理解 Arm 架构的 gdb。这通常不是标准的“gdb”,它仅在主机体系结构支持的情况下进行编译。您可以使用 gdb 命令“set arch arm”对此进行测试;如果 gdb 说“目标架构设置为“arm””,则它具有 Arm 支持;如果它显示“未定义的项目:“arm””,那么它就不会,您需要使用编译了 Arm 支持的 gdb。这可能是一个名为“gdb-multiarch”的二进制文件或包(它支持许多架构) )或者它可能是专门构建的带有跨 gdb 手臂支持的 gdb:您需要检查您的发行版如何打包它。