diff options
author | Akinobu Mita <akinobu.mita@gmail.com> | 2009-03-31 18:23:17 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2009-04-01 11:59:13 -0400 |
commit | 6a11f75b6a17b5d9ac5025f8d048382fd1f47377 (patch) | |
tree | a3415cd897823975b927f89a233d2703bf97ba2b /mm/debug-pagealloc.c | |
parent | 610a77e04a8d9fe8764dc484e2182fa251ce1cc2 (diff) |
generic debug pagealloc
CONFIG_DEBUG_PAGEALLOC is now supported by x86, powerpc, sparc64, and
s390. This patch implements it for the rest of the architectures by
filling the pages with poison byte patterns after free_pages() and
verifying the poison patterns before alloc_pages().
This generic one cannot detect invalid page accesses immediately but
invalid read access may cause invalid dereference by poisoned memory and
invalid write access can be detected after a long delay.
Signed-off-by: Akinobu Mita <akinobu.mita@gmail.com>
Cc: <linux-arch@vger.kernel.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'mm/debug-pagealloc.c')
-rw-r--r-- | mm/debug-pagealloc.c | 129 |
1 files changed, 129 insertions, 0 deletions
diff --git a/mm/debug-pagealloc.c b/mm/debug-pagealloc.c new file mode 100644 index 000000000000..a1e3324de2b5 --- /dev/null +++ b/mm/debug-pagealloc.c | |||
@@ -0,0 +1,129 @@ | |||
1 | #include <linux/kernel.h> | ||
2 | #include <linux/mm.h> | ||
3 | #include <linux/page-debug-flags.h> | ||
4 | #include <linux/poison.h> | ||
5 | |||
6 | static inline void set_page_poison(struct page *page) | ||
7 | { | ||
8 | __set_bit(PAGE_DEBUG_FLAG_POISON, &page->debug_flags); | ||
9 | } | ||
10 | |||
11 | static inline void clear_page_poison(struct page *page) | ||
12 | { | ||
13 | __clear_bit(PAGE_DEBUG_FLAG_POISON, &page->debug_flags); | ||
14 | } | ||
15 | |||
16 | static inline bool page_poison(struct page *page) | ||
17 | { | ||
18 | return test_bit(PAGE_DEBUG_FLAG_POISON, &page->debug_flags); | ||
19 | } | ||
20 | |||
21 | static void poison_highpage(struct page *page) | ||
22 | { | ||
23 | /* | ||
24 | * Page poisoning for highmem pages is not implemented. | ||
25 | * | ||
26 | * This can be called from interrupt contexts. | ||
27 | * So we need to create a new kmap_atomic slot for this | ||
28 | * application and it will need interrupt protection. | ||
29 | */ | ||
30 | } | ||
31 | |||
32 | static void poison_page(struct page *page) | ||
33 | { | ||
34 | void *addr; | ||
35 | |||
36 | if (PageHighMem(page)) { | ||
37 | poison_highpage(page); | ||
38 | return; | ||
39 | } | ||
40 | set_page_poison(page); | ||
41 | addr = page_address(page); | ||
42 | memset(addr, PAGE_POISON, PAGE_SIZE); | ||
43 | } | ||
44 | |||
45 | static void poison_pages(struct page *page, int n) | ||
46 | { | ||
47 | int i; | ||
48 | |||
49 | for (i = 0; i < n; i++) | ||
50 | poison_page(page + i); | ||
51 | } | ||
52 | |||
53 | static bool single_bit_flip(unsigned char a, unsigned char b) | ||
54 | { | ||
55 | unsigned char error = a ^ b; | ||
56 | |||
57 | return error && !(error & (error - 1)); | ||
58 | } | ||
59 | |||
60 | static void check_poison_mem(unsigned char *mem, size_t bytes) | ||
61 | { | ||
62 | unsigned char *start; | ||
63 | unsigned char *end; | ||
64 | |||
65 | for (start = mem; start < mem + bytes; start++) { | ||
66 | if (*start != PAGE_POISON) | ||
67 | break; | ||
68 | } | ||
69 | if (start == mem + bytes) | ||
70 | return; | ||
71 | |||
72 | for (end = mem + bytes - 1; end > start; end--) { | ||
73 | if (*end != PAGE_POISON) | ||
74 | break; | ||
75 | } | ||
76 | |||
77 | if (!printk_ratelimit()) | ||
78 | return; | ||
79 | else if (start == end && single_bit_flip(*start, PAGE_POISON)) | ||
80 | printk(KERN_ERR "pagealloc: single bit error\n"); | ||
81 | else | ||
82 | printk(KERN_ERR "pagealloc: memory corruption\n"); | ||
83 | |||
84 | print_hex_dump(KERN_ERR, "", DUMP_PREFIX_ADDRESS, 16, 1, start, | ||
85 | end - start + 1, 1); | ||
86 | dump_stack(); | ||
87 | } | ||
88 | |||
89 | static void unpoison_highpage(struct page *page) | ||
90 | { | ||
91 | /* | ||
92 | * See comment in poison_highpage(). | ||
93 | * Highmem pages should not be poisoned for now | ||
94 | */ | ||
95 | BUG_ON(page_poison(page)); | ||
96 | } | ||
97 | |||
98 | static void unpoison_page(struct page *page) | ||
99 | { | ||
100 | if (PageHighMem(page)) { | ||
101 | unpoison_highpage(page); | ||
102 | return; | ||
103 | } | ||
104 | if (page_poison(page)) { | ||
105 | void *addr = page_address(page); | ||
106 | |||
107 | check_poison_mem(addr, PAGE_SIZE); | ||
108 | clear_page_poison(page); | ||
109 | } | ||
110 | } | ||
111 | |||
112 | static void unpoison_pages(struct page *page, int n) | ||
113 | { | ||
114 | int i; | ||
115 | |||
116 | for (i = 0; i < n; i++) | ||
117 | unpoison_page(page + i); | ||
118 | } | ||
119 | |||
120 | void kernel_map_pages(struct page *page, int numpages, int enable) | ||
121 | { | ||
122 | if (!debug_pagealloc_enabled) | ||
123 | return; | ||
124 | |||
125 | if (enable) | ||
126 | unpoison_pages(page, numpages); | ||
127 | else | ||
128 | poison_pages(page, numpages); | ||
129 | } | ||