Windows-Server-2003/base/ntdll/sxsactctx.c

647 lines
19 KiB
C
Raw Permalink Normal View History

2024-08-04 01:28:15 +02:00
/*++
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 <ntos.h>
#include <ntrtl.h>
#include <nturtl.h>
#include <sxstypes.h>
#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; i<NUMBER_OF(NewActCtx->SentNotifications); i++)
NewActCtx->SentNotifications[i] = 0;
for (i=0; i<NUMBER_OF(NewActCtx->DisabledNotifications); i++)
NewActCtx->DisabledNotifications[i] = 0;
for (i=0; i<ACTCTX_RELEASE_STACK_SLOTS; i++)
for (j=0; j<ACTCTX_RELEASE_STACK_DEPTH; j++)
NewActCtx->StackTraces[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