QEMU中的“-bios”与“-kernel”与“-device loader,file = ...”有何区别?

15

背景介绍,我正在在aarch64上运行裸机QEMU-4.1.0。

有几种方法可以让QEMU将编译好的代码加载到内存中。我想了解其中的根本区别,因为我看到了非常不同的行为,而文档并没有提供任何帮助。

考虑第一条命令行:

qemu-system-aarch64 \
  -s -S \
  -machine virt,secure=on,virtualization=on \
  -m 512M \
  -smp 4 \
  -display none \
  -nographic \
  -semihosting \
  -serial mon:stdio \
  -kernel my_file.elf \
  -device loader,addr=0x40004000,cpu_num=0 \
  -device loader,addr=0x40004000,cpu_num=1 \
  -device loader,addr=0x40004000,cpu_num=2 \
  -device loader,addr=0x40004000,cpu_num=3 \
  ;

在另一个终端中,如果我启动gdb来查看QEMU加载到内存中的内容,它恰好对应我所期望的。事实上,gdb有一个内置命令可以做到这一点...
(gdb) compare-sections
Section .start, range 0x40004000 -- 0x40006164: matched.
Section .vectors, range 0x40006800 -- 0x40006f90: matched.
Section .text, range 0x40006fc0 -- 0x4002ca7c: matched.
...
Section .stacks, range 0x4207c120 -- 0x420bc120: matched.
(gdb) x/10x 0x40004000
0x40004000 <_start>:  0x14000800 0x00000000 0x00000000 0x00000000
...

太完美了!我的ELF文件中的所有内容都在0x40004000处,我在内存中看到了它们,就像我预期的那样!我的第一个核心启动并按照我预期的方式运行。

有趣的是,如果我转储内存中位置零的内容,那里面有一些加载的东西。我没有要求它,也没有明确加载它。我不执行它。它不在我的ELF文件中。我不知道它是什么或者它来自哪里。我猜测QEMU假设我想要一些BIOS在闪存中,并将其放在那里。我不能确定。它还在0x40000000处放置了一些东西(很小)。我也不知道那是什么...我希望小心一点,如果我加载了某些东西,我们不会互相干扰...

编辑:QEMU-7文档澄清了DTB放置在0x40000000(RAM的起始位置)。

  1. 那么我的第一个问题是:我能否启用一些调试消息来了解QEMU加载的位置以及可能的原因?

继续...

如果我更改命令行,用“-bios my_file.elf”开关替换“-kernel my_file.elf”开关(不更改其他任何内容),然后重复运行/gdb,那么我会看到两件不同的事情...

首先,我看到所有的内核都在运行。我不需要使用PSCI调用来启动它们。好吧,但我认为这与我的问题无关。其次(非常重要的是)我的内存不包含我期望的内容!

(gdb) compare-sections
Section .start, range 0x40004000 -- 0x40006164: MIS-MATCHED!
Section .vectors, range 0x40006800 -- 0x40006f90: MIS-MATCHED!
Section .text, range 0x40006fc0 -- 0x4002ca7c: MIS-MATCHED!
...
Section .stacks, range 0x4207c120 -- 0x420bc120: matched.
(gdb) x/8x 0x40000000
0x40004000 <_start>:  0x00000000 0x00000000 0x00000000 0x00000000
0x40004010 <_start+16>:  0x00000000 0x00000000 0x00000000 0x00000000
(gdb) x/8x 0x40006800
0x40006800 <my_vector_name>:  0x00000000 0x00000000 0x00000000 0x00000000
0x40006810 <my_vector_name+16>:  0x00000000 0x00000000 0x00000000 0x00000000
(gdb) x/8x 0x40006fc0
0x40006800 <my_symbol_name>:  0x00000000 0x00000000 0x00000000 0x00000000
0x40006810 <my_symbol_name+16>:  0x00000000 0x00000000 0x00000000 0x00000000

一切都是零。我看不到我的代码,尽管神秘的代码仍然在0x0和0x4000000处加载。正如你可能预料的那样,在我发出"gdb nexti"之后,内核立即因为"未定义指令"异常而死亡。

嗯...

