04.S/370マシン命令入門

By 神居 - Posted: 2008/11/04 Last updated: 2010/01/04 - Leave a Comment

アセンブラー言語におけるプログラムの書式と基本的なアセンブラー命令が理解できるとプログラムを書く準備が整います。しかしアセンブラー言語ではCPUに直接指示を与えるため、プログラムを書くためにはCPUと関連するハードウェアに関する知識がどうしても必要です。ここではアセンブラー・プログラミングに関するCPUのしくみや実際にCPUを動かすためのマシン命令の基礎について解説します。


CPUとPSW

S/360、S/370以降ESA/390および互換アーキテクチャーではCPUは32ビットCPUです。命令セットはIBM社独自のものが採用されています。この講座では現在のzアーキテクチャーの前身である32ビットCPU命令を中心に解説します。zアーキテクチャーではCPUは64ビットになり命令セットも大幅に拡張されましたが従来のアーキテクチャーでサポートされている命令セットはそのまま使用できます。

PSW(Program Status Word)はプログラム状態語と呼ばれ、CPUで現在動いているプログラムの走行状態(実行状況)を示す64ビットの特殊なレジスターで、CPU動作を制御する基本的な情報が格納されています。最初からPSWすべての情報について覚える必要はなく、アセンブラー言語でプログラムを作る上で、デバッグ作業をする上で必要なものを最初に覚えればいいでしょう。PSWのフォーマット

PSWには「 078D0000 00006192 」こんな値が格納されていたりします。
PSWの後半32ビットが次に実行する命令の仮想アドレスを示します。32ビット目がアドレスモードでプログラムが24ビット・31ビットモードのどちらで動いているかを示します。ビットが1なら31ビットモードです。残りの31ビットに命令アドレスが入ります。今実行している命令ではなく、次に実行する命令のアドレスであることを覚えて下さい。後は2バイト目です。例ではx8Dとなっていますが、8が記憶保護キーでPSWキーと言います。PSWキーとメモリー上のキーが一致していれば書き込みができます。次のDが問題プログラム状態を示します。ここがCなら監視プログラム状態(スーパーバイザーモード)です。正確には他のビットとの組み合わせでDやCになるのですが、滅多なことでは他のパターンにはなりません。取りあえずはこれだけでいいでしょう。3バイト目の前半4ビットのうち、第2,3ビットが命令の結果を示す条件コードですが、実際PSWの条件コードを見てデバッグするようなことはあまりありません。


レジスター

CPUには16個の汎用レジスター(General Purpose Register)があります。GPRまたはGRと略され32ビットの長さを持ち、それぞれ番号(0?15)で識別されます。GPRは命令オペランドのアドレス計算、整数演算、論理演算、実行結果の格納などに用いられます。汎用レジスターの他にCPUの制御に使う制御レジスター、クロスメモリー機能で使用するアクセスレジスター、浮動小数点演算に使う浮動小数点レジスター(64ビット)があります。汎用レジスター以外は一般のプログラムでは基本的に使用しません。zアーキテクチャーではCPUに合わせて汎用レジスターと制御レジスターは64ビットに拡張されていますが、これまでの32ビットCPU命令を使う場合はその違いを意識する必要はありません。

レジスターのいちばん左端がビット0です、以降右へビット1,2,3と続き右端がビット31になります。※CPUが複数個実装されているマルチCPU構成のプロセッサーでは、レジスターは各々のCPU毎にあります。


主記憶装置(メモリー)へのアクセス

主記憶装置はプログラムとプログラムが使用するさまざなデータを格納します。S/370以降のアーキテクチャーでは仮想記憶システムなので、我々が作り動かすプログラムはすべて仮想記憶上で動作します。実記憶(リアルメモリー)を直接アクセスすることはありません。以降の解説も主記憶装置あるいはメモリーはすべて仮想記憶装置を指します。
主記憶には先頭からアドレス(番地)が振られ、バイト単位でアクセスできます。アドレスはx00000000から始まりx00FFFFFF(16MB)さらにx7FFFFFFF(2GB)へと続きます。


アドレッシング

