• 創世記機械語

すべてのコンピュータは「機械語」を実行する。すべてのCPUは「命令セット」を持ち、機械語はこの命令セットの組み合わせで記述されている。

機械語のプログラミングは、CPUの特性を体感できる唯一の方法だ。最近では、高水準言語(高級言語)によるプログラミングが普通だが、マイクロプロセッサが入手可能になった1970年台には、機械語によるプログラミングはごく普通に行われていた。

機械語でプログラムを書くにはいくつか方法がある。「アセンブラ」と呼ばれるプログラムが利用できるなら、命令セットに人間に分かりやすい名前(ニモニック)をつけたアセンブリ言語でプログラムを書けばよい。そのためには、現在のパソコンのようなシステムが必要になる。しかし、当時、筆者が利用できたのは、自作したメモリが4キロバイト程度の8 bit CPU(最初のものはモトローラのMC6800だった)を使ったコンピュータだけである。

なので、当時の趣味人は、ニモニックなどで命令を考えるものの、自分でバイナリに変換してプログラムを作っていた。これをハンドアッセンブルという。いろいろな方法があったが、筆者は、一度タイプライタで打ったニモニックで書いたプログラムを、あとから16進数表現の機械語に直していった。もっとも、当時の8 bit CPUの命令は単純で、比較的簡単に理解できた。

機械語命令では、メモリアドレスを命令の引数(オペランド)に書かねばならない。アセンブラでは、機械語を生成するときに自動的にオペランドにアドレスを入れてくれる。たとえば、サブルーチン呼び出しCALL命令は、オペランドにサブルーチンの開始アドレスを書き込む必要がある。アセンブラでは「CALL SUB01」と書けばよい。あとからサブルーチンを書いて、先頭に同じラベル「SUB01」を記述すればよかった。

しかし、ハンドアッセンブルの場合、オペランドのアドレスの扱いが面倒だった。プログラムを書き終え、先頭から16進数の機械語に変換していき、サブルーチンの開始アドレスが決定すると、ようやくCALL命令のオペランドが決まる。こうしたところは一か所だけでなく、複数箇所あるのが普通だ。一応テクニックとしては、プログラムの先頭付近にサブルーチンへのジャンプ命令を並べて、そこをCALLで呼び出すようにすると、アドレスを早い段階で固定でき、サブルーチンのアドレスが変わっても修正箇所は1つだけになる、というものがある(それでも何回も間違ったが……)。

いまでもプログラムを書くことがあるが、たまに昔のハンドアッセンブルを懐かしく感じることがある。しかし、今、機械語命令を直接書いて実行させようとするのは、かなり大変な作業だ。C++などには、機械語命令をニモニックで記述して埋め込む仕組みがあるのだが、筆者のセンチメンタリズムには不釣り合いである。

筆者は、Visual Studioなどでプログラムを作りたいのではなく、数バイト程度の機械語をハンドアッセンブルして実行させみたいだけなのだ。MS-DOSが動くなら付属のDEBUG.EXEを使うと、簡易アセンブラの機能が使える。しかし、そのために仮想環境を作り、MS-DOSを探してインストールするのも大がかりすぎる。

ただ、Windowsなどの最近のオペレーティングシステムでは、ハンドアッセンブルした機械語の実行は、以前ほど簡単ではない。というのは、ウィルスの温床になったため、データ部分に機械語を書き込んで実行することは禁止されているからだ。

数バイトの機械語を簡単に実行できないかと、インターネット検索して見つけたのが「Is it possible to execute an x86 assembly sequence from within C#」という記事。ここで参照している「execute-byte-array.cs」というプログラムを使えば、バイト配列で記述した機械語プログラムをC#から呼び出して実行できる。そのためには、メモリ領域を確保して、プログラム実行用に設定した上で機械語を書き込んで、関数として呼び出す。

概念的にはこれでいいので、これを改良して、コマンドライン引数で指定した16進数の機械語を実行できる、ExecMLというプログラムを作った(写真01)。GitHubにソースコードを置いてある。

  • 写真01: ExecML.exeの実行。コマンドラインの引数のうち、最初の2つはRCXとRDXレジスタに入る64bitのパラメーター。その後ろの48 89……となっている部分が機械語(引数はすべて16進数である)。機械語は、下に表示されているように、RCXをRAXにコピーしたあと、RDXとRAXを加算(結果はRAXに残る)し、メインプログラムに戻る(RET)だけだ

64 bit版Windows(x64)で、関数を呼び出すときのルールは「x64 での呼び出し規則」に記述がある。これによれば、最初の2つ引数は、RCX、RDXレジスタに入る。このプログラムでは、最初の2つの引数がRCX、RDXに入って機械語プログラムが呼び出される。機械語プログラムは、処理結果をRAXに入れてRet命令(0xC3)で戻るようにすればよい。

今回のタイトルネタは、J・P・ホーガンの「創世記機械」(原題Genesis Machine,1978)である。原作の刊行時、筆者は機械語と格闘中だった。翻訳を最初に読んだときには、若かったのでかなり感動した。だが、歳取るとダメだねぇ、本書後半に、ついて行けない。とはいえ、前半の架空物理理論のところだけでも読む価値はある。