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種類となります。
- ベースレジスタ ± 12ビット定数
- ベースレジスタ ± レジスタ
- ベースレジスタ ± レジスタ (シフト操作付き)
この命令に関するアドレッシングモードは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種類となります。
- ベースレジスタ ± 8ビット定数
- ベースレジスタ ± レジスタ
この命令に関するアドレッシングモードは32ビット/8ビットのロードストア命令と同じ 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] ; 80988078: 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) とオフセット を使ったアドレッシングモードを使っていることが確認できます。 また、逆アセンブラがデータとプログラムの区別ができていないことも確認できます。