• 組立指令

今回は、x64系CPUのハンドアッセンブルをやってみる。x64はx86の64 bit版だが元になるx86の機械語は、命令の長さが可変長の16 bit CPU i8086で始まり、互換性を持ったまま32 bit化するまで拡張されたので、かなり面倒なパターンを持っていた。これをAMDが64 bit化したときプリフィックスを使って命令を拡張したため、さらに面倒なものになった。

前回の記事で解説した"FFS"のような最初の1になるビットを探すには、“BSF”、“BSR”という命令がある。前者は最下位ビット(LSB)から、後者は最上位ビットから検索を行う。この命令はパイプライン中なら1クロックサイクルで演算結果を出せるため、どのアルゴリズムを使うよりも速い。

x64でハンドアッセンブルを行うなら、まずは、基礎資料としてインテルの「Intel 64 and IA-32 Architectures Software Developer's Manual」をダウンロードしておく。これにはVolume 1~4までが1つになったPDFファイルと、Volumeごとに分かれたPDFファイルがあるが中身は同じ。分かれているほうがファイルとしては扱いやすい。

まずは、Volume 2から、使いたい命令を探す。今回は、BSF、“Bit Scan Forward”命令を探す(改訂でページ数が変わるのでページ数は示さない。検索でみつけてほしい)。命令ごとの解説ページの先頭に命令ビットパターンを示す表がある。それによれば、64 bitレジスタを使う命令パターン(Opcode列)は、「REX.W+0F BC /r」となっている(図01)。これは、64bitデータを扱うことを示すREXプリフィックス・バイトに続いて、0x0F、0xBCが続き、その後ろにレジスタを指定するModR/Mバイトがあり、合計4バイトの命令になるという意味である。

  • 図01: BSF命令の機械語パターンのうち、64 bitデータを処理するものは最下行にある。表の“Opcode”列は、機械語のビットパターンを、Instruction列はアセンブラ表記を示す(Intel 64 and IA-32 Architectures Software Developer's Manual Volume 2。325383-080USより引用)

アセンブラで表記するなら「BSF <64 bitレジスタ>,<64 bitレジスタ/メモリ>」になることを示しているのがInstruction列である。ハンドアッセンブルするのであれば、とりあえず、「BSF RAX, RCX」などとして命令を決める。インテルのアセンブラでは、オペランド(引数)部分は、ディストネーション、ソースの順に並ぶ。ただし、アセンブラによっては逆になることもある。Linuxで使われるgccでは、ソース、ディストネーションの順に記述する。とはいえ、ここはハンドアッセンブルなので、プログラム中で一貫した順番を守ればよい。

この命令は、インテル順でRCXレジスタ(ソース)にBSF命令を適用して、結果をRAX(ディストネーション)に格納するという意味である。

x64には、16個の汎用レジスタがある(図02)。レジスタを4ビットの0b0000~0b1111のパターンで表す。図のRegBitがそれぞれのレジスタを表すビットパターンである。なお、データサイズは、プリフィックスや命令コード側で決まるので、RegBit番号はデータサイズにかかわらず共通である。

  • 図02: x64でプログラムから使う整数汎用レジスタは16本ある。アセンブラでは、名前で対象データのサイズを示す(Intel 64 and IA-32 Architectures Software Developer's Manual Volume 2。325383-080USの内容を元に筆者が作成)

レジスタの名前順とレジスタビットパターンが異なるのは、ちょっと理解に苦しむ。そもそも、AX、BXなどは人間しか使わない名前なのでドキュメントの問題である。ここまで普及すると直しようもないが、それに、気がつくことができるのはハンドアッセンブルするユーザーだけだ。

レジスタを決めるビットパターンは、REXプレフィックスの一部と、ModR/Mバイトの組合せで作る。これに関してはVolume 2のPDFに図がある(図03)。REXプリフィックスは上位4ビットが0b0100(0x4)で、下から3ビット目(Rビット)とModR/Mバイトの下から4ビット目から6ビット目(Reg)を組み合わせて4ビットにしてソースレジスタの指定にする。REXの下から4ビット目(W)が1だと64 bitデータ、0だとオペコードでデータサイズが決まる。これをREX.Wと表記する。

  • 図03: レジスタを指定する4 bitのRegBitは、REXプリフィックスバイトの一部とModR/Mバイトの一部を組み合わせて作る。BSFでは、Rrrrでディストネーション・レジスタを、Bbbbでソース・レジスタを指定する(Intel 64 and IA-32 Architectures Software Developer's Manual Volume 2。325383-080USより引用)

ディストネーション・レジスタは、REXプリフィックスの最下位ビット(Bビット)とModR/Mバイトの下位3 bit(R/M)を組合せる。ソースとディストネーションがともにレジスタであれば、ModR/Mの上位2ビット(mod)は0b11である。

RCX(ソース)は0b0 001、RAXは0b0 000になるので、REXプリフィックスは0x48(0b01001 0 0 0)、ModR/Mバイトは、0xC1(0b11 000 001)となる。なので、機械語は「48 0F BC C1」である。これにret命令(オペコードはC3)を組み合わせる。前々回の記事で紹介したExecML.exeを使い、下位16 bitがゼロになった0x10000にBSF命令を適用するプログラムを下記のようにして実行する(RDXはゼロとする)。


ExecML.exe 0x10000 0 48 0F BC C1 C3

答えは16(表示は64 bitの16進数で0000000000000010)となる。

今回のタイトルネタは、日本のテレビドラマ「マイティジャック」(1968年)の第10話「爆破指令」である。大人向けを狙った特撮物で、1963年の映画「海底軍艦」の直系ともいえる作品。MJ号の出動シーンは、今でも見込んでしまうが、操縦室でボタンのように押しているのが陸軍型端子だったりと「チャチ」なところもある。今から見るなら、背景説明のある第1話とこの話だけは見ておくべき。