10 Stimmen

Verwendung von "GS:" in 64-Bit-Windows-Assembly (z. B. bei der Portierung von TLS-Code)

Wie kann ein User-Space-Programm "GS:" unter 64-Bit-Windows (derzeit XP-64) konfigurieren?
(Bei der Konfiguration ist GS:0 auf eine beliebige lineare 64-Bit-Adresse zu setzen).

Ich versuche, eine "JIT"-Umgebung auf X86-64 zu portieren, die ursprünglich für Win32 entwickelt wurde.

Ein unglücklicher Design-Aspekt ist, dass identischer Code in mehreren User-Space-Threads (z.B. "Fasern") laufen muss. Die Win32-Version des Codes verwendet hierfür den GS-Selektor und generiert das richtige Präfix für den Zugriff auf die lokalen Daten - "mov eax,GS:[offset]" zeigt auf die richtigen Daten für die aktuelle Aufgabe. Der Code der Win32-Version würde einen Wert in GS laden, wenn er nur einen Wert hätte, der funktionieren würde.

Bisher konnte ich feststellen, dass 64-Bit-Windows die LDT nicht unterstützt, so dass die unter Win32 verwendete Methode nicht funktioniert. Der X86-64-Befehlssatz enthält jedoch "SWAPGS" sowie eine Methode zum Laden von GS ohne Verwendung der Legacy-Segmentierung - dies funktioniert jedoch nur im Kernelbereich.

Laut den X64-Handbüchern gibt es, selbst wenn Win64 den Zugriff auf Deskriptoren erlauben würde - was nicht der Fall ist - keine Möglichkeit, die hohen 32-Bits der Segmentbasis zu setzen. Die einzige Möglichkeit, diese zu setzen, ist über GS_BASE_MSR (und die entsprechende FS_BASE_MSR - die anderen Segmentbasen werden im 64-Bit-Modus ignoriert). Der WRMSR-Befehl ist Ring0, so dass ich ihn nicht direkt verwenden kann.

Ich hoffe auf eine Zw*-Funktion, mit der ich "GS:" im Userspace ändern kann, oder auf eine andere dunkle Ecke der Windows-API. Ich glaube, Windows verwendet immer noch FS: für sein eigenes TLS, also muss ein Mechanismus vorhanden sein?


Dieser Beispielcode veranschaulicht das Problem. Ich entschuldige mich im Voraus für die Verwendung von Byte-Code - VS wird nicht Inline-Assembler für die 64-Bit-Kompilierung zu tun, und ich war versucht, dies als eine Datei für illustrative Zwecke zu halten.

Das Programm zeigt auf XP-32 "PASS" an, auf XP-x64 jedoch nicht.


#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");
}

2voto

Ira Baxter Punkte 91118

Warum müssen Sie einstellen. das GS-Register? Windows setzt es für Sie, um auf den TLS-Speicherplatz zu verweisen.

Ich habe zwar nicht für X64 programmiert, aber ich habe einen Compiler gebaut, der X32-Bit-Code erzeugt, der Threads verwaltet und FS verwendet. Unter X64, GS ersetzt FS und alles andere funktioniert ziemlich gleich . GS zeigt also auf den thread-lokalen Speicher. Wenn Sie einen Block von thread-lokalen Variablen zugewiesen haben (unter Win32 werden 32 von 64 an Offset 0 zugewiesen), hat Ihr Thread jetzt direkten Zugriff auf 32 Speicherplätze, mit denen er tun kann, was er will. Sie brauchen keinen thread-spezifischen Speicherplatz zuzuweisen; Windows hat dies für Sie getan.

Natürlich können Sie auch kopieren, was Sie als Ihr spezifischen Thread-Daten in den von Ihnen beiseite gelegten Bereich in dem Scheduler, den Sie für die Ausführung Ihrer sprachspezifischen Threads eingerichtet haben.

1voto

PhiS Punkte 4380

Ich habe noch nie GS in x64-Code geändert, so kann ich falsch sein, aber sollten Sie nicht in der Lage sein, GS durch PUSH/POP oder durch LGS zu ändern?

Update: Laut Intel-Handbüchern ist auch mov SegReg, Reg im 64-Bit-Modus zulässig.

1voto

bdonlan Punkte 213545

Da x86_64 viel mehr Register als x86 hat, wäre eine Option, die Sie in Betracht ziehen könnten, wenn Sie GS nicht verwenden können, einfach eines der Allzweckregister (z. B. EBP) als Basiszeiger zu verwenden und die Differenz mit den neuen R8-R15-Registern auszugleichen.

1voto

Marsh Ray Punkte 2767

Was passiert, wenn Sie einfach zu OS-Threads wechseln? Ist die Leistung so schlecht?

Sie könnten einen einzelnen TLS-Slot in Zeigergröße verwenden, um die Basis des Speicherbereichs Ihres leichtgewichtigen Threads zu speichern. Sie müssten nur einen Zeiger während Ihres Kontextwechsels austauschen. Laden Sie eines der neuen temporären Register von dort, wann immer Sie den Wert benötigen, und Sie müssen sich nicht darum kümmern, eines der wenigen erhaltenen Funktionsaufrufe zu verwenden.

Eine andere unterstützte Lösung wäre die Verwendung der Faser-APIs um Ihre leichtgewichtigen Threads zu planen. Sie würden dann das JIT so ändern, dass es die richtigen Aufrufe an FlsGet/SetValue .

Tut mir leid, es klingt so, als ob der alte Code so geschrieben wurde, dass er sich auf Segment-Präfixe für die Adressierung verlässt, und jetzt ist die LDT für diese Art von Dingen einfach nicht verfügbar. Sie werden die Codegenerierung ein wenig korrigieren müssen.

der bestehende Code verwendet in hohem Maße die Adressierung mit skaliertem Index und Basis, wobei der GS-Term als einem 3. Begriff. Ich denke, ich kann 'lea' gefolgt von einer Zwei-Register-Form verwenden

Das klingt nach einem guten Plan.

Fälle wie "mov eax, mem", die ein Präfix akzeptieren, aber vollständig ersetzt werden müssen, um eine registerbasierte Adressierung zu verwenden

Vielleicht könnten Sie diese in die Adress- und Offset-Adressierung verschieben. Das Offset-Register könnte das Register sein, das die Basis Ihres TLS-Blocks enthält.

1voto

atomice Punkte 3002

Warum verwenden Sie nicht GetFiberData oder wollen Sie die zwei zusätzlichen Anweisungen vermeiden?

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X