1 /*
2  * Copyright (c) 2014 Sippy Software, Inc., http://www.sippysoft.com
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24  * SUCH DAMAGE.
25  *
26  */
27 
28 /*
29  * Simple memory debug layer to track any unallocated memory as well as to
30  * catch any other common mistakes, such as double free or freeing of
31  * unallocated memory. Our attitude here is "fail with core dump early" if
32  * some error of inconsistency is found to aid debugging. Some extra smarts
33  * can be added, such as guard area to detect any buffer overflows.
34  */
35 
36 #include <sys/types.h>
37 #include <pthread.h>
38 #include <inttypes.h>
39 #include <stdarg.h>
40 #include <stddef.h>
41 #include <stdint.h>
42 #include <stdio.h>
43 #include <stdlib.h>
44 #include <string.h>
45 
46 #include "rtpp_log.h"
47 #include "rtpp_types.h"
48 #include "rtpp_refcnt.h"
49 #include "rtpp_log_obj.h"
50 #include "rtpp_memdeb.h"
51 #include "rtpp_memdeb_internal.h"
52 #include "rtpp_memdeb_stats.h"
53 
54 #undef malloc
55 #undef free
56 #undef realloc
57 #undef strdup
58 #undef asprintf
59 #undef vasprintf
60 #undef memcpy
61 
62 #define _memcpy(dp, sp, len) rtpp_memdeb_memcpy((dp), (sp), (len), \
63   p, __FILE__, __LINE__, __func__)
64 
65 #define UNUSED(x) (void)(x)
66 
67 #define MEMDEB_SIGNATURE 0x8b26e00041dfdec6UL
68 
69 #define MEMDEB_SIGNATURE_ALLOC(x) (MEMDEB_SIGNATURE ^ (uint64_t)(x))
70 #define MEMDEB_SIGNATURE_FREE(x) (~MEMDEB_SIGNATURE_ALLOC(x))
71 
72 #define MEMDEB_SIG_PRIV_SALT 0x7d442e4532bb9ef0UL
73 #define MEMDEB_SIGNATURE_PRIV(x) \
74   (MEMDEB_SIG_PRIV_SALT ^ (uint64_t)(x))
75 
76 #define MEMDEB_GUARD_SIZE 8
77 
78 struct memdeb_loc {
79     const char *fname;
80     int linen;
81     const char *funcn;
82 };
83 
84 struct memdeb_node
85 {
86     uint64_t magic;
87     struct memdeb_loc loc;
88     struct memdeb_stats mstats;
89     struct memdeb_node *next;
90 };
91 
92 struct memdeb_pfx
93 {
94     struct memdeb_node *mnp;
95     size_t asize;
96     uint64_t magic;
97     /* Use longest datatype to ensure proper alignment */
98     long long real_data[0];
99 };
100 
101 struct rtpp_memdeb_au {
102     const char *funcn;
103     int max_nunalloc;
104     const char *why;
105 };
106 
107 #define MAX_APPROVED 10
108 
109 struct rtpp_memdeb_priv {
110     uint64_t magic;
111     struct memdeb_node *nodes;
112     pthread_mutex_t mutex;
113     struct rtpp_memdeb_au au[MAX_APPROVED];
114     struct rtpp_log *_md_glog;
115 };
116 
117 void *
rtpp_memdeb_init()118 rtpp_memdeb_init()
119 {
120     struct rtpp_memdeb_priv *pvt;
121 
122     pvt = malloc(sizeof(struct rtpp_memdeb_priv));
123     if (pvt == NULL) {
124         return (NULL);
125     }
126     memset(pvt, '\0', sizeof(struct rtpp_memdeb_priv));
127     pthread_mutex_init(&pvt->mutex, NULL);
128     pvt->magic = MEMDEB_SIGNATURE_PRIV(pvt);
129     return (pvt);
130 }
131 
132 #define CHK_PRIV(pvt, p) { \
133         (pvt) = (struct rtpp_memdeb_priv *)(p); \
134         if (pvt->magic != MEMDEB_SIGNATURE_PRIV(pvt)) { \
135             RTPP_MEMDEB_REPORT(NULL, "%s(): bogus private pointer: %p", \
136               __func__, pvt); \
137             abort(); \
138         } \
139     }
140 
141 void
rtpp_memdeb_dtor(void * p)142 rtpp_memdeb_dtor(void *p)
143 {
144     struct rtpp_memdeb_priv *pvt;
145 
146     CHK_PRIV(pvt, p);
147     if (pvt->_md_glog != NULL) {
148         CALL_SMETHOD(pvt->_md_glog->rcnt, decref);
149     }
150     pvt->magic = MEMDEB_SIGNATURE_FREE(pvt);
151     pthread_mutex_destroy(&pvt->mutex);
152     free(pvt);
153     return;
154 }
155 
156 void
rtpp_memdeb_setlog(void * p,struct rtpp_log * log)157 rtpp_memdeb_setlog(void *p, struct rtpp_log *log)
158 {
159     struct rtpp_memdeb_priv *pvt;
160 
161     CHK_PRIV(pvt, p);
162     CALL_SMETHOD(log->rcnt, incref);
163     pvt->_md_glog = log;
164 }
165 
166 void
rtpp_memdeb_approve(void * p,const char * funcn,int max_nunalloc,const char * why)167 rtpp_memdeb_approve(void *p, const char *funcn, int max_nunalloc,
168   const char *why)
169 {
170     int i;
171     struct rtpp_memdeb_priv *pvt;
172 
173     CHK_PRIV(pvt, p);
174     for (i = 0; i < MAX_APPROVED; i++) {
175         if (pvt->au[i].funcn != NULL)
176             continue;
177         pvt->au[i].funcn = funcn;
178         pvt->au[i].max_nunalloc = max_nunalloc;
179         pvt->au[i].why = why;
180         return;
181     }
182 }
183 
184 #define RTPP_MEMDEB_REPORT_LOC(handle, mlp, format, args...) { \
185         RTPP_MEMDEB_REPORT(handle, format, ## args); \
186         RTPP_MEMDEB_REPORT(handle, "    called from %s+%d, %s()", \
187           (mlp)->fname, (mlp)->linen, (mlp)->funcn); \
188     }
189 
190 static struct memdeb_node *
rtpp_memdeb_nget(struct rtpp_memdeb_priv * pvt,struct memdeb_loc * mlp,int doalloc)191 rtpp_memdeb_nget(struct rtpp_memdeb_priv *pvt, struct memdeb_loc *mlp,
192   int doalloc)
193 {
194     struct memdeb_node *rval, *mnp, *lastnode;
195 
196     pthread_mutex_lock(&pvt->mutex);
197     lastnode = NULL;
198     for (mnp = pvt->nodes; mnp != NULL; mnp = mnp->next) {
199         if (mnp->magic != MEMDEB_SIGNATURE) {
200             /* nodelist is corrupt */
201             RTPP_MEMDEB_REPORT_LOC(pvt->_md_glog, mlp, "Nodelist %p is corrupt", mnp);
202             abort();
203         }
204         if (mnp->loc.fname == mlp->fname && mnp->loc.linen == mlp->linen && \
205           mnp->loc.funcn == mlp->funcn)
206             return (mnp);
207         lastnode = mnp;
208     }
209     if (doalloc == 0) {
210         pthread_mutex_unlock(&pvt->mutex);
211         return (NULL);
212     }
213     rval = malloc(sizeof(struct memdeb_node));
214     if (rval == NULL) {
215         RTPP_MEMDEB_REPORT_LOC(pvt->_md_glog, mlp, "Allocation for the new nodelist failed");
216         abort();
217     }
218     memset(rval, '\0', sizeof(struct memdeb_node));
219     rval->magic = MEMDEB_SIGNATURE;
220     rval->loc = *mlp;
221     if (pvt->nodes == NULL) {
222         pvt->nodes = rval;
223     } else {
224         lastnode->next = rval;
225     }
226     return (rval);
227 }
228 
229 #define CHK_PRIV_VRB(pvt, p, mlp) { \
230         (pvt) = (struct rtpp_memdeb_priv *)(p); \
231         if (pvt->magic != MEMDEB_SIGNATURE_PRIV(pvt)) { \
232             RTPP_MEMDEB_REPORT_LOC(NULL, mlp, "%s(): bogus private pointer: %p", \
233               __func__, pvt); \
234             abort(); \
235         } \
236     }
237 
238 void *
rtpp_memdeb_malloc(size_t size,void * p,const char * fname,int linen,const char * funcn)239 rtpp_memdeb_malloc(size_t size, void *p, const char *fname, int linen, const char *funcn)
240 {
241     struct memdeb_node *mnp;
242     struct memdeb_pfx *mpf;
243     unsigned char *gp;
244     uint64_t guard;
245     struct rtpp_memdeb_priv *pvt;
246     struct memdeb_loc ml;
247 
248     ml.fname = fname;
249     ml.linen = linen;
250     ml.funcn = funcn;
251 
252     CHK_PRIV_VRB(pvt, p, &ml);
253     mpf = malloc(offsetof(struct memdeb_pfx, real_data) + size + MEMDEB_GUARD_SIZE);
254     mnp = rtpp_memdeb_nget(pvt, &ml, 1);
255     if (mpf == NULL) {
256         mnp->mstats.afails++;
257         pthread_mutex_unlock(&pvt->mutex);
258         return (NULL);
259     }
260     mnp->mstats.nalloc++;
261     mnp->mstats.balloc += size;
262     mpf->magic = MEMDEB_SIGNATURE_ALLOC(mpf);
263     pthread_mutex_unlock(&pvt->mutex);
264     mpf->asize = size;
265     mpf->mnp = mnp;
266     gp = (unsigned char *)mpf->real_data + size;
267     guard = MEMDEB_SIGNATURE_ALLOC(gp);
268     _memcpy(gp, &guard, MEMDEB_GUARD_SIZE);
269     return (mpf->real_data);
270 }
271 
272 static struct memdeb_pfx *
ptr2mpf(struct rtpp_memdeb_priv * pvt,void * ptr,struct memdeb_loc * mlp)273 ptr2mpf(struct rtpp_memdeb_priv *pvt, void *ptr, struct memdeb_loc *mlp)
274 {
275     char *cp;
276     struct memdeb_pfx *mpf;
277 
278     cp = ptr;
279     cp -= offsetof(struct memdeb_pfx, real_data);
280     mpf = (struct memdeb_pfx *)cp;
281 
282     if (mpf->magic != MEMDEB_SIGNATURE_ALLOC(mpf)) {
283         /* Random or de-allocated pointer */
284         RTPP_MEMDEB_REPORT_LOC(pvt->_md_glog, mlp, "Random or de-allocated pointer");
285         abort();
286     }
287     if (mpf->mnp->magic != MEMDEB_SIGNATURE) {
288         /* Free of unallocated pointer or nodelist is corrupt */
289         RTPP_MEMDEB_REPORT_LOC(pvt->_md_glog, mlp, "Nodelist %p is corrupt", mpf->mnp);
290         abort();
291     }
292     return (mpf);
293 }
294 
295 void
rtpp_memdeb_free(void * ptr,void * p,const char * fname,int linen,const char * funcn)296 rtpp_memdeb_free(void *ptr, void *p, const char *fname, int linen, const char *funcn)
297 {
298     UNUSED(fname);
299     UNUSED(linen);
300     UNUSED(funcn);
301     struct memdeb_pfx *mpf;
302     unsigned char *gp;
303     uint64_t guard;
304     struct rtpp_memdeb_priv *pvt;
305     struct memdeb_loc ml;
306 
307     ml.fname = fname;
308     ml.linen = linen;
309     ml.funcn = funcn;
310 
311     CHK_PRIV_VRB(pvt, p, &ml);
312     mpf = ptr2mpf(pvt, ptr, &ml);
313     gp = (unsigned char *)mpf->real_data + mpf->asize;
314     guard = MEMDEB_SIGNATURE_ALLOC(gp);
315     if (memcmp(gp, &guard, MEMDEB_GUARD_SIZE) != 0) {
316         /* Guard is b0rken, probably out-of-bound write */
317         RTPP_MEMDEB_REPORT_LOC(pvt->_md_glog, &ml, "Guard is b0rken, probably out-of-bound write");
318         abort();
319     }
320     pthread_mutex_lock(&pvt->mutex);
321     mpf->mnp->mstats.nfree++;
322     mpf->mnp->mstats.bfree += mpf->asize;
323     mpf->magic = MEMDEB_SIGNATURE_FREE(mpf);
324     pthread_mutex_unlock(&pvt->mutex);
325     return free(mpf);
326 }
327 
328 void *
rtpp_memdeb_realloc(void * ptr,size_t size,void * p,const char * fname,int linen,const char * funcn)329 rtpp_memdeb_realloc(void *ptr, size_t size, void *p, const char *fname, int linen,
330   const char *funcn)
331 {
332     UNUSED(fname);
333     UNUSED(linen);
334     UNUSED(funcn);
335     struct memdeb_pfx *mpf, *new_mpf;
336     char *cp;
337     uint64_t sig_save;
338     unsigned char *gp;
339     uint64_t guard;
340     struct rtpp_memdeb_priv *pvt;
341     struct memdeb_loc ml;
342 
343     ml.fname = fname;
344     ml.linen = linen;
345     ml.funcn = funcn;
346 
347     CHK_PRIV_VRB(pvt, p, &ml);
348     if (ptr == NULL) {
349         return (rtpp_memdeb_malloc(size, pvt, fname, linen, funcn));
350     }
351     mpf = ptr2mpf(pvt, ptr, &ml);
352     sig_save = MEMDEB_SIGNATURE_ALLOC(mpf);
353     pthread_mutex_lock(&pvt->mutex);
354     mpf->magic = MEMDEB_SIGNATURE_FREE(mpf);
355     pthread_mutex_unlock(&pvt->mutex);
356     cp = realloc(mpf, size + offsetof(struct memdeb_pfx, real_data) + MEMDEB_GUARD_SIZE);
357     if (cp == NULL) {
358         pthread_mutex_lock(&pvt->mutex);
359         mpf->magic = sig_save;
360         mpf->mnp->mstats.afails++;
361         pthread_mutex_unlock(&pvt->mutex);
362         return (cp);
363     }
364     new_mpf = (struct memdeb_pfx *)cp;
365     if (new_mpf != mpf) {
366         sig_save = MEMDEB_SIGNATURE_ALLOC(new_mpf);
367     }
368     pthread_mutex_lock(&pvt->mutex);
369     new_mpf->magic = sig_save;
370     new_mpf->mnp->mstats.nrealloc++;
371     new_mpf->mnp->mstats.brealloc += size;
372     new_mpf->mnp->mstats.balloc += size - new_mpf->asize;
373     pthread_mutex_unlock(&pvt->mutex);
374     new_mpf->asize = size;
375     gp = (unsigned char *)new_mpf->real_data + size;
376     guard = MEMDEB_SIGNATURE_ALLOC(gp);
377     _memcpy(gp, &guard, MEMDEB_GUARD_SIZE);
378     return (new_mpf->real_data);
379 }
380 
381 char *
rtpp_memdeb_strdup(const char * ptr,void * p,const char * fname,int linen,const char * funcn)382 rtpp_memdeb_strdup(const char *ptr, void *p, const char *fname, int linen, \
383   const char *funcn)
384 {
385     struct memdeb_node *mnp;
386     struct memdeb_pfx *mpf;
387     size_t size;
388     unsigned char *gp;
389     uint64_t guard;
390     struct rtpp_memdeb_priv *pvt;
391     struct memdeb_loc ml;
392 
393     ml.fname = fname;
394     ml.linen = linen;
395     ml.funcn = funcn;
396 
397     CHK_PRIV_VRB(pvt, p, &ml);
398     size = strlen(ptr) + 1;
399     mpf = malloc(size + offsetof(struct memdeb_pfx, real_data) + MEMDEB_GUARD_SIZE);
400     mnp = rtpp_memdeb_nget(pvt, &ml, 1);
401     if (mpf == NULL) {
402         mnp->mstats.afails++;
403         pthread_mutex_unlock(&pvt->mutex);
404         return (NULL);
405     }
406     mnp->mstats.nalloc++;
407     mnp->mstats.balloc += size;
408     pthread_mutex_unlock(&pvt->mutex);
409     mpf->mnp = mnp;
410     mpf->asize = size;
411     _memcpy(mpf->real_data, ptr, size);
412     mpf->magic = MEMDEB_SIGNATURE_ALLOC(mpf);
413     gp = (unsigned char *)mpf->real_data + size;
414     guard = MEMDEB_SIGNATURE_ALLOC(gp);
415     _memcpy(gp, &guard, MEMDEB_GUARD_SIZE);
416     return ((char *)mpf->real_data);
417 }
418 
419 int
rtpp_memdeb_asprintf(char ** pp,const char * fmt,void * p,const char * fname,int linen,const char * funcn,...)420 rtpp_memdeb_asprintf(char **pp, const char *fmt, void *p, const char *fname,
421   int linen, const char *funcn, ...)
422 {
423     va_list ap;
424     int rval;
425 
426     va_start(ap, funcn);
427     rval = rtpp_memdeb_vasprintf(pp, fmt, p, fname, linen, funcn, ap);
428     va_end(ap);
429     return (rval);
430 }
431 
432 int
rtpp_memdeb_vasprintf(char ** pp,const char * fmt,void * p,const char * fname,int linen,const char * funcn,va_list ap)433 rtpp_memdeb_vasprintf(char **pp, const char *fmt, void *p, const char *fname,
434   int linen, const char *funcn, va_list ap)
435 {
436     int rval;
437     void *tp;
438 
439     rval = vasprintf(pp, fmt, ap);
440     if (rval <= 0) {
441         return (rval);
442     }
443     tp = rtpp_memdeb_malloc(rval + 1, p, fname, linen, funcn);
444     if (tp == NULL) {
445         free(*pp);
446         *pp = NULL;
447         return (-1);
448     }
449     _memcpy(tp, *pp, rval + 1);
450     free(*pp);
451     *pp = tp;
452     return (rval);
453 }
454 
455 void *
rtpp_memdeb_memcpy(void * dst,const void * src,size_t len,void * p,const char * fname,int linen,const char * funcn)456 rtpp_memdeb_memcpy(void *dst, const void *src, size_t len, void *p,
457   const char *fname, int linen, const char *funcn)
458 {
459     struct memdeb_loc ml;
460     struct rtpp_memdeb_priv *pvt;
461 
462     CHK_PRIV(pvt, p);
463 
464     if ((dst < src && src < (dst + len)) || (src < dst && dst < (src + len))) {
465         ml.fname = fname;
466         ml.linen = linen;
467         ml.funcn = funcn;
468         RTPP_MEMDEB_REPORT_LOC(pvt->_md_glog, &ml, "memcpy(%p, %p, %ld) overlapping regions, use memmove()",
469           dst, src, (long)len);
470         abort();
471     }
472     return (memcpy(dst, src, len));
473 }
474 
475 static int
is_approved(struct rtpp_memdeb_priv * pvt,const char * funcn)476 is_approved(struct rtpp_memdeb_priv *pvt, const char *funcn)
477 {
478     int i;
479 
480     for (i = 0; pvt->au[i].funcn != NULL && i < MAX_APPROVED; i++) {
481         if (strcmp(pvt->au[i].funcn, funcn) != 0)
482             continue;
483         return (pvt->au[i].max_nunalloc);
484     }
485     return (0);
486 }
487 
488 int
rtpp_memdeb_dumpstats(void * p,int nostdout)489 rtpp_memdeb_dumpstats(void *p, int nostdout)
490 {
491     struct memdeb_node *mnp;
492     int errors_found, max_nunalloc;
493     int64_t nunalloc;
494     struct rtpp_log *log;
495     struct rtpp_memdeb_priv *pvt;
496 
497     CHK_PRIV(pvt, p);
498     errors_found = 0;
499     log = pvt->_md_glog;
500     pthread_mutex_lock(&pvt->mutex);
501     for (mnp = pvt->nodes; mnp != NULL; mnp = mnp->next) {
502         nunalloc = mnp->mstats.nalloc - mnp->mstats.nfree;
503         if (mnp->mstats.afails == 0) {
504             if (mnp->mstats.nalloc == 0)
505                 continue;
506             if (mnp->mstats.nalloc == mnp->mstats.nfree)
507                 continue;
508             if (nunalloc <= mnp->mstats.nunalloc_baseln)
509                 continue;
510         }
511         if (nunalloc > 0) {
512             max_nunalloc = is_approved(pvt, mnp->loc.funcn);
513             if (max_nunalloc > 0 && nunalloc <= max_nunalloc)
514                 continue;
515         }
516         if (errors_found == 0) {
517             RTPP_MEMDEB_REPORT2(log, nostdout,
518               "MEMDEB suspicious allocations:");
519         }
520         errors_found++;
521         RTPP_MEMDEB_REPORT2(log, nostdout,
522           "  %s+%d, %s(): nalloc = %" PRId64 ", balloc = %" PRId64 ", nfree = %"
523           PRId64 ", bfree = %" PRId64 ", afails = %" PRId64 ", nunalloc_baseln"
524           " = %" PRId64, mnp->loc.fname, mnp->loc.linen, mnp->loc.funcn, mnp->mstats.nalloc,
525           mnp->mstats.balloc, mnp->mstats.nfree, mnp->mstats.bfree,
526           mnp->mstats.afails, mnp->mstats.nunalloc_baseln);
527     }
528     pthread_mutex_unlock(&pvt->mutex);
529     if (errors_found == 0) {
530         RTPP_MEMDEB_REPORT2(log, nostdout,
531           "MEMDEB: all clear");
532     } else {
533         RTPP_MEMDEB_REPORT2(log, nostdout,
534           "MEMDEB: errors found: %d", errors_found);
535     }
536     return (errors_found);
537 }
538 
539 void
rtpp_memdeb_setbaseln(void * p)540 rtpp_memdeb_setbaseln(void *p)
541 {
542 
543     struct memdeb_node *mnp;
544     struct rtpp_memdeb_priv *pvt;
545 
546     CHK_PRIV(pvt, p);
547     pthread_mutex_lock(&pvt->mutex);
548     for (mnp = pvt->nodes; mnp != NULL; mnp = mnp->next) {
549         if (mnp->magic != MEMDEB_SIGNATURE) {
550             /* Nodelist is corrupt */
551             RTPP_MEMDEB_REPORT(pvt->_md_glog, "Nodelist %p is corrupt", mnp);
552             abort();
553         }
554         if (mnp->mstats.nalloc == 0)
555             continue;
556         mnp->mstats.nunalloc_baseln = mnp->mstats.nalloc - mnp->mstats.nfree;
557         mnp->mstats.bunalloc_baseln = mnp->mstats.balloc - mnp->mstats.bfree;
558     }
559     pthread_mutex_unlock(&pvt->mutex);
560 }
561 
562 int
rtpp_memdeb_get_stats(void * p,const char * fname,const char * funcn,struct memdeb_stats * mstatp)563 rtpp_memdeb_get_stats(void *p, const char *fname, const char *funcn,
564   struct memdeb_stats *mstatp)
565 {
566     struct memdeb_node *mnp;
567     int nmatches;
568     struct rtpp_memdeb_priv *pvt;
569 
570     CHK_PRIV(pvt, p);
571     nmatches = 0;
572     pthread_mutex_lock(&pvt->mutex);
573     for (mnp = pvt->nodes; mnp != NULL; mnp = mnp->next) {
574         if (mnp->magic != MEMDEB_SIGNATURE) {
575             /* Nodelist is corrupt */
576             RTPP_MEMDEB_REPORT(pvt->_md_glog, "Nodelist %p is corrupt", mnp);
577             abort();
578         }
579         if (funcn != NULL && strcmp(funcn, mnp->loc.funcn) != 0) {
580             continue;
581         }
582         if (fname != NULL && strcmp(fname, mnp->loc.fname) != 0) {
583             continue;
584         }
585         RTPP_MD_STATS_ADD(mstatp, &mnp->mstats);
586         nmatches += 1;
587     }
588     pthread_mutex_unlock(&pvt->mutex);
589     return (nmatches);
590 }
591