tracing/probes: Add symstr type for dynamic events
authorMasami Hiramatsu (Google) <mhiramat@kernel.org>
Mon, 14 Nov 2022 04:47:56 +0000 (13:47 +0900)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 3 Aug 2023 08:23:54 +0000 (10:23 +0200)
[ Upstream commit b26a124cbfa80f42bfc4e63e1d5643ca98159d66 ]

Add 'symstr' type for storing the kernel symbol as a string data
instead of the symbol address. This allows us to filter the
events by wildcard symbol name.

e.g.
  # echo 'e:wqfunc workqueue.workqueue_execute_start symname=$function:symstr' >> dynamic_events
  # cat events/eprobes/wqfunc/format
  name: wqfunc
  ID: 2110
  format:
   field:unsigned short common_type; offset:0; size:2; signed:0;
   field:unsigned char common_flags; offset:2; size:1; signed:0;
   field:unsigned char common_preempt_count; offset:3; size:1; signed:0;
   field:int common_pid; offset:4; size:4; signed:1;

   field:__data_loc char[] symname; offset:8; size:4; signed:1;

  print fmt: " symname=\"%s\"", __get_str(symname)

Note that there is already 'symbol' type which just change the
print format (so it still stores the symbol address in the tracing
ring buffer.) On the other hand, 'symstr' type stores the actual
"symbol+offset/size" data as a string.

Link: https://lore.kernel.org/all/166679930847.1528100.4124308529180235965.stgit@devnote3/
Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
Stable-dep-of: 66bcf65d6cf0 ("tracing/probes: Fix to avoid double count of the string length on the array")
Signed-off-by: Sasha Levin <sashal@kernel.org>
Documentation/trace/kprobetrace.rst
kernel/trace/trace.c
kernel/trace/trace_probe.c
kernel/trace/trace_probe.h
kernel/trace/trace_probe_tmpl.h

index 4274cc6..08a2a6a 100644 (file)
@@ -58,8 +58,8 @@ Synopsis of kprobe_events
   NAME=FETCHARG : Set NAME as the argument name of FETCHARG.
   FETCHARG:TYPE : Set TYPE as the type of FETCHARG. Currently, basic types
                  (u8/u16/u32/u64/s8/s16/s32/s64), hexadecimal types
-                 (x8/x16/x32/x64), "string", "ustring" and bitfield
-                 are supported.
+                 (x8/x16/x32/x64), "string", "ustring", "symbol", "symstr"
+                  and bitfield are supported.
 
   (\*1) only for the probe on function entry (offs == 0).
   (\*2) only for return probe.
@@ -96,6 +96,10 @@ offset, and container-size (usually 32). The syntax is::
 
 Symbol type('symbol') is an alias of u32 or u64 type (depends on BITS_PER_LONG)
 which shows given pointer in "symbol+offset" style.
+On the other hand, symbol-string type ('symstr') converts the given address to
+"symbol+offset/symbolsize" style and stores it as a null-terminated string.
+With 'symstr' type, you can filter the event with wildcard pattern of the
+symbols, and you don't need to solve symbol name by yourself.
 For $comm, the default type is "string"; any other type is invalid.
 
 .. _user_mem_access:
index 27bbe18..709af96 100644 (file)
@@ -5627,7 +5627,7 @@ static const char readme_msg[] =
        "\t           +|-[u]<offset>(<fetcharg>), \\imm-value, \\\"imm-string\"\n"
        "\t     type: s8/16/32/64, u8/16/32/64, x8/16/32/64, string, symbol,\n"
        "\t           b<bit-width>@<bit-offset>/<container-size>, ustring,\n"
-       "\t           <type>\\[<array-size>\\]\n"
+       "\t           symstr, <type>\\[<array-size>\\]\n"
 #ifdef CONFIG_HIST_TRIGGERS
        "\t    field: <stype> <name>;\n"
        "\t    stype: u8/u16/u32/u64, s8/s16/s32/s64, pid_t,\n"
