在 qemu 上调试远程 arm 目标时断点对 gdb 不起作用

Breakpoints not working for gdb while debugging remote arm target on qemu

我编译了这个裸机示例:https://github.com/s-matyukevich/raspberry-pi-os/tree/master/src/lesson01。我修改了 Makefile 以具有如下调试符号:

diff --git a/src/lesson01/Makefile b/src/lesson01/Makefile
index 4f92a49..daa1f7d 100644
--- a/src/lesson01/Makefile
+++ b/src/lesson01/Makefile
@@ -1,7 +1,7 @@
-ARMGNU ?= aarch64-linux-gnu
+ARMGNU ?= /opt/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu

-COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only
-ASMOPS = -Iinclude
+COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only -g
+ASMOPS = -Iinclude -g

 BUILD_DIR = build
 SRC_DIR = src
@@ -27,5 +27,5 @@ DEP_FILES = $(OBJ_FILES:%.o=%.d)
 -include $(DEP_FILES)

 kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES)
-       $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf  $(OBJ_FILES)
+       $(ARMGNU)-ld -g -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf  $(OBJ_FILES)
        $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img

从一个终端,我 运行 qemu 上的程序 :

$ qemu-system-aarch64 -M raspi3 -kernel kernel8.img -display none -serial null -serial stdio -S -s

我从其他终端启动 gdb :

$ /opt/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-gdb ./build/kernel8.elf -ex 'target remote localhost:1234' -ex 'break _start' -ex 'break kernel_main' -ex 'continue'

但从未命中断点。

GNU gdb (GNU Toolchain for the A-profile Architecture 10.3-2021.07 (arm-10.29)) 10.2.90.20210621-git
Copyright (C) 2021 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "--host=x86_64-pc-linux-gnu --target=aarch64-none-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://bugs.linaro.org/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./build/kernel8.elf...
Remote debugging using localhost:1234
_start () at src/boot.S:7
7       mrs x0, mpidr_el1
Breakpoint 1 at 0x0: file src/boot.S, line 7.
Breakpoint 2 at 0x234: file src/kernel.c, line 5.
Continuing.

我做错了什么?我查看了所有相关问题,但没有任何帮助。这是我的图片供参考:kernel8.elf and kernel8.img

编辑

当我 continue 时,我的控制台上确实出现了 Hello, world!,所以 kernel8.img 可以正常启动。另外,供参考:

$ qemu-system-aarch64 --version
QEMU emulator version 4.2.1 (Debian 1:4.2-3ubuntu6.18)
Copyright (c) 2003-2019 Fabrice Bellard and the QEMU Project developers

GDB 正在根据 ELF 文件所说的代码位置放置断点。您可以看到,在您的成绩单中,它认为 boot.S 中的 _start 函数位于地址 0x0。然而,当您告诉 QEMU 加载您的二进制文件时,您并没有按照 ELF 文件所说的方式进行加载。所以实际执行的代码位于完全不同的地址,断点不在与执行代码匹配的地址中,因此它们不会命中。由于您没有将代码编译为 position-independent,因此当它从该地址 运行 时,它的工作主要靠运气(因为即使是非 position-independent aarch64 代码通常也没有position-dependent里面有说明)。

地址不匹配的原因是因为您的 ELF 文件说代码从地址 0x0 开始,但是您将二进制文件传递给 QEMU 到 -kernel 选项,这意味着“我是 Linux 内核,按照 Linux 内核启动协议规定的方式启动我”(参见 https://www.kernel.org/doc/Documentation/arm64/booting.txt)。这意味着很多事情,包括(对于当前的 QEMU 实现——引导协议没有严格要求)我们将映像加载到地址 0x80000,以及 运行 生成的一些存根代码QEMU 设置一些寄存器并跳转到该位置。这就是为什么当您将 linker 脚本设置为 link 该地址的图像时它恰好开始工作。

解决此问题的方法是选择您希望如何启动访客代码:

  • 您可以让它满足 Linux 内核启动协议的各种要求,并将其作为二进制文件传递给 -kernel
  • 你可以把它写成一个纯 bare-metal 图像,在地址 0x0 包含一个向量 table,然后用 QEMU“通用加载器”加载它,它将获取一个 ELF 文件和按照 ELF headers 指定的方式加载其所有段。 (也可以将 ELF 文件传递​​给 -kernel,但在这种情况下通用加载器更有意义。)

QEMU 支持“加载此 ELF 文件并以 Raspberry Pi 固件支持 运行ning ELF 文件的方式启动它从 SD 卡加载”。因此,您可能需要对仅针对 运行 实际硬件设计的 bare-metal 代码教程进行一些调整。

(有关用于加载访客代码的各种 QEMU 选项的更多信息,请参阅 this answer。)