diff options
author | Masami Hiramatsu <mhiramat@kernel.org> | 2017-01-08 09:58:09 -0500 |
---|---|---|
committer | Ingo Molnar <mingo@kernel.org> | 2017-01-14 02:38:05 -0500 |
commit | 5b485629ba0d5d027880769ff467c587b24b4bde (patch) | |
tree | 395ec5583989fd297007dfd2c7d04292ed273242 /kernel/kprobes.c | |
parent | f913f3a655cb4c37129bb36c9f175071e1fbdc29 (diff) |
kprobes, extable: Identify kprobes trampolines as kernel text area
Improve __kernel_text_address()/kernel_text_address() to return
true if the given address is on a kprobe's instruction slot
trampoline.
This can help stacktraces to determine the address is on a
text area or not.
To implement this atomically in is_kprobe_*_slot(), also change
the insn_cache page list to an RCU list.
This changes timings a bit (it delays page freeing to the RCU garbage
collection phase), but none of that is in the hot path.
Note: this change can add small overhead to stack unwinders because
it adds 2 additional checks to __kernel_text_address(). However, the
impact should be very small, because kprobe_insn_pages list has 1 entry
per 256 probes(on x86, on arm/arm64 it will be 1024 probes),
and kprobe_optinsn_pages has 1 entry per 32 probes(on x86).
In most use cases, the number of kprobe events may be less
than 20, which means that is_kprobe_*_slot() will check just one entry.
Tested-by: Josh Poimboeuf <jpoimboe@redhat.com>
Signed-off-by: Masami Hiramatsu <mhiramat@kernel.org>
Acked-by: Peter Zijlstra <peterz@infradead.org>
Cc: Alexander Shishkin <alexander.shishkin@linux.intel.com>
Cc: Ananth N Mavinakayanahalli <ananth@linux.vnet.ibm.com>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Andrey Konovalov <andreyknvl@google.com>
Cc: Arnaldo Carvalho de Melo <acme@redhat.com>
Cc: Jiri Olsa <jolsa@redhat.com>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Thomas Gleixner <tglx@linutronix.de>
Link: http://lkml.kernel.org/r/148388747896.6869.6354262871751682264.stgit@devbox
[ Improved the changelog and coding style. ]
Signed-off-by: Ingo Molnar <mingo@kernel.org>
Diffstat (limited to 'kernel/kprobes.c')
-rw-r--r-- | kernel/kprobes.c | 73 |
1 files changed, 54 insertions, 19 deletions
diff --git a/kernel/kprobes.c b/kernel/kprobes.c index 43460104f119..ebb4dadca66b 100644 --- a/kernel/kprobes.c +++ b/kernel/kprobes.c | |||
@@ -149,9 +149,11 @@ kprobe_opcode_t *__get_insn_slot(struct kprobe_insn_cache *c) | |||
149 | struct kprobe_insn_page *kip; | 149 | struct kprobe_insn_page *kip; |
150 | kprobe_opcode_t *slot = NULL; | 150 | kprobe_opcode_t *slot = NULL; |
151 | 151 | ||
152 | /* Since the slot array is not protected by rcu, we need a mutex */ | ||
152 | mutex_lock(&c->mutex); | 153 | mutex_lock(&c->mutex); |
153 | retry: | 154 | retry: |
154 | list_for_each_entry(kip, &c->pages, list) { | 155 | rcu_read_lock(); |
156 | list_for_each_entry_rcu(kip, &c->pages, list) { | ||
155 | if (kip->nused < slots_per_page(c)) { | 157 | if (kip->nused < slots_per_page(c)) { |
156 | int i; | 158 | int i; |
157 | for (i = 0; i < slots_per_page(c); i++) { | 159 | for (i = 0; i < slots_per_page(c); i++) { |
@@ -159,6 +161,7 @@ kprobe_opcode_t *__get_insn_slot(struct kprobe_insn_cache *c) | |||
159 | kip->slot_used[i] = SLOT_USED; | 161 | kip->slot_used[i] = SLOT_USED; |
160 | kip->nused++; | 162 | kip->nused++; |
161 | slot = kip->insns + (i * c->insn_size); | 163 | slot = kip->insns + (i * c->insn_size); |
164 | rcu_read_unlock(); | ||
162 | goto out; | 165 | goto out; |
163 | } | 166 | } |
164 | } | 167 | } |
@@ -167,6 +170,7 @@ kprobe_opcode_t *__get_insn_slot(struct kprobe_insn_cache *c) | |||
167 | WARN_ON(1); | 170 | WARN_ON(1); |
168 | } | 171 | } |
169 | } | 172 | } |
173 | rcu_read_unlock(); | ||
170 | 174 | ||
171 | /* If there are any garbage slots, collect it and try again. */ | 175 | /* If there are any garbage slots, collect it and try again. */ |
172 | if (c->nr_garbage && collect_garbage_slots(c) == 0) | 176 | if (c->nr_garbage && collect_garbage_slots(c) == 0) |
@@ -193,7 +197,7 @@ kprobe_opcode_t *__get_insn_slot(struct kprobe_insn_cache *c) | |||
193 | kip->nused = 1; | 197 | kip->nused = 1; |
194 | kip->ngarbage = 0; | 198 | kip->ngarbage = 0; |
195 | kip->cache = c; | 199 | kip->cache = c; |
196 | list_add(&kip->list, &c->pages); | 200 | list_add_rcu(&kip->list, &c->pages); |
197 | slot = kip->insns; | 201 | slot = kip->insns; |
198 | out: | 202 | out: |
199 | mutex_unlock(&c->mutex); | 203 | mutex_unlock(&c->mutex); |
@@ -213,7 +217,8 @@ static int collect_one_slot(struct kprobe_insn_page *kip, int idx) | |||
213 | * next time somebody inserts a probe. | 217 | * next time somebody inserts a probe. |
214 | */ | 218 | */ |
215 | if (!list_is_singular(&kip->list)) { | 219 | if (!list_is_singular(&kip->list)) { |
216 | list_del(&kip->list); | 220 | list_del_rcu(&kip->list); |
221 | synchronize_rcu(); | ||
217 | kip->cache->free(kip->insns); | 222 | kip->cache->free(kip->insns); |
218 | kfree(kip); | 223 | kfree(kip); |
219 | } | 224 | } |
@@ -235,8 +240,7 @@ static int collect_garbage_slots(struct kprobe_insn_cache *c) | |||
235 | continue; | 240 | continue; |
236 | kip->ngarbage = 0; /* we will collect all garbages */ | 241 | kip->ngarbage = 0; /* we will collect all garbages */ |
237 | for (i = 0; i < slots_per_page(c); i++) { | 242 | for (i = 0; i < slots_per_page(c); i++) { |
238 | if (kip->slot_used[i] == SLOT_DIRTY && | 243 | if (kip->slot_used[i] == SLOT_DIRTY && collect_one_slot(kip, i)) |
239 | collect_one_slot(kip, i)) | ||
240 | break; | 244 | break; |
241 | } | 245 | } |
242 | } | 246 | } |
@@ -248,29 +252,60 @@ void __free_insn_slot(struct kprobe_insn_cache *c, | |||
248 | kprobe_opcode_t *slot, int dirty) | 252 | kprobe_opcode_t *slot, int dirty) |
249 | { | 253 | { |
250 | struct kprobe_insn_page *kip; | 254 | struct kprobe_insn_page *kip; |
255 | long idx; | ||
251 | 256 | ||
252 | mutex_lock(&c->mutex); | 257 | mutex_lock(&c->mutex); |
253 | list_for_each_entry(kip, &c->pages, list) { | 258 | rcu_read_lock(); |
254 | long idx = ((long)slot - (long)kip->insns) / | 259 | list_for_each_entry_rcu(kip, &c->pages, list) { |
255 | (c->insn_size * sizeof(kprobe_opcode_t)); | 260 | idx = ((long)slot - (long)kip->insns) / |
256 | if (idx >= 0 && idx < slots_per_page(c)) { | 261 | (c->insn_size * sizeof(kprobe_opcode_t)); |
257 | WARN_ON(kip->slot_used[idx] != SLOT_USED); | 262 | if (idx >= 0 && idx < slots_per_page(c)) |
258 | if (dirty) { | ||
259 | kip->slot_used[idx] = SLOT_DIRTY; | ||
260 | kip->ngarbage++; | ||
261 | if (++c->nr_garbage > slots_per_page(c)) | ||
262 | collect_garbage_slots(c); | ||
263 | } else | ||
264 | collect_one_slot(kip, idx); | ||
265 | goto out; | 263 | goto out; |
266 | } | ||
267 | } | 264 | } |
268 | /* Could not free this slot. */ | 265 | /* Could not find this slot. */ |
269 | WARN_ON(1); | 266 | WARN_ON(1); |
267 | kip = NULL; | ||
270 | out: | 268 | out: |
269 | rcu_read_unlock(); | ||
270 | /* Mark and sweep: this may sleep */ | ||
271 | if (kip) { | ||
272 | /* Check double free */ | ||
273 | WARN_ON(kip->slot_used[idx] != SLOT_USED); | ||
274 | if (dirty) { | ||
275 | kip->slot_used[idx] = SLOT_DIRTY; | ||
276 | kip->ngarbage++; | ||
277 | if (++c->nr_garbage > slots_per_page(c)) | ||
278 | collect_garbage_slots(c); | ||
279 | } else { | ||
280 | collect_one_slot(kip, idx); | ||
281 | } | ||
282 | } | ||
271 | mutex_unlock(&c->mutex); | 283 | mutex_unlock(&c->mutex); |
272 | } | 284 | } |
273 | 285 | ||
286 | /* | ||
287 | * Check given address is on the page of kprobe instruction slots. | ||
288 | * This will be used for checking whether the address on a stack | ||
289 | * is on a text area or not. | ||
290 | */ | ||
291 | bool __is_insn_slot_addr(struct kprobe_insn_cache *c, unsigned long addr) | ||
292 | { | ||
293 | struct kprobe_insn_page *kip; | ||
294 | bool ret = false; | ||
295 | |||
296 | rcu_read_lock(); | ||
297 | list_for_each_entry_rcu(kip, &c->pages, list) { | ||
298 | if (addr >= (unsigned long)kip->insns && | ||
299 | addr < (unsigned long)kip->insns + PAGE_SIZE) { | ||
300 | ret = true; | ||
301 | break; | ||
302 | } | ||
303 | } | ||
304 | rcu_read_unlock(); | ||
305 | |||
306 | return ret; | ||
307 | } | ||
308 | |||
274 | #ifdef CONFIG_OPTPROBES | 309 | #ifdef CONFIG_OPTPROBES |
275 | /* For optimized_kprobe buffer */ | 310 | /* For optimized_kprobe buffer */ |
276 | struct kprobe_insn_cache kprobe_optinsn_slots = { | 311 | struct kprobe_insn_cache kprobe_optinsn_slots = { |