/*********************************************** Name: gdistat.c Description: This tool is designed to retrieve information about GDI and USER objects from their handle managers. It runs as an user application. Won't work with 3.51 and earlier releases It can retrieve the number of objects associated with each process, as well as the total number of objects in the system and public objects. On checked builds it can also retrieve the same data as displayed with the !gdiexts.dumphmgr function. Future Changes: The following information describes the changes necessary to add new queries in future (for example memory information): -Add a new entry in the indexlist array in gdistats.c -increment the value of NUM_INDEX_VALUES in gdistats.h -add a new #define GS_xxxxx to ntgdi.h -add code to NtGdiGetStats in hmgrapi.cxx to deal with the new item. Ensure that the buffer size specified in the indexlist array is large enough to contain this information. OR add code to HMGetStats in handtabl.c to add user queries -add code to DisplayResults() in gdistats.c to present the results correctly. The default is a raw hex dump. Changes to the organization of the handle tables, or the addition of new handle types would require changes to the aszUserObjType or aszGdiObjType structures. Uses Unicode. Access must be enabled in the registry through the Session Manager/global flag/FLG_POOL_ENABLE_TAGGING bit Other files: gsysqry.c History: 01-August-1995 -by- Andrew Skowronski [t-andsko] Added user query, new interface, sum calculated 14-June-1995 -by- Andrew Skowronski [t-andsko] Wrote it ************************************************/ #include #include #include "gs_dlg.h" //dialog box child window values #include "gdistats.h" //These values must match with those used by NtGdiGetStats #define GS_NUM_OBJS_ALL 0 //Gdi queries #define GS_HANDOBJ_CURRENT 1 #define GS_HANDOBJ_MAX 2 #define GS_HANDOBJ_ALLOC 3 #define GS_LOOKASIDE_INFO 4 #define GU_NUM_OBJS_ALL 5 //User information /***************************** GLOBALS ****************************/ //This structure gives information about all the queries that are allowed struct { UINT nIndex; //Index value LPTSTR szLabel; //String to describe function UINT nSize; //Size of the buffer to store the results BOOL bPublic; //These mark determine whether the radio buttons should //be enabled to select Pubic Objects or a specific process BOOL bOneProcess; } indexlist [] = { //Gdi object information GS_NUM_OBJS_ALL, _TEXT("Gdi objects per type"), sizeof(DWORD) * NUM_GDI_OBJS,TRUE,TRUE, //These queries get information from structures that //are only kept in checked builds. It fails elegantly if //run on the free build GS_HANDOBJ_CURRENT,_TEXT("Gdi Handles/Objects current"), sizeof(DWORD) * NUM_GDI_OBJS * 2,FALSE,FALSE, GS_HANDOBJ_MAX, _TEXT("Gdi Handles/Objects maximum"), sizeof(DWORD) * NUM_GDI_OBJS * 2,FALSE,FALSE, GS_HANDOBJ_ALLOC, _TEXT("Gdi Handles/Objects allocated"), sizeof(DWORD) * NUM_GDI_OBJS * 2,FALSE,FALSE, //This information is also only for checked builds. //It compares the number of lookaside hits with the total //number of objects allocated GS_LOOKASIDE_INFO, _TEXT("Gdi Lookaside hit rate"), sizeof(DWORD) * NUM_GDI_OBJS * 2,FALSE,FALSE, //The user handle table information GU_NUM_OBJS_ALL, _TEXT("User objects per type"), sizeof(DWORD) * NUM_USER_OBJS,FALSE,TRUE }; //Each object type for Gdi Objects LPTSTR aszGdiObjType[] = { _TEXT("DEF "), // 0 _TEXT("DC "), // 1 _TEXT("LDB "), // 2 _TEXT("PDB "), // 3 _TEXT("RGN "), // 4 _TEXT("SURF "), // 5 _TEXT("XFORM "), // 6 _TEXT("PATH "), // 7 _TEXT("PAL "), // 8 _TEXT("FD "), // 9 _TEXT("LFONT "), // 10 _TEXT("RFONT "), // 11 _TEXT("PFE "), // 12 _TEXT("PFT "), // 13 _TEXT("IDB "), // 14 _TEXT("XLATE "), // 15 _TEXT("BRUSH "), // 16 _TEXT("PFF "), // 17 _TEXT("CACHE "), // 18 _TEXT("SPACE "), // 19 _TEXT("DBRUSH "), // 20 _TEXT("META "), // 21 _TEXT("EFSTA "), // 22 _TEXT("BMFD "), // 23 _TEXT("VTFD "), // 24 _TEXT("TTFD "), // 25 _TEXT("RC "), // 26 _TEXT("TEMP "), // 27 _TEXT("DRVOBJ "), // 28 _TEXT("DCIOBJ "), // 29 _TEXT("SPOOL ") // 30 }; //Each object type for User objects LPTSTR aszUserObjType[] = { _TEXT("Free "), // 0 _TEXT("Window "), // 1 _TEXT("Menu "), // 2 _TEXT("Icon/Cursor "), // 3 _TEXT("SWP Structure "), // 4 _TEXT("Hook "), // 5 _TEXT("ClipData "), // 6 _TEXT("CallProcData "), // 7 _TEXT("Accelerator "), // 8 _TEXT("DDE Access "), // 9 _TEXT("DDE Conv "), // 10 _TEXT("DDE Transaction "), // 11 _TEXT("Monitor "), // 12 _TEXT("Keyboard layout "), // 13 _TEXT("Keyboard File "), // 14 _TEXT("Winevent "), // 15 _TEXT("Timer "), // 16 _TEXT("InputContext "), // 17 _TEXT("Undefined ") // Should never occur }; // This ones replace HMGetStats and are private in user32.dll #define QUC_PID_TOTAL 0xffffffff #define QUERYUSER_HANDLES 0x1 BOOL (WINAPI *QueryUserCounters)(DWORD, LPVOID, DWORD, LPVOID, DWORD ); int NtGdiGetStats(HANDLE,int,int,PVOID,UINT); /*********************************************** Main Procedure ***********************************************/ int __cdecl main () { HANDLE hWndDialog; MSG msg; TCHAR szUser32DllPath[MAX_PATH+15]; HMODULE hUser32Module; HANDLE hInstance; hInstance = GetModuleHandle(NULL); if (!GetSystemDirectory(szUser32DllPath, MAX_PATH+1)) { return 0; } wcscat( szUser32DllPath, L"\\user32.dll"); hUser32Module = GetModuleHandle(szUser32DllPath); if (!hUser32Module) { return 0; } QueryUserCounters = (BOOL (WINAPI *)(DWORD, LPVOID, DWORD, LPVOID, DWORD )) GetProcAddress(hUser32Module, "QueryUserCounters"); if (!QueryUserCounters) { return 0; } hWndDialog = CreateDialogParam (hInstance, MAKEINTRESOURCE (GD_DIALOG), NULL, (DLGPROC) GSDlgProc, (LONG) 0); while(GetMessage (&msg, NULL, 0, 0)) if (!IsDialogMessage (hWndDialog, &msg)) { TranslateMessage (&msg); DispatchMessage (&msg); } DestroyWindow (hWndDialog); return 0; } /************************************************* Dialog Procedure *************************************************/ BOOL CALLBACK GSDlgProc (HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam) { switch (wMsg) { //Initialize everything case WM_INITDIALOG : SetClassLongPtr(hWnd, GCLP_HICON, (INT_PTR)LoadIcon((HINSTANCE)GetWindowLongPtr(hWnd, GWLP_HINSTANCE), _TEXT("GSICON"))); if (!UpdateProcessList(hWnd)) PostQuitMessage(0); UpdateIndexList(hWnd); UpdateRadioButtons(hWnd); break; case WM_CLOSE : PostQuitMessage (0); break; case WM_COMMAND: switch (LOWORD (wParam)) { //The list box selection has been changed case GD_INDEX : if (HIWORD(wParam) == CBN_SELCHANGE) { UpdateRadioButtons(hWnd); DoQuery(hWnd); } else return FALSE; break; //Re-run the query when we double click //on the process case GD_PROCESS : if (HIWORD(wParam) == CBN_DBLCLK) DoQuery(hWnd); //Refresh the process list every time //the list is displayed to avoid //the need for a refresh button else if (HIWORD(wParam) == CBN_DROPDOWN) UpdateProcessList(hWnd); else return FALSE; break; //Exit button case GD_EXIT : PostQuitMessage (0); break; //Run button case GD_GO : DoQuery(hWnd); break; default : return FALSE; } default : return FALSE; } return TRUE; } /******************************************************** UpdateProcessList This function fills in the process combo box with the list of processes running at the time of this call It returns FALSE if not successful. *********************************************************/ BOOL UpdateProcessList(HWND hWnd) { HWND hProcessList; //Handle to process list box PVOID pProcessInfo; //pointer to process information TCHAR szProcStr[40]; //Buffer for procedure name strings DWORD dwStatus; LONG lNotLast; LRESULT nIndex; //Position of new string in list LRESULT lSavedPos; //Saved position in list HANDLE hProc; //Handle to the process hProcessList = GetDlgItem (hWnd, GD_PROCESS); //Remember currently selected item lSavedPos = SendMessage(hProcessList, CB_GETCURSEL, 0 ,0); SendMessage(hProcessList, WM_SETREDRAW, FALSE, 0); SendMessage(hProcessList, CB_RESETCONTENT, 0, 0); //Fill in the process information dwStatus = FindProcessList(&pProcessInfo); if (!dwStatus) { //Print the process information to screen do { lNotLast = GetNextProcString(&pProcessInfo,szProcStr,&hProc); nIndex = SendMessage(hProcessList,CB_ADDSTRING,0,(LPARAM)szProcStr); //Associate the handle value with the list box item SendMessage(hProcessList,CB_SETITEMDATA,(WPARAM) nIndex,(LPARAM) hProc); } while (lNotLast); } else { //Unrecoverable error - could not get process information MessageBox( hWnd,_TEXT("Unable to create process listing"),_TEXT("ERROR"),MB_OK ); return FALSE; } LocalFree (pProcessInfo); //Attempt to restore the cursor position if (lSavedPos != CB_ERR) SendMessage(hProcessList, CB_SETCURSEL, (WPARAM) lSavedPos, 0); else SendMessage(hProcessList, CB_SETCURSEL, 0, 0); SendMessage(hProcessList, WM_SETREDRAW, TRUE, 0); return TRUE; } /******************************************************** UpdateIndexList This function fills in the index list with the list of possible strings associated with each index. The information comes from the index list array *********************************************************/ VOID UpdateIndexList(HWND hWnd) { HWND hIndexCombo; LONG iCnt; hIndexCombo = GetDlgItem (hWnd, GD_INDEX); SendMessage(hIndexCombo, WM_SETREDRAW, FALSE, 0); SendMessage(hIndexCombo, CB_RESETCONTENT, 0, 0); //Fill in the names of the various predefined index values from the indexlist structure for (iCnt = 0; iCnt < NUM_INDEX_VALUES; iCnt++) { SendMessage(hIndexCombo, CB_ADDSTRING, 0, (LPARAM)indexlist[iCnt].szLabel); } SendMessage(hIndexCombo, CB_SETCURSEL, 0, 0); //Set the first item to be default SendMessage(hIndexCombo, WM_SETREDRAW, TRUE, 0); } /*******************************************************\ ValidateProcessSelection Returns the handle to the process selected, SEL_PUBLIC, SEL_ALL if PUBLIC or ALL are selected, and 0 otherwise \*******************************************************/ HANDLE ValidateProcessSelection (HWND hMainWindow, HWND hErrorWindow) { HWND hProcessList; HANDLE hSelProcHandle = 0; LRESULT nProcIndex; hProcessList = GetDlgItem (hMainWindow, GD_PROCESS); //Check currently seleted process value nProcIndex = SendMessage (hProcessList, CB_GETCURSEL, 0, 0); if (nProcIndex != CB_ERR) { //Get the client handle hSelProcHandle = (PVOID) SendMessage(hProcessList,CB_GETITEMDATA,(WPARAM) nProcIndex,0L); } else { ErrorString(hErrorWindow, _TEXT("ERROR: Must select a process")); } return hSelProcHandle; } /*******************************************************\ ValidateIndexSelection Takes the current selection of the index combo box, and verifies that it is a valid item Returns the value of the index or -1 if error \*******************************************************/ LONG ValidateIndexSelection (HWND hMainWindow, HWND hErrorWindow) { HWND hIndexCombo; LRESULT nListIndex; LONG nQueryIndex = -1; TCHAR IndexStr[40]; //the actual data entered in the edit box hIndexCombo = GetDlgItem (hMainWindow, GD_INDEX); //Check currently selected Index value nListIndex = SendMessage (hIndexCombo, CB_GETCURSEL, 0, 0); if (nListIndex != CB_ERR) { //We don't use the index as the value but the actual constant //value from the structure nQueryIndex = indexlist[nListIndex].nIndex; } else //value was not selected out of the list { //See if any string has been entered if (!SendMessage (hIndexCombo, WM_GETTEXT, 40, (LPARAM)IndexStr)) { ErrorString(hErrorWindow, _TEXT("ERROR: Need to Select Index")); } else { //The string should be a valid number (but not zero) nQueryIndex = _ttoi(IndexStr); if (nQueryIndex == 0) { nQueryIndex = -1; ErrorString(hErrorWindow,_TEXT("ERROR: Index must be from list or a number")); } } } return nQueryIndex; } /******************************************************* UpdateRadioButtons Depending on the currently selected index value, set which radio buttons are enabled. Handles the case where nothing is set, and the case where a selected button becomes disabled. Assumes the "All Processes" button will never be disabled \*******************************************************/ VOID UpdateRadioButtons(HWND hWnd) { HWND hResultList; HWND hRadio_All; HWND hRadio_One; HWND hRadio_Public; HWND hProcessList; LONG nQueryIndex; int iCurrentChecked; hResultList = GetDlgItem(hWnd, GD_RESULT); hRadio_All = GetDlgItem(hWnd, GD_RADIO_ALL); hRadio_Public = GetDlgItem(hWnd, GD_RADIO_PUBLIC); hRadio_One = GetDlgItem(hWnd, GD_RADIO_ONE); hProcessList = GetDlgItem(hWnd, GD_PROCESS); nQueryIndex = ValidateIndexSelection(hWnd, hResultList); //Save current setting of radio buttons, if any iCurrentChecked = WhichRadioButton(hWnd); if (nQueryIndex != -1) { EnableWindow(hRadio_Public,indexlist[nQueryIndex].bPublic); EnableWindow(hRadio_One,indexlist[nQueryIndex].bOneProcess); //Also disable/enable process list EnableWindow(hProcessList,indexlist[nQueryIndex].bOneProcess); } //Return the setting if the item is not now disabled if ((iCurrentChecked == SEL_PUBLIC) && indexlist[nQueryIndex].bPublic) CheckRadioButton(hWnd,GD_RADIO_ALL,GD_RADIO_ONE,GD_RADIO_PUBLIC); else if ((iCurrentChecked == SEL_ONE) && indexlist[nQueryIndex].bOneProcess) CheckRadioButton(hWnd,GD_RADIO_ALL,GD_RADIO_ONE,GD_RADIO_ONE); else CheckRadioButton(hWnd,GD_RADIO_ALL,GD_RADIO_ONE,GD_RADIO_ALL); } /******************************************************* int WhichRadioButton(HWND hWnd) Returns the selected radio button (and returns SEL_ALL if nothing is selected) \*******************************************************/ int WhichRadioButton(HWND hWnd) { HWND hRadio_One; HWND hRadio_Public; hRadio_Public = GetDlgItem(hWnd, GD_RADIO_PUBLIC); hRadio_One = GetDlgItem(hWnd, GD_RADIO_ONE); if (SendMessage(hRadio_Public, BM_GETCHECK, 0, 0)) return SEL_PUBLIC; else if (SendMessage(hRadio_One, BM_GETCHECK, 0, 0)) return SEL_ONE; else return SEL_ALL; } /******************************************************** DoQuery This function does error checking on the user's parameter choices, then calls the GetGdiStats function. In the case of an error the error message is displayed in the result window *********************************************************/ VOID DoQuery(HWND hWnd) { HWND hResultList; BOOL bOk = TRUE; UINT nQueryResult = 0; //Parameters for the call LONG nQueryIndex; HANDLE hSelProcHandle = NULL; //the handle of the process selected UINT cjResultSize; PVOID pvResultBuf; LONG lPidType; TCHAR szOutputStr[40]; //Get handles hResultList = GetDlgItem (hWnd, GD_RESULT); //Erase results window SendMessage(hResultList, WM_SETREDRAW, FALSE, 0); SendMessage(hResultList, LB_RESETCONTENT, 0, 0); nQueryIndex = ValidateIndexSelection(hWnd, hResultList); cjResultSize = indexlist[nQueryIndex].nSize; switch (WhichRadioButton(hWnd)) { case (SEL_PUBLIC) : lPidType = OBJS_PUBLIC; break; case (SEL_ALL) : lPidType = OBJS_ALL; break; default : lPidType = OBJS_CURRENT; hSelProcHandle = ValidateProcessSelection(hWnd, hResultList); if (!hSelProcHandle) bOk = FALSE; break; } bOk = bOk && (nQueryIndex != -1) && (cjResultSize); //Allocate memory for results if (bOk) { pvResultBuf = (PVOID) LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, cjResultSize); if (pvResultBuf == NULL) { bOk = FALSE; _stprintf(szOutputStr, _TEXT("ERROR: Memory Allocation failed (0x%lx)"), GetLastError()); ErrorString(hResultList, szOutputStr); } } //Now we can make the call if (bOk) { if (nQueryIndex != GU_NUM_OBJS_ALL) { nQueryResult = NtGdiGetStats(hSelProcHandle,nQueryIndex,lPidType,pvResultBuf,cjResultSize); } else { DWORD pid; pid = (lPidType == OBJS_CURRENT) ? HandleToLong(hSelProcHandle) : QUC_PID_TOTAL; nQueryResult = QueryUserCounters(QUERYUSER_HANDLES, &pid, sizeof(DWORD), pvResultBuf, sizeof(DWORD) * NUM_USER_OBJS); } if (!nQueryResult) DisplayResults(hResultList,pvResultBuf,cjResultSize,nQueryIndex); else { _stprintf(szOutputStr,_TEXT("ERROR: Query failed (code 0x%lx)"),nQueryResult); ErrorString(hResultList, szOutputStr); } } LocalFree(pvResultBuf); //Now show what has been written onto the results list box SendMessage(hResultList, WM_SETREDRAW, TRUE, 0); } /***************************************************************\ DisplayResults This function displays the results of the query in the Result Window \***************************************************************/ VOID DisplayResults(HWND hResultWindow, PVOID pResults, UINT cjResultSize, LONG nIndex) { DWORD * pdwResults; TCHAR szResultStr[60]; LONG cObjSum = 0; LONG cObjPos = 0; pdwResults = (DWORD *) pResults; //The output format is based on the type of query we are running switch(nIndex) { case (GS_NUM_OBJS_ALL) : case (GU_NUM_OBJS_ALL) : while (cjResultSize) { //Only print out non-zero results if (*pdwResults) { //Print out the results with the associated object's name if (nIndex == GS_NUM_OBJS_ALL) { _stprintf(szResultStr, _TEXT("%s%6d"), aszGdiObjType[cObjPos], *pdwResults); } else { _stprintf(szResultStr, _TEXT("%s%6d"), aszUserObjType[cObjPos], *pdwResults); } SendMessage(hResultWindow, LB_ADDSTRING, 0, (LPARAM) szResultStr); cObjSum += *pdwResults; } pdwResults++; cObjPos++; cjResultSize -= sizeof(DWORD); } //print out the sum information if (!cObjSum) _stprintf(szResultStr, _TEXT("No gdi objects")); else _stprintf(szResultStr, _TEXT("TOTAL %6d"),cObjSum); SendMessage(hResultWindow, LB_ADDSTRING, 0, (LPARAM) szResultStr); break; case (GS_HANDOBJ_CURRENT) : case (GS_HANDOBJ_MAX) : case (GS_HANDOBJ_ALLOC) : case (GS_LOOKASIDE_INFO) : for (cObjPos = 0; cObjPos < NUM_GDI_OBJS; cObjPos++) { if (pdwResults[cObjPos] || pdwResults[cObjPos + NUM_GDI_OBJS]) { _stprintf(szResultStr, _TEXT("%s %6u %6u"), aszGdiObjType[cObjPos], pdwResults[cObjPos], pdwResults[cObjPos + NUM_GDI_OBJS]); SendMessage(hResultWindow, LB_ADDSTRING, 0, (LPARAM) szResultStr); //just want to know if there is at least one entry cObjSum = 1; } } if (cObjSum) { if (nIndex != GS_LOOKASIDE_INFO) { _stprintf(szResultStr, _TEXT("(Column 1 is handle count,")); SendMessage(hResultWindow,LB_ADDSTRING, 0 , (LPARAM) szResultStr); _stprintf(szResultStr, _TEXT("Column 2 is object count)")); SendMessage(hResultWindow,LB_ADDSTRING, 0 , (LPARAM) szResultStr); } else { _stprintf(szResultStr, _TEXT("(Column 1 is number of hits,")); SendMessage(hResultWindow,LB_ADDSTRING, 0 , (LPARAM) szResultStr); _stprintf(szResultStr, _TEXT("Column 2 is total allocations)")); SendMessage(hResultWindow,LB_ADDSTRING, 0 , (LPARAM) szResultStr); } } else { //All zero results indicate that the system is not //checked _stprintf(szResultStr, _TEXT("Valid for Checked builds only")); SendMessage(hResultWindow,LB_ADDSTRING, 0 , (LPARAM) szResultStr); } break; default : //Raw dump while (cjResultSize) { _stprintf(szResultStr, _TEXT("Result: 0x%lx"), *pdwResults); SendMessage(hResultWindow, LB_ADDSTRING, 0, (LPARAM) szResultStr); pdwResults++; cjResultSize -= sizeof(DWORD); } break; } } /************************************************* ErrorString This function outputs a string into the error window. It does not erase the entire window in case more than one line of errorstrings are to be displayed ***************************************************/ VOID ErrorString(HWND hErrorList, LPTSTR szErrorString) { SendMessage(hErrorList, LB_ADDSTRING, 0, (LPARAM) szErrorString); }