Release 4.10 tools/perf/util/probe-finder.c
/*
* probe-finder.c : C expression to kprobe event converter
*
* Written by Masami Hiramatsu <mhiramat@redhat.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
*/
#include <sys/utsname.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <dwarf-regs.h>
#include <linux/bitops.h>
#include "event.h"
#include "dso.h"
#include "debug.h"
#include "intlist.h"
#include "util.h"
#include "symbol.h"
#include "probe-finder.h"
#include "probe-file.h"
/* Kprobe tracer basic type is up to u64 */
#define MAX_BASIC_TYPE_BITS 64
/* Dwarf FL wrappers */
static char *debuginfo_path;
/* Currently dummy */
static const Dwfl_Callbacks offline_callbacks = {
.find_debuginfo = dwfl_standard_find_debuginfo,
.debuginfo_path = &debuginfo_path,
.section_address = dwfl_offline_section_address,
/* We use this table for core files too. */
.find_elf = dwfl_build_id_find_elf,
};
/* Get a Dwarf from offline image */
static int debuginfo__init_offline_dwarf(struct debuginfo *dbg,
const char *path)
{
int fd;
fd = open(path, O_RDONLY);
if (fd < 0)
return fd;
dbg->dwfl = dwfl_begin(&offline_callbacks);
if (!dbg->dwfl)
goto error;
dwfl_report_begin(dbg->dwfl);
dbg->mod = dwfl_report_offline(dbg->dwfl, "", "", fd);
if (!dbg->mod)
goto error;
dbg->dbg = dwfl_module_getdwarf(dbg->mod, &dbg->bias);
if (!dbg->dbg)
goto error;
dwfl_report_end(dbg->dwfl, NULL, NULL);
return 0;
error:
if (dbg->dwfl)
dwfl_end(dbg->dwfl);
else
close(fd);
memset(dbg, 0, sizeof(*dbg));
return -ENOENT;
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
masami hiramatsu | masami hiramatsu | 155 | 91.72% | 4 | 80.00% |
arnaldo carvalho de melo | arnaldo carvalho de melo | 14 | 8.28% | 1 | 20.00% |
| Total | 169 | 100.00% | 5 | 100.00% |
static struct debuginfo *__debuginfo__new(const char *path)
{
struct debuginfo *dbg = zalloc(sizeof(*dbg));
if (!dbg)
return NULL;
if (debuginfo__init_offline_dwarf(dbg, path) < 0)
zfree(&dbg);
if (dbg)
pr_debug("Open Debuginfo file: %s\n", path);
return dbg;
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
masami hiramatsu | masami hiramatsu | 57 | 86.36% | 4 | 66.67% |
arnaldo carvalho de melo | arnaldo carvalho de melo | 9 | 13.64% | 2 | 33.33% |
| Total | 66 | 100.00% | 6 | 100.00% |
enum dso_binary_type distro_dwarf_types[] = {
DSO_BINARY_TYPE__FEDORA_DEBUGINFO,
DSO_BINARY_TYPE__UBUNTU_DEBUGINFO,
DSO_BINARY_TYPE__OPENEMBEDDED_DEBUGINFO,
DSO_BINARY_TYPE__BUILDID_DEBUGINFO,
DSO_BINARY_TYPE__NOT_FOUND,
};
struct debuginfo *debuginfo__new(const char *path)
{
enum dso_binary_type *type;
char buf[PATH_MAX], nil = '\0';
struct dso *dso;
struct debuginfo *dinfo = NULL;
/* Try to open distro debuginfo files */
dso = dso__new(path);
if (!dso)
goto out;
for (type = distro_dwarf_types;
!dinfo && *type != DSO_BINARY_TYPE__NOT_FOUND;
type++) {
if (dso__read_binary_type_filename(dso, *type, &nil,
buf, PATH_MAX) < 0)
continue;
dinfo = __debuginfo__new(buf);
}
dso__put(dso);
out:
/* if failed to open all distro debuginfo, open given binary */
return dinfo ? : __debuginfo__new(path);
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
masami hiramatsu | masami hiramatsu | 117 | 99.15% | 1 | 50.00% |
arnaldo carvalho de melo | arnaldo carvalho de melo | 1 | 0.85% | 1 | 50.00% |
| Total | 118 | 100.00% | 2 | 100.00% |
void debuginfo__delete(struct debuginfo *dbg)
{
if (dbg) {
if (dbg->dwfl)
dwfl_end(dbg->dwfl);
free(dbg);
}
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
masami hiramatsu | masami hiramatsu | 29 | 85.29% | 1 | 50.00% |
arnaldo carvalho de melo | arnaldo carvalho de melo | 5 | 14.71% | 1 | 50.00% |
| Total | 34 | 100.00% | 2 | 100.00% |
/*
* Probe finder related functions
*/
static struct probe_trace_arg_ref *alloc_trace_arg_ref(long offs)
{
struct probe_trace_arg_ref *ref;
ref = zalloc(sizeof(struct probe_trace_arg_ref));
if (ref != NULL)
ref->offset = offs;
return ref;
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
masami hiramatsu | masami hiramatsu | 42 | 100.00% | 5 | 100.00% |
| Total | 42 | 100.00% | 5 | 100.00% |
/*
* Convert a location into trace_arg.
* If tvar == NULL, this just checks variable can be converted.
* If fentry == true and vr_die is a parameter, do huristic search
* for the location fuzzed by function entry mcount.
*/
static int convert_variable_location(Dwarf_Die *vr_die, Dwarf_Addr addr,
Dwarf_Op *fb_ops, Dwarf_Die *sp_die,
unsigned int machine,
struct probe_trace_arg *tvar)
{
Dwarf_Attribute attr;
Dwarf_Addr tmp = 0;
Dwarf_Op *op;
size_t nops;
unsigned int regn;
Dwarf_Word offs = 0;
bool ref = false;
const char *regs;
int ret, ret2 = 0;
if (dwarf_attr(vr_die, DW_AT_external, &attr) != NULL)
goto static_var;
/* TODO: handle more than 1 exprs */
if (dwarf_attr(vr_die, DW_AT_location, &attr) == NULL)
return -EINVAL; /* Broken DIE ? */
if (dwarf_getlocation_addr(&attr, addr, &op, &nops, 1) <= 0) {
ret = dwarf_entrypc(sp_die, &tmp);
if (ret)
return -ENOENT;
if (probe_conf.show_location_range &&
(dwarf_tag(vr_die) == DW_TAG_variable)) {
ret2 = -ERANGE;
} else if (addr != tmp ||
dwarf_tag(vr_die) != DW_TAG_formal_parameter) {
return -ENOENT;
}
ret = dwarf_highpc(sp_die, &tmp);
if (ret)
return -ENOENT;
/*
* This is fuzzed by fentry mcount. We try to find the
* parameter location at the earliest address.
*/
for (addr += 1; addr <= tmp; addr++) {
if (dwarf_getlocation_addr(&attr, addr, &op,
&nops, 1) > 0)
goto found;
}
return -ENOENT;
}
found:
if (nops == 0)
/* TODO: Support const_value */
return -ENOENT;
if (op->atom == DW_OP_addr) {
static_var:
if (!tvar)
return ret2;
/* Static variables on memory (not stack), make @varname */
ret = strlen(dwarf_diename(vr_die));
tvar->value = zalloc(ret + 2);
if (tvar->value == NULL)
return -ENOMEM;
snprintf(tvar->value, ret + 2, "@%s", dwarf_diename(vr_die));
tvar->ref = alloc_trace_arg_ref((long)offs);
if (tvar->ref == NULL)
return -ENOMEM;
return ret2;
}
/* If this is based on frame buffer, set the offset */
if (op->atom == DW_OP_fbreg) {
if (fb_ops == NULL)
return -ENOTSUP;
ref = true;
offs = op->number;
op = &fb_ops[0];
}
if (op->atom >= DW_OP_breg0 && op->atom <= DW_OP_breg31) {
regn = op->atom - DW_OP_breg0;
offs += op->number;
ref = true;
} else if (op->atom >= DW_OP_reg0 && op->atom <= DW_OP_reg31) {
regn = op->atom - DW_OP_reg0;
} else if (op->atom == DW_OP_bregx) {
regn = op->number;
offs += op->number2;
ref = true;
} else if (op->atom == DW_OP_regx) {
regn = op->number;
} else {
pr_debug("DW_OP %x is not supported.\n", op->atom);
return -ENOTSUP;
}
if (!tvar)
return ret2;
regs = get_dwarf_regstr(regn, machine);
if (!regs) {
/* This should be a bug in DWARF or this tool */
pr_warning("Mapping for the register number %u "
"missing on this architecture.\n", regn);
return -ENOTSUP;
}
tvar->value = strdup(regs);
if (tvar->value == NULL)
return -ENOMEM;
if (ref) {
tvar->ref = alloc_trace_arg_ref((long)offs);
if (tvar->ref == NULL)
return -ENOMEM;
}
return ret2;
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
masami hiramatsu | masami hiramatsu | 560 | 91.50% | 8 | 88.89% |
he kuang | he kuang | 52 | 8.50% | 1 | 11.11% |
| Total | 612 | 100.00% | 9 | 100.00% |
#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 probe_trace_arg_ref **ref_ptr = &tvar->ref;
Dwarf_Die type;
char buf[16];
char sbuf[STRERR_BUFSIZE];
int bsize, boffs, total;
int ret;
char prefix;
/* TODO: check all types */
if (cast && strcmp(cast, "string") != 0 && strcmp(cast, "x") != 0 &&
strcmp(cast, "s") != 0 && strcmp(cast, "u") != 0) {
/* Non string type is OK */
/* and respect signedness/hexadecimal cast */
tvar->type = strdup(cast);
return (tvar->type == NULL) ? -ENOMEM : 0;
}
bsize = dwarf_bitsize(vr_die);
if (bsize > 0) {
/* This is a bitfield */
boffs = dwarf_bitoffset(vr_die);
total = dwarf_bytesize(vr_die);
if (boffs < 0 || total < 0)
return -ENOENT;
ret = snprintf(buf, 16, "b%d@%d/%zd", bsize, boffs,
BYTES_TO_BITS(total));
goto formatted;
}
if (die_get_real_type(vr_die, &type) == NULL) {
pr_warning("Failed to get a type information of %s.\n",
dwarf_diename(vr_die));
return -ENOENT;
}
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, "u") == 0))
prefix = 'u';
else if (cast && (strcmp(cast, "s") == 0))
prefix = 's';
else if (cast && (strcmp(cast, "x") == 0) &&
probe_type_is_available(PROBE_TYPE_X))
prefix = 'x';
else
prefix = die_is_signed_type(&type) ? 's' :
probe_type_is_available(PROBE_TYPE_X) ? 'x' : 'u';
ret = dwarf_bytesize(&type);
if (ret <= 0)
/* No size ... try to use default type */
return 0;
ret = BYTES_TO_BITS(ret);
/* Check the bitwidth */
if (ret > MAX_BASIC_TYPE_BITS) {
pr_info("%s exceeds max-bitwidth. Cut down to %d bits.\n",
dwarf_diename(&type), MAX_BASIC_TYPE_BITS);
ret = MAX_BASIC_TYPE_BITS;
}
ret = snprintf(buf, 16, "%c%d", prefix, ret);
formatted:
if (ret < 0 || ret >= 16) {
if (ret >= 16)
ret = -E2BIG;
pr_warning("Failed to convert variable type: %s\n",
str_error_r(-ret, sbuf, sizeof(sbuf)));
return ret;
}
tvar->type = strdup(buf);
if (tvar->type == NULL)
return -ENOMEM;
return 0;
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
masami hiramatsu | masami hiramatsu | 577 | 88.23% | 19 | 86.36% |
naohiro aota | naohiro aota | 69 | 10.55% | 1 | 4.55% |
hyeoncheol lee | hyeoncheol lee | 7 | 1.07% | 1 | 4.55% |
arnaldo carvalho de melo | arnaldo carvalho de melo | 1 | 0.15% | 1 | 4.55% |
| Total | 654 | 100.00% | 22 | 100.00% |
static int convert_variable_fields(Dwarf_Die *vr_die, const char *varname,
struct perf_probe_arg_field *field,
struct probe_trace_arg_ref **ref_ptr,
Dwarf_Die *die_mem)
{
struct probe_trace_arg_ref *ref = *ref_ptr;
Dwarf_Die type;
Dwarf_Word offs;
int ret, tag;
pr_debug("converting %s in %s\n", field->name, varname);
if (die_get_real_type(vr_die, &type) == NULL) {
pr_warning("Failed to get the type of %s.\n", varname);
return -ENOENT;
}
pr_debug2("Var real type: (%x)\n", (unsigned)dwarf_dieoffset(&type));
tag = dwarf_tag(&type);
if (field->name[0] == '[' &&
(tag == DW_TAG_array_type || tag == DW_TAG_pointer_type)) {
if (field->next)
/* Save original type for next field */
memcpy(die_mem, &type, sizeof(*die_mem));
/* Get the type of this array */
if (die_get_real_type(&type, &type) == NULL) {
pr_warning("Failed to get the type of %s.\n", varname);
return -ENOENT;
}
pr_debug2("Array real type: (%x)\n",
(unsigned)dwarf_dieoffset(&type));
if (tag == DW_TAG_pointer_type) {
ref = zalloc(sizeof(struct probe_trace_arg_ref));
if (ref == NULL)
return -ENOMEM;
if (*ref_ptr)
(*ref_ptr)->next = ref;
else
*ref_ptr = ref;
}
ref->offset += dwarf_bytesize(&type) * field->index;
if (!field->next)
/* Save vr_die for converting types */
memcpy(die_mem, vr_die, sizeof(*die_mem));
goto next;
} else if (tag == DW_TAG_pointer_type) {
/* Check the pointer and dereference */
if (!field->ref) {
pr_err("Semantic error: %s must be referred by '->'\n",
field->name);
return -EINVAL;
}
/* Get the type pointed by this pointer */
if (die_get_real_type(&type, &type) == NULL) {
pr_warning("Failed to get the type of %s.\n", varname);
return -ENOENT;
}
/* Verify it is a data structure */
tag = dwarf_tag(&type);
if (tag != DW_TAG_structure_type && tag != DW_TAG_union_type) {
pr_warning("%s is not a data structure nor an union.\n",
varname);
return -EINVAL;
}
ref = zalloc(sizeof(struct probe_trace_arg_ref));
if (ref == NULL)
return -ENOMEM;
if (*ref_ptr)
(*ref_ptr)->next = ref;
else
*ref_ptr = ref;
} else {
/* Verify it is a data structure */
if (tag != DW_TAG_structure_type && tag != DW_TAG_union_type) {
pr_warning("%s is not a data structure nor an union.\n",
varname);
return -EINVAL;
}
if (field->name[0] == '[') {
pr_err("Semantic error: %s is not a pointer"
" nor array.\n", varname);
return -EINVAL;
}
/* While prcessing unnamed field, we don't care about this */
if (field->ref && dwarf_diename(vr_die)) {
pr_err("Semantic error: %s must be referred by '.'\n",
field->name);
return -EINVAL;
}
if (!ref) {
pr_warning("Structure on a register is not "
"supported yet.\n");
return -ENOTSUP;
}
}
if (die_find_member(&type, field->name, die_mem) == NULL) {
pr_warning("%s(type:%s) has no member %s.\n", varname,
dwarf_diename(&type), field->name);
return -EINVAL;
}
/* Get the offset of the field */
if (tag == DW_TAG_union_type) {
offs = 0;
} else {
ret = die_get_data_member_location(die_mem, &offs);
if (ret < 0) {
pr_warning("Failed to get the offset of %s.\n",
field->name);
return ret;
}
}
ref->offset += (long)offs;
/* If this member is unnamed, we need to reuse this field */
if (!dwarf_diename(die_mem))
return convert_variable_fields(die_mem, varname, field,
&ref, die_mem);
next:
/* Converting next field */
if (field->next)
return convert_variable_fields(die_mem, field->name,
field->next, &ref, die_mem);
else
return 0;
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
masami hiramatsu | masami hiramatsu | 624 | 94.98% | 16 | 84.21% |
hyeoncheol lee | hyeoncheol lee | 31 | 4.72% | 1 | 5.26% |
masanari iida | masanari iida | 1 | 0.15% | 1 | 5.26% |
arnaldo carvalho de melo | arnaldo carvalho de melo | 1 | 0.15% | 1 | 5.26% |
| Total | 657 | 100.00% | 19 | 100.00% |
/* Show a variables in kprobe event format */
static int convert_variable(Dwarf_Die *vr_die, struct probe_finder *pf)
{
Dwarf_Die die_mem;
int ret;
pr_debug("Converting variable %s into trace event.\n",
dwarf_diename(vr_die));
ret = convert_variable_location(vr_die, pf->addr, pf->fb_ops,
&pf->sp_die, pf->machine, pf->tvar);
if (ret == -ENOENT || ret == -EINVAL) {
pr_err("Failed to find the location of the '%s' variable at this address.\n"
" Perhaps it has been optimized out.\n"
" Use -V with the --range option to show '%s' location range.\n",
pf->pvar->var, pf->pvar->var);
} else if (ret == -ENOTSUP)
pr_err("Sorry, we don't support this variable location yet.\n");
else if (ret == 0 && pf->pvar->field) {
ret = convert_variable_fields(vr_die, pf->pvar->var,
pf->pvar->field, &pf->tvar->ref,
&die_mem);
vr_die = &die_mem;
}
if (ret == 0)
ret = convert_variable_type(vr_die, pf->tvar, pf->pvar->type);
/* *expr will be cached in libdw. Don't free it. */
return ret;
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
masami hiramatsu | masami hiramatsu | 170 | 93.92% | 9 | 90.00% |
he kuang | he kuang | 11 | 6.08% | 1 | 10.00% |
| Total | 181 | 100.00% | 10 | 100.00% |
/* Find a variable in a scope DIE */
static int find_variable(Dwarf_Die *sc_die, struct probe_finder *pf)
{
Dwarf_Die vr_die;
char *buf, *ptr;
int ret = 0;
/* Copy raw parameters */
if (!is_c_varname(pf->pvar->var))
return copy_to_probe_trace_arg(pf->tvar, pf->pvar);
if (pf->pvar->name)
pf->tvar->name = strdup(pf->pvar->name);
else {
buf = synthesize_perf_probe_arg(pf->pvar);
if (!buf)
return -ENOMEM;
ptr = strchr(buf, ':'); /* Change type separator to _ */
if (ptr)
*ptr = '_';
pf->tvar->name = buf;
}
if (pf->tvar->name == NULL)
return -ENOMEM;
pr_debug("Searching '%s' variable in context.\n", pf->pvar->var);
/* Search child die for local variables and parameters. */
if (!die_find_variable_at(sc_die, pf->pvar->var, pf->addr, &vr_die)) {
/* Search again in global variables */
if (!die_find_variable_at(&pf->cu_die, pf->pvar->var,
0, &vr_die)) {
pr_warning("Failed to find '%s' in this function.\n",
pf->pvar->var);
ret = -ENOENT;
}
}
if (ret >= 0)
ret = convert_variable(&vr_die, pf);
return ret;
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
masami hiramatsu | masami hiramatsu | 226 | 96.17% | 9 | 75.00% |
arnaldo carvalho de melo | arnaldo carvalho de melo | 4 | 1.70% | 1 | 8.33% |
wang nan | wang nan | 3 | 1.28% | 1 | 8.33% |
he kuang | he kuang | 2 | 0.85% | 1 | 8.33% |
| Total | 235 | 100.00% | 12 | 100.00% |
/* Convert subprogram DIE to trace point */
static int convert_to_trace_point(Dwarf_Die *sp_die, Dwfl_Module *mod,
Dwarf_Addr paddr, bool retprobe