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