block: fix 32 bit overflow in __blkdev_issue_discard()
[sfrench/cifs-2.6.git] / kernel / trace / trace_uprobe.c
index e696667da29a49aa2f94372fa301b8ab1e7ec373..31ea48eceda184ed5e807f9fe22394f831980982 100644 (file)
@@ -15,6 +15,7 @@
 #include <linux/rculist.h>
 
 #include "trace_probe.h"
+#include "trace_probe_tmpl.h"
 
 #define UPROBE_EVENT_SYSTEM    "uprobes"
 
@@ -47,6 +48,7 @@ struct trace_uprobe {
        struct inode                    *inode;
        char                            *filename;
        unsigned long                   offset;
+       unsigned long                   ref_ctr_offset;
        unsigned long                   nhit;
        struct trace_probe              tp;
 };
@@ -98,74 +100,52 @@ static unsigned long get_user_stack_nth(struct pt_regs *regs, unsigned int n)
 /*
  * Uprobes-specific fetch functions
  */
-#define DEFINE_FETCH_stack(type)                                       \
-static void FETCH_FUNC_NAME(stack, type)(struct pt_regs *regs,         \
-                                        void *offset, void *dest)      \
-{                                                                      \
-       *(type *)dest = (type)get_user_stack_nth(regs,                  \
-                                             ((unsigned long)offset)); \
-}
-DEFINE_BASIC_FETCH_FUNCS(stack)
-/* No string on the stack entry */
-#define fetch_stack_string     NULL
-#define fetch_stack_string_size        NULL
-
-#define DEFINE_FETCH_memory(type)                                      \
-static void FETCH_FUNC_NAME(memory, type)(struct pt_regs *regs,                \
-                                         void *addr, void *dest)       \
-{                                                                      \
-       type retval;                                                    \
-       void __user *vaddr = (void __force __user *) addr;              \
-                                                                       \
-       if (copy_from_user(&retval, vaddr, sizeof(type)))               \
-               *(type *)dest = 0;                                      \
-       else                                                            \
-               *(type *) dest = retval;                                \
+static nokprobe_inline int
+probe_mem_read(void *dest, void *src, size_t size)
+{
+       void __user *vaddr = (void __force __user *)src;
+
+       return copy_from_user(dest, vaddr, size) ? -EFAULT : 0;
 }
-DEFINE_BASIC_FETCH_FUNCS(memory)
 /*
  * Fetch a null-terminated string. Caller MUST set *(u32 *)dest with max
  * length and relative data location.
  */
-static void FETCH_FUNC_NAME(memory, string)(struct pt_regs *regs,
-                                           void *addr, void *dest)
+static nokprobe_inline int
+fetch_store_string(unsigned long addr, void *dest, void *base)
 {
        long ret;
-       u32 rloc = *(u32 *)dest;
-       int maxlen  = get_rloc_len(rloc);
-       u8 *dst = get_rloc_data(dest);
+       u32 loc = *(u32 *)dest;
+       int maxlen  = get_loc_len(loc);
+       u8 *dst = get_loc_data(dest, base);
        void __user *src = (void __force __user *) addr;
 
-       if (!maxlen)
-               return;
+       if (unlikely(!maxlen))
+               return -ENOMEM;
 
        ret = strncpy_from_user(dst, src, maxlen);
-       if (ret == maxlen)
-               dst[--ret] = '\0';
-
-       if (ret < 0) {  /* Failed to fetch string */
-               ((u8 *)get_rloc_data(dest))[0] = '\0';
-               *(u32 *)dest = make_data_rloc(0, get_rloc_offs(rloc));
-       } else {
-               *(u32 *)dest = make_data_rloc(ret, get_rloc_offs(rloc));
+       if (ret >= 0) {
+               if (ret == maxlen)
+                       dst[ret - 1] = '\0';
+               *(u32 *)dest = make_data_loc(ret, (void *)dst - base);
        }
+
+       return ret;
 }
 
-static void FETCH_FUNC_NAME(memory, string_size)(struct pt_regs *regs,
-                                                void *addr, void *dest)
+/* Return the length of string -- including null terminal byte */
+static nokprobe_inline int
+fetch_store_strlen(unsigned long addr)
 {
        int len;
        void __user *vaddr = (void __force __user *) addr;
 
        len = strnlen_user(vaddr, MAX_STRING_SIZE);
 
-       if (len == 0 || len > MAX_STRING_SIZE)  /* Failed to check length */
-               *(u32 *)dest = 0;
-       else
-               *(u32 *)dest = len;
+       return (len > MAX_STRING_SIZE) ? 0 : len;
 }
 
-static unsigned long translate_user_vaddr(void *file_offset)
+static unsigned long translate_user_vaddr(unsigned long file_offset)
 {
        unsigned long base_addr;
        struct uprobe_dispatch_data *udd;
@@ -173,44 +153,44 @@ static unsigned long translate_user_vaddr(void *file_offset)
        udd = (void *) current->utask->vaddr;
 
        base_addr = udd->bp_addr - udd->tu->offset;
-       return base_addr + (unsigned long)file_offset;
+       return base_addr + file_offset;
 }
 
-#define DEFINE_FETCH_file_offset(type)                                 \
-static void FETCH_FUNC_NAME(file_offset, type)(struct pt_regs *regs,   \
-                                              void *offset, void *dest)\
-{                                                                      \
-       void *vaddr = (void *)translate_user_vaddr(offset);             \
-                                                                       \
-       FETCH_FUNC_NAME(memory, type)(regs, vaddr, dest);               \
+/* Note that we don't verify it, since the code does not come from user space */
+static int
+process_fetch_insn(struct fetch_insn *code, struct pt_regs *regs, void *dest,
+                  void *base)
+{
+       unsigned long val;
+
+       /* 1st stage: get value from context */
+       switch (code->op) {
+       case FETCH_OP_REG:
+               val = regs_get_register(regs, code->param);
+               break;
+       case FETCH_OP_STACK:
+               val = get_user_stack_nth(regs, code->param);
+               break;
+       case FETCH_OP_STACKP:
+               val = user_stack_pointer(regs);
+               break;
+       case FETCH_OP_RETVAL:
+               val = regs_return_value(regs);
+               break;
+       case FETCH_OP_IMM:
+               val = code->immediate;
+               break;
+       case FETCH_OP_FOFFS:
+               val = translate_user_vaddr(code->immediate);
+               break;
+       default:
+               return -EILSEQ;
+       }
+       code++;
+
+       return process_fetch_insn_bottom(code, val, dest, base);
 }
-DEFINE_BASIC_FETCH_FUNCS(file_offset)
-DEFINE_FETCH_file_offset(string)
-DEFINE_FETCH_file_offset(string_size)
-
-/* Fetch type information table */
-static const struct fetch_type uprobes_fetch_type_table[] = {
-       /* Special types */
-       [FETCH_TYPE_STRING] = __ASSIGN_FETCH_TYPE("string", string, string,
-                                       sizeof(u32), 1, "__data_loc char[]"),
-       [FETCH_TYPE_STRSIZE] = __ASSIGN_FETCH_TYPE("string_size", u32,
-                                       string_size, sizeof(u32), 0, "u32"),
-       /* Basic types */
-       ASSIGN_FETCH_TYPE(u8,  u8,  0),
-       ASSIGN_FETCH_TYPE(u16, u16, 0),
-       ASSIGN_FETCH_TYPE(u32, u32, 0),
-       ASSIGN_FETCH_TYPE(u64, u64, 0),
-       ASSIGN_FETCH_TYPE(s8,  u8,  1),
-       ASSIGN_FETCH_TYPE(s16, u16, 1),
-       ASSIGN_FETCH_TYPE(s32, u32, 1),
-       ASSIGN_FETCH_TYPE(s64, u64, 1),
-       ASSIGN_FETCH_TYPE_ALIAS(x8,  u8,  u8,  0),
-       ASSIGN_FETCH_TYPE_ALIAS(x16, u16, u16, 0),
-       ASSIGN_FETCH_TYPE_ALIAS(x32, u32, u32, 0),
-       ASSIGN_FETCH_TYPE_ALIAS(x64, u64, u64, 0),
-
-       ASSIGN_FETCH_TYPE_END
-};
+NOKPROBE_SYMBOL(process_fetch_insn)
 
 static inline void init_trace_uprobe_filter(struct trace_uprobe_filter *filter)
 {
@@ -311,6 +291,35 @@ static int unregister_trace_uprobe(struct trace_uprobe *tu)
        return 0;
 }
 
+/*
+ * Uprobe with multiple reference counter is not allowed. i.e.
+ * If inode and offset matches, reference counter offset *must*
+ * match as well. Though, there is one exception: If user is
+ * replacing old trace_uprobe with new one(same group/event),
+ * then we allow same uprobe with new reference counter as far
+ * as the new one does not conflict with any other existing
+ * ones.
+ */
+static struct trace_uprobe *find_old_trace_uprobe(struct trace_uprobe *new)
+{
+       struct trace_uprobe *tmp, *old = NULL;
+       struct inode *new_inode = d_real_inode(new->path.dentry);
+
+       old = find_probe_event(trace_event_name(&new->tp.call),
+                               new->tp.call.class->system);
+
+       list_for_each_entry(tmp, &uprobe_list, list) {
+               if ((old ? old != tmp : true) &&
+                   new_inode == d_real_inode(tmp->path.dentry) &&
+                   new->offset == tmp->offset &&
+                   new->ref_ctr_offset != tmp->ref_ctr_offset) {
+                       pr_warn("Reference counter offset mismatch.");
+                       return ERR_PTR(-EINVAL);
+               }
+       }
+       return old;
+}
+
 /* Register a trace_uprobe and probe_event */
 static int register_trace_uprobe(struct trace_uprobe *tu)
 {
@@ -320,8 +329,12 @@ static int register_trace_uprobe(struct trace_uprobe *tu)
        mutex_lock(&uprobe_lock);
 
        /* register as an event */
-       old_tu = find_probe_event(trace_event_name(&tu->tp.call),
-                       tu->tp.call.class->system);
+       old_tu = find_old_trace_uprobe(tu);
+       if (IS_ERR(old_tu)) {
+               ret = PTR_ERR(old_tu);
+               goto end;
+       }
+
        if (old_tu) {
                /* delete old event */
                ret = unregister_trace_uprobe(old_tu);
@@ -352,10 +365,10 @@ end:
 static int create_trace_uprobe(int argc, char **argv)
 {
        struct trace_uprobe *tu;
-       char *arg, *event, *group, *filename;
+       char *arg, *event, *group, *filename, *rctr, *rctr_end;
        char buf[MAX_EVENT_NAME_LEN];
        struct path path;
-       unsigned long offset;
+       unsigned long offset, ref_ctr_offset;
        bool is_delete, is_return;
        int i, ret;
 
@@ -364,6 +377,7 @@ static int create_trace_uprobe(int argc, char **argv)
        is_return = false;
        event = NULL;
        group = NULL;
+       ref_ctr_offset = 0;
 
        /* argc must be >= 1 */
        if (argv[0][0] == '-')
@@ -438,6 +452,26 @@ static int create_trace_uprobe(int argc, char **argv)
                goto fail_address_parse;
        }
 
+       /* Parse reference counter offset if specified. */
+       rctr = strchr(arg, '(');
+       if (rctr) {
+               rctr_end = strchr(rctr, ')');
+               if (rctr > rctr_end || *(rctr_end + 1) != 0) {
+                       ret = -EINVAL;
+                       pr_info("Invalid reference counter offset.\n");
+                       goto fail_address_parse;
+               }
+
+               *rctr++ = '\0';
+               *rctr_end = '\0';
+               ret = kstrtoul(rctr, 0, &ref_ctr_offset);
+               if (ret) {
+                       pr_info("Invalid reference counter offset.\n");
+                       goto fail_address_parse;
+               }
+       }
+
+       /* Parse uprobe offset. */
        ret = kstrtoul(arg, 0, &offset);
        if (ret)
                goto fail_address_parse;
@@ -472,6 +506,7 @@ static int create_trace_uprobe(int argc, char **argv)
                goto fail_address_parse;
        }
        tu->offset = offset;
+       tu->ref_ctr_offset = ref_ctr_offset;
        tu->path = path;
        tu->filename = kstrdup(filename, GFP_KERNEL);
 
@@ -522,8 +557,7 @@ static int create_trace_uprobe(int argc, char **argv)
 
                /* Parse fetch argument */
                ret = traceprobe_parse_probe_arg(arg, &tu->tp.size, parg,
-                                                is_return, false,
-                                                uprobes_fetch_type_table);
+                                       is_return ? TPARG_FL_RETURN : 0);
                if (ret) {
                        pr_info("Parse error at argument[%d]. (%d)\n", i, ret);
                        goto error;
@@ -590,6 +624,9 @@ static int probes_seq_show(struct seq_file *m, void *v)
                        trace_event_name(&tu->tp.call), tu->filename,
                        (int)(sizeof(void *) * 2), tu->offset);
 
+       if (tu->ref_ctr_offset)
+               seq_printf(m, "(0x%lx)", tu->ref_ctr_offset);
+
        for (i = 0; i < tu->tp.nr_args; i++)
                seq_printf(m, " %s=%s", tu->tp.args[i].name, tu->tp.args[i].comm);
 
@@ -833,7 +870,6 @@ print_uprobe_event(struct trace_iterator *iter, int flags, struct trace_event *e
        struct trace_seq *s = &iter->seq;
        struct trace_uprobe *tu;
        u8 *data;
-       int i;
 
        entry = (struct uprobe_trace_entry_head *)iter->ent;
        tu = container_of(event, struct trace_uprobe, tp.call.event);
@@ -850,12 +886,8 @@ print_uprobe_event(struct trace_iterator *iter, int flags, struct trace_event *e
                data = DATAOF_TRACE_ENTRY(entry, false);
        }
 
-       for (i = 0; i < tu->tp.nr_args; i++) {
-               struct probe_arg *parg = &tu->tp.args[i];
-
-               if (!parg->type->print(s, parg->name, data + parg->offset, entry))
-                       goto out;
-       }
+       if (print_probe_args(s, tu->tp.args, tu->tp.nr_args, data, entry) < 0)
+               goto out;
 
        trace_seq_putc(s, '\n');
 
@@ -905,7 +937,13 @@ probe_event_enable(struct trace_uprobe *tu, struct trace_event_file *file,
 
        tu->consumer.filter = filter;
        tu->inode = d_real_inode(tu->path.dentry);
-       ret = uprobe_register(tu->inode, tu->offset, &tu->consumer);
+       if (tu->ref_ctr_offset) {
+               ret = uprobe_register_refctr(tu->inode, tu->offset,
+                               tu->ref_ctr_offset, &tu->consumer);
+       } else {
+               ret = uprobe_register(tu->inode, tu->offset, &tu->consumer);
+       }
+
        if (ret)
                goto err_buffer;
 
@@ -958,7 +996,7 @@ probe_event_disable(struct trace_uprobe *tu, struct trace_event_file *file)
 
 static int uprobe_event_define_fields(struct trace_event_call *event_call)
 {
-       int ret, i, size;
+       int ret, size;
        struct uprobe_trace_entry_head field;
        struct trace_uprobe *tu = event_call->data;
 
@@ -970,19 +1008,8 @@ static int uprobe_event_define_fields(struct trace_event_call *event_call)
                DEFINE_FIELD(unsigned long, vaddr[0], FIELD_STRING_IP, 0);
                size = SIZEOF_TRACE_ENTRY(false);
        }
-       /* Set argument names as fields */
-       for (i = 0; i < tu->tp.nr_args; i++) {
-               struct probe_arg *parg = &tu->tp.args[i];
-
-               ret = trace_define_field(event_call, parg->type->fmttype,
-                                        parg->name, size + parg->offset,
-                                        parg->type->size, parg->type->is_signed,
-                                        FILTER_OTHER);
 
-               if (ret)
-                       return ret;
-       }
-       return 0;
+       return traceprobe_define_arg_fields(event_call, size, &tu->tp);
 }
 
 #ifdef CONFIG_PERF_EVENTS
@@ -1233,7 +1260,7 @@ static int uprobe_dispatcher(struct uprobe_consumer *con, struct pt_regs *regs)
        esize = SIZEOF_TRACE_ENTRY(is_ret_probe(tu));
 
        ucb = uprobe_buffer_get();
-       store_trace_args(esize, &tu->tp, regs, ucb->buf, dsize);
+       store_trace_args(ucb->buf, &tu->tp, regs, esize, dsize);
 
        if (tu->tp.flags & TP_FLAG_TRACE)
                ret |= uprobe_trace_func(tu, regs, ucb, dsize);
@@ -1268,7 +1295,7 @@ static int uretprobe_dispatcher(struct uprobe_consumer *con,
        esize = SIZEOF_TRACE_ENTRY(is_ret_probe(tu));
 
        ucb = uprobe_buffer_get();
-       store_trace_args(esize, &tu->tp, regs, ucb->buf, dsize);
+       store_trace_args(ucb->buf, &tu->tp, regs, esize, dsize);
 
        if (tu->tp.flags & TP_FLAG_TRACE)
                uretprobe_trace_func(tu, func, regs, ucb, dsize);
@@ -1304,7 +1331,7 @@ static int register_uprobe_event(struct trace_uprobe *tu)
 
        init_trace_event_call(tu, call);
 
-       if (set_print_fmt(&tu->tp, is_ret_probe(tu)) < 0)
+       if (traceprobe_set_print_fmt(&tu->tp, is_ret_probe(tu)) < 0)
                return -ENOMEM;
 
        ret = register_trace_event(&call->event);
@@ -1340,7 +1367,8 @@ static int unregister_uprobe_event(struct trace_uprobe *tu)
 
 #ifdef CONFIG_PERF_EVENTS
 struct trace_event_call *
-create_local_trace_uprobe(char *name, unsigned long offs, bool is_return)
+create_local_trace_uprobe(char *name, unsigned long offs,
+                         unsigned long ref_ctr_offset, bool is_return)
 {
        struct trace_uprobe *tu;
        struct path path;
@@ -1372,10 +1400,11 @@ create_local_trace_uprobe(char *name, unsigned long offs, bool is_return)
 
        tu->offset = offs;
        tu->path = path;
+       tu->ref_ctr_offset = ref_ctr_offset;
        tu->filename = kstrdup(name, GFP_KERNEL);
        init_trace_event_call(tu, &tu->tp.call);
 
-       if (set_print_fmt(&tu->tp, is_ret_probe(tu)) < 0) {
+       if (traceprobe_set_print_fmt(&tu->tp, is_ret_probe(tu)) < 0) {
                ret = -ENOMEM;
                goto error;
        }