10

ユーザー空間プログラムは、64 ビット Windows (現在 XP-64) で "GS:" をどのように構成できますか?
(設定により、GS:0 を任意の 64 ビット リニア アドレスに設定します)。

「JIT」環境を、もともと Win32 用に開発された X86-64 に移植しようとしています。

残念な設計上の側面の 1 つは、同一のコードを複数のユーザー空間スレッド (「ファイバー」など) で実行する必要があることです。コードの Win32 バージョンは、これに GS セレクターを使用し、ローカル データにアクセスするための適切なプレフィックスを生成します。「mov eax,GS:[offset]」は、現在のタスクの正しいデータを指します。Win32 バージョンのコードは、機能する値を持っていれば、その値を GS にロードします。

これまでのところ、64 ビット Windows は LDT をサポートしていないことがわかっているため、Win32 で使用される方法は機能しません。ただし、X86-64 命令セットには「SWAPGS」と、従来のセグメンテーションを使用せずに GS をロードする方法が含まれていますが、これはカーネル空間でのみ機能します。

X64 のマニュアルによると、たとえ Win64 が記述子へのアクセスを許可していたとしても (そうではありません)、セグメント ベースの上位 32 ビットを設定する方法はありません。これらを設定する唯一の方法は、GS_BASE_MSR を使用することです (および対応する FS_BASE_MSR - 64 ビット モードでは他のセグメント ベースは無視されます)。WRMSR命令はRing0なので直接使えません。

ユーザー空間の "GS:" を変更できる Zw* 関数、または Windows API のその他のダーク コーナーを期待しています。Windows はまだ FS: を独自の TLS に使用していると思いますが、何らかのメカニズムが利用可能でなければなりませんか?


このサンプル コードは、問題を示しています。バイト コードを使用していることをあらかじめお詫び申し上げます。VS は 64 ビット コンパイルのインライン アセンブリを行いません。説明のために、これを 1 つのファイルとして保持しようとしていました。

XP-32 では「PASS」と表示されますが、XP-x64 では表示されません。


#include <windows.h>
#include <string.h>
#include <stdio.h>


unsigned char GetDS32[] = 
            {0x8C,0xD8,     // mov eax, ds
             0xC3};         // ret

unsigned char SetGS32[] =
            {0x8E,0x6C,0x24,0x04,   // mov gs, ss:[sp+4] 
             0xC3 };                // ret

unsigned char UseGS32[] = 
           { 0x8B,0x44,0x24,0x04,   // mov eax, ss:[sp+4] 
             0x65,0x8B,0x00,        // mov eax, gs:[eax] 
             0xc3 };                // ret

unsigned char SetGS64[] =
            {0x8E,0xe9,             // mov gs, rcx
             0xC3 };                // ret

unsigned char UseGS64[] =       
           { 0x65,0x8B,0x01,         // mov eax, gs:[rcx]
             0xc3 };

typedef WORD(*fcnGetDS)(void);
typedef void(*fcnSetGS)(WORD);
typedef DWORD(*fcnUseGS)(LPVOID);
int (*NtSetLdtEntries)(DWORD, DWORD, DWORD, DWORD, DWORD, DWORD);

int main( void )
{
    SYSTEM_INFO si;
    GetSystemInfo(&si);
    LPVOID p = VirtualAlloc(NULL, 1024, MEM_COMMIT|MEM_TOP_DOWN,PAGE_EXECUTE_READWRITE);
    fcnGetDS GetDS = (fcnGetDS)((LPBYTE)p+16);
    fcnUseGS UseGS = (fcnUseGS)((LPBYTE)p+32);
    fcnSetGS SetGS = (fcnSetGS)((LPBYTE)p+48);
    *(DWORD *)p = 0x12345678;

    if (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64) 
    {
        memcpy( GetDS, &GetDS32, sizeof(GetDS32));
        memcpy( UseGS, &UseGS64, sizeof(UseGS64));
        memcpy( SetGS, &SetGS64, sizeof(SetGS64));
    }
    else
    {
        memcpy( GetDS, &GetDS32, sizeof(GetDS32));
        memcpy( UseGS, &UseGS32, sizeof(UseGS32));
        memcpy( SetGS, &SetGS32, sizeof(SetGS32));
    }

    SetGS(GetDS());
    if (UseGS(p) != 0x12345678) exit(-1);

    if (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64) 
    {
        // The gist of the question - What is the 64-bit equivalent of the following code
    }
    else
    {
        DWORD base = (DWORD)p;
        LDT_ENTRY ll;
        int ret;
        *(FARPROC*)(&NtSetLdtEntries) = GetProcAddress(LoadLibrary("ntdll.dll"), "NtSetLdtEntries");
        ll.BaseLow = base & 0xFFFF;
        ll.HighWord.Bytes.BaseMid = base >> 16;
        ll.HighWord.Bytes.BaseHi = base >> 24;
        ll.LimitLow = 400;     
        ll.HighWord.Bits.LimitHi = 0;
        ll.HighWord.Bits.Granularity = 0;
        ll.HighWord.Bits.Default_Big = 1; 
        ll.HighWord.Bits.Reserved_0 = 0;
        ll.HighWord.Bits.Sys = 0; 
        ll.HighWord.Bits.Pres = 1;
        ll.HighWord.Bits.Dpl = 3; 
        ll.HighWord.Bits.Type = 0x13; 
        ret = NtSetLdtEntries(0x80, *(DWORD*)&ll, *((DWORD*)(&ll)+1),0,0,0);
        if (ret < 0) { exit(-1);}
        SetGS(0x84);
    }
    if (UseGS(0) != 0x12345678) exit(-1);
    printf("PASS\n");
}
4

7 に答える 7

4

