使用 QEMU 调试内核

注意:使用 QEMU 调试内核不受支持。现有的支持和工具非常脆弱,经常出现故障。如果以下步骤不起作用,请不要感到意外。

Zircon 可以在使用 QEMU 的模拟环境下运行。QEMU 可以通过预构建的二进制文件安装,也可以在本地构建。

预构建的 QEMU

QEMU 由 jiri 下载,作为 jiri updatejiri run-hooks 的一部分。

QEMU 被提取到 //prebuilt/third_party/qemu 中。您可以使用 fx qemu(见下文)以最便捷的方式运行它。

构建 QEMU

cd $SRC
git clone --recursive https://fuchsia.googlesource.com/third_party/qemu
cd qemu
./configure --target-list=aarch64-softmmu,x86_64-softmmu
make -j32
sudo make install

如果您不想安装到 /usr/local(默认位置,需要您是 root 用户),请添加 --prefix=/path/to/install(可能是 $HOME/qemu)。然后,您需要将 /path/to/install/bin 添加到 PATH 中,或者在调用 run-zircon-{arch} 时使用 -q /path/to/install。

在 QEMU 下运行 Zircon

# for aarch64
fx set bringup.arm64
fx build
fx qemu

# for x86
fx set bringup.x64
fx build
fx qemu

如果 QEMU 不在您的路径中,请使用 -q 指定其位置。

-h 标志会列出许多选项,包括 -b(如果需要,先重建)和 -g(使用图形帧缓冲区运行)等。

如需退出 qemu,请输入 Ctrl-a x。使用 Ctrl-a h 可查看其他命令。

在 QEMU 下启用网络

如果为 run-zircon 脚本提供 -N 实参,该脚本将尝试使用名为“qemu”的 Linux tun/tap 网络设备创建网络接口。QEMU 无需任何特殊权限即可运行,但您需要提前创建一个持久性 tun/tap 设备(这需要您是 root 用户):

sudo ip tuntap add dev qemu mode tap user $USER
sudo ip link set qemu up

这足以启用链路本地 IPv6(如 loglistener 工具所用)。

在 QEMU 下使用模拟磁盘

使用基于核心的 build(实际上是任何高于启动的产品)将自动意味着提供一个磁盘来服务于 fvm 分区,该分区包括用于可变存储的 minfs 分区和用于软件包数据存储的 blobfs 分区。

您可以使用标志附加其他图片,如下所示:

fx qemu -d [-D <disk_image_path (default: "blk.bin")>]

使用 GDB 调试内核

示例会话

以下是一个示例会话,可帮助您快速入门。

在运行 QEMU 的 shell 中:

shell1$ fx qemu -- -s -S
[... some QEMU start up text ...]

这将启动 QEMU,但在启动时冻结系统,等待您在 GDB 中使用“continue”命令恢复系统。 如果您想在不使用 GDB 的情况下运行 QEMU,但希望稍后能够使用 GDB 进行连接,请在启动 QEMU 时不要使用上述示例中的“-S”:

shell1$ fx qemu -- -s
[... some QEMU start up text ...]

然后,您需要在 out 目录中找到 zircon.elf

然后在运行 GDB 的 shell 中:

shell2$ gdb path-to-zircon.elf -x ${FUCHSIA_DIR}/zircon/kernel/scripts/zircon.elf-gdb.py
Reading symbols from /fuchsia/out/default/kernel_x64-clang/zircon.elf...
Loading zircon.elf-gdb.py ...
Zircon extensions installed for /fuchsia/out/default/kernel_x64-clang/zircon.elf
(gdb) target extended-remote :1234
Remote debugging using :1234
0x000000000000fff0 in ?? ()

Thread 1 hit Breakpoint -1, 0x0000000000100050 in ?? ()
Watchpoint set on KASLR relocated base variable

Thread 1 hit Hardware read watchpoint -2: *0x767ca0

Value = 0
0x00000000001000ef in ?? ()
Update symbols and breakpoints for KASLR
KASLR: Correctly reloaded kernel at 0xffffffff00000000
(gdb) # Don't try to do too much at this point.
(gdb) # GDB can't handle architecture switching in one session,
(gdb) # and at this point the architecture is 16-bit x86.
(gdb) break lk_main
Breakpoint 1 at 0xfffffffff010cb58: file kernel/top/main.c, line 59.
(gdb) continue
Continuing.

