1*c1c95addSBrooks Davis /* $Id: mandoc_dbg.c,v 1.1 2022/04/14 16:43:44 schwarze Exp $ */ 2*c1c95addSBrooks Davis /* 3*c1c95addSBrooks Davis * Copyright (c) 2021, 2022 Ingo Schwarze <schwarze@openbsd.org> 4*c1c95addSBrooks Davis * 5*c1c95addSBrooks Davis * Permission to use, copy, modify, and distribute this software for any 6*c1c95addSBrooks Davis * purpose with or without fee is hereby granted, provided that the above 7*c1c95addSBrooks Davis * copyright notice and this permission notice appear in all copies. 8*c1c95addSBrooks Davis * 9*c1c95addSBrooks Davis * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10*c1c95addSBrooks Davis * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11*c1c95addSBrooks Davis * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12*c1c95addSBrooks Davis * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13*c1c95addSBrooks Davis * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14*c1c95addSBrooks Davis * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15*c1c95addSBrooks Davis * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16*c1c95addSBrooks Davis */ 17*c1c95addSBrooks Davis #include "config.h" 18*c1c95addSBrooks Davis 19*c1c95addSBrooks Davis #include <sys/types.h> 20*c1c95addSBrooks Davis 21*c1c95addSBrooks Davis #if HAVE_ERR 22*c1c95addSBrooks Davis #include <err.h> 23*c1c95addSBrooks Davis #endif 24*c1c95addSBrooks Davis #include <stdarg.h> 25*c1c95addSBrooks Davis #include <stddef.h> 26*c1c95addSBrooks Davis #include <stdint.h> 27*c1c95addSBrooks Davis #include <stdio.h> 28*c1c95addSBrooks Davis #include <stdlib.h> 29*c1c95addSBrooks Davis #include <string.h> 30*c1c95addSBrooks Davis #include <unistd.h> 31*c1c95addSBrooks Davis 32*c1c95addSBrooks Davis #if HAVE_OHASH 33*c1c95addSBrooks Davis #include <ohash.h> 34*c1c95addSBrooks Davis #else 35*c1c95addSBrooks Davis #include "compat_ohash.h" 36*c1c95addSBrooks Davis #endif 37*c1c95addSBrooks Davis 38*c1c95addSBrooks Davis #define DEBUG_NODEF 1 39*c1c95addSBrooks Davis #include "mandoc_aux.h" 40*c1c95addSBrooks Davis #include "mandoc_dbg.h" 41*c1c95addSBrooks Davis #include "mandoc.h" 42*c1c95addSBrooks Davis 43*c1c95addSBrooks Davis /* Store information about one allocation. */ 44*c1c95addSBrooks Davis struct dhash_entry { 45*c1c95addSBrooks Davis const char *file; 46*c1c95addSBrooks Davis int line; 47*c1c95addSBrooks Davis const char *func; 48*c1c95addSBrooks Davis size_t num; 49*c1c95addSBrooks Davis size_t size; 50*c1c95addSBrooks Davis void *ptr; 51*c1c95addSBrooks Davis }; 52*c1c95addSBrooks Davis 53*c1c95addSBrooks Davis /* Store information about all allocations. */ 54*c1c95addSBrooks Davis static struct ohash dhash_table; 55*c1c95addSBrooks Davis static FILE *dhash_fp; 56*c1c95addSBrooks Davis static int dhash_aflag; 57*c1c95addSBrooks Davis static int dhash_fflag; 58*c1c95addSBrooks Davis static int dhash_lflag; 59*c1c95addSBrooks Davis static int dhash_nflag; 60*c1c95addSBrooks Davis static int dhash_sflag; 61*c1c95addSBrooks Davis 62*c1c95addSBrooks Davis static void *dhash_alloc(size_t, void *); 63*c1c95addSBrooks Davis static void *dhash_calloc(size_t, size_t, void *); 64*c1c95addSBrooks Davis static void dhash_free(void *, void *); 65*c1c95addSBrooks Davis static unsigned int dhash_slot(void *); 66*c1c95addSBrooks Davis static void dhash_register(const char *, int, const char *, 67*c1c95addSBrooks Davis size_t, size_t, void *, const char *); 68*c1c95addSBrooks Davis static void dhash_print(struct dhash_entry *); 69*c1c95addSBrooks Davis static void dhash_purge(const char *, int, const char *, void *); 70*c1c95addSBrooks Davis 71*c1c95addSBrooks Davis 72*c1c95addSBrooks Davis /* *** Debugging wrappers of public API functions. ************************ */ 73*c1c95addSBrooks Davis 74*c1c95addSBrooks Davis int 75*c1c95addSBrooks Davis mandoc_dbg_asprintf(const char *file, int line, 76*c1c95addSBrooks Davis char **dest, const char *fmt, ...) 77*c1c95addSBrooks Davis { 78*c1c95addSBrooks Davis va_list ap; 79*c1c95addSBrooks Davis int ret; 80*c1c95addSBrooks Davis 81*c1c95addSBrooks Davis va_start(ap, fmt); 82*c1c95addSBrooks Davis ret = vasprintf(dest, fmt, ap); 83*c1c95addSBrooks Davis va_end(ap); 84*c1c95addSBrooks Davis 85*c1c95addSBrooks Davis if (ret == -1) 86*c1c95addSBrooks Davis err((int)MANDOCLEVEL_SYSERR, NULL); 87*c1c95addSBrooks Davis 88*c1c95addSBrooks Davis dhash_register(file, line, "asprintf", 1, strlen(*dest) + 1, 89*c1c95addSBrooks Davis *dest, *dest); 90*c1c95addSBrooks Davis 91*c1c95addSBrooks Davis return ret; 92*c1c95addSBrooks Davis } 93*c1c95addSBrooks Davis 94*c1c95addSBrooks Davis void * 95*c1c95addSBrooks Davis mandoc_dbg_calloc(size_t num, size_t size, const char *file, int line) 96*c1c95addSBrooks Davis { 97*c1c95addSBrooks Davis void *ptr = mandoc_calloc(num, size); 98*c1c95addSBrooks Davis dhash_register(file, line, "calloc", num, size, ptr, NULL); 99*c1c95addSBrooks Davis return ptr; 100*c1c95addSBrooks Davis } 101*c1c95addSBrooks Davis 102*c1c95addSBrooks Davis void * 103*c1c95addSBrooks Davis mandoc_dbg_malloc(size_t size, const char *file, int line) 104*c1c95addSBrooks Davis { 105*c1c95addSBrooks Davis void *ptr = mandoc_malloc(size); 106*c1c95addSBrooks Davis dhash_register(file, line, "malloc", 1, size, ptr, NULL); 107*c1c95addSBrooks Davis return ptr; 108*c1c95addSBrooks Davis } 109*c1c95addSBrooks Davis 110*c1c95addSBrooks Davis void * 111*c1c95addSBrooks Davis mandoc_dbg_realloc(void *ptr, size_t size, const char *file, int line) 112*c1c95addSBrooks Davis { 113*c1c95addSBrooks Davis dhash_purge(file, line, "realloc", ptr); 114*c1c95addSBrooks Davis ptr = mandoc_realloc(ptr, size); 115*c1c95addSBrooks Davis dhash_register(file, line, "realloc", 1, size, ptr, NULL); 116*c1c95addSBrooks Davis return ptr; 117*c1c95addSBrooks Davis } 118*c1c95addSBrooks Davis 119*c1c95addSBrooks Davis void * 120*c1c95addSBrooks Davis mandoc_dbg_reallocarray(void *ptr, size_t num, size_t size, 121*c1c95addSBrooks Davis const char *file, int line) 122*c1c95addSBrooks Davis { 123*c1c95addSBrooks Davis dhash_purge(file, line, "reallocarray", ptr); 124*c1c95addSBrooks Davis ptr = mandoc_reallocarray(ptr, num, size); 125*c1c95addSBrooks Davis dhash_register(file, line, "reallocarray", num, size, ptr, NULL); 126*c1c95addSBrooks Davis return ptr; 127*c1c95addSBrooks Davis } 128*c1c95addSBrooks Davis 129*c1c95addSBrooks Davis void * 130*c1c95addSBrooks Davis mandoc_dbg_recallocarray(void *ptr, size_t oldnum, size_t num, size_t size, 131*c1c95addSBrooks Davis const char *file, int line) 132*c1c95addSBrooks Davis { 133*c1c95addSBrooks Davis dhash_purge(file, line, "recallocarray", ptr); 134*c1c95addSBrooks Davis ptr = mandoc_recallocarray(ptr, oldnum, num, size); 135*c1c95addSBrooks Davis dhash_register(file, line, "recallocarray", num, size, ptr, NULL); 136*c1c95addSBrooks Davis return ptr; 137*c1c95addSBrooks Davis } 138*c1c95addSBrooks Davis 139*c1c95addSBrooks Davis char * 140*c1c95addSBrooks Davis mandoc_dbg_strdup(const char *ptr, const char *file, int line) 141*c1c95addSBrooks Davis { 142*c1c95addSBrooks Davis char *p = mandoc_strdup(ptr); 143*c1c95addSBrooks Davis dhash_register(file, line, "strdup", 1, strlen(p) + 1, p, ptr); 144*c1c95addSBrooks Davis return p; 145*c1c95addSBrooks Davis } 146*c1c95addSBrooks Davis 147*c1c95addSBrooks Davis char * 148*c1c95addSBrooks Davis mandoc_dbg_strndup(const char *ptr, size_t sz, const char *file, int line) 149*c1c95addSBrooks Davis { 150*c1c95addSBrooks Davis char *p = mandoc_strndup(ptr, sz); 151*c1c95addSBrooks Davis dhash_register(file, line, "strndup", 1, strlen(p) + 1, p, NULL); 152*c1c95addSBrooks Davis return p; 153*c1c95addSBrooks Davis } 154*c1c95addSBrooks Davis 155*c1c95addSBrooks Davis void 156*c1c95addSBrooks Davis mandoc_dbg_free(void *ptr, const char *file, int line) 157*c1c95addSBrooks Davis { 158*c1c95addSBrooks Davis dhash_purge(file, line, "free", ptr); 159*c1c95addSBrooks Davis free(ptr); 160*c1c95addSBrooks Davis } 161*c1c95addSBrooks Davis 162*c1c95addSBrooks Davis 163*c1c95addSBrooks Davis /* *** Memory allocation callbacks for the debugging table. *************** */ 164*c1c95addSBrooks Davis 165*c1c95addSBrooks Davis static void * 166*c1c95addSBrooks Davis dhash_alloc(size_t sz, void *arg) 167*c1c95addSBrooks Davis { 168*c1c95addSBrooks Davis return malloc(sz); 169*c1c95addSBrooks Davis } 170*c1c95addSBrooks Davis 171*c1c95addSBrooks Davis static void * 172*c1c95addSBrooks Davis dhash_calloc(size_t n, size_t sz, void *arg) 173*c1c95addSBrooks Davis { 174*c1c95addSBrooks Davis return calloc(n, sz); 175*c1c95addSBrooks Davis } 176*c1c95addSBrooks Davis 177*c1c95addSBrooks Davis static void 178*c1c95addSBrooks Davis dhash_free(void *p, void *arg) 179*c1c95addSBrooks Davis { 180*c1c95addSBrooks Davis free(p); 181*c1c95addSBrooks Davis } 182*c1c95addSBrooks Davis 183*c1c95addSBrooks Davis 184*c1c95addSBrooks Davis /* *** Debugging utility functions. *************************************** */ 185*c1c95addSBrooks Davis 186*c1c95addSBrooks Davis /* Initialize the debugging table, to be called from the top of main(). */ 187*c1c95addSBrooks Davis void 188*c1c95addSBrooks Davis mandoc_dbg_init(int argc, char *argv[]) 189*c1c95addSBrooks Davis { 190*c1c95addSBrooks Davis struct ohash_info info; 191*c1c95addSBrooks Davis char *dhash_fn; 192*c1c95addSBrooks Davis int argi; 193*c1c95addSBrooks Davis 194*c1c95addSBrooks Davis info.alloc = dhash_alloc; 195*c1c95addSBrooks Davis info.calloc = dhash_calloc; 196*c1c95addSBrooks Davis info.free = dhash_free; 197*c1c95addSBrooks Davis info.data = NULL; 198*c1c95addSBrooks Davis info.key_offset = offsetof(struct dhash_entry, ptr); 199*c1c95addSBrooks Davis ohash_init(&dhash_table, 18, &info); 200*c1c95addSBrooks Davis 201*c1c95addSBrooks Davis dhash_fp = stderr; 202*c1c95addSBrooks Davis if ((dhash_fn = getenv("DEBUG_MEMORY")) == NULL) 203*c1c95addSBrooks Davis return; 204*c1c95addSBrooks Davis 205*c1c95addSBrooks Davis dhash_sflag = 1; 206*c1c95addSBrooks Davis for(;; dhash_fn++) { 207*c1c95addSBrooks Davis switch (*dhash_fn) { 208*c1c95addSBrooks Davis case '\0': 209*c1c95addSBrooks Davis break; 210*c1c95addSBrooks Davis case 'A': 211*c1c95addSBrooks Davis dhash_aflag = 1; 212*c1c95addSBrooks Davis continue; 213*c1c95addSBrooks Davis case 'F': 214*c1c95addSBrooks Davis dhash_fflag = 1; 215*c1c95addSBrooks Davis continue; 216*c1c95addSBrooks Davis case 'L': 217*c1c95addSBrooks Davis dhash_lflag = 1; 218*c1c95addSBrooks Davis continue; 219*c1c95addSBrooks Davis case 'N': 220*c1c95addSBrooks Davis dhash_nflag = 1; 221*c1c95addSBrooks Davis continue; 222*c1c95addSBrooks Davis case '/': 223*c1c95addSBrooks Davis if ((dhash_fp = fopen(dhash_fn, "a+e")) == NULL) 224*c1c95addSBrooks Davis err((int)MANDOCLEVEL_SYSERR, "%s", dhash_fn); 225*c1c95addSBrooks Davis break; 226*c1c95addSBrooks Davis default: 227*c1c95addSBrooks Davis errx((int)MANDOCLEVEL_BADARG, 228*c1c95addSBrooks Davis "invalid char '%c' in $DEBUG_MEMORY", 229*c1c95addSBrooks Davis *dhash_fn); 230*c1c95addSBrooks Davis } 231*c1c95addSBrooks Davis break; 232*c1c95addSBrooks Davis } 233*c1c95addSBrooks Davis if (setvbuf(dhash_fp, NULL, _IOLBF, 0) != 0) 234*c1c95addSBrooks Davis err((int)MANDOCLEVEL_SYSERR, "setvbuf"); 235*c1c95addSBrooks Davis 236*c1c95addSBrooks Davis fprintf(dhash_fp, "P %d", getpid()); 237*c1c95addSBrooks Davis for (argi = 0; argi < argc; argi++) 238*c1c95addSBrooks Davis fprintf(dhash_fp, " [%s]", argv[argi]); 239*c1c95addSBrooks Davis fprintf(dhash_fp, "\n"); 240*c1c95addSBrooks Davis } 241*c1c95addSBrooks Davis 242*c1c95addSBrooks Davis void 243*c1c95addSBrooks Davis mandoc_dbg_name(const char *name) 244*c1c95addSBrooks Davis { 245*c1c95addSBrooks Davis if (dhash_nflag) 246*c1c95addSBrooks Davis fprintf(dhash_fp, "N %s\n", name); 247*c1c95addSBrooks Davis } 248*c1c95addSBrooks Davis 249*c1c95addSBrooks Davis /* Hash a pointer and return the table slot currently used for it. */ 250*c1c95addSBrooks Davis static unsigned int 251*c1c95addSBrooks Davis dhash_slot(void *ptr) 252*c1c95addSBrooks Davis { 253*c1c95addSBrooks Davis const char *ks, *ke; 254*c1c95addSBrooks Davis uint32_t hv; 255*c1c95addSBrooks Davis 256*c1c95addSBrooks Davis ks = (const char *)&ptr; 257*c1c95addSBrooks Davis ke = ks + sizeof(ptr); 258*c1c95addSBrooks Davis hv = ohash_interval(ks, &ke); 259*c1c95addSBrooks Davis return ohash_lookup_memory(&dhash_table, ks, sizeof(ptr), hv); 260*c1c95addSBrooks Davis } 261*c1c95addSBrooks Davis 262*c1c95addSBrooks Davis /* Record one allocation in the debugging table. */ 263*c1c95addSBrooks Davis static void 264*c1c95addSBrooks Davis dhash_register(const char *file, int line, const char *func, 265*c1c95addSBrooks Davis size_t num, size_t size, void *ptr, const char *str) 266*c1c95addSBrooks Davis { 267*c1c95addSBrooks Davis struct dhash_entry *e; 268*c1c95addSBrooks Davis unsigned int slot; 269*c1c95addSBrooks Davis 270*c1c95addSBrooks Davis slot = dhash_slot(ptr); 271*c1c95addSBrooks Davis e = ohash_find(&dhash_table, slot); 272*c1c95addSBrooks Davis if (dhash_aflag || e != NULL) { 273*c1c95addSBrooks Davis fprintf(dhash_fp, "A %s:%d %s(%zu, %zu) = %p", 274*c1c95addSBrooks Davis file, line, func, num, size, ptr); 275*c1c95addSBrooks Davis if (str != NULL) 276*c1c95addSBrooks Davis fprintf(dhash_fp, " \"%s\"", str); 277*c1c95addSBrooks Davis fprintf(dhash_fp, "\n"); 278*c1c95addSBrooks Davis } 279*c1c95addSBrooks Davis if (e != NULL) { 280*c1c95addSBrooks Davis dhash_print(e); 281*c1c95addSBrooks Davis fprintf(dhash_fp, "E duplicate address %p\n", e->ptr); 282*c1c95addSBrooks Davis errx((int)MANDOCLEVEL_BADARG, "duplicate address %p", e->ptr); 283*c1c95addSBrooks Davis } 284*c1c95addSBrooks Davis 285*c1c95addSBrooks Davis if ((e = malloc(sizeof(*e))) == NULL) 286*c1c95addSBrooks Davis err(1, NULL); 287*c1c95addSBrooks Davis e->file = file; 288*c1c95addSBrooks Davis e->line = line; 289*c1c95addSBrooks Davis e->func = func; 290*c1c95addSBrooks Davis e->num = num; 291*c1c95addSBrooks Davis e->size = size; 292*c1c95addSBrooks Davis e->ptr = ptr; 293*c1c95addSBrooks Davis 294*c1c95addSBrooks Davis ohash_insert(&dhash_table, slot, e); 295*c1c95addSBrooks Davis } 296*c1c95addSBrooks Davis 297*c1c95addSBrooks Davis /* Remove one allocation from the debugging table. */ 298*c1c95addSBrooks Davis static void 299*c1c95addSBrooks Davis dhash_purge(const char *file, int line, const char *func, void *ptr) 300*c1c95addSBrooks Davis { 301*c1c95addSBrooks Davis struct dhash_entry *e; 302*c1c95addSBrooks Davis unsigned int slot; 303*c1c95addSBrooks Davis 304*c1c95addSBrooks Davis if (ptr == NULL) 305*c1c95addSBrooks Davis return; 306*c1c95addSBrooks Davis 307*c1c95addSBrooks Davis if (dhash_fflag) 308*c1c95addSBrooks Davis fprintf(dhash_fp, "F %s:%d %s(%p)\n", file, line, func, ptr); 309*c1c95addSBrooks Davis 310*c1c95addSBrooks Davis slot = dhash_slot(ptr); 311*c1c95addSBrooks Davis e = ohash_remove(&dhash_table, slot); 312*c1c95addSBrooks Davis free(e); 313*c1c95addSBrooks Davis } 314*c1c95addSBrooks Davis 315*c1c95addSBrooks Davis /* Pretty-print information about one allocation. */ 316*c1c95addSBrooks Davis static void 317*c1c95addSBrooks Davis dhash_print(struct dhash_entry *e) 318*c1c95addSBrooks Davis { 319*c1c95addSBrooks Davis fprintf(dhash_fp, "L %s:%d %s(%zu, %zu) = %p\n", 320*c1c95addSBrooks Davis e->file, e->line, e->func, e->num, e->size, e->ptr); 321*c1c95addSBrooks Davis } 322*c1c95addSBrooks Davis 323*c1c95addSBrooks Davis /* Pretty-print information about all active allocations. */ 324*c1c95addSBrooks Davis void 325*c1c95addSBrooks Davis mandoc_dbg_finish(void) 326*c1c95addSBrooks Davis { 327*c1c95addSBrooks Davis struct dhash_entry *e; 328*c1c95addSBrooks Davis unsigned int errcount, slot; 329*c1c95addSBrooks Davis 330*c1c95addSBrooks Davis errcount = ohash_entries(&dhash_table); 331*c1c95addSBrooks Davis e = ohash_first(&dhash_table, &slot); 332*c1c95addSBrooks Davis while (e != NULL) { 333*c1c95addSBrooks Davis if (dhash_lflag) 334*c1c95addSBrooks Davis dhash_print(e); 335*c1c95addSBrooks Davis free(e); 336*c1c95addSBrooks Davis e = ohash_next(&dhash_table, &slot); 337*c1c95addSBrooks Davis } 338*c1c95addSBrooks Davis ohash_delete(&dhash_table); 339*c1c95addSBrooks Davis if (dhash_sflag) 340*c1c95addSBrooks Davis fprintf(dhash_fp, "S %u memory leaks found\n", errcount); 341*c1c95addSBrooks Davis if (dhash_fp != stderr) 342*c1c95addSBrooks Davis fclose(dhash_fp); 343*c1c95addSBrooks Davis } 344