/ «2004-05-06 (Thu) ^ 2004-07-01 (Thu)» ?
   西田 亙の本:GNU 開発ツール -- hello.c から a.out が誕生するまで --

Categories Books | Hard | Hardware | Linux | MCU | Misc | Publish | Radio | Repository | Thoughts | Time | UNIX | Writing | プロフィール


2004-05-09 (Sun)

[Writing] Linux徹底詳解再訪 続報・その4

オペランドサイズ・プレフィックスの復習

さて、しばらく間が空いてしまいましたが "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ビットのオフセットでメモリーを操作することは出来ないのでしょうか?

NASM dword prefix

これが、出来るんです。そのための仕掛けが「アドレスサイズ・プレフィックス」です。先ほどの 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" の謎に迫りましょう!