summaryrefslogtreecommitdiff
path: root/app/xlockmore/xlock/memcheck.c
blob: cd7f2ea0d963517131325f98705fdf8979a9ef31 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
/*****************************************************************************
*  (c) Copyright 1996,1997 Metapath Software Corporation
*
*  Permission to use, copy, modify, and distribute this software and its
*  documentation for any purpose and without fee is hereby granted,
*  provided that the above copyright notice appear in all copies and that
*  both that copyright notice and this permission notice appear in
*  supporting documentation.
*
*  This file is provided AS IS with no warranties of any kind.  The author
*  shall have no liability with respect to the infringement of copyrights,
*  trade secrets or any patents by this file or any part thereof.  In no
*  event will the author be liable for any lost revenue or profits or
*  other special, indirect and consequential damages.
*
*  Author:  David A. Hansen
*  Created: 28-MAR-1996
*
*  Change History:
*  22-JUL-96   D.Hansen    Fixed some bugs and added length check.
*  01-AUG-96   D.Hansen    Added Usage dump on SIGHUP
*  17-JUL-97   D.Hansen    Removed dependencies for use with xlock
*
*  Description:
*     This module replaces the standard malloc/free routines with more
*     enhanced/robust version to aid in catching memory bugs.
*
*****************************************************************************/

/*-
   It's still a little crude, but it works.  So the next thing I need to do is
   add some more refinement.  First on my list is to figure out a way to
   translate the caller's address into something useful, like a symbol.
   Currently you have to be in gdb and use something like:

   gdb> x <addr>

   Oh, and don't forget to link with debugging, LDFLAGS=-g

   To build, I just put memcheck.c in the xlock subdirectory and hand edited

   the Makefile and ../mode/Makefile to include it.  You'll probably want to
   create a debug subdirectory and figure out a way to eloquently get
   configure to build with it.

   Also, there is no comparison/growth detection utility.  Maybe that's
   something you can add.  Basically, you send HUP signals to the process
   every so often and it will dump the memory users and amount they have
   allocated.  After running and HUPing for a while, a script could analyze
   the output and determine which caller addresses are continuing to consume
   memory.

   Also, the method for determining the caller's address is probably specific
   to Intel machines since I use a trick that is based on the way the frame is
   stacked.  It may work on Sun with a little playing around with the
   reference variable, like the last variable in the parameter list instead of
   the first, or maybe by changing the reference to add 1 long word instead of
   subtract 1 long word.  If you look at the variable caller_addr, you'll see
   what I'm talking about.  It all depends on things like whether the stack
   grows up or down, etc.  I would try it first without changing anything and
   use gdb or dbx or whatever to see if the addresses translate into
   appropriate symbols.  In any case, it will always be very machine
   dependent.  Not much one can do about that.

   Finally, what we probably want to do in the long run is create a script
   that runs xlock randomly through all the modes on a given interval and at
   the same interval issues SIGHUPs to the process to take a snapshot of the
   used memory.  Then after letting it iterate through all the modes several
   times, another script can post-process the output looking for memory growth
   and bad memory users.  (gdb> handle SIGHUP print pass nostop) (ifndebug
   around the SIGHUP handler in xlock.c).

   I've also thought about adding another signal catcher to snapshot a stable
   allocation.  In other words, once xlock is started, issue a SIGUSR1 or
   something, and then all subsequent SIGHUPs would print deviations from the
   initial SIGUSR1.  Of course, that would produce a different list for every
   mode, but it would delete the common mallocs done at process
   initialization.  Sometimes it's also hard to tell if a library is doing a
   one time permanent malloc and just reusing it later.  I think MesaGL does
   this. */

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <signal.h>
#include <time.h>

#ifdef ULONG
#undef ULONG
#endif
#if LINT			/* Lint complains so give it what it expects */
#define ULONG unsigned int
#else
#define ULONG unsigned long
#endif

extern char *ProgramName;
extern pid_t ProgramPID;
extern void *sbrk(int incr);

