]> git.ipfire.org Git - thirdparty/squid.git/blob - src/cbdata.cc
Summary: Fix cbdata debugging memory leak.
[thirdparty/squid.git] / src / cbdata.cc
1
2 /*
3 * $Id: cbdata.cc,v 1.60 2003/09/01 23:41:17 robertc Exp $
4 *
5 * DEBUG: section 45 Callback Data Registry
6 * ORIGINAL AUTHOR: Duane Wessels
7 * Modified by Moez Mahfoudh (08/12/2000)
8 * History added by Robert Collins (2002-10-25)
9 *
10 * SQUID Web Proxy Cache http://www.squid-cache.org/
11 * ----------------------------------------------------------
12 *
13 * Squid is the result of efforts by numerous individuals from
14 * the Internet community; see the CONTRIBUTORS file for full
15 * details. Many organizations have provided support for Squid's
16 * development; see the SPONSORS file for full details. Squid is
17 * Copyrighted (C) 2001 by the Regents of the University of
18 * California; see the COPYRIGHT file for full details. Squid
19 * incorporates software developed and/or copyrighted by other
20 * sources; see the CREDITS file for full details.
21 *
22 * This program is free software; you can redistribute it and/or modify
23 * it under the terms of the GNU General Public License as published by
24 * the Free Software Foundation; either version 2 of the License, or
25 * (at your option) any later version.
26 *
27 * This program is distributed in the hope that it will be useful,
28 * but WITHOUT ANY WARRANTY; without even the implied warranty of
29 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
30 * GNU General Public License for more details.
31 *
32 * You should have received a copy of the GNU General Public License
33 * along with this program; if not, write to the Free Software
34 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
35 *
36 */
37
38 /*
39 * These routines manage a set of registered callback data pointers.
40 * One of the easiest ways to make Squid coredump is to issue a
41 * callback to for some data structure which has previously been
42 * freed. With these routines, we register (add) callback data
43 * pointers, lock them just before registering the callback function,
44 * validate them before issuing the callback, and then free them
45 * when finished.
46 */
47
48 #include "squid.h"
49 #include "Store.h"
50 #if CBDATA_DEBUG
51 #include "Stack.h"
52 #endif
53 #include "Generic.h"
54
55 static int cbdataCount = 0;
56 #if CBDATA_DEBUG
57 dlink_list cbdataEntries;
58 #endif
59
60 #if CBDATA_DEBUG
61
62 class CBDataCall
63 {
64
65 public:
66 CBDataCall (char const *callLabel, char const *aFile, int aLine) : label(callLabel), file(aFile), line(aLine){}
67
68 char const *label;
69 char const *file;
70 int line;
71 };
72
73 #endif
74
75 #define OFFSET_OF(TYPE, MEMBER) ((size_t) &(((TYPE) *)0)->(MEMBER))
76
77 class cbdata
78 {
79
80 public:
81 #if CBDATA_DEBUG
82
83 void dump(StoreEntry *)const;
84 #endif
85
86 void *operator new(size_t size, void *where);
87 void operator delete(void *where, void *where);
88
89 ~cbdata();
90 int valid;
91 int locks;
92 cbdata_type type;
93 #if CBDATA_DEBUG
94
95 void addHistory(char const *label, char const *file, int line)
96 {
97 if (calls.size() > 1000)
98 return;
99
100 calls.push_back(new CBDataCall(label, file, line));
101 }
102
103 dlink_node link;
104 const char *file;
105 int line;
106 Stack<CBDataCall*> calls;
107 #endif
108
109 /* cookie used while debugging */
110 long cookie;
111 /* MUST be the last per-instance member */
112 /* TODO: examine making cbdata templated on this - so we get type
113 * safe access to data - RBC 20030902 */
114 void *data;
115 void check() const { assert(cookie == ((long)this ^ Cookie));}
116
117 size_t dataSize() const { return sizeof(data);}
118
119 static const long Cookie;
120 static long MakeOffset();
121 static const long Offset;
122 };
123
124 const long cbdata::Cookie((long)0xDEADBEEF);
125 const long cbdata::Offset(MakeOffset());
126
127 void *
128 cbdata::operator new(size_t size, void *where)
129 {
130 // assert (size == sizeof(cbdata));
131 return where;
132 }
133
134 void
135 cbdata::operator delete(void *where, void *where2)
136 {
137 /* Only ever invoked when placement new throws
138 * an exception. Used to prevent an incorrect
139 * free.
140 */
141 }
142
143 long
144 cbdata::MakeOffset()
145 {
146 cbdata *zero = (cbdata *)0L;
147 void **dataOffset = &zero->data;
148 return (long)dataOffset;
149 }
150
151 static OBJH cbdataDump;
152 #ifdef CBDATA_DEBUG
153 static OBJH cbdataDumpHistory;
154 #endif
155
156 struct CBDataIndex
157 {
158 MemPool *pool;
159 FREE *free_func;
160 }
161
162 *cbdata_index = NULL;
163 int cbdata_types = 0;
164
165 cbdata::~cbdata()
166 {
167 #if CBDATA_DEBUG
168 CBDataCall *aCall;
169
170 while ((aCall = calls.pop()))
171 delete aCall;
172
173 #endif
174
175 FREE *free_func = cbdata_index[type].free_func;
176
177 if (free_func)
178 free_func(&data);
179 }
180
181 static void
182 cbdataInternalInitType(cbdata_type type, const char *name, int size, FREE * free_func)
183 {
184 char *label;
185
186 if (type >= cbdata_types) {
187 cbdata_index = (CBDataIndex *)xrealloc(cbdata_index, (type + 1) * sizeof(*cbdata_index));
188 memset(&cbdata_index[cbdata_types], 0,
189 (type + 1 - cbdata_types) * sizeof(*cbdata_index));
190 cbdata_types = type + 1;
191 }
192
193 if (cbdata_index[type].pool)
194 return;
195
196 label = (char *)xmalloc(strlen(name) + 20);
197
198 snprintf(label, strlen(name) + 20, "cbdata %s (%d)", name, (int) type);
199
200 assert((size_t)cbdata::Offset == (sizeof(cbdata) - ((cbdata *)NULL)->dataSize()));
201
202 cbdata_index[type].pool = memPoolCreate(label, size + cbdata::Offset);
203
204 cbdata_index[type].free_func = free_func;
205 }
206
207 cbdata_type
208 cbdataInternalAddType(cbdata_type type, const char *name, int size, FREE * free_func)
209 {
210 if (type)
211 return type;
212
213 type = (cbdata_type)cbdata_types;
214
215 cbdataInternalInitType(type, name, size, free_func);
216
217 return type;
218 }
219
220 void
221 cbdataInit(void)
222 {
223 debug(45, 3) ("cbdataInit\n");
224 cachemgrRegister("cbdata",
225 "Callback Data Registry Contents",
226 cbdataDump, 0, 1);
227 #if CBDATA_DEBUG
228
229 cachemgrRegister("cbdatahistory",
230 "Detailed call history for all current cbdata contents",
231 cbdataDumpHistory, 0, 1);
232 #endif
233 #define CREATE_CBDATA(type) cbdataInternalInitType(CBDATA_##type, #type, sizeof(type), NULL)
234 #define CREATE_CBDATA_FREE(type, free_func) cbdataInternalInitType(CBDATA_##type, #type, sizeof(type), free_func)
235 /* XXX
236 * most of these should be moved out to their respective module.
237 */
238 CREATE_CBDATA(ErrorState);
239 CREATE_CBDATA(FwdState);
240 CREATE_CBDATA(generic_cbdata);
241 CREATE_CBDATA(helper);
242 CREATE_CBDATA(helper_server);
243 CREATE_CBDATA(statefulhelper);
244 CREATE_CBDATA(helper_stateful_server);
245 CREATE_CBDATA_FREE(peer, peerDestroy);
246 CREATE_CBDATA(ps_state);
247 CREATE_CBDATA(RemovalPolicy);
248 CREATE_CBDATA(RemovalPolicyWalker);
249 CREATE_CBDATA(RemovalPurgeWalker);
250 }
251
252 void *
253 #if CBDATA_DEBUG
254 cbdataInternalAllocDbg(cbdata_type type, const char *file, int line)
255 #else
256 cbdataInternalAlloc(cbdata_type type)
257 #endif
258 {
259 cbdata *p;
260 assert(type > 0 && type < cbdata_types);
261 p = new (memPoolAlloc(cbdata_index[type].pool)) cbdata;
262 // p = (cbdata *)memPoolAlloc(cbdata_index[type].pool);
263
264 p->type = type;
265 p->valid = 1;
266 p->locks = 0;
267 p->cookie = (long) p ^ cbdata::Cookie;
268 cbdataCount++;
269 #if CBDATA_DEBUG
270
271 p->file = file;
272 p->line = line;
273 p->calls = Stack<CBDataCall *> ();
274 p->addHistory("Alloc", file, line);
275 dlinkAdd(p, &p->link, &cbdataEntries);
276 debug(45, 3) ("cbdataAlloc: %p %s:%d\n", &p->data, file, line);
277 #endif
278
279 return (void *) &p->data;
280 }
281
282 void *
283 #if CBDATA_DEBUG
284 cbdataInternalFreeDbg(void *p, const char *file, int line)
285 #else
286 cbdataInternalFree(void *p)
287 #endif
288 {
289 cbdata *c;
290 c = (cbdata *) (((char *) p) - cbdata::Offset);
291 #if CBDATA_DEBUG
292
293 debug(45, 3) ("cbdataFree: %p %s:%d\n", p, file, line);
294 #else
295
296 debug(45, 3) ("cbdataFree: %p\n", p);
297 #endif
298
299 c->check();
300 assert(c->valid);
301 c->valid = 0;
302 #if CBDATA_DEBUG
303
304 c->addHistory("Free", file, line);
305 #endif
306
307 if (c->locks) {
308 debug(45, 3) ("cbdataFree: %p has %d locks, not freeing\n",
309 p, c->locks);
310 return NULL;
311 }
312
313 cbdataCount--;
314 debug(45, 3) ("cbdataFree: Freeing %p\n", p);
315 #if CBDATA_DEBUG
316
317 dlinkDelete(&c->link, &cbdataEntries);
318 #endif
319
320 cbdata_type theType = c->type;
321 c->cbdata::~cbdata();
322
323 /* This is ugly. But: operator delete doesn't get
324 * the type parameter, so we can't use that
325 * to free the memory.
326 * So, we free it ourselves.
327 * Note that this means a non-placement
328 * new would be a seriously bad idea.
329 * Lastly, if we where a templated class,
330 * we could use the normal delete operator
331 * and it would Just Work. RBC 20030902
332 */
333 memPoolFree(cbdata_index[theType].pool, c);
334 return NULL;
335 }
336
337 void
338 #if CBDATA_DEBUG
339 cbdataInternalLockDbg(const void *p, const char *file, int line)
340 #else
341 cbdataInternalLock(const void *p)
342 #endif
343 {
344 cbdata *c;
345
346 if (p == NULL)
347 return;
348
349 c = (cbdata *) (((char *) p) - cbdata::Offset);
350
351 #if CBDATA_DEBUG
352
353 debug(45, 3) ("cbdataLock: %p=%d %s:%d\n", p, c ? c->locks + 1 : -1, file, line);
354
355 c->addHistory("Reference", file, line);
356
357 #else
358
359 debug(45, 3) ("cbdataLock: %p=%d\n", p, c ? c->locks + 1 : -1);
360
361 #endif
362
363 c->check();
364
365 assert(c->locks < 65535);
366
367 c->locks++;
368 }
369
370 void
371 #if CBDATA_DEBUG
372 cbdataInternalUnlockDbg(const void *p, const char *file, int line)
373 #else
374 cbdataInternalUnlock(const void *p)
375 #endif
376 {
377 cbdata *c;
378
379 if (p == NULL)
380 return;
381
382 c = (cbdata *) (((char *) p) - cbdata::Offset);
383
384 #if CBDATA_DEBUG
385
386 debug(45, 3) ("cbdataUnlock: %p=%d %s:%d\n", p, c ? c->locks - 1 : -1, file, line);
387
388 c->addHistory("Dereference", file, line);
389
390 #else
391
392 debug(45, 3) ("cbdataUnlock: %p=%d\n", p, c ? c->locks - 1 : -1);
393
394 #endif
395
396 c->check();
397
398 assert(c != NULL);
399
400 assert(c->locks > 0);
401
402 c->locks--;
403
404 if (c->valid || c->locks)
405 return;
406
407 cbdataCount--;
408
409 debug(45, 3) ("cbdataUnlock: Freeing %p\n", p);
410
411 #if CBDATA_DEBUG
412
413 dlinkDelete(&c->link, &cbdataEntries);
414
415 #endif
416
417 cbdata_type theType = c->type;
418
419 c->cbdata::~cbdata();
420
421 /* This is ugly. But: operator delete doesn't get
422 * the type parameter, so we can't use that
423 * to free the memory.
424 * So, we free it ourselves.
425 * Note that this means a non-placement
426 * new would be a seriously bad idea.
427 * Lastly, if we where a templated class,
428 * we could use the normal delete operator
429 * and it would Just Work. RBC 20030902
430 */
431 memPoolFree(cbdata_index[theType].pool, c);
432 }
433
434 int
435 cbdataReferenceValid(const void *p)
436 {
437 cbdata *c;
438
439 if (p == NULL)
440 return 1; /* A NULL pointer cannot become invalid */
441
442 debug(45, 3) ("cbdataReferenceValid: %p\n", p);
443
444 c = (cbdata *) (((char *) p) - cbdata::Offset);
445
446 c->check();
447
448 assert(c->locks > 0);
449
450 return c->valid;
451 }
452
453 int
454 #if CBDATA_DEBUG
455 cbdataInternalReferenceDoneValidDbg(void **pp, void **tp, const char *file, int line)
456 #else
457 cbdataInternalReferenceDoneValid(void **pp, void **tp)
458 #endif
459 {
460 void *p = (void *) *pp;
461 int valid = cbdataReferenceValid(p);
462 *pp = NULL;
463 #if CBDATA_DEBUG
464
465 cbdataInternalUnlockDbg(p, file, line);
466 #else
467
468 cbdataInternalUnlock(p);
469 #endif
470
471 if (valid) {
472 *tp = p;
473 return 1;
474 } else {
475 *tp = NULL;
476 return 0;
477 }
478 }
479
480 #if CBDATA_DEBUG
481 void
482 cbdata::dump(StoreEntry *sentry) const
483 {
484 storeAppendPrintf(sentry, "%c%p\t%d\t%d\t%20s:%-5d\n", valid ? ' ' :
485 '!', &data, type, locks, file, line);
486 }
487
488 struct CBDataDumper : public unary_function<cbdata, void>
489 {
490 CBDataDumper(StoreEntry *anEntry):where(anEntry){}
491
492 void operator()(cbdata const &x)
493 {
494 x.dump(where);
495 }
496
497 StoreEntry *where;
498 };
499
500 #endif
501
502 static void
503 cbdataDump(StoreEntry * sentry)
504 {
505 storeAppendPrintf(sentry, "%d cbdata entries\n", cbdataCount);
506 #if CBDATA_DEBUG
507
508 storeAppendPrintf(sentry, "Pointer\tType\tLocks\tAllocated by\n");
509 CBDataDumper dumper(sentry);
510 for_each (cbdataEntries, dumper);
511 storeAppendPrintf(sentry, "\n");
512 storeAppendPrintf(sentry, "types\tsize\tallocated\ttotal\n");
513
514 for (int i = 1; i < cbdata_types; i++) {
515 MemPool *pool = cbdata_index[i].pool;
516
517 if (pool) {
518 int obj_size = pool->obj_size - cbdata::Offset;
519 storeAppendPrintf(sentry, "%s\t%d\t%d\t%d\n", pool->label + 7, obj_size, pool->meter.inuse.level, obj_size * pool->meter.inuse.level);
520 }
521 }
522
523 #else
524 storeAppendPrintf(sentry, "detailed allocation information only available when compiled with CBDATA_DEBUG\n");
525
526 #endif
527
528 storeAppendPrintf(sentry, "\nsee also \"Memory utilization\" for detailed per type statistics\n");
529 }
530
531 #if CBDATA_DEBUG
532
533 struct CBDataCallDumper : public unary_function<CBDataCall, void>
534 {
535 CBDataCallDumper (StoreEntry *anEntry):where(anEntry){}
536
537 void operator()(CBDataCall const &x)
538 {
539 storeAppendPrintf(where, "%s\t%s\t%d\n", x.label, x.file, x.line);
540 }
541
542 StoreEntry *where;
543 };
544
545 struct CBDataHistoryDumper : public CBDataDumper
546 {
547 CBDataHistoryDumper(StoreEntry *anEntry):CBDataDumper(anEntry),where(anEntry), callDumper(anEntry){}
548
549 void operator()(cbdata const &x)
550 {
551 CBDataDumper::operator()(x);
552 storeAppendPrintf(where, "\n");
553 storeAppendPrintf(where, "Action\tFile\tLine\n");
554 for_each (x.calls,callDumper);
555 storeAppendPrintf(where, "\n");
556 }
557
558 StoreEntry *where;
559 CBDataCallDumper callDumper;
560 };
561
562 void
563 cbdataDumpHistory(StoreEntry *sentry)
564 {
565 storeAppendPrintf(sentry, "%d cbdata entries\n", cbdataCount);
566 storeAppendPrintf(sentry, "Pointer\tType\tLocks\tAllocated by\n");
567 CBDataHistoryDumper dumper(sentry);
568 for_each (cbdataEntries, dumper);
569 }
570
571 #endif