Skip to content

Commit 5b48562

Browse files
mhiramatIngo Molnar
authored andcommitted
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 <[email protected]> Signed-off-by: Masami Hiramatsu <[email protected]> Acked-by: Peter Zijlstra <[email protected]> Cc: Alexander Shishkin <[email protected]> Cc: Ananth N Mavinakayanahalli <[email protected]> Cc: Andrew Morton <[email protected]> Cc: Andrey Konovalov <[email protected]> Cc: Arnaldo Carvalho de Melo <[email protected]> Cc: Jiri Olsa <[email protected]> Cc: Linus Torvalds <[email protected]> Cc: Thomas Gleixner <[email protected]> Link: http://lkml.kernel.org/r/148388747896.6869.6354262871751682264.stgit@devbox [ Improved the changelog and coding style. ] Signed-off-by: Ingo Molnar <[email protected]>
1 parent f913f3a commit 5b48562

File tree

3 files changed

+91
-21
lines changed

3 files changed

+91
-21
lines changed

include/linux/kprobes.h

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,9 +278,13 @@ struct kprobe_insn_cache {
278278
int nr_garbage;
279279
};
280280

281+
#ifdef __ARCH_WANT_KPROBES_INSN_SLOT
281282
extern kprobe_opcode_t *__get_insn_slot(struct kprobe_insn_cache *c);
282283
extern void __free_insn_slot(struct kprobe_insn_cache *c,
283284
kprobe_opcode_t *slot, int dirty);
285+
/* sleep-less address checking routine */
286+
extern bool __is_insn_slot_addr(struct kprobe_insn_cache *c,
287+
unsigned long addr);
284288