typedef struct mem_struct {
	struct mem_hdr {
		ULONG       check_mark;
		ULONG       chunk_size;
		ULONG       used_length;
		struct mem_struct *next;
		void       *caller;
	} hdr;

	/* allocate space for the marker at the end of the data */
	/* but return the address of data */
	unsigned char data[2];

} mem_type;


#define USED_MARKER   ((ULONG) 0xBABECAFE)
#define FREE_MARKER   ((ULONG) 0xDEADBEEF)
#define HEAD_MARKER   ((ULONG) 0xFACEF00D)
#define EOD_MARKER    ((ULONG) 0xEC)

#define SBRK_MIN      4096
#define SPLIT_MIN     32

static mem_type *free_head = NULL;
static mem_type *malloc_head = NULL;
static int  reentrancy_check = 0;
static int  first_time = 1;
static FILE *dump_file;
static char dump_fname[256];

static struct sigaction hup_action;
static struct sigaction old_action;

static ULONG total_count;
static ULONG total_size;
static ULONG total_chunk;
static ULONG caller_count;
static ULONG caller_size;
static ULONG caller_chunk;
static time_t hup_time;
static char time_str[256];

/*-------------------------------------------------------------------------*/
static void
message(char *msgstr)
{
	if (dump_file == NULL)
		return;
	(void) time(&hup_time);
	(void) strftime(time_str, sizeof (time_str), "%d-%b-%y %H:%M:%S",
			localtime(&hup_time));
	(void) fprintf(dump_file, "%s - %s (%d): %s\n",
		       time_str, ProgramName, (int) ProgramPID, msgstr);
	(void) fflush(dump_file);
}				/* message */


