💮

Fuchsia の ZBI データ構造

2021/08/28に公開

Fuchsia の起動時に使用する ZBI(Zircon Boot Image)のデータ構造を紹介します。

ZBI とは

  • Zircon Boot Image

    Zircon Boot Image には、ドライバーが動作する前のブートプロセスに必要なものがすべて含まれています。
    カーネルイメージとブートファイルシステム用の RAM ディスクが含まれています。

  • ZBI に含まれるもの(Boot Item と呼ぶ)

    • ブートローダ
    • 圧縮カーネルイメージ
    • カーネル起動オプション
    • ブートファイルシステム
      など

Linux の vmlinuz + initrd に近いです。

ZBI データ構造

ZBI Structure

  • Container Header
    ZBI の先頭ヘッダー
  • Boot Item
    • Boot Item Header
      データ(Payload)を説明するヘッダー
    • Payload
      データ本体

Container Header と Boot Item Header の実体は zbi_header_t 構造体です。

  • zbi_header_t
    • type: Payload の種別
    • length: Header を除いたデータ(Payload)サイズ
    • extra: type によって用途が異なる
    • flags: 次のフラグの論理和
    • reserved0: 0x0
    • reserved1: 0x0
    • magic: 0xb5781729 (ZBI_ITEM_MAGIC)
    • crc32:
      • CRC32 を持つ場合:CRC32 の値
      • CRC32 を持たない場合:0x4a87e8d6 (ZBI_ITEM_NO_CRC32)

ソースコード:zircon/system/public/zircon/boot/image.h

Container Header

Boot Item Header

Boot Item Payload

データ本体です。

  • 8 バイト境界にパディング
    • Boot Item Header の length は、パディング無しのサイズ

ZBI のヘッダー具体例

次の環境で生成される ZBI(fuchsia-ssh.zbi)について紹介します。

fx set bringup.x64
fx build
fx qemu

bringup.x64 は、x86-64 アーキテクチャで動作する最小のブート環境です。

fx qemu で実行する qemu の引数を簡単に示します。

qemu -kernel multiboot.bin -initrd fuchsia-ssh.zbi

-kernel でブートローダ(multiboot.bin)を、-initrd で ZBI(fuchsia-ssh.zbi)を指定します。

  1. qemu は、ブートローダと ZBI の両方をメモリにロードし、ブートローダを実行します
  2. ブートローダは ZBI から physboot ブートローダを見つけ、実行します
  3. physboot は ZBI から圧縮された zircon カーネルを見つけ、展開し、実行します

ブートローダについて

Fuchsia は複数のブートローダを使用します。
Qemu で Fuchsia を動かす場合、multiboot ブートローダと physboot ブートローダを使用します。

multiboot → physboot → zircon

fuchsia-ssh.zbi の中身

fuchsia-ssh.zbi

Container Header

$ hexdump -C -s 0 -n 32 fuchsia-ssh.zbi
00000000  42 4f 4f 54 d8 b8 aa 01  e6 f7 8c 86 00 00 01 00  |BOOT............|
00000010  00 00 00 00 00 00 00 00  29 17 78 b5 d6 e8 87 4a  |........).x....J|

-s 0 -n 32 で先頭から 32 バイト表示します。
リトルエンディアンなので、42 4f 4f 540x544f4f42 となります。

physboot Boot Item Header

Zircon 互換ブートローダのヘッダーです。

$ hexdump -C -s 32 -n 32 fuchsia-ssh.zbi
00000020  4b 52 4e 4c 78 7c 0a 00  00 00 00 00 00 00 01 00  |KRNLx|..........|
00000030  00 00 00 00 00 00 00 00  29 17 78 b5 d6 e8 87 4a  |........).x....J|

-s 32 -n 32 で先頭 32 バイト目から 32 バイト表示します。

compressed zircon Boot Item Header

圧縮された Zircon カーネルイメージのヘッダーです。

$ hexdump -C -s 687288 -n 32 fuchsia-ssh.zbi
000a7cb8  4b 53 54 52 d6 bd 0e 00  88 c9 29 00 01 00 03 00  |KSTR......).....|
000a7cc8  00 00 00 00 00 00 00 00  29 17 78 b5 fd 06 d8 1a  |........).x.....|

