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を通しておきます。

  1. export PATH="/usr/local/arm_linux/bin:$PATH"

2.2.2 binutilsのビルド

  1. bzip2 -dc ~/Downloads/binutils-2.25.tar.bz2 | tar xf -
  2. mkdir -p binutils-2.25/build_linux
  3. pushd binutils-2.25/build_linux
  4. ../configure --target=arm-linux-gnueabi --prefix=/usr/local/arm_linux
  5. make
  6. make install
  7. popd

2.2.3 gccのビルド(1回目)

  1. bzip2 -dc ~/Downloads/gcc-4.9.2.tar.bz2 | tar xf -
  2. mkdir -p gcc-4.9.2/build1_linux
  3. pushd gcc-4.9.2/build1_linux
  4. ../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++
  5. make
  6. make install
  7. popd

まだARM用のCライブラリが存在していませんので、configureにはgcc内に含まれるライブラリ群を ビルドしないように指定しています。

ソースはもう一度利用する為、そのままにしておきます。

2.2.4 Linuxヘッダのインストール

  1. xz -dc ~/Downloads/linux-3.18.1.tar.xz | tar xf -
  2. pushd linux-3.18.1
  3. make mrproper
  4. make headers_install ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- INSTALL_HDR_PATH=/usr/local/arm_linux/arm-linux-gnueabi
  5. popd

システムコールを行う方法はOSに依存する為、Linuxのヘッダだけをインストールします。

2.2.5 glibcのビルド

  1. xz -dc ~/Downloads/glibc-2.20.tar.xz | tar xf -
  2. mkdir -p glibc-2.20/build_linux
  3. pushd glibc-2.20/build_linux
  4. ../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
  5. make
  6. make install
  7. popd

2.2.6 libstdc++のビルド

  1. mkdir -p gcc-4.9.2/build_linux_libstdc++
  2. pushd gcc-4.9.2/build_linux_libstdc++
  3. ../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
  4. make
  5. make install
  6. popd

1回目のgccビルドで使用したソースを利用しています。

2.2.7 gccのビルド(2回目)

  1. mkdir -p gcc-4.9.2/build2_linux
  2. pushd gcc-4.9.2/build2_linux
  3. ../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
  4. make
  5. make install
  6. popd

ビルドしたglibcを参照するgccをビルドします。1回目のgccビルドで使用したソースを利用しています。

2.2.8 gdbのビルド

  1. xz -dc ~/Downloads/gdb-7.8.1.tar.xz | tar xf -
  2. mkdir -p gdb-7.8.1/build_linux
  3. pushd gdb-7.8.1/build_linux
  4. ../configure --target=arm-linux-gnueabi --prefix=/usr/local/arm_linux
  5. make
  6. make install
  7. 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 終わりに

唐突に思いついて遊んだ結果なので役に立つ事はあまり無いと思いますが、 もし役に立つ事があれば幸いです。

5 履歴

  2014/12/27 : 初版。

TOP PREV

Author: TANE

Emacs 24.4.1 (Org mode 8.2.10)