SetThreadcontext APIを介してスレッド コンテキストを直接変更できます。ただし、コンテキストが変更されている間はスレッドが実行されていないことを確認する必要があります。中断して別のスレッドからコンテキストを変更するか、偽の SEH 例外をトリガーして SEH ハンドラーのスレッド コンテキストを変更します。その後、OS がスレッド コンテキストを変更し、スレッドを再スケジュールします。

アップデート:

2 番目のアプローチのサンプル コード:

__try
{
    __asm int 3 // trigger fake exception
}
__except(filter(GetExceptionCode(), GetExceptionInformation()))
{
}

int filter(unsigned int code, struct _EXCEPTION_POINTERS *ep)
{
    ep->ContextRecord->SegGs = 23;
    ep->ContextRecord->Eip++;
    return EXCEPTION_CONTINUE_EXECUTION;
}

try ブロック内の命令は、基本的にソフトウェア例外を発生させます。次に、OS は制御をフィルター プロシージャに渡します。フィルター プロシージャはスレッド コンテキストを変更し、OS に int3 命令をスキップして実行を継続するように効果的に伝えます。
これは一種のハックですが、すべての機能が文書化されています:)

于 2009-07-26T19:19:33.407 に答える
2

なぜGSレジスタを設定する必要があるのですか? Windows は、TLS スペースを指すように設定します。

X64 用のコーディングはしていませんが、FS を使用して、スレッドを管理する X32 ビット コードを生成するコンパイラを作成しました。X64 では、GS が FS に置き換わり、他のすべてはほとんど同じように機能します。したがって、GS はスレッド ローカル ストアを指します。スレッド ローカル変数のブロックを割り当てた場合 (Win32 では、オフセット 0 に 64 個のうち 32 個を割り当てます)、スレッドは 32 個の格納場所に直接アクセスして、処理したいものにアクセスできます。作業スレッド固有の領域を割り当てる必要はありません。Windows がそれを行います。

もちろん、言語固有のスレッドを実行するために設定したスケジューラーで、特定のスレッド データと見なすものを、確保したこのスペースにコピーすることもできます。

于 2009-07-28T04:12:32.573 に答える
1

OSスレッドに移行するとどうなりますか?パフォーマンスはそんなに悪いですか?

単一のポインターサイズのTLSスロットを使用して、軽量スレッドのストレージ領域のベースを保管できます。コンテキストスイッチ中に1つのポインタを交換する必要があります。値が必要なときはいつでも、そこから新しい一時レジスタの1つをロードします。関数呼び出し全体で保持される、数少ないものの1つを使用することを心配する必要はありません。

サポートされているもう1つのソリューションは、FiberAPIを使用して軽量スレッドをスケジュールすることです。次に、JITを変更して、を適切に呼び出しますFlsGet/SetValue

申し訳ありませんが、古いコードはアドレス指定にセグメントプレフィックスを使用するように記述されているようですが、LDTはそのようなものには使用できません。コード生成を少し修正する必要があります。

既存のコードは、GS用語を3番目の用語として、scaled-index+ベースアドレス指定を多用しています。「lea」の後に2つの登録フォームを使用できると思います

良い計画のように聞こえます。

「moveax、mem」のように、プレフィックスを受け入れるが、レジスタベースのアドレス指定を使用するには完全に置き換える必要がある場合

おそらく、それらをアドレス+オフセットアドレスに移動することができます。オフセットレジスタは、TLSブロックのベースを保持するレジスタである可能性があります。

于 2009-07-31T00:11:48.240 に答える
1

x64 コードで GS を変更したことがないので、間違っているかもしれませんが、PUSH/POP または LGS で GS を変更できるはずではありませんか?

更新: Intel のマニュアルには、mov SegReg も記載されています。Reg は 64 ビット モードで許可されています。

于 2009-07-26T19:13:45.400 に答える
1

x86_64 には x86 よりも多くのレジスタがあるため、GS を使用できない場合に考慮すべきオプションの 1 つは、単純に汎用レジスタ (EBP など) の 1 つをベース ポインタとして使用し、それを補うことです。新しい R8 ~ R15 レジスタとの違い。

于 2009-07-29T02:46:15.903 に答える
1

GetFiberData を使用しないのはなぜですか、それとも 2 つの余分な命令を避けようとしているのでしょうか?

于 2009-07-31T15:56:52.180 に答える
0

x86-64 はアドレス指定に新しい用語を追加しませんでした。既存のコードは、GS 用語を 3 番目の用語として、スケールド インデックス + ベース アドレス指定を多用しています。

あなたの質問にはかなり混乱していますが、このアセンブラが役に立てば幸いです。まだ C コードに移植していませんが、まもなく移植する予定です。

__declspec(thread)データの読み取り

    mov     ecx, cs:TlsIndex ; TlsIndex is a memory location 
                             ; containing a DWORD with the value 0
    mov     rax, gs:58h
    mov     edx, 830h
    mov     rax, [rax+rcx*8]
    mov     rax, [rdx+rax]
    retn

申し訳ありませんが、データを書き込む例はありません。上記は、リバースエンジニアリングを行っている逆アセンブルコードから取得したものです。

更新:これは同等です。私は書いていませんが、上記のCコード。NTAuthorityおよび/またはcitizenmpによって作成されたと思います。

rage::scrThread* GetActiveThread()
{
    char* moduleTls = *(char**)__readgsqword(88);

    return *reinterpret_cast<rage::scrThread**>(moduleTls + 2096);
}

そして、ここに同じことが書かれています:

void SetActiveThread(rage::scrThread* thread)
{
    char* moduleTls = *(char**)__readgsqword(88);
    *reinterpret_cast<rage::scrThread**>(moduleTls + 2096) = thread;
}
于 2017-02-01T09:46:57.970 に答える