Visual Studio 2005 の「最適化付き x64 build」で不正なコードが生成される問題について

コミケ直前にこの問題に遭遇して、x64 build のリリース大丈夫かとヒヤヒヤしましたが…^^;

さて、この問題について検証してみました。

テストコードとして次のコードを打ち*1、x64 build 最適化付き (/O2, /Ox) でビルドしてみると可憐に落ちます。


enum ETest
{
    TEST_FAILED = INT_MIN,
    TEST_F_HOGE1,
    TEST_F_HOGE2,

    TEST_F_MAX,

    TEST_SUCCEEDED = 0,
    TEST_S_HOGE1,
    TEST_S_HOGE2,

    TEST_S_MAX,
};

const int g_anArray[] =
{
    1, 2, 3, 4
};

// main にある eTest を *非*コンパイル時定数にする為に
// noline にする
__declspec(noinline) ETest GetValue()
{
    return TEST_FAILED;
}

int _tmain(int argc, _TCHAR* argv[])
{
    const ETest eTest = GetValue();
    __int64     n     = g_anArray[eTest - INT_MIN];

    printf("%I64d", n);

    getchar();

    return 0;
}

で、↓が混合モードのコード


int _tmain(int argc, _TCHAR* argv[])
{
0000000000401010  sub         rsp,28h 
	const ETest eTest = GetValue();
0000000000401014  call        GetValue (401000h) 
	__int64     n     = g_anArray[eTest - INT_MIN];
0000000000401019  lea         rdx,[4021B0h] 

	printf("%I64d", n);
0000000000401020  movsxd      rcx,eax 
0000000000401023  mov         rax,0FFFFFFFE00000000h 
000000000040102D  add         rax,rdx 
0000000000401030  movsxd      rdx,dword ptr [rax+rcx*4] ; ココで落ちる
0000000000401034  lea         rcx,[string "%I64d" (4021C0h)] 
000000000040103B  call        qword ptr [__imp_printf (402130h)] 

	getchar();
0000000000401041  call        qword ptr [__imp_getchar (402128h)] 

	return 0;
0000000000401047  xor         eax,eax 
}
0000000000401049  add         rsp,28h 
000000000040104D  ret

落ちた所の dword ptr [rax+rcx*4] を見ると、

  • rax に g_anArray へのポインタ
  • rcx に 添え字

見たいなのが来るべきなんだと思います。が、落ちた時のレジスタの内容 を見てみると…

  • rax = FFFFFFFE004021B0
  • rcx = FFFFFFFF80000000

計算すると、アドレス FFFFFFFC004021B0 を参照してます…って、おまえ何処見てるねん! そりゃ落ちるわ。



じゃぁ、何がおかしいのかなー……と、いろいろ調査してみた結果、おそらく 0000000000401020 の "movsxd rcx,eax" がおかしいんだと思います。

movsxd と符号拡張付きmove が使われてますが、movzx*2 のゼロ拡張付きmove が正解だと思います。
この仮定で計算してみると…


movzx    rcx,eax                   ; rcx = 0x00000000_80000000
mov      rax,0FFFFFFFE00000000h    ;
add      rax,rdx                   ; rax = 0xFFFFFFFE_004021B0 -> g_anArray + 0xFFFFFFFE_00000000
movsxd   rdx,dword ptr [rax+rcx*4] ;
; rcx * 4                       = 0x00000002_00000000
; rcx * 4 + 0xFFFFFFFE_00000000 = 0x00000000_00000000 ゆえに
; rcx * 4 + rax                 = 0x00000000_004021B0 -> OK!

OK、結論。




Microsoft本当に些細なチョンボミス




んもー Microsoft おっちょこちょいさん! あはははははははははははははは(右手をグーにして振り上げながら



添え字に負数と減算が絡んでくるようなコード書いてる人は、ちょっと注意かもしれません。

*1:あまり良くないコードですけど^^;

*2:合ってる?^^;