前回の記事(アセンブラに手を出してみる)でx86、x64のアーキテクチャのアセンブラについて簡単にまとめました。
今回はARM向けの仕様を見てみようと思います。
(ちなみに最新のiPhoneとかではarm64プロセッサを搭載しているので、ここでまとめたのとは若干異なると思います)
つらつらとデバッグ時に遭遇したものとかをメモしていくので随時更新です。
汎用レジスタ
なにはともあれ、レジスタの状況を見ないとなにも始まらないのでまずはレジスタです。
ARMプロセッサはr0
〜r15
の16本、32bitレジスタを持っています。
このうち、r15
は「プログラムカウンタ」で、実行中のメモリアドレスを保持します。
(x86とかではIP
レジスタあたりが似たものでしょうか)
r14
はリンクレジスタでBL
命令(サブルーチンコール)で分岐した場合の戻りアドレスを保持します。
(x86ではbp
とかかな?(こっちのbp
の認識も怪しいけど・・))
x86と似たように、このレジスタも関数コール時にスタックに積まれるようです。(ひとつの戻りアドレスしか保持できないため)
(なお、通常はr13
がスタック領域のレジスタの模様)
名称付き汎用レジスタ
ちなみに上記の役割があるレジスタはそれぞれ、以下のように呼ばれることもあるようです。(数字で表されるより分かりやすい)
レジスタ | 別名 | _ | 説明 |
---|---|---|---|
r0 | a1 | - | 汎用レジスタ |
r1 | a2 | - | 汎用レジスタ |
r2 | a3 | - | 汎用レジスタ |
r3 | a4 | - | 汎用レジスタ |
r4 | v1 | - | 汎用レジスタ |
r5 | v2 | - | 汎用レジスタ |
r6 | v3 | - | 汎用レジスタ |
r7 | v4 | wr | 汎用レジスタ |
r8 | v5 | - | 汎用レジスタ |
r9 | v6 | sb | 汎用レジスタ |
r10 | v7 | sl | 汎用レジスタ |
r11 | v8 | fp | 汎用レジスタ |
r12 | ip | - | 汎用レジスタ |
r13 | sp | - | 汎用レジスタ、スタックポインタ |
r14 | lr | - | リンクレジスタ |
r15 | pc | - | プログラムカウンタ |
sp
などはそれぞれ名称の頭文字ですね。
W**
, X**
Xcodeでデバッグしていると見るこんな命令。
stp x29, x30, [sp, #-16]!
STP
命令は「レジスタペアストア」ということのよう。
ドキュメント
ドキュメントの一部を引用すると以下の感じ。
STP Wt1, Wt2, [Xn|SP], #imm ; 32 ビット汎用レジスタ、ポストインデクス
STP Xt1, Xt2, [Xn|SP], #imm ; 64 ビット汎用レジスタ、ポストインデクス
Xt1
転送される最初の汎用レジスタ(64 ビット)名を 0 ~ 31 の範囲内で指定します。
コメント
「ARM」では@
以降がコメントとして扱われます。
条件指定実行
ARMの仕様として、演算命令に対して条件指定することができるようです。
例えば以下。
SUBEQ a, b, c
SUB
は減算命令ですね。続くEQ
はEqualのEQです。
つまり、所定のフラグがEqualの場合に実行する、という意味になります。
ステータスレジスタ
bit | フラグ | 意味 | 備考 |
---|---|---|---|
31 | N | ネガティブ | 2の補数表現で負なら1 |
30 | Z | ゼロ | ゼロなら1 |
29 | C | キャリー/ボロー | 無符号加減算の桁あふれの時1 |
28 | V | オーバーフロー | 符号付加減算の桁あふれの時1 |
27 | Q | オーバーフロー | オーバーフロー、飽和(DSP)の時1 |
条件 () | 意味 | フラグ条件 |
---|---|---|
AL | 無条件に実行 | 無条件 |
EQ | 等しい | Zセット |
NE | 等しくない | Zクリア |
MI | 負 | Nセット |
PL | 正かゼロ | Nクリア |
VS | オーバフロー | Vセット |
VC | オーバフローでない | Vクリア |
CS/HS | ≧ 大きいか等しい (符号無し) | Cセット |
CC/LO | < 小さい (符号無し) | Cクリア |
HI | > 大きい (符号無し) | CセットかつZクリア |
LS | ≦ 小さいか等しい (符号無し) | CクリアまたはZセット |
GE | ≧ 大きいか等しい (符号付き) | NとVが同じ |
LT | < 小さい (符号付き) | NとVが異なる |
GT | > 大きい (符号付き) | GEの条件かつZクリア |
LE | ≦ 小さいか等しい (符号付き) | LTの条件かつZクリア |
演算命令
add
add r1, r2, r3 @ r1 = r2 + r3
add
には3つのレジスタが指定できます。(3番目は定数なども指定できます)
ここで、r1
はディスティネーションレジスタで、最終的な演算結果が代入されます。
r2
は第1ソースオペランドレジスタ、r3
は第2オペランドと呼びます。
前述の通り、r3
は「レジスタ」、「メモリアドレス」、「定数」を指定することができます。
また以下のように、シフト演算も含めて指定することができるようです。
add r1, r2, r3 LSL #20 @ r1 = r2 + (r3 << 20)
シフト演算の命令は以下のようになります。
命令 | 意味 |
---|---|
LSL | (Logical Shift Left) 左シフト |
LSR | (Logical Shift Right) 右シフト |
ASR | (Arithmetic Shift Right) 算術右シフト(符号を保存) |
ROR | (Rotate Right) 右ローテイト |
RRX | (Rotate Right with extend) 右ローテイト(含キャリーフラグ) |
ループで10を加算するプログラム例
MOV r0, #0 @ r0 = 0
Next: BL Inc @ 戻りアドレスをLRに入れ、Incに分岐
CMP r0, #10 @ r0と10を比較
BNE Next @ r0が10でなければNextへ
B End @ 無条件にEndへ分岐
Inc: ADD r0, r0, #1 @ r0 = r0 + 1
MOV PC, LR @ LRをPCに代入(つまりリターン)
End:
ロード/ストア命令
レジスタからメモリへデータを転送することをストア、メモリからレジスタへデータを転送することをロード、と呼びます。
(ちなみにレジスタからレジスタへの移動をムーブ(MOV)と呼びます)
それぞれの命令は以下のようになります。
動作 | 命令 |
---|---|
レジスタ → レジスタ | MOV |
メモリ→ レジスタ | LDR |
レジスタ→メモリ | STR |
プログラム例
こちらの記事から引用させてもらいます。
#-------------------------------------
# プログラム例 list01.s
#-------------------------------------
#-------------------------------------
# text セクション
#-------------------------------------
.text
.align 2
.global _start @ 必須
/* 定数に名前をつける */
sys_write = 0x900004 @ write システムコール
sys_exit = 0x900001 @ exit システムコール
/* ここからプログラムを記述 */
_start: /* 常にこのラベルから実行開始 */
mov r3, #5 @ 5回繰り返す
1: ldr r1, msg @ 文字列先頭アドレス
mov r0, #1 @ 標準出力(fd=1)を指定
ldr r2, msg_sz @ 出力データの長さ(バイト)
swi #sys_write
subs r3, r3, #1 @ カウンタを更新
bne 1b @ 後方のローカルラベルへ
/* プログラム終了時に実行する */
mov r0, #0 @ 終了コードを0
swi #sys_exit @ プログラム終了
/* 書き換え不要な定数は text セクションに置く */
msg: .long msg0 @ 文字列格納アドレス
msg_sz: .long msg_sz0 @ 文字列の長さ格納アドレス
#-------------------------------------
# data セクション
#-------------------------------------
.data
.align 2
/* ここは初期化が必要なデータを置く *
* プログラムサイズに影響する */
msg0: .asciz "hello, world\n"
.align 2 @ 4バイト境界に設定
msg_sz0 = . - msg0 @ 文字列の長さを計算
data1: .hword 12345
#-------------------------------------
# bss セクション
#-------------------------------------
.bss
.align 2
/* ここは初期化が不要なデータを置く *
* プログラムサイズには影響しない */
data: .long 0 @ この例では使っていない
#-------------------------------------
どうやら最終行は空行が必要なようです。(ないとエラー)
ざっと見てみると、だいぶx86のアセンブラと似ている感じです。
.text
セクションがいわゆるプログラムコード、.data
セクションが変数などのデータですね。
なお、.data
は、
C言語では、「0以外の初期値を持つ大域変数」「0以外の初期値を持つ静的局所変数」がここに置かれます。
ということで、初期値を持つものが置かれる領域のようです。
一方、初期値を持たないものは.bss
セクションに置かれます。
また、.rodata
という「定数」を置くセクションもあるようです。
msg0: .asciz "hello, world\n"
このあたりが文字列定数の宣言ですね。
続く以下のものはアドレスから文字列長を計算している?
msg_sz0 = . - msg0
.
は現在のアドレスで、そこからmsg0
分引くと差分は文字列長、ということだろうか?
x86のところでも似たような記述がありました。
こんな感じの。
.len: equ $ - msg
$
が現在のアドレスを表し、そこからmsg
のアドレスを引く、という計算です。