前回:2. レジスタと基本的な算術演算 次回:4. GNU デバッガ (GDB)
目次(記事一覧)
※この記事はRoger Ferrer IbáñezさんのブログARM assembler in Raspberry Pi – Chapter 3の翻訳です。
第1章と第2章では、mov
命令を使って値をレジスタに移すことができるのを確認しました。もしプロセッサーがレジスタでしか作業ができないのだとしたら、かなりできることが少なくなってしまいます。
メモリ
コンピューターには、コード(アセンブラの.text
セクション-後述)とデータが格納されるメモリがあります。そのためメモリにプロセッサーからアクセスする方法が必要です。余談ですが、386(※=i386=x86の32bit版)及びx86-64アーキテクチャでは、命令はレジスタとメモリのどちらでもアクセスできます。例えば、2つの値のうち片方がメモリにあっても加算ができます。しかしARMではこのようなことはできません。オペランド(引数)は全てレジスタである必要があるからです。この問題(実際の所、問題というより意図的にそのような仕組みになっているのですが、その説明はこのテキストの範囲を超えます)はメモリからレジスタに積む(ロードする)ことと、レジスタからメモリに保存する(ストアする)ことで解消できます。
メモリとデータをロード/ストアする方法はいくつかありますが、今回は一番簡単な方法を使います。レジスタにロードする(load to register)のldr
命令と、レジスタからストアする(store from register)のstr
命令です。
データをメモリからロードするのは少し複雑で、アドレスの話をしなくてはいけません。
アドレス
データにアクセスするには、データに名前をつける必要があります。そうしないと欲しいデータを参照できないからです。実際、メモリのデータには名前が振られていてそれをアドレスと呼びます。アドレスは数字で、ARMでは32ビットの数字でメモリのすべてのバイト(8ビットのまとまり)を識別します。

