UP | HOME

アセンブラ指令 - 2021年度 システムプログラミング

アセンブラ指令

はじめに

アセンブリ言語では,通常の laadd といった命令と違って, .text.byte のようにドットで始まる命令が見受けられます. これらの意味と用途について解説します.

教科書には,A.2節「アセンブラ」の「追加機能」, B.10節「MIPS R2000 のアセンブリ言語」の「アセンブラの構文規則」 で触れられています.しかし,若干難解な部分があるため,本文書にて補足をしています. 教科書も合わせて参照してください.

アセンブラ指令の意義

アセンブラの仕事は,ユーザが書いたアセンブリコードを CPU が実行すべき数値の列に変換して,メモリに配置していくことです. アセンブラがメモリに配置するのは,CPUが実行する命令だけではなく, 実行中に使用されるデータも含まれます.例えば,

printf("Hello");

をアセンブラで変換すると,

(1) printf を呼出す機械語命令列
(2) "Hello" の文字列に対応する数値の列

の2つがメモリ中に用意されることが想像できるでしょう. アセンブラプログラミングにおいて,前者を一般的に「テキスト」, 後者を「データ」と大別して呼びます. 「テキスト」といっても,文字ではなくCPU にとっての命令列(数の列)を指すことに注意して下さい.

では,上の Hello のC言語に相当するアセンブリ言語を見てみましょう.

 1:       .data
 2:       .align  2
 3: msg:
 4:       .byte 72, 101, 108, 108, 111, 0
 5: 
 6:       .text
 7:       .align  2
 8: main:
 9:       la      $a0, msg
10:       jal     printf

4行目に書いてある 72, 101, 108 … は,Hello の文字コード(ASCIIコード)に対応する数値です. このように, .byte を使うと, プログラム実行前にメモリ中にデータをあらかじめ配置できます.

アセンブラには, .byte 以外にも, CPU が直接実行する訳ではないけれども, プログラム実行に不可欠なデータの用意や, メモリ中におけるプログラムの配置をコントロールする指令が各種用意されています. これらのことをここでは,アセンブラ指令と呼びます. このアセンブラ指令は,まるでCPUの命令のようにプログラム中に書けるので, 擬似命令と呼ばれることもあります. しかし,MIPS プログラミングにおいて,「擬似命令」は,別の意味を持っているので, ここではそれと区別するために,アセンブラ指令と呼ぶことにします.

よく使うアセンブラ指令

以下では,本講義でよく使用されるアセンブラ指令について解説します.

.byte b1, …, bn

b1〜bn で示すバイト(8ビット)列をメモリに順番に配置します. 先の Helloの例のように使用します. 通常,C言語の char 型の数値と1対1で対応していると考えることができます.

前述の

.byte 72, 101, 108, 108, 111, 0

は,

.byte 'H', 'e', 'l', 'l', 'o', 0

のようにも記述できます.なぜなら,'H' は C言語と同じく 72 と同等に扱われるからです.

.word w1, …, wn

w1,...,wn で示す32ビットの数値をメモリに順番に配置します. 通常,C言語の int型の数値と1対1で対応していると考えることができます.

.byte 0, 0, 0, 0

と書くことと

.word 0

と書くことは同等です.

.ascii str

str をメモリに配置します. これによって,C言語の文字列のように簡便な表現が可能になります. 前述の

.byte 72, 101, 108, 108, 111, 0

は,

.ascii "Hello\000"

と書けます.

.asciiz str

末尾に 0 が自動的に配置されることを除けば, .ascii と同じです. C言語の文字列を表現するときには末尾を 0 にする必要があるので,便利です. つまり,

.ascii "Hello\000"

と書く代わりに

.asciiz "Hello"

と書けます.

.space n

メモリ中に n バイトの領域をあらかじめ空けておきます. これによって配列のような大きなデータを保存する領域を確保できます.例えば,

array:
  .space 400

で,400バイト分のデータ(100個の int の配列)を用意して, その先頭を指すアドレスを array で参照できます. つまり,C言語の

static int array[100];

にあたります. int が 32ビットで,かつ static (静的にはじめから領域が確保されている) 配列宣言のことです.

.comm symbol, n

.space と同じで,メモリを確保します.

.data
symbol: .space n

と書いたことと同じです. .data については,次に説明します.

.data と .text

