为什么ARM内核在ELF和二进制文件中表现不同



我正在ARM上进行裸机开发,并在QEMU上模拟Raspi 3。以下是我的最低组装代码:

.section ".text.boot"
.global _start
_start:
1:  wfe
b 1b

下面是我的链接器脚本:

SECTIONS
{
. = 0x80000;
.text : {*(.text.boot)}
/DISCARD/ : { *(.comment) *(.gnu*) *(.note*) *(.eh_frame*) }
}

以下是我的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 -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

现在从一个终端,我正在加载我的kernel8.elf,如下所示:

$ /opt/qemu-6.2.0/build/qemu-system-aarch64 -M raspi3b -kernel kernel8.elf -display none -S -s

从另一个终端,我连接我的gdb:

$ /opt/gcc-arm-10.3-2021.07-x86_64-aarch64-none-elf/bin/aarch64-none-elf-gdb ./kernel8.elf -ex 'target remote localhost:1234' -ex 'break *0x80000' -ex 'continue'
(gdb) info threads
Id   Target Id                    Frame
1    Thread 1.1 (CPU#0 [running]) _start () at start.S:6
2    Thread 1.2 (CPU#1 [running]) _start () at start.S:5
* 3    Thread 1.3 (CPU#2 [running]) _start () at start.S:5
4    Thread 1.4 (CPU#3 [running]) _start () at start.S:5

在这种情况下,我的核心是可以的,因为所有的4个核心都在运行我的汇编代码。在continue时,核心随机命中断点,这是完美的。

然而,如果我使用kernel8.img(objcopy二进制输出(而不是kernel8.elf,我会发现只有Core 1在运行我的程序集,但其他3个Core似乎都被卡住了。在continue上,每次只有核心1重复命中断点。

(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]) 0x0000000000000300 in ?? ()
4    Thread 1.4 (CPU#3 [running]) 0x0000000000000300 in ?? ()

我在其他3个核心上尝试了set scheduler-locking oncontinue,但它们似乎被卡住了。

为什么kernel8.img不能作为kernel8.elf工作?我预计所有ARM内核在重置时都会运行相同的代码(就像kernel8.elf一样(,但kernel8.img却没有。

QEMU-kernel选项根据加载的文件是否为ELF文件而对其进行不同的处理。

如果它是一个ELF文件,那么它将根据ELF文件所说的应该加载的内容进行加载,并通过从ELF入口点执行来启动。如果它不是ELF文件,则假定它是一个Linux内核,并按照Linux内核的引导协议所要求的方式启动。

特别是,对于多核心板,如果-kernel得到一个ELF文件,它会在入口点同时启动所有核心。如果它得到一个非ELF文件,那么它将执行硬件应该执行的加载Linux内核的任何操作。对于raspi3b,这意味着模拟";辅助核心处于一个循环中,等待主核心通过写入"邮箱"地址来释放它们。这就是你在gdb中看到的行为——内核1-3所在的0x300地址在";循环旋转等待";密码

一般来说,除非您的客户代码是Linux内核,或者希望以与Linux内核相同的方式启动,否则不要使用-kernel选项来加载它;尝试做Linux内核想要的事情";,而且它也往往有很多遗产";这对某人来说似乎是一件有用的事情&;不同板之间或不同客户CPU架构之间不同的行为。";通用加载器";是加载ELF文件的好方法,如果你想完全手动控制";裸金属";工作

有关加载来宾代码的各种QEMU选项的更多信息,请参阅以下答案。

最新更新