diff options
author | Ingo Molnar <mingo@elte.hu> | 2010-10-23 20:05:43 +0200 |
---|---|---|
committer | Ingo Molnar <mingo@elte.hu> | 2010-10-23 20:05:43 +0200 |
commit | b8ecad8b2f8757d51632b1ea6d602c1f7b9760a2 (patch) | |
tree | 5a03029dbf02df606fa48a82525eb321e59b5c33 /tools | |
parent | eea4a0b19a2719e3e23b5450dd9fbe97789d2a57 (diff) | |
parent | 8bfb5e7d6a14b29cffddd113f4b0be7d9aafc1e8 (diff) |
Merge branch 'perf/core' of git://git.kernel.org/pub/scm/linux/kernel/git/acme/linux-2.6 into perf/urgent
Diffstat (limited to 'tools')
-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 |
8 files changed, 793 insertions, 194 deletions
diff --git a/tools/perf/Documentation/perf-probe.txt b/tools/perf/Documentation/perf-probe.txt index 27d52dae5a4..62de1b7f4e7 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 199d5e19554..2e000c068cc 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 78575796d5f..b397c038372 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 fcc16e4349d..3b6a5297bf1 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 5af39243a25..5accbedfea3 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 32b81f707ff..c20bd52833a 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; - } - } |