/** * 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); }