diff options
Diffstat (limited to 'tools/perf/builtin-probe.c')
| -rw-r--r-- | tools/perf/builtin-probe.c | 575 |
1 files changed, 398 insertions, 177 deletions
diff --git a/tools/perf/builtin-probe.c b/tools/perf/builtin-probe.c index c1e6774fd3e..c63fa292507 100644 --- a/tools/perf/builtin-probe.c +++ b/tools/perf/builtin-probe.c @@ -20,7 +20,6 @@ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * */ -#define _GNU_SOURCE #include <sys/utsname.h> #include <sys/types.h> #include <sys/stat.h> @@ -31,167 +30,352 @@ #include <stdlib.h> #include <string.h> -#undef _GNU_SOURCE #include "perf.h" #include "builtin.h" #include "util/util.h" #include "util/strlist.h" -#include "util/event.h" -#include "util/debug.h" -#include "util/debugfs.h" +#include "util/strfilter.h" #include "util/symbol.h" -#include "util/thread.h" -#include "util/session.h" +#include "util/debug.h" +#include <api/fs/debugfs.h> #include "util/parse-options.h" -#include "util/parse-events.h" /* For debugfs_path */ #include "util/probe-finder.h" #include "util/probe-event.h" -#define MAX_PATH_LEN 256 -#define MAX_PROBES 128 +#define DEFAULT_VAR_FILTER "!__k???tab_* & !__crc_*" +#define DEFAULT_FUNC_FILTER "!_*" /* Session management structure */ static struct { - bool need_dwarf; bool list_events; bool force_add; - int nr_probe; - struct probe_point probes[MAX_PROBES]; + bool show_lines; + bool show_vars; + bool show_ext_vars; + bool show_funcs; + bool mod_events; + bool uprobes; + int nevents; + struct perf_probe_event events[MAX_PROBES]; struct strlist *dellist; - struct perf_session *psession; - struct map *kmap; -} session; - + struct line_range line_range; + char *target; + int max_probe_points; + struct strfilter *filter; +} params; /* Parse an event definition. Note that any error must die. */ -static void parse_probe_event(const char *str) +static int parse_probe_event(const char *str) { - struct probe_point *pp = &session.probes[session.nr_probe]; + struct perf_probe_event *pev = ¶ms.events[params.nevents]; + int ret; + + pr_debug("probe-definition(%d): %s\n", params.nevents, str); + if (++params.nevents == MAX_PROBES) { + pr_err("Too many probes (> %d) were specified.", MAX_PROBES); + return -1; + } - pr_debug("probe-definition(%d): %s\n", session.nr_probe, str); - if (++session.nr_probe == MAX_PROBES) - die("Too many probes (> %d) are specified.", MAX_PROBES); + pev->uprobes = params.uprobes; - /* Parse perf-probe event into probe_point */ - parse_perf_probe_event(str, pp, &session.need_dwarf); + /* Parse a perf-probe command into event */ + ret = parse_perf_probe_command(str, pev); + pr_debug("%d arguments\n", pev->nargs); - pr_debug("%d arguments\n", pp->nr_args); + return ret; } -static void parse_probe_event_argv(int argc, const char **argv) +static int set_target(const char *ptr) { - int i, len; + int found = 0; + const char *buf; + + /* + * The first argument after options can be an absolute path + * to an executable / library or kernel module. + * + * TODO: Support relative path, and $PATH, $LD_LIBRARY_PATH, + * short module name. + */ + if (!params.target && ptr && *ptr == '/') { + params.target = strdup(ptr); + if (!params.target) + return -ENOMEM; + + found = 1; + buf = ptr + (strlen(ptr) - 3); + + if (strcmp(buf, ".ko")) + params.uprobes = true; + + } + + return found; +} + +static int parse_probe_event_argv(int argc, const char **argv) +{ + int i, len, ret, found_target; char *buf; + found_target = set_target(argv[0]); + if (found_target < 0) + return found_target; + + if (found_target && argc == 1) + return 0; + /* Bind up rest arguments */ len = 0; - for (i = 0; i < argc; i++) + for (i = 0; i < argc; i++) { + if (i == 0 && found_target) + continue; + len += strlen(argv[i]) + 1; + } buf = zalloc(len + 1); - if (!buf) - die("Failed to allocate memory for binding arguments."); + if (buf == NULL) + return -ENOMEM; len = 0; - for (i = 0; i < argc; i++) + for (i = 0; i < argc; i++) { + if (i == 0 && found_target) + continue; + len += sprintf(&buf[len], "%s ", argv[i]); - parse_probe_event(buf); + } + params.mod_events = true; + ret = parse_probe_event(buf); free(buf); + return ret; } -static int opt_add_probe_event(const struct option *opt __used, - const char *str, int unset __used) +static int opt_add_probe_event(const struct option *opt __maybe_unused, + const char *str, int unset __maybe_unused) { - if (str) - parse_probe_event(str); - return 0; + if (str) { + params.mod_events = true; + return parse_probe_event(str); + } else + return 0; } -static int opt_del_probe_event(const struct option *opt __used, - const char *str, int unset __used) +static int opt_del_probe_event(const struct option *opt __maybe_unused, + const char *str, int unset __maybe_unused) { if (str) { - if (!session.dellist) - session.dellist = strlist__new(true, NULL); - strlist__add(session.dellist, str); + params.mod_events = true; + if (!params.dellist) + params.dellist = strlist__new(true, NULL); + strlist__add(params.dellist, str); } return 0; } -/* Currently just checking function name from symbol map */ -static void evaluate_probe_point(struct probe_point *pp) +static int opt_set_target(const struct option *opt, const char *str, + int unset __maybe_unused) +{ + int ret = -ENOENT; + char *tmp; + + if (str && !params.target) { + if (!strcmp(opt->long_name, "exec")) + params.uprobes = true; +#ifdef HAVE_DWARF_SUPPORT + else if (!strcmp(opt->long_name, "module")) + params.uprobes = false; +#endif + else + return ret; + + /* Expand given path to absolute path, except for modulename */ + if (params.uprobes || strchr(str, '/')) { + tmp = realpath(str, NULL); + if (!tmp) { + pr_warning("Failed to get the absolute path of %s: %m\n", str); + return ret; + } + } else { + tmp = strdup(str); + if (!tmp) + return -ENOMEM; + } + params.target = tmp; + ret = 0; + } + + return ret; +} + +#ifdef HAVE_DWARF_SUPPORT +static int opt_show_lines(const struct option *opt __maybe_unused, + const char *str, int unset __maybe_unused) { - struct symbol *sym; - sym = map__find_symbol_by_name(session.kmap, pp->function, - session.psession, NULL); - if (!sym) - die("Kernel symbol \'%s\' not found - probe not added.", - pp->function); + int ret = 0; + + if (!str) + return 0; + + if (params.show_lines) { + pr_warning("Warning: more than one --line options are" + " detected. Only the first one is valid.\n"); + return 0; + } + + params.show_lines = true; + ret = parse_line_range_desc(str, ¶ms.line_range); + + return ret; } -#ifndef NO_LIBDWARF -static int open_vmlinux(void) +static int opt_show_vars(const struct option *opt __maybe_unused, + const char *str, int unset __maybe_unused) { - if (map__load(session.kmap, session.psession, NULL) < 0) { - pr_debug("Failed to load kernel map.\n"); + struct perf_probe_event *pev = ¶ms.events[params.nevents]; + int ret; + + if (!str) + return 0; + + ret = parse_probe_event(str); + if (!ret && pev->nargs != 0) { + pr_err(" Error: '--vars' doesn't accept arguments.\n"); return -EINVAL; } - pr_debug("Try to open %s\n", session.kmap->dso->long_name); - return open(session.kmap->dso->long_name, O_RDONLY); + params.show_vars = true; + + return ret; } #endif -static const char * const probe_usage[] = { - "perf probe [<options>] 'PROBEDEF' ['PROBEDEF' ...]", - "perf probe [<options>] --add 'PROBEDEF' [--add 'PROBEDEF' ...]", - "perf probe [<options>] --del '[GROUP:]EVENT' ...", - "perf probe --list", - NULL -}; +static int opt_set_filter(const struct option *opt __maybe_unused, + const char *str, int unset __maybe_unused) +{ + const char *err; -static const struct option options[] = { - OPT_BOOLEAN('v', "verbose", &verbose, - "be more verbose (show parsed arguments, etc)"), -#ifndef NO_LIBDWARF - OPT_STRING('k', "vmlinux", &symbol_conf.vmlinux_name, - "file", "vmlinux pathname"), + if (str) { + pr_debug2("Set filter: %s\n", str); + if (params.filter) + strfilter__delete(params.filter); + params.filter = strfilter__new(str, &err); + if (!params.filter) { + pr_err("Filter parse error at %td.\n", err - str + 1); + pr_err("Source: \"%s\"\n", str); + pr_err(" %*c\n", (int)(err - str + 1), '^'); + return -EINVAL; + } + } + + return 0; +} + +static int init_params(void) +{ + return line_range__init(¶ms.line_range); +} + +static void cleanup_params(void) +{ + int i; + + for (i = 0; i < params.nevents; i++) + clear_perf_probe_event(params.events + i); + if (params.dellist) + strlist__delete(params.dellist); + line_range__clear(¶ms.line_range); + free(params.target); + if (params.filter) + strfilter__delete(params.filter); + memset(¶ms, 0, sizeof(params)); +} + +static void pr_err_with_code(const char *msg, int err) +{ + pr_err("%s", msg); + pr_debug(" Reason: %s (Code: %d)", strerror(-err), err); + pr_err("\n"); +} + +static int +__cmd_probe(int argc, const char **argv, const char *prefix __maybe_unused) +{ + const char * const probe_usage[] = { + "perf probe [<options>] 'PROBEDEF' ['PROBEDEF' ...]", + "perf probe [<options>] --add 'PROBEDEF' [--add 'PROBEDEF' ...]", + "perf probe [<options>] --del '[GROUP:]EVENT' ...", + "perf probe --list", +#ifdef HAVE_DWARF_SUPPORT + "perf probe [<options>] --line 'LINEDESC'", + "perf probe [<options>] --vars 'PROBEPOINT'", #endif - OPT_BOOLEAN('l', "list", &session.list_events, + NULL +}; + const struct option options[] = { + OPT_INCR('v', "verbose", &verbose, + "be more verbose (show parsed arguments, etc)"), + OPT_BOOLEAN('l', "list", ¶ms.list_events, "list up current probe events"), OPT_CALLBACK('d', "del", NULL, "[GROUP:]EVENT", "delete a probe event.", opt_del_probe_event), OPT_CALLBACK('a', "add", NULL, -#ifdef NO_LIBDWARF - "[EVENT=]FUNC[+OFFS|%return] [ARG ...]", +#ifdef HAVE_DWARF_SUPPORT + "[EVENT=]FUNC[@SRC][+OFF|%return|:RL|;PT]|SRC:AL|SRC;PT" + " [[NAME=]ARG ...]", #else - "[EVENT=]FUNC[+OFFS|%return|:RLN][@SRC]|SRC:ALN [ARG ...]", + "[EVENT=]FUNC[+OFF|%return] [[NAME=]ARG ...]", #endif "probe point definition, where\n" "\t\tGROUP:\tGroup name (optional)\n" "\t\tEVENT:\tEvent name\n" "\t\tFUNC:\tFunction name\n" - "\t\tOFFS:\tOffset from function entry (in byte)\n" + "\t\tOFF:\tOffset from function entry (in byte)\n" "\t\t%return:\tPut the probe at function return\n" -#ifdef NO_LIBDWARF - "\t\tARG:\tProbe argument (only \n" -#else +#ifdef HAVE_DWARF_SUPPORT "\t\tSRC:\tSource code path\n" - "\t\tRLN:\tRelative line number from function entry.\n" - "\t\tALN:\tAbsolute line number in file.\n" + "\t\tRL:\tRelative line number from function entry.\n" + "\t\tAL:\tAbsolute line number in file.\n" + "\t\tPT:\tLazy expression of line code.\n" "\t\tARG:\tProbe argument (local variable name or\n" -#endif "\t\t\tkprobe-tracer argument format.)\n", +#else + "\t\tARG:\tProbe argument (kprobe-tracer argument format.)\n", +#endif opt_add_probe_event), - OPT_BOOLEAN('f', "force", &session.force_add, "forcibly add events" + OPT_BOOLEAN('f', "force", ¶ms.force_add, "forcibly add events" " with existing name"), - OPT_END() -}; - -int cmd_probe(int argc, const char **argv, const char *prefix __used) -{ - int i, ret; -#ifndef NO_LIBDWARF - int fd; +#ifdef HAVE_DWARF_SUPPORT + OPT_CALLBACK('L', "line", NULL, + "FUNC[:RLN[+NUM|-RLN2]]|SRC:ALN[+NUM|-ALN2]", + "Show source code lines.", opt_show_lines), + OPT_CALLBACK('V', "vars", NULL, + "FUNC[@SRC][+OFF|%return|:RL|;PT]|SRC:AL|SRC;PT", + "Show accessible variables on PROBEDEF", opt_show_vars), + OPT_BOOLEAN('\0', "externs", ¶ms.show_ext_vars, + "Show external variables too (with --vars only)"), + OPT_STRING('k', "vmlinux", &symbol_conf.vmlinux_name, + "file", "vmlinux pathname"), + OPT_STRING('s', "source", &symbol_conf.source_prefix, + "directory", "path to kernel source"), + OPT_CALLBACK('m', "module", NULL, "modname|path", + "target module name (for online) or path (for offline)", + opt_set_target), #endif - struct probe_point *pp; + OPT__DRY_RUN(&probe_event_dry_run), + OPT_INTEGER('\0', "max-probes", ¶ms.max_probe_points, + "Set how many probe points can be found for a probe."), + OPT_BOOLEAN('F', "funcs", ¶ms.show_funcs, + "Show potential probe-able functions."), + OPT_CALLBACK('\0', "filter", NULL, + "[!]FILTER", "Set a filter (with --vars/funcs only)\n" + "\t\t\t(default: \"" DEFAULT_VAR_FILTER "\" for --vars,\n" + "\t\t\t \"" DEFAULT_FUNC_FILTER "\" for --funcs)", + opt_set_filter), + OPT_CALLBACK('x', "exec", NULL, "executable|path", + "target executable name or path", opt_set_target), + OPT_BOOLEAN(0, "demangle", &symbol_conf.demangle, + "Disable symbol demangling"), + OPT_END() + }; + int ret; argc = parse_options(argc, argv, options, probe_usage, PARSE_OPT_STOP_AT_NON_OPTION); @@ -200,110 +384,147 @@ int cmd_probe(int argc, const char **argv, const char *prefix __used) pr_warning(" Error: '-' is not supported.\n"); usage_with_options(probe_usage, options); } - parse_probe_event_argv(argc, argv); + ret = parse_probe_event_argv(argc, argv); + if (ret < 0) { + pr_err_with_code(" Error: Command Parse Error.", ret); + return ret; + } } - if ((!session.nr_probe && !session.dellist && !session.list_events)) + if (params.max_probe_points == 0) + params.max_probe_points = MAX_PROBES; + + if ((!params.nevents && !params.dellist && !params.list_events && + !params.show_lines && !params.show_funcs)) usage_with_options(probe_usage, options); - if (debugfs_valid_mountpoint(debugfs_path) < 0) - die("Failed to find debugfs path."); + /* + * Only consider the user's kernel image path if given. + */ + symbol_conf.try_vmlinux_path = (symbol_conf.vmlinux_name == NULL); - if (session.list_events) { - if (session.nr_probe != 0 || session.dellist) { - pr_warning(" Error: Don't use --list with" - " --add/--del.\n"); + if (params.list_events) { + if (params.mod_events) { + pr_err(" Error: Don't use --list with --add/--del.\n"); usage_with_options(probe_usage, options); } - show_perf_probe_events(); - return 0; - } - - if (session.dellist) { - del_trace_kprobe_events(session.dellist); - strlist__delete(session.dellist); - if (session.nr_probe == 0) - return 0; + if (params.show_lines) { + pr_err(" Error: Don't use --list with --line.\n"); + usage_with_options(probe_usage, options); + } + if (params.show_vars) { + pr_err(" Error: Don't use --list with --vars.\n"); + usage_with_options(probe_usage, options); + } + if (params.show_funcs) { + pr_err(" Error: Don't use --list with --funcs.\n"); + usage_with_options(probe_usage, options); + } + if (params.uprobes) { + pr_warning(" Error: Don't use --list with --exec.\n"); + usage_with_options(probe_usage, options); + } + ret = show_perf_probe_events(); + if (ret < 0) + pr_err_with_code(" Error: Failed to show event list.", ret); + return ret; } - - /* Initialize symbol maps for vmlinux */ - symbol_conf.sort_by_name = true; - if (symbol_conf.vmlinux_name == NULL) - symbol_conf.try_vmlinux_path = true; - if (symbol__init() < 0) - die("Failed to init symbol map."); - session.psession = perf_session__new(NULL, O_WRONLY, false); - if (session.psession == NULL) - die("Failed to init perf_session."); - session.kmap = map_groups__find_by_name(&session.psession->kmaps, - MAP__FUNCTION, - "[kernel.kallsyms]"); - if (!session.kmap) - die("Could not find kernel map.\n"); - - if (session.need_dwarf) -#ifdef NO_LIBDWARF - die("Debuginfo-analysis is not supported"); -#else /* !NO_LIBDWARF */ - pr_debug("Some probes require debuginfo.\n"); - - fd = open_vmlinux(); - if (fd < 0) { - if (session.need_dwarf) - die("Could not open debuginfo file."); - - pr_debug("Could not open vmlinux/module file." - " Try to use symbols.\n"); - goto end_dwarf; + if (params.show_funcs) { + if (params.nevents != 0 || params.dellist) { + pr_err(" Error: Don't use --funcs with" + " --add/--del.\n"); + usage_with_options(probe_usage, options); + } + if (params.show_lines) { + pr_err(" Error: Don't use --funcs with --line.\n"); + usage_with_options(probe_usage, options); + } + if (params.show_vars) { + pr_err(" Error: Don't use --funcs with --vars.\n"); + usage_with_options(probe_usage, options); + } + if (!params.filter) + params.filter = strfilter__new(DEFAULT_FUNC_FILTER, + NULL); + ret = show_available_funcs(params.target, params.filter, + params.uprobes); + strfilter__delete(params.filter); + params.filter = NULL; + if (ret < 0) + pr_err_with_code(" Error: Failed to show functions.", ret); + return ret; } - /* Searching probe points */ - for (i = 0; i < session.nr_probe; i++) { - pp = &session.probes[i]; - if (pp->found) - continue; +#ifdef HAVE_DWARF_SUPPORT + if (params.show_lines) { + if (params.mod_events) { + pr_err(" Error: Don't use --line with" + " --add/--del.\n"); + usage_with_options(probe_usage, options); + } + if (params.show_vars) { + pr_err(" Error: Don't use --line with --vars.\n"); + usage_with_options(probe_usage, options); + } - lseek(fd, SEEK_SET, 0); - ret = find_probepoint(fd, pp); - if (ret > 0) - continue; - if (ret == 0) { /* No error but failed to find probe point. */ - synthesize_perf_probe_point(pp); - die("Probe point '%s' not found. - probe not added.", - pp->probes[0]); + ret = show_line_range(¶ms.line_range, params.target); + if (ret < 0) + pr_err_with_code(" Error: Failed to show lines.", ret); + return ret; + } + if (params.show_vars) { + if (params.mod_events) { + pr_err(" Error: Don't use --vars with" + " --add/--del.\n"); + usage_with_options(probe_usage, options); } - /* Error path */ - if (session.need_dwarf) { - if (ret == -ENOENT) - pr_warning("No dwarf info found in the vmlinux - please rebuild with CONFIG_DEBUG_INFO=y.\n"); - die("Could not analyze debuginfo."); + if (!params.filter) + params.filter = strfilter__new(DEFAULT_VAR_FILTER, + NULL); + + ret = show_available_vars(params.events, params.nevents, + params.max_probe_points, + params.target, + params.filter, + params.show_ext_vars); + strfilter__delete(params.filter); + params.filter = NULL; + if (ret < 0) + pr_err_with_code(" Error: Failed to show vars.", ret); + return ret; + } +#endif + + if (params.dellist) { + ret = del_perf_probe_events(params.dellist); + if (ret < 0) { + pr_err_with_code(" Error: Failed to delete events.", ret); + return ret; } - pr_debug("An error occurred in debuginfo analysis." - " Try to use symbols.\n"); - break; } - close(fd); -end_dwarf: -#endif /* !NO_LIBDWARF */ + if (params.nevents) { + ret = add_perf_probe_events(params.events, params.nevents, + params.max_probe_points, + params.target, + params.force_add); + if (ret < 0) { + pr_err_with_code(" Error: Failed to add events.", ret); + return ret; + } + } + return 0; +} - /* Synthesize probes without dwarf */ - for (i = 0; i < session.nr_probe; i++) { - pp = &session.probes[i]; - if (pp->found) /* This probe is already found. */ - continue; +int cmd_probe(int argc, const char **argv, const char *prefix) +{ + int ret; - evaluate_probe_point(pp); - ret = synthesize_trace_kprobe_event(pp); - if (ret == -E2BIG) - die("probe point definition becomes too long."); - else if (ret < 0) - die("Failed to synthesize a probe point."); + ret = init_params(); + if (!ret) { + ret = __cmd_probe(argc, argv, prefix); + cleanup_params(); } - /* Settng up probe points */ - add_trace_kprobe_events(session.probes, session.nr_probe, - session.force_add); - return 0; + return ret; } - |
