/*++ Copyright (c) Microsoft Corporation Module Name: sxsactctx.c Abstract: Side-by-side activation support for Windows/NT Implementation of the application context object. Author: Michael Grier (MGrier) 2/1/2000 Revision History: --*/ #if defined(__cplusplus) extern "C" { #endif #pragma warning(disable:4214) // bit field types other than int #pragma warning(disable:4201) // nameless struct/union #pragma warning(disable:4115) // named type definition in parentheses #pragma warning(disable:4127) // condition expression is constant #include #include #include #include #include "sxsp.h" #include "limits.h" #define IS_ALIGNED(_p, _n) ((((ULONG_PTR) (_p)) & ((_n) - 1)) == 0) #define IS_WORD_ALIGNED(_p) IS_ALIGNED((_p), 2) #define IS_DWORD_ALIGNED(_p) IS_ALIGNED((_p), 4) BOOLEAN g_SxsKeepActivationContextsAlive; BOOLEAN g_SxsTrackReleaseStacks; // These must be accessed -only- with the peb lock held LIST_ENTRY g_SxsLiveActivationContexts; LIST_ENTRY g_SxsFreeActivationContexts; ULONG g_SxsMaxDeadActivationContexts = ULONG_MAX; ULONG g_SxsCurrentDeadActivationContexts; #if DBG VOID RtlpSxsBreakOnInvalidMarker(PCACTIVATION_CONTEXT ActivationContext, ULONG FailureCode); static CHAR *SxsSteppedOnMarkerText = "%s : Invalid activation context marker %p found in activation context %p\n" " This means someone stepped on the allocation, or someone is using a\n" " deallocated activation context\n"; #define VALIDATE_ACTCTX(pA) do { \ const PACTIVATION_CONTEXT_WRAPPED pActual = CONTAINING_RECORD(pA, ACTIVATION_CONTEXT_WRAPPED, ActivationContext); \ if (pActual->MagicMarker != ACTCTX_MAGIC_MARKER) { \ DbgPrint(SxsSteppedOnMarkerText, __FUNCTION__, pActual->MagicMarker, pA); \ ASSERT(pActual->MagicMarker == ACTCTX_MAGIC_MARKER); \ RtlpSxsBreakOnInvalidMarker((pA), SXS_CORRUPTION_ACTCTX_MAGIC_NOT_MATCHED); \ } \ } while (0) #else #define VALIDATE_ACTCTX(pA) #endif VOID FASTCALL RtlpMoveActCtxToFreeList( PACTIVATION_CONTEXT ActCtx ); VOID FASTCALL RtlpPlaceActivationContextOnLiveList( PACTIVATION_CONTEXT ActCtx ); NTSTATUS RtlpValidateActivationContextData( IN ULONG Flags, IN PCACTIVATION_CONTEXT_DATA Data, IN SIZE_T BufferSize OPTIONAL ) { NTSTATUS Status = STATUS_SUCCESS; PCACTIVATION_CONTEXT_DATA_TOC_HEADER TocHeader; PCACTIVATION_CONTEXT_DATA_ASSEMBLY_ROSTER_HEADER AssemblyRosterHeader; if (Flags != 0) { Status = STATUS_INVALID_PARAMETER; goto Exit; } if ((Data->Magic != ACTIVATION_CONTEXT_DATA_MAGIC) || (Data->FormatVersion != ACTIVATION_CONTEXT_DATA_FORMAT_WHISTLER) || ((BufferSize != 0) && (BufferSize < Data->TotalSize))) { Status = STATUS_SXS_INVALID_ACTCTXDATA_FORMAT; goto Exit; } // Check required elements... if ((Data->DefaultTocOffset == 0) || !IS_DWORD_ALIGNED(Data->DefaultTocOffset)) { DbgPrintEx( DPFLTR_SXS_ID, DPFLTR_ERROR_LEVEL, "SXS: Warning: Activation context data at %p missing default TOC\n", Data); Status = STATUS_SXS_INVALID_ACTCTXDATA_FORMAT; goto Exit; } // How can we not have an assembly roster? if ((Data->AssemblyRosterOffset == 0) || !IS_DWORD_ALIGNED(Data->AssemblyRosterOffset)) { DbgPrintEx( DPFLTR_SXS_ID, DPFLTR_ERROR_LEVEL, "SXS: Warning: Activation context data at %p lacks assembly roster\n", Data); Status = STATUS_SXS_INVALID_ACTCTXDATA_FORMAT; goto Exit; } if (Data->DefaultTocOffset != 0) { if ((Data->DefaultTocOffset >= Data->TotalSize) || ((Data->DefaultTocOffset + sizeof(ACTIVATION_CONTEXT_DATA_TOC_HEADER)) > Data->TotalSize)) { DbgPrintEx( DPFLTR_SXS_ID, DPFLTR_ERROR_LEVEL, "SXS: Activation context data at %p has invalid TOC header offset\n", Data); Status = STATUS_SXS_INVALID_ACTCTXDATA_FORMAT; goto Exit; } TocHeader = (PCACTIVATION_CONTEXT_DATA_TOC_HEADER) (((ULONG_PTR) Data) + Data->DefaultTocOffset); if (TocHeader->HeaderSize < sizeof(ACTIVATION_CONTEXT_DATA_TOC_HEADER)) { DbgPrintEx( DPFLTR_SXS_ID, DPFLTR_ERROR_LEVEL, "SXS: Activation context data at %p has TOC header too small (%lu)\n", Data, TocHeader->HeaderSize); Status = STATUS_SXS_INVALID_ACTCTXDATA_FORMAT; goto Exit; } if ((TocHeader->FirstEntryOffset >= Data->TotalSize) || (!IS_DWORD_ALIGNED(TocHeader->FirstEntryOffset)) || ((TocHeader->FirstEntryOffset + (TocHeader->EntryCount * sizeof(ACTIVATION_CONTEXT_DATA_TOC_ENTRY))) > Data->TotalSize)) { DbgPrintEx( DPFLTR_SXS_ID, DPFLTR_ERROR_LEVEL, "SXS: Activation context data at %p has invalid TOC entry array offset\n", Data); Status = STATUS_SXS_INVALID_ACTCTXDATA_FORMAT; goto Exit; } } // we should finish validating the rest of the structure... if ((Data->AssemblyRosterOffset >= Data->TotalSize) || ((Data->AssemblyRosterOffset + sizeof(ACTIVATION_CONTEXT_DATA_ASSEMBLY_ROSTER_HEADER)) > Data->TotalSize)) { DbgPrintEx( DPFLTR_SXS_ID, DPFLTR_ERROR_LEVEL, "SXS: Activation context data at %p has invalid assembly roster offset\n", Data); Status = STATUS_SXS_INVALID_ACTCTXDATA_FORMAT; goto Exit; } AssemblyRosterHeader = (PCACTIVATION_CONTEXT_DATA_ASSEMBLY_ROSTER_HEADER) (((ULONG_PTR) Data) + Data->AssemblyRosterOffset); if (Data->AssemblyRosterOffset != 0) { if (AssemblyRosterHeader->HeaderSize < sizeof(ACTIVATION_CONTEXT_DATA_ASSEMBLY_ROSTER_HEADER)) { DbgPrintEx( DPFLTR_SXS_ID, DPFLTR_ERROR_LEVEL, "SXS: Activation context data at %p has assembly roster header too small (%lu)\n", Data, AssemblyRosterHeader->HeaderSize); Status = STATUS_SXS_INVALID_ACTCTXDATA_FORMAT; goto Exit; } } Status = STATUS_SUCCESS; Exit: return Status; } NTSTATUS NTAPI RtlCreateActivationContext( IN ULONG Flags, IN PCACTIVATION_CONTEXT_DATA ActivationContextData, IN ULONG ExtraBytes, IN PACTIVATION_CONTEXT_NOTIFY_ROUTINE NotificationRoutine, IN PVOID NotificationContext, OUT PACTIVATION_CONTEXT *ActCtx ) { PACTIVATION_CONTEXT NewActCtx = NULL; PACTIVATION_CONTEXT_WRAPPED AllocatedActCtx = NULL; NTSTATUS Status = STATUS_SUCCESS; ULONG i, j; PCACTIVATION_CONTEXT_DATA_ASSEMBLY_ROSTER_HEADER AssemblyRosterHeader; BOOLEAN UninitializeStorageMapOnExit = FALSE; DbgPrintEx( DPFLTR_SXS_ID, DPFLTR_TRACE_LEVEL, "SXS: RtlCreateActivationContext() called with parameters:\n" " Flags = 0x%08lx\n" " ActivationContextData = %p\n" " ExtraBytes = %lu\n" " NotificationRoutine = %p\n" " NotificationContext = %p\n" " ActCtx = %p\n", Flags, ActivationContextData, ExtraBytes, NotificationRoutine, NotificationContext, ActCtx); RTLP_DISALLOW_THE_EMPTY_ACTIVATION_CONTEXT_DATA(ActivationContextData); if (ActCtx != NULL) *ActCtx = NULL; if ((Flags != 0) || (ActivationContextData == NULL) || (ExtraBytes > 65536) || (ActCtx == NULL)) { Status = STATUS_INVALID_PARAMETER; goto Exit; } // Make sure that the activation context data passes muster Status = RtlpValidateActivationContextData(0, ActivationContextData, 0); if (!NT_SUCCESS(Status)) goto Exit; // Allocate enough space to hold the new activation context, plus space for the 'magic' // marker AllocatedActCtx = (PACTIVATION_CONTEXT_WRAPPED)RtlAllocateHeap( RtlProcessHeap(), 0, sizeof(ACTIVATION_CONTEXT_WRAPPED) + ExtraBytes); if (AllocatedActCtx == NULL) { Status = STATUS_NO_MEMORY; goto Exit; } // Get the new activation context object, then stamp in the magic signature NewActCtx = &AllocatedActCtx->ActivationContext; AllocatedActCtx->MagicMarker = ACTCTX_MAGIC_MARKER; AssemblyRosterHeader = (PCACTIVATION_CONTEXT_DATA_ASSEMBLY_ROSTER_HEADER) (((ULONG_PTR) ActivationContextData) + ActivationContextData->AssemblyRosterOffset); Status = RtlpInitializeAssemblyStorageMap( &NewActCtx->StorageMap, AssemblyRosterHeader->EntryCount, (AssemblyRosterHeader->EntryCount > NUMBER_OF(NewActCtx->InlineStorageMapEntries)) ? NULL : NewActCtx->InlineStorageMapEntries); if (!NT_SUCCESS(Status)) goto Exit; UninitializeStorageMapOnExit = TRUE; NewActCtx->RefCount = 1; NewActCtx->Flags = 0; NewActCtx->ActivationContextData = (PCACTIVATION_CONTEXT_DATA) ActivationContextData; NewActCtx->NotificationRoutine = NotificationRoutine; NewActCtx->NotificationContext = NotificationContext; for (i=0; iSentNotifications); i++) NewActCtx->SentNotifications[i] = 0; for (i=0; iDisabledNotifications); i++) NewActCtx->DisabledNotifications[i] = 0; for (i=0; iStackTraces[i][j] = NULL; NewActCtx->StackTraceIndex = 0; if (g_SxsKeepActivationContextsAlive) { RtlpPlaceActivationContextOnLiveList(NewActCtx); } *ActCtx = &AllocatedActCtx->ActivationContext; AllocatedActCtx = NULL; UninitializeStorageMapOnExit = FALSE; Status = STATUS_SUCCESS; Exit: if (AllocatedActCtx != NULL) { if (UninitializeStorageMapOnExit) { RtlpUninitializeAssemblyStorageMap(&AllocatedActCtx->ActivationContext.StorageMap); } RtlFreeHeap(RtlProcessHeap(), 0, AllocatedActCtx); } return Status; } VOID NTAPI RtlAddRefActivationContext( PACTIVATION_CONTEXT ActCtx ) { if ((ActCtx != NULL) && (!IS_SPECIAL_ACTCTX(ActCtx)) && (ActCtx->RefCount != LONG_MAX)) { LONG NewRefCount = LONG_MAX; VALIDATE_ACTCTX(ActCtx); for (;;) { LONG OldRefCount = ActCtx->RefCount; ASSERT(OldRefCount > 0); if (OldRefCount == LONG_MAX) { NewRefCount = LONG_MAX; break; } NewRefCount = OldRefCount + 1; if (InterlockedCompareExchange(&ActCtx->RefCount, NewRefCount, OldRefCount) == OldRefCount) break; } ASSERT(NewRefCount > 0); } } VOID NTAPI RtlpFreeActivationContext( PACTIVATION_CONTEXT ActCtx ) { VALIDATE_ACTCTX(ActCtx); ASSERT(ActCtx->RefCount == 0); BOOLEAN DisableNotification = FALSE; if (ActCtx->NotificationRoutine != NULL) { // There's no need to check for the notification being disabled; destroy // notifications are sent only once, so if the notification routine is not // null, we can just call it. (*(ActCtx->NotificationRoutine))( ACTIVATION_CONTEXT_NOTIFICATION_DESTROY, ActCtx, ActCtx->ActivationContextData, ActCtx->NotificationContext, NULL, &DisableNotification); } RtlpUninitializeAssemblyStorageMap(&ActCtx->StorageMap); // // This predates the MAXULONG refcount, maybe we can get rid of the the flag now? // if ((ActCtx->Flags & ACTIVATION_CONTEXT_NOT_HEAP_ALLOCATED) == 0) { RtlFreeHeap(RtlProcessHeap(), 0, CONTAINING_RECORD(ActCtx, ACTIVATION_CONTEXT_WRAPPED, ActivationContext)); } } VOID NTAPI RtlReleaseActivationContext( PACTIVATION_CONTEXT ActCtx ) { if ((ActCtx != NULL) && (!IS_SPECIAL_ACTCTX(ActCtx)) && (ActCtx->RefCount > 0) && (ActCtx->RefCount != LONG_MAX)) { LONG NewRefCount = LONG_MAX; ULONG StackTraceSlot = 0; VALIDATE_ACTCTX(ActCtx); // Complicated version of InterlockedDecrement that won't decrement LONG_MAX for (;;) { LONG OldRefCount = ActCtx->RefCount; ASSERT(OldRefCount != 0); if (OldRefCount == LONG_MAX) { NewRefCount = OldRefCount; break; } NewRefCount = OldRefCount - 1; if (InterlockedCompareExchange(&ActCtx->RefCount, NewRefCount, OldRefCount) == OldRefCount) break; } // This spiffiness will capture the last N releases of this activation context, in // a circular list fashion. Just look at ((ActCtx->StackTraceIndex - 1) % ACTCTX_RELEASE_STACK_SLOTS) // to find the most recent release call. This is especially handy for people who over-release. if (g_SxsTrackReleaseStacks) { StackTraceSlot = ((ULONG)InterlockedIncrement((LONG*)&ActCtx->StackTraceIndex)) % ACTCTX_RELEASE_STACK_SLOTS; RtlCaptureStackBackTrace(1, ACTCTX_RELEASE_STACK_DEPTH, ActCtx->StackTraces[StackTraceSlot], NULL); } if (NewRefCount == 0) { // If this flag is set, then we need to put "dead" activation // contexts into this special list. This should help us diagnose // actctx over-releasing better. Don't do this if we haven't // initialized the list head yet. if (g_SxsKeepActivationContextsAlive) { RtlpMoveActCtxToFreeList(ActCtx); } // Otherwise, just free it. else { RtlpFreeActivationContext(ActCtx); } } } } NTSTATUS NTAPI RtlZombifyActivationContext( PACTIVATION_CONTEXT ActCtx ) { NTSTATUS Status = STATUS_SUCCESS; if ((ActCtx == NULL) || IS_SPECIAL_ACTCTX(ActCtx)) { Status = STATUS_INVALID_PARAMETER; goto Exit; } VALIDATE_ACTCTX(ActCtx); if ((ActCtx->Flags & ACTIVATION_CONTEXT_ZOMBIFIED) == 0) { if (ActCtx->NotificationRoutine != NULL) { // Since disable is sent only once, there's no need to check for // disabled notifications. BOOLEAN DisableNotification = FALSE; (*(ActCtx->NotificationRoutine))( ACTIVATION_CONTEXT_NOTIFICATION_ZOMBIFY, ActCtx, ActCtx->ActivationContextData, ActCtx->NotificationContext, NULL, &DisableNotification); } ActCtx->Flags |= ACTIVATION_CONTEXT_ZOMBIFIED; } Status = STATUS_SUCCESS; Exit: return Status; } VOID FASTCALL RtlpEnsureLiveDeadListsInitialized( VOID ) { if (g_SxsLiveActivationContexts.Flink == NULL) { RtlAcquirePebLock(); __try { if (g_SxsLiveActivationContexts.Flink != NULL) __leave; InitializeListHead(&g_SxsLiveActivationContexts); InitializeListHead(&g_SxsFreeActivationContexts); } __finally { RtlReleasePebLock(); } } } // PRECONDITION: Called only when g_SxsKeepActivationContextsAlive is set, but not dangerous // to call at other times, just nonperformant VOID FASTCALL RtlpMoveActCtxToFreeList( PACTIVATION_CONTEXT ActCtx ) { RtlpEnsureLiveDeadListsInitialized(); RtlAcquirePebLock(); __try { // Remove this entry from whatever list it's on. This works fine for entries that were // never on any list as well. RemoveEntryList(&ActCtx->Links); // If we are about to overflow the "max dead count" and there's items on the // dead list to clear out, start clearing out entries until we're underwater // again. while ((g_SxsCurrentDeadActivationContexts != 0) && (g_SxsCurrentDeadActivationContexts >= g_SxsMaxDeadActivationContexts)) { LIST_ENTRY *ple2 = RemoveHeadList(&g_SxsFreeActivationContexts); PACTIVATION_CONTEXT ActToFree = CONTAINING_RECORD(ple2, ACTIVATION_CONTEXT, Links); // If this assert fires, then "something bad" happened while walking the list ASSERT(ple2 != &g_SxsFreeActivationContexts); RtlpFreeActivationContext(ActToFree); g_SxsCurrentDeadActivationContexts--; } // Now, if the max dead count is greater than zero, add this to the dead list if (g_SxsMaxDeadActivationContexts > 0) { InsertTailList(&g_SxsFreeActivationContexts, &ActCtx->Links); g_SxsCurrentDeadActivationContexts++; } // Otherwise, just free it else { RtlpFreeActivationContext(ActCtx); } } __finally { RtlReleasePebLock(); } } // PRECONDITION: g_SxsKeepActivationContextsAlive set. Not dangerous to call when not set, // just underperformant. VOID FASTCALL RtlpPlaceActivationContextOnLiveList( PACTIVATION_CONTEXT ActCtx ) { // Ensure these are initialized. RtlpEnsureLiveDeadListsInitialized(); RtlAcquirePebLock(); __try { InsertHeadList(&g_SxsLiveActivationContexts, &ActCtx->Links); } __finally { RtlReleasePebLock(); } } VOID FASTCALL RtlpFreeCachedActivationContexts( VOID ) { LIST_ENTRY *ple = NULL; // Don't bother if these were never initialized if ((g_SxsLiveActivationContexts.Flink == NULL) || (g_SxsFreeActivationContexts.Flink == NULL)) return; RtlAcquirePebLock(); __try { ple = g_SxsLiveActivationContexts.Flink; while (ple != &g_SxsLiveActivationContexts) { PACTIVATION_CONTEXT ActCtx = CONTAINING_RECORD(ple, ACTIVATION_CONTEXT, Links); ple = ActCtx->Links.Flink; RemoveEntryList(&ActCtx->Links); RtlpFreeActivationContext(ActCtx); } ple = g_SxsFreeActivationContexts.Flink; while (ple != &g_SxsFreeActivationContexts) { PACTIVATION_CONTEXT ActCtx = CONTAINING_RECORD(ple, ACTIVATION_CONTEXT, Links); ple = ActCtx->Links.Flink; RemoveEntryList(&ActCtx->Links); RtlpFreeActivationContext(ActCtx); } } __finally { RtlReleasePebLock(); } } #if DBG VOID RtlpSxsBreakOnInvalidMarker( PCACTIVATION_CONTEXT ActivationContext, ULONG FailureCode ) { EXCEPTION_RECORD Exr; Exr.ExceptionRecord = NULL; Exr.ExceptionCode = STATUS_SXS_CORRUPTION; Exr.ExceptionFlags = EXCEPTION_NONCONTINUABLE; Exr.NumberParameters = 3; Exr.ExceptionInformation[0] = SXS_CORRUPTION_ACTCTX_MAGIC; Exr.ExceptionInformation[1] = FailureCode; Exr.ExceptionInformation[2] = (ULONG_PTR)ActivationContext; RtlRaiseException(&Exr); } #endif #if defined(__cplusplus) } /* extern "C" */ #endif