Re: [PATCH v5 3/5] perf callchain: Add support for cross-platform unwind

From: Jiri Olsa
Date: Thu May 26 2016 - 13:52:03 EST


On Tue, May 24, 2016 at 09:20:27AM +0000, He Kuang wrote:
> Use thread specific unwind ops to unwind cross-platform callchains.
>
> Currently, unwind methods is suitable for local unwind, this patch
> changes the fixed methods to thread/map related. Each time a map is
> inserted, we find the target arch and see if this platform can be
> remote unwind. We test for x86 platform and only show proper
> messages. The real unwind methods are not implemented, will be
> introduced in next patch.
>
> CONFIG_LIBUNWIND/NO_LIBUNWIND are changed to
> CONFIG_LOCAL_LIBUNWIND/NO_LOCAL_LIBUNWIND for retaining local unwind
> features. CONFIG_LIBUNWIND stands for either local or remote or both
> unwind are supported and NO_LIBUNWIND means neither local nor remote
> libunwind are supported.
>
> Signed-off-by: He Kuang <hekuang@xxxxxxxxxx>
> ---
> tools/perf/arch/x86/util/Build | 2 +-
> tools/perf/config/Makefile | 23 +++++++++-
> tools/perf/util/Build | 2 +-
> tools/perf/util/thread.c | 5 +--
> tools/perf/util/thread.h | 17 ++++++--
> tools/perf/util/unwind-libunwind.c | 49 +++++++++++++++++----
> tools/perf/util/unwind-libunwind_common.c | 71 +++++++++++++++++++++++++++++--
> tools/perf/util/unwind.h | 37 +++++++++++-----
> 8 files changed, 173 insertions(+), 33 deletions(-)
>
> diff --git a/tools/perf/arch/x86/util/Build b/tools/perf/arch/x86/util/Build
> index 4659703..bc24b75 100644
> --- a/tools/perf/arch/x86/util/Build
> +++ b/tools/perf/arch/x86/util/Build
> @@ -7,7 +7,7 @@ libperf-y += perf_regs.o
> libperf-$(CONFIG_DWARF) += dwarf-regs.o
> libperf-$(CONFIG_BPF_PROLOGUE) += dwarf-regs.o
>
> -libperf-$(CONFIG_LIBUNWIND) += unwind-libunwind.o
> +libperf-$(CONFIG_LOCAL_LIBUNWIND) += unwind-libunwind.o
> libperf-$(CONFIG_LIBDW_DWARF_UNWIND) += unwind-libdw.o
>
> libperf-$(CONFIG_AUXTRACE) += auxtrace.o
> diff --git a/tools/perf/config/Makefile b/tools/perf/config/Makefile
> index c9e1625..8ac0440 100644
> --- a/tools/perf/config/Makefile
> +++ b/tools/perf/config/Makefile
> @@ -354,15 +354,31 @@ ifeq ($(ARCH),powerpc)
> endif
>
> ifndef NO_LIBUNWIND
> + have_libunwind =
> ifeq ($(feature-libunwind-x86), 1)
> $(call detected,CONFIG_LIBUNWIND_X86)
> CFLAGS += -DHAVE_LIBUNWIND_X86_SUPPORT
> + LDFLAGS += -lunwind-x86
> + have_libunwind = 1
> endif
>
> ifneq ($(feature-libunwind), 1)
> msg := $(warning No libunwind found. Please install libunwind-dev[el] >= 1.1 and/or set LIBUNWIND_DIR);
> + NO_LOCAL_LIBUNWIND := 1
> + else
> + have_libunwind = 1
> + CFLAGS += -DHAVE_LIBUNWIND_LOCAL_SUPPORT
> + $(call detected,CONFIG_LOCAL_LIBUNWIND)
> + endif
> +
> + ifneq ($(have_libunwind), 1)
> NO_LIBUNWIND := 1
> + else
> + CFLAGS += -I$(LIBUNWIND_DIR)/include
> + LDFLAGS += -L$(LIBUNWIND_DIR)/lib
> endif
> +else
> + NO_LOCAL_LIBUNWIND := 1
> endif
>
> ifndef NO_LIBBPF
> @@ -400,7 +416,7 @@ else
> NO_DWARF_UNWIND := 1
> endif
>
> -ifndef NO_LIBUNWIND
> +ifndef NO_LOCAL_LIBUNWIND
> ifeq ($(ARCH),$(filter $(ARCH),arm arm64))
> $(call feature_check,libunwind-debug-frame)
> ifneq ($(feature-libunwind-debug-frame), 1)
> @@ -411,12 +427,15 @@ ifndef NO_LIBUNWIND
> # non-ARM has no dwarf_find_debug_frame() function:
> CFLAGS += -DNO_LIBUNWIND_DEBUG_FRAME
> endif
> - CFLAGS += -DHAVE_LIBUNWIND_SUPPORT
> EXTLIBS += $(LIBUNWIND_LIBS)
> CFLAGS += $(LIBUNWIND_CFLAGS)
> LDFLAGS += $(LIBUNWIND_LDFLAGS)
> endif
>
> +ifndef NO_LIBUNWIND
> + CFLAGS += -DHAVE_LIBUNWIND_SUPPORT
> +endif
> +
> ifndef NO_LIBAUDIT
> ifneq ($(feature-libaudit), 1)
> msg := $(warning No libaudit.h found, disables 'trace' tool, please install audit-libs-devel or libaudit-dev);
> diff --git a/tools/perf/util/Build b/tools/perf/util/Build
> index 25c31fb..ce69721 100644
> --- a/tools/perf/util/Build
> +++ b/tools/perf/util/Build
> @@ -99,7 +99,7 @@ libperf-$(CONFIG_DWARF) += probe-finder.o
> libperf-$(CONFIG_DWARF) += dwarf-aux.o
>
> libperf-$(CONFIG_LIBDW_DWARF_UNWIND) += unwind-libdw.o
> -libperf-$(CONFIG_LIBUNWIND) += unwind-libunwind.o
> +libperf-$(CONFIG_LOCAL_LIBUNWIND) += unwind-libunwind.o
> libperf-$(CONFIG_LIBUNWIND) += unwind-libunwind_common.o
>
> libperf-$(CONFIG_LIBBABELTRACE) += data-convert-bt.o
> diff --git a/tools/perf/util/thread.c b/tools/perf/util/thread.c
> index 3043113..4e1aaf5 100644
> --- a/tools/perf/util/thread.c
> +++ b/tools/perf/util/thread.c
> @@ -43,9 +43,6 @@ struct thread *thread__new(pid_t pid, pid_t tid)
> thread->cpu = -1;
> INIT_LIST_HEAD(&thread->comm_list);
>
> - if (unwind__prepare_access(thread) < 0)
> - goto err_thread;
> -
> comm_str = malloc(32);
> if (!comm_str)
> goto err_thread;
> @@ -59,6 +56,8 @@ struct thread *thread__new(pid_t pid, pid_t tid)
> list_add(&comm->list, &thread->comm_list);
> atomic_set(&thread->refcnt, 1);
> RB_CLEAR_NODE(&thread->rb_node);
> +
> + register_null_unwind_libunwind_ops(thread);
> }
>
> return thread;
> diff --git a/tools/perf/util/thread.h b/tools/perf/util/thread.h
> index 45fba13..647b011 100644
> --- a/tools/perf/util/thread.h
> +++ b/tools/perf/util/thread.h
> @@ -9,12 +9,20 @@
> #include "symbol.h"
> #include <strlist.h>
> #include <intlist.h>
> -#ifdef HAVE_LIBUNWIND_SUPPORT
> -#include <libunwind.h>
> -#endif
>
> struct thread_stack;
>
> +struct unwind_entry;
> +typedef int (*unwind_entry_cb_t)(struct unwind_entry *entry, void *arg);
> +struct unwind_libunwind_ops {
> + int (*prepare_access)(struct thread *thread);
> + void (*flush_access)(struct thread *thread);
> + void (*finish_access)(struct thread *thread);
> + int (*get_entries)(unwind_entry_cb_t cb, void *arg,
> + struct thread *thread,
> + struct perf_sample *data, int max_stack);
> +};
> +
> struct thread {
> union {
> struct rb_node rb_node;
> @@ -36,7 +44,8 @@ struct thread {
> void *priv;
> struct thread_stack *ts;
> #ifdef HAVE_LIBUNWIND_SUPPORT
> - unw_addr_space_t addr_space;
> + void *addr_space;
> + struct unwind_libunwind_ops *unwind_libunwind_ops;
> #endif
> };
>
> diff --git a/tools/perf/util/unwind-libunwind.c b/tools/perf/util/unwind-libunwind.c
> index 63687d3..a6ec587 100644
> --- a/tools/perf/util/unwind-libunwind.c
> +++ b/tools/perf/util/unwind-libunwind.c
> @@ -22,8 +22,11 @@
> #include <unistd.h>
> #include <sys/mman.h>
> #include <linux/list.h>
> +#ifdef REMOTE_UNWIND_LIBUNWIND
> +#include "libunwind-arch.h"
> +#else
> #include <libunwind.h>
> -#include <libunwind-ptrace.h>
> +#endif
> #include "callchain.h"
> #include "thread.h"
> #include "session.h"
> @@ -34,6 +37,20 @@
> #include "debug.h"
> #include "asm/bug.h"
>
> +#ifndef REMOTE_UNWIND_LIBUNWIND
> + #define LOCAL_UNWIND_LIBUNWIND
> + #undef UNWT_OBJ
> + #define UNWT_OBJ(x) _##x
> +#else
> + #undef NO_LIBUNWIND_DEBUG_FRAME
> + #if defined(LIBUNWIND_ARM) && !defined(NO_LIBUNWIND_DEBUG_FRAME_ARM)
> + #elif defined(LIBUNWIND_AARCH64) && \
> + defined(NO_LIBUNWIND_DEBUG_FRAME_ARM_AARCH64)
> + #else
> + #define NO_LIBUNWIND_DEBUG_FRAME
> + #endif
> +#endif
> +
> extern int
> UNW_OBJ(dwarf_search_unwind_table) (unw_addr_space_t as,
> unw_word_t ip,
> @@ -508,7 +525,7 @@ static int access_reg(unw_addr_space_t __maybe_unused as,
> return 0;
> }
>
> - id = libunwind__arch_reg_id(regnum);
> + id = LIBUNWIND__ARCH_REG_ID(regnum);
> if (id < 0)
> return -EINVAL;
>
> @@ -579,7 +596,7 @@ static unw_accessors_t accessors = {
> .get_proc_name = get_proc_name,
> };
>
> -int unwind__prepare_access(struct thread *thread)
> +static int UNWT_OBJ(_unwind__prepare_access)(struct thread *thread)
> {
> if (callchain_param.record_mode != CALLCHAIN_DWARF)
> return 0;
> @@ -594,7 +611,7 @@ int unwind__prepare_access(struct thread *thread)
> return 0;
> }
>
> -void unwind__flush_access(struct thread *thread)
> +static void UNWT_OBJ(_unwind__flush_access)(struct thread *thread)
> {
> if (callchain_param.record_mode != CALLCHAIN_DWARF)
> return;
> @@ -602,7 +619,7 @@ void unwind__flush_access(struct thread *thread)
> unw_flush_cache(thread->addr_space, 0, 0);
> }
>
> -void unwind__finish_access(struct thread *thread)
> +static void UNWT_OBJ(_unwind__finish_access)(struct thread *thread)
> {
> if (callchain_param.record_mode != CALLCHAIN_DWARF)
> return;
> @@ -662,9 +679,10 @@ static int get_entries(struct unwind_info *ui, unwind_entry_cb_t cb,
> return ret;
> }
>
> -int unwind__get_entries(unwind_entry_cb_t cb, void *arg,
> - struct thread *thread,
> - struct perf_sample *data, int max_stack)
> +static int UNWT_OBJ(_unwind__get_entries)(unwind_entry_cb_t cb, void *arg,
> + struct thread *thread,
> + struct perf_sample *data,
> + int max_stack)
> {
> struct unwind_info ui = {
> .sample = data,
> @@ -680,3 +698,18 @@ int unwind__get_entries(unwind_entry_cb_t cb, void *arg,
>
> return get_entries(&ui, cb, arg, max_stack);
> }
> +
> +struct unwind_libunwind_ops
> +UNWT_OBJ(unwind_libunwind_ops) = {
> + .prepare_access = UNWT_OBJ(_unwind__prepare_access),
> + .flush_access = UNWT_OBJ(_unwind__flush_access),
> + .finish_access = UNWT_OBJ(_unwind__finish_access),
> + .get_entries = UNWT_OBJ(_unwind__get_entries),
> +};
> +
> +#ifdef LOCAL_UNWIND_LIBUNWIND
> +void register_local_unwind_libunwind_ops(struct thread *thread)
> +{
> + thread->unwind_libunwind_ops = &UNWT_OBJ(unwind_libunwind_ops);
> +}
> +#endif
> diff --git a/tools/perf/util/unwind-libunwind_common.c b/tools/perf/util/unwind-libunwind_common.c
> index 3946c99..f44833b 100644
> --- a/tools/perf/util/unwind-libunwind_common.c
> +++ b/tools/perf/util/unwind-libunwind_common.c
> @@ -5,10 +5,64 @@
> #include "debug.h"
> #include "arch/common.h"
>
> +static int __null__prepare_access(struct thread *thread __maybe_unused)
> +{
> + return 0;
> +}
> +
> +static void __null__flush_access(struct thread *thread __maybe_unused)
> +{
> +}
> +
> +static void __null__finish_access(struct thread *thread __maybe_unused)
> +{
> +}
> +
> +static int __null__get_entries(unwind_entry_cb_t cb __maybe_unused,
> + void *arg __maybe_unused,
> + struct thread *thread __maybe_unused,
> + struct perf_sample *data __maybe_unused,
> + int max_stack __maybe_unused)
> +{
> + return 0;
> +}
> +
> +static struct unwind_libunwind_ops null_unwind_libunwind_ops = {
> + .prepare_access = __null__prepare_access,
> + .flush_access = __null__flush_access,
> + .finish_access = __null__finish_access,
> + .get_entries = __null__get_entries,
> +};
> +
> +void register_null_unwind_libunwind_ops(struct thread *thread)
> +{
> + thread->unwind_libunwind_ops = &null_unwind_libunwind_ops;
> + if (thread->mg)
> + pr_err("unwind: target platform=%s unwind unsupported\n",
> + thread->mg->machine->env->arch);
> +}
> +
> +void register_unwind_libunwind_ops(struct unwind_libunwind_ops *ops,
> + struct thread *thread)
> +{
> + thread->unwind_libunwind_ops = ops;
> +}
> +
> +void unwind__flush_access(struct thread *thread)
> +{
> + thread->unwind_libunwind_ops->flush_access(thread);
> +}
> +
> +void unwind__finish_access(struct thread *thread)
> +{
> + thread->unwind_libunwind_ops->finish_access(thread);
> +}
> +
> void unwind__get_arch(struct thread *thread, struct map *map)
> {
> const char *arch;
> enum dso_type dso_type;
> + int use_local_unwind = 1;
>
> if (!thread->mg->machine->env)
> return;
> @@ -17,18 +71,27 @@ void unwind__get_arch(struct thread *thread, struct map *map)
> if (dso_type == DSO__TYPE_UNKNOWN)
> return;
>
> - if (thread->addr_space)
> + if (thread->addr_space) {
> pr_debug("unwind: thread map already set, 64bit is %d, dso=%s\n",
> dso_type == DSO__TYPE_64BIT, map->dso->name);
> + return;
> + }
>
> arch = normalize_arch(thread->mg->machine->env->arch);
>
> if (!strcmp(arch, "x86")) {
> - if (dso_type != DSO__TYPE_64BIT)
> + if (dso_type != DSO__TYPE_64BIT) {
> #ifdef HAVE_LIBUNWIND_X86_SUPPORT
> pr_err("unwind: target platform=%s is not implemented\n", arch);
> -#else
> - pr_err("unwind: target platform=%s is not supported\n", arch);
> #endif
> + register_null_unwind_libunwind_ops(thread);
> + use_local_unwind = 0;
> + }
> }
> +
> + if (use_local_unwind)
> + register_local_unwind_libunwind_ops(thread);
> +
> + if (thread->unwind_libunwind_ops->prepare_access(thread) < 0)
> + return;
> }
> diff --git a/tools/perf/util/unwind.h b/tools/perf/util/unwind.h
> index 889d630..e170be7 100644
> --- a/tools/perf/util/unwind.h
> +++ b/tools/perf/util/unwind.h
> @@ -15,26 +15,46 @@ struct unwind_entry {
> typedef int (*unwind_entry_cb_t)(struct unwind_entry *entry, void *arg);
>
> #ifdef HAVE_DWARF_UNWIND_SUPPORT
> +
> +#ifndef LIBUNWIND__ARCH_REG_ID
> +#define LIBUNWIND__ARCH_REG_ID libunwind__arch_reg_id
> +#endif
> +
> int unwind__get_entries(unwind_entry_cb_t cb, void *arg,
> struct thread *thread,
> struct perf_sample *data, int max_stack);
> /* libunwind specific */
> #ifdef HAVE_LIBUNWIND_SUPPORT
> int libunwind__arch_reg_id(int regnum);
> -int unwind__prepare_access(struct thread *thread);
> void unwind__flush_access(struct thread *thread);
> void unwind__finish_access(struct thread *thread);
> void unwind__get_arch(struct thread *thread, struct map *map);
> -#else
> -static inline int unwind__prepare_access(struct thread *thread __maybe_unused)
> -{
> - return 0;
> +void register_unwind_libunwind_ops(struct unwind_libunwind_ops *ops,
> + struct thread *thread);
> +void register_null_unwind_libunwind_ops(struct thread *thread);
> +
> +#ifndef HAVE_LIBUNWIND_LOCAL_SUPPORT
> +static inline void
> +register_local_unwind_libunwind_ops(struct thread *thread) {
> + register_null_unwind_libunwind_ops(thread);
> }
> +#else
> +void register_local_unwind_libunwind_ops(struct thread *thread);
> +#endif
> +
> +#define unwind__get_entries(cb, arg, \
> + thread, \
> + data, max_stack) \
> + thread->unwind_libunwind_ops->get_entries(cb, arg, thread, \
> + data, max_stack)

I think this should go along with other ops wrappers like
unwind__flush_access/unwind__finish_access.. in unwind-libunwind_common.c

jirka