使用 QEMU 调试内核

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

预构建 QEMU

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

QEMU 被提取到 //prebuilt/third_party/qemu 中。您可以使用 fx qemu 最为方便地运行它(请参阅下文)。

构建 QEMU

安装前提条件

在 macOS 上构建 QEMU 需要一些软件包。从 macOS 10.12.1 开始:

# Using http://brew.sh
brew install pkg-config glib automake libtool

# Or use http://macports.org ("port install ...") or build manually

Build

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 中添加 /path/to/install/bin 或在调用 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 下启用网络

如果给定了 -N 参数,run-zircon 脚本将尝试使用名为“qemu”的 Linux tun/tap 网络设备创建网络接口。为此,不需要使用任何特殊权限运行 QEMU,但您需要提前创建永久性 tun/tap 设备(这要求您拥有 root 权限):

在 Linux 上:

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

这足以启用链接本地 IPv6(如日志监听器工具使用)。

在 macOS 上:

macOS 不支持开箱即用的 tun/tap 设备;但是有一组广泛使用的内核扩展,称为 tuntaposx,可在此处下载。安装程序完成后,扩展程序将创建最多 16 个 tun/tap 设备。run-zircon-x64 脚本使用 /dev/tap0。

sudo chown $USER /dev/tap0

# Run zircon in QEMU, which will open /dev/tap0
fx qemu -N

# (In a different window) bring up tap0 with a link local IPv6 address
sudo ip addr add dev tap0 fc00::/7
sudo ip link set tap0 up

在 QEMU 下使用模拟磁盘

使用基于核心的 build(实际上是上述任何产品)会自动暗示一个磁盘,该磁盘用于提供 fvm 分区。该分区包含用于可变存储空间的 minfs 分区以及用于软件包数据存储的 blobfs 分区。

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

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

使用 GDB 调试内核

示例会话

下面提供了一个示例会话,以帮助您上手。

在 shell 中,您在以下环境中运行 QEMU:

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

这将启动 QEMU,但会在启动时冻结系统,等待您在 GDB 中使用“continue”将其恢复。如果要在没有 GDB 的情况下运行 QEMU,但稍后能够连接 GDB,请在上例中启动不带“-S”的 QEMU:

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

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

然后在 shell 中运行 GDB:

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 脚本

zircon/kernel/scripts/zircon.elf-gdb.py 脚本应该由 gdb 自动加载。如果它没有自动加载,您可能需要将其路径添加到 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”前缀。若要查看这些建议,请执行以下操作:

(gdb) help info zircon
(gdb) help set zircon
(gdb) help show zircon
  • 增强了展开程序对通过内核故障自动展开的支持。

注意:此脚本并不总是随着 zircon 的变化而更新。

注意:由于 bug 67893,如果将 qemu 与 kvm 搭配使用,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 并告知它有关设备的信息,但不告知其将设备连接到客户机系统。这无关紧要;我们不打算使用它来备份磁盘状态,我们只想要核心转储。注意:如果您已经在模拟块设备且它使用的是 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

以恢复状态。或者,您也可以在 cmd 中使用以下命令执行此操作:

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

理论上,您可以将 qcow2 映像与 build 输出目录打包在一起,这样任何人都可以恢复您的状态并开始通过 QEMU 控制台执行一些操作。