[PATCH v6 21/21] perf-probe: Add array argument support

From: Masami Hiramatsu
Date: Sat Mar 17 2018 - 08:54:00 EST


Since kprobes events support an array argument, perf-probe
can also support dumping array now.
The syntax are

<array-var>[<range>]
or
<pointer-var>[<range>]

where the <range> is <start>..<end>. e.g. array[0..5].
This can also be available with string type. In this
case, the string array should be "char *array[]" or
"char **array_ptr".

Note that this feature is only available on the kernel which
supports array type.

Signed-off-by: Masami Hiramatsu <mhiramat@xxxxxxxxxx>
---
tools/perf/Documentation/perf-probe.txt | 2 -
tools/perf/util/probe-event.c | 20 ++++++-
tools/perf/util/probe-event.h | 2 +
tools/perf/util/probe-file.c | 5 ++
tools/perf/util/probe-file.h | 1
tools/perf/util/probe-finder.c | 95 ++++++++++++++++++-------------
6 files changed, 84 insertions(+), 41 deletions(-)

diff --git a/tools/perf/Documentation/perf-probe.txt b/tools/perf/Documentation/perf-probe.txt
index b6866a05edd2..a68e423cac48 100644
--- a/tools/perf/Documentation/perf-probe.txt
+++ b/tools/perf/Documentation/perf-probe.txt
@@ -196,7 +196,7 @@ Each probe argument follows below syntax.

[NAME=]LOCALVAR|$retval|%REG|@SYMBOL[:TYPE]

