ネタ 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行で記述することができる。