-s 687288 -n 32 で先頭 687,288 バイト目から 32 バイト表示します。

Container Header 32 bytes +
physboot Boot Item Header 32 bytes + payload 687,224 bytes
= 687,288 bytes

entropy Boot Item Header

エントロピーのヘッダーです。疑似乱数生成のシードに利用します。

$ hexdump -C -s 1653424 -n 32 fuchsia-ssh.zbi
00193ab0  52 41 4e 44 40 00 00 00  00 00 00 00 00 00 03 00  |RAND@...........|
00193ac0  00 00 00 00 00 00 00 00  29 17 78 b5 ae 6a 58 a5  |........).x..jX.|

-s 1653424 -n 32 で先頭 1,653,424 バイト目から 32 バイト表示します。

Container Header 32 bytes +
physboot          Boot Item Header 32 bytes + payload 687,224 bytes
compressed zircon Boot Item Header 32 bytes + payload 966,102 bytes + padding 2 bytes
= 1,653,424 bytes

cmdline Boot Item Header

カーネル起動オプションのヘッダーです。

$ hexdump -C -s 1653520 -n 32 fuchsia-ssh.zbi
00193b10  43 4d 44 4c e4 00 00 00  00 00 00 00 00 00 03 00  |CMDL............|
00193b20  00 00 00 00 00 00 00 00  29 17 78 b5 03 da 17 a9  |........).x.....|

-s 1653520 -n 32 で先頭 1,653,520 バイト目から 32 バイト表示します。

Container Header 32 bytes +
physboot          Boot Item Header 32 bytes + payload 687,224 bytes
compressed zircon Boot Item Header 32 bytes + payload 966,102 bytes + padding 2 bytes
entropy           Boot Item Header 32 bytes + payload 64 bytes
= 1,653,520 bytes

bootfs Boot Item Header

ブートファイルシステムのヘッダーです。

$ hexdump -C -s 1653784 -n 32 fuchsia-ssh.zbi
00193c18  42 46 53 42 bc 7c 91 01  00 70 a8 06 01 00 03 00  |BFSB.|...p......|
00193c28  00 00 00 00 00 00 00 00  29 17 78 b5 bb ed 4f 06  |........).x...O.|

-s 1653784 -n 32 で先頭 1,653,784 バイト目から 32 バイト表示します。

Container Header 32 bytes +
physboot          Boot Item Header 32 bytes + payload 687,224 bytes
compressed zircon Boot Item Header 32 bytes + payload 966,102 bytes + padding 2 bytes
entropy           Boot Item Header 32 bytes + payload 64 bytes
cmdline           Boot Item Header 32 bytes + payload 228 bytes     + padding 4 bytes
= 1,653,784 bytes

合計サイズ

Container Header 32 bytes +
physboot          Boot Item Header 32 bytes + payload 687,224 bytes
compressed zircon Boot Item Header 32 bytes + payload 966,102 bytes    + padding 2 bytes
entropy           Boot Item Header 32 bytes + payload 64 bytes
cmdline           Boot Item Header 32 bytes + payload 228 bytes        + padding 4 bytes
bootfs            Boot Item Header 32 bytes + payload 26,311,868 bytes + padding 4 bytes
= 27,965,688 bytes

これは、Container Header size 32 bytes + Container Header length 27,965,656 bytes に一致します。

ZBI のデータ具体例