-'NAME' specifies the name of this argument (optional). You can use the name of local variable, local data structure member (e.g. var->field, var.field2), local array with fixed index (e.g. array[1], var->array[0], var->pointer[2]), or kprobe-tracer argument format (e.g. $retval, %ax, etc). Note that the name of this argument will be set as the last member name if you specify a local data structure member (e.g. field2 for 'var->field1.field2'.)
+'NAME' specifies the name of this argument (optional). You can use the name of local variable, local data structure member (e.g. var->field, var.field2), local array with fixed index (e.g. array[1], var->array[0], var->pointer[2]) or range (e.g. array[1..4], var->array[0..2]), or kprobe-tracer argument format (e.g. $retval, %ax, etc). Note that the name of this argument will be set as the last member name if you specify a local data structure member (e.g. field2 for 'var->field1.field2'.)
'$vars' and '$params' special arguments are also available for NAME, '$vars' is expanded to the local variables (including function parameters) which can access at given probe point. '$params' is expanded to only the function parameters.
'TYPE' casts the type of this argument (optional). If omitted, perf probe automatically set the type based on debuginfo (*). Currently, basic types (u8/u16/u32/u64/s8/s16/s32/s64), hexadecimal integers (x/x8/x16/x32/x64), signedness casting (u/s), "string" and bitfield are supported. (see TYPES for detail)
On x86 systems %REG is always the short form of the register: for example %AX. %RAX or %EAX is not valid.
diff --git a/tools/perf/util/probe-event.c b/tools/perf/util/probe-event.c
index e1dbc9821617..e07a1957c9cd 100644
--- a/tools/perf/util/probe-event.c
+++ b/tools/perf/util/probe-event.c
@@ -1570,6 +1570,7 @@ static int parse_perf_probe_arg(char *str, struct perf_probe_arg *arg)
{
char *tmp, *goodname;
struct perf_probe_arg_field **fieldp;
+ long end;

pr_debug("parsing arg: %s into ", str);

@@ -1617,7 +1618,18 @@ static int parse_perf_probe_arg(char *str, struct perf_probe_arg *arg)
str = tmp;
(*fieldp)->index = strtol(str + 1, &tmp, 0);
(*fieldp)->ref = true;
- if (*tmp != ']' || tmp == str + 1) {
+ if (tmp[0] == '.' && tmp[1] == '.') { /* dump array */
+ end = strtol(tmp + 2, &tmp, 0);
+ if (end < (*fieldp)->index || *tmp != ']') {
+ semantic_error("Invalid array range\n");
+ return -EINVAL;
+ }
+ arg->length = end - (*fieldp)->index + 1;
+ if (tmp[1] != '\0') {
+ semantic_error("Array range must not follow anything.");
+ return -EINVAL;
+ }
+ } else if (*tmp != ']' || tmp == str + 1) {
semantic_error("Array index must be a"
" number.\n");
return -EINVAL;
@@ -2022,6 +2034,12 @@ static int synthesize_probe_trace_arg(struct probe_trace_arg *arg,
if (!err && arg->type)
err = strbuf_addf(buf, ":%s", arg->type);

+ if (!err && arg->length) {
+ if (!arg->type)
+ return -EINVAL;
+ err = strbuf_addf(buf, "[%d]", arg->length);
+ }
+
return err;
}

diff --git a/tools/perf/util/probe-event.h b/tools/perf/util/probe-event.h
index 45b14f020558..e5ca1bf33ee7 100644
--- a/tools/perf/util/probe-event.h
+++ b/tools/perf/util/probe-event.h
@@ -42,6 +42,7 @@ struct probe_trace_arg {
char *name; /* Argument name */
char *value; /* Base value */
char *type; /* Type name */
+ unsigned int length; /* Length of array */
struct probe_trace_arg_ref *ref; /* Referencing offset */
};

@@ -79,6 +80,7 @@ struct perf_probe_arg {
char *name; /* Argument name */
char *var; /* Variable name */
char *type; /* Type name */
+ unsigned int length; /* Length of array */
struct perf_probe_arg_field *field; /* Structure fields */
};

diff --git a/tools/perf/util/probe-file.c b/tools/perf/util/probe-file.c
index 4ae1123c6794..a879fe8eede3 100644
--- a/tools/perf/util/probe-file.c
+++ b/tools/perf/util/probe-file.c
@@ -999,6 +999,7 @@ int probe_cache__show_all_caches(struct strfilter *filter)
enum ftrace_readme {
FTRACE_README_PROBE_TYPE_X = 0,
FTRACE_README_KRETPROBE_OFFSET,
+ FTRACE_README_ARRAY_TYPE,
FTRACE_README_END,
};

@@ -1010,6 +1011,8 @@ static struct {
[idx] = {.pattern = pat, .avail = false}
DEFINE_TYPE(FTRACE_README_PROBE_TYPE_X, "*type: * x8/16/32/64,*"),
DEFINE_TYPE(FTRACE_README_KRETPROBE_OFFSET, "*place (kretprobe): *"),
+ DEFINE_TYPE(FTRACE_README_ARRAY_TYPE,
+ "*<type>\\\\\\[<array-size>\\\\\\]*"),
};

static bool scan_ftrace_readme(enum ftrace_readme type)
@@ -1057,6 +1060,8 @@ bool probe_type_is_available(enum probe_type type)
return false;
else if (type == PROBE_TYPE_X)
return scan_ftrace_readme(FTRACE_README_PROBE_TYPE_X);
+ else if (type == PROBE_TYPE_ARRAY)
+ return scan_ftrace_readme(FTRACE_README_ARRAY_TYPE);

return true;
}
diff --git a/tools/perf/util/probe-file.h b/tools/perf/util/probe-file.h
index 63f29b1d22c1..654290b2fd9c 100644
--- a/tools/perf/util/probe-file.h
+++ b/tools/perf/util/probe-file.h
@@ -27,6 +27,7 @@ enum probe_type {
PROBE_TYPE_X,
PROBE_TYPE_STRING,
PROBE_TYPE_BITFIELD,
+ PROBE_TYPE_ARRAY,
PROBE_TYPE_END,
};

diff --git a/tools/perf/util/probe-finder.c b/tools/perf/util/probe-finder.c
index c37fbef1711d..a1df7926edfc 100644
--- a/tools/perf/util/probe-finder.c
+++ b/tools/perf/util/probe-finder.c
@@ -290,13 +290,52 @@ static int convert_variable_location(Dwarf_Die *vr_die, Dwarf_Addr addr,
return ret2;
}

+static int convert_variable_string_type(Dwarf_Die *vr_die, Dwarf_Die *type,
+ struct probe_trace_arg *tvar)
+{
+ struct probe_trace_arg_ref **ref_ptr = &tvar->ref;
+ int ret;
+
+ ret = dwarf_tag(type);
+ if (ret != DW_TAG_pointer_type && ret != DW_TAG_array_type) {
+ pr_warning("Failed to cast into string: "
+ "%s(%s) is not a pointer nor array.\n",
+ dwarf_diename(vr_die), dwarf_diename(type));
+ return -EINVAL;
+ }
+ if (ret == DW_TAG_pointer_type && tvar->length == 0) {
+ while (*ref_ptr)
+ ref_ptr = &(*ref_ptr)->next;
+ /* Add new reference with offset +0 */
+ *ref_ptr = zalloc(sizeof(struct probe_trace_arg_ref));
+ if (*ref_ptr == NULL) {
+ pr_warning("Out of memory error\n");
+ return -ENOMEM;
+ }
+ }
+ if (die_get_real_type(type, type) == NULL) {
+ pr_warning("Failed to get a type"
+ " information.\n");
+ return -ENOENT;
+ }
+ if (!die_compare_name(type, "char") &&
+ !die_compare_name(type, "unsigned char")) {
+ pr_warning("Failed to cast into string: "
+ "%s is not (unsigned) char *.\n",
+ dwarf_diename(vr_die));
+ return -EINVAL;
+ }
+ tvar->type = strdup("string");
+ return (tvar->type == NULL) ? -ENOMEM : 0;
+}
+
#define BYTES_TO_BITS(nb) ((nb) * BITS_PER_LONG / sizeof(long))

static int convert_variable_type(Dwarf_Die *vr_die,
struct probe_trace_arg *tvar,
- const char *cast)
+ struct perf_probe_arg *pvar)
{
- struct probe_trace_arg_ref **ref_ptr = &tvar->ref;
+ const char *cast = pvar->type;
Dwarf_Die type;
char buf[16];
char sbuf[STRERR_BUFSIZE];
@@ -304,6 +343,12 @@ static int convert_variable_type(Dwarf_Die *vr_die,
int ret;
char prefix;

+ tvar->length = pvar->length;
+ if (tvar->length && !probe_type_is_available(PROBE_TYPE_ARRAY)) {
+ pr_warning("This kerneld doesn't support array type.\n");
+ return -ENOTSUP;
+ }
+
/* TODO: check all types */
if (cast && strcmp(cast, "string") != 0 && strcmp(cast, "x") != 0 &&
strcmp(cast, "s") != 0 && strcmp(cast, "u") != 0) {
@@ -334,40 +379,8 @@ static int convert_variable_type(Dwarf_Die *vr_die,
pr_debug("%s type is %s.\n",
dwarf_diename(vr_die), dwarf_diename(&type));

- if (cast && strcmp(cast, "string") == 0) { /* String type */
- ret = dwarf_tag(&type);
- if (ret != DW_TAG_pointer_type &&
- ret != DW_TAG_array_type) {
- pr_warning("Failed to cast into string: "
- "%s(%s) is not a pointer nor array.\n",
- dwarf_diename(vr_die), dwarf_diename(&type));
- return -EINVAL;
- }
- if (die_get_real_type(&type, &type) == NULL) {
- pr_warning("Failed to get a type"
- " information.\n");
- return -ENOENT;
- }
- if (ret == DW_TAG_pointer_type) {
- while (*ref_ptr)
- ref_ptr = &(*ref_ptr)->next;
- /* Add new reference with offset +0 */
- *ref_ptr = zalloc(sizeof(struct probe_trace_arg_ref));
- if (*ref_ptr == NULL) {
- pr_warning("Out of memory error\n");
- return -ENOMEM;
- }
- }
- if (!die_compare_name(&type, "char") &&
- !die_compare_name(&type, "unsigned char")) {
- pr_warning("Failed to cast into string: "
- "%s is not (unsigned) char *.\n",
- dwarf_diename(vr_die));
- return -EINVAL;
- }
- tvar->type = strdup(cast);
- return (tvar->type == NULL) ? -ENOMEM : 0;
- }
+ if (cast && strcmp(cast, "string") == 0) /* String type */
+ return convert_variable_string_type(vr_die, &type, tvar);

if (cast && (strcmp(cast, "u") == 0))
prefix = 'u';
@@ -381,9 +394,13 @@ static int convert_variable_type(Dwarf_Die *vr_die,
probe_type_is_available(PROBE_TYPE_X) ? 'x' : 'u';

ret = dwarf_bytesize(&type);
- if (ret <= 0)
- /* No size ... try to use default type */
+ if (ret <= 0) { /* No size ... try to use default type */
+ if (tvar->length) { /* But array can not be dumped */
+ pr_warning("Failed to convert array with unsupported type\n");
+ return -EINVAL;
+ }
return 0;
+ }
ret = BYTES_TO_BITS(ret);

/* Check the bitwidth */
@@ -559,7 +576,7 @@ static int convert_variable(Dwarf_Die *vr_die, struct probe_finder *pf)
vr_die = &die_mem;
}
if (ret == 0)
- ret = convert_variable_type(vr_die, pf->tvar, pf->pvar->type);
+ ret = convert_variable_type(vr_die, pf->tvar, pf->pvar);
/* *expr will be cached in libdw. Don't free it. */
return ret;
}