はじめに
Web系エンジニアも低レイヤーを学ぶべきと巷で言われていますが、本当なのだろうか。。。
基本情報技術者試験を勉強するという選択肢もありましたが、個人的に分かりにくかったので(レジスタはCPU内部にある記憶装置って言われてもピンとこない)、アウトプット主体で学んで、その都度理解できない概念を調べてインプットしていくことにしました。
アウトプットの方法として、前から触ってみたかった**Raspberry Pi 3(ラズパイ)**を購入してアセンブラを約1ヶ月ほど勉強してみました。同じような方々の助けになれば幸いです。
今記事ではラズパイを使用するためARMでの実装です。
「ARMとは?」と言ったアーキテクチャから発展編では簡単なアルゴリズムを実装するまで解説しています。
間違っているところがあればご指摘いただけるとありがたいです。
現在の記事 → Raspberry Pi 3でアセンブラをはじめから〜基礎編〜
作成中 → Raspberry Pi 3でアセンブラをはじめから〜発展編〜
セットアップ
具体的なセットアップ方法はこちら
私が用意したものは以下の7点です。
・ラズパイ(Raspberry Pi 3を使用)
・microSDカード(スターターキットで既に付いている場合は不要)
・アダプター(スターターキットで既にmicroSDカードにOSがインストールされている場合は不要)
・モニター
・HDMLケーブル
・有線マウス
・有線キーボード
*時々死んでしまうため、OSの再インストールが必要な場合はこちら
前提知識
そもそもアセンブラとは
C言語のコンパイルはgcc main.c
をするだけで可能ですが、実際は以下の4つの手順を踏んでいます。
- main.cをpreprocessorでコンパイラの前処理をする。簡単にいうと別のC言語「.i」に変換している(preprocessorとは#include <stdio.h>のようなディレクティブのこと)
$gcc -E main.c
で**「.i」**ファイルができるが基本的に2をすれば自動的に1もされる。 - **「.i」をアセンブラ「.s」**に直す。コンピュータが解釈できるアセンブリ言語の形式に変換。
$gcc -S main.c
- **「.s」を機械語「.o」**に直す。
$gcc -o {実行ファイル名} main.s
- 実行ファイルに直す。
$./{実行ファイル名}
アセンブラは**「.s」拡張子なので、1, 2を飛ばして3から始めます。
機械語に近い低水準言語**を学ぶことで、コンピューターの生の動作を意識した無駄のないプログラムを高水準言語でも作れるようになります。
Instruction set architecture(ISA)
CPUのアーキテクチャの種類としてx86とARMが2大巨塔です。
有名どこだと、2020年以降MacはIntel製のx86アーキテクチャから自社開発のARMアーキテクチャに切り替わりました。
x-86: 高性能+消費電力大
ARM: 性能にばらつきあり+省電力
という違いがあるそうです。だからM1 Macからバッテリー駆動時間が爆発的に伸びたのかな?
ちなみに、x-86なのかARMなのかで、Dockerイメージを使い分けないといけません。M1 Macに変えてこれまでのDockerfileが使えないなんてよく騒がれてましたが、CPUのアーキテクチャが原因なので対応すれば普通に使えます。
レジスタ
r0からr15までの16個のレジスタがあり、limited memoryのようなイメージ。一時記憶領域のため、プログラムが終了すると消えてしまうが最も高速。
- r0~r10: 一般的な目的で使われる
- r0~r3に第1~第4引数までセットされ、r0に戻り値が入る。それ以降はスタック
- r4以降は普通のレジスタ
- r11~r15: 特別な目的で使われる
- r11(fp): frame pointer
- r12(ip): intra-pocedure call scratch pointer(よく分からない)
- r13(sp): stack pointer
- r14(lr): link register(BL命令で分岐した場合の戻りアドレスを保持)
- r15(pc): program counter(実行中のメモリアドレスを保持)
ディレクティブの定義
- .text
- テキストセグメントの開始を指定する.ARM命令などの実行コードはテキストセグメントに配置する必要がある。
- .align 2
- Raspberry Pi 3は32ビットOSがインストールされているため、4byte単位でメモリが作られる。
- .global func
- 外部ファイルからもアクセス可能にする。
ARM命令セット
命令フォーマット | 説明 |
---|---|
mov r1, #4 | r1 = 4 *説明あり |
add r1, r2, r3 | r1 = r2 + r3 |
sub r1, r2, r3 | r1 = r2 - r3 |
mul r1, r2, r3 | r1 = r2 * r3 |
udiv r1, r2, r3 | r1 = r2 / r3 |
b branch | branch(自分で命名)にジャンプ |
bl func | func(自分で命名もしくは既存のものを使用)関数の呼び出し |
bx lr | レジスタlrの指す命令へジャンプ |
ldr r0, address | レジスタ←memory(load command) |
str r0, address | レジスタの値をaddressにストア(store command) |
ldr r0, address | addressをレジスタに移動(load command) |
cmp r0, r1 | r0とr1を比較 *説明あり |
movの説明
数字は**#を使用しなくてもいいが、古いバージョンでは必要なので数字の前に書いておく。
アセンブリ言語は高水準言語と違って、1行で四則演算をできないため分けて書く必要がある。
また、udivを使う際は下記の実践**編で仕様している.cpu cortex-a53, .fpu neon-fp-armv8
を記述する必要がある、
cmpの説明
cmpの後に比較結果を判別する必要がある。
mov r0, #10
cmp r0, #4 @r0と4を比較する
bgt b big @r0>4であれば自分で命名したinner関数にジャンプ
big:
@inner関数のなかみ
比較方法 | イメージ | 説明 |
---|---|---|
bne branch | r0 != 4の時branchにジャンプ | not equal |
beq branch | r0 == 4の時branchにジャンプ | equal |
blt branch | r0 < 4の時branchにジャンプ | less than |
bgt branch | r0 > 4の時branchにジャンプ | less than |
ble branch | r0 <= 4の時branchにジャンプ | less or equal |
bge branch | r0 >= 4の時branchにジャンプ | grater or equal |
この条件に合わない場合は、その行をスキップして次の行に進う。 | ||
またbXX branch はb + 比較 なので、ジャンプするbranchを記述している。 |
実践
Hello Worldを実行してみる
@この2行はおまじないとして書いている
.cpu cortex-a53 @浮動小数点アーキテクチャ
.fpu neon-fp-armv8 @コンパイラオプション
.data
prompt: .asciz "Hello World!\n" @メモリ
.text
.align 2
.global main @main関数をglobalにして外からでも呼べるように
.type main, %function
main:
mov r4, lr @r4=lr
ldr r0, =prompt @メモリからレジスタに移動 r0="Hello World!\n"
bl printf @branch labeled: printするfunctionを呼ぶ
mov r0, #0 @r0=0
mov lr, r4 @lr=r4
bx lr @branch x link register: returnする
bl printf
で表示されるのはr0の内容なので、Hello World!が表示。
インプット
.cpu cortex-a53
.fpu neon-fp-armv8
.data
prompt: .asciz "Enter the number: "
inp: .asciz "%d"
outp: .asciz "The number that you input is %d\n"
.text
.align 2
.global main
.type main, %function
main:
mov r7, lr
ldr r0, =prompt
bl printf
@ scanf("%d", &sp);
sub sp, sp, #4 @ アドレスが4byte単位なので、inputのデータ保持する空箱をストアする。
ldr r0, =inp @ 第一引数"%d"をr0に
mov r1, sp @ 第二引数にアドレスspを入れる。
bl scanf @ scanfメソッド発火。
ldr r4, [sp] @ spはアドレスなのでdereferenceする。
@ printf("The number that you input is %d\n", r1);
ldr r0, =outp
mov r1, r4
bl printf
@ return 0;
mov r0, #0
mov lr, r7
bx lr
発展編へ
発展編ではarrayなどをARMアセンブラを用いて解説しています。
参考文献
プログラムはなぜ動くのか
ARMについて
You Can Learn ARM Assembly Language in 15 Minutes | ARM Hello World Tutorial