/* *@@sourcefile memdebug_win.c: * implementation of the PM heap debugging window, * if the __XWPMEMDEBUG__ #define is set in setup.h. * * This has been extracted from memdebug.c to allow * linking with non-PM programs. * *@@added V0.9.6 (2000-11-12) [umoeller] */ /* * Copyright (C) 2000-2005 Ulrich M”ller. * This program is part of the XWorkplace package. * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, in version 2 as it comes in the COPYING * file of the XWorkplace main distribution. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #define OS2EMX_PLAIN_CHAR // this is needed for "os2emx.h"; if this is defined, // emx will define PSZ as _signed_ char, otherwise // as unsigned char #define INCL_DOSSEMAPHORES #define INCL_DOSEXCEPTIONS #define INCL_DOSPROCESS #define INCL_DOSERRORS #define INCL_WINWINDOWMGR #define INCL_WINFRAMEMGR #define INCL_WINCOUNTRY #define INCL_WINSYS #define INCL_WINMENUS #define INCL_WINSTDCNR #include #include #include // #include #include #include "helpers\tree.h" #define DONT_REPLACE_MALLOC // never do debug memory for this #define MEMDEBUG_PRIVATE #include "setup.h" #ifdef __XWPMEMDEBUG__ #include "helpers\cnrh.h" #include "helpers\except.h" // #include "helpers\memdebug.h" // included by setup.h already #include "helpers\nls.h" #include "helpers\stringh.h" #include "helpers\winh.h" /* *@@category: Helpers\C helpers\Heap debugging */ /* ****************************************************************** * * Global variables * ********************************************************************/ PSZ G_pszMemCnrTitle = NULL; HWND G_hwndMemDebugMenu = NULLHANDLE; /* ****************************************************************** * * Heap debugging window * ********************************************************************/ /* *@@ MEMRECORD: * *@@added V0.9.1 (99-12-04) [umoeller] */ typedef struct _MEMRECORD { RECORDCORE recc; ULONG ulIndex; CDATE cdateAllocated; CTIME ctimeAllocated; PSZ pszFreed; ULONG ulTID; PSZ pszFunction; // points to szFunction CHAR szFunction[400]; PSZ pszSource; // points to szSource CHAR szSource[CCHMAXPATH]; ULONG ulLine; ULONG ulAddress; // has pAfterMagic, invisible, only // for sorting V0.9.14 (2001-08-01) [umoeller] PSZ pszAddress; // points to szAddress CHAR szAddress[20]; ULONG ulSize; } MEMRECORD, *PMEMRECORD; /* ULONG ulHeapItemsCount1, ulHeapItemsCount2; ULONG ulTotalAllocated, ulTotalFreed; PMEMRECORD pMemRecordThis = NULL; PSZ pszMemCnrTitle = NULL; */ #if 0 /* *@@ fncbMemHeapWalkCount: * callback func for _heap_walk function used for * fnwpMemDebug. * *@@added V0.9.1 (99-12-04) [umoeller] */ int fncbMemHeapWalkCount(const void *pObject, size_t Size, int useflag, int status, const char *filename, size_t line) { // skip all the items which seem to be // internal to the runtime if ((filename) || (useflag == _FREEENTRY)) { ulHeapItemsCount1++; if (useflag == _FREEENTRY) ulTotalFreed += Size; else ulTotalAllocated += Size; } return 0; } /* *@@ fncbMemHeapWalkFill: * callback func for _heap_walk function used for * fnwpMemDebug. * *@@added V0.9.1 (99-12-04) [umoeller] */ int fncbMemHeapWalkFill(const void *pObject, size_t Size, int useflag, int status, const char *filename, size_t line) { // skip all the items which seem to be // internal to the runtime if ((filename) || (useflag == _FREEENTRY)) { ulHeapItemsCount2++; if ((pMemRecordThis) && (ulHeapItemsCount2 < ulHeapItemsCount1)) { pMemRecordThis->ulIndex = ulHeapItemsCount2 - 1; pMemRecordThis->pObject = pObject; pMemRecordThis->useflag = useflag; pMemRecordThis->status = status; pMemRecordThis->filename = filename; pMemRecordThis->pszAddress = pMemRecordThis->szAddress; pMemRecordThis->ulSize = Size; pMemRecordThis->pszSource = pMemRecordThis->szSource; pMemRecordThis->ulLine = line; pMemRecordThis = (PMEMRECORD)pMemRecordThis->recc.preccNextRecord; } else return 1; // stop } return 0; } /* *@@ CreateRecordsVAC: * *@@added V0.9.3 (2000-04-10) [umoeller] */ VOID CreateRecordsVAC(HWND hwndCnr) { // count heap items ulHeapItemsCount1 = 0; ulTotalFreed = 0; ulTotalAllocated = 0; _heap_walk(fncbMemHeapWalkCount); pMemRecordFirst = (PMEMRECORD)cnrhAllocRecords(hwndCnr, sizeof(MEMRECORD), ulHeapItemsCount1); if (pMemRecordFirst) { ulHeapItemsCount2 = 0; pMemRecordThis = pMemRecordFirst; _heap_walk(fncbMemHeapWalkFill); // the following doesn't work while _heap_walk is running pMemRecordThis = pMemRecordFirst; while (pMemRecordThis) { switch (pMemRecordThis->useflag) { case _USEDENTRY: pMemRecordThis->pszUseFlag = "Used"; break; case _FREEENTRY: pMemRecordThis->pszUseFlag = "Freed"; break; } switch (pMemRecordThis->status) { case _HEAPBADBEGIN: pMemRecordThis->pszStatus = "heap bad begin"; break; case _HEAPBADNODE: pMemRecordThis->pszStatus = "heap bad node"; break; case _HEAPEMPTY: pMemRecordThis->pszStatus = "heap empty"; break; case _HEAPOK: pMemRecordThis->pszStatus = "OK"; break; } pMemRecordThis->ulAddress = (ULONG)pMemRecordThis->pObject; sprintf(pMemRecordThis->szAddress, "0x%lX", pMemRecordThis->pObject); strhcpy(pMemRecordThis->szSource, (pMemRecordThis->filename) ? pMemRecordThis->filename : "?"); pMemRecordThis = (PMEMRECORD)pMemRecordThis->recc.preccNextRecord; } cnrhInsertRecords(hwndCnr, NULL, // parent (PRECORDCORE)pMemRecordFirst, TRUE, NULL, CRA_RECORDREADONLY, ulHeapItemsCount2); } } #endif /* *@@ CreateRecords: * *@@added V0.9.3 (2000-04-10) [umoeller] */ STATIC VOID CreateRecords(HWND hwndCnr, PULONG pulTotalItems, PULONG pulAllocatedItems, PULONG pulFreedItems, PULONG pulTotalBytes, PULONG pulAllocatedBytes, PULONG pulFreedBytes) { // count heap items // ULONG ulHeapItemsCount1 = 0; PMEMRECORD pMemRecordFirst; ULONG cHeapItems = 0; volatile BOOL fLocked = FALSE; // XWP V1.0.4 (2005-10-09) [pr] TRY_LOUD(excpt1) { if (fLocked = memdLock()) { PHEAPITEM pHeapItem = (PHEAPITEM)treeFirst(G_pHeapItemsRoot); *pulTotalItems = 0; *pulAllocatedItems = 0; *pulFreedItems = 0; *pulTotalBytes = 0; *pulAllocatedBytes = 0; *pulFreedBytes = 0; *pulTotalItems = G_cHeapItems; if (pMemRecordFirst = (PMEMRECORD)cnrhAllocRecords(hwndCnr, sizeof(MEMRECORD), G_cHeapItems)) { PMEMRECORD pMemRecordThis = pMemRecordFirst; pHeapItem = (PHEAPITEM)treeFirst(G_pHeapItemsRoot); while ((pMemRecordThis) && (pHeapItem)) { if (pHeapItem->fFreed) { (*pulFreedItems)++; (*pulFreedBytes) += pHeapItem->ulSize; } else { (*pulAllocatedItems)++; (*pulAllocatedBytes) += pHeapItem->ulSize; } (*pulTotalBytes) += pHeapItem->ulSize; pMemRecordThis->ulIndex = cHeapItems++; cnrhDateTimeDos2Win(&pHeapItem->dtAllocated, &pMemRecordThis->cdateAllocated, &pMemRecordThis->ctimeAllocated); if (pHeapItem->fFreed) pMemRecordThis->pszFreed = "yes"; pMemRecordThis->ulTID = pHeapItem->ulTID; strhcpy(pMemRecordThis->szSource, pHeapItem->pcszSourceFile); pMemRecordThis->pszSource = pMemRecordThis->szSource; pMemRecordThis->ulLine = pHeapItem->ulLine; strhcpy(pMemRecordThis->szFunction, pHeapItem->pcszFunction); pMemRecordThis->pszFunction = pMemRecordThis->szFunction; pMemRecordThis->ulAddress = pHeapItem->Tree.ulKey; sprintf(pMemRecordThis->szAddress, "0x%lX", pHeapItem->Tree.ulKey); pMemRecordThis->pszAddress = pMemRecordThis->szAddress; pMemRecordThis->ulSize = pHeapItem->ulSize; /* switch (pMemRecordThis->useflag) { case _USEDENTRY: pMemRecordThis->pszUseFlag = "Used"; break; case _FREEENTRY: pMemRecordThis->pszUseFlag = "Freed"; break; } switch (pMemRecordThis->status) { case _HEAPBADBEGIN: pMemRecordThis->pszStatus = "heap bad begin"; break; case _HEAPBADNODE: pMemRecordThis->pszStatus = "heap bad node"; break; case _HEAPEMPTY: pMemRecordThis->pszStatus = "heap empty"; break; case _HEAPOK: pMemRecordThis->pszStatus = "OK"; break; } sprintf(pMemRecordThis->szAddress, "0x%lX", pMemRecordThis->pObject); strhcpy(pMemRecordThis->szSource, (pMemRecordThis->filename) ? pMemRecordThis->filename : "?"); */ pMemRecordThis = (PMEMRECORD)pMemRecordThis->recc.preccNextRecord; pHeapItem = (PHEAPITEM)treeNext((TREE*)pHeapItem); } cnrhInsertRecords(hwndCnr, NULL, // parent (PRECORDCORE)pMemRecordFirst, TRUE, NULL, CRA_RECORDREADONLY, cHeapItems); } } } CATCH(excpt1) { if (G_pMemdLogFunc) { CHAR szMsg[1000]; sprintf(szMsg, "Crash occurred at object #%d out of %d", cHeapItems, G_cHeapItems); G_pMemdLogFunc(szMsg); } } END_CATCH(); if (fLocked) memdUnlock(); } /* *@@ mnu_fnCompareIndex: * *@@added V0.9.1 (99-12-03) [umoeller] */ STATIC SHORT EXPENTRY mnu_fnCompareIndex(PMEMRECORD pmrc1, PMEMRECORD pmrc2, PVOID pStorage) { pStorage = pStorage; // to keep the compiler happy if ((pmrc1) && (pmrc2)) if (pmrc1->ulIndex < pmrc2->ulIndex) return -1; else if (pmrc1->ulIndex > pmrc2->ulIndex) return 1; return 0; } /* *@@ mnu_fnCompareSourceFile: * *@@added V0.9.1 (99-12-03) [umoeller] */ STATIC SHORT EXPENTRY mnu_fnCompareSourceFile(PMEMRECORD pmrc1, PMEMRECORD pmrc2, PVOID pStorage) { HAB habDesktop = WinQueryAnchorBlock(HWND_DESKTOP); pStorage = pStorage; // to keep the compiler happy if ((pmrc1) && (pmrc2)) switch (WinCompareStrings(habDesktop, 0, 0, pmrc1->szSource, pmrc2->szSource, 0)) { case WCS_LT: return -1; case WCS_GT: return 1; default: // equal if (pmrc1->ulLine < pmrc2->ulLine) return -1; else if (pmrc1->ulLine > pmrc2->ulLine) return 1; } return 0; } /* *@@ mnu_fnCompareSourceFile: * *@@added V0.9.6 (2000-11-12) [umoeller] */ STATIC SHORT EXPENTRY mnu_fnCompareSize(PMEMRECORD pmrc1, PMEMRECORD pmrc2, PVOID pStorage) { pStorage = pStorage; // to keep the compiler happy if ((pmrc1) && (pmrc2)) { if (pmrc1->ulSize > pmrc2->ulSize) return 1; else if (pmrc1->ulSize < pmrc2->ulSize) return -1; } return 0; } /* *@@ mnu_fnCompareAddress: * *@@added V0.9.14 (2001-08-01) [umoeller] */ STATIC SHORT EXPENTRY mnu_fnCompareAddress(PMEMRECORD pmrc1, PMEMRECORD pmrc2, PVOID pStorage) { pStorage = pStorage; // to keep the compiler happy if ((pmrc1) && (pmrc2)) { if (pmrc1->ulAddress > pmrc2->ulAddress) return 1; else if (pmrc1->ulAddress < pmrc2->ulAddress) return -1; } return 0; } #define ID_MEMCNR 1000 /* *@@ memd_fnwpMemDebug: * client window proc for the heap debugger window * accessible from the Desktop context menu if * __XWPMEMDEBUG__ is defined. Otherwise, this is not * compiled. * * Usage: this is a regular PM client window procedure * to be used with WinRegisterClass and WinCreateStdWindow. * See dtpMenuItemSelected, which uses this. * * This creates a container with all the memory objects * with the size of the client area in turn. * *@@added V0.9.1 (99-12-04) [umoeller] *@@changed V0.9.14 (2001-08-01) [umoeller]: added sort by address */ MRESULT EXPENTRY memd_fnwpMemDebug(HWND hwndClient, ULONG msg, MPARAM mp1, MPARAM mp2) { MRESULT mrc = 0; switch (msg) { case WM_CREATE: { TRY_LOUD(excpt1) { // PCREATESTRUCT pcs = (PCREATESTRUCT)mp2; HWND hwndCnr; hwndCnr = WinCreateWindow(hwndClient, // parent WC_CONTAINER, "", WS_VISIBLE | CCS_MINIICONS | CCS_READONLY | CCS_SINGLESEL, 0, 0, 0, 0, hwndClient, // owner HWND_TOP, ID_MEMCNR, NULL, NULL); if (hwndCnr) { XFIELDINFO xfi[11]; PFIELDINFO pfi = NULL; PMEMRECORD pMemRecordFirst; int i = 0; ULONG ulTotalItems = 0, ulAllocatedItems = 0, ulFreedItems = 0; ULONG ulTotalBytes = 0, ulAllocatedBytes = 0, ulFreedBytes = 0; // set up cnr details view xfi[i].ulFieldOffset = FIELDOFFSET(MEMRECORD, ulIndex); xfi[i].pszColumnTitle = "No."; xfi[i].ulDataType = CFA_ULONG; xfi[i++].ulOrientation = CFA_RIGHT; xfi[i].ulFieldOffset = FIELDOFFSET(MEMRECORD, cdateAllocated); xfi[i].pszColumnTitle = "Date"; xfi[i].ulDataType = CFA_DATE; xfi[i++].ulOrientation = CFA_LEFT; xfi[i].ulFieldOffset = FIELDOFFSET(MEMRECORD, ctimeAllocated); xfi[i].pszColumnTitle = "Time"; xfi[i].ulDataType = CFA_TIME; xfi[i++].ulOrientation = CFA_LEFT; xfi[i].ulFieldOffset = FIELDOFFSET(MEMRECORD, pszFreed); xfi[i].pszColumnTitle = "Freed"; xfi[i].ulDataType = CFA_STRING; xfi[i++].ulOrientation = CFA_CENTER; xfi[i].ulFieldOffset = FIELDOFFSET(MEMRECORD, ulTID); xfi[i].pszColumnTitle = "TID"; xfi[i].ulDataType = CFA_ULONG; xfi[i++].ulOrientation = CFA_RIGHT; xfi[i].ulFieldOffset = FIELDOFFSET(MEMRECORD, pszFunction); xfi[i].pszColumnTitle = "Function"; xfi[i].ulDataType = CFA_STRING; xfi[i++].ulOrientation = CFA_LEFT; xfi[i].ulFieldOffset = FIELDOFFSET(MEMRECORD, pszSource); xfi[i].pszColumnTitle = "Source"; xfi[i].ulDataType = CFA_STRING; xfi[i++].ulOrientation = CFA_LEFT; xfi[i].ulFieldOffset = FIELDOFFSET(MEMRECORD, ulLine); xfi[i].pszColumnTitle = "Line"; xfi[i].ulDataType = CFA_ULONG; xfi[i++].ulOrientation = CFA_RIGHT; xfi[i].ulFieldOffset = FIELDOFFSET(MEMRECORD, ulSize); xfi[i].pszColumnTitle = "Size"; xfi[i].ulDataType = CFA_ULONG; xfi[i++].ulOrientation = CFA_RIGHT; xfi[i].ulFieldOffset = FIELDOFFSET(MEMRECORD, pszAddress); xfi[i].pszColumnTitle = "Address"; xfi[i].ulDataType = CFA_STRING; xfi[i++].ulOrientation = CFA_LEFT; pfi = cnrhSetFieldInfos(hwndCnr, &xfi[0], i, // array item count TRUE, // no draw lines 3); // return column { PSZ pszFont = "9.WarpSans"; WinSetPresParam(hwndCnr, PP_FONTNAMESIZE, strlen(pszFont), pszFont); } CreateRecords(hwndCnr, &ulTotalItems, &ulAllocatedItems, &ulFreedItems, &ulTotalBytes, &ulAllocatedBytes, &ulFreedBytes); BEGIN_CNRINFO() { CHAR szCnrTitle[1000]; CHAR szTotalItems[100], szAllocatedItems[100], szFreedItems[100], szReleasedItems[100]; CHAR szTotalBytes[100], szAllocatedBytes[100], szFreedBytes[100], szReleasedBytes[100]; sprintf(szCnrTitle, "Total memory log entries in use: %s items = %s bytes\n" " Total memory logs allocated: %s items = %s bytes\n" " Total memory logs freed: %s items = %s bytes\n" "Total memory logs freed and discarded: %s items = %s bytes", nlsThousandsDouble(szTotalItems, ulTotalItems, '.'), nlsThousandsDouble(szTotalBytes, ulTotalBytes, '.'), nlsThousandsDouble(szAllocatedItems, ulAllocatedItems, '.'), nlsThousandsDouble(szAllocatedBytes, ulAllocatedBytes, '.'), nlsThousandsDouble(szFreedItems, ulFreedItems, '.'), nlsThousandsDouble(szFreedBytes, ulFreedBytes, '.'), nlsThousandsDouble(szReleasedItems, G_ulItemsReleased, '.'), nlsThousandsDouble(szReleasedBytes, G_ulBytesReleased, '.')); G_pszMemCnrTitle = strdup(szCnrTitle); cnrhSetTitle(G_pszMemCnrTitle); cnrhSetView(CV_DETAIL | CV_MINI | CA_DETAILSVIEWTITLES | CA_DRAWICON | CA_CONTAINERTITLE | CA_TITLEREADONLY | CA_TITLESEPARATOR | CA_TITLELEFT); cnrhSetSplitBarAfter(pfi); cnrhSetSplitBarPos(250); } END_CNRINFO(hwndCnr); // create menu G_hwndMemDebugMenu = WinCreateMenu(HWND_DESKTOP, NULL); // no menu template winhInsertMenuItem(G_hwndMemDebugMenu, MIT_END, 1001, "Sort by index", MIS_TEXT, 0); winhInsertMenuItem(G_hwndMemDebugMenu, MIT_END, 1002, "Sort by source file", MIS_TEXT, 0); winhInsertMenuItem(G_hwndMemDebugMenu, MIT_END, 1003, "Sort by object size", MIS_TEXT, 0); winhInsertMenuItem(G_hwndMemDebugMenu, MIT_END, 1004, "Sort by address", MIS_TEXT, 0); WinSetFocus(HWND_DESKTOP, hwndCnr); } } CATCH(excpt1) {} END_CATCH(); mrc = WinDefWindowProc(hwndClient, msg, mp1, mp2); break; } case WM_WINDOWPOSCHANGED: { PSWP pswp = (PSWP)mp1; mrc = WinDefWindowProc(hwndClient, msg, mp1, mp2); if (pswp->fl & SWP_SIZE) { WinSetWindowPos(WinWindowFromID(hwndClient, ID_MEMCNR), // cnr HWND_TOP, 0, 0, pswp->cx, pswp->cy, SWP_SIZE | SWP_MOVE | SWP_SHOW); } break; } case WM_CONTROL: { USHORT usItemID = SHORT1FROMMP(mp1), usNotifyCode = SHORT2FROMMP(mp1); if (usItemID == ID_MEMCNR) // cnr { switch (usNotifyCode) { case CN_CONTEXTMENU: { PMEMRECORD precc = (PMEMRECORD)mp2; if (precc == NULL) { // whitespace: cnrhShowContextMenu(WinWindowFromID(hwndClient, ID_MEMCNR), NULL, // record G_hwndMemDebugMenu, hwndClient); } } } } break; } case WM_COMMAND: switch (SHORT1FROMMP(mp1)) { case 1001: // sort by index WinSendMsg(WinWindowFromID(hwndClient, ID_MEMCNR), CM_SORTRECORD, (MPARAM)mnu_fnCompareIndex, 0); break; case 1002: // sort by source file WinSendMsg(WinWindowFromID(hwndClient, ID_MEMCNR), CM_SORTRECORD, (MPARAM)mnu_fnCompareSourceFile, 0); break; case 1003: // sort by object size WinSendMsg(WinWindowFromID(hwndClient, ID_MEMCNR), CM_SORTRECORD, (MPARAM)mnu_fnCompareSize, 0); break; case 1004: // sort by address V0.9.14 (2001-08-01) [umoeller] WinSendMsg(WinWindowFromID(hwndClient, ID_MEMCNR), CM_SORTRECORD, (MPARAM)mnu_fnCompareAddress, 0); break; } break; case WM_CLOSE: WinDestroyWindow(WinWindowFromID(hwndClient, ID_MEMCNR)); WinDestroyWindow(WinQueryWindow(hwndClient, QW_PARENT)); free(G_pszMemCnrTitle); WinDestroyWindow(G_hwndMemDebugMenu); G_pszMemCnrTitle = NULL; break; default: mrc = WinDefWindowProc(hwndClient, msg, mp1, mp2); } return mrc; } /* *@@ memdCreateMemDebugWindow: * creates a heap debugging window which * is a standard frame with a container, * listing all heap objects ever allocated. * * The client of this standard frame is in * memd_fnwpMemDebug. * * This thing lists all calls to malloc() * which were ever made, including the * source file and source line number which * made the call. Extreeeemely useful for * detecting memory leaks. * * This only works if the memory functions * have been replaced with the debug versions * in this file. */ VOID memdCreateMemDebugWindow(VOID) { ULONG flStyle = FCF_TITLEBAR | FCF_SYSMENU | FCF_HIDEMAX | FCF_SIZEBORDER | FCF_SHELLPOSITION | FCF_NOBYTEALIGN | FCF_TASKLIST; if (WinRegisterClass(WinQueryAnchorBlock(HWND_DESKTOP), "XWPMemDebug", memd_fnwpMemDebug, 0L, 0)) { HWND hwndClient; HWND hwndMemFrame = WinCreateStdWindow(HWND_DESKTOP, 0L, &flStyle, "XWPMemDebug", "Allocated XWorkplace Memory Objects", 0L, NULLHANDLE, // resource 0, &hwndClient); if (hwndMemFrame) { WinSetWindowPos(hwndMemFrame, HWND_TOP, 0, 0, 0, 0, SWP_ZORDER | SWP_SHOW | SWP_ACTIVATE); } } } #else void memdDummy2(void) { int i = 0; i++; } #endif // __XWPMEMDEBUG__