💮

Zircon の QEMU ブート(aarch64)

2021/10/09に公開

本記事では、aarch64 用 Zircon カーネルを QEMU 上で起動させるときの動作を紹介します。

Zircon 起動の流れ

  • qemu-boot-shim

    QEMU 上で physboot を起動するためのブートローダ[1]

  • physboot

    圧縮された Zircon カーネルイメージを展開するブートローダ[2]

  • zircon

    Zircon カーネルイメージ

本記事では、QEMU の部分を紹介します。

イメージファイル

Zircon の起動には 2 つのイメージファイルが必要です。

  • qemu-boot-shim.bin

    • qemu-boot-shim ブートローダの実体
  • fuchsia-ssh.zbi

    • physboot圧縮Zirconカーネルイメージなどを含むイメージファイル
    • ZBI データ構造についてはこちらの記事を参照

QEMU

Fuchsia 開発環境では、 fx qemu コマンドを使って QEMU 上で Zircon カーネルおよび Fuchsia OS を起動します。

fx qemu コマンドは最終的に次のコマンドを実行します。

qemu コマンド(簡易)
qemu-system-aarch64 -kernel qemu-boot-shim.bin -initrd fuchsia-ssh.zbi \
  -m 8192 -machine virt-2.12(以下略)
  • -kernel qemu-boot-shim.bin
    qemu-boot-shim.bin をメモリへロードし、エントリポイントへジャンプする

  • -initrd fuchsia-ssh.zbi
    fuchsia-ssh.zbi を RAM ディスクとしてメモリへロードする

  • -m 8192
    メモリ 8GiB

  • -machine virt-2.12

    QEMU で使用する aarch64 ボードに virt を指定する

qemu コマンドの詳細は付録を参照。

virt のメモリマップ

Address Size Description
0x00000000 0x40000000 (1GiB) device I/O
0x40000000 0x200000000 (8GiB) RAM

qemu/hw/arm/virt.c

RAM はアドレス 0x40000000 から開始します。

イメージファイルのロードアドレス

QEMU は qemu-boot-shim.binfuchsia-ssh.zbi をメモリにロードします。

Load Address Size Description
0x40080000 65,536 qemu-boot-shim.bin
0x48000000 24,676,320 fucnshia-ssh.zbi

ロードアドレス決定の詳細は付録を参照。

Device Tree Blob の作成

virt ボードの場合、DTB(Device Tree Blob)は QEMU が生成します。

DTB の内容