メモリーをアクセスする命令は、オペランドで操作の対象となるデータがメモリー上のどこにあるかを指し示す必要があります。これがアドレス表現で、命令実行時に実際のメモリーアドレス(実効アドレス)が計算されます。CPU命令ではメモリー上のアドレスは基本的に以下の2つの方法でアドレスが計算されます。


ベース+変位

レジスターに格納されたベースアドレス(基底アドレス)の値に変位(displacement)を加えて主記憶アドレスを求めます。変位はベースアドレスから何バイト離れたところを示すかの数で0?+4095の値が使えます。変位は命令のオペランド中に固定値で示されます。ベースアドレスを格納したレジスターのことをベースレジスターと呼びます。この方式では4ビットでベースレジスター番号、12ビットで変位を示し、アドレス部の命令オペランドは16ビットとなります。
ベースレジスターにx6000が入っていて命令オペランドで変位がx128(296)となっていれば、主記憶アドレスはx6128となります。

「STCK 120(12)」 ベース+変位方式はオペランドをこのように表現します。書式は変位(ベースレジスター番号)です。12番レジスターの内容に120を加えた数がアドレスになります。人間がいちいち変位を計算するのは面倒なので、普通は変位(ベースレジスター番号)を直接書くよりはラベル名で示します。
「STCK AREA」 のようにオペランドにラベル名が書かれた命令はアセンブラーによって STCK 120(12) と翻訳されます。命令を翻訳する時、どれをベースレジスターに使うかはUSING命令で決まります。USING LABELA,8とすれば、LABELAがベースアドレスでベースレジスターが8番です。USING *,12とすれば、USING命令を書いた場所がベースアドレスでベースレジスターは12番となります。基本的にプログラムは最低でも1つのベースレジスターを使います。通常はプログラムの先頭をベースアドレスにします。ベースレジスターは0以外であればどのレジスターでもいいのですが、1,2,13,14,15は避け3?12番の中から選びます。どれを使うかは最初に習ったり、手本にしたりしたプログラムのスタイルで決まることが多いようです。
アセンブラーはUSING命令で指定したベースレジスターを使って命令を翻訳しますが、指定されたレジスターにベースアドレス値を設定することまではしません。アセンブラー命令とCPU命令の違いがわかっていないとこう勘違いしてしまうかも知れません。アセンブラーのUSINGは命令オペランドをラベル名で書けるようにするだけです。USING命令で指定したベースレジスターに正しいベースアドレス値を設定するのはプログラマーの仕事で、どのプログラムも最初にこれを行います。


ベース+インデックス+変位

ベース+変位のアドレス計算にさらにインデックスアドレスを加えたものです。
レジスターに格納されたベースアドレス(基底アドレス)とインデックスアドレスの値に変位(displacement)を加えて主記憶アドレスを求めます。変位はベース+インデックスアドレスから何バイト離れたところを示すかの数で0?+4095の値が使えます。変位は命令のオペランド中に固定値で示されます。インデックスアドレスを格納したレジスターのことをインデックスレジスターと呼びます。この方式では4ビットでインデックスレジスター番号、4ビットでベースレジスター番号、12ビットで変位を示し、アドレス部の命令オペランドは20ビットとなります。
ベースレジスターにx6000が、インデックスレジスターにx2000が入っていて命令オペランドで変位がx128(296)となっていれば、主記憶アドレスはx8128となります。

「LA R1,120(10,12)」 インデックス+ベース+変位方式はオペランドをこのように表現します。書式は変位(インデックスレジスター番号,ベースレジスター番号)です。10番および12番レジスターの内容に120を加えた数がアドレスになります。
アセンブラーではラベル名のみでオペランドを指定した場合、インデックスレジスターは使用されません。インデックスレジスターを使うには「LA R1,AREA(10)」のようにインデックスレジスターを使用することを明示する必要があります。

