GDB 在 QEMU 上的裸机开发中逐步执行有关特定核心的指令

GDB stepping through instructions on a particular core in baremetal development on QEMU

我正在学习ARM上的裸机开发,为此我选择在QEMU上模拟Raspi3。因此,它是一个实现 ARMv8 架构的虚拟 ARM Cortex A-53。我编译了以下简单的裸机代码:

.global _start
_start:
1:  wfe
    b 1b

我使用 :

启动它
qemu-system-aarch64 -M raspi3 -kernel kernel8.img -display none -S -s

并且 GDB 使用以下方式从另一个终端连接到它:

gdb-multiarch ./kernel8.elf -ex 'target remote localhost:1234' -ex 'break *0x80000' -ex 'continue'

到目前为止一切正常,我可以注意到 gdb 中的断点。

Reading symbols from ./kernel8.elf...
Remote debugging using localhost:1234
0x0000000000000000 in ?? ()
Breakpoint 1 at 0x80000: file start.S, line 5.
Continuing.

Thread 1 hit Breakpoint 1, _start () at start.S:5
5   1:  wfe
(gdb) info threads
  Id   Target Id                    Frame
* 1    Thread 1.1 (CPU#0 [running]) _start () at start.S:5
  2    Thread 1.2 (CPU#1 [running]) 0x0000000000000300 in ?? ()
  3    Thread 1.3 (CPU#2 [running]) 0x000000000000030c in ?? ()
  4    Thread 1.4 (CPU#3 [running]) 0x000000000000030c in ?? ()
(gdb) list
1   .section ".text.boot"
2
3   .global _start
4   _start:
5   1:  wfe
6       b 1b
(gdb)

据我了解,在 ARM 的情况下,所有内核将在复位时执行相同的代码,因此理想情况下,我的案例中的所有内核都必须 运行 相同的代码。我只想通过设置断点来验证这一点,这就是问题所在。其他内核的断点未命中。如果我没记错的话,在我的案例中,线程只不过是核心。我试过休息但没有用:

(gdb) break *0x80000 thread 2
Note: breakpoint 1 (all threads) also set at pc 0x80000.
Breakpoint 2 at 0x80000: file start.S, line 5.
(gdb) thread 2
[Switching to thread 2 (Thread 1.2)]
#0  0x0000000000000300 in ?? ()
(gdb) info threads
  Id   Target Id                    Frame
  1    Thread 1.1 (CPU#0 [running]) _start () at start.S:5
* 2    Thread 1.2 (CPU#1 [running]) 0x0000000000000300 in ?? ()
  3    Thread 1.3 (CPU#2 [running]) 0x000000000000030c in ?? ()
  4    Thread 1.4 (CPU#3 [running]) 0x000000000000030c in ?? ()
(gdb) s
Cannot find bounds of current function
(gdb) c
Continuing.
[Switching to Thread 1.1]

Thread 1 hit Breakpoint 1, _start () at start.S:5
5   1:  wfe
(gdb)

我删除了核心1断点,然后核心2就永远挂了:

(gdb) info b
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x0000000000080000 start.S:5
    breakpoint already hit 2 times
2       breakpoint     keep y   0x0000000000080000 start.S:5 thread 2
    stop only in thread 2
(gdb) delete br 1
(gdb) info break
Num     Type           Disp Enb Address            What
2       breakpoint     keep y   0x0000000000080000 start.S:5 thread 2
    stop only in thread 2
(gdb) thread 2
[Switching to thread 2 (Thread 1.2)]
#0  0x000000000000030c in ?? ()
(gdb) c
Continuing.

我该怎么做才能在核心 2 上设置断点?我在这里做错了什么?

编辑

我尝试了 set scheduler-locking on(假设这是我需要的),但这似乎也不适合我。

(gdb) break *0x80000
Breakpoint 3 at 0x80000: file start.S, line 5.
(gdb) thread 2
[Switching to thread 2 (Thread 1.2)]
#0  0x000000000000030c in ?? ()
(gdb) set scheduler-locking on
(gdb) c
Continuing.


^C/build/gdb-OxeNvS/gdb-9.2/gdb/inline-frame.c:367: internal-error: void skip_inline_frames(thread_info*, bpstat): Assertion `find_inline_frame_state (thread) == NULL' failed.
A problem internal to GDB has been detected,
further debugging may prove unreliable.
Quit this debugging session? (y or n) n

This is a bug, please report it.  For instructions, see:
<http://www.gnu.org/software/gdb/bugs/>.

/build/gdb-OxeNvS/gdb-9.2/gdb/inline-frame.c:367: internal-error: void skip_inline_frames(thread_info*, bpstat): Assertion `find_inline_frame_state (thread) == NULL' failed.
A problem internal to GDB has been detected,
further debugging may prove unreliable.
Create a core file of GDB? (y or n)

编辑 2

根据@Frank 的建议,我在本地构建了(最新的)qemu 6.2.0 并使用了 arm 工具链中可用的 gdb

naveen@workstation:~/.repos/src/arm64/baremetal/raspi3-tutorial/01_bareminimum$ /opt/qemu-6.2.0/build/qemu-system-aarch64 -version
QEMU emulator version 6.2.0
Copyright (c) 2003-2021 Fabrice Bellard and the QEMU Project developers

naveen@workstation:~/.repos/src/arm64/baremetal/raspi3-tutorial/01_bareminimum$ /opt/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-gdb -version
GNU gdb (GNU Toolchain for the A-profile Architecture 10.3-2021.07 (arm-10.29)) 10.2.90.20210621-git

但我仍然遇到问题。我的其他核心 2、3 和 4 从未达到断点。似乎它们甚至都不是 运行 我的代码,因为它们指向的地址看起来不正常。

(gdb) info threads
  Id   Target Id                    Frame
* 1    Thread 1.1 (CPU#0 [running]) _start () at start.S:5
  2    Thread 1.2 (CPU#1 [running]) 0x000000000000030c in ?? ()
  3    Thread 1.3 (CPU#2 [running]) 0x000000000000030c in ?? ()
  4    Thread 1.4 (CPU#3 [running]) 0x000000000000030c in ?? ()

编辑 3

我的 Makefile 似乎有问题,因为当我按照 Frank 的建议使用命令进行构建时,它对我有用。有人可以看看这个 Makefile 有什么问题吗:

CC = /opt/gcc-arm-10.3-2021.07-x86_64-aarch64-none-elf/bin/aarch64-none-elf
CFLAGS = -Wall -O2 -ffreestanding -nostdinc -nostartfiles -nostdlib -g

all: clean kernel8.img

start.o: start.S
    ${CC}-gcc $(CFLAGS) -c start.S -o start.o

kernel8.img: start.o
    ${CC}-ld -g -nostdlib start.o -T link.ld -o kernel8.elf
    ${CC}-objcopy -O binary kernel8.elf kernel8.img

clean:
    rm kernel8.elf kernel8.img *.o >/dev/null 2>/dev/null || true

编辑 4

事实证明,当我使用 kernel8.elf 和 QEMU 进行引导时,一切都按预期进行。但是当我使用二进制格式的 kernel8.img 时,我遇到了问题。通过阅读,我了解到 ELF 包含使示例工作所需的“额外”信息。但为了澄清,我怎样才能使 kernel8.img 工作?

您使用的 gdbqemu 版本可能有问题,因为我无法使用 aarch64-elf-gdb 10.1 版和在 Ubuntu 20.04.3 LTS 系统上从头开始编译的 qemu-system-aarch64 版本 6.2.0:

wfe.s:

        .global _start
_start:
1:      wfe
        b 1b

建筑物wfe.elf

/opt/arm/10/gcc-arm-10.3-2021.07-x86_64-aarch64-none-elf/bin/aarch64-none-elf-gcc -g -ffreestanding -nostdlib -nostartfiles -Wl,-Ttext=0x80000 -o wfe.elf wfe.s

查看生成的代码:

/opt/arm/10/gcc-arm-10.3-2021.07-x86_64-aarch64-none-elf/bin/aarch64-none-elf-objdump -d wfe.elf

wfe.elf:     file format elf64-littleaarch64


Disassembly of section .text:

0000000000080000 <_stack>:
   80000:       d503205f        wfe
   80004:       17ffffff        b       80000 <_stack>

在 shell 会话中开始 qemu

/opt/qemu-6.2.0/bin/qemu-system-aarch64 -M raspi3b -kernel wfe.elf -display none -S -s

开始 gdb 在另一个:

/opt/gdb/gdb-10.1-aarch64-elf-x86_64-linux-gnu/bin/aarch64-elf-gdb wfe.elf -ex 'target remote localhost:1234' -ex 'break *0x80000' -ex 'continue'

gdb 会话:

GNU gdb (GDB) 10.1
Copyright (C) 2020 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-linux-gnu --target=aarch64-elf".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>.
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".
Remote debugging using localhost:1234
warning: No executable has been specified and target does not support
determining executable automatically.  Try using the "file" command.
0x0000000000080000 in ?? ()
Breakpoint 1 at 0x80000
Continuing.
[Switching to Thread 1.4]

Thread 4 hit Breakpoint 1, 0x0000000000080000 in ?? ()
(gdb) break *0x80000 thread 2
Note: breakpoint 1 (all threads) also set at pc 0x80000.
Breakpoint 2 at 0x80000
(gdb) info threads
  Id   Target Id                    Frame 
  1    Thread 1.1 (CPU#0 [running]) 0x0000000000080000 in ?? ()
  2    Thread 1.2 (CPU#1 [running]) 0x0000000000080000 in ?? ()
  3    Thread 1.3 (CPU#2 [running]) 0x0000000000080000 in ?? ()
* 4    Thread 1.4 (CPU#3 [running]) 0x0000000000080000 in ?? ()
(gdb) c
Continuing.
[Switching to Thread 1.2]

Thread 2 hit Breakpoint 1, 0x0000000000080000 in ?? ()
(gdb) info b
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x0000000000080000 
        breakpoint already hit 2 times
2       breakpoint     keep y   0x0000000000080000  thread 2
        stop only in thread 2
        breakpoint already hit 1 time
(gdb) del 1
(gdb) info b
Num     Type           Disp Enb Address            What
2       breakpoint     keep y   0x0000000000080000  thread 2
        stop only in thread 2
        breakpoint already hit 1 time
(gdb) c
Continuing.

Thread 2 hit Breakpoint 2, 0x0000000000080000 in ?? ()
(gdb) c
Continuing.

Thread 2 hit Breakpoint 2, 0x0000000000080000 in ?? ()
(gdb) c
Continuing.

Thread 2 hit Breakpoint 2, 0x0000000000080000 in ?? ()
(gdb) c
Continuing.

Thread 2 hit Breakpoint 2, 0x0000000000080000 in ?? ()
(gdb) c
Continuing.

Thread 2 hit Breakpoint 2, 0x0000000000080000 in ?? ()
(gdb) 

因此,您的两个问题的答案是:

  1. 我该怎么做才能在核心 2 上设置断点?

正是你在做什么。

  1. 我做错了什么?

没什么,但可能正在使用 gdb and/or qemu 的 old/buggy 版本 - 我猜你的情况是 gdb 是罪魁祸首,但我可能错了。

您可以通过使用 Arm 提供的 gcc 工具链中提供的 gdb 版本再次测试来轻松验证,AArch64 ELF bare-metal target (aarch64-none-elf) - 我试过了,它也运行良好:

/opt/arm/10/gcc-arm-10.3-2021.07-x86_64-aarch64-none-elf/bin/aarch64-none-elf-gdb wfe.elf -ex 'target remote localhost:1234' -ex 'break *0x80000' -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-elf".
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 wfe.elf...
Remote debugging using localhost:1234
_start () at wfe.s:3
3       1:      wfe
Breakpoint 1 at 0x80000: file wfe.s, line 3.
Continuing.

Thread 1 hit Breakpoint 1, _start () at wfe.s:3
3       1:      wfe
(gdb) break *0x80000 thread 2
Note: breakpoint 1 (all threads) also set at pc 0x80000.
Breakpoint 2 at 0x80000: file wfe.s, line 3.
(gdb)  info threads
  Id   Target Id                    Frame 
* 1    Thread 1.1 (CPU#0 [running]) _start () at wfe.s:3
  2    Thread 1.2 (CPU#1 [running]) _start () at wfe.s:3
  3    Thread 1.3 (CPU#2 [running]) _start () at wfe.s:3
  4    Thread 1.4 (CPU#3 [running]) _start () at wfe.s:3
(gdb) c
Continuing.
[Switching to Thread 1.2]

Thread 2 hit Breakpoint 1, _start () at wfe.s:3
3       1:      wfe
(gdb) info b
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x0000000000080000 wfe.s:3
        breakpoint already hit 2 times
2       breakpoint     keep y   0x0000000000080000 wfe.s:3 thread 2
        stop only in thread 2
        breakpoint already hit 1 time
(gdb) del 1
(gdb) info b
Num     Type           Disp Enb Address            What
2       breakpoint     keep y   0x0000000000080000 wfe.s:3 thread 2
        stop only in thread 2
        breakpoint already hit 1 time
(gdb) c
Continuing.

Thread 2 hit Breakpoint 2, _start () at wfe.s:3
3       1:      wfe
(gdb) c
Continuing.

Thread 2 hit Breakpoint 2, _start () at wfe.s:3
3       1:      wfe
(gdb) c
Continuing.

Thread 2 hit Breakpoint 2, _start () at wfe.s:3
3       1:      wfe
(gdb) c
Continuing.

Thread 2 hit Breakpoint 2, _start () at wfe.s:3
3       1:      wfe
(gdb) c
Continuing.

Thread 2 hit Breakpoint 2, _start () at wfe.s:3
3       1:      wfe
(gdb) 

请注意,解释如何构建 gdbqemu 的最新版本超出了当前答案的范围。