source: trunk/src/helpers/memdebug.c@ 57

Last change on this file since 57 was 21, checked in by umoeller, 25 years ago

Final changes for 0.9.7, i hope...

  • Property svn:eol-style set to CRLF
  • Property svn:keywords set to Author Date Id Revision
File size: 24.1 KB
Line 
1
2/*
3 *@@sourcefile memdebug.c:
4 * memory debugging helpers.
5 *
6 * Several things are in here which might turn out to
7 * be useful:
8 *
9 * -- Memory block dumping (memdDumpMemoryBlock).
10 *
11 * -- Sophisticated heap debugging functions, which
12 * automatically replace malloc() and free() etc.
13 * If the __XWPMEMDEBUG__ #define is set before including
14 * memdebug.h, all those standard library calls
15 * are remapped to use the functions in this file
16 * instead.
17 *
18 * At present, malloc(), calloc(), realloc(), strdup()
19 * and free() are supported.
20 *
21 * These log every memory allocation made and log
22 * much more data compared to the VAC++ memory
23 * debugging functions. See HEAPITEM for details.
24 * Automatic heap checking is also included, using
25 * special "magic string" which are checked to
26 * detect overwrites.
27 *
28 * To be able to trace memory errors, set the global
29 * variable G_pMemdLogFunc to a function which can
30 * write an error string to a meaningful place (the
31 * screen or a file). WARNING: While that error function
32 * is executed, the system might be in a global memory
33 * lock, so DON'T display a message box while in that
34 * function.
35 *
36 * These debug functions have been added with V0.9.3
37 * and should now be compiler-independent.
38 *
39 * V0.9.6 added realloc() support and fixed a few bugs.
40 *
41 * -- A PM heap debugging window which shows the status
42 * of the heap logging list. See memdCreateMemDebugWindow
43 * (memdebug_win.c) for details.
44 *
45 * To enable memory debugging, do the following in each (!)
46 * of your code modules:
47 *
48 * 1) Include at least <stdlib.h> and <string.h>.
49 *
50 * 2) Include memdebug.h AFTER those two. This will remap
51 * the malloc() etc. calls.
52 *
53 * If you don't want those replaced, add
54 + #define DONT_REPLACE_MALLOC
55 * before including memdebug.h.
56 *
57 * That's all. XWorkplace's setup.h does this automatically
58 * if XWorkplace is compiled with debug code.
59 *
60 * A couple of WARNINGS:
61 *
62 * 1) Memory debugging can greatly slow down the system
63 * after a while. When free() is invoked, the memory
64 * that was allocated is freed, but not the memory
65 * log entry (the HEAPITEM) to allow tracing what was
66 * freed. As a result, the linked list of memory items
67 * keeps growing longer, and free() becomes terribly
68 * slow after a while because it must traverse the
69 * entire list for each free() call. Use memdReleaseFreed
70 * from time to time.
71 *
72 * 2) The replacement functions in this file allocate
73 * extra memory for the magic strings. For example, if
74 * you call malloc(100), more than 100 bytes get allocated
75 * to allow for storing the magic strings to detect
76 * memory overwrites. Two magic strings are allocated,
77 * one before the actual buffer, and one behind it.
78 *
79 * As a result, YOU MUST NOT confuse the replacement
80 * memory functions with the original ones. If you
81 * use malloc() in one source file and free() the
82 * buffer in another one where debug memory has not
83 * been enabled, you'll get crashes.
84 *
85 * As a rule of thumb, enable memory debugging for all
86 * your source files or for none. And make sure everything
87 * is recompiled when you change your mind.
88 *
89 *@@added V0.9.1 (2000-02-12) [umoeller]
90 */
91
92/*
93 * Copyright (C) 2000 Ulrich M”ller.
94 * This program is part of the XWorkplace package.
95 * This program is free software; you can redistribute it and/or modify
96 * it under the terms of the GNU General Public License as published by
97 * the Free Software Foundation, in version 2 as it comes in the COPYING
98 * file of the XWorkplace main distribution.
99 * This program is distributed in the hope that it will be useful,
100 * but WITHOUT ANY WARRANTY; without even the implied warranty of
101 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
102 * GNU General Public License for more details.
103 */
104
105#define OS2EMX_PLAIN_CHAR
106 // this is needed for "os2emx.h"; if this is defined,
107 // emx will define PSZ as _signed_ char, otherwise
108 // as unsigned char
109
110#define INCL_DOSSEMAPHORES
111#define INCL_DOSEXCEPTIONS
112#define INCL_DOSPROCESS
113#define INCL_DOSERRORS
114
115#include <os2.h>
116
117#include <stdio.h>
118#include <string.h>
119#include <setjmp.h>
120
121#define DONT_REPLACE_MALLOC // never do debug memory for this
122#define MEMDEBUG_PRIVATE
123#include "setup.h"
124
125#ifdef __XWPMEMDEBUG__
126
127#include "helpers\except.h"
128#include "helpers\memdebug.h" // included by setup.h already
129#include "helpers\stringh.h"
130
131/*
132 *@@category: Helpers\C helpers\Heap debugging
133 * See memdebug.c.
134 */
135
136/* ******************************************************************
137 *
138 * Global variables
139 *
140 ********************************************************************/
141
142#define MEMBLOCKMAGIC_HEAD "\210\203`H&cx$&%\254"
143 // size must be a multiple of 4 or dword-alignment will fail;
144 // there's an extra 0 byte at the end, so we have a size of 12
145 // V0.9.3 (2000-04-17) [umoeller]
146#define MEMBLOCKMAGIC_TAIL "\250\210&%/dfjsk%#,dlhf\223"
147
148HMTX G_hmtxMallocList = NULLHANDLE;
149
150extern PHEAPITEM G_pHeapItemsRoot = NULL;
151PHEAPITEM G_pHeapItemsLast = NULL;
152
153PFNCBMEMDLOG G_pMemdLogFunc = NULL;
154
155extern ULONG G_ulItemsReleased = 0;
156extern ULONG G_ulBytesReleased = 0;
157
158/* ******************************************************************
159 *
160 * Debug heap management
161 *
162 ********************************************************************/
163
164/*
165 *@@ memdLock:
166 * enables the global memory lock to protect
167 * the global variables here. Use memdUnlock
168 * to unlock again, and lock only for the shortest
169 * possible time. This is only used by the memdebug.c
170 * functions.
171 *
172 *@@added V0.9.3 (2000-04-10) [umoeller]
173 */
174
175BOOL memdLock(VOID)
176{
177 APIRET arc = NO_ERROR;
178 if (G_hmtxMallocList == NULLHANDLE)
179 // first call:
180 arc = DosCreateMutexSem(NULL,
181 &G_hmtxMallocList,
182 0, // unshared
183 TRUE); // request now!
184 else
185 arc = DosRequestMutexSem(G_hmtxMallocList,
186 SEM_INDEFINITE_WAIT);
187
188 return (arc == NO_ERROR);
189}
190
191/*
192 *@@ memdUnlock:
193 * the reverse to memdLock.
194 *
195 *@@added V0.9.3 (2000-04-10) [umoeller]
196 */
197
198VOID memdUnlock(VOID)
199{
200 DosReleaseMutexSem(G_hmtxMallocList);
201}
202
203/*
204 *@@ memdMalloc:
205 * wrapper function for malloc() to trace malloc()
206 * calls more precisely.
207 *
208 * If XWorkplace is compiled with debug code, setup.h
209 * automatically #includes memdebug.h, which maps
210 * malloc to this function so that the source file
211 * etc. parameters automatically get passed.
212 *
213 * For each call, we call the default malloc(), whose
214 * return value is returned, and create a HEAPITEM
215 * for remembering the call, which is stored in a global
216 * linked list.
217 *
218 *@@added V0.9.3 (2000-04-11) [umoeller]
219 */
220
221void* memdMalloc(size_t stSize,
222 const char *pcszSourceFile, // in: source file name
223 unsigned long ulLine, // in: source line
224 const char *pcszFunction) // in: function name
225{
226 void *prc = NULL;
227
228 if (stSize == 0)
229 // malloc(0) called: report error
230 if (G_pMemdLogFunc)
231 {
232 CHAR szMsg[1000];
233 sprintf(szMsg,
234 "Function %s (%s, line %d) called malloc(0).",
235 pcszFunction,
236 pcszSourceFile,
237 ulLine);
238 G_pMemdLogFunc(szMsg);
239 }
240
241 if (memdLock())
242 {
243 // call default malloc(), but with the additional
244 // size of our MEMBLOCKMAGIC strings; we'll return
245 // the first byte after the "front" string so we can
246 // check for string overwrites
247 void *pObj = malloc(stSize
248 + sizeof(MEMBLOCKMAGIC_HEAD)
249 + sizeof(MEMBLOCKMAGIC_TAIL));
250 if (pObj)
251 {
252 PHEAPITEM pHeapItem = (PHEAPITEM)malloc(sizeof(HEAPITEM));
253
254 // store "front" magic string
255 memcpy(pObj,
256 MEMBLOCKMAGIC_HEAD,
257 sizeof(MEMBLOCKMAGIC_HEAD));
258 // return address: first byte after "front" magic string
259 prc = ((PBYTE)pObj) + sizeof(MEMBLOCKMAGIC_HEAD);
260 // store "tail" magic string to block which
261 // will be returned plus the size which was requested
262 memcpy(((PBYTE)prc) + stSize,
263 MEMBLOCKMAGIC_TAIL,
264 sizeof(MEMBLOCKMAGIC_TAIL));
265
266 if (pHeapItem)
267 {
268 PTIB ptib;
269 PPIB ppib;
270
271 pHeapItem->pNext = 0;
272
273 pHeapItem->pAfterMagic = prc;
274 pHeapItem->ulSize = stSize;
275 pHeapItem->pcszSourceFile = pcszSourceFile;
276 pHeapItem->ulLine = ulLine;
277 pHeapItem->pcszFunction = pcszFunction;
278
279 DosGetDateTime(&pHeapItem->dtAllocated);
280
281 pHeapItem->ulTID = 0;
282
283 if (DosGetInfoBlocks(&ptib, &ppib) == NO_ERROR)
284 if (ptib)
285 if (ptib->tib_ptib2)
286 pHeapItem->ulTID = ptib->tib_ptib2->tib2_ultid;
287
288 pHeapItem->fFreed = FALSE;
289
290 // append heap item to linked list
291 if (G_pHeapItemsRoot == NULL)
292 // first item:
293 G_pHeapItemsRoot = pHeapItem;
294 else
295 // we have items already:
296 if (G_pHeapItemsLast)
297 {
298 // last item cached:
299 G_pHeapItemsLast->pNext = pHeapItem;
300 G_pHeapItemsLast = pHeapItem;
301 }
302 else
303 {
304 // not cached: find end of list
305 PHEAPITEM phi = G_pHeapItemsRoot;
306 while (phi->pNext)
307 phi = phi->pNext;
308
309 phi->pNext = pHeapItem;
310 G_pHeapItemsLast = pHeapItem;
311 }
312 }
313 }
314
315 memdUnlock();
316 }
317
318 return (prc);
319}
320
321/*
322 *@@ memdCalloc:
323 * similar to memdMalloc; this is the wrapper for
324 * the calloc() call. This is automatically
325 * remapped also.
326 *
327 *@@added V0.9.3 (2000-04-11) [umoeller]
328 */
329
330void* memdCalloc(size_t num,
331 size_t stSize,
332 const char *pcszSourceFile,
333 unsigned long ulLine,
334 const char *pcszFunction)
335{
336 void *p = memdMalloc(num * stSize,
337 pcszSourceFile,
338 ulLine,
339 pcszFunction);
340 memset(p, 0, num * stSize);
341 return (p);
342}
343
344/*
345 *@@ memdFree:
346 * wrapper for the free() call, which is remapped
347 * by setup.h and memdebug.h like memdMalloc
348 * and memdCalloc. This searches the memory object
349 * (p) which was previously allocated on the linked
350 * list of HEAPITEM's and frees it then by calling
351 * the default free().
352 *
353 * The HEAPITEM itself is not freed, but only marked
354 * as freed. As a result, the linked list can grow
355 * REALLY large. While memdMalloc does not become
356 * slower with large HEAPITEM lists because it only
357 * appends to the end of the list, which is remembered,
358 * memdFree can become extremely slow because the entire
359 * list needs to be searched with each call.
360 * So call memdReleaseFreed from time to time.
361 *
362 *@@added V0.9.3 (2000-04-10) [umoeller]
363 */
364
365void memdFree(void *p,
366 const char *pcszSourceFile,
367 unsigned long ulLine,
368 const char *pcszFunction)
369{
370 BOOL fFound = FALSE;
371 if (memdLock())
372 {
373 // PLISTNODE pNode = lstQueryFirstNode(&G_llHeapItems);
374 PHEAPITEM pHeapItem = G_pHeapItemsRoot;
375
376 // search the list with the pointer which was
377 // really returned by the original malloc(),
378 // that is, the byte before the magic string
379 void *pBeforeMagic = ((PBYTE)p) - sizeof(MEMBLOCKMAGIC_HEAD);
380
381 while (pHeapItem)
382 {
383 if (pHeapItem->pAfterMagic == p)
384 // the same address may be allocated and freed
385 // several times, so if this address has been
386 // freed, search on
387 if (!pHeapItem->fFreed)
388 {
389 // found:
390 ULONG ulError = 0;
391 // check magic string
392 if (memcmp(pBeforeMagic,
393 MEMBLOCKMAGIC_HEAD,
394 sizeof(MEMBLOCKMAGIC_HEAD))
395 != 0)
396 ulError = 1;
397 else if (memcmp(((PBYTE)pHeapItem->pAfterMagic) + pHeapItem->ulSize,
398 MEMBLOCKMAGIC_TAIL,
399 sizeof(MEMBLOCKMAGIC_TAIL))
400 != 0)
401 ulError = 2;
402
403 if (ulError)
404 {
405 // magic block has been overwritten:
406 if (G_pMemdLogFunc)
407 {
408 CHAR szMsg[1000];
409 sprintf(szMsg,
410 "Magic string %s memory block at 0x%lX has been overwritten.\n"
411 "This was detected by the free() call at %s (%s, line %d).\n"
412 "The block was allocated by %s (%s, line %d).",
413 (ulError == 1) ? "before" : "after",
414 p,
415 pcszFunction,
416 pcszSourceFile,
417 ulLine, // free
418 pHeapItem->pcszFunction,
419 pHeapItem->pcszSourceFile,
420 pHeapItem->ulLine);
421 G_pMemdLogFunc(szMsg);
422 }
423 }
424
425 free(pBeforeMagic);
426 pHeapItem->fFreed = TRUE;
427
428 fFound = TRUE;
429 break;
430 } // if (!pHeapItem->fFreed)
431
432 pHeapItem = pHeapItem->pNext;
433 }
434
435 memdUnlock();
436 }
437
438 if (!fFound)
439 if (G_pMemdLogFunc)
440 {
441 CHAR szMsg[1000];
442 sprintf(szMsg,
443 "free() called with invalid object from %s (%s, line %d) for object 0x%lX.",
444 pcszFunction,
445 pcszSourceFile,
446 ulLine,
447 p);
448 G_pMemdLogFunc(szMsg);
449 }
450}
451
452/*
453 *@@ memdRealloc:
454 * wrapper function for realloc(). See memdMalloc for
455 * details.
456 *
457 *@@added V0.9.6 (2000-11-12) [umoeller]
458 */
459
460void* memdRealloc(void *p,
461 size_t stSize,
462 const char *pcszSourceFile, // in: source file name
463 unsigned long ulLine, // in: source line
464 const char *pcszFunction) // in: function name
465{
466 void *prc = NULL;
467 BOOL fFound = FALSE;
468
469 if (memdLock())
470 {
471 PHEAPITEM pHeapItem = G_pHeapItemsRoot;
472
473 // search the list with the pointer which was
474 // really returned by the original malloc(),
475 // that is, the byte before the magic string
476 void *pBeforeMagic = ((PBYTE)p) - sizeof(MEMBLOCKMAGIC_HEAD);
477
478 while (pHeapItem)
479 {
480 if (pHeapItem->pAfterMagic == p)
481 // the same address may be allocated and freed
482 // several times, so if this address has been
483 // freed, search on
484 if (!pHeapItem->fFreed)
485 {
486 // found:
487 PVOID pObjNew = 0;
488 ULONG ulError = 0;
489 ULONG cbCopy = 0;
490 PTIB ptib;
491 PPIB ppib;
492
493 // check magic string
494 if (memcmp(pBeforeMagic,
495 MEMBLOCKMAGIC_HEAD,
496 sizeof(MEMBLOCKMAGIC_HEAD))
497 != 0)
498 ulError = 1;
499 else if (memcmp(((PBYTE)pHeapItem->pAfterMagic) + pHeapItem->ulSize,
500 MEMBLOCKMAGIC_TAIL,
501 sizeof(MEMBLOCKMAGIC_TAIL))
502 != 0)
503 ulError = 2;
504
505 if (ulError)
506 {
507 // magic block has been overwritten:
508 if (G_pMemdLogFunc)
509 {
510 CHAR szMsg[1000];
511 sprintf(szMsg,
512 "Magic string %s memory block at 0x%lX has been overwritten.\n"
513 "This was detected by the realloc() call at %s (%s, line %d).\n"
514 "The block was allocated by %s (%s, line %d).",
515 (ulError == 1) ? "before" : "after",
516 p,
517 pcszFunction,
518 pcszSourceFile,
519 ulLine, // free
520 pHeapItem->pcszFunction,
521 pHeapItem->pcszSourceFile,
522 pHeapItem->ulLine);
523 G_pMemdLogFunc(szMsg);
524 }
525 }
526
527 // now reallocate!
528 pObjNew = malloc(stSize // new size
529 + sizeof(MEMBLOCKMAGIC_HEAD)
530 + sizeof(MEMBLOCKMAGIC_TAIL));
531
532 // store "front" magic string
533 memcpy(pObjNew,
534 MEMBLOCKMAGIC_HEAD,
535 sizeof(MEMBLOCKMAGIC_HEAD));
536 // return address: first byte after "front" magic string
537 prc = ((PBYTE)pObjNew) + sizeof(MEMBLOCKMAGIC_HEAD);
538
539 // bytes to copy: the smaller of the old and the new size
540 cbCopy = pHeapItem->ulSize;
541 if (stSize < pHeapItem->ulSize)
542 cbCopy = stSize;
543
544 // copy buffer from old memory object
545 memcpy(prc, // after "front" magic
546 pHeapItem->pAfterMagic,
547 cbCopy);
548
549 // store "tail" magic string to block which
550 // will be returned plus the size which was requested
551 memcpy(((PBYTE)prc) + stSize,
552 MEMBLOCKMAGIC_TAIL,
553 sizeof(MEMBLOCKMAGIC_TAIL));
554
555 // free the old buffer
556 free(pBeforeMagic);
557
558 // update the HEAPITEM
559 pHeapItem->pAfterMagic = prc; // new pointer!
560 pHeapItem->ulSize = stSize; // new size!
561 pHeapItem->pcszSourceFile = pcszSourceFile;
562 pHeapItem->ulLine = ulLine;
563 pHeapItem->pcszFunction = pcszFunction;
564
565 // update date, time, TID
566 DosGetDateTime(&pHeapItem->dtAllocated);
567 pHeapItem->ulTID = 0;
568 if (DosGetInfoBlocks(&ptib, &ppib) == NO_ERROR)
569 if (ptib)
570 if (ptib->tib_ptib2)
571 pHeapItem->ulTID = ptib->tib_ptib2->tib2_ultid;
572
573 fFound = TRUE;
574 break;
575 } // if (!pHeapItem->fFreed)
576
577 pHeapItem = pHeapItem->pNext;
578 }
579
580 memdUnlock();
581 }
582
583 if (!fFound)
584 if (G_pMemdLogFunc)
585 {
586 CHAR szMsg[1000];
587 sprintf(szMsg,
588 "realloc() called with invalid object from %s (%s, line %d) for object 0x%lX.",
589 pcszFunction,
590 pcszSourceFile,
591 ulLine,
592 p);
593 G_pMemdLogFunc(szMsg);
594 }
595
596 return (prc);
597}
598
599/*
600 *@@ memdReleaseFreed:
601 * goes thru the entire global HEAPITEM's list
602 * and throws out all items which have been freed.
603 * Call this from time to time in order to keep
604 * the system usable. See memdFree() for details.
605 *
606 * Returns the no. of HEAPITEM's that have been
607 * released.
608 *
609 *@@added V0.9.3 (2000-04-11) [umoeller]
610 */
611
612unsigned long memdReleaseFreed(void)
613{
614 BOOL ulItemsReleased = 0,
615 ulBytesReleased = 0;
616 if (memdLock())
617 {
618 PHEAPITEM pHeapItem = G_pHeapItemsRoot,
619 pPrevious = NULL;
620
621 while (pHeapItem)
622 {
623 // store next first, because we can change the "next" pointer
624 PHEAPITEM pNext = pHeapItem->pNext; // can be NULL
625
626 if (pHeapItem->fFreed)
627 {
628 // item was freed:
629 if (pPrevious == NULL)
630 // head of list:
631 G_pHeapItemsRoot = pNext; // can be NULL
632 else
633 // somewhere later:
634 // link next to previous to skip current
635 pPrevious->pNext = pNext; // can be NULL
636
637 ulItemsReleased++;
638 ulBytesReleased += pHeapItem->ulSize;
639
640 if (pHeapItem == G_pHeapItemsLast)
641 // reset "last item" cache
642 G_pHeapItemsLast = NULL;
643
644 free(pHeapItem);
645 }
646 else
647 // item still valid:
648 pPrevious = pHeapItem;
649
650 pHeapItem = pNext;
651 }
652
653 G_ulItemsReleased += ulItemsReleased;
654 G_ulBytesReleased += ulBytesReleased;
655
656 memdUnlock();
657 }
658
659 return (ulItemsReleased);
660}
661
662/* ******************************************************************
663 *
664 * XFolder debugging helpers
665 *
666 ********************************************************************/
667
668#ifdef _PMPRINTF_
669 /*
670 *@@ memdDumpMemoryBlock:
671 * if _PMPRINTF_ has been #define'd before including
672 * memdebug.h,
673 * this will dump a block of memory to the PMPRINTF
674 * output window. Useful for debugging internal
675 * structures.
676 * If _PMPRINTF_ has been NOT #define'd,
677 * no code will be produced at all. :-)
678 */
679
680 void memdDumpMemoryBlock(PBYTE pb, // in: start address
681 ULONG ulSize, // in: size of block
682 ULONG ulIndent) // in: how many spaces to put
683 // before each output line
684 {
685 PSZ psz = strhCreateDump(pb, ulSize, ulIndent);
686 if (psz)
687 {
688 _Pmpf(("\n%s", psz));
689 free(psz);
690 }
691 }
692#endif
693
694#else
695void memdDummy(void)
696{
697 int i = 0;
698}
699#endif
700
Note: See TracBrowser for help on using the repository browser.