aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNaveen N. Rao <naveen.n.rao@linux.vnet.ibm.com>2017-03-08 03:26:06 -0500
committerArnaldo Carvalho de Melo <acme@redhat.com>2017-03-15 16:48:37 -0400
commit1d585e70905e03e8c19c9aaf523ec246ae6b18a1 (patch)
tree9395f6217ea999a080abe2324104cf0b826715bf
parentaf9100ad149cf31a1ab1160f71bb4025443dbdb6 (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.h1
-rw-r--r--kernel/kprobes.c40
-rw-r--r--kernel/trace/trace_kprobe.c2
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);
268extern void kprobes_inc_nmissed_count(struct kprobe *p); 268extern void kprobes_inc_nmissed_count(struct kprobe *p);
269extern bool arch_within_kprobe_blacklist(unsigned long addr); 269extern bool arch_within_kprobe_blacklist(unsigned long addr);
270extern bool arch_function_offset_within_entry(unsigned long offset); 270extern bool arch_function_offset_within_entry(unsigned long offset);
271extern bool function_offset_within_entry(kprobe_opcode_t *addr, const char *sym, unsigned long offset);
271 272
272extern bool within_kprobe_blacklist(unsigned long addr); 273extern 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 */
1394static kprobe_opcode_t *kprobe_addr(struct kprobe *p) 1394static 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
1414static 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. */
1417static struct kprobe *__get_valid_kprobe(struct kprobe *p) 1420static 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
1887bool 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
1884int register_kretprobe(struct kretprobe *rp) 1901int 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 }