インデックスレジスターはすべての命令で使えるわけではなく使用できる命令が決まっています。またベース+インデックス+変位方式の命令でもインデックスレジスターの使用は任意で、使わずにベース+変位でのみアドレス計算をするなら、インデックスレジスター番号に0を指定します。(あるいは省略)
S/370ではレジスター0番はベースレジスター、インデックスレジスターどちらとしても使用することはできません。オペランドで指定した0は0番レジスターではなく、数としての0そのものを意味します。「STCK 120(0)」「LA R1,120(0,0)」はいずれも主記憶アドレス120番地を指します。これはとても重要なことなのでよく覚えておいて下さい。

マシン命令における主記憶のアドレッシング

マシン命令における主記憶のアドレッシング


S/360やS/370アーキテクチャーにおける命令のオペランド・アドレスはプログラム内に直接持たれない、間接アドレッシング方式です。ベースレジスターの値を変えることで、プログラムを例えば主記憶の5000番地、10000番地あるいは32080番地など、主記憶のどこにでも配置できるようになります。これによってプログラムはリロケータブル(再配置可能)な性質を持つことが出来ます。仮想記憶システムが登場する以前の、実記憶上でプログラムを直接動かしていた時代では大きなアドバンテージでした。実効アドレスを求めるためのアドレス表現が複雑に見えますが、実記憶装置をいかに効率よく使い、いかに少ない長さでオペランドを表現するかと言うことで考え抜かれた設計なのです。


アドレッシングモード

レジスターは32ビットなのでフルビット使えば実効アドレスの範囲はは0?4GB-1となりますが、S/360システムでは下位24ビット(3バイト)を主記憶アドレスに使用しました。24ビットでは0?16MB-1です。当時のメモリー容量は数十?数百KB、大きくても数MBでしたから、32ビット全部をアドレスに使うよりは、1部を他の目的に使おうと言う発想は自然でした。S/370システムでも24ビットアドレスはそのまま踏襲され仮想記憶も16MBの大きさを持ちました。基本的にプログラムはx00000000?x00FFFFFFの範囲でしか主記憶をアクセスできません。これが24ビット・アドレッシングモードです。先頭の1バイトは主記憶アドレスとしては無視されるため、メモリー容量が小さかった当時はメモリーを節約するために、この部分にさまざまな制御用の情報をフラグバイトとして持ちました。4バイトあればXL1’フラグ’+AL3(アドレス)として使われることも多く、MVSでは今でも随所にこのパターンが残っています。

さすがに主記憶容量が16MBではどうにもならなくなったため、仮想記憶の大きさは31ビットで表現できる2GBに拡張されました。32ビットにしなかったのは24ビットモード・プログラムに対する互換性のためです。32ビットのうち、先頭ビットをアドレスモードの判定用に用い、残りの31ビットを主記憶アドレスに使いました。この場合プログラムはx00000000?x7FFFFFFFの範囲で主記憶をアクセスできます。これが31ビット・アドレッシングモードです。PSWのアドレス部(後半32ビット)の先頭ビットが0であれば24ビットモード、1であれば31ビットモードとなります。
x00006000 → 24ビットモード
x80006000 → 31ビットモード

特別に指定しない限り、アセンブラー言語で作成したプログラムは24ビット・アドレッシングモードのプログラムになります。まずは基本となるこのモードでプログラミングを覚え、それから31ビット・アドレッシングモードへ進む方がいいでしょう。もし新しいアプリケーションをこれからアセンブラー言語で作るなら最初から31ビットモード(あるいは64ビットモード)のプログラムで覚えてしまってもいいのですが、実戦でのアセンブラー言語はOSやパッケージソフトウェアの出口ルーチンを作ったり、過去に作られた古いプログラムの改良や他言語への書き換え、と言ったことに使われますから24ビット、31ビットの両方のアドレッシングモードを理解して必要に応じて使い分けられるようにすべきです。またそれは今後64ビット・アドレッシングモードを理解する上で必ず役に立つはずです。


境界調整

