| /** |
| * This module provides OS specific helper function for threads support |
| * |
| * Copyright: Copyright Digital Mars 2010 - 2010. |
| * License: Distributed under the |
| * $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0). |
| * (See accompanying file LICENSE) |
| * Source: $(DRUNTIMESRC core/sys/windows/_threadaux.d) |
| * Authors: Rainer Schuetze |
| */ |
| |
| /* NOTE: This file has been patched from the original DMD distribution to |
| * work with the GDC compiler. |
| */ |
| module core.sys.windows.threadaux; |
| version (Windows): |
| @system: |
| |
| import core.sys.windows.basetsd/+ : HANDLE+/; |
| import core.sys.windows.winbase/+ : CloseHandle, GetCurrentThreadId, GetCurrentProcessId, |
| GetModuleHandleA, GetProcAddress+/; |
| import core.sys.windows.windef/+ : BOOL, DWORD, FALSE, HRESULT+/; |
| import core.stdc.stdlib; |
| |
| public import core.thread; |
| |
| extern(Windows) |
| HANDLE OpenThread(DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwThreadId) nothrow @nogc; |
| |
| extern (C) extern __gshared int _tls_index; |
| |
| extern (C) // rt.minfo |
| { |
| void rt_moduleTlsCtor(); |
| void rt_moduleTlsDtor(); |
| } |
| |
| private: |
| /////////////////////////////////////////////////////////////////// |
| struct thread_aux |
| { |
| // don't let symbols leak into other modules |
| |
| enum SystemProcessInformation = 5; |
| enum STATUS_INFO_LENGTH_MISMATCH = 0xc0000004; |
| |
| // structs subject to change according to MSDN, more info at http://undocumented.ntinternals.net |
| // declarations according to http://processhacker.sourceforge.net/doc/ntexapi_8h_source.html |
| // NOTE: the declarations assume default alignment for Win64 and contain some padding data |
| struct UNICODE_STRING |
| { |
| short Length; |
| short MaximumLength; |
| wchar* Buffer; |
| } |
| // process or thread ID, documentation says it is a HANDLE, but it's actually the ID (a DWORD) |
| alias size_t PTID; |
| |
| struct _SYSTEM_PROCESS_INFORMATION |
| { |
| int NextEntryOffset; // When this entry is 0, there are no more processes to be read. |
| int NumberOfThreads; |
| long WorkingSetPrivateSize; |
| uint HardFaultCount; |
| uint NumberOfThreadsHighWatermark; |
| ulong CycleTime; |
| long CreateTime; |
| long UserTime; |
| long KernelTime; |
| UNICODE_STRING ImageName; |
| int BasePriority; |
| PTID /*Unique*/ProcessId; |
| PTID InheritedFromUniqueProcessId; |
| uint HandleCount; |
| uint SessionId; |
| size_t UniqueProcessKey; |
| size_t PeakVirtualSize; |
| size_t VirtualSize; |
| uint PageFaultCount; |
| size_t PeakWorkingSetSize; |
| size_t WorkingSetSize; |
| size_t QuotaPeakPagedPoolUsage; |
| size_t QuotaPagedPoolUsage; |
| size_t QuotaPeakNonPagedPoolUsage; |
| size_t QuotaNonPagedPoolUsage; |
| size_t PagefileUsage; |
| size_t PeakPagefileUsage; |
| size_t PrivatePageCount; |
| long ReadOperationCount; |
| long WriteOperationCount; |
| long OtherOperationCount; |
| long ReadTransferCount; |
| long WriteTransferCount; |
| long OtherTransferCount; |
| |
| // SYSTEM_THREAD_INFORMATION or SYSTEM_EXTENDED_THREAD_INFORMATION structures follow. |
| } |
| |
| struct _SYSTEM_THREAD_INFORMATION |
| { |
| long KernelTime; |
| long UserTime; |
| long CreateTime; |
| uint WaitTime; |
| void* StartAddress; |
| PTID ProcessId; |
| PTID ThreadId; |
| int Priority; |
| int BasePriority; |
| uint ContextSwitches; |
| uint ThreadState; |
| int WaitReason; |
| int reserved; |
| } |
| |
| alias fnNtQuerySystemInformation = extern(Windows) |
| HRESULT function( uint SystemInformationClass, void* info, uint infoLength, uint* ReturnLength ) nothrow @nogc; |
| |
| enum ThreadBasicInformation = 0; |
| |
| struct THREAD_BASIC_INFORMATION |
| { |
| int ExitStatus; |
| void** TebBaseAddress; |
| PTID ProcessId; |
| PTID ThreadId; |
| size_t AffinityMask; |
| int Priority; |
| int BasePriority; |
| } |
| |
| alias fnNtQueryInformationThread = extern(Windows) |
| int function( HANDLE ThreadHandle, uint ThreadInformationClass, void* buf, uint size, uint* ReturnLength ) nothrow @nogc; |
| |
| enum SYNCHRONIZE = 0x00100000; |
| enum THREAD_GET_CONTEXT = 8; |
| enum THREAD_QUERY_INFORMATION = 0x40; |
| enum THREAD_SUSPEND_RESUME = 2; |
| |
| /////////////////////////////////////////////////////////////////// |
| // get the thread environment block (TEB) of the thread with the given handle |
| static void** getTEB( HANDLE hnd ) nothrow @nogc |
| { |
| HANDLE nthnd = GetModuleHandleA( "NTDLL" ); |
| assert( nthnd, "cannot get module handle for ntdll" ); |
| fnNtQueryInformationThread fn = cast(fnNtQueryInformationThread) GetProcAddress( nthnd, "NtQueryInformationThread" ); |
| assert( fn, "cannot find NtQueryInformationThread in ntdll" ); |
| |
| THREAD_BASIC_INFORMATION tbi; |
| int Status = (*fn)(hnd, ThreadBasicInformation, &tbi, tbi.sizeof, null); |
| assert(Status == 0); |
| |
| return tbi.TebBaseAddress; |
| } |
| |
| // get the thread environment block (TEB) of the thread with the given identifier |
| static void** getTEB( uint id ) nothrow @nogc |
| { |
| HANDLE hnd = OpenThread( THREAD_QUERY_INFORMATION, FALSE, id ); |
| assert( hnd, "OpenThread failed" ); |
| |
| void** teb = getTEB( hnd ); |
| CloseHandle( hnd ); |
| return teb; |
| } |
| |
| // get linear address of TEB of current thread |
| static void** getTEB() nothrow @nogc |
| { |
| version (Win32) |
| { |
| version (GNU_InlineAsm) |
| { |
| void** teb; |
| asm pure nothrow @nogc { "movl %%fs:0x18, %0;" : "=r" (teb); } |
| return teb; |
| } |
| else |
| { |
| asm pure nothrow @nogc |
| { |
| naked; |
| mov EAX,FS:[0x18]; |
| ret; |
| } |
| } |
| } |
| else version (Win64) |
| { |
| version (GNU_InlineAsm) |
| { |
| void** teb; |
| asm pure nothrow @nogc { "movq %%gs:0x30, %0;" : "=r" (teb); } |
| return teb; |
| } |
| else |
| { |
| asm pure nothrow @nogc |
| { |
| naked; |
| mov RAX,0x30; |
| mov RAX,GS:[RAX]; // immediate value causes fixup |
| ret; |
| } |
| } |
| } |
| else |
| { |
| static assert(false); |
| } |
| } |
| |
| // get the stack bottom (the top address) of the thread with the given handle |
| static void* getThreadStackBottom( HANDLE hnd ) nothrow @nogc |
| { |
| void** teb = getTEB( hnd ); |
| return teb[1]; |
| } |
| |
| // get the stack bottom (the top address) of the thread with the given identifier |
| static void* getThreadStackBottom( uint id ) nothrow @nogc |
| { |
| void** teb = getTEB( id ); |
| return teb[1]; |
| } |
| |
| // create a thread handle with full access to the thread with the given identifier |
| static HANDLE OpenThreadHandle( uint id ) nothrow @nogc |
| { |
| return OpenThread( SYNCHRONIZE|THREAD_GET_CONTEXT|THREAD_QUERY_INFORMATION|THREAD_SUSPEND_RESUME, FALSE, id ); |
| } |
| |
| /////////////////////////////////////////////////////////////////// |
| // enumerate threads of the given process calling the passed function on each thread |
| // using function instead of delegate here to avoid allocating closure |
| static bool enumProcessThreads( uint procid, bool function( uint id, void* context ) dg, void* context ) |
| { |
| HANDLE hnd = GetModuleHandleA( "NTDLL" ); |
| fnNtQuerySystemInformation fn = cast(fnNtQuerySystemInformation) GetProcAddress( hnd, "NtQuerySystemInformation" ); |
| if ( !fn ) |
| return false; |
| |
| uint sz = 16384; |
| uint retLength; |
| HRESULT rc; |
| char* buf; |
| for ( ; ; ) |
| { |
| buf = cast(char*) core.stdc.stdlib.malloc(sz); |
| if (!buf) |
| return false; |
| rc = fn( SystemProcessInformation, buf, sz, &retLength ); |
| if ( rc != STATUS_INFO_LENGTH_MISMATCH ) |
| break; |
| core.stdc.stdlib.free( buf ); |
| sz *= 2; |
| } |
| scope(exit) core.stdc.stdlib.free( buf ); |
| |
| if (rc != 0) |
| return false; |
| |
| auto pinfo = cast(_SYSTEM_PROCESS_INFORMATION*) buf; |
| auto pend = cast(_SYSTEM_PROCESS_INFORMATION*) (buf + retLength); |
| for ( ; pinfo < pend; ) |
| { |
| if ( pinfo.ProcessId == procid ) |
| { |
| auto tinfo = cast(_SYSTEM_THREAD_INFORMATION*)(pinfo + 1); |
| for ( int i = 0; i < pinfo.NumberOfThreads; i++, tinfo++ ) |
| if ( tinfo.ProcessId == procid ) |
| if ( !dg( cast(uint) tinfo.ThreadId, context ) ) // IDs are actually DWORDs |
| return false; |
| } |
| if ( pinfo.NextEntryOffset == 0 ) |
| break; |
| pinfo = cast(_SYSTEM_PROCESS_INFORMATION*) (cast(char*) pinfo + pinfo.NextEntryOffset); |
| } |
| return true; |
| } |
| |
| static bool enumProcessThreads( bool function( uint id, void* context ) dg, void* context ) |
| { |
| return enumProcessThreads( GetCurrentProcessId(), dg, context ); |
| } |
| |
| // execute function on the TLS for the given thread |
| alias extern(C) void function() externCVoidFunc; |
| static void impersonate_thread( uint id, externCVoidFunc fn ) |
| { |
| impersonate_thread(id, () => fn()); |
| } |
| |
| static void impersonate_thread( uint id, scope void delegate() dg) |
| { |
| if ( id == GetCurrentThreadId() ) |
| { |
| dg(); |
| return; |
| } |
| |
| // temporarily set current TLS array pointer to the array pointer of the referenced thread |
| void** curteb = getTEB(); |
| void** teb = getTEB( id ); |
| assert( teb && curteb ); |
| |
| void** curtlsarray = cast(void**) curteb[11]; |
| void** tlsarray = cast(void**) teb[11]; |
| if ( !curtlsarray || !tlsarray ) |
| return; |
| |
| curteb[11] = tlsarray; |
| |
| // swap out the TLS slots aswell |
| version (Win64) |
| { |
| enum TEB_offset_TlsSlots = 0x1480; |
| enum TEB_offset_TlsExpansionSlots = 0x1780; |
| } |
| else |
| { |
| enum TEB_offset_TlsSlots = 0xE10; |
| enum TEB_offset_TlsExpansionSlots = 0xF94; |
| } |
| void* tlsSlotsAdr(void** teb) { return cast(void*) teb + TEB_offset_TlsSlots; } |
| ref void* tlsExpansionSlots(void** teb) { return *cast(void**)(cast(void*) teb + TEB_offset_TlsExpansionSlots); } |
| |
| import core.stdc.string; |
| void*[64] slots = void; |
| memcpy(slots.ptr, tlsSlotsAdr(curteb), slots.sizeof); |
| void* extraSlots = tlsExpansionSlots(curteb); |
| |
| memcpy(tlsSlotsAdr(curteb), tlsSlotsAdr(teb), slots.sizeof); |
| tlsExpansionSlots(curteb) = tlsExpansionSlots(teb); |
| |
| dg(); |
| |
| curteb[11] = curtlsarray; |
| |
| // copy the TLS slots back in case they have been changed in dg |
| memcpy(tlsSlotsAdr(teb), tlsSlotsAdr(curteb), slots.sizeof); |
| tlsExpansionSlots(teb) = tlsExpansionSlots(curteb); |
| |
| memcpy(tlsSlotsAdr(curteb), slots.ptr, slots.sizeof); |
| tlsExpansionSlots(curteb) = extraSlots; |
| } |
| } |
| |
| public: |
| // forward as few symbols as possible into the "global" name space |
| alias thread_aux.getTEB getTEB; |
| alias thread_aux.getThreadStackBottom getThreadStackBottom; |
| alias thread_aux.OpenThreadHandle OpenThreadHandle; |
| alias thread_aux.enumProcessThreads enumProcessThreads; |
| alias thread_aux.impersonate_thread impersonate_thread; |
| |
| // get the start of the TLS memory of the thread with the given handle |
| void* GetTlsDataAddress( HANDLE hnd ) nothrow |
| { |
| if ( void** teb = getTEB( hnd ) ) |
| if ( void** tlsarray = cast(void**) teb[11] ) |
| return tlsarray[_tls_index]; |
| return null; |
| } |
| |
| // get the start of the TLS memory of the thread with the given identifier |
| void* GetTlsDataAddress( uint id ) nothrow |
| { |
| HANDLE hnd = OpenThread( thread_aux.THREAD_QUERY_INFORMATION, FALSE, id ); |
| assert( hnd, "OpenThread failed" ); |
| |
| void* tls = GetTlsDataAddress( hnd ); |
| CloseHandle( hnd ); |
| return tls; |
| } |
| |
| /////////////////////////////////////////////////////////////////// |
| // run rt_moduleTlsCtor in the context of the given thread |
| void thread_moduleTlsCtor( uint id ) |
| { |
| thread_aux.impersonate_thread(id, &rt_moduleTlsCtor); |
| } |
| |
| /////////////////////////////////////////////////////////////////// |
| // run rt_moduleTlsDtor in the context of the given thread |
| void thread_moduleTlsDtor( uint id ) |
| { |
| thread_aux.impersonate_thread(id, &rt_moduleTlsDtor); |
| } |