Merge tag 'probes-v6.6' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux...
authorLinus Torvalds <torvalds@linux-foundation.org>
Sat, 2 Sep 2023 18:10:50 +0000 (11:10 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Sat, 2 Sep 2023 18:10:50 +0000 (11:10 -0700)
Pull probes updates from Masami Hiramatsu:

 - kprobes: use struct_size() for variable size kretprobe_instance data
   structure.

 - eprobe: Simplify trace_eprobe list iteration.

 - probe events: Data structure field access support on BTF argument.

     - Update BTF argument support on the functions in the kernel
       loadable modules (only loaded modules are supported).

     - Move generic BTF access function (search function prototype and
       get function parameters) to a separated file.

     - Add a function to search a member of data structure in BTF.

     - Support accessing BTF data structure member from probe args by
       C-like arrow('->') and dot('.') operators. e.g.
          't sched_switch next=next->pid vruntime=next->se.vruntime'

     - Support accessing BTF data structure member from $retval. e.g.
          'f getname_flags%return +0($retval->name):string'

     - Add string type checking if BTF type info is available. This will
       reject if user specify ":string" type for non "char pointer"
       type.

     - Automatically assume the fprobe event as a function return event
       if $retval is used.

 - selftests/ftrace: Add BTF data field access test cases.

 - Documentation: Update fprobe event example with BTF data field.

* tag 'probes-v6.6' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace:
  Documentation: tracing: Update fprobe event example with BTF field
  selftests/ftrace: Add BTF fields access testcases
  tracing/fprobe-event: Assume fprobe is a return event by $retval
  tracing/probes: Add string type check with BTF
  tracing/probes: Support BTF field access from $retval
  tracing/probes: Support BTF based data structure field access
  tracing/probes: Add a function to search a member of a struct/union
  tracing/probes: Move finding func-proto API and getting func-param API to trace_btf
  tracing/probes: Support BTF argument on module functions
  tracing/eprobe: Iterate trace_eprobe directly
  kernel: kprobes: Use struct_size()

16 files changed:
Documentation/trace/fprobetrace.rst
include/linux/btf.h
kernel/bpf/btf.c
kernel/kprobes.c
kernel/trace/Makefile
kernel/trace/trace.c
kernel/trace/trace_btf.c [new file with mode: 0644]
kernel/trace/trace_btf.h [new file with mode: 0644]
kernel/trace/trace_eprobe.c
kernel/trace/trace_fprobe.c
kernel/trace/trace_kprobe.c
kernel/trace/trace_probe.c
kernel/trace/trace_probe.h
kernel/trace/trace_uprobe.c
tools/testing/selftests/ftrace/test.d/dynevent/add_remove_btfarg.tc
tools/testing/selftests/ftrace/test.d/dynevent/fprobe_syntax_errors.tc

index 7297f94784594f30cae94a6a92ce03749d7b0592..8e9bebcf0a2e8f0d05edca303d9fbdf718873ce1 100644 (file)
@@ -79,9 +79,9 @@ automatically set by the given name. ::
  f:fprobes/myprobe vfs_read count=count pos=pos
 
 It also chooses the fetch type from BTF information. For example, in the above
-example, the ``count`` is unsigned long, and the ``pos`` is a pointer. Thus, both
-are converted to 64bit unsigned long, but only ``pos`` has "%Lx" print-format as
-below ::
+example, the ``count`` is unsigned long, and the ``pos`` is a pointer. Thus,
+both are converted to 64bit unsigned long, but only ``pos`` has "%Lx"
+print-format as below ::
 
  # cat events/fprobes/myprobe/format
  name: myprobe
@@ -105,9 +105,47 @@ is expanded to all function arguments of the function or the tracepoint. ::
  # cat dynamic_events
  f:fprobes/myprobe vfs_read file=file buf=buf count=count pos=pos
 
-BTF also affects the ``$retval``. If user doesn't set any type, the retval type is
-automatically picked from the BTF. If the function returns ``void``, ``$retval``
-is rejected.
+BTF also affects the ``$retval``. If user doesn't set any type, the retval
+type is automatically picked from the BTF. If the function returns ``void``,
+``$retval`` is rejected.
+
+You can access the data fields of a data structure using allow operator ``->``
+(for pointer type) and dot operator ``.`` (for data structure type.)::
+
+# echo 't sched_switch preempt prev_pid=prev->pid next_pid=next->pid' >> dynamic_events
+
+The field access operators, ``->`` and ``.`` can be combined for accessing deeper
+members and other structure members pointed by the member. e.g. ``foo->bar.baz->qux``
+If there is non-name union member, you can directly access it as the C code does.
+For example::
+
+ struct {
+       union {
+       int a;
+       int b;
+       };
+ } *foo;
+
+To access ``a`` and ``b``, use ``foo->a`` and ``foo->b`` in this case.
+
+This data field access is available for the return value via ``$retval``,
+e.g. ``$retval->name``.
+
+For these BTF arguments and fields, ``:string`` and ``:ustring`` change the
+behavior. If these are used for BTF argument or field, it checks whether
+the BTF type of the argument or the data field is ``char *`` or ``char []``,
+or not.  If not, it rejects applying the string types. Also, with the BTF
+support, you don't need a memory dereference operator (``+0(PTR)``) for
+accessing the string pointed by a ``PTR``. It automatically adds the memory
+dereference operator according to the BTF type. e.g. ::
+
+# echo 't sched_switch prev->comm:string' >> dynamic_events
+# echo 'f getname_flags%return $retval->name:string' >> dynamic_events
+
+The ``prev->comm`` is an embedded char array in the data structure, and
+``$retval->name`` is a char pointer in the data structure. But in both
+cases, you can use ``:string`` type to get the string.
+
 
 Usage examples
 --------------
@@ -161,10 +199,10 @@ parameters. This means you can access any field values in the task
 structure pointed by the ``prev`` and ``next`` arguments.
 
 For example, usually ``task_struct::start_time`` is not traced, but with this
-traceprobe event, you can trace it as below.
+traceprobe event, you can trace that field as below.
 ::
 
-  # echo 't sched_switch comm=+1896(next):string start_time=+1728(next):u64' > dynamic_events
+  # echo 't sched_switch comm=next->comm:string next->start_time' > dynamic_events
   # head -n 20 trace | tail
  #           TASK-PID     CPU#  |||||  TIMESTAMP  FUNCTION
  #              | |         |   |||||     |         |
@@ -176,13 +214,3 @@ traceprobe event, you can trace it as below.
            <idle>-0       [000] d..3.  5606.690317: sched_switch: (__probestub_sched_switch+0x4/0x10) comm="kworker/0:1" usage=1 start_time=137000000
       kworker/0:1-14      [000] d..3.  5606.690339: sched_switch: (__probestub_sched_switch+0x4/0x10) comm="swapper/0" usage=2 start_time=0
            <idle>-0       [000] d..3.  5606.692368: sched_switch: (__probestub_sched_switch+0x4/0x10) comm="kworker/0:1" usage=1 start_time=137000000
-
-Currently, to find the offset of a specific field in the data structure,
-you need to build kernel with debuginfo and run `perf probe` command with
-`-D` option. e.g.
-::
-
- # perf probe -D "__probestub_sched_switch next->comm:string next->start_time"
- p:probe/__probestub_sched_switch __probestub_sched_switch+0 comm=+1896(%cx):string start_time=+1728(%cx):u64
-
-And replace the ``%cx`` with the ``next``.
index df64cc642074162e94123e8caf507def4f0dacbf..928113a80a95278e44161ed8a7c559716e237651 100644 (file)
@@ -209,6 +209,7 @@ struct btf_record *btf_parse_fields(const struct btf *btf, const struct btf_type
 int btf_check_and_fixup_fields(const struct btf *btf, struct btf_record *rec);
 bool btf_type_is_void(const struct btf_type *t);
 s32 btf_find_by_name_kind(const struct btf *btf, const char *name, u8 kind);
+s32 bpf_find_btf_id(const char *name, u32 kind, struct btf **btf_p);
 const struct btf_type *btf_type_skip_modifiers(const struct btf *btf,
                                               u32 id, u32 *res_id);
 const struct btf_type *btf_type_resolve_ptr(const struct btf *btf,
index 249657c466dd33af21c686e827d3608e31d832af..1095bbe2985930f486010f955d96ee7b57d0a8a1 100644 (file)
@@ -553,7 +553,7 @@ s32 btf_find_by_name_kind(const struct btf *btf, const char *name, u8 kind)
        return -ENOENT;
 }
 
-static s32 bpf_find_btf_id(const char *name, u32 kind, struct btf **btf_p)
+s32 bpf_find_btf_id(const char *name, u32 kind, struct btf **btf_p)
 {
        struct btf *btf;
        s32 ret;
index ca385b61d546722161f3c720fdf3198f4e9445d1..0c6185aefaef576c55fb324e6d30a8e2c0050d1f 100644 (file)
@@ -2232,8 +2232,7 @@ int register_kretprobe(struct kretprobe *rp)
                return -ENOMEM;
 
        for (i = 0; i < rp->maxactive; i++) {
-               inst = kzalloc(sizeof(struct kretprobe_instance) +
-                              rp->data_size, GFP_KERNEL);
+               inst = kzalloc(struct_size(inst, data, rp->data_size), GFP_KERNEL);
                if (inst == NULL) {
                        rethook_free(rp->rh);
                        rp->rh = NULL;
@@ -2256,8 +2255,7 @@ int register_kretprobe(struct kretprobe *rp)
 
        rp->rph->rp = rp;
        for (i = 0; i < rp->maxactive; i++) {
-               inst = kzalloc(sizeof(struct kretprobe_instance) +
-                              rp->data_size, GFP_KERNEL);
+               inst = kzalloc(struct_size(inst, data, rp->data_size), GFP_KERNEL);
                if (inst == NULL) {
                        refcount_set(&rp->rph->ref, i);
                        free_rp_inst(rp);
index 64b61f67a403e249c04769c7931194b2132da078..057cd975d0146a38933364bfe834b9b536115e5d 100644 (file)
@@ -99,6 +99,7 @@ obj-$(CONFIG_KGDB_KDB) += trace_kdb.o
 endif
 obj-$(CONFIG_DYNAMIC_EVENTS) += trace_dynevent.o
 obj-$(CONFIG_PROBE_EVENTS) += trace_probe.o
+obj-$(CONFIG_PROBE_EVENTS_BTF_ARGS) += trace_btf.o
 obj-$(CONFIG_UPROBE_EVENTS) += trace_uprobe.o
 obj-$(CONFIG_BOOTTIME_TRACING) += trace_boot.o
 obj-$(CONFIG_FTRACE_RECORD_RECURSION) += trace_recursion_record.o
index 35783a7baf151cf7f3c7eb65cbf228bba433fe97..2b4ded75336706dc6650c7420d0c8972ac4910db 100644 (file)
@@ -5711,7 +5711,8 @@ static const char readme_msg[] =
        "\t fetcharg: (%<register>|$<efield>), @<address>, @<symbol>[+|-<offset>],\n"
 #ifdef CONFIG_HAVE_FUNCTION_ARG_ACCESS_API
 #ifdef CONFIG_PROBE_EVENTS_BTF_ARGS
-       "\t           $stack<index>, $stack, $retval, $comm, $arg<N>, <argname>\n"
+       "\t           $stack<index>, $stack, $retval, $comm, $arg<N>,\n"
+       "\t           <argname>[->field[->field|.field...]],\n"
 #else
        "\t           $stack<index>, $stack, $retval, $comm, $arg<N>,\n"
 #endif
diff --git a/kernel/trace/trace_btf.c b/kernel/trace/trace_btf.c
new file mode 100644 (file)
index 0000000..ca224d5
--- /dev/null
@@ -0,0 +1,122 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/btf.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+
+#include "trace_btf.h"
+
+/*
+ * Find a function proto type by name, and return the btf_type with its btf
+ * in *@btf_p. Return NULL if not found.
+ * Note that caller has to call btf_put(*@btf_p) after using the btf_type.
+ */
+const struct btf_type *btf_find_func_proto(const char *func_name, struct btf **btf_p)
+{
+       const struct btf_type *t;
+       s32 id;
+
+       id = bpf_find_btf_id(func_name, BTF_KIND_FUNC, btf_p);
+       if (id < 0)
+               return NULL;
+
+       /* Get BTF_KIND_FUNC type */
+       t = btf_type_by_id(*btf_p, id);
+       if (!t || !btf_type_is_func(t))
+               goto err;
+
+       /* The type of BTF_KIND_FUNC is BTF_KIND_FUNC_PROTO */
+       t = btf_type_by_id(*btf_p, t->type);
+       if (!t || !btf_type_is_func_proto(t))
+               goto err;
+
+       return t;
+err:
+       btf_put(*btf_p);
+       return NULL;
+}
+
+/*
+ * Get function parameter with the number of parameters.
+ * This can return NULL if the function has no parameters.
+ * It can return -EINVAL if the @func_proto is not a function proto type.
+ */
+const struct btf_param *btf_get_func_param(const struct btf_type *func_proto, s32 *nr)
+{
+       if (!btf_type_is_func_proto(func_proto))
+               return ERR_PTR(-EINVAL);
+
+       *nr = btf_type_vlen(func_proto);
+       if (*nr > 0)
+               return (const struct btf_param *)(func_proto + 1);
+       else
+               return NULL;
+}
+
+#define BTF_ANON_STACK_MAX     16
+
+struct btf_anon_stack {
+       u32 tid;
+       u32 offset;
+};
+
+/*
+ * Find a member of data structure/union by name and return it.
+ * Return NULL if not found, or -EINVAL if parameter is invalid.
+ * If the member is an member of anonymous union/structure, the offset
+ * of that anonymous union/structure is stored into @anon_offset. Caller
+ * can calculate the correct offset from the root data structure by
+ * adding anon_offset to the member's offset.
+ */
+const struct btf_member *btf_find_struct_member(struct btf *btf,
+                                               const struct btf_type *type,
+                                               const char *member_name,
+                                               u32 *anon_offset)
+{
+       struct btf_anon_stack *anon_stack;
+       const struct btf_member *member;
+       u32 tid, cur_offset = 0;
+       const char *name;
+       int i, top = 0;
+
+       anon_stack = kcalloc(BTF_ANON_STACK_MAX, sizeof(*anon_stack), GFP_KERNEL);
+       if (!anon_stack)
+               return ERR_PTR(-ENOMEM);
+
+retry:
+       if (!btf_type_is_struct(type)) {
+               member = ERR_PTR(-EINVAL);
+               goto out;
+       }
+
+       for_each_member(i, type, member) {
+               if (!member->name_off) {
+                       /* Anonymous union/struct: push it for later use */
+                       type = btf_type_skip_modifiers(btf, member->type, &tid);
+                       if (type && top < BTF_ANON_STACK_MAX) {
+                               anon_stack[top].tid = tid;
+                               anon_stack[top++].offset =
+                                       cur_offset + member->offset;
+                       }
+               } else {
+                       name = btf_name_by_offset(btf, member->name_off);
+                       if (name && !strcmp(member_name, name)) {
+                               if (anon_offset)
+                                       *anon_offset = cur_offset;
+                               goto out;
+                       }
+               }
+       }
+       if (top > 0) {
+               /* Pop from the anonymous stack and retry */
+               tid = anon_stack[--top].tid;
+               cur_offset = anon_stack[top].offset;
+               type = btf_type_by_id(btf, tid);
+               goto retry;
+       }
+       member = NULL;
+
+out:
+       kfree(anon_stack);
+       return member;
+}
+
diff --git a/kernel/trace/trace_btf.h b/kernel/trace/trace_btf.h
new file mode 100644 (file)
index 0000000..4bc44bc
--- /dev/null
@@ -0,0 +1,11 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#include <linux/btf.h>
+
+const struct btf_type *btf_find_func_proto(const char *func_name,
+                                          struct btf **btf_p);
+const struct btf_param *btf_get_func_param(const struct btf_type *func_proto,
+                                          s32 *nr);
+const struct btf_member *btf_find_struct_member(struct btf *btf,
+                                               const struct btf_type *type,
+                                               const char *member_name,
+                                               u32 *anon_offset);
index a0a704ba27db3471b4d029a387d50b2b12c78fed..72714cbf475c78377925ea4c13a42ce76529dc69 100644 (file)
@@ -41,6 +41,10 @@ struct eprobe_data {
        struct trace_eprobe     *ep;
 };
 
+
+#define for_each_trace_eprobe_tp(ep, _tp) \
+       list_for_each_entry(ep, trace_probe_probe_list(_tp), tp.list)
+
 static int __trace_eprobe_create(int argc, const char *argv[]);
 
 static void trace_event_probe_cleanup(struct trace_eprobe *ep)
@@ -640,7 +644,7 @@ static int disable_eprobe(struct trace_eprobe *ep,
 static int enable_trace_eprobe(struct trace_event_call *call,
                               struct trace_event_file *file)
 {
-       struct trace_probe *pos, *tp;
+       struct trace_probe *tp;
        struct trace_eprobe *ep;
        bool enabled;
        int ret = 0;
@@ -662,8 +666,7 @@ static int enable_trace_eprobe(struct trace_event_call *call,
        if (enabled)
                return 0;
 
-       list_for_each_entry(pos, trace_probe_probe_list(tp), list) {
-               ep = container_of(pos, struct trace_eprobe, tp);
+       for_each_trace_eprobe_tp(ep, tp) {
                ret = enable_eprobe(ep, file);
                if (ret)
                        break;
@@ -680,8 +683,7 @@ static int enable_trace_eprobe(struct trace_event_call *call,
                         */
                        WARN_ON_ONCE(ret != -ENOMEM);
 
-                       list_for_each_entry(pos, trace_probe_probe_list(tp), list) {
-                               ep = container_of(pos, struct trace_eprobe, tp);
+                       for_each_trace_eprobe_tp(ep, tp) {
                                disable_eprobe(ep, file->tr);
                                if (!--cnt)
                                        break;
@@ -699,7 +701,7 @@ static int enable_trace_eprobe(struct trace_event_call *call,
 static int disable_trace_eprobe(struct trace_event_call *call,
                                struct trace_event_file *file)
 {
-       struct trace_probe *pos, *tp;
+       struct trace_probe *tp;
        struct trace_eprobe *ep;
 
        tp = trace_probe_primary_from_call(call);
@@ -716,10 +718,8 @@ static int disable_trace_eprobe(struct trace_event_call *call,
                trace_probe_clear_flag(tp, TP_FLAG_PROFILE);
 
        if (!trace_probe_is_enabled(tp)) {
-               list_for_each_entry(pos, trace_probe_probe_list(tp), list) {
-                       ep = container_of(pos, struct trace_eprobe, tp);
+               for_each_trace_eprobe_tp(ep, tp)
                        disable_eprobe(ep, file->tr);
-               }
        }
 
  out:
@@ -807,13 +807,11 @@ static int trace_eprobe_tp_update_arg(struct trace_eprobe *ep, const char *argv[
        int ret;
 
        ret = traceprobe_parse_probe_arg(&ep->tp, i, argv[i], &ctx);
-       if (ret)
-               return ret;
-
        /* Handle symbols "@" */
        if (!ret)
                ret = traceprobe_update_arg(&ep->tp.args[i]);
 
+       traceprobe_finish_parse(&ctx);
        return ret;
 }
 
index dfe2e546acdcd6dbea9996d4dd48f6fd6d9d44ae..8bfe23af9c739a5042e93b2bba956a6e366de2b9 100644 (file)
@@ -898,6 +898,46 @@ static struct tracepoint *find_tracepoint(const char *tp_name)
        return data.tpoint;
 }
 
+static int parse_symbol_and_return(int argc, const char *argv[],
+                                  char **symbol, bool *is_return,
+                                  bool is_tracepoint)
+{
+       char *tmp = strchr(argv[1], '%');
+       int i;
+
+       if (tmp) {
+               int len = tmp - argv[1];
+
+               if (!is_tracepoint && !strcmp(tmp, "%return")) {
+                       *is_return = true;
+               } else {
+                       trace_probe_log_err(len, BAD_ADDR_SUFFIX);
+                       return -EINVAL;
+               }
+               *symbol = kmemdup_nul(argv[1], len, GFP_KERNEL);
+       } else
+               *symbol = kstrdup(argv[1], GFP_KERNEL);
+       if (!*symbol)
+               return -ENOMEM;
+
+       if (*is_return)
+               return 0;
+
+       /* If there is $retval, this should be a return fprobe. */
+       for (i = 2; i < argc; i++) {
+               tmp = strstr(argv[i], "$retval");
+               if (tmp && !isalnum(tmp[7]) && tmp[7] != '_') {
+                       *is_return = true;
+                       /*
+                        * NOTE: Don't check is_tracepoint here, because it will
+                        * be checked when the argument is parsed.
+                        */
+                       break;
+               }
+       }
+       return 0;
+}
+
 static int __trace_fprobe_create(int argc, const char *argv[])
 {
        /*
@@ -927,7 +967,7 @@ static int __trace_fprobe_create(int argc, const char *argv[])
        struct trace_fprobe *tf = NULL;
        int i, len, new_argc = 0, ret = 0;
        bool is_return = false;
-       char *symbol = NULL, *tmp = NULL;
+       char *symbol = NULL;
        const char *event = NULL, *group = FPROBE_EVENT_SYSTEM;
        const char **new_argv = NULL;
        int maxactive = 0;
@@ -983,20 +1023,10 @@ static int __trace_fprobe_create(int argc, const char *argv[])
        trace_probe_log_set_index(1);
 
        /* a symbol(or tracepoint) must be specified */
-       symbol = kstrdup(argv[1], GFP_KERNEL);
-       if (!symbol)
-               return -ENOMEM;
+       ret = parse_symbol_and_return(argc, argv, &symbol, &is_return, is_tracepoint);
+       if (ret < 0)
+               goto parse_error;
 
-       tmp = strchr(symbol, '%');
-       if (tmp) {
-               if (!is_tracepoint && !strcmp(tmp, "%return")) {
-                       *tmp = '\0';
-                       is_return = true;
-               } else {
-                       trace_probe_log_err(tmp - symbol, BAD_ADDR_SUFFIX);
-                       goto parse_error;
-               }
-       }
        if (!is_return && maxactive) {
                trace_probe_log_set_index(0);
                trace_probe_log_err(1, BAD_MAXACT_TYPE);
@@ -1096,6 +1126,7 @@ static int __trace_fprobe_create(int argc, const char *argv[])
        }
 
 out:
+       traceprobe_finish_parse(&ctx);
        trace_probe_log_clear();
        kfree(new_argv);
        kfree(symbol);
index 17c21c0b2dd1e0869f53a82e8c55a036ececcb79..3d7a180a84272e891458c5274daa2897a91f867c 100644 (file)
@@ -907,6 +907,7 @@ static int __trace_kprobe_create(int argc, const char *argv[])
        }
 
 out:
+       traceprobe_finish_parse(&ctx);
        trace_probe_log_clear();
        kfree(new_argv);
        kfree(symbol);
index c68a727078525ce7d867ae4e6eb2097a74eaf869..4dc74d73fc1df5af9ee297611865705d44259663 100644 (file)
@@ -12,6 +12,7 @@
 #define pr_fmt(fmt)    "trace_probe: " fmt
 
 #include <linux/bpf.h>
+#include "trace_btf.h"
 
 #include "trace_probe.h"
 
@@ -304,31 +305,90 @@ static int parse_trace_event_arg(char *arg, struct fetch_insn *code,
 
 #ifdef CONFIG_PROBE_EVENTS_BTF_ARGS
 
-static struct btf *traceprobe_get_btf(void)
+static u32 btf_type_int(const struct btf_type *t)
 {
-       struct btf *btf = bpf_get_btf_vmlinux();
+       return *(u32 *)(t + 1);
+}
 
-       if (IS_ERR_OR_NULL(btf))
-               return NULL;
+static bool btf_type_is_char_ptr(struct btf *btf, const struct btf_type *type)
+{
+       const struct btf_type *real_type;
+       u32 intdata;
+       s32 tid;
+
+       real_type = btf_type_skip_modifiers(btf, type->type, &tid);
+       if (!real_type)
+               return false;
+
+       if (BTF_INFO_KIND(real_type->info) != BTF_KIND_INT)
+               return false;
 
-       return btf;
+       intdata = btf_type_int(real_type);
+       return !(BTF_INT_ENCODING(intdata) & BTF_INT_SIGNED)
+               && BTF_INT_BITS(intdata) == 8;
 }
 
-static u32 btf_type_int(const struct btf_type *t)
+static bool btf_type_is_char_array(struct btf *btf, const struct btf_type *type)
 {
-       return *(u32 *)(t + 1);
+       const struct btf_type *real_type;
+       const struct btf_array *array;
+       u32 intdata;
+       s32 tid;
+
+       if (BTF_INFO_KIND(type->info) != BTF_KIND_ARRAY)
+               return false;
+
+       array = (const struct btf_array *)(type + 1);
+
+       real_type = btf_type_skip_modifiers(btf, array->type, &tid);
+
+       intdata = btf_type_int(real_type);
+       return !(BTF_INT_ENCODING(intdata) & BTF_INT_SIGNED)
+               && BTF_INT_BITS(intdata) == 8;
 }
 
-static const char *type_from_btf_id(struct btf *btf, s32 id)
+static int check_prepare_btf_string_fetch(char *typename,
+                               struct fetch_insn **pcode,
+                               struct traceprobe_parse_context *ctx)
+{
+       struct btf *btf = ctx->btf;
+
+       if (!btf || !ctx->last_type)
+               return 0;
+
+       /* char [] does not need any change. */
+       if (btf_type_is_char_array(btf, ctx->last_type))
+               return 0;
+
+       /* char * requires dereference the pointer. */
+       if (btf_type_is_char_ptr(btf, ctx->last_type)) {
+               struct fetch_insn *code = *pcode + 1;
+
+               if (code->op == FETCH_OP_END) {
+                       trace_probe_log_err(ctx->offset, TOO_MANY_OPS);
+                       return -E2BIG;
+               }
+               if (typename[0] == 'u')
+                       code->op = FETCH_OP_UDEREF;
+               else
+                       code->op = FETCH_OP_DEREF;
+               code->offset = 0;
+               *pcode = code;
+               return 0;
+       }
+       /* Other types are not available for string */
+       trace_probe_log_err(ctx->offset, BAD_TYPE4STR);
+       return -EINVAL;
+}
+
+static const char *fetch_type_from_btf_type(struct btf *btf,
+                                       const struct btf_type *type,
+                                       struct traceprobe_parse_context *ctx)
 {
-       const struct btf_type *t;
        u32 intdata;
-       s32 tid;
 
        /* TODO: const char * could be converted as a string */
-       t = btf_type_skip_modifiers(btf, id, &tid);
-
-       switch (BTF_INFO_KIND(t->info)) {
+       switch (BTF_INFO_KIND(type->info)) {
        case BTF_KIND_ENUM:
                /* enum is "int", so convert to "s32" */
                return "s32";
@@ -341,7 +401,7 @@ static const char *type_from_btf_id(struct btf *btf, s32 id)
                else
                        return "x32";
        case BTF_KIND_INT:
-               intdata = btf_type_int(t);
+               intdata = btf_type_int(type);
                if (BTF_INT_ENCODING(intdata) & BTF_INT_SIGNED) {
                        switch (BTF_INT_BITS(intdata)) {
                        case 8:
@@ -364,6 +424,10 @@ static const char *type_from_btf_id(struct btf *btf, s32 id)
                        case 64:
                                return "u64";
                        }
+                       /* bitfield, size is encoded in the type */
+                       ctx->last_bitsize = BTF_INT_BITS(intdata);
+                       ctx->last_bitoffs += BTF_INT_OFFSET(intdata);
+                       return "u64";
                }
        }
        /* TODO: support other types */
@@ -371,88 +435,223 @@ static const char *type_from_btf_id(struct btf *btf, s32 id)
        return NULL;
 }
 
-static const struct btf_type *find_btf_func_proto(const char *funcname)
+static int query_btf_context(struct traceprobe_parse_context *ctx)
 {
-       struct btf *btf = traceprobe_get_btf();
-       const struct btf_type *t;
-       s32 id;
+       const struct btf_param *param;
+       const struct btf_type *type;
+       struct btf *btf;
+       s32 nr;
 
-       if (!btf || !funcname)
-               return ERR_PTR(-EINVAL);
+       if (ctx->btf)
+               return 0;
+
+       if (!ctx->funcname)
+               return -EINVAL;
+
+       type = btf_find_func_proto(ctx->funcname, &btf);
+       if (!type)
+               return -ENOENT;
 
-       id = btf_find_by_name_kind(btf, funcname, BTF_KIND_FUNC);
-       if (id <= 0)
-               return ERR_PTR(-ENOENT);
+       ctx->btf = btf;
+       ctx->proto = type;
+
+       /* ctx->params is optional, since func(void) will not have params. */
+       nr = 0;
+       param = btf_get_func_param(type, &nr);
+       if (!IS_ERR_OR_NULL(param)) {
+               /* Hide the first 'data' argument of tracepoint */
+               if (ctx->flags & TPARG_FL_TPOINT) {
+                       nr--;
+                       param++;
+               }
+       }
 
-       /* Get BTF_KIND_FUNC type */
-       t = btf_type_by_id(btf, id);
-       if (!t || !btf_type_is_func(t))
-               return ERR_PTR(-ENOENT);
+       if (nr > 0) {
+               ctx->nr_params = nr;
+               ctx->params = param;
+       } else {
+               ctx->nr_params = 0;
+               ctx->params = NULL;
+       }
 
-       /* The type of BTF_KIND_FUNC is BTF_KIND_FUNC_PROTO */
-       t = btf_type_by_id(btf, t->type);
-       if (!t || !btf_type_is_func_proto(t))
-               return ERR_PTR(-ENOENT);
+       return 0;
+}
 
-       return t;
+static void clear_btf_context(struct traceprobe_parse_context *ctx)
+{
+       if (ctx->btf) {
+               btf_put(ctx->btf);
+               ctx->btf = NULL;
+               ctx->proto = NULL;
+               ctx->params = NULL;
+               ctx->nr_params = 0;
+       }
 }
 
-static const struct btf_param *find_btf_func_param(const char *funcname, s32 *nr,
-                                                  bool tracepoint)
+/* Return 1 if the field separater is arrow operator ('->') */
+static int split_next_field(char *varname, char **next_field,
+                           struct traceprobe_parse_context *ctx)
 {
-       const struct btf_param *param;
-       const struct btf_type *t;
+       char *field;
+       int ret = 0;
+
+       field = strpbrk(varname, ".-");
+       if (field) {
+               if (field[0] == '-' && field[1] == '>') {
+                       field[0] = '\0';
+                       field += 2;
+                       ret = 1;
+               } else if (field[0] == '.') {
+                       field[0] = '\0';
+                       field += 1;
+               } else {
+                       trace_probe_log_err(ctx->offset + field - varname, BAD_HYPHEN);
+                       return -EINVAL;
+               }
+               *next_field = field;
+       }
 
-       if (!funcname || !nr)
-               return ERR_PTR(-EINVAL);
+       return ret;
+}
 
-       t = find_btf_func_proto(funcname);
-       if (IS_ERR(t))
-               return (const struct btf_param *)t;
+/*
+ * Parse the field of data structure. The @type must be a pointer type
+ * pointing the target data structure type.
+ */
+static int parse_btf_field(char *fieldname, const struct btf_type *type,
+                          struct fetch_insn **pcode, struct fetch_insn *end,
+                          struct traceprobe_parse_context *ctx)
+{
+       struct fetch_insn *code = *pcode;
+       const struct btf_member *field;
+       u32 bitoffs, anon_offs;
+       char *next;
+       int is_ptr;
+       s32 tid;
 
-       *nr = btf_type_vlen(t);
-       param = (const struct btf_param *)(t + 1);
+       do {
+               /* Outer loop for solving arrow operator ('->') */
+               if (BTF_INFO_KIND(type->info) != BTF_KIND_PTR) {
+                       trace_probe_log_err(ctx->offset, NO_PTR_STRCT);
+                       return -EINVAL;
+               }
+               /* Convert a struct pointer type to a struct type */
+               type = btf_type_skip_modifiers(ctx->btf, type->type, &tid);
+               if (!type) {
+                       trace_probe_log_err(ctx->offset, BAD_BTF_TID);
+                       return -EINVAL;
+               }
 
-       /* Hide the first 'data' argument of tracepoint */
-       if (tracepoint) {
-               (*nr)--;
-               param++;
-       }
+               bitoffs = 0;
+               do {
+                       /* Inner loop for solving dot operator ('.') */
+                       next = NULL;
+                       is_ptr = split_next_field(fieldname, &next, ctx);
+                       if (is_ptr < 0)
+                               return is_ptr;
+
+                       anon_offs = 0;
+                       field = btf_find_struct_member(ctx->btf, type, fieldname,
+                                                      &anon_offs);
+                       if (!field) {
+                               trace_probe_log_err(ctx->offset, NO_BTF_FIELD);
+                               return -ENOENT;
+                       }
+                       /* Add anonymous structure/union offset */
+                       bitoffs += anon_offs;
+
+                       /* Accumulate the bit-offsets of the dot-connected fields */
+                       if (btf_type_kflag(type)) {
+                               bitoffs += BTF_MEMBER_BIT_OFFSET(field->offset);
+                               ctx->last_bitsize = BTF_MEMBER_BITFIELD_SIZE(field->offset);
+                       } else {
+                               bitoffs += field->offset;
+                               ctx->last_bitsize = 0;
+                       }
 
-       if (*nr > 0)
-               return param;
-       else
-               return NULL;
+                       type = btf_type_skip_modifiers(ctx->btf, field->type, &tid);
+                       if (!type) {
+                               trace_probe_log_err(ctx->offset, BAD_BTF_TID);
+                               return -EINVAL;
+                       }
+
+                       ctx->offset += next - fieldname;
+                       fieldname = next;
+               } while (!is_ptr && fieldname);
+
+               if (++code == end) {
+                       trace_probe_log_err(ctx->offset, TOO_MANY_OPS);
+                       return -EINVAL;
+               }
+               code->op = FETCH_OP_DEREF;      /* TODO: user deref support */
+               code->offset = bitoffs / 8;
+               *pcode = code;
+
+               ctx->last_bitoffs = bitoffs % 8;
+               ctx->last_type = type;
+       } while (fieldname);
+
+       return 0;
 }
 
-static int parse_btf_arg(const char *varname, struct fetch_insn *code,
+static int parse_btf_arg(char *varname,
+                        struct fetch_insn **pcode, struct fetch_insn *end,
                         struct traceprobe_parse_context *ctx)
 {
-       struct btf *btf = traceprobe_get_btf();
+       struct fetch_insn *code = *pcode;
        const struct btf_param *params;
-       int i;
+       const struct btf_type *type;
+       char *field = NULL;
+       int i, is_ptr, ret;
+       u32 tid;
+
+       if (WARN_ON_ONCE(!ctx->funcname))
+               return -EINVAL;
 
-       if (!btf) {
-               trace_probe_log_err(ctx->offset, NOSUP_BTFARG);
+       is_ptr = split_next_field(varname, &field, ctx);
+       if (is_ptr < 0)
+               return is_ptr;
+       if (!is_ptr && field) {
+               /* dot-connected field on an argument is not supported. */
+               trace_probe_log_err(ctx->offset + field - varname,
+                                   NOSUP_DAT_ARG);
                return -EOPNOTSUPP;
        }
 
-       if (WARN_ON_ONCE(!ctx->funcname))
-               return -EINVAL;
+       if (ctx->flags & TPARG_FL_RETURN) {
+               if (strcmp(varname, "$retval") != 0) {
+                       trace_probe_log_err(ctx->offset, NO_BTFARG);
+                       return -ENOENT;
+               }
+               code->op = FETCH_OP_RETVAL;
+               /* Check whether the function return type is not void */
+               if (query_btf_context(ctx) == 0) {
+                       if (ctx->proto->type == 0) {
+                               trace_probe_log_err(ctx->offset, NO_RETVAL);
+                               return -ENOENT;
+                       }
+                       tid = ctx->proto->type;
+                       goto found;
+               }
+               if (field) {
+                       trace_probe_log_err(ctx->offset + field - varname,
+                                           NO_BTF_ENTRY);
+                       return -ENOENT;
+               }
+               return 0;
+       }
 
-       if (!ctx->params) {
-               params = find_btf_func_param(ctx->funcname, &ctx->nr_params,
-                                            ctx->flags & TPARG_FL_TPOINT);
-               if (IS_ERR_OR_NULL(params)) {
+       if (!ctx->btf) {
+               ret = query_btf_context(ctx);
+               if (ret < 0 || ctx->nr_params == 0) {
                        trace_probe_log_err(ctx->offset, NO_BTF_ENTRY);
                        return PTR_ERR(params);
                }
-               ctx->params = params;
-       } else
-               params = ctx->params;
+       }
+       params = ctx->params;
 
        for (i = 0; i < ctx->nr_params; i++) {
-               const char *name = btf_name_by_offset(btf, params[i].name_off);
+               const char *name = btf_name_by_offset(ctx->btf, params[i].name_off);
 
                if (name && !strcmp(name, varname)) {
                        code->op = FETCH_OP_ARG;
@@ -460,91 +659,114 @@ static int parse_btf_arg(const char *varname, struct fetch_insn *code,
                                code->param = i + 1;
                        else
                                code->param = i;
-                       return 0;
+                       tid = params[i].type;
+                       goto found;
                }
        }
        trace_probe_log_err(ctx->offset, NO_BTFARG);
        return -ENOENT;
-}
-
-static const struct fetch_type *parse_btf_arg_type(int arg_idx,
-                                       struct traceprobe_parse_context *ctx)
-{
-       struct btf *btf = traceprobe_get_btf();
-       const char *typestr = NULL;
 
-       if (btf && ctx->params) {
-               if (ctx->flags & TPARG_FL_TPOINT)
-                       arg_idx--;
-               typestr = type_from_btf_id(btf, ctx->params[arg_idx].type);
+found:
+       type = btf_type_skip_modifiers(ctx->btf, tid, &tid);
+       if (!type) {
+               trace_probe_log_err(ctx->offset, BAD_BTF_TID);
+               return -EINVAL;
        }
-
-       return find_fetch_type(typestr, ctx->flags);
+       /* Initialize the last type information */
+       ctx->last_type = type;
+       ctx->last_bitoffs = 0;
+       ctx->last_bitsize = 0;
+       if (field) {
+               ctx->offset += field - varname;
+               return parse_btf_field(field, type, pcode, end, ctx);
+       }
+       return 0;
 }
 
-static const struct fetch_type *parse_btf_retval_type(
+static const struct fetch_type *find_fetch_type_from_btf_type(
                                        struct traceprobe_parse_context *ctx)
 {
-       struct btf *btf = traceprobe_get_btf();
+       struct btf *btf = ctx->btf;
        const char *typestr = NULL;
-       const struct btf_type *t;
 
-       if (btf && ctx->funcname) {
-               t = find_btf_func_proto(ctx->funcname);
-               if (!IS_ERR(t))
-                       typestr = type_from_btf_id(btf, t->type);
-       }
+       if (btf && ctx->last_type)
+               typestr = fetch_type_from_btf_type(btf, ctx->last_type, ctx);
 
        return find_fetch_type(typestr, ctx->flags);
 }
 
-static bool is_btf_retval_void(const char *funcname)
+static int parse_btf_bitfield(struct fetch_insn **pcode,
+                             struct traceprobe_parse_context *ctx)
 {
-       const struct btf_type *t;
+       struct fetch_insn *code = *pcode;
 
-       t = find_btf_func_proto(funcname);
-       if (IS_ERR(t))
-               return false;
+       if ((ctx->last_bitsize % 8 == 0) && ctx->last_bitoffs == 0)
+               return 0;
+
+       code++;
+       if (code->op != FETCH_OP_NOP) {
+               trace_probe_log_err(ctx->offset, TOO_MANY_OPS);
+               return -EINVAL;
+       }
+       *pcode = code;
 
-       return t->type == 0;
+       code->op = FETCH_OP_MOD_BF;
+       code->lshift = 64 - (ctx->last_bitsize + ctx->last_bitoffs);
+       code->rshift = 64 - ctx->last_bitsize;
+       code->basesize = 64 / 8;
+       return 0;
 }
+
 #else
-static struct btf *traceprobe_get_btf(void)
+static void clear_btf_context(struct traceprobe_parse_context *ctx)
 {
-       return NULL;
+       ctx->btf = NULL;
 }
 
-static const struct btf_param *find_btf_func_param(const char *funcname, s32 *nr,
-                                                  bool tracepoint)
+static int query_btf_context(struct traceprobe_parse_context *ctx)
 {
-       return ERR_PTR(-EOPNOTSUPP);
+       return -EOPNOTSUPP;
 }
 
-static int parse_btf_arg(const char *varname, struct fetch_insn *code,
+static int parse_btf_arg(char *varname,
+                        struct fetch_insn **pcode, struct fetch_insn *end,
                         struct traceprobe_parse_context *ctx)
 {
        trace_probe_log_err(ctx->offset, NOSUP_BTFARG);
        return -EOPNOTSUPP;
 }
 
-#define parse_btf_arg_type(idx, ctx)           \
-       find_fetch_type(NULL, ctx->flags)
+static int parse_btf_bitfield(struct fetch_insn **pcode,
+                             struct traceprobe_parse_context *ctx)
+{
+       trace_probe_log_err(ctx->offset, NOSUP_BTFARG);
+       return -EOPNOTSUPP;
+}
 
-#define parse_btf_retval_type(ctx)             \
+#define find_fetch_type_from_btf_type(ctx)             \
        find_fetch_type(NULL, ctx->flags)
 
-#define is_btf_retval_void(funcname)   (false)
+static int check_prepare_btf_string_fetch(char *typename,
+                               struct fetch_insn **pcode,
+                               struct traceprobe_parse_context *ctx)
+{
+       return 0;
+}
 
 #endif
 
 #define PARAM_MAX_STACK (THREAD_SIZE / sizeof(unsigned long))
 
-static int parse_probe_vars(char *arg, const struct fetch_type *t,
-                           struct fetch_insn *code,
+/* Parse $vars. @orig_arg points '$', which syncs to @ctx->offset */
+static int parse_probe_vars(char *orig_arg, const struct fetch_type *t,
+                           struct fetch_insn **pcode,
+                           struct fetch_insn *end,
                            struct traceprobe_parse_context *ctx)
 {
-       unsigned long param;
+       struct fetch_insn *code = *pcode;
        int err = TP_ERR_BAD_VAR;
+       char *arg = orig_arg + 1;
+       unsigned long param;
        int ret = 0;
        int len;
 
@@ -563,18 +785,17 @@ static int parse_probe_vars(char *arg, const struct fetch_type *t,
                goto inval;
        }
 
-       if (strcmp(arg, "retval") == 0) {
-               if (ctx->flags & TPARG_FL_RETURN) {
-                       if ((ctx->flags & TPARG_FL_KERNEL) &&
-                           is_btf_retval_void(ctx->funcname)) {
-                               err = TP_ERR_NO_RETVAL;
-                               goto inval;
-                       }
+       if (str_has_prefix(arg, "retval")) {
+               if (!(ctx->flags & TPARG_FL_RETURN)) {
+                       err = TP_ERR_RETVAL_ON_PROBE;
+                       goto inval;
+               }
+               if (!(ctx->flags & TPARG_FL_KERNEL) ||
+                   !IS_ENABLED(CONFIG_PROBE_EVENTS_BTF_ARGS)) {
                        code->op = FETCH_OP_RETVAL;
                        return 0;
                }
-               err = TP_ERR_RETVAL_ON_PROBE;
-               goto inval;
+               return parse_btf_arg(orig_arg, pcode, end, ctx);
        }
 
        len = str_has_prefix(arg, "stack");
@@ -676,7 +897,7 @@ parse_probe_arg(char *arg, const struct fetch_type *type,
 
        switch (arg[0]) {
        case '$':
-               ret = parse_probe_vars(arg + 1, type, code, ctx);
+               ret = parse_probe_vars(arg, type, pcode, end, ctx);
                break;
 
        case '%':       /* named register */
@@ -795,6 +1016,8 @@ parse_probe_arg(char *arg, const struct fetch_type *type,
 
                        code->op = deref;
                        code->offset = offset;
+                       /* Reset the last type if used */
+                       ctx->last_type = NULL;
                }
                break;
        case '\\':      /* Immediate value */
@@ -818,7 +1041,7 @@ parse_probe_arg(char *arg, const struct fetch_type *type,
                                trace_probe_log_err(ctx->offset, NOSUP_BTFARG);
                                return -EINVAL;
                        }
-                       ret = parse_btf_arg(arg, code, ctx);
+                       ret = parse_btf_arg(arg, pcode, end, ctx);
                        break;
                }
        }
@@ -964,17 +1187,22 @@ static int traceprobe_parse_probe_arg_body(const char *argv, ssize_t *size,
                goto out;
        code[FETCH_INSN_MAX - 1].op = FETCH_OP_END;
 
+       ctx->last_type = NULL;
        ret = parse_probe_arg(arg, parg->type, &code, &code[FETCH_INSN_MAX - 1],
                              ctx);
        if (ret)
                goto fail;
 
        /* Update storing type if BTF is available */
-       if (IS_ENABLED(CONFIG_PROBE_EVENTS_BTF_ARGS) && !t) {
-               if (code->op == FETCH_OP_ARG)
-                       parg->type = parse_btf_arg_type(code->param, ctx);
-               else if (code->op == FETCH_OP_RETVAL)
-                       parg->type = parse_btf_retval_type(ctx);
+       if (IS_ENABLED(CONFIG_PROBE_EVENTS_BTF_ARGS) &&
+           ctx->last_type) {
+               if (!t) {
+                       parg->type = find_fetch_type_from_btf_type(ctx);
+               } else if (strstr(t, "string")) {
+                       ret = check_prepare_btf_string_fetch(t, &code, ctx);
+                       if (ret)
+                               goto fail;
+               }
        }
 
        ret = -EINVAL;
@@ -1048,6 +1276,11 @@ static int traceprobe_parse_probe_arg_body(const char *argv, ssize_t *size,
                        trace_probe_log_err(ctx->offset + t - arg, BAD_BITFIELD);
                        goto fail;
                }
+       } else if (IS_ENABLED(CONFIG_PROBE_EVENTS_BTF_ARGS) &&
+                  ctx->last_type) {
+               ret = parse_btf_bitfield(&code, ctx);
+               if (ret)
+                       goto fail;
        }
        ret = -EINVAL;
        /* Loop(Array) operation */
@@ -1231,7 +1464,6 @@ static int sprint_nth_btf_arg(int idx, const char *type,
                              char *buf, int bufsize,
                              struct traceprobe_parse_context *ctx)
 {
-       struct btf *btf = traceprobe_get_btf();
        const char *name;
        int ret;
 
@@ -1239,7 +1471,7 @@ static int sprint_nth_btf_arg(int idx, const char *type,
                trace_probe_log_err(0, NO_BTFARG);
                return -ENOENT;
        }
-       name = btf_name_by_offset(btf, ctx->params[idx].name_off);
+       name = btf_name_by_offset(ctx->btf, ctx->params[idx].name_off);
        if (!name) {
                trace_probe_log_err(0, NO_BTF_ENTRY);
                return -ENOENT;
@@ -1260,7 +1492,6 @@ const char **traceprobe_expand_meta_args(int argc, const char *argv[],
        const struct btf_param *params = NULL;
        int i, j, n, used, ret, args_idx = -1;
        const char **new_argv = NULL;
-       int nr_params;
 
        ret = argv_has_var_arg(argc, argv, &args_idx, ctx);
        if (ret < 0)
@@ -1271,9 +1502,8 @@ const char **traceprobe_expand_meta_args(int argc, const char *argv[],
                return NULL;
        }
 
-       params = find_btf_func_param(ctx->funcname, &nr_params,
-                                    ctx->flags & TPARG_FL_TPOINT);
-       if (IS_ERR_OR_NULL(params)) {
+       ret = query_btf_context(ctx);
+       if (ret < 0 || ctx->nr_params == 0) {
                if (args_idx != -1) {
                        /* $arg* requires BTF info */
                        trace_probe_log_err(0, NOSUP_BTFARG);
@@ -1282,8 +1512,6 @@ const char **traceprobe_expand_meta_args(int argc, const char *argv[],
                *new_argc = argc;
                return NULL;
        }
-       ctx->params = params;
-       ctx->nr_params = nr_params;
 
        if (args_idx >= 0)
                *new_argc = argc + ctx->nr_params - 1;
@@ -1298,7 +1526,7 @@ const char **traceprobe_expand_meta_args(int argc, const char *argv[],
        for (i = 0, j = 0; i < argc; i++) {
                trace_probe_log_set_index(i + 2);
                if (i == args_idx) {
-                       for (n = 0; n < nr_params; n++) {
+                       for (n = 0; n < ctx->nr_params; n++) {
                                ret = sprint_nth_btf_arg(n, "", buf + used,
                                                         bufsize - used, ctx);
                                if (ret < 0)
@@ -1337,6 +1565,11 @@ error:
        return ERR_PTR(ret);
 }
 
+void traceprobe_finish_parse(struct traceprobe_parse_context *ctx)
+{
+       clear_btf_context(ctx);
+}
+
 int traceprobe_update_arg(struct probe_arg *arg)
 {
        struct fetch_insn *code = arg->code;
index 7dde806be91ef5f60480cde8b51f3e8d4b6dd1f1..02b432ae7513137922d90a73621dad3071ec0fb5 100644 (file)
@@ -383,9 +383,15 @@ static inline bool tparg_is_function_entry(unsigned int flags)
 
 struct traceprobe_parse_context {
        struct trace_event_call *event;
-       const struct btf_param *params;
-       s32 nr_params;
-       const char *funcname;
+       /* BTF related parameters */
+       const char *funcname;           /* Function name in BTF */
+       const struct btf_type  *proto;  /* Prototype of the function */
+       const struct btf_param *params; /* Parameter of the function */
+       s32 nr_params;                  /* The number of the parameters */
+       struct btf *btf;                /* The BTF to be used */
+       const struct btf_type *last_type;       /* Saved type */
+       u32 last_bitoffs;               /* Saved bitoffs */
+       u32 last_bitsize;               /* Saved bitsize */
        unsigned int flags;
        int offset;
 };
@@ -400,6 +406,12 @@ const char **traceprobe_expand_meta_args(int argc, const char *argv[],
 extern int traceprobe_update_arg(struct probe_arg *arg);
 extern void traceprobe_free_probe_arg(struct probe_arg *arg);
 
+/*
+ * If either traceprobe_parse_probe_arg() or traceprobe_expand_meta_args() is called,
+ * this MUST be called for clean up the context and return a resource.
+ */
+void traceprobe_finish_parse(struct traceprobe_parse_context *ctx);
+
 extern int traceprobe_split_symbol_offset(char *symbol, long *offset);
 int traceprobe_parse_event_name(const char **pevent, const char **pgroup,
                                char *buf, int offset);
@@ -495,7 +507,14 @@ extern int traceprobe_define_arg_fields(struct trace_event_call *event_call,
        C(BAD_VAR_ARGS,         "$arg* must be an independent parameter without name etc."),\
        C(NOFENTRY_ARGS,        "$arg* can be used only on function entry"),    \
        C(DOUBLE_ARGS,          "$arg* can be used only once in the parameters"),       \
-       C(ARGS_2LONG,           "$arg* failed because the argument list is too long"),
+       C(ARGS_2LONG,           "$arg* failed because the argument list is too long"),  \
+       C(ARGIDX_2BIG,          "$argN index is too big"),              \
+       C(NO_PTR_STRCT,         "This is not a pointer to union/structure."),   \
+       C(NOSUP_DAT_ARG,        "Non pointer structure/union argument is not supported."),\
+       C(BAD_HYPHEN,           "Failed to parse single hyphen. Forgot '>'?"),  \
+       C(NO_BTF_FIELD,         "This field is not found."),    \
+       C(BAD_BTF_TID,          "Failed to get BTF type info."),\
+       C(BAD_TYPE4STR,         "This type does not fit for string."),
 
 #undef C
 #define C(a, b)                TP_ERR_##a
index 576b3bcb8ebd332ac1e1edf9ab21b972c616e99c..99c051de412afa4d6a433d1250a7abbd0d2deb3f 100644 (file)
@@ -688,6 +688,7 @@ static int __trace_uprobe_create(int argc, const char **argv)
 
                trace_probe_log_set_index(i + 2);
                ret = traceprobe_parse_probe_arg(&tu->tp, i, argv[i], &ctx);
+               traceprobe_finish_parse(&ctx);
                if (ret)
                        goto error;
        }
index f34b14ef9781f9b30c7bcda3da3af1ab088b3d61..b9c21a81d2481b6f34e14bc42cb1e933884e561a 100644 (file)
@@ -5,6 +5,7 @@
 
 KPROBES=
 FPROBES=
+FIELDS=
 
 if grep -qF "p[:[<group>/][<event>]] <place> [<args>]" README ; then
   KPROBES=yes
@@ -12,6 +13,9 @@ fi
 if grep -qF "f[:[<group>/][<event>]] <func-name>[%return] [<args>]" README ; then
   FPROBES=yes
 fi
+if grep -qF "<argname>[->field[->field|.field...]]" README ; then
+  FIELDS=yes
+fi
 
 if [ -z "$KPROBES" -a -z "$FPROBES" ] ; then
   exit_unsupported
@@ -21,6 +25,9 @@ echo 0 > events/enable
 echo > dynamic_events
 
 TP=kfree
+TP2=kmem_cache_alloc
+TP3=getname_flags
+TP4=sched_wakeup
 
 if [ "$FPROBES" ] ; then
 echo "f:fpevent $TP object" >> dynamic_events
@@ -33,6 +40,7 @@ echo > dynamic_events
 
 echo "f:fpevent $TP "'$arg1' >> dynamic_events
 grep -q "fpevent.*object=object" dynamic_events
+
 echo > dynamic_events
 
 echo "f:fpevent $TP "'$arg*' >> dynamic_events
@@ -45,6 +53,18 @@ fi
 
 echo > dynamic_events
 
+if [ "$FIELDS" ] ; then
+echo "t:tpevent ${TP2} obj_size=s->object_size" >> dynamic_events
+echo "f:fpevent ${TP3}%return path=\$retval->name:string" >> dynamic_events
+echo "t:tpevent2 ${TP4} p->se.group_node.next->prev" >> dynamic_events
+
+grep -q "tpevent .*obj_size=s->object_size" dynamic_events
+grep -q "fpevent.*path=\$retval->name:string" dynamic_events
+grep -q 'tpevent2 .*p->se.group_node.next->prev' dynamic_events
+
+echo > dynamic_events
+fi
+
 if [ "$KPROBES" ] ; then
 echo "p:kpevent $TP object" >> dynamic_events
 grep -q "kpevent.*object=object" dynamic_events
index 812f5b3f60550fa3dc427fbb474473e80776e67f..20e42c030095b0e13c17fe889ec714512e0ae87e 100644 (file)
@@ -30,11 +30,11 @@ check_error 'f:^ vfs_read'          # NO_EVENT_NAME
 check_error 'f:foo/^12345678901234567890123456789012345678901234567890123456789012345 vfs_read'        # EVENT_TOO_LONG
 check_error 'f:foo/^bar.1 vfs_read'    # BAD_EVENT_NAME
 
-check_error 'f vfs_read ^$retval'      # RETVAL_ON_PROBE
 check_error 'f vfs_read ^$stack10000'  # BAD_STACK_NUM
 
 check_error 'f vfs_read ^$arg10000'    # BAD_ARG_NUM
 
+check_error 'f vfs_read $retval ^$arg1' # BAD_VAR
 check_error 'f vfs_read ^$none_var'    # BAD_VAR
 check_error 'f vfs_read ^'$REG         # BAD_VAR
 
@@ -103,6 +103,14 @@ check_error 'f vfs_read%return ^$arg*'             # NOFENTRY_ARGS
 check_error 'f vfs_read ^hoge'                 # NO_BTFARG
 check_error 'f kfree ^$arg10'                  # NO_BTFARG (exceed the number of parameters)
 check_error 'f kfree%return ^$retval'          # NO_RETVAL
+
+if grep -qF "<argname>[->field[->field|.field...]]" README ; then
+check_error 'f vfs_read%return $retval->^foo'  # NO_PTR_STRCT
+check_error 'f vfs_read file->^foo'            # NO_BTF_FIELD
+check_error 'f vfs_read file^-.foo'            # BAD_HYPHEN
+check_error 'f vfs_read ^file:string'          # BAD_TYPE4STR
+fi
+
 else
 check_error 'f vfs_read ^$arg*'                        # NOSUP_BTFARG
 check_error 't kfree ^$arg*'                   # NOSUP_BTFARG