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