Commit-ID: d451a205da29c5485ca634367154e83997571aa0 Gitweb: http://git.kernel.org/tip/d451a205da29c5485ca634367154e83997571aa0 Author: Ravi Bangoria <ravi.bango...@linux.vnet.ibm.com> AuthorDate: Tue, 28 Mar 2017 15:17:53 +0530 Committer: Arnaldo Carvalho de Melo <a...@redhat.com> CommitDate: Tue, 28 Mar 2017 12:25:30 -0300
perf/sdt/x86: Move OP parser to tools/perf/arch/x86/ SDT marker argument is in N@OP format. N is the size of argument and OP is the actual assembly operand. OP is arch dependent component and hence it's parsing logic also should be placed under tools/perf/arch/. Signed-off-by: Ravi Bangoria <ravi.bango...@linux.vnet.ibm.com> Acked-by: Masami Hiramatsu <mhira...@kernel.org> Cc: Alexander Shishkin <alexander.shish...@linux.intel.com> Cc: Alexis Berlemont <alexis.berlem...@gmail.com> Cc: Hemant Kumar <hem...@linux.vnet.ibm.com> Cc: Michael Ellerman <m...@ellerman.id.au> Cc: Naveen N. Rao <naveen.n....@linux.vnet.ibm.com> Cc: Peter Zijlstra <pet...@infradead.org> Link: http://lkml.kernel.org/r/20170328094754.3156-3-ravi.bango...@linux.vnet.ibm.com Signed-off-by: Arnaldo Carvalho de Melo <a...@redhat.com> --- tools/perf/arch/x86/util/perf_regs.c | 179 ++++++++++++++++++++++++++++------- tools/perf/util/perf_regs.c | 6 +- tools/perf/util/perf_regs.h | 11 ++- tools/perf/util/probe-file.c | 132 ++++++++------------------ 4 files changed, 194 insertions(+), 134 deletions(-) diff --git a/tools/perf/arch/x86/util/perf_regs.c b/tools/perf/arch/x86/util/perf_regs.c index fa1fd19..3bf3548 100644 --- a/tools/perf/arch/x86/util/perf_regs.c +++ b/tools/perf/arch/x86/util/perf_regs.c @@ -1,8 +1,10 @@ #include <string.h> +#include <regex.h> #include "../../perf.h" #include "../../util/util.h" #include "../../util/perf_regs.h" +#include "../../util/debug.h" const struct sample_reg sample_reg_masks[] = { SMPL_REG(AX, PERF_REG_X86_AX), @@ -37,7 +39,7 @@ struct sdt_name_reg { #define SDT_NAME_REG(n, m) {.sdt_name = "%" #n, .uprobe_name = "%" #m} #define SDT_NAME_REG_END {.sdt_name = NULL, .uprobe_name = NULL} -static const struct sdt_name_reg sdt_reg_renamings[] = { +static const struct sdt_name_reg sdt_reg_tbl[] = { SDT_NAME_REG(eax, ax), SDT_NAME_REG(rax, ax), SDT_NAME_REG(al, ax), @@ -95,45 +97,158 @@ static const struct sdt_name_reg sdt_reg_renamings[] = { SDT_NAME_REG_END, }; -int sdt_rename_register(char **pdesc, char *old_name) +/* + * Perf only supports OP which is in +/-NUM(REG) form. + * Here plus-minus sign, NUM and parenthesis are optional, + * only REG is mandatory. + * + * SDT events also supports indirect addressing mode with a + * symbol as offset, scaled mode and constants in OP. But + * perf does not support them yet. Below are few examples. + * + * OP with scaled mode: + * (%rax,%rsi,8) + * 10(%ras,%rsi,8) + * + * OP with indirect addressing mode: + * check_action(%rip) + * mp_+52(%rip) + * 44+mp_(%rip) + * + * OP with constant values: + * $0 + * $123 + * $-1 + */ +#define SDT_OP_REGEX "^([+\\-]?)([0-9]*)(\\(?)(%[a-z][a-z0-9]+)(\\)?)$" + +static regex_t sdt_op_regex; + +static int sdt_init_op_regex(void) { - const struct sdt_name_reg *rnames = sdt_reg_renamings; - char *new_desc, *old_desc = *pdesc; - size_t prefix_len, sdt_len, uprobe_len, old_desc_len, offset; - int ret = -1; - - while (ret != 0 && rnames->sdt_name != NULL) { - sdt_len = strlen(rnames->sdt_name); - ret = strncmp(old_name, rnames->sdt_name, sdt_len); - rnames += !!ret; - } + static int initialized; + int ret = 0; - if (rnames->sdt_name == NULL) + if (initialized) return 0; - sdt_len = strlen(rnames->sdt_name); - uprobe_len = strlen(rnames->uprobe_name); - old_desc_len = strlen(old_desc) + 1; + ret = regcomp(&sdt_op_regex, SDT_OP_REGEX, REG_EXTENDED); + if (ret < 0) { + pr_debug4("Regex compilation error.\n"); + return ret; + } - new_desc = zalloc(old_desc_len + uprobe_len - sdt_len); - if (new_desc == NULL) - return -1; + initialized = 1; + return 0; +} - /* Copy the chars before the register name (at least '%') */ - prefix_len = old_name - old_desc; - memcpy(new_desc, old_desc, prefix_len); +/* + * Max x86 register name length is 5(ex: %r15d). So, 6th char + * should always contain NULL. This helps to find register name + * length using strlen, insted of maintaing one more variable. + */ +#define SDT_REG_NAME_SIZE 6 - /* Copy the new register name */ - memcpy(new_desc + prefix_len, rnames->uprobe_name, uprobe_len); +/* + * The uprobe parser does not support all gas register names; + * so, we have to replace them (ex. for x86_64: %rax -> %ax). + * Note: If register does not require renaming, just copy + * paste as it is, but don't leave it empty. + */ +static void sdt_rename_register(char *sdt_reg, int sdt_len, char *uprobe_reg) +{ + int i = 0; - /* Copy the chars after the register name (if need be) */ - offset = prefix_len + sdt_len; - if (offset < old_desc_len) - memcpy(new_desc + prefix_len + uprobe_len, - old_desc + offset, old_desc_len - offset); + for (i = 0; sdt_reg_tbl[i].sdt_name != NULL; i++) { + if (!strncmp(sdt_reg_tbl[i].sdt_name, sdt_reg, sdt_len)) { + strcpy(uprobe_reg, sdt_reg_tbl[i].uprobe_name); + return; + } + } - free(old_desc); - *pdesc = new_desc; + strncpy(uprobe_reg, sdt_reg, sdt_len); +} - return 0; +int arch_sdt_arg_parse_op(char *old_op, char **new_op) +{ + char new_reg[SDT_REG_NAME_SIZE] = {0}; + int new_len = 0, ret; + /* + * rm[0]: +/-NUM(REG) + * rm[1]: +/- + * rm[2]: NUM + * rm[3]: ( + * rm[4]: REG + * rm[5]: ) + */ + regmatch_t rm[6]; + /* + * Max prefix length is 2 as it may contains sign(+/-) + * and displacement 0 (Both sign and displacement 0 are + * optional so it may be empty). Use one more character + * to hold last NULL so that strlen can be used to find + * prefix length, instead of maintaing one more variable. + */ + char prefix[3] = {0}; + + ret = sdt_init_op_regex(); + if (ret < 0) + return ret; + + /* + * If unsupported OR does not match with regex OR + * register name too long, skip it. + */ + if (strchr(old_op, ',') || strchr(old_op, '$') || + regexec(&sdt_op_regex, old_op, 6, rm, 0) || + rm[4].rm_eo - rm[4].rm_so > SDT_REG_NAME_SIZE) { + pr_debug4("Skipping unsupported SDT argument: %s\n", old_op); + return SDT_ARG_SKIP; + } + + /* + * Prepare prefix. + * If SDT OP has parenthesis but does not provide + * displacement, add 0 for displacement. + * SDT Uprobe Prefix + * ----------------------------- + * +24(%rdi) +24(%di) + + * 24(%rdi) +24(%di) + + * %rdi %di + * (%rdi) +0(%di) +0 + * -80(%rbx) -80(%bx) - + */ + if (rm[3].rm_so != rm[3].rm_eo) { + if (rm[1].rm_so != rm[1].rm_eo) + prefix[0] = *(old_op + rm[1].rm_so); + else if (rm[2].rm_so != rm[2].rm_eo) + prefix[0] = '+'; + else + strncpy(prefix, "+0", 2); + } + + /* Rename register */ + sdt_rename_register(old_op + rm[4].rm_so, rm[4].rm_eo - rm[4].rm_so, + new_reg); + + /* Prepare final OP which should be valid for uprobe_events */ + new_len = strlen(prefix) + + (rm[2].rm_eo - rm[2].rm_so) + + (rm[3].rm_eo - rm[3].rm_so) + + strlen(new_reg) + + (rm[5].rm_eo - rm[5].rm_so) + + 1; /* NULL */ + + *new_op = zalloc(new_len); + if (!*new_op) + return -ENOMEM; + + scnprintf(*new_op, new_len, "%.*s%.*s%.*s%.*s%.*s", + strlen(prefix), prefix, + (int)(rm[2].rm_eo - rm[2].rm_so), old_op + rm[2].rm_so, + (int)(rm[3].rm_eo - rm[3].rm_so), old_op + rm[3].rm_so, + strlen(new_reg), new_reg, + (int)(rm[5].rm_eo - rm[5].rm_so), old_op + rm[5].rm_so); + + return SDT_ARG_VALID; } diff --git a/tools/perf/util/perf_regs.c b/tools/perf/util/perf_regs.c index a37e593..b2ae039 100644 --- a/tools/perf/util/perf_regs.c +++ b/tools/perf/util/perf_regs.c @@ -6,10 +6,10 @@ const struct sample_reg __weak sample_reg_masks[] = { SMPL_REG_END }; -int __weak sdt_rename_register(char **pdesc __maybe_unused, - char *old_name __maybe_unused) +int __weak arch_sdt_arg_parse_op(char *old_op __maybe_unused, + char **new_op __maybe_unused) { - return 0; + return SDT_ARG_SKIP; } #ifdef HAVE_PERF_REGS_SUPPORT diff --git a/tools/perf/util/perf_regs.h b/tools/perf/util/perf_regs.h index 7544a15..32b37d1 100644 --- a/tools/perf/util/perf_regs.h +++ b/tools/perf/util/perf_regs.h @@ -15,11 +15,12 @@ struct sample_reg { extern const struct sample_reg sample_reg_masks[]; -/* - * The table sdt_reg_renamings is used for adjusting gcc/gas-generated - * registers before filling the uprobe tracer interface. - */ -int sdt_rename_register(char **pdesc, char *old_name); +enum { + SDT_ARG_VALID = 0, + SDT_ARG_SKIP, +}; + +int arch_sdt_arg_parse_op(char *old_op, char **new_op); #ifdef HAVE_PERF_REGS_SUPPORT #include <perf_regs.h> diff --git a/tools/perf/util/probe-file.c b/tools/perf/util/probe-file.c index d741634..88714de 100644 --- a/tools/perf/util/probe-file.c +++ b/tools/perf/util/probe-file.c @@ -694,10 +694,29 @@ static const char * const type_to_suffix[] = { "", ":u8", ":u16", "", ":u32", "", "", "", ":u64" }; +/* + * Isolate the string number and convert it into a decimal value; + * this will be an index to get suffix of the uprobe name (defining + * the type) + */ +static int sdt_arg_parse_size(char *n_ptr, const char **suffix) +{ + long type_idx; + + type_idx = strtol(n_ptr, NULL, 10); + if (type_idx < -8 || type_idx > 8) { + pr_debug4("Failed to get a valid sdt type\n"); + return -1; + } + + *suffix = type_to_suffix[type_idx + 8]; + return 0; +} + static int synthesize_sdt_probe_arg(struct strbuf *buf, int i, const char *arg) { - char *tmp, *desc = strdup(arg); - const char *prefix = "", *suffix = ""; + char *op, *desc = strdup(arg), *new_op = NULL; + const char *suffix = ""; int ret = -1; if (desc == NULL) { @@ -705,112 +724,37 @@ static int synthesize_sdt_probe_arg(struct strbuf *buf, int i, const char *arg) return ret; } - tmp = strchr(desc, '@'); - if (tmp) { - long type_idx; - /* - * Isolate the string number and convert it into a - * binary value; this will be an index to get suffix - * of the uprobe name (defining the type) - */ - tmp[0] = '\0'; - type_idx = strtol(desc, NULL, 10); - /* Check that the conversion went OK */ - if (type_idx == LONG_MIN || type_idx == LONG_MAX) { - pr_debug4("Failed to parse sdt type\n"); - goto error; - } - /* Check that the converted value is OK */ - if (type_idx < -8 || type_idx > 8) { - pr_debug4("Failed to get a valid sdt type\n"); - goto error; - } - suffix = type_to_suffix[type_idx + 8]; - /* Get rid of the sdt prefix which is now useless */ - tmp++; - memmove(desc, tmp, strlen(tmp) + 1); - } - /* - * The uprobe tracer format does not support all the - * addressing modes (notably: in x86 the scaled mode); so, we - * detect ',' characters, if there is just one, there is no - * use converting the sdt arg into a uprobe one. + * Argument is in N@OP format. N is size of the argument and OP is + * the actual assembly operand. N can be omitted; in that case + * argument is just OP(without @). */ - if (strchr(desc, ',')) { - pr_debug4("Skipping unsupported SDT argument; %s\n", desc); - goto out; - } + op = strchr(desc, '@'); + if (op) { + op[0] = '\0'; + op++; - /* - * If the argument addressing mode is indirect, we must check - * a few things... - */ - tmp = strchr(desc, '('); - if (tmp) { - int j; - - /* - * ...if the addressing mode is indirect with a - * positive offset (ex.: "1608(%ax)"), we need to add - * a '+' prefix so as to be compliant with uprobe - * format. - */ - if (desc[0] != '+' && desc[0] != '-') - prefix = "+"; - - /* - * ...or if the addressing mode is indirect with a symbol - * as offset, the argument will not be supported by - * the uprobe tracer format; so, let's skip this one. - */ - for (j = 0; j < tmp - desc; j++) { - if (desc[j] != '+' && desc[j] != '-' && - !isdigit(desc[j])) { - pr_debug4("Skipping unsupported SDT argument; " - "%s\n", desc); - goto out; - } - } + if (sdt_arg_parse_size(desc, &suffix)) + goto error; + } else { + op = desc; } - /* - * The uprobe tracer format does not support constants; if we - * find one in the current argument, let's skip the argument. - */ - if (strchr(desc, '$')) { - pr_debug4("Skipping unsupported SDT argument; %s\n", desc); - goto out; - } + ret = arch_sdt_arg_parse_op(op, &new_op); - /* - * The uprobe parser does not support all gas register names; - * so, we have to replace them (ex. for x86_64: %rax -> %ax); - * the loop below looks for the register names (starting with - * a '%' and tries to perform the needed renamings. - */ - tmp = strchr(desc, '%'); - while (tmp) { - size_t offset = tmp - desc; + if (ret < 0) + goto error; - ret = sdt_rename_register(&desc, desc + offset); + if (ret == SDT_ARG_VALID) { + ret = strbuf_addf(buf, " arg%d=%s%s", i + 1, new_op, suffix); if (ret < 0) goto error; - - /* - * The desc pointer might have changed; so, let's not - * try to reuse tmp for next lookup - */ - tmp = strchr(desc + offset + 1, '%'); } - if (strbuf_addf(buf, " arg%d=%s%s%s", i + 1, prefix, desc, suffix) < 0) - goto error; - -out: ret = 0; error: free(desc); + free(new_op); return ret; }