好的,现在我将"-bios my_file.elf"更改为"-device loader,file=my_file.elf"。我得到了相同的结果。我无法在内存中找到我的代码。

  1. 在QEMU中,"-bios"和"-kernel"有什么不同?这在哪里有记录,或者我该如何跟踪源代码?我该如何最好地调试这个问题?

谢谢您,亲爱的先生/女士!


编辑:

为了调试,所有好的/相关的东西似乎都在“virt.c”中...


更多编辑(添加信息“-device loader=my_file.elf”)

我的命令行是:

/tools/gnu/qemu-4.1.0/bin/qemu-system-aarch64 \
  -s -S \
  -machine virt,secure=on,virtualization=on \
  -cpu cortex-a53 \
  -d int \
  -m 512M \
  -smp 4 \
  -display none \
  -nographic \
  -semihosting \
  -serial mon:stdio \
  -device loader,file=NEW_AT_ZERO.elf \
  ;

以下是NEW_AT_ZERO.dis文件的相关部分:

NEW_AT_ZERO.elf:     file format elf64-littleaarch64
NEW_AT_ZERO.elf
architecture: aarch64, flags 0x00000112:
EXEC_P, HAS_SYMS, D_PAGED
start address 0x0000000000000000

Program Header:
    LOAD off    0x0000000000010000 vaddr 0x0000000000000000 paddr 0x0000000000000000 align 2**16
       filesz 0x00000000020b8120 memsz 0x00000000020b8120 flags rwx
    NOTE off    0x0000000000043484 vaddr 0x0000000000033484 paddr 0x0000000000033484 align 2**2
       filesz 0x0000000000000024 memsz 0x0000000000000024 flags r--
private flags = 0:

Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .start        00002164  0000000000000000  0000000000000000  00010000  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  1 .vectors      00000790  0000000000002800  0000000000002800  00012800  2**11
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  2 .text         00025bbc  0000000000002fc0  0000000000002fc0  00012fc0  2**6
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  3 .bss          0000a904  0000000000028b80  0000000000028b80  00038b7c  2**3
              ALLOC
....
Contents of section .start:
 0000 00080014 00000000 00000000 00000000  ................
....
 1000 fd170094 a00038d5 01044092 020c7892  ......8...@...x.
 1010 261842aa 660000b5 00038052 00005ed4  &.B.f......R..^.
 1020 7f2003d5 ffffff17 00000000 00000000  . ..............
....

...但当然...

GNU gdb (Linaro_GDB-2017.05.09) 7.12.1.20170417-git
Copyright (C) 2017 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-unknown-linux-gnu --target=aarch64-none-elf".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://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.
0x0000000000000000 in ?? ()
Reading symbols from ./NEW_AT_ZERO.elf...done.
(gdb) compare_sections
Undefined command: "compare_sections".  Try "help".
(gdb) compare-sections
Section .start, range 0x0 -- 0x2164: MIS-MATCHED!
Section .vectors, range 0x2800 -- 0x2f90: MIS-MATCHED!
Section .text, range 0x2fc0 -- 0x28b7c: MIS-MATCHED!
....
Section .stacks, range 0x2078120 -- 0x20b8120: matched.
warning: One or more sections of the target image does not match
the loaded file

(gdb) x/4x 0
0x0 <_start>:   0x00000000  0x00000000  0x00000000  0x00000000
(gdb) x/12x 0x1000
0x1000 <symbol>:    0x00000000  0x00000000  0x00000000  0x00000000
0x1010 <symbol+16>: 0x00000000  0x00000000  0x00000000  0x00000000
0x1020 <end_symbol>:    0x00000000  0x00000000  0x00000000  0x00000000

3
啊,你不能使用通用加载器写入地址0,因为你已经启用了TrustZone(secure=on),这样做时,虚拟板会设置第一个闪存设备仅在安全地址空间中可见。但是通用加载器只能写入非安全地址空间,因此它无法写入该闪存设备。 - Peter Maydell
1个回答

