source: branches/branch-1-0/src/helpers/memdebug.c@ 297

Last change on this file since 297 was 297, checked in by pr, 20 years ago

Update functions using exception handlers to force non-register variables

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