mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-11-16 16:54:20 +08:00
perf/core improvements and fixes:
User visible: - Support cross unwinding, i.e. collecting '--call-graph dwarf' perf.data files in one machine and then doing analysis in another machine of a different hardware architecture. This enables, for instance, to do: perf record -a --call-graph dwarf on a x86-32 or aarch64 system and then do 'perf report' on it on a x86_64 workstation. (He Kuang) - Fix crash in build_id_cache__kallsyms_path(), recent regression (Wang Nan) Infrastructure: - Make tools/lib/bpf use the IS_ERR return facility consistently and also stop using the _get_ term for non-reference count methods (Arnaldo Carvalho de Melo) - 'perf config' refactorings (Taeung Song) Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com> -----BEGIN PGP SIGNATURE----- Version: GnuPG v2 iQIcBAABCAAGBQJXVyVuAAoJENZQFvNTUqpAcXAP/R+mXODq3brY9C9N4/ujnAvc vu/WHq7J1kbL8JrAsUHos0avPCZJigibx3rzG6ZuV3SqOpvKBc7hy+nSOrRyXgT3 QG8mMpdhgyexCYRkd8nELAxS2q1E6XzeHJ5Ot5gcHx+N1QCCTm9xZ4++m5ee/Juo 0ceud29dljmjjTehAzEht5N4fgRPavAwjO8DTYVQF0+hMPbGQE7EAS7LF+mDTFxY UAe7n1buyBm4Y/OLXNP79zQG78eSktau3amUiZ/W3owJkhbUrE7trfWNnNPCYQVU ySVm0xbmy8fdhm6XNxCem+ya9SZ1ROF71A0FiDWlShioutcKBbustBo5UO82Rjxg htHLOoE08BqniIWrqg8VCG2SP8PjzC9JrjxsppnZd4UgfgvwJ0hmRZ++h5dN1HFk /RK2GJTot4EaxR9etkWG9R1sCBLAtJy+MLO/oHMxpR8Rw/1RlJRPV7amnLMvrmdx wRlEVa+OkV0Yb968aai9DObqOkNmNSMLXXQ5ez5T01h2+VjA/PV7UtJ7z0O7Nk4L lyn6e1IE5uko1oaPJqNhfnu7imq1AhRt8ysXfEHF4RD+eIlUMTZNxRzfyruKyJXo YUxpfVGho60pYKMYHjriH966wWxCmWZhhY2qERbt3A9ECkVuagUWiFwx/u5ngiG1 BWxVkGtBh1yUKuttY7We =DvZE -----END PGP SIGNATURE----- Merge tag 'perf-core-for-mingo-20160607' of git://git.kernel.org/pub/scm/linux/kernel/git/acme/linux into perf/core Pull perf/core improvements and fixes from Arnaldo Carvalho de Melo: User visible changes: - Support cross unwinding, i.e. collecting '--call-graph dwarf' perf.data files in one machine and then doing analysis in another machine of a different hardware architecture. This enables, for instance, to do: perf record -a --call-graph dwarf on a x86-32 or aarch64 system and then do 'perf report' on it on a x86_64 workstation. (He Kuang) - Fix crash in build_id_cache__kallsyms_path(), recent regression (Wang Nan) Infrastructure changes: - Make tools/lib/bpf use the IS_ERR return facility consistently and also stop using the _get_ term for non-reference count methods (Arnaldo Carvalho de Melo) - 'perf config' refactorings (Taeung Song) Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com> Signed-off-by: Ingo Molnar <mingo@kernel.org>
This commit is contained in:
commit
b8ab92201a
@ -1186,20 +1186,14 @@ bpf_object__next(struct bpf_object *prev)
|
||||
return next;
|
||||
}
|
||||
|
||||
const char *
|
||||
bpf_object__get_name(struct bpf_object *obj)
|
||||
const char *bpf_object__name(struct bpf_object *obj)
|
||||
{
|
||||
if (!obj)
|
||||
return ERR_PTR(-EINVAL);
|
||||
return obj->path;
|
||||
return obj ? obj->path : ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
unsigned int
|
||||
bpf_object__get_kversion(struct bpf_object *obj)
|
||||
unsigned int bpf_object__kversion(struct bpf_object *obj)
|
||||
{
|
||||
if (!obj)
|
||||
return 0;
|
||||
return obj->kern_version;
|
||||
return obj ? obj->kern_version : 0;
|
||||
}
|
||||
|
||||
struct bpf_program *
|
||||
@ -1224,9 +1218,8 @@ bpf_program__next(struct bpf_program *prev, struct bpf_object *obj)
|
||||
return &obj->programs[idx];
|
||||
}
|
||||
|
||||
int bpf_program__set_private(struct bpf_program *prog,
|
||||
void *priv,
|
||||
bpf_program_clear_priv_t clear_priv)
|
||||
int bpf_program__set_priv(struct bpf_program *prog, void *priv,
|
||||
bpf_program_clear_priv_t clear_priv)
|
||||
{
|
||||
if (prog->priv && prog->clear_priv)
|
||||
prog->clear_priv(prog, prog->priv);
|
||||
@ -1236,10 +1229,9 @@ int bpf_program__set_private(struct bpf_program *prog,
|
||||
return 0;
|
||||
}
|
||||
|
||||
int bpf_program__get_private(struct bpf_program *prog, void **ppriv)
|
||||
void *bpf_program__priv(struct bpf_program *prog)
|
||||
{
|
||||
*ppriv = prog->priv;
|
||||
return 0;
|
||||
return prog ? prog->priv : ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
const char *bpf_program__title(struct bpf_program *prog, bool needs_copy)
|
||||
@ -1311,32 +1303,23 @@ int bpf_program__nth_fd(struct bpf_program *prog, int n)
|
||||
return fd;
|
||||
}
|
||||
|
||||
int bpf_map__get_fd(struct bpf_map *map)
|
||||
int bpf_map__fd(struct bpf_map *map)
|
||||
{
|
||||
if (!map)
|
||||
return -EINVAL;
|
||||
|
||||
return map->fd;
|
||||
return map ? map->fd : -EINVAL;
|
||||
}
|
||||
|
||||
int bpf_map__get_def(struct bpf_map *map, struct bpf_map_def *pdef)
|
||||
const struct bpf_map_def *bpf_map__def(struct bpf_map *map)
|
||||
{
|
||||
if (!map || !pdef)
|
||||
return -EINVAL;
|
||||
|
||||
*pdef = map->def;
|
||||
return 0;
|
||||
return map ? &map->def : ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
const char *bpf_map__get_name(struct bpf_map *map)
|
||||
const char *bpf_map__name(struct bpf_map *map)
|
||||
{
|
||||
if (!map)
|
||||
return NULL;
|
||||
return map->name;
|
||||
return map ? map->name : NULL;
|
||||
}
|
||||
|
||||
int bpf_map__set_private(struct bpf_map *map, void *priv,
|
||||
bpf_map_clear_priv_t clear_priv)
|
||||
int bpf_map__set_priv(struct bpf_map *map, void *priv,
|
||||
bpf_map_clear_priv_t clear_priv)
|
||||
{
|
||||
if (!map)
|
||||
return -EINVAL;
|
||||
@ -1351,14 +1334,9 @@ int bpf_map__set_private(struct bpf_map *map, void *priv,
|
||||
return 0;
|
||||
}
|
||||
|
||||
int bpf_map__get_private(struct bpf_map *map, void **ppriv)
|
||||
void *bpf_map__priv(struct bpf_map *map)
|
||||
{
|
||||
if (!map)
|
||||
return -EINVAL;
|
||||
|
||||
if (ppriv)
|
||||
*ppriv = map->priv;
|
||||
return 0;
|
||||
return map ? map->priv : ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
struct bpf_map *
|
||||
@ -1389,7 +1367,7 @@ bpf_map__next(struct bpf_map *prev, struct bpf_object *obj)
|
||||
}
|
||||
|
||||
struct bpf_map *
|
||||
bpf_object__get_map_by_name(struct bpf_object *obj, const char *name)
|
||||
bpf_object__find_map_by_name(struct bpf_object *obj, const char *name)
|
||||
{
|
||||
struct bpf_map *pos;
|
||||
|
||||
|
@ -55,8 +55,8 @@ void bpf_object__close(struct bpf_object *object);
|
||||
/* Load/unload object into/from kernel */
|
||||
int bpf_object__load(struct bpf_object *obj);
|
||||
int bpf_object__unload(struct bpf_object *obj);
|
||||
const char *bpf_object__get_name(struct bpf_object *obj);
|
||||
unsigned int bpf_object__get_kversion(struct bpf_object *obj);
|
||||
const char *bpf_object__name(struct bpf_object *obj);
|
||||
unsigned int bpf_object__kversion(struct bpf_object *obj);
|
||||
|
||||
struct bpf_object *bpf_object__next(struct bpf_object *prev);
|
||||
#define bpf_object__for_each_safe(pos, tmp) \
|
||||
@ -78,11 +78,10 @@ struct bpf_program *bpf_program__next(struct bpf_program *prog,
|
||||
typedef void (*bpf_program_clear_priv_t)(struct bpf_program *,
|
||||
void *);
|
||||
|
||||
int bpf_program__set_private(struct bpf_program *prog, void *priv,
|
||||
bpf_program_clear_priv_t clear_priv);
|
||||
int bpf_program__set_priv(struct bpf_program *prog, void *priv,
|
||||
bpf_program_clear_priv_t clear_priv);
|
||||
|
||||
int bpf_program__get_private(struct bpf_program *prog,
|
||||
void **ppriv);
|
||||
void *bpf_program__priv(struct bpf_program *prog);
|
||||
|
||||
const char *bpf_program__title(struct bpf_program *prog, bool needs_copy);
|
||||
|
||||
@ -171,7 +170,7 @@ struct bpf_map_def {
|
||||
*/
|
||||
struct bpf_map;
|
||||
struct bpf_map *
|
||||
bpf_object__get_map_by_name(struct bpf_object *obj, const char *name);
|
||||
bpf_object__find_map_by_name(struct bpf_object *obj, const char *name);
|
||||
|
||||
struct bpf_map *
|
||||
bpf_map__next(struct bpf_map *map, struct bpf_object *obj);
|
||||
@ -180,13 +179,13 @@ bpf_map__next(struct bpf_map *map, struct bpf_object *obj);
|
||||
(pos) != NULL; \
|
||||
(pos) = bpf_map__next((pos), (obj)))
|
||||
|
||||
int bpf_map__get_fd(struct bpf_map *map);
|
||||
int bpf_map__get_def(struct bpf_map *map, struct bpf_map_def *pdef);
|
||||
const char *bpf_map__get_name(struct bpf_map *map);
|
||||
int bpf_map__fd(struct bpf_map *map);
|
||||
const struct bpf_map_def *bpf_map__def(struct bpf_map *map);
|
||||
const char *bpf_map__name(struct bpf_map *map);
|
||||
|
||||
typedef void (*bpf_map_clear_priv_t)(struct bpf_map *, void *);
|
||||
int bpf_map__set_private(struct bpf_map *map, void *priv,
|
||||
bpf_map_clear_priv_t clear_priv);
|
||||
int bpf_map__get_private(struct bpf_map *map, void **ppriv);
|
||||
int bpf_map__set_priv(struct bpf_map *map, void *priv,
|
||||
bpf_map_clear_priv_t clear_priv);
|
||||
void *bpf_map__priv(struct bpf_map *map);
|
||||
|
||||
#endif
|
||||
|
@ -1,4 +1,4 @@
|
||||
libperf-$(CONFIG_DWARF) += dwarf-regs.o
|
||||
|
||||
libperf-$(CONFIG_LIBUNWIND) += unwind-libunwind.o
|
||||
libperf-$(CONFIG_LOCAL_LIBUNWIND) += unwind-libunwind.o
|
||||
libperf-$(CONFIG_LIBDW_DWARF_UNWIND) += unwind-libdw.o
|
||||
|
@ -1,2 +1,2 @@
|
||||
libperf-$(CONFIG_DWARF) += dwarf-regs.o
|
||||
libperf-$(CONFIG_LIBUNWIND) += unwind-libunwind.o
|
||||
libperf-$(CONFIG_LOCAL_LIBUNWIND) += unwind-libunwind.o
|
||||
|
@ -1,11 +1,13 @@
|
||||
|
||||
#ifndef REMOTE_UNWIND_LIBUNWIND
|
||||
#include <errno.h>
|
||||
#include <libunwind.h>
|
||||
#include "perf_regs.h"
|
||||
#include "../../util/unwind.h"
|
||||
#include "../../util/debug.h"
|
||||
#endif
|
||||
|
||||
int libunwind__arch_reg_id(int regnum)
|
||||
int LIBUNWIND__ARCH_REG_ID(int regnum)
|
||||
{
|
||||
switch (regnum) {
|
||||
case UNW_AARCH64_X0:
|
||||
|
@ -102,7 +102,7 @@ static int lookup_triplets(const char *const *triplets, const char *name)
|
||||
* Return architecture name in a normalized form.
|
||||
* The conversion logic comes from the Makefile.
|
||||
*/
|
||||
static const char *normalize_arch(char *arch)
|
||||
const char *normalize_arch(char *arch)
|
||||
{
|
||||
if (!strcmp(arch, "x86_64"))
|
||||
return "x86";
|
||||
|
@ -6,5 +6,6 @@
|
||||
extern const char *objdump_path;
|
||||
|
||||
int perf_env__lookup_objdump(struct perf_env *env);
|
||||
const char *normalize_arch(char *arch);
|
||||
|
||||
#endif /* ARCH_PERF_COMMON_H */
|
||||
|
@ -8,7 +8,7 @@ libperf-y += group.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
|
||||
|
@ -1,12 +1,14 @@
|
||||
|
||||
#ifndef REMOTE_UNWIND_LIBUNWIND
|
||||
#include <errno.h>
|
||||
#include <libunwind.h>
|
||||
#include "perf_regs.h"
|
||||
#include "../../util/unwind.h"
|
||||
#include "../../util/debug.h"
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_ARCH_X86_64_SUPPORT
|
||||
int libunwind__arch_reg_id(int regnum)
|
||||
int LIBUNWIND__ARCH_REG_ID(int regnum)
|
||||
{
|
||||
int id;
|
||||
|
||||
@ -70,7 +72,7 @@ int libunwind__arch_reg_id(int regnum)
|
||||
return id;
|
||||
}
|
||||
#else
|
||||
int libunwind__arch_reg_id(int regnum)
|
||||
int LIBUNWIND__ARCH_REG_ID(int regnum)
|
||||
{
|
||||
int id;
|
||||
|
||||
|
@ -73,17 +73,25 @@ endif
|
||||
#
|
||||
# make DEBUG=1 LIBUNWIND_DIR=/opt/libunwind/
|
||||
#
|
||||
|
||||
libunwind_arch_set_flags = $(eval $(libunwind_arch_set_flags_code))
|
||||
define libunwind_arch_set_flags_code
|
||||
FEATURE_CHECK_CFLAGS-libunwind-$(1) = -I$(LIBUNWIND_DIR)/include
|
||||
FEATURE_CHECK_LDFLAGS-libunwind-$(1) = -L$(LIBUNWIND_DIR)/lib
|
||||
endef
|
||||
|
||||
ifdef LIBUNWIND_DIR
|
||||
LIBUNWIND_CFLAGS = -I$(LIBUNWIND_DIR)/include
|
||||
LIBUNWIND_LDFLAGS = -L$(LIBUNWIND_DIR)/lib
|
||||
LIBUNWIND_ARCHS = x86 x86_64 arm aarch64 debug-frame-arm debug-frame-aarch64
|
||||
$(foreach libunwind_arch,$(LIBUNWIND_ARCHS),$(call libunwind_arch_set_flags,$(libunwind_arch)))
|
||||
endif
|
||||
LIBUNWIND_LDFLAGS += $(LIBUNWIND_LIBS)
|
||||
|
||||
# Set per-feature check compilation flags
|
||||
FEATURE_CHECK_CFLAGS-libunwind = $(LIBUNWIND_CFLAGS)
|
||||
FEATURE_CHECK_LDFLAGS-libunwind = $(LIBUNWIND_LDFLAGS)
|
||||
FEATURE_CHECK_LDFLAGS-libunwind = $(LIBUNWIND_LDFLAGS) $(LIBUNWIND_LIBS)
|
||||
FEATURE_CHECK_CFLAGS-libunwind-debug-frame = $(LIBUNWIND_CFLAGS)
|
||||
FEATURE_CHECK_LDFLAGS-libunwind-debug-frame = $(LIBUNWIND_LDFLAGS)
|
||||
FEATURE_CHECK_LDFLAGS-libunwind-debug-frame = $(LIBUNWIND_LDFLAGS) $(LIBUNWIND_LIBS)
|
||||
|
||||
ifeq ($(NO_PERF_REGS),0)
|
||||
CFLAGS += -DHAVE_PERF_REGS_SUPPORT
|
||||
@ -351,10 +359,40 @@ 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
|
||||
|
||||
ifeq ($(feature-libunwind-aarch64), 1)
|
||||
$(call detected,CONFIG_LIBUNWIND_AARCH64)
|
||||
CFLAGS += -DHAVE_LIBUNWIND_AARCH64_SUPPORT
|
||||
LDFLAGS += -lunwind-aarch64
|
||||
have_libunwind = 1
|
||||
$(call feature_check,libunwind-debug-frame-aarch64)
|
||||
ifneq ($(feature-libunwind-debug-frame-aarch64), 1)
|
||||
msg := $(warning No debug_frame support found in libunwind-aarch64);
|
||||
CFLAGS += -DNO_LIBUNWIND_DEBUG_FRAME_AARCH64
|
||||
endif
|
||||
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
|
||||
$(call detected,CONFIG_LOCAL_LIBUNWIND)
|
||||
endif
|
||||
|
||||
ifneq ($(have_libunwind), 1)
|
||||
NO_LIBUNWIND := 1
|
||||
endif
|
||||
else
|
||||
NO_LOCAL_LIBUNWIND := 1
|
||||
endif
|
||||
|
||||
ifndef NO_LIBBPF
|
||||
@ -392,7 +430,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)
|
||||
@ -403,8 +441,12 @@ 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)
|
||||
LDFLAGS += $(LIBUNWIND_LIBS)
|
||||
endif
|
||||
|
||||
ifndef NO_LIBUNWIND
|
||||
CFLAGS += -DHAVE_LIBUNWIND_SUPPORT
|
||||
CFLAGS += $(LIBUNWIND_CFLAGS)
|
||||
LDFLAGS += $(LIBUNWIND_LDFLAGS)
|
||||
endif
|
||||
|
@ -99,7 +99,10 @@ libperf-$(CONFIG_DWARF) += probe-finder.o
|
||||
libperf-$(CONFIG_DWARF) += dwarf-aux.o
|
||||
|
||||
libperf-$(CONFIG_LIBDW_DWARF_UNWIND) += unwind-libdw.o
|
||||
libperf-$(CONFIG_LOCAL_LIBUNWIND) += unwind-libunwind-local.o
|
||||
libperf-$(CONFIG_LIBUNWIND) += unwind-libunwind.o
|
||||
libperf-$(CONFIG_LIBUNWIND_X86) += libunwind/x86_32.o
|
||||
libperf-$(CONFIG_LIBUNWIND_AARCH64) += libunwind/arm64.o
|
||||
|
||||
libperf-$(CONFIG_LIBBABELTRACE) += data-convert-bt.o
|
||||
|
||||
|
@ -339,7 +339,7 @@ config_bpf_program(struct bpf_program *prog)
|
||||
}
|
||||
pr_debug("bpf: config '%s' is ok\n", config_str);
|
||||
|
||||
err = bpf_program__set_private(prog, priv, clear_prog_priv);
|
||||
err = bpf_program__set_priv(prog, priv, clear_prog_priv);
|
||||
if (err) {
|
||||
pr_debug("Failed to set priv for program '%s'\n", config_str);
|
||||
goto errout;
|
||||
@ -380,15 +380,14 @@ preproc_gen_prologue(struct bpf_program *prog, int n,
|
||||
struct bpf_insn *orig_insns, int orig_insns_cnt,
|
||||
struct bpf_prog_prep_result *res)
|
||||
{
|
||||
struct bpf_prog_priv *priv = bpf_program__priv(prog);
|
||||
struct probe_trace_event *tev;
|
||||
struct perf_probe_event *pev;
|
||||
struct bpf_prog_priv *priv;
|
||||
struct bpf_insn *buf;
|
||||
size_t prologue_cnt = 0;
|
||||
int i, err;
|
||||
|
||||
err = bpf_program__get_private(prog, (void **)&priv);
|
||||
if (err || !priv)
|
||||
if (IS_ERR(priv) || !priv)
|
||||
goto errout;
|
||||
|
||||
pev = &priv->pev;
|
||||
@ -535,13 +534,12 @@ static int map_prologue(struct perf_probe_event *pev, int *mapping,
|
||||
|
||||
static int hook_load_preprocessor(struct bpf_program *prog)
|
||||
{
|
||||
struct bpf_prog_priv *priv = bpf_program__priv(prog);
|
||||
struct perf_probe_event *pev;
|
||||
struct bpf_prog_priv *priv;
|
||||
bool need_prologue = false;
|
||||
int err, i;
|
||||
|
||||
err = bpf_program__get_private(prog, (void **)&priv);
|
||||
if (err || !priv) {
|
||||
if (IS_ERR(priv) || !priv) {
|
||||
pr_debug("Internal error when hook preprocessor\n");
|
||||
return -BPF_LOADER_ERRNO__INTERNAL;
|
||||
}
|
||||
@ -607,9 +605,11 @@ int bpf__probe(struct bpf_object *obj)
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
err = bpf_program__get_private(prog, (void **)&priv);
|
||||
if (err || !priv)
|
||||
priv = bpf_program__priv(prog);
|
||||
if (IS_ERR(priv) || !priv) {
|
||||
err = PTR_ERR(priv);
|
||||
goto out;
|
||||
}
|
||||
pev = &priv->pev;
|
||||
|
||||
err = convert_perf_probe_events(pev, 1);
|
||||
@ -645,13 +645,12 @@ int bpf__unprobe(struct bpf_object *obj)
|
||||
{
|
||||
int err, ret = 0;
|
||||
struct bpf_program *prog;
|
||||
struct bpf_prog_priv *priv;
|
||||
|
||||
bpf_object__for_each_program(prog, obj) {
|
||||
struct bpf_prog_priv *priv = bpf_program__priv(prog);
|
||||
int i;
|
||||
|
||||
err = bpf_program__get_private(prog, (void **)&priv);
|
||||
if (err || !priv)
|
||||
if (IS_ERR(priv) || !priv)
|
||||
continue;
|
||||
|
||||
for (i = 0; i < priv->pev.ntevs; i++) {
|
||||
@ -702,14 +701,12 @@ int bpf__foreach_tev(struct bpf_object *obj,
|
||||
int err;
|
||||
|
||||
bpf_object__for_each_program(prog, obj) {
|
||||
struct bpf_prog_priv *priv = bpf_program__priv(prog);
|
||||
struct probe_trace_event *tev;
|
||||
struct perf_probe_event *pev;
|
||||
struct bpf_prog_priv *priv;
|
||||
int i, fd;
|
||||
|
||||
err = bpf_program__get_private(prog,
|
||||
(void **)&priv);
|
||||
if (err || !priv) {
|
||||
if (IS_ERR(priv) || !priv) {
|
||||
pr_debug("bpf: failed to get private field\n");
|
||||
return -BPF_LOADER_ERRNO__INTERNAL;
|
||||
}
|
||||
@ -897,15 +894,12 @@ bpf_map_priv__clone(struct bpf_map_priv *priv)
|
||||
static int
|
||||
bpf_map__add_op(struct bpf_map *map, struct bpf_map_op *op)
|
||||
{
|
||||
struct bpf_map_priv *priv;
|
||||
const char *map_name;
|
||||
int err;
|
||||
const char *map_name = bpf_map__name(map);
|
||||
struct bpf_map_priv *priv = bpf_map__priv(map);
|
||||
|
||||
map_name = bpf_map__get_name(map);
|
||||
err = bpf_map__get_private(map, (void **)&priv);
|
||||
if (err) {
|
||||
if (IS_ERR(priv)) {
|
||||
pr_debug("Failed to get private from map %s\n", map_name);
|
||||
return err;
|
||||
return PTR_ERR(priv);
|
||||
}
|
||||
|
||||
if (!priv) {
|
||||
@ -916,7 +910,7 @@ bpf_map__add_op(struct bpf_map *map, struct bpf_map_op *op)
|
||||
}
|
||||
INIT_LIST_HEAD(&priv->ops_list);
|
||||
|
||||
if (bpf_map__set_private(map, priv, bpf_map_priv__clear)) {
|
||||
if (bpf_map__set_priv(map, priv, bpf_map_priv__clear)) {
|
||||
free(priv);
|
||||
return -BPF_LOADER_ERRNO__INTERNAL;
|
||||
}
|
||||
@ -948,30 +942,26 @@ static int
|
||||
__bpf_map__config_value(struct bpf_map *map,
|
||||
struct parse_events_term *term)
|
||||
{
|
||||
struct bpf_map_def def;
|
||||
struct bpf_map_op *op;
|
||||
const char *map_name;
|
||||
int err;
|
||||
const char *map_name = bpf_map__name(map);
|
||||
const struct bpf_map_def *def = bpf_map__def(map);
|
||||
|
||||
map_name = bpf_map__get_name(map);
|
||||
|
||||
err = bpf_map__get_def(map, &def);
|
||||
if (err) {
|
||||
if (IS_ERR(def)) {
|
||||
pr_debug("Unable to get map definition from '%s'\n",
|
||||
map_name);
|
||||
return -BPF_LOADER_ERRNO__INTERNAL;
|
||||
}
|
||||
|
||||
if (def.type != BPF_MAP_TYPE_ARRAY) {
|
||||
if (def->type != BPF_MAP_TYPE_ARRAY) {
|
||||
pr_debug("Map %s type is not BPF_MAP_TYPE_ARRAY\n",
|
||||
map_name);
|
||||
return -BPF_LOADER_ERRNO__OBJCONF_MAP_TYPE;
|
||||
}
|
||||
if (def.key_size < sizeof(unsigned int)) {
|
||||
if (def->key_size < sizeof(unsigned int)) {
|
||||
pr_debug("Map %s has incorrect key size\n", map_name);
|
||||
return -BPF_LOADER_ERRNO__OBJCONF_MAP_KEYSIZE;
|
||||
}
|
||||
switch (def.value_size) {
|
||||
switch (def->value_size) {
|
||||
case 1:
|
||||
case 2:
|
||||
case 4:
|
||||
@ -1014,12 +1004,10 @@ __bpf_map__config_event(struct bpf_map *map,
|
||||
struct perf_evlist *evlist)
|
||||
{
|
||||
struct perf_evsel *evsel;
|
||||
struct bpf_map_def def;
|
||||
const struct bpf_map_def *def;
|
||||
struct bpf_map_op *op;
|
||||
const char *map_name;
|
||||
int err;
|
||||
const char *map_name = bpf_map__name(map);
|
||||
|
||||
map_name = bpf_map__get_name(map);
|
||||
evsel = perf_evlist__find_evsel_by_str(evlist, term->val.str);
|
||||
if (!evsel) {
|
||||
pr_debug("Event (for '%s') '%s' doesn't exist\n",
|
||||
@ -1027,18 +1015,18 @@ __bpf_map__config_event(struct bpf_map *map,
|
||||
return -BPF_LOADER_ERRNO__OBJCONF_MAP_NOEVT;
|
||||
}
|
||||
|
||||
err = bpf_map__get_def(map, &def);
|
||||
if (err) {
|
||||
def = bpf_map__def(map);
|
||||
if (IS_ERR(def)) {
|
||||
pr_debug("Unable to get map definition from '%s'\n",
|
||||
map_name);
|
||||
return err;
|
||||
return PTR_ERR(def);
|
||||
}
|
||||
|
||||
/*
|
||||
* No need to check key_size and value_size:
|
||||
* kernel has already checked them.
|
||||
*/
|
||||
if (def.type != BPF_MAP_TYPE_PERF_EVENT_ARRAY) {
|
||||
if (def->type != BPF_MAP_TYPE_PERF_EVENT_ARRAY) {
|
||||
pr_debug("Map %s type is not BPF_MAP_TYPE_PERF_EVENT_ARRAY\n",
|
||||
map_name);
|
||||
return -BPF_LOADER_ERRNO__OBJCONF_MAP_TYPE;
|
||||
@ -1087,9 +1075,8 @@ config_map_indices_range_check(struct parse_events_term *term,
|
||||
const char *map_name)
|
||||
{
|
||||
struct parse_events_array *array = &term->array;
|
||||
struct bpf_map_def def;
|
||||
const struct bpf_map_def *def;
|
||||
unsigned int i;
|
||||
int err;
|
||||
|
||||
if (!array->nr_ranges)
|
||||
return 0;
|
||||
@ -1099,8 +1086,8 @@ config_map_indices_range_check(struct parse_events_term *term,
|
||||
return -BPF_LOADER_ERRNO__INTERNAL;
|
||||
}
|
||||
|
||||
err = bpf_map__get_def(map, &def);
|
||||
if (err) {
|
||||
def = bpf_map__def(map);
|
||||
if (IS_ERR(def)) {
|
||||
pr_debug("ERROR: Unable to get map definition from '%s'\n",
|
||||
map_name);
|
||||
return -BPF_LOADER_ERRNO__INTERNAL;
|
||||
@ -1111,7 +1098,7 @@ config_map_indices_range_check(struct parse_events_term *term,
|
||||
size_t length = array->ranges[i].length;
|
||||
unsigned int idx = start + length - 1;
|
||||
|
||||
if (idx >= def.max_entries) {
|
||||
if (idx >= def->max_entries) {
|
||||
pr_debug("ERROR: index %d too large\n", idx);
|
||||
return -BPF_LOADER_ERRNO__OBJCONF_MAP_IDX2BIG;
|
||||
}
|
||||
@ -1147,7 +1134,7 @@ bpf__obj_config_map(struct bpf_object *obj,
|
||||
goto out;
|
||||
}
|
||||
|
||||
map = bpf_object__get_map_by_name(obj, map_name);
|
||||
map = bpf_object__find_map_by_name(obj, map_name);
|
||||
if (!map) {
|
||||
pr_debug("ERROR: Map %s doesn't exist\n", map_name);
|
||||
err = -BPF_LOADER_ERRNO__OBJCONF_MAP_NOTEXIST;
|
||||
@ -1204,14 +1191,14 @@ out:
|
||||
}
|
||||
|
||||
typedef int (*map_config_func_t)(const char *name, int map_fd,
|
||||
struct bpf_map_def *pdef,
|
||||
const struct bpf_map_def *pdef,
|
||||
struct bpf_map_op *op,
|
||||
void *pkey, void *arg);
|
||||
|
||||
static int
|
||||
foreach_key_array_all(map_config_func_t func,
|
||||
void *arg, const char *name,
|
||||
int map_fd, struct bpf_map_def *pdef,
|
||||
int map_fd, const struct bpf_map_def *pdef,
|
||||
struct bpf_map_op *op)
|
||||
{
|
||||
unsigned int i;
|
||||
@ -1231,7 +1218,7 @@ foreach_key_array_all(map_config_func_t func,
|
||||
static int
|
||||
foreach_key_array_ranges(map_config_func_t func, void *arg,
|
||||
const char *name, int map_fd,
|
||||
struct bpf_map_def *pdef,
|
||||
const struct bpf_map_def *pdef,
|
||||
struct bpf_map_op *op)
|
||||
{
|
||||
unsigned int i, j;
|
||||
@ -1261,15 +1248,12 @@ bpf_map_config_foreach_key(struct bpf_map *map,
|
||||
void *arg)
|
||||
{
|
||||
int err, map_fd;
|
||||
const char *name;
|
||||
struct bpf_map_op *op;
|
||||
struct bpf_map_def def;
|
||||
struct bpf_map_priv *priv;
|
||||
const struct bpf_map_def *def;
|
||||
const char *name = bpf_map__name(map);
|
||||
struct bpf_map_priv *priv = bpf_map__priv(map);
|
||||
|
||||
name = bpf_map__get_name(map);
|
||||
|
||||
err = bpf_map__get_private(map, (void **)&priv);
|
||||
if (err) {
|
||||
if (IS_ERR(priv)) {
|
||||
pr_debug("ERROR: failed to get private from map %s\n", name);
|
||||
return -BPF_LOADER_ERRNO__INTERNAL;
|
||||
}
|
||||
@ -1278,29 +1262,29 @@ bpf_map_config_foreach_key(struct bpf_map *map,
|
||||
return 0;
|
||||
}
|
||||
|
||||
err = bpf_map__get_def(map, &def);
|
||||
if (err) {
|
||||
def = bpf_map__def(map);
|
||||
if (IS_ERR(def)) {
|
||||
pr_debug("ERROR: failed to get definition from map %s\n", name);
|
||||
return -BPF_LOADER_ERRNO__INTERNAL;
|
||||
}
|
||||
map_fd = bpf_map__get_fd(map);
|
||||
map_fd = bpf_map__fd(map);
|
||||
if (map_fd < 0) {
|
||||
pr_debug("ERROR: failed to get fd from map %s\n", name);
|
||||
return map_fd;
|
||||
}
|
||||
|
||||
list_for_each_entry(op, &priv->ops_list, list) {
|
||||
switch (def.type) {
|
||||
switch (def->type) {
|
||||
case BPF_MAP_TYPE_ARRAY:
|
||||
case BPF_MAP_TYPE_PERF_EVENT_ARRAY:
|
||||
switch (op->key_type) {
|
||||
case BPF_MAP_KEY_ALL:
|
||||
err = foreach_key_array_all(func, arg, name,
|
||||
map_fd, &def, op);
|
||||
map_fd, def, op);
|
||||
break;
|
||||
case BPF_MAP_KEY_RANGES:
|
||||
err = foreach_key_array_ranges(func, arg, name,
|
||||
map_fd, &def,
|
||||
map_fd, def,
|
||||
op);
|
||||
break;
|
||||
default:
|
||||
@ -1410,7 +1394,7 @@ apply_config_evsel_for_key(const char *name, int map_fd, void *pkey,
|
||||
|
||||
static int
|
||||
apply_obj_config_map_for_key(const char *name, int map_fd,
|
||||
struct bpf_map_def *pdef __maybe_unused,
|
||||
const struct bpf_map_def *pdef,
|
||||
struct bpf_map_op *op,
|
||||
void *pkey, void *arg __maybe_unused)
|
||||
{
|
||||
@ -1475,9 +1459,9 @@ int bpf__apply_obj_config(void)
|
||||
|
||||
#define bpf__for_each_stdout_map(pos, obj, objtmp) \
|
||||
bpf__for_each_map(pos, obj, objtmp) \
|
||||
if (bpf_map__get_name(pos) && \
|
||||
if (bpf_map__name(pos) && \
|
||||
(strcmp("__bpf_stdout__", \
|
||||
bpf_map__get_name(pos)) == 0))
|
||||
bpf_map__name(pos)) == 0))
|
||||
|
||||
int bpf__setup_stdout(struct perf_evlist *evlist __maybe_unused)
|
||||
{
|
||||
@ -1489,10 +1473,9 @@ int bpf__setup_stdout(struct perf_evlist *evlist __maybe_unused)
|
||||
bool need_init = false;
|
||||
|
||||
bpf__for_each_stdout_map(map, obj, tmp) {
|
||||
struct bpf_map_priv *priv;
|
||||
struct bpf_map_priv *priv = bpf_map__priv(map);
|
||||
|
||||
err = bpf_map__get_private(map, (void **)&priv);
|
||||
if (err)
|
||||
if (IS_ERR(priv))
|
||||
return -BPF_LOADER_ERRNO__INTERNAL;
|
||||
|
||||
/*
|
||||
@ -1520,10 +1503,9 @@ int bpf__setup_stdout(struct perf_evlist *evlist __maybe_unused)
|
||||
}
|
||||
|
||||
bpf__for_each_stdout_map(map, obj, tmp) {
|
||||
struct bpf_map_priv *priv;
|
||||
struct bpf_map_priv *priv = bpf_map__priv(map);
|
||||
|
||||
err = bpf_map__get_private(map, (void **)&priv);
|
||||
if (err)
|
||||
if (IS_ERR(priv))
|
||||
return -BPF_LOADER_ERRNO__INTERNAL;
|
||||
if (priv)
|
||||
continue;
|
||||
@ -1533,7 +1515,7 @@ int bpf__setup_stdout(struct perf_evlist *evlist __maybe_unused)
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
err = bpf_map__set_private(map, priv, bpf_map_priv__clear);
|
||||
err = bpf_map__set_priv(map, priv, bpf_map_priv__clear);
|
||||
if (err) {
|
||||
bpf_map_priv__clear(map, priv);
|
||||
return err;
|
||||
@ -1677,7 +1659,7 @@ int bpf__strerror_load(struct bpf_object *obj,
|
||||
{
|
||||
bpf__strerror_head(err, buf, size);
|
||||
case LIBBPF_ERRNO__KVER: {
|
||||
unsigned int obj_kver = bpf_object__get_kversion(obj);
|
||||
unsigned int obj_kver = bpf_object__kversion(obj);
|
||||
unsigned int real_kver;
|
||||
|
||||
if (fetch_kernel_version(&real_kver, NULL, 0)) {
|
||||
|
@ -147,20 +147,17 @@ static int asnprintf(char **strp, size_t size, const char *fmt, ...)
|
||||
char *build_id_cache__kallsyms_path(const char *sbuild_id, char *bf,
|
||||
size_t size)
|
||||
{
|
||||
bool is_alloc = !!bf;
|
||||
bool retry_old = true;
|
||||
|
||||
asnprintf(&bf, size, "%s/%s/%s/kallsyms",
|
||||
buildid_dir, DSO__NAME_KALLSYMS, sbuild_id);
|
||||
snprintf(bf, size, "%s/%s/%s/kallsyms",
|
||||
buildid_dir, DSO__NAME_KALLSYMS, sbuild_id);
|
||||
retry:
|
||||
if (!access(bf, F_OK))
|
||||
return bf;
|
||||
if (is_alloc)
|
||||
free(bf);
|
||||
if (retry_old) {
|
||||
/* Try old style kallsyms cache */
|
||||
asnprintf(&bf, size, "%s/%s/%s",
|
||||
buildid_dir, DSO__NAME_KALLSYMS, sbuild_id);
|
||||
snprintf(bf, size, "%s/%s/%s",
|
||||
buildid_dir, DSO__NAME_KALLSYMS, sbuild_id);
|
||||
retry_old = false;
|
||||
goto retry;
|
||||
}
|
||||
|
@ -643,17 +643,64 @@ static int collect_config(const char *var, const char *value,
|
||||
|
||||
out_free:
|
||||
free(key);
|
||||
perf_config_set__delete(set);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int perf_config_set__init(struct perf_config_set *set)
|
||||
{
|
||||
int ret = -1;
|
||||
const char *home = NULL;
|
||||
|
||||
/* Setting $PERF_CONFIG makes perf read _only_ the given config file. */
|
||||
if (config_exclusive_filename)
|
||||
return perf_config_from_file(collect_config, config_exclusive_filename, set);
|
||||
if (perf_config_system() && !access(perf_etc_perfconfig(), R_OK)) {
|
||||
if (perf_config_from_file(collect_config, perf_etc_perfconfig(), set) < 0)
|
||||
goto out;
|
||||
}
|
||||
|
||||
home = getenv("HOME");
|
||||
if (perf_config_global() && home) {
|
||||
char *user_config = strdup(mkpath("%s/.perfconfig", home));
|
||||
struct stat st;
|
||||
|
||||
if (user_config == NULL) {
|
||||
warning("Not enough memory to process %s/.perfconfig, "
|
||||
"ignoring it.", home);
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (stat(user_config, &st) < 0)
|
||||
goto out_free;
|
||||
|
||||
if (st.st_uid && (st.st_uid != geteuid())) {
|
||||
warning("File %s not owned by current user or root, "
|
||||
"ignoring it.", user_config);
|
||||
goto out_free;
|
||||
}
|
||||
|
||||
if (!st.st_size)
|
||||
goto out_free;
|
||||
|
||||
ret = perf_config_from_file(collect_config, user_config, set);
|
||||
|
||||
out_free:
|
||||
free(user_config);
|
||||
}
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct perf_config_set *perf_config_set__new(void)
|
||||
{
|
||||
struct perf_config_set *set = zalloc(sizeof(*set));
|
||||
|
||||
if (set) {
|
||||
INIT_LIST_HEAD(&set->sections);
|
||||
perf_config(collect_config, set);
|
||||
if (perf_config_set__init(set) < 0) {
|
||||
perf_config_set__delete(set);
|
||||
set = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return set;
|
||||
|
35
tools/perf/util/libunwind/arm64.c
Normal file
35
tools/perf/util/libunwind/arm64.c
Normal file
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* This file setups defines to compile arch specific binary from the
|
||||
* generic one.
|
||||
*
|
||||
* The function 'LIBUNWIND__ARCH_REG_ID' name is set according to arch
|
||||
* name and the defination of this function is included directly from
|
||||
* 'arch/arm64/util/unwind-libunwind.c', to make sure that this function
|
||||
* is defined no matter what arch the host is.
|
||||
*
|
||||
* Finally, the arch specific unwind methods are exported which will
|
||||
* be assigned to each arm64 thread.
|
||||
*/
|
||||
|
||||
#define REMOTE_UNWIND_LIBUNWIND
|
||||
|
||||
#define LIBUNWIND__ARCH_REG_ID(regnum) libunwind__arm64_reg_id(regnum)
|
||||
|
||||
#include "unwind.h"
|
||||
#include "debug.h"
|
||||
#include "libunwind-aarch64.h"
|
||||
#include <../../../../arch/arm64/include/uapi/asm/perf_regs.h>
|
||||
#include "../../arch/arm64/util/unwind-libunwind.c"
|
||||
|
||||
/* NO_LIBUNWIND_DEBUG_FRAME is a feature flag for local libunwind,
|
||||
* assign NO_LIBUNWIND_DEBUG_FRAME_AARCH64 to it for compiling arm64
|
||||
* unwind methods.
|
||||
*/
|
||||
#undef NO_LIBUNWIND_DEBUG_FRAME
|
||||
#ifdef NO_LIBUNWIND_DEBUG_FRAME_AARCH64
|
||||
#define NO_LIBUNWIND_DEBUG_FRAME
|
||||
#endif
|
||||
#include "util/unwind-libunwind-local.c"
|
||||
|
||||
struct unwind_libunwind_ops *
|
||||
arm64_unwind_libunwind_ops = &_unwind_libunwind_ops;
|
37
tools/perf/util/libunwind/x86_32.c
Normal file
37
tools/perf/util/libunwind/x86_32.c
Normal file
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* This file setups defines to compile arch specific binary from the
|
||||
* generic one.
|
||||
*
|
||||
* The function 'LIBUNWIND__ARCH_REG_ID' name is set according to arch
|
||||
* name and the defination of this function is included directly from
|
||||
* 'arch/x86/util/unwind-libunwind.c', to make sure that this function
|
||||
* is defined no matter what arch the host is.
|
||||
*
|
||||
* Finally, the arch specific unwind methods are exported which will
|
||||
* be assigned to each x86 thread.
|
||||
*/
|
||||
|
||||
#define REMOTE_UNWIND_LIBUNWIND
|
||||
#define LIBUNWIND__ARCH_REG_ID(regnum) libunwind__x86_reg_id(regnum)
|
||||
|
||||
#include "unwind.h"
|
||||
#include "debug.h"
|
||||
#include "libunwind-x86.h"
|
||||
#include <../../../../arch/x86/include/uapi/asm/perf_regs.h>
|
||||
|
||||
/* HAVE_ARCH_X86_64_SUPPORT is used in'arch/x86/util/unwind-libunwind.c'
|
||||
* for x86_32, we undef it to compile code for x86_32 only.
|
||||
*/
|
||||
#undef HAVE_ARCH_X86_64_SUPPORT
|
||||
#include "../../arch/x86/util/unwind-libunwind.c"
|
||||
|
||||
/* Explicitly define NO_LIBUNWIND_DEBUG_FRAME, because non-ARM has no
|
||||
* dwarf_find_debug_frame() function.
|
||||
*/
|
||||
#ifndef NO_LIBUNWIND_DEBUG_FRAME
|
||||
#define NO_LIBUNWIND_DEBUG_FRAME
|
||||
#endif
|
||||
#include "util/unwind-libunwind-local.c"
|
||||
|
||||
struct unwind_libunwind_ops *
|
||||
x86_32_unwind_libunwind_ops = &_unwind_libunwind_ops;
|
@ -1353,11 +1353,16 @@ int machine__process_mmap2_event(struct machine *machine,
|
||||
if (map == NULL)
|
||||
goto out_problem_map;
|
||||
|
||||
thread__insert_map(thread, map);
|
||||
ret = thread__insert_map(thread, map);
|
||||
if (ret)
|
||||
goto out_problem_insert;
|
||||
|
||||
thread__put(thread);
|
||||
map__put(map);
|
||||
return 0;
|
||||
|
||||
out_problem_insert:
|
||||
map__put(map);
|
||||
out_problem_map:
|
||||
thread__put(thread);
|
||||
out_problem:
|
||||
@ -1403,11 +1408,16 @@ int machine__process_mmap_event(struct machine *machine, union perf_event *event
|
||||
if (map == NULL)
|
||||
goto out_problem_map;
|
||||
|
||||
thread__insert_map(thread, map);
|
||||
ret = thread__insert_map(thread, map);
|
||||
if (ret)
|
||||
goto out_problem_insert;
|
||||
|
||||
thread__put(thread);
|
||||
map__put(map);
|
||||
return 0;
|
||||
|
||||
out_problem_insert:
|
||||
map__put(map);
|
||||
out_problem_map:
|
||||
thread__put(thread);
|
||||
out_problem:
|
||||
|
@ -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;
|
||||
@ -201,10 +198,18 @@ size_t thread__fprintf(struct thread *thread, FILE *fp)
|
||||
map_groups__fprintf(thread->mg, fp);
|
||||
}
|
||||
|
||||
void thread__insert_map(struct thread *thread, struct map *map)
|
||||
int thread__insert_map(struct thread *thread, struct map *map)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = unwind__prepare_access(thread, map);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
map_groups__fixup_overlappings(thread->mg, map, stderr);
|
||||
map_groups__insert(thread->mg, map);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int thread__clone_map_groups(struct thread *thread,
|
||||
|
@ -9,11 +9,9 @@
|
||||
#include "symbol.h"
|
||||
#include <strlist.h>
|
||||
#include <intlist.h>
|
||||
#ifdef HAVE_LIBUNWIND_SUPPORT
|
||||
#include <libunwind.h>
|
||||
#endif
|
||||
|
||||
struct thread_stack;
|
||||
struct unwind_libunwind_ops;
|
||||
|
||||
struct thread {
|
||||
union {
|
||||
@ -36,7 +34,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
|
||||
};
|
||||
|
||||
@ -77,7 +76,7 @@ int thread__comm_len(struct thread *thread);
|
||||
struct comm *thread__comm(const struct thread *thread);
|
||||
struct comm *thread__exec_comm(const struct thread *thread);
|
||||
const char *thread__comm_str(const struct thread *thread);
|
||||
void thread__insert_map(struct thread *thread, struct map *map);
|
||||
int thread__insert_map(struct thread *thread, struct map *map);
|
||||
int thread__fork(struct thread *thread, struct thread *parent, u64 timestamp);
|
||||
size_t thread__fprintf(struct thread *thread, FILE *fp);
|
||||
|
||||
|
697
tools/perf/util/unwind-libunwind-local.c
Normal file
697
tools/perf/util/unwind-libunwind-local.c
Normal file
@ -0,0 +1,697 @@
|
||||
/*
|
||||
* Post mortem Dwarf CFI based unwinding on top of regs and stack dumps.
|
||||
*
|
||||
* Lots of this code have been borrowed or heavily inspired from parts of
|
||||
* the libunwind 0.99 code which are (amongst other contributors I may have
|
||||
* forgotten):
|
||||
*
|
||||
* Copyright (C) 2002-2007 Hewlett-Packard Co
|
||||
* Contributed by David Mosberger-Tang <davidm@hpl.hp.com>
|
||||
*
|
||||
* And the bugs have been added by:
|
||||
*
|
||||
* Copyright (C) 2010, Frederic Weisbecker <fweisbec@gmail.com>
|
||||
* Copyright (C) 2012, Jiri Olsa <jolsa@redhat.com>
|
||||
*
|
||||
*/
|
||||
|
||||
#include <elf.h>
|
||||
#include <gelf.h>
|
||||
#include <fcntl.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/mman.h>
|
||||
#include <linux/list.h>
|
||||
#ifndef REMOTE_UNWIND_LIBUNWIND
|
||||
#include <libunwind.h>
|
||||
#include <libunwind-ptrace.h>
|
||||
#endif
|
||||
#include "callchain.h"
|
||||
#include "thread.h"
|
||||
#include "session.h"
|
||||
#include "perf_regs.h"
|
||||
#include "unwind.h"
|
||||
#include "symbol.h"
|
||||
#include "util.h"
|
||||
#include "debug.h"
|
||||
#include "asm/bug.h"
|
||||
|
||||
extern int
|
||||
UNW_OBJ(dwarf_search_unwind_table) (unw_addr_space_t as,
|
||||
unw_word_t ip,
|
||||
unw_dyn_info_t *di,
|
||||
unw_proc_info_t *pi,
|
||||
int need_unwind_info, void *arg);
|
||||
|
||||
#define dwarf_search_unwind_table UNW_OBJ(dwarf_search_unwind_table)
|
||||
|
||||
extern int
|
||||
UNW_OBJ(dwarf_find_debug_frame) (int found, unw_dyn_info_t *di_debug,
|
||||
unw_word_t ip,
|
||||
unw_word_t segbase,
|
||||
const char *obj_name, unw_word_t start,
|
||||
unw_word_t end);
|
||||
|
||||
#define dwarf_find_debug_frame UNW_OBJ(dwarf_find_debug_frame)
|
||||
|
||||
#define DW_EH_PE_FORMAT_MASK 0x0f /* format of the encoded value */
|
||||
#define DW_EH_PE_APPL_MASK 0x70 /* how the value is to be applied */
|
||||
|
||||
/* Pointer-encoding formats: */
|
||||
#define DW_EH_PE_omit 0xff
|
||||
#define DW_EH_PE_ptr 0x00 /* pointer-sized unsigned value */
|
||||
#define DW_EH_PE_udata4 0x03 /* unsigned 32-bit value */
|
||||
#define DW_EH_PE_udata8 0x04 /* unsigned 64-bit value */
|
||||
#define DW_EH_PE_sdata4 0x0b /* signed 32-bit value */
|
||||
#define DW_EH_PE_sdata8 0x0c /* signed 64-bit value */
|
||||
|
||||
/* Pointer-encoding application: */
|
||||
#define DW_EH_PE_absptr 0x00 /* absolute value */
|
||||
#define DW_EH_PE_pcrel 0x10 /* rel. to addr. of encoded value */
|
||||
|
||||
/*
|
||||
* The following are not documented by LSB v1.3, yet they are used by
|
||||
* GCC, presumably they aren't documented by LSB since they aren't
|
||||
* used on Linux:
|
||||
*/
|
||||
#define DW_EH_PE_funcrel 0x40 /* start-of-procedure-relative */
|
||||
#define DW_EH_PE_aligned 0x50 /* aligned pointer */
|
||||
|
||||
/* Flags intentionaly not handled, since they're not needed:
|
||||
* #define DW_EH_PE_indirect 0x80
|
||||
* #define DW_EH_PE_uleb128 0x01
|
||||
* #define DW_EH_PE_udata2 0x02
|
||||
* #define DW_EH_PE_sleb128 0x09
|
||||
* #define DW_EH_PE_sdata2 0x0a
|
||||
* #define DW_EH_PE_textrel 0x20
|
||||
* #define DW_EH_PE_datarel 0x30
|
||||
*/
|
||||
|
||||
struct unwind_info {
|
||||
struct perf_sample *sample;
|
||||
struct machine *machine;
|
||||
struct thread *thread;
|
||||
};
|
||||
|
||||
#define dw_read(ptr, type, end) ({ \
|
||||
type *__p = (type *) ptr; \
|
||||
type __v; \
|
||||
if ((__p + 1) > (type *) end) \
|
||||
return -EINVAL; \
|
||||
__v = *__p++; \
|
||||
ptr = (typeof(ptr)) __p; \
|
||||
__v; \
|
||||
})
|
||||
|
||||
static int __dw_read_encoded_value(u8 **p, u8 *end, u64 *val,
|
||||
u8 encoding)
|
||||
{
|
||||
u8 *cur = *p;
|
||||
*val = 0;
|
||||
|
||||
switch (encoding) {
|
||||
case DW_EH_PE_omit:
|
||||
*val = 0;
|
||||
goto out;
|
||||
case DW_EH_PE_ptr:
|
||||
*val = dw_read(cur, unsigned long, end);
|
||||
goto out;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
switch (encoding & DW_EH_PE_APPL_MASK) {
|
||||
case DW_EH_PE_absptr:
|
||||
break;
|
||||
case DW_EH_PE_pcrel:
|
||||
*val = (unsigned long) cur;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if ((encoding & 0x07) == 0x00)
|
||||
encoding |= DW_EH_PE_udata4;
|
||||
|
||||
switch (encoding & DW_EH_PE_FORMAT_MASK) {
|
||||
case DW_EH_PE_sdata4:
|
||||
*val += dw_read(cur, s32, end);
|
||||
break;
|
||||
case DW_EH_PE_udata4:
|
||||
*val += dw_read(cur, u32, end);
|
||||
break;
|
||||
case DW_EH_PE_sdata8:
|
||||
*val += dw_read(cur, s64, end);
|
||||
break;
|
||||
case DW_EH_PE_udata8:
|
||||
*val += dw_read(cur, u64, end);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
out:
|
||||
*p = cur;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define dw_read_encoded_value(ptr, end, enc) ({ \
|
||||
u64 __v; \
|
||||
if (__dw_read_encoded_value(&ptr, end, &__v, enc)) { \
|
||||
return -EINVAL; \
|
||||
} \
|
||||
__v; \
|
||||
})
|
||||
|
||||
static u64 elf_section_offset(int fd, const char *name)
|
||||
{
|
||||
Elf *elf;
|
||||
GElf_Ehdr ehdr;
|
||||
GElf_Shdr shdr;
|
||||
u64 offset = 0;
|
||||
|
||||
elf = elf_begin(fd, PERF_ELF_C_READ_MMAP, NULL);
|
||||
if (elf == NULL)
|
||||
return 0;
|
||||
|
||||
do {
|
||||
if (gelf_getehdr(elf, &ehdr) == NULL)
|
||||
break;
|
||||
|
||||
if (!elf_section_by_name(elf, &ehdr, &shdr, name, NULL))
|
||||
break;
|
||||
|
||||
offset = shdr.sh_offset;
|
||||
} while (0);
|
||||
|
||||
elf_end(elf);
|
||||
return offset;
|
||||
}
|
||||
|
||||
#ifndef NO_LIBUNWIND_DEBUG_FRAME
|
||||
static int elf_is_exec(int fd, const char *name)
|
||||
{
|
||||
Elf *elf;
|
||||
GElf_Ehdr ehdr;
|
||||
int retval = 0;
|
||||
|
||||
elf = elf_begin(fd, PERF_ELF_C_READ_MMAP, NULL);
|
||||
if (elf == NULL)
|
||||
return 0;
|
||||
if (gelf_getehdr(elf, &ehdr) == NULL)
|
||||
goto out;
|
||||
|
||||
retval = (ehdr.e_type == ET_EXEC);
|
||||
|
||||
out:
|
||||
elf_end(elf);
|
||||
pr_debug("unwind: elf_is_exec(%s): %d\n", name, retval);
|
||||
return retval;
|
||||
}
|
||||
#endif
|
||||
|
||||
struct table_entry {
|
||||
u32 start_ip_offset;
|
||||
u32 fde_offset;
|
||||
};
|
||||
|
||||
struct eh_frame_hdr {
|
||||
unsigned char version;
|
||||
unsigned char eh_frame_ptr_enc;
|
||||
unsigned char fde_count_enc;
|
||||
unsigned char table_enc;
|
||||
|
||||
/*
|
||||
* The rest of the header is variable-length and consists of the
|
||||
* following members:
|
||||
*
|
||||
* encoded_t eh_frame_ptr;
|
||||
* encoded_t fde_count;
|
||||
*/
|
||||
|
||||
/* A single encoded pointer should not be more than 8 bytes. */
|
||||
u64 enc[2];
|
||||
|
||||
/*
|
||||
* struct {
|
||||
* encoded_t start_ip;
|
||||
* encoded_t fde_addr;
|
||||
* } binary_search_table[fde_count];
|
||||
*/
|
||||
char data[0];
|
||||
} __packed;
|
||||
|
||||
static int unwind_spec_ehframe(struct dso *dso, struct machine *machine,
|
||||
u64 offset, u64 *table_data, u64 *segbase,
|
||||
u64 *fde_count)
|
||||
{
|
||||
struct eh_frame_hdr hdr;
|
||||
u8 *enc = (u8 *) &hdr.enc;
|
||||
u8 *end = (u8 *) &hdr.data;
|
||||
ssize_t r;
|
||||
|
||||
r = dso__data_read_offset(dso, machine, offset,
|
||||
(u8 *) &hdr, sizeof(hdr));
|
||||
if (r != sizeof(hdr))
|
||||
return -EINVAL;
|
||||
|
||||
/* We dont need eh_frame_ptr, just skip it. */
|
||||
dw_read_encoded_value(enc, end, hdr.eh_frame_ptr_enc);
|
||||
|
||||
*fde_count = dw_read_encoded_value(enc, end, hdr.fde_count_enc);
|
||||
*segbase = offset;
|
||||
*table_data = (enc - (u8 *) &hdr) + offset;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int read_unwind_spec_eh_frame(struct dso *dso, struct machine *machine,
|
||||
u64 *table_data, u64 *segbase,
|
||||
u64 *fde_count)
|
||||
{
|
||||
int ret = -EINVAL, fd;
|
||||
u64 offset = dso->data.eh_frame_hdr_offset;
|
||||
|
||||
if (offset == 0) {
|
||||
fd = dso__data_get_fd(dso, machine);
|
||||
if (fd < 0)
|
||||
return -EINVAL;
|
||||
|
||||
/* Check the .eh_frame section for unwinding info */
|
||||
offset = elf_section_offset(fd, ".eh_frame_hdr");
|
||||
dso->data.eh_frame_hdr_offset = offset;
|
||||
dso__data_put_fd(dso);
|
||||
}
|
||||
|
||||
if (offset)
|
||||
ret = unwind_spec_ehframe(dso, machine, offset,
|
||||
table_data, segbase,
|
||||
fde_count);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifndef NO_LIBUNWIND_DEBUG_FRAME
|
||||
static int read_unwind_spec_debug_frame(struct dso *dso,
|
||||
struct machine *machine, u64 *offset)
|
||||
{
|
||||
int fd;
|
||||
u64 ofs = dso->data.debug_frame_offset;
|
||||
|
||||
if (ofs == 0) {
|
||||
fd = dso__data_get_fd(dso, machine);
|
||||
if (fd < 0)
|
||||
return -EINVAL;
|
||||
|
||||
/* Check the .debug_frame section for unwinding info */
|
||||
ofs = elf_section_offset(fd, ".debug_frame");
|
||||
dso->data.debug_frame_offset = ofs;
|
||||
dso__data_put_fd(dso);
|
||||
}
|
||||
|
||||
*offset = ofs;
|
||||
if (*offset)
|
||||
return 0;
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
#endif
|
||||
|
||||
static struct map *find_map(unw_word_t ip, struct unwind_info *ui)
|
||||
{
|
||||
struct addr_location al;
|
||||
|
||||
thread__find_addr_map(ui->thread, PERF_RECORD_MISC_USER,
|
||||
MAP__FUNCTION, ip, &al);
|
||||
if (!al.map) {
|
||||
/*
|
||||
* We've seen cases (softice) where DWARF unwinder went
|
||||
* through non executable mmaps, which we need to lookup
|
||||
* in MAP__VARIABLE tree.
|
||||
*/
|
||||
thread__find_addr_map(ui->thread, PERF_RECORD_MISC_USER,
|
||||
MAP__VARIABLE, ip, &al);
|
||||
}
|
||||
return al.map;
|
||||
}
|
||||
|
||||
static int
|
||||
find_proc_info(unw_addr_space_t as, unw_word_t ip, unw_proc_info_t *pi,
|
||||
int need_unwind_info, void *arg)
|
||||
{
|
||||
struct unwind_info *ui = arg;
|
||||
struct map *map;
|
||||
unw_dyn_info_t di;
|
||||
u64 table_data, segbase, fde_count;
|
||||
int ret = -EINVAL;
|
||||
|
||||
map = find_map(ip, ui);
|
||||
if (!map || !map->dso)
|
||||
return -EINVAL;
|
||||
|
||||
pr_debug("unwind: find_proc_info dso %s\n", map->dso->name);
|
||||
|
||||
/* Check the .eh_frame section for unwinding info */
|
||||
if (!read_unwind_spec_eh_frame(map->dso, ui->machine,
|
||||
&table_data, &segbase, &fde_count)) {
|
||||
memset(&di, 0, sizeof(di));
|
||||
di.format = UNW_INFO_FORMAT_REMOTE_TABLE;
|
||||
di.start_ip = map->start;
|
||||
di.end_ip = map->end;
|
||||
di.u.rti.segbase = map->start + segbase;
|
||||
di.u.rti.table_data = map->start + table_data;
|
||||
di.u.rti.table_len = fde_count * sizeof(struct table_entry)
|
||||
/ sizeof(unw_word_t);
|
||||
ret = dwarf_search_unwind_table(as, ip, &di, pi,
|
||||
need_unwind_info, arg);
|
||||
}
|
||||
|
||||
#ifndef NO_LIBUNWIND_DEBUG_FRAME
|
||||
/* Check the .debug_frame section for unwinding info */
|
||||
if (ret < 0 &&
|
||||
!read_unwind_spec_debug_frame(map->dso, ui->machine, &segbase)) {
|
||||
int fd = dso__data_get_fd(map->dso, ui->machine);
|
||||
int is_exec = elf_is_exec(fd, map->dso->name);
|
||||
unw_word_t base = is_exec ? 0 : map->start;
|
||||
const char *symfile;
|
||||
|
||||
if (fd >= 0)
|
||||
dso__data_put_fd(map->dso);
|
||||
|
||||
symfile = map->dso->symsrc_filename ?: map->dso->name;
|
||||
|
||||
memset(&di, 0, sizeof(di));
|
||||
if (dwarf_find_debug_frame(0, &di, ip, base, symfile,
|
||||
map->start, map->end))
|
||||
return dwarf_search_unwind_table(as, ip, &di, pi,
|
||||
need_unwind_info, arg);
|
||||
}
|
||||
#endif
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int access_fpreg(unw_addr_space_t __maybe_unused as,
|
||||
unw_regnum_t __maybe_unused num,
|
||||
unw_fpreg_t __maybe_unused *val,
|
||||
int __maybe_unused __write,
|
||||
void __maybe_unused *arg)
|
||||
{
|
||||
pr_err("unwind: access_fpreg unsupported\n");
|
||||
return -UNW_EINVAL;
|
||||
}
|
||||
|
||||
static int get_dyn_info_list_addr(unw_addr_space_t __maybe_unused as,
|
||||
unw_word_t __maybe_unused *dil_addr,
|
||||
void __maybe_unused *arg)
|
||||
{
|
||||
return -UNW_ENOINFO;
|
||||
}
|
||||
|
||||
static int resume(unw_addr_space_t __maybe_unused as,
|
||||
unw_cursor_t __maybe_unused *cu,
|
||||
void __maybe_unused *arg)
|
||||
{
|
||||
pr_err("unwind: resume unsupported\n");
|
||||
return -UNW_EINVAL;
|
||||
}
|
||||
|
||||
static int
|
||||
get_proc_name(unw_addr_space_t __maybe_unused as,
|
||||
unw_word_t __maybe_unused addr,
|
||||
char __maybe_unused *bufp, size_t __maybe_unused buf_len,
|
||||
unw_word_t __maybe_unused *offp, void __maybe_unused *arg)
|
||||
{
|
||||
pr_err("unwind: get_proc_name unsupported\n");
|
||||
return -UNW_EINVAL;
|
||||
}
|
||||
|
||||
static int access_dso_mem(struct unwind_info *ui, unw_word_t addr,
|
||||
unw_word_t *data)
|
||||
{
|
||||
struct map *map;
|
||||
ssize_t size;
|
||||
|
||||
map = find_map(addr, ui);
|
||||
if (!map) {
|
||||
pr_debug("unwind: no map for %lx\n", (unsigned long)addr);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!map->dso)
|
||||
return -1;
|
||||
|
||||
size = dso__data_read_addr(map->dso, map, ui->machine,
|
||||
addr, (u8 *) data, sizeof(*data));
|
||||
|
||||
return !(size == sizeof(*data));
|
||||
}
|
||||
|
||||
static int access_mem(unw_addr_space_t __maybe_unused as,
|
||||
unw_word_t addr, unw_word_t *valp,
|
||||
int __write, void *arg)
|
||||
{
|
||||
struct unwind_info *ui = arg;
|
||||
struct stack_dump *stack = &ui->sample->user_stack;
|
||||
u64 start, end;
|
||||
int offset;
|
||||
int ret;
|
||||
|
||||
/* Don't support write, probably not needed. */
|
||||
if (__write || !stack || !ui->sample->user_regs.regs) {
|
||||
*valp = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = perf_reg_value(&start, &ui->sample->user_regs, PERF_REG_SP);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
end = start + stack->size;
|
||||
|
||||
/* Check overflow. */
|
||||
if (addr + sizeof(unw_word_t) < addr)
|
||||
return -EINVAL;
|
||||
|
||||
if (addr < start || addr + sizeof(unw_word_t) >= end) {
|
||||
ret = access_dso_mem(ui, addr, valp);
|
||||
if (ret) {
|
||||
pr_debug("unwind: access_mem %p not inside range"
|
||||
" 0x%" PRIx64 "-0x%" PRIx64 "\n",
|
||||
(void *) (uintptr_t) addr, start, end);
|
||||
*valp = 0;
|
||||
return ret;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
offset = addr - start;
|
||||
*valp = *(unw_word_t *)&stack->data[offset];
|
||||
pr_debug("unwind: access_mem addr %p val %lx, offset %d\n",
|
||||
(void *) (uintptr_t) addr, (unsigned long)*valp, offset);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int access_reg(unw_addr_space_t __maybe_unused as,
|
||||
unw_regnum_t regnum, unw_word_t *valp,
|
||||
int __write, void *arg)
|
||||
{
|
||||
struct unwind_info *ui = arg;
|
||||
int id, ret;
|
||||
u64 val;
|
||||
|
||||
/* Don't support write, I suspect we don't need it. */
|
||||
if (__write) {
|
||||
pr_err("unwind: access_reg w %d\n", regnum);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!ui->sample->user_regs.regs) {
|
||||
*valp = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
id = LIBUNWIND__ARCH_REG_ID(regnum);
|
||||
if (id < 0)
|
||||
return -EINVAL;
|
||||
|
||||
ret = perf_reg_value(&val, &ui->sample->user_regs, id);
|
||||
if (ret) {
|
||||
pr_err("unwind: can't read reg %d\n", regnum);
|
||||
return ret;
|
||||
}
|
||||
|
||||
*valp = (unw_word_t) val;
|
||||
pr_debug("unwind: reg %d, val %lx\n", regnum, (unsigned long)*valp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void put_unwind_info(unw_addr_space_t __maybe_unused as,
|
||||
unw_proc_info_t *pi __maybe_unused,
|
||||
void *arg __maybe_unused)
|
||||
{
|
||||
pr_debug("unwind: put_unwind_info called\n");
|
||||
}
|
||||
|
||||
static int entry(u64 ip, struct thread *thread,
|
||||
unwind_entry_cb_t cb, void *arg)
|
||||
{
|
||||
struct unwind_entry e;
|
||||
struct addr_location al;
|
||||
|
||||
thread__find_addr_location(thread, PERF_RECORD_MISC_USER,
|
||||
MAP__FUNCTION, ip, &al);
|
||||
|
||||
e.ip = ip;
|
||||
e.map = al.map;
|
||||
e.sym = al.sym;
|
||||
|
||||
pr_debug("unwind: %s:ip = 0x%" PRIx64 " (0x%" PRIx64 ")\n",
|
||||
al.sym ? al.sym->name : "''",
|
||||
ip,
|
||||
al.map ? al.map->map_ip(al.map, ip) : (u64) 0);
|
||||
|
||||
return cb(&e, arg);
|
||||
}
|
||||
|
||||
static void display_error(int err)
|
||||
{
|
||||
switch (err) {
|
||||
case UNW_EINVAL:
|
||||
pr_err("unwind: Only supports local.\n");
|
||||
break;
|
||||
case UNW_EUNSPEC:
|
||||
pr_err("unwind: Unspecified error.\n");
|
||||
break;
|
||||
case UNW_EBADREG:
|
||||
pr_err("unwind: Register unavailable.\n");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static unw_accessors_t accessors = {
|
||||
.find_proc_info = find_proc_info,
|
||||
.put_unwind_info = put_unwind_info,
|
||||
.get_dyn_info_list_addr = get_dyn_info_list_addr,
|
||||
.access_mem = access_mem,
|
||||
.access_reg = access_reg,
|
||||
.access_fpreg = access_fpreg,
|
||||
.resume = resume,
|
||||
.get_proc_name = get_proc_name,
|
||||
};
|
||||
|
||||
static int _unwind__prepare_access(struct thread *thread)
|
||||
{
|
||||
if (callchain_param.record_mode != CALLCHAIN_DWARF)
|
||||
return 0;
|
||||
|
||||
thread->addr_space = unw_create_addr_space(&accessors, 0);
|
||||
if (!thread->addr_space) {
|
||||
pr_err("unwind: Can't create unwind address space.\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
unw_set_caching_policy(thread->addr_space, UNW_CACHE_GLOBAL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void _unwind__flush_access(struct thread *thread)
|
||||
{
|
||||
if (callchain_param.record_mode != CALLCHAIN_DWARF)
|
||||
return;
|
||||
|
||||
unw_flush_cache(thread->addr_space, 0, 0);
|
||||
}
|
||||
|
||||
static void _unwind__finish_access(struct thread *thread)
|
||||
{
|
||||
if (callchain_param.record_mode != CALLCHAIN_DWARF)
|
||||
return;
|
||||
|
||||
unw_destroy_addr_space(thread->addr_space);
|
||||
}
|
||||
|
||||
static int get_entries(struct unwind_info *ui, unwind_entry_cb_t cb,
|
||||
void *arg, int max_stack)
|
||||
{
|
||||
u64 val;
|
||||
unw_word_t ips[max_stack];
|
||||
unw_addr_space_t addr_space;
|
||||
unw_cursor_t c;
|
||||
int ret, i = 0;
|
||||
|
||||
ret = perf_reg_value(&val, &ui->sample->user_regs, PERF_REG_IP);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ips[i++] = (unw_word_t) val;
|
||||
|
||||
/*
|
||||
* If we need more than one entry, do the DWARF
|
||||
* unwind itself.
|
||||
*/
|
||||
if (max_stack - 1 > 0) {
|
||||
WARN_ONCE(!ui->thread, "WARNING: ui->thread is NULL");
|
||||
addr_space = ui->thread->addr_space;
|
||||
|
||||
if (addr_space == NULL)
|
||||
return -1;
|
||||
|
||||
ret = unw_init_remote(&c, addr_space, ui);
|
||||
if (ret)
|
||||
display_error(ret);
|
||||
|
||||
while (!ret && (unw_step(&c) > 0) && i < max_stack) {
|
||||
unw_get_reg(&c, UNW_REG_IP, &ips[i]);
|
||||
++i;
|
||||
}
|
||||
|
||||
max_stack = i;
|
||||
}
|
||||
|
||||
/*
|
||||
* Display what we got based on the order setup.
|
||||
*/
|
||||
for (i = 0; i < max_stack && !ret; i++) {
|
||||
int j = i;
|
||||
|
||||
if (callchain_param.order == ORDER_CALLER)
|
||||
j = max_stack - i - 1;
|
||||
ret = ips[j] ? entry(ips[j], ui->thread, cb, arg) : 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int _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,
|
||||
.thread = thread,
|
||||
.machine = thread->mg->machine,
|
||||
};
|
||||
|
||||
if (!data->user_regs.regs)
|
||||
return -EINVAL;
|
||||
|
||||
if (max_stack <= 0)
|
||||
return -EINVAL;
|
||||
|
||||
return get_entries(&ui, cb, arg, max_stack);
|
||||
}
|
||||
|
||||
static struct unwind_libunwind_ops
|
||||
_unwind_libunwind_ops = {
|
||||
.prepare_access = _unwind__prepare_access,
|
||||
.flush_access = _unwind__flush_access,
|
||||
.finish_access = _unwind__finish_access,
|
||||
.get_entries = _unwind__get_entries,
|
||||
};
|
||||
|
||||
#ifndef REMOTE_UNWIND_LIBUNWIND
|
||||
struct unwind_libunwind_ops *
|
||||
local_unwind_libunwind_ops = &_unwind_libunwind_ops;
|
||||
#endif
|
@ -1,682 +1,76 @@
|
||||
/*
|
||||
* Post mortem Dwarf CFI based unwinding on top of regs and stack dumps.
|
||||
*
|
||||
* Lots of this code have been borrowed or heavily inspired from parts of
|
||||
* the libunwind 0.99 code which are (amongst other contributors I may have
|
||||
* forgotten):
|
||||
*
|
||||
* Copyright (C) 2002-2007 Hewlett-Packard Co
|
||||
* Contributed by David Mosberger-Tang <davidm@hpl.hp.com>
|
||||
*
|
||||
* And the bugs have been added by:
|
||||
*
|
||||
* Copyright (C) 2010, Frederic Weisbecker <fweisbec@gmail.com>
|
||||
* Copyright (C) 2012, Jiri Olsa <jolsa@redhat.com>
|
||||
*
|
||||
*/
|
||||
|
||||
#include <elf.h>
|
||||
#include <gelf.h>
|
||||
#include <fcntl.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/mman.h>
|
||||
#include <linux/list.h>
|
||||
#include <libunwind.h>
|
||||
#include <libunwind-ptrace.h>
|
||||
#include "callchain.h"
|
||||
#include "unwind.h"
|
||||
#include "thread.h"
|
||||
#include "session.h"
|
||||
#include "perf_regs.h"
|
||||
#include "unwind.h"
|
||||
#include "symbol.h"
|
||||
#include "util.h"
|
||||
#include "debug.h"
|
||||
#include "asm/bug.h"
|
||||
#include "arch/common.h"
|
||||
|
||||
extern int
|
||||
UNW_OBJ(dwarf_search_unwind_table) (unw_addr_space_t as,
|
||||
unw_word_t ip,
|
||||
unw_dyn_info_t *di,
|
||||
unw_proc_info_t *pi,
|
||||
int need_unwind_info, void *arg);
|
||||
struct unwind_libunwind_ops __weak *local_unwind_libunwind_ops;
|
||||
struct unwind_libunwind_ops __weak *x86_32_unwind_libunwind_ops;
|
||||
struct unwind_libunwind_ops __weak *arm64_unwind_libunwind_ops;
|
||||
|
||||
#define dwarf_search_unwind_table UNW_OBJ(dwarf_search_unwind_table)
|
||||
|
||||
extern int
|
||||
UNW_OBJ(dwarf_find_debug_frame) (int found, unw_dyn_info_t *di_debug,
|
||||
unw_word_t ip,
|
||||
unw_word_t segbase,
|
||||
const char *obj_name, unw_word_t start,
|
||||
unw_word_t end);
|
||||
|
||||
#define dwarf_find_debug_frame UNW_OBJ(dwarf_find_debug_frame)
|
||||
|
||||
#define DW_EH_PE_FORMAT_MASK 0x0f /* format of the encoded value */
|
||||
#define DW_EH_PE_APPL_MASK 0x70 /* how the value is to be applied */
|
||||
|
||||
/* Pointer-encoding formats: */
|
||||
#define DW_EH_PE_omit 0xff
|
||||
#define DW_EH_PE_ptr 0x00 /* pointer-sized unsigned value */
|
||||
#define DW_EH_PE_udata4 0x03 /* unsigned 32-bit value */
|
||||
#define DW_EH_PE_udata8 0x04 /* unsigned 64-bit value */
|
||||
#define DW_EH_PE_sdata4 0x0b /* signed 32-bit value */
|
||||
#define DW_EH_PE_sdata8 0x0c /* signed 64-bit value */
|
||||
|
||||
/* Pointer-encoding application: */
|
||||
#define DW_EH_PE_absptr 0x00 /* absolute value */
|
||||
#define DW_EH_PE_pcrel 0x10 /* rel. to addr. of encoded value */
|
||||
|
||||
/*
|
||||
* The following are not documented by LSB v1.3, yet they are used by
|
||||
* GCC, presumably they aren't documented by LSB since they aren't
|
||||
* used on Linux:
|
||||
*/
|
||||
#define DW_EH_PE_funcrel 0x40 /* start-of-procedure-relative */
|
||||
#define DW_EH_PE_aligned 0x50 /* aligned pointer */
|
||||
|
||||
/* Flags intentionaly not handled, since they're not needed:
|
||||
* #define DW_EH_PE_indirect 0x80
|
||||
* #define DW_EH_PE_uleb128 0x01
|
||||
* #define DW_EH_PE_udata2 0x02
|
||||
* #define DW_EH_PE_sleb128 0x09
|
||||
* #define DW_EH_PE_sdata2 0x0a
|
||||
* #define DW_EH_PE_textrel 0x20
|
||||
* #define DW_EH_PE_datarel 0x30
|
||||
*/
|
||||
|
||||
struct unwind_info {
|
||||
struct perf_sample *sample;
|
||||
struct machine *machine;
|
||||
struct thread *thread;
|
||||
};
|
||||
|
||||
#define dw_read(ptr, type, end) ({ \
|
||||
type *__p = (type *) ptr; \
|
||||
type __v; \
|
||||
if ((__p + 1) > (type *) end) \
|
||||
return -EINVAL; \
|
||||
__v = *__p++; \
|
||||
ptr = (typeof(ptr)) __p; \
|
||||
__v; \
|
||||
})
|
||||
|
||||
static int __dw_read_encoded_value(u8 **p, u8 *end, u64 *val,
|
||||
u8 encoding)
|
||||
static void unwind__register_ops(struct thread *thread,
|
||||
struct unwind_libunwind_ops *ops)
|
||||
{
|
||||
u8 *cur = *p;
|
||||
*val = 0;
|
||||
|
||||
switch (encoding) {
|
||||
case DW_EH_PE_omit:
|
||||
*val = 0;
|
||||
goto out;
|
||||
case DW_EH_PE_ptr:
|
||||
*val = dw_read(cur, unsigned long, end);
|
||||
goto out;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
switch (encoding & DW_EH_PE_APPL_MASK) {
|
||||
case DW_EH_PE_absptr:
|
||||
break;
|
||||
case DW_EH_PE_pcrel:
|
||||
*val = (unsigned long) cur;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if ((encoding & 0x07) == 0x00)
|
||||
encoding |= DW_EH_PE_udata4;
|
||||
|
||||
switch (encoding & DW_EH_PE_FORMAT_MASK) {
|
||||
case DW_EH_PE_sdata4:
|
||||
*val += dw_read(cur, s32, end);
|
||||
break;
|
||||
case DW_EH_PE_udata4:
|
||||
*val += dw_read(cur, u32, end);
|
||||
break;
|
||||
case DW_EH_PE_sdata8:
|
||||
*val += dw_read(cur, s64, end);
|
||||
break;
|
||||
case DW_EH_PE_udata8:
|
||||
*val += dw_read(cur, u64, end);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
out:
|
||||
*p = cur;
|
||||
return 0;
|
||||
thread->unwind_libunwind_ops = ops;
|
||||
}
|
||||
|
||||
#define dw_read_encoded_value(ptr, end, enc) ({ \
|
||||
u64 __v; \
|
||||
if (__dw_read_encoded_value(&ptr, end, &__v, enc)) { \
|
||||
return -EINVAL; \
|
||||
} \
|
||||
__v; \
|
||||
})
|
||||
|
||||
static u64 elf_section_offset(int fd, const char *name)
|
||||
int unwind__prepare_access(struct thread *thread, struct map *map)
|
||||
{
|
||||
Elf *elf;
|
||||
GElf_Ehdr ehdr;
|
||||
GElf_Shdr shdr;
|
||||
u64 offset = 0;
|
||||
const char *arch;
|
||||
enum dso_type dso_type;
|
||||
struct unwind_libunwind_ops *ops = local_unwind_libunwind_ops;
|
||||
|
||||
elf = elf_begin(fd, PERF_ELF_C_READ_MMAP, NULL);
|
||||
if (elf == NULL)
|
||||
if (thread->addr_space) {
|
||||
pr_debug("unwind: thread map already set, dso=%s\n",
|
||||
map->dso->name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* env->arch is NULL for live-mode (i.e. perf top) */
|
||||
if (!thread->mg->machine->env || !thread->mg->machine->env->arch)
|
||||
goto out_register;
|
||||
|
||||
dso_type = dso__type(map->dso, thread->mg->machine);
|
||||
if (dso_type == DSO__TYPE_UNKNOWN)
|
||||
return 0;
|
||||
|
||||
do {
|
||||
if (gelf_getehdr(elf, &ehdr) == NULL)
|
||||
break;
|
||||
arch = normalize_arch(thread->mg->machine->env->arch);
|
||||
|
||||
if (!elf_section_by_name(elf, &ehdr, &shdr, name, NULL))
|
||||
break;
|
||||
|
||||
offset = shdr.sh_offset;
|
||||
} while (0);
|
||||
|
||||
elf_end(elf);
|
||||
return offset;
|
||||
}
|
||||
|
||||
#ifndef NO_LIBUNWIND_DEBUG_FRAME
|
||||
static int elf_is_exec(int fd, const char *name)
|
||||
{
|
||||
Elf *elf;
|
||||
GElf_Ehdr ehdr;
|
||||
int retval = 0;
|
||||
|
||||
elf = elf_begin(fd, PERF_ELF_C_READ_MMAP, NULL);
|
||||
if (elf == NULL)
|
||||
return 0;
|
||||
if (gelf_getehdr(elf, &ehdr) == NULL)
|
||||
goto out;
|
||||
|
||||
retval = (ehdr.e_type == ET_EXEC);
|
||||
|
||||
out:
|
||||
elf_end(elf);
|
||||
pr_debug("unwind: elf_is_exec(%s): %d\n", name, retval);
|
||||
return retval;
|
||||
}
|
||||
#endif
|
||||
|
||||
struct table_entry {
|
||||
u32 start_ip_offset;
|
||||
u32 fde_offset;
|
||||
};
|
||||
|
||||
struct eh_frame_hdr {
|
||||
unsigned char version;
|
||||
unsigned char eh_frame_ptr_enc;
|
||||
unsigned char fde_count_enc;
|
||||
unsigned char table_enc;
|
||||
|
||||
/*
|
||||
* The rest of the header is variable-length and consists of the
|
||||
* following members:
|
||||
*
|
||||
* encoded_t eh_frame_ptr;
|
||||
* encoded_t fde_count;
|
||||
*/
|
||||
|
||||
/* A single encoded pointer should not be more than 8 bytes. */
|
||||
u64 enc[2];
|
||||
|
||||
/*
|
||||
* struct {
|
||||
* encoded_t start_ip;
|
||||
* encoded_t fde_addr;
|
||||
* } binary_search_table[fde_count];
|
||||
*/
|
||||
char data[0];
|
||||
} __packed;
|
||||
|
||||
static int unwind_spec_ehframe(struct dso *dso, struct machine *machine,
|
||||
u64 offset, u64 *table_data, u64 *segbase,
|
||||
u64 *fde_count)
|
||||
{
|
||||
struct eh_frame_hdr hdr;
|
||||
u8 *enc = (u8 *) &hdr.enc;
|
||||
u8 *end = (u8 *) &hdr.data;
|
||||
ssize_t r;
|
||||
|
||||
r = dso__data_read_offset(dso, machine, offset,
|
||||
(u8 *) &hdr, sizeof(hdr));
|
||||
if (r != sizeof(hdr))
|
||||
return -EINVAL;
|
||||
|
||||
/* We dont need eh_frame_ptr, just skip it. */
|
||||
dw_read_encoded_value(enc, end, hdr.eh_frame_ptr_enc);
|
||||
|
||||
*fde_count = dw_read_encoded_value(enc, end, hdr.fde_count_enc);
|
||||
*segbase = offset;
|
||||
*table_data = (enc - (u8 *) &hdr) + offset;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int read_unwind_spec_eh_frame(struct dso *dso, struct machine *machine,
|
||||
u64 *table_data, u64 *segbase,
|
||||
u64 *fde_count)
|
||||
{
|
||||
int ret = -EINVAL, fd;
|
||||
u64 offset = dso->data.eh_frame_hdr_offset;
|
||||
|
||||
if (offset == 0) {
|
||||
fd = dso__data_get_fd(dso, machine);
|
||||
if (fd < 0)
|
||||
return -EINVAL;
|
||||
|
||||
/* Check the .eh_frame section for unwinding info */
|
||||
offset = elf_section_offset(fd, ".eh_frame_hdr");
|
||||
dso->data.eh_frame_hdr_offset = offset;
|
||||
dso__data_put_fd(dso);
|
||||
if (!strcmp(arch, "x86")) {
|
||||
if (dso_type != DSO__TYPE_64BIT)
|
||||
ops = x86_32_unwind_libunwind_ops;
|
||||
} else if (!strcmp(arch, "arm64") || !strcmp(arch, "arm")) {
|
||||
if (dso_type == DSO__TYPE_64BIT)
|
||||
ops = arm64_unwind_libunwind_ops;
|
||||
}
|
||||
|
||||
if (offset)
|
||||
ret = unwind_spec_ehframe(dso, machine, offset,
|
||||
table_data, segbase,
|
||||
fde_count);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifndef NO_LIBUNWIND_DEBUG_FRAME
|
||||
static int read_unwind_spec_debug_frame(struct dso *dso,
|
||||
struct machine *machine, u64 *offset)
|
||||
{
|
||||
int fd;
|
||||
u64 ofs = dso->data.debug_frame_offset;
|
||||
|
||||
if (ofs == 0) {
|
||||
fd = dso__data_get_fd(dso, machine);
|
||||
if (fd < 0)
|
||||
return -EINVAL;
|
||||
|
||||
/* Check the .debug_frame section for unwinding info */
|
||||
ofs = elf_section_offset(fd, ".debug_frame");
|
||||
dso->data.debug_frame_offset = ofs;
|
||||
dso__data_put_fd(dso);
|
||||
}
|
||||
|
||||
*offset = ofs;
|
||||
if (*offset)
|
||||
return 0;
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
#endif
|
||||
|
||||
static struct map *find_map(unw_word_t ip, struct unwind_info *ui)
|
||||
{
|
||||
struct addr_location al;
|
||||
|
||||
thread__find_addr_map(ui->thread, PERF_RECORD_MISC_USER,
|
||||
MAP__FUNCTION, ip, &al);
|
||||
if (!al.map) {
|
||||
/*
|
||||
* We've seen cases (softice) where DWARF unwinder went
|
||||
* through non executable mmaps, which we need to lookup
|
||||
* in MAP__VARIABLE tree.
|
||||
*/
|
||||
thread__find_addr_map(ui->thread, PERF_RECORD_MISC_USER,
|
||||
MAP__VARIABLE, ip, &al);
|
||||
}
|
||||
return al.map;
|
||||
}
|
||||
|
||||
static int
|
||||
find_proc_info(unw_addr_space_t as, unw_word_t ip, unw_proc_info_t *pi,
|
||||
int need_unwind_info, void *arg)
|
||||
{
|
||||
struct unwind_info *ui = arg;
|
||||
struct map *map;
|
||||
unw_dyn_info_t di;
|
||||
u64 table_data, segbase, fde_count;
|
||||
int ret = -EINVAL;
|
||||
|
||||
map = find_map(ip, ui);
|
||||
if (!map || !map->dso)
|
||||
return -EINVAL;
|
||||
|
||||
pr_debug("unwind: find_proc_info dso %s\n", map->dso->name);
|
||||
|
||||
/* Check the .eh_frame section for unwinding info */
|
||||
if (!read_unwind_spec_eh_frame(map->dso, ui->machine,
|
||||
&table_data, &segbase, &fde_count)) {
|
||||
memset(&di, 0, sizeof(di));
|
||||
di.format = UNW_INFO_FORMAT_REMOTE_TABLE;
|
||||
di.start_ip = map->start;
|
||||
di.end_ip = map->end;
|
||||
di.u.rti.segbase = map->start + segbase;
|
||||
di.u.rti.table_data = map->start + table_data;
|
||||
di.u.rti.table_len = fde_count * sizeof(struct table_entry)
|
||||
/ sizeof(unw_word_t);
|
||||
ret = dwarf_search_unwind_table(as, ip, &di, pi,
|
||||
need_unwind_info, arg);
|
||||
}
|
||||
|
||||
#ifndef NO_LIBUNWIND_DEBUG_FRAME
|
||||
/* Check the .debug_frame section for unwinding info */
|
||||
if (ret < 0 &&
|
||||
!read_unwind_spec_debug_frame(map->dso, ui->machine, &segbase)) {
|
||||
int fd = dso__data_get_fd(map->dso, ui->machine);
|
||||
int is_exec = elf_is_exec(fd, map->dso->name);
|
||||
unw_word_t base = is_exec ? 0 : map->start;
|
||||
const char *symfile;
|
||||
|
||||
if (fd >= 0)
|
||||
dso__data_put_fd(map->dso);
|
||||
|
||||
symfile = map->dso->symsrc_filename ?: map->dso->name;
|
||||
|
||||
memset(&di, 0, sizeof(di));
|
||||
if (dwarf_find_debug_frame(0, &di, ip, base, symfile,
|
||||
map->start, map->end))
|
||||
return dwarf_search_unwind_table(as, ip, &di, pi,
|
||||
need_unwind_info, arg);
|
||||
}
|
||||
#endif
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int access_fpreg(unw_addr_space_t __maybe_unused as,
|
||||
unw_regnum_t __maybe_unused num,
|
||||
unw_fpreg_t __maybe_unused *val,
|
||||
int __maybe_unused __write,
|
||||
void __maybe_unused *arg)
|
||||
{
|
||||
pr_err("unwind: access_fpreg unsupported\n");
|
||||
return -UNW_EINVAL;
|
||||
}
|
||||
|
||||
static int get_dyn_info_list_addr(unw_addr_space_t __maybe_unused as,
|
||||
unw_word_t __maybe_unused *dil_addr,
|
||||
void __maybe_unused *arg)
|
||||
{
|
||||
return -UNW_ENOINFO;
|
||||
}
|
||||
|
||||
static int resume(unw_addr_space_t __maybe_unused as,
|
||||
unw_cursor_t __maybe_unused *cu,
|
||||
void __maybe_unused *arg)
|
||||
{
|
||||
pr_err("unwind: resume unsupported\n");
|
||||
return -UNW_EINVAL;
|
||||
}
|
||||
|
||||
static int
|
||||
get_proc_name(unw_addr_space_t __maybe_unused as,
|
||||
unw_word_t __maybe_unused addr,
|
||||
char __maybe_unused *bufp, size_t __maybe_unused buf_len,
|
||||
unw_word_t __maybe_unused *offp, void __maybe_unused *arg)
|
||||
{
|
||||
pr_err("unwind: get_proc_name unsupported\n");
|
||||
return -UNW_EINVAL;
|
||||
}
|
||||
|
||||
static int access_dso_mem(struct unwind_info *ui, unw_word_t addr,
|
||||
unw_word_t *data)
|
||||
{
|
||||
struct map *map;
|
||||
ssize_t size;
|
||||
|
||||
map = find_map(addr, ui);
|
||||
if (!map) {
|
||||
pr_debug("unwind: no map for %lx\n", (unsigned long)addr);
|
||||
if (!ops) {
|
||||
pr_err("unwind: target platform=%s is not supported\n", arch);
|
||||
return -1;
|
||||
}
|
||||
out_register:
|
||||
unwind__register_ops(thread, ops);
|
||||
|
||||
if (!map->dso)
|
||||
return -1;
|
||||
|
||||
size = dso__data_read_addr(map->dso, map, ui->machine,
|
||||
addr, (u8 *) data, sizeof(*data));
|
||||
|
||||
return !(size == sizeof(*data));
|
||||
}
|
||||
|
||||
static int access_mem(unw_addr_space_t __maybe_unused as,
|
||||
unw_word_t addr, unw_word_t *valp,
|
||||
int __write, void *arg)
|
||||
{
|
||||
struct unwind_info *ui = arg;
|
||||
struct stack_dump *stack = &ui->sample->user_stack;
|
||||
u64 start, end;
|
||||
int offset;
|
||||
int ret;
|
||||
|
||||
/* Don't support write, probably not needed. */
|
||||
if (__write || !stack || !ui->sample->user_regs.regs) {
|
||||
*valp = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = perf_reg_value(&start, &ui->sample->user_regs, PERF_REG_SP);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
end = start + stack->size;
|
||||
|
||||
/* Check overflow. */
|
||||
if (addr + sizeof(unw_word_t) < addr)
|
||||
return -EINVAL;
|
||||
|
||||
if (addr < start || addr + sizeof(unw_word_t) >= end) {
|
||||
ret = access_dso_mem(ui, addr, valp);
|
||||
if (ret) {
|
||||
pr_debug("unwind: access_mem %p not inside range"
|
||||
" 0x%" PRIx64 "-0x%" PRIx64 "\n",
|
||||
(void *) (uintptr_t) addr, start, end);
|
||||
*valp = 0;
|
||||
return ret;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
offset = addr - start;
|
||||
*valp = *(unw_word_t *)&stack->data[offset];
|
||||
pr_debug("unwind: access_mem addr %p val %lx, offset %d\n",
|
||||
(void *) (uintptr_t) addr, (unsigned long)*valp, offset);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int access_reg(unw_addr_space_t __maybe_unused as,
|
||||
unw_regnum_t regnum, unw_word_t *valp,
|
||||
int __write, void *arg)
|
||||
{
|
||||
struct unwind_info *ui = arg;
|
||||
int id, ret;
|
||||
u64 val;
|
||||
|
||||
/* Don't support write, I suspect we don't need it. */
|
||||
if (__write) {
|
||||
pr_err("unwind: access_reg w %d\n", regnum);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!ui->sample->user_regs.regs) {
|
||||
*valp = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
id = libunwind__arch_reg_id(regnum);
|
||||
if (id < 0)
|
||||
return -EINVAL;
|
||||
|
||||
ret = perf_reg_value(&val, &ui->sample->user_regs, id);
|
||||
if (ret) {
|
||||
pr_err("unwind: can't read reg %d\n", regnum);
|
||||
return ret;
|
||||
}
|
||||
|
||||
*valp = (unw_word_t) val;
|
||||
pr_debug("unwind: reg %d, val %lx\n", regnum, (unsigned long)*valp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void put_unwind_info(unw_addr_space_t __maybe_unused as,
|
||||
unw_proc_info_t *pi __maybe_unused,
|
||||
void *arg __maybe_unused)
|
||||
{
|
||||
pr_debug("unwind: put_unwind_info called\n");
|
||||
}
|
||||
|
||||
static int entry(u64 ip, struct thread *thread,
|
||||
unwind_entry_cb_t cb, void *arg)
|
||||
{
|
||||
struct unwind_entry e;
|
||||
struct addr_location al;
|
||||
|
||||
thread__find_addr_location(thread, PERF_RECORD_MISC_USER,
|
||||
MAP__FUNCTION, ip, &al);
|
||||
|
||||
e.ip = ip;
|
||||
e.map = al.map;
|
||||
e.sym = al.sym;
|
||||
|
||||
pr_debug("unwind: %s:ip = 0x%" PRIx64 " (0x%" PRIx64 ")\n",
|
||||
al.sym ? al.sym->name : "''",
|
||||
ip,
|
||||
al.map ? al.map->map_ip(al.map, ip) : (u64) 0);
|
||||
|
||||
return cb(&e, arg);
|
||||
}
|
||||
|
||||
static void display_error(int err)
|
||||
{
|
||||
switch (err) {
|
||||
case UNW_EINVAL:
|
||||
pr_err("unwind: Only supports local.\n");
|
||||
break;
|
||||
case UNW_EUNSPEC:
|
||||
pr_err("unwind: Unspecified error.\n");
|
||||
break;
|
||||
case UNW_EBADREG:
|
||||
pr_err("unwind: Register unavailable.\n");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static unw_accessors_t accessors = {
|
||||
.find_proc_info = find_proc_info,
|
||||
.put_unwind_info = put_unwind_info,
|
||||
.get_dyn_info_list_addr = get_dyn_info_list_addr,
|
||||
.access_mem = access_mem,
|
||||
.access_reg = access_reg,
|
||||
.access_fpreg = access_fpreg,
|
||||
.resume = resume,
|
||||
.get_proc_name = get_proc_name,
|
||||
};
|
||||
|
||||
int unwind__prepare_access(struct thread *thread)
|
||||
{
|
||||
if (callchain_param.record_mode != CALLCHAIN_DWARF)
|
||||
return 0;
|
||||
|
||||
thread->addr_space = unw_create_addr_space(&accessors, 0);
|
||||
if (!thread->addr_space) {
|
||||
pr_err("unwind: Can't create unwind address space.\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
unw_set_caching_policy(thread->addr_space, UNW_CACHE_GLOBAL);
|
||||
return 0;
|
||||
return thread->unwind_libunwind_ops->prepare_access(thread);
|
||||
}
|
||||
|
||||
void unwind__flush_access(struct thread *thread)
|
||||
{
|
||||
if (callchain_param.record_mode != CALLCHAIN_DWARF)
|
||||
return;
|
||||
|
||||
unw_flush_cache(thread->addr_space, 0, 0);
|
||||
if (thread->unwind_libunwind_ops)
|
||||
thread->unwind_libunwind_ops->flush_access(thread);
|
||||
}
|
||||
|
||||
void unwind__finish_access(struct thread *thread)
|
||||
{
|
||||
if (callchain_param.record_mode != CALLCHAIN_DWARF)
|
||||
return;
|
||||
|
||||
unw_destroy_addr_space(thread->addr_space);
|
||||
}
|
||||
|
||||
static int get_entries(struct unwind_info *ui, unwind_entry_cb_t cb,
|
||||
void *arg, int max_stack)
|
||||
{
|
||||
u64 val;
|
||||
unw_word_t ips[max_stack];
|
||||
unw_addr_space_t addr_space;
|
||||
unw_cursor_t c;
|
||||
int ret, i = 0;
|
||||
|
||||
ret = perf_reg_value(&val, &ui->sample->user_regs, PERF_REG_IP);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ips[i++] = (unw_word_t) val;
|
||||
|
||||
/*
|
||||
* If we need more than one entry, do the DWARF
|
||||
* unwind itself.
|
||||
*/
|
||||
if (max_stack - 1 > 0) {
|
||||
WARN_ONCE(!ui->thread, "WARNING: ui->thread is NULL");
|
||||
addr_space = ui->thread->addr_space;
|
||||
|
||||
if (addr_space == NULL)
|
||||
return -1;
|
||||
|
||||
ret = unw_init_remote(&c, addr_space, ui);
|
||||
if (ret)
|
||||
display_error(ret);
|
||||
|
||||
while (!ret && (unw_step(&c) > 0) && i < max_stack) {
|
||||
unw_get_reg(&c, UNW_REG_IP, &ips[i]);
|
||||
++i;
|
||||
}
|
||||
|
||||
max_stack = i;
|
||||
}
|
||||
|
||||
/*
|
||||
* Display what we got based on the order setup.
|
||||
*/
|
||||
for (i = 0; i < max_stack && !ret; i++) {
|
||||
int j = i;
|
||||
|
||||
if (callchain_param.order == ORDER_CALLER)
|
||||
j = max_stack - i - 1;
|
||||
ret = ips[j] ? entry(ips[j], ui->thread, cb, arg) : 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
if (thread->unwind_libunwind_ops)
|
||||
thread->unwind_libunwind_ops->finish_access(thread);
|
||||
}
|
||||
|
||||
int unwind__get_entries(unwind_entry_cb_t cb, void *arg,
|
||||
struct thread *thread,
|
||||
struct perf_sample *data, int max_stack)
|
||||
struct thread *thread,
|
||||
struct perf_sample *data, int max_stack)
|
||||
{
|
||||
struct unwind_info ui = {
|
||||
.sample = data,
|
||||
.thread = thread,
|
||||
.machine = thread->mg->machine,
|
||||
};
|
||||
|
||||
if (!data->user_regs.regs)
|
||||
return -EINVAL;
|
||||
|
||||
if (max_stack <= 0)
|
||||
return -EINVAL;
|
||||
|
||||
return get_entries(&ui, cb, arg, max_stack);
|
||||
if (thread->unwind_libunwind_ops)
|
||||
return thread->unwind_libunwind_ops->get_entries(cb, arg, thread, data, max_stack);
|
||||
return 0;
|
||||
}
|
||||
|
@ -14,18 +14,31 @@ 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);
|
||||
};
|
||||
|
||||
#ifdef HAVE_DWARF_UNWIND_SUPPORT
|
||||
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);
|
||||
#ifndef LIBUNWIND__ARCH_REG_ID
|
||||
#define LIBUNWIND__ARCH_REG_ID(regnum) libunwind__arch_reg_id(regnum)
|
||||
#endif
|
||||
int LIBUNWIND__ARCH_REG_ID(int regnum);
|
||||
int unwind__prepare_access(struct thread *thread, struct map *map);
|
||||
void unwind__flush_access(struct thread *thread);
|
||||
void unwind__finish_access(struct thread *thread);
|
||||
#else
|
||||
static inline int unwind__prepare_access(struct thread *thread __maybe_unused)
|
||||
static inline int unwind__prepare_access(struct thread *thread __maybe_unused,
|
||||
struct map *map __maybe_unused)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
@ -44,7 +57,8 @@ unwind__get_entries(unwind_entry_cb_t cb __maybe_unused,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int unwind__prepare_access(struct thread *thread __maybe_unused)
|
||||
static inline int unwind__prepare_access(struct thread *thread __maybe_unused,
|
||||
struct map *map __maybe_unused)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user