diff options
Diffstat (limited to 'kernel/kprobes.c')
-rw-r--r-- | kernel/kprobes.c | 117 |
1 files changed, 96 insertions, 21 deletions
diff --git a/kernel/kprobes.c b/kernel/kprobes.c index 610c837ad9e0..17ec4afb0994 100644 --- a/kernel/kprobes.c +++ b/kernel/kprobes.c | |||
@@ -38,6 +38,7 @@ | |||
38 | #include <linux/module.h> | 38 | #include <linux/module.h> |
39 | #include <linux/moduleloader.h> | 39 | #include <linux/moduleloader.h> |
40 | #include <linux/kallsyms.h> | 40 | #include <linux/kallsyms.h> |
41 | #include <linux/freezer.h> | ||
41 | #include <asm-generic/sections.h> | 42 | #include <asm-generic/sections.h> |
42 | #include <asm/cacheflush.h> | 43 | #include <asm/cacheflush.h> |
43 | #include <asm/errno.h> | 44 | #include <asm/errno.h> |
@@ -83,9 +84,36 @@ struct kprobe_insn_page { | |||
83 | kprobe_opcode_t *insns; /* Page of instruction slots */ | 84 | kprobe_opcode_t *insns; /* Page of instruction slots */ |
84 | char slot_used[INSNS_PER_PAGE]; | 85 | char slot_used[INSNS_PER_PAGE]; |
85 | int nused; | 86 | int nused; |
87 | int ngarbage; | ||
86 | }; | 88 | }; |
87 | 89 | ||
88 | static struct hlist_head kprobe_insn_pages; | 90 | static struct hlist_head kprobe_insn_pages; |
91 | static int kprobe_garbage_slots; | ||
92 | static int collect_garbage_slots(void); | ||
93 | |||
94 | static int __kprobes check_safety(void) | ||
95 | { | ||
96 | int ret = 0; | ||
97 | #if defined(CONFIG_PREEMPT) && defined(CONFIG_PM) | ||
98 | ret = freeze_processes(); | ||
99 | if (ret == 0) { | ||
100 | struct task_struct *p, *q; | ||
101 | do_each_thread(p, q) { | ||
102 | if (p != current && p->state == TASK_RUNNING && | ||
103 | p->pid != 0) { | ||
104 | printk("Check failed: %s is running\n",p->comm); | ||
105 | ret = -1; | ||
106 | goto loop_end; | ||
107 | } | ||
108 | } while_each_thread(p, q); | ||
109 | } | ||
110 | loop_end: | ||
111 | thaw_processes(); | ||
112 | #else | ||
113 | synchronize_sched(); | ||
114 | #endif | ||
115 | return ret; | ||
116 | } | ||
89 | 117 | ||
90 | /** | 118 | /** |
91 | * get_insn_slot() - Find a slot on an executable page for an instruction. | 119 | * get_insn_slot() - Find a slot on an executable page for an instruction. |
@@ -96,6 +124,7 @@ kprobe_opcode_t __kprobes *get_insn_slot(void) | |||
96 | struct kprobe_insn_page *kip; | 124 | struct kprobe_insn_page *kip; |
97 | struct hlist_node *pos; | 125 | struct hlist_node *pos; |
98 | 126 | ||
127 | retry: | ||
99 | hlist_for_each(pos, &kprobe_insn_pages) { | 128 | hlist_for_each(pos, &kprobe_insn_pages) { |
100 | kip = hlist_entry(pos, struct kprobe_insn_page, hlist); | 129 | kip = hlist_entry(pos, struct kprobe_insn_page, hlist); |
101 | if (kip->nused < INSNS_PER_PAGE) { | 130 | if (kip->nused < INSNS_PER_PAGE) { |
@@ -112,7 +141,11 @@ kprobe_opcode_t __kprobes *get_insn_slot(void) | |||
112 | } | 141 | } |
113 | } | 142 | } |
114 | 143 | ||
115 | /* All out of space. Need to allocate a new page. Use slot 0.*/ | 144 | /* If there are any garbage slots, collect it and try again. */ |
145 | if (kprobe_garbage_slots && collect_garbage_slots() == 0) { | ||
146 | goto retry; | ||
147 | } | ||
148 | /* All out of space. Need to allocate a new page. Use slot 0. */ | ||
116 | kip = kmalloc(sizeof(struct kprobe_insn_page), GFP_KERNEL); | 149 | kip = kmalloc(sizeof(struct kprobe_insn_page), GFP_KERNEL); |
117 | if (!kip) { | 150 | if (!kip) { |
118 | return NULL; | 151 | return NULL; |
@@ -133,10 +166,62 @@ kprobe_opcode_t __kprobes *get_insn_slot(void) | |||
133 | memset(kip->slot_used, 0, INSNS_PER_PAGE); | 166 | memset(kip->slot_used, 0, INSNS_PER_PAGE); |
134 | kip->slot_used[0] = 1; | 167 | kip->slot_used[0] = 1; |
135 | kip->nused = 1; | 168 | kip->nused = 1; |
169 | kip->ngarbage = 0; | ||
136 | return kip->insns; | 170 | return kip->insns; |
137 | } | 171 | } |
138 | 172 | ||
139 | void __kprobes free_insn_slot(kprobe_opcode_t *slot) | 173 | /* Return 1 if all garbages are collected, otherwise 0. */ |
174 | static int __kprobes collect_one_slot(struct kprobe_insn_page *kip, int idx) | ||
175 | { | ||
176 | kip->slot_used[idx] = 0; | ||
177 | kip->nused--; | ||
178 | if (kip->nused == 0) { | ||
179 | /* | ||
180 | * Page is no longer in use. Free it unless | ||
181 | * it's the last one. We keep the last one | ||
182 | * so as not to have to set it up again the | ||
183 | * next time somebody inserts a probe. | ||
184 | */ | ||
185 | hlist_del(&kip->hlist); | ||
186 | if (hlist_empty(&kprobe_insn_pages)) { | ||
187 | INIT_HLIST_NODE(&kip->hlist); | ||
188 | hlist_add_head(&kip->hlist, | ||
189 | &kprobe_insn_pages); | ||
190 | } else { | ||
191 | module_free(NULL, kip->insns); | ||
192 | kfree(kip); | ||
193 | } | ||
194 | return 1; | ||
195 | } | ||
196 | return 0; | ||
197 | } | ||
198 | |||
199 | static int __kprobes collect_garbage_slots(void) | ||
200 | { | ||
201 | struct kprobe_insn_page *kip; | ||
202 | struct hlist_node *pos, *next; | ||
203 | |||
204 | /* Ensure no-one is preepmted on the garbages */ | ||
205 | if (check_safety() != 0) | ||
206 | return -EAGAIN; | ||
207 | |||
208 | hlist_for_each_safe(pos, next, &kprobe_insn_pages) { | ||
209 | int i; | ||
210 | kip = hlist_entry(pos, struct kprobe_insn_page, hlist); | ||
211 | if (kip->ngarbage == 0) | ||
212 | continue; | ||
213 | kip->ngarbage = 0; /* we will collect all garbages */ | ||
214 | for (i = 0; i < INSNS_PER_PAGE; i++) { | ||
215 | if (kip->slot_used[i] == -1 && | ||
216 | collect_one_slot(kip, i)) | ||
217 | break; | ||
218 | } | ||
219 | } | ||
220 | kprobe_garbage_slots = 0; | ||
221 | return 0; | ||
222 | } | ||
223 | |||
224 | void __kprobes free_insn_slot(kprobe_opcode_t * slot, int dirty) | ||
140 | { | 225 | { |
141 | struct kprobe_insn_page *kip; | 226 | struct kprobe_insn_page *kip; |
142 | struct hlist_node *pos; | 227 | struct hlist_node *pos; |
@@ -146,28 +231,18 @@ void __kprobes free_insn_slot(kprobe_opcode_t *slot) | |||
146 | if (kip->insns <= slot && | 231 | if (kip->insns <= slot && |
147 | slot < kip->insns + (INSNS_PER_PAGE * MAX_INSN_SIZE)) { | 232 | slot < kip->insns + (INSNS_PER_PAGE * MAX_INSN_SIZE)) { |
148 | int i = (slot - kip->insns) / MAX_INSN_SIZE; | 233 | int i = (slot - kip->insns) / MAX_INSN_SIZE; |
149 | kip->slot_used[i] = 0; | 234 | if (dirty) { |
150 | kip->nused--; | 235 | kip->slot_used[i] = -1; |
151 | if (kip->nused == 0) { | 236 | kip->ngarbage++; |
152 | /* | 237 | } else { |
153 | * Page is no longer in use. Free it unless | 238 | collect_one_slot(kip, i); |
154 | * it's the last one. We keep the last one | ||
155 | * so as not to have to set it up again the | ||
156 | * next time somebody inserts a probe. | ||
157 | */ | ||
158 | hlist_del(&kip->hlist); | ||
159 | if (hlist_empty(&kprobe_insn_pages)) { | ||
160 | INIT_HLIST_NODE(&kip->hlist); | ||
161 | hlist_add_head(&kip->hlist, | ||
162 | &kprobe_insn_pages); | ||
163 | } else { | ||
164 | module_free(NULL, kip->insns); | ||
165 | kfree(kip); | ||
166 | } | ||
167 | } | 239 | } |
168 | return; | 240 | break; |
169 | } | 241 | } |
170 | } | 242 | } |
243 | if (dirty && (++kprobe_garbage_slots > INSNS_PER_PAGE)) { | ||
244 | collect_garbage_slots(); | ||
245 | } | ||
171 | } | 246 | } |
172 | #endif | 247 | #endif |
173 | 248 | ||