1 /*	$OpenBSD: kern_malloc_debug.c,v 1.22 2003/06/03 01:27:31 art Exp $	*/
2 
3 /*
4  * Copyright (c) 1999, 2000 Artur Grabowski <art@openbsd.org>
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. The name of the author may not be used to endorse or promote products
14  *    derived from this software without specific prior written permission.
15  *
16  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
17  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
18  * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
19  * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20  * EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27 
28 /*
29  * This really belongs in kern/kern_malloc.c, but it was too much pollution.
30  */
31 
32 /*
33  * It's only possible to debug one type/size at a time. The question is
34  * if this is a limitation or a feature. We never want to run this as the
35  * default malloc because we'll run out of memory really fast. Adding
36  * more types will also add to the complexity of the code.
37  *
38  * This is really simple. Every malloc() allocates two virtual pages,
39  * the second page is left unmapped, and the value returned is aligned
40  * so that it ends at (or very close to) the page boundary to catch overflows.
41  * Every free() changes the protection of the first page to VM_PROT_NONE so
42  * that we can catch any dangling writes to it.
43  * To minimize the risk of writes to recycled chunks we keep an LRU of latest
44  * freed chunks. The length of it is controlled by MALLOC_DEBUG_CHUNKS.
45  *
46  * Don't expect any performance.
47  *
48  * TODO:
49  *  - support for size >= PAGE_SIZE
50  *  - add support to the fault handler to give better diagnostics if we fail.
51  */
52 
53 #include <sys/param.h>
54 #include <sys/proc.h>
55 #include <sys/kernel.h>
56 #include <sys/malloc.h>
57 #include <sys/systm.h>
58 #include <sys/pool.h>
59 
60 #include <uvm/uvm.h>
61 
62 /*
63  * debug_malloc_type and debug_malloc_size define the type and size of
64  * memory to be debugged. Use 0 for a wildcard. debug_malloc_size_lo
65  * is the lower limit and debug_malloc_size_hi the upper limit of sizes
66  * being debugged; 0 will not work as a wildcard for the upper limit.
67  * For any debugging to take place, type must be != -1, size must be >= 0,
68  * and if the limits are being used, size must be set to 0.
69  * See /usr/src/sys/sys/malloc.h and malloc(9) for a list of types.
70  *
71  * Although those are variables, it's a really bad idea to change the type
72  * if any memory chunks of this type are used. It's ok to change the size
73  * in runtime.
74  */
75 int debug_malloc_type = -1;
76 int debug_malloc_size = -1;
77 int debug_malloc_size_lo = -1;
78 int debug_malloc_size_hi = -1;
79 
80 /*
81  * MALLOC_DEBUG_CHUNKS is the number of memory chunks we require on the
82  * freelist before we reuse them.
83  */
84 #define MALLOC_DEBUG_CHUNKS 16
85 
86 void debug_malloc_allocate_free(int);
87 
88 struct debug_malloc_entry {
89 	TAILQ_ENTRY(debug_malloc_entry) md_list;
90 	vaddr_t md_va;
91 	paddr_t md_pa;
92 	size_t md_size;
93 	int md_type;
94 };
95 
96 TAILQ_HEAD(,debug_malloc_entry) debug_malloc_freelist;
97 TAILQ_HEAD(,debug_malloc_entry) debug_malloc_usedlist;
98 
99 int debug_malloc_allocs;
100 int debug_malloc_frees;
101 int debug_malloc_pages;
102 int debug_malloc_chunks_on_freelist;
103 
104 int debug_malloc_initialized;
105 
106 struct pool debug_malloc_pool;
107 
108 int
debug_malloc(unsigned long size,int type,int flags,void ** addr)109 debug_malloc(unsigned long size, int type, int flags, void **addr)
110 {
111 	struct debug_malloc_entry *md = NULL;
112 	int s, wait = flags & M_NOWAIT;
113 
114 	/* Careful not to compare unsigned long to int -1 */
115 	if (((type != debug_malloc_type && debug_malloc_type != 0) ||
116 	    (size != debug_malloc_size && debug_malloc_size != 0) ||
117 	    (debug_malloc_size_lo != -1 && size < debug_malloc_size_lo) ||
118 	    (debug_malloc_size_hi != -1 && size > debug_malloc_size_hi) ||
119 	    !debug_malloc_initialized) && type != M_DEBUG)
120 		return (0);
121 
122 	/* XXX - fix later */
123 	if (size > PAGE_SIZE)
124 		return (0);
125 
126 	s = splvm();
127 	if (debug_malloc_chunks_on_freelist < MALLOC_DEBUG_CHUNKS)
128 		debug_malloc_allocate_free(wait);
129 
130 	md = TAILQ_FIRST(&debug_malloc_freelist);
131 	if (md == NULL) {
132 		splx(s);
133 		return (0);
134 	}
135 	TAILQ_REMOVE(&debug_malloc_freelist, md, md_list);
136 	debug_malloc_chunks_on_freelist--;
137 
138 	TAILQ_INSERT_HEAD(&debug_malloc_usedlist, md, md_list);
139 	debug_malloc_allocs++;
140 	splx(s);
141 
142 	pmap_kenter_pa(md->md_va, md->md_pa, VM_PROT_READ|VM_PROT_WRITE);
143 	pmap_update(pmap_kernel());
144 
145 	md->md_size = size;
146 	md->md_type = type;
147 
148 	/*
149 	 * Align the returned addr so that it ends where the first page
150 	 * ends. roundup to get decent alignment.
151 	 */
152 	*addr = (void *)(md->md_va + PAGE_SIZE - roundup(size, sizeof(long)));
153 	return (1);
154 }
155 
156 int
debug_free(void * addr,int type)157 debug_free(void *addr, int type)
158 {
159 	struct debug_malloc_entry *md;
160 	vaddr_t va;
161 	int s;
162 
163 	if (type != debug_malloc_type && debug_malloc_type != 0 &&
164 	    type != M_DEBUG)
165 		return (0);
166 
167 	/*
168 	 * trunc_page to get the address of the page.
169 	 */
170 	va = trunc_page((vaddr_t)addr);
171 
172 	s = splvm();
173 	TAILQ_FOREACH(md, &debug_malloc_usedlist, md_list)
174 		if (md->md_va == va)
175 			break;
176 
177 	/*
178 	 * If we are not responsible for this entry, let the normal free
179 	 * handle it
180 	 */
181 	if (md == NULL) {
182 		/*
183 		 * sanity check. Check for multiple frees.
184 		 */
185 		TAILQ_FOREACH(md, &debug_malloc_freelist, md_list)
186 			if (md->md_va == va)
187 				panic("debug_free: already free");
188 		splx(s);
189 		return (0);
190 	}
191 
192 	debug_malloc_frees++;
193 	TAILQ_REMOVE(&debug_malloc_usedlist, md, md_list);
194 
195 	TAILQ_INSERT_TAIL(&debug_malloc_freelist, md, md_list);
196 	debug_malloc_chunks_on_freelist++;
197 	/*
198 	 * unmap the page.
199 	 */
200 	pmap_kremove(md->md_va, PAGE_SIZE);
201 	pmap_update(pmap_kernel());
202 	splx(s);
203 
204 	return (1);
205 }
206 
207 void
debug_malloc_init(void)208 debug_malloc_init(void)
209 {
210 
211 	TAILQ_INIT(&debug_malloc_freelist);
212 	TAILQ_INIT(&debug_malloc_usedlist);
213 
214 	debug_malloc_allocs = 0;
215 	debug_malloc_frees = 0;
216 	debug_malloc_pages = 0;
217 	debug_malloc_chunks_on_freelist = 0;
218 
219 	pool_init(&debug_malloc_pool, sizeof(struct debug_malloc_entry),
220 	    0, 0, 0, "mdbepl", NULL);
221 
222 	debug_malloc_initialized = 1;
223 }
224 
225 /*
226  * Add one chunk to the freelist.
227  *
228  * called at splvm.
229  */
230 void
debug_malloc_allocate_free(int wait)231 debug_malloc_allocate_free(int wait)
232 {
233 	vaddr_t va, offset;
234 	struct vm_page *pg;
235 	struct debug_malloc_entry *md;
236 
237 	splassert(IPL_VM);
238 
239 	md = pool_get(&debug_malloc_pool, wait ? PR_WAITOK : PR_NOWAIT);
240 	if (md == NULL)
241 		return;
242 
243 	va = uvm_km_kmemalloc(kmem_map, uvmexp.kmem_object, PAGE_SIZE * 2,
244 	    UVM_KMF_VALLOC | (wait ? UVM_KMF_NOWAIT : 0));
245 	if (va == 0) {
246 		pool_put(&debug_malloc_pool, md);
247 		return;
248 	}
249 
250 	offset = va - vm_map_min(kernel_map);
251 	for (;;) {
252 		simple_lock(&uvmexp.kmem_object->vmobjlock);
253 		pg = uvm_pagealloc(uvmexp.kmem_object, offset, NULL, 0);
254 		if (pg) {
255 			pg->flags &= ~PG_BUSY;  /* new page */
256 			UVM_PAGE_OWN(pg, NULL);
257 		}
258 		simple_unlock(&uvmexp.kmem_object->vmobjlock);
259 
260 		if (pg)
261 			break;
262 
263 		if (wait == 0) {
264 			uvm_unmap(kmem_map, va, va + PAGE_SIZE * 2);
265 			pool_put(&debug_malloc_pool, md);
266 			return;
267 		}
268 		uvm_wait("debug_malloc");
269 	}
270 
271 	md->md_va = va;
272 	md->md_pa = VM_PAGE_TO_PHYS(pg);
273 
274 	debug_malloc_pages++;
275 	TAILQ_INSERT_HEAD(&debug_malloc_freelist, md, md_list);
276 	debug_malloc_chunks_on_freelist++;
277 }
278 
279 void
debug_malloc_print(void)280 debug_malloc_print(void)
281 {
282 
283 	debug_malloc_printit(printf, NULL);
284 }
285 
286 void
debug_malloc_printit(int (* pr)(const char *,...),vaddr_t addr)287 debug_malloc_printit(int (*pr)(const char *, ...), vaddr_t addr)
288 {
289 	struct debug_malloc_entry *md;
290 
291 	if (addr) {
292 		TAILQ_FOREACH(md, &debug_malloc_freelist, md_list) {
293 			if (addr >= md->md_va &&
294 			    addr < md->md_va + 2 * PAGE_SIZE) {
295 				(*pr)("Memory at address 0x%x is in a freed "
296 				      "area. type %d, size: %d\n ",
297 				      addr, md->md_type, md->md_size);
298 				return;
299 			}
300 		}
301 		TAILQ_FOREACH(md, &debug_malloc_usedlist, md_list) {
302 			if (addr >= md->md_va + PAGE_SIZE &&
303 			    addr < md->md_va + 2 * PAGE_SIZE) {
304 				(*pr)("Memory at address 0x%x is just outside "
305 				      "an allocated area. type %d, size: %d\n",
306 				      addr, md->md_type, md->md_size);
307 				return;
308 			}
309 		}
310 		(*pr)("Memory at address 0x%x is outside debugged malloc.\n");
311 		return;
312 	}
313 
314 	(*pr)("allocs: %d\n", debug_malloc_allocs);
315 	(*pr)("frees: %d\n", debug_malloc_frees);
316 	(*pr)("pages used: %d\n", debug_malloc_pages);
317 	(*pr)("chunks on freelist: %d\n", debug_malloc_chunks_on_freelist);
318 
319 	(*pr)("\taddr:\tsize:\n");
320 	(*pr)("free chunks:\n");
321 	TAILQ_FOREACH(md, &debug_malloc_freelist, md_list)
322 		(*pr)("\t0x%x\t0x%x\t%d\n", md->md_va, md->md_size,
323 		      md->md_type);
324 	(*pr)("used chunks:\n");
325 	TAILQ_FOREACH(md, &debug_malloc_usedlist, md_list)
326 		(*pr)("\t0x%x\t0x%x\t%d\n", md->md_va, md->md_size,
327 		      md->md_type);
328 }
329