diff --git a/tools/perf/builtin-record.c b/tools/perf/builtin-record.c index 67d2a9003294..fa26865364b6 100644 --- a/tools/perf/builtin-record.c +++ b/tools/perf/builtin-record.c @@ -37,6 +37,7 @@ #include "util/llvm-utils.h" #include "util/bpf-loader.h" #include "util/trigger.h" +#include "util/perf-hooks.h" #include "asm/bug.h" #include @@ -206,6 +207,12 @@ static void sig_handler(int sig) done = 1; } +static void sigsegv_handler(int sig) +{ + perf_hooks__recover(); + sighandler_dump_stack(sig); +} + static void record__sig_exit(void) { if (signr == -1) @@ -833,6 +840,7 @@ static int __cmd_record(struct record *rec, int argc, const char **argv) signal(SIGCHLD, sig_handler); signal(SIGINT, sig_handler); signal(SIGTERM, sig_handler); + signal(SIGSEGV, sigsegv_handler); if (rec->opts.auxtrace_snapshot_mode || rec->switch_output) { signal(SIGUSR2, snapshot_sig_handler); @@ -970,6 +978,7 @@ static int __cmd_record(struct record *rec, int argc, const char **argv) trigger_ready(&auxtrace_snapshot_trigger); trigger_ready(&switch_output_trigger); + perf_hooks__invoke_record_start(); for (;;) { unsigned long long hits = rec->samples; @@ -1114,6 +1123,8 @@ out_child: } } + perf_hooks__invoke_record_end(); + if (!err && !quiet) { char samples[128]; const char *postfix = rec->timestamp_filename ? diff --git a/tools/perf/tests/Build b/tools/perf/tests/Build index 8a4ce492f7b2..af3ec94869aa 100644 --- a/tools/perf/tests/Build +++ b/tools/perf/tests/Build @@ -42,6 +42,7 @@ perf-y += backward-ring-buffer.o perf-y += sdt.o perf-y += is_printable_array.o perf-y += bitmap.o +perf-y += perf-hooks.o $(OUTPUT)tests/llvm-src-base.c: tests/bpf-script-example.c tests/Build $(call rule_mkdir) diff --git a/tools/perf/tests/builtin-test.c b/tools/perf/tests/builtin-test.c index 778668a2a966..dab83f7042fa 100644 --- a/tools/perf/tests/builtin-test.c +++ b/tools/perf/tests/builtin-test.c @@ -229,6 +229,10 @@ static struct test generic_tests[] = { .desc = "Test bitmap print", .func = test__bitmap_print, }, + { + .desc = "Test perf hooks", + .func = test__perf_hooks, + }, { .func = NULL, }, diff --git a/tools/perf/tests/perf-hooks.c b/tools/perf/tests/perf-hooks.c new file mode 100644 index 000000000000..9338cb2c25ab --- /dev/null +++ b/tools/perf/tests/perf-hooks.c @@ -0,0 +1,44 @@ +#include +#include + +#include "tests.h" +#include "debug.h" +#include "util.h" +#include "perf-hooks.h" + +static void sigsegv_handler(int sig __maybe_unused) +{ + pr_debug("SIGSEGV is observed as expected, try to recover.\n"); + perf_hooks__recover(); + signal(SIGSEGV, SIG_DFL); + raise(SIGSEGV); + exit(-1); +} + +static int hook_flags; + +static void the_hook(void) +{ + int *p = NULL; + + hook_flags = 1234; + + /* Generate a segfault, test perf_hooks__recover */ + *p = 0; +} + +int test__perf_hooks(int subtest __maybe_unused) +{ + signal(SIGSEGV, sigsegv_handler); + perf_hooks__set_hook("test", the_hook); + perf_hooks__invoke_test(); + + /* hook is triggered? */ + if (hook_flags != 1234) + return TEST_FAIL; + + /* the buggy hook is removed? */ + if (perf_hooks__get_hook("test")) + return TEST_FAIL; + return TEST_OK; +} diff --git a/tools/perf/tests/tests.h b/tools/perf/tests/tests.h index 7c196c585472..3a1f98f291ba 100644 --- a/tools/perf/tests/tests.h +++ b/tools/perf/tests/tests.h @@ -91,6 +91,7 @@ int test__cpu_map_print(int subtest); int test__sdt_event(int subtest); int test__is_printable_array(int subtest); int test__bitmap_print(int subtest); +int test__perf_hooks(int subtest); #if defined(__arm__) || defined(__aarch64__) #ifdef HAVE_DWARF_UNWIND_SUPPORT diff --git a/tools/perf/util/Build b/tools/perf/util/Build index 1dc67efad634..b2a47aac8d1c 100644 --- a/tools/perf/util/Build +++ b/tools/perf/util/Build @@ -123,6 +123,8 @@ libperf-$(CONFIG_LIBELF) += genelf.o libperf-$(CONFIG_DWARF) += genelf_debug.o endif +libperf-y += perf-hooks.o + CFLAGS_config.o += -DETC_PERFCONFIG="BUILD_STR($(ETC_PERFCONFIG_SQ))" # avoid compiler warnings in 32-bit mode CFLAGS_genelf_debug.o += -Wno-packed diff --git a/tools/perf/util/perf-hooks-list.h b/tools/perf/util/perf-hooks-list.h new file mode 100644 index 000000000000..2867c07ee84e --- /dev/null +++ b/tools/perf/util/perf-hooks-list.h @@ -0,0 +1,3 @@ +PERF_HOOK(record_start) +PERF_HOOK(record_end) +PERF_HOOK(test) diff --git a/tools/perf/util/perf-hooks.c b/tools/perf/util/perf-hooks.c new file mode 100644 index 000000000000..4ce88e37dd63 --- /dev/null +++ b/tools/perf/util/perf-hooks.c @@ -0,0 +1,84 @@ +/* + * perf_hooks.c + * + * Copyright (C) 2016 Wang Nan + * Copyright (C) 2016 Huawei Inc. + */ + +#include +#include +#include +#include +#include "util/util.h" +#include "util/debug.h" +#include "util/perf-hooks.h" + +static sigjmp_buf jmpbuf; +static const struct perf_hook_desc *current_perf_hook; + +void perf_hooks__invoke(const struct perf_hook_desc *desc) +{ + if (!(desc && desc->p_hook_func && *desc->p_hook_func)) + return; + + if (sigsetjmp(jmpbuf, 1)) { + pr_warning("Fatal error (SEGFAULT) in perf hook '%s'\n", + desc->hook_name); + *(current_perf_hook->p_hook_func) = NULL; + } else { + current_perf_hook = desc; + (**desc->p_hook_func)(); + } + current_perf_hook = NULL; +} + +void perf_hooks__recover(void) +{ + if (current_perf_hook) + siglongjmp(jmpbuf, 1); +} + +#define PERF_HOOK(name) \ +perf_hook_func_t __perf_hook_func_##name = NULL; \ +struct perf_hook_desc __perf_hook_desc_##name = \ + {.hook_name = #name, .p_hook_func = &__perf_hook_func_##name}; +#include "perf-hooks-list.h" +#undef PERF_HOOK + +#define PERF_HOOK(name) \ + &__perf_hook_desc_##name, + +static struct perf_hook_desc *perf_hooks[] = { +#include "perf-hooks-list.h" +}; +#undef PERF_HOOK + +int perf_hooks__set_hook(const char *hook_name, + perf_hook_func_t hook_func) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(perf_hooks); i++) { + if (strcmp(hook_name, perf_hooks[i]->hook_name) != 0) + continue; + + if (*(perf_hooks[i]->p_hook_func)) + pr_warning("Overwrite existing hook: %s\n", hook_name); + *(perf_hooks[i]->p_hook_func) = hook_func; + return 0; + } + return -ENOENT; +} + +perf_hook_func_t perf_hooks__get_hook(const char *hook_name) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(perf_hooks); i++) { + if (strcmp(hook_name, perf_hooks[i]->hook_name) != 0) + continue; + + return *(perf_hooks[i]->p_hook_func); + } + return ERR_PTR(-ENOENT); +} diff --git a/tools/perf/util/perf-hooks.h b/tools/perf/util/perf-hooks.h new file mode 100644 index 000000000000..1d482b26b4b9 --- /dev/null +++ b/tools/perf/util/perf-hooks.h @@ -0,0 +1,37 @@ +#ifndef PERF_UTIL_PERF_HOOKS_H +#define PERF_UTIL_PERF_HOOKS_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void (*perf_hook_func_t)(void); +struct perf_hook_desc { + const char * const hook_name; + perf_hook_func_t * const p_hook_func; +}; + +extern void perf_hooks__invoke(const struct perf_hook_desc *); +extern void perf_hooks__recover(void); + +#define PERF_HOOK(name) \ +extern struct perf_hook_desc __perf_hook_desc_##name; \ +static inline void perf_hooks__invoke_##name(void) \ +{ \ + perf_hooks__invoke(&__perf_hook_desc_##name); \ +} + +#include "perf-hooks-list.h" +#undef PERF_HOOK + +extern int +perf_hooks__set_hook(const char *hook_name, + perf_hook_func_t hook_func); + +extern perf_hook_func_t +perf_hooks__get_hook(const char *hook_name); + +#ifdef __cplusplus +} +#endif +#endif