2 * PROJECT: ReactOS win32 kernel mode subsystem
3 * LICENSE: GPL - See COPYING in the top level directory
4 * FILE: win32ss/gdi/ntgdi/gdidbg.c
5 * PURPOSE: Special debugging functions for GDI
6 * PROGRAMMERS: Timo Kreuzer
9 /** INCLUDES ******************************************************************/
16 extern ULONG gulFirstFree
;
17 extern ULONG gulFirstUnused
;
18 extern PENTRY gpentHmgr
;
20 ULONG gulLogUnique
= 0;
22 /* Note: the following values need to be sorted */
23 DBG_CHANNEL DbgChannels
[DbgChCount
] = {
24 {L
"EngBlt", DbgChEngBlt
},
25 {L
"EngBrush", DbgChEngBrush
},
26 {L
"EngClip", DbgChEngClip
},
27 {L
"EngCursor", DbgChEngCursor
},
28 {L
"EngDev", DbgChEngDev
},
29 {L
"EngErr", DbgChEngErr
},
30 {L
"EngEvent", DbgChEngEvent
},
31 {L
"EngGrad", DbgChEngGrad
},
32 {L
"EngLDev", DbgChEngLDev
},
33 {L
"EngLine", DbgChEngLine
},
34 {L
"EngMapping", DbgChEngMapping
},
35 {L
"EngPDev", DbgChEngPDev
},
36 {L
"EngSurface", DbgChEngSurface
},
37 {L
"EngWnd", DbgChEngWnd
},
38 {L
"EngXlate", DbgChEngXlate
},
39 {L
"GdiBitmap", DbgChGdiBitmap
},
40 {L
"GdiBlt", DbgChGdiBlt
},
41 {L
"GdiBrush", DbgChGdiBrush
},
42 {L
"GdiClipRgn", DbgChGdiClipRgn
},
43 {L
"GdiCoord", DbgChGdiCoord
},
44 {L
"GdiDC", DbgChGdiDC
},
45 {L
"GdiDCAttr", DbgChGdiDCAttr
},
46 {L
"GdiDCState", DbgChGdiDCState
},
47 {L
"GdiDev", DbgChGdiDev
},
48 {L
"GdiDib", DbgChGdiDib
},
49 {L
"GdiFont", DbgChGdiFont
},
50 {L
"GdiLine", DbgChGdiLine
},
51 {L
"GdiObj", DbgChGdiObj
},
52 {L
"GdiPalette", DbgChGdiPalette
},
53 {L
"GdiPath", DbgChGdiPath
},
54 {L
"GdiPen", DbgChGdiPen
},
55 {L
"GdiPool", DbgChGdiPool
},
56 {L
"GdiRgn", DbgChGdiRgn
},
57 {L
"GdiText", DbgChGdiText
},
58 {L
"GdiXFormObj", DbgChGdiXFormObj
},
59 {L
"UserAccel", DbgChUserAccel
},
60 {L
"UserCallback", DbgChUserCallback
},
61 {L
"UserCallProc", DbgChUserCallProc
},
62 {L
"UserCaret", DbgChUserCaret
},
63 {L
"UserClass", DbgChUserClass
},
64 {L
"UserClipbrd", DbgChUserClipbrd
},
65 {L
"UserCsr", DbgChUserCsr
},
66 {L
"UserDce", DbgChUserDce
},
67 {L
"UserDefwnd", DbgChUserDefwnd
},
68 {L
"UserDesktop", DbgChUserDesktop
},
69 {L
"UserDisplay",DbgChUserDisplay
},
70 {L
"UserEvent", DbgChUserEvent
},
71 {L
"UserFocus", DbgChUserFocus
},
72 {L
"UserHook", DbgChUserHook
},
73 {L
"UserHotkey", DbgChUserHotkey
},
74 {L
"UserIcon", DbgChUserIcon
},
75 {L
"UserInput", DbgChUserInput
},
76 {L
"UserKbd", DbgChUserKbd
},
77 {L
"UserKbdLayout", DbgChUserKbdLayout
},
78 {L
"UserMenu", DbgChUserMenu
},
79 {L
"UserMetric", DbgChUserMetric
},
80 {L
"UserMisc", DbgChUserMisc
},
81 {L
"UserMonitor", DbgChUserMonitor
},
82 {L
"UserMsg", DbgChUserMsg
},
83 {L
"UserMsgQ", DbgChUserMsgQ
},
84 {L
"UserObj", DbgChUserObj
},
85 {L
"UserPainting", DbgChUserPainting
},
86 {L
"UserProcess", DbgChUserProcess
},
87 {L
"UserProp", DbgChUserProp
},
88 {L
"UserScrollbar", DbgChUserScrollbar
},
89 {L
"UserShutdown", DbgChUserShutdown
},
90 {L
"UserSysparams", DbgChUserSysparams
},
91 {L
"UserTimer", DbgChUserTimer
},
92 {L
"UserThread", DbgChUserThread
},
93 {L
"UserWinpos", DbgChUserWinpos
},
94 {L
"UserWinsta", DbgChUserWinsta
},
95 {L
"UserWnd", DbgChUserWnd
}
100 DbgCaptureStackBackTace(
101 _Out_writes_(cFramesToCapture
) PVOID
* ppvFrames
,
102 _In_ ULONG cFramesToSkip
,
103 _In_ ULONG cFramesToCapture
)
107 NT_ASSERT(cFramesToCapture
<= _countof(apvTemp
));
110 RtlZeroMemory(ppvFrames
, cFramesToCapture
* sizeof(PVOID
));
112 /* Capture kernel stack */
113 cFrameCount
= RtlWalkFrameChain(apvTemp
, _countof(apvTemp
), 0);
115 /* If we should skip more than we have, we are done */
116 if (cFramesToSkip
> cFrameCount
)
119 /* Copy, but skip frames */
120 cFrameCount
-= cFramesToSkip
;
121 cFrameCount
= min(cFrameCount
, cFramesToCapture
);
122 RtlCopyMemory(ppvFrames
, &apvTemp
[cFramesToSkip
], cFrameCount
* sizeof(PVOID
));
124 /* Check if there is still space left */
125 if (cFrameCount
< cFramesToCapture
)
127 /* Capture user stack */
128 cFrameCount
+= RtlWalkFrameChain(&ppvFrames
[cFrameCount
],
129 cFramesToCapture
- cFrameCount
,
136 #if DBG_ENABLE_GDIOBJ_BACKTRACES
147 /* Get the objects */
148 pobj1
= gpentHmgr
[idx1
].einfo
.pobj
;
149 pobj2
= gpentHmgr
[idx2
].einfo
.pobj
;
151 /* Loop all stack levels */
152 for (iLevel
= 0; iLevel
< GDI_OBJECT_STACK_LEVELS
; iLevel
++)
154 /* If one level doesn't match we are done */
155 if (pobj1
->apvBackTrace
[iLevel
] != pobj2
->apvBackTrace
[iLevel
])
172 DbgDumpGdiHandleTableWithBT(void)
174 static BOOL bLeakReported
= FALSE
;
176 BOOL bAlreadyPresent
;
177 GDI_DBG_HANDLE_BT aBacktraceTable
[GDI_DBG_MAX_BTS
];
183 /* Only report once */
186 DPRINT1("GDI handle abusers already reported!\n");
190 bLeakReported
= TRUE
;
191 DPRINT1("Reporting GDI handle abusers:\n");
193 /* Zero out the table */
194 RtlZeroMemory(aBacktraceTable
, sizeof(aBacktraceTable
));
196 /* We've got serious business to do */
197 KeRaiseIrql(DISPATCH_LEVEL
, &OldIrql
);
199 /* Step through GDI handle table and find out who our culprit is... */
200 for (idx
= RESERVE_ENTRIES_COUNT
; idx
< GDI_HANDLE_COUNT
; idx
++)
202 /* If the handle is free, continue */
203 if (gpentHmgr
[idx
].einfo
.pobj
== 0) continue;
205 /* Check if this backtrace is already covered */
206 bAlreadyPresent
= FALSE
;
207 for (j
= RESERVE_ENTRIES_COUNT
; j
< idx
; j
++)
209 if (CompareBacktraces(idx
, j
))
211 bAlreadyPresent
= TRUE
;
216 if (bAlreadyPresent
) continue;
218 /* We don't have this BT yet, count how often it is present */
220 for (j
= idx
+ 1; j
< GDI_HANDLE_COUNT
; j
++)
222 if (CompareBacktraces(idx
, j
))
228 /* Now add this backtrace */
229 for (j
= 0; j
< GDI_DBG_MAX_BTS
; j
++)
231 /* Insert it below the next smaller count */
232 if (aBacktraceTable
[j
].iCount
< iCount
)
234 /* Check if there are entries above */
235 if (j
< GDI_DBG_MAX_BTS
- 1)
237 /* Move the following entries up by 1 */
238 RtlMoveMemory(&aBacktraceTable
[j
],
239 &aBacktraceTable
[j
+ 1],
240 GDI_DBG_MAX_BTS
- j
- 1);
244 aBacktraceTable
[j
].idx
= idx
;
245 aBacktraceTable
[j
].iCount
= iCount
;
247 /* We are done here */
253 /* Print the worst offenders... */
254 DbgPrint("Count Handle Backtrace\n");
255 DbgPrint("------------------------------------------------\n");
256 for (j
= 0; j
< GDI_DBG_MAX_BTS
; j
++)
258 idx
= aBacktraceTable
[j
].idx
;
262 ulObj
= ((ULONG
)gpentHmgr
[idx
].FullUnique
<< 16) | idx
;
263 pobj
= gpentHmgr
[idx
].einfo
.pobj
;
265 DbgPrint("%5d %08lx ", aBacktraceTable
[j
].iCount
, ulObj
);
266 for (iLevel
= 0; iLevel
< GDI_OBJECT_STACK_LEVELS
; iLevel
++)
268 DbgPrint("%p,", pobj
->apvBackTrace
[iLevel
]);
275 KeLowerIrql(OldIrql
);
278 #endif /* DBG_ENABLE_GDIOBJ_BACKTRACES */
284 DbgGdiHTIntegrityCheck(VOID
)
286 ULONG i
, nDeleted
= 0, nFree
= 0, nUsed
= 0;
287 PGDI_TABLE_ENTRY pEntry
;
290 KeEnterCriticalRegion();
292 /* FIXME: Check reserved entries */
294 /* Now go through the deleted objects */
295 i
= gulFirstFree
& 0xffff;
298 pEntry
= &GdiHandleTable
->Entries
[i
];
299 if (i
>= GDI_HANDLE_COUNT
)
301 DPRINT1("nDeleted=%lu\n", nDeleted
);
307 /* Check the entry */
308 if ((pEntry
->Type
& GDI_ENTRY_BASETYPE_MASK
) != 0)
311 DPRINT1("Deleted Entry has a type != 0\n");
313 if ((ULONG_PTR
)pEntry
->KernelData
>= GDI_HANDLE_COUNT
)
316 DPRINT1("Deleted entries KernelPointer too big\n");
318 if (pEntry
->UserData
!= NULL
)
321 DPRINT1("Deleted entry has UserData != 0\n");
323 if (pEntry
->ProcessId
!= 0)
326 DPRINT1("Deleted entry has ProcessId != 0\n");
329 i
= (ULONG_PTR
)pEntry
->KernelData
& 0xffff;
332 for (i
= gulFirstUnused
;
333 i
< GDI_HANDLE_COUNT
;
336 pEntry
= &GdiHandleTable
->Entries
[i
];
338 if ((pEntry
->Type
) != 0)
341 DPRINT1("Free Entry has a type != 0\n");
343 if ((ULONG_PTR
)pEntry
->KernelData
!= 0)
346 DPRINT1("Free entries KernelPointer != 0\n");
348 if (pEntry
->UserData
!= NULL
)
351 DPRINT1("Free entry has UserData != 0\n");
353 if (pEntry
->ProcessId
!= 0)
356 DPRINT1("Free entry has ProcessId != 0\n");
361 for (i
= RESERVE_ENTRIES_COUNT
; i
< GDI_HANDLE_COUNT
; i
++)
366 pEntry
= &GdiHandleTable
->Entries
[i
];
368 Handle
= (HGDIOBJ
)(ULONG_PTR
)((Type
<< GDI_ENTRY_UPPER_SHIFT
) + i
);
370 if (Type
& GDI_ENTRY_BASETYPE_MASK
)
372 if (pEntry
->KernelData
== NULL
)
375 DPRINT1("Used entry has KernelData == 0\n");
377 else if (pEntry
->KernelData
<= MmHighestUserAddress
)
380 DPRINT1("Used entry invalid KernelData\n");
382 else if (((POBJ
)(pEntry
->KernelData
))->hHmgr
!= Handle
)
385 DPRINT1("Used entry %lu, has invalid hHmg %p (expected: %p)\n",
386 i
, ((POBJ
)(pEntry
->KernelData
))->hHmgr
, Handle
);
392 if (RESERVE_ENTRIES_COUNT
+ nDeleted
+ nFree
+ nUsed
!= GDI_HANDLE_COUNT
)
395 DPRINT1("Number of all entries incorrect: RESERVE_ENTRIES_COUNT = %lu, nDeleted = %lu, nFree = %lu, nUsed = %lu\n",
396 RESERVE_ENTRIES_COUNT
, nDeleted
, nFree
, nUsed
);
399 KeLeaveCriticalRegion();
407 #if DBG_ENABLE_EVENT_LOGGING
411 DbgLogEvent(PSLIST_HEADER pslh
, LOG_EVENT_TYPE nEventType
, LPARAM lParam
)
415 /* Log a maximum of 100 events */
416 if (QueryDepthSList(pslh
) >= 1000) return;
418 /* Allocate a logentry */
419 pLogEntry
= EngAllocMem(0, sizeof(LOGENTRY
), 'golG');
420 if (!pLogEntry
) return;
423 pLogEntry
->nEventType
= nEventType
;
424 pLogEntry
->ulUnique
= InterlockedIncrement((LONG
*)&gulLogUnique
);
425 pLogEntry
->dwProcessId
= HandleToUlong(PsGetCurrentProcessId());
426 pLogEntry
->dwThreadId
= HandleToUlong(PsGetCurrentThreadId());
427 pLogEntry
->lParam
= lParam
;
429 /* Capture a backtrace */
430 DbgCaptureStackBackTace(pLogEntry
->apvBackTrace
, 1, 20);
435 case EVENT_CREATE_HANDLE
:
436 case EVENT_REFERENCE
:
437 case EVENT_DEREFERENCE
:
442 case EVENT_SET_OWNER
:
447 /* Push it on the list */
448 InterlockedPushEntrySList(pslh
, &pLogEntry
->sleLink
);
451 #define REL_ADDR(va) ((ULONG_PTR)va - (ULONG_PTR)&__ImageBase)
455 DbgPrintEvent(PLOGENTRY pLogEntry
)
459 switch (pLogEntry
->nEventType
)
461 case EVENT_ALLOCATE
: pstr
= "Allocate"; break;
462 case EVENT_CREATE_HANDLE
: pstr
= "CreatHdl"; break;
463 case EVENT_REFERENCE
: pstr
= "Ref"; break;
464 case EVENT_DEREFERENCE
: pstr
= "Deref"; break;
465 case EVENT_LOCK
: pstr
= "Lock"; break;
466 case EVENT_UNLOCK
: pstr
= "Unlock"; break;
467 case EVENT_DELETE
: pstr
= "Delete"; break;
468 case EVENT_FREE
: pstr
= "Free"; break;
469 case EVENT_SET_OWNER
: pstr
= "SetOwner"; break;
470 default: pstr
= "Unknown"; break;
473 DbgPrint("[%lu] %03x:%03x %.8s val=%p <%lx,%lx,%lx,%lx>\n",
475 pLogEntry
->dwProcessId
,
476 pLogEntry
->dwThreadId
,
478 (PVOID
)pLogEntry
->lParam
,
479 REL_ADDR(pLogEntry
->apvBackTrace
[2]),
480 REL_ADDR(pLogEntry
->apvBackTrace
[3]),
481 REL_ADDR(pLogEntry
->apvBackTrace
[4]),
482 REL_ADDR(pLogEntry
->apvBackTrace
[5]));
487 DbgDumpEventList(PSLIST_HEADER pslh
)
492 while ((psle
= InterlockedPopEntrySList(pslh
)))
494 pLogEntry
= CONTAINING_RECORD(psle
, LOGENTRY
, sleLink
);
495 DbgPrintEvent(pLogEntry
);
501 DbgCleanupEventList(PSLIST_HEADER pslh
)
506 while ((psle
= InterlockedPopEntrySList(pslh
)))
508 pLogEntry
= CONTAINING_RECORD(psle
, LOGENTRY
, sleLink
);
509 EngFreeMem(pLogEntry
);
513 #endif /* DBG_ENABLE_EVENT_LOGGING */
515 #if 1 || DBG_ENABLE_SERVICE_HOOKS
519 DbgDumpLockedGdiHandles(VOID
)
523 for (i
= RESERVE_ENTRIES_COUNT
; i
< GDI_HANDLE_COUNT
; i
++)
525 PENTRY pentry
= &gpentHmgr
[i
];
529 POBJ pobj
= pentry
->einfo
.pobj
;
530 if (pobj
->cExclusiveLock
> 0)
532 DPRINT1("Locked object: %lx, type = %lx. allocated from:\n",
534 DBG_DUMP_EVENT_LIST(&pobj
->slhLog
);
542 GdiDbgPreServiceHook(ULONG ulSyscallId
, PULONG_PTR pulArguments
)
544 PTHREADINFO pti
= (PTHREADINFO
)PsGetCurrentThreadWin32Thread();
545 if (pti
&& pti
->cExclusiveLocks
!= 0)
547 DbgPrint("FATAL: Win32DbgPreServiceHook(0x%lx): There are %lu exclusive locks!\n",
548 ulSyscallId
, pti
->cExclusiveLocks
);
549 DbgDumpLockedGdiHandles();
557 GdiDbgPostServiceHook(ULONG ulSyscallId
, ULONG_PTR ulResult
)
559 PTHREADINFO pti
= (PTHREADINFO
)PsGetCurrentThreadWin32Thread();
560 if (pti
&& pti
->cExclusiveLocks
!= 0)
562 DbgPrint("FATAL: Win32DbgPostServiceHook(0x%lx): There are %lu exclusive locks!\n",
563 ulSyscallId
, pti
->cExclusiveLocks
);
564 DbgDumpLockedGdiHandles();
570 #endif /* DBG_ENABLE_SERVICE_HOOKS */
574 QueryEnvironmentVariable(PUNICODE_STRING Name
,
575 PUNICODE_STRING Value
)
584 /* Ugly HACK for ReactOS system threads */
587 return(STATUS_VARIABLE_NOT_FOUND
);
590 Peb
= NtCurrentPeb();
594 return(STATUS_VARIABLE_NOT_FOUND
);
597 Environment
= Peb
->ProcessParameters
->Environment
;
599 if (Environment
== NULL
)
601 return(STATUS_VARIABLE_NOT_FOUND
);
610 wcs
= wcschr(wcs
, L
'=');
613 wcs
= var
.Buffer
+ wcslen(var
.Buffer
);
617 var
.Length
= var
.MaximumLength
= (wcs
- var
.Buffer
) * sizeof(WCHAR
);
621 if (RtlEqualUnicodeString(&var
, Name
, TRUE
))
623 Value
->Length
= (wcs
- val
) * sizeof(WCHAR
);
624 if (Value
->Length
<= Value
->MaximumLength
)
626 memcpy(Value
->Buffer
, val
,
627 min(Value
->Length
+ sizeof(WCHAR
), Value
->MaximumLength
));
628 Status
= STATUS_SUCCESS
;
632 Status
= STATUS_BUFFER_TOO_SMALL
;
641 return(STATUS_VARIABLE_NOT_FOUND
);
645 DbgCompareChannels(const void * a
, const void * b
)
647 return wcscmp((WCHAR
*)a
, ((DBG_CHANNEL
*)b
)->Name
);
651 DbgAddDebugChannel(PPROCESSINFO ppi
, WCHAR
* channel
, WCHAR
* level
, WCHAR op
)
653 DBG_CHANNEL
*ChannelEntry
;
654 UINT iLevel
, iChannel
;
656 /* Special treatment for the "all" channel */
657 if (wcscmp(channel
, L
"all") == 0)
659 for (iChannel
= 0; iChannel
< DbgChCount
; iChannel
++)
661 DbgAddDebugChannel(ppi
, DbgChannels
[iChannel
].Name
, level
, op
);
666 ChannelEntry
= (DBG_CHANNEL
*)bsearch(channel
,
671 if(ChannelEntry
== NULL
)
676 iChannel
= ChannelEntry
->Id
;
677 ASSERT(iChannel
< DbgChCount
);
679 if(level
== NULL
|| *level
== L
'\0' ||wcslen(level
) == 0 )
681 else if(wcsncmp(level
, L
"err", 3) == 0)
683 else if(wcsncmp(level
, L
"fixme", 5) == 0)
684 iLevel
= FIXME_LEVEL
;
685 else if(wcsncmp(level
, L
"warn", 4) == 0)
687 else if (wcsncmp(level
, L
"trace", 4) == 0)
688 iLevel
= TRACE_LEVEL
;
694 DBG_ENABLE_CHANNEL(ppi
, iChannel
, iLevel
);
698 DBG_DISABLE_CHANNEL(ppi
, iChannel
, iLevel
);
705 DbgParseDebugChannels(PPROCESSINFO ppi
, PUNICODE_STRING Value
)
707 WCHAR
*str
, *separator
, *c
, op
;
713 separator
= wcschr(str
, L
',');
714 if(separator
!= NULL
)
717 c
= wcschr(str
, L
'+');
719 c
= wcschr(str
, L
'-');
727 DbgAddDebugChannel(ppi
, c
, str
, op
);
731 }while(separator
!= NULL
);
736 BOOL
DbgInitDebugChannels(VOID
)
738 WCHAR valBuffer
[100];
739 UNICODE_STRING Value
;
740 UNICODE_STRING Name
= RTL_CONSTANT_STRING(L
"DEBUGCHANNEL");
745 /* Initialize all channels to ERROR */
746 ppi
= PsGetCurrentProcessWin32Process();
747 RtlFillMemory( ppi
->DbgChannelLevel
,
748 sizeof(ppi
->DbgChannelLevel
),
751 /* Find DEBUGCHANNEL env var */
752 Value
.Buffer
= valBuffer
;
754 Value
.MaximumLength
= sizeof(valBuffer
);
755 Status
= QueryEnvironmentVariable(&Name
, &Value
);
757 /* It does not exist */
758 if(Status
== STATUS_VARIABLE_NOT_FOUND
)
760 /* There is nothing more to do */
764 /* If the buffer in the stack is not enough allocate it */
765 if(Status
== STATUS_BUFFER_TOO_SMALL
)
767 Value
.Buffer
= ExAllocatePool(PagedPool
, Value
.MaximumLength
);
768 if(Value
.Buffer
== NULL
)
773 /* Get the env var again */
774 Status
= QueryEnvironmentVariable(&Name
, &Value
);
777 /* Check for error */
778 if(!NT_SUCCESS(Status
))
780 if(Value
.Buffer
!= valBuffer
)
782 ExFreePool(Value
.Buffer
);
788 /* Parse the variable */
789 ret
= DbgParseDebugChannels(ppi
, &Value
);
792 if(Value
.Buffer
!= valBuffer
)
794 ExFreePool(Value
.Buffer
);