これらの2つは,メモリ中の何処にデータやテキストを配置するかを制御するためのアセンブラ指令です. 教科書A.5節「主記憶領域の使用法」に示すように本講義で使用する SPIM では,

テキストセグメント (0x00400000〜)
データセグメント   (0x10000000〜)

のようなセグメント分割を行っています. つまり,プログラム (テキスト)は, 0x00400000 番地から, データは, 0x10000000 番地からメモリ中に並べて行くようになっています.

テキストとデータは,最終的にどちらも数値になって並びますから, アセンブラにとって,どれをどちらに配置するかは, プログラムを書いた人の指令に頼るしかありません.

そこで,もう分かると思いますが, .text は,以下の記述をテキストセグメントに配置せよとの指令で, 対して .data は,データセグメントにデータを配置することを示しています. アセンブラ命令列の前に .text 指令, .byte 72, 101, 108, 108, 111, 0 の 前には, .data が書かれていることで分かると思います.

 1:       .data
 2:       .align  2
 3: msg:
 4:       .byte 72, 101, 108, 108, 111, 0
 5: 
 6:       .text
 7:       .align  2
 8: main:
 9:       la      $a0, msg
10:       jal     printf

このように,データとテキストを意識して区別するのは何故でしょう. いくつかの理由があります.

  1. テキストは,通常,動作中に書き変えたりしないので, データセグメントと違って,ROM (Read Only Memory) 上に配置することができる.
  2. 異なるプロセスで同じプログラムを実行する場合, テキストは同一なので,仮想記憶を使った共有ができる. しかし,データ部は,プロセス毎に異なるので,別の実メモリを必要とする.

詳細はオペレーティングシステムの講義に譲りますが, このようにテキストとデータを別の領域に置いて管理することが普通です. その他,メモリの保護の問題やアセンブリ言語の読み書きのしやすさ (データとそれを使うテキストが隣接してあるほうが分かりやすい)も関係します.

使用するCコンパイラによっては,.rdata (読み出し専用データ)や .kdata (カーネルが使用するデータ) といった .data をさらに区別するアセンブラ指令もありますが, 本講義の範囲では,同等に考えて差し支えありません.

.align n

.align 2 とは,何でしょうか.以下の例を見てください.

1: .data
2: .ascii  "ABC"
3: .word   0x11223344

これは,メモリにどのような数値列を配置するでしょうか. 例えば,以下のようになります.1行に4バイトづつ,'A' の ASCII コードは0x41 であることに気をつけてください.

0x10000000: 41 42 43 11
0x10000004: 22 33 44 ??

実際には,11 22 33 44 の並び順は逆かもしれませんが, ここではそれは問題ではありません.

ここで,MIPS は 32ビットアーキテクチャであるということを思い出してください. CPU が 32ビットの数値 (ここでは 0x11223344) を効率よく一度に読むためには, 32bit つまり 4バイト境界のアドレスにデータが整列(align) している必要があります. ここでは 1個の word が被害を被るだけのように見えますが, これより後に配置されるデータは,すべて半端な境界の上に並ぶことになります.

したがって,理想的には,

0x10000000: 41 42 43 00 (最後の 00 は使わない)
0x10000004: 11 22 33 44

となる必要があります.このためには,たとえば,

1: .data
2: .ascii  "ABC"
3: .byte   0x00
4: .word   0x11223344

と書けばうまくいきそうです. このような間を埋める .byte 00 のことをパディング(padding)といいます. しかしながら,このパディングが何バイト必要になるかを一々計算するのは面倒です. .ascii で指定している文字が 1文字増えたら?…そこで, 自動的にそのパディング量を計算して隙間を空けてくれるのが, .align です.

.align n は 2の n 乗の境界上になるまで隙間を空けてくれる (丁度の場合は何もしない)という便利な指令です.

1: .data
2: .ascii  "ABC"
3: .align  2
4: .word   0x11223344

これでうまくいきました.

.text.data の直後によく .align 2 と書いているのは, .data.text がプログラム中に複数現われた場合に, 前の領域が綺麗に4バイト境界で終わっていないことを仮定した予防です.

ちなみに,仮想記憶が絡んでくると, 仮想記憶のページ境界にデータを配置した方が効率のいい場合があります. そのような場合は,仮想記憶のページサイズ, たとえば4KB境界になるように, .align 12 とすることもあります. 本演習では,仮想記憶は扱いません.

Author: Yoshinari Nomura

Emacs 27.1.91 (Org mode 9.4)