You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
676 lines
18 KiB
676 lines
18 KiB
/*++
|
|
|
|
Copyright (c) 2002 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
tls.cpp
|
|
|
|
Abstract:
|
|
|
|
WinDbg Extension Api
|
|
|
|
Author:
|
|
|
|
Deon Brewis (deonb) 2-Jun-2002
|
|
|
|
Environment:
|
|
|
|
User Mode.
|
|
Kernel Mode.
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
#include "precomp.h"
|
|
#pragma hdrstop
|
|
#include <time.h>
|
|
|
|
#define TLS_ALL -2
|
|
#define TLS_CURRENT -1
|
|
|
|
// #define TLS_DBG
|
|
|
|
#ifdef TLS_DBG
|
|
#define trace dprintf
|
|
#else
|
|
#define trace __noop
|
|
#endif
|
|
|
|
EXTERN_C BOOL GetTeb32FromWowTeb(ULONG64 Teb, PULONG64 pTeb32); // implemented in peb.c
|
|
EXTERN_C BOOL GetPeb32FromWowTeb(ULONG64 Teb, PULONG64 pPeb32); // implemented in peb.c
|
|
|
|
BOOLEAN TestBit (
|
|
IN PRTL_BITMAP BitMapHeader,
|
|
IN ULONG BitNumber
|
|
)
|
|
{
|
|
|
|
PCHAR ByteAddress;
|
|
ULONG ShiftCount;
|
|
|
|
ASSERT(BitNumber < BitMapHeader->SizeOfBitMap);
|
|
|
|
ByteAddress = (PCHAR)BitMapHeader->Buffer + (BitNumber >> 3);
|
|
ShiftCount = BitNumber & 0x7;
|
|
return (BOOLEAN)((*ByteAddress >> ShiftCount) & 1);
|
|
}
|
|
|
|
ULONG64 GetPebForTarget()
|
|
{
|
|
ULONG64 pebAddress;
|
|
ULONG64 peb;
|
|
|
|
pebAddress = GetExpression("@$peb");
|
|
|
|
ULONG64 tebAddress;
|
|
tebAddress = GetExpression("@$teb");
|
|
if (tebAddress)
|
|
{
|
|
if (TargetMachine == IMAGE_FILE_MACHINE_IA64 && tebAddress)
|
|
{
|
|
ULONG64 Peb32=0;
|
|
if (GetPeb32FromWowTeb(tebAddress, &Peb32) && Peb32)
|
|
{
|
|
trace("Wow64 PEB32 at %lx\n", Peb32);
|
|
pebAddress = Peb32;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (pebAddress)
|
|
{
|
|
trace( "PEB at %p\n", pebAddress );
|
|
peb = IsPtr64() ? pebAddress : (ULONG64)(LONG64)(LONG)pebAddress;
|
|
}
|
|
else
|
|
{
|
|
trace( "PEB NULL...\n" );
|
|
peb = 0;
|
|
}
|
|
|
|
return peb;
|
|
}
|
|
|
|
ULONG64 GetTebForTarget(ULONG64 ulThread)
|
|
{
|
|
trace("GetTebForTarget %p\n", ulThread);
|
|
|
|
ULONG64 tebAddress;
|
|
ULONG64 teb;
|
|
|
|
if (TLS_ALL == ulThread)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (TLS_CURRENT == ulThread)
|
|
{
|
|
tebAddress = GetExpression("@$teb");
|
|
}
|
|
else
|
|
{
|
|
tebAddress = ulThread; // GetTebForThread!!
|
|
}
|
|
|
|
if ( tebAddress )
|
|
{
|
|
if (TargetMachine == IMAGE_FILE_MACHINE_IA64 && tebAddress)
|
|
{
|
|
ULONG64 Teb32=0;
|
|
if (GetTeb32FromWowTeb(tebAddress, &Teb32) && Teb32)
|
|
{
|
|
trace("Wow64 TEB32 at %p\n", Teb32);
|
|
tebAddress = Teb32;
|
|
trace("\n\nWow64 ");
|
|
}
|
|
}
|
|
trace( "TEB at %p\n", tebAddress);
|
|
}
|
|
else
|
|
{
|
|
trace( "TEB NULL...\n" );
|
|
teb = 0;
|
|
}
|
|
|
|
if (tebAddress)
|
|
{
|
|
teb = IsPtr64() ? tebAddress : (ULONG64)(LONG64)(LONG)tebAddress;
|
|
}
|
|
else
|
|
{
|
|
teb = 0;
|
|
}
|
|
|
|
return teb;
|
|
}
|
|
|
|
// Function: HrReadPRtlBitmap
|
|
//
|
|
// Arguments: Address [in] Location of RTL BITMAP
|
|
// pRtlBitmap [out] RTL Bitmap. Free with LocalFree / not
|
|
HRESULT HrReadPRtlBitmap(IN ULONG64 pAddress, OUT PRTL_BITMAP *ppRtlBitmap)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
if (!pAddress || !ppRtlBitmap)
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
ULONG64 Address;
|
|
if (!ReadPointer(pAddress, &Address))
|
|
{
|
|
*ppRtlBitmap = NULL;
|
|
return E_FAIL;
|
|
}
|
|
|
|
DWORD dwPtrSize;
|
|
if (IsPtr64())
|
|
{
|
|
dwPtrSize = sizeof(DWORD64);
|
|
}
|
|
else
|
|
{
|
|
dwPtrSize = sizeof(DWORD);
|
|
}
|
|
|
|
ULONG SizeOfBitMap;
|
|
if (ReadMemory(Address, &SizeOfBitMap, sizeof(SizeOfBitMap), NULL))
|
|
{
|
|
*ppRtlBitmap = reinterpret_cast<PRTL_BITMAP>(LocalAlloc(LMEM_FIXED | LMEM_ZEROINIT, sizeof(RTL_BITMAP) + (SizeOfBitMap / 8) ));
|
|
if (*ppRtlBitmap)
|
|
{
|
|
// Create an internal pointer into itself
|
|
(*ppRtlBitmap)->Buffer = reinterpret_cast<PULONG>(reinterpret_cast<LPBYTE>(*ppRtlBitmap) + sizeof(RTL_BITMAP));
|
|
(*ppRtlBitmap)->SizeOfBitMap = SizeOfBitMap;
|
|
|
|
ULONG64 pBuffer = NULL;
|
|
if (ReadPointer(Address + dwPtrSize, &pBuffer))
|
|
{
|
|
if (!ReadMemory(pBuffer, (*ppRtlBitmap)->Buffer, SizeOfBitMap / 8, NULL))
|
|
{
|
|
hr = E_FAIL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
hr = E_FAIL;
|
|
}
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
LocalFree(*ppRtlBitmap);
|
|
*ppRtlBitmap = NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
#define return_msg(hr, msg) dprintf(msg); return hr;
|
|
|
|
//
|
|
// Function: DumpTls
|
|
//
|
|
// Arguments:
|
|
// ulSlot [in] Slot id || TLS_ALL for all.
|
|
// ulThread [in] Thread id || TLS_CURRENT for current || TLS_ALL for all
|
|
//
|
|
// Returns: S_OK is succeeded
|
|
//
|
|
HRESULT
|
|
DumpTls (
|
|
IN ULONG ulSlot,
|
|
IN ULONG64 ulThread,
|
|
IN LPCWSTR szThreadDescription
|
|
)
|
|
{
|
|
HRESULT hr;
|
|
|
|
trace("DUMPTLS: %p %p\n", ulSlot, ulThread);
|
|
|
|
if ( (TLS_ALL != ulSlot) && (ulSlot > 1088) )
|
|
{
|
|
return_msg (E_INVALIDARG, "Slot must be 0 to 1088 (or -1 to dump slot 0)\n");
|
|
}
|
|
|
|
ULONG64 Peb = GetPebForTarget();
|
|
if (!Peb)
|
|
{
|
|
return_msg (E_INVALIDARG, "Could not get Peb for target - check your symbols for nt!\n");
|
|
}
|
|
trace("Peb = %p\n", Peb);
|
|
|
|
ULONG64 Teb = GetTebForTarget(ulThread);
|
|
if (!Teb)
|
|
{
|
|
return_msg (E_INVALIDARG, "Could not get Teb for target - check your symbols for nt!\n");
|
|
}
|
|
trace("Teb = %p\n", Teb);
|
|
|
|
WCHAR szOwnDescription[MAX_PATH];
|
|
if ( (TLS_CURRENT == ulThread) && !szThreadDescription)
|
|
{
|
|
ULONG64 ethread = GetExpression("@$thread");
|
|
|
|
if (ethread)
|
|
{
|
|
if (ERROR_SUCCESS == InitTypeRead(ethread, nt!_ETHREAD))
|
|
{
|
|
ULONG64 Cid_UniqueProcess = GetExpression("@$tpid");
|
|
ULONG64 Cid_UniqueThread = GetExpression("@$tid");
|
|
|
|
if (Cid_UniqueProcess && Cid_UniqueThread)
|
|
{
|
|
trace("%04x %1p.%1p\n", ulSlot, Cid_UniqueProcess, Cid_UniqueThread);
|
|
swprintf(szOwnDescription, L"%I64x.%I64x", Cid_UniqueProcess, Cid_UniqueThread);
|
|
szThreadDescription = szOwnDescription;
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
if (TLS_ALL == ulSlot)
|
|
{
|
|
if (szThreadDescription)
|
|
{
|
|
dprintf("TLS slots on thread: %S\n", szThreadDescription);
|
|
}
|
|
else
|
|
{
|
|
dprintf("TLS slots on thread: %p\n", Teb);
|
|
}
|
|
}
|
|
|
|
DWORD dwPtrSize;
|
|
if (IsPtr64())
|
|
{
|
|
dwPtrSize = sizeof(DWORD64);
|
|
}
|
|
else
|
|
{
|
|
dwPtrSize = sizeof(DWORD);
|
|
}
|
|
|
|
hr = E_FAIL;
|
|
PRTL_BITMAP pTlsBitmap = NULL;
|
|
PRTL_BITMAP pTlsExpansionBitmap = NULL;
|
|
|
|
ULONG TlsBitmap_Offset;
|
|
GetFieldOffset("PEB", "TlsBitmap", &TlsBitmap_Offset);
|
|
if (TlsBitmap_Offset)
|
|
{
|
|
ULONG TlsExpansionBitmap_Offset;
|
|
GetFieldOffset("PEB", "TlsExpansionBitmap", &TlsExpansionBitmap_Offset);
|
|
if (TlsExpansionBitmap_Offset)
|
|
{
|
|
hr = HrReadPRtlBitmap(Peb + TlsBitmap_Offset, &pTlsBitmap);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = HrReadPRtlBitmap(Peb + TlsExpansionBitmap_Offset, &pTlsExpansionBitmap);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
trace("pTlsBitmap: %p\n", pTlsBitmap);
|
|
trace("pTlsExpansionBitmap: %p\n", pTlsExpansionBitmap);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (FAILED(hr))
|
|
{
|
|
LocalFree(pTlsBitmap);
|
|
LocalFree(pTlsExpansionBitmap);
|
|
return_msg (E_FAIL, "Could not get read TlsBitmap or TlsExpansionBitmap in peb - check your symbols for nt!\n");
|
|
}
|
|
|
|
hr = E_FAIL;
|
|
ULONG TlsSlots_Offset;
|
|
ULONG TlsExpansionSlots_Offset;
|
|
GetFieldOffset("TEB", "TlsSlots", &TlsSlots_Offset);
|
|
if (TlsSlots_Offset)
|
|
{
|
|
GetFieldOffset("TEB", "TlsExpansionSlots", &TlsExpansionSlots_Offset);
|
|
if (TlsExpansionSlots_Offset)
|
|
{
|
|
hr = S_OK;
|
|
}
|
|
}
|
|
if (FAILED(hr))
|
|
{
|
|
LocalFree(pTlsBitmap);
|
|
LocalFree(pTlsExpansionBitmap);
|
|
return_msg (E_FAIL, "Could not get read TlsSlots or TlsExpansionSlots in teb - check your symbols for nt!\n");
|
|
}
|
|
|
|
if (TLS_ALL == ulSlot)
|
|
{
|
|
trace("All slots\n");
|
|
|
|
LPBYTE arrTlsSlots = new BYTE[dwPtrSize * 1088];
|
|
if (arrTlsSlots)
|
|
{
|
|
hr = E_FAIL;
|
|
|
|
if (ReadMemory(Teb + TlsSlots_Offset, arrTlsSlots, 64 * dwPtrSize, NULL))
|
|
{
|
|
ULONG64 pTlsExpansionSlots;
|
|
if (ReadPointer(Teb + TlsExpansionSlots_Offset, &pTlsExpansionSlots))
|
|
{
|
|
hr = S_OK;
|
|
if (pTlsExpansionSlots)
|
|
{
|
|
if (!ReadMemory(pTlsExpansionSlots, arrTlsSlots + (64 * dwPtrSize), 1024, NULL))
|
|
{
|
|
hr = E_FAIL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
delete[] arrTlsSlots;
|
|
LocalFree(pTlsBitmap);
|
|
LocalFree(pTlsExpansionBitmap);
|
|
return_msg (E_FAIL, "Could not read content of Tls Slots from teb - check your symbols for nt!\n");
|
|
}
|
|
|
|
BOOL bFound = FALSE;
|
|
|
|
for (int x = 0; x < 1088; x++)
|
|
{
|
|
if (CheckControlC())
|
|
{
|
|
delete[] arrTlsSlots;
|
|
LocalFree(pTlsBitmap);
|
|
LocalFree(pTlsExpansionBitmap);
|
|
return FALSE;
|
|
}
|
|
|
|
BOOL bSet = FALSE;
|
|
|
|
if (x < TLS_MINIMUM_AVAILABLE)
|
|
{
|
|
if (!TestBit(pTlsBitmap, x))
|
|
{
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
bFound = TRUE;;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!TestBit(pTlsExpansionBitmap, x - TLS_MINIMUM_AVAILABLE))
|
|
{
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
bFound = TRUE;;
|
|
}
|
|
}
|
|
|
|
if ( sizeof(DWORD64) == dwPtrSize )
|
|
{
|
|
dprintf("0x%04x : %p\n", x, reinterpret_cast<DWORD64*>(arrTlsSlots)[x]);
|
|
}
|
|
else
|
|
{
|
|
dprintf("0x%04x : %p\n", x, reinterpret_cast<DWORD*>(arrTlsSlots)[x]);
|
|
}
|
|
}
|
|
|
|
if (!bFound)
|
|
{
|
|
dprintf(" No TLS slots have been allocated for this process.\n");
|
|
}
|
|
|
|
delete[] arrTlsSlots;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ULONG64 Tls_Location = 0;
|
|
if (ulSlot < TLS_MINIMUM_AVAILABLE)
|
|
{
|
|
Tls_Location = Teb + TlsSlots_Offset + ulSlot * dwPtrSize;
|
|
}
|
|
else
|
|
{
|
|
if (ReadPointer(Teb + TlsExpansionSlots_Offset, &Tls_Location))
|
|
{
|
|
Tls_Location += (ulSlot * dwPtrSize);
|
|
}
|
|
}
|
|
if (!Tls_Location)
|
|
{
|
|
LocalFree(pTlsBitmap);
|
|
LocalFree(pTlsExpansionBitmap);
|
|
return_msg (E_FAIL, "Could not read content TlsLocation from teb - check your symbols for nt!\n");
|
|
}
|
|
|
|
ULONG64 Tls_SlotX;
|
|
if (ReadPointer(Tls_Location, &Tls_SlotX))
|
|
{
|
|
if (szThreadDescription)
|
|
{
|
|
dprintf("%S: %p\n", szThreadDescription, Tls_SlotX);
|
|
}
|
|
else
|
|
{
|
|
dprintf("%I64x: %p\n", Teb, szThreadDescription, Tls_SlotX);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
dprintf("Could not read TLS value from %p - check your symbols for nt!\n", Tls_Location);
|
|
}
|
|
}
|
|
|
|
LocalFree(pTlsBitmap);
|
|
LocalFree(pTlsExpansionBitmap);
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
HRESULT DumpThreadsUserMode(ULONG ulSlot)
|
|
{
|
|
ULONG ulOldThread;
|
|
HRESULT hr = g_ExtSystem->GetCurrentThreadId(&ulOldThread);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
ULONG ulNumThreads;
|
|
hr = g_ExtSystem->GetNumberThreads(&ulNumThreads);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
trace("Threads (current %d): %d\n", ulOldThread, ulNumThreads);
|
|
|
|
PULONG pIds = new ULONG[ulNumThreads];
|
|
if (pIds)
|
|
{
|
|
PULONG pSysIds = new ULONG[ulNumThreads];
|
|
if (pSysIds)
|
|
{
|
|
hr = g_ExtSystem->GetThreadIdsByIndex(0, ulNumThreads, pIds, pSysIds);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
if (TLS_ALL != ulSlot)
|
|
{
|
|
dprintf("Per-thread values for slot 0x%03x:\n", static_cast<DWORD>(ulSlot));
|
|
}
|
|
|
|
for (ULONG x = 0; x < ulNumThreads; x++)
|
|
{
|
|
hr = g_ExtSystem->SetCurrentThreadId(pIds[x]);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
ULONG64 Cid_UniqueProcess = GetExpression("@$tpid");
|
|
|
|
ULONG64 teb;
|
|
g_ExtSystem->GetCurrentThreadTeb(&teb);
|
|
|
|
WCHAR szThreadDescription[MAX_PATH];
|
|
swprintf(szThreadDescription, L"%I64x.%1x", Cid_UniqueProcess, pSysIds[x]);
|
|
|
|
trace("Thread: %d %d %x %x\n", x, pIds[x], pSysIds[x], teb);
|
|
|
|
DumpTls (ulSlot, teb, szThreadDescription);
|
|
}
|
|
}
|
|
}
|
|
delete[] pSysIds;
|
|
}
|
|
delete[] pIds;
|
|
}
|
|
}
|
|
|
|
g_ExtSystem->SetCurrentThreadId(ulOldThread);
|
|
}
|
|
return S_OK;
|
|
}
|
|
|
|
ULONG
|
|
ThreadListCallback (
|
|
PFIELD_INFO NextThrd,
|
|
PVOID Context
|
|
)
|
|
{
|
|
ULONG ulSlot = static_cast<ULONG>(reinterpret_cast<ULONG_PTR>(Context));
|
|
ULONG64 RealThreadBase = NextThrd->address;
|
|
if (!IsPtr64())
|
|
{
|
|
RealThreadBase = (ULONG64) (LONG64) (LONG) RealThreadBase;
|
|
}
|
|
|
|
trace("Reading %p\n", RealThreadBase);
|
|
|
|
if (InitTypeRead(RealThreadBase, nt!_ETHREAD))
|
|
{
|
|
dprintf("*** Error in in reading nt!_ETHREAD @ %p\n", RealThreadBase);
|
|
return TRUE;
|
|
}
|
|
|
|
ULONG64 Cid_UniqueProcess = ReadField(Cid.UniqueProcess);
|
|
ULONG64 Cid_UniqueThread = ReadField(Cid.UniqueThread);
|
|
ULONG64 Teb = ReadField(Tcb.Teb);
|
|
|
|
trace("%04x %1p.%1p %1p\n", ulSlot, Cid_UniqueProcess, Cid_UniqueThread, Teb);
|
|
|
|
WCHAR szThreadDescription[MAX_PATH];
|
|
swprintf(szThreadDescription, L"%I64x.%I64x", Cid_UniqueProcess, Cid_UniqueThread);
|
|
|
|
DumpTls (ulSlot, Teb, szThreadDescription);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
HRESULT DumpThreadsKernelMode(ULONG ulSlot)
|
|
{
|
|
trace("DumpThreadsKernelMode %p %p\n", ulSlot);
|
|
|
|
ULONG64 ThreadListHead_Flink = 0;
|
|
ULONG64 process = GetExpression("@$proc");
|
|
|
|
trace("Process is %p\n", process);
|
|
|
|
GetFieldValue(process, "nt!_EPROCESS", "Pcb.ThreadListHead.Flink", ThreadListHead_Flink);
|
|
trace("GetFieldValue returned %p\n", ThreadListHead_Flink);
|
|
|
|
ULONG64 Next;
|
|
if (!ReadPointer(ThreadListHead_Flink, &Next) ||
|
|
(Next == ThreadListHead_Flink))
|
|
{
|
|
trace("Empty\n");
|
|
return S_OK;
|
|
}
|
|
|
|
if (TLS_ALL != ulSlot)
|
|
{
|
|
dprintf("Per-thread values for slot 0x%03x:\n", static_cast<DWORD>(ulSlot));
|
|
}
|
|
|
|
ULONG ulList = ListType("nt!_ETHREAD", ThreadListHead_Flink, 1,
|
|
"Tcb.ThreadListEntry.Flink", reinterpret_cast<LPVOID>(static_cast<ULONG_PTR>(ulSlot)), &ThreadListCallback);
|
|
trace("ListType returned %x\n",ListType);
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
DECLARE_API( tls )
|
|
{
|
|
ULONG64 ulProcess = NULL;
|
|
ULONG64 ulThread = NULL;
|
|
ULONG64 ul64Slot = NULL;
|
|
ULONG ulSlot = NULL;
|
|
|
|
INIT_API();
|
|
|
|
BOOL bKernelMode = FALSE;
|
|
|
|
KDDEBUGGER_DATA64 kdd;
|
|
if (GetDebuggerData('GBDK', &kdd, sizeof(kdd)))
|
|
{
|
|
bKernelMode = TRUE;
|
|
}
|
|
|
|
// Skip past leading spaces
|
|
while (*args == ' ')
|
|
{
|
|
args++;
|
|
}
|
|
|
|
if (!GetExpressionEx(args, &ul64Slot, &args))
|
|
{
|
|
dprintf("Usage:\n"
|
|
"tls <slot> [teb]\n"
|
|
" slot: -1 to dump all allocated slots\n"
|
|
" {0-1088} to dump specific slot\n"
|
|
" teb: <empty> for current thread\n"
|
|
" 0 for all threads in this process\n"
|
|
" <teb address> (not threadid) to dump for specific thread.\n"
|
|
);
|
|
return S_OK;
|
|
}
|
|
|
|
ulSlot = static_cast<ULONG>(ul64Slot);
|
|
|
|
if (ulSlot == -1)
|
|
{
|
|
ulSlot = TLS_ALL;
|
|
}
|
|
|
|
if (!GetExpressionEx(args, &ulThread, &args))
|
|
{
|
|
ulThread = TLS_CURRENT;
|
|
}
|
|
|
|
if (0 == ulThread)
|
|
{
|
|
if (bKernelMode)
|
|
{
|
|
DumpThreadsKernelMode(ulSlot);
|
|
}
|
|
else
|
|
{
|
|
DumpThreadsUserMode(ulSlot);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DumpTls (ulSlot, ulThread, NULL);
|
|
}
|
|
|
|
EXIT_API();
|
|
return S_OK;
|
|
}
|