[RFC 06/12] perf event action: Add parsing print() call expr support

Yang Jihong
2024-11-28T13:36:47Z

Add support for parsing print() function calls, the parameter format is
similar to C's printf(), the first parameter is a string format,
and format specifiers support %c, %d, %d, %o, %u, %x, %X, %s.
Currently, it simply checks whether the number of arguments matches
the number of format specifiers.

Signed-off-by: Yang Jihong <yangjihong@bytedance.com>
---
tools/perf/util/parse-action.c | 329 +++++++++++++++++++++++++++++++++
tools/perf/util/parse-action.h | 1 +
tools/perf/util/parse-action.l | 9 +
3 files changed, 339 insertions(+)

diff --git a/tools/perf/util/parse-action.c b/tools/perf/util/parse-action.c
index 40e7c8aad7be..95c06cc071ad 100644
--- a/tools/perf/util/parse-action.c
+++ b/tools/perf/util/parse-action.c
@@ -10,8 +10,11 @@
* - integer
* - string
* - call:
+ * - print
*/

+#include <regex.h>
+
#include "util/debug.h"
#include "util/parse-action.h"
#include "util/parse-action-flex.h"
@@ -171,6 +174,12 @@ static struct evtact_expr_ops expr_const_int_ops = {
.eval = expr_const_int_eval,
};

