CMakeとAndroid NDKでhello worldをビルドする

(ARM ABIの復習のためにHello worldが動く程度のsyscall emulatorを作ろう企画。実際には真面目に作ってもWindroy( http://www.socketeq.com/ )程度の互換性しか取れないので実用的なエミュレータを作るならfull system emulationは必須だと思う。)
Windows向けのARM Linuxターゲットコンパイラを入手する一番簡単な方法はAndroid NDKだということに気付いた。というわけで、Android NDKでhello worldを作り、それをELF的にロードしてuser mode emulationで実行してみたら面白いような気がする。

Hello worldをビルドする

何はなくとも、とりあえずHello worldのバイナリをビルドする。
長いコマンドをいちいち入力するのは面倒なので、CMakeでMakefileを生成し、それを使ってビルドすることにする。CMakeを使えば、makeの代りにninjaを使うことができるので、高速にビルドできて便利。
CMake用のツールチェインファイルは https://github.com/taka-no-me/android-cmake を使う。

list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake")

set(ENV{ANDROID_NDK} "c:/android/android-ndk-r9d")
set(CMAKE_TOOLCHAIN_FILE "${CMAKE_CURRENT_LIST_DIR}/cmake/android.toolchain.cmake")
set(LIBRARY_OUTPUT_PATH_ROOT "${CMAKE_CURRENT_BINARY_DIR}" CACHE PATH "")
set(EXECUTABLE_OUTPUT_PATH_ROOT "${CMAKE_CURRENT_BINARY_DIR}" CACHE PATH "")

cmake_minimum_required(VERSION 2.8)

project(test)

add_executable(hello hello.c)

android.toolchain.cmake は、デフォルトではソースディレクトリにバイナリを出力する。この動作は頂けないので、LIBRARY_OUTPUT_PATHとEXECUTABLE_OUTPUT_PATHは手動で設定する。
hello.cは普通の内容。

#include <stdio.h>

int
main(int ac, char** av){
    printf("hello. %d\n",123);
    return 0;
}

出力されたhello worldを観察する

ELFの情報はreadelf -aで見る。

C:\build\armtest>c:\android\android-ndk-r9d\toolchains\arm-linux-androideabi-4.6\prebuilt\windows-x86_64\bin\arm-linux-androideabi-readelf.exe -a bin\hello > out.txt

(Cygwinのreadelfを普通に使ってもOK)
出力を見ると、

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:               0x8c0c
  Start of program headers:          52 (bytes into file)
  Start of section headers:          40412 (bytes into file)
  Flags:                             0x5000000, Version5 EABI
- snip -
Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  PHDR           0x000034 0x00008034 0x00008034 0x00100 0x00100 R   0x4
  INTERP         0x000134 0x00008134 0x00008134 0x00013 0x00013 R   0x1
      [Requesting program interpreter: /system/bin/linker]
  LOAD           0x000000 0x00008000 0x00008000 0x0238c 0x0238c R E 0x1000
  LOAD           0x002e9c 0x0000be9c 0x0000be9c 0x00164 0x00168 RW  0x1000
  DYNAMIC        0x002ebc 0x0000bebc 0x0000bebc 0x000e8 0x000e8 RW  0x4
  GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0
  EXIDX          0x00227c 0x0000a27c 0x0000a27c 0x00110 0x00110 R   0x4
  GNU_RELRO      0x002e9c 0x0000be9c 0x0000be9c 0x00164 0x00164 RW  0x4
- snip -
Dynamic section at offset 0x2ebc contains 24 entries:
  Tag        Type                         Name/Value
- snip -
 0x00000001 (NEEDED)                     Shared library: [libc.so]
 0x00000001 (NEEDED)                     Shared library: [libdl.so]
- snip -
 0x0000001e (FLAGS)                      BIND_NOW
 0x6ffffffb (FLAGS_1)                    Flags: NOW
 0x00000000 (NULL)                       0x0

この情報からエミュレートすべきABI等が判る。

エミュレーションに必要そうなもの

全てをC++で書くのは流石に面倒なので、ELFローダやダイナミックリンカ、MMUとsyscallエミュレーションはnmoshで書き、ARMエミュレータは他所から拾ってくることにする。
ARMエミュレータのメモリ読み書き要求はちょっとしたTLBを持ち、nmosh側のインターフェースは"ページ要求"と"例外"、"syscall"の3つに絞る。(実際にはページ要求やsyscallは例外の一種なので、例外だけがインターフェースということになる。)
サポートすべきABIはNDKの docs/text/CPU-ARCH-ABIS.text に書かれている。

- ARM Architecture Reference manual (a.k.a ARMARM)
- Procedure Call Standard for the ARM Architecture (a.k.a. AAPCS)
- ELF for the ARM Architecture (a.k.a. ARMELF)
- ABI for the ARM Architecture (a.k.a. BSABI)
- Base Platform ABI for the ARM Architecture (a.k.a. BPABI)
- C Library ABI for the ARM Architecture (a.k.a. CLIABI)
- C++ ABI for the ARM Architecture (a.k.a. CPPABI)
- Runtime ABI for the ARM Architecture (a.k.a. RTABI)

- ELF System V Application Binary Interface
(DRAFT - 24 April 2001)

- Generic C++ ABI (http://mentorembedded.github.com/cxx-abi/abi.html)

Note that the AAPCS standard defines 'EABI' as a moniker used to specify
a _family_ of similar but distinct ABIs. Android follows the little-endian
ARM GNU/Linux ABI as documented in the following document:

> http://sourcery.mentor.com/sgpp/lite/arm/portal/kbattach142/arm_gnu_linux_abi.pdf

これらのドキュメントを読みこまないといけない。
実装のためには:

ビルドが容易で、Cで書かれていて、実績の有る、三拍子そろったエミュレータはなかなか無い。MAMEのarm7( http://git.redump.net/mame/tree/src/emu/cpu/arm7 )かuARM( https://github.com/syuu1228/uARM )かなぁ。。いっそのことOVPsimのようなフルシステムエミュレータを真面目に使っても面白いかもしれない。
もちろん自前で書いても良いけど、あまり優先度は高くない。

  • ELFローダとダイナミックリンカ

これは特に難しいポイントは無い。... ただこれも実装は面倒なので、いっそのことAndroidをビルドして本物のダイナミックリンカを使うと面白そうではある。
この場合、syscallエミュレータのかなりの部分を真面目に実装する必要が有る。initを実行して最初の"A N D R O I D"を見る を目標に変えようかな。。

一番面白いパート。Androidのlibcであるbionicから参照されているsyscallは https://github.com/android/platform_bionic/tree/master/libc/arch-arm/syscalls 程度。しかし、問題はioctlとかsysctlの類で、本当にエミュレートすべきAPIがどのくらいあるのかは未知。例えば、カーネルフレームバッファ等のデバイスもエミュレーションする必要は有るだろう。