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 ******************************************************************/
15 extern ULONG gulFirstFree
;
16 extern ULONG gulFirstUnused
;
18 ULONG gulLogUnique
= 0;
20 /* Note: the following values need to be sorted */
21 DBG_CHANNEL DbgChannels
[DbgChCount
]={
22 {L
"EngBlt", DbgChEngBlt
},
23 {L
"EngBrush", DbgChEngBrush
},
24 {L
"EngClip", DbgChEngClip
},
25 {L
"EngCursor", DbgChEngCursor
},
26 {L
"EngDev", DbgChEngDev
},
27 {L
"EngErr", DbgChEngErr
},
28 {L
"EngEvent", DbgChEngEvent
},
29 {L
"EngGrad", DbgChEngGrad
},
30 {L
"EngLDev", DbgChEngLDev
},
31 {L
"EngLine", DbgChEngLine
},
32 {L
"EngMapping", DbgChEngMapping
},
33 {L
"EngPDev", DbgChEngPDev
},
34 {L
"EngSurface", DbgChEngSurface
},
35 {L
"EngWnd", DbgChEngWnd
},
36 {L
"EngXlate", DbgChEngXlate
},
37 {L
"GdiBitmap", DbgChGdiBitmap
},
38 {L
"GdiBlt", DbgChGdiBlt
},
39 {L
"GdiBrush", DbgChGdiBrush
},
40 {L
"GdiClipRgn", DbgChGdiClipRgn
},
41 {L
"GdiCoord", DbgChGdiCoord
},
42 {L
"GdiDC", DbgChGdiDC
},
43 {L
"GdiDCAttr", DbgChGdiDCAttr
},
44 {L
"GdiDCState", DbgChGdiDCState
},
45 {L
"GdiDev", DbgChGdiDev
},
46 {L
"GdiDib", DbgChGdiDib
},
47 {L
"GdiFont", DbgChGdiFont
},
48 {L
"GdiLine", DbgChGdiLine
},
49 {L
"GdiObj", DbgChGdiObj
},
50 {L
"GdiPalette", DbgChGdiPalette
},
51 {L
"GdiPath", DbgChGdiPath
},
52 {L
"GdiPen", DbgChGdiPen
},
53 {L
"GdiPool", DbgChGdiPool
},
54 {L
"GdiRgn", DbgChGdiRgn
},
55 {L
"GdiText", DbgChGdiText
},
56 {L
"GdiXFormObj", DbgChGdiXFormObj
},
57 {L
"UserAccel", DbgChUserAccel
},
58 {L
"UserCallback", DbgChUserCallback
},
59 {L
"UserCallProc", DbgChUserCallProc
},
60 {L
"UserCaret", DbgChUserCaret
},
61 {L
"UserClass", DbgChUserClass
},
62 {L
"UserClipbrd", DbgChUserClipbrd
},
63 {L
"UserCsr", DbgChUserCsr
},
64 {L
"UserDce", DbgChUserDce
},
65 {L
"UserDefwnd", DbgChUserDefwnd
},
66 {L
"UserDesktop", DbgChUserDesktop
},
67 {L
"UserDisplay",DbgChUserDisplay
},
68 {L
"UserEvent", DbgChUserEvent
},
69 {L
"UserFocus", DbgChUserFocus
},
70 {L
"UserHook", DbgChUserHook
},
71 {L
"UserHotkey", DbgChUserHotkey
},
72 {L
"UserIcon", DbgChUserIcon
},
73 {L
"UserInput", DbgChUserInput
},
74 {L
"UserKbd", DbgChUserKbd
},
75 {L
"UserKbdLayout", DbgChUserKbdLayout
},
76 {L
"UserMenu", DbgChUserMenu
},
77 {L
"UserMetric", DbgChUserMetric
},
78 {L
"UserMisc", DbgChUserMisc
},
79 {L
"UserMonitor", DbgChUserMonitor
},
80 {L
"UserMsg", DbgChUserMsg
},
81 {L
"UserMsgQ", DbgChUserMsgQ
},
82 {L
"UserObj", DbgChUserObj
},
83 {L
"UserPainting", DbgChUserPainting
},
84 {L
"UserProcess", DbgChUserProcess
},
85 {L
"UserProp", DbgChUserProp
},
86 {L
"UserScrollbar", DbgChUserScrollbar
},
87 {L
"UserSysparams", DbgChUserSysparams
},
88 {L
"UserTimer", DbgChUserTimer
},
89 {L
"UserThread", DbgChUserThread
},
90 {L
"UserWinpos", DbgChUserWinpos
},
91 {L
"UserWinsta", DbgChUserWinsta
},
92 {L
"UserWnd", DbgChUserWnd
}
99 CompareBacktraces(ULONG idx1
, ULONG idx2
)
103 /* Loop all stack levels */
104 for (iLevel
= 0; iLevel
< GDI_STACK_LEVELS
; iLevel
++)
106 if (GDIHandleAllocator
[idx1
][iLevel
]
107 != GDIHandleAllocator
[idx2
][iLevel
])
108 // if (GDIHandleShareLocker[idx1][iLevel]
109 // != GDIHandleShareLocker[idx2][iLevel])
120 DbgDumpGdiHandleTable(void)
122 static int leak_reported
= 0;
123 int i
, j
, idx
, nTraces
= 0;
128 DPRINT1("GDI handle abusers already reported!\n");
133 DPRINT1("Reporting GDI handle abusers:\n");
135 /* We've got serious business to do */
136 KeRaiseIrql(DISPATCH_LEVEL
, &OldIrql
);
138 /* Step through GDI handle table and find out who our culprit is... */
139 for (idx
= RESERVE_ENTRIES_COUNT
; idx
< GDI_HANDLE_COUNT
; idx
++)
141 /* If the handle is free, continue */
142 if (!IS_HANDLE_VALID(idx
)) continue;
144 /* Step through all previous backtraces */
145 for (j
= 0; j
< nTraces
; j
++)
147 /* Check if the backtrace matches */
148 if (CompareBacktraces(idx
, AllocatorTable
[j
].idx
))
150 /* It matches, increment count and break out */
151 AllocatorTable
[j
].count
++;
156 /* Did we find a new backtrace? */
159 /* Break out, if we reached the maximum */
160 if (nTraces
== MAX_BACKTRACES
) break;
162 /* Initialize this entry */
163 AllocatorTable
[j
].idx
= idx
;
164 AllocatorTable
[j
].count
= 1;
169 /* bubble sort time! weeeeee!! */
170 for (i
= 0; i
< nTraces
-1; i
++)
172 if (AllocatorTable
[i
].count
< AllocatorTable
[i
+1].count
)
174 struct DbgOpenGDIHandle temp
;
176 temp
= AllocatorTable
[i
+1];
177 AllocatorTable
[i
+1] = AllocatorTable
[i
];
179 while (j
> 0 && AllocatorTable
[j
-1].count
< temp
.count
)
181 AllocatorTable
[j
] = temp
;
185 /* Print the worst offenders... */
186 DbgPrint("Worst GDI Handle leak offenders (out of %i unique locations):\n", nTraces
);
187 for (i
= 0; i
< nTraces
&& AllocatorTable
[i
].count
> 1; i
++)
189 /* Print out the allocation count */
190 DbgPrint(" %i allocs, type = 0x%lx:\n",
191 AllocatorTable
[i
].count
,
192 GdiHandleTable
->Entries
[AllocatorTable
[i
].idx
].Type
);
194 /* Dump the frames */
195 KeRosDumpStackFrames(GDIHandleAllocator
[AllocatorTable
[i
].idx
], GDI_STACK_LEVELS
);
196 //KeRosDumpStackFrames(GDIHandleShareLocker[AllocatorTable[i].idx], GDI_STACK_LEVELS);
198 /* Print new line for better readability */
203 DbgPrint("(List terminated - the remaining entries have 1 allocation only)\n");
205 KeLowerIrql(OldIrql
);
213 DbgCaptureStackBackTace(PVOID
* pFrames
, ULONG nFramesToCapture
)
217 memset(pFrames
, 0x00, (nFramesToCapture
+ 1) * sizeof(PVOID
));
219 nFrameCount
= RtlWalkFrameChain(pFrames
, nFramesToCapture
, 0);
221 if (nFrameCount
< nFramesToCapture
)
223 nFrameCount
+= RtlWalkFrameChain(pFrames
+ nFrameCount
,
224 nFramesToCapture
- nFrameCount
,
233 DbgGdiHTIntegrityCheck()
235 ULONG i
, nDeleted
= 0, nFree
= 0, nUsed
= 0;
236 PGDI_TABLE_ENTRY pEntry
;
239 KeEnterCriticalRegion();
241 /* FIXME: Check reserved entries */
243 /* Now go through the deleted objects */
244 i
= gulFirstFree
& 0xffff;
247 pEntry
= &GdiHandleTable
->Entries
[i
];
248 if (i
> GDI_HANDLE_COUNT
)
250 DPRINT1("nDeleted=%ld\n", nDeleted
);
256 /* Check the entry */
257 if ((pEntry
->Type
& GDI_ENTRY_BASETYPE_MASK
) != 0)
260 DPRINT1("Deleted Entry has a type != 0\n");
262 if ((ULONG_PTR
)pEntry
->KernelData
>= GDI_HANDLE_COUNT
)
265 DPRINT1("Deleted entries KernelPointer too big\n");
267 if (pEntry
->UserData
!= NULL
)
270 DPRINT1("Deleted entry has UserData != 0\n");
272 if (pEntry
->ProcessId
!= 0)
275 DPRINT1("Deleted entry has ProcessId != 0\n");
278 i
= (ULONG_PTR
)pEntry
->KernelData
& 0xffff;
281 for (i
= gulFirstUnused
;
282 i
< GDI_HANDLE_COUNT
;
285 pEntry
= &GdiHandleTable
->Entries
[i
];
287 if ((pEntry
->Type
) != 0)
290 DPRINT1("Free Entry has a type != 0\n");
292 if ((ULONG_PTR
)pEntry
->KernelData
!= 0)
295 DPRINT1("Free entries KernelPointer != 0\n");
297 if (pEntry
->UserData
!= NULL
)
300 DPRINT1("Free entry has UserData != 0\n");
302 if (pEntry
->ProcessId
!= 0)
305 DPRINT1("Free entry has ProcessId != 0\n");
310 for (i
= RESERVE_ENTRIES_COUNT
; i
< GDI_HANDLE_COUNT
; i
++)
315 pEntry
= &GdiHandleTable
->Entries
[i
];
317 Handle
= (HGDIOBJ
)((Type
<< GDI_ENTRY_UPPER_SHIFT
) + i
);
319 if (Type
& GDI_ENTRY_BASETYPE_MASK
)
321 if (pEntry
->KernelData
== NULL
)
324 DPRINT1("Used entry has KernelData == 0\n");
326 if (pEntry
->KernelData
<= MmHighestUserAddress
)
329 DPRINT1("Used entry invalid KernelData\n");
331 if (((POBJ
)(pEntry
->KernelData
))->hHmgr
!= Handle
)
334 DPRINT1("Used entry %ld, has invalid hHmg %p (expected: %p)\n",
335 i
, ((POBJ
)(pEntry
->KernelData
))->hHmgr
, Handle
);
341 if (RESERVE_ENTRIES_COUNT
+ nDeleted
+ nFree
+ nUsed
!= GDI_HANDLE_COUNT
)
344 DPRINT1("Number of all entries incorrect: RESERVE_ENTRIES_COUNT = %ld, nDeleted = %ld, nFree = %ld, nUsed = %ld\n",
345 RESERVE_ENTRIES_COUNT
, nDeleted
, nFree
, nUsed
);
348 KeLeaveCriticalRegion();
353 #endif /* GDI_DEBUG */
357 DbgDumpLockedGdiHandles()
362 for (i
= RESERVE_ENTRIES_COUNT
; i
< GDI_HANDLE_COUNT
; i
++)
364 PENTRY pentry
= &gpentHmgr
[i
];
368 POBJ pobj
= pentry
->einfo
.pobj
;
369 if (pobj
->cExclusiveLock
> 0)
371 DPRINT1("Locked object: %lx, type = %lx. allocated from:\n",
373 DBG_DUMP_EVENT_LIST(&pobj
->slhLog
);
382 DbgLogEvent(PSLIST_HEADER pslh
, LOG_EVENT_TYPE nEventType
, LPARAM lParam
)
386 /* Log a maximum of 100 events */
387 if (QueryDepthSList(pslh
) >= 1000) return;
389 /* Allocate a logentry */
390 pLogEntry
= EngAllocMem(0, sizeof(LOGENTRY
), 'golG');
391 if (!pLogEntry
) return;
394 pLogEntry
->nEventType
= nEventType
;
395 pLogEntry
->ulUnique
= InterlockedIncrement((LONG
*)&gulLogUnique
);
396 pLogEntry
->dwProcessId
= HandleToUlong(PsGetCurrentProcessId());
397 pLogEntry
->dwThreadId
= HandleToUlong(PsGetCurrentThreadId());
398 pLogEntry
->lParam
= lParam
;
400 /* Capture a backtrace */
401 DbgCaptureStackBackTace(pLogEntry
->apvBackTrace
, 20);
406 case EVENT_CREATE_HANDLE
:
407 case EVENT_REFERENCE
:
408 case EVENT_DEREFERENCE
:
413 case EVENT_SET_OWNER
:
418 /* Push it on the list */
419 InterlockedPushEntrySList(pslh
, &pLogEntry
->sleLink
);
422 #define REL_ADDR(va) ((ULONG_PTR)va - (ULONG_PTR)&__ImageBase)
425 DbgPrintEvent(PLOGENTRY pLogEntry
)
429 switch (pLogEntry
->nEventType
)
431 case EVENT_ALLOCATE
: pstr
= "Allocate"; break;
432 case EVENT_CREATE_HANDLE
: pstr
= "CreatHdl"; break;
433 case EVENT_REFERENCE
: pstr
= "Ref"; break;
434 case EVENT_DEREFERENCE
: pstr
= "Deref"; break;
435 case EVENT_LOCK
: pstr
= "Lock"; break;
436 case EVENT_UNLOCK
: pstr
= "Unlock"; break;
437 case EVENT_DELETE
: pstr
= "Delete"; break;
438 case EVENT_FREE
: pstr
= "Free"; break;
439 case EVENT_SET_OWNER
: pstr
= "SetOwner"; break;
440 default: pstr
= "Unknown"; break;
443 DbgPrint("[%ld] %03x:%03x %.8s val=%p <%lx,%lx,%lx,%lx>\n",
445 pLogEntry
->dwProcessId
,
446 pLogEntry
->dwThreadId
,
449 REL_ADDR(pLogEntry
->apvBackTrace
[2]),
450 REL_ADDR(pLogEntry
->apvBackTrace
[3]),
451 REL_ADDR(pLogEntry
->apvBackTrace
[4]),
452 REL_ADDR(pLogEntry
->apvBackTrace
[5]));
457 DbgDumpEventList(PSLIST_HEADER pslh
)
462 while ((psle
= InterlockedPopEntrySList(pslh
)))
464 pLogEntry
= CONTAINING_RECORD(psle
, LOGENTRY
, sleLink
);
465 DbgPrintEvent(pLogEntry
);
472 DbgCleanupEventList(PSLIST_HEADER pslh
)
477 while ((psle
= InterlockedPopEntrySList(pslh
)))
479 pLogEntry
= CONTAINING_RECORD(psle
, LOGENTRY
, sleLink
);
480 EngFreeMem(pLogEntry
);
486 GdiDbgPreServiceHook(ULONG ulSyscallId
, PULONG_PTR pulArguments
)
488 PTHREADINFO pti
= (PTHREADINFO
)PsGetCurrentThreadWin32Thread();
489 if (pti
&& pti
->cExclusiveLocks
!= 0)
491 DbgPrint("FATAL: Win32DbgPreServiceHook(0x%lx): There are %ld exclusive locks!\n",
492 ulSyscallId
, pti
->cExclusiveLocks
);
493 DbgDumpLockedGdiHandles();
501 GdiDbgPostServiceHook(ULONG ulSyscallId
, ULONG_PTR ulResult
)
503 PTHREADINFO pti
= (PTHREADINFO
)PsGetCurrentThreadWin32Thread();
504 if (pti
&& pti
->cExclusiveLocks
!= 0)
506 DbgPrint("FATAL: Win32DbgPostServiceHook(0x%lx): There are %ld exclusive locks!\n",
507 ulSyscallId
, pti
->cExclusiveLocks
);
508 DbgDumpLockedGdiHandles();
515 QueryEnvironmentVariable(PUNICODE_STRING Name
,
516 PUNICODE_STRING Value
)
525 /* Ugly HACK for ReactOS system threads */
528 return(STATUS_VARIABLE_NOT_FOUND
);
531 Peb
= NtCurrentPeb();
535 return(STATUS_VARIABLE_NOT_FOUND
);
538 Environment
= Peb
->ProcessParameters
->Environment
;
540 if (Environment
== NULL
)
542 return(STATUS_VARIABLE_NOT_FOUND
);
551 wcs
= wcschr(wcs
, L
'=');
554 wcs
= var
.Buffer
+ wcslen(var
.Buffer
);
558 var
.Length
= var
.MaximumLength
= (wcs
- var
.Buffer
) * sizeof(WCHAR
);
562 if (RtlEqualUnicodeString(&var
, Name
, TRUE
))
564 Value
->Length
= (wcs
- val
) * sizeof(WCHAR
);
565 if (Value
->Length
<= Value
->MaximumLength
)
567 memcpy(Value
->Buffer
, val
,
568 min(Value
->Length
+ sizeof(WCHAR
), Value
->MaximumLength
));
569 Status
= STATUS_SUCCESS
;
573 Status
= STATUS_BUFFER_TOO_SMALL
;
582 return(STATUS_VARIABLE_NOT_FOUND
);
586 DbgCompareChannels(const void * a
, const void * b
)
588 return wcscmp((WCHAR
*)a
, ((DBG_CHANNEL
*)b
)->Name
);
592 DbgAddDebugChannel(PPROCESSINFO ppi
, WCHAR
* channel
, WCHAR
* level
, WCHAR op
)
594 DBG_CHANNEL
*ChannelEntry
;
595 UINT iLevel
, iChannel
;
597 ChannelEntry
= (DBG_CHANNEL
*)bsearch(channel
,
602 if(ChannelEntry
== NULL
)
607 iChannel
= ChannelEntry
->Id
;
608 ASSERT(iChannel
>= 0 && iChannel
< DbgChCount
);
610 if(level
== NULL
|| *level
== L
'\0' ||wcslen(level
) == 0 )
612 else if(wcsncmp(level
, L
"err", 3) == 0)
614 else if(wcsncmp(level
, L
"fixme", 5) == 0)
615 iLevel
= FIXME_LEVEL
;
616 else if(wcsncmp(level
, L
"warn", 4) == 0)
618 else if (wcsncmp(level
, L
"trace", 4) == 0)
619 iLevel
= TRACE_LEVEL
;
625 DBG_ENABLE_CHANNEL(ppi
, iChannel
, iLevel
);
629 DBG_DISABLE_CHANNEL(ppi
, iChannel
, iLevel
);
636 DbgParseDebugChannels(PPROCESSINFO ppi
, PUNICODE_STRING Value
)
638 WCHAR
*str
, *separator
, *c
, op
;
644 separator
= wcschr(str
, L
',');
645 if(separator
!= NULL
)
648 c
= wcschr(str
, L
'+');
650 c
= wcschr(str
, L
'-');
658 DbgAddDebugChannel(ppi
, c
, str
, op
);
662 }while(separator
!= NULL
);
667 BOOL
DbgInitDebugChannels()
669 WCHAR valBuffer
[100];
670 UNICODE_STRING Value
;
671 UNICODE_STRING Name
= RTL_CONSTANT_STRING(L
"DEBUGCHANNEL");
676 /* Initialize all channels to ERROR */
677 ppi
= PsGetCurrentProcessWin32Process();
678 RtlFillMemory( ppi
->DbgChannelLevel
,
679 sizeof(ppi
->DbgChannelLevel
),
682 /* Find DEBUGCHANNEL env var */
683 Value
.Buffer
= valBuffer
;
685 Value
.MaximumLength
= sizeof(valBuffer
);
686 Status
= QueryEnvironmentVariable(&Name
, &Value
);
688 /* It does not exist */
689 if(Status
== STATUS_VARIABLE_NOT_FOUND
)
691 /* There is nothing more to do */
695 /* If the buffer in the stack is not enough allocate it */
696 if(Status
== STATUS_BUFFER_TOO_SMALL
)
698 Value
.Buffer
= ExAllocatePool(PagedPool
, Value
.MaximumLength
);
699 if(Value
.Buffer
== NULL
)
704 /* Get the env var again */
705 Status
= QueryEnvironmentVariable(&Name
, &Value
);
708 /* Check for error */
709 if(!NT_SUCCESS(Status
))
711 if(Value
.Buffer
!= valBuffer
)
713 ExFreePool(Value
.Buffer
);
719 /* Parse the variable */
720 ret
= DbgParseDebugChannels(ppi
, &Value
);
723 if(Value
.Buffer
!= valBuffer
)
725 ExFreePool(Value
.Buffer
);