33
QEMU的命令行选项用于将代码加载到虚拟机中,这些选项因架构不同甚至在相同架构的不同机型之间的语义也有所不同。这很不幸,但是这是为了向后兼容旧版QEMU和对"为此映像文件类型做正确的事情会很好"特殊情况的逐步累积。
总体概述:
-kernel是"加载Linux内核"选项。它将以架构正在使用的最佳方式加载和引导内核。例如,在x86 PC机器上,它只需将文件提供给虚拟BIOS,然后依赖于虚拟BIOS实际将文件加载到RAM中。在Arm上,加载Linux内核意味着我们遵循内核为如何引导内核设置的规则(64位请参阅https://www.kernel.org/doc/Documentation/arm64/booting.txt,32位请参阅https://www.kernel.org/doc/Documentation/arm/Booting),并且我们通过一小段存根引导加载程序代码来实现(这就是你在低内存中看到的)。内核引导规则还要求我们在RAM中提供设备树blob,而这是0x40000000处的数据。我们还按照Linux内核引导的期望,通过保持PSCI关闭状态或使用少量二级CPU引导加载程序代码来处理二级CPU,该代码使用WFI循环,以便主CPU可以唤醒它们。(我们所做的取决于正在使用的板型,因为我们会像真实板子一样操作,尤其是对于32位板子来说变化很大。)
作为一个奇怪的例外,在Arm架构中,如果你将ELF文件传递给-kernel参数,我们会假设它不是Linux内核,并通过从ELF入口点开始引导它。我们提供DTB blob在RAM的基础上,但只有在它不与加载的ELF文件重叠时才提供。(顺便说一下:特别是对于“virt”,您仍然需要DTB,因为我们不能保证在QEMU版本之间保持设备在相同的物理地址 -- DTB是告诉客户代码应该在哪里查找东西的方式。您可以依赖flash在0x0处和RAM从0x4000_0000开始,但真正的做法是从DTB中提取所有其他设备地址。实际上,我们已经努力避免重新排列板子内存映射,但读取DTB是客户代码正确的做法。)
-device loader是“通用加载器”,在任何架构上都表现相同。它只是将ELF镜像加载到客户机RAM中,并不会改变CPU复位行为。如果您有完全裸机的镜像包括异常向量表,并希望以硬件重置的方式启动它,则这是一个很好的选择。
-bios是“以适合此机型的任何方式加载bios图像”的选项。同样,这是一种“做我想要的”类型的选项,其具体细节因机型和架构而异;有些机器根本不支持它。一些机器(例如x86 PC)将始终加载bios,如果用户没有指定,则使用默认二进制文件。如果用户要求,某些机器会加载bios,但否则不会(arm virt板就是这样)。通常预期BIOS映像是一个“裸金属原始二进制”映像,它将被加载到与硬件从复位时开始执行的位置相对应的某个闪存或ROM内存中。在至少一些机器上,包括“virt”,您可以使用类似于“-drive if = pflash,...”的命令行来提供闪存/ROM设备的内容。这是QEMU中常见模式的示例,您可以使用一个方便但底层有很多魔法的简短的“做我想要的”选项,或者使用一个较长的“正交”选项,该选项允许您指定大量子选项并获得所需的确切行为。请注意,BIOS映像不应该是ELF文件,它们预计只是放入ROM中的原始数据。

其中很多内容都没有记录,因为“我想运行自己设计的裸机程序”是一个非常小众的用例,而且我们在文档中没有一个好的地方可以使其易于记录不同板型的具体细节。


非常感谢您的建议和帮助!我很困惑为什么“-device loader,file = my_file.elf”没有将我的代码放在0x0处,尽管elf文件中明确有代码...(当我不使用“-bios”和“-kernel”时) - Lance E.T. Compte
没有看到ELF文件,很难说。但是你引用的gdb compare-sections输出并不像一个在0处有段的ELF文件。 - Peter Maydell
我在问题文本中又添加了一个编辑,显示“-device loader,file”不像我期望的那样工作... 最终,我想尝试让“-bios”起作用,因为这样我就不必走学习PSCI和SMC以启动所有内核的路线了。再次感谢! - Lance E.T. Compte
使用PSCI来启动一个核心比同时处理所有核心要少学习 - 这只需要5条汇编指令... - Peter Maydell

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接