fx build 時に実行されるコマンドは out/default/*/toolchain.ninja に記載。
*.ninja についてはこちらの記事を参照。

fuchsia-ssh.zbi

bringup.zbi に SSH 公開鍵 data/ssh/authorized_keys とエントロピーを追加したものです。

fucnshia-ssh.zbi = bringup.zbi + data/ssh/authorized_keys + entropy

生成方法

fx qemu 時に zbi コマンドで fuchsia-ssh.zbi を生成します。

  • zbi

    zbi \
      --compressed="zstd" \
      -o "fuchsia-ssh.zbi" \
      "bringup.zbi" \
      --entry "data/ssh/authorized_keys=${HOME}/.ssh/fuchsia_authorized_keys" \
      --type=entropy:64 /dev/urandom
    
    • bringup.zbibootfs に SSH 公開鍵を追加
      • SSH 公開鍵の実体は、${HOME}/.ssh/fuchsia_authorized_keys
    • (SSH 公開鍵追加後に)bootfszstd で圧縮
    • エントロピーは、/dev/urandom から 64 バイト読み出した値
関連するソースコード

bringup.zbi

fuchsia-ssh.zbi のベースとなる ZBI です。
physzircon.zbicmdline 起動オプションと bootfs ブートファイルシステムから構成されます。

  • bootfs

    ブートファイルシステムは、ブートプロセスの初期に必要なファイルを格納しています。

    bootfs RAM ディスクには、ブートプロセスの初期段階で、他のファイルシステムが利用できない場合に必要なファイルが格納されています。これは zircon ブートイメージの一部であり、 bootsvc によって解凍されて提供されます。初期のブートプロセスが完了すると、bootfs は /boot にマウントされます。

bringup.zbi = physzircon.zbi + cmdline + bootfs

生成方法

fx build 時に ffx コマンドで bringup.zbi を生成します。

  • ffx assembly image

    out/default/toolchain.ninja より抜粋
    ffx \
      --env .ffx.env \
      --config assembly_enabled=true \
      assembly image \
      --product obj/build/images/bringup/bringup_product_config.json \
      --board obj/build/images/bringup/bringup_board_config.json \
      --gendir obj/build/images/bringup/bringup/gen \
      --outdir obj/build/images/bringup/bringup
    

    construct_zbi()@src/developer/ffx/plugins/assembly/src/zbi.rs に関係する部分のみ紹介。

    • assembly image
      カーネルイメージ、起動オプション、ブートファイルシステムを結合して ZBI イメージファイルを生成
    • --product bringup_product_config.json
      カーネルイメージphyszircon.zbi、起動オプションcmdline、ブートファイルシステムbootfsを指定
    • --board bringup_board_config.json
      出力ファイル名、圧縮方式などを指定
  • bringup_product_config.json

    generate_product_config()@build/images/assemble_system.gniで生成

    bringup_product_config.json 抜粋
    {
      "base_packages": [],
      "cache_packages": [],
      "extra_packages_for_base_package": [],
      "version_file": "latest-commit-date.txt",
      "epoch_file": "obj/src/sys/pkg/bin/system-updater/epoch.json",
      "kernel_image": "kernel_x64/physzircon.zbi",
      "kernel_cmdline": [
        "kernel.serial=legacy",
        "blobfs.cache-eviction-policy=NEVER_EVICT",
        "console.shell=true",
        "kernel.enable-debugging-syscalls=true",
        "kernel.enable-serial-syscalls=true",
        "kernel.oom.behavior=jobkill",
        "netsvc.all-features=true",
        "netsvc.disable=false"
      ],
      "boot_args": [],
      "bootfs_files": [
        {
          "destination": "bin/acpidump",
          "label": "//src/devices/bin/acpidump:acpidump(//build/toolchain/fuchsia:x64)",
          "source": "zircon-migrated/acpidump"
        },
        ...
        {
          "destination": "config/archivist/archivist_config.json",
          "label": "//src/diagnostics/archivist:root-config(//build/toolchain/fuchsia:x64)",
          "source": "../../src/diagnostics/archivist/configs/root-config.json"
        },
        ...
        {
          "destination": "data/lspci/pci.ids",
          "label": "//src/devices/pci/bin/lspci:database(//build/toolchain/fuchsia:x64)",
          "source": "../../third_party/pciids/pci.ids"
        },
        {
          "destination": "driver/ahci.so",
          "label": "//src/devices/block/drivers/ahci:ahci-driver(//build/toolchain/fuchsia:x64-shared)",
          "source": "x64-shared/ahci.so"
        },
        ...
        {
          "destination": "lib/ld.so.1",
          "label": "//zircon/system/ulib/c:c(//zircon/system/ulib/c:user.libc_x64)",
          "source": "user.libc_x64/libc.so"
        },
        ...
        {
          "destination": "meta/ahci.cm",
          "label": "//src/devices/block/drivers/ahci:ahci_manifest_resource(//build/toolchain/fuchsia:x64)",
          "source": "obj/src/devices/block/drivers/ahci/cml/ahci/ahci.cm"
        },
        ...
      ],
      "update_package_name": "update",
      "base_package_name": "system_image"
    }
    
    • kernel_image
      カーネルイメージを指定
    • kernel_cmdline
      起動オプションを指定
    • bootfs_files
      ブートファイルシステムの中身を指定
  • bringup_board_config.json

    generate_board_config()@build/images/assemble_system.gniで生成

    bringup_board_config.json
    {
      "arch": "x64",
      "blobfs": {
        "compress": true,
        "layout": "compact"
      },
      "board_name": "x64",
      "bootloaders": [
        {
          "name": "bootloader",
          "partition": "bootloader",
          "source": "fuchsia.esp.blk",
          "type": "esp"
        }
      ],
      "recovery": {
        "name": "zedboot",
        "vbmeta": "zedboot.vbmeta",
        "zbi": "zedboot.zbi"
      },
      "vbmeta": {
        "additional_descriptor_files": [
    
        ],
        "kernel_partition": "zircon",
        "key": "../../third_party/android/platform/external/avb/test/data/testkey_atx_psk.pem",
        "key_metadata": "../../third_party/android/platform/external/avb/test/data/atx_metadata.bin",
        "partition": "vbmeta_a"
      },
      "zbi": {
        "backstop_file": "minimum-utc-stamp.txt",
        "compression": "zstd",
        "embed_fvm_in_zbi": false,
        "max_size": 0,
        "name": "bringup",
        "partition": "zircon-a"
      }
    }
    
    • zbi.compression
      Boot Item の圧縮方法
    • zbi.name
      出力する ZBI ファイルの名前
    • recovoerybootloadersvbmeta(verified boot metadata)などは更新パッケージに必要
関連するソースコード
ドキュメント

https://fuchsia.dev/fuchsia-src/contribute/governance/rfcs/0072_standalone_image_assembly_tool?hl=en

physzircon.zbi

physboot.zbi ブートローダと、"kernel.zbiカーネルイメージを圧縮した"データ。

physzircon.zbi = physboot.zbi + kernel.zbi自体(ZBIヘッダ込)を圧縮したもの

生成方法

fx build 時に zbi コマンドで physzircon.zbi を生成します。

  • zbi

    out/default/kernel_x64/toolchain.ninja
    ../../build/zbi/run_zbi.py \
      --zbi host_x64/zbi \
      --depfile kernel_x64/physzircon.zbi.d \
      --rspfile kernel_x64/gen/zircon/kernel/physzircon.zbi.rsp \
      --output kernel_x64/physzircon.zbi \
      --json-output kernel_x64/physzircon.zbi.json \
      --bootable=x64 \
      --compressed=zstd
    
    ↓ になります。
    
    zbi \
      --depfile kernel_x64/physzircon.zbi.d.intermediate \
      --prefix= --files --compressed --type=kernel --compressed=max kernel_x64/kernel.zbi \
      --files kernel_x64/gen/zircon/kernel/vmzircon.fini \
      --type=container kernel.phys_x64/obj/zircon/kernel/phys/physboot.zbi \
      --prefix= --compressed --files kernel_x64/gen/zircon/kernel/physzircon_input.fini \
      --output kernel_x64/physzircon.zbi \
      --json-output kernel_x64/physzircon.zbi.json \
      --bootable=x64 \
      --compressed=zstd
    

    physboot.zbi と圧縮した kernel.zbi を結合します。

    • 入力オプション

      • --prefix= --files --type=kernel --compressed=max kernel_x64/kernel.zbi

        • kernel.zbi をカーネルイメージファイル(ZBI_TYPE_STORAGE_KERNEL)として扱う
        • ペイロードはzstd・圧縮率最大でkernel.zbiを圧縮したデータ
        • Item::CreateFromFile()Item::WriteZBI()
      • --files kernel_x64/gen/zircon/kernel/vmzircon.fini

        今回は空ファイル

      • --type=container kernel.phys_x64/obj/zircon/kernel/phys/physboot.zbi

        • physboot.zbi を ZBI ファイル(ZBI_TYPE_CONTAINER)として扱う
          • physboot.zbi 中の BootItem をそのまま出力ファイルにコピーする
        • ImportFile()
      • --prefix= --compressed --files kernel_x64/gen/zircon/kernel/physzircon_input.fini

        今回は空ファイル

    • 出力オプション

      • --output kernel_x64/physzircon.zbi
        出力 ZBI ファイル名
      • --json-output kernel_x64/physzircon.zbi.json
        ZBI 中の BootItem を記載した JSON ファイル
      • --depfile kernel_x64/physzircon.zbi.d.intermediate
        依存関係を記載したファイル。自動で削除される
    • フォーマットオプション

      • --bootable=x64
        bootable image であるか検証する。
        bootable image とは、physbootZBI_TYPE_KERNEL_{X64,ARM64})が BootItem の先頭である ZBI のこと
      • --compressed=zstd
        bootfszstd で圧縮する。今回は bootfs がないので無効
関連するソースコード

physboot.zbi

physboot ブートローダのみを含む ZBI。

physboot.zbi = Container Header + BootItem Header + physboot

ペイロードは objcopy -O binary physboot で ELF ヘッダーを除去されたバイナリファイルです。

生成方法

kernel.phys_x64/toolchain.ninja
../../build/zircon/toolchain_utils_action.sh \
  kernel.phys_x64/obj/zircon/kernel/phys/physboot.zbi \
  kernel.phys_x64/obj/zircon/kernel/phys/_host_tool_action.physboot.d \
  ../../prebuilt/third_party/clang/linux-x64/bin/llvm-objcopy \
  -O binary \
  @kernel.phys_x64/gen/zircon/kernel/phys/physboot.image.rsp \
  kernel.phys_x64/obj/zircon/kernel/phys/physboot.zbi

↓ になります。

llvm-objcopy \
  -O binary \
  kernel.phys_x64/physboot
  kernel.phys_x64/obj/zircon/kernel/phys/physboot.zbi

physboot のコンパイル、リンクは out/default/kernel.phys_x64/obj/zircon/kernel/phys/_phys_executable.physboot.executable.ninja に記載。
zircon/kernel/phys/zbi-header.Szircon/kernel/phys/zbi-header.ld でリンクすることで、ELF ファイルの段階ですでに ZBI ヘッダーが含まれています。
objcopy で ELF ヘッダーを除去すると、ZBI ファイルが出来上がります。

  • zircon/kernel/phys/zbi-header.S 抜粋

    .section .boot.header, "a", %progbits
    
    ZBI_CONTAINER_HEADER(kZbiContainerHeader, PHYS_LOAD_END - kZbiItemHeader)
    
    // zbi_header_t
    .object kZbiItemHeader, rodata, nosection=nosection
    #ifdef __aarch64__
      .int ZBI_TYPE_KERNEL_ARM64    // type
    #elif defined(__x86_64__)
      .int ZBI_TYPE_KERNEL_X64      // type
    #else
    #error "what machine?"
    #endif
      .int PHYS_LOAD_END - kZbiKernelHeader // length
      .int 0                                // extra
      .int ZBI_FLAG_VERSION                 // flags
      .int 0, 0                             // reserved0, reserved1
      .int ZBI_ITEM_MAGIC                   // magic
      .int ZBI_ITEM_NO_CRC32                // crc32
    .end_object
    
関連するソースコード

kernel.zbi

(非圧縮)カーネルイメージのみを含む ZBI。

kernel.zbi = Container Header + BootItem Header + image

ペイロードはカーネルイメージファイル image から objcopy -O binary で ELF ヘッダーを除去したバイナリファイルです。

生成方法

out/default/kernel_x64/toolchain.ninja
../../build/zircon/toolchain_utils_action.sh \
  kernel_x64/kernel.zbi \
  kernel_x64/obj/zircon/kernel/_host_tool_action.kernel.d \
  ../../prebuilt/third_party/clang/linux-x64/bin/llvm-objcopy \
  -O binary \
  @kernel_x64/gen/zircon/kernel/kernel.image.rsp \
  kernel_x64/kernel.zbi

↓ になります。

llvm-objcopy \
  -O binary \
  kernel_x64/image \
  kernel_x64/kernel.zbi

image から ELF ヘッダーを除去します。
image は、zircon/kernel/arch/x86/image.Szircon/kernel/image.ld でリンクすることで、ELF ファイルの段階ですでに ZBI ヘッダーが含まれています。
objcopy で ELF ヘッダーを除去すると、ZBI ファイルが出来上がります。

kernel_x64/image ができるまで
  1. zircon ELF ファイルをビルド(out/default/kernel_x64/zircon.elf)
    カーネル本体。
    リンカスクリプトは zircon/kernel/kernel.ld
    zircon のコンパイル、リンクは out/default/kernel.phys_x64/obj/zircon/kernel/zircon.ninja に記載。

  2. objcopy -O binary で zircon から ELF ヘッダーを除去

  3. image ELF ファイルをビルド(out/default/kernel_x64/image)
    リンカスクリプトは zircon/kernel/image.ld
    ヘッダーは zircon/kernel/arch/x86/image.S で指定。
    image のコンパイル、リンクは out/default/kernel.phys_x64/obj/zircon/kernel/image.ninja に記載。

    image = ELF Header + Container Header + BootItem Header
            + ZBI kernel header + zircon + fixupコード
    
    • ZBI kernel header は、ZBI_TYPE が KERNEL の時に使われるヘッダー
      実体は、zbi_kernel_t 構造体

    • fixup コードは、KASLR(Kernel Address Space Layout Randomization)を実現するためのコード

      リンク時に決まった zircon 中の(絶対)アドレスを、実際にロードされたアドレスに書き換えるコード。
      実体は、zircon ビルド後に生成される out/default/kernel_x64/gen/zircon/kernel/kernel-fixups.inc

  • zircon/kernel/arch/x86/image.S 抜粋

    // ZBI file header (zbi_header_t)
    ZBI_CONTAINER_HEADER(_zbi_file_header, boot_load_end - _zbi_kernel_header)
    
    // ZBI kernel header (zbi_header_t)
    DATA(_zbi_kernel_header)
        .int ZBI_TYPE_KERNEL_X64
        .int boot_load_end - _zbi_kernel_payload
        .int 0
        .int ZBI_FLAG_VERSION
        .int 0
        .int 0
        .int ZBI_ITEM_MAGIC
        .int ZBI_ITEM_NO_CRC32
    END_DATA(_zbi_kernel_header)
    
    // ZBI_TYPE_KERNEL payload (zbi_kernel_t)
    DATA(_zbi_kernel_payload)
        .quad PHYS(IMAGE_ELF_ENTRY)
        .quad boot_bss_end - boot_load_end
    END_DATA(_zbi_kernel_payload)
    
関連するソースコード

成果物一覧

  • ZBI ファイル

    $ rg --files | rg ".zbi$" | rg -v test
    host_arm64/zbi
    fuchsia_prime.zbi
    fuchsia.zbi
    netboot.zbi
    zedboot.zbi
    bringup.zbi
    host_x64/zbi
    kernel_x64/kernel.zbi
    kernel_x64/physzircon.zbi
    host_arm64/exe.unstripped/zbi
    host_x64/exe.unstripped/zbi
    obj/build/images/fuchsia/fuchsia/fuchsia.zbi
    obj/build/images/fuchsia/fuchsia_prime/fuchsia_prime.zbi
    obj/build/images/fuchsia/netboot/netboot.zbi
    obj/build/images/bringup/bringup/bringup.zbi
    kernel.phys_x64/obj/zircon/kernel/phys/physboot.zbi
    kernel.phys_x64/obj/zircon/kernel/arch/x86/phys/boot-shim/phys-1mb-hello-world.zbi
    kernel.phys_x64/obj/zircon/kernel/arch/x86/phys/boot-shim/pic-1mb-boot-shim.zbi
    kernel.phys_x64/obj/zircon/kernel/arch/x86/phys/boot-shim/x86-legacy-zbi-boot-shim.zbi
    
  • ZBI JSON ファイル
    *.zbi.json で ZBI ファイルの中身を知ることができます。

    $ rg --files | rg ".zbi.json$" | rg -v test
    zedboot.zbi.json
    fuchsia_prime.zbi.json
    fuchsia.zbi.json
    netboot.zbi.json
    bringup.zbi.json
    kernel_x64/physzircon.zbi.json
    obj/build/images/sizes/fuchsia.zbi.json
    obj/build/images/fuchsia/fuchsia/gen/zbi.json
    obj/build/images/fuchsia/fuchsia_prime/gen/zbi.json
    obj/build/images/fuchsia/netboot/gen/zbi.json
    obj/build/images/bringup/bringup/gen/zbi.json
    

Discussion