SetThreadContextが変な動きをする

64bitプロセスがSetThreadContextをCONTEXT_CONTROLフラグなしで行うと、対象になったスレッドのCSがなぜか勝手に0x23になる場合がある。64bitプロセスのCSは本来0x33で、0x23はWOW64(32bitプロセス)のための値である。この異常な変更が行われると、そのスレッドは32bitを超えるアドレスを正しく扱えなくなるため、正常に動作しなくなる。


手元のx64 Vista SP2/x64 XP SP2でこの異常な変更を確認できた。x64 Win7 ではこのような現象は起こらない。回避策としては、(仮に値が不要でも)常にCONTEXT_CONTROLフラグをつけ、正しい値を取得したうえでSetThreadContextする。以下は再現コード。

//
// >cl generic.cpp
//
#include <windows.h>
#include <stdio.h>                 
#ifndef _AMD64_
#error "only work on x64"
#endif

DWORD WINAPI StartRoutine(LPVOID lpThreadParameter)
{
    printf("%s running on %p\n", __FUNCTION__, (void*)StartRoutine);
    for (;;){}
    return 0;
}


int main(int argc, char* argv[])
{
    HANDLE Thread;
    CONTEXT Context;
    
    printf("CreateThread\n");
    Thread = CreateThread(NULL, 0, StartRoutine, NULL, 0, NULL);
    Sleep(2000);
    SuspendThread(Thread);

    printf("GetThreadContext\n");
    Context.ContextFlags = CONTEXT_CONTROL;
    GetThreadContext(Thread, &Context);
    printf("CS = 0x%02X\n", Context.SegCs);

    printf("Get/SetThreadContext\n");
    Context.ContextFlags = CONTEXT_INTEGER;
    GetThreadContext(Thread, &Context);
    SetThreadContext(Thread, &Context);

    printf("GetThreadContext\n");
    Context.ContextFlags = CONTEXT_CONTROL;
    GetThreadContext(Thread, &Context);
    printf("CS = 0x%02X\n", Context.SegCs);
    printf("ResumeThread\n");
    ResumeThread(Thread);
    CloseHandle(Thread);


    Sleep(10000);
    return 0;
}
>generic.exe
CreateThread
StartRoutine running on 0000000140001000    // 32bitを超えるアドレス空間で動作開始
GetThreadContext
CS = 0x33
Get/SetThreadContext
GetThreadContext
CS = 0x23               // ここでなぜか0x23になってる
ResumeThread
// スレッドを再開するとクラッシュ

なにこれこわい。