ネタ 2015-005
Nexus9をroot化して64bit ARM(AARCH64)のアセンブラを試してみた
Nexus9は64bit ARM CPUを搭載しており、いち早く64bit ARM CPU用のソフトを自作して動かすことができる。
とりあえず、単純なHello worldのようなプログラムや、2つの数を加算するだけといったソフトを書いて動かすのを試してみた。
blogに書いていて散らばっているので、まとめをここに書く。
1. 使ったもの
・Nexus9
・Winddows PC
2. 準備
(1)Neuxus9をroot化する
→ Nexus 9をroot化
(2)Windows PCにNDKをインストールする
Windows7(64bit版)のPCで、c:\android-ndk-r10dにNDKをインストールした。(NDKは新しいバージョンが出ているので、そのあたりは読み換えで)
3. Hello worldを動かす手順
まず、手始めに簡単はHello worldから動かしてみる。(ここでは、まだアセンブラは使わない)
適当な作業フォルダー(例えば、c:\workなど)を作って、ソースコードを書く。
c:\workの下にjniというフォルダを作成し、そこにソースコードのファイルのhello.cを置く。
hello.c (c:\work\jni\hello.c) の内容は、以下の通り。
|
#include <stdio.h> int main(int argc, char **argv, char **evnp) { #ifdef __LP64__ printf("64bit\n"); #else printf("32bit\n"); #endif printf("Hello world.\n"); return 0; } |
そして、Android.mkというビルド用の設定を記述したファイル作る。
Android.mk (c:\work\jni\Android.mk) の内容は、以下の通り。
|
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := hello LOCAL_SRC_FILES := hello.c LOCAL_CFLAGS += -pie -fPIE LOCAL_LDFLAGS += -pie -fPIE include $(BUILD_EXECUTABLE) |
ビルド用のバッチファイルbuild.batというファイルも作成した。
build.bat (c:\work\build.bat) の内容は以下の通り。
|
rem =========== environment setting ========== set PATH=%PATH%;c:\android-ndk-r10d\ set ANDROID_NDK_ROOT=c:\android-ndk-r10d\ set NDK_PROJECT_PATH=. echo =========== 64bit build ============ call ndk-build -B V=1 APP_ABI=arm64-v8a pause |
このバッチファイルを実行すると、ソースコードhello.cがビルドされて、64bitのバイナリがc:\work\libsフォルダー以下に生成される。
hello (c:\work\libs\arm64-v8a\hello) が、64ビットのバイナリのファイルだ。
このファイルをNexus9の実機に転送して実行してみた。
Nexus9はroot化したものを使った。
Nexus9の/sdcardフォルダーの直下にtmpというフォルダを作ってそこに一旦helloを転送する。
|
adb push hello /sdcard/tmp/hello |
そして、Nexus9側で、
/dataフォルダー直下にtmpというフォルダを作って、そちらにコピーする。
(フォルダーのパーミッション設定が必要だったかも)
|
cp /sdcard/tmp/* /data/tmp |
そして、実行するためにchmodコマンドで実行属性を付ける。
|
chmod 744 /data/tmp/hello |
|
/data/tmp/hello |
|
64bit Hello world. |
と表示された。
ちゃんと64bit ARMのコードが動いているのが確認できた。
4. インラインアセンブラを使ってみる
次に64bit ARMのインラインアセンブラも試してみた。
先ほどと同様の手順で、ソースコード(addvalue.c)は次のように書いた。
アセンブラで2つの数字を加算するというコードだ。
|
#include <stdio.h> long int addvalues(long int val1,long int val2) { long int result; __asm__ ( "ADD %[Rd],%[Rs1],%[Rs2]" : [Rd] "=r" (result) : [Rs1] "r" (val1), [Rs2] "r" (val2) ); return result; } int main(int argc, char **argv, char **envp) { long int val=addvalues(1234567890123456789L,1L); printf("addvalues = %ld\n",val); return 0; } |
2つの数 1234567890123456789と1を加算するというコードである。
CPUのレジスタが64bitになったので、intの代わりにlong intを使う。
同様にビルドして実行すると、次のようになった。
addvalues = 1234567890123456790
と正しく加算結果が表示された。
インラインアセンブラで64bitの数値の加算のコードがうまく動いたのが確認できた。
レジスタ指定の書式は32bit ARMと同じ文法である。この32bit用にビルドしても通ったりする。
5. 直接アセンブラで書いてみる
先程のインラインアセンブラで記述した2つの整数を加算する部分をアセンブラで書き直してみた。
アセンブラ部分のソースコードadd.s (c:\work\jni\add.s)はこのようになった。
|
.global addvalues .text .align 4 addvalues: ADD x0,x0,x1; ret; |
C言語のソース部分(c:\work\jni\addvalue.c)は、以下の通り。
|
#include <stdio.h> extern long int addvalues(long int, long int); int main(int argc, char **argv, char **envp) { long int val=addvalues(1234567890123456789L,1L); printf("addvalues = %ld\n",val); return 0; } |
アセンブラで書いたaddvaluesという関数を呼び出している。
Android.mkファイルは、このように設定した。
|
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := addvalue LOCAL_SRC_FILES := addvalue.c LOCAL_SRC_FILES += add.s LOCAL_CFLAGS += -pie -fPIE LOCAL_LDFLAGS += -pie -fPIE include $(BUILD_EXECUTABLE) |
-pieと-fPIEのコンパイルオプションの追加は、しなくても64bit ARMのバイナリ生成したものは実機で動作したのだけど、一応念のために追加しておいた。
ビルド手順を記述したバッチファイルのbuild.bat (c:\work\build.bat) の内容は前と一緒で以下の通り。
|
rem =========== environment setting ========== set PATH=%PATH%;c:\android-ndk-r10d\ set ANDROID_NDK_ROOT=c:\android-ndk-r10d\ set NDK_PROJECT_PATH=. echo =========== 64bit build ============ call ndk-build -B V=1 APP_ABI=arm64-v8a pause |
ソースコードのadd.sについては、32bit ARM用のものを64bit ARM用に書き直して作成した。
加算の部分について
32bit ARM用だと
|
ADD r0,r0,r1; |
と書くところを、64bit用に書き直すと以下のようにxレジスタを使う。
|
ADD x0,x0,x1; |
また、関数からのリターン命令を書き直す必要がある。
|
bx lr; |
と書くところを、64bit用に書き直すと以下のようにretだけ書けば良い。
|
ret; |
このように書き直して、ビルドしたものは、先程と同様に実機に転送して同じように動作させる。
インラインアセンブラ版のものと同じ動作結果が得られるのが、確認できるはずだ。
6. Hello worldをアセンブラで書きなおす
64bit ARMのアセンブラでHello worldを書くと、このようになった。C言語部分は無くなって、アセンブリのソースコードのみになる。
ソースコード hello.s (c:\work\jni\hello.s)
|
.global main .text .align 4 main: sub sp,sp,16 str x30,[sp] adrp x0,message add x0,x0,#:lo12:message bl puts ldr x30,[sp] add sp,sp,16 mov w0,0 ret message: .string "Hello world.\n" |
Android.mkファイル(c:\work\jni\Android.mk)は次のようになっている。
|
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := hello LOCAL_SRC_FILES := hello.s LOCAL_CFLAGS += -pie -fPIE LOCAL_LDFLAGS += -pie -fPIE include $(BUILD_EXECUTABLE) |
build.bat (c:\work\build.bat) の内容は以下の通り。
|
rem =========== environment setting ========== set PATH=%PATH%;c:\android-ndk-r10d\ set ANDROID_NDK_ROOT=c:\android-ndk-r10d\ set NDK_PROJECT_PATH=. echo =========== 64bit build ============ call ndk-build -B V=1 APP_ABI=arm64-v8a pause |
このバッチファイルを実行すれば、ソースコードがビルドされる。
上記のソースコードhello.sは、C言語で記述された
|
puts("Hello world\n"); |
というコードをgccに-Sオプションを付けてビルドして得られたソース出力ファイルを参考にした。
puts関数を呼び出すのは、
|
bl puts |
となる。
putsに渡すパラメータとして文字列”Hello world”を定義したmessageのアドレスをx0レジスタに入れておく必要がある。
|
adrp x0,message add x0,x0,#:lo12:message |
と書く。
32bitのARMと比べるとアドレスを指定するのが面倒になっているらしい。
adrp命令とか「:lo12:」という修飾子がよく分からない。
x86 CPUの8086とかの16bit CPUのSegment+Offsetみたいなものだろうか?
7. Hello worldのコードの修正
64bit ARMアセンブラのHello worldのソースコードをマクロを使って少し書き直してみた。
|
.global main
.text .align 4 .macro adrl reg,label adrp \reg,\label add \reg,\reg,:lo12:\label .endm main: sub sp,sp,16 str x30,[sp] adrl x0,message bl puts ldr x30,[sp] add sp,sp,16 mov w0,0 ret message: .string "Hello world.\n" |
文字列”Hello world”のアドレスmessageをx0レジスタに入れるのに
|
adrp x0,message add x0,x0,#:lo12:message |
と書いていたが、これをadrl疑似命令で記述したかった。
しかし、adrl疑似命令はNDKのアセンブラではサポートされていないので、アセンブラのマクロ機能を使って記述してみた。
|
.macro adrl reg,label adrp \reg,\label add \reg,\reg,:lo12:\label .endm |
と、adrl疑似命令をマクロで定義して、
|
adrl x0,message
|
このように書くことで、adrp命令とadd命令で2行書いていた部分を簡潔に1行で記述することができる。