Breakpoint 1, lk_main (arg0=1, arg1=18446744071568293116, arg2=0, arg3=0)
    at kernel/top/main.c:59
59  {
(gdb) continue

此时,Zircon 会启动,回到 shell1 后,您将看到 Zircon 提示。

mxsh>

如果您此时在 shell2 中按 Ctrl-C,可以返回到 GDB。

(gdb) # Having just done "continue"
^C
Program received signal SIGINT, Interrupt.
arch_idle () at kernel/arch/x86/64/ops.S:32
32      ret
(gdb) info threads
  Id   Target Id         Frame
  4    Thread 4 (CPU#3 [halted ]) arch_idle () at kernel/arch/x86/64/ops.S:32
  3    Thread 3 (CPU#2 [halted ]) arch_idle () at kernel/arch/x86/64/ops.S:32
  2    Thread 2 (CPU#1 [halted ]) arch_idle () at kernel/arch/x86/64/ops.S:32
* 1    Thread 1 (CPU#0 [halted ]) arch_idle () at kernel/arch/x86/64/ops.S:32

QEMU 会针对每个 CPU 向 GDB 报告一个线程。

zircon.elf-gdb.py 脚本

gdb 应该会自动加载 zircon/kernel/scripts/zircon.elf-gdb.py 脚本。如果未自动加载,您可能需要将其路径添加到 gdb 的 auto-load-safe-path 中。或者,您也可以在 gdb 的命令行标志中手动设置:

$ gdb path-to-zircon.elf -x ${FUCHSIA_DIR}/zircon/kernel/scripts/zircon.elf-gdb.py

它提供以下几项功能:

  • 针对 gdb 的 KASLR 重定位,可让您在函数中正确设置断点。

  • Zircon 对象的精美打印器(目前尚无)。

  • 多个 Zircon 特有命令,均带有“zircon”前缀。如需查看这些信息,请执行以下操作:

(gdb) help info zircon
(gdb) help set zircon
(gdb) help show zircon
  • 增强了对通过内核故障进行自动解开的解开器的支持。

注意:此脚本不会随着 zircon 的变化而始终保持更新。

注意:由于存在 bug 67893,如果使用带有 KVM 的 QEMU,KASLR 部分可能无法正常运行。 作为一种解决方法,您可以在 gdb 中执行以下命令:

(gdb) # before attaching to qemu
(gdb) mem 0 0xffffffffffffffff ro
(gdb) target extended-remote :1234
(gdb) ...
(gdb) # after the script performed the kaslr relocations
(gdb) mem auto

终止会话

如需终止 QEMU,您可以从 GDB 向 QEMU 发送命令:

(gdb) monitor quit
(gdb) quit

通过 Gdb 与 QEMU 交互

如需查看可从 GDB 执行的 QEMU 命令列表,请执行以下操作:

(gdb) monitor help

保存系统状态以进行调试

如果您遇到难以调试的崩溃问题,或者需要帮助调试崩溃问题,可以保存类似于核心转储的系统状态。

bash$ qemu-img create -f qcow2 /tmp/my_snapshots.qcow2 32M

将创建一个“32M”块存储设备。接下来,启动 QEMU 并告知它有关设备的信息,但不要告知它将设备连接到 guest 系统。 这没问题;我们不打算使用它来备份磁盘状态,只是想要一个核心转储。注意:如果您已在模拟块设备,并且该设备使用的是 qcow2 格式,则可以跳过所有这些步骤。

bash$ qemu <normal_launch_args> -drive if=none,format=qcow2,file=/tmp/my_snapshots.qcow2

当您想要保存核心状态时,请使用 切换到 QEMU 控制台。此时,您应该会看到 (qemu) 提示符。 之后,只需说出

(qemu) savevm my_backup_tag_name

稍后,在同一台机器上(使用与之前相同的实参启动),您可以进入控制台并运行以下命令:

(qemu) loadvm my_backup_tag_name

以恢复状态。或者,您也可以通过命令行执行此操作:

bash$ qemu <normal_launch_args> -drive if=none,format=qcow2,file=/tmp/my_snapshots.qcow2 -loadvm my_backup_tag_name

从理论上讲,您可以将 qcow2 映像与 build 输出目录一起打包,这样任何人都可以恢复您的状态并开始从 QEMU 控制台进行测试。