+static bool is_const_str_expr(struct evtact_expr *expr)
+{
+ return expr->id == evtact_expr_id_encode(EVTACT_EXPR_TYPE_CONST,
+ EVTACT_EXPR_CONST_TYPE_STR);
+}
+
static int expr_const_str_new(struct evtact_expr *expr,
void *data, int size __maybe_unused)
{
@@ -233,7 +242,327 @@ static struct evtact_expr_class expr_const = {
.set_ops = expr_const_set_ops,
};

+enum print_fmt_spec_len {
+ PRINT_FMT_SPEC_LEN_INT,
+ PRINT_FMT_SPEC_LEN_LONG,
+ PRINT_FMT_SPEC_LEN_LONG_LONG,
+};
+
+enum print_fmt_spec_type {
+ PRINT_FMT_SPEC_TYPE_VOID,
+ PRINT_FMT_SPEC_TYPE_INT,
+ PRINT_FMT_SPEC_TYPE_STRING,
+};
+
+struct print_fmt_spec {
+ struct list_head list;
+ enum print_fmt_spec_type type;
+ enum print_fmt_spec_len len;
+ char *str;
+};
+
+#define PRINT_OUT_BUF_DEFAULT_LEN 64
+struct print_expr_priv {
+ struct list_head fmt_specs;
+ int fmt_spec_num;
+ int args_num;
+ char *out;
+ int out_len;
+};
+
+static int print_fmt_add_spec(struct print_expr_priv *priv, char *s,
+ regoff_t len, enum print_fmt_spec_len spec_len,
+ enum print_fmt_spec_type spec_type)
+{
+ struct print_fmt_spec *spec;
+
+ spec = malloc(sizeof(*spec));
+ if (spec == NULL) {
+ pr_err("call print fmt spec malloc failed\n");
+ return -ENOMEM;
+ }
+
+ spec->str = strndup(s, len);
+ if (spec->str == NULL) {
+ pr_err("call print fmt spec strndup failed\n");
+ free(spec);
+ return -ENOMEM;
+ }
+
+ spec->len = spec_len;
+ spec->type = spec_type;
+ list_add_tail(&spec->list, &priv->fmt_specs);
+ return 0;
+}
+
+static int print_fmt_spec_get_len(char *s, regoff_t len)
+{
+ if (len == 1 && !strncmp(s, "l", len))
+ return PRINT_FMT_SPEC_LEN_LONG;
+ else if (len == 2 && !strncmp(s, "ll", len))
+ return PRINT_FMT_SPEC_LEN_LONG_LONG;
+
+ return PRINT_FMT_SPEC_LEN_INT;
+}
+
+static int print_fmt_spec_get_type(char *s)
+{
+ switch (s[0]) {
+ case 'c':
+ case 'd':
+ case 'o':
+ case 'u':
+ case 'x':
+ case 'X':
+ return PRINT_FMT_SPEC_TYPE_INT;
+ case 's':
+ return PRINT_FMT_SPEC_TYPE_STRING;
+ default:
+ break;
+ }
+
+ return PRINT_FMT_SPEC_TYPE_VOID;
+}
+
+static int print_fmt_get_string(struct evtact_expr *expr, char **fmt)
+{
+ int ret, fmt_len;
+ struct evtact_expr *fmt_expr;
+
+ fmt_expr = list_first_entry_or_null(&expr->opnds, struct evtact_expr, list);
+ if (fmt_expr == NULL) {
+ pr_err("print() requires at least one argument\n");
+ return -EINVAL;
+ } else if (!is_const_str_expr(fmt_expr)) {
+ pr_err("print() first argument expected to be string\n");
+ return -EINVAL;
+ }
+
+ if (fmt_expr->ops->eval != NULL) {
+ ret = fmt_expr->ops->eval(fmt_expr, NULL, 0, (void **)fmt, &fmt_len);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static void print_fmt_free_specs(struct print_expr_priv *priv)
+{
+ struct print_fmt_spec *spec, *tmp;
+
+ if (priv == NULL)
+ return;
+
+ list_for_each_entry_safe(spec, tmp, &priv->fmt_specs, list) {
+ list_del(&spec->list);
+ free(spec->str);
+ free(spec);
+ }
+}
+
+static int print_fmt_split(struct evtact_expr *expr, struct print_expr_priv *priv)
+{
+ int i, ret;
+ char *s, *fmt;
+ regex_t regex;
+ regmatch_t pmatch[6];
+ int spec_len, spec_type;
+ const char *const re = "%(-?)([0-9]*)(\\.[0-9]+)?(ll|l)?([cdosuxX])";
+
+ ret = print_fmt_get_string(expr, &fmt);
+ if (ret)
+ return ret;
+
+ if (regcomp(&regex, re, REG_EXTENDED)) {
+ pr_err("expr call print fmt regcomp failed\n");
+ return -1;
+ }
+
+ s = fmt;
+ for (i = 0;; i++) {
+ if (regexec(&regex, s, ARRAY_SIZE(pmatch), pmatch, 0))
+ break;
+
+ spec_len = print_fmt_spec_get_len(s + pmatch[4].rm_so,
+ pmatch[4].rm_eo - pmatch[4].rm_so);
+ spec_type = print_fmt_spec_get_type(s + pmatch[5].rm_so);
+
+ ret = print_fmt_add_spec(priv, s, pmatch[0].rm_eo, spec_len, spec_type);
+ if (ret)
+ goto out_free_specs;
+
+ s += pmatch[0].rm_eo;
+ }
+
+ if ((size_t)(s - fmt) < strlen(fmt)) {
+ ret = print_fmt_add_spec(priv, s, strlen(fmt) - (s - fmt),
+ PRINT_FMT_SPEC_LEN_INT, PRINT_FMT_SPEC_TYPE_VOID);
+ if (ret)
+ goto out_free_specs;
+ }
+
+ priv->fmt_spec_num = i;
+ return 0;
+
+out_free_specs:
+ print_fmt_free_specs(priv);
+ return ret;
+}
+
+static int print_check_args(struct evtact_expr *expr, struct print_expr_priv *priv)
+{
+ struct evtact_expr *arg;
+
+ priv->args_num = 0;
+ list_for_each_entry(arg, &expr->opnds, list)
+ priv->args_num++;
+
+ /* do not count format string argument */
+ priv->args_num--;
+
+ if (priv->args_num != priv->fmt_spec_num) {
+ pr_err("print() arguments number for format string mismatch: %d expected, %d provided\n",
+ priv->fmt_spec_num, priv->args_num);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int expr_call_print_new(struct evtact_expr *expr,
+ void *data __maybe_unused, int size __maybe_unused)
+{
+ int ret;
+ struct print_expr_priv *priv;
+
+ priv = malloc(sizeof(struct print_expr_priv));
+ if (priv == NULL)
+ return -ENOMEM;
+
+ priv->out = malloc(PRINT_OUT_BUF_DEFAULT_LEN);
+ if (priv->out == NULL) {
+ ret = -ENOMEM;
+ goto out_free_priv;
+ }
+ priv->out_len = PRINT_OUT_BUF_DEFAULT_LEN;
+
+ INIT_LIST_HEAD(&priv->fmt_specs);
+ ret = print_fmt_split(expr, priv);
+ if (ret)
+ goto out_free_out_buf;
+
+ ret = print_check_args(expr, priv);
+ if (ret)
+ goto out_free_specs;
+
+ expr->priv = priv;
+ return 0;
+
+out_free_specs:
+ print_fmt_free_specs(priv);
+out_free_out_buf:
+ free(priv->out);
+ priv->out_len = 0;
+out_free_priv:
+ free(priv);
+ return ret;
+}
+
+static void expr_call_print_free(struct evtact_expr *expr)
+{
+ struct print_expr_priv *priv;
+
+ priv = expr->priv;
+ if (priv == NULL)
+ return;
+
+ print_fmt_free_specs(expr->priv);
+ zfree(&priv->out);
+ priv->out_len = 0;
+ zfree(&expr->priv);
+}
+
+static int expr_call_print_eval(struct evtact_expr *expr,
+ void *in, int in_size,
+ void **out __maybe_unused, int *out_size __maybe_unused)
+{
+ int ret, len;
+ char *buf_out;
+ int arg_val_size;
+ unsigned long long *arg_val;
+ struct evtact_expr *arg;
+ struct print_fmt_spec *spec;
+ struct print_expr_priv *priv = expr->priv;
+
+retry:
+ len = 0;
+ priv->out[0] = '\0';
+ arg = list_first_entry(&expr->opnds, struct evtact_expr, list);
+ list_for_each_entry(spec, &priv->fmt_specs, list) {
+ if (spec->type == PRINT_FMT_SPEC_TYPE_VOID) {
+ len += snprintf(priv->out + len, priv->out_len - len, "%s", spec->str);
+ } else {
+ arg = list_next_entry(arg, list);
+ if (arg == NULL) {
+ pr_err("expr call print arguments are empty\n");
+ return -EINVAL;
+ }
+
+ ret = arg->ops->eval(arg, in, in_size, (void **)&arg_val, &arg_val_size);
+ if (ret) {
+ pr_err("expr call print eval argument failed %d\n", ret);
+ return ret;
+ }
+
+ if (spec->type == PRINT_FMT_SPEC_TYPE_STRING) {
+ len += snprintf(priv->out + len, priv->out_len - len,
+ spec->str, arg_val);
+ } else if (spec->type == PRINT_FMT_SPEC_TYPE_INT) {
+ switch (spec->len) {
+ case PRINT_FMT_SPEC_LEN_INT:
+ len += snprintf(priv->out + len, priv->out_len - len,
+ spec->str, *(unsigned int *)arg_val);
+ break;
+ case PRINT_FMT_SPEC_LEN_LONG:
+ len += snprintf(priv->out + len, priv->out_len - len,
+ spec->str, *(unsigned long *)arg_val);
+ break;
+ case PRINT_FMT_SPEC_LEN_LONG_LONG:
+ len += snprintf(priv->out + len, priv->out_len - len,
+ spec->str, *(unsigned long long *)arg_val);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ if (len >= priv->out_len) {
+ buf_out = realloc(priv->out, priv->out_len << 1);
+ if (buf_out != NULL) {
+ priv->out = buf_out;
+ priv->out_len <<= 1;
+ goto retry;
+ }
+ }
+
+ if (len >= priv->out_len)
+ break;
+ }
+
+ printf("%s", priv->out);
+ return 0;
+}
+
+static struct evtact_expr_ops expr_call_print_ops = {
+ .new = expr_call_print_new,
+ .free = expr_call_print_free,
+ .eval = expr_call_print_eval,
+};
+
static struct evtact_expr_ops *expr_call_ops_list[EVTACT_EXPR_CALL_TYPE_MAX] = {
+ [EVTACT_EXPR_CALL_TYPE_PRINT] = &expr_call_print_ops,
};

static int expr_call_set_ops(struct evtact_expr *expr, u32 opcode)
diff --git a/tools/perf/util/parse-action.h b/tools/perf/util/parse-action.h
index 30c2fd6e81d0..f8aece601a84 100644
--- a/tools/perf/util/parse-action.h
+++ b/tools/perf/util/parse-action.h
@@ -21,6 +21,7 @@ enum evtact_expr_const_type {
};

enum evtact_expr_call_type {
+ EVTACT_EXPR_CALL_TYPE_PRINT,
EVTACT_EXPR_CALL_TYPE_MAX,
};

diff --git a/tools/perf/util/parse-action.l b/tools/perf/util/parse-action.l
index 189f73dfc3b1..5e75383ba3e7 100644
--- a/tools/perf/util/parse-action.l
+++ b/tools/perf/util/parse-action.l
@@ -76,6 +76,12 @@ static int str_end(void)
return STRING;
}

+static int call(u32 opcode)
+{
+ parse_action_lval.opcode = opcode;
+ return CALL;
+}
+
%}

num_dec [0-9]+
@@ -91,6 +97,9 @@ ident [_a-zA-Z][_a-zA-Z0-9]*
{num_hex} { return value(16); }
{space} { }

+
+print { return call(EVTACT_EXPR_CALL_TYPE_PRINT); }
+
";" { return SEMI; }
"(" { return LP; }
")" { return RP; }
--
2.25.1