index bb2f95d..c385218 100644 (file)
@@ -76,9 +76,11 @@ const char PRINT_TYPE_FMT_NAME(string)[] = "\\\"%s\\\"";
 /* Fetch type information table */
 static const struct fetch_type probe_fetch_types[] = {
        /* Special types */
-       __ASSIGN_FETCH_TYPE("string", string, string, sizeof(u32), 1,
+       __ASSIGN_FETCH_TYPE("string", string, string, sizeof(u32), 1, 1,
                            "__data_loc char[]"),
-       __ASSIGN_FETCH_TYPE("ustring", string, string, sizeof(u32), 1,
+       __ASSIGN_FETCH_TYPE("ustring", string, string, sizeof(u32), 1, 1,
+                           "__data_loc char[]"),
+       __ASSIGN_FETCH_TYPE("symstr", string, string, sizeof(u32), 1, 1,
                            "__data_loc char[]"),
        /* Basic types */
        ASSIGN_FETCH_TYPE(u8,  u8,  0),
@@ -662,16 +664,26 @@ static int traceprobe_parse_probe_arg_body(const char *argv, ssize_t *size,
 
        ret = -EINVAL;
        /* Store operation */
-       if (!strcmp(parg->type->name, "string") ||
-           !strcmp(parg->type->name, "ustring")) {
-               if (code->op != FETCH_OP_DEREF && code->op != FETCH_OP_UDEREF &&
-                   code->op != FETCH_OP_IMM && code->op != FETCH_OP_COMM &&
-                   code->op != FETCH_OP_DATA && code->op != FETCH_OP_TP_ARG) {
-                       trace_probe_log_err(offset + (t ? (t - arg) : 0),
-                                           BAD_STRING);
-                       goto fail;
+       if (parg->type->is_string) {
+               if (!strcmp(parg->type->name, "symstr")) {
+                       if (code->op != FETCH_OP_REG && code->op != FETCH_OP_STACK &&
+                           code->op != FETCH_OP_RETVAL && code->op != FETCH_OP_ARG &&
+                           code->op != FETCH_OP_DEREF && code->op != FETCH_OP_TP_ARG) {
+                               trace_probe_log_err(offset + (t ? (t - arg) : 0),
+                                                   BAD_SYMSTRING);
+                               goto fail;
+                       }
+               } else {
+                       if (code->op != FETCH_OP_DEREF && code->op != FETCH_OP_UDEREF &&
+                           code->op != FETCH_OP_IMM && code->op != FETCH_OP_COMM &&
+                           code->op != FETCH_OP_DATA && code->op != FETCH_OP_TP_ARG) {
+                               trace_probe_log_err(offset + (t ? (t - arg) : 0),
+                                                   BAD_STRING);
+                               goto fail;
+                       }
                }
-               if ((code->op == FETCH_OP_IMM || code->op == FETCH_OP_COMM ||
+               if (!strcmp(parg->type->name, "symstr") ||
+                   (code->op == FETCH_OP_IMM || code->op == FETCH_OP_COMM ||
                     code->op == FETCH_OP_DATA) || code->op == FETCH_OP_TP_ARG ||
                     parg->count) {
                        /*
@@ -679,6 +691,8 @@ static int traceprobe_parse_probe_arg_body(const char *argv, ssize_t *size,
                         * must be kept, and if parg->count != 0, this is an
                         * array of string pointers instead of string address
                         * itself.
+                        * For the symstr, it doesn't need to dereference, thus
+                        * it just get the value.
                         */
                        code++;
                        if (code->op != FETCH_OP_NOP) {
@@ -690,6 +704,8 @@ static int traceprobe_parse_probe_arg_body(const char *argv, ssize_t *size,
                if (!strcmp(parg->type->name, "ustring") ||
                    code->op == FETCH_OP_UDEREF)
                        code->op = FETCH_OP_ST_USTRING;
+               else if (!strcmp(parg->type->name, "symstr"))
+                       code->op = FETCH_OP_ST_SYMSTR;
                else
                        code->op = FETCH_OP_ST_STRING;
                code->size = parg->type->size;
@@ -919,8 +935,7 @@ static int __set_print_fmt(struct trace_probe *tp, char *buf, int len,
        for (i = 0; i < tp->nr_args; i++) {
                parg = tp->args + i;
                if (parg->count) {
-                       if ((strcmp(parg->type->name, "string") == 0) ||
-                           (strcmp(parg->type->name, "ustring") == 0))
+                       if (parg->type->is_string)
                                fmt = ", __get_str(%s[%d])";
                        else
                                fmt = ", REC->%s[%d]";
@@ -928,8 +943,7 @@ static int __set_print_fmt(struct trace_probe *tp, char *buf, int len,
                                pos += snprintf(buf + pos, LEN_OR_ZERO,
                                                fmt, parg->name, j);
                } else {
-                       if ((strcmp(parg->type->name, "string") == 0) ||
-                           (strcmp(parg->type->name, "ustring") == 0))
+                       if (parg->type->is_string)
                                fmt = ", __get_str(%s)";
                        else
                                fmt = ", REC->%s";
index 3d731aa..f41c330 100644 (file)
@@ -98,6 +98,7 @@ enum fetch_op {
        FETCH_OP_ST_UMEM,       /* Mem: .offset, .size */
        FETCH_OP_ST_STRING,     /* String: .offset, .size */
        FETCH_OP_ST_USTRING,    /* User String: .offset, .size */
+       FETCH_OP_ST_SYMSTR,     /* Kernel Symbol String: .offset, .size */
        // Stage 4 (modify) op
        FETCH_OP_MOD_BF,        /* Bitfield: .basesize, .lshift, .rshift */
        // Stage 5 (loop) op
@@ -133,7 +134,8 @@ struct fetch_insn {
 struct fetch_type {
        const char              *name;          /* Name of type */
        size_t                  size;           /* Byte size of type */
-       int                     is_signed;      /* Signed flag */
+       bool                    is_signed;      /* Signed flag */
+       bool                    is_string;      /* String flag */
        print_type_func_t       print;          /* Print functions */
        const char              *fmt;           /* Format string */
        const char              *fmttype;       /* Name in format file */
@@ -177,16 +179,19 @@ DECLARE_BASIC_PRINT_TYPE_FUNC(symbol);
 #define _ADDR_FETCH_TYPE(t) __ADDR_FETCH_TYPE(t)
 #define ADDR_FETCH_TYPE _ADDR_FETCH_TYPE(BITS_PER_LONG)
 
-#define __ASSIGN_FETCH_TYPE(_name, ptype, ftype, _size, sign, _fmttype)        \
-       {.name = _name,                         \
+#define __ASSIGN_FETCH_TYPE(_name, ptype, ftype, _size, sign, str, _fmttype)   \
+       {.name = _name,                                 \
         .size = _size,                                 \
-        .is_signed = sign,                             \
+        .is_signed = (bool)sign,                       \
+        .is_string = (bool)str,                        \
         .print = PRINT_TYPE_FUNC_NAME(ptype),          \
         .fmt = PRINT_TYPE_FMT_NAME(ptype),             \
         .fmttype = _fmttype,                           \
        }
+
+/* Non string types can use these macros */
 #define _ASSIGN_FETCH_TYPE(_name, ptype, ftype, _size, sign, _fmttype) \
-       __ASSIGN_FETCH_TYPE(_name, ptype, ftype, _size, sign, #_fmttype)
+       __ASSIGN_FETCH_TYPE(_name, ptype, ftype, _size, sign, 0, #_fmttype)
 #define ASSIGN_FETCH_TYPE(ptype, ftype, sign)                  \
        _ASSIGN_FETCH_TYPE(#ptype, ptype, ftype, sizeof(ftype), sign, ptype)
 
@@ -431,6 +436,7 @@ extern int traceprobe_define_arg_fields(struct trace_event_call *event_call,
        C(ARRAY_TOO_BIG,        "Array number is too big"),             \
        C(BAD_TYPE,             "Unknown type is specified"),           \
        C(BAD_STRING,           "String accepts only memory argument"), \
+       C(BAD_SYMSTRING,        "Symbol String doesn't accept data/userdata"),  \
        C(BAD_BITFIELD,         "Invalid bitfield"),                    \
        C(ARG_NAME_TOO_LONG,    "Argument name is too long"),           \
        C(NO_ARG_NAME,          "Argument name is not specified"),      \
index c293a60..21799fa 100644 (file)
@@ -67,6 +67,37 @@ probe_mem_read(void *dest, void *src, size_t size);
 static nokprobe_inline int
 probe_mem_read_user(void *dest, void *src, size_t size);
 
+static nokprobe_inline int
+fetch_store_symstrlen(unsigned long addr)
+{
+       char namebuf[KSYM_SYMBOL_LEN];
+       int ret;
+
+       ret = sprint_symbol(namebuf, addr);
+       if (ret < 0)
+               return 0;
+
+       return ret + 1;
+}
+
+/*
+ * Fetch a null-terminated symbol string + offset. Caller MUST set *(u32 *)buf
+ * with max length and relative data location.
+ */
+static nokprobe_inline int
+fetch_store_symstring(unsigned long addr, void *dest, void *base)
+{
+       int maxlen = get_loc_len(*(u32 *)dest);
+       void *__dest;
+
+       if (unlikely(!maxlen))
+               return -ENOMEM;
+
+       __dest = get_loc_data(dest, base);
+
+       return sprint_symbol(__dest, addr);
+}
+
 /* From the 2nd stage, routine is same */
 static nokprobe_inline int
 process_fetch_insn_bottom(struct fetch_insn *code, unsigned long val,
@@ -99,16 +130,22 @@ stage2:
 stage3:
        /* 3rd stage: store value to buffer */
        if (unlikely(!dest)) {
-               if (code->op == FETCH_OP_ST_STRING) {
+               switch (code->op) {
+               case FETCH_OP_ST_STRING:
                        ret = fetch_store_strlen(val + code->offset);
                        code++;
                        goto array;
-               } else if (code->op == FETCH_OP_ST_USTRING) {
+               case FETCH_OP_ST_USTRING:
                        ret += fetch_store_strlen_user(val + code->offset);
                        code++;
                        goto array;
-               } else
+               case FETCH_OP_ST_SYMSTR:
+                       ret += fetch_store_symstrlen(val + code->offset);
+                       code++;
+                       goto array;
+               default:
                        return -EILSEQ;
+               }
        }
 
        switch (code->op) {
@@ -129,6 +166,10 @@ stage3:
                loc = *(u32 *)dest;
                ret = fetch_store_string_user(val + code->offset, dest, base);
                break;
+       case FETCH_OP_ST_SYMSTR:
+               loc = *(u32 *)dest;
+               ret = fetch_store_symstring(val + code->offset, dest, base);
+               break;
        default:
                return -EILSEQ;
        }