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.bangoria@linux.vnet.ibm.com>
Acked-by: Masami Hiramatsu <mhiramat@kernel.org>
Cc: Alexander Shishkin <alexander.shishkin@linux.intel.com>
Cc: Alexis Berlemont <alexis.berlemont@gmail.com>
Cc: Hemant Kumar <hemant@linux.vnet.ibm.com>
Cc: Michael Ellerman <mpe@ellerman.id.au>
Cc: Naveen N. Rao <naveen.n.rao@linux.vnet.ibm.com>
Cc: Peter Zijlstra <peterz@infradead.org>
Link: http://lkml.kernel.org/r/20170328094754.3156-3-ravi.bangoria@linux.vnet.ibm.com
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
This commit is contained in:
Ravi Bangoria 2017-03-28 15:17:53 +05:30 committed by Arnaldo Carvalho de Melo
parent 2d01ecc580
commit d451a205da
4 changed files with 203 additions and 143 deletions

View File

@ -1,8 +1,10 @@
#include <string.h> #include <string.h>
#include <regex.h>
#include "../../perf.h" #include "../../perf.h"
#include "../../util/util.h" #include "../../util/util.h"
#include "../../util/perf_regs.h" #include "../../util/perf_regs.h"
#include "../../util/debug.h"
const struct sample_reg sample_reg_masks[] = { const struct sample_reg sample_reg_masks[] = {
SMPL_REG(AX, PERF_REG_X86_AX), 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(n, m) {.sdt_name = "%" #n, .uprobe_name = "%" #m}
#define SDT_NAME_REG_END {.sdt_name = NULL, .uprobe_name = NULL} #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(eax, ax),
SDT_NAME_REG(rax, ax), SDT_NAME_REG(rax, ax),
SDT_NAME_REG(al, ax), SDT_NAME_REG(al, ax),
@ -95,45 +97,158 @@ static const struct sdt_name_reg sdt_reg_renamings[] = {
SDT_NAME_REG_END, 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; static int initialized;
char *new_desc, *old_desc = *pdesc; int ret = 0;
size_t prefix_len, sdt_len, uprobe_len, old_desc_len, offset;
int ret = -1;
while (ret != 0 && rnames->sdt_name != NULL) { if (initialized)
sdt_len = strlen(rnames->sdt_name);
ret = strncmp(old_name, rnames->sdt_name, sdt_len);
rnames += !!ret;
}
if (rnames->sdt_name == NULL)
return 0; return 0;
sdt_len = strlen(rnames->sdt_name); ret = regcomp(&sdt_op_regex, SDT_OP_REGEX, REG_EXTENDED);
uprobe_len = strlen(rnames->uprobe_name); if (ret < 0) {
old_desc_len = strlen(old_desc) + 1; pr_debug4("Regex compilation error.\n");
return ret;
new_desc = zalloc(old_desc_len + uprobe_len - sdt_len); }
if (new_desc == NULL)
return -1;
/* Copy the chars before the register name (at least '%') */
prefix_len = old_name - old_desc;
memcpy(new_desc, old_desc, prefix_len);
/* Copy the new register name */
memcpy(new_desc + prefix_len, rnames->uprobe_name, uprobe_len);
/* 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);
free(old_desc);
*pdesc = new_desc;
initialized = 1;
return 0; return 0;
} }
/*
* 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
/*
* 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;
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;
}
}
strncpy(uprobe_reg, sdt_reg, sdt_len);
}
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;
}

View File

@ -6,10 +6,10 @@ const struct sample_reg __weak sample_reg_masks[] = {
SMPL_REG_END SMPL_REG_END
}; };
int __weak sdt_rename_register(char **pdesc __maybe_unused, int __weak arch_sdt_arg_parse_op(char *old_op __maybe_unused,
char *old_name __maybe_unused) char **new_op __maybe_unused)
{ {
return 0; return SDT_ARG_SKIP;
} }
#ifdef HAVE_PERF_REGS_SUPPORT #ifdef HAVE_PERF_REGS_SUPPORT

View File

@ -15,11 +15,12 @@ struct sample_reg {
extern const struct sample_reg sample_reg_masks[]; extern const struct sample_reg sample_reg_masks[];
/* enum {
* The table sdt_reg_renamings is used for adjusting gcc/gas-generated SDT_ARG_VALID = 0,
* registers before filling the uprobe tracer interface. SDT_ARG_SKIP,
*/ };
int sdt_rename_register(char **pdesc, char *old_name);
int arch_sdt_arg_parse_op(char *old_op, char **new_op);
#ifdef HAVE_PERF_REGS_SUPPORT #ifdef HAVE_PERF_REGS_SUPPORT
#include <perf_regs.h> #include <perf_regs.h>

View File

@ -694,10 +694,29 @@ static const char * const type_to_suffix[] = {
"", ":u8", ":u16", "", ":u32", "", "", "", ":u64" "", ":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) static int synthesize_sdt_probe_arg(struct strbuf *buf, int i, const char *arg)
{ {
char *tmp, *desc = strdup(arg); char *op, *desc = strdup(arg), *new_op = NULL;
const char *prefix = "", *suffix = ""; const char *suffix = "";
int ret = -1; int ret = -1;
if (desc == NULL) { if (desc == NULL) {
@ -705,112 +724,37 @@ static int synthesize_sdt_probe_arg(struct strbuf *buf, int i, const char *arg)
return ret; return ret;
} }
tmp = strchr(desc, '@'); /*
if (tmp) { * Argument is in N@OP format. N is size of the argument and OP is
long type_idx; * the actual assembly operand. N can be omitted; in that case
/* * argument is just OP(without @).
* Isolate the string number and convert it into a */
* binary value; this will be an index to get suffix op = strchr(desc, '@');
* of the uprobe name (defining the type) if (op) {
*/ op[0] = '\0';
tmp[0] = '\0'; op++;
type_idx = strtol(desc, NULL, 10);
/* Check that the conversion went OK */ if (sdt_arg_parse_size(desc, &suffix))
if (type_idx == LONG_MIN || type_idx == LONG_MAX) {
pr_debug4("Failed to parse sdt type\n");
goto error; goto error;
} } else {
/* Check that the converted value is OK */ op = desc;
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);
} }
/* ret = arch_sdt_arg_parse_op(op, &new_op);
* 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.
*/
if (strchr(desc, ',')) {
pr_debug4("Skipping unsupported SDT argument; %s\n", desc);
goto out;
}
/* if (ret < 0)
* 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;
}
}
}
/*
* 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;
}
/*
* 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;
ret = sdt_rename_register(&desc, desc + offset);
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; goto error;
out: if (ret == SDT_ARG_VALID) {
ret = strbuf_addf(buf, " arg%d=%s%s", i + 1, new_op, suffix);
if (ret < 0)
goto error;
}
ret = 0; ret = 0;
error: error:
free(desc); free(desc);
free(new_op);
return ret; return ret;
} }