図 メモリは、各バイトが独自のアドレスを持つバイト配列のようなものです。
メモリとデータをロード/ストアする場合、アドレスを計算する必要があります。アドレスはさまざまな方法で計算できます。アドレスの計算方法の種類をアドレッシングモードと呼び、ARMには複数のアドレッシングモードが用意されています。すべてを説明するのは時間がかかるためレジスタを介したアドレッシングモードだけを紹介します。
ARMに32ビットの整数レジスタがあることとメモリのアドレスが32ビットの数値であることは偶然ではありません。アドレスをレジスタに入れることができるということです。アドレスをレジスタに入れたら、そのレジスタを利用してデータをロードまたはストアすることができます。
データ
第1章で、アセンブリコードにはテキストとデータの両方が含まれていることを確認しました。実は、アセンブリコードのラベルを説明するときわざと詳しい説明を避けていましたが今は詳しい説明ができます。ラベルはプログラム中のアドレスを示す単なる名前です。アドレスはデータかテキストを参照します。これまではmain
関数のアドレスを指定するためにmain
というラベルひとつだけを使いました。ラベルはアドレスのみを示し、その内容は示さないということを心に留めておいてください。
ラベルのアドレスに実際の値を割り当てるというをしているのはアセンブラ(as)です。アセンブリコードを書く人はそのような面倒な作業を気にせずにラベルを使えます。
したがって、データを定義しそれのアドレスにラベルをひも付けることができるのわけですが、ラベルに参照される場所が適切なサイズと値を持っていることを確認するのはアセンブリコードを書くプログラマーの責任です。
では、4バイトの変数を定義し、それを3で初期化しましょう。myvar1
というラベルを付けます。
.balign 4
myvar1:
.word 3
新出のディレクティブ(疑似命令)の.balign
と.word
があります。アセンブラ(as)は.balign 4
のその後のアドレスが4バイト境界で始まることを保証します。つまり、命令やデータを表す次のバイナリのアドレス値が4の倍数になります。ARMは作業するデータのアドレスにいくつかの決まりごとを定めているので、こうすることは重要です。この.balign
は既に4バイト境界の場合は何もせず、そうでない場合にはプログラムで無視されるバイト(パディングバイト)を用いて要求された境界になるようにします。アセンブリコード中のデータやテキストが全て4バイト(32ビット)幅なら省略できることもありえますが、そうではないサイズのデータを用いるならこのディレクティブを使わなくてはなりません。
さて、上の例ではmyvar1
というアドレスを定義しました。前の行の.balign
のおかげでアドレスが4バイト境界を満たすことがわかります。
.word
ディレクティブは、引数の値を4バイト整数として出力するようにアセンブラに指示します。この場合、値3を表現する4バイトが出力されます。データサイズを決めるために.word
が4バイトを出力するという事実に依存しているので注意です。
セクション
データはコード(テキスト)と同じようにメモリ内に存在していますが、ある役に立つ技術(詳しくは触れません)を用いてデータセクションと呼ばれる場所にまとめられます。.data
ディレクティブは、データセクションの内容を出力するようにアセンブラに指示します。.text
はテキスト(コード)に対して同様のことをします。したがって、データは.data
の後に置き、テキスト(コード)は.text
の後に置きます。
ロード
では、第2章で使ったサンプルコードをメモリへのアクセスを使って拡張してみましょう。2個の4バイト変数myvar1
とmyvar2
を定義してそれぞれ3と4に初期化します。それらをldr
を使ってロードして、加算を実行します。結果のエラーコードは第2章のサンプルコードと同じく7になるはずです。
/* -- load01.s */
/* -- Data section */
.data
/* Ensure variable is 4-byte aligned */
.balign 4
/* Define storage for myvar1 */
myvar1:
/* Contents of myvar1 is just 4 bytes containing value '3' */
.word 3
/* Ensure variable is 4-byte aligned */
.balign 4
/* Define storage for myvar2 */
myvar2:
/* Contents of myvar2 is just 4 bytes containing value '4' */
.word 4
/* -- Code section */
.text
/* Ensure code is 4 byte aligned */
.balign 4
.global main
main:
ldr r1, addr_of_myvar1 /* r1 ← &myvar1 */
ldr r1, [r1] /* r1 ← *r1 */
ldr r2, addr_of_myvar2 /* r2 ← &myvar2 */
ldr r2, [r2] /* r2 ← *r2 */
add r0, r1, r2 /* r0 ← r1 + r2 */
bx lr
/* Labels needed to access data */
addr_of_myvar1 : .word myvar1
addr_of_myvar2 : .word myvar2
アセンブラの制限のため、上の例では少しごまかしました。見ての通り4つのldr
命令があります。それらの意味について説明します。最初に、次の2つのラベルについて議論します。
/* Labels needed to access data */
addr_of_myvar1 : .word myvar1
addr_of_myvar2 : .word myvar2
さて、この2つのラベルはmyvar1
とmyvar2
のアドレスを含んでいます。どうして既にデータのアドレスがmyvar1
とmyvar2
にあるのにさらにラベルが要るのかと疑問に思うかもしれません。詳細は少し長くなります。ここで問題となるのはmyvar1
とmyvar2
が別のセクション、つまり.data
セクションにあることです。プログラムはそのセクションのデータを操作できます。そのため変数をデータセクションに保持するのです。一方、通常は効率とセキュリティー上の理由からテキスト(コード)をプログラムによって変更することはありません。異なる性質を持つ2つのセクションが結び付けられているのはそのためです。あるセクションから別のセクションの識別子に直接アクセスすることはできません。したがって、.code
セクションに.data
セクション内の実体を指すアドレスを参照する特別なラベル(※addr_of_myvar1
とaddr_of_myvar2
)が必要となります。
さて、アセンブラがバイナリコードを出力するとき、.word myvar1
はmyvar1
のアドレスではなく再配置アドレスとなります。再配置はアドレスをアセンブラがアドレスを発行するときに使用する方法であり、その正確な値は不明ですがプログラムがリンクされた時(最終的な実行ファイルが作成された時)にわかります。雰囲気としてはアセンブラが「この変数が実際にどこに配置されるかはわからない。後でリンカに値を埋めてもらおう。」と言っているようなものです。つまり、このaddr_of_myvar1
が代わりに使用されます。addr_of_myvar1
のアドレスは同じ.text
セクションにあります。リンク段階(最終的な実行ファイルが作成され、プログラム中の全実体がメモリのどこに絶対的に配置されるかがわかる時)でリンカはその値を埋めます。これが、(gccによって内部的に呼び出される)リンカがld
(Link eDitor: 連係編集プログラム)と呼ばれる理由です。
ldr r1, addr_of_myvar1 /* r1 ← &myvar1 */
ldr r1, [r1] /* r1 ← *r1 */
ここで、2回ロードをします。1つ目は再配置されるmyvar1
のアドレスの値を実際にロードします。つまり、メモリに置いたデータのアドレスはmyvar1
の実際のアドレスを含む4バイトの大きさのaddr_of_myvar1
です。よって、最初のldr
によってr1
にmyvar1
の実際のアドレスが入ります。アドレス自体は欲しくありませんが、そのアドレスのメモリの中身が要るので2つ目のldr
を使います。

