QEMU AArch64(ARM64) で遊ぼう
1 はじめに
QEMUはオープンソースのCPUエミュレータです。
ARMは 組み込み機器、スマートフォン、携帯ゲーム機 などに広く用いられている CPUです。
以前、QEMUにLinuxのユーザーランドをエミュレートするユーザーエミュレーション と呼ばれる環境があるのを知り 32bit ARMで遊んでみました (QEMU ARM で遊ぼう)。 その後、しばらくして ARM64(AArch64)もいけるらしいという事を知り遊んでみたという次第です。
ただの暇つぶしなので、役に立たないかも知れませんが、ログをだらだらと記してみたいと 思います。
因みに、ARM64は64bit版のARMアーキテクチャを指す俗称のようです。 公式(?)にはAArch64と呼ぶようです。本ページではインストールディレクトリには 俗称のarm64を使用しています。他は各ツールのビルドパラメータに従い aarch64とarm64を使い分けています。
2 AAarch64クロスコンパイル環境を作る
本へっぽこページのコンテンツでは日常的な事なのですが、クロスコンパイラはソースからビルドします。 今回は VMware上の Fedora(Linuxのディストリビューションの一つ)でビルドをしました。 ビルドに必要なライブラリなどは予めインストールされているものと仮定しています。 クロスgccのビルドには、GMP,MPFR,MPC を始め、texinfoやncurses などの開発ライブラリ (これらのライブラリはいずれもx86ネイティブなものです)なども必要になるかと思いますが、 それらはパッケージインストールしてもらえれば良いと思います。
2.1 AAarch64クロスコンパイラのビルドに必要なファイル
以下のアーカイブを使用しました。
- binutils-2.25.tar.bz2 : アセンブラ/リンカ
- gcc-4.9.2.tar.bz2 : コンパイラ
- linux-3.18.1.tar.xz : Linuxカーネルアーカイブ(実際に使用するのはその一部)
- glibc-2.20.tar.xz : Cライブラリ
- gdb-7.8.1.tar.xz : デバッガ
他のバージョンでも大丈夫かも知れませんが、古すぎたり新しすぎたりすると 指定したコンパイルオプションではうまくビルドできなかったりするかも知れません。 その場合はうまく解決してください(^^;
2.2 ビルド手順
例では /usr/local/arm64_linux にインストールするものとしてビルドします。 アーカイブ群は ~/Downloads の下に、ビルドはカレントディレクトリで行う ものとしています。
また、作法的には良くないのですが、/usr/local および /usr/local/arm64_linuxは ファイルパーミッションを一般ユーザに空けたものとして実行しています。 「そういうの嫌なんですけど?」という方はインストール先ディレクトリを 任意のディレクトリに読み替えるか、「sudo make install」していると読み替えて ください。
今回はLinux(x86)の QEMUを使う都合でコンパイラのビルドもLinux(x86)上で 行いましたが、Cygwinなんかでもビルドできるのではないかと思います。
2.2.1 事前の設定
予めビルドされるであろう実行ファイル群が置かれるパスにPATHを通しておきます。
- export PATH="/usr/local/arm64_linux/bin:$PATH"
2.2.2 binutilsのビルド
bzip2 -dc ~/Downloads/binutils-2.25.tar.bz2 | tar xf -
mkdir -p binutils-2.25/build_linux
pushd binutils-2.25/build_linux
../configure --target=aarch64-linux-gnu --prefix=/usr/local/arm64_linux
make
make install
popd
2.2.3 gccのビルド(1回目)
bzip2 -dc ~/Downloads/gcc-4.9.2.tar.bz2 | tar xf -
mkdir -p gcc-4.9.2/build1_linux
pushd gcc-4.9.2/build1_linux
../configure --target=aarch64-linux-gnu --prefix=/usr/local/arm64_linux --with-newlib --without-headers --disable-nls --disable-shared --disable-multilib --disable-decimal-float --disable-threads --disable-libatomic --disable-libgomp --disable-libitm --disable-libquadmath --disable-libsanitizer --disable-libssp --disable-libvtv --disable-libcilkrts --disable-libstdc++-v3 --enable-languages=c,c++
make
make install
popd
まだAArch64用のCライブラリが存在していませんので、configureにはgcc内に含まれるライブラリ群を ビルドしないように指定しています。
ソースはもう一度利用する為、そのままにしておきます。
2.2.4 Linuxヘッダのインストール
xz -dc ~/Downloads/linux-3.18.1.tar.xz | tar xf -
pushd linux-3.18.1
make mrproper
make headers_install ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- INSTALL_HDR_PATH=/usr/local/arm64_linux/aarch64-linux-gnu
popd
システムコールを行う方法はOSに依存する為、Linuxのヘッダだけをインストールします。
2.2.5 glibcのビルド
xz -dc ~/Downloads/glibc-2.20.tar.xz | tar xf -
mkdir -p glibc-2.20/build_linux
pushd glibc-2.20/build_linux
../configure --prefix=/usr/local/arm64_linux/aarch64-linux-gnu --host=aarch64-linux-gnu --build=$(../scripts/config.guess) --disable-profile --enable-kernel=2.6.32 --with-headers=/usr/local/arm64_linux/aarch64-linux-gnu/include libc_cv_forced_unwind=yes libc_cv_ctors_header=yes libc_cv_c_cleanup=yes
make
make install
popd
2.2.6 libstdc++のビルド
mkdir -p gcc-4.9.2/build_linux_libstdc++
pushd gcc-4.9.2/build_linux_libstdc++
../libstdc++-v3/configure --target=aarch64-linux-gnu --prefix=/usr/local/arm64_linux/aarch64-linux-gnu --disable-multilib --disable-shared --disable-nls --disable-libstdcxx-threads --disable-libstdcxx-pch --with-gxx-include-dir=/usr/local/arm64_linux/aarch64-linux-gnu/include/c++/4.9.2
make
make install
popd
1回目のgccビルドで使用したソースを利用しています。
2.2.7 gccのビルド(2回目)
mkdir -p gcc-4.9.2/build2_linux
pushd gcc-4.9.2/build2_linux
../configure --target=aarch64-linux-gnu --prefix=/usr/local/arm64_linux --disable-nls --disable-shared --disable-multilib --disable-libgomp --disable-threads --enable-languages=c,c++ --disable-libstdcxx-pch --disable-bootstrap
make
make install
popd
ビルドしたglibcを参照するgccをビルドします。1回目のgccビルドで使用したソースを利用しています。
2.2.8 gdbのビルド
xz -dc ~/Downloads/gdb-7.8.1.tar.xz | tar xf -
mkdir -p gdb-7.8.1/build_linux
pushd gdb-7.8.1/build_linux
../configure --target=aarch64-linux-gnu --prefix=/usr/local/arm64_linux
make
make install
popd
2.3 コンパイルテスト
ビルドしたクロスコンパイラを使って以下のソースをコンパイルしてみます。
#include <stdlib.h> #include <stdio.h> int main() { printf("Hello AArch64 world.\n") ; return(0) ; }
hello.cなどにセーブし、以下のような感じでコンパイルします。
$ aarch64-linux-gnu-gcc hello.c -o hello $ aarch64-linux-gnu-readelf -h hello ELF Header: Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 Class: ELF64 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: EXEC (Executable file) Machine: AArch64 Version: 0x1 Entry point address: 0x400410 Start of program headers: 64 (bytes into file) Start of section headers: 9304 (bytes into file) Flags: 0x0 Size of this header: 64 (bytes) Size of program headers: 56 (bytes) Number of program headers: 7 Size of section headers: 64 (bytes) Number of section headers: 36 Section header string table index: 33
この例では 共有ライブラリを使用する実行ファイルが生成されます。
3 QEMUでAArch64バイナリを動かしてみる
QEMUを使って、AArch64のバイナリを動かしてみます。
3.1 QEMUのビルド
AArch64のバイナリが無かったのでソースからビルドします。ビルドにはautoconf,automake, libtoolパッケージをインストールしておく必要があります。 アーカイブはQEMU公式ページ より、バージョン2.4.0.1 をダウンロードし、~/Downloads/qemu-2.4.0.1.tar.bz2においてあるものとします。
bzip2 -dc ~/Downloads/qemu-2.4.0.1.tar.bz2 | tar xf -
pushd qemu-2.4.0.1
./configure --target-list=aarch64-linux-user
make
popd
実行バイナリは qemu-2.4.0.1/aarch64-linux-user/qemu-aarch64 に出来上がります。
$ ./qemu-2.4.0.1/aarch64-linux-user/qemu-aarch64 usage: qemu-aarch64 [options] program [arguments...] Linux CPU emulator (compiled for aarch64 emulation) Options and associated environment variables: Argument Env-variable Description -h print this help -g port QEMU_GDB wait gdb connection to 'port' -L path QEMU_LD_PREFIX set the elf interpreter prefix to 'path' -s size QEMU_STACK_SIZE set the stack size to 'size' bytes -cpu model QEMU_CPU select CPU (-cpu help for list) -E var=value QEMU_SET_ENV sets targets environment variable (see below) -U var QEMU_UNSET_ENV unsets targets environment variable (see below) -0 argv0 QEMU_ARGV0 forces target process argv[0] to be 'argv0' -r uname QEMU_UNAME set qemu uname release string to 'uname' -B address QEMU_GUEST_BASE set guest_base address to 'address' -R size QEMU_RESERVED_VA reserve 'size' bytes for guest virtual address space -d item[,...] QEMU_LOG enable logging of specified items (use '-d help' for a list of items) -D logfile QEMU_LOG_FILENAME write logs to 'logfile' (default stderr) -p pagesize QEMU_PAGESIZE set the host page size to 'pagesize' -singlestep QEMU_SINGLESTEP run in singlestep mode -strace QEMU_STRACE log system calls -seed QEMU_RAND_SEED Seed for pseudo-random number generator -version QEMU_VERSION display version information and exit Defaults: QEMU_LD_PREFIX = /usr/gnemul/qemu-aarch64 QEMU_STACK_SIZE = 8388608 byte You can use -E and -U options or the QEMU_SET_ENV and QEMU_UNSET_ENV environment variables to set and unset environment variables for the target process. It is possible to provide several variables by separating them by commas in getsubopt(3) style. Additionally it is possible to provide the -E and -U options multiple times. The following lines are equivalent: -E var1=val2 -E var2=val2 -U LD_PRELOAD -U LD_DEBUG -E var1=val2,var2=val2 -U LD_PRELOAD,LD_DEBUG QEMU_SET_ENV=var1=val2,var2=val2 QEMU_UNSET_ENV=LD_PRELOAD,LD_DEBUG Note that if you provide several changes to a single variable the last change will stay in effect.
3.2 動かしてみる
「2.3 コンパイルテスト」のバイナリを動かしてみます。
$ ./qemu-2.4.0.1/aarch64-linux-user/qemu-aarch64 -L /usr/local/arm64_linux/aarch64-linux-gnu ./hello Hello AArch64 world.
-Lオプションで共有ライブラリパスを指定しています。
3.3 デバッガを使ってみる
qemu-armでは、-gオプションを使ってgdbから接続する事ができます。 argを表示するプログラムを例にして接続手順を示します。
注意: スタティックリンクしないと、ステップ実行の度にアクセスエラーするようです。この為、コンパイル時に-staticオプションを指定します。
以下のようにコンパイルした実行ファイルをqemuで起動します。 このとき、プロンプトは戻らず、ずっと待った状態になります。
$ cat gdbtest.c #include <stdlib.h> #include <stdio.h> int main(int argc, char* argv[]) { int i ; printf("argc=%d\n",argc) ; for( i=0 ; i<argc ; i++ ){ printf("%s\n",argv[i]) ; } return(0) ; } $ aarch64-linux-gnu-gcc -static -g gdbtest.c -o gdbtest $ ./qemu-2.4.0.1/aarch64-linux-user/qemu-aarch64 -g 1111 -L /usr/local/arm64_linux/aarch64-linux-gnu ./gdbtest aaa bbb ccc ddd eee
別のターミナルなどでgdbを起動します。
$ aarch64-linux-gnu-gdb -q (gdb) file gdbtest Reading symbols from gdbtest...done. (gdb) target remote localhost:1111 Remote debugging using localhost:1111 warning: Can not parse XML target description; XML support was disabled at compile time _start () at ../sysdeps/aarch64/start.S:48 48 mov x29, #0 (gdb) b main Breakpoint 1 at 0x400ab0: file gdbtest.c, line 7. (gdb) c Continuing. Breakpoint 1, main (argc=6, argv=0x40007fffb8) at gdbtest.c:7 7 printf("argc=%d\n",argc) ; (gdb) l 2 #include <stdio.h> 3 4 int main(int argc, char* argv[]) 5 { 6 int i ; 7 printf("argc=%d\n",argc) ; 8 for( i=0 ; i<argc ; i++ ){ 9 printf("%s\n",argv[i]) ; 10 } 11 return(0) ; (gdb)
ブレークポイントを指定したり変数の内容を表示できます。 Cプログラムの真の開始位置である_start()で止まっている状態なので、 「c」(continue)コマンドで 実行再開します。
4 終わりに
唐突に思いついて遊んだ結果なので役に立つ事はあまり無いと思いますが、 もし役に立つ事があれば幸いです。