データや命令をメモリー上の整数倍アドレスから配置することを境界調整(バウンダリー合わせ)と言います。例えばフルワードの整数は4の倍数で始まるアドレス、ハーフワード整数は2の倍数で始まるアドレスにデータを揃えることです。DCやDS命令を使用して定義されたデータはアセンブラーが必要に応じて境界調整してくれます。
なお現在のCPUにはバイト式オペランド機構(byte oriented operand)が備わっているため、フルワード整数が2の倍数や奇数のアドレスで始まっていてもエラーにならずに実行されます。しかしきちんと境界調整がなされている場合に比べて若干のオーバーヘッドが生じます。ただし命令は必ずハーフワード境界に調整されていなければなりません。命令が奇数番地に置かれるとその命令を実行しようとした時に、プログラムは指定例外(S0C6)でABENDします。命令と命令の間にキャラクター型のデータを挟むような場合は特に注意が必要です。アセンブラーのDS 0HやCNOP命令で明示的に境界調整を行います。
とりあえずは実行命令の列の中でフルワード境界に調整する必要があるならCNOP 0,4、データ域の直後に命令を置く場合はDS 0Hと覚えておけばいいでしょう。特に命令についてはデータ領域の直後であろうがなかろうが、命令自身にラベルを付けるのではなく、直前にラベル付きのDS 0Hを置きます。普段からそういう癖をつけるとよいでしょう。CNOP命令の詳細はアセンブラーのマニュアルを参照して下さい。


命令の形式

CPU命令は2,4,6バイトのいずれかの長さを持ち、命令によってその長さが決まっています。オペランドの内容によって長さが変わることはありません。また命令には形式があって命令長やオペランドのフォーマットなどは形式によって定められています。同じ命令なら形式もオペランドの内容によって変わることはありません。現在のzアーキテクチャーでは命令セットが増えたため命令の形式も増え約20種類近くもあります。ここでは基本となる6つの命令形式を紹介します。

命令形式を知らなくてもアセンブラー言語でのプログラミングはできます。ただしデバッグ作業でダンプリストの解析をしたり、意図しない実行結果となった時に改めてアセンブルリストをトレースする際には命令形式の理解は必要になります。特にCPU命令のオペランドを間違った時、構文自体が正しければアセンブラーはエラーにしません。そのためソースプログラムだけを追っていては誤りに気づかないことは多いのです。命令形式がわかっていれば、マシンコードに翻訳されたCPU命令を見て、元のソースの命令記述の誤りに気づけるようになります。
命令形式を理解する早道はとにかくアセンブルリストを見ることです。翻訳されたマシンコードと自分が書いた命令を対比させて読む癖をつけます。デバッグには必ずアセンブルリストを使い、元のソースプログラムはプログラム修正作業以外では絶対に見ない、を徹底すれば命令形式や具体的な命令コードはすぐにわかるようになります。プログラミングをやって見ればわかりますが、実戦で作成するプログラムであっても使用する命令の種類はそう多くないのです。せいぜい数十個です。これはプログラムの規模にあまり影響を受けません。またマシンコードから命令記述をイメージする、ハンド逆アセンブルができるようになるとデバッグ作業の効率は飛躍的に向上します。


レジスターとメモリーアクセスに関する基本的な命令


プログラム割込み

命令の使い方を誤るとCPUは割込みを起こします。これはプログラム割込みと呼ばれます。発生した内容に応じてコードが設定されていて、割込みによってOSに通知します。一般にプログラム割込み=バグと考えられていますが、正確にはそうではありません。
多くの割込みはプログラム・エラーではあるのですが、「仮想アドレスから対応する実記憶を参照しようとしたら、そこにページが無かった」と言うケースもあります。これはページ変換例外と言う割込みで通知されます。MVSはこの割込みを受けると、本当にページが無いのか?実際はページアウトされているだけか?などを調べます。ページアウトされていれば、そのページを読み込めばいいのですからプログラムの実行をエラーにはしません。対応するページが本当に無い場合はアドレスが誤っているのですからMVSによってプログラムはエラーにさせられます。このような時、MVSはCPUからの割込みコードをOSのシステム完了コードに変換してプログラムを異常終了させます。これが S0Cx または S0Dx と言うABENDです。xの部分はCPUの割込みコードに基本的に対応しますが例外もあります。我々は通常のプログラムではCPUからの割込みコードを直接意識しませんので、ここではMVSのABENDコードで解説します。

Posted in S/370アセンブラー講座 • • Top Of Page