CPUには隠し命令、すなわち実装されているもののドキュメントで触れられていない命令があります。実際にプログラミングして試してみましょう。
(本記事の初稿は2004年です。10年以上経過した現在でも有用な内容と思われるので、そのままの内容で公開します。)
なお、これらの命令を実際のアプリケーションに組み込むのは止めましょう。ドキュメント化されていない、すなわちCPUメーカーが存在を公認していない命令なので、将来のCPUで動作しなくなる恐れがあります。
■SETALC/SALC
SETALC(もしくはSALC)という命令があります。Set AL on Carry Flagの略で、CF(キャリー・フラグ)のビットをALレジスタの各ビットにコピーします。
【SETALC/SALC】ニーモニック:SETALCもしくはSALC
オペコード:0xD6
擬似コード:
IF (CF==0) THEN
AL=0x0
ELSE
AL=0xFF
END
この命令の存在は286の頃から知られていますが、インテルのドキュメントではいまだに隠し命令扱いです。AMD64ではオペコードマップにSALCのニーモニックで記述があるので隠し命令ではありませんが、命令そのものについての記述はありません。(オペコードマップの注釈には「Invalid on 64-bit mode」とある。)
テスト用に以下のようなプログラムを作ります。なお、32ビットと64ビットでソースコードを共用できるようにし、
命令実行時にCPUのEXCEPTIONが発生しても大丈夫なように構造化例外処理(SEH)内でSETALC/SALCを実行します。
【setalcc.c】
#include <windows.h>
#include <stdio.h>
SIZE_T SetALCbyZero(void);
SIZE_T SetALCbyOne(void);
void main(void)
{
BOOL result;
// 構造化例外処理機構(SEH)内でSETALC/SALCを実行する。
__try{
result=((SetALCbyZero()==0x0)&&(SetALCbyOne()==0xFF));
}
__except(EXCEPTION_EXECUTE_HANDLER){
printf("×CPUでExceptionが発生してしまいました!¥n");
exit(0);
}
// 結果を表示する。
if(result) {
printf("◎SETALC/SALCは実装されているみたいです。¥n");
} else {
printf("×SETALC/SALCは実装されていないみたいです。¥n");
}
}
;
【setalca.asm】
; [Custom Build Step for Win32]
; debug:
; ml -c -D_WIN64=0 -Zi "-Fl$(IntDir)\$(InputName).lst" "-Fo$(IntDir)\$(InputName).obj" "$(InputPath)"
; release:
; ml -c -D_WIN64=0 "-Fl$(IntDir)\$(InputName).lst" "-Fo$(IntDir)\$(InputName).obj" "$(InputPath)"
; outputs:
; $(IntDir)\$(InputName).obj
;
; [Custom Build Step for Win64]
; debug:
; ml64 -c -D_WIN64=1 -Zi "-Fl$(IntDir)\$(InputName).lst" "-Fo$(IntDir)\$(InputName).obj" "$(InputPath)"
; release:
; ml64 -c -D_WIN64=1 "-Fl$(IntDir)\$(InputName).lst" "-Fo$(IntDir)\$(InputName).obj" "$(InputPath)"
; outputs:
; $(IntDir)\$(InputName).obj
;
IFE _WIN64
; ---- Win32 ----
.686p
.model flat, c
.code
SetALCbyZero PROC
xor EAX, EAX
not AL ; AL=0xFF
clc ; CF=0
db 0d6h ; SETALC(成功すれば、AL=0x0)
ret
SetALCbyZero ENDP
SetALCbyOne PROC
xor EAX, EAX ; AL=0x0
stc ; CF=1
db 0d6h ; SETALC(成功すれば、AL=0xFF)
ret
SetALCbyOne ENDP
ELSE
; ---- Win64 ----
.code
SetALCbyZero PROC
xor RAX, RAX
not AL ; AL=0xFF
clc ; CF=0
db 0d6h ; SETALC(成功すれば、AL=0x0)
ret
SetALCbyZero ENDP
SetALCbyOne PROC
xor RAX, RAX ; AL=0x0
stc ; CF=1
db 0d6h ; SETALC(成功すれば、AL=0xFF)
ret
SetALCbyOne ENDP
ENDIF
end
まず、Pentium4-3.06GHz(Northwood, HT対応)+WindowsXP SP2で試してみました。
【Pentium4(Northwood)で実行 - 32ビット】
D:\>setalcc
◎SETALC/SALCは実装されているみたいです。
D:\>
次に、Athlon64 3000+(NewCastle)+x64版WindowsXPで試してみます。
【Athlon64(NewCastle)で実行 - 32ビット(WOW64上)】
D:\>setalcc
◎SETALC/SALCは実装されているみたいです。
D:\>
【Athlon64(NewCastle)で実行 - 64ビット】
D:\>setalcc
×CPUでExceptionが発生してしまいました!
D:\>
32ビットについては、実質使えるみたいです。64-bitについては、AMD64ではダメみたいです。EM64Tではどうなるのでしょうか?EM64TがEnableなPentium4/CeleronD/Xeonで果たしてどうなるか、興味津々です。(たぶんAMD64と同じになるのでしょうが。)
■その他の命令
その他の命令についても、ニーモニック、オペコード、概要、AMD64オペコードマップ記載の有無のみ記しておきます。
【ICEBP】
ニーモニック:ICEBP
オペコード:0xF1
概要:ICE(In-Circuit Emulatorの略、ちなみにこれはインテルの登録商標)用の
ブレークポイント命令(INT 01)
AMD64オペコードマップ:記載あり(INT1, ICE Bkpt)
【UMOV】
ニーモニック:UMOV
オペコード:0x0F 0x10, 0x0F 0x11, 0x0F 0x12, 0x0F 0x13
概要:ICE用の命令(User-MOVe)
存在が確認されているCPU:386/486
AMD64オペコードマップ:記載なし(SSE命令(MOVUPS, MOVLPS, MOVHLPS)で使用)
【LOADALL】
ニーモニック:LOADALL
オペコード:0x0F 0x05(286の場合), 0x0F 0x07(386/486の場合)
概要:CPUの全状態をロードする。(レジスタだけでなくディスクリプタキャッシュなども含む)
存在が確認されているCPU:286/386/486
AMD64オペコードマップ:記載なし(SYSCALLとSYSRETで使用)
【CMPXCHG】(オペコードが途中で変更された命令)
ニーモニック:CMPXCHG
オペコード:0x0F 0xA6, 0x0F 0xA7(486(A-Step)のみ)
0x0F 0xB0, 0x0F 0xB1(486(B-Step)以降)
概要:オペランドとアキュムレータとのアトミックな比較・交換
AMD64オペコードマップ:0x0F 0xA6, 0x0F 0xA7は記載なし(invalid)
【IBTS】(廃止された命令)
ニーモニック:IBTS
オペコード:0x0F 0xA7
概要:ビット・ストリングの挿入(Insert Bit String)
存在が確認されているCPU:386(A-Step, B0-Step)のみ
AMD64オペコードマップ:記載なし(invalid)
【XBTS】(廃止された命令)
ニーモニック:XBTS
オペコード:0x0F 0xA6
概要:ビット・ストリングの抽出(Extract Bit String)
存在が確認されているCPU:386(A-Step, B0-Step)のみ
AMD64オペコードマップ:記載なし(invalid)
執筆日:2004年12月21日(火)(www.marbacka.net内の別のサイトで公開)
最終更新日:2017年2月19日(日)