Changes for v4: - Separate out libdwfl_stacktrace, as requested.
Changes for v3: - Fix initialization of elf in sysprof_init_dwfl (previously triggered -Wmaybe-uninitialized). * * * Remove the code from src/stacktrace.c that is now covered by libdwfl_stacktrace/dwflst_perf_frame.c and dwflst_perf_sample_getframes. * src/stacktrace.c (show_memory_reads): Remove this verbose option as the relevant code is inside libdwfl_stacktrace now. (struct __sample_arg): Remove, handled by libdwfl_stacktrace/dwflst_perf_frame.c. (sample_next_thread): Ditto. (sample_getthread): Ditto. (copy_word_64): Ditto. (copy_word_32): Ditto. (elf_memory_read): Ditto. (sample_memory_read): Ditto. (sample_set_initial_registers): Ditto. (sample_detach): Ditto. (sample_thread_callbacks): Ditto. (sysprof_find_dwfl): Now also return the Elf* so that it can be passed to dwflst_perf_sample_getframes. Don't create sample_arg. Do record sp in sui->last_sp. Don't dwfl_attach_state, dwflst_perf_sample_getframes handles that now. (sysprof_unwind_cb): Adapt to sysprof_find_dwfl changes, now invoke dwflst_perf_sample_getframes instead of dwfl_getthread_frames. (parse_opt): Remove show_memory_reads. (main): Remove show_memory_reads. --- src/stacktrace.c | 276 ++++++----------------------------------------- 1 file changed, 30 insertions(+), 246 deletions(-) diff --git a/src/stacktrace.c b/src/stacktrace.c index dd58ef3b..43b091b7 100644 --- a/src/stacktrace.c +++ b/src/stacktrace.c @@ -96,8 +96,7 @@ #include ELFUTILS_HEADER(ebl) /* #include ELFUTILS_HEADER(dwfl) */ #include "../libdwfl/libdwflP.h" -/* XXX: Private header needed for find_procfile, sysprof_find_dwfl, - sample_set_initial_registers. */ +/* XXX: Private header needed for find_procfile. */ #include ELFUTILS_HEADER(dwfl_stacktrace) /************************************* @@ -177,11 +176,13 @@ static int processing_mode = MODE_NAIVE; static int input_format; static int output_format = FORMAT_SYSPROF; +/* XXX Used to decide regs_mask for dwflst_perf_sample_getframes. */ +Ebl *default_ebl = NULL; + /* non-printable argp options. */ #define OPT_DEBUG 0x100 /* Diagnostic options. */ -static bool show_memory_reads = false; static bool show_frames = false; static bool show_samples = false; static bool show_failures = false; @@ -191,9 +192,6 @@ static bool show_summary = true; #define ELFUTILS_STACKTRACE_VERBOSE_ENV_VAR "ELFUTILS_STACKTRACE_VERBOSE" /* Valid values that turn on diagnostics: 'true', 'verbose', 'debug', '1', '2'. */ -/* Enables detailed tracing of simulated memory reads: */ -/* #define DEBUG_MEMORY_READ */ - /* Enables even more diagnostics on modules: */ /* #define DEBUG_MODULES */ @@ -470,190 +468,6 @@ sysprof_reader_getframes (SysprofReader *reader, #endif /* HAVE_SYSPROF_HEADERS */ -/******************************************* - * Memory read interface for stack samples * - *******************************************/ - -/* TODO: elfutils (internal) libraries use libNN_set_errno and DWFL_E_WHATEVER; - this code fails silently in sample_getthread. */ - -struct __sample_arg -{ - int tid; - Dwarf_Addr base_addr; - uint64_t size; - uint8_t *data; - uint64_t n_regs; - uint64_t abi; /* PERF_SAMPLE_REGS_ABI_{32,64} */ - Dwarf_Addr pc; - Dwarf_Addr sp; - Dwarf_Addr *regs; -}; - -/* The next few functions imitate the corefile interface for a single - stack sample, with very restricted access to registers and memory. */ - -/* Just yield the single thread id matching the sample. */ -static pid_t -sample_next_thread (Dwfl *dwfl __attribute__ ((unused)), void *dwfl_arg, - void **thread_argp) -{ - struct __sample_arg *sample_arg = (struct __sample_arg *)dwfl_arg; - if (*thread_argp == NULL) - { - *thread_argp = (void *)0xea7b3375; - return sample_arg->tid; - } - else - return 0; -} - -/* Just check that the thread id matches the sample. */ -static bool -sample_getthread (Dwfl *dwfl __attribute__ ((unused)), pid_t tid, - void *dwfl_arg, void **thread_argp) -{ - struct __sample_arg *sample_arg = (struct __sample_arg *)dwfl_arg; - *thread_argp = (void *)sample_arg; - if (sample_arg->tid != tid) - { - return false; - } - return true; -} - -#define copy_word_64(result, d) \ - if ((((uintptr_t) (d)) & (sizeof (uint64_t) - 1)) == 0) \ - *(result) = *(uint64_t *)(d); \ - else \ - memcpy ((result), (d), sizeof (uint64_t)); - -#define copy_word_32(result, d) \ - if ((((uintptr_t) (d)) & (sizeof (uint32_t) - 1)) == 0) \ - *(result) = *(uint32_t *)(d); \ - else \ - memcpy ((result), (d), sizeof (uint32_t)); - -#define copy_word(result, d, abi) \ - if ((abi) == PERF_SAMPLE_REGS_ABI_64) \ - { copy_word_64((result), (d)); } \ - else if ((abi) == PERF_SAMPLE_REGS_ABI_32) \ - { copy_word_32((result), (d)); } \ - else \ - *(result) = 0; - -static bool -elf_memory_read (Dwfl *dwfl, Dwarf_Addr addr, Dwarf_Word *result, void *arg) -{ - struct __sample_arg *sample_arg = (struct __sample_arg *)arg; - Dwfl_Module *mod = dwfl_addrmodule(dwfl, addr); - Dwarf_Addr bias; - Elf_Scn *section = dwfl_module_address_section(mod, &addr, &bias); - - if (!section) - return false; - - Elf_Data *data = elf_getdata(section, NULL); - if (data && data->d_buf && data->d_size > addr) { - uint8_t *d = ((uint8_t *)data->d_buf) + addr; - copy_word(result, d, sample_arg->abi); - return true; - } - return false; -} - -static bool -sample_memory_read (Dwfl *dwfl, Dwarf_Addr addr, Dwarf_Word *result, void *arg) -{ - struct __sample_arg *sample_arg = (struct __sample_arg *)arg; - if (show_memory_reads) - fprintf(stderr, "* memory_read addr=%lx+(%lx) size=%lx\n", - sample_arg->base_addr, addr - sample_arg->base_addr, sample_arg->size); - /* Imitate read_cached_memory() with the stack sample data as the cache. */ - if (addr < sample_arg->base_addr || addr - sample_arg->base_addr >= sample_arg->size) - return elf_memory_read(dwfl, addr, result, arg); - uint8_t *d = &sample_arg->data[addr - sample_arg->base_addr]; - copy_word(result, d, sample_arg->abi); - return true; -} - -static bool -sample_set_initial_registers (Dwfl_Thread *thread, void *arg) -{ -#if 0 - /* TODO: __libdwfl_set_initial_registers_thread not exported from libdwfl, - after it was decided to be unsuitable for a public API. - - A subsequent patch in the series removes sample_set_initial_registers, - replacing it with code in libdwfl_stacktrace/dwflst_perf_frame.c. - Keeping this code commented-out for the record, cf how we would - implement if the set_initial_registers utility func was public. - - To *actually* make this work, would need to copy the set_initial_registers - implementation into stacktrace.c; not worth doing since the later patch - overrides this code. */ - struct __sample_arg *sample_arg = (struct __sample_arg *)arg; - dwfl_thread_state_register_pc (thread, sample_arg->pc); - Dwfl_Process *process = thread->process; - Ebl *ebl = process->ebl; - /* XXX Sysprof provides exactly the required registers for unwinding: */ - uint64_t regs_mask = ebl_perf_frame_regs_mask (ebl); - return ebl_set_initial_registers_sample - (ebl, sample_arg->regs, sample_arg->n_regs, regs_mask, sample_arg->abi, - __libdwfl_set_initial_registers_thread, thread); -#else - /* The following facts are needed to translate x86 registers correctly: - - perf register order seen in linux arch/x86/include/uapi/asm/perf_regs.h - The registers array is built in the same order as the enum! - (See the code in tools/perf/util/intel-pt.c intel_pt_add_gp_regs().) - - sysprof libsysprof/perf-event-stream-private.h records all registers - except segment and flags. - - TODO: Should include the perf regs mask in sysprof data and - translate registers in fully-general fashion, removing this assumption. - - dwarf register order seen in elfutils backends/{x86_64,i386}_initreg.c; - and it's a fairly different register order! - - For comparison, you can study codereview.qt-project.org/gitweb?p=qt-creator/perfparser.git;a=blob;f=app/perfregisterinfo.cpp;hb=HEAD - and follow the code which uses those tables of magic numbers. - But it's better to follow original sources of truth for this. */ - struct __sample_arg *sample_arg = (struct __sample_arg *)arg; - bool is_abi32 = (sample_arg->abi == PERF_SAMPLE_REGS_ABI_32); - static const int regs_i386[] = {0, 2, 3, 1, 7/*sp*/, 6, 4, 5, 8/*ip*/}; - static const int regs_x86_64[] = {0, 3, 2, 1, 4, 5, 6, 7/*sp*/, 9, 10, 11, 12, 13, 14, 15, 16, 8/*ip*/}; - const int *reg_xlat = is_abi32 ? regs_i386 : regs_x86_64; - int n_regs = is_abi32 ? 9 : 17; - dwfl_thread_state_register_pc (thread, sample_arg->pc); - if (sample_arg->n_regs < (uint64_t)n_regs && show_failures) - fprintf(stderr, N_("sample_set_initial_regs: n_regs=%ld, expected %d\n"), - sample_arg->n_regs, n_regs); - for (int i = 0; i < n_regs; i++) - { - int j = reg_xlat[i]; - if (j < 0) continue; - if (sample_arg->n_regs <= (uint64_t)j) continue; - dwfl_thread_state_registers (thread, i, 1, &sample_arg->regs[j]); - } - return true; -#endif -} - -static void -sample_detach (Dwfl *dwfl __attribute__ ((unused)), void *dwfl_arg) -{ - struct __sample_arg *sample_arg = (struct __sample_arg *)dwfl_arg; - free (sample_arg); -} - -static const Dwfl_Thread_Callbacks sample_thread_callbacks = -{ - sample_next_thread, - sample_getthread, - sample_memory_read, - sample_set_initial_registers, - sample_detach, - NULL, /* sample_thread_detach */ -}; - /**************************************************** * Dwfl and statistics table for multiple processes * ****************************************************/ @@ -1043,33 +857,30 @@ sysprof_init_dwfl (Dwflst_Process_Tracker *cb_tracker, Dwfl * sysprof_find_dwfl (struct sysprof_unwind_info *sui, SysprofCaptureStackUser *ev, - SysprofCaptureUserRegs *regs) + SysprofCaptureUserRegs *regs, + Elf **out_elf) { pid_t pid = ev->frame.pid; - /* TODO: Need to generalize this code beyond x86 architectures. */ /* XXX: Note that sysprof requesting the x86_64 register file from perf_events will result in an array of 17 regs even for 32-bit applications. */ -#define EXPECTED_REGS 17 - if (regs->n_regs < EXPECTED_REGS) /* XXX expecting everything except FLAGS */ + if (regs->n_regs < ebl_frame_nregs(default_ebl)) /* XXX expecting everything except FLAGS */ { if (show_failures) - fprintf(stderr, N_("sysprof_find_dwfl: n_regs=%d, expected %d\n"), - regs->n_regs, EXPECTED_REGS); + fprintf(stderr, N_("sysprof_find_dwfl: n_regs=%d, expected %ld\n"), + regs->n_regs, ebl_frame_nregs(default_ebl)); return NULL; } + Elf *elf = NULL; Dwfl *dwfl = dwflst_tracker_find_pid (tracker, pid, sysprof_init_dwfl, NULL); - struct __sample_arg *sample_arg; bool cached = false; if (dwfl != NULL && dwfl->process != NULL) { - sample_arg = dwfl->process->callbacks_arg; /* XXX requires libdwflP.h */ cached = true; goto reuse; } - Elf *elf = NULL; int elf_fd = -1; int err = find_procfile (dwfl, &pid, &elf, &elf_fd); if (err < 0) @@ -1080,53 +891,22 @@ sysprof_find_dwfl (struct sysprof_unwind_info *sui, return NULL; } - sample_arg = malloc (sizeof *sample_arg); - if (sample_arg == NULL) - { - if (elf != NULL) { - elf_end (elf); - close (elf_fd); - } - free (sample_arg); - return NULL; - } - reuse: - sample_arg->tid = ev->tid; - sample_arg->size = ev->size; - sample_arg->data = (uint8_t *)&ev->data; - sample_arg->regs = regs->regs; - sample_arg->n_regs = (uint64_t)regs->n_regs; - sample_arg->abi = (uint64_t)regs->abi; - /* TODO: Need to generalize this code beyond x86 architectures. */ - /* Register ordering cf. linux arch/x86/include/uapi/asm/perf_regs.h enum perf_event_x86_regs: */ - sample_arg->sp = regs->regs[7]; - sample_arg->pc = regs->regs[8]; - sample_arg->base_addr = sample_arg->sp; - sui->last_sp = sample_arg->base_addr; - sui->last_base = sample_arg->base_addr; + /* TODO: Generalize to other architectures than x86_64. */ + sui->last_sp = regs->regs[7]; + sui->last_base = sui->last_sp; if (show_frames) { - bool is_abi32 = (sample_arg->abi == PERF_SAMPLE_REGS_ABI_32); + bool is_abi32 = (regs->abi == PERF_SAMPLE_REGS_ABI_32); fprintf(stderr, "sysprof_find_dwfl pid %lld%s: size=%ld%s pc=%lx sp=%lx+(%lx)\n", (long long) pid, cached ? " (cached)" : "", - sample_arg->size, is_abi32 ? " (32-bit)" : "", - sample_arg->pc, sample_arg->base_addr, - sample_arg->sp - sample_arg->base_addr); + ev->size, is_abi32 ? " (32-bit)" : "", + regs->regs[8], sui->last_base, (long)0); } - if (!cached && ! dwfl_attach_state (dwfl, elf, pid, &sample_thread_callbacks, sample_arg)) - { - if (show_failures) - fprintf(stderr, "dwfl_attach_state pid %lld: %s\n", - (long long)pid, dwfl_errmsg(-1)); - elf_end (elf); - close (elf_fd); - free (sample_arg); - return NULL; - } if (!cached) pid_store_dwfl (pid, dwfl); + *out_elf = elf; return dwfl; } @@ -1269,7 +1049,8 @@ sysprof_unwind_cb (SysprofCaptureFrame *frame, void *arg) SysprofCaptureUserRegs *regs = (SysprofCaptureUserRegs *)tail_ptr; if (show_frames) fprintf(stderr, "\n"); /* extra newline for padding */ - Dwfl *dwfl = sysprof_find_dwfl (sui, ev, regs); + Elf *elf = NULL; + Dwfl *dwfl = sysprof_find_dwfl (sui, ev, regs, &elf); if (dwfl == NULL) { if (show_summary) @@ -1289,11 +1070,16 @@ sysprof_unwind_cb (SysprofCaptureFrame *frame, void *arg) sui->last_dwfl = dwfl; #endif sui->last_pid = frame->pid; - int rc = dwfl_getthread_frames (dwfl, ev->tid, sysprof_unwind_frame_cb, sui); + uint64_t regs_mask = ebl_perf_frame_regs_mask (default_ebl); + int rc = dwflst_perf_sample_getframes (dwfl, elf, frame->pid, ev->tid, + (uint8_t *)&ev->data, ev->size, + regs->regs, regs->n_regs, + regs_mask, regs->abi, + sysprof_unwind_frame_cb, sui); if (rc < 0) { if (show_failures) - fprintf(stderr, "dwfl_getthread_frames pid %lld: %s\n", + fprintf(stderr, "dwflst_perf_sample_getframes pid %lld: %s\n", (long long)frame->pid, dwfl_errmsg(-1)); } if (show_samples) @@ -1409,9 +1195,6 @@ parse_opt (int key, char *arg __attribute__ ((unused)), break; case OPT_DEBUG: -#ifdef DEBUG_MEMORY_READ - show_memory_reads = true; -#endif show_frames = true; FALLTHROUGH; case 'v': @@ -1503,9 +1286,6 @@ https://sourceware.org/cgit/elfutils/tree/README.eu-stacktrace?h=users/serhei/eu else if (strcmp(env_verbose, "debug") == 0 || strcmp(env_verbose, "2") == 0) { -#ifdef DEBUG_MEMORY_READ - show_memory_reads = true; -#endif show_frames = true; show_samples = true; show_failures = true; @@ -1571,6 +1351,10 @@ https://sourceware.org/cgit/elfutils/tree/README.eu-stacktrace?h=users/serhei/eu { if (!dwfltab_init()) error (EXIT_BAD, errno, N_("Could not initialize Dwfl table")); + + /* TODO: Generalize to other architectures. */ + default_ebl = ebl_openbackend_machine(EM_X86_64); + struct sysprof_unwind_info sui; sui.output_fd = output_fd; sui.reader = reader; -- 2.47.0