QEMU ARM で遊ぼう
Table of Contents
1 はじめに
QEMUはオープンソースのCPUエミュレータです。
ARMは 組み込み機器、スマートフォン、携帯ゲーム機 などに広く用いられている CPUです。
ある日、QEMUにLinuxのユーザーランドをエミュレートするユーザーエミュレーション と呼ばれる環境があるのを知りました。QEMUではARMのエミュレーションもサポートしている ということで、これらを組み合わせて遊んでみた結果です。
ただの暇つぶしなので、役に立たないかも知れませんが、ログをだらだらと記してみたいと 思います。
2 ARMクロスコンパイル環境を作る
本へっぽこページのコンテンツでは日常的な事なのですが、クロスコンパイラはソースからビルドします。 今回は VMware上の Fedora(Linuxのディストリビューションの一つ)でビルドをしました。 ビルドに必要なライブラリなどは予めインストールされているものと仮定しています。 クロスgccのビルドには、GMP,MPFR,MPC を始め、texinfoやncurses などの開発ライブラリ (これらのライブラリはいずれもx86ネイティブなものです)なども必要になるかと思いますが、 それらはパッケージインストールしてもらえれば良いと思います。
2.1 ARMクロスコンパイラのビルドに必要なファイル
以下のアーカイブを使用しました。
- 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/arm_linux にインストールするものとしてビルドします。 アーカイブ群は ~/Downloads の下に、ビルドはカレントディレクトリで行う ものとしています。
また、作法的には良くないのですが、/usr/local および /usr/local/arm_linuxは ファイルパーミッションを一般ユーザに空けたものとして実行しています。 「そういうの嫌なんですけど?」という方はインストール先ディレクトリを 任意のディレクトリに読み替えるか、「sudo make install」していると読み替えて ください。
今回はLinux(x86)の QEMUを使う都合でコンパイラのビルドもLinux(x86)上で 行いましたが、Cygwinなんかでもビルドできるのではないかと思います。
2.2.1 事前の設定
予めビルドされるであろう実行ファイル群が置かれるパスにPATHを通しておきます。
- export PATH="/usr/local/arm_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=arm-linux-gnueabi --prefix=/usr/local/arm_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=arm-linux-gnueabi --prefix=/usr/local/arm_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
まだARM用の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=arm CROSS_COMPILE=arm-linux-gnueabi- INSTALL_HDR_PATH=/usr/local/arm_linux/arm-linux-gnueabi
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/arm_linux/arm-linux-gnueabi --host=arm-linux-gnueabi --build=$(../scripts/config.guess) --disable-profile --enable-kernel=2.6.32 --with-headers=/usr/local/arm_linux/arm-linux-gnueabi/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=arm-linux-gnueabi --prefix=/usr/local/arm_linux/arm-linux-gnueabi --disable-multilib --disable-shared --disable-nls --disable-libstdcxx-threads --disable-libstdcxx-pch --with-gxx-include-dir=/usr/local/arm_linux/arm-linux-gnueabi/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=arm-linux-gnueabi --prefix=/usr/local/arm_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=arm-linux-gnueabi --prefix=/usr/local/arm_linux
make
make install
popd
2.3 コンパイルテスト
ビルドしたクロスコンパイラを使って以下のソースをコンパイルしてみます。
#include <stdlib.h> #include <stdio.h> int main() { printf("Hello ARM world.\n") ; return(0) ; }
hello.cなどにセーブし、以下のような感じでコンパイルします。
$ arm-linux-gnueabi-gcc hello.c -o hello $ arm-linux-gnueabi-readelf -h hello ELF Header: Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 Class: ELF32 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: EXEC (Executable file) Machine: ARM Version: 0x1 Entry point address: 0x102c4 Start of program headers: 52 (bytes into file) Start of section headers: 7368 (bytes into file) Flags: 0x5000202, has entry point, Version5 EABI, soft-float ABI Size of this header: 52 (bytes) Size of program headers: 32 (bytes) Number of program headers: 8 Size of section headers: 40 (bytes) Number of section headers: 37 Section header string table index: 34
この例では 共有ライブラリを使用する実行ファイルが生成されます。
3 QEMUでARMバイナリを動かしてみる
QEMUを使って、ARMのバイナリを動かしてみます。
3.1 QEMUのインストール
ソースからビルドしたい所ですが、ここはパッケージをありがたく使わせてもらいます。 「qemu-arm」コマンドが使えればひとまずOKです。
$ qemu-arm usage: qemu-arm [options] program [arguments...] Linux CPU emulator (compiled for arm 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 options QEMU_LOG activate log -D logfile QEMU_LOG_FILENAME override default logfile location -p pagesize QEMU_PAGESIZE set the host page size to 'pagesize' -singlestep QEMU_SINGLESTEP run in singlestep mode -strace QEMU_STRACE log system calls -version QEMU_VERSION display version information and exit Defaults: QEMU_LD_PREFIX = /usr/qemu-arm QEMU_STACK_SIZE = 8388608 byte QEMU_LOG = /tmp/qemu.log 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-arm -L /usr/local/arm_linux/arm-linux-gnueabi ./hello Hello ARM 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) ; } $ arm-linux-gnueabi-gcc -static -g gdbtest.c -o gdbtest $ qemu-arm -g 1111 -L /usr/local/arm_linux/arm-linux-gnueabi ./gdbtest aaa bbb ccc ddd eee
別のターミナルなどでgdbを起動します。
$ arm-linux-gnueabi-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/arm/start.S:79 79 mov fp, #0 (gdb) b main Breakpoint 1 at 0x10920: file gdbtest.c, line 7. (gdb) c Continuing. Breakpoint 1, main (argc=6, argv=0xf6fff14c) 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 終わりに
唐突に思いついて遊んだ結果なので役に立つ事はあまり無いと思いますが、 もし役に立つ事があれば幸いです。