x86は増築に増築を重ねた複雑なモードと命令セットを持っています。その昔、比較的シンプルな命令セットから出発したArmもまた、重ねた歴史の中で命令セットは複雑化してきています。それどころか、用途が幅広く、かつカスタマイズ可能なArmの方がターゲットによって使用可能な命令セットの選択範囲はx86よりバリエーションが多様にも思えます。アセンブラ書きをするにあたって、ターゲットにするボードやらOS上でどんな範囲の命令が使えるのかまず知らなければなりますまい。今回は、32ビットか、64ビットかという基本中の基本のところから調べて行きます。
※「ぐだぐだ低レベルプログラミング」投稿順indexはこちら
最初は、Raspberry Pi 3 model B+です。SoCはBroadcom社のBCM2837、CPUは、Arm Cortex A53の4コアです。Armv8命令セットを実装したCPUであるので
ハードウエア的には64bit機
です。しかし、前回のプログラミング例は32ビット機のつもりでアセンブラ書いてしまっていました。なんでか、と言えば、OSにRaspbianをインストールしてあったからです。unameコマンドで「ハードウエア名」を調べてみると
$ uname -m armv7l
armv7l でした。lはlittle endianです(x86などと同じ、LSバイトをアドレス下位に置く)。CPUコア自体は、64ビットモードをサポートしたarmv8の筈ですが、一つ前のarmv7(32ビットモードのみ)に設定されていました。cpuinfoを見てみると以下のとおり。
$ cat /proc/cpuinfo processor : 0 model name : ARMv7 Processor rev 4 (v7l) BogoMIPS : 38.40 Features : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae evtstrm crc32 CPU implementer : 0x41 CPU architecture: 7 CPU variant : 0x0 CPU part : 0xd03 CPU revision : 4 ~以下略~
それで実行ファイルのオブジェクト型式をみてみれば(調べたのは gcc のリンク先の実体ファイル arm-linux-gnueabihf-gcc-8)、
$ file /usr/bin/arm-linux-gnueabihf-gcc-8 /usr/bin/arm-linux-gnueabihf-gcc-8: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-armhf.so.3, for GNU/Linux 3.2.0, BuildID[sha1]=b507462b5ae8ff3ec55a4d432b7f2787e08d54b5, stripped
32ビット、リトルエンディアンのフォーマットでした。そこで前回作成の小さなプログラムをダンプして確認してみました。一番下の get5 関数部分をアセンブラ記述したわけですが、32ビット幅の「ARM命令」にアセンブルされていることが分かります。
$ objdump -d main main: ファイル形式 elf32-littlearm ~途中略~ 00010410 <main>: 10410: e92d4800 push {fp, lr} 10414: e28db004 add fp, sp, #4 10418: e24dd008 sub sp, sp, #8 1041c: eb00000b bl 10450 <get5> 10420: e50b0008 str r0, [fp, #-8] 10424: e51b1008 ldr r1, [fp, #-8] 10428: e59f0010 ldr r0, [pc, #16] ; 10440 <main+0x30> 1042c: ebffffad bl 102e8 <printf@plt> 10430: e3a03000 mov r3, #0 10434: e1a00003 mov r0, r3 10438: e24bd004 sub sp, fp, #4 1043c: e8bd8800 pop {fp, pc} 10440: 000104c8 .word 0x000104c8 ~以下略~ 00010450 <get5>: 10450: e3a00005 mov r0, #5 10454: e12fff1e bx lr
レジスタがどんな見え方なのかも確認しておくと、以下のような感じ。32ビット幅のレジスタが16本(r13はsp, r14はlr, r15はpcと表記)。
(gdb) info reg r0 0x1 1 r1 0x7efff5a4 2130703780 r2 0x7efff5ac 2130703788 ~途中略~ r11 0x7efff454 2130703444 r12 0x7efff4d0 2130703568 sp 0x7efff444 0x7efff444 lr 0x10458 66648 pc 0x10460 0x10460 cpsr 0x60000030 1610612784 fpscr 0x0 0
Raspbianを走らせているPi3は気兼ねなく32ビット機と思ってプログラムできるのでした。
念のため、Raspberry Pi 1 model B+についても調べておきます。SoCはBCM2835、CPUはARM1176JSFです。アーキテクチャは、
armv6l
でした。armv7のPi 3より一つ古く見えますが、cpuinfoの CPU architecture番号をみれば同じ 7。Pi 3とPi 1ならPi 3では NEONが使えるけれど、Pi 1では使えないとか差が無い分けではないですが、「素」のCPU部分であれば互換性は高い筈。
$ cat /proc/cpuinfo processor : 0 model name : ARMv6-compatible processor rev 7 (v6l) BogoMIPS : 697.95 Features : half thumb fastmult vfp edsp java tls CPU implementer : 0x41 CPU architecture: 7 CPU variant : 0x0 CPU part : 0xb76 CPU revision : 7 Hardware : BCM2835 Revision : 0010
Raspbian OSは Pi 1以来、かつ現在でも Pi ZeroのようなArm11機が活躍しているので、互換性の面からも32ビットということなのかもしれません。
さて、手元のマシンにArmの64ビットコード書けるものはないのか?あります。Jetson nanoです。CPUコアは Cortex A57、これはPi 3のCortex A53と兄弟機種ともいえる64ビットコアです。Jetson nanoは、いろいろ仕様面ではRaspberry Piを意識していると思いますが、GPUを動かすための装置であるので、OSは64ビット化されています。
$ uname -m aarch64
aarch64は、armv8以降で64ビットモードを表すArmの呼び名です。(32ビットの場合は、aarch32)cpuinfoも確認しておきます。
$ cat /proc/cpuinfo processor : 0 model name : ARMv8 Processor rev 1 (v8l) BogoMIPS : 38.40 Features : fp asimd evtstrm aes pmull sha1 sha2 crc32 CPU implementer : 0x41 CPU architecture: 8 CPU variant : 0x1 CPU part : 0xd07 CPU revision : 1 ~以下略~
以前にビルドしてあった、GPUのサンプルプログラム vectorAddの実行ファイルのオブジェクトコード型式を確認すれば
$ file vectorAdd vectorAdd: ELF 64-bit LSB shared object, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-, for GNU/Linux 3.7.0, BuildID[sha1]=6431d33571922bbfbd048c7e5edeeef06fc71be7, with debug_info, not stripped
64ビットのリトルエンディアン型式であることが確認できます。念のためCPUレジスタも確認すると
(gdb) info reg x0 0x1 1 x1 0x7ffffff298 549755810456 x2 0x7ffffff2a8 549755810472 x3 0x555555d0d8 366503907544 x4 0x0 0 x5 0x0 0 x6 0x0 0 x7 0x0 0 x8 0x555560af20 366504619808 x9 0x0 0 x10 0x0 0 x11 0x0 0 x12 0x0 0 x13 0x0 0 x14 0x2 2 x15 0x0 0 x16 0x55555f65b0 366504535472 x17 0x7fb7c7d298 548544172696 x18 0x7fb7d9ba70 548545346160 x19 0x55555c0578 366504314232 x20 0x0 0 x21 0x555555cf74 366503907188 x22 0x0 0 x23 0x0 0 x24 0x0 0 x25 0x0 0 x26 0x0 0 x27 0x0 0 x28 0x0 0 x29 0x7ffffff0c0 549755809984 x30 0x7fb7c686e0 548544087776 sp 0x7ffffff0c0 0x7ffffff0c0 pc 0x555555d0e4 0x555555d0e4 <main()+12> cpsr 0x60000000 [ EL=0 C Z ] fpsr 0x0 0 fpcr 0x0 0
31本の汎用レジスタと、32本目に割り付けられたsp(場合によってはzeroとなる)、および32ビットモードでは16本目のレジスタであったpcが独立していることが見えます。レジスタの幅は64ビットの筈ですが、上記例ではアドレスとして実際使われてる48ビット幅の値が見えています。
Jetson nanoのcpuinfo見ると、Pi 1 にも Pi 3にも当然のようにある thumb が見当たらないし、vfpもありません。勿論 neonもです。SIMDやFP処理のメインはあくまで GPUなので、CPU側には無駄なものは搭載しない、という方針が見てとれます。
他にもマイコン系のNucleoやPSoCなどArm Cortex-M系のボードが何枚もあるのですが、こちらは32ビット機ばかりなので今回は取り上げませんでした。また後で、差が気になる局面があったらまとめてみるかもしれません。
今回は、32ビットと64ビットということで、OSとかライブラリとかのサポートが不可欠で、ユーザモードの中で「気楽に」スイッチできるモードではなかったです。次回は、ユーザーモードの中でスイッチできる
Arm と Thumb
について、Pi 3上で実際にアセンブラ書いて動かしてみたいと思います。