/******************************Module*Header*******************************\ * Module Name: cleanup.cxx * * Process termination - this file cleans up objects when a process * terminates. * * Created: 22-Jul-1991 12:24:52 * Author: Eric Kutter [erick] * * Copyright (c) 1990-1999 Microsoft Corporation \**************************************************************************/ #include "precomp.hxx" #include "muclean.hxx" extern BOOL bDeleteBrush(HBRUSH,BOOL); // brushobj.cxx extern VOID vCleanupPrivateFonts(); // pftobj.cxx extern HCOLORSPACE ghStockColorSpace; // icmapi.cxx #if DBG ULONG DbgPrint( PCH Format, ... ); #define DBGPRINT(x) DbgPrint(x) #else #define DBGPRINT(x) #endif ULONG gInitialBatchCount = 0x14; /******************************Public*Routine******************************\ * * History: * 24-Jul-1991 -by- Eric Kutter [erick] * Wrote it. \**************************************************************************/ VOID vCleanupDCs(W32PID pid) { HOBJ hobj = HmgNextOwned((HOBJ) 0, pid); for (;(hobj != (HOBJ) NULL);hobj = HmgNextOwned(hobj, pid)) { if (HmgObjtype(hobj) == DC_TYPE) { HmgSetLock(hobj, 0); bDeleteDCInternal((HDC)hobj,TRUE,TRUE); } } } /******************************Public*Routine******************************\ * * History: * Sat 20-Jun-1992 -by- Patrick Haluptzok [patrickh] * Wrote it. \**************************************************************************/ VOID vCleanupBrushes(W32PID pid) { HOBJ hobj = HmgNextOwned((HOBJ) 0, pid); for (;(hobj != (HOBJ) NULL);hobj = HmgNextOwned(hobj, pid)) { if (HmgObjtype(hobj) == BRUSH_TYPE) { bDeleteBrush((HBRUSH)hobj,TRUE); } } } /******************************Public*Routine******************************\ * * History: * Sat 20-Jun-1992 -by- Patrick Haluptzok [patrickh] * Wrote it. \**************************************************************************/ VOID vCleanupSurfaces(W32PID pid, CLEANUPTYPE cutype) { HOBJ hobj = HmgNextOwned((HOBJ) 0, pid); for (;(hobj != (HOBJ) NULL);hobj = HmgNextOwned(hobj, pid)) { if (HmgObjtype(hobj) == SURF_TYPE) { SURFREF so((HSURF)hobj); // // Skip PDEV surfaces; they are cleaned up with the PDEV // (see PDEVOBJ::vUnreferencePdev and PDEVOBJ::vDisableSurface). // // Also cleanup if this is a UMPD surface. This is to handle the // case when a DrvDisableSurface call to usermode printer driver // is nuked due to a terminate APC and we start process GDI cleanup. // If we did not have the || so.ps->bUMPD() we will leak the handle. if (!so.ps->bPDEVSurface() || so.ps->bUMPD()) so.bDeleteSurface(cutype); } } } /******************************Public*Routine******************************\ * * History: * 24-Jul-1991 -by- Eric Kutter [erick] * Wrote it. \**************************************************************************/ VOID vCleanupFonts(W32PID pid) { HOBJ hobj = HmgNextOwned((HOBJ) 0, pid); for (;(hobj != (HOBJ) NULL);hobj = HmgNextOwned(hobj, pid)) { if (HmgObjtype(hobj) == LFONT_TYPE) { bDeleteFont((HLFONT) hobj, FALSE); } } } /******************************Public*Routine******************************\ * vCleanupLCSPs * * History: * * 9/20/1996 Mark Enstrom [marke] * \**************************************************************************/ VOID vCleanupLCSPs(W32PID pid) { HOBJ hobj = HmgNextOwned((HOBJ) 0, pid); for (;(hobj != (HOBJ) NULL);hobj = HmgNextOwned(hobj, pid)) { if (HmgObjtype(hobj) == ICMLCS_TYPE) { bDeleteColorSpace((HCOLORSPACE)hobj); } } } /******************************Public*Routine******************************\ * * Eliminate user pregion to make sure delete succceds * \**************************************************************************/ VOID vCleanupRegions(W32PID pid) { HOBJ hobj = HmgNextOwned((HOBJ) 0, pid); for (;(hobj != (HOBJ) NULL);hobj = HmgNextOwned(hobj, pid)) { if (HmgObjtype(hobj) == RGN_TYPE) { RGNOBJ ro; ro.prgn = (PREGION)HmgLock(hobj,RGN_TYPE); if (ro.prgn) { PENTRY pent = PENTRY_FROM_POBJ(ro.prgn); if (pent) { pent->pUser = NULL; } DEC_EXCLUSIVE_REF_CNT(ro.prgn); } else { WARNING("vCleanupRegions: locked region has bad pEntry"); } bDeleteRegion((HRGN)hobj); } } } /******************************Public*Routine******************************\ * * History: * 01-Feb-2001 -by- Xudong Wu [tessiew] * Wrote it. \**************************************************************************/ VOID vRemoveRefPalettes(W32PID pid) { HOBJ hobj = HmgNextOwned((HOBJ) 0, pid); for (;(hobj != (HOBJ) NULL);hobj = HmgNextOwned(hobj, pid)) { if (HmgObjtype(hobj) == PAL_TYPE) { SEMOBJ semo(ghsemPalette); EPALOBJ palobj((HPALETTE)hobj); palobj.apalResetColorTable(); } } } /******************************Public*Routine******************************\ * * History: * 07-Aug-1992 -by- Gilman Wong [gilmanw] * Wrote it. \**************************************************************************/ BOOL bEnumFontClose(ULONG_PTR ulEnum) { EFSOBJ efso((HEFS) ulEnum); if (!efso.bValid()) { WARNING("gdisrv!bDeleteFontEnumState(): bad HEFS handle\n"); return FALSE; } efso.vDeleteEFSOBJ(); return TRUE; } /******************************Public*Routine******************************\ * NtGdiInit() * * This routine must be called before any other GDI routines. Currently * it doesn't actualy do anything, just forces a kernel mode transition * which will cause GdiProcessCallout to get called. * * History: * 07-Sep-1995 -by- Eric Kutter [erick] * Wrote it. \**************************************************************************/ BOOL NtGdiInit() { return(TRUE); } /******************************Public*Routine******************************\ * NtGdiCloseProcess * * Release the resources held by the specified process. * * The cutype is set to CLEANUP_SESSION only for MultiUserGreCleanup * (Hydra) processing. It is used to do extra work to allow cleanup * of cross-process data not normally cleaned up on process termination, * just GRE termination (e.g., global and default data such as the * default bitmap and the public font tables). * * History: * 03-Feb-1998 -by- Gilman Wong [gilmanw] * Wrote it. \**************************************************************************/ BOOL FASTCALL NtGdiCloseProcess(W32PID W32Pid, CLEANUPTYPE cutype) { BOOL bRes = TRUE; ASSERTGDI(cutype != CLEANUP_NONE, "NtGdiCloseProcess: illegal cutype\n"); // // Enum all the objects for the process and kill them. // // For MultiUserGreCleanup, MultiUserGreCleanupHmgRemoveAllLocks is // called for each object type to force all such objects in the // handle manager to be unlocked and deletable. // // // Cleanup DCs. // vCleanupDCs(W32Pid); // // Cleanup fonts. // if (cutype == CLEANUP_SESSION) MultiUserGreCleanupHmgRemoveAllLocks((OBJTYPE)LFONT_TYPE); else if (cutype == CLEANUP_PROCESS) MultiUserGreCleanupHmgOwnRemoveAllLocks((OBJTYPE)LFONT_TYPE); vCleanupFonts(W32Pid); // // Cleanup brushes. // if (cutype == CLEANUP_SESSION) MultiUserGreCleanupHmgRemoveAllLocks((OBJTYPE)BRUSH_TYPE); else if(cutype == CLEANUP_PROCESS) MultiUserGreCleanupHmgOwnRemoveAllLocks((OBJTYPE)BRUSH_TYPE); vCleanupBrushes(W32Pid); // // Clean up the ddraw & d3d types // DxDdCloseProcess(W32Pid); // // Cleanup surfaces. // if (cutype == CLEANUP_SESSION) { // // Need to forget about some global/default objects so they // can be deleted. // SURFACE::pdibDefault = NULL; ppalDefault = NULL; ppalMono = NULL; hpalMono = (HPALETTE) 0; MultiUserGreCleanupHmgRemoveAllLocks((OBJTYPE)SURF_TYPE); } else if (cutype == CLEANUP_PROCESS) MultiUserGreCleanupHmgOwnRemoveAllLocks((OBJTYPE)SURF_TYPE); vCleanupSurfaces(W32Pid, cutype); // // Cleanup regions. // if (cutype == CLEANUP_SESSION) { // // Need to forget about some global/default objects so they // can be deleted. // hrgnDefault = NULL; prgnDefault = NULL; MultiUserGreCleanupHmgRemoveAllLocks((OBJTYPE)RGN_TYPE); } else if (cutype == CLEANUP_PROCESS) MultiUserGreCleanupHmgOwnRemoveAllLocks((OBJTYPE)RGN_TYPE); vCleanupRegions(W32Pid); // // Cleanup ICM color spaces. // if (cutype == CLEANUP_SESSION) { // // Need to forget about some global/default objects so they // can be deleted. // ghStockColorSpace = NULL; MultiUserGreCleanupHmgRemoveAllLocks((OBJTYPE)ICMLCS_TYPE); } else if (cutype == CLEANUP_PROCESS) MultiUserGreCleanupHmgOwnRemoveAllLocks((OBJTYPE)ICMLCS_TYPE); vCleanupLCSPs(W32Pid); // // Cleanup private fonts. // if (cutype == CLEANUP_SESSION) { // // Relinquish locks on ALL remaining objects in handle manager. // MultiUserGreCleanupHmgRemoveAllLocks((OBJTYPE)DEF_TYPE); } else if (cutype == CLEANUP_PROCESS) { MultiUserGreCleanupHmgOwnRemoveAllLocks((OBJTYPE)DEF_TYPE); } if (cutype == CLEANUP_PROCESS) { // Private fonts should be cleaned up when a process goes away. vCleanupPrivateFonts(); } // // Remove the ppalColor reference in the palettes // vRemoveRefPalettes(W32Pid); // Clean up the rest // HOBJ hobj = HmgNextOwned((HOBJ) 0, W32Pid); for (;(hobj != (HOBJ) NULL);hobj = HmgNextOwned(hobj, W32Pid)) { switch (HmgObjtype(hobj)) { case PAL_TYPE: bRes = bDeletePalette((HPAL)hobj, TRUE, cutype); break; case EFSTATE_TYPE: bRes = bEnumFontClose((ULONG_PTR)hobj); break; case DRVOBJ_TYPE: { HmgSetLock(hobj, 0); // // Free the DRIVEROBJ. // DRIVEROBJ *pdriv = EngLockDriverObj((HDRVOBJ)hobj); PDEVOBJ po(pdriv->hdev); ASSERTGDI(po.bValid(), "ERROR invalid PDEV in DRIVEROBJ"); BOOL bRet = EngDeleteDriverObj((HDRVOBJ)hobj, TRUE, TRUE); ASSERTGDI(bRet, "Cleanup driver objects failed in process termination"); } break; case CLIENTOBJ_TYPE: GreDeleteClientObj(hobj); break; default: bRes = FALSE; break; } #if DBG if (bRes == FALSE) { // // During shutdown, fonts are handled later so that public // fonts and tables can be safely deleted (see function // MultiUserGreCleanupAllFonts). // if ((cutype != CLEANUP_SESSION) || ((HmgObjtype(hobj) != PFE_TYPE) && (HmgObjtype(hobj) != PFT_TYPE))) { DbgPrint("GDI ERROR: vCleanup couldn't delete " "obj = %lx, type j=%lx\n", hobj, HmgObjtype(hobj)); DbgBreakPoint(); } } #endif } return bRes; } /******************************Public*Routine******************************\ * * History: * 24-Jul-1991 -by- Eric Kutter [erick] * Wrote it. \**************************************************************************/ extern "C" NTSTATUS GdiProcessCallout( IN PW32PROCESS Process, IN BOOLEAN Initialize ) { BOOL bRes = TRUE; if (Initialize) { NTSTATUS ntStatus = STATUS_SUCCESS; PPEB Peb; RtlInitializeGenericTableAvl(&Process->GDIEngUserMemAllocTable, GDIEngUserMemAllocNodeCompare, GDIEngUserMemAllocNodeAlloc, GDIEngUserMemAllocNodeFree, 0); InitializeListHead(&Process->GDIDcAttrFreeList); InitializeListHead(&Process->GDIBrushAttrFreeList); // // check if the PEB is valid, if not then this is the SYSTEM process // and has a NULL PEB. This process has no user-mode access so it // is not neccessary to map in the shared handle table. // Peb = PsGetProcessPeb(Process->Process); if (Peb != NULL) { // // Temporary entry to allow setting GDI // batch limit before each process startup. // Peb->GdiDCAttributeList = gInitialBatchCount; ASSERTGDI(sizeof(Peb->GdiHandleBuffer) >= sizeof(GDIHANDLECACHE), "Handle cache not large enough"); RtlZeroMemory(Peb->GdiHandleBuffer, sizeof(GDIHANDLECACHE)); // // map a READ_ONLY view of the hmgr shared handle table into the // process's address space // PVOID BaseAddress = NULL; SIZE_T CommitSize = 0; OBJECT_ATTRIBUTES ObjectAttributes; UNICODE_STRING UnicodeString; HANDLE SectionHandle = NULL; ntStatus = ObOpenObjectByPointer( gpHmgrSharedHandleSection, OBJ_KERNEL_HANDLE, (PACCESS_STATE) NULL, SECTION_ALL_ACCESS, (POBJECT_TYPE) NULL, KernelMode, &SectionHandle); if (NT_SUCCESS(ntStatus)) { ntStatus = ZwMapViewOfSection( SectionHandle, NtCurrentProcess(), &BaseAddress, 0L, 0L, NULL, &CommitSize, ViewUnmap, 0L, PAGE_READONLY ); if (NT_SUCCESS(ntStatus)) { // // set table address // // we must set the GdiSharedHandleTable value // to the shared table pointer so that if GDI32 gets // unloaded and re-loaded it can still get the pointer. // // NOTE: we also depend on this pointer being initialized // *BEFORE* we make any GDI or USER call to the kernel // (which has the automatic side-effect of calling this // routine. // Peb->GdiSharedHandleTable = (PVOID)BaseAddress; } else { KdPrint(("ZwMapViewOfSection fails, status = 0x%lx\n",ntStatus)); ntStatus = STATUS_DLL_INIT_FAILED; } ZwClose(SectionHandle); } else { KdPrint(("ObOpenObjectByPointer fails, status = 0x%lx\n",ntStatus)); ntStatus = STATUS_DLL_INIT_FAILED; } } return ntStatus; } else { // // This call takes place when the last thread of a process goes away. // Note that such thread might not be a w32 thread // // // first lets see if this is the spooler and if so, clean him up vCleanupSpool(); W32PID W32Pid = W32GetCurrentPID(); bRes = NtGdiCloseProcess(W32Pid, CLEANUP_PROCESS); if (bRes) { if(Process->GDIHandleCount != 0) { WARNING("GdiProcessCallout: handle count != 0 at termination\n"); } } if (Process) { PGDIENGUSERMEMALLOCNODE pNode; PVOID Restart = NULL; for (pNode = (PGDIENGUSERMEMALLOCNODE)RtlEnumerateGenericTableAvl(&Process->GDIEngUserMemAllocTable,TRUE); pNode; pNode = (PGDIENGUSERMEMALLOCNODE)RtlEnumerateGenericTableAvl(&Process->GDIEngUserMemAllocTable,FALSE)) { RtlDeleteElementGenericTableAvl(&Process->GDIEngUserMemAllocTable,pNode); } PLIST_ENTRY ListHead = &Process->GDIDcAttrFreeList; PLIST_ENTRY NextEntry = ListHead->Flink; // Note in these loops we check for non NULL NextEntry also. // Why? Sometimes it seems GDIProcessCallout is called for // process termination even though it was not called for // initialization. If this is the case then the NextEntry will // be NULL instead of pointing to List Anchor. Hence to handle // this we do this extra check. if (NextEntry) { while (NextEntry != ListHead) { PLIST_ENTRY pFree = NextEntry; NextEntry = NextEntry->Flink; VFREEMEM(pFree); } } ListHead = &Process->GDIBrushAttrFreeList; NextEntry = ListHead->Flink; if (NextEntry) { while (NextEntry != ListHead) { PLIST_ENTRY pFree = NextEntry; NextEntry = NextEntry->Flink; VFREEMEM(pFree); } } } } return (bRes ? STATUS_SUCCESS : STATUS_CANNOT_DELETE); } /******************************Public*Routine******************************\ * GdiThreadCallout * * For Inintialize case, set initial values for W32THREAD elements. * For rundown case, move all thread DCATTR memory blocks to the process * list. * * History: * * 15-May-1995 -by- Mark Enstrom [marke] * \**************************************************************************/ extern "C" VOID GdiThreadCallout( IN PETHREAD pEThread, IN PSW32THREADCALLOUTTYPE CalloutType ) { switch (CalloutType) { case PsW32ThreadCalloutInitialize: { PW32THREAD pThread; pThread = (PW32THREAD) PsGetThreadWin32Thread(pEThread); InitializeListHead(&pThread->GdiTmpAllocList); break; } case PsW32ThreadCalloutExit: { PDC_ATTR pdca; PW32THREAD pThread; // // Flush the TEB batch. KiSystemService flushes the batch // only if the service is a win32k.sys service. During thread // termination, the transition to kernel mode is not one of // these, and so we can get here without the batch being // flushed. Bug #338052 demonstrated this. // GdiThreadCalloutFlushUserBatch(); // // W32 thread execution end. Note that the thread // object can be locked so it might be used after // this call returns. // pdca = (PDC_ATTR)((PW32THREAD)PsGetThreadWin32Thread(pEThread))->pgdiDcattr; if (pdca != NULL) { // // Thread->pgdiDcattr is not NULL so HmgFreeDcAttr will // not put pdca back on thread but will place it on the // process list. // HmgFreeDcAttr(pdca); } #if !defined(_GDIPLUS_) // // Clean up user mode printer driver related stuff // pThread = (PW32THREAD) PsGetThreadWin32Thread(pEThread); { PUMPDOBJ pumpdobj; while (pumpdobj = (PUMPDOBJ)pThread->pUMPDObjs) { pumpdobj->Cleanup(); VFREEMEM(pumpdobj); } } if (pThread->pUMPDHeap != NULL) DestroyUMPDHeap((PUMPDHEAP) pThread->pUMPDHeap); #if defined(_WIN64) // // Cleanup Proxy port // if(pThread->pProxyPort) { PROXYPORT proxyport((ProxyPort*)pThread->pProxyPort); proxyport.Close(); pThread->pProxyPort = NULL; } #endif #ifdef VALIDATE_LOCKS // // Cleanup any debug block that may have been allocated // if(pThread->pSemTable) { VFREEMEM(pThread->pSemTable); pThread->pSemTable = NULL; } #endif // Cleanup per thread Tmp Allocations { PLIST_ENTRY ListHead = &pThread->GdiTmpAllocList; PLIST_ENTRY NextEntry = ListHead->Flink; while(NextEntry != ListHead) { PLIST_ENTRY pFree = NextEntry; NextEntry = NextEntry->Flink; VFREEMEM(pFree); } } #endif // !_GDIPLUS_ break; } } }