4. レジスタ-メモリ転送命令

アセンブリ言語のプログラムで非常に使用頻度が高い命令が値をコピー(代入)する命令です。 どのCPUもニーモニックには load, store, move という単語に近い表現を使っています。 i386系のCPU (Athron、Pentium4など) は mov のみを使っていますが、ARMでは次の3種類 を使い分けています。

動作 読み 命令
レジスタからレジスタへコピー ムーブ MOV
メモリからレジスタへコピー ロード LDR
レジスタからメモリへコピー ストア STR

レジスタ間のコピーに使う mov は 前回に解説していますが、 今回はメモリとレジスタ間の転送を実行するロード/ストア命令について紹介します。

ロード/ストア命令

データ転送命令は、1バイト(8bit)または1ワード(32bit)のデータをレジスタからメモリ (ストア)へ、 またはメモリからレジスタ (ロード) へコピーします。 ハーフワード (16ビット) のロード/ストア命令も別に用意されています。

32ビットと8ビットのロード/ストア命令

メモリからレジスタにコピー(ロード)、レジスタをメモリにコピー(ストア)する 命令です。ユーザモードで使う命令は32ビットと8ビットのロードとストアで4種類 (LDR/STR/LDRB/STRB) になります。

オペコード オペランド 動作 意味
LDR<cd> Rd, <am2> Rd=<am2> 32ビットロード
LDR<cd>B Rd, <am2> Rd=<am2> 8ビットロード
LDR<cd>BT Rd, <am2> Rd=<am2> ユーザモードでは LDRB と同じ
LDR<cd>T Rd, <am2> Rd=<am2> ユーザモードでは LDR と同じ
STR<cd> Rd, <am2> <am2>=Rd 32ビットストア
STR<cd>B Rd, <am2> <am2>=Rd 8ビットストア
STR<cd>BT Rd, <am2> <am2>=Rd ユーザモードでは STRB と同じ
STR<cd>T Rd, <am2> <am2>=Rd ユーザモードでは STR と同じ

上の表の <am2> はアドレッシングモードと呼んでデータを操作する メモリアドレスの指定方法の1つを示しています。これらの命令では以下に 示す方法でメモリアドレスを指定できます。命令は簡単ですが、アドレッシングモード が9種類もあり複雑に見えます。

  LDR|STR<cond><B><T>     Rd, <am2>

<am2>

  表記            意味

  [Rn, #+/-offset_12]        Rd <--> Mem[Rn +/- offset_12]

  [Rn, +/-Rm]                Rd <--> Mem[Rn +/- Rm]

  [Rn, +/-Rm, LSL #5bit]     Rd <--> Mem[Rn +/- (Rm LSL #5bit)]

  [Rn, #+/-offset_12]!       Rd <--> Mem[Rn +/- offset_12]
                             Rn = Rn +/- offset_12

  [Rn, +/-Rm]!               Rd <--> Mem[Rn +/- Rm]
                             Rn = Rn +/- Rm

  [Rn, +/-Rm, LSL #5bit]!    Rd <--> Mem[Rn +/- (Rm LSL #5bit)]
                             Rn = Rn +/- (Rm LSL #5bit)

  [Rn], #+/-offset_12        Rd <--> Mem[Rn]
                             Rn = Rn +/- offset_12

  [Rn], +/-Rm                Rd <--> Mem[Rn]
                             Rn = Rn +/- Rm

  [Rn] +/-Rm, LSL #5bit      Rd <--> Mem[Rn]
                             Rn = Rn +/- (Rm LSL #5bit)

レジスタに読み込む、またはレジスタから書き込むメモリアドレスは、上記の Rn で示される レジスタ (ベースレジスタ) の値が示すアドレスを基準に決められます。ベースレジスタの 値に定数 (12ビットイミディエイト) あるいは 別のレジスタの値 (シフトすることも可) を加減算することができます。アドレス計算は次の3種類となります。

  1. ベースレジスタ ± 12ビット定数
  2. ベースレジスタ ± レジスタ
  3. ベースレジスタ ± レジスタ (シフト操作付き)

この命令に関するアドレッシングモードは3つに分類できます。

  1. プレインデックスモード
  2. 自動インクリメント、自動デクリメントモード
  3. ポストインデックスモード

プレインデックスモードは転送アドレスとして使用される前にアドレス計算を行う方法で、普通はこのモードを使用することが多くなります。 自動インクリメント、自動デクリメントモードはプレインデックスモードと同じですが、ベースレジスタとオフセットの計算結果を最後にベースレジスタに書き戻します。 連続した領域をコピーする場合などに対象アドレスを自動的に更新する場合に使います。 ポストインデックスモードはベースレジスタの内容が示すアドレスで転送が行われた後に、 ベースレジスタにオフセットを加えてベースレジスタを更新するモードです。

32ビットの命令 (LDR/STR) で指定するメモリアドレスは、ワード境界にアラインメントする (4で割り切れるアドレス) 必要があります。ワード境界にそろってない場合には期待する結果と異なる動作になります。 GNU as では .align 2 で4バイトの境界に揃えます。

16ビットのロード/ストア命令

16ビットのデータをレジスタからメモリへ、またはメモリからレジスタへ転送する命令です。 また16/8ビットのデータをメモリからレジスタへ符号拡張して転送する命令も含みます。 16ビットのロード/ストア命令は、32/8ビットの命令とメモリアドレスの 指定方法が少し異なります。

オペコード オペランド 動作 意味
LDR<cd>H Rd, <am3> Rd=<am3> 16ビットロード
LDR<cd>SB Rd, <am3> Rd=<am3> 符号拡張8ビットロード
LDR<cd>SH Rd, <am3> Rd=<am3> 符号拡張16ビットロード
STR<cd>H Rd, <address> <am3>=Rd 16ビットストア

命令中のS (LDRSB, LDRSH) は符号拡張を伴うデータのロードを示します。 LDRSB命令は指定したバイトをレジスタの下位8ビットにロードします。 レジスタの上位のビットは第7ビットと同じ値に設定されます(符号拡張)。 LDRSH命令は指定したハーフワード(16ビット)をレジスタの下位16ビットに ロードします。 レジスタの上位16ビットは第15ビット(符号ビット)と同じ値に設定されます(符号拡張)。

上の表の <am3> は32ビット/8ビットのアドレッシングモードのメモリアドレス指定と 少し異なることに注意してください。

  LDR|STRH|SB|SB|D   Rd, <am3>

<am3>

  表記            意味

  [Rn, #+/-offset_8]      Rd <--> Mem[Rn +/- offset_8]

  [Rn, +/-Rm]             Rd <--> Mem[Rn +/- Rm]

  [Rn, #+/-offset_8]!     Rd <--> Mem[Rn +/- offset_8]
                          Rn = Rn +/- offset_8

  [Rn, +/-Rm]!            Rd <--> Mem[Rn +/- Rm]
                          Rn = Rn +/- Rm

  [Rn], #+/-offset_8      Rd <--> Mem[Rn]
                          Rn=Rn+/-offset_8

  [Rn], +/-Rm             Rd <--> Mem[Rn]
                          Rn=Rn+/-Rm

レジスタに読み込む、またはレジスタから書き込むメモリアドレスは、上記の Rn で示される レジスタ (ベースレジスタ) の値が示すアドレスを基準に決められます。ベースレジスタの 値に定数 (8ビットイミディエイト) あるいは 別のレジスタの値を加減算することができます。 アドレス計算は次の2種類となります。

  1. ベースレジスタ ± 8ビット定数
  2. ベースレジスタ ± レジスタ

この命令に関するアドレッシングモードは32ビット/8ビットのロードストア命令と同じ 3つに分類できます。

  1. プレインデックスモード
  2. 自動インクリメント、自動デクリメントモード
  3. ポストインデックスモード

16ビットのロード/ストア命令と、32ビット/8ビットのロードストア命令との アドレッシングとの違い (オフセットが8ビット、シフト操作がない) に注意すれば 問題ないでしょう。

プログラム例

次の例は src の示すメモリアドレスから4096バイトを dest の示すアドレスまで 1バイトづつ転送するプログラムです。実行しても何も表示されませんが、 これまでに出てきた命令を使っています。プログラムの終了だけは ソフトウェア割り込み命令 (SWI) による システムコール を使っています。

.text
        .align  2
        .global _start
_start:
        ldr     r0, src0        @ src のアドレスをロード
        ldr     r1, dest0       @ dest のアドレスをロード

        mov     r4, #0x1000     @ count=4096
1:      ldrb    r3, [r0, #+1]!  @ r3=mem[r0++]
        strb    r3, [r1, #+1]!  @ mem[r1++]=r3
        subs    r4, r4, #1      @ count--
        bne     1b              @ if(count) goto 1

        mov     r0, #0
        swi     #0x900001       @ システムコール exit

        .align  2
src0:   .word   src             @ src のアドレスを格納
dest0:  .word   dest            @ destのアドレスを格納

.bss
src:    .skip   4096            @ 4096バイト
dest:   .skip   4096            @ 4096バイト

ldrb と strb 命令は自動インクリメント (!) を指定しているため、r0 と r1 を別に更新する 必要はありません。ループ内の命令数を少なくすることができます。

ARMではメモリに格納されたデータとレジスタ間の転送に注意が必要です。転送に必要な メモリアドレスの指定にはラベルで参照するのが普通ですが、mov 命令1つでは 8ビットの定数までしか設定できないため、メモリアドレスをレジスタに設定するために ldr 命令がよく使用されます。

データ領域として8192バイトを使っていますが、 src は4096バイトの領域の先頭アドレス、 dest は4096バイトの領域の先頭アドレスのラベルとなっています。 アセンブラの制限で ldr/str 命令のオペランドのラベルには同じファイルの同じセクション のラベルだけが使用可能です。さらにオフセットは 12ビット(4096) に制限されるため 任意のアドレスを指定できません。したがって .bss セクションに確保した領域のラベル src と dest を .text セクションの ldr 命令で直接指定することはできません。

上のプログラム例では、.bss セクションのラベル(のアドレス)を .text セクションの データとして宣言する方法を使っています。src0 と dest0 に格納された src と dest のアドレスを ldr 命令でロードしています。ちょっと複雑ですが、ARMでは必須のテクニック です。GNU as は少し楽になる擬似命令を提供していますが、以上の方法を内部的に利用します。

src と dest を .text セクションに置くと直接 ldr 命令で参照することができますが、 今回の例では、src の領域が 4096バイトであるため、オフセットが12ビットを超えて dest に 届かなくなります。 また一般的に値の初期化の必要がないデータ領域を .text セクション (プログラム用)  や .data セクション (初期化する必要のあるデータ用) に置くとプログラムサイズが 無駄に大きくなります。.bss セクションにデータ領域を確保することで、アセンブル されたプログラムがデータを持たず、プログラムを無駄に大きくしないようにしています。

上のリスト (ex002_0.s) をアセンブル、リンクして作成したプログラムを逆アセンブル して実際に生成されたコードを見てみましょう。

~$ as -o ex002_0.o ex002_0.s
~$ ld -o ex002_0 ex002_0.o
~$ objdump -d ex002_0

ex002_0:     file format elf32-littlearm

Disassembly of section .text:

00008074 <_start>:
    8074:       e59f001c        ldr     r0, [pc, #28]   ; 8098 
    8078:       e59f101c        ldr     r1, [pc, #28]   ; 809c 
    807c:       e3a04a01        mov     r4, #4096       ; 0x1000
    8080:       e5f03001        ldrb    r3, [r0, #1]!
    8084:       e5e13001        strb    r3, [r1, #1]!
    8088:       e2544001        subs    r4, r4, #1      ; 0x1
    808c:       1afffffb        bne     8080 <_start+0xc>
    8090:       e3a00000        mov     r0, #0  ; 0x0
    8094:       ef900001        swi     0x00900001

00008098 :
    8098:       000100a0        andeq   r0, r1, r0, lsr #1 ここはデータ

0000809c :
    809c:       000110a0        andeq   r1, r1, r0, lsr #1 ここはデータ

ldr にメモリアドレスとしてラベルが指定された場合は、アセンブラが pc (r15) とオフセット を使ったアドレッシングモードを使っていることが確認できます。 また、逆アセンブラがデータとプログラムの区別ができていないことも確認できます。