LoginSignup
97
95

More than 5 years have passed since last update.

[アセンブラ] ARMの仕様を見てみる

Last updated at Posted at 2015-04-20

前回の記事(アセンブラに手を出してみる)でx86、x64のアーキテクチャのアセンブラについて簡単にまとめました。
今回はARM向けの仕様を見てみようと思います。
(ちなみに最新のiPhoneとかではarm64プロセッサを搭載しているので、ここでまとめたのとは若干異なると思います)

つらつらとデバッグ時に遭遇したものとかをメモしていくので随時更新です。

汎用レジスタ

なにはともあれ、レジスタの状況を見ないとなにも始まらないのでまずはレジスタです。

ARMプロセッサはr0r15の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

プログラム例

こちらの記事から引用させてもらいます。

arm
#-------------------------------------
# プログラム例 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のところでも似たような記述がありました。

こんな感じの。

x86
.len: equ $ - msg

$が現在のアドレスを表し、そこからmsgのアドレスを引く、という計算です。

97
95
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
97
95