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
これらのドキュメントを読みこまないといけない。
実装のためには:
- ARMエミュレータ
ビルドが容易で、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"を見る を目標に変えようかな。。
- syscallエミュレータ
一番面白いパート。Androidのlibcであるbionicから参照されているsyscallは https://github.com/android/platform_bionic/tree/master/libc/arch-arm/syscalls 程度。しかし、問題はioctlとかsysctlの類で、本当にエミュレートすべきAPIがどのくらいあるのかは未知。例えば、カーネルフレームバッファ等のデバイスもエミュレーションする必要は有るだろう。