bounding tests
[tinycc.git] / bcheck.c
blob47d186695825941722bb47a996363edea94ca183
1 /*
2 * Tiny C Memory and bounds checker
3 *
4 * Copyright (c) 2002 Fabrice Bellard
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21 //#define BOUND_DEBUG
23 /* define so that bound array is static (faster, but use memory if
24 bound checking not used) */
25 //#define BOUND_STATIC
27 #define BOUND_T1_BITS 13
28 #define BOUND_T2_BITS 11
29 #define BOUND_T3_BITS (32 - BOUND_T1_BITS - BOUND_T2_BITS)
31 #define BOUND_T1_SIZE (1 << BOUND_T1_BITS)
32 #define BOUND_T2_SIZE (1 << BOUND_T2_BITS)
33 #define BOUND_T3_SIZE (1 << BOUND_T3_BITS)
34 #define BOUND_E_BITS 4
36 #define BOUND_T23_BITS (BOUND_T2_BITS + BOUND_T3_BITS)
37 #define BOUND_T23_SIZE (1 << BOUND_T23_BITS)
40 /* this pointer is generated when bound check is incorrect */
41 #define INVALID_POINTER ((void *)(-2))
42 /* size of an empty region */
43 #define EMPTY_SIZE 0xffffffff
44 /* size of an invalid region */
45 #define INVALID_SIZE 0
47 typedef struct BoundEntry {
48 unsigned long start;
49 unsigned long size;
50 struct BoundEntry *next;
51 unsigned long is_invalid; /* true if pointers outside region are invalid */
52 } BoundEntry;
54 /* external interface */
55 void __bound_init(void);
56 void __bound_new_region(void *p, unsigned long size);
57 void __bound_delete_region(void *p);
59 /* currently, tcc cannot compile that because we use unsupported GNUC
60 extensions */
61 #if !defined(__TINYC__)
62 void *__bound_ptr_add(void *p, int offset) __attribute__((regparm(2)));
63 void *__bound_ptr_indir1(void *p, int offset) __attribute__((regparm(2)));
64 void *__bound_ptr_indir2(void *p, int offset) __attribute__((regparm(2)));
65 void *__bound_ptr_indir4(void *p, int offset) __attribute__((regparm(2)));
66 void *__bound_ptr_indir8(void *p, int offset) __attribute__((regparm(2)));
67 void *__bound_ptr_indir12(void *p, int offset) __attribute__((regparm(2)));
68 void *__bound_ptr_indir16(void *p, int offset) __attribute__((regparm(2)));
69 void __bound_local_new(void *p) __attribute__((regparm(1)));
70 void __bound_local_delete(void *p) __attribute__((regparm(1)));
71 #endif
73 void *__bound_malloc(size_t size, const void *caller);
74 void *__bound_memalign(size_t size, size_t align, const void *caller);
75 void __bound_free(void *ptr, const void *caller);
76 void *__bound_realloc(void *ptr, size_t size, const void *caller);
77 static void *libc_malloc(size_t size);
78 static void libc_free(void *ptr);
79 static void install_malloc_hooks(void);
80 static void restore_malloc_hooks(void);
82 static void *saved_malloc_hook;
83 static void *saved_free_hook;
84 static void *saved_realloc_hook;
85 static void *saved_memalign_hook;
87 extern char _end;
89 #ifdef BOUND_STATIC
90 static BoundEntry *__bound_t1[BOUND_T1_SIZE]; /* page table */
91 #else
92 static BoundEntry **__bound_t1; /* page table */
93 #endif
94 static BoundEntry *__bound_empty_t2; /* empty page, for unused pages */
95 static BoundEntry *__bound_invalid_t2; /* invalid page, for invalid pointers */
97 static BoundEntry *__bound_find_region(BoundEntry *e1, void *p)
99 unsigned long addr, tmp;
100 BoundEntry *e;
102 e = e1;
103 while (e != NULL) {
104 addr = (unsigned long)p;
105 addr -= e->start;
106 if (addr <= e->size) {
107 /* put region at the head */
108 tmp = e1->start;
109 e1->start = e->start;
110 e->start = tmp;
111 tmp = e1->size;
112 e1->size = e->size;
113 e->size = tmp;
114 return e1;
116 e = e->next;
118 /* no entry found: return empty entry or invalid entry */
119 if (e1->is_invalid)
120 return __bound_invalid_t2;
121 else
122 return __bound_empty_t2;
125 static void bound_error(const char *fmt, ...)
127 va_list ap;
128 va_start(ap, fmt);
129 fprintf(stderr, "bounds check: ");
130 vfprintf(stderr, fmt, ap);
131 fprintf(stderr, "\n");
132 exit(1);
133 va_end(ap);
136 static void bound_alloc_error(void)
138 bound_error("not enough memory for bound checking code\n");
141 /* currently, tcc cannot compile that because we use GNUC extensions */
142 #if !defined(__TINYC__)
144 /* return '(p + offset)' for pointer arithmetic (a pointer can reach
145 the end of a region in this case */
146 void *__bound_ptr_add(void *p, int offset)
148 unsigned long addr = (unsigned long)p;
149 BoundEntry *e;
150 #if defined(BOUND_DEBUG)
151 printf("add: 0x%x %d\n", (int)p, offset);
152 #endif
154 e = __bound_t1[addr >> (BOUND_T2_BITS + BOUND_T3_BITS)];
155 e = (BoundEntry *)((char *)e +
156 ((addr >> (BOUND_T3_BITS - BOUND_E_BITS)) &
157 ((BOUND_T2_SIZE - 1) << BOUND_E_BITS)));
158 addr -= e->start;
159 if (addr > e->size) {
160 e = __bound_find_region(e, p);
161 addr = (unsigned long)p - e->start;
163 addr += offset;
164 if (addr > e->size)
165 return INVALID_POINTER; /* return an invalid pointer */
166 return p + offset;
169 /* return '(p + offset)' for pointer indirection (the resulting must
170 be strictly inside the region */
171 #define BOUND_PTR_INDIR(dsize) \
172 void *__bound_ptr_indir ## dsize (void *p, int offset) \
174 unsigned long addr = (unsigned long)p; \
175 BoundEntry *e; \
177 e = __bound_t1[addr >> (BOUND_T2_BITS + BOUND_T3_BITS)]; \
178 e = (BoundEntry *)((char *)e + \
179 ((addr >> (BOUND_T3_BITS - BOUND_E_BITS)) & \
180 ((BOUND_T2_SIZE - 1) << BOUND_E_BITS))); \
181 addr -= e->start; \
182 if (addr > e->size) { \
183 e = __bound_find_region(e, p); \
184 addr = (unsigned long)p - e->start; \
186 addr += offset + dsize; \
187 if (addr > e->size) \
188 return INVALID_POINTER; /* return an invalid pointer */ \
189 return p + offset; \
192 #ifdef __i386__
193 #define GET_CALLER_FP(fp)\
195 unsigned long *fp1;\
196 __asm__ __volatile__ ("movl %%ebp,%0" :"=g" (fp1));\
197 fp = fp1[0];\
199 #else
200 #error put code to extract the calling frame pointer
201 #endif
203 /* called when entering a function to add all the local regions */
204 void __bound_local_new(void *p1)
206 unsigned long addr, size, fp, *p = p1;
207 GET_CALLER_FP(fp);
208 for(;;) {
209 addr = p[0];
210 if (addr == 0)
211 break;
212 addr += fp;
213 size = p[1];
214 p += 2;
215 __bound_new_region((void *)addr, size);
219 /* called when leaving a function to delete all the local regions */
220 void __bound_local_delete(void *p1)
222 unsigned long addr, fp, *p = p1;
223 GET_CALLER_FP(fp);
224 for(;;) {
225 addr = p[0];
226 if (addr == 0)
227 break;
228 addr += fp;
229 p += 2;
230 __bound_delete_region((void *)addr);
234 #else
236 void __bound_local_new(void *p)
239 void __bound_local_delete(void *p)
243 void *__bound_ptr_add(void *p, int offset)
245 return p + offset;
248 #define BOUND_PTR_INDIR(dsize) \
249 void *__bound_ptr_indir ## dsize (void *p, int offset) \
251 return p + offset; \
254 #endif
256 BOUND_PTR_INDIR(1)
257 BOUND_PTR_INDIR(2)
258 BOUND_PTR_INDIR(4)
259 BOUND_PTR_INDIR(8)
260 BOUND_PTR_INDIR(12)
261 BOUND_PTR_INDIR(16)
263 static BoundEntry *__bound_new_page(void)
265 BoundEntry *page;
266 int i;
268 page = libc_malloc(sizeof(BoundEntry) * BOUND_T2_SIZE);
269 if (!page)
270 bound_alloc_error();
271 for(i=0;i<BOUND_T2_SIZE;i++) {
272 /* put empty entries */
273 page[i].start = 0;
274 page[i].size = EMPTY_SIZE;
275 page[i].next = NULL;
276 page[i].is_invalid = 0;
278 return page;
281 /* currently we use malloc(). Should use bound_new_page() */
282 static BoundEntry *bound_new_entry(void)
284 BoundEntry *e;
285 e = libc_malloc(sizeof(BoundEntry));
286 return e;
289 static void bound_free_entry(BoundEntry *e)
291 libc_free(e);
294 static inline BoundEntry *get_page(int index)
296 BoundEntry *page;
297 page = __bound_t1[index];
298 if (page == __bound_empty_t2 || page == __bound_invalid_t2) {
299 /* create a new page if necessary */
300 page = __bound_new_page();
301 __bound_t1[index] = page;
303 return page;
306 /* mark a region as being invalid (can only be used during init) */
307 static void mark_invalid(unsigned long addr, unsigned long size)
309 unsigned long start, end;
310 BoundEntry *page;
311 int t1_start, t1_end, i, j, t2_start, t2_end;
313 start = addr;
314 end = addr + size;
316 t2_start = (start + BOUND_T3_SIZE - 1) >> BOUND_T3_BITS;
317 if (end != 0)
318 t2_end = end >> BOUND_T3_BITS;
319 else
320 t2_end = 1 << (BOUND_T1_BITS + BOUND_T2_BITS);
322 #if 0
323 printf("mark_invalid: start = %x %x\n", t2_start, t2_end);
324 #endif
326 /* first we handle full pages */
327 t1_start = (t2_start + BOUND_T2_SIZE - 1) >> BOUND_T2_BITS;
328 t1_end = t2_end >> BOUND_T2_BITS;
330 i = t2_start & (BOUND_T2_SIZE - 1);
331 j = t2_end & (BOUND_T2_SIZE - 1);
333 if (t1_start == t1_end) {
334 page = get_page(t2_start >> BOUND_T2_BITS);
335 for(; i < j; i++) {
336 page[i].size = INVALID_SIZE;
337 page[i].is_invalid = 1;
339 } else {
340 if (i > 0) {
341 page = get_page(t2_start >> BOUND_T2_BITS);
342 for(; i < BOUND_T2_SIZE; i++) {
343 page[i].size = INVALID_SIZE;
344 page[i].is_invalid = 1;
347 for(i = t1_start; i < t1_end; i++) {
348 __bound_t1[i] = __bound_invalid_t2;
350 if (j != 0) {
351 page = get_page(t1_end);
352 for(i = 0; i < j; i++) {
353 page[i].size = INVALID_SIZE;
354 page[i].is_invalid = 1;
360 void __bound_init(void)
362 int i;
363 BoundEntry *page;
364 unsigned long start, size;
366 /* save malloc hooks and install bound check hooks */
367 install_malloc_hooks();
369 #ifndef BOUND_STATIC
370 __bound_t1 = libc_malloc(BOUND_T1_SIZE * sizeof(BoundEntry *));
371 if (!__bound_t1)
372 bound_alloc_error();
373 #endif
374 __bound_empty_t2 = __bound_new_page();
375 for(i=0;i<BOUND_T1_SIZE;i++) {
376 __bound_t1[i] = __bound_empty_t2;
379 page = __bound_new_page();
380 for(i=0;i<BOUND_T2_SIZE;i++) {
381 /* put invalid entries */
382 page[i].start = 0;
383 page[i].size = INVALID_SIZE;
384 page[i].next = NULL;
385 page[i].is_invalid = 1;
387 __bound_invalid_t2 = page;
389 /* invalid pointer zone */
390 start = (unsigned long)INVALID_POINTER & ~(BOUND_T23_SIZE - 1);
391 size = BOUND_T23_SIZE;
392 mark_invalid(start, size);
394 #if !defined(__TINYC__)
395 /* malloc zone is also marked invalid */
396 start = (unsigned long)&_end;
397 size = 128 * 0x100000;
398 mark_invalid(start, size);
399 #endif
402 static inline void add_region(BoundEntry *e,
403 unsigned long start, unsigned long size)
405 BoundEntry *e1;
406 if (e->start == 0) {
407 /* no region : add it */
408 e->start = start;
409 e->size = size;
410 } else {
411 /* already regions in the list: add it at the head */
412 e1 = bound_new_entry();
413 e1->start = e->start;
414 e1->size = e->size;
415 e1->next = e->next;
416 e->start = start;
417 e->size = size;
418 e->next = e1;
422 /* create a new region. It should not already exist in the region list */
423 void __bound_new_region(void *p, unsigned long size)
425 unsigned long start, end;
426 BoundEntry *page, *e, *e2;
427 int t1_start, t1_end, i, t2_start, t2_end;
429 start = (unsigned long)p;
430 end = start + size;
431 t1_start = start >> (BOUND_T2_BITS + BOUND_T3_BITS);
432 t1_end = end >> (BOUND_T2_BITS + BOUND_T3_BITS);
434 /* start */
435 page = get_page(t1_start);
436 t2_start = (start >> (BOUND_T3_BITS - BOUND_E_BITS)) &
437 ((BOUND_T2_SIZE - 1) << BOUND_E_BITS);
438 t2_end = (end >> (BOUND_T3_BITS - BOUND_E_BITS)) &
439 ((BOUND_T2_SIZE - 1) << BOUND_E_BITS);
440 #ifdef BOUND_DEBUG
441 printf("new %lx %lx %x %x %x %x\n",
442 start, end, t1_start, t1_end, t2_start, t2_end);
443 #endif
445 e = (BoundEntry *)((char *)page + t2_start);
446 add_region(e, start, size);
448 if (t1_end == t1_start) {
449 /* same ending page */
450 e2 = (BoundEntry *)((char *)page + t2_end);
451 if (e2 > e) {
452 e++;
453 for(;e<e2;e++) {
454 e->start = start;
455 e->size = size;
457 add_region(e, start, size);
459 } else {
460 /* mark until end of page */
461 e2 = page + BOUND_T2_SIZE;
462 e++;
463 for(;e<e2;e++) {
464 e->start = start;
465 e->size = size;
467 /* mark intermediate pages, if any */
468 for(i=t1_start+1;i<t1_end;i++) {
469 page = get_page(i);
470 e2 = page + BOUND_T2_SIZE;
471 for(e=page;e<e2;e++) {
472 e->start = start;
473 e->size = size;
476 /* last page */
477 page = get_page(t2_end);
478 e2 = (BoundEntry *)((char *)page + t2_end);
479 for(e=page;e<e2;e++) {
480 e->start = start;
481 e->size = size;
483 add_region(e, start, size);
487 /* delete a region */
488 static inline void delete_region(BoundEntry *e,
489 void *p, unsigned long empty_size)
491 unsigned long addr;
492 BoundEntry *e1;
494 addr = (unsigned long)p;
495 addr -= e->start;
496 if (addr <= e->size) {
497 /* region found is first one */
498 e1 = e->next;
499 if (e1 == NULL) {
500 /* no more region: mark it empty */
501 e->start = 0;
502 e->size = empty_size;
503 } else {
504 /* copy next region in head */
505 e->start = e1->start;
506 e->size = e1->size;
507 e->next = e1->next;
508 bound_free_entry(e1);
510 } else {
511 /* find the matching region */
512 for(;;) {
513 e1 = e;
514 e = e->next;
515 /* region not found: do nothing */
516 if (e == NULL)
517 break;
518 addr = (unsigned long)p - e->start;
519 if (addr <= e->size) {
520 /* found: remove entry */
521 e1->next = e->next;
522 bound_free_entry(e);
523 break;
529 /* WARNING: 'p' must be the starting point of the region. */
530 void __bound_delete_region(void *p)
532 unsigned long start, end, addr, size, empty_size;
533 BoundEntry *page, *e, *e2;
534 int t1_start, t1_end, t2_start, t2_end, i;
536 start = (unsigned long)p;
537 t1_start = start >> (BOUND_T2_BITS + BOUND_T3_BITS);
538 t2_start = (start >> (BOUND_T3_BITS - BOUND_E_BITS)) &
539 ((BOUND_T2_SIZE - 1) << BOUND_E_BITS);
541 /* find region size */
542 page = __bound_t1[t1_start];
543 e = (BoundEntry *)((char *)page + t2_start);
544 addr = start - e->start;
545 if (addr > e->size)
546 e = __bound_find_region(e, p);
547 /* test if invalid region */
548 if (e->size == EMPTY_SIZE || (unsigned long)p != e->start)
549 bound_error("freeing invalid region");
550 /* compute the size we put in invalid regions */
551 if (e->is_invalid)
552 empty_size = INVALID_SIZE;
553 else
554 empty_size = EMPTY_SIZE;
555 size = e->size;
556 end = start + size;
558 /* now we can free each entry */
559 t1_end = end >> (BOUND_T2_BITS + BOUND_T3_BITS);
560 t2_end = (end >> (BOUND_T3_BITS - BOUND_E_BITS)) &
561 ((BOUND_T2_SIZE - 1) << BOUND_E_BITS);
563 delete_region(e, p, empty_size);
564 if (t1_end == t1_start) {
565 /* same ending page */
566 e2 = (BoundEntry *)((char *)page + t2_end);
567 if (e2 > e) {
568 e++;
569 for(;e<e2;e++) {
570 e->start = 0;
571 e->size = empty_size;
573 delete_region(e, p, empty_size);
575 } else {
576 /* mark until end of page */
577 e2 = page + BOUND_T2_SIZE;
578 e++;
579 for(;e<e2;e++) {
580 e->start = 0;
581 e->size = empty_size;
583 /* mark intermediate pages, if any */
584 /* XXX: should free them */
585 for(i=t1_start+1;i<t1_end;i++) {
586 page = get_page(i);
587 e2 = page + BOUND_T2_SIZE;
588 for(e=page;e<e2;e++) {
589 e->start = 0;
590 e->size = empty_size;
593 /* last page */
594 page = get_page(t2_end);
595 e2 = (BoundEntry *)((char *)page + t2_end);
596 for(e=page;e<e2;e++) {
597 e->start = 0;
598 e->size = empty_size;
600 delete_region(e, p, empty_size);
604 /* return the size of the region starting at p, or EMPTY_SIZE if non
605 existant region. */
606 static unsigned long get_region_size(void *p)
608 unsigned long addr = (unsigned long)p;
609 BoundEntry *e;
611 e = __bound_t1[addr >> (BOUND_T2_BITS + BOUND_T3_BITS)];
612 e = (BoundEntry *)((char *)e +
613 ((addr >> (BOUND_T3_BITS - BOUND_E_BITS)) &
614 ((BOUND_T2_SIZE - 1) << BOUND_E_BITS)));
615 addr -= e->start;
616 if (addr > e->size)
617 e = __bound_find_region(e, p);
618 if (e->start != (unsigned long)p)
619 return EMPTY_SIZE;
620 return e->size;
623 /* patched memory functions */
625 static void install_malloc_hooks(void)
627 saved_malloc_hook = __malloc_hook;
628 saved_free_hook = __free_hook;
629 saved_realloc_hook = __realloc_hook;
630 saved_memalign_hook = __memalign_hook;
631 __malloc_hook = __bound_malloc;
632 __free_hook = __bound_free;
633 __realloc_hook = __bound_realloc;
634 __memalign_hook = __bound_memalign;
637 static void restore_malloc_hooks(void)
639 __malloc_hook = saved_malloc_hook;
640 __free_hook = saved_free_hook;
641 __realloc_hook = saved_realloc_hook;
642 __memalign_hook = saved_memalign_hook;
645 static void *libc_malloc(size_t size)
647 void *ptr;
648 restore_malloc_hooks();
649 ptr = malloc(size);
650 install_malloc_hooks();
651 return ptr;
654 static void libc_free(void *ptr)
656 restore_malloc_hooks();
657 free(ptr);
658 install_malloc_hooks();
661 /* XXX: we should use a malloc which ensure that it is unlikely that
662 two malloc'ed data have the same address if 'free' are made in
663 between. */
664 void *__bound_malloc(size_t size, const void *caller)
666 void *ptr;
668 /* we allocate one more byte to ensure the regions will be
669 separated by at least one byte. With the glibc malloc, it may
670 be in fact not necessary */
671 ptr = libc_malloc(size + 1);
673 if (!ptr)
674 return NULL;
675 __bound_new_region(ptr, size);
676 return ptr;
679 void *__bound_memalign(size_t size, size_t align, const void *caller)
681 void *ptr;
683 restore_malloc_hooks();
685 /* we allocate one more byte to ensure the regions will be
686 separated by at least one byte. With the glibc malloc, it may
687 be in fact not necessary */
688 ptr = memalign(size + 1, align);
690 install_malloc_hooks();
692 if (!ptr)
693 return NULL;
694 __bound_new_region(ptr, size);
695 return ptr;
698 void __bound_free(void *ptr, const void *caller)
700 if (ptr == NULL)
701 return;
702 __bound_delete_region(ptr);
704 libc_free(ptr);
707 void *__bound_realloc(void *ptr, size_t size, const void *caller)
709 void *ptr1;
710 int old_size;
712 if (size == 0) {
713 __bound_free(ptr, caller);
714 return NULL;
715 } else {
716 ptr1 = __bound_malloc(size, caller);
717 if (ptr == NULL || ptr1 == NULL)
718 return ptr1;
719 old_size = get_region_size(ptr);
720 if (old_size == EMPTY_SIZE)
721 bound_error("realloc'ing invalid pointer");
722 memcpy(ptr1, ptr, old_size);
723 __bound_free(ptr, caller);
724 return ptr1;
728 #if 0
729 static void bound_dump(void)
731 BoundEntry *page, *e;
732 int i, j;
734 printf("region dump:\n");
735 for(i=0;i<BOUND_T1_SIZE;i++) {
736 page = __bound_t1[i];
737 for(j=0;j<BOUND_T2_SIZE;j++) {
738 e = page + j;
739 /* do not print invalid or empty entries */
740 if (e->size != EMPTY_SIZE && e->start != 0) {
741 printf("%08x:",
742 (i << (BOUND_T2_BITS + BOUND_T3_BITS)) +
743 (j << BOUND_T3_BITS));
744 do {
745 printf(" %08lx:%08lx", e->start, e->start + e->size);
746 e = e->next;
747 } while (e != NULL);
748 printf("\n");
753 #endif
755 #if 0
756 /* resolve check check syms */
757 typedef struct BCSyms {
758 char *str;
759 void *ptr;
760 } BCSyms;
762 static BCSyms bcheck_syms[] = {
763 { NULL, NULL },
765 #endif
767 static void *bound_resolve_sym(const char *sym)
769 #if 0
770 BCSyms *p;
771 p = bcheck_syms;
772 while (p->str != NULL) {
773 if (!strcmp(p->str, sym))
774 return p->ptr;
775 p++;
777 #endif
778 return NULL;