tracing/probes: Add symstr type for dynamic events

[ Upstream commit b26a124cbf ]

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: 66bcf65d6c ("tracing/probes: Fix to avoid double count of the string length on the array")
Signed-off-by: Sasha Levin <sashal@kernel.org>
This commit is contained in:
Masami Hiramatsu (Google) 2022-11-14 13:47:56 +09:00 committed by Greg Kroah-Hartman
parent 7ac170d93b
commit 3a1a229712
5 changed files with 91 additions and 26 deletions

View File

@ -58,8 +58,8 @@ Synopsis of kprobe_events
NAME=FETCHARG : Set NAME as the argument name of FETCHARG. NAME=FETCHARG : Set NAME as the argument name of FETCHARG.
FETCHARG:TYPE : Set TYPE as the type of FETCHARG. Currently, basic types FETCHARG:TYPE : Set TYPE as the type of FETCHARG. Currently, basic types
(u8/u16/u32/u64/s8/s16/s32/s64), hexadecimal types (u8/u16/u32/u64/s8/s16/s32/s64), hexadecimal types
(x8/x16/x32/x64), "string", "ustring" and bitfield (x8/x16/x32/x64), "string", "ustring", "symbol", "symstr"
are supported. and bitfield are supported.
(\*1) only for the probe on function entry (offs == 0). (\*1) only for the probe on function entry (offs == 0).
(\*2) only for return probe. (\*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) Symbol type('symbol') is an alias of u32 or u64 type (depends on BITS_PER_LONG)
which shows given pointer in "symbol+offset" style. 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. For $comm, the default type is "string"; any other type is invalid.
.. _user_mem_access: .. _user_mem_access:

View File

@ -5609,7 +5609,7 @@ static const char readme_msg[] =
"\t +|-[u]<offset>(<fetcharg>), \\imm-value, \\\"imm-string\"\n" "\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 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 b<bit-width>@<bit-offset>/<container-size>, ustring,\n"
"\t <type>\\[<array-size>\\]\n" "\t symstr, <type>\\[<array-size>\\]\n"
#ifdef CONFIG_HIST_TRIGGERS #ifdef CONFIG_HIST_TRIGGERS
"\t field: <stype> <name>;\n" "\t field: <stype> <name>;\n"
"\t stype: u8/u16/u32/u64, s8/s16/s32/s64, pid_t,\n" "\t stype: u8/u16/u32/u64, s8/s16/s32/s64, pid_t,\n"

View File

