Categories Books | Hard | Hardware | Linux | MCU | Misc | Publish | Radio | Repository | Thoughts | Time | UNIX | Writing | プロフィール
さて、しばらく間が空いてしまいましたが "Linux徹底詳解再訪" の続きです。まずは、ちょっとした復習から。次に示す、inc.s を用意してください。
inc ecx inc cx
32ビット・ECX レジスター、および16ビット・CX レジスターを1インクリメントしていますが、NASM でアセンブルするとどのようなコードが出力されるでしょうか?opcode table 片手に、予想してみてください。
$ nasm -o inc inc.s $ hexdump -C inc 00000000 66 41 41 |fAA| 00000003
opcode table を見ると、0x41 に "inc eCX" というニーモニックが割り当てられています。NASM はデフォルトで 16ビットモードを仮定していますから、"inc ecx" の場合は、オペランドサイズ・プレフィックスを前置して、オペコードの意味を inc cx から inc ecx に変更する必要があります。ダンプ出力を確認すると、確かに最初の inc 命令(0x41) には 0x66 が前置されていますが、"inc cx" の場合は、そのまま 0x41 が出力されていることが分かります。
次に、32ビットモードを指定した inc32.s を用意してください。
bits 32 inc ecx inc cx
アセンブル&ダンプです。
$ nasm -o inc32 inc32.s $ hexdump -C inc32 00000000 41 66 41 |AfA| 00000003
先ほどの場合とは逆に、"inc cx" 命令の時にオペランドサイズ・プレフィックスが前置されていることが分かります。
このように、オペランドサイズ・プレフィックスはビットモードに応じて後続の機械語の意味を変化させる、いわば「逆転スイッチ」なのです。
x86 におけるサイズ・プレフィックスにはもうひとつ、「アドレスサイズ・プレフィックス」なるものが存在します。ここで既に紹介した mov 命令を再考してみましょう。
機械語 0xA1 には "mov eAX, [iv]" というニーモニックが割り当てられていましたが、この命令の中には実は「ふたつのサイズ」が隠れているのです。
ひとつは、先ほどの inc 命令と同じくオペランドであるレジスター(EAX/AX)のサイズ。そしてもうひとつは、[iv] で指定されるオフセットアドレスのサイズです。
具体的にソースを通じて理解してみましょう。次の例題、mov16.s を用意してください。
bits 16 mov eax, [ 0x1234 ]
"bits 16" で 16ビットモードであることを宣言し、0x1234 番地の内容を EAX レジスターに転送しています(0x12345678 ではない点がミソ)。それでは、アセンブルです。
$ nasm -o mov16 mov16.s $ hexdump -C mov16 00000000 66 a1 34 12 |f.4.| 00000004
オペランドサイズ・プレフィックスが前置されている点は予想通りですが、何かおかしくありませんか?そう、アドレス指定が 0x00001234 ではなく、0x1234 と2バイトになっています。ついでに、次の mov16err.s も実行してみてください。
bits 16 mov eax, [ 0x12345678 ]
オフセットアドレスを 0x1234 から 0x12345678 に変更してみました。
$nasm -o mov16err mov16err.s mov16err.s:3: warning: word value exceeds bounds $ hexdump -C mov16err 00000000 66 a1 78 56 |f.xV| 00000004
アセンブルしてみると、"word value exceeds bounds" という警告が発生した上に、実際に出力されたコードは 0x12345678 の「下位2バイトのみ」でした。この奇妙な現象は、16ビットモードにおいて、オフセットアドレスは 16ビットに制限されていることに由来しています。
となると、リアルモード上で 32ビットレジスターを取り扱うことは出来ても、32ビットのオフセットでメモリーを操作することは出来ないのでしょうか?
これが、出来るんです。そのための仕掛けが「アドレスサイズ・プレフィックス」です。先ほどの mov16err.s を次のように修正してください(mov16dword.s)。
bits 16 mov eax, [ dword 0x12345678 ]
アドレスの前に "dword (Double WORD)" を前置していますが、この結果 NASM はプログラマーがオフセットアドレスとして 16ビットではなく、32ビットを要求していることを知ります。アセンブルに際して、どのような変化が生じるか見てみましょう。
$ nasm mov16dword.s $ hexdump -C mov16dword 00000000 67 66 a1 78 56 34 12 |gf.xV4.| 00000007
今度は意図通り、0x12345678 の計4バイトがオフセットアドレスとして展開されました。警告も表示されていません。さて、注目すべきは先頭の 0x67 ですが、これは一体なぁに?
もうお分かりの通り、0x67 がアドレスサイズ・プレフィックスです。「アドレスサイズ」という名前はあまり良くないのですが、要するに「オフセットアドレスを 16ビット長・32ビット長のどちらで表現するか」ということを意味しています。
それでは最後に、オペランドサイズとアドレスサイズの総当たり戦をやって、頭の中を整理しておきましょう。まずは、16ビット版の movs16.s からです。
bits 16 mov ax, [ 0x1234 ] mov ax, [ dword 0x12345678 ] mov eax, [ 0x1234 ] mov eax, [ dword 0x12345678 ]
例によってアセンブル&逆アセンブルです。
$ nasm movs16.s $ ndisasm movs16 00000000 A13412 mov ax,[0x1234] 00000003 67A178563412 mov ax,[dword 0x12345678] 00000009 66A13412 mov eax,[0x1234] 0000000D 6766A178563412 mov eax,[dword 0x12345678]
4種類のサイズプレフィックスの組み合わせが確認できました。少しずつ、目の前の視界が開けてきたでしょうか?この勢いで次は、32ビット版の movs32.s です。
bits 32 mov ax, [ word 0x1234 ] mov ax, [ 0x12345678 ] mov eax, [ word 0x1234 ] mov eax, [ 0x12345678 ]
32ビットモードでは、Double word がデフォルトになり、逆に2バイト長の場合は明示的に word を前置する必要がありますので、注意してください。
$ nasm movs32.s $ ndisasm -b 32 movs32 00000000 6766A13412 mov ax,[word 0x1234] 00000005 66A178563412 mov ax,[0x12345678] 0000000B 67A13412 mov eax,[word 0x1234] 0000000F A178563412 mov eax,[0x12345678]
ndisasm 起動時に 32ビットモードを指定する -b オプションの指定を忘れないでください(オプションなしの場合、何が起こるでしょう?)。サイズプレフィックスの付き方が、movs16 とは正反対であることが分かります。
さあ、これでようやく基礎知識が整いました。次回はいよいよ、".code16gcc" の謎に迫りましょう!