From: Masami Hiramatsu (Google) <[email protected]>

Add a field specifier option for the typecast. This works like
container_of() macro.

    (STRUCT[,FIELD[.FIELD2...]])VAR

This is equivalent to :

    container_of(VAR, struct STRUCT, FIELD[.FIELD2...])

For example:

 echo "f tick_nohz_handler next_tick=(tick_sched,sched_timer)timer->next_tick" 
>> dynamic_events

This will trace tick_nohz_handler() with its tick_sched::next_tick which
is converted from @timer by contianer_of(tick, struct tick_sched, sched_timer).
So, if you enabkle both fprobes:tick_nohz_handler__entry and
timer:hrtimer_expire_entry events, we will see something like:


          <idle>-0       [002] d.h1.  3778.087272: hrtimer_expire_entry: 
hrtimer=00000000d63db328 f
unction=tick_nohz_handler now=3777450051040
          <idle>-0       [002] d.h1.  3778.087281: tick_nohz_handler__entry: 
(tick_nohz_handler+0x4
/0x140) next_tick=3777450000000


Signed-off-by: Masami Hiramatsu (Google) <[email protected]>
---
 Changes in v2:
  - Use byteoffset for typecast field offset instead of bitoffset. This fixes 
negative modulo calculation.
  - Check whether a field is specified after typecast.
  - Reject if typecast field option  has arrow operator.
---
 Documentation/trace/eprobetrace.rst |    5 +
 Documentation/trace/fprobetrace.rst |    8 +-
 Documentation/trace/kprobetrace.rst |    8 +-
 kernel/trace/trace.c                |    4 -
 kernel/trace/trace_probe.c          |  178 ++++++++++++++++++++++++-----------
 kernel/trace/trace_probe.h          |    5 +
 6 files changed, 141 insertions(+), 67 deletions(-)

diff --git a/Documentation/trace/eprobetrace.rst 
b/Documentation/trace/eprobetrace.rst
index cd0b4aa7f896..680e0af43d5d 100644
--- a/Documentation/trace/eprobetrace.rst
+++ b/Documentation/trace/eprobetrace.rst
@@ -49,7 +49,10 @@ Synopsis of eprobe_events
   (STRUCT)FIELD->MEMBER[->MEMBER] : If BTF is supported, typecast FIELD to
                   a pointer to STRUCT and then derference the pointer defined 
by
                   ->MEMBER. Note that when this is used, the FIELD name does 
not
-                  need to be prefixed with a '$'.
+                  need to be prefixed with a '$'. ASGN can be specified 
optionally.
+                 If ASGN is specified, FIELD will be cast to the same offset
+                 position as the ASGN member, rather than to the beginning of
+                 the STRUCT.
   (STRUCT)(FETCHARG)->MEMBER[->MEMBER] : typecast can nest, so the above can
                  also be used with another FETCHARG instead of FIELD.
 
diff --git a/Documentation/trace/fprobetrace.rst 
b/Documentation/trace/fprobetrace.rst
index 6b8bb27bb62d..290a9e6f7491 100644
--- a/Documentation/trace/fprobetrace.rst
+++ b/Documentation/trace/fprobetrace.rst
@@ -57,10 +57,12 @@ Synopsis of fprobe-events
                   (u8/u16/u32/u64/s8/s16/s32/s64), hexadecimal types
                   (x8/x16/x32/x64), "char", "string", "ustring", "symbol", 
"symstr"
                   and bitfield are supported.
-  (STRUCT)FIELD->MEMBER[->MEMBER] : If BTF is supported, typecast FIELD to
+  (STRUCT[,ASGN])FIELD->MEMBER[->MEMBER] : If BTF is supported, typecast FIELD 
to
                   a pointer to STRUCT and then derference the pointer defined 
