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
mandoc_dbg_asprintf(const char * file,int line,char ** dest,const char * fmt,...)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 *
mandoc_dbg_calloc(size_t num,size_t size,const char * file,int line)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 *
mandoc_dbg_malloc(size_t size,const char * file,int line)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 *
mandoc_dbg_realloc(void * ptr,size_t size,const char * file,int line)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 *
mandoc_dbg_reallocarray(void * ptr,size_t num,size_t size,const char * file,int line)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 *
mandoc_dbg_recallocarray(void * ptr,size_t oldnum,size_t num,size_t size,const char * file,int line)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 *
mandoc_dbg_strdup(const char * ptr,const char * file,int line)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 *
mandoc_dbg_strndup(const char * ptr,size_t sz,const char * file,int line)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
mandoc_dbg_free(void * ptr,const char * file,int line)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 *
dhash_alloc(size_t sz,void * arg)166 dhash_alloc(size_t sz, void *arg)
167 {
168 return malloc(sz);
169 }
170
171 static void *
dhash_calloc(size_t n,size_t sz,void * arg)172 dhash_calloc(size_t n, size_t sz, void *arg)
173 {
174 return calloc(n, sz);
175 }
176
177 static void
dhash_free(void * p,void * arg)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
mandoc_dbg_init(int argc,char * argv[])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
mandoc_dbg_name(const char * name)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
dhash_slot(void * ptr)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
dhash_register(const char * file,int line,const char * func,size_t num,size_t size,void * ptr,const char * str)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
dhash_purge(const char * file,int line,const char * func,void * ptr)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
dhash_print(struct dhash_entry * e)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
mandoc_dbg_finish(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