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