2 * PROJECT: ReactOS win32 kernel mode subsystem
3 * LICENSE: GPL - See COPYING in the top level directory
4 * FILE: subsystems/win32/win32k/objects/gdidbg.c
5 * PURPOSE: Special debugging functions for GDI
6 * PROGRAMMERS: Timo Kreuzer
9 /** INCLUDES ******************************************************************/
16 extern ULONG gulFirstFree
;
17 extern ULONG gulFirstUnused
;
19 ULONG gulLogUnique
= 0;
21 /* Note: the following values need to be sorted */
22 DBG_CHANNEL DbgChannels
[DbgChCount
]={
23 {L
"EngBlt", DbgChEngBlt
},
24 {L
"EngBrush", DbgChEngBrush
},
25 {L
"EngClip", DbgChEngClip
},
26 {L
"EngCursor", DbgChEngCursor
},
27 {L
"EngDev", DbgChEngDev
},
28 {L
"EngErr", DbgChEngErr
},
29 {L
"EngEvent", DbgChEngEvent
},
30 {L
"EngGrad", DbgChEngGrad
},
31 {L
"EngLDev", DbgChEngLDev
},
32 {L
"EngLine", DbgChEngLine
},
33 {L
"EngMapping", DbgChEngMapping
},
34 {L
"EngPDev", DbgChEngPDev
},
35 {L
"EngSurface", DbgChEngSurface
},
36 {L
"EngWnd", DbgChEngWnd
},
37 {L
"EngXlate", DbgChEngXlate
},
38 {L
"GdiBitmap", DbgChGdiBitmap
},
39 {L
"GdiBlt", DbgChGdiBlt
},
40 {L
"GdiBrush", DbgChGdiBrush
},
41 {L
"GdiClipRgn", DbgChGdiClipRgn
},
42 {L
"GdiCoord", DbgChGdiCoord
},
43 {L
"GdiDC", DbgChGdiDC
},
44 {L
"GdiDCAttr", DbgChGdiDCAttr
},
45 {L
"GdiDCState", DbgChGdiDCState
},
46 {L
"GdiDev", DbgChGdiDev
},
47 {L
"GdiDib", DbgChGdiDib
},
48 {L
"GdiFont", DbgChGdiFont
},
49 {L
"GdiLine", DbgChGdiLine
},
50 {L
"GdiObj", DbgChGdiObj
},
51 {L
"GdiPalette", DbgChGdiPalette
},
52 {L
"GdiPath", DbgChGdiPath
},
53 {L
"GdiPen", DbgChGdiPen
},
54 {L
"GdiPool", DbgChGdiPool
},
55 {L
"GdiRgn", DbgChGdiRgn
},
56 {L
"GdiText", DbgChGdiText
},
57 {L
"GdiXFormObj", DbgChGdiXFormObj
},
58 {L
"UserAccel", DbgChUserAccel
},
59 {L
"UserCallback", DbgChUserCallback
},
60 {L
"UserCallProc", DbgChUserCallProc
},
61 {L
"UserCaret", DbgChUserCaret
},
62 {L
"UserClass", DbgChUserClass
},
63 {L
"UserClipbrd", DbgChUserClipbrd
},
64 {L
"UserCsr", DbgChUserCsr
},
65 {L
"UserDce", DbgChUserDce
},
66 {L
"UserDefwnd", DbgChUserDefwnd
},
67 {L
"UserDesktop", DbgChUserDesktop
},
68 {L
"UserDisplay",DbgChUserDisplay
},
69 {L
"UserEvent", DbgChUserEvent
},
70 {L
"UserFocus", DbgChUserFocus
},
71 {L
"UserHook", DbgChUserHook
},
72 {L
"UserHotkey", DbgChUserHotkey
},
73 {L
"UserIcon", DbgChUserIcon
},
74 {L
"UserInput", DbgChUserInput
},
75 {L
"UserKbd", DbgChUserKbd
},
76 {L
"UserKbdLayout", DbgChUserKbdLayout
},
77 {L
"UserMenu", DbgChUserMenu
},
78 {L
"UserMetric", DbgChUserMetric
},
79 {L
"UserMisc", DbgChUserMisc
},
80 {L
"UserMonitor", DbgChUserMonitor
},
81 {L
"UserMsg", DbgChUserMsg
},
82 {L
"UserMsgQ", DbgChUserMsgQ
},
83 {L
"UserObj", DbgChUserObj
},
84 {L
"UserPainting", DbgChUserPainting
},
85 {L
"UserProcess", DbgChUserProcess
},
86 {L
"UserProp", DbgChUserProp
},
87 {L
"UserScrollbar", DbgChUserScrollbar
},
88 {L
"UserSysparams", DbgChUserSysparams
},
89 {L
"UserTimer", DbgChUserTimer
},
90 {L
"UserThread", DbgChUserThread
},
91 {L
"UserWinpos", DbgChUserWinpos
},
92 {L
"UserWinsta", DbgChUserWinsta
},
93 {L
"UserWnd", DbgChUserWnd
}
100 CompareBacktraces(ULONG idx1
, ULONG idx2
)
104 /* Loop all stack levels */
105 for (iLevel
= 0; iLevel
< GDI_STACK_LEVELS
; iLevel
++)
107 if (GDIHandleAllocator
[idx1
][iLevel
]
108 != GDIHandleAllocator
[idx2
][iLevel
])
109 // if (GDIHandleShareLocker[idx1][iLevel]
110 // != GDIHandleShareLocker[idx2][iLevel])
121 DbgDumpGdiHandleTableWithBT(void)
123 static int leak_reported
= 0;
124 int i
, j
, idx
, nTraces
= 0;
129 DPRINT1("GDI handle abusers already reported!\n");
134 DPRINT1("Reporting GDI handle abusers:\n");
136 /* We've got serious business to do */
137 KeRaiseIrql(DISPATCH_LEVEL
, &OldIrql
);
139 /* Step through GDI handle table and find out who our culprit is... */
140 for (idx
= RESERVE_ENTRIES_COUNT
; idx
< GDI_HANDLE_COUNT
; idx
++)
142 /* If the handle is free, continue */
143 if (!IS_HANDLE_VALID(idx
)) continue;
145 /* Step through all previous backtraces */
146 for (j
= 0; j
< nTraces
; j
++)
148 /* Check if the backtrace matches */
149 if (CompareBacktraces(idx
, AllocatorTable
[j
].idx
))
151 /* It matches, increment count and break out */
152 AllocatorTable
[j
].count
++;
157 /* Did we find a new backtrace? */
160 /* Break out, if we reached the maximum */
161 if (nTraces
== MAX_BACKTRACES
) break;
163 /* Initialize this entry */
164 AllocatorTable
[j
].idx
= idx
;
165 AllocatorTable
[j
].count
= 1;
170 /* bubble sort time! weeeeee!! */
171 for (i
= 0; i
< nTraces
-1; i
++)
173 if (AllocatorTable
[i
].count
< AllocatorTable
[i
+1].count
)
175 struct DbgOpenGDIHandle temp
;
177 temp
= AllocatorTable
[i
+1];
178 AllocatorTable
[i
+1] = AllocatorTable
[i
];
180 while (j
> 0 && AllocatorTable
[j
-1].count
< temp
.count
)
182 AllocatorTable
[j
] = temp
;
186 /* Print the worst offenders... */
187 DbgPrint("Worst GDI Handle leak offenders (out of %i unique locations):\n", nTraces
);
188 for (i
= 0; i
< nTraces
&& AllocatorTable
[i
].count
> 1; i
++)
190 /* Print out the allocation count */
191 DbgPrint(" %i allocs, type = 0x%lx:\n",
192 AllocatorTable
[i
].count
,
193 GdiHandleTable
->Entries
[AllocatorTable
[i
].idx
].Type
);
195 /* Dump the frames */
196 KeRosDumpStackFrames(GDIHandleAllocator
[AllocatorTable
[i
].idx
], GDI_STACK_LEVELS
);
197 //KeRosDumpStackFrames(GDIHandleShareLocker[AllocatorTable[i].idx], GDI_STACK_LEVELS);
199 /* Print new line for better readability */
204 DbgPrint("(List terminated - the remaining entries have 1 allocation only)\n");
206 KeLowerIrql(OldIrql
);
214 DbgCaptureStackBackTace(PVOID
* pFrames
, ULONG nFramesToCapture
)
218 memset(pFrames
, 0x00, (nFramesToCapture
+ 1) * sizeof(PVOID
));
220 nFrameCount
= RtlWalkFrameChain(pFrames
, nFramesToCapture
, 0);
222 if (nFrameCount
< nFramesToCapture
)
224 nFrameCount
+= RtlWalkFrameChain(pFrames
+ nFrameCount
,
225 nFramesToCapture
- nFrameCount
,
234 DbgGdiHTIntegrityCheck()
236 ULONG i
, nDeleted
= 0, nFree
= 0, nUsed
= 0;
237 PGDI_TABLE_ENTRY pEntry
;
240 KeEnterCriticalRegion();
242 /* FIXME: Check reserved entries */
244 /* Now go through the deleted objects */
245 i
= gulFirstFree
& 0xffff;
248 pEntry
= &GdiHandleTable
->Entries
[i
];
249 if (i
>= GDI_HANDLE_COUNT
)
251 DPRINT1("nDeleted=%lu\n", nDeleted
);
257 /* Check the entry */
258 if ((pEntry
->Type
& GDI_ENTRY_BASETYPE_MASK
) != 0)
261 DPRINT1("Deleted Entry has a type != 0\n");
263 if ((ULONG_PTR
)pEntry
->KernelData
>= GDI_HANDLE_COUNT
)
266 DPRINT1("Deleted entries KernelPointer too big\n");
268 if (pEntry
->UserData
!= NULL
)
271 DPRINT1("Deleted entry has UserData != 0\n");
273 if (pEntry
->ProcessId
!= 0)
276 DPRINT1("Deleted entry has ProcessId != 0\n");
279 i
= (ULONG_PTR
)pEntry
->KernelData
& 0xffff;
282 for (i
= gulFirstUnused
;
283 i
< GDI_HANDLE_COUNT
;
286 pEntry
= &GdiHandleTable
->Entries
[i
];
288 if ((pEntry
->Type
) != 0)
291 DPRINT1("Free Entry has a type != 0\n");
293 if ((ULONG_PTR
)pEntry
->KernelData
!= 0)
296 DPRINT1("Free entries KernelPointer != 0\n");
298 if (pEntry
->UserData
!= NULL
)
301 DPRINT1("Free entry has UserData != 0\n");
303 if (pEntry
->ProcessId
!= 0)
306 DPRINT1("Free entry has ProcessId != 0\n");
311 for (i
= RESERVE_ENTRIES_COUNT
; i
< GDI_HANDLE_COUNT
; i
++)
316 pEntry
= &GdiHandleTable
->Entries
[i
];
318 Handle
= (HGDIOBJ
)((Type
<< GDI_ENTRY_UPPER_SHIFT
) + i
);
320 if (Type
& GDI_ENTRY_BASETYPE_MASK
)
322 if (pEntry
->KernelData
== NULL
)
325 DPRINT1("Used entry has KernelData == 0\n");
327 if (pEntry
->KernelData
<= MmHighestUserAddress
)
330 DPRINT1("Used entry invalid KernelData\n");
332 if (((POBJ
)(pEntry
->KernelData
))->hHmgr
!= Handle
)
335 DPRINT1("Used entry %lu, has invalid hHmg %p (expected: %p)\n",
336 i
, ((POBJ
)(pEntry
->KernelData
))->hHmgr
, Handle
);
342 if (RESERVE_ENTRIES_COUNT
+ nDeleted
+ nFree
+ nUsed
!= GDI_HANDLE_COUNT
)
345 DPRINT1("Number of all entries incorrect: RESERVE_ENTRIES_COUNT = %lu, nDeleted = %lu, nFree = %lu, nUsed = %lu\n",
346 RESERVE_ENTRIES_COUNT
, nDeleted
, nFree
, nUsed
);
349 KeLeaveCriticalRegion();
354 #endif /* GDI_DEBUG */
358 DbgDumpLockedGdiHandles()
363 for (i
= RESERVE_ENTRIES_COUNT
; i
< GDI_HANDLE_COUNT
; i
++)
365 PENTRY pentry
= &gpentHmgr
[i
];
369 POBJ pobj
= pentry
->einfo
.pobj
;
370 if (pobj
->cExclusiveLock
> 0)
372 DPRINT1("Locked object: %lx, type = %lx. allocated from:\n",
374 DBG_DUMP_EVENT_LIST(&pobj
->slhLog
);
383 DbgLogEvent(PSLIST_HEADER pslh
, LOG_EVENT_TYPE nEventType
, LPARAM lParam
)
387 /* Log a maximum of 100 events */
388 if (QueryDepthSList(pslh
) >= 1000) return;
390 /* Allocate a logentry */
391 pLogEntry
= EngAllocMem(0, sizeof(LOGENTRY
), 'golG');
392 if (!pLogEntry
) return;
395 pLogEntry
->nEventType
= nEventType
;
396 pLogEntry
->ulUnique
= InterlockedIncrement((LONG
*)&gulLogUnique
);
397 pLogEntry
->dwProcessId
= HandleToUlong(PsGetCurrentProcessId());
398 pLogEntry
->dwThreadId
= HandleToUlong(PsGetCurrentThreadId());
399 pLogEntry
->lParam
= lParam
;
401 /* Capture a backtrace */
402 DbgCaptureStackBackTace(pLogEntry
->apvBackTrace
, 20);
407 case EVENT_CREATE_HANDLE
:
408 case EVENT_REFERENCE
:
409 case EVENT_DEREFERENCE
:
414 case EVENT_SET_OWNER
:
419 /* Push it on the list */
420 InterlockedPushEntrySList(pslh
, &pLogEntry
->sleLink
);
423 #define REL_ADDR(va) ((ULONG_PTR)va - (ULONG_PTR)&__ImageBase)
427 DbgPrintEvent(PLOGENTRY pLogEntry
)
431 switch (pLogEntry
->nEventType
)
433 case EVENT_ALLOCATE
: pstr
= "Allocate"; break;
434 case EVENT_CREATE_HANDLE
: pstr
= "CreatHdl"; break;
435 case EVENT_REFERENCE
: pstr
= "Ref"; break;
436 case EVENT_DEREFERENCE
: pstr
= "Deref"; break;
437 case EVENT_LOCK
: pstr
= "Lock"; break;
438 case EVENT_UNLOCK
: pstr
= "Unlock"; break;
439 case EVENT_DELETE
: pstr
= "Delete"; break;
440 case EVENT_FREE
: pstr
= "Free"; break;
441 case EVENT_SET_OWNER
: pstr
= "SetOwner"; break;
442 default: pstr
= "Unknown"; break;
445 DbgPrint("[%lu] %03x:%03x %.8s val=%p <%lx,%lx,%lx,%lx>\n",
447 pLogEntry
->dwProcessId
,
448 pLogEntry
->dwThreadId
,
450 (PVOID
)pLogEntry
->lParam
,
451 REL_ADDR(pLogEntry
->apvBackTrace
[2]),
452 REL_ADDR(pLogEntry
->apvBackTrace
[3]),
453 REL_ADDR(pLogEntry
->apvBackTrace
[4]),
454 REL_ADDR(pLogEntry
->apvBackTrace
[5]));
459 DbgDumpEventList(PSLIST_HEADER pslh
)
464 while ((psle
= InterlockedPopEntrySList(pslh
)))
466 pLogEntry
= CONTAINING_RECORD(psle
, LOGENTRY
, sleLink
);
467 DbgPrintEvent(pLogEntry
);
473 DbgCleanupEventList(PSLIST_HEADER pslh
)
478 while ((psle
= InterlockedPopEntrySList(pslh
)))
480 pLogEntry
= CONTAINING_RECORD(psle
, LOGENTRY
, sleLink
);
481 EngFreeMem(pLogEntry
);
487 GdiDbgPreServiceHook(ULONG ulSyscallId
, PULONG_PTR pulArguments
)
489 PTHREADINFO pti
= (PTHREADINFO
)PsGetCurrentThreadWin32Thread();
490 if (pti
&& pti
->cExclusiveLocks
!= 0)
492 DbgPrint("FATAL: Win32DbgPreServiceHook(0x%lx): There are %lu exclusive locks!\n",
493 ulSyscallId
, pti
->cExclusiveLocks
);
494 DbgDumpLockedGdiHandles();
502 GdiDbgPostServiceHook(ULONG ulSyscallId
, ULONG_PTR ulResult
)
504 PTHREADINFO pti
= (PTHREADINFO
)PsGetCurrentThreadWin32Thread();
505 if (pti
&& pti
->cExclusiveLocks
!= 0)
507 DbgPrint("FATAL: Win32DbgPostServiceHook(0x%lx): There are %lu exclusive locks!\n",
508 ulSyscallId
, pti
->cExclusiveLocks
);
509 DbgDumpLockedGdiHandles();
516 QueryEnvironmentVariable(PUNICODE_STRING Name
,
517 PUNICODE_STRING Value
)
526 /* Ugly HACK for ReactOS system threads */
529 return(STATUS_VARIABLE_NOT_FOUND
);
532 Peb
= NtCurrentPeb();
536 return(STATUS_VARIABLE_NOT_FOUND
);
539 Environment
= Peb
->ProcessParameters
->Environment
;
541 if (Environment
== NULL
)
543 return(STATUS_VARIABLE_NOT_FOUND
);
552 wcs
= wcschr(wcs
, L
'=');
555 wcs
= var
.Buffer
+ wcslen(var
.Buffer
);
559 var
.Length
= var
.MaximumLength
= (wcs
- var
.Buffer
) * sizeof(WCHAR
);
563 if (RtlEqualUnicodeString(&var
, Name
, TRUE
))
565 Value
->Length
= (wcs
- val
) * sizeof(WCHAR
);
566 if (Value
->Length
<= Value
->MaximumLength
)
568 memcpy(Value
->Buffer
, val
,
569 min(Value
->Length
+ sizeof(WCHAR
), Value
->MaximumLength
));
570 Status
= STATUS_SUCCESS
;
574 Status
= STATUS_BUFFER_TOO_SMALL
;
583 return(STATUS_VARIABLE_NOT_FOUND
);
587 DbgCompareChannels(const void * a
, const void * b
)
589 return wcscmp((WCHAR
*)a
, ((DBG_CHANNEL
*)b
)->Name
);
593 DbgAddDebugChannel(PPROCESSINFO ppi
, WCHAR
* channel
, WCHAR
* level
, WCHAR op
)
595 DBG_CHANNEL
*ChannelEntry
;
596 UINT iLevel
, iChannel
;
598 ChannelEntry
= (DBG_CHANNEL
*)bsearch(channel
,
603 if(ChannelEntry
== NULL
)
608 iChannel
= ChannelEntry
->Id
;
609 ASSERT(iChannel
>= 0 && iChannel
< DbgChCount
);
611 if(level
== NULL
|| *level
== L
'\0' ||wcslen(level
) == 0 )
613 else if(wcsncmp(level
, L
"err", 3) == 0)
615 else if(wcsncmp(level
, L
"fixme", 5) == 0)
616 iLevel
= FIXME_LEVEL
;
617 else if(wcsncmp(level
, L
"warn", 4) == 0)
619 else if (wcsncmp(level
, L
"trace", 4) == 0)
620 iLevel
= TRACE_LEVEL
;
626 DBG_ENABLE_CHANNEL(ppi
, iChannel
, iLevel
);
630 DBG_DISABLE_CHANNEL(ppi
, iChannel
, iLevel
);
637 DbgParseDebugChannels(PPROCESSINFO ppi
, PUNICODE_STRING Value
)
639 WCHAR
*str
, *separator
, *c
, op
;
645 separator
= wcschr(str
, L
',');
646 if(separator
!= NULL
)
649 c
= wcschr(str
, L
'+');
651 c
= wcschr(str
, L
'-');
659 DbgAddDebugChannel(ppi
, c
, str
, op
);
663 }while(separator
!= NULL
);
668 BOOL
DbgInitDebugChannels()
670 WCHAR valBuffer
[100];
671 UNICODE_STRING Value
;
672 UNICODE_STRING Name
= RTL_CONSTANT_STRING(L
"DEBUGCHANNEL");
677 /* Initialize all channels to ERROR */
678 ppi
= PsGetCurrentProcessWin32Process();
679 RtlFillMemory( ppi
->DbgChannelLevel
,
680 sizeof(ppi
->DbgChannelLevel
),
683 /* Find DEBUGCHANNEL env var */
684 Value
.Buffer
= valBuffer
;
686 Value
.MaximumLength
= sizeof(valBuffer
);
687 Status
= QueryEnvironmentVariable(&Name
, &Value
);
689 /* It does not exist */
690 if(Status
== STATUS_VARIABLE_NOT_FOUND
)
692 /* There is nothing more to do */
696 /* If the buffer in the stack is not enough allocate it */
697 if(Status
== STATUS_BUFFER_TOO_SMALL
)
699 Value
.Buffer
= ExAllocatePool(PagedPool
, Value
.MaximumLength
);
700 if(Value
.Buffer
== NULL
)
705 /* Get the env var again */
706 Status
= QueryEnvironmentVariable(&Name
, &Value
);
709 /* Check for error */
710 if(!NT_SUCCESS(Status
))
712 if(Value
.Buffer
!= valBuffer
)
714 ExFreePool(Value
.Buffer
);
720 /* Parse the variable */
721 ret
= DbgParseDebugChannels(ppi
, &Value
);
724 if(Value
.Buffer
!= valBuffer
)
726 ExFreePool(Value
.Buffer
);