by
-                  ->MEMBER.
-  (STRUCT)(FETCHARG)->MEMBER[->MEMBER] : typecast can nest, so the above can
+                  ->MEMBER. ASGN can be specified optionally. If ASGN is 
specified,
+                 FIELD will be cast to the same offset position as the ASGN 
member,
+                 rather than to the beginning of the STRUCT.
+  (STRUCT[,ASGN])(FETCHARG)->MEMBER[->MEMBER] : typecast can nest, so the 
above can
                  also be used with another FETCHARG instead of FIELD.
 
   (\*1) This is available only when BTF is enabled.
diff --git a/Documentation/trace/kprobetrace.rst 
b/Documentation/trace/kprobetrace.rst
index c4382765d5b2..a62707e6a9f2 100644
--- a/Documentation/trace/kprobetrace.rst
+++ b/Documentation/trace/kprobetrace.rst
@@ -61,11 +61,13 @@ Synopsis of kprobe_events
                  (x8/x16/x32/x64), VFS layer common type(%pd/%pD), "char",
                   "string", "ustring", "symbol", "symstr" and bitfield are
                   supported.
-  (STRUCT)FIELD->MEMBER[->MEMBER] : If BTF is supported, typecast FIELD to
+  (STRUCT[,ASGN])FIELD->MEMBER[->MEMBER] : If BTF is supported, typecast FIELD 
to
                   a pointer to STRUCT and then derference the pointer defined 
by
                   ->MEMBER. Note that this is available only when the probe is
-                  on function entry.
-  (STRUCT)(FETCHARG)->MEMBER[->MEMBER] : typecast can nest, so the above can
+                  on function entry. ASGN can be specified optionally. If ASGN
+                  is specified, FIELD will be cast to the same offset position
+                  as the ASGN member, rather than to the beginning of the 
STRUCT.
+  (STRUCT[,ASGN])(FETCHARG)->MEMBER[->MEMBER] : typecast can nest, so the 
above can
                  also be used with another FETCHARG instead of FIELD.
 
   (\*1) only for the probe on function entry (offs == 0). Note, this argument 
access
diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c
index 4f70318918c2..0e36af853199 100644
--- a/kernel/trace/trace.c
+++ b/kernel/trace/trace.c
@@ -4325,8 +4325,8 @@ static const char readme_msg[] =
 #ifdef CONFIG_HAVE_FUNCTION_ARG_ACCESS_API
        "\t           $stack<index>, $stack, $retval, $comm, $arg<N>,\n"
 #ifdef CONFIG_PROBE_EVENTS_BTF_ARGS
-       "\t           [(structname)]<argname>[->field[->field|.field...]],\n"
-       "\t           [(structname)](fetcharg)->field[->field|.field...],\n"
+       "\t           
[(structname[,field])]<argname>[->field[->field|.field...]],\n"
+       "\t           
[(structname[,field])](fetcharg)->field[->field|.field...],\n"
 #endif
 #else
        "\t           $stack<index>, $stack, $retval, $comm,\n"
diff --git a/kernel/trace/trace_probe.c b/kernel/trace/trace_probe.c
index dba73aaa8ade..726be9782775 100644
--- a/kernel/trace/trace_probe.c
+++ b/kernel/trace/trace_probe.c
@@ -574,6 +574,65 @@ static int split_next_field(char *varname, char 
**next_field,
        return ret;
 }
 
