[PATCH 4/4] perf tools: unwinding: try to read from map_adj for a unmapped address.

Wang Nan
2015-04-01T10:33:59Z

Previous patch allows users to use --map-adjustment to hint perf about
address ranges known to be shared object files backended. However, if
only parts of such object files are read, libunwind will fail to get
required information from unmapped area of those files.

This patch makes access_dso_mem() to try it best when when reading from
DSO. When it tried and fail the first time, it iterates over
map_adj_list to search a mapped DSO whcih contains such address and read
from it.

Signed-off-by: Wang Nan <wangnan0@huawei.com>
---
tools/perf/util/machine.c | 55 ++++++++++++++++++++++++++++++++++++++
tools/perf/util/machine.h | 1 +
tools/perf/util/unwind-libunwind.c | 18 +++++++++++--
3 files changed, 72 insertions(+), 2 deletions(-)

diff --git a/tools/perf/util/machine.c b/tools/perf/util/machine.c
index dc9e91e..5176932 100644
--- a/tools/perf/util/machine.c
+++ b/tools/perf/util/machine.c
@@ -1186,6 +1186,9 @@ struct map_adj {
u64 start;
u64 len;
u64 pgoff;
+ /* filled when first mapping */
+ bool mapped;
+ u64 map_start;
struct list_head list;
char filename[PATH_MAX];
};
@@ -1304,6 +1307,7 @@ static int machine_add_map_adj(u32 pid, u64 start, u64 len,
new->start = start;
new->len = len;
new->pgoff = pgoff;
+ new->mapped = false;
strncpy(new->filename, filename, PATH_MAX);
list_add(&new->list, pos->list.prev);
return 0;
@@ -1351,6 +1355,11 @@ again:
map = map__new(machine, adj_start, adj_len, adj_pgoff,
pid, 0, 0, 0, 0, prot, flags,
pos->filename, type, thread);
+
+ if (!pos->mapped) {
+ pos->mapped = true;
+ pos->map_start = adj_start;
+ }
} else {
/*
* |<----- tmp ----->|
@@ -1393,6 +1402,52 @@ error:
return -1;
}

+/*
+ * Search map_adj according to pid and address and return an already
+ * mapped address. This function is used for dwarf unwinding. When
+ * libunwind tries to read from a DSO and fail because the area is not
+ * mapped, this function can help it to find cooresponding map_adj, and
+ * give it another chance to read from DSO.
+ *
+ * However, we are unable to ensure this searching always correct. See
+ * this:
+ *
+ * |<--- liba.so --->|<-.. liba.so (unmapped) ..->|
+ * |<--- libb.so --->|<-.. libb.so (unmapped) ..->|
+ * |<--- mmaped map_adj --->| ^
+ *
+ * Actually mapped area is assembled by part of liba.so and part of
+ * libb.so. When libunwind tries to read from ^ marked place, this
+ * searching returns liba.so, but in fact no one (expect libunwind
+ * itself) knows which map_adj is correct.
+ */
+int machine__search_map_adj(pid_t pid, u64 addr, u64 *map_start)
+{
+ struct map_adj *pos;
+
+ if (!map_start)
+ return -EINVAL;
+
+ list_for_each_entry(pos, &map_adj_list, list) {
+ if (pos->pid != (u32)(-1)) {
+ if (pos->pid < (u32)pid)
+ continue;
+ if (pos->pid > (u32)pid)
+ break;
+ }
+
+ if (!pos->mapped)
+ continue;
+
+ if ((pos->start <= addr) && (pos->start + pos->len > addr)) {
+ *map_start = pos->map_start;
+ return 0;
+ }
+ }
+
+ return -EEXIST;
+}
+
int parse_map_adjustment(const struct option *opt __maybe_unused,
const char *arg, int unset __maybe_unused)
{
diff --git a/tools/perf/util/machine.h b/tools/perf/util/machine.h
index 73b49e4..b0c66ce 100644
--- a/tools/perf/util/machine.h
+++ b/tools/perf/util/machine.h
@@ -224,5 +224,6 @@ int machine__set_current_tid(struct machine *machine, int cpu, pid_t pid,
pid_t tid);

int parse_map_adjustment(const struct option *opt, const char *arg, int unset);
+int machine__search_map_adj(pid_t pid, u64 addr, u64 *map_start);

#endif /* __PERF_MACHINE_H */
diff --git a/tools/perf/util/unwind-libunwind.c b/tools/perf/util/unwind-libunwind.c
index 78a32c7..a78c280 100644
--- a/tools/perf/util/unwind-libunwind.c
+++ b/tools/perf/util/unwind-libunwind.c
@@ -412,8 +412,22 @@ static int access_dso_mem(struct unwind_info *ui, unw_word_t addr,
thread__find_addr_map(ui->thread, PERF_RECORD_MISC_USER,
MAP__FUNCTION, addr, &al);
if (!al.map) {
- pr_debug("unwind: no map for %lx\n", (unsigned long)addr);
- return -1;
+ u64 map_start;
+
+ pr_debug("unwind: try find map from map_adj: %lx\n", (unsigned long)addr);
+ if (machine__search_map_adj(ui->thread->pid_, (u64)addr, &map_start)) {
+ pr_debug("unwind: no map for %lx even consider map adjustment\n",
+ (unsigned long)addr);
+ return -1;
+ }
+
+ thread__find_addr_map(ui->thread, PERF_RECORD_MISC_USER,
+ MAP__FUNCTION, map_start, &al);
+ if (!al.map) {
+ pr_debug("unwind: unable to find map for %lx (through %lx)\n",
+ (unsigned long)addr, (unsigned long)map_start);
+ return -1;
+ }
}

if (!al.map->dso)
--
1.8.3.4