/*-------------------------------------------------------------------------*/
static void
hup_handler(int interrupt)
{
	mem_type   *t1;
	mem_type   *t2;

	if (dump_file == NULL)
		return;
	if (reentrancy_check > 1) {
		message("Memory allocation list is currently unaccessible");
		return;
	}
	message("malloc/free usage dump:");

	(void) fprintf(dump_file,
		       "Dumping on interrupt %d.\n", interrupt);
	(void) fprintf(dump_file,
		       "=================================================\n");
	(void) fprintf(dump_file,
		       "Caller      |   Number  |    Size   |    Heap   |\n");
	(void) fprintf(dump_file,
		       "------------|-----------|-----------|-----------|\n");
	total_count = 0;
	total_size = 0;
	total_chunk = 0;
	for (t1 = malloc_head; t1; t1 = t1->hdr.next) {
		total_count++;
		total_size += t1->hdr.used_length;
		total_chunk += t1->hdr.chunk_size;
		if (t1->hdr.check_mark == HEAD_MARKER) {
			t1->hdr.check_mark = USED_MARKER;
			continue;
		}
		caller_count = 1;
		caller_size = t1->hdr.used_length;
		caller_chunk = t1->hdr.chunk_size;
		for (t2 = t1->hdr.next; t2; t2 = t2->hdr.next) {
			if (t2->hdr.caller == t1->hdr.caller) {
				t2->hdr.check_mark = HEAD_MARKER;
				caller_count++;
				caller_size += t2->hdr.used_length;
				caller_chunk += t2->hdr.chunk_size;
			}
		}
#ifdef LINT
		(void) fprintf(dump_file, "0x%08X: |%10u |%10u |%10u |\n",
#else
		(void) fprintf(dump_file, "0x%08lX: |%10lu |%10lu |%10lu |\n",
#endif
			       (ULONG) t1->hdr.caller,
			       caller_count, caller_size, caller_chunk);
	}
	(void) fprintf(dump_file,
		       "------------|-----------|-----------|-----------|\n");
#ifdef LINT
	(void) fprintf(dump_file, "totals:     |%10u |%10u |%10u |\n\n",
#else
	(void) fprintf(dump_file, "totals:     |%10lu |%10lu |%10lu |\n\n",
#endif
		       total_count, total_size, total_chunk);
	(void) fflush(dump_file);
}				/* hup_handler */



/*-------------------------------------------------------------------------*/
static void *
allocate_memory(ULONG length, void *caller_addr)
{
	mem_type   *temp;
	mem_type   *cnew;
	mem_type   *last;
	ULONG       req_size;
	ULONG       incr;

	if (first_time) {
		/* install SIGHUP handler to dump usage info */
		first_time = 0;
		(void) sprintf(dump_fname,
			     "memdiag.%s-%d", ProgramName, (int) ProgramPID);
		dump_file = fopen(dump_fname, "a");

		message("malloc/free diagnostics started");

		(void) sigaction(SIGHUP, NULL, &old_action);
		if (old_action.sa_handler == SIG_DFL) {
			hup_action.sa_handler = hup_handler;
#ifdef _INCLUDE_HPUX_SOURCE
			hup_action.sa_flags = SA_RESETHAND;	/* Just gettting it to compile */
#else
			hup_action.sa_flags = SA_RESTART;
#endif
			(void) sigaction(SIGHUP, &hup_action, NULL);
			message("Installed SIGHUP handler for usage dump");
		} else {
			message("Another SIGHUP handler already installed");
		}
	}
	if (++reentrancy_check > 1) {
		message("MALLOC - reentrancy detected");
		*(ULONG *) 1 = 1L;
	}
	/* round length up to next long word boundary */
	req_size = (length + 3) & ~3;

	/* add in the mem_type overhead */
	req_size += sizeof (mem_type);

	/* check the current list of free space */
	last = NULL;
	for (temp = free_head; temp != NULL; temp = temp->hdr.next) {
		if (temp->hdr.check_mark != FREE_MARKER) {
			message("MALLOC - corrupt free list");
			*(ULONG *) 1 = 1L;
		}
		if (temp->hdr.chunk_size >= req_size)
			break;
		last = temp;
	}

	/* no free space large enough, lets sbrk some more */
	if (temp == NULL) {
		/* round up to the next page boundary */
		incr = (req_size + SBRK_MIN) & ~SBRK_MIN;
		temp = (mem_type *) sbrk(incr);
		if (temp == NULL) {
			message("MALLOC - no memory available");
			*(ULONG *) 1 = 1L;
		}
		temp->hdr.check_mark = FREE_MARKER;
		temp->hdr.chunk_size = incr;
		temp->hdr.caller = NULL;
		temp->hdr.next = NULL;
	}
	/* if space is large enough to split */
	if ((temp->hdr.chunk_size - req_size) > SPLIT_MIN) {
		cnew = (mem_type *) ((char *) temp + req_size);
		cnew->hdr.check_mark = FREE_MARKER;
		cnew->hdr.chunk_size = temp->hdr.chunk_size - req_size;
		cnew->hdr.caller = NULL;
		cnew->hdr.next = temp->hdr.next;
		temp->hdr.next = cnew;
		temp->hdr.chunk_size = req_size;
	}
	/* remove block from the free list */
	if (last == NULL)
		free_head = temp->hdr.next;
	else
		last->hdr.next = temp->hdr.next;

	/* add block to the malloc list */
	temp->hdr.next = malloc_head;
	malloc_head = temp;

	temp->hdr.caller = caller_addr;
	temp->hdr.check_mark = USED_MARKER;
	temp->hdr.used_length = length;
	temp->data[length] = EOD_MARKER;

	reentrancy_check--;

	return ((void *) temp->data);

}				/* allocate_memory */



/*-------------------------------------------------------------------------*/
void       *
malloc(ULONG length)
{
	void       *caller_addr = (void *) *((ULONG *) & length - 1);

	return (allocate_memory(length, caller_addr));

}				/* malloc */



/*-------------------------------------------------------------------------*/
void
free(void *ptr)
{
	mem_type   *cur;
	mem_type   *temp;
	mem_type   *last;

	/* Don't try to free null */
	if (ptr == NULL) {
		message("FREE - NULL pointer");
		*(ULONG *) 1 = 1L;
	}
	if (++reentrancy_check > 1) {
		message("FREE - reentrancy detected");
		*(ULONG *) 1 = 1L;
	}
	/* subtract off mem_type header */
	cur = (mem_type *) ((char *) ptr - sizeof (struct mem_hdr));

	/* check data length integrity */
	if (cur->data[cur->hdr.used_length] != EOD_MARKER) {
		message("FREE - end of data corrupted");
		*(ULONG *) 1 = 1L;
	}
	/* find the current memory in the malloc list */
	last = NULL;
	for (temp = malloc_head; temp != NULL; temp = temp->hdr.next) {
		if (temp->hdr.check_mark != USED_MARKER) {
			message("FREE - corrupt malloc list");
			*(ULONG *) 1 = 1L;
		}
		if (temp == cur)
			break;
		last = temp;
	}

	if (temp == NULL) {
		message("FREE - pointer not found");
		*(ULONG *) 1 = 1L;
	}
	/* remove block from the malloc list */
	if (last == NULL)
		malloc_head = temp->hdr.next;
	else
		last->hdr.next = temp->hdr.next;

	cur->hdr.check_mark = FREE_MARKER;

	/* add block by insertion sort to the free list */
	last = NULL;
	for (temp = free_head; temp != NULL; temp = temp->hdr.next) {
		if (temp > cur)
			break;
		last = temp;
	}
	cur->hdr.next = temp;
	if (last == NULL)
		free_head = cur;
	else
		last->hdr.next = cur;

	/* do garbage collection */

	/* forward chunk reconciliation */
	if (cur->hdr.next != NULL) {
		temp = (mem_type *) ((char *) cur + cur->hdr.chunk_size);
		if (temp == cur->hdr.next) {
			cur->hdr.next = temp->hdr.next;
			cur->hdr.chunk_size += temp->hdr.chunk_size;
			temp->hdr.check_mark = 0L;
			temp->hdr.next = NULL;
		}
	}
	/* reverse chunk reconciliation */
	if (last != NULL) {
		temp = (mem_type *) ((char *) last + last->hdr.chunk_size);
		if (temp == cur) {
			last->hdr.next = temp->hdr.next;
			last->hdr.chunk_size += temp->hdr.chunk_size;
			temp->hdr.check_mark = 0L;
			temp->hdr.next = NULL;
		}
	}
	reentrancy_check--;

}				/* free */



/*-------------------------------------------------------------------------*/
void       *
calloc(ULONG nelem, ULONG length)
{
	void       *caller_addr = (void *) *((ULONG *) & nelem - 1);
	register void *temp;

	length *= nelem;
	temp = allocate_memory(length, caller_addr);
	(void) memset(temp, 0, length);
	return (temp);

}				/* calloc */



/*-------------------------------------------------------------------------*/
void       *
realloc(void *ptr, ULONG new_length)
{
	void       *caller_addr = (void *) *((ULONG *) & ptr - 1);
	mem_type   *temp;
	ULONG       alloc_length;
	void       *cnew;

	if (new_length == 0) {
		if (ptr)
			free(ptr);
		return (NULL);
	}
	if (ptr == NULL)
		return (allocate_memory(new_length, caller_addr));

	temp = (mem_type *) ((char *) ptr - sizeof (struct mem_hdr));

	if (temp->hdr.check_mark != USED_MARKER) {
		message("REALLOC - corrupt malloc list");
		*(ULONG *) 1 = 1L;
	}
	alloc_length = temp->hdr.chunk_size - sizeof (mem_type);

	if (new_length <= alloc_length) {
		/* check data length integrity */
		if (temp->data[temp->hdr.used_length] != EOD_MARKER) {
			message("FREE - end of data corrupted");
			*(ULONG *) 1 = 1L;
		}
		/* update the info */
		temp->hdr.used_length = new_length;
		temp->data[new_length] = EOD_MARKER;
	} else {
		/* we need a new chunk */
		cnew = allocate_memory(new_length, caller_addr);
		(void) memcpy(cnew, ptr, temp->hdr.used_length);
		free(ptr);
		ptr = cnew;
	}

	return (ptr);

}				/* realloc */