diff options
author | Naveen N. Rao <naveen.n.rao@linux.vnet.ibm.com> | 2017-03-08 03:26:06 -0500 |
---|---|---|
committer | Arnaldo Carvalho de Melo <acme@redhat.com> | 2017-03-15 16:48:37 -0400 |
commit | 1d585e70905e03e8c19c9aaf523ec246ae6b18a1 (patch) | |
tree | 9395f6217ea999a080abe2324104cf0b826715bf | |
parent | af9100ad149cf31a1ab1160f71bb4025443dbdb6 (diff) |
trace/kprobes: Fix check for kretprobe offset within function entry
perf specifies an offset from _text and since this offset is fed
directly into the arch-specific helper, kprobes tracer rejects
installation of kretprobes through perf. Fix this by looking up the
actual offset from a function for the specified sym+offset.
Refactor and reuse existing routines to limit code duplication -- we
repurpose kprobe_addr() for determining final kprobe address and we
split out the function entry offset determination into a separate
generic helper.
Before patch:
naveen@ubuntu:~/linux/tools/perf$ sudo ./perf probe -v do_open%return
probe-definition(0): do_open%return
symbol:do_open file:(null) line:0 offset:0 return:1 lazy:(null)
0 arguments
Looking at the vmlinux_path (8 entries long)
Using /boot/vmlinux for symbols
Open Debuginfo file: /boot/vmlinux
Try to find probe point from debuginfo.
Matched function: do_open [2d0c7ff]
Probe point found: do_open+0
Matched function: do_open [35d76dc]
found inline addr: 0xc0000000004ba9c4
Failed to find "do_open%return",
because do_open is an inlined function and has no return point.
An error occurred in debuginfo analysis (-22).
Trying to use symbols.
Opening /sys/kernel/debug/tracing//README write=0
Opening /sys/kernel/debug/tracing//kprobe_events write=1
Writing event: r:probe/do_open _text+4469776
Failed to write event: Invalid argument
Error: Failed to add events. Reason: Invalid argument (Code: -22)
naveen@ubuntu:~/linux/tools/perf$ dmesg | tail
<snip>
[ 33.568656] Given offset is not valid for return probe.
After patch:
naveen@ubuntu:~/linux/tools/perf$ sudo ./perf probe -v do_open%return
probe-definition(0): do_open%return
symbol:do_open file:(null) line:0 offset:0 return:1 lazy:(null)
0 arguments
Looking at the vmlinux_path (8 entries long)
Using /boot/vmlinux for symbols
Open Debuginfo file: /boot/vmlinux
Try to find probe point from debuginfo.
Matched function: do_open [2d0c7d6]
Probe point found: do_open+0
Matched function: do_open [35d76b3]
found inline addr: 0xc0000000004ba9e4
Failed to find "do_open%return",
because do_open is an inlined function and has no return point.
An error occurred in debuginfo analysis (-22).
Trying to use symbols.
Opening /sys/kernel/debug/tracing//README write=0
Opening /sys/kernel/debug/tracing//kprobe_events write=1
Writing event: r:probe/do_open _text+4469808
Writing event: r:probe/do_open_1 _text+4956344
Added new events:
probe:do_open (on do_open%return)
probe:do_open_1 (on do_open%return)
You can now use it in all perf tools, such as:
perf record -e probe:do_open_1 -aR sleep 1
naveen@ubuntu:~/linux/tools/perf$ sudo cat /sys/kernel/debug/kprobes/list
c000000000041370 k kretprobe_trampoline+0x0 [OPTIMIZED]
c0000000004ba0b8 r do_open+0x8 [DISABLED]
c000000000443430 r do_open+0x0 [DISABLED]
Signed-off-by: Naveen N. Rao <naveen.n.rao@linux.vnet.ibm.com>
Acked-by: Masami Hiramatsu <mhiramat@kernel.org>
Cc: Ananth N Mavinakayanahalli <ananth@linux.vnet.ibm.com>
Cc: Michael Ellerman <mpe@ellerman.id.au>
Cc: Steven Rostedt <rostedt@goodmis.org>
Cc: linuxppc-dev@lists.ozlabs.org
Link: http://lkml.kernel.org/r/d8cd1ef420ec22e3643ac332fdabcffc77319a42.1488961018.git.naveen.n.rao@linux.vnet.ibm.com
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
-rw-r--r-- | include/linux/kprobes.h | 1 | ||||
-rw-r--r-- | kernel/kprobes.c | 40 | ||||
-rw-r--r-- | kernel/trace/trace_kprobe.c | 2 |
3 files changed, 28 insertions, 15 deletions
diff --git a/include/linux/kprobes.h b/include/linux/kprobes.h index 177bdf6c6aeb..47e4da5b4fa2 100644 --- a/include/linux/kprobes.h +++ b/include/linux/kprobes.h | |||
@@ -268,6 +268,7 @@ extern void show_registers(struct pt_regs *regs); | |||
268 | extern void kprobes_inc_nmissed_count(struct kprobe *p); | 268 | extern void kprobes_inc_nmissed_count(struct kprobe *p); |
269 | extern bool arch_within_kprobe_blacklist(unsigned long addr); | 269 | extern bool arch_within_kprobe_blacklist(unsigned long addr); |
270 | extern bool arch_function_offset_within_entry(unsigned long offset); | 270 | extern bool arch_function_offset_within_entry(unsigned long offset); |
271 | extern bool function_offset_within_entry(kprobe_opcode_t *addr, const char *sym, unsigned long offset); | ||
271 | 272 | ||
272 | extern bool within_kprobe_blacklist(unsigned long addr); | 273 | extern bool within_kprobe_blacklist(unsigned long addr); |
273 | 274 | ||
diff --git a/kernel/kprobes.c b/kernel/kprobes.c index 4780ec236035..d733479a10ee 100644 --- a/kernel/kprobes.c +++ b/kernel/kprobes.c | |||
@@ -1391,21 +1391,19 @@ bool within_kprobe_blacklist(unsigned long addr) | |||
1391 | * This returns encoded errors if it fails to look up symbol or invalid | 1391 | * This returns encoded errors if it fails to look up symbol or invalid |
1392 | * combination of parameters. | 1392 | * combination of parameters. |
1393 | */ | 1393 | */ |
1394 | static kprobe_opcode_t *kprobe_addr(struct kprobe *p) | 1394 | static kprobe_opcode_t *_kprobe_addr(kprobe_opcode_t *addr, |
1395 | const char *symbol_name, unsigned int offset) | ||
1395 | { | 1396 | { |
1396 | kprobe_opcode_t *addr = p->addr; | 1397 | if ((symbol_name && addr) || (!symbol_name && !addr)) |
1397 | |||
1398 | if ((p->symbol_name && p->addr) || | ||
1399 | (!p->symbol_name && !p->addr)) | ||
1400 | goto invalid; | 1398 | goto invalid; |
1401 | 1399 | ||
1402 | if (p->symbol_name) { | 1400 | if (symbol_name) { |
1403 | kprobe_lookup_name(p->symbol_name, addr); | 1401 | kprobe_lookup_name(symbol_name, addr); |
1404 | if (!addr) | 1402 | if (!addr) |
1405 | return ERR_PTR(-ENOENT); | 1403 | return ERR_PTR(-ENOENT); |
1406 | } | 1404 | } |
1407 | 1405 | ||
1408 | addr = (kprobe_opcode_t *)(((char *)addr) + p->offset); | 1406 | addr = (kprobe_opcode_t *)(((char *)addr) + offset); |
1409 | if (addr) | 1407 | if (addr) |
1410 | return addr; | 1408 | return addr; |
1411 | 1409 | ||
@@ -1413,6 +1411,11 @@ invalid: | |||
1413 | return ERR_PTR(-EINVAL); | 1411 | return ERR_PTR(-EINVAL); |
1414 | } | 1412 | } |
1415 | 1413 | ||
1414 | static kprobe_opcode_t *kprobe_addr(struct kprobe *p) | ||
1415 | { | ||
1416 | return _kprobe_addr(p->addr, p->symbol_name, p->offset); | ||
1417 | } | ||
1418 | |||
1416 | /* Check passed kprobe is valid and return kprobe in kprobe_table. */ | 1419 | /* Check passed kprobe is valid and return kprobe in kprobe_table. */ |
1417 | static struct kprobe *__get_valid_kprobe(struct kprobe *p) | 1420 | static struct kprobe *__get_valid_kprobe(struct kprobe *p) |
1418 | { | 1421 | { |
@@ -1881,19 +1884,28 @@ bool __weak arch_function_offset_within_entry(unsigned long offset) | |||
1881 | return !offset; | 1884 | return !offset; |
1882 | } | 1885 | } |
1883 | 1886 | ||
1887 | bool function_offset_within_entry(kprobe_opcode_t *addr, const char *sym, unsigned long offset) | ||
1888 | { | ||
1889 | kprobe_opcode_t *kp_addr = _kprobe_addr(addr, sym, offset); | ||
1890 | |||
1891 | if (IS_ERR(kp_addr)) | ||
1892 | return false; | ||
1893 | |||
1894 | if (!kallsyms_lookup_size_offset((unsigned long)kp_addr, NULL, &offset) || | ||
1895 | !arch_function_offset_within_entry(offset)) | ||
1896 | return false; | ||
1897 | |||
1898 | return true; | ||
1899 | } | ||
1900 | |||
1884 | int register_kretprobe(struct kretprobe *rp) | 1901 | int register_kretprobe(struct kretprobe *rp) |
1885 | { | 1902 | { |
1886 | int ret = 0; | 1903 | int ret = 0; |
1887 | struct kretprobe_instance *inst; | 1904 | struct kretprobe_instance *inst; |
1888 | int i; | 1905 | int i; |
1889 | void *addr; | 1906 | void *addr; |
1890 | unsigned long offset; | ||
1891 | |||
1892 | addr = kprobe_addr(&rp->kp); | ||
1893 | if (!kallsyms_lookup_size_offset((unsigned long)addr, NULL, &offset)) | ||
1894 | return -EINVAL; | ||
1895 | 1907 | ||
1896 | if (!arch_function_offset_within_entry(offset)) | 1908 | if (!function_offset_within_entry(rp->kp.addr, rp->kp.symbol_name, rp->kp.offset)) |
1897 | return -EINVAL; | 1909 | return -EINVAL; |
1898 | 1910 | ||
1899 | if (kretprobe_blacklist_size) { | 1911 | if (kretprobe_blacklist_size) { |
diff --git a/kernel/trace/trace_kprobe.c b/kernel/trace/trace_kprobe.c index 12fb540da0e5..013f4e7146d4 100644 --- a/kernel/trace/trace_kprobe.c +++ b/kernel/trace/trace_kprobe.c | |||
@@ -697,7 +697,7 @@ static int create_trace_kprobe(int argc, char **argv) | |||
697 | return ret; | 697 | return ret; |
698 | } | 698 | } |
699 | if (offset && is_return && | 699 | if (offset && is_return && |
700 | !arch_function_offset_within_entry(offset)) { | 700 | !function_offset_within_entry(NULL, symbol, offset)) { |
701 | pr_info("Given offset is not valid for return probe.\n"); | 701 | pr_info("Given offset is not valid for return probe.\n"); |
702 | return -EINVAL; | 702 | return -EINVAL; |
703 | } | 703 | } |