1 /** 2 * This module provides OS specific helper function for threads support 3 * 4 * Copyright: Copyright Digital Mars 2010 - 2010. 5 * License: Distributed under the 6 * $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0). 7 * (See accompanying file LICENSE) 8 * Source: $(DRUNTIMESRC core/sys/windows/_threadaux.d) 9 * Authors: Rainer Schuetze 10 */ 11 12 module nulib.system.win32.threadaux; 13 14 15 import nulib.system.win32.basetsd/+ : HANDLE+/; 16 import nulib.system.win32.winbase/+ : CloseHandle, GetCurrentThreadId, GetCurrentProcessId, 17 GetModuleHandleA, GetProcAddress+/; 18 import nulib.system.win32.windef/+ : BOOL, DWORD, FALSE, HRESULT+/; 19 import core.stdc.stdlib; 20 21 public import core.thread; 22 23 version (LDC) import ldc.attributes, ldc.llvmasm; 24 25 extern(Windows) 26 HANDLE OpenThread(DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwThreadId) nothrow @nogc; 27 28 extern (C) extern __gshared int _tls_index; 29 30 extern (C) // rt.minfo 31 { 32 void rt_moduleTlsCtor(); 33 void rt_moduleTlsDtor(); 34 } 35 36 private: 37 /////////////////////////////////////////////////////////////////// 38 struct thread_aux 39 { 40 // don't let symbols leak into other modules 41 42 enum SystemProcessInformation = 5; 43 enum STATUS_INFO_LENGTH_MISMATCH = 0xc0000004; 44 45 // structs subject to change according to MSDN, more info at http://undocumented.ntinternals.net 46 // declarations according to http://processhacker.sourceforge.net/doc/ntexapi_8h_source.html 47 // NOTE: the declarations assume default alignment for Win64 and contain some padding data 48 struct UNICODE_STRING 49 { 50 short Length; 51 short MaximumLength; 52 wchar* Buffer; 53 } 54 // process or thread ID, documentation says it is a HANDLE, but it's actually the ID (a DWORD) 55 alias PTID = size_t; 56 57 struct _SYSTEM_PROCESS_INFORMATION 58 { 59 int NextEntryOffset; // When this entry is 0, there are no more processes to be read. 60 int NumberOfThreads; 61 long WorkingSetPrivateSize; 62 uint HardFaultCount; 63 uint NumberOfThreadsHighWatermark; 64 ulong CycleTime; 65 long CreateTime; 66 long UserTime; 67 long KernelTime; 68 UNICODE_STRING ImageName; 69 int BasePriority; 70 PTID /*Unique*/ProcessId; 71 PTID InheritedFromUniqueProcessId; 72 uint HandleCount; 73 uint SessionId; 74 size_t UniqueProcessKey; 75 size_t PeakVirtualSize; 76 size_t VirtualSize; 77 uint PageFaultCount; 78 size_t PeakWorkingSetSize; 79 size_t WorkingSetSize; 80 size_t QuotaPeakPagedPoolUsage; 81 size_t QuotaPagedPoolUsage; 82 size_t QuotaPeakNonPagedPoolUsage; 83 size_t QuotaNonPagedPoolUsage; 84 size_t PagefileUsage; 85 size_t PeakPagefileUsage; 86 size_t PrivatePageCount; 87 long ReadOperationCount; 88 long WriteOperationCount; 89 long OtherOperationCount; 90 long ReadTransferCount; 91 long WriteTransferCount; 92 long OtherTransferCount; 93 94 // SYSTEM_THREAD_INFORMATION or SYSTEM_EXTENDED_THREAD_INFORMATION structures follow. 95 } 96 97 struct _SYSTEM_THREAD_INFORMATION 98 { 99 long KernelTime; 100 long UserTime; 101 long CreateTime; 102 uint WaitTime; 103 void* StartAddress; 104 PTID ProcessId; 105 PTID ThreadId; 106 int Priority; 107 int BasePriority; 108 uint ContextSwitches; 109 uint ThreadState; 110 int WaitReason; 111 int reserved; 112 } 113 114 alias fnNtQuerySystemInformation = extern(Windows) 115 HRESULT function( uint SystemInformationClass, void* info, uint infoLength, uint* ReturnLength ) nothrow @nogc; 116 117 enum ThreadBasicInformation = 0; 118 119 struct THREAD_BASIC_INFORMATION 120 { 121 int ExitStatus; 122 void** TebBaseAddress; 123 PTID ProcessId; 124 PTID ThreadId; 125 size_t AffinityMask; 126 int Priority; 127 int BasePriority; 128 } 129 130 alias fnNtQueryInformationThread = extern(Windows) 131 int function( HANDLE ThreadHandle, uint ThreadInformationClass, void* buf, uint size, uint* ReturnLength ) nothrow @nogc; 132 133 enum SYNCHRONIZE = 0x00100000; 134 enum THREAD_GET_CONTEXT = 8; 135 enum THREAD_QUERY_INFORMATION = 0x40; 136 enum THREAD_SUSPEND_RESUME = 2; 137 138 /////////////////////////////////////////////////////////////////// 139 // get the thread environment block (TEB) of the thread with the given handle 140 static void** getTEB( HANDLE hnd ) nothrow @nogc 141 { 142 HANDLE nthnd = GetModuleHandleA( "NTDLL" ); 143 assert( nthnd, "cannot get module handle for ntdll" ); 144 fnNtQueryInformationThread fn = cast(fnNtQueryInformationThread) GetProcAddress( nthnd, "NtQueryInformationThread" ); 145 assert( fn, "cannot find NtQueryInformationThread in ntdll" ); 146 147 THREAD_BASIC_INFORMATION tbi; 148 int Status = (*fn)(hnd, ThreadBasicInformation, &tbi, tbi.sizeof, null); 149 assert(Status == 0); 150 151 return tbi.TebBaseAddress; 152 } 153 154 // get the thread environment block (TEB) of the thread with the given identifier 155 static void** getTEB( uint id ) nothrow @nogc 156 { 157 HANDLE hnd = OpenThread( THREAD_QUERY_INFORMATION, FALSE, id ); 158 assert( hnd, "OpenThread failed" ); 159 160 void** teb = getTEB( hnd ); 161 CloseHandle( hnd ); 162 return teb; 163 } 164 165 // get linear address of TEB of current thread 166 version (LDC) 167 { 168 static void** getTEB() nothrow @nogc @naked 169 { 170 version (X86) return __asm!(void**)("mov %fs:(0x18), $0", "=r"); 171 else version (X86_64) return __asm!(void**)("mov %gs:0($1), $0", "=r,r", 0x30); 172 else version (AArch64) return __asm!(void**)("mov $0, x18", "=r"); 173 else static assert(false); 174 } 175 } 176 else 177 { 178 static void** getTEB() nothrow @nogc 179 { 180 version (D_InlineAsm_X86) 181 { 182 asm pure nothrow @nogc 183 { 184 naked; 185 mov EAX,FS:[0x18]; 186 ret; 187 } 188 } 189 else version (D_InlineAsm_X86_64) 190 { 191 asm pure nothrow @nogc 192 { 193 naked; 194 mov RAX,0x30; 195 mov RAX,GS:[RAX]; // immediate value causes fixup 196 ret; 197 } 198 } 199 else version (GNU_InlineAsm) 200 { 201 void** teb; 202 version (X86) 203 asm pure nothrow @nogc { "movl %%fs:0x18, %0;" : "=r" (teb); } 204 else version (X86_64) 205 asm pure nothrow @nogc { "movq %%gs:0x30, %0;" : "=r" (teb); } 206 else 207 static assert(false); 208 return teb; 209 } 210 else 211 { 212 static assert(false); 213 } 214 } 215 } 216 217 // get the stack bottom (the top address) of the thread with the given handle 218 static void* getThreadStackBottom( HANDLE hnd ) nothrow @nogc 219 { 220 void** teb = getTEB( hnd ); 221 return teb[1]; 222 } 223 224 // get the stack bottom (the top address) of the thread with the given identifier 225 static void* getThreadStackBottom( uint id ) nothrow @nogc 226 { 227 void** teb = getTEB( id ); 228 return teb[1]; 229 } 230 231 // create a thread handle with full access to the thread with the given identifier 232 static HANDLE OpenThreadHandle( uint id ) nothrow @nogc 233 { 234 return OpenThread( SYNCHRONIZE|THREAD_GET_CONTEXT|THREAD_QUERY_INFORMATION|THREAD_SUSPEND_RESUME, FALSE, id ); 235 } 236 237 /////////////////////////////////////////////////////////////////// 238 // enumerate threads of the given process calling the passed function on each thread 239 // using function instead of delegate here to avoid allocating closure 240 static bool enumProcessThreads( uint procid, bool function( uint id, void* context ) dg, void* context ) 241 { 242 HANDLE hnd = GetModuleHandleA( "NTDLL" ); 243 fnNtQuerySystemInformation fn = cast(fnNtQuerySystemInformation) GetProcAddress( hnd, "NtQuerySystemInformation" ); 244 if ( !fn ) 245 return false; 246 247 uint sz = 16384; 248 uint retLength; 249 HRESULT rc; 250 char* buf; 251 for ( ; ; ) 252 { 253 buf = cast(char*) core.stdc.stdlib.malloc(sz); 254 if (!buf) 255 return false; 256 rc = fn( SystemProcessInformation, buf, sz, &retLength ); 257 if ( rc != STATUS_INFO_LENGTH_MISMATCH ) 258 break; 259 core.stdc.stdlib.free( buf ); 260 sz *= 2; 261 } 262 scope(exit) core.stdc.stdlib.free( buf ); 263 264 if (rc != 0) 265 return false; 266 267 auto pinfo = cast(_SYSTEM_PROCESS_INFORMATION*) buf; 268 auto pend = cast(_SYSTEM_PROCESS_INFORMATION*) (buf + retLength); 269 for ( ; pinfo < pend; ) 270 { 271 if ( pinfo.ProcessId == procid ) 272 { 273 auto tinfo = cast(_SYSTEM_THREAD_INFORMATION*)(pinfo + 1); 274 for ( int i = 0; i < pinfo.NumberOfThreads; i++, tinfo++ ) 275 if ( tinfo.ProcessId == procid ) 276 if ( !dg( cast(uint) tinfo.ThreadId, context ) ) // IDs are actually DWORDs 277 return false; 278 } 279 if ( pinfo.NextEntryOffset == 0 ) 280 break; 281 pinfo = cast(_SYSTEM_PROCESS_INFORMATION*) (cast(char*) pinfo + pinfo.NextEntryOffset); 282 } 283 return true; 284 } 285 286 static bool enumProcessThreads( bool function( uint id, void* context ) dg, void* context ) 287 { 288 return enumProcessThreads( GetCurrentProcessId(), dg, context ); 289 } 290 291 // execute function on the TLS for the given thread 292 alias extern(C) void function() externCVoidFunc; 293 static void impersonate_thread( uint id, externCVoidFunc fn ) 294 { 295 impersonate_thread(id, () => fn()); 296 } 297 298 static void impersonate_thread( uint id, scope void delegate() dg) 299 { 300 if ( id == GetCurrentThreadId() ) 301 { 302 dg(); 303 return; 304 } 305 306 // temporarily set current TLS array pointer to the array pointer of the referenced thread 307 void** curteb = getTEB(); 308 void** teb = getTEB( id ); 309 assert( teb && curteb ); 310 311 void** curtlsarray = cast(void**) curteb[11]; 312 void** tlsarray = cast(void**) teb[11]; 313 if ( !curtlsarray || !tlsarray ) 314 return; 315 316 curteb[11] = tlsarray; 317 318 // swap out the TLS slots aswell 319 version (Win64) 320 { 321 enum TEB_offset_TlsSlots = 0x1480; 322 enum TEB_offset_TlsExpansionSlots = 0x1780; 323 } 324 else 325 { 326 enum TEB_offset_TlsSlots = 0xE10; 327 enum TEB_offset_TlsExpansionSlots = 0xF94; 328 } 329 void* tlsSlotsAdr(void** teb) { return cast(void*) teb + TEB_offset_TlsSlots; } 330 ref void* tlsExpansionSlots(void** teb) { return *cast(void**)(cast(void*) teb + TEB_offset_TlsExpansionSlots); } 331 332 import core.stdc.string; 333 void*[64] slots = void; 334 memcpy(slots.ptr, tlsSlotsAdr(curteb), slots.sizeof); 335 void* extraSlots = tlsExpansionSlots(curteb); 336 337 memcpy(tlsSlotsAdr(curteb), tlsSlotsAdr(teb), slots.sizeof); 338 tlsExpansionSlots(curteb) = tlsExpansionSlots(teb); 339 340 dg(); 341 342 curteb[11] = curtlsarray; 343 344 // copy the TLS slots back in case they have been changed in dg 345 memcpy(tlsSlotsAdr(teb), tlsSlotsAdr(curteb), slots.sizeof); 346 tlsExpansionSlots(teb) = tlsExpansionSlots(curteb); 347 348 memcpy(tlsSlotsAdr(curteb), slots.ptr, slots.sizeof); 349 tlsExpansionSlots(curteb) = extraSlots; 350 } 351 } 352 353 public: 354 // forward as few symbols as possible into the "global" name space 355 alias getTEB = thread_aux.getTEB; 356 alias getThreadStackBottom = thread_aux.getThreadStackBottom; 357 alias OpenThreadHandle = thread_aux.OpenThreadHandle; 358 alias enumProcessThreads = thread_aux.enumProcessThreads; 359 alias impersonate_thread = thread_aux.impersonate_thread; 360 361 // get the start of the TLS memory of the thread with the given handle 362 void* GetTlsDataAddress( HANDLE hnd ) nothrow 363 { 364 if ( void** teb = getTEB( hnd ) ) 365 if ( void** tlsarray = cast(void**) teb[11] ) 366 return tlsarray[_tls_index]; 367 return null; 368 } 369 370 // get the start of the TLS memory of the thread with the given identifier 371 void* GetTlsDataAddress( uint id ) nothrow 372 { 373 HANDLE hnd = OpenThread( thread_aux.THREAD_QUERY_INFORMATION, FALSE, id ); 374 assert( hnd, "OpenThread failed" ); 375 376 void* tls = GetTlsDataAddress( hnd ); 377 CloseHandle( hnd ); 378 return tls; 379 } 380 381 // get the address of the entry in the TLS array for the current thread 382 // use C mangling to access it from msvc.c 383 extern(C) void** GetTlsEntryAdr() 384 { 385 if( void** teb = getTEB() ) 386 if( void** tlsarray = cast(void**) teb[11] ) 387 return tlsarray + _tls_index; 388 return null; 389 } 390 391 /////////////////////////////////////////////////////////////////// 392 // run rt_moduleTlsCtor in the context of the given thread 393 void thread_moduleTlsCtor( uint id ) 394 { 395 thread_aux.impersonate_thread(id, &rt_moduleTlsCtor); 396 } 397 398 /////////////////////////////////////////////////////////////////// 399 // run rt_moduleTlsDtor in the context of the given thread 400 void thread_moduleTlsDtor( uint id ) 401 { 402 thread_aux.impersonate_thread(id, &rt_moduleTlsDtor); 403 }