qemu-boot-shim で使用する情報だけ掲載します。

  • CPU 情報

      cpu@0 {
        reg = < 0x00 >;
        enable-method = "psci";
        compatible = "arm,cortex-a57";
        device_type = "cpu";
        };
      cpu@1 {...};
      cpu@2 {...};
      cpu@3 {...};
    
    • CPU 数 4
  • メモリ情報

    memory@40000000 {
      reg = < 0x00 0x40000000 0x02 0x00 >;
      device_type = "memory";
    };
    
    • 開始アドレス 0x00000000_40000000
    • サイズ 0x00000002_00000000(8 GiB)
  • 割り込みコントローラ情報

    intc@8000000 {
      (省略)
      compatible = "arm,gic-v3";
    
    • gic(Generic Interrupt Controller)のバージョン 3
  • initrd(fuchsia-ssh.zbi)情報

    chosen {
            linux,initrd-end = < 0x497887e0 >;
            linux,initrd-start = < 0x48000000 >;
    
    • fuchsia-ssh.zbi のロードアドレス 0x00000000_48000000
  • 起動オプション

    chosen {
            (省略)
            bootargs = "TERM=xterm-256color kernel.entropy-mixin=7f9f544b87325f191bc692748707fc14d77b0055f3dbb6f8c1408bfb96db210f kernel.halt-on-panic=true ";
    
    
    • Zircon の起動オプション

      TERM=xterm-256color kernel.entropy-mixin=7f9f544b87325f191bc692748707fc14d77b0055f3dbb6f8c1408bfb96db210f kernel.halt-on-panic=true

DTB 内容の確認方法は付録を参照。

DTB のアドレス

Load Address Size Description
0x40080000 65,536 qemu-boot-shim.bin
0x48000000 24,676,320 fucnshia-ssh.zbi
0x49800000 1,048,576 Device Tree Blob

DTB は 0x49800000 に配置します。
これは、fuchsia-ssh.zbi の後方、2 MiB 境界のアドレスとなります。
また、このアドレスを x0 レジスタ経由で qemu-boot-shim に渡します。

アドレス決定の詳細は付録を参照。

qemu-boot-shim へジャンプ

qemu-boot-shim.binfuchsia-ssh.zbi、DTB のロード後、qemu-boot-shim のエントリポイント(0x40080000)へジャンプします。

qemu-boot-shim へ渡す情報は次のとおりです。

Register Value Description
x0 0x49800000 Device Tree Blob
x4 0x40080000 Entry Address
  • DTB 中に、fuchsia-ssh.zbi のロードアドレスが含まれています。

引数レジスタおよびジャンプの詳細は付録を参照。

ジャンプ前の状態

qemu-boot-shim で関係のある CPU ステータスのみ掲載します。

Function Status
Exception Level EL2
IRQ Mask
FIQ Mask
I-Cache Disable
D-Cache Disable
MMU Disable

gdb にて 0x40080000 でブレークさせ、info registers コマンドで PSTATE_EL2SCTLR_EL2 レジスタを確認しました。

  • PSTATE_EL2 レジスタ = 0x4000_07c9
  • SCTLR_EL2 レジスタ = 0x0

レジスタの詳細は付録を参照。

付録

QEMU コマンド詳細

qemu コマンド(詳細)
fuchsia/prebuilt/third_party/qemu/linux-x64/bin/qemu-system-aarch64 \
  -kernel fuchsia/out/default/qemu-boot-shim.bin \
  -initrd fuchsia/out/default/tmp.yym/fuchsia-ssh.zbi \
  -m 8192 -nographic -nic none -smp 4 -machine virtualization=true \
  -cpu max -machine virt-2.12,gic-version=max \
  -append 'TERM=xterm-256color kernel.entropy-mixin=7f9f544b87325f191bc692748707fc14d77b0055f3dbb6f8c1408bfb96db210f kernel.halt-on-panic=true '
  • fuchsia/prebuilt/third_party/qemu/linux-x64/bin/qemu-system-aarch64
    fuchsia リポジトリに含まれている qemu コマンドを使用する
  • -kernel fuchsia/out/default/qemu-boot-shim.bin
    QEMU が起動するバイナリイメージファイル
  • -initrd fuchsia/out/default/tmp.yym/fuchsia-ssh.zbi
    RAM ディスクイメージファイル
  • -m 8192
    メモリ 8GiB
  • -nographic
    グラフィカル出力を無効
  • -nic none
    ネットワークデバイスなし
  • -smp 4
    4 CPU
  • -machine virtualization=true
    QEMU で動作させる CPU の仮想化サポートを有効。
    EL2(例外レベル 2)で動作する
    ‘virt’ generic virtual platform (virt)
  • -cpu max
    すべての CPU feature を有効
  • -machine virt-2.12,gic-version=max
    QEMU が提供するボードを virt-2.12 に指定。
    Generic Interrupt Controller のバージョンをベストなものにする
    ‘virt’ generic virtual platform (virt)
  • -append 'TERM=xterm-256color (省略)
    起動するバイナリに DTB 経由で提供するコマンドラインオプション

QEMU User Documentation

qemu-boot-shim.bin のロードアドレス

qemu/hw/arm/boot.c
    *entry = mem_base + kernel_load_offset;

qemu/hw/arm/boot.c

  • *entry
    qemu-boot-shim.bin のロードアドレスおよびエントリポイント

  • mem_base
    RAM の開始アドレス。今回は 0x40000000

  • kernel_load_offset

    qemu/hw/arm/boot.c
    #define KERNEL64_LOAD_ADDR 0x00080000
    

    qemu/hw/arm/boot.c

qemu-boot-shim.bin のロードアドレスおよびエントリポイントは、0x40080000 となります。

fuchsia-ssh.zbi のロードアドレス

qemu/hw/arm/boot.c
    info->initrd_start = info->loader_start +
        MIN(info->ram_size / 2, 128 * MiB);

qemu/hw/arm/boot.c

  • info->initrd_start
    fucnshia-ssh.zbi のロードアドレス
  • info->loader_start
    RAM の開始アドレス。今回は 0x40000000
  • info->ram_size
    RAM のサイズ。今回は 8 GiB

RAM サイズが 256MiB 以上の場合、fuchsia-ssh.zbiloader_start + 128MiB(0x48000000)にロードされます。
ただし、-kernel で指定したイメージがこの領域にある場合は、別の領域を使用します。

Device Tree Blob のアドレス

qemu/hw/arm/boot.c
            info->dtb_start = QEMU_ALIGN_UP(info->initrd_start + initrd_size,
                                           align);

qemu/hw/arm/boot.c

qemu/include/qemu/osdep.h
/* Round number down to multiple */
#define QEMU_ALIGN_DOWN(n, m) ((n) / (m) * (m))

/* Round number up to multiple. Safe when m is not a power of 2 (see
 * ROUND_UP for a faster version when a power of 2 is guaranteed) */
#define QEMU_ALIGN_UP(n, m) QEMU_ALIGN_DOWN((n) + (m) - 1, (m))

qemu/include/qemu/osdep.h

  • info->dtb_start
    DTB(Device Tree Blob)のアドレス

  • info->initrd_start
    0x48000000

  • initrd_size
    0x17887e0(23.5MiB)

    $ ls -l fuchsia/out/default/tmp.HA0/fuchsia-ssh.zbi
    -rw-r--r-- 1 junkawa junkawa 24676320 Oct  3 11:23 fuchsia/out/default/tmp.HA0/fuchsia-ssh.zbi
    
  • align
    2MiB(0x200000)

  • アドレス
    0x49800000
    x0 レジスタ経由で qemu-boot-shim に渡す

    (gdb) break *0x40080000
    (gdb) target extended-remote :1234
    Remote debugging using :1234
    0x0000000040000000 in ?? ()
    
    Thread 1 hit Breakpoint 1, 0x0000000040080000 in ?? ()
    (gdb) info registers
    x0             0x49800000          1233125376
    

    x00x49800000 であることが確認できます

qemu-boot-shim のファイル形式

$ file fuchsia/out/default/qemu-boot-shim.bin
fuchsia/out/default/qemu-boot-shim.bin: MS-DOS executable, MZ for MS-DOS
boot-shim.S
.section .text.boot0,"ax"
FUNCTION(_start)
    // magic instruction that gives us UEFI "MZ" signature
    add x13, x18, #0x16
    b header_end

    .quad   0             // image offset from start of ram (unused)
    .quad   0             // image size (unused)
    .quad   0
    .quad   0
    .quad   0
    .quad   0

    // arm64 magic number
    .byte   'A'
    .byte   'R'
    .byte   'M'
    .byte   0x64
    .align 3

header_end:
    // x0 typically points to device tree at entry
  • QEMU から _start にジャンプしたとき、add 命令後、b 命令で header_end にジャンプする
$hexdump -C -v -n 64 qemu-boot-shim.bin
00000000 4d 5a 00 91 0f 00 00 14 00 00 00 00 00 00 00 00 |MZ..............|
00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000030 00 00 00 00 00 00 00 00 41 52 4d 64 1f 20 03 d5 |........ARMd. ..|
00000040
Offset Field Value Description
0x00 Executable code 4d,ta,00.91 "MZ"
0x04 Executable code 0f,00,00,14 branch
0x08 Image load offset 0 unused
0x10 Effective Image size 0 unused
0x18 kernel flags 0
0x20 reserved 0
0x28 reserved 0
0x30 reserved 0
0x38 Magic number 41,52,4d,64 "ARM",0x64

4. Call the kernel image - Booting AArch64 Linux

  • 圧縮 Linux カーネルのヘッダーフォーマットを利用
  • 先頭に "MZ" シグネチャを利用
    これは UEFI アプリケーションのシグネチャ

QEMU でのファイル形式確認

QEMU は、-kernel で指定したファイルの形式を確認して処理を行います。

  • arm_load_elf()
    ヘッダー先頭が 0x7f かどうかで ELF 形式かどうかを確認
  • load_uimage_as()
    ヘッダー先頭が 0x27051956 かどうかで U-Boot 形式かどうか確認
  • load_image_gzipped_buffer()
    ヘッダー先頭が0x1f、0x8b かどうかで gzip 形式かどうかを確認

以上のどれも当てはまらない、今回の qemu-boot-shim.bin ような場合、RAW イメージとして処理します。
このとき、ファイル先頭から 56 バイト目の arm64 magic number を確認します
qemu-boot-shim.bin は前述のアセンブラ boot-shim.S で見たようにこれに該当します。
ここで、ヘッダーから image offsetimage size を取得しようと試みます。
しかし、どちらも 0 なのでこれらの値は使用されません。

以上より、MZ による PE フォーマットも、ARM+0x64 による arm64 magic header も QEMU では意味のある扱いは行いません。
(調査した範囲において)

QEMU virt DTB

virt ボードで使用する DTB を確認する方法を紹介します。

$ qemu-system-aarch64 \
  -kernel fuchsia/out/default/qemu-boot-shim.bin \
  -initrd fuchsia/out/default/tmp.yym/fuchsia-ssh.zbi \
  -m 8192 -nographic -nic none -smp 4 -machine virtualization=true \
  -cpu max -machine virt-2.12,gic-version=max \
  -append 'TERM=xterm-256color kernel.entropy-mixin=7f9f544b87325f191bc692748707fc14d77b0055f3dbb6f8c1408bfb96db210f kernel.halt-on-panic=true ' \
  -machine dumpdtb=aarch64-virt.dtb

qemu-system-aarch64: info: dtb dumped to aarch64-virt.dtb. Exiting.

$ dtc -o aarch64-virt.dts -O dts -I dtb aarch64-virt.dtb
  • -machine dumpdtb=aarch64-virt.dtb を指定して、DTB をファイルに出力する
  • dtc コマンドで DTB バイナリファイルを DTS テキスト形式に変換する

参考:kaz399; QEMU aarch64 virt のデバイスツリー · uchan-nos/os-from-zero Wiki





ジャンプ前の CPU ステータス

PSTATE_EL2

0x4000_07c9

Bits Name Value Description
31 N 0 Negative condition flag
30 Z 1 Zero condition flag
29 C 0 Carry condition flag
28 V 0 oVerflow condition flag
25 TCO 0 Tag Check Override
24 DIT 0 Data Independent Timing
23 UAO 0 User Access Override
22 PAN 0 Privileged Access Never Bit
21 SS 0 Software step bit
20 IL 0 Illegal Execution state bit
12 SSBS 0 Speculative Store Bypass Safe
11,10 BTYPE 0,1 Branch Type
9 D 1 Debug mask bit
8 A 1 SError interrupt mask bit
7 I 1 IRQ mask bit
6 F 1 FIQ mask bit
4 nRW 0 not Register Width: 0=64, 1=32
3,2 EL 1,0 Exception Level: EL2
0 SP 1 Stack pointer select: 0=SP0, 1=SPx, use SP_EL2

— C5.2.18 SPSR_EL2, Saved Program Status Register (EL2), Arm® Architecture Reference Manual

QEMU は Linux カーネルの呼び出しルールに従って、CPU ステータスを設定しているようです。
参考:4. Call the kernel image - Booting AArch64 Linux

qemu-boot-shim 実行の詳細

QEMU は数十バイトのブートローダを介して qemu-boot-shim を実行します。
QEMU はブートローダを RAM の先頭(0x40000000)に配置し、そこへジャンプします。

ブートローダ
ldr x0, arg
mov x1, xzr
mov x2, xzr
mov x3, xzr
ldr x4, entry
br x4
arg:
.word 0x49800000
.word 0
entry:
.word 0x40080000
.word 0
  • arg
    DTB のアドレス
  • entry
    qemu-boot-shim.bin のアドレス
Register Value Description
x0 0x49800000 Device Tree Blob
x1 0
x2 0
x3 0
x4 0x40080000 Entry Address

ブートローダは、レジスタを上記の状態にして、qemu-boot-shim のエントリポイントへジャンプします。

脚注
  1. shim は QEMU と physboot 間の(もしくは U-Boot と QEMU 間の)緩衝材の意と思われる ↩︎

  2. Zircon ソースコード中で phys がつくのは、MMU 無効で物理メモリアドレスをそのまま扱うときに多い気がする ↩︎

Discussion