285289
#define DEFINE_INSN_CACHE_OPS(__name) \
286290
extern struct kprobe_insn_cache kprobe_##__name##_slots; \
@@ -294,6 +298,18 @@ static inline void free_##__name##_slot(kprobe_opcode_t *slot, int dirty)\
294298
{ \
295299
__free_insn_slot(&kprobe_##__name##_slots, slot, dirty); \
296300
} \
301+
\
302+
static inline bool is_kprobe_##__name##_slot(unsigned long addr) \
303+
{ \
304+
return __is_insn_slot_addr(&kprobe_##__name##_slots, addr); \
305+
}
306+
#else /* __ARCH_WANT_KPROBES_INSN_SLOT */
307+
#define DEFINE_INSN_CACHE_OPS(__name) \
308+
static inline bool is_kprobe_##__name##_slot(unsigned long addr) \
309+
{ \
310+
return 0; \
311+
}
312+
#endif
297313

298314
DEFINE_INSN_CACHE_OPS(insn);
299315

@@ -330,7 +346,6 @@ extern int proc_kprobes_optimization_handler(struct ctl_table *table,
330346
int write, void __user *buffer,
331347
size_t *length, loff_t *ppos);
332348
#endif
333-
334349
#endif /* CONFIG_OPTPROBES */
335350
#ifdef CONFIG_KPROBES_ON_FTRACE
336351
extern void kprobe_ftrace_handler(unsigned long ip, unsigned long parent_ip,
@@ -481,6 +496,19 @@ static inline int enable_jprobe(struct jprobe *jp)
481496
return enable_kprobe(&jp->kp);
482497
}
483498

499+
#ifndef CONFIG_KPROBES
500+
static inline bool is_kprobe_insn_slot(unsigned long addr)
501+
{
502+
return false;
503+
}
504+
#endif
505+
#ifndef CONFIG_OPTPROBES
506+
static inline bool is_kprobe_optinsn_slot(unsigned long addr)
507+
{
508+
return false;
509+
}
510+
#endif
511+
484512
#ifdef CONFIG_KPROBES
485513
/*
486514
* Blacklist ganerating macro. Specify functions which is not probed

kernel/extable.c

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include <linux/module.h>
2121
#include <linux/mutex.h>
2222
#include <linux/init.h>
23+
#include <linux/kprobes.h>
2324

2425
#include <asm/sections.h>
2526
#include <linux/uaccess.h>
@@ -104,6 +105,8 @@ int __kernel_text_address(unsigned long addr)
104105
return 1;
105106
if (is_ftrace_trampoline(addr))
106107
return 1;
108+
if (is_kprobe_optinsn_slot(addr) || is_kprobe_insn_slot(addr))
109+
return 1;
107110
/*
108111
* There might be init symbols in saved stacktraces.
109112
* Give those symbols a chance to be printed in
@@ -123,7 +126,11 @@ int kernel_text_address(unsigned long addr)
123126
return 1;
124127
if (is_module_text_address(addr))
125128
return 1;
126-
return is_ftrace_trampoline(addr);
129+
if (is_ftrace_trampoline(addr))
130+
return 1;
131+
if (is_kprobe_optinsn_slot(addr) || is_kprobe_insn_slot(addr))
132+
return 1;
133+
return 0;
127134
}
128135

129136
/*

kernel/kprobes.c

Lines changed: 54 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -149,16 +149,19 @@ kprobe_opcode_t *__get_insn_slot(struct kprobe_insn_cache *c)
149149
struct kprobe_insn_page *kip;
150150
kprobe_opcode_t *slot = NULL;
151151

152+
/* Since the slot array is not protected by rcu, we need a mutex */
152153
mutex_lock(&c->mutex);
153154
retry:
154-
list_for_each_entry(kip, &c->pages, list) {
155+
rcu_read_lock();
156+
list_for_each_entry_rcu(kip, &c->pages, list) {
155157
if (kip->nused < slots_per_page(c)) {
156158
int i;
157159
for (i = 0; i < slots_per_page(c); i++) {
158160
if (kip->slot_used[i] == SLOT_CLEAN) {
159161
kip->slot_used[i] = SLOT_USED;
160162
kip->nused++;
161163
slot = kip->insns + (i * c->insn_size);
164+
rcu_read_unlock();
162165
goto out;
163166
}
164167
}
@@ -167,6 +170,7 @@ kprobe_opcode_t *__get_insn_slot(struct kprobe_insn_cache *c)
167170
WARN_ON(1);
168171
}
169172
}
173+
rcu_read_unlock();
170174

171175
/* If there are any garbage slots, collect it and try again. */
172176
if (c->nr_garbage && collect_garbage_slots(c) == 0)
@@ -193,7 +197,7 @@ kprobe_opcode_t *__get_insn_slot(struct kprobe_insn_cache *c)
193197
kip->nused = 1;
194198
kip->ngarbage = 0;
195199
kip->cache = c;
196-
list_add(&kip->list, &c->pages);
200+
list_add_rcu(&kip->list, &c->pages);
197201
slot = kip->insns;
198202
out:
199203
mutex_unlock(&c->mutex);
@@ -213,7 +217,8 @@ static int collect_one_slot(struct kprobe_insn_page *kip, int idx)
213217
* next time somebody inserts a probe.
214218
*/
215219
if (!list_is_singular(&kip->list)) {
216-
list_del(&kip->list);
220+
list_del_rcu(&kip->list);
221+
synchronize_rcu();
217222
kip->cache->free(kip->insns);
218223
kfree(kip);
219224
}
@@ -235,8 +240,7 @@ static int collect_garbage_slots(struct kprobe_insn_cache *c)
235240
continue;
236241
kip->ngarbage = 0; /* we will collect all garbages */
237242
for (i = 0; i < slots_per_page(c); i++) {
238-
if (kip->slot_used[i] == SLOT_DIRTY &&
239-
collect_one_slot(kip, i))
243+
if (kip->slot_used[i] == SLOT_DIRTY && collect_one_slot(kip, i))
240244
break;
241245
}
242246
}
@@ -248,29 +252,60 @@ void __free_insn_slot(struct kprobe_insn_cache *c,
248252
kprobe_opcode_t *slot, int dirty)
249253
{
250254
struct kprobe_insn_page *kip;
255+
long idx;
251256

252257
mutex_lock(&c->mutex);
253-
list_for_each_entry(kip, &c->pages, list) {
254-
long idx = ((long)slot - (long)kip->insns) /
255-
(c->insn_size * sizeof(kprobe_opcode_t));
256-
if (idx >= 0 && idx < slots_per_page(c)) {
257-
WARN_ON(kip->slot_used[idx] != SLOT_USED);
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);
258+
rcu_read_lock();
259+
list_for_each_entry_rcu(kip, &c->pages, list) {
260+
idx = ((long)slot - (long)kip->insns) /
261+
(c->insn_size * sizeof(kprobe_opcode_t));
262+
if (idx >= 0 && idx < slots_per_page(c))
265263
goto out;
266-
}
267264
}
268-
/* Could not free this slot. */
265+
/* Could not find this slot. */
269266
WARN_ON(1);
267+
kip = NULL;
270268
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+
}
271283
mutex_unlock(&c->mutex);
272284
}
273285

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+
274309
#ifdef CONFIG_OPTPROBES
275310
/* For optimized_kprobe buffer */
276311
struct kprobe_insn_cache kprobe_optinsn_slots = {

0 commit comments

Comments
 (0)