1 /*
2 * Copyright (C) 1996-2021 The Squid Software Foundation and contributors
3 *
4 * Squid software is distributed under GPLv2+ license and includes
5 * contributions from numerous individuals and organizations.
6 * Please see the COPYING and CONTRIBUTORS files for details.
7 */
8
9 /* DEBUG: section 45 Callback Data Registry */
10
11 #include "squid.h"
12 #include "cbdata.h"
13 #include "Generic.h"
14 #include "mem/Pool.h"
15 #include "mgr/Registration.h"
16 #include "Store.h"
17
18 #include <climits>
19 #include <cstddef>
20
21 #if USE_CBDATA_DEBUG
22 #include <algorithm>
23 #include <vector>
24 #endif
25
26 #if WITH_VALGRIND
27 #include <map>
28 #endif
29
30 static int cbdataCount = 0;
31 #if USE_CBDATA_DEBUG
32 dlink_list cbdataEntries;
33 #endif
34
35 #if USE_CBDATA_DEBUG
36
37 class CBDataCall
38 {
39
40 public:
CBDataCall(char const * callLabel,char const * aFile,int aLine)41 CBDataCall (char const *callLabel, char const *aFile, int aLine) : label(callLabel), file(aFile), line(aLine) {}
42
43 char const *label;
44 char const *file;
45 int line;
46 };
47
48 #endif
49
50 /**
51 * Manage a set of registered callback data pointers.
52 * One of the easiest ways to make Squid coredump is to issue a
53 * callback to for some data structure which has previously been
54 * freed. With this class, we register (add) callback data
55 * pointers, lock them just before registering the callback function,
56 * validate them before issuing the callback, and then free them
57 * when finished.
58 */
59 class cbdata
60 {
61 #if !WITH_VALGRIND
62 public:
operator new(size_t,void * where)63 void *operator new(size_t, void *where) {return where;}
64 /**
65 * Only ever invoked when placement new throws
66 * an exception. Used to prevent an incorrect free.
67 */
operator delete(void *,void *)68 void operator delete(void *, void *) {}
69 #else
70 MEMPROXY_CLASS(cbdata);
71 #endif
72
73 /** \todo examine making cbdata templated on this - so we get type
74 * safe access to data - RBC 20030902 */
75 public:
76 #if USE_CBDATA_DEBUG
77
78 void dump(StoreEntry *)const;
79 #endif
cbdata()80 cbdata() :
81 valid(0),
82 locks(0),
83 type(CBDATA_UNKNOWN),
84 #if USE_CBDATA_DEBUG
85 file(NULL),
86 line(0),
87 #endif
88 cookie(0),
89 data(NULL)
90 {}
91 ~cbdata();
92
FromUserData(const void * p)93 static cbdata *FromUserData(const void *p) {
94 #if WITH_VALGRIND
95 return cbdata_htable.at(p);
96 #else
97 const auto t = static_cast<const char *>(p) - offsetof(cbdata, data);
98 return reinterpret_cast<cbdata *>(const_cast<char *>(t));
99 #endif
100 }
101
102 int valid;
103 int32_t locks;
104 cbdata_type type;
105 #if USE_CBDATA_DEBUG
106
addHistory(char const * label,char const * aFile,int aLine)107 void addHistory(char const *label, char const *aFile, int aLine) {
108 if (calls.size() > 1000)
109 return;
110
111 calls.push_back(new CBDataCall(label, aFile, aLine));
112 }
113
114 dlink_node link;
115 const char *file;
116 int line;
117 std::vector<CBDataCall*> calls; // used as a stack with random access operator
118 #endif
119
120 /* cookie used while debugging */
121 long cookie;
check(int) const122 void check(int) const {assert(cookie == ((long)this ^ Cookie));}
123 static const long Cookie;
124
125 #if !WITH_VALGRIND
dataSize() const126 size_t dataSize() const { return sizeof(data);}
127 #endif
128 /* MUST be the last per-instance member */
129 void *data;
130 };
131
132 static_assert(std::is_standard_layout<cbdata>::value, "the behavior of offsetof(cbdata) is defined");
133
134 const long cbdata::Cookie((long)0xDEADBEEF);
135
136 static OBJH cbdataDump;
137 #if USE_CBDATA_DEBUG
138 static OBJH cbdataDumpHistory;
139 #endif
140
141 struct CBDataIndex {
142 MemAllocator *pool;
143 }
144 *cbdata_index = NULL;
145
146 int cbdata_types = 0;
147
148 #if WITH_VALGRIND
149 static std::map<const void *, cbdata *> cbdata_htable;
150 #endif
151
~cbdata()152 cbdata::~cbdata()
153 {
154 #if USE_CBDATA_DEBUG
155
156 while (!calls.empty()) {
157 delete calls.back();
158 calls.pop_back();
159 }
160
161 #endif
162
163 #if WITH_VALGRIND
164 void *p = data;
165 #else
166 void *p = this;
167 #endif
168 cbdata_index[type].pool->freeOne(p);
169 }
170
171 static void
cbdataInternalInitType(cbdata_type type,const char * name,int size)172 cbdataInternalInitType(cbdata_type type, const char *name, int size)
173 {
174 char *label;
175 assert (type == cbdata_types + 1);
176
177 cbdata_index = (CBDataIndex *)xrealloc(cbdata_index, (type + 1) * sizeof(*cbdata_index));
178 memset(&cbdata_index[type], 0, sizeof(*cbdata_index));
179 cbdata_types = type;
180
181 label = (char *)xmalloc(strlen(name) + 20);
182
183 snprintf(label, strlen(name) + 20, "cbdata %s (%d)", name, (int) type);
184
185 #if !WITH_VALGRIND
186 size += offsetof(cbdata, data);
187 #endif
188
189 cbdata_index[type].pool = memPoolCreate(label, size);
190 }
191
192 cbdata_type
cbdataInternalAddType(cbdata_type type,const char * name,int size)193 cbdataInternalAddType(cbdata_type type, const char *name, int size)
194 {
195 if (type)
196 return type;
197
198 type = (cbdata_type)(cbdata_types + 1);
199
200 cbdataInternalInitType(type, name, size);
201
202 return type;
203 }
204
205 void
cbdataRegisterWithCacheManager(void)206 cbdataRegisterWithCacheManager(void)
207 {
208 Mgr::RegisterAction("cbdata",
209 "Callback Data Registry Contents",
210 cbdataDump, 0, 1);
211 #if USE_CBDATA_DEBUG
212
213 Mgr::RegisterAction("cbdatahistory",
214 "Detailed call history for all current cbdata contents",
215 cbdataDumpHistory, 0, 1);
216 #endif
217 }
218
219 void *
cbdataInternalAlloc(cbdata_type type,const char * file,int line)220 cbdataInternalAlloc(cbdata_type type, const char *file, int line)
221 {
222 cbdata *c;
223 void *p;
224 assert(type > 0 && type <= cbdata_types);
225 /* placement new: the pool alloc gives us cbdata + user type memory space
226 * and we init it with cbdata at the start of it
227 */
228 #if WITH_VALGRIND
229 c = new cbdata;
230 p = cbdata_index[type].pool->alloc();
231 c->data = p;
232 cbdata_htable.emplace(p,c);
233 #else
234 c = new (cbdata_index[type].pool->alloc()) cbdata;
235 p = (void *)&c->data;
236 #endif
237
238 c->type = type;
239 c->valid = 1;
240 c->locks = 0;
241 c->cookie = (long) c ^ cbdata::Cookie;
242 ++cbdataCount;
243 #if USE_CBDATA_DEBUG
244
245 c->file = file;
246 c->line = line;
247 c->calls = std::vector<CBDataCall *> ();
248 c->addHistory("Alloc", file, line);
249 dlinkAdd(c, &c->link, &cbdataEntries);
250 debugs(45, 3, "Allocating " << p << " " << file << ":" << line);
251 #else
252 debugs(45, 9, "Allocating " << p);
253 #endif
254
255 return p;
256 }
257
258 void
cbdataRealFree(cbdata * c,const char * file,const int line)259 cbdataRealFree(cbdata *c, const char *file, const int line)
260 {
261 #if WITH_VALGRIND
262 void *p = c->data;
263 #else
264 void *p = (void *)&c->data;
265 #endif
266
267 --cbdataCount;
268 #if USE_CBDATA_DEBUG
269 debugs(45, 3, "Freeing " << p << ' ' << file << ':' << line);
270 dlinkDelete(&c->link, &cbdataEntries);
271 #else
272 debugs(45, 9, "Freeing " << p);
273 #endif
274
275 #if WITH_VALGRIND
276 cbdata_htable.erase(p);
277 delete c;
278 #else
279 /* This is ugly. But: operator delete doesn't get
280 * the type parameter, so we can't use that
281 * to free the memory.
282 * So, we free it ourselves.
283 * Note that this means a non-placement
284 * new would be a seriously bad idea.
285 * Lastly, if we where a templated class,
286 * we could use the normal delete operator
287 * and it would Just Work. RBC 20030902
288 */
289 c->cbdata::~cbdata();
290 #endif
291 }
292
293 void *
cbdataInternalFree(void * p,const char * file,int line)294 cbdataInternalFree(void *p, const char *file, int line)
295 {
296 auto *c = cbdata::FromUserData(p);
297 #if USE_CBDATA_DEBUG
298 debugs(45, 3, p << " " << file << ":" << line);
299 #else
300 debugs(45, 9, p);
301 #endif
302
303 c->check(__LINE__);
304 assert(c->valid);
305 c->valid = 0;
306 #if USE_CBDATA_DEBUG
307
308 c->addHistory("Free", file, line);
309 #endif
310
311 if (c->locks) {
312 debugs(45, 9, p << " has " << c->locks << " locks, not freeing");
313 return NULL;
314 }
315
316 cbdataRealFree(c, file, line);
317 return NULL;
318 }
319
320 void
321 #if USE_CBDATA_DEBUG
cbdataInternalLockDbg(const void * p,const char * file,int line)322 cbdataInternalLockDbg(const void *p, const char *file, int line)
323 #else
324 cbdataInternalLock(const void *p)
325 #endif
326 {
327 if (p == NULL)
328 return;
329
330 auto *c = cbdata::FromUserData(p);
331
332 #if USE_CBDATA_DEBUG
333 debugs(45, 3, p << "=" << (c ? c->locks + 1 : -1) << " " << file << ":" << line);
334 c->addHistory("Reference", file, line);
335 #else
336 debugs(45, 9, p << "=" << (c ? c->locks + 1 : -1));
337 #endif
338
339 c->check(__LINE__);
340
341 assert(c->locks < INT_MAX);
342
343 ++ c->locks;
344 }
345
346 void
347 #if USE_CBDATA_DEBUG
cbdataInternalUnlockDbg(const void * p,const char * file,int line)348 cbdataInternalUnlockDbg(const void *p, const char *file, int line)
349 #else
350 cbdataInternalUnlock(const void *p)
351 #endif
352 {
353 if (p == NULL)
354 return;
355
356 auto *c = cbdata::FromUserData(p);
357
358 #if USE_CBDATA_DEBUG
359 debugs(45, 3, p << "=" << (c ? c->locks - 1 : -1) << " " << file << ":" << line);
360 c->addHistory("Dereference", file, line);
361 #else
362 debugs(45, 9, p << "=" << (c ? c->locks - 1 : -1));
363 #endif
364
365 c->check(__LINE__);
366
367 assert(c != NULL);
368
369 assert(c->locks > 0);
370
371 -- c->locks;
372
373 if (c->locks)
374 return;
375
376 if (c->valid) {
377 #if USE_CBDATA_DEBUG
378 debugs(45, 3, "CBDATA valid with no references ... cbdata=" << p << " " << file << ":" << line);
379 #endif
380 return;
381 }
382
383 #if USE_CBDATA_DEBUG
384 cbdataRealFree(c, file, line);
385 #else
386 cbdataRealFree(c, NULL, 0);
387 #endif
388 }
389
390 int
cbdataReferenceValid(const void * p)391 cbdataReferenceValid(const void *p)
392 {
393 if (p == NULL)
394 return 1; /* A NULL pointer cannot become invalid */
395
396 debugs(45, 9, p);
397
398 const auto c = cbdata::FromUserData(p);
399
400 c->check(__LINE__);
401
402 assert(c->locks > 0);
403
404 return c->valid;
405 }
406
407 int
408 #if USE_CBDATA_DEBUG
cbdataInternalReferenceDoneValidDbg(void ** pp,void ** tp,const char * file,int line)409 cbdataInternalReferenceDoneValidDbg(void **pp, void **tp, const char *file, int line)
410 #else
411 cbdataInternalReferenceDoneValid(void **pp, void **tp)
412 #endif
413 {
414 void *p = (void *) *pp;
415 int valid = cbdataReferenceValid(p);
416 *pp = NULL;
417 #if USE_CBDATA_DEBUG
418
419 cbdataInternalUnlockDbg(p, file, line);
420 #else
421
422 cbdataInternalUnlock(p);
423 #endif
424
425 if (valid) {
426 *tp = p;
427 return 1;
428 } else {
429 *tp = NULL;
430 return 0;
431 }
432 }
433
434 #if USE_CBDATA_DEBUG
435 void
dump(StoreEntry * sentry) const436 cbdata::dump(StoreEntry *sentry) const
437 {
438 #if WITH_VALGRIND
439 void *p = data;
440 #else
441 void *p = (void *)&data;
442 #endif
443 storeAppendPrintf(sentry, "%c%p\t%d\t%d\t%20s:%-5d\n", valid ? ' ' :
444 '!', p, type, locks, file, line);
445 }
446
447 struct CBDataDumper : public unary_function<cbdata, void> {
CBDataDumperCBDataDumper448 CBDataDumper(StoreEntry *anEntry):where(anEntry) {}
449
operator ()CBDataDumper450 void operator()(cbdata const &x) {
451 x.dump(where);
452 }
453
454 StoreEntry *where;
455 };
456
457 #endif
458
459 static void
cbdataDump(StoreEntry * sentry)460 cbdataDump(StoreEntry * sentry)
461 {
462 storeAppendPrintf(sentry, "%d cbdata entries\n", cbdataCount);
463 #if USE_CBDATA_DEBUG
464
465 storeAppendPrintf(sentry, "Pointer\tType\tLocks\tAllocated by\n");
466 CBDataDumper dumper(sentry);
467 for_each (cbdataEntries, dumper);
468 storeAppendPrintf(sentry, "\n");
469 storeAppendPrintf(sentry, "types\tsize\tallocated\ttotal\n");
470
471 for (int i = 1; i < cbdata_types; ++i) {
472 MemAllocator *pool = cbdata_index[i].pool;
473
474 if (pool) {
475 #if WITH_VALGRIND
476 int obj_size = pool->objectSize();
477 #else
478 int obj_size = pool->objectSize() - offsetof(cbdata, data);
479 #endif
480 storeAppendPrintf(sentry, "%s\t%d\t%ld\t%ld\n", pool->objectType() + 7, obj_size, (long int)pool->getMeter().inuse.currentLevel(), (long int)obj_size * pool->getMeter().inuse.currentLevel());
481 }
482 }
483
484 #else
485 storeAppendPrintf(sentry, "detailed allocation information only available when compiled with --enable-debug-cbdata\n");
486
487 #endif
488
489 storeAppendPrintf(sentry, "\nsee also \"Memory utilization\" for detailed per type statistics\n");
490 }
491
492 CBDATA_CLASS_INIT(generic_cbdata);
493
494 #if USE_CBDATA_DEBUG
495
496 struct CBDataCallDumper : public unary_function<CBDataCall, void> {
CBDataCallDumperCBDataCallDumper497 CBDataCallDumper (StoreEntry *anEntry):where(anEntry) {}
498
operator ()CBDataCallDumper499 void operator()(CBDataCall * const &x) {
500 storeAppendPrintf(where, "%s\t%s\t%d\n", x->label, x->file, x->line);
501 }
502
503 StoreEntry *where;
504 };
505
506 struct CBDataHistoryDumper : public CBDataDumper {
CBDataHistoryDumperCBDataHistoryDumper507 CBDataHistoryDumper(StoreEntry *anEntry):CBDataDumper(anEntry),where(anEntry), callDumper(anEntry) {}
508
operator ()CBDataHistoryDumper509 void operator()(cbdata const &x) {
510 CBDataDumper::operator()(x);
511 storeAppendPrintf(where, "\n");
512 storeAppendPrintf(where, "Action\tFile\tLine\n");
513 std::for_each (x.calls.begin(), x.calls.end(), callDumper);
514 storeAppendPrintf(where, "\n");
515 }
516
517 StoreEntry *where;
518 CBDataCallDumper callDumper;
519 };
520
521 void
cbdataDumpHistory(StoreEntry * sentry)522 cbdataDumpHistory(StoreEntry *sentry)
523 {
524 storeAppendPrintf(sentry, "%d cbdata entries\n", cbdataCount);
525 storeAppendPrintf(sentry, "Pointer\tType\tLocks\tAllocated by\n");
526 CBDataHistoryDumper dumper(sentry);
527 for_each (cbdataEntries, dumper);
528 }
529
530 #endif
531
532