@ -76,9 +76,11 @@ const char PRINT_TYPE_FMT_NAME(string)[] = "\\\"%s\\\"";
/* Fetch type information table */ /* Fetch type information table */
static const struct fetch_type probe_fetch_types[] = { static const struct fetch_type probe_fetch_types[] = {
/* Special 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[]"), "__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[]"), "__data_loc char[]"),
/* Basic types */ /* Basic types */
ASSIGN_FETCH_TYPE(u8, u8, 0), ASSIGN_FETCH_TYPE(u8, u8, 0),
@ -658,16 +660,26 @@ static int traceprobe_parse_probe_arg_body(const char *argv, ssize_t *size,
ret = -EINVAL; ret = -EINVAL;
/* Store operation */ /* Store operation */
if (!strcmp(parg->type->name, "string") || if (parg->type->is_string) {
!strcmp(parg->type->name, "ustring")) { if (!strcmp(parg->type->name, "symstr")) {
if (code->op != FETCH_OP_DEREF && code->op != FETCH_OP_UDEREF && if (code->op != FETCH_OP_REG && code->op != FETCH_OP_STACK &&
code->op != FETCH_OP_IMM && code->op != FETCH_OP_COMM && code->op != FETCH_OP_RETVAL && code->op != FETCH_OP_ARG &&
code->op != FETCH_OP_DATA && code->op != FETCH_OP_TP_ARG) { code->op != FETCH_OP_DEREF && code->op != FETCH_OP_TP_ARG) {
trace_probe_log_err(offset + (t ? (t - arg) : 0), trace_probe_log_err(offset + (t ? (t - arg) : 0),
BAD_STRING); BAD_SYMSTRING);
goto fail; 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 || code->op == FETCH_OP_DATA) || code->op == FETCH_OP_TP_ARG ||
parg->count) { parg->count) {
/* /*
@ -675,6 +687,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 * must be kept, and if parg->count != 0, this is an
* array of string pointers instead of string address * array of string pointers instead of string address
* itself. * itself.
* For the symstr, it doesn't need to dereference, thus
* it just get the value.
*/ */
code++; code++;
if (code->op != FETCH_OP_NOP) { if (code->op != FETCH_OP_NOP) {
@ -686,6 +700,8 @@ static int traceprobe_parse_probe_arg_body(const char *argv, ssize_t *size,
if (!strcmp(parg->type->name, "ustring") || if (!strcmp(parg->type->name, "ustring") ||
code->op == FETCH_OP_UDEREF) code->op == FETCH_OP_UDEREF)
code->op = FETCH_OP_ST_USTRING; code->op = FETCH_OP_ST_USTRING;
else if (!strcmp(parg->type->name, "symstr"))
code->op = FETCH_OP_ST_SYMSTR;
else else
code->op = FETCH_OP_ST_STRING; code->op = FETCH_OP_ST_STRING;
code->size = parg->type->size; code->size = parg->type->size;
@ -915,8 +931,7 @@ static int __set_print_fmt(struct trace_probe *tp, char *buf, int len,
for (i = 0; i < tp->nr_args; i++) { for (i = 0; i < tp->nr_args; i++) {
parg = tp->args + i; parg = tp->args + i;
if (parg->count) { if (parg->count) {
if ((strcmp(parg->type->name, "string") == 0) || if (parg->type->is_string)
(strcmp(parg->type->name, "ustring") == 0))
fmt = ", __get_str(%s[%d])"; fmt = ", __get_str(%s[%d])";
else else
fmt = ", REC->%s[%d]"; fmt = ", REC->%s[%d]";
@ -924,8 +939,7 @@ static int __set_print_fmt(struct trace_probe *tp, char *buf, int len,
pos += snprintf(buf + pos, LEN_OR_ZERO, pos += snprintf(buf + pos, LEN_OR_ZERO,
fmt, parg->name, j); fmt, parg->name, j);
} else { } else {
if ((strcmp(parg->type->name, "string") == 0) || if (parg->type->is_string)
(strcmp(parg->type->name, "ustring") == 0))
fmt = ", __get_str(%s)"; fmt = ", __get_str(%s)";
else else
fmt = ", REC->%s"; fmt = ", REC->%s";

View File

@ -99,6 +99,7 @@ enum fetch_op {
FETCH_OP_ST_UMEM, /* Mem: .offset, .size */ FETCH_OP_ST_UMEM, /* Mem: .offset, .size */
FETCH_OP_ST_STRING, /* String: .offset, .size */ FETCH_OP_ST_STRING, /* String: .offset, .size */
FETCH_OP_ST_USTRING, /* User String: .offset, .size */ FETCH_OP_ST_USTRING, /* User String: .offset, .size */
FETCH_OP_ST_SYMSTR, /* Kernel Symbol String: .offset, .size */
// Stage 4 (modify) op // Stage 4 (modify) op
FETCH_OP_MOD_BF, /* Bitfield: .basesize, .lshift, .rshift */ FETCH_OP_MOD_BF, /* Bitfield: .basesize, .lshift, .rshift */
// Stage 5 (loop) op // Stage 5 (loop) op
@ -134,7 +135,8 @@ struct fetch_insn {
struct fetch_type { struct fetch_type {
const char *name; /* Name of type */ const char *name; /* Name of type */
size_t size; /* Byte size 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 */ print_type_func_t print; /* Print functions */
const char *fmt; /* Format string */ const char *fmt; /* Format string */
const char *fmttype; /* Name in format file */ const char *fmttype; /* Name in format file */
@ -178,16 +180,19 @@ DECLARE_BASIC_PRINT_TYPE_FUNC(symbol);
#define _ADDR_FETCH_TYPE(t) __ADDR_FETCH_TYPE(t) #define _ADDR_FETCH_TYPE(t) __ADDR_FETCH_TYPE(t)
#define ADDR_FETCH_TYPE _ADDR_FETCH_TYPE(BITS_PER_LONG) #define ADDR_FETCH_TYPE _ADDR_FETCH_TYPE(BITS_PER_LONG)
#define __ASSIGN_FETCH_TYPE(_name, ptype, ftype, _size, sign, _fmttype) \ #define __ASSIGN_FETCH_TYPE(_name, ptype, ftype, _size, sign, str, _fmttype) \
{.name = _name, \ {.name = _name, \
.size = _size, \ .size = _size, \
.is_signed = sign, \ .is_signed = (bool)sign, \
.is_string = (bool)str, \
.print = PRINT_TYPE_FUNC_NAME(ptype), \ .print = PRINT_TYPE_FUNC_NAME(ptype), \
.fmt = PRINT_TYPE_FMT_NAME(ptype), \ .fmt = PRINT_TYPE_FMT_NAME(ptype), \
.fmttype = _fmttype, \ .fmttype = _fmttype, \
} }
/* Non string types can use these macros */
#define _ASSIGN_FETCH_TYPE(_name, ptype, ftype, _size, sign, _fmttype) \ #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) \ #define ASSIGN_FETCH_TYPE(ptype, ftype, sign) \
_ASSIGN_FETCH_TYPE(#ptype, ptype, ftype, sizeof(ftype), sign, ptype) _ASSIGN_FETCH_TYPE(#ptype, ptype, ftype, sizeof(ftype), sign, ptype)
@ -432,6 +437,7 @@ extern int traceprobe_define_arg_fields(struct trace_event_call *event_call,
C(ARRAY_TOO_BIG, "Array number is too big"), \ C(ARRAY_TOO_BIG, "Array number is too big"), \
C(BAD_TYPE, "Unknown type is specified"), \ C(BAD_TYPE, "Unknown type is specified"), \
C(BAD_STRING, "String accepts only memory argument"), \ C(BAD_STRING, "String accepts only memory argument"), \
C(BAD_SYMSTRING, "Symbol String doesn't accept data/userdata"), \
C(BAD_BITFIELD, "Invalid bitfield"), \ C(BAD_BITFIELD, "Invalid bitfield"), \
C(ARG_NAME_TOO_LONG, "Argument name is too long"), \ C(ARG_NAME_TOO_LONG, "Argument name is too long"), \
C(NO_ARG_NAME, "Argument name is not specified"), \ C(NO_ARG_NAME, "Argument name is not specified"), \

View File

@ -67,6 +67,37 @@ probe_mem_read(void *dest, void *src, size_t size);
static nokprobe_inline int static nokprobe_inline int
probe_mem_read_user(void *dest, void *src, size_t size); 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 */ /* From the 2nd stage, routine is same */
static nokprobe_inline int static nokprobe_inline int
process_fetch_insn_bottom(struct fetch_insn *code, unsigned long val, process_fetch_insn_bottom(struct fetch_insn *code, unsigned long val,
@ -99,16 +130,22 @@ stage2:
stage3: stage3:
/* 3rd stage: store value to buffer */ /* 3rd stage: store value to buffer */
if (unlikely(!dest)) { 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); ret = fetch_store_strlen(val + code->offset);
code++; code++;
goto array; goto array;
} else if (code->op == FETCH_OP_ST_USTRING) { case FETCH_OP_ST_USTRING:
ret += fetch_store_strlen_user(val + code->offset); ret += fetch_store_strlen_user(val + code->offset);
code++; code++;
goto array; goto array;
} else case FETCH_OP_ST_SYMSTR:
ret += fetch_store_symstrlen(val + code->offset);
code++;
goto array;
default:
return -EILSEQ; return -EILSEQ;
}
} }
switch (code->op) { switch (code->op) {
@ -129,6 +166,10 @@ stage3:
loc = *(u32 *)dest; loc = *(u32 *)dest;
ret = fetch_store_string_user(val + code->offset, dest, base); ret = fetch_store_string_user(val + code->offset, dest, base);
break; break;
case FETCH_OP_ST_SYMSTR:
loc = *(u32 *)dest;
ret = fetch_store_symstring(val + code->offset, dest, base);
break;
default: default:
return -EILSEQ; return -EILSEQ;
} }