]>
Commit | Line | Data |
---|---|---|
1 | ||
2 | /* | |
3 | * $Id$ | |
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 | \defgroup CBDATAInternal Callback Data Allocator Internals | |
40 | \ingroup CBDATAAPI | |
41 | * | |
42 | * These routines manage a set of registered callback data pointers. | |
43 | * One of the easiest ways to make Squid coredump is to issue a | |
44 | * callback to for some data structure which has previously been | |
45 | * freed. With these routines, we register (add) callback data | |
46 | * pointers, lock them just before registering the callback function, | |
47 | * validate them before issuing the callback, and then free them | |
48 | * when finished. | |
49 | */ | |
50 | ||
51 | #include "cbdata.h" | |
52 | #include "CacheManager.h" | |
53 | #include "Store.h" | |
54 | #if CBDATA_DEBUG | |
55 | #include "Stack.h" | |
56 | #endif | |
57 | #include "Generic.h" | |
58 | ||
59 | #if WITH_VALGRIND | |
60 | #define HASHED_CBDATA 1 | |
61 | #endif | |
62 | ||
63 | static int cbdataCount = 0; | |
64 | #if CBDATA_DEBUG | |
65 | dlink_list cbdataEntries; | |
66 | #endif | |
67 | ||
68 | #if CBDATA_DEBUG | |
69 | ||
70 | class CBDataCall | |
71 | { | |
72 | ||
73 | public: | |
74 | CBDataCall (char const *callLabel, char const *aFile, int aLine) : label(callLabel), file(aFile), line(aLine) {} | |
75 | ||
76 | char const *label; | |
77 | char const *file; | |
78 | int line; | |
79 | }; | |
80 | ||
81 | #endif | |
82 | ||
83 | /// \ingroup CBDATAInternal | |
84 | #define OFFSET_OF(TYPE, MEMBER) ((size_t) &(((TYPE) *)0)->(MEMBER)) | |
85 | ||
86 | /// \ingroup CBDATAInternal | |
87 | class cbdata | |
88 | { | |
89 | /** \todo examine making cbdata templated on this - so we get type | |
90 | * safe access to data - RBC 20030902 */ | |
91 | public: | |
92 | #if HASHED_CBDATA | |
93 | hash_link hash; // Must be first | |
94 | #endif | |
95 | ||
96 | #if CBDATA_DEBUG | |
97 | ||
98 | void dump(StoreEntry *)const; | |
99 | #endif | |
100 | ||
101 | #if !HASHED_CBDATA | |
102 | void *operator new(size_t size, void *where); | |
103 | void operator delete(void *where, void *where2); | |
104 | #else | |
105 | MEMPROXY_CLASS(cndata); | |
106 | #endif | |
107 | ||
108 | ~cbdata(); | |
109 | int valid; | |
110 | int locks; | |
111 | cbdata_type type; | |
112 | #if CBDATA_DEBUG | |
113 | ||
114 | void addHistory(char const *label, char const *file, int line) { | |
115 | if (calls.size() > 1000) | |
116 | return; | |
117 | ||
118 | calls.push_back(new CBDataCall(label, file, line)); | |
119 | } | |
120 | ||
121 | dlink_node link; | |
122 | const char *file; | |
123 | int line; | |
124 | Stack<CBDataCall*> calls; | |
125 | #endif | |
126 | ||
127 | /* cookie used while debugging */ | |
128 | long cookie; | |
129 | void check(int line) const {assert(cookie == ((long)this ^ Cookie));} | |
130 | static const long Cookie; | |
131 | ||
132 | #if !HASHED_CBDATA | |
133 | size_t dataSize() const { return sizeof(data);} | |
134 | static long MakeOffset(); | |
135 | static const long Offset; | |
136 | /* MUST be the last per-instance member */ | |
137 | void *data; | |
138 | #endif | |
139 | ||
140 | }; | |
141 | ||
142 | const long cbdata::Cookie((long)0xDEADBEEF); | |
143 | #if !HASHED_CBDATA | |
144 | const long cbdata::Offset(MakeOffset()); | |
145 | ||
146 | void * | |
147 | cbdata::operator new(size_t size, void *where) | |
148 | { | |
149 | // assert (size == sizeof(cbdata)); | |
150 | return where; | |
151 | } | |
152 | ||
153 | /** | |
154 | * Only ever invoked when placement new throws | |
155 | * an exception. Used to prevent an incorrect | |
156 | * free. | |
157 | */ | |
158 | void | |
159 | cbdata::operator delete(void *where, void *where2) | |
160 | { | |
161 | ; // empty. | |
162 | } | |
163 | ||
164 | long | |
165 | cbdata::MakeOffset() | |
166 | { | |
167 | cbdata *zero = (cbdata *)0L; | |
168 | void **dataOffset = &zero->data; | |
169 | return (long)dataOffset; | |
170 | } | |
171 | #else | |
172 | MEMPROXY_CLASS_INLINE(cbdata); | |
173 | #endif | |
174 | ||
175 | static OBJH cbdataDump; | |
176 | #ifdef CBDATA_DEBUG | |
177 | static OBJH cbdataDumpHistory; | |
178 | #endif | |
179 | ||
180 | /// \ingroup CBDATAInternal | |
181 | struct CBDataIndex { | |
182 | MemAllocator *pool; | |
183 | FREE *free_func; | |
184 | } | |
185 | *cbdata_index = NULL; | |
186 | ||
187 | /// \ingroup CBDATAInternal | |
188 | int cbdata_types = 0; | |
189 | ||
190 | #if HASHED_CBDATA | |
191 | static hash_table *cbdata_htable = NULL; | |
192 | ||
193 | static int | |
194 | cbdata_cmp(const void *p1, const void *p2) | |
195 | { | |
196 | return (char *) p1 - (char *) p2; | |
197 | } | |
198 | ||
199 | static unsigned int | |
200 | cbdata_hash(const void *p, unsigned int mod) | |
201 | { | |
202 | return ((unsigned long) p >> 8) % mod; | |
203 | } | |
204 | #endif | |
205 | ||
206 | ||
207 | cbdata::~cbdata() | |
208 | { | |
209 | #if CBDATA_DEBUG | |
210 | CBDataCall *aCall; | |
211 | ||
212 | while ((aCall = calls.pop())) | |
213 | delete aCall; | |
214 | ||
215 | #endif | |
216 | ||
217 | FREE *free_func = cbdata_index[type].free_func; | |
218 | ||
219 | #if HASHED_CBDATA | |
220 | void *p = hash.key; | |
221 | #else | |
222 | void *p = &data; | |
223 | #endif | |
224 | ||
225 | if (free_func) | |
226 | free_func(p); | |
227 | } | |
228 | ||
229 | static void | |
230 | cbdataInternalInitType(cbdata_type type, const char *name, int size, FREE * free_func) | |
231 | { | |
232 | char *label; | |
233 | assert (type == cbdata_types + 1); | |
234 | ||
235 | cbdata_index = (CBDataIndex *)xrealloc(cbdata_index, (type + 1) * sizeof(*cbdata_index)); | |
236 | memset(&cbdata_index[type], 0, sizeof(*cbdata_index)); | |
237 | cbdata_types = type; | |
238 | ||
239 | label = (char *)xmalloc(strlen(name) + 20); | |
240 | ||
241 | snprintf(label, strlen(name) + 20, "cbdata %s (%d)", name, (int) type); | |
242 | ||
243 | #if !HASHED_CBDATA | |
244 | assert((size_t)cbdata::Offset == (sizeof(cbdata) - ((cbdata *)NULL)->dataSize())); | |
245 | size += cbdata::Offset; | |
246 | #endif | |
247 | ||
248 | cbdata_index[type].pool = memPoolCreate(label, size); | |
249 | ||
250 | cbdata_index[type].free_func = free_func; | |
251 | ||
252 | #if HASHED_CBDATA | |
253 | if (!cbdata_htable) | |
254 | cbdata_htable = hash_create(cbdata_cmp, 1 << 12, cbdata_hash); | |
255 | #endif | |
256 | } | |
257 | ||
258 | cbdata_type | |
259 | cbdataInternalAddType(cbdata_type type, const char *name, int size, FREE * free_func) | |
260 | { | |
261 | if (type) | |
262 | return type; | |
263 | ||
264 | type = (cbdata_type)(cbdata_types + 1); | |
265 | ||
266 | cbdataInternalInitType(type, name, size, free_func); | |
267 | ||
268 | return type; | |
269 | } | |
270 | ||
271 | void | |
272 | cbdataRegisterWithCacheManager(void) | |
273 | { | |
274 | CacheManager *manager=CacheManager::GetInstance(); | |
275 | manager->registerAction("cbdata", | |
276 | "Callback Data Registry Contents", | |
277 | cbdataDump, 0, 1); | |
278 | #if CBDATA_DEBUG | |
279 | ||
280 | manager->registerAction("cbdatahistory", | |
281 | "Detailed call history for all current cbdata contents", | |
282 | cbdataDumpHistory, 0, 1); | |
283 | #endif | |
284 | } | |
285 | ||
286 | void * | |
287 | #if CBDATA_DEBUG | |
288 | cbdataInternalAllocDbg(cbdata_type type, const char *file, int line) | |
289 | #else | |
290 | cbdataInternalAlloc(cbdata_type type) | |
291 | #endif | |
292 | { | |
293 | cbdata *c; | |
294 | void *p; | |
295 | assert(type > 0 && type <= cbdata_types); | |
296 | /* placement new: the pool alloc gives us cbdata + user type memory space | |
297 | * and we init it with cbdata at the start of it | |
298 | */ | |
299 | #if HASHED_CBDATA | |
300 | c = new cbdata; | |
301 | p = cbdata_index[type].pool->alloc(); | |
302 | c->hash.key = p; | |
303 | hash_join(cbdata_htable, &c->hash); | |
304 | #else | |
305 | c = new (cbdata_index[type].pool->alloc()) cbdata; | |
306 | p = (void *)&c->data; | |
307 | #endif | |
308 | ||
309 | c->type = type; | |
310 | c->valid = 1; | |
311 | c->locks = 0; | |
312 | c->cookie = (long) c ^ cbdata::Cookie; | |
313 | cbdataCount++; | |
314 | #if CBDATA_DEBUG | |
315 | ||
316 | c->file = file; | |
317 | c->line = line; | |
318 | c->calls = Stack<CBDataCall *> (); | |
319 | c->addHistory("Alloc", file, line); | |
320 | dlinkAdd(c, &c->link, &cbdataEntries); | |
321 | debugs(45, 3, "cbdataAlloc: " << p << " " << file << ":" << line); | |
322 | #endif | |
323 | ||
324 | return p; | |
325 | } | |
326 | ||
327 | void * | |
328 | #if CBDATA_DEBUG | |
329 | cbdataInternalFreeDbg(void *p, const char *file, int line) | |
330 | #else | |
331 | cbdataInternalFree(void *p) | |
332 | #endif | |
333 | { | |
334 | cbdata *c; | |
335 | #if HASHED_CBDATA | |
336 | c = (cbdata *) hash_lookup(cbdata_htable, p); | |
337 | #else | |
338 | c = (cbdata *) (((char *) p) - cbdata::Offset); | |
339 | #endif | |
340 | #if CBDATA_DEBUG | |
341 | ||
342 | debugs(45, 3, "cbdataFree: " << p << " " << file << ":" << line); | |
343 | #else | |
344 | ||
345 | debugs(45, 9, "cbdataFree: " << p); | |
346 | #endif | |
347 | ||
348 | c->check(__LINE__); | |
349 | assert(c->valid); | |
350 | c->valid = 0; | |
351 | #if CBDATA_DEBUG | |
352 | ||
353 | c->addHistory("Free", file, line); | |
354 | #endif | |
355 | ||
356 | if (c->locks) { | |
357 | debugs(45, 9, "cbdataFree: " << p << " has " << c->locks << " locks, not freeing"); | |
358 | return NULL; | |
359 | } | |
360 | ||
361 | cbdataCount--; | |
362 | debugs(45, 9, "cbdataFree: Freeing " << p); | |
363 | #if CBDATA_DEBUG | |
364 | ||
365 | dlinkDelete(&c->link, &cbdataEntries); | |
366 | #endif | |
367 | ||
368 | /* This is ugly. But: operator delete doesn't get | |
369 | * the type parameter, so we can't use that | |
370 | * to free the memory. | |
371 | * So, we free it ourselves. | |
372 | * Note that this means a non-placement | |
373 | * new would be a seriously bad idea. | |
374 | * Lastly, if we where a templated class, | |
375 | * we could use the normal delete operator | |
376 | * and it would Just Work. RBC 20030902 | |
377 | */ | |
378 | cbdata_type theType = c->type; | |
379 | #if HASHED_CBDATA | |
380 | hash_remove_link(cbdata_htable, &c->hash); | |
381 | delete c; | |
382 | cbdata_index[theType].pool->free((void *)p); | |
383 | #else | |
384 | c->cbdata::~cbdata(); | |
385 | cbdata_index[theType].pool->free(c); | |
386 | #endif | |
387 | return NULL; | |
388 | } | |
389 | ||
390 | void | |
391 | #if CBDATA_DEBUG | |
392 | cbdataInternalLockDbg(const void *p, const char *file, int line) | |
393 | #else | |
394 | cbdataInternalLock(const void *p) | |
395 | #endif | |
396 | { | |
397 | cbdata *c; | |
398 | ||
399 | if (p == NULL) | |
400 | return; | |
401 | ||
402 | #if HASHED_CBDATA | |
403 | c = (cbdata *) hash_lookup(cbdata_htable, p); | |
404 | #else | |
405 | c = (cbdata *) (((char *) p) - cbdata::Offset); | |
406 | #endif | |
407 | ||
408 | #if CBDATA_DEBUG | |
409 | ||
410 | debugs(45, 3, "cbdataLock: " << p << "=" << (c ? c->locks + 1 : -1) << " " << file << ":" << line); | |
411 | ||
412 | c->addHistory("Reference", file, line); | |
413 | ||
414 | #else | |
415 | ||
416 | debugs(45, 9, "cbdataLock: " << p << "=" << (c ? c->locks + 1 : -1)); | |
417 | ||
418 | #endif | |
419 | ||
420 | c->check(__LINE__); | |
421 | ||
422 | assert(c->locks < 65535); | |
423 | ||
424 | c->locks++; | |
425 | } | |
426 | ||
427 | void | |
428 | #if CBDATA_DEBUG | |
429 | cbdataInternalUnlockDbg(const void *p, const char *file, int line) | |
430 | #else | |
431 | cbdataInternalUnlock(const void *p) | |
432 | #endif | |
433 | { | |
434 | cbdata *c; | |
435 | ||
436 | if (p == NULL) | |
437 | return; | |
438 | ||
439 | #if HASHED_CBDATA | |
440 | c = (cbdata *) hash_lookup(cbdata_htable, p); | |
441 | #else | |
442 | c = (cbdata *) (((char *) p) - cbdata::Offset); | |
443 | #endif | |
444 | ||
445 | #if CBDATA_DEBUG | |
446 | ||
447 | debugs(45, 3, "cbdataUnlock: " << p << "=" << (c ? c->locks - 1 : -1) << " " << file << ":" << line); | |
448 | ||
449 | c->addHistory("Dereference", file, line); | |
450 | ||
451 | #else | |
452 | ||
453 | debugs(45, 9, "cbdataUnlock: " << p << "=" << (c ? c->locks - 1 : -1)); | |
454 | ||
455 | #endif | |
456 | ||
457 | c->check(__LINE__); | |
458 | ||
459 | assert(c != NULL); | |
460 | ||
461 | assert(c->locks > 0); | |
462 | ||
463 | c->locks--; | |
464 | ||
465 | if (c->valid || c->locks) | |
466 | return; | |
467 | ||
468 | cbdataCount--; | |
469 | ||
470 | debugs(45, 9, "cbdataUnlock: Freeing " << p); | |
471 | ||
472 | #if CBDATA_DEBUG | |
473 | ||
474 | dlinkDelete(&c->link, &cbdataEntries); | |
475 | ||
476 | #endif | |
477 | ||
478 | /* This is ugly. But: operator delete doesn't get | |
479 | * the type parameter, so we can't use that | |
480 | * to free the memory. | |
481 | * So, we free it ourselves. | |
482 | * Note that this means a non-placement | |
483 | * new would be a seriously bad idea. | |
484 | * Lastly, if we where a templated class, | |
485 | * we could use the normal delete operator | |
486 | * and it would Just Work. RBC 20030902 | |
487 | */ | |
488 | cbdata_type theType = c->type; | |
489 | #if HASHED_CBDATA | |
490 | hash_remove_link(cbdata_htable, &c->hash); | |
491 | delete c; | |
492 | cbdata_index[theType].pool->free((void *)p); | |
493 | #else | |
494 | c->cbdata::~cbdata(); | |
495 | cbdata_index[theType].pool->free(c); | |
496 | #endif | |
497 | } | |
498 | ||
499 | int | |
500 | cbdataReferenceValid(const void *p) | |
501 | { | |
502 | cbdata *c; | |
503 | ||
504 | if (p == NULL) | |
505 | return 1; /* A NULL pointer cannot become invalid */ | |
506 | ||
507 | debugs(45, 9, "cbdataReferenceValid: " << p); | |
508 | ||
509 | #if HASHED_CBDATA | |
510 | c = (cbdata *) hash_lookup(cbdata_htable, p); | |
511 | #else | |
512 | c = (cbdata *) (((char *) p) - cbdata::Offset); | |
513 | #endif | |
514 | ||
515 | c->check(__LINE__); | |
516 | ||
517 | assert(c->locks > 0); | |
518 | ||
519 | return c->valid; | |
520 | } | |
521 | ||
522 | int | |
523 | #if CBDATA_DEBUG | |
524 | cbdataInternalReferenceDoneValidDbg(void **pp, void **tp, const char *file, int line) | |
525 | #else | |
526 | cbdataInternalReferenceDoneValid(void **pp, void **tp) | |
527 | #endif | |
528 | { | |
529 | void *p = (void *) *pp; | |
530 | int valid = cbdataReferenceValid(p); | |
531 | *pp = NULL; | |
532 | #if CBDATA_DEBUG | |
533 | ||
534 | cbdataInternalUnlockDbg(p, file, line); | |
535 | #else | |
536 | ||
537 | cbdataInternalUnlock(p); | |
538 | #endif | |
539 | ||
540 | if (valid) { | |
541 | *tp = p; | |
542 | return 1; | |
543 | } else { | |
544 | *tp = NULL; | |
545 | return 0; | |
546 | } | |
547 | } | |
548 | ||
549 | #if CBDATA_DEBUG | |
550 | void | |
551 | cbdata::dump(StoreEntry *sentry) const | |
552 | { | |
553 | #if HASHED_CBDATA | |
554 | void *p = (void *)hash.key; | |
555 | #else | |
556 | void *p = (void *)&data; | |
557 | #endif | |
558 | storeAppendPrintf(sentry, "%c%p\t%d\t%d\t%20s:%-5d\n", valid ? ' ' : | |
559 | '!', p, type, locks, file, line); | |
560 | } | |
561 | ||
562 | struct CBDataDumper : public unary_function<cbdata, void> { | |
563 | CBDataDumper(StoreEntry *anEntry):where(anEntry) {} | |
564 | ||
565 | void operator()(cbdata const &x) { | |
566 | x.dump(where); | |
567 | } | |
568 | ||
569 | StoreEntry *where; | |
570 | }; | |
571 | ||
572 | #endif | |
573 | ||
574 | static void | |
575 | cbdataDump(StoreEntry * sentry) | |
576 | { | |
577 | storeAppendPrintf(sentry, "%d cbdata entries\n", cbdataCount); | |
578 | #if CBDATA_DEBUG | |
579 | ||
580 | storeAppendPrintf(sentry, "Pointer\tType\tLocks\tAllocated by\n"); | |
581 | CBDataDumper dumper(sentry); | |
582 | for_each (cbdataEntries, dumper); | |
583 | storeAppendPrintf(sentry, "\n"); | |
584 | storeAppendPrintf(sentry, "types\tsize\tallocated\ttotal\n"); | |
585 | ||
586 | for (int i = 1; i < cbdata_types; i++) { | |
587 | MemAllocator *pool = cbdata_index[i].pool; | |
588 | ||
589 | if (pool) { | |
590 | #if HASHED_CBDATA | |
591 | int obj_size = pool->objectSize(); | |
592 | #else | |
593 | int obj_size = pool->objectSize() - cbdata::Offset; | |
594 | #endif | |
595 | storeAppendPrintf(sentry, "%s\t%d\t%ld\t%ld\n", pool->objectType() + 7, obj_size, (long int)pool->getMeter().inuse.level, (long int)obj_size * pool->getMeter().inuse.level); | |
596 | } | |
597 | } | |
598 | ||
599 | #else | |
600 | storeAppendPrintf(sentry, "detailed allocation information only available when compiled with CBDATA_DEBUG\n"); | |
601 | ||
602 | #endif | |
603 | ||
604 | storeAppendPrintf(sentry, "\nsee also \"Memory utilization\" for detailed per type statistics\n"); | |
605 | } | |
606 | ||
607 | CBDATA_CLASS_INIT(generic_cbdata); | |
608 | ||
609 | #if CBDATA_DEBUG | |
610 | ||
611 | struct CBDataCallDumper : public unary_function<CBDataCall, void> { | |
612 | CBDataCallDumper (StoreEntry *anEntry):where(anEntry) {} | |
613 | ||
614 | void operator()(CBDataCall const &x) { | |
615 | storeAppendPrintf(where, "%s\t%s\t%d\n", x.label, x.file, x.line); | |
616 | } | |
617 | ||
618 | StoreEntry *where; | |
619 | }; | |
620 | ||
621 | struct CBDataHistoryDumper : public CBDataDumper { | |
622 | CBDataHistoryDumper(StoreEntry *anEntry):CBDataDumper(anEntry),where(anEntry), callDumper(anEntry) {} | |
623 | ||
624 | void operator()(cbdata const &x) { | |
625 | CBDataDumper::operator()(x); | |
626 | storeAppendPrintf(where, "\n"); | |
627 | storeAppendPrintf(where, "Action\tFile\tLine\n"); | |
628 | for_each (x.calls,callDumper); | |
629 | storeAppendPrintf(where, "\n"); | |
630 | } | |
631 | ||
632 | StoreEntry *where; | |
633 | CBDataCallDumper callDumper; | |
634 | }; | |
635 | ||
636 | void | |
637 | cbdataDumpHistory(StoreEntry *sentry) | |
638 | { | |
639 | storeAppendPrintf(sentry, "%d cbdata entries\n", cbdataCount); | |
640 | storeAppendPrintf(sentry, "Pointer\tType\tLocks\tAllocated by\n"); | |
641 | CBDataHistoryDumper dumper(sentry); | |
642 | for_each (cbdataEntries, dumper); | |
643 | } | |
644 | ||
645 | #endif |