diff options
-rw-r--r-- | include/linux/interrupt.h | 2 | ||||
-rw-r--r-- | include/trace/events/irq.h | 54 | ||||
-rw-r--r-- | kernel/softirq.c | 16 | ||||
-rw-r--r-- | tools/perf/Documentation/perf-probe.txt | 18 | ||||
-rw-r--r-- | tools/perf/builtin-probe.c | 78 | ||||
-rw-r--r-- | tools/perf/util/map.h | 10 | ||||
-rw-r--r-- | tools/perf/util/probe-event.c | 189 | ||||
-rw-r--r-- | tools/perf/util/probe-event.h | 16 | ||||
-rw-r--r-- | tools/perf/util/probe-finder.c | 644 | ||||
-rw-r--r-- | tools/perf/util/probe-finder.h | 31 | ||||
-rw-r--r-- | tools/perf/util/ui/browser.c | 1 |
11 files changed, 823 insertions, 236 deletions
diff --git a/include/linux/interrupt.h b/include/linux/interrupt.h index 4143285..07f741a 100644 --- a/include/linux/interrupt.h +++ b/include/linux/interrupt.h @@ -410,7 +410,7 @@ extern void open_softirq(int nr, void (*action)(struct softirq_action *)); extern void softirq_init(void); static inline void __raise_softirq_irqoff(unsigned int nr) { - trace_softirq_raise((struct softirq_action *)(unsigned long)nr, NULL); + trace_softirq_raise(nr); or_softirq_pending(1UL << nr); } diff --git a/include/trace/events/irq.h b/include/trace/events/irq.h index 6fa7cbab7..1c09820 100644 --- a/include/trace/events/irq.h +++ b/include/trace/events/irq.h @@ -86,76 +86,62 @@ TRACE_EVENT(irq_handler_exit, DECLARE_EVENT_CLASS(softirq, - TP_PROTO(struct softirq_action *h, struct softirq_action *vec), + TP_PROTO(unsigned int vec_nr), - TP_ARGS(h, vec), + TP_ARGS(vec_nr), TP_STRUCT__entry( - __field( int, vec ) + __field( unsigned int, vec ) ), TP_fast_assign( - if (vec) - __entry->vec = (int)(h - vec); - else - __entry->vec = (int)(long)h; + __entry->vec = vec_nr; ), - TP_printk("vec=%d [action=%s]", __entry->vec, + TP_printk("vec=%u [action=%s]", __entry->vec, show_softirq_name(__entry->vec)) ); /** * softirq_entry - called immediately before the softirq handler - * @h: pointer to struct softirq_action - * @vec: pointer to first struct softirq_action in softirq_vec array + * @vec_nr: softirq vector number * - * The @h parameter, contains a pointer to the struct softirq_action - * which has a pointer to the action handler that is called. By subtracting - * the @vec pointer from the @h pointer, we can determine the softirq - * number. Also, when used in combination with the softirq_exit tracepoint - * we can determine the softirq latency. + * When used in combination with the softirq_exit tracepoint + * we can determine the softirq handler runtine. */ DEFINE_EVENT(softirq, softirq_entry, - TP_PROTO(struct softirq_action *h, struct softirq_action *vec), + TP_PROTO(unsigned int vec_nr), - TP_ARGS(h, vec) + TP_ARGS(vec_nr) ); /** * softirq_exit - called immediately after the softirq handler returns - * @h: pointer to struct softirq_action - * @vec: pointer to first struct softirq_action in softirq_vec array + * @vec_nr: softirq vector number * - * The @h parameter contains a pointer to the struct softirq_action - * that has handled the softirq. By subtracting the @vec pointer from - * the @h pointer, we can determine the softirq number. Also, when used in - * combination with the softirq_entry tracepoint we can determine the softirq - * latency. + * When used in combination with the softirq_entry tracepoint + * we can determine the softirq handler runtine. */ DEFINE_EVENT(softirq, softirq_exit, - TP_PROTO(struct softirq_action *h, struct softirq_action *vec), + TP_PROTO(unsigned int vec_nr), - TP_ARGS(h, vec) + TP_ARGS(vec_nr) ); /** * softirq_raise - called immediately when a softirq is raised - * @h: pointer to struct softirq_action - * @vec: pointer to first struct softirq_action in softirq_vec array + * @vec_nr: softirq vector number * - * The @h parameter contains a pointer to the softirq vector number which is - * raised. @vec is NULL and it means @h includes vector number not - * softirq_action. When used in combination with the softirq_entry tracepoint - * we can determine the softirq raise latency. + * When used in combination with the softirq_entry tracepoint + * we can determine the softirq raise to run latency. */ DEFINE_EVENT(softirq, softirq_raise, - TP_PROTO(struct softirq_action *h, struct softirq_action *vec), + TP_PROTO(unsigned int vec_nr), - TP_ARGS(h, vec) + TP_ARGS(vec_nr) ); #endif /* _TRACE_IRQ_H */ diff --git a/kernel/softirq.c b/kernel/softirq.c index fc97888..e33fd71 100644 --- a/kernel/softirq.c +++ b/kernel/softirq.c @@ -229,18 +229,20 @@ restart: do { if (pending & 1) { + unsigned int vec_nr = h - softirq_vec; int prev_count = preempt_count(); - kstat_incr_softirqs_this_cpu(h - softirq_vec); - trace_softirq_entry(h, softirq_vec); + kstat_incr_softirqs_this_cpu(vec_nr); + + trace_softirq_entry(vec_nr); h->action(h); - trace_softirq_exit(h, softirq_vec); + trace_softirq_exit(vec_nr); if (unlikely(prev_count != preempt_count())) { - printk(KERN_ERR "huh, entered softirq %td %s %p" + printk(KERN_ERR "huh, entered softirq %u %s %p" "with preempt_count %08x," - " exited with %08x?\n", h - softirq_vec, - softirq_to_name[h - softirq_vec], - h->action, prev_count, preempt_count()); + " exited with %08x?\n", vec_nr, + softirq_to_name[vec_nr], h->action, + prev_count, preempt_count()); preempt_count() = prev_count; } diff --git a/tools/perf/Documentation/perf-probe.txt b/tools/perf/Documentation/perf-probe.txt index 27d52da..62de1b7 100644 --- a/tools/perf/Documentation/perf-probe.txt +++ b/tools/perf/Documentation/perf-probe.txt @@ -16,7 +16,9 @@ or or 'perf probe' --list or -'perf probe' --line='FUNC[:RLN[+NUM|:RLN2]]|SRC:ALN[+NUM|:ALN2]' +'perf probe' [options] --line='FUNC[:RLN[+NUM|:RLN2]]|SRC:ALN[+NUM|:ALN2]' +or +'perf probe' [options] --vars='PROBEPOINT' DESCRIPTION ----------- @@ -31,6 +33,11 @@ OPTIONS --vmlinux=PATH:: Specify vmlinux path which has debuginfo (Dwarf binary). +-m:: +--module=MODNAME:: + Specify module name in which perf-probe searches probe points + or lines. + -s:: --source=PATH:: Specify path to kernel source. @@ -57,6 +64,15 @@ OPTIONS Show source code lines which can be probed. This needs an argument which specifies a range of the source code. (see LINE SYNTAX for detail) +-V:: +--vars=:: + Show available local variables at given probe point. The argument + syntax is same as PROBE SYNTAX, but NO ARGs. + +--externs:: + (Only for --vars) Show external defined variables in addition to local + variables. + -f:: --force:: Forcibly add events with existing name. diff --git a/tools/perf/builtin-probe.c b/tools/perf/builtin-probe.c index 199d5e1..2e000c0 100644 --- a/tools/perf/builtin-probe.c +++ b/tools/perf/builtin-probe.c @@ -50,14 +50,17 @@ static struct { bool list_events; bool force_add; bool show_lines; + bool show_vars; + bool show_ext_vars; + bool mod_events; int nevents; struct perf_probe_event events[MAX_PROBES]; struct strlist *dellist; struct line_range line_range; + const char *target_module; int max_probe_points; } params; - /* Parse an event definition. Note that any error must die. */ static int parse_probe_event(const char *str) { @@ -92,6 +95,7 @@ static int parse_probe_event_argv(int argc, const char **argv) len = 0; for (i = 0; i < argc; i++) len += sprintf(&buf[len], "%s ", argv[i]); + params.mod_events = true; ret = parse_probe_event(buf); free(buf); return ret; @@ -100,9 +104,10 @@ static int parse_probe_event_argv(int argc, const char **argv) static int opt_add_probe_event(const struct option *opt __used, const char *str, int unset __used) { - if (str) + if (str) { + params.mod_events = true; return parse_probe_event(str); - else + } else return 0; } @@ -110,6 +115,7 @@ static int opt_del_probe_event(const struct option *opt __used, const char *str, int unset __used) { if (str) { + params.mod_events = true; if (!params.dellist) params.dellist = strlist__new(true, NULL); strlist__add(params.dellist, str); @@ -130,6 +136,25 @@ static int opt_show_lines(const struct option *opt __used, return ret; } + +static int opt_show_vars(const struct option *opt __used, + const char *str, int unset __used) +{ + 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; + } + params.show_vars = true; + + return ret; +} #endif static const char * const probe_usage[] = { @@ -138,7 +163,8 @@ static const char * const probe_usage[] = { "perf probe [<options>] --del '[GROUP:]EVENT' ...", "perf probe --list", #ifdef DWARF_SUPPORT - "perf probe --line 'LINEDESC'", + "perf probe [<options>] --line 'LINEDESC'", + "perf probe [<options>] --vars 'PROBEPOINT'", #endif NULL }; @@ -180,10 +206,17 @@ static const struct option options[] = { 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_STRING('m', "module", ¶ms.target_module, + "modname", "target module name"), #endif OPT__DRY_RUN(&probe_event_dry_run), OPT_INTEGER('\0', "max-probes", ¶ms.max_probe_points, @@ -217,7 +250,7 @@ int cmd_probe(int argc, const char **argv, const char *prefix __used) usage_with_options(probe_usage, options); if (params.list_events) { - if (params.nevents != 0 || params.dellist) { + if (params.mod_events) { pr_err(" Error: Don't use --list with --add/--del.\n"); usage_with_options(probe_usage, options); } @@ -225,6 +258,10 @@ int cmd_probe(int argc, const char **argv, const char *prefix __used) 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); + } ret = show_perf_probe_events(); if (ret < 0) pr_err(" Error: Failed to show event list. (%d)\n", @@ -234,17 +271,35 @@ int cmd_probe(int argc, const char **argv, const char *prefix __used) #ifdef DWARF_SUPPORT if (params.show_lines) { - if (params.nevents != 0 || params.dellist) { - pr_warning(" Error: Don't use --line with" - " --add/--del.\n"); + 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); } - ret = show_line_range(¶ms.line_range); + ret = show_line_range(¶ms.line_range, params.target_module); if (ret < 0) pr_err(" Error: Failed to show lines. (%d)\n", 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); + } + ret = show_available_vars(params.events, params.nevents, + params.max_probe_points, + params.target_module, + params.show_ext_vars); + if (ret < 0) + pr_err(" Error: Failed to show vars. (%d)\n", ret); + return ret; + } #endif if (params.dellist) { @@ -258,8 +313,9 @@ int cmd_probe(int argc, const char **argv, const char *prefix __used) if (params.nevents) { ret = add_perf_probe_events(params.events, params.nevents, - params.force_add, - params.max_probe_points); + params.max_probe_points, + params.target_module, + params.force_add); if (ret < 0) { pr_err(" Error: Failed to add events. (%d)\n", ret); return ret; diff --git a/tools/perf/util/map.h b/tools/perf/util/map.h index 7857579..b397c03 100644 --- a/tools/perf/util/map.h +++ b/tools/perf/util/map.h @@ -215,6 +215,16 @@ struct symbol *map_groups__find_function_by_name(struct map_groups *self, return map_groups__find_symbol_by_name(self, MAP__FUNCTION, name, mapp, filter); } +static inline +struct symbol *machine__find_kernel_function_by_name(struct machine *self, + const char *name, + struct map **mapp, + symbol_filter_t filter) +{ + return map_groups__find_function_by_name(&self->kmaps, name, mapp, + filter); +} + int map_groups__fixup_overlappings(struct map_groups *self, struct map *map, int verbose, FILE *fp); diff --git a/tools/perf/util/probe-event.c b/tools/perf/util/probe-event.c index fcc16e4..3b6a529 100644 --- a/tools/perf/util/probe-event.c +++ b/tools/perf/util/probe-event.c @@ -74,10 +74,9 @@ static int e_snprintf(char *str, size_t size, const char *format, ...) static char *synthesize_perf_probe_point(struct perf_probe_point *pp); static struct machine machine; -/* Initialize symbol maps and path of vmlinux */ +/* Initialize symbol maps and path of vmlinux/modules */ static int init_vmlinux(void) { - struct dso *kernel; int ret; symbol_conf.sort_by_name = true; @@ -91,33 +90,61 @@ static int init_vmlinux(void) goto out; } - ret = machine__init(&machine, "/", 0); + ret = machine__init(&machine, "", HOST_KERNEL_ID); if (ret < 0) goto out; - kernel = dso__new_kernel(symbol_conf.vmlinux_name); - if (kernel == NULL) - die("Failed to create kernel dso."); - - ret = __machine__create_kernel_maps(&machine, kernel); - if (ret < 0) - pr_debug("Failed to create kernel maps.\n"); - + if (machine__create_kernel_maps(&machine) < 0) { + pr_debug("machine__create_kernel_maps "); + goto out; + } out: if (ret < 0) pr_warning("Failed to init vmlinux path.\n"); return ret; } +static struct symbol *__find_kernel_function_by_name(const char *name, + struct map **mapp) +{ + return machine__find_kernel_function_by_name(&machine, name, mapp, + NULL); +} + +const char *kernel_get_module_path(const char *module) +{ + struct dso *dso; + + if (module) { + list_for_each_entry(dso, &machine.kernel_dsos, node) { + if (strncmp(dso->short_name + 1, module, + dso->short_name_len - 2) == 0) + goto found; + } + pr_debug("Failed to find module %s.\n", module); + return NULL; + } else { + dso = machine.vmlinux_maps[MAP__FUNCTION]->dso; + if (dso__load_vmlinux_path(dso, + machine.vmlinux_maps[MAP__FUNCTION], NULL) < 0) { + pr_debug("Failed to load kernel map.\n"); + return NULL; + } + } +found: + return dso->long_name; +} + #ifdef DWARF_SUPPORT -static int open_vmlinux(void) +static int open_vmlinux(const char *module) { - if (map__load(machine.vmlinux_maps[MAP__FUNCTION], NULL) < 0) { - pr_debug("Failed to load kernel map.\n"); - return -EINVAL; + const char *path = kernel_get_module_path(module); + if (!path) { + pr_err("Failed to find path of %s module", module ?: "kernel"); + return -ENOENT; } - pr_debug("Try to open %s\n", machine.vmlinux_maps[MAP__FUNCTION]->dso->long_name); - return open(machine.vmlinux_maps[MAP__FUNCTION]->dso->long_name, O_RDONLY); + pr_debug("Try to open %s\n", path); + return open(path, O_RDONLY); } /* @@ -125,20 +152,19 @@ static int open_vmlinux(void) * Currently only handles kprobes. */ static int kprobe_convert_to_perf_probe(struct probe_trace_point *tp, - struct perf_probe_point *pp) + struct perf_probe_point *pp) { struct symbol *sym; - int fd, ret = -ENOENT; + struct map *map; + u64 addr; + int ret = -ENOENT; - sym = map__find_symbol_by_name(machine.vmlinux_maps[MAP__FUNCTION], - tp->symbol, NULL); + sym = __find_kernel_function_by_name(tp->symbol, &map); if (sym) { - fd = open_vmlinux(); - if (fd >= 0) { - ret = find_perf_probe_point(fd, - sym->start + tp->offset, pp); - close(fd); - } + addr = map->unmap_ip(map, sym->start + tp->offset); + pr_debug("try to find %s+%ld@%llx\n", tp->symbol, + tp->offset, addr); + ret = find_perf_probe_point((unsigned long)addr, pp); } if (ret <= 0) { pr_debug("Failed to find corresponding probes from " @@ -156,12 +182,12 @@ static int kprobe_convert_to_perf_probe(struct probe_trace_point *tp, /* Try to find perf_probe_event with debuginfo */ static int try_to_find_probe_trace_events(struct perf_probe_event *pev, struct probe_trace_event **tevs, - int max_tevs) + int max_tevs, const char *module) { bool need_dwarf = perf_probe_event_need_dwarf(pev); int fd, ntevs; - fd = open_vmlinux(); + fd = open_vmlinux(module); if (fd < 0) { if (need_dwarf) { pr_warning("Failed to open debuginfo file.\n"); @@ -300,7 +326,7 @@ error: * Show line-range always requires debuginfo to find source file and * line number. */ -int show_line_range(struct line_range *lr) +int show_line_range(struct line_range *lr, const char *module) { int l = 1; struct line_node *ln; @@ -313,7 +339,7 @@ int show_line_range(struct line_range *lr) if (ret < 0) return ret; - fd = open_vmlinux(); + fd = open_vmlinux(module); if (fd < 0) { pr_warning("Failed to open debuginfo file.\n"); return fd; @@ -378,11 +404,84 @@ end: return ret; } +static int show_available_vars_at(int fd, struct perf_probe_event *pev, + int max_vls, bool externs) +{ + char *buf; + int ret, i; + struct str_node *node; + struct variable_list *vls = NULL, *vl; + + buf = synthesize_perf_probe_point(&pev->point); + if (!buf) + return -EINVAL; + pr_debug("Searching variables at %s\n", buf); + + ret = find_available_vars_at(fd, pev, &vls, max_vls, externs); + if (ret > 0) { + /* Some variables were found */ + fprintf(stdout, "Available variables at %s\n", buf); + for (i = 0; i < ret; i++) { + vl = &vls[i]; + /* + * A probe point might be converted to + * several trace points. + */ + fprintf(stdout, "\t@<%s+%lu>\n", vl->point.symbol, + vl->point.offset); + free(vl->point.symbol); + if (vl->vars) { + strlist__for_each(node, vl->vars) + fprintf(stdout, "\t\t%s\n", node->s); + strlist__delete(vl->vars); + } else + fprintf(stdout, "(No variables)\n"); + } + free(vls); + } else + pr_err("Failed to find variables at %s (%d)\n", buf, ret); + + free(buf); + return ret; +} + +/* Show available variables on given probe point */ +int show_available_vars(struct perf_probe_event *pevs, int npevs, + int max_vls, const char *module, bool externs) +{ + int i, fd, ret = 0; + + ret = init_vmlinux(); + if (ret < 0) + return ret; + + fd = open_vmlinux(module); + if (fd < 0) { + pr_warning("Failed to open debuginfo file.\n"); + return fd; + } + + setup_pager(); + + for (i = 0; i < npevs && ret >= 0; i++) + ret = show_available_vars_at(fd, &pevs[i], max_vls, externs); + + close(fd); + return ret; +} + #else /* !DWARF_SUPPORT */ static int kprobe_convert_to_perf_probe(struct probe_trace_point *tp, - struct perf_probe_point *pp) + struct perf_probe_point *pp) { + struct symbol *sym; + + sym = __find_kernel_function_by_name(tp->symbol, NULL); + if (!sym) { + pr_err("Failed to find symbol %s in kernel.\n", tp->symbol); + return -ENOENT; + } pp->function = strdup(tp->symbol); if (pp->function == NULL) return -ENOMEM; @@ -394,7 +493,7 @@ static int kprobe_convert_to_perf_probe(struct probe_trace_point *tp, static int try_to_find_probe_trace_events(struct perf_probe_event *pev, struct probe_trace_event **tevs __unused, - int max_tevs __unused) + int max_tevs __unused, const char *mod __unused) { if (perf_probe_event_need_dwarf(pev)) { pr_warning("Debuginfo-analysis is not supported.\n"); @@ -403,12 +502,19 @@ static int try_to_find_probe_trace_events(struct perf_probe_event *pev, return 0; } -int show_line_range(struct line_range *lr __unused) +int show_line_range(struct line_range *lr __unused, const char *module __unused) { pr_warning("Debuginfo-analysis is not supported.\n"); return -ENOSYS; } +int show_available_vars(struct perf_probe_event *pevs __unused, + int npevs __unused, int max_vls __unused, + const char *module __unused, bool externs __unused) +{ + pr_warning("Debuginfo-analysis is not supported.\n"); + return -ENOSYS; +} #endif int parse_line_range_desc(const char *arg, struct line_range *lr) @@ -1087,7 +1193,7 @@ error: } static int convert_to_perf_probe_event(struct probe_trace_event *tev, - struct perf_probe_event *pev) + struct perf_probe_event *pev) { char buf[64] = ""; int i, ret; @@ -1516,14 +1622,14 @@ static int __add_probe_trace_events(struct perf_probe_event *pev, static int convert_to_probe_trace_events(struct perf_probe_event *pev, struct probe_trace_event **tevs, - int max_tevs) + int max_tevs, const char *module) { struct symbol *sym; int ret = 0, i; struct probe_trace_event *tev; /* Convert perf_probe_event with debuginfo */ - ret = try_to_find_probe_trace_events(pev, tevs, max_tevs); + ret = try_to_find_probe_trace_events(pev, tevs, max_tevs, module); if (ret != 0) return ret; @@ -1572,8 +1678,7 @@ static int convert_to_probe_trace_events(struct perf_probe_event *pev, } /* Currently just checking function name from symbol map */ - sym = map__find_symbol_by_name(machine.vmlinux_maps[MAP__FUNCTION], - tev->point.symbol, NULL); + sym = __find_kernel_function_by_name(tev->point.symbol, NULL); if (!sym) { pr_warning("Kernel symbol \'%s\' not found.\n", tev->point.symbol); @@ -1596,7 +1701,7 @@ struct __event_package { }; int add_perf_probe_events(struct perf_probe_event *pevs, int npevs, - bool force_add, int max_tevs) + int max_tevs, const char *module, bool force_add) { int i, j, ret; struct __event_package *pkgs; @@ -1617,7 +1722,9 @@ int add_perf_probe_events(struct perf_probe_event *pevs, int npevs, pkgs[i].pev = &pevs[i]; /* Convert with or without debuginfo */ ret = convert_to_probe_trace_events(pkgs[i].pev, - &pkgs[i].tevs, max_tevs); + &pkgs[i].tevs, + max_tevs, + module); if (ret < 0) goto end; pkgs[i].ntevs = ret; diff --git a/tools/perf/util/probe-event.h b/tools/perf/util/probe-event.h index 5af3924..5accbed 100644 --- a/tools/perf/util/probe-event.h +++ b/tools/perf/util/probe-event.h @@ -90,6 +90,12 @@ struct line_range { struct list_head line_list; /* Visible lines */ }; +/* List of variables */ +struct variable_list { + struct probe_trace_point point; /* Actual probepoint */ + struct strlist *vars; /* Available variables */ +}; + /* Command string to events */ extern int parse_perf_probe_command(const char *cmd, struct perf_probe_event *pev); @@ -109,12 +115,18 @@ extern void clear_perf_probe_event(struct perf_probe_event *pev); /* Command string to line-range */ extern int parse_line_range_desc(const char *cmd, struct line_range *lr); +/* Internal use: Return kernel/module path */ +extern const char *kernel_get_module_path(const char *module); extern int add_perf_probe_events(struct perf_probe_event *pevs, int npevs, - bool force_add, int max_probe_points); + int max_probe_points, const char *module, + bool force_add); extern int del_perf_probe_events(struct strlist *dellist); extern int show_perf_probe_events(void); -extern int show_line_range(struct line_range *lr); +extern int show_line_range(struct line_range *lr, const char *module); +extern int show_available_vars(struct perf_probe_event *pevs, int npevs, + int max_probe_points, const char *module, + bool externs); /* Maximum index number of event-name postfix */ diff --git a/tools/perf/util/probe-finder.c b/tools/perf/util/probe-finder.c index 32b81f7..c20bd52 100644 --- a/tools/perf/util/probe-finder.c +++ b/tools/perf/util/probe-finder.c @@ -116,6 +116,101 @@ static void line_list__free(struct list_head *head) } } +/* Dwarf FL wrappers */ + +static int __linux_kernel_find_elf(Dwfl_Module *mod, + void **userdata, + const char *module_name, + Dwarf_Addr base, + char **file_name, Elf **elfp) +{ + int fd; + const char *path = kernel_get_module_path(module_name); + + if (path) { + fd = open(path, O_RDONLY); + if (fd >= 0) { + *file_name = strdup(path); + return fd; + } + } + /* If failed, try to call standard method */ + return dwfl_linux_kernel_find_elf(mod, userdata, module_name, base, + file_name, elfp); +} + +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, +}; + +static const Dwfl_Callbacks kernel_callbacks = { + .find_debuginfo = dwfl_standard_find_debuginfo, + .debuginfo_path = &debuginfo_path, + + .find_elf = __linux_kernel_find_elf, + .section_address = dwfl_linux_kernel_module_section_address, +}; + +/* Get a Dwarf from offline image */ +static Dwarf *dwfl_init_offline_dwarf(int fd, Dwfl **dwflp, Dwarf_Addr *bias) +{ + Dwfl_Module *mod; + Dwarf *dbg = NULL; + + if (!dwflp) + return NULL; + + *dwflp = dwfl_begin(&offline_callbacks); + if (!*dwflp) + return NULL; + + mod = dwfl_report_offline(*dwflp, "", "", fd); + if (!mod) + goto error; + + dbg = dwfl_module_getdwarf(mod, bias); + if (!dbg) { +error: + dwfl_end(*dwflp); + *dwflp = NULL; + } + return dbg; +} + +/* Get a Dwarf from live kernel image */ +static Dwarf *dwfl_init_live_kernel_dwarf(Dwarf_Addr addr, Dwfl **dwflp, + Dwarf_Addr *bias) +{ + Dwarf *dbg; + + if (!dwflp) + return NULL; + + *dwflp = dwfl_begin(&kernel_callbacks); + if (!*dwflp) + return NULL; + + /* Load the kernel dwarves: Don't care the result here */ + dwfl_linux_kernel_report_kernel(*dwflp); + dwfl_linux_kernel_report_modules(*dwflp); + + dbg = dwfl_addrdwarf(*dwflp, addr, bias); + /* Here, check whether we could get a real dwarf */ + if (!dbg) { + dwfl_end(*dwflp); + *dwflp = NULL; + } + return dbg; +} + /* Dwarf wrappers */ /* Find the realpath of the target file. */ @@ -160,26 +255,44 @@ static bool die_compare_name(Dwarf_Die *dw_die, const char *tname) return name ? (strcmp(tname, name) == 0) : false; } -/* Get type die, but skip qualifiers and typedef */ -static Dwarf_Die *die_get_real_type(Dwarf_Die *vr_die, Dwarf_Die *die_mem) +/* Get type die */ +static Dwarf_Die *die_get_type(Dwarf_Die *vr_die, Dwarf_Die *die_mem) { Dwarf_Attribute attr; + + if (dwarf_attr_integrate(vr_die, DW_AT_type, &attr) && + dwarf_formref_die(&attr, die_mem)) + return die_mem; + else + return NULL; +} + +/* Get a type die, but skip qualifiers */ +static Dwarf_Die *__die_get_real_type(Dwarf_Die *vr_die, Dwarf_Die *die_mem) +{ int tag; do { - if (dwarf_attr(vr_die, DW_AT_type, &attr) == NULL || - dwarf_formref_die(&attr, die_mem) == NULL) - return NULL; - - tag = dwarf_tag(die_mem); - vr_die = die_mem; + vr_die = die_get_type(vr_die, die_mem); + if (!vr_die) + break; + tag = dwarf_tag(vr_die); } while (tag == DW_TAG_const_type || tag == DW_TAG_restrict_type || tag == DW_TAG_volatile_type || - tag == DW_TAG_shared_type || - tag == DW_TAG_typedef); + tag == DW_TAG_shared_type); + + return vr_die; +} - return die_mem; +/* Get a type die, but skip qualifiers and typedef */ +static Dwarf_Die *die_get_real_type(Dwarf_Die *vr_die, Dwarf_Die *die_mem) +{ + do { + vr_die = __die_get_real_type(vr_die, die_mem); + } while (vr_die && dwarf_tag(vr_die) == DW_TAG_typedef); + + return vr_die; } static bool die_is_signed_type(Dwarf_Die *tp_die) @@ -320,25 +433,35 @@ static Dwarf_Die *die_find_inlinefunc(Dwarf_Die *sp_die, Dwarf_Addr addr, return die_find_child(sp_die, __die_find_inline_cb, &addr, die_mem); } +struct __find_variable_param { + const char *name; + Dwarf_Addr addr; +}; + static int __die_find_variable_cb(Dwarf_Die *die_mem, void *data) { - const char *name = data; + struct __find_variable_param *fvp = data; int tag; tag = dwarf_tag(die_mem); if ((tag == DW_TAG_formal_parameter || tag == DW_TAG_variable) && - die_compare_name(die_mem, name)) + die_compare_name(die_mem, fvp->name)) return DIE_FIND_CB_FOUND; - return DIE_FIND_CB_CONTINUE; + if (dwarf_haspc(die_mem, fvp->addr)) + return DIE_FIND_CB_CONTINUE; + else + return DIE_FIND_CB_SIBLING; } -/* Find a variable called 'name' */ -static Dwarf_Die *die_find_variable(Dwarf_Die *sp_die, const char *name, - Dwarf_Die *die_mem) +/* Find a variable called 'name' at given address */ +static Dwarf_Die *die_find_variable_at(Dwarf_Die *sp_die, const char *name, + Dwarf_Addr addr, Dwarf_Die *die_mem) { - return die_find_child(sp_die, __die_find_variable_cb, (void *)name, + struct __find_variable_param fvp = { .name = name, .addr = addr}; + + return die_find_child(sp_die, __die_find_variable_cb, (void *)&fvp, die_mem); } @@ -361,6 +484,60 @@ static Dwarf_Die *die_find_member(Dwarf_Die *st_die, const char *name, die_mem); } +/* Get the name of given variable DIE */ +static int die_get_typename(Dwarf_Die *vr_die, char *buf, int len) +{ + Dwarf_Die type; + int tag, ret, ret2; + const char *tmp = ""; + + if (__die_get_real_type(vr_die, &type) == NULL) + return -ENOENT; + + tag = dwarf_tag(&type); + if (tag == DW_TAG_array_type || tag == DW_TAG_pointer_type) + tmp = "*"; + else if (tag == DW_TAG_subroutine_type) { + /* Function pointer */ + ret = snprintf(buf, len, "(function_type)"); + return (ret >= len) ? -E2BIG : ret; + } else { + if (!dwarf_diename(&type)) + return -ENOENT; + if (tag == DW_TAG_union_type) + tmp = "union "; + else if (tag == DW_TAG_structure_type) + tmp = "struct "; + /* Write a base name */ + ret = snprintf(buf, len, "%s%s", tmp, dwarf_diename(&type)); + return (ret >= len) ? -E2BIG : ret; + } + ret = die_get_typename(&type, buf, len); + if (ret > 0) { + ret2 = snprintf(buf + ret, len - ret, "%s", tmp); + ret = (ret2 >= len - ret) ? -E2BIG : ret2 + ret; + } + return ret; +} + +/* Get the name and type of given variable DIE, stored as "type\tname" */ +static int die_get_varname(Dwarf_Die *vr_die, char *buf, int len) +{ + int ret, ret2; + + ret = die_get_typename(vr_die, buf, len); + if (ret < 0) { + pr_debug("Failed to get type, make it unknown.\n"); + ret = snprintf(buf, len, "(unknown_type)"); + } + if (ret > 0) { + ret2 = snprintf(buf + ret, len - ret, "\t%s", + dwarf_diename(vr_die)); + ret = (ret2 >= len - ret) ? -E2BIG : ret2 + ret; + } + return ret; +} + /* * Probe finder related functions */ @@ -374,8 +551,13 @@ static struct probe_trace_arg_ref *alloc_trace_arg_ref(long offs) return ref; } -/* Show a location */ -static int convert_variable_location(Dwarf_Die *vr_die, struct probe_finder *pf) +/* + * Convert a location into trace_arg. + * If tvar == NULL, this just checks variable can be converted. + */ +static int convert_variable_location(Dwarf_Die *vr_die, Dwarf_Addr addr, + Dwarf_Op *fb_ops, + struct probe_trace_arg *tvar) { Dwarf_Attribute attr; Dwarf_Op *op; @@ -384,20 +566,23 @@ static int convert_variable_location(Dwarf_Die *vr_die, struct probe_finder *pf) Dwarf_Word offs = 0; bool ref = false; const char *regs; - struct probe_trace_arg *tvar = pf->tvar; int ret; + 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 || - dwarf_getlocation_addr(&attr, pf->addr, &op, &nops, 1) <= 0 || + dwarf_getlocation_addr(&attr, addr, &op, &nops, 1) <= 0 || nops == 0) { /* TODO: Support const_value */ - pr_err("Failed to find the location of %s at this address.\n" - " Perhaps, it has been optimized out.\n", pf->pvar->var); return -ENOENT; } if (op->atom == DW_OP_addr) { +static_var: + if (!tvar) + return 0; /* Static variables on memory (not stack), make @varname */ ret = strlen(dwarf_diename(vr_die)); tvar->value = zalloc(ret + 2); @@ -412,14 +597,11 @@ static int convert_variable_location(Dwarf_Die *vr_die, struct probe_finder *pf) /* If this is based on frame buffer, set the offset */ if (op->atom == DW_OP_fbreg) { - if (pf->fb_ops == NULL) { - pr_warning("The attribute of frame base is not " - "supported.\n"); + if (fb_ops == NULL) return -ENOTSUP; - } ref = true; offs = op->number; - op = &pf->fb_ops[0]; + op = &fb_ops[0]; } if (op->atom >= DW_OP_breg0 && op->atom <= DW_OP_breg31) { @@ -435,13 +617,18 @@ static int convert_variable_location(Dwarf_Die *vr_die, struct probe_finder *pf) } else if (op->atom == DW_OP_regx) { regn = op->number; } else { - pr_warning("DW_OP %x is not supported.\n", op->atom); + pr_debug("DW_OP %x is not supported.\n", op->atom); return -ENOTSUP; } + if (!tvar) + return 0; + regs = get_arch_regstr(regn); if (!regs) { - pr_warning("Mapping for DWARF register number %u missing on this architecture.", regn); + /* This should be a bug in DWARF or this tool */ + pr_warning("Mapping for DWARF register number %u " + "missing on this architecture.", regn); return -ERANGE; } @@ -666,8 +853,14 @@ static int convert_variable(Dwarf_Die *vr_die, struct probe_finder *pf) pr_debug("Converting variable %s into trace event.\n", dwarf_diename(vr_die)); - ret = convert_variable_location(vr_die, pf); - if (ret == 0 && pf->pvar->field) { + ret = convert_variable_location(vr_die, pf->addr, pf->fb_ops, + pf->tvar); + if (ret == -ENOENT) + pr_err("Failed to find the location of %s at this address.\n" + " Perhaps, it has been optimized out.\n", pf->pvar->var); + else if (ret == -ENOTSUP) + pr_err("Sorry, we don't support this variable location yet.\n"); + else if (pf->pvar->field) { ret = convert_variable_fields(vr_die, pf->pvar->var, pf->pvar->field, &pf->tvar->ref, &die_mem); @@ -722,56 +915,39 @@ static int find_variable(Dwarf_Die *sp_die, struct probe_finder *pf) pr_debug("Searching '%s' variable in context.\n", pf->pvar->var); /* Search child die for local variables and parameters. */ - if (die_find_variable(sp_die, pf->pvar->var, &vr_die)) + if (die_find_variable_at(sp_die, pf->pvar->var, pf->addr, &vr_die)) ret = convert_variable(&vr_die, pf); else { /* Search upper class */ nscopes = dwarf_getscopes_die(sp_die, &scopes); - if (nscopes > 0) { - ret = dwarf_getscopevar(scopes, nscopes, pf->pvar->var, - 0, NULL, 0, 0, &vr_die); - if (ret >= 0) + while (nscopes-- > 1) { + pr_debug("Searching variables in %s\n", + dwarf_diename(&scopes[nscopes])); + /* We should check this scope, so give dummy address */ + if (die_find_variable_at(&scopes[nscopes], + pf->pvar->var, 0, + &vr_die)) { ret = convert_variable(&vr_die, pf); - else - ret = -ENOENT; + goto found; + } + } + if (scopes) free(scopes); - } else - ret = -ENOENT; + ret = -ENOENT; } +found: if (ret < 0) pr_warning("Failed to find '%s' in this function.\n", pf->pvar->var); return ret; } -/* Show a probe point to output buffer */ -static int convert_probe_point(Dwarf_Die *sp_die, struct probe_finder *pf) +/* Convert subprogram DIE to trace point */ +static int convert_to_trace_point(Dwarf_Die *sp_die, Dwarf_Addr paddr, + bool retprobe, struct probe_trace_point *tp) { - struct probe_trace_event *tev; Dwarf_Addr eaddr; - Dwarf_Die die_mem; const char *name; - int ret, i; - Dwarf_Attribute fb_attr; - size_t nops; - - if (pf->ntevs == pf->max_tevs) { - pr_warning("Too many( > %d) probe point found.\n", - pf->max_tevs); - return -ERANGE; - } - tev = &pf->tevs[pf->ntevs++]; - - /* If no real subprogram, find a real one */ - if (!sp_die || dwarf_tag(sp_die) != DW_TAG_subprogram) { - sp_die = die_find_real_subprogram(&pf->cu_die, - pf->addr, &die_mem); - if (!sp_die) { - pr_warning("Failed to find probe point in any " - "functions.\n"); - return -ENOENT; - } - } /* Copy the name of probe point */ name = dwarf_diename(sp_die); @@ -781,26 +957,45 @@ static int convert_probe_point(Dwarf_Die *sp_die, struct probe_finder *pf) dwarf_diename(sp_die)); return -ENOENT; } - tev->point.symbol = strdup(name); - if (tev->point.symbol == NULL) + tp->symbol = strdup(name); + if (tp->symbol == NULL) return -ENOMEM; - tev->point.offset = (unsigned long)(pf->addr - eaddr); + tp->offset = (unsigned long)(paddr - eaddr); } else /* This function has no name. */ - tev->point.offset = (unsigned long)pf->addr; + tp->offset = (unsigned long)paddr; /* Return probe must be on the head of a subprogram */ - if (pf->pev->point.retprobe) { - if (tev->point.offset != 0) { + if (retprobe) { + if (eaddr != paddr) { pr_warning("Return probe must be on the head of" " a real function\n"); return -EINVAL; } - tev->point.retprobe = true; + tp->retprobe = true; } - pr_debug("Probe point found: %s+%lu\n", tev->point.symbol, - tev->point.offset); + return 0; +} + +/* Call probe_finder callback with real subprogram DIE */ +static int call_probe_finder(Dwarf_Die *sp_die, struct probe_finder *pf) +{ + Dwarf_Die die_mem; + Dwarf_Attribute fb_attr; + size_t nops; + int ret; + + /* If no real subprogram, find a real one */ + if (!sp_die || dwarf_tag(sp_die) != DW_TAG_subprogram) { + sp_die = die_find_real_subprogram(&pf->cu_die, + pf->addr, &die_mem); + if (!sp_die) { + pr_warning("Failed to find probe point in any " + "functions.\n"); + return -ENOENT; + } + } /* Get the frame base attribute/ops */ dwarf_attr(sp_die, DW_AT_frame_base, &fb_attr); @@ -820,22 +1015,13 @@ static int convert_probe_point(Dwarf_Die *sp_die, struct probe_finder *pf) #endif } - /* Find each argument */ - tev->nargs = pf->pev->nargs; - tev->args = zalloc(sizeof(struct probe_trace_arg) * tev->nargs); - if (tev->args == NULL) - return -ENOMEM; - for (i = 0; i < pf->pev->nargs; i++) { - pf->pvar = &pf->pev->args[i]; - pf->tvar = &tev->args[i]; - ret = find_variable(sp_die, pf); - if (ret != 0) - return ret; - } + /* Call finder's callback handler */ + ret = pf->callback(sp_die, pf); /* *pf->fb_ops will be cached in libdw. Don't free it. */ pf->fb_ops = NULL; - return 0; + + return ret; } /* Find probe point from its line number */ @@ -871,7 +1057,7 @@ static int find_probe_point_by_line(struct probe_finder *pf) (int)i, lineno, (uintmax_t)addr); pf->addr = addr; - ret = convert_probe_point(NULL, pf); + ret = call_probe_finder(NULL, pf); /* Continuing, because target line might be inlined. */ } return ret; @@ -984,7 +1170,7 @@ static int find_probe_point_lazy(Dwarf_Die *sp_die, struct probe_finder *pf) (int)i, lineno, (unsigned long long)addr); pf->addr = addr; - ret = convert_probe_point(sp_die, pf); + ret = call_probe_finder(sp_die, pf); /* Continuing, because target line might be inlined. */ } /* TODO: deallocate lines, but how? */ @@ -1019,7 +1205,7 @@ static int probe_point_inline_cb(Dwarf_Die *in_die, void *data) pr_debug("found inline addr: 0x%jx\n", (uintmax_t)pf->addr); - param->retval = convert_probe_point(in_die, pf); + param->retval = call_probe_finder(in_die, pf); if (param->retval < 0) return DWARF_CB_ABORT; } @@ -1057,7 +1243,7 @@ static int probe_point_search_cb(Dwarf_Die *sp_die, void *data) } pf->addr += pp->offset; /* TODO: Check the address in this function */ - param->retval = convert_probe_point(sp_die, pf); + param->retval = call_probe_finder(sp_die, pf); } } else { struct dwarf_callback_param _param = {.data = (void *)pf, @@ -1079,90 +1265,276 @@ static int find_probe_point_by_func(struct probe_finder *pf) return _param.retval; } -/* Find probe_trace_events specified by perf_probe_event from debuginfo */ -int find_probe_trace_events(int fd, struct perf_probe_event *pev, - struct probe_trace_event **tevs, int max_tevs) +/* Find probe points from debuginfo */ +static int find_probes(int fd, struct probe_finder *pf) { - struct probe_finder pf = {.pev = pev, .max_tevs = max_tevs}; - struct perf_probe_point *pp = &pev->point; + struct perf_probe_point *pp = &pf->pev->point; Dwarf_Off off, noff; size_t cuhl; Dwarf_Die *diep; - Dwarf *dbg; + Dwarf *dbg = NULL; + Dwfl *dwfl; + Dwarf_Addr bias; /* Currently ignored */ int ret = 0; - pf.tevs = zalloc(sizeof(struct probe_trace_event) * max_tevs); - if (pf.tevs == NULL) - return -ENOMEM; - *tevs = pf.tevs; - pf.ntevs = 0; - - dbg = dwarf_begin(fd, DWARF_C_READ); + dbg = dwfl_init_offline_dwarf(fd, &dwfl, &bias); if (!dbg) { pr_warning("No dwarf info found in the vmlinux - " "please rebuild with CONFIG_DEBUG_INFO=y.\n"); - free(pf.tevs); - *tevs = NULL; return -EBADF; } #if _ELFUTILS_PREREQ(0, 142) /* Get the call frame information from this dwarf */ - pf.cfi = dwarf_getcfi(dbg); + pf->cfi = dwarf_getcfi(dbg); #endif off = 0; - line_list__init(&pf.lcache); + line_list__init(&pf->lcache); /* Loop on CUs (Compilation Unit) */ while (!dwarf_nextcu(dbg, off, &noff, &cuhl, NULL, NULL, NULL) && ret >= 0) { /* Get the DIE(Debugging Information Entry) of this CU */ - diep = dwarf_offdie(dbg, off + cuhl, &pf.cu_die); + diep = dwarf_offdie(dbg, off + cuhl, &pf->cu_die); if (!diep) continue; /* Check if target file is included. */ if (pp->file) - pf.fname = cu_find_realpath(&pf.cu_die, pp->file); + pf->fname = cu_find_realpath(&pf->cu_die, pp->file); else - pf.fname = NULL; + pf->fname = NULL; - if (!pp->file || pf.fname) { + if (!pp->file || pf->fname) { if (pp->function) - ret = find_probe_point_by_func(&pf); + ret = find_probe_point_by_func(pf); else if (pp->lazy_line) - ret = find_probe_point_lazy(NULL, &pf); + ret = find_probe_point_lazy(NULL, pf); else { - pf.lno = pp->line; - ret = find_probe_point_by_line(&pf); + pf->lno = pp->line; + ret = find_probe_point_by_line(pf); } } off = noff; } - line_list__free(&pf.lcache); - dwarf_end(dbg); + line_list__free(&pf->lcache); + if (dwfl) + dwfl_end(dwfl); - return (ret < 0) ? ret : pf.ntevs; + return ret; +} + +/* Add a found probe point into trace event list */ +static int add_probe_trace_event(Dwarf_Die *sp_die, struct probe_finder *pf) +{ + struct trace_event_finder *tf = + container_of(pf, struct trace_event_finder, pf); + struct probe_trace_event *tev; + int ret, i; + + /* Check number of tevs */ + if (tf->ntevs == tf->max_tevs) { + pr_warning("Too many( > %d) probe point found.\n", + tf->max_tevs); + return -ERANGE; + } + tev = &tf->tevs[tf->ntevs++]; + + ret = convert_to_trace_point(sp_die, pf->addr, pf->pev->point.retprobe, + &tev->point); + if (ret < 0) + return ret; + + pr_debug("Probe point found: %s+%lu\n", tev->point.symbol, + tev->point.offset); + + /* Find each argument */ + tev->nargs = pf->pev->nargs; + tev->args = zalloc(sizeof(struct probe_trace_arg) * tev->nargs); + if (tev->args == NULL) + return -ENOMEM; + for (i = 0; i < pf->pev->nargs; i++) { + pf->pvar = &pf->pev->args[i]; + pf->tvar = &tev->args[i]; + ret = find_variable(sp_die, pf); + if (ret != 0) + return ret; + } + + return 0; +} + +/* Find probe_trace_events specified by perf_probe_event from debuginfo */ +int find_probe_trace_events(int fd, struct perf_probe_event *pev, + struct probe_trace_event **tevs, int max_tevs) +{ + struct trace_event_finder tf = { + .pf = {.pev = pev, .callback = add_probe_trace_event}, + .max_tevs = max_tevs}; + int ret; + + /* Allocate result tevs array */ + *tevs = zalloc(sizeof(struct probe_trace_event) * max_tevs); + if (*tevs == NULL) + return -ENOMEM; + + tf.tevs = *tevs; + tf.ntevs = 0; + + ret = find_probes(fd, &tf.pf); + if (ret < 0) { + free(*tevs); + *tevs = NULL; + return ret; + } + + return (ret < 0) ? ret : tf.ntevs; +} + +#define MAX_VAR_LEN 64 + +/* Collect available variables in this scope */ +static int collect_variables_cb(Dwarf_Die *die_mem, void *data) +{ + struct available_var_finder *af = data; + struct variable_list *vl; + char buf[MAX_VAR_LEN]; + int tag, ret; + + vl = &af->vls[af->nvls - 1]; + + tag = dwarf_tag(die_mem); + if (tag == DW_TAG_formal_parameter || + tag == DW_TAG_variable) { + ret = convert_variable_location(die_mem, af->pf.addr, + af->pf.fb_ops, NULL); + if (ret == 0) { + ret = die_get_varname(die_mem, buf, MAX_VAR_LEN); + pr_debug2("Add new var: %s\n", buf); + if (ret > 0) + strlist__add(vl->vars, buf); + } + } + + if (af->child && dwarf_haspc(die_mem, af->pf.addr)) + return DIE_FIND_CB_CONTINUE; + else + return DIE_FIND_CB_SIBLING; +} + +/* Add a found vars into available variables list */ +static int add_available_vars(Dwarf_Die *sp_die, struct probe_finder *pf) +{ + struct available_var_finder *af = + container_of(pf, struct available_var_finder, pf); + struct variable_list *vl; + Dwarf_Die die_mem, *scopes = NULL; + int ret, nscopes; + + /* Check number of tevs */ + if (af->nvls == af->max_vls) { + pr_warning("Too many( > %d) probe point found.\n", af->max_vls); + return -ERANGE; + } + vl = &af->vls[af->nvls++]; + + ret = convert_to_trace_point(sp_die, pf->addr, pf->pev->point.retprobe, + &vl->point); + if (ret < 0) + return ret; + + pr_debug("Probe point found: %s+%lu\n", vl->point.symbol, + vl->point.offset); + + /* Find local variables */ + vl->vars = strlist__new(true, NULL); + if (vl->vars == NULL) + return -ENOMEM; + af->child = true; + die_find_child(sp_die, collect_variables_cb, (void *)af, &die_mem); + + /* Find external variables */ + if (!af->externs) + goto out; + /* Don't need to search child DIE for externs. */ + af->child = false; + nscopes = dwarf_getscopes_die(sp_die, &scopes); + while (nscopes-- > 1) + die_find_child(&scopes[nscopes], collect_variables_cb, + (void *)af, &die_mem); + if (scopes) + free(scopes); + +out: + if (strlist__empty(vl->vars)) { + strlist__delete(vl->vars); + vl->vars = NULL; + } + + return ret; +} + +/* Find available variables at given probe point */ +int find_available_vars_at(int fd, struct perf_probe_event *pev, + struct variable_list **vls, int max_vls, + bool externs) +{ + struct available_var_finder af = { + .pf = {.pev = pev, .callback = add_available_vars}, + .max_vls = max_vls, .externs = externs}; + int ret; + + /* Allocate result vls array */ + *vls = zalloc(sizeof(struct variable_list) * max_vls); + if (*vls == NULL) + return -ENOMEM; + + af.vls = *vls; + af.nvls = 0; + + ret = find_probes(fd, &af.pf); + if (ret < 0) { + /* Free vlist for error */ + while (af.nvls--) { + if (af.vls[af.nvls].point.symbol) + free(af.vls[af.nvls].point.symbol); + if (af.vls[af.nvls].vars) + strlist__delete(af.vls[af.nvls].vars); + } + free(af.vls); + *vls = NULL; + return ret; + } + + return (ret < 0) ? ret : af.nvls; } /* Reverse search */ -int find_perf_probe_point(int fd, unsigned long addr, - struct perf_probe_point *ppt) +int find_perf_probe_point(unsigned long addr, struct perf_probe_point *ppt) { Dwarf_Die cudie, spdie, indie; - Dwarf *dbg; + Dwarf *dbg = NULL; + Dwfl *dwfl = NULL; Dwarf_Line *line; - Dwarf_Addr laddr, eaddr; + Dwarf_Addr laddr, eaddr, bias = 0; const char *tmp; int lineno, ret = 0; bool found = false; - dbg = dwarf_begin(fd, DWARF_C_READ); - if (!dbg) - return -EBADF; + /* Open the live linux kernel */ + dbg = dwfl_init_live_kernel_dwarf(addr, &dwfl, &bias); + if (!dbg) { + pr_warning("No dwarf info found in the vmlinux - " + "please rebuild with CONFIG_DEBUG_INFO=y.\n"); + ret = -EINVAL; + goto end; + } + /* Adjust address with bias */ + addr += bias; /* Find cu die */ - if (!dwarf_addrdie(dbg, (Dwarf_Addr)addr, &cudie)) { + if (!dwarf_addrdie(dbg, (Dwarf_Addr)addr - bias, &cudie)) { + pr_warning("No CU DIE is found at %lx\n", addr); ret = -EINVAL; goto end; } @@ -1225,7 +1597,8 @@ found: } end: - dwarf_end(dbg); + if (dwfl) + dwfl_end(dwfl); if (ret >= 0) ret = found ? 1 : 0; return ret; @@ -1358,6 +1731,8 @@ static int line_range_search_cb(Dwarf_Die *sp_die, void *data) struct line_finder *lf = param->data; struct line_range *lr = lf->lr; + pr_debug("find (%lx) %s\n", dwarf_dieoffset(sp_die), + dwarf_diename(sp_die)); if (dwarf_tag(sp_die) == DW_TAG_subprogram && die_compare_name(sp_die, lr->function)) { lf->fname = dwarf_decl_file(sp_die); @@ -1401,10 +1776,12 @@ int find_line_range(int fd, struct line_range *lr) Dwarf_Off off = 0, noff; size_t cuhl; Dwarf_Die *diep; - Dwarf *dbg; + Dwarf *dbg = NULL; + Dwfl *dwfl; + Dwarf_Addr bias; /* Currently ignored */ const char *comp_dir; - dbg = dwarf_begin(fd, DWARF_C_READ); + dbg = dwfl_init_offline_dwarf(fd, &dwfl, &bias); if (!dbg) { pr_warning("No dwarf info found in the vmlinux - " "please rebuild with CONFIG_DEBUG_INFO=y.\n"); @@ -1450,8 +1827,7 @@ int find_line_range(int fd, struct line_range *lr) } pr_debug("path: %s\n", lr->path); - dwarf_end(dbg); - + dwfl_end(dwfl); return (ret < 0) ? ret : lf.found; } diff --git a/tools/perf/util/probe-finder.h b/tools/perf/util/probe-finder.h index 4507d51..bba69d4 100644 --- a/tools/perf/util/probe-finder.h +++ b/tools/perf/util/probe-finder.h @@ -22,20 +22,27 @@ extern int find_probe_trace_events(int fd, struct perf_probe_event *pev, int max_tevs); /* Find a perf_probe_point from debuginfo */ -extern int find_perf_probe_point(int fd, unsigned long addr, +extern int find_perf_probe_point(unsigned long addr, struct perf_probe_point *ppt); +/* Find a line range */ extern int find_line_range(int fd, struct line_range *lr); +/* Find available variables */ +extern int find_available_vars_at(int fd, struct perf_probe_event *pev, + struct variable_list **vls, int max_points, + bool externs); + #include <dwarf.h> #include <libdw.h> +#include <libdwfl.h> #include <version.h> struct probe_finder { struct perf_probe_event *pev; /* Target probe event */ - struct probe_trace_event *tevs; /* Result trace events */ - int ntevs; /* Number of trace events */ - int max_tevs; /* Max number of trace events */ + + /* Callback when a probe point is found */ + int (*callback)(Dwarf_Die *sp_die, struct probe_finder *pf); /* For function searching */ int lno; /* Line number */ @@ -53,6 +60,22 @@ struct probe_finder { struct probe_trace_arg *tvar; /* Current result variable */ }; +struct trace_event_finder { + struct probe_finder pf; + struct probe_trace_event *tevs; /* Found trace events */ + int ntevs; /* Number of trace events */ + int max_tevs; /* Max number of trace events */ +}; + +struct available_var_finder { + struct probe_finder pf; + struct variable_list *vls; /* Found variable lists */ + int nvls; /* Number of variable lists */ + int max_vls; /* Max no. of variable lists */ + bool externs; /* Find external vars too */ + bool child; /* Search child scopes */ +}; + struct line_finder { struct line_range *lr; /* Target line range */ diff --git a/tools/perf/util/ui/browser.c b/tools/perf/util/ui/browser.c index 6d0df80..8bc010e 100644 --- a/tools/perf/util/ui/browser.c +++ b/tools/perf/util/ui/browser.c @@ -1,4 +1,3 @@ -#include <slang.h> #include "libslang.h" #include <linux/compiler.h> #include <linux/list.h> |