567 lines
13 KiB
C
567 lines
13 KiB
C
|
|
|||
|
/*++
|
|||
|
|
|||
|
|
|||
|
|
|||
|
Copyright (c) 1991 Microsoft Corporation
|
|||
|
|
|||
|
Module Name:
|
|||
|
|
|||
|
RegLeak.h
|
|||
|
|
|||
|
Abstract:
|
|||
|
|
|||
|
This module contains helper functions for tracking
|
|||
|
n win32 registry leaks
|
|||
|
|
|||
|
Author:
|
|||
|
|
|||
|
Adam Edwards (adamed) 06-May-1998
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
|
|||
|
#ifdef LOCAL
|
|||
|
#ifdef LEAK_TRACK
|
|||
|
|
|||
|
#include "ntverp.h"
|
|||
|
#include <rpc.h>
|
|||
|
#include "regrpc.h"
|
|||
|
#include "localreg.h"
|
|||
|
#include "regclass.h"
|
|||
|
#include "stkwalk.h"
|
|||
|
#include "regleak.h"
|
|||
|
#include <malloc.h>
|
|||
|
|
|||
|
RegLeakTable gLeakTable;
|
|||
|
RegLeakTraceInfo g_RegLeakTraceInfo;
|
|||
|
|
|||
|
|
|||
|
void TrackObjectDataPrint(TrackObjectData* pKeyData)
|
|||
|
{
|
|||
|
NTSTATUS Status;
|
|||
|
SKeySemantics keyinfo;
|
|||
|
UNICODE_STRING EmptyString = {0, 0, 0};
|
|||
|
BYTE rgNameBuf[REG_MAX_CLASSKEY_LEN + REG_CHAR_SIZE + sizeof(KEY_NAME_INFORMATION)];
|
|||
|
|
|||
|
DbgPrint("WINREG: Tracked key data for object 0x%x\n", pKeyData->hKey);
|
|||
|
|
|||
|
//
|
|||
|
// Set buffer to store info about this key
|
|||
|
//
|
|||
|
keyinfo._pFullPath = (PKEY_NAME_INFORMATION) rgNameBuf;
|
|||
|
keyinfo._cbFullPath = sizeof(rgNameBuf);
|
|||
|
|
|||
|
//
|
|||
|
// get information about this key
|
|||
|
//
|
|||
|
Status = BaseRegGetKeySemantics(pKeyData->hKey, &EmptyString, &keyinfo);
|
|||
|
|
|||
|
if (!NT_SUCCESS(Status)) {
|
|||
|
DbgPrint("WINREG: Unable to retrieve object name error 0x%x\n", Status);
|
|||
|
} else {
|
|||
|
DbgPrint("WINREG: Name: %S\n", keyinfo._pFullPath->Name);
|
|||
|
}
|
|||
|
|
|||
|
BaseRegReleaseKeySemantics(&keyinfo);
|
|||
|
|
|||
|
DbgPrint("Frames %d", pKeyData->dwStackDepth);
|
|||
|
|
|||
|
{
|
|||
|
DWORD iFrame;
|
|||
|
|
|||
|
for (iFrame = 0; iFrame < pKeyData->dwStackDepth; iFrame++)
|
|||
|
{
|
|||
|
DbgPrint("WINREG: Frame %d = 0x%x\n", iFrame, pKeyData->rgStack[iFrame]);
|
|||
|
}
|
|||
|
}
|
|||
|
DbgPrint("\n");
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
NTSTATUS TrackObjectDataInit(TrackObjectData* pKeyData, PVOID* rgStack, DWORD dwMaxStackDepth, HKEY hKey)
|
|||
|
{
|
|||
|
RtlZeroMemory(pKeyData, sizeof(*pKeyData));
|
|||
|
|
|||
|
pKeyData->hKey = REG_CLASS_RESET_SPECIAL_KEY(hKey);
|
|||
|
|
|||
|
pKeyData->dwStackDepth = dwMaxStackDepth;
|
|||
|
pKeyData->rgStack = rgStack;
|
|||
|
|
|||
|
return STATUS_SUCCESS;
|
|||
|
}
|
|||
|
|
|||
|
NTSTATUS TrackObjectDataClear(TrackObjectData* pKeyData)
|
|||
|
{
|
|||
|
if (pKeyData->rgStack) {
|
|||
|
RtlFreeHeap(RtlProcessHeap(), 0, pKeyData->rgStack);
|
|||
|
pKeyData->rgStack = NULL;
|
|||
|
}
|
|||
|
|
|||
|
return STATUS_SUCCESS;
|
|||
|
}
|
|||
|
|
|||
|
NTSTATUS RegLeakTableInit(RegLeakTable* pLeakTable, DWORD dwFlags)
|
|||
|
{
|
|||
|
NTSTATUS Status;
|
|||
|
|
|||
|
RtlZeroMemory(pLeakTable, sizeof(*pLeakTable));
|
|||
|
|
|||
|
pLeakTable->dwFlags = dwFlags;
|
|||
|
|
|||
|
Status = RtlInitializeCriticalSection(
|
|||
|
&(pLeakTable->CriticalSection));
|
|||
|
|
|||
|
if (!NT_SUCCESS(Status)) {
|
|||
|
return Status;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Remember that we have initialized this critical section
|
|||
|
// so we can remember to delete it.
|
|||
|
//
|
|||
|
|
|||
|
pLeakTable->bCriticalSectionInitialized = TRUE;
|
|||
|
|
|||
|
Status = RtlInitializeCriticalSection(
|
|||
|
&(g_RegLeakTraceInfo.StackInitCriticalSection));
|
|||
|
|
|||
|
if (!NT_SUCCESS(Status)) {
|
|||
|
return Status;
|
|||
|
}
|
|||
|
|
|||
|
return STATUS_SUCCESS;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
NTSTATUS RegLeakTableClear(RegLeakTable* pLeakTable)
|
|||
|
{
|
|||
|
NTSTATUS Status;
|
|||
|
|
|||
|
#if defined(DBG) // LEAK_TRACK
|
|||
|
DbgPrint("WINREG: Leak data for process id 0x%x\n", NtCurrentTeb()->ClientId.UniqueProcess);
|
|||
|
DbgPrint("WINREG: Keys Leaked 0x%x\n", pLeakTable->cKeys);
|
|||
|
#endif // LEAK_TRACK
|
|||
|
|
|||
|
Status = RtlDeleteCriticalSection(
|
|||
|
&(pLeakTable->CriticalSection));
|
|||
|
|
|||
|
ASSERT(NT_SUCCESS(Status));
|
|||
|
|
|||
|
Status = RtlDeleteCriticalSection(
|
|||
|
&(g_RegLeakTraceInfo.StackInitCriticalSection));
|
|||
|
|
|||
|
|
|||
|
ASSERT(NT_SUCCESS(Status));
|
|||
|
#if DBG
|
|||
|
if ( !NT_SUCCESS( Status ) ) {
|
|||
|
DbgPrint( "WINREG: RtlDeleteCriticalSection() in EnumTableClear() failed. Status = %lx \n", Status );
|
|||
|
}
|
|||
|
#endif
|
|||
|
|
|||
|
{
|
|||
|
DWORD cKeys;
|
|||
|
|
|||
|
cKeys = 0;
|
|||
|
|
|||
|
for (;;)
|
|||
|
{
|
|||
|
if (!(pLeakTable->pHead)) {
|
|||
|
break;
|
|||
|
}
|
|||
|
TrackObjectDataPrint(pLeakTable->pHead);
|
|||
|
|
|||
|
cKeys++;
|
|||
|
|
|||
|
(void) RegLeakTableRemoveKey(pLeakTable, pLeakTable->pHead->hKey);
|
|||
|
}
|
|||
|
|
|||
|
#if defined(DBG) // LEAK_TRACK
|
|||
|
DbgPrint("WINREG: 0x%x total keys leaked\n", cKeys);
|
|||
|
#endif // LEAK_TRACK
|
|||
|
}
|
|||
|
|
|||
|
return STATUS_SUCCESS;
|
|||
|
}
|
|||
|
|
|||
|
NTSTATUS RegLeakTableAddKey(RegLeakTable* pLeakTable, HKEY hKey)
|
|||
|
{
|
|||
|
NTSTATUS Status;
|
|||
|
TrackObjectData* pNewData;
|
|||
|
PVOID* rgStack;
|
|||
|
DWORD dwMaxStackDepth;
|
|||
|
|
|||
|
rgStack = NULL;
|
|||
|
dwMaxStackDepth = 0;
|
|||
|
|
|||
|
hKey = REG_CLASS_RESET_SPECIAL_KEY(hKey);
|
|||
|
|
|||
|
if (!RegLeakTableIsTrackedObject(pLeakTable, hKey)) {
|
|||
|
return STATUS_SUCCESS;
|
|||
|
}
|
|||
|
|
|||
|
(void) GetLeakStack(
|
|||
|
&rgStack,
|
|||
|
&dwMaxStackDepth,
|
|||
|
g_RegLeakTraceInfo.dwMaxStackDepth);
|
|||
|
|
|||
|
Status = RtlEnterCriticalSection(&(pLeakTable->CriticalSection));
|
|||
|
|
|||
|
ASSERT( NT_SUCCESS( Status ) );
|
|||
|
if ( !NT_SUCCESS( Status ) ) {
|
|||
|
#if DBG
|
|||
|
DbgPrint( "WINREG: RtlEnterCriticalSection() in EnumTableRemoveKey() failed. Status = %lx \n", Status );
|
|||
|
#endif
|
|||
|
return Status;
|
|||
|
}
|
|||
|
|
|||
|
pNewData = RtlAllocateHeap(RtlProcessHeap(), 0, sizeof(*pNewData));
|
|||
|
|
|||
|
if (!pNewData) {
|
|||
|
Status = STATUS_NO_MEMORY;
|
|||
|
goto cleanup;
|
|||
|
}
|
|||
|
|
|||
|
Status = TrackObjectDataInit(pNewData, rgStack, dwMaxStackDepth, hKey);
|
|||
|
|
|||
|
if (!NT_SUCCESS(Status)) {
|
|||
|
goto cleanup;
|
|||
|
}
|
|||
|
|
|||
|
if (!RegLeakTableIsEmpty(pLeakTable)) {
|
|||
|
|
|||
|
pNewData->Links.Flink = (PLIST_ENTRY) pLeakTable->pHead;
|
|||
|
pLeakTable->pHead->Links.Blink = (PLIST_ENTRY) pNewData;
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
pLeakTable->pHead = pNewData;
|
|||
|
|
|||
|
pLeakTable->cKeys++;
|
|||
|
|
|||
|
cleanup:
|
|||
|
{
|
|||
|
NTSTATUS Status;
|
|||
|
|
|||
|
Status = RtlLeaveCriticalSection(&(pLeakTable->CriticalSection));
|
|||
|
|
|||
|
ASSERT( NT_SUCCESS( Status ) );
|
|||
|
#if DBG
|
|||
|
if ( !NT_SUCCESS( Status ) ) {
|
|||
|
DbgPrint( "WINREG: RtlLeaveCriticalSection() in EnumTableClear() failed. Status = %lx \n", Status );
|
|||
|
}
|
|||
|
#endif
|
|||
|
}
|
|||
|
|
|||
|
return Status;
|
|||
|
}
|
|||
|
|
|||
|
NTSTATUS RegLeakTableRemoveKey(RegLeakTable* pLeakTable, HKEY hKey)
|
|||
|
{
|
|||
|
NTSTATUS Status;
|
|||
|
TrackObjectData* pData;
|
|||
|
|
|||
|
Status = RtlEnterCriticalSection(&(pLeakTable->CriticalSection));
|
|||
|
|
|||
|
ASSERT( NT_SUCCESS( Status ) );
|
|||
|
if ( !NT_SUCCESS( Status ) ) {
|
|||
|
#if DBG
|
|||
|
DbgPrint( "WINREG: RtlEnterCriticalSection() in EnumTableRemoveKey() failed. Status = %lx \n", Status );
|
|||
|
#endif
|
|||
|
return Status;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
hKey = REG_CLASS_RESET_SPECIAL_KEY(hKey);
|
|||
|
|
|||
|
for (pData = pLeakTable->pHead;
|
|||
|
pData != NULL;
|
|||
|
pData = (TrackObjectData*) pData->Links.Flink)
|
|||
|
{
|
|||
|
if (hKey == pData->hKey) {
|
|||
|
|
|||
|
PLIST_ENTRY pFlink;
|
|||
|
PLIST_ENTRY pBlink;
|
|||
|
|
|||
|
pBlink = pData->Links.Blink;
|
|||
|
pFlink = pData->Links.Flink;
|
|||
|
|
|||
|
if (pBlink) {
|
|||
|
pBlink->Flink = pFlink;
|
|||
|
}
|
|||
|
|
|||
|
if (pFlink) {
|
|||
|
pFlink->Blink = pBlink;
|
|||
|
}
|
|||
|
|
|||
|
if (pData == pLeakTable->pHead) {
|
|||
|
pLeakTable->pHead = (TrackObjectData*) pFlink;
|
|||
|
}
|
|||
|
|
|||
|
(void) TrackObjectDataClear(pData);
|
|||
|
|
|||
|
RtlFreeHeap(RtlProcessHeap(), 0, pData);
|
|||
|
|
|||
|
pLeakTable->cKeys--;
|
|||
|
|
|||
|
goto cleanup;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
Status = STATUS_OBJECT_NAME_NOT_FOUND;
|
|||
|
|
|||
|
cleanup:
|
|||
|
|
|||
|
{
|
|||
|
NTSTATUS Status;
|
|||
|
|
|||
|
Status = RtlLeaveCriticalSection(&(pLeakTable->CriticalSection));
|
|||
|
|
|||
|
ASSERT( NT_SUCCESS( Status ) );
|
|||
|
#if DBG
|
|||
|
if ( !NT_SUCCESS( Status ) ) {
|
|||
|
DbgPrint( "WINREG: RtlLeaveCriticalSection() in EnumTableClear() failed. Status = %lx \n", Status );
|
|||
|
}
|
|||
|
#endif
|
|||
|
}
|
|||
|
|
|||
|
return Status;
|
|||
|
}
|
|||
|
|
|||
|
BOOL RegLeakTableIsEmpty(RegLeakTable* pLeakTable)
|
|||
|
{
|
|||
|
return pLeakTable->pHead == NULL;
|
|||
|
}
|
|||
|
|
|||
|
BOOL RegLeakTableIsTrackedObject(RegLeakTable* pLeakTable, HKEY hKey)
|
|||
|
{
|
|||
|
NTSTATUS Status;
|
|||
|
SKeySemantics keyinfo;
|
|||
|
UNICODE_STRING EmptyString = {0, 0, 0};
|
|||
|
BYTE rgNameBuf[REG_MAX_CLASSKEY_LEN + REG_CHAR_SIZE + sizeof(KEY_NAME_INFORMATION)];
|
|||
|
BOOL fTrackObject;
|
|||
|
|
|||
|
fTrackObject = FALSE;
|
|||
|
|
|||
|
if (LEAK_TRACK_FLAG_ALL == pLeakTable->dwFlags) {
|
|||
|
return TRUE;
|
|||
|
}
|
|||
|
|
|||
|
if (LEAK_TRACK_FLAG_NONE == pLeakTable->dwFlags) {
|
|||
|
return FALSE;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Set buffer to store info about this key
|
|||
|
//
|
|||
|
keyinfo._pFullPath = (PKEY_NAME_INFORMATION) rgNameBuf;
|
|||
|
keyinfo._cbFullPath = sizeof(rgNameBuf);
|
|||
|
|
|||
|
//
|
|||
|
// get information about this key
|
|||
|
//
|
|||
|
Status = BaseRegGetKeySemantics(hKey, &EmptyString, &keyinfo);
|
|||
|
|
|||
|
if (!NT_SUCCESS(Status)) {
|
|||
|
return Status;
|
|||
|
}
|
|||
|
|
|||
|
if (LEAK_TRACK_FLAG_USER & pLeakTable->dwFlags) {
|
|||
|
|
|||
|
WCHAR UserChar;
|
|||
|
|
|||
|
UserChar = keyinfo._pFullPath->Name[REG_CLASSES_FIRST_DISTINCT_ICH];
|
|||
|
|
|||
|
if ((L'U' == UserChar) || (L'u' == UserChar)) {
|
|||
|
fTrackObject = TRUE;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
BaseRegReleaseKeySemantics(&keyinfo);
|
|||
|
|
|||
|
return fTrackObject;
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
NTSTATUS TrackObject(HKEY hKey)
|
|||
|
{
|
|||
|
return RegLeakTableAddKey(&gLeakTable, hKey);
|
|||
|
}
|
|||
|
|
|||
|
#define WINLOGON_KEY L"\\Registry\\Machine\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon"
|
|||
|
#define LEAKTRACK_VALUE L"LeakTrack"
|
|||
|
#define DEFAULT_VALUE_SIZE 128
|
|||
|
|
|||
|
void ReadRegLeakTrackInfo()
|
|||
|
{
|
|||
|
LPTSTR lpWinlogonKey;
|
|||
|
LONG error;
|
|||
|
OBJECT_ATTRIBUTES Attributes;
|
|||
|
NTSTATUS Status;
|
|||
|
HKEY hKey;
|
|||
|
UNICODE_STRING uWinlogonPath;
|
|||
|
UNICODE_STRING uValueName;
|
|||
|
|
|||
|
KEY_VALUE_INFORMATION_CLASS KeyValueInformationClass;
|
|||
|
PVOID KeyValueInformation;
|
|||
|
|
|||
|
BYTE PrivateKeyValueInformation[ sizeof( KEY_VALUE_PARTIAL_INFORMATION) +
|
|||
|
DEFAULT_VALUE_SIZE ];
|
|||
|
ULONG BufferLength;
|
|||
|
ULONG ResultLength;
|
|||
|
|
|||
|
//
|
|||
|
// Look in the registry whether tracking is enabled uder
|
|||
|
// \Registry\Machine\Software\Microsoft\Windows NT\CurrentVersion\Winlogon
|
|||
|
//
|
|||
|
|
|||
|
memset(&g_RegLeakTraceInfo, 0, sizeof(g_RegLeakTraceInfo));
|
|||
|
|
|||
|
g_RegLeakTraceInfo.bEnableLeakTrack = 0;
|
|||
|
|
|||
|
RtlInitUnicodeString(&uWinlogonPath, WINLOGON_KEY);
|
|||
|
|
|||
|
InitializeObjectAttributes(&Attributes,
|
|||
|
&uWinlogonPath,
|
|||
|
OBJ_CASE_INSENSITIVE,
|
|||
|
NULL,
|
|||
|
NULL);
|
|||
|
|
|||
|
|
|||
|
Status = NtOpenKey( &hKey,
|
|||
|
KEY_READ,
|
|||
|
&Attributes );
|
|||
|
|
|||
|
|
|||
|
if (NT_SUCCESS(Status)) {
|
|||
|
|
|||
|
RtlInitUnicodeString(&uValueName, LEAKTRACK_VALUE);
|
|||
|
|
|||
|
KeyValueInformationClass = KeyValuePartialInformation;
|
|||
|
|
|||
|
KeyValueInformation = PrivateKeyValueInformation;
|
|||
|
BufferLength = sizeof( PrivateKeyValueInformation );
|
|||
|
|
|||
|
Status = NtQueryValueKey( hKey,
|
|||
|
&uValueName,
|
|||
|
KeyValueInformationClass,
|
|||
|
KeyValueInformation,
|
|||
|
BufferLength,
|
|||
|
&ResultLength );
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// if it succeeded and the datalength is greater than zero
|
|||
|
// check whether it is non-zero
|
|||
|
//
|
|||
|
|
|||
|
if ((NT_SUCCESS(Status)) &&
|
|||
|
(((PKEY_VALUE_PARTIAL_INFORMATION )KeyValueInformation )->DataLength)) {
|
|||
|
|
|||
|
if (((( PKEY_VALUE_PARTIAL_INFORMATION )KeyValueInformation)->Data) &&
|
|||
|
(*((( PKEY_VALUE_PARTIAL_INFORMATION )KeyValueInformation)->Data)))
|
|||
|
g_RegLeakTraceInfo.bEnableLeakTrack = 1;
|
|||
|
}
|
|||
|
|
|||
|
NtClose(hKey);
|
|||
|
|
|||
|
}
|
|||
|
// g_RegLeakTraceInfo.bEnableLeakTrack = GetProfileInt(TEXT("RegistryLeak"), TEXT("Enable"), 0);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
BOOL InitializeLeakTrackTable()
|
|||
|
{
|
|||
|
ReadRegLeakTrackInfo();
|
|||
|
|
|||
|
if (g_RegLeakTraceInfo.bEnableLeakTrack)
|
|||
|
return NT_SUCCESS(RegLeakTableInit(&gLeakTable, LEAK_TRACK_FLAG_USER));
|
|||
|
else
|
|||
|
return TRUE;
|
|||
|
}
|
|||
|
|
|||
|
BOOL CleanupLeakTrackTable()
|
|||
|
{
|
|||
|
BOOL fSuccess;
|
|||
|
|
|||
|
if (!g_RegLeakTraceInfo.bEnableLeakTrack)
|
|||
|
return TRUE;
|
|||
|
|
|||
|
//
|
|||
|
// if leak_tracking is not enabled, quit quickly.
|
|||
|
//
|
|||
|
|
|||
|
fSuccess = NT_SUCCESS(RegLeakTableClear(&gLeakTable));
|
|||
|
|
|||
|
(void) StopDebug();
|
|||
|
|
|||
|
return fSuccess;
|
|||
|
}
|
|||
|
|
|||
|
NTSTATUS UnTrackObject(HKEY hKey)
|
|||
|
{
|
|||
|
return RegLeakTableRemoveKey(&gLeakTable, hKey);
|
|||
|
}
|
|||
|
|
|||
|
NTSTATUS GetLeakStack(PVOID** prgStack, DWORD* pdwMaxDepth, DWORD dwMaxDepth)
|
|||
|
{
|
|||
|
|
|||
|
PCALLER_SYM pStack;
|
|||
|
DWORD dwDepth;
|
|||
|
|
|||
|
pStack = (PCALLER_SYM) RtlAllocateHeap(
|
|||
|
RtlProcessHeap(),
|
|||
|
0,
|
|||
|
dwMaxDepth * sizeof(*pStack));
|
|||
|
|
|||
|
if (!pStack) {
|
|||
|
return STATUS_NO_MEMORY;
|
|||
|
}
|
|||
|
|
|||
|
RtlZeroMemory(pStack, sizeof(*pStack) * dwMaxDepth);
|
|||
|
|
|||
|
*prgStack = RtlAllocateHeap(
|
|||
|
RtlProcessHeap(),
|
|||
|
0,
|
|||
|
dwMaxDepth * sizeof(*(*prgStack)));
|
|||
|
|
|||
|
if (!*prgStack) {
|
|||
|
RtlFreeHeap(RtlProcessHeap(),
|
|||
|
0,
|
|||
|
pStack);
|
|||
|
|
|||
|
return STATUS_NO_MEMORY;
|
|||
|
}
|
|||
|
|
|||
|
RtlZeroMemory(*prgStack, sizeof(*(*prgStack)) * dwMaxDepth);
|
|||
|
|
|||
|
GetCallStack(
|
|||
|
pStack,
|
|||
|
4,
|
|||
|
dwMaxDepth,
|
|||
|
FALSE);
|
|||
|
|
|||
|
for (dwDepth = 0; dwDepth < dwMaxDepth; dwDepth++)
|
|||
|
{
|
|||
|
if (!(pStack[dwDepth].Addr)) {
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
(*prgStack)[dwDepth] = pStack[dwDepth].Addr;
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
*pdwMaxDepth = dwDepth;
|
|||
|
|
|||
|
RtlFreeHeap(
|
|||
|
RtlProcessHeap(),
|
|||
|
0,
|
|||
|
pStack);
|
|||
|
|
|||
|
return STATUS_SUCCESS;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
#endif // DBG
|
|||
|
#endif // LOCAL
|