2019年3月14日木曜日

Xilinx QEMU で Aarch64 ベアメタルの勉強をするための準備をした

Raspberry Pi 3+ をつかったり、 qemu-system-aarch64 を使ったりして、 ほんのちょっとベアメタルに挑戦したけど、仕切り直ししたくなったので、開発環境の構築からやりなおす。

今回は、環境一式そろえるところから、デバッグできることを確認するところまで。

前提環境

  • OS: Windows 10 Pro

構築予定環境を決める

ビルド環境と実行環境を決める。

ツールチェイン選定

Aarch64 のベアメタルに使えるツールチェインはいくつかあるらしい。

  • GNU Toolchain – Arm Developer
    • Aarch64 は、 GNU-A のやつ
  • Downloads - Linaro
    • aarch64-linux-gnu
      • ベアメタル開発も可能
      • 前勉強したときはこのツールチェインを使っていた
    • aarch64-elf
      • 本当はこっちを使うのが正しかったっぽい???(未検証)

それぞれのツールチェインの目的とか特徴とか言えれば良いのだけれど、そんな知識はない。

なんとなく、 GNU Toolchain – Arm Developer を使っていくことにする。

実行環境選定

この前コンパイルしたXilinx/qemu で実行するのがよさそう。

Raspberry Pi 3 も UltraScale+ も Cortex-A53 っぽいし、 Xilinx Quick Emulator User Guide を読めば比較的簡単にベアメタルプログラムの実行ができそうな気がする。

構成検討

慣れ親しんだ環境でやろうということで、以下の構成とする。

  • ビルド環境
    • OS: Windows 10 Pro
    • ツールチェイン: GNU Toolchain – Arm Developer
    • シェル環境: MSYS2
  • 実行環境

という感じでやっていく。

環境構築

GNU Toolchain をインストール

Windows 用バイナリが 32bit 用のものしかないけど問題ないでしょう。

  1. GNU Toolchain – Arm Developer にアクセス
  2. GNU toolchain for A-Profile -> Downloads を選択
  3. gcc-arm-8.2-2019.01-i686-mingw32-aarch64-elf.tar.xz をダウンロードして任意の場所に展開
    • この記事では ~/app/gcc-arm-8.2-2019.01-i686-mingw32-aarch64-elf に展開
  4. ~/app/gcc-arm-8.2-2019.01-i686-mingw32-aarch64-elf/bin にパスを通す

MSYS2 インストール

MSYS2 homepageから、 msys2-x86_64-20180531.exe をダウンロード。

  1. インストール先フォルダ: C:\msys64_aarch64_gnu_toolchain
  2. スタートメニューのショートカット: MSYS2 64bit Aarch64 GNU Toolchain
  3. pacman -Syu して再起動
  4. pacman -Su

ビルドに必要なパッケージをインストール

make をインストール。

pacman -S make

ツールチェインへパスを通す

MSYS2 上で、次のコマンドを実行し、ツールチェインへのパスを通す。 必要であれば .bashrc への登録もする。

export PATH=$PATH:~/app/gcc-arm-8.2-2019.01-i686-mingw32-aarch64-elf/bin

プログラムの作成

何もせずに無限ループするプログラムを作成する。

プロジェクトディレクトリ構成

first_step/
    +- Makefile : ビルド手順を書くやつ
    +- boot.S   : プログラム本体
    +- memmap   : リンカスクリプト

boot.S

// ブート時のエントリーポイント
.global start
start:
    // スタックポインタの開始位置指定
    mov sp, #0x80000

    // 何もせず無限ループ
    b halt

// 無限ループ
halt:
    wfe
    b halt

memmap

MEMORY
{
    ram : ORIGIN = 0x80000, LENGTH = 0x1000000
}

SECTIONS
{
    .text : { *(.text*) } > ram
    .bss : { *(.bss*) } > ram
}

Makefile

CROSS := aarch64-elf

AS = $(CROSS)-as
LD = $(CROSS)-ld
OBJDUMP = $(CROSS)-objdump
OBJCOPY = $(CROSS)-objcopy

AFLAGS = -g3

all: kernel8.img

boot.o: boot.S
    $(AS) $(AFLAGS) $< -o $@

kernel8.elf : memmap boot.o
    $(LD) boot.o -T memmap -o kernel8.elf

kernel8.img : kernel8.elf
    $(OBJCOPY) $< -O binary kernel8.img

.PHONY: clean
clean:
    rm -rf *.o *.elf *.img

プログラムのビルド

MSYS2 の環境で、プロジェクトディレクトリに移動して make するだけ。 (MSYS2 上での作業)

cd /PATH/TO/first_step
make

ビルドに成功すると、boot.okernel8.elf, kernel8.img が生成される。 QEMU での実行時に必要なのは kernel8.elf

実行とデバッグ

デバッグ用の docker コンテナを立ち上げて、実行とデバッグを行う。

QEMU を GDB 接続待ち状態で起動する

プロジェクトディレクトリをマウントしつつ、デバッグを可能とするオプションを付けてコンテナを起動。 (PowerShell 上での作業)

docker run -it --rm -v "$(pwd):/work" --workdir "/work" --name "work" --cap-add=SYS_PTRACE --security-opt="seccomp=unconfined" mikoto2000/qemu-xilinx

起動したコンテナで、QEMU を実行。

qemu-system-aarch64 -M arm-generic-fdt -nographic -serial mon:stdio -dtb /var/dts/LATEST/SINGLE_ARCH/zcu102-arm.dtb -device loader,file=./kernel8.elf,cpu-num=0 -device loader,addr=0xfd1a0104,data=0x8000000e,data-len=4 -s -S
  • -M arm-generic-fdt: デバイスツリーでマシン定義しますよという指定
  • -nographic: GUI 無しで起動
  • -serial mon:stdio: シリアル通信を QEMU モニタに統合
  • -dtb /var/dts/LATEST/SINGLE_ARCH/zcu102-arm.dtb: マシン情報が記載されたファイルを指定
  • -device loader,file=./kernel8.img,cpu-num=0: どのバイナリをどの CPU で実行するかの指定
  • -device loader,addr=0xfd1a0104,data=0x8000000e,data-len=4: Cortex-A53 の CPU0 を開始するための設定(レジスタ CRF_APB.RST_FPD_APU の設定)
  • -s: GDB からの接続を、 localhost:1234 で待ち受ける
  • -S: 起動と同時に一時停止する

詳細は Xilinx Quick Emulator User Guide 参照。

QEMU に GDB で接続する

同じコンテナに接続し、 GDB で QEMU に接続する。 (PowerShell 上での作業)

docker exec -it work /bin/bash

GDB で QEMU に接続。

gdb-multiarch -q --eval-command="target remote localhost:1234" ./kernel8.elf
  • -q: ロゴを表示しない
  • --eval-command="target remote localhost:1234": GDB 起動後に実行するコマンド
    • 今回は、起動のたびに GDB UI 上で接続コマンドを打つのが面倒だったので起動時に渡すようにした

GDB を使ったステップ実行

デバッグ中のターミナルログ

root@4934294dab10:/work# gdb-multiarch -q --eval-command="target remote localhost:1234" ./kernel8.elf
Reading symbols from ./kernel8.elf...done.
Remote debugging using localhost:1234
start () at boot.S:6
6           mov sp, #0x80000
(gdb) b start
Breakpoint 1 at 0x80000: file boot.S, line 6.
(gdb) b halt
Breakpoint 2 at 0x80008: file boot.S, line 12.
(gdb) n

Thread 1 hit Breakpoint 2, halt () at boot.S:12
12          wfe
(gdb)
13          b halt
(gdb)

Thread 1 hit Breakpoint 2, halt () at boot.S:12
12          wfe
(gdb)
13          b halt
(gdb)
...(snip)

作ったプログラムの先頭から実行され、無限ループに入ることが確認できた。

QEMU モニタによるレジスタ確認

GDB によるデバッグ中でも、 QEMU モニタを使って各種情報を取得できる。 試しにレジスタ情報を表示する。

QEMU を起動しているターミナルで、 CTRL-a c と、キーを押すと QEMU モニタに命令を出せるようになる。

(qemu) info registers
PC=000000000008000c  SP=0000000000080000
X00=0000000000000000 X01=0000000000000000 X02=0000000000000000 X03=0000000000000000
X04=0000000000000000 X05=0000000000000000 X06=0000000000000000 X07=0000000000000000
X08=0000000000000000 X09=0000000000000000 X10=0000000000000000 X11=0000000000000000
X12=0000000000000000 X13=0000000000000000 X14=0000000000000000 X15=0000000000000000
X16=0000000000000000 X17=0000000000000000 X18=0000000000000000 X19=0000000000000000
X20=0000000000000000 X21=0000000000000000 X22=0000000000000000 X23=0000000000000000
X24=0000000000000000 X25=0000000000000000 X26=0000000000000000 X27=0000000000000000
X28=0000000000000000 X29=0000000000000000 X30=0000000000000000
PSTATE=400003cd -Z-- EL3h
q00=0000000000000000:0000000000000000 q01=0000000000000000:0000000000000000
q02=0000000000000000:0000000000000000 q03=0000000000000000:0000000000000000
q04=0000000000000000:0000000000000000 q05=0000000000000000:0000000000000000
q06=0000000000000000:0000000000000000 q07=0000000000000000:0000000000000000
q08=0000000000000000:0000000000000000 q09=0000000000000000:0000000000000000
q10=0000000000000000:0000000000000000 q11=0000000000000000:0000000000000000
q12=0000000000000000:0000000000000000 q13=0000000000000000:0000000000000000
q14=0000000000000000:0000000000000000 q15=0000000000000000:0000000000000000
q16=0000000000000000:0000000000000000 q17=0000000000000000:0000000000000000
q18=0000000000000000:0000000000000000 q19=0000000000000000:0000000000000000
q20=0000000000000000:0000000000000000 q21=0000000000000000:0000000000000000
q22=0000000000000000:0000000000000000 q23=0000000000000000:0000000000000000
q24=0000000000000000:0000000000000000 q25=0000000000000000:0000000000000000
q26=0000000000000000:0000000000000000 q27=0000000000000000:0000000000000000
q28=0000000000000000:0000000000000000 q29=0000000000000000:0000000000000000
q30=0000000000000000:0000000000000000 q31=0000000000000000:0000000000000000
FPCR: 00000000  FPSR: 00000000
(qemu)

こんな感じ。

以上。

参考資料

0 件のコメント:

コメントを投稿