図 メモリの内容からして、この図はロード命令でレジスタに起こることを表しています。
おそらく、2つのロードの構文が異なる理由を疑問に思うことでしょう。1つ目のldr
はaddr_of_myvar1
のシンボリックアドレスを使用しています。2つ目のldr
はレジスタの値をアドレッシングモードとして使用しています。つまり、2つ目のケースではr1
の中の値をアドレスとして使用しています。1つ目のケースではアセンブラが実際のアドレッシングモードに何を使うかわからないため、ここでは無視します。
プログラムは、myvar1
とmyvar2
から2つの32ビット値をロードします。初期値は3と4で、加算し、main
を離れる前に結果をプログラムのエラーコードとしてr0
レジスタに保存します。
$ ./load01 ; echo $?
7
ストア
さきほどのサンプルコードを作り変えます。myvar1
とmyvar2
の初期値に3と4をセットする代わりに、両方とも0をセットします。コードを再利用しますが、変数に3と4をストアするためにいくつか付け加えます。
/* -- store01.s */
/* -- Data section */
.data
/* Ensure variable is 4-byte aligned */
.balign 4
/* Define storage for myvar1 */
myvar1:
/* Contents of myvar1 is just '3' */
.word 0
/* Ensure variable is 4-byte aligned */
.balign 4
/* Define storage for myvar2 */
myvar2:
/* Contents of myvar2 is just '3' */
.word 0
/* -- Code section */
.text
/* Ensure function section starts 4 byte aligned */
.balign 4
.global main
main:
ldr r1, addr_of_myvar1 /* r1 ← &myvar1 */
mov r3, #3 /* r3 ← 3 */
str r3, [r1] /* *r1 ← r3 */
ldr r2, addr_of_myvar2 /* r2 ← &myvar2 */
mov r3, #4 /* r3 ← 4 */
str r3, [r2] /* *r2 ← r3 */
/* Same instructions as above */
ldr r1, addr_of_myvar1 /* r1 ← &myvar1 */
ldr r1, [r1] /* r1 ← *r1 */
ldr r2, addr_of_myvar2 /* r2 ← &myvar2 */
ldr r2, [r2] /* r2 ← *r2 */
add r0, r1, r2
bx lr
/* Labels needed to access data */
addr_of_myvar1 : .word myvar1
addr_of_myvar2 : .word myvar2
str
命令の少し奇妙な点に注意してください。命令のオペランドのうち保存先は第1オペランドではありません。第1オペランドはソースレジスタで、第2オペランドにアドレッシングモードが入ります。
$ ./store01; echo $?
7
今日はここまで。