+/* Inner loop for solving dot operator ('.'). Return bit-offset of the given 
field */
+static int get_bitoffset_of_field(char **pfieldname, const struct btf_type 
**ptype,
+                                 struct traceprobe_parse_context *ctx)
+{
+       const struct btf_type *type = *ptype;
+       const struct btf_member *field;
+       struct btf *btf = ctx_btf(ctx);
+       char *fieldname = *pfieldname;
+       int bitoffs = 0;
+       u32 anon_offs;
+       char *next;
+       int is_ptr;
+       s32 tid;
+
+       do {
+               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(btf, type, fieldname,
+                                               &anon_offs);
+               if (IS_ERR(field)) {
+                       trace_probe_log_err(ctx->offset, BAD_BTF_TID);
+                       return PTR_ERR(field);
+               }
+               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;
+               }
+
+               type = btf_type_skip_modifiers(btf, field->type, &tid);
+               if (!type) {
+                       trace_probe_log_err(ctx->offset, BAD_BTF_TID);
+                       return -EINVAL;
+               }
+
+               if (next)
+                       ctx->offset += next - fieldname;
+               fieldname = next;
+       } while (!is_ptr && fieldname);
+
+       *pfieldname = fieldname;
+       *ptype = type;
+
+       return bitoffs;
+}
 /*
  * Parse the field of data structure. The @type must be a pointer type
  * pointing the target data structure type.
@@ -583,16 +642,14 @@ static int parse_btf_field(char *fieldname, const struct 
btf_type *type,
                           struct traceprobe_parse_context *ctx)
 {
        struct fetch_insn *code = *pcode;
-       const struct btf_member *field;
-       u32 bitoffs, anon_offs;
-       bool is_struct = ctx->struct_btf != NULL;
        struct btf *btf = ctx_btf(ctx);
-       char *next;
-       int is_ptr;
+       bool is_first_field = true;
+       int bitoffs;
        s32 tid;
 
        do {
-               if (!is_struct) {
+               /* For the first field of typecast, @type will be the target 
structure type. */
+               if (!(is_first_field && ctx->struct_btf)) {
                        /* Outer loop for solving arrow operator ('->') */
                        if (BTF_INFO_KIND(type->info) != BTF_KIND_PTR) {
                                trace_probe_log_err(ctx->offset, NO_PTR_STRCT);
@@ -606,60 +663,25 @@ static int parse_btf_field(char *fieldname, const struct 
btf_type *type,
                                return -EINVAL;
                        }
                }
-               /* Only the first type can skip being a pointer */
-               is_struct = false;
-
-               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(btf, type, fieldname,
-                                                      &anon_offs);
-                       if (IS_ERR(field)) {
-                               trace_probe_log_err(ctx->offset, BAD_BTF_TID);
-                               return PTR_ERR(field);
-                       }
-                       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;
-                       }
-
-                       type = btf_type_skip_modifiers(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);
 
+               bitoffs = get_bitoffset_of_field(&fieldname, &type, ctx);
+               if (bitoffs < 0)
+                       return bitoffs;
                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;
+               if (is_first_field && ctx->struct_btf) {
+                       /* The first field can be typecasted with field option. 
*/
+                       code->offset -= ctx->prefix_byteoffs;
+               }
                *pcode = code;
 
                ctx->last_bitoffs = bitoffs % 8;
                ctx->last_type = type;
+               is_first_field = false;
        } while (fieldname);
 
        return 0;
@@ -690,6 +712,11 @@ static int parse_btf_arg(char *varname,
                                    NOSUP_DAT_ARG);
                return -EOPNOTSUPP;
        }
+       if (!field && ctx->struct_btf) {
+               /* Typecast without field option is not supported */
+               trace_probe_log_err(ctx->offset, TYPECAST_REQ_FIELD);
+               return -EOPNOTSUPP;
+       }
 
        if (ctx->flags & TPARG_FL_TEVENT) {
                ret = parse_trace_event(varname, code, ctx);
@@ -700,8 +727,7 @@ static int parse_btf_arg(char *varname,
                /* TEVENT is only here via a typecast */
                if (WARN_ON_ONCE(ctx->struct_btf == NULL))
                        return -EINVAL;
-               type = ctx->last_struct;
-               goto found_type;
+               goto found;
        }
 
        if (ctx->flags & TPARG_FL_RETURN && !strcmp(varname, "$retval")) {
@@ -763,7 +789,6 @@ static int parse_btf_arg(char *varname,
                type = ctx->last_struct;
        else
                type = btf_type_skip_modifiers(ctx->btf, tid, &tid);
-found_type:
        if (!type) {
                trace_probe_log_err(ctx->offset, BAD_BTF_TID);
                return -EINVAL;
@@ -832,6 +857,45 @@ static int query_btf_struct(const char *sname, struct 
traceprobe_parse_context *
        return 0;
 }
 
+static int parse_btf_casttype(char *casttype, struct traceprobe_parse_context 
*ctx)
+{
+       char *field;
+       int ret;
+
+       /* Field option - evaluated later. */
+       field = strchr(casttype, ',');
+       if (field)
+               *field++ = '\0';
+
+       ret = query_btf_struct(casttype, ctx);
+       if (ret < 0) {
+               trace_probe_log_err(ctx->offset, NO_PTR_STRCT);
+               return -EINVAL;
+       }
+
+       if (field) {
+               struct btf_type *type = (struct btf_type *)ctx->last_struct;
+
+               ctx->offset += field - casttype;
+               ret = get_bitoffset_of_field(&field, &ctx->last_struct, ctx);
+               if (ret < 0)
+                       return ret;
+               if (ret % 8) {
+                       trace_probe_log_err(ctx->offset, TYPECAST_NOT_ALIGNED);
+                       return -EINVAL;
+               }
+               if (field != NULL) {
+                       trace_probe_log_err(ctx->offset + field - casttype, 
TYPECAST_BAD_ARROW);
+                       return -EINVAL;
+               }
+               ctx->prefix_byteoffs = ret / 8;
+               /* Restore the original struct type (overwritten by 
get_bitoffset_of_field) */
+               ctx->last_struct = type;
+       }
+
+       return ret;
+}
+
 /* Find the matching closing parenthesis for a given opening parenthesis. */
 static char *find_matched_close_paren(char *s)
 {
@@ -915,11 +979,10 @@ static int handle_typecast(char *arg, struct fetch_insn 
**pcode,
                nested = true;
        }
 
-       ret = query_btf_struct(arg + 1, ctx);
-       if (ret < 0) {
-               trace_probe_log_err(ctx->offset + 1, NO_PTR_STRCT);
-               return -EINVAL;
-       }
+       ctx->offset = orig_offset + 1;  /* for the '(' */
+       ret = parse_btf_casttype(arg + 1, ctx);
+       if (ret < 0)
+               return ret;
 
        ctx->offset = orig_offset + tmp - arg;
        /* If it is nested, tmp points to the field name. */
@@ -927,6 +990,7 @@ static int handle_typecast(char *arg, struct fetch_insn 
**pcode,
                ret = parse_btf_field(tmp, ctx->last_struct, pcode, end, ctx);
        else
                ret = parse_btf_arg(tmp, pcode, end, ctx);
+       ctx->prefix_byteoffs = 0;
        return ret;
 }
 
diff --git a/kernel/trace/trace_probe.h b/kernel/trace/trace_probe.h
index 982d32a5df8b..44f113faae61 100644
--- a/kernel/trace/trace_probe.h
+++ b/kernel/trace/trace_probe.h
@@ -436,6 +436,7 @@ struct traceprobe_parse_context {
        unsigned int flags;
        int offset;
        int nested_level;
+       int prefix_byteoffs;    /* The byte offset of the prefix field of 
typecast */
 };
 
 #define TRACEPROBE_MAX_NESTED_LEVEL 3
@@ -576,7 +577,9 @@ extern int traceprobe_define_arg_fields(struct 
trace_event_call *event_call,
        C(EVENT_TOO_BIG,        "Event too big (too many fields?)"),  \
        C(TYPECAST_NOT_EVENT,   "Typecasts are only for eprobe fields"), \
        C(TYPECAST_REQ_FIELD,   "Typecast requires a field access"),    \
-       C(TOO_MANY_NESTED,      "Too many nested typecasts/dereferences"),
+       C(TOO_MANY_NESTED,      "Too many nested typecasts/dereferences"), \
+       C(TYPECAST_NOT_ALIGNED, "Typecast field option is not byte-aligned"), \
+       C(TYPECAST_BAD_ARROW,   "Typecast field option does not support -> 
operator"),
 
 #undef C
 #define C(a, b)                TP_ERR_##a


Reply via email to