cregit-Linux how code gets into the kernel

Release 4.10 tools/perf/util/probe-finder.c

Directory: tools/perf/util
/*
 * 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

PersonTokensPropCommitsCommitProp
masami hiramatsumasami hiramatsu15591.72%480.00%
arnaldo carvalho de meloarnaldo carvalho de melo148.28%120.00%
Total169100.00%5100.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

PersonTokensPropCommitsCommitProp
masami hiramatsumasami hiramatsu5786.36%466.67%
arnaldo carvalho de meloarnaldo carvalho de melo913.64%233.33%
Total66100.00%6100.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

PersonTokensPropCommitsCommitProp
masami hiramatsumasami hiramatsu11799.15%150.00%
arnaldo carvalho de meloarnaldo carvalho de melo10.85%150.00%
Total118100.00%2100.00%


void debuginfo__delete(struct debuginfo *dbg) { if (dbg) { if (dbg->dwfl) dwfl_end(dbg->dwfl); free(dbg); } }

Contributors

PersonTokensPropCommitsCommitProp
masami hiramatsumasami hiramatsu2985.29%150.00%
arnaldo carvalho de meloarnaldo carvalho de melo514.71%150.00%
Total34100.00%2100.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

PersonTokensPropCommitsCommitProp
masami hiramatsumasami hiramatsu42100.00%5100.00%
Total42100.00%5100.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

PersonTokensPropCommitsCommitProp
masami hiramatsumasami hiramatsu56091.50%888.89%
he kuanghe kuang528.50%111.11%
Total612100.00%9100.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

PersonTokensPropCommitsCommitProp
masami hiramatsumasami hiramatsu57788.23%1986.36%
naohiro aotanaohiro aota6910.55%14.55%
hyeoncheol leehyeoncheol lee71.07%14.55%
arnaldo carvalho de meloarnaldo carvalho de melo10.15%14.55%
Total654100.00%22100.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

PersonTokensPropCommitsCommitProp
masami hiramatsumasami hiramatsu62494.98%1684.21%
hyeoncheol leehyeoncheol lee314.72%15.26%
masanari iidamasanari iida10.15%15.26%
arnaldo carvalho de meloarnaldo carvalho de melo10.15%15.26%
Total657100.00%19100.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

PersonTokensPropCommitsCommitProp
masami hiramatsumasami hiramatsu17093.92%990.00%
he kuanghe kuang116.08%110.00%
Total181100.00%10100.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

PersonTokensPropCommitsCommitProp
masami hiramatsumasami hiramatsu22696.17%975.00%
arnaldo carvalho de meloarnaldo carvalho de melo41.70%18.33%
wang nanwang nan31.28%18.33%
he kuanghe kuang20.85%18.33%
Total235100.00%12100.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