Hi,
dependent on: [patch 3/4] unwinder: Provide dwfl_core_filename_report
present in: jankratochvil/forunwind-baseaddr-hookvars-corereport-unwindx86 but this branch is not suitable for merge as it is unclear in which form get checked in the patches above. The branch is usable only for "git checkout", neither diff nor merge should be done on it.
ppc* and s390* extensions exist in jankratochvil/forunwind-baseaddr-hookvars-corereport-unwindx86-nonx86 and they are not present here for simplicity, I may post them later.
Some comments were already posted, it supports natively core files and live PIDs, "data" API interface for backtracing application-provided inferiors is also present.
Testcases are provided, src/stack.c utility is also provided.
Thanks, Jan
backends/ 2012-11-13 Jan Kratochvil jan.kratochvil@redhat.com
* Makefile.am (i386_SRCS): Add i386_frame_state.c. (x86_64_SRCS): Add x86_64_frame_state.c. * i386_frame_state.c: New file. * i386_init.c (i386_init): Initialize frame_state_nregs and frame_state. * x86_64_frame_state.c: New file. * x86_64_init.c (x86_64_init): Initialize frame_state_nregs and frame_state.
./ 2012-11-13 Jan Kratochvil jan.kratochvil@redhat.com
* configure.ac: New AC_CHECK_SIZEOF for long. Call utrace_BIARCH, new AC_SUBST for CC_BIARCH.
libdw/ 2012-11-13 Jan Kratochvil jan.kratochvil@redhat.com
* cfi.h (struct Dwarf_Frame_s): Make the comment more specific. * libdw.map (ELFUTILS_0.156): Add dwfl_report_elf_baseaddr, dwfl_frame_state_pid, dwfl_frame_state_core, dwfl_frame_state_data, dwfl_frame_unwind, dwfl_frame_state_pc, dwfl_frame_tid_get and dwfl_frame_thread_next.
libdwfl/ 2012-11-13 Jan Kratochvil jan.kratochvil@redhat.com
* Makefile.am (INCLUDES): Add ../libasm. (libdwfl_a_SOURCES): Add dwfl_frame_state.c, dwfl_frame_unwind.c, dwfl_frame_state_pc.c, dwfl_frame_state_pid.c, dwfl_frame_state_core.c, dwfl_frame_state_data.c, dwfl_frame_thread_next.c and dwfl_frame_tid_get.c. * dwfl_end.c (dwfl_end): Call __libdwfl_process_free for dwfl->framestatelist. * dwfl_frame_state.c: New file. * dwfl_frame_state_core.c: New file. * dwfl_frame_state_data.c: New file. * dwfl_frame_state_pc.c: New file. * dwfl_frame_state_pid.c: New file. * dwfl_frame_thread_next.c: New file. * dwfl_frame_tid_get.c: New file. * dwfl_frame_unwind.c: New file. * libdwfl.h (Dwfl_Frame_State): New typedef. (dwfl_frame_state_pid, dwfl_frame_state_core, dwfl_frame_memory_read_t) (dwfl_frame_state_data, dwfl_frame_unwind, dwfl_frame_state_pc) (dwfl_frame_thread_next, dwfl_frame_tid_get): New declaration. * libdwflP.h: Include libeblP.h. (Dwfl_Frame_State_Process, Dwfl_Frame_State_Thread): New typedefs. (LIBEBL_BAD, CORE_MISSING, INVALID_REGISTER, PROCESS_MEMORY_READ) (PROCESS_NO_ARCH, PARSE_PROC, NO_THREAD, INVALID_DWARF) (UNSUPPORTED_DWARF): New DWFL_ERROR entries. (struct Dwfl): New entry framestatelist. (struct Dwfl_Frame_State_Process, struct Dwfl_Frame_State_Thread) (struct Dwfl_Frame_State, dwfl_frame_state_reg_get) (dwfl_frame_state_reg_set): New. (__libdwfl_state_fetch_pc, __libdwfl_thread_free) (__libdwfl_thread_alloc, __libdwfl_process_free) (__libdwfl_process_alloc, __libdwfl_segment_start) (__libdwfl_segment_end): New declarations. (dwfl_frame_state_pid, dwfl_frame_state_core, dwfl_frame_unwind) (dwfl_frame_state_pc): New INTDECL entries. * segment.c (segment_start): Rename to ... (__libdwfl_segment_start): ... here and make it internal_function. (segment_end): Rename to ... (__libdwfl_segment_end): ... here and make it internal_function. (reify_segments, dwfl_report_segment): Rename them at the callers.
libebl/ 2012-11-13 Jan Kratochvil jan.kratochvil@redhat.com
* Makefile.am (INCLUDES): Add ../libdwfl. (gen_SOURCES): Add eblframestate.c. * ebl-hooks.h (frame_state, frame_state_nregs): New entries. * eblframestate.c: New file. * libebl.h (struct Dwfl_Frame_State, ebl_frame_state) (ebl_frame_state_nregs): New declarations.
m4/ 2012-11-13 Jan Kratochvil jan.kratochvil@redhat.com
* biarch.m4: New file.
src/ 2012-11-13 Jan Kratochvil jan.kratochvil@redhat.com
* Makefile.am (bin_PROGRAMS): Add stack. (stack_LDADD): New. * stack.c: New file.
tests/ 2012-11-13 Jan Kratochvil jan.kratochvil@redhat.com
* Makefile.am (check_PROGRAMS): Add backtrace, backtrace-child and backtrace-data. (BUILT_SOURCES, clean-local, backtrace-child-biarch): New. (TESTS): Add run-backtrace.sh. (backtrace_LDADD, backtrace_child_CFLAGS, backtrace_child_LDFLAGS) (backtrace_data_LDADD): New. * backtrace-child.c: New file. * backtrace-data.c: New file. * backtrace.c: New file. * run-backtrace.sh: New file.
diff --git a/backends/Makefile.am b/backends/Makefile.am index 243a95a..6f99758 100644 --- a/backends/Makefile.am +++ b/backends/Makefile.am @@ -50,7 +50,8 @@ libdw = ../libdw/libdw.so endif
i386_SRCS = i386_init.c i386_symbol.c i386_corenote.c i386_cfi.c \ - i386_retval.c i386_regs.c i386_auxv.c i386_syscall.c + i386_retval.c i386_regs.c i386_auxv.c i386_syscall.c \ + i386_frame_state.c cpu_i386 = ../libcpu/libcpu_i386.a libebl_i386_pic_a_SOURCES = $(i386_SRCS) am_libebl_i386_pic_a_OBJECTS = $(i386_SRCS:.c=.os) @@ -60,7 +61,8 @@ libebl_sh_pic_a_SOURCES = $(sh_SRCS) am_libebl_sh_pic_a_OBJECTS = $(sh_SRCS:.c=.os)
x86_64_SRCS = x86_64_init.c x86_64_symbol.c x86_64_corenote.c x86_64_cfi.c \ - x86_64_retval.c x86_64_regs.c i386_auxv.c x86_64_syscall.c + x86_64_retval.c x86_64_regs.c i386_auxv.c x86_64_syscall.c \ + x86_64_frame_state.c cpu_x86_64 = ../libcpu/libcpu_x86_64.a libebl_x86_64_pic_a_SOURCES = $(x86_64_SRCS) am_libebl_x86_64_pic_a_OBJECTS = $(x86_64_SRCS:.c=.os) diff --git a/backends/i386_frame_state.c b/backends/i386_frame_state.c new file mode 100644 index 0000000..b5d5439 --- /dev/null +++ b/backends/i386_frame_state.c @@ -0,0 +1,80 @@ +/* Fetch process data from STATE->base->pid or STATE->base->core. + Copyright (C) 2012 Red Hat, Inc. + This file is part of elfutils. + + This file is free software; you can redistribute it and/or modify + it under the terms of either + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at + your option) any later version + + or + + * the GNU General Public License as published by the Free + Software Foundation; either version 2 of the License, or (at + your option) any later version + + or both in parallel, as here. + + elfutils is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received copies of the GNU General Public License and + the GNU Lesser General Public License along with this program. If + not, see http://www.gnu.org/licenses/. */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#if defined __i386__ || defined __x86_64__ +# include <sys/user.h> +# include <sys/ptrace.h> +#endif +#include "libdwflP.h" + +#define BACKEND i386_ +#include "libebl_CPU.h" + +bool +i386_frame_state (Dwfl_Frame_State *state) +{ + pid_t tid = state->thread->tid; + if (state->thread->process->core == NULL && tid) + { +#if !defined __i386__ && !defined __x86_64__ + return false; +#else /* __i386__ || __x86_64__ */ + struct user_regs_struct user_regs; + if (ptrace (PTRACE_GETREGS, tid, NULL, &user_regs) != 0) + return false; +# if defined __i386__ + dwfl_frame_state_reg_set (state, 0, user_regs.eax); + dwfl_frame_state_reg_set (state, 1, user_regs.ecx); + dwfl_frame_state_reg_set (state, 2, user_regs.edx); + dwfl_frame_state_reg_set (state, 3, user_regs.ebx); + dwfl_frame_state_reg_set (state, 4, user_regs.esp); + dwfl_frame_state_reg_set (state, 5, user_regs.ebp); + dwfl_frame_state_reg_set (state, 6, user_regs.esi); + dwfl_frame_state_reg_set (state, 7, user_regs.edi); + dwfl_frame_state_reg_set (state, 8, user_regs.eip); +# elif defined __x86_64__ + dwfl_frame_state_reg_set (state, 0, user_regs.rax); + dwfl_frame_state_reg_set (state, 1, user_regs.rcx); + dwfl_frame_state_reg_set (state, 2, user_regs.rdx); + dwfl_frame_state_reg_set (state, 3, user_regs.rbx); + dwfl_frame_state_reg_set (state, 4, user_regs.rsp); + dwfl_frame_state_reg_set (state, 5, user_regs.rbp); + dwfl_frame_state_reg_set (state, 6, user_regs.rsi); + dwfl_frame_state_reg_set (state, 7, user_regs.rdi); + dwfl_frame_state_reg_set (state, 8, user_regs.rip); +# else /* (__i386__ || __x86_64__) && (!__i386__ && !__x86_64__) */ +# error +# endif /* (__i386__ || __x86_64__) && (!__i386__ && !__x86_64__) */ +#endif /* __i386__ || __x86_64__ */ + } + return true; +} diff --git a/backends/i386_init.c b/backends/i386_init.c index cc9b2d7..4200f31 100644 --- a/backends/i386_init.c +++ b/backends/i386_init.c @@ -63,6 +63,9 @@ i386_init (elf, machine, eh, ehlen) HOOK (eh, auxv_info); HOOK (eh, disasm); HOOK (eh, abi_cfi); + /* gcc/config/ #define DWARF_FRAME_REGISTERS. For i386 it is 17, why? */ + eh->frame_state_nregs = 9; + HOOK (eh, frame_state);
return MODVERSION; } diff --git a/backends/x86_64_frame_state.c b/backends/x86_64_frame_state.c new file mode 100644 index 0000000..b304250 --- /dev/null +++ b/backends/x86_64_frame_state.c @@ -0,0 +1,75 @@ +/* Fetch live process Dwfl_Frame_State from PID. + Copyright (C) 2012 Red Hat, Inc. + This file is part of elfutils. + + This file is free software; you can redistribute it and/or modify + it under the terms of either + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at + your option) any later version + + or + + * the GNU General Public License as published by the Free + Software Foundation; either version 2 of the License, or (at + your option) any later version + + or both in parallel, as here. + + elfutils is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received copies of the GNU General Public License and + the GNU Lesser General Public License along with this program. If + not, see http://www.gnu.org/licenses/. */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include <stdlib.h> +#ifdef __x86_64__ +# include <sys/user.h> +# include <sys/ptrace.h> +#endif +#include "libdwflP.h" + +#define BACKEND x86_64_ +#include "libebl_CPU.h" + +bool +x86_64_frame_state (Dwfl_Frame_State *state) +{ + pid_t tid = state->thread->tid; + if (state->thread->process->core == NULL && tid) + { +#ifndef __x86_64__ + return false; +#else /* __x86_64__ */ + struct user_regs_struct user_regs; + if (ptrace (PTRACE_GETREGS, tid, NULL, &user_regs) != 0) + return false; + dwfl_frame_state_reg_set (state, 0, user_regs.rax); + dwfl_frame_state_reg_set (state, 1, user_regs.rdx); + dwfl_frame_state_reg_set (state, 2, user_regs.rcx); + dwfl_frame_state_reg_set (state, 3, user_regs.rbx); + dwfl_frame_state_reg_set (state, 4, user_regs.rsi); + dwfl_frame_state_reg_set (state, 5, user_regs.rdi); + dwfl_frame_state_reg_set (state, 6, user_regs.rbp); + dwfl_frame_state_reg_set (state, 7, user_regs.rsp); + dwfl_frame_state_reg_set (state, 8, user_regs.r8); + dwfl_frame_state_reg_set (state, 9, user_regs.r9); + dwfl_frame_state_reg_set (state, 10, user_regs.r10); + dwfl_frame_state_reg_set (state, 11, user_regs.r11); + dwfl_frame_state_reg_set (state, 12, user_regs.r12); + dwfl_frame_state_reg_set (state, 13, user_regs.r13); + dwfl_frame_state_reg_set (state, 14, user_regs.r14); + dwfl_frame_state_reg_set (state, 15, user_regs.r15); + dwfl_frame_state_reg_set (state, 16, user_regs.rip); +#endif /* __x86_64__ */ + } + return true; +} diff --git a/backends/x86_64_init.c b/backends/x86_64_init.c index 67a5880..08c63f1 100644 --- a/backends/x86_64_init.c +++ b/backends/x86_64_init.c @@ -60,6 +60,9 @@ x86_64_init (elf, machine, eh, ehlen) HOOK (eh, auxv_info); HOOK (eh, disasm); HOOK (eh, abi_cfi); + /* gcc/config/ #define DWARF_FRAME_REGISTERS. */ + eh->frame_state_nregs = 17; + HOOK (eh, frame_state);
return MODVERSION; } diff --git a/configure.ac b/configure.ac index 52a2322..01c5510 100644 --- a/configure.ac +++ b/configure.ac @@ -317,4 +317,15 @@ esac # Round up to the next release API (x.y) version. eu_version=$(( (eu_version + 999) / 1000 ))
+AC_CHECK_SIZEOF(long) + +# On a 64-bit host where can can use $CC -m32, we'll run two sets of tests. +# Likewise in a 32-bit build on a host where $CC -m64 works. +utrace_BIARCH +# `$utrace_biarch' will be `-m64' even on an uniarch i386 machine. +AS_IF([test $utrace_cv_cc_biarch = yes], + [CC_BIARCH="$CC $utrace_biarch"], + [CC_BIARCH="$CC"]) +AC_SUBST([CC_BIARCH]) + AC_OUTPUT diff --git a/libdw/cfi.h b/libdw/cfi.h index 8949833..44a84d4 100644 --- a/libdw/cfi.h +++ b/libdw/cfi.h @@ -150,8 +150,8 @@ struct dwarf_frame_register Dwarf_Sword value:(sizeof (Dwarf_Sword) * 8 - 3); };
-/* This holds everything we know about the state of the frame - at a particular PC location described by an FDE. */ +/* This holds instructions for unwinding frame at a particular PC location + described by an FDE. */ struct Dwarf_Frame_s { /* This frame description covers PC values in [start, end). */ diff --git a/libdw/libdw.map b/libdw/libdw.map index 5a055e8..bf65d83 100644 --- a/libdw/libdw.map +++ b/libdw/libdw.map @@ -258,4 +258,12 @@ ELFUTILS_0.149 { ELFUTILS_0.156 { global: dwfl_core_filename_report; + dwfl_report_elf_baseaddr; + dwfl_frame_state_pid; + dwfl_frame_state_core; + dwfl_frame_state_data; + dwfl_frame_unwind; + dwfl_frame_state_pc; + dwfl_frame_tid_get; + dwfl_frame_thread_next; } ELFUTILS_0.149; diff --git a/libdwfl/Makefile.am b/libdwfl/Makefile.am index 0ef4928..641b9eb 100644 --- a/libdwfl/Makefile.am +++ b/libdwfl/Makefile.am @@ -31,7 +31,7 @@ ## include $(top_srcdir)/config/eu.am INCLUDES += -I$(srcdir) -I$(srcdir)/../libelf -I$(srcdir)/../libebl \ - -I$(srcdir)/../libdw + -I$(srcdir)/../libdw -I$(srcdir)/../libasm VERSION = 1
noinst_LIBRARIES = libdwfl.a @@ -68,7 +68,11 @@ libdwfl_a_SOURCES = dwfl_begin.c dwfl_end.c dwfl_error.c dwfl_version.c \ dwfl_module_return_value_location.c \ dwfl_module_register_names.c \ dwfl_segment_report_module.c \ - link_map.c core-file.c open.c image-header.c + link_map.c core-file.c open.c image-header.c \ + dwfl_frame_state.c dwfl_frame_unwind.c \ + dwfl_frame_state_pc.c dwfl_frame_state_pid.c \ + dwfl_frame_state_core.c dwfl_frame_state_data.c \ + dwfl_frame_thread_next.c dwfl_frame_tid_get.c
if ZLIB libdwfl_a_SOURCES += gzip.c diff --git a/libdwfl/dwfl_end.c b/libdwfl/dwfl_end.c index fd38e0f..cca38c7 100644 --- a/libdwfl/dwfl_end.c +++ b/libdwfl/dwfl_end.c @@ -34,6 +34,9 @@ dwfl_end (Dwfl *dwfl) if (dwfl == NULL) return;
+ while (dwfl->framestatelist) + __libdwfl_process_free (dwfl->framestatelist); + free (dwfl->lookup_addr); free (dwfl->lookup_module); free (dwfl->lookup_segndx); diff --git a/libdwfl/dwfl_frame_state.c b/libdwfl/dwfl_frame_state.c new file mode 100644 index 0000000..1229aa2 --- /dev/null +++ b/libdwfl/dwfl_frame_state.c @@ -0,0 +1,175 @@ +/* Get Dwarf Frame state for target PID or core file. + Copyright (C) 2012 Red Hat, Inc. + This file is part of elfutils. + + This file is free software; you can redistribute it and/or modify + it under the terms of either + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at + your option) any later version + + or + + * the GNU General Public License as published by the Free + Software Foundation; either version 2 of the License, or (at + your option) any later version + + or both in parallel, as here. + + elfutils is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received copies of the GNU General Public License and + the GNU Lesser General Public License along with this program. If + not, see http://www.gnu.org/licenses/. */ + +#include "libdwflP.h" +#include <sys/ptrace.h> +#include <unistd.h> + +#ifndef MIN +# define MIN(a, b) ((a) < (b) ? (a) : (b)) +#endif + +bool +internal_function +__libdwfl_state_fetch_pc (Dwfl_Frame_State *state) +{ + switch (state->pc_state) + { + case DWFL_FRAME_STATE_PC_SET: + return true; + case DWFL_FRAME_STATE_PC_UNDEFINED: + abort (); + case DWFL_FRAME_STATE_ERROR:; + Ebl *ebl = state->thread->process->ebl; + Dwarf_CIE abi_info; + if (ebl_abi_cfi (ebl, &abi_info) != 0) + { + __libdwfl_seterrno (DWFL_E_LIBEBL); + return false; + } + unsigned ra = abi_info.return_address_register; + /* dwarf_frame_state_reg_is_set is not applied here. */ + if (ra >= ebl_frame_state_nregs (ebl)) + { + __libdwfl_seterrno (DWFL_E_LIBEBL_BAD); + return false; + } + state->pc = state->regs[ra]; + state->pc_state = DWFL_FRAME_STATE_PC_SET; + return true; + } + abort (); +} + +/* Do not call it on your own, to be used by thread_* functions only. */ + +static void +state_free (Dwfl_Frame_State *state) +{ + Dwfl_Frame_State_Thread *thread = state->thread; + assert (thread->unwound == state); + thread->unwound = state->unwound; + free (state); +} + +/* Do not call it on your own, to be used by thread_* functions only. */ + +static Dwfl_Frame_State * +state_alloc (Dwfl_Frame_State_Thread *thread) +{ + assert (thread->unwound == NULL); + Ebl *ebl = thread->process->ebl; + size_t nregs = ebl_frame_state_nregs (ebl); + if (nregs == 0) + return NULL; + assert (nregs < sizeof (((Dwfl_Frame_State *) NULL)->regs_set) * 8); + Dwfl_Frame_State *state = malloc (sizeof (*state) + + sizeof (*state->regs) * nregs); + if (state == NULL) + return NULL; + state->thread = thread; + state->signal_frame = false; + state->pc_state = DWFL_FRAME_STATE_ERROR; + memset (state->regs_set, 0, sizeof (state->regs_set)); + thread->unwound = state; + state->unwound = NULL; + return state; +} + +void +internal_function +__libdwfl_thread_free (Dwfl_Frame_State_Thread *thread) +{ + while (thread->unwound) + state_free (thread->unwound); + if (thread->tid_attached) + ptrace (PTRACE_DETACH, thread->tid, NULL, NULL); + Dwfl_Frame_State_Process *process = thread->process; + assert (process->thread == thread); + process->thread = thread->next; + free (thread); +} + +Dwfl_Frame_State_Thread * +internal_function +__libdwfl_thread_alloc (Dwfl_Frame_State_Process *process, pid_t tid) +{ + Dwfl_Frame_State_Thread *thread = malloc (sizeof (*thread)); + if (thread == NULL) + return NULL; + thread->process = process; + thread->tid = tid; + thread->tid_attached = false; + thread->unwound = NULL; + thread->next = process->thread; + process->thread = thread; + if (state_alloc (thread) == NULL) + { + __libdwfl_thread_free (thread); + return NULL; + } + return thread; +} + +void +internal_function +__libdwfl_process_free (Dwfl_Frame_State_Process *process) +{ + while (process->thread) + __libdwfl_thread_free (process->thread); + if (process->ebl_close) + ebl_closebackend (process->ebl); + elf_end (process->core); + if (process->core_fd != -1) + close (process->core_fd); + Dwfl *dwfl = process->dwfl; + assert (dwfl->framestatelist == process); + dwfl->framestatelist = process->next; + free (process); +} + +Dwfl_Frame_State_Process * +internal_function +__libdwfl_process_alloc (Dwfl *dwfl, dwfl_frame_memory_read_t *memory_read, + void *memory_read_user_data) +{ + Dwfl_Frame_State_Process *process = malloc (sizeof (*process)); + if (process == NULL) + return NULL; + process->dwfl = dwfl; + process->ebl = NULL; + process->ebl_close = NULL; + process->memory_read = memory_read; + process->memory_read_user_data = memory_read_user_data; + process->core = NULL; + process->core_fd = -1; + process->thread = NULL; + process->next = dwfl->framestatelist; + dwfl->framestatelist = process; + return process; +} diff --git a/libdwfl/dwfl_frame_state_core.c b/libdwfl/dwfl_frame_state_core.c new file mode 100644 index 0000000..737bcbb --- /dev/null +++ b/libdwfl/dwfl_frame_state_core.c @@ -0,0 +1,286 @@ +/* Get Dwarf Frame state for target core file. + Copyright (C) 2012 Red Hat, Inc. + This file is part of elfutils. + + This file is free software; you can redistribute it and/or modify + it under the terms of either + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at + your option) any later version + + or + + * the GNU General Public License as published by the Free + Software Foundation; either version 2 of the License, or (at + your option) any later version + + or both in parallel, as here. + + elfutils is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received copies of the GNU General Public License and + the GNU Lesser General Public License along with this program. If + not, see http://www.gnu.org/licenses/. */ + +#include "libdwflP.h" +#include <fcntl.h> +#include "system.h" + +#ifndef MIN +# define MIN(a, b) ((a) < (b) ? (a) : (b)) +#endif + +static bool +dwfl_frame_state_core_memory_read (Dwarf_Addr addr, Dwarf_Addr *result, + void *user_data) +{ + Dwfl_Frame_State_Process *process = user_data; + Elf *core = process->core; + assert (core != NULL); + Dwfl *dwfl = process->dwfl; + static size_t phnum; + if (elf_getphdrnum (core, &phnum) < 0) + { + __libdwfl_seterrno (DWFL_E_LIBELF); + return false; + } + for (size_t cnt = 0; cnt < phnum; ++cnt) + { + GElf_Phdr phdr_mem, *phdr = gelf_getphdr (core, cnt, &phdr_mem); + if (phdr == NULL || phdr->p_type != PT_LOAD) + continue; + /* Bias is zero here, a core file itself has no bias. */ + GElf_Addr start = __libdwfl_segment_start (dwfl, phdr->p_vaddr); + GElf_Addr end = __libdwfl_segment_end (dwfl, + phdr->p_vaddr + phdr->p_memsz); + unsigned bytes = process->ebl->class == ELFCLASS64 ? 8 : 4; + if (addr < start || addr + bytes > end) + continue; + Elf_Data *data; + data = elf_getdata_rawchunk (core, phdr->p_offset + addr - start, + bytes, ELF_T_ADDR); + if (data == NULL) + { + __libdwfl_seterrno (DWFL_E_LIBELF); + return false; + } + assert (data->d_size == bytes); + /* FIXME: Currently any arch supported for unwinding supports + unaligned access. */ + if (bytes == 8) + *result = *(const uint64_t *) data->d_buf; + else + *result = *(const uint32_t *) data->d_buf; + return true; + } + __libdwfl_seterrno (DWFL_E_CORE_MISSING); + return false; +} + +Dwfl_Frame_State * +dwfl_frame_state_core (Dwfl *dwfl, const char *corefile) +{ + Dwfl_Frame_State_Process *process; + process = __libdwfl_process_alloc (dwfl, dwfl_frame_state_core_memory_read, + NULL); + if (process == NULL) + return NULL; + process->memory_read_user_data = process; + int core_fd = open64 (corefile, O_RDONLY); + if (core_fd < 0) + { + __libdwfl_process_free (process); + __libdwfl_seterrno (DWFL_E_BADELF); + return NULL; + } + process->core_fd = core_fd; + Elf *core; + Dwfl_Error err = __libdw_open_file (&core_fd, &core, true, false); + if (err != DWFL_E_NOERROR) + { + __libdwfl_process_free (process); + __libdwfl_seterrno (err); + return NULL; + } + process->core = core; + Ebl *ebl = ebl_openbackend (core); + if (ebl == NULL) + { + __libdwfl_process_free (process); + __libdwfl_seterrno (DWFL_E_LIBEBL); + return NULL; + } + process->ebl = ebl; + process->ebl_close = true; + size_t nregs = ebl_frame_state_nregs (ebl); + if (nregs == 0) + { + /* We do not support unwinding this CORE file EBL. */ + __libdwfl_process_free (process); + __libdwfl_seterrno (DWFL_E_LIBEBL); + return NULL; + } + GElf_Ehdr ehdr_mem, *ehdr = gelf_getehdr (core, &ehdr_mem); + if (ehdr == NULL) + { + __libdwfl_process_free (process); + __libdwfl_seterrno (DWFL_E_LIBELF); + return NULL; + } + assert (ehdr->e_type == ET_CORE); + size_t phnum; + if (elf_getphdrnum (core, &phnum) < 0) + { + __libdwfl_process_free (process); + __libdwfl_seterrno (DWFL_E_LIBELF); + return NULL; + } + Dwfl_Frame_State_Thread *thread = NULL; + for (size_t cnt = 0; cnt < phnum; ++cnt) + { + GElf_Phdr phdr_mem, *phdr = gelf_getphdr (core, cnt, &phdr_mem); + if (phdr == NULL || phdr->p_type != PT_NOTE) + continue; + Elf_Data *data = elf_getdata_rawchunk (core, phdr->p_offset, + phdr->p_filesz, ELF_T_NHDR); + if (data == NULL) + { + __libdwfl_process_free (process); + __libdwfl_seterrno (DWFL_E_LIBELF); + return NULL; + } + size_t offset = 0; + GElf_Nhdr nhdr; + size_t name_offset; + size_t desc_offset; + while (offset < data->d_size + && (offset = gelf_getnote (data, offset, + &nhdr, &name_offset, &desc_offset)) > 0) + { + /* Do not check NAME for now, help broken Linux kernels. */ + const char *name = data->d_buf + name_offset; + const char *desc = data->d_buf + desc_offset; + GElf_Word regs_offset; + size_t nregloc; + const Ebl_Register_Location *reglocs; + size_t nitems; + const Ebl_Core_Item *items; + if (! ebl_core_note (ebl, &nhdr, name, + ®s_offset, &nregloc, ®locs, &nitems, &items)) + { + /* This note may be just not recognized, skip it. */ + continue; + } + if (nhdr.n_type == NT_PRSTATUS) + { + const Ebl_Core_Item *item; + for (item = items; item < items + nitems; item++) + if (strcmp (item->name, "pid") == 0) + break; + if (item == items + nitems) + { + __libdwfl_process_free (process); + __libdwfl_seterrno (DWFL_E_BADELF); + return NULL; + } + uint32_t val32 = *(const uint32_t *) (desc + item->offset); + val32 = (elf_getident (core, NULL)[EI_DATA] == ELFDATA2MSB + ? be32toh (val32) : le32toh (val32)); + pid_t tid = (int32_t) val32; + eu_static_assert (sizeof val32 <= sizeof tid); + if (thread) + { + /* Delay initialization of THREAD till all notes for it have + been read in. */ + Dwfl_Frame_State *state = thread->unwound; + if (! ebl_frame_state (state) + || ! __libdwfl_state_fetch_pc (state)) + { + __libdwfl_thread_free (thread); + thread = NULL; + continue; + } + } + thread = __libdwfl_thread_alloc (process, tid); + if (thread == NULL) + { + __libdwfl_process_free (process); + __libdwfl_seterrno (DWFL_E_NOMEM); + return NULL; + } + } + if (thread == NULL) + { + /* Ignore notes before first PR_NTSTATUS. */ + continue; + } + Dwfl_Frame_State *state = thread->unwound; + desc += regs_offset; + for (size_t regloci = 0; regloci < nregloc; regloci++) + { + const Ebl_Register_Location *regloc = reglocs + regloci; + if (regloc->regno >= nregs) + continue; + assert (regloc->bits == 32 || regloc->bits == 64); + const char *reg_desc = desc + regloc->offset; + for (unsigned regno = regloc->regno; + regno < MIN (regloc->regno + (regloc->count ?: 1U), nregs); + regno++) + { + /* PPC provides DWARF register 65 irrelevant for + CFI which clashes with register 108 (LR) we need. + LR (108) is provided earlier (in NT_PRSTATUS) than the # 65. + FIXME: It depends now on their order in core notes. */ + if (dwfl_frame_state_reg_get (state, regno, NULL)) + continue; + Dwarf_Addr val; + switch (regloc->bits) + { + case 32:; + uint32_t val32 = *(const uint32_t *) reg_desc; + reg_desc += sizeof val32; + val32 = (elf_getident (core, NULL)[EI_DATA] == ELFDATA2MSB + ? be32toh (val32) : le32toh (val32)); + /* Do a host width conversion. */ + val = val32; + break; + case 64:; + uint64_t val64 = *(const uint64_t *) reg_desc; + reg_desc += sizeof val64; + val64 = (elf_getident (core, NULL)[EI_DATA] == ELFDATA2MSB + ? be64toh (val64) : le64toh (val64)); + assert (sizeof (*state->regs) == sizeof val64); + val = val64; + break; + default: + abort (); + } + /* Registers not valid for CFI are just ignored. */ + dwfl_frame_state_reg_set (state, regno, val); + reg_desc += regloc->pad; + } + } + } + } + if (thread) + { + /* Delay initialization of THREAD till all notes for it have been read + in. */ + Dwfl_Frame_State *state = thread->unwound; + if (! ebl_frame_state (state) || ! __libdwfl_state_fetch_pc (state)) + __libdwfl_thread_free (thread); + } + if (process->thread == NULL) + { + /* No valid threads recognized in this CORE. */ + __libdwfl_process_free (process); + __libdwfl_seterrno (DWFL_E_BADELF); + return NULL; + } + return process->thread->unwound; +} +INTDEF (dwfl_frame_state_core) diff --git a/libdwfl/dwfl_frame_state_data.c b/libdwfl/dwfl_frame_state_data.c new file mode 100644 index 0000000..9adfa7c --- /dev/null +++ b/libdwfl/dwfl_frame_state_data.c @@ -0,0 +1,91 @@ +/* Get Dwarf Frame state from modules present in DWFL. + Copyright (C) 2012 Red Hat, Inc. + This file is part of elfutils. + + This file is free software; you can redistribute it and/or modify + it under the terms of either + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at + your option) any later version + + or + + * the GNU General Public License as published by the Free + Software Foundation; either version 2 of the License, or (at + your option) any later version + + or both in parallel, as here. + + elfutils is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received copies of the GNU General Public License and + the GNU Lesser General Public License along with this program. If + not, see http://www.gnu.org/licenses/. */ + +#include "libdwflP.h" + +Dwfl_Frame_State * +dwfl_frame_state_data (Dwfl *dwfl, bool pc_set, Dwarf_Addr pc, unsigned nregs, + const uint64_t *regs_set, const Dwarf_Addr *regs, + dwfl_frame_memory_read_t *memory_read, + void *memory_read_user_data) +{ + Ebl *ebl = NULL; + for (Dwfl_Module *mod = dwfl->modulelist; mod != NULL; mod = mod->next) + { + Dwfl_Error error = __libdwfl_module_getebl (mod); + if (error != DWFL_E_NOERROR) + continue; + ebl = mod->ebl; + } + if (ebl == NULL || nregs > ebl_frame_state_nregs (ebl)) + { + __libdwfl_seterrno (DWFL_E_LIBEBL_BAD); + return NULL; + } + Dwfl_Frame_State_Process *process; + process = __libdwfl_process_alloc (dwfl, memory_read, memory_read_user_data); + if (process == NULL) + return NULL; + process->ebl = ebl; + Dwfl_Frame_State_Thread *thread = __libdwfl_thread_alloc (process, 0); + if (thread == NULL) + { + __libdwfl_process_free (process); + __libdwfl_seterrno (DWFL_E_NOMEM); + return NULL; + } + Dwfl_Frame_State *state = thread->unwound; + state->pc_state = DWFL_FRAME_STATE_ERROR; + if (pc_set) + { + state->pc = pc; + state->pc_state = DWFL_FRAME_STATE_PC_SET; + } + for (unsigned regno = 0; regno < nregs; regno++) + if ((regs_set[regno / sizeof (*regs_set) / 8] + & (1U << (regno % (sizeof (*regs_set) * 8)))) != 0 + && ! dwfl_frame_state_reg_set (state, regno, regs[regno])) + { + __libdwfl_process_free (process); + __libdwfl_seterrno (DWFL_E_INVALID_REGISTER); + return NULL; + } + if (! ebl_frame_state (state)) + { + __libdwfl_process_free (process); + __libdwfl_seterrno (DWFL_E_LIBEBL); + return NULL; + } + if (! __libdwfl_state_fetch_pc (state)) + { + __libdwfl_process_free (process); + return NULL; + } + return process->thread->unwound; +} +INTDEF (dwfl_frame_state_data) diff --git a/libdwfl/dwfl_frame_state_pc.c b/libdwfl/dwfl_frame_state_pc.c new file mode 100644 index 0000000..6e9928b --- /dev/null +++ b/libdwfl/dwfl_frame_state_pc.c @@ -0,0 +1,56 @@ +/* Get return address register value for frame. + Copyright (C) 2012 Red Hat, Inc. + This file is part of elfutils. + + This file is free software; you can redistribute it and/or modify + it under the terms of either + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at + your option) any later version + + or + + * the GNU General Public License as published by the Free + Software Foundation; either version 2 of the License, or (at + your option) any later version + + or both in parallel, as here. + + elfutils is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received copies of the GNU General Public License and + the GNU Lesser General Public License along with this program. If + not, see http://www.gnu.org/licenses/. */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include "libdwflP.h" + +bool +dwfl_frame_state_pc (Dwfl_Frame_State *state, Dwarf_Addr *pc, bool *minusone) +{ + assert (state->pc_state == DWFL_FRAME_STATE_PC_SET); + *pc = state->pc; + if (minusone) + { + /* Bottom frame? */ + if (state == state->thread->unwound) + *minusone = false; + /* *MINUSONE is logical or of both current and previous frame state. */ + else if (state->signal_frame) + *minusone = false; + /* Not affected by unsuccessfully unwound frame. */ + else if (! INTUSE(dwfl_frame_unwind) (&state) || state == NULL) + *minusone = true; + else + *minusone = ! state->signal_frame; + } + return true; +} +INTDEF (dwfl_frame_state_pc) diff --git a/libdwfl/dwfl_frame_state_pid.c b/libdwfl/dwfl_frame_state_pid.c new file mode 100644 index 0000000..89fbee2 --- /dev/null +++ b/libdwfl/dwfl_frame_state_pid.c @@ -0,0 +1,220 @@ +/* Get Dwarf Frame state for target live PID process. + Copyright (C) 2012 Red Hat, Inc. + This file is part of elfutils. + + This file is free software; you can redistribute it and/or modify + it under the terms of either + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at + your option) any later version + + or + + * the GNU General Public License as published by the Free + Software Foundation; either version 2 of the License, or (at + your option) any later version + + or both in parallel, as here. + + elfutils is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received copies of the GNU General Public License and + the GNU Lesser General Public License along with this program. If + not, see http://www.gnu.org/licenses/. */ + +#include "libdwflP.h" +#include <sys/ptrace.h> +#include <sys/wait.h> +#include <dirent.h> + +static bool +tid_is_attached (Dwfl *dwfl, pid_t tid) +{ + for (Dwfl_Frame_State_Process *process = dwfl->framestatelist; process; + process = process->next) + for (Dwfl_Frame_State_Thread *thread = process->thread; thread; + thread = thread->next) + if (thread->tid_attached && thread->tid == tid) + return true; + return false; +} + +static bool +ptrace_attach (pid_t tid) +{ + if (ptrace (PTRACE_ATTACH, tid, NULL, NULL) != 0) + return false; + /* FIXME: Handle missing SIGSTOP on old Linux kernels. */ + for (;;) + { + int status; + if (waitpid (tid, &status, __WALL) != tid || !WIFSTOPPED (status)) + { + ptrace (PTRACE_DETACH, tid, NULL, NULL); + return false; + } + if (WSTOPSIG (status) == SIGSTOP) + break; + if (ptrace (PTRACE_CONT, tid, NULL, + (void *) (uintptr_t) WSTOPSIG (status)) != 0) + { + ptrace (PTRACE_DETACH, tid, NULL, NULL); + return false; + } + } + return true; +} + +static bool +dwfl_frame_state_pid_memory_read (Dwarf_Addr addr, Dwarf_Addr *result, + void *user_data) +{ + Dwfl_Frame_State_Process *process = user_data; + assert (process->core == NULL && process->thread->tid); + if (process->ebl->class == ELFCLASS64) + { + errno = 0; + *result = ptrace (PTRACE_PEEKDATA, process->thread->tid, + (void *) (uintptr_t) addr, NULL); + if (errno != 0) + { + __libdwfl_seterrno (DWFL_E_PROCESS_MEMORY_READ); + return false; + } + return true; + } +#if SIZEOF_LONG == 8 + /* We do not care about reads unaliged to 4 bytes boundary. + But 0x...ffc read of 8 bytes could overrun a page. */ + bool lowered = (addr & 4) != 0; + if (lowered) + addr -= 4; +#endif /* SIZEOF_LONG == 8 */ + errno = 0; + *result = ptrace (PTRACE_PEEKDATA, process->thread->tid, + (void *) (uintptr_t) addr, NULL); + if (errno != 0) + { + __libdwfl_seterrno (DWFL_E_PROCESS_MEMORY_READ); + return false; + } +#if SIZEOF_LONG == 8 +# if BYTE_ORDER == BIG_ENDIAN + if (! lowered) + *result >>= 32; +# else + if (lowered) + *result >>= 32; +# endif +#endif /* SIZEOF_LONG == 8 */ + *result &= 0xffffffff; + return true; +} + +Dwfl_Frame_State * +dwfl_frame_state_pid (Dwfl *dwfl, pid_t pid) +{ + char dirname[64]; + int i = snprintf (dirname, sizeof (dirname), "/proc/%ld/task", (long) pid); + assert (i > 0 && i < (ssize_t) sizeof (dirname) - 1); + Dwfl_Frame_State_Process *process; + process = __libdwfl_process_alloc (dwfl, dwfl_frame_state_pid_memory_read, + NULL); + if (process == NULL) + return NULL; + process->memory_read_user_data = process; + for (Dwfl_Module *mod = dwfl->modulelist; mod != NULL; mod = mod->next) + { + Dwfl_Error error = __libdwfl_module_getebl (mod); + if (error != DWFL_E_NOERROR) + continue; + process->ebl = mod->ebl; + } + if (process->ebl == NULL) + { + /* Not identified EBL from any of the modules. */ + __libdwfl_process_free (process); + __libdwfl_seterrno (DWFL_E_PROCESS_NO_ARCH); + return NULL; + } + DIR *dir = opendir (dirname); + if (dir == NULL) + { + __libdwfl_process_free (process); + __libdwfl_seterrno (DWFL_E_PARSE_PROC); + return NULL; + } + for (;;) + { + errno = 0; + struct dirent *dirent = readdir (dir); + if (dirent == NULL) + { + if (errno == 0) + break; + __libdwfl_process_free (process); + __libdwfl_seterrno (DWFL_E_PARSE_PROC); + return NULL; + } + if (strcmp (dirent->d_name, ".") == 0 + || strcmp (dirent->d_name, "..") == 0) + continue; + char *end; + errno = 0; + long tidl = strtol (dirent->d_name, &end, 10); + if (errno != 0) + { + __libdwfl_process_free (process); + __libdwfl_seterrno (DWFL_E_PARSE_PROC); + return NULL; + } + pid_t tid = tidl; + if (tidl <= 0 || (end && *end) || tid != tidl) + { + __libdwfl_process_free (process); + __libdwfl_seterrno (DWFL_E_PARSE_PROC); + return NULL; + } + Dwfl_Frame_State_Thread *thread = __libdwfl_thread_alloc (process, tid); + if (thread == NULL) + { + __libdwfl_process_free (process); + __libdwfl_seterrno (DWFL_E_NOMEM); + return NULL; + } + if (! tid_is_attached (dwfl, thread->tid)) + { + if (! ptrace_attach (thread->tid)) + { + __libdwfl_thread_free (thread); + continue; + } + thread->tid_attached = true; + } + Dwfl_Frame_State *state = thread->unwound; + if (! ebl_frame_state (state) || ! __libdwfl_state_fetch_pc (state)) + { + __libdwfl_thread_free (thread); + continue; + } + } + if (closedir (dir) != 0) + { + __libdwfl_process_free (process); + __libdwfl_seterrno (DWFL_E_PARSE_PROC); + return NULL; + } + if (process->thread == NULL) + { + /* No valid threads recognized. */ + __libdwfl_process_free (process); + __libdwfl_seterrno (DWFL_E_NO_THREAD); + return NULL; + } + return process->thread->unwound; +} +INTDEF (dwfl_frame_state_pid) diff --git a/libdwfl/dwfl_frame_thread_next.c b/libdwfl/dwfl_frame_thread_next.c new file mode 100644 index 0000000..df4aebb --- /dev/null +++ b/libdwfl/dwfl_frame_thread_next.c @@ -0,0 +1,37 @@ +/* Get innermost frame of the next thread from state. + Copyright (C) 2012 Red Hat, Inc. + This file is part of elfutils. + + This file is free software; you can redistribute it and/or modify + it under the terms of either + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at + your option) any later version + + or + + * the GNU General Public License as published by the Free + Software Foundation; either version 2 of the License, or (at + your option) any later version + + or both in parallel, as here. + + elfutils is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received copies of the GNU General Public License and + the GNU Lesser General Public License along with this program. If + not, see http://www.gnu.org/licenses/. */ + +#include "libdwflP.h" + +Dwfl_Frame_State * +dwfl_frame_thread_next (Dwfl_Frame_State *state) +{ + Dwfl_Frame_State_Thread *thread_next = state->thread->next; + return thread_next ? thread_next->unwound : NULL; +} +INTDEF (dwfl_frame_thread_next) diff --git a/libdwfl/dwfl_frame_tid_get.c b/libdwfl/dwfl_frame_tid_get.c new file mode 100644 index 0000000..7883b5e --- /dev/null +++ b/libdwfl/dwfl_frame_tid_get.c @@ -0,0 +1,36 @@ +/* Get Task ID of the thread of state. + Copyright (C) 2012 Red Hat, Inc. + This file is part of elfutils. + + This file is free software; you can redistribute it and/or modify + it under the terms of either + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at + your option) any later version + + or + + * the GNU General Public License as published by the Free + Software Foundation; either version 2 of the License, or (at + your option) any later version + + or both in parallel, as here. + + elfutils is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received copies of the GNU General Public License and + the GNU Lesser General Public License along with this program. If + not, see http://www.gnu.org/licenses/. */ + +#include "libdwflP.h" + +pid_t +dwfl_frame_tid_get (Dwfl_Frame_State *state) +{ + return state->thread->tid; +} +INTDEF (dwfl_frame_tid_get) diff --git a/libdwfl/dwfl_frame_unwind.c b/libdwfl/dwfl_frame_unwind.c new file mode 100644 index 0000000..5705f51 --- /dev/null +++ b/libdwfl/dwfl_frame_unwind.c @@ -0,0 +1,500 @@ +/* Get previous frame state for an existing frame state. + Copyright (C) 2012 Red Hat, Inc. + This file is part of elfutils. + + This file is free software; you can redistribute it and/or modify + it under the terms of either + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at + your option) any later version + + or + + * the GNU General Public License as published by the Free + Software Foundation; either version 2 of the License, or (at + your option) any later version + + or both in parallel, as here. + + elfutils is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received copies of the GNU General Public License and + the GNU Lesser General Public License along with this program. If + not, see http://www.gnu.org/licenses/. */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include "cfi.h" +#include <stdlib.h> +#include "libdwflP.h" +#include "../libdw/dwarf.h" +#include <sys/ptrace.h> + +#ifndef MAX +# define MAX(a, b) ((a) > (b) ? (a) : (b)) +#endif + +static bool +state_get_reg (Dwfl_Frame_State *state, unsigned regno, Dwarf_Addr *val) +{ + if (! dwfl_frame_state_reg_get (state, regno, val)) + { + __libdwfl_seterrno (DWFL_E_INVALID_REGISTER); + return false; + } + return true; +} + +static int +bra_compar (const void *key_voidp, const void *elem_voidp) +{ + Dwarf_Word offset = (uintptr_t) key_voidp; + const Dwarf_Op *op = elem_voidp; + return (offset > op->offset) - (offset < op->offset); +} + +/* FIXME: Handle bytecode deadlocks and overflows. */ + +static bool +expr_eval (Dwfl_Frame_State *state, Dwarf_Frame *frame, const Dwarf_Op *ops, + size_t nops, Dwarf_Addr *result) +{ + Dwfl_Frame_State_Process *process = state->thread->process; + if (nops == 0) + { + __libdwfl_seterrno (DWFL_E_INVALID_DWARF); + return false; + } + Dwarf_Addr *stack = NULL; + size_t stack_used = 0, stack_allocated = 0; + bool + push (Dwarf_Addr val) + { + if (stack_used == stack_allocated) + { + stack_allocated = MAX (stack_allocated * 2, 32); + Dwarf_Addr *stack_new = realloc (stack, stack_allocated * sizeof (*stack)); + if (stack_new == NULL) + { + __libdwfl_seterrno (DWFL_E_NOMEM); + return false; + } + stack = stack_new; + } + stack[stack_used++] = val; + return true; + } + bool + pop (Dwarf_Addr *val) + { + if (stack_used == 0) + { + __libdwfl_seterrno (DWFL_E_INVALID_DWARF); + return false; + } + *val = stack[--stack_used]; + return true; + } + Dwarf_Addr val1, val2; + bool is_location = false; + for (const Dwarf_Op *op = ops; op < ops + nops; op++) + switch (op->atom) + { + case DW_OP_reg0 ... DW_OP_reg31: + if (! state_get_reg (state, op->atom - DW_OP_reg0, &val1) + || ! push (val1)) + { + free (stack); + return false; + } + break; + case DW_OP_regx: + if (! state_get_reg (state, op->number, &val1) || ! push (val1)) + { + free (stack); + return false; + } + break; + case DW_OP_breg0 ... DW_OP_breg31: + if (! state_get_reg (state, op->atom - DW_OP_breg0, &val1)) + { + free (stack); + return false; + } + val1 += op->number; + if (! push (val1)) + { + free (stack); + return false; + } + break; + case DW_OP_bregx: + if (! state_get_reg (state, op->number, &val1)) + { + free (stack); + return false; + } + val1 += op->number2; + if (! push (val1)) + { + free (stack); + return false; + } + break; + case DW_OP_lit0 ... DW_OP_lit31: + if (! push (op->atom - DW_OP_lit0)) + { + free (stack); + return false; + } + break; + case DW_OP_plus_uconst: + if (! pop (&val1) || ! push (val1 + op->number)) + { + free (stack); + return false; + } + break; + case DW_OP_call_frame_cfa:; + Dwarf_Op *cfa_ops; + size_t cfa_nops; + Dwarf_Addr cfa; + if (dwarf_frame_cfa (frame, &cfa_ops, &cfa_nops) != 0 + || ! expr_eval (state, frame, cfa_ops, cfa_nops, &cfa) + || ! push (cfa)) + { + __libdwfl_seterrno (DWFL_E_LIBDW); + free (stack); + return false; + } + is_location = true; + break; + case DW_OP_stack_value: + is_location = false; + break; + case DW_OP_deref: + if (! pop (&val1) + || ! process->memory_read (val1, &val1, + process->memory_read_user_data) + || ! push (val1)) + { + free (stack); + return false; + } + break; + case DW_OP_nop: + break; + case DW_OP_dup: + if (! pop (&val1) || ! push (val1) || ! push (val1)) + { + free (stack); + return false; + } + break; + case DW_OP_const1u: + case DW_OP_const1s: + case DW_OP_const2u: + case DW_OP_const2s: + case DW_OP_const4u: + case DW_OP_const4s: + case DW_OP_const8u: + case DW_OP_const8s: + case DW_OP_constu: + case DW_OP_consts: + if (! push (op->number)) + { + free (stack); + return false; + } + break; + case DW_OP_bra: + if (! pop (&val1)) + { + free (stack); + return false; + } + if (val1 == 0) + break; + /* FALLTHRU */ + case DW_OP_skip:; + Dwarf_Word offset = op->offset + 1 + 2 + (int16_t) op->number; + const Dwarf_Op *found = bsearch ((void *) (uintptr_t) offset, ops, nops, + sizeof (*ops), bra_compar); + if (found == NULL) + { + free (stack); + /* PPC32 vDSO has such invalid operations. */ + __libdwfl_seterrno (DWFL_E_INVALID_DWARF); + return false; + } + /* Undo the 'for' statement increment. */ + op = found - 1; + break; + case DW_OP_drop: + if (! pop (&val1)) + { + free (stack); + return false; + } + break; +#define BINOP(atom, op) \ + case atom: \ + if (! pop (&val2) || ! pop (&val1) || ! push (val1 op val2)) \ + { \ + free (stack); \ + return false; \ + } \ + break; + BINOP (DW_OP_and, &) + BINOP (DW_OP_shl, <<) + BINOP (DW_OP_plus, +) + BINOP (DW_OP_mul, *) +#undef BINOP +#define BINOP_SIGNED(atom, op) \ + case atom: \ + if (! pop (&val2) || ! pop (&val1) \ + || ! push ((int64_t) val1 op (int64_t) val2)) \ + { \ + free (stack); \ + return false; \ + } \ + break; + BINOP_SIGNED (DW_OP_le, <=) + BINOP_SIGNED (DW_OP_ge, >=) + BINOP_SIGNED (DW_OP_eq, ==) + BINOP_SIGNED (DW_OP_lt, <) + BINOP_SIGNED (DW_OP_gt, >) + BINOP_SIGNED (DW_OP_ne, !=) +#undef BINOP_SIGNED + default: + __libdwfl_seterrno (DWFL_E_UNSUPPORTED_DWARF); + return false; + } + if (! pop (result)) + { + free (stack); + return false; + } + free (stack); + if (is_location && ! process->memory_read (*result, result, + process->memory_read_user_data)) + return false; + return true; +} + +/* Return TRUE and update *STATEP for the unwound frame for successful unwind. + Return TRUE and set *STATEP to NULL for the outermost frame. Return FALSE + (and call __libdwfl_seterrno) otherwise. */ + +static bool +have_unwound (Dwfl_Frame_State **statep) +{ + Dwfl_Frame_State *state = *statep, *unwound = state->unwound; + switch (unwound->pc_state) + { + case DWFL_FRAME_STATE_ERROR: + __libdwfl_seterrno (DWFL_E_INVALID_DWARF); + *statep = NULL; + return false; + case DWFL_FRAME_STATE_PC_SET: + *statep = unwound; + return true; + case DWFL_FRAME_STATE_PC_UNDEFINED: + *statep = NULL; + return true; + } + abort (); +} + +/* Check if PC is in the "_start" function which may have no FDE. + It corresponds to the GDB get_prev_frame logic "inside entry func". + Return TRUE if PC is in an outer frame. Return FALSE (and call + __libdwfl_seterrno) otherwise. */ + +static bool +no_fde (Dwarf_Addr pc, Dwfl_Module *mod, Dwarf_Addr bias) +{ + GElf_Sym sym; + const char *symname = INTUSE(dwfl_module_addrsym) (mod, pc, &sym, NULL); + if (symname == NULL) + { + __libdwfl_seterrno (DWFL_E_NO_DWARF); + return false; + } + /* It has no FDE on PPC64; it can be still unwound via the stack frame. */ + if (strcmp (symname, ".generic_start_main") == 0) + return true; + GElf_Ehdr ehdr_mem, *ehdr = gelf_getehdr (mod->main.elf, &ehdr_mem); + if (ehdr == NULL) + { + __libdwfl_seterrno (DWFL_E_LIBELF); + return false; + } + if (pc < ehdr->e_entry + bias) + { + __libdwfl_seterrno (DWFL_E_NO_DWARF); + return false; + } + /* "_start" is size-less. Search for PC, if the closest symbol is the one + for E_ENTRY it belongs into the function starting at E_ENTRY. */ + if (sym.st_value != ehdr->e_entry + bias + || (sym.st_size != 0 && pc >= sym.st_value + sym.st_size)) + { + __libdwfl_seterrno (DWFL_E_NO_DWARF); + return false; + } + return true; +} + +/* The logic is to call __libdwfl_seterrno for any CFI bytecode interpretation + error so one can easily catch the problem with a debugger. Still there are + archs with invalid CFI for some registers where the registers are never used + later. Therefore we continue unwinding leaving the registers undefined. + + The only exception is PC itself, when there is an error unwinding PC we + return false. Otherwise we would return successful end of backtrace seeing + an undefined PC register (due to an error unwinding it). */ + +static bool +handle_cfi (Dwfl_Frame_State **statep, Dwarf_Addr pc, Dwfl_Module *mod, + Dwarf_CFI *cfi, Dwarf_Addr bias) +{ + Dwfl_Frame_State *state = *statep; + Dwarf_Frame *frame; + if (INTUSE(dwarf_cfi_addrframe) (cfi, pc - bias, &frame) != 0) + { + int dw_errno = dwarf_errno (); + if (dw_errno == DWARF_E_NO_MATCH) + { + if (! no_fde (pc, mod, bias)) + return false; + *statep = NULL; + return true; + } + __libdw_seterrno (dw_errno); + __libdwfl_seterrno (DWFL_E_LIBDW); + return false; + } + Dwfl_Frame_State_Thread *thread = state->thread; + Dwfl_Frame_State_Process *process = thread->process; + Ebl *ebl = process->ebl; + size_t nregs = ebl_frame_state_nregs (ebl); + Dwfl_Frame_State *unwound; + unwound = malloc (sizeof (*unwound) + sizeof (*unwound->regs) * nregs); + state->unwound = unwound; + unwound->thread = thread; + unwound->unwound = NULL; + unwound->signal_frame = frame->fde->cie->signal_frame; + unwound->pc_state = DWFL_FRAME_STATE_ERROR; + memset (unwound->regs_set, 0, sizeof (unwound->regs_set)); + for (unsigned regno = 0; regno < nregs; regno++) + { + Dwarf_Op reg_ops_mem[3], *reg_ops; + size_t reg_nops; + if (dwarf_frame_register (frame, regno, reg_ops_mem, ®_ops, + ®_nops) != 0) + { + __libdwfl_seterrno (DWFL_E_LIBDW); + continue; + } + Dwarf_Addr regval; + if (reg_nops == 0) + { + if (reg_ops == reg_ops_mem) + { + /* REGNO is undefined. */ + unsigned ra = frame->fde->cie->return_address_register; + if (regno == ra) + unwound->pc_state = DWFL_FRAME_STATE_PC_UNDEFINED; + continue; + } + else if (reg_ops == NULL) + { + /* REGNO is same-value. */ + if (! state_get_reg (state, regno, ®val)) + continue; + } + else + { + __libdwfl_seterrno (DWFL_E_INVALID_DWARF); + continue; + } + } + else if (! expr_eval (state, frame, reg_ops, reg_nops, ®val)) + { + /* PPC32 vDSO has various invalid operations, ignore them. The + register will look as unset causing an error later, if used. + But PPC32 does not use such registers. */ + continue; + } + if (! dwfl_frame_state_reg_set (unwound, regno, regval)) + { + __libdwfl_seterrno (DWFL_E_INVALID_REGISTER); + continue; + } + } + if (unwound->pc_state == DWFL_FRAME_STATE_ERROR + && dwfl_frame_state_reg_get (unwound, + frame->fde->cie->return_address_register, + &unwound->pc)) + { + /* PPC32 __libc_start_main properly CFI-unwinds PC as zero. Currently + none of the archs supported for unwinding have zero as a valid PC. */ + if (unwound->pc == 0) + unwound->pc_state = DWFL_FRAME_STATE_PC_UNDEFINED; + else + unwound->pc_state = DWFL_FRAME_STATE_PC_SET; + } + return have_unwound (statep); +} + +bool +dwfl_frame_unwind (Dwfl_Frame_State **statep) +{ + Dwfl_Frame_State *state = *statep; + if (state->unwound) + return have_unwound (statep); + Dwarf_Addr pc; + bool ok = INTUSE(dwfl_frame_state_pc) (state, &pc, NULL); + assert (ok); + /* Do not ask for MINUSONE dwfl_frame_state_pc, it would try to unwind STATE + which would deadlock us. */ + if (state != state->thread->unwound && ! state->signal_frame) + pc--; + Dwfl_Module *mod = INTUSE(dwfl_addrmodule) (state->thread->process->dwfl, pc); + if (mod != NULL) + { + Dwarf_Addr bias; + Dwarf_CFI *cfi_eh = INTUSE(dwfl_module_eh_cfi) (mod, &bias); + if (cfi_eh) + { + if (handle_cfi (statep, pc, mod, cfi_eh, bias)) + return true; + if (state->unwound) + { + assert (state->unwound->pc_state == DWFL_FRAME_STATE_ERROR); + return false; + } + } + Dwarf_CFI *cfi_dwarf = INTUSE(dwfl_module_dwarf_cfi) (mod, &bias); + if (cfi_dwarf) + { + if (handle_cfi (statep, pc, mod, cfi_dwarf, bias) && state->unwound) + return true; + if (state->unwound) + { + assert (state->unwound->pc_state == DWFL_FRAME_STATE_ERROR); + return false; + } + } + } + __libdwfl_seterrno (DWFL_E_NO_DWARF); + return false; +} +INTDEF(dwfl_frame_unwind) diff --git a/libdwfl/libdwfl.h b/libdwfl/libdwfl.h index c2b85f4..dee3e2f 100644 --- a/libdwfl/libdwfl.h +++ b/libdwfl/libdwfl.h @@ -41,6 +41,10 @@ typedef struct Dwfl_Module Dwfl_Module; /* Handle describing a line record. */ typedef struct Dwfl_Line Dwfl_Line;
+/* This holds everything we know about the state of the frame at a particular + PC location described by an FDE. */ +typedef struct Dwfl_Frame_State Dwfl_Frame_State; + /* Callbacks. */ typedef struct { @@ -573,6 +577,46 @@ extern int dwfl_module_register_names (Dwfl_Module *mod, extern Dwarf_CFI *dwfl_module_dwarf_cfi (Dwfl_Module *mod, Dwarf_Addr *bias); extern Dwarf_CFI *dwfl_module_eh_cfi (Dwfl_Module *mod, Dwarf_Addr *bias);
+/* Get innermost frame of first thread of live process PID. Returns NULL on + failure. */ +extern Dwfl_Frame_State *dwfl_frame_state_pid (Dwfl *dwfl, pid_t pid); + +/* Get innermost frame of first thread of core file COREFILE. Returns NULL on + failure. */ +extern Dwfl_Frame_State *dwfl_frame_state_core (Dwfl *dwfl, + const char *corefile); + +/* Fetch inferior registers from a caller supplied storage. */ +typedef bool dwfl_frame_memory_read_t (Dwarf_Addr addr, Dwarf_Addr *result, + void *user_data); +extern Dwfl_Frame_State *dwfl_frame_state_data (Dwfl *dwfl, bool pc_set, + Dwarf_Addr pc, unsigned nregs, + const uint64_t *regs_set, + const Dwarf_Addr *regs, + dwfl_frame_memory_read_t + *memory_read, + void *memory_read_user_data); + +/* Return TRUE and update *STATEP for the unwound frame for successful unwind. + Return TRUE and set *STATEP to NULL for the outermost frame. Return FALSE + (and call __libdwfl_seterrno) otherwise. */ +extern bool dwfl_frame_unwind (Dwfl_Frame_State **statep); + +/* Get return address register value for frame. Return TRUE if *PC set and + optionally *MINUSONE is also set, if MINUSONE is not NULL. Return FALSE + (and call __libdw_seterrno) otherwise. *MINUSONE is TRUE for normal calls + where *PC should be decremented by one to get the call instruction, it is + FALSE if this frame was interrupted by a signal handler. */ +extern bool dwfl_frame_state_pc (Dwfl_Frame_State *state, Dwarf_Addr *pc, + bool *minusone); + +/* Get innermost frame of the next thread from STATE. STATE can be any frame + of (the previous) thread. */ +extern Dwfl_Frame_State *dwfl_frame_thread_next (Dwfl_Frame_State *state); + +/* Get Task ID of the thread of STATE. This is PID for the thread started by + function main and gettid () for the other threads. */ +extern pid_t dwfl_frame_tid_get (Dwfl_Frame_State *state);
#ifdef __cplusplus } diff --git a/libdwfl/libdwflP.h b/libdwfl/libdwflP.h index e5e638c..3999d66 100644 --- a/libdwfl/libdwflP.h +++ b/libdwfl/libdwflP.h @@ -41,6 +41,10 @@ #include <string.h>
#include "../libdw/libdwP.h" /* We need its INTDECLs. */ +#include "libeblP.h" + +typedef struct Dwfl_Frame_State_Process Dwfl_Frame_State_Process; +typedef struct Dwfl_Frame_State_Thread Dwfl_Frame_State_Thread;
/* gettext helper macros. */ #define _(Str) dgettext ("elfutils", Str) @@ -74,7 +78,16 @@ DWFL_ERROR (BADELF, N_("not a valid ELF file")) \ DWFL_ERROR (WEIRD_TYPE, N_("cannot handle DWARF type description")) \ DWFL_ERROR (WRONG_ID_ELF, N_("ELF file does not match build ID")) \ - DWFL_ERROR (BAD_PRELINK, N_("corrupt .gnu.prelink_undo section data")) + DWFL_ERROR (BAD_PRELINK, N_("corrupt .gnu.prelink_undo section data")) \ + DWFL_ERROR (LIBEBL_BAD, N_("Internal error due to ebl")) \ + DWFL_ERROR (CORE_MISSING, N_("Missing data in core file")) \ + DWFL_ERROR (INVALID_REGISTER, N_("Invalid register")) \ + DWFL_ERROR (PROCESS_MEMORY_READ, N_("Error reading process memory")) \ + DWFL_ERROR (PROCESS_NO_ARCH, N_("Have not found ELF module in a process")) \ + DWFL_ERROR (PARSE_PROC, N_("Error parsing /proc filesystem")) \ + DWFL_ERROR (NO_THREAD, N_("No thread found")) \ + DWFL_ERROR (INVALID_DWARF, N_("Invalid DWARF")) \ + DWFL_ERROR (UNSUPPORTED_DWARF, N_("Unsupported DWARF"))
#define DWFL_ERROR(name, text) DWFL_E_##name, typedef enum { DWFL_ERRORS DWFL_E_NUM } Dwfl_Error; @@ -92,6 +105,8 @@ struct Dwfl
Dwfl_Module *modulelist; /* List in order used by full traversals. */
+ Dwfl_Frame_State_Process *framestatelist; + GElf_Addr offline_next_address;
GElf_Addr segment_align; /* Smallest granularity of segments. */ @@ -182,7 +197,102 @@ struct Dwfl_Module bool gc; /* Mark/sweep flag. */ };
+/* This holds information common for all the threads/tasks/TIDs of one process + for backtraces. */ + +struct Dwfl_Frame_State_Process +{ + Dwfl_Frame_State_Process *next; + struct Dwfl *dwfl; + struct ebl *ebl; + /* If it is false we share EBL with one of DWFL's Dwfl_Module->ebl. */ + bool ebl_close : 1; + dwfl_frame_memory_read_t *memory_read; + void *memory_read_user_data; + /* If there is no core file both CORE is NULL and CORE_FD is -1. */ + Elf *core; + int core_fd; + Dwfl_Frame_State_Thread *thread; +}; + +/* This holds information common for all the frames of one backtrace for + a partical thread/task/TID. */ + +struct Dwfl_Frame_State_Thread +{ + Dwfl_Frame_State_Process *process; + Dwfl_Frame_State_Thread *next; + /* If there is no TID it is 0. */ + pid_t tid; + bool tid_attached : 1; + /* Bottom frame. */ + Dwfl_Frame_State *unwound; +}; + +/* See its typedef in libdwfl.h. */ + +struct Dwfl_Frame_State +{ + Dwfl_Frame_State_Thread *thread; + /* Previous (outer) frame. */ + Dwfl_Frame_State *unwound; + bool signal_frame : 1; + enum + { + /* This structure is still being initialized or there was an error + initializing it. */ + DWFL_FRAME_STATE_ERROR, + /* PC field is valid. */ + DWFL_FRAME_STATE_PC_SET, + /* PC field is undefined, this means the next (inner) frame was the + outermost frame. */ + DWFL_FRAME_STATE_PC_UNDEFINED + } pc_state; + /* Either initialized from appropriate REGS element or on some archs + initialized separately as the return address has no DWARF register. */ + Dwarf_Addr pc; + /* (1 << X) bitmask where 0 <= X < ebl_frame_state_nregs. */ + uint64_t regs_set[3]; + /* REGS array size is ebl_frame_state_nregs. */ + Dwarf_Addr regs[]; +}; + +/* Fetch value from Dwfl_Frame_State->regs indexed by DWARF REGNO. + No error code is set if the function returns FALSE. */ + +static inline bool +dwfl_frame_state_reg_get (Dwfl_Frame_State *state, unsigned regno, + Dwarf_Addr *val) +{ + Ebl *ebl = state->thread->process->ebl; + if (regno >= ebl->frame_state_nregs) + return false; + if ((state->regs_set[regno / sizeof (*state->regs_set) / 8] + & (1U << (regno % (sizeof (*state->regs_set) * 8)))) == 0) + return false; + if (val) + *val = state->regs[regno]; + return true; +}
+/* Store value to Dwfl_Frame_State->regs indexed by DWARF REGNO. + No error code is set if the function returns FALSE. */ + +static inline bool +dwfl_frame_state_reg_set (Dwfl_Frame_State *state, unsigned regno, + Dwarf_Addr val) +{ + Ebl *ebl = state->thread->process->ebl; + if (regno >= ebl->frame_state_nregs) + return false; + /* For example i386 user_regs_struct has signed fields. */ + if (ebl->class == ELFCLASS32) + val &= 0xffffffff; + state->regs_set[regno / sizeof (*state->regs_set) / 8] |= + (1U << (regno % (sizeof (*state->regs_set) * 8))); + state->regs[regno] = val; + return true; +}
/* Information cached about each CU in Dwfl_Module.dw. */ struct dwfl_cu @@ -365,6 +475,36 @@ extern Dwfl_Module *__libdwfl_report_offline (Dwfl *dwfl, const char *name, const char *)) internal_function;
+/* Set STATE->pc_set from STATE->regs according to the backend. Return true on + success, false on error. */ +extern bool __libdwfl_state_fetch_pc (Dwfl_Frame_State *state) + internal_function; + +/* Free and unlink THREAD from the internal lists. */ +extern void __libdwfl_thread_free (Dwfl_Frame_State_Thread *thread) + internal_function; + +/* Allocate new Dwfl_Frame_State_Thread for PID and link it to PROCESS. + Automatically create and link in also the first Dwfl_Frame_State. */ +extern Dwfl_Frame_State_Thread * + __libdwfl_thread_alloc (Dwfl_Frame_State_Process *process, pid_t tid) + internal_function; + +/* Free PROCESS. Unlink and free also any structures it references. */ +extern void __libdwfl_process_free (Dwfl_Frame_State_Process *process) + internal_function; + +/* Allocate new Dwfl_Frame_State_Process for DWFL with callback MEMORY_READ + (which is passed MEMORY_READ_USER_DATA). */ +extern Dwfl_Frame_State_Process * + __libdwfl_process_alloc (Dwfl *dwfl, dwfl_frame_memory_read_t *memory_read, + void *memory_read_user_data) + internal_function; + +/* Align segment START downwards or END upwards addresses according to DWFL. */ +extern GElf_Addr __libdwfl_segment_start (Dwfl *dwfl, GElf_Addr start); +extern GElf_Addr __libdwfl_segment_end (Dwfl *dwfl, GElf_Addr end); + /* Decompression wrappers: decompress whole file into memory. */ extern Dwfl_Error __libdw_gunzip (int fd, off64_t start_offset, void *mapped, size_t mapped_size, @@ -479,6 +619,10 @@ INTDECL (dwfl_offline_section_address) INTDECL (dwfl_module_relocate_address) INTDECL (dwfl_module_dwarf_cfi) INTDECL (dwfl_module_eh_cfi) +INTDECL (dwfl_frame_state_pid) +INTDECL (dwfl_frame_state_core) +INTDECL (dwfl_frame_unwind) +INTDECL (dwfl_frame_state_pc)
/* Leading arguments standard to callbacks passed a Dwfl_Module. */ #define MODCB_ARGS(mod) (mod), &(mod)->userdata, (mod)->name, (mod)->low_addr diff --git a/libdwfl/segment.c b/libdwfl/segment.c index 496b4fd..3a2f349 100644 --- a/libdwfl/segment.c +++ b/libdwfl/segment.c @@ -28,16 +28,18 @@
#include "libdwflP.h"
-static GElf_Addr -segment_start (Dwfl *dwfl, GElf_Addr start) +GElf_Addr +internal_function +__libdwfl_segment_start (Dwfl *dwfl, GElf_Addr start) { if (dwfl->segment_align > 1) start &= -dwfl->segment_align; return start; }
-static GElf_Addr -segment_end (Dwfl *dwfl, GElf_Addr end) +GElf_Addr +internal_function +__libdwfl_segment_end (Dwfl *dwfl, GElf_Addr end) { if (dwfl->segment_align > 1) end = (end + dwfl->segment_align - 1) & -dwfl->segment_align; @@ -156,8 +158,8 @@ reify_segments (Dwfl *dwfl) for (Dwfl_Module *mod = dwfl->modulelist; mod != NULL; mod = mod->next) if (! mod->gc) { - const GElf_Addr start = segment_start (dwfl, mod->low_addr); - const GElf_Addr end = segment_end (dwfl, mod->high_addr); + const GElf_Addr start = __libdwfl_segment_start (dwfl, mod->low_addr); + const GElf_Addr end = __libdwfl_segment_end (dwfl, mod->high_addr); bool resized = false;
int idx = lookup (dwfl, start, hint); @@ -296,8 +298,9 @@ dwfl_report_segment (Dwfl *dwfl, int ndx, const GElf_Phdr *phdr, GElf_Addr bias, dwfl->lookup_module = NULL; }
- GElf_Addr start = segment_start (dwfl, bias + phdr->p_vaddr); - GElf_Addr end = segment_end (dwfl, bias + phdr->p_vaddr + phdr->p_memsz); + GElf_Addr start = __libdwfl_segment_start (dwfl, bias + phdr->p_vaddr); + GElf_Addr end = __libdwfl_segment_end (dwfl, + bias + phdr->p_vaddr + phdr->p_memsz);
/* Coalesce into the last one if contiguous and matching. */ if (ndx != dwfl->lookup_tail_ndx diff --git a/libebl/Makefile.am b/libebl/Makefile.am index 1ab08e7..1e39689 100644 --- a/libebl/Makefile.am +++ b/libebl/Makefile.am @@ -29,7 +29,8 @@ ## include $(top_srcdir)/config/eu.am AM_CFLAGS += -fpic -INCLUDES += -I$(srcdir)/../libelf -I$(srcdir)/../libdw -I$(srcdir)/../libasm +INCLUDES += -I$(srcdir)/../libelf -I$(srcdir)/../libdw -I$(srcdir)/../libasm \ + -I$(srcdir)/../libdwfl VERSION = 1 LIBEBL_SUBDIR = @LIBEBL_SUBDIR@
@@ -54,7 +55,7 @@ gen_SOURCES = eblopenbackend.c eblclosebackend.c eblstrtab.c \ eblreginfo.c eblnonerelocp.c eblrelativerelocp.c \ eblsysvhashentrysize.c eblauxvinfo.c eblcheckobjattr.c \ ebl_check_special_section.c ebl_syscall_abi.c eblabicfi.c \ - eblstother.c eblgetfuncpc.c + eblstother.c eblgetfuncpc.c eblframestate.c
libebl_a_SOURCES = $(gen_SOURCES)
diff --git a/libebl/ebl-hooks.h b/libebl/ebl-hooks.h index b39131d..5b77f92 100644 --- a/libebl/ebl-hooks.h +++ b/libebl/ebl-hooks.h @@ -161,5 +161,12 @@ int EBLHOOK(abi_cfi) (Ebl *ebl, Dwarf_CIE *abi_info); const char *EBLHOOK(get_func_pc) (Ebl *ebl, struct Dwfl_Module *mod, GElf_Sym *sym);
+/* Fetch process data from STATE->base->pid or STATE->base->core. */ +bool EBLHOOK(frame_state) (struct Dwfl_Frame_State *state); + +/* Number of Dwarf_Frame_State->regs entries to allocate for frame_state + above. */ +size_t EBLHOOKVAR(frame_state_nregs); + /* Destructor for ELF backend handle. */ void EBLHOOK(destr) (struct ebl *); diff --git a/libebl/eblframestate.c b/libebl/eblframestate.c new file mode 100644 index 0000000..a7cc13c --- /dev/null +++ b/libebl/eblframestate.c @@ -0,0 +1,52 @@ +/* Fetch live process Dwfl_Frame_State from PID. + Copyright (C) 2012 Red Hat, Inc. + This file is part of elfutils. + + This file is free software; you can redistribute it and/or modify + it under the terms of either + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at + your option) any later version + + or + + * the GNU General Public License as published by the Free + Software Foundation; either version 2 of the License, or (at + your option) any later version + + or both in parallel, as here. + + elfutils is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received copies of the GNU General Public License and + the GNU Lesser General Public License along with this program. If + not, see http://www.gnu.org/licenses/. */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include <libeblP.h> +#include <assert.h> +#include "libdwflP.h" + +bool +ebl_frame_state (Dwfl_Frame_State *state) +{ + Dwfl_Frame_State_Process *process = state->thread->process; + Ebl *ebl = process->ebl; + /* Otherwise caller could not allocate STATE of proper size. If FRAME_STATE + is unsupported then FRAME_STATE_NREGS is zero. */ + assert (ebl->frame_state != NULL); + return ebl->frame_state (state); +} + +size_t +ebl_frame_state_nregs (Ebl *ebl) +{ + return ebl == NULL ? 0 : ebl->frame_state_nregs; +} diff --git a/libebl/libebl.h b/libebl/libebl.h index 6c50769..09b7bfb 100644 --- a/libebl/libebl.h +++ b/libebl/libebl.h @@ -384,6 +384,14 @@ extern const char *ebl_get_func_pc (Ebl *ebl, struct Dwfl_Module *mod, GElf_Sym *sym) __nonnull_attribute__ (1, 2, 3);
+/* Fetch process data from STATE->base->pid or STATE->base->core. */ +struct Dwfl_Frame_State; +extern bool ebl_frame_state (struct Dwfl_Frame_State *state) + __nonnull_attribute__ (1); + +/* Number of registers to allocate for STATE of ebl_frame_state. */ +extern size_t ebl_frame_state_nregs (Ebl *ebl) + __nonnull_attribute__ (1);
#ifdef __cplusplus } diff --git a/m4/biarch.m4 b/m4/biarch.m4 new file mode 100644 index 0000000..a15323e --- /dev/null +++ b/m4/biarch.m4 @@ -0,0 +1,45 @@ +AC_DEFUN([utrace_CC_m32], [dnl +AC_CACHE_CHECK([$CC option for 32-bit word size], utrace_cv_CC_m32, [dnl +save_CC="$CC" +utrace_cv_CC_m32=none +for ut_try in -m32 -m31; do + [CC=`echo "$save_CC" | sed 's/ -m[36][241]//'`" $ut_try"] + AC_COMPILE_IFELSE([AC_LANG_SOURCE([[int foo (void) { return 1; }]])], + [utrace_cv_CC_m32=$ut_try]) + test x$utrace_cv_CC_m32 = xnone || break +done +CC="$save_CC"])]) + +AC_DEFUN([utrace_HOST64], [AC_REQUIRE([utrace_CC_m32]) +AS_IF([test x$utrace_cv_CC_m32 != xnone], [dnl +AC_CACHE_CHECK([for 64-bit host], utrace_cv_host64, [dnl +AC_EGREP_CPP([@utrace_host64@], [#include <stdint.h> +#if (UINTPTR_MAX > 0xffffffffUL) +@utrace_host64@ +#endif], + utrace_cv_host64=yes, utrace_cv_host64=no)]) +AS_IF([test $utrace_cv_host64 = no], + [utrace_biarch=-m64 utrace_thisarch=$utrace_cv_CC_m32], + [utrace_biarch=$utrace_cv_CC_m32 utrace_thisarch=-m64]) + +biarch_CC=`echo "$CC" | sed "s/ *${utrace_thisarch}//"` +biarch_CC="$biarch_CC $utrace_biarch"])]) + +AC_DEFUN([utrace_BIARCH], [AC_REQUIRE([utrace_HOST64]) +utrace_biarch_forced=no +AC_ARG_WITH([biarch], + AC_HELP_STRING([--with-biarch], + [enable biarch tests despite build problems]), + [AS_IF([test "x$with_biarch" != xno], [utrace_biarch_forced=yes])]) +AS_IF([test $utrace_biarch_forced = yes], [dnl +utrace_cv_cc_biarch=yes +AC_MSG_NOTICE([enabling biarch tests regardless using $biarch_CC])], [dnl +AS_IF([test x$utrace_cv_CC_m32 != xnone], [dnl +AC_CACHE_CHECK([whether $biarch_CC makes executables we can run], + utrace_cv_cc_biarch, [dnl +save_CC="$CC" +CC="$biarch_CC" +AC_RUN_IFELSE([AC_LANG_PROGRAM([], [])], + utrace_cv_cc_biarch=yes, utrace_cv_cc_biarch=no) +CC="$save_CC"])], [utrace_cv_cc_biarch=no])]) +AM_CONDITIONAL(BIARCH, [test $utrace_cv_cc_biarch = yes])]) diff --git a/src/Makefile.am b/src/Makefile.am index cf4875e..2fa5009 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -37,7 +37,7 @@ native_ld = @native_ld@ base_cpu = @base_cpu@
bin_PROGRAMS = readelf nm size strip ld elflint findtextrel addr2line \ - elfcmp objdump ranlib strings ar unstrip + elfcmp objdump ranlib strings ar unstrip stack
ld_dsos = libld_elf_i386_pic.a @@ -115,6 +115,7 @@ ranlib_LDADD = libar.a $(libelf) $(libeu) $(libmudflap) strings_LDADD = $(libelf) $(libeu) $(libmudflap) ar_LDADD = libar.a $(libelf) $(libeu) $(libmudflap) unstrip_LDADD = $(libebl) $(libelf) $(libdw) $(libeu) $(libmudflap) -ldl +stack_LDADD = $(libebl) $(libelf) $(libdw) $(libeu) $(libmudflap) -ldl
ldlex.o: ldscript.c ldlex_no_Werror = yes diff --git a/src/stack.c b/src/stack.c new file mode 100644 index 0000000..dd7be04 --- /dev/null +++ b/src/stack.c @@ -0,0 +1,145 @@ +/* Unwinding of frames like gstack/pstack. + Copyright (C) 2012 Red Hat, Inc. + This file is part of elfutils. + + This file is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + elfutils is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see http://www.gnu.org/licenses/. */ + +#include <config.h> +#include <assert.h> +#include <argp.h> +#include <error.h> +#include <stdlib.h> +#include <inttypes.h> +#include <stdio.h> +#include <stdio_ext.h> +#include <locale.h> +#include ELFUTILS_HEADER(dwfl) + +/* libdwfl/argp-std.c */ +#define OPT_COREFILE 0x101 + +static void +report_pid (Dwfl *dwfl, pid_t pid) +{ + int result = dwfl_linux_proc_report (dwfl, pid); + if (result < 0) + error (2, 0, "dwfl_linux_proc_report: %s", dwfl_errmsg (-1)); + else if (result > 0) + error (2, result, "dwfl_linux_proc_report"); + + if (dwfl_report_end (dwfl, NULL, NULL) != 0) + error (2, 0, "dwfl_report_end: %s", dwfl_errmsg (-1)); +} + +static void +dump (Dwfl *dwfl, pid_t pid, const char *corefile) +{ + if (pid) + report_pid (dwfl, pid); + Dwfl_Frame_State *state; + if (pid) + state = dwfl_frame_state_pid (dwfl, pid); + else if (corefile) + state = dwfl_frame_state_core (dwfl, corefile); + else + abort (); + if (state == NULL) + error (2, 0, "dwfl_frame_state: %s", dwfl_errmsg (-1)); + do + { + Dwfl_Frame_State *thread = state; + pid_t tid = dwfl_frame_tid_get (thread); + printf ("TID %ld:\n", (long) tid); + unsigned frameno; + for (frameno = 0; state; frameno++) + { + Dwarf_Addr pc; + bool minusone; + if (! dwfl_frame_state_pc (state, &pc, &minusone)) + { + fprintf (stderr, "%s\n", dwfl_errmsg (-1)); + break; + } + Dwarf_Addr pc_adjusted = pc - (minusone ? 1 : 0); + + /* Get PC->SYMNAME. */ + Dwfl_Module *mod = dwfl_addrmodule (dwfl, pc_adjusted); + const char *symname = NULL; + if (mod) + symname = dwfl_module_addrname (mod, pc_adjusted); + + printf ("#%2u %#" PRIx64 "%4s\t%s\n", frameno, (uint64_t) pc, + minusone ? "- 1" : "", symname); + if (! dwfl_frame_unwind (&state)) + { + fprintf (stderr, "%s\n", dwfl_errmsg (-1)); + break; + } + } + state = dwfl_frame_thread_next (thread); + } + while (state); + + dwfl_end (dwfl); +} + +static argp_parser_t parse_opt_orig; +static pid_t pid; +static const char *corefile; + +static error_t +parse_opt (int key, char *arg, struct argp_state *state) +{ + switch (key) + { + case 'p': + pid = atoi (arg); + break; + case OPT_COREFILE: + corefile = arg; + break; + } + return parse_opt_orig (key, arg, state); +} + +int +main (int argc, char **argv) +{ + /* We use no threads here which can interfere with handling a stream. */ + __fsetlocking (stdin, FSETLOCKING_BYCALLER); + __fsetlocking (stdout, FSETLOCKING_BYCALLER); + __fsetlocking (stderr, FSETLOCKING_BYCALLER); + + /* Set locale. */ + (void) setlocale (LC_ALL, ""); + + struct argp argp = *dwfl_standard_argp (); + parse_opt_orig = argp.parser; + argp.parser = parse_opt; + int remaining; + Dwfl *dwfl = NULL; + argp_parse (&argp, argc, argv, 0, &remaining, &dwfl); + assert (dwfl != NULL); + assert (remaining == argc); + + if (pid && !corefile) + dump (dwfl, pid, NULL); + else if (corefile && !pid) + dump (dwfl, 0, corefile); + else + error (2, 0, "eu-stack [--debuginfo-path=<path>] {-p <process id>|" + "--core=<file> [--executable=<file>]|--help}"); + + return 0; +} diff --git a/tests/Makefile.am b/tests/Makefile.am index a1ed284..b72bcc4 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -51,10 +51,24 @@ check_PROGRAMS = arextract arsymtest newfile saridx scnnames sectiondump \ dwfl-bug-getmodules dwarf-getmacros addrcfi \ test-flag-nobits dwarf-getstring rerequest_tag \ alldts md5-sha1-test typeiter low_high_pc \ - test-elf_cntl_gelf_getshdr dwfl-report-elf-align + test-elf_cntl_gelf_getshdr dwfl-report-elf-align backtrace \ + backtrace-child backtrace-data asm_TESTS = asm-tst1 asm-tst2 asm-tst3 asm-tst4 asm-tst5 \ asm-tst6 asm-tst7 asm-tst8 asm-tst9
+BUILT_SOURCES = backtrace-child-biarch + +clean-local: + $(RM) backtrace-child-biarch + +# Substitute $(COMPILE). +backtrace-child-biarch: backtrace-child.c + $(CC_BIARCH) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \ + $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) $(backtrace_child_CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) $(backtrace_child_LDFLAGS) \ + -o $@ $< + TESTS = run-arextract.sh run-arsymtest.sh newfile test-nlist \ update1 update2 update3 update4 \ run-show-die-info.sh run-get-files.sh run-get-lines.sh \ @@ -85,7 +99,8 @@ TESTS = run-arextract.sh run-arsymtest.sh newfile test-nlist \ run-readelf-d.sh run-readelf-gdb_index.sh run-unstrip-n.sh \ run-low_high_pc.sh run-macro-test.sh run-elf_cntl_gelf_getshdr.sh \ run-test-archive64.sh run-readelf-vmcoreinfo.sh \ - run-readelf-mixed-corenote.sh run-dwfl-report-elf-align.sh + run-readelf-mixed-corenote.sh run-dwfl-report-elf-align.sh \ + run-backtrace.sh
if !STANDALONE check_PROGRAMS += msg_tst md5-sha1-test @@ -290,6 +305,10 @@ typeiter_LDADD = $(libdw) $(libelf) $(libmudflap) low_high_pc_LDADD = $(libdw) $(libelf) $(libmudflap) test_elf_cntl_gelf_getshdr_LDADD = $(libelf) $(libmudflap) dwfl_report_elf_align_LDADD = $(libdw) $(libmudflap) +backtrace_LDADD = $(libdw) $(libelf) $(libmudflap) +backtrace_child_CFLAGS = -fPIE +backtrace_child_LDFLAGS = -pie -pthread +backtrace_data_LDADD = $(libdw) $(libelf) $(libmudflap)
if GCOV check: check-am coverage diff --git a/tests/backtrace-child.c b/tests/backtrace-child.c new file mode 100644 index 0000000..68ea3a7 --- /dev/null +++ b/tests/backtrace-child.c @@ -0,0 +1,147 @@ +/* Test child for parent backtrace test. + Copyright (C) 2012 Red Hat, Inc. + This file is part of elfutils. + + This file is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + elfutils is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see http://www.gnu.org/licenses/. */ + +#include <config.h> +#include <assert.h> +#include <stdlib.h> +#include <signal.h> +#include <errno.h> +#include <sys/ptrace.h> +#include <string.h> +#include <pthread.h> +#include <stdio.h> +#include <unistd.h> + +static int ptraceme, gencore; + +/* Execution will arrive here from jmp by an artificial ptrace-spawn signal. */ + +static void +sigusr2 (int signo) +{ + assert (signo == SIGUSR2); + if (! gencore) + raise (SIGUSR1); + + /* Catch the .plt jump, it will come from this abort call. */ + abort (); +} + +static __attribute__ ((noinline, noclone)) void +dummy1 (void) +{ + asm volatile (""); +} + +#ifdef __x86_64__ +static __attribute__ ((noinline, noclone, used)) void +jmp (void) +{ + /* Not reached, signal will get ptrace-spawn to jump into sigusr2. */ + abort (); +} +#endif + +static __attribute__ ((noinline, noclone)) void +dummy2 (void) +{ + asm volatile (""); +} + +static __attribute__ ((noinline, noclone, noreturn)) void +stdarg (int f __attribute__ ((unused)), ...) +{ + sighandler_t sigusr2_orig = signal (SIGUSR2, sigusr2); + assert (sigusr2_orig == SIG_DFL); + errno = 0; + if (ptraceme) + { + long l = ptrace (PTRACE_TRACEME, 0, NULL, NULL); + assert_perror (errno); + assert (l == 0); + } +#ifdef __x86_64__ + if (! gencore) + { + /* Execution will get PC patched into function jmp. */ + raise (SIGUSR1); + } +#endif + sigusr2 (SIGUSR2); + abort (); +} + +static __attribute__ ((noinline, noclone)) void +dummy3 (void) +{ + asm volatile (""); +} + +static __attribute__ ((noinline, noclone)) void +backtracegen (void) +{ + stdarg (1); + /* Here should be no instruction after the stdarg call as it is noreturn + function. It must be stdarg so that it is a call and not jump (jump as + a tail-call). */ +} + +static __attribute__ ((noinline, noclone)) void +dummy4 (void) +{ + asm volatile (""); +} + +static void * +start (void *arg __attribute__ ((unused))) +{ + backtracegen (); + abort (); +} + +int +main (int argc __attribute__ ((unused)), char **argv) +{ + assert (*argv++); + ptraceme = (*argv && strcmp (*argv, "--ptraceme") == 0); + argv += ptraceme; + gencore = (*argv && strcmp (*argv, "--gencore") == 0); + argv += gencore; + assert (*argv && strcmp (*argv, "--run") == 0); + dummy1 (); + dummy2 (); + dummy3 (); + dummy4 (); + if (gencore) + printf ("%ld\n", (long) getpid ()); + errno = 0; + pthread_t thread; + int i = pthread_create (&thread, NULL, start, NULL); + assert_perror (errno); + assert (i == 0); + if (ptraceme) + { + long l = ptrace (PTRACE_TRACEME, 0, NULL, NULL); + assert_perror (errno); + assert (l == 0); + } + if (gencore) + pthread_join (thread, NULL); + else + raise (SIGUSR2); + abort (); +} diff --git a/tests/backtrace-data.c b/tests/backtrace-data.c new file mode 100644 index 0000000..bdaf99d --- /dev/null +++ b/tests/backtrace-data.c @@ -0,0 +1,255 @@ +/* Test program for unwinding of frames. + Copyright (C) 2012 Red Hat, Inc. + This file is part of elfutils. + + This file is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + elfutils is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see http://www.gnu.org/licenses/. */ + +#include <config.h> +#include <assert.h> +#include <inttypes.h> +#include <stdio.h> +#include <stdio_ext.h> +#include <locale.h> +#include <dirent.h> +#include <stdlib.h> +#include <errno.h> +#include <error.h> +#include <unistd.h> +#include <dwarf.h> +#include <sys/resource.h> +#include <sys/ptrace.h> +#include <signal.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/user.h> +#include <fcntl.h> +#include <string.h> +#include ELFUTILS_HEADER(dwfl) + +#ifndef __x86_64__ + +int +main (void) +{ + return 77; +} + +#else /* __x86_64__ */ + +static int +find_elf (Dwfl_Module *mod __attribute__ ((unused)), + void **userdata __attribute__ ((unused)), + const char *modname __attribute__ ((unused)), + Dwarf_Addr base __attribute__ ((unused)), + char **file_name __attribute__ ((unused)), + Elf **elfp __attribute__ ((unused))) +{ + /* Not used as modules are reported explicitly. */ + assert (0); +} + +static bool +memory_read (Dwarf_Addr addr, Dwarf_Addr *result, void *user_data) +{ + pid_t child = (uintptr_t) user_data; + + errno = 0; + long l = ptrace (PTRACE_PEEKDATA, child, (void *) (uintptr_t) addr, NULL); + assert_perror (errno); + *result = l; + + /* We could also return false for failed ptrace. */ + return true; +} + +/* Return filename and VMA address *BASEP where its mapping starts which + contains ADDR. */ + +static char * +maps_lookup (pid_t pid, Dwarf_Addr addr, GElf_Addr *basep) +{ + char *fname; + int i = asprintf (&fname, "/proc/%ld/maps", (long) pid); + assert_perror (errno); + assert (i > 0); + FILE *f = fopen (fname, "r"); + assert_perror (errno); + assert (f); + free (fname); + for (;;) + { + // 37e3c22000-37e3c23000 rw-p 00022000 00:11 49532 /lib64/ld-2.14.90.so */ + unsigned long start, end, offset; + i = fscanf (f, "%lx-%lx %*s %lx %*x:%*x %*x", &start, &end, &offset); + assert_perror (errno); + assert (i == 3); + char *filename = strdup (""); + assert (filename); + size_t filename_len = 0; + for (;;) + { + int c = fgetc (f); + assert (c != EOF); + if (c == '\n') + break; + if (c == ' ' && *filename == '\0') + continue; + filename = realloc (filename, filename_len + 2); + assert (filename); + filename[filename_len++] = c; + filename[filename_len] = '\0'; + } + if (start <= addr && addr < end) + { + i = fclose (f); + assert_perror (errno); + assert (i == 0); + + *basep = start - offset; + return filename; + } + free (filename); + } +} + +/* Add module containing ADDR to the DWFL address space. */ + +static void +report_module (Dwfl *dwfl, pid_t child, Dwarf_Addr addr) +{ + GElf_Addr base; + char *long_name = maps_lookup (child, addr, &base); + Dwfl_Module *mod = dwfl_report_elf_baseaddr (dwfl, long_name, long_name, -1, + base); + assert (mod); + free (long_name); + assert (dwfl_addrmodule (dwfl, addr) == mod); +} + +int +main (int argc __attribute__ ((unused)), char **argv __attribute__ ((unused))) +{ + /* We use no threads here which can interfere with handling a stream. */ + __fsetlocking (stdin, FSETLOCKING_BYCALLER); + __fsetlocking (stdout, FSETLOCKING_BYCALLER); + __fsetlocking (stderr, FSETLOCKING_BYCALLER); + + /* Set locale. */ + (void) setlocale (LC_ALL, ""); + + pid_t child = fork (); + switch (child) + { + case -1: + assert_perror (errno); + assert (0); + case 0:; + long l = ptrace (PTRACE_TRACEME, 0, NULL, NULL); + assert_perror (errno); + assert (l == 0); + raise (SIGUSR1); + assert (0); + default: + break; + } + + int status; + pid_t pid = waitpid (child, &status, 0); + assert_perror (errno); + assert (pid == child); + assert (WIFSTOPPED (status)); + assert (WSTOPSIG (status) == SIGUSR1); + + struct user_regs_struct user_regs; + long l = ptrace (PTRACE_GETREGS, child, NULL, &user_regs); + assert_perror (errno); + assert (l == 0); + + const unsigned nregs = 17; + const uint64_t regs_set = (1U << nregs) - 1; + Dwarf_Addr regs[17]; + regs[0] = user_regs.rax; + regs[1] = user_regs.rdx; + regs[2] = user_regs.rcx; + regs[3] = user_regs.rbx; + regs[4] = user_regs.rsi; + regs[5] = user_regs.rdi; + regs[6] = user_regs.rbp; + regs[7] = user_regs.rsp; + regs[8] = user_regs.r8; + regs[9] = user_regs.r9; + regs[10] = user_regs.r10; + regs[11] = user_regs.r11; + regs[12] = user_regs.r12; + regs[13] = user_regs.r13; + regs[14] = user_regs.r14; + regs[15] = user_regs.r15; + regs[16] = user_regs.rip; + + /* x86_64 has PC contained in its CFI subset of DWARF register set so + elfutils will figure out the real PC value from REGS. */ + const bool pc_set = false; + Dwarf_Addr pc = 0; + + void *memory_read_user_data = (void *) (uintptr_t) child; + + static char *debuginfo_path; + static const Dwfl_Callbacks offline_callbacks = + { + .find_debuginfo = dwfl_standard_find_debuginfo, + .debuginfo_path = &debuginfo_path, + .section_address = dwfl_offline_section_address, + .find_elf = find_elf, + }; + Dwfl *dwfl = dwfl_begin (&offline_callbacks); + assert (dwfl); + + report_module (dwfl, child, user_regs.rip); + + Dwfl_Frame_State *state; + state = dwfl_frame_state_data (dwfl, pc_set, pc, nregs, ®s_set, regs, + memory_read, memory_read_user_data); + assert (state != NULL); + + /* Multiple threads are not handled here. */ + do + { + bool minusone; + if (! dwfl_frame_state_pc (state, &pc, &minusone)) + error (1, 0, "dwfl_frame_state_pc: %s", dwfl_errmsg (-1)); + Dwarf_Addr pc_adjusted = pc - (minusone ? 1 : 0); + + printf ("%#" PRIx64 "\n", (uint64_t) pc); + + Dwfl_Module *mod = dwfl_addrmodule (dwfl, pc_adjusted); + if (mod == NULL) + report_module (dwfl, child, pc_adjusted); + + if (! dwfl_frame_unwind (&state)) + error (1, 0, "dwfl_frame_unwind: %s", dwfl_errmsg (-1)); + } + while (state); + + dwfl_end (dwfl); + kill (child, SIGKILL); + pid = waitpid (child, &status, 0); + assert_perror (errno); + assert (pid == child); + assert (WIFSIGNALED (status)); + assert (WTERMSIG (status) == SIGKILL); + + return EXIT_SUCCESS; +} + +#endif /* x86_64 */ diff --git a/tests/backtrace.c b/tests/backtrace.c new file mode 100644 index 0000000..a01ba35 --- /dev/null +++ b/tests/backtrace.c @@ -0,0 +1,499 @@ +/* Test program for unwinding of frames. + Copyright (C) 2012 Red Hat, Inc. + This file is part of elfutils. + + This file is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + elfutils is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see http://www.gnu.org/licenses/. */ + +#include <config.h> +#include <assert.h> +#include <inttypes.h> +#include <stdio.h> +#include <stdio_ext.h> +#include <locale.h> +#include <dirent.h> +#include <stdlib.h> +#include <errno.h> +#include <error.h> +#include <unistd.h> +#include <dwarf.h> +#include <sys/resource.h> +#include <sys/ptrace.h> +#include <signal.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/user.h> +#include <fcntl.h> +#include <string.h> +#include ELFUTILS_HEADER(dwfl) + +static void +report_pid (Dwfl *dwfl, pid_t pid) +{ + int result = dwfl_linux_proc_report (dwfl, pid); + if (result < 0) + error (2, 0, "dwfl_linux_proc_report: %s", dwfl_errmsg (-1)); + else if (result > 0) + error (2, result, "dwfl_linux_proc_report"); + + if (dwfl_report_end (dwfl, NULL, NULL) != 0) + error (2, 0, "dwfl_report_end: %s", dwfl_errmsg (-1)); +} + +static Dwfl * +dwfl_pid (pid_t pid) +{ + static char *debuginfo_path; + static const Dwfl_Callbacks proc_callbacks = + { + .find_debuginfo = dwfl_standard_find_debuginfo, + .debuginfo_path = &debuginfo_path, + + .find_elf = dwfl_linux_proc_find_elf, + }; + Dwfl *dwfl = dwfl_begin (&proc_callbacks); + if (dwfl == NULL) + error (2, 0, "dwfl_begin: %s", dwfl_errmsg (-1)); + report_pid (dwfl, pid); + return dwfl; +} + +static const char *executable; + +static int +find_elf (Dwfl_Module *mod, void **userdata, const char *modname, + Dwarf_Addr base, char **file_name, Elf **elfp) +{ + if (executable && modname != NULL + && (strcmp (modname, "[exe]") == 0 || strcmp (modname, "[pie]") == 0)) + { + char *executable_dup = strdup (executable); + if (executable_dup) + { + free (*file_name); + *file_name = executable_dup; + return -1; + } + } + return dwfl_build_id_find_elf (mod, userdata, modname, base, file_name, elfp); +} + +static Dwfl * +dwfl_offline (void) +{ + static char *debuginfo_path; + static const Dwfl_Callbacks offline_callbacks = + { + .find_debuginfo = dwfl_standard_find_debuginfo, + .debuginfo_path = &debuginfo_path, + + .section_address = dwfl_offline_section_address, + + /* We use this table for core files too. */ + .find_elf = find_elf, + }; + Dwfl *dwfl = dwfl_begin (&offline_callbacks); + if (dwfl == NULL) + error (2, 0, "dwfl_begin: %s", dwfl_errmsg (-1)); + return dwfl; +} + +static Dwfl * +dwfl_core (const char *corefile) +{ + Dwfl *dwfl = dwfl_offline (); + if (dwfl_core_filename_report (dwfl, NULL, corefile) == NULL) + error (2, 0, "dwfl_core_filename_report: %s", dwfl_errmsg (-1)); + if (dwfl_report_end (dwfl, NULL, NULL) != 0) + error (2, 0, "dwfl_report_end: %s", dwfl_errmsg (-1)); + return dwfl; +} + +static int +dump_modules (Dwfl_Module *mod, void **userdata __attribute__ ((unused)), + const char *name, Dwarf_Addr start, + void *arg __attribute__ ((unused))) +{ + Dwarf_Addr end; + dwfl_module_info (mod, NULL, NULL, &end, NULL, NULL, NULL, NULL); + printf ("%#" PRIx64 "\t%#" PRIx64 "\t%s\n", (uint64_t) start, (uint64_t) end, + name); + return DWARF_CB_OK; +} + +static void +dump (pid_t pid, const char *corefile, + void (*callback) (pid_t tid, unsigned frameno, Dwarf_Addr pc, + const char *symname, Dwfl *dwfl, void *data), + void *data) +{ + Dwfl *dwfl; + Dwfl_Frame_State *state; + if (pid && !corefile) + { + dwfl = dwfl_pid (pid); + state = dwfl_frame_state_pid (dwfl, pid); + } + else if (corefile && !pid) + { + dwfl = dwfl_core (corefile); + state = dwfl_frame_state_core (dwfl, corefile); + } + else + abort (); + if (state == NULL) + error (2, 0, "dwfl_frame_state: %s", dwfl_errmsg (-1)); + ptrdiff_t ptrdiff = dwfl_getmodules (dwfl, dump_modules, NULL, 0); + assert (ptrdiff == 0); + int err = 0; + do + { + Dwfl_Frame_State *thread = state; + pid_t tid = dwfl_frame_tid_get (thread); + printf ("TID %ld:\n", (long) tid); + unsigned frameno; + for (frameno = 0; state; frameno++) + { + Dwarf_Addr pc; + bool minusone; + if (! dwfl_frame_state_pc (state, &pc, &minusone)) + { + fprintf (stderr, "%s\n", dwfl_errmsg (-1)); + err = 1; + break; + } + Dwarf_Addr pc_adjusted = pc - (minusone ? 1 : 0); + + /* Get PC->SYMNAME. */ + Dwfl_Module *mod = dwfl_addrmodule (dwfl, pc_adjusted); + const char *symname = NULL; + if (mod) + symname = dwfl_module_addrname (mod, pc_adjusted); + + printf ("#%2u %#" PRIx64 "%4s\t%s\n", frameno, (uint64_t) pc, + minusone ? "- 1" : "", symname); + if (callback) + callback (tid, frameno, pc, symname, dwfl, data); + if (! dwfl_frame_unwind (&state)) + { + fprintf (stderr, "%s\n", dwfl_errmsg (-1)); + err = 1; + break; + } + } + state = dwfl_frame_thread_next (thread); + } + while (state); + if (callback) + callback (0, 0, 0, NULL, dwfl, data); + dwfl_end (dwfl); + if (err) + exit (EXIT_FAILURE); +} + +struct see_exec_module +{ + Dwfl_Module *mod; + char selfpath[PATH_MAX + 1]; +}; + +static int +see_exec_module (Dwfl_Module *mod, void **userdata __attribute__ ((unused)), + const char *name __attribute__ ((unused)), + Dwarf_Addr start __attribute__ ((unused)), void *arg) +{ + struct see_exec_module *data = arg; + if (strcmp (name, data->selfpath) != 0) + return DWARF_CB_OK; + assert (data->mod == NULL); + data->mod = mod; + return DWARF_CB_OK; +} + +static void +selfdump_callback (pid_t tid, unsigned frameno, Dwarf_Addr pc, + const char *symname, Dwfl *dwfl, void *data) +{ + pid_t check_tid = (intptr_t) data; + bool disable = check_tid < 0; + if (disable) + check_tid = -check_tid; + static bool seen_main = false; + if (symname && strcmp (symname, "main") == 0) + seen_main = true; + if (pc == 0) + { + assert (seen_main); + return; + } + if (disable || tid != check_tid) + return; + Dwfl_Module *mod; + const char *symname2 = NULL; + switch (frameno) + { + case 0: + /* .plt has no symbols. */ + assert (symname == NULL); + break; + case 1: + assert (symname != NULL && strcmp (symname, "sigusr2") == 0); + break; + case 2: + /* __restore_rt - glibc maybe does not have to have this symbol. */ + break; + case 3: + /* Verify we trapped on the very first instruction of jmp. */ + assert (symname != NULL && strcmp (symname, "jmp") == 0); + mod = dwfl_addrmodule (dwfl, pc - 1); + if (mod) + symname2 = dwfl_module_addrname (mod, pc - 1); + assert (symname2 == NULL || strcmp (symname2, "jmp") != 0); + break; + case 4: + assert (symname != NULL && strcmp (symname, "stdarg") == 0); + break; + case 5: + /* Verify we trapped on the very last instruction of child. */ + assert (symname != NULL && strcmp (symname, "backtracegen") == 0); + mod = dwfl_addrmodule (dwfl, pc); + if (mod) + symname2 = dwfl_module_addrname (mod, pc); + assert (symname2 == NULL || strcmp (symname2, "backtracegen") != 0); + break; + } +} + +#ifdef __x86_64__ +static void +prepare_thread (pid_t pid2, Dwarf_Addr plt_start, Dwarf_Addr plt_end, + void (*jmp) (void)) +{ + long l; + errno = 0; + l = ptrace (PTRACE_POKEUSER, pid2, + (void *) (intptr_t) offsetof (struct user_regs_struct, rip), jmp); + assert_perror (errno); + assert (l == 0); + l = ptrace (PTRACE_CONT, pid2, NULL, (void *) (intptr_t) SIGUSR2); + int status; + pid_t got = waitpid (pid2, &status, __WALL); + assert_perror (errno); + assert (got == pid2); + assert (WIFSTOPPED (status)); + assert (WSTOPSIG (status) == SIGUSR1); + for (;;) + { + errno = 0; + l = ptrace (PTRACE_PEEKUSER, pid2, + (void *) (intptr_t) offsetof (struct user_regs_struct, rip), + NULL); + assert_perror (errno); + if ((unsigned long) l >= plt_start && (unsigned long) l < plt_end) + break; + l = ptrace (PTRACE_SINGLESTEP, pid2, NULL, NULL); + assert_perror (errno); + assert (l == 0); + got = waitpid (pid2, &status, __WALL); + assert_perror (errno); + assert (got == pid2); + assert (WIFSTOPPED (status)); + assert (WSTOPSIG (status) == SIGTRAP); + } +} +#endif /* __x86_64__ */ + +#include <asm/unistd.h> +#include <unistd.h> +#define tgkill(pid, tid, sig) syscall (__NR_tgkill, (pid), (tid), (sig)) + +static void +ptrace_detach_stopped (pid_t pid) +{ + errno = 0; + long l = ptrace (PTRACE_DETACH, pid, NULL, (void *) (intptr_t) SIGSTOP); + assert_perror (errno); + assert (l == 0); +} + +static void +selfdump (const char *exec) +{ + pid_t pid = fork (); + switch (pid) + { + case -1: + abort (); + case 0: + execl (exec, exec, "--ptraceme", "--run", NULL); + abort (); + default: + break; + } + + /* Catch the main thread. Catch it first otherwise the /proc evaluation of + PID may have caught still ourselves before executing execl above. */ + errno = 0; + int status; + pid_t got = waitpid (pid, &status, 0); + assert_perror (errno); + assert (got == pid); + assert (WIFSTOPPED (status)); + assert (WSTOPSIG (status) == SIGUSR2); + + /* Catch the spawned thread. Do not use __WCLONE as we could get racy + __WCLONE, probably despite pthread_create already had to be called the new + task is not yet alive enough for waitpid. */ + pid_t pid2 = waitpid (-1, &status, __WALL); + assert_perror (errno); + assert (pid2 > 0); + assert (pid2 != pid); + assert (WIFSTOPPED (status)); + assert (WSTOPSIG (status) == SIGUSR1); + + Dwfl *dwfl = dwfl_pid (pid); + char *selfpathname; + int i = asprintf (&selfpathname, "/proc/%ld/exe", (long) pid); + assert (i > 0); + struct see_exec_module data; + ssize_t ssize = readlink (selfpathname, data.selfpath, + sizeof (data.selfpath)); + free (selfpathname); + assert (ssize > 0 && ssize < (ssize_t) sizeof (data.selfpath)); + data.selfpath[ssize] = '\0'; + data.mod = NULL; + ptrdiff_t ptrdiff = dwfl_getmodules (dwfl, see_exec_module, &data, 0); + assert (ptrdiff == 0); + assert (data.mod != NULL); + GElf_Addr loadbase; + Elf *elf = dwfl_module_getelf (data.mod, &loadbase); + GElf_Ehdr ehdr_mem, *ehdr = gelf_getehdr (elf, &ehdr_mem); + assert (ehdr != NULL); + Elf_Scn *scn = NULL, *plt = NULL; + while ((scn = elf_nextscn (elf, scn)) != NULL) + { + GElf_Shdr scn_shdr_mem, *scn_shdr = gelf_getshdr (scn, &scn_shdr_mem); + assert (scn_shdr != NULL); + if (strcmp (elf_strptr (elf, ehdr->e_shstrndx, scn_shdr->sh_name), + ".plt") != 0) + continue; + assert (plt == NULL); + plt = scn; + } + assert (plt != NULL); + GElf_Shdr scn_shdr_mem, *scn_shdr = gelf_getshdr (plt, &scn_shdr_mem); + assert (scn_shdr != NULL); + /* Make it true on x86_64 with i386 inferior. */ + int disable = ehdr->e_ident[EI_CLASS] == ELFCLASS32; +#ifdef __x86_64__ + Dwarf_Addr plt_start = scn_shdr->sh_addr + loadbase; + Dwarf_Addr plt_end = plt_start + scn_shdr->sh_size; + void (*jmp) (void); + if (! disable) + { + int nsym = dwfl_module_getsymtab (data.mod); + int symi; + for (symi = 1; symi < nsym; ++symi) + { + GElf_Sym symbol; + const char *symbol_name = dwfl_module_getsym (data.mod, symi, &symbol, NULL); + if (symbol_name == NULL) + continue; + switch (GELF_ST_TYPE (symbol.st_info)) + { + case STT_SECTION: + case STT_FILE: + case STT_TLS: + continue; + default: + if (strcmp (symbol_name, "jmp") != 0) + continue; + break; + } + /* LOADBASE is already applied here. */ + jmp = (void (*) (void)) (uintptr_t) symbol.st_value; + break; + } + assert (symi < nsym); + prepare_thread (pid2, plt_start, plt_end, jmp); + } +#endif + dwfl_end (dwfl); + ptrace_detach_stopped (pid); + ptrace_detach_stopped (pid2); + dump (pid, NULL, selfdump_callback, + (void *) (intptr_t) (disable ? -pid2 : pid2)); +} + +static bool +is_core (const char *corefile) +{ + Dwfl *dwfl = dwfl_offline (); + Dwfl_Module *mod = dwfl_report_elf (dwfl, "core", corefile, -1, 0 /* base */); + assert (mod != NULL); + GElf_Addr loadbase_ignore; + Elf *core = dwfl_module_getelf (mod, &loadbase_ignore); + assert (core != NULL); + GElf_Ehdr ehdr_mem, *ehdr = gelf_getehdr (core, &ehdr_mem); + assert (ehdr != NULL); + assert (ehdr->e_type == ET_CORE || ehdr->e_type == ET_EXEC + || ehdr->e_type == ET_DYN); + bool retval = ehdr->e_type == ET_CORE; + dwfl_end (dwfl); + return retval; +} + +int +main (int argc __attribute__ ((unused)), char **argv) +{ + /* We use no threads here which can interfere with handling a stream. */ + __fsetlocking (stdin, FSETLOCKING_BYCALLER); + __fsetlocking (stdout, FSETLOCKING_BYCALLER); + __fsetlocking (stderr, FSETLOCKING_BYCALLER); + + /* Set locale. */ + (void) setlocale (LC_ALL, ""); + + if (argc == 1) + { + selfdump ("./backtrace-child"); + return 0; + } + argv++; + if (argc == 2) + { + if (strcmp (*argv, "--help") == 0) + error (2, 0, "backtrace {{no args for ./backtrace-child}|<pid>|<core>|" + "<executable>|<executable core>}"); + char *end; + long l = strtol (*argv, &end, 10); + if (**argv && !*end) + dump (l, NULL, NULL, NULL); + else if (is_core (*argv)) + dump (0, *argv, NULL, NULL); + else + selfdump (*argv); + return 0; + } + if (argc == 3) + { + assert (! is_core (argv[0])); + assert (is_core (argv[1])); + executable = argv[0]; + dump (0, argv[1], NULL, NULL); + return 0; + } + assert (0); + + return 0; +} diff --git a/tests/run-backtrace.sh b/tests/run-backtrace.sh new file mode 100755 index 0000000..6e14653 --- /dev/null +++ b/tests/run-backtrace.sh @@ -0,0 +1,56 @@ +#! /bin/sh +# Copyright (C) 2012 Red Hat, Inc. +# This file is part of elfutils. +# +# This file is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# elfutils is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see http://www.gnu.org/licenses/. + +. $srcdir/test-subr.sh + +if [ -z "$VERBOSE" ]; then + exec >/dev/null +fi + +mytestrun() +{ + echo "$*" + testrun "$@" +} + +check_gsignal() +{ + # Without proper ELF symbols resolution we could get inappropriate weak + # symbol "gsignal" with the same address as the correct symbol "raise". + if grep -w gsignal $1; then + false + fi +} + +check_empty() +{ + if test -s $1; then + false + fi +} + +for child in backtrace-child{,-biarch}; do + mytestrun ./backtrace ./$child + core="core.`ulimit -c unlimited; set +e; ./$child --gencore --run; true`" + tempfiles $core{,.bt,.err} + mytestrun ./backtrace ./$child ./$core 1>$core.bt 2>$core.err + cat $core.{bt,err} + check_gsignal $core.bt + check_empty $core.err +done + +exit 0
Hi Mark,
made a new branch: jankratochvil/unwindx86
It is a single commit with all its pre-requisites already checked in.
Thanks, Jan
commit b5f6f0ca766ccd33702ad810534aedab03c360bf Author: Jan Kratochvil jan.kratochvil@redhat.com Date: Thu May 30 14:37:38 2013 +0200
Unwinder for x86*.
backends/ 2012-11-13 Jan Kratochvil jan.kratochvil@redhat.com
* Makefile.am (AM_CPPFLAGS): Add ../libdwfl. (i386_SRCS): Add i386_frame_state.c. (x86_64_SRCS): Add x86_64_frame_state.c. * i386_frame_state.c: New file. * i386_init.c (i386_init): Initialize frame_state_nregs and frame_state. * x86_64_frame_state.c: New file. * x86_64_init.c (x86_64_init): Initialize frame_state_nregs and frame_state.
./ 2012-11-13 Jan Kratochvil jan.kratochvil@redhat.com
* configure.ac: New AC_CHECK_SIZEOF for long. Call utrace_BIARCH, new AC_SUBST for CC_BIARCH.
libdw/ 2012-11-13 Jan Kratochvil jan.kratochvil@redhat.com
* cfi.h (struct Dwarf_Frame_s): Make the comment more specific. * libdw.map (ELFUTILS_0.156): Add dwfl_report_elf_baseaddr, dwfl_frame_state_pid, dwfl_frame_state_core, dwfl_frame_state_data, dwfl_frame_unwind, dwfl_frame_state_pc, dwfl_frame_tid_get and dwfl_frame_thread_next.
libdwfl/ 2012-11-13 Jan Kratochvil jan.kratochvil@redhat.com
* Makefile.am (AM_CPPFLAGS): Add ../libasm. (libdwfl_a_SOURCES): Add dwfl_frame_state.c, dwfl_frame_unwind.c, dwfl_frame_state_pc.c, dwfl_frame_state_pid.c, dwfl_frame_state_core.c, dwfl_frame_state_data.c, dwfl_frame_thread_next.c and dwfl_frame_tid_get.c. * dwfl_end.c (dwfl_end): Call __libdwfl_process_free for dwfl->framestatelist. * dwfl_frame_state.c: New file. * dwfl_frame_state_core.c: New file. * dwfl_frame_state_data.c: New file. * dwfl_frame_state_pc.c: New file. * dwfl_frame_state_pid.c: New file. * dwfl_frame_thread_next.c: New file. * dwfl_frame_tid_get.c: New file. * dwfl_frame_unwind.c: New file. * libdwfl.h (Dwfl_Frame_State): New typedef. (dwfl_frame_state_pid, dwfl_frame_state_core, dwfl_frame_memory_read_t) (dwfl_frame_state_data, dwfl_frame_unwind, dwfl_frame_state_pc) (dwfl_frame_thread_next, dwfl_frame_tid_get): New declaration. * libdwflP.h: Include libeblP.h. (Dwfl_Frame_State_Process, Dwfl_Frame_State_Thread): New typedefs. (LIBEBL_BAD, CORE_MISSING, INVALID_REGISTER, PROCESS_MEMORY_READ) (PROCESS_NO_ARCH, PARSE_PROC, NO_THREAD, INVALID_DWARF) (UNSUPPORTED_DWARF): New DWFL_ERROR entries. (struct Dwfl): New entry framestatelist. (struct Dwfl_Frame_State_Process, struct Dwfl_Frame_State_Thread) (struct Dwfl_Frame_State, dwfl_frame_state_reg_get) (dwfl_frame_state_reg_set): New. (__libdwfl_state_fetch_pc, __libdwfl_thread_free) (__libdwfl_thread_alloc, __libdwfl_process_free) (__libdwfl_process_alloc, __libdwfl_segment_start) (__libdwfl_segment_end): New declarations. (dwfl_frame_state_pid, dwfl_frame_state_core, dwfl_frame_unwind) (dwfl_frame_state_pc): New INTDECL entries. * segment.c (segment_start): Rename to ... (__libdwfl_segment_start): ... here and make it internal_function. (segment_end): Rename to ... (__libdwfl_segment_end): ... here and make it internal_function. (reify_segments, dwfl_report_segment): Rename them at the callers.
libebl/ 2012-11-13 Jan Kratochvil jan.kratochvil@redhat.com
* Makefile.am (AM_CPPFLAGS): Add ../libdwfl. (gen_SOURCES): Add eblframestate.c. * ebl-hooks.h (frame_state): New entry. * eblframestate.c: New file. * libebl.h (struct Dwfl_Frame_State, ebl_frame_state) (ebl_frame_state_nregs): New declarations. * libeblP.h (frame_state_nregs): New entry.
m4/ 2012-11-13 Jan Kratochvil jan.kratochvil@redhat.com
* biarch.m4: New file.
src/ 2012-11-13 Jan Kratochvil jan.kratochvil@redhat.com
* Makefile.am (bin_PROGRAMS): Add stack. (stack_LDADD): New. * stack.c: New file.
tests/ 2012-11-13 Jan Kratochvil jan.kratochvil@redhat.com
* Makefile.am (check_PROGRAMS): Add backtrace, backtrace-child and backtrace-data. (BUILT_SOURCES, clean-local, backtrace-child-biarch): New. (TESTS): Add run-backtrace.sh. (backtrace_LDADD, backtrace_child_CFLAGS, backtrace_child_LDFLAGS) (backtrace_data_LDADD): New. * backtrace-child.c: New file. * backtrace-data.c: New file. * backtrace.c: New file. * run-backtrace.sh: New file.
Signed-off-by: Jan Kratochvil jan.kratochvil@redhat.com
diff --git a/backends/Makefile.am b/backends/Makefile.am index 1923702..80c9d92 100644 --- a/backends/Makefile.am +++ b/backends/Makefile.am @@ -29,7 +29,8 @@ ## not, see http://www.gnu.org/licenses/. include $(top_srcdir)/config/eu.am AM_CPPFLAGS += -I$(top_srcdir)/libebl -I$(top_srcdir)/libasm \ - -I$(top_srcdir)/libelf -I$(top_srcdir)/libdw + -I$(top_srcdir)/libelf -I$(top_srcdir)/libdw \ + -I$(top_srcdir)/libdwfl
modules = i386 sh x86_64 ia64 alpha arm sparc ppc ppc64 s390 tilegx @@ -50,7 +51,8 @@ libdw = ../libdw/libdw.so endif
i386_SRCS = i386_init.c i386_symbol.c i386_corenote.c i386_cfi.c \ - i386_retval.c i386_regs.c i386_auxv.c i386_syscall.c + i386_retval.c i386_regs.c i386_auxv.c i386_syscall.c \ + i386_frame_state.c cpu_i386 = ../libcpu/libcpu_i386.a libebl_i386_pic_a_SOURCES = $(i386_SRCS) am_libebl_i386_pic_a_OBJECTS = $(i386_SRCS:.c=.os) @@ -60,7 +62,8 @@ libebl_sh_pic_a_SOURCES = $(sh_SRCS) am_libebl_sh_pic_a_OBJECTS = $(sh_SRCS:.c=.os)
x86_64_SRCS = x86_64_init.c x86_64_symbol.c x86_64_corenote.c x86_64_cfi.c \ - x86_64_retval.c x86_64_regs.c i386_auxv.c x86_64_syscall.c + x86_64_retval.c x86_64_regs.c i386_auxv.c x86_64_syscall.c \ + x86_64_frame_state.c cpu_x86_64 = ../libcpu/libcpu_x86_64.a libebl_x86_64_pic_a_SOURCES = $(x86_64_SRCS) am_libebl_x86_64_pic_a_OBJECTS = $(x86_64_SRCS:.c=.os) diff --git a/backends/i386_frame_state.c b/backends/i386_frame_state.c new file mode 100644 index 0000000..b5d5439 --- /dev/null +++ b/backends/i386_frame_state.c @@ -0,0 +1,80 @@ +/* Fetch process data from STATE->base->pid or STATE->base->core. + Copyright (C) 2012 Red Hat, Inc. + This file is part of elfutils. + + This file is free software; you can redistribute it and/or modify + it under the terms of either + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at + your option) any later version + + or + + * the GNU General Public License as published by the Free + Software Foundation; either version 2 of the License, or (at + your option) any later version + + or both in parallel, as here. + + elfutils is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received copies of the GNU General Public License and + the GNU Lesser General Public License along with this program. If + not, see http://www.gnu.org/licenses/. */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#if defined __i386__ || defined __x86_64__ +# include <sys/user.h> +# include <sys/ptrace.h> +#endif +#include "libdwflP.h" + +#define BACKEND i386_ +#include "libebl_CPU.h" + +bool +i386_frame_state (Dwfl_Frame_State *state) +{ + pid_t tid = state->thread->tid; + if (state->thread->process->core == NULL && tid) + { +#if !defined __i386__ && !defined __x86_64__ + return false; +#else /* __i386__ || __x86_64__ */ + struct user_regs_struct user_regs; + if (ptrace (PTRACE_GETREGS, tid, NULL, &user_regs) != 0) + return false; +# if defined __i386__ + dwfl_frame_state_reg_set (state, 0, user_regs.eax); + dwfl_frame_state_reg_set (state, 1, user_regs.ecx); + dwfl_frame_state_reg_set (state, 2, user_regs.edx); + dwfl_frame_state_reg_set (state, 3, user_regs.ebx); + dwfl_frame_state_reg_set (state, 4, user_regs.esp); + dwfl_frame_state_reg_set (state, 5, user_regs.ebp); + dwfl_frame_state_reg_set (state, 6, user_regs.esi); + dwfl_frame_state_reg_set (state, 7, user_regs.edi); + dwfl_frame_state_reg_set (state, 8, user_regs.eip); +# elif defined __x86_64__ + dwfl_frame_state_reg_set (state, 0, user_regs.rax); + dwfl_frame_state_reg_set (state, 1, user_regs.rcx); + dwfl_frame_state_reg_set (state, 2, user_regs.rdx); + dwfl_frame_state_reg_set (state, 3, user_regs.rbx); + dwfl_frame_state_reg_set (state, 4, user_regs.rsp); + dwfl_frame_state_reg_set (state, 5, user_regs.rbp); + dwfl_frame_state_reg_set (state, 6, user_regs.rsi); + dwfl_frame_state_reg_set (state, 7, user_regs.rdi); + dwfl_frame_state_reg_set (state, 8, user_regs.rip); +# else /* (__i386__ || __x86_64__) && (!__i386__ && !__x86_64__) */ +# error +# endif /* (__i386__ || __x86_64__) && (!__i386__ && !__x86_64__) */ +#endif /* __i386__ || __x86_64__ */ + } + return true; +} diff --git a/backends/i386_init.c b/backends/i386_init.c index cc9b2d7..4200f31 100644 --- a/backends/i386_init.c +++ b/backends/i386_init.c @@ -63,6 +63,9 @@ i386_init (elf, machine, eh, ehlen) HOOK (eh, auxv_info); HOOK (eh, disasm); HOOK (eh, abi_cfi); + /* gcc/config/ #define DWARF_FRAME_REGISTERS. For i386 it is 17, why? */ + eh->frame_state_nregs = 9; + HOOK (eh, frame_state);
return MODVERSION; } diff --git a/backends/x86_64_frame_state.c b/backends/x86_64_frame_state.c new file mode 100644 index 0000000..b304250 --- /dev/null +++ b/backends/x86_64_frame_state.c @@ -0,0 +1,75 @@ +/* Fetch live process Dwfl_Frame_State from PID. + Copyright (C) 2012 Red Hat, Inc. + This file is part of elfutils. + + This file is free software; you can redistribute it and/or modify + it under the terms of either + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at + your option) any later version + + or + + * the GNU General Public License as published by the Free + Software Foundation; either version 2 of the License, or (at + your option) any later version + + or both in parallel, as here. + + elfutils is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received copies of the GNU General Public License and + the GNU Lesser General Public License along with this program. If + not, see http://www.gnu.org/licenses/. */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include <stdlib.h> +#ifdef __x86_64__ +# include <sys/user.h> +# include <sys/ptrace.h> +#endif +#include "libdwflP.h" + +#define BACKEND x86_64_ +#include "libebl_CPU.h" + +bool +x86_64_frame_state (Dwfl_Frame_State *state) +{ + pid_t tid = state->thread->tid; + if (state->thread->process->core == NULL && tid) + { +#ifndef __x86_64__ + return false; +#else /* __x86_64__ */ + struct user_regs_struct user_regs; + if (ptrace (PTRACE_GETREGS, tid, NULL, &user_regs) != 0) + return false; + dwfl_frame_state_reg_set (state, 0, user_regs.rax); + dwfl_frame_state_reg_set (state, 1, user_regs.rdx); + dwfl_frame_state_reg_set (state, 2, user_regs.rcx); + dwfl_frame_state_reg_set (state, 3, user_regs.rbx); + dwfl_frame_state_reg_set (state, 4, user_regs.rsi); + dwfl_frame_state_reg_set (state, 5, user_regs.rdi); + dwfl_frame_state_reg_set (state, 6, user_regs.rbp); + dwfl_frame_state_reg_set (state, 7, user_regs.rsp); + dwfl_frame_state_reg_set (state, 8, user_regs.r8); + dwfl_frame_state_reg_set (state, 9, user_regs.r9); + dwfl_frame_state_reg_set (state, 10, user_regs.r10); + dwfl_frame_state_reg_set (state, 11, user_regs.r11); + dwfl_frame_state_reg_set (state, 12, user_regs.r12); + dwfl_frame_state_reg_set (state, 13, user_regs.r13); + dwfl_frame_state_reg_set (state, 14, user_regs.r14); + dwfl_frame_state_reg_set (state, 15, user_regs.r15); + dwfl_frame_state_reg_set (state, 16, user_regs.rip); +#endif /* __x86_64__ */ + } + return true; +} diff --git a/backends/x86_64_init.c b/backends/x86_64_init.c index 67a5880..08c63f1 100644 --- a/backends/x86_64_init.c +++ b/backends/x86_64_init.c @@ -60,6 +60,9 @@ x86_64_init (elf, machine, eh, ehlen) HOOK (eh, auxv_info); HOOK (eh, disasm); HOOK (eh, abi_cfi); + /* gcc/config/ #define DWARF_FRAME_REGISTERS. */ + eh->frame_state_nregs = 17; + HOOK (eh, frame_state);
return MODVERSION; } diff --git a/configure.ac b/configure.ac index d7c75ce..47cfaa3 100644 --- a/configure.ac +++ b/configure.ac @@ -317,4 +317,15 @@ esac # Round up to the next release API (x.y) version. eu_version=$(( (eu_version + 999) / 1000 ))
+AC_CHECK_SIZEOF(long) + +# On a 64-bit host where can can use $CC -m32, we'll run two sets of tests. +# Likewise in a 32-bit build on a host where $CC -m64 works. +utrace_BIARCH +# `$utrace_biarch' will be `-m64' even on an uniarch i386 machine. +AS_IF([test $utrace_cv_cc_biarch = yes], + [CC_BIARCH="$CC $utrace_biarch"], + [CC_BIARCH="$CC"]) +AC_SUBST([CC_BIARCH]) + AC_OUTPUT diff --git a/libdw/cfi.h b/libdw/cfi.h index 8949833..44a84d4 100644 --- a/libdw/cfi.h +++ b/libdw/cfi.h @@ -150,8 +150,8 @@ struct dwarf_frame_register Dwarf_Sword value:(sizeof (Dwarf_Sword) * 8 - 3); };
-/* This holds everything we know about the state of the frame - at a particular PC location described by an FDE. */ +/* This holds instructions for unwinding frame at a particular PC location + described by an FDE. */ struct Dwarf_Frame_s { /* This frame description covers PC values in [start, end). */ diff --git a/libdw/libdw.map b/libdw/libdw.map index d38a8ef..54ee7f2 100644 --- a/libdw/libdw.map +++ b/libdw/libdw.map @@ -259,4 +259,11 @@ ELFUTILS_0.156 { global: # Replaced ELFUTILS_0.122 version, which has a wrapper without add_p_vaddr. dwfl_report_elf; + dwfl_frame_state_pid; + dwfl_frame_state_core; + dwfl_frame_state_data; + dwfl_frame_unwind; + dwfl_frame_state_pc; + dwfl_frame_tid_get; + dwfl_frame_thread_next; } ELFUTILS_0.149; diff --git a/libdwfl/Makefile.am b/libdwfl/Makefile.am index 3ef4dd6..d9dd5ab 100644 --- a/libdwfl/Makefile.am +++ b/libdwfl/Makefile.am @@ -31,7 +31,7 @@ ## include $(top_srcdir)/config/eu.am AM_CPPFLAGS += -I$(srcdir) -I$(srcdir)/../libelf -I$(srcdir)/../libebl \ - -I$(srcdir)/../libdw + -I$(srcdir)/../libdw -I$(srcdir)/../libasm VERSION = 1
noinst_LIBRARIES = libdwfl.a @@ -68,7 +68,11 @@ libdwfl_a_SOURCES = dwfl_begin.c dwfl_end.c dwfl_error.c dwfl_version.c \ dwfl_module_return_value_location.c \ dwfl_module_register_names.c \ dwfl_segment_report_module.c \ - link_map.c core-file.c open.c image-header.c + link_map.c core-file.c open.c image-header.c \ + dwfl_frame_state.c dwfl_frame_unwind.c \ + dwfl_frame_state_pc.c dwfl_frame_state_pid.c \ + dwfl_frame_state_core.c dwfl_frame_state_data.c \ + dwfl_frame_thread_next.c dwfl_frame_tid_get.c
if ZLIB libdwfl_a_SOURCES += gzip.c diff --git a/libdwfl/dwfl_end.c b/libdwfl/dwfl_end.c index 94fcfc6..babcba7 100644 --- a/libdwfl/dwfl_end.c +++ b/libdwfl/dwfl_end.c @@ -34,6 +34,9 @@ dwfl_end (Dwfl *dwfl) if (dwfl == NULL) return;
+ while (dwfl->framestatelist) + __libdwfl_process_free (dwfl->framestatelist); + free (dwfl->lookup_addr); free (dwfl->lookup_module); free (dwfl->lookup_segndx); diff --git a/libdwfl/dwfl_frame_state.c b/libdwfl/dwfl_frame_state.c new file mode 100644 index 0000000..1229aa2 --- /dev/null +++ b/libdwfl/dwfl_frame_state.c @@ -0,0 +1,175 @@ +/* Get Dwarf Frame state for target PID or core file. + Copyright (C) 2012 Red Hat, Inc. + This file is part of elfutils. + + This file is free software; you can redistribute it and/or modify + it under the terms of either + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at + your option) any later version + + or + + * the GNU General Public License as published by the Free + Software Foundation; either version 2 of the License, or (at + your option) any later version + + or both in parallel, as here. + + elfutils is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received copies of the GNU General Public License and + the GNU Lesser General Public License along with this program. If + not, see http://www.gnu.org/licenses/. */ + +#include "libdwflP.h" +#include <sys/ptrace.h> +#include <unistd.h> + +#ifndef MIN +# define MIN(a, b) ((a) < (b) ? (a) : (b)) +#endif + +bool +internal_function +__libdwfl_state_fetch_pc (Dwfl_Frame_State *state) +{ + switch (state->pc_state) + { + case DWFL_FRAME_STATE_PC_SET: + return true; + case DWFL_FRAME_STATE_PC_UNDEFINED: + abort (); + case DWFL_FRAME_STATE_ERROR:; + Ebl *ebl = state->thread->process->ebl; + Dwarf_CIE abi_info; + if (ebl_abi_cfi (ebl, &abi_info) != 0) + { + __libdwfl_seterrno (DWFL_E_LIBEBL); + return false; + } + unsigned ra = abi_info.return_address_register; + /* dwarf_frame_state_reg_is_set is not applied here. */ + if (ra >= ebl_frame_state_nregs (ebl)) + { + __libdwfl_seterrno (DWFL_E_LIBEBL_BAD); + return false; + } + state->pc = state->regs[ra]; + state->pc_state = DWFL_FRAME_STATE_PC_SET; + return true; + } + abort (); +} + +/* Do not call it on your own, to be used by thread_* functions only. */ + +static void +state_free (Dwfl_Frame_State *state) +{ + Dwfl_Frame_State_Thread *thread = state->thread; + assert (thread->unwound == state); + thread->unwound = state->unwound; + free (state); +} + +/* Do not call it on your own, to be used by thread_* functions only. */ + +static Dwfl_Frame_State * +state_alloc (Dwfl_Frame_State_Thread *thread) +{ + assert (thread->unwound == NULL); + Ebl *ebl = thread->process->ebl; + size_t nregs = ebl_frame_state_nregs (ebl); + if (nregs == 0) + return NULL; + assert (nregs < sizeof (((Dwfl_Frame_State *) NULL)->regs_set) * 8); + Dwfl_Frame_State *state = malloc (sizeof (*state) + + sizeof (*state->regs) * nregs); + if (state == NULL) + return NULL; + state->thread = thread; + state->signal_frame = false; + state->pc_state = DWFL_FRAME_STATE_ERROR; + memset (state->regs_set, 0, sizeof (state->regs_set)); + thread->unwound = state; + state->unwound = NULL; + return state; +} + +void +internal_function +__libdwfl_thread_free (Dwfl_Frame_State_Thread *thread) +{ + while (thread->unwound) + state_free (thread->unwound); + if (thread->tid_attached) + ptrace (PTRACE_DETACH, thread->tid, NULL, NULL); + Dwfl_Frame_State_Process *process = thread->process; + assert (process->thread == thread); + process->thread = thread->next; + free (thread); +} + +Dwfl_Frame_State_Thread * +internal_function +__libdwfl_thread_alloc (Dwfl_Frame_State_Process *process, pid_t tid) +{ + Dwfl_Frame_State_Thread *thread = malloc (sizeof (*thread)); + if (thread == NULL) + return NULL; + thread->process = process; + thread->tid = tid; + thread->tid_attached = false; + thread->unwound = NULL; + thread->next = process->thread; + process->thread = thread; + if (state_alloc (thread) == NULL) + { + __libdwfl_thread_free (thread); + return NULL; + } + return thread; +} + +void +internal_function +__libdwfl_process_free (Dwfl_Frame_State_Process *process) +{ + while (process->thread) + __libdwfl_thread_free (process->thread); + if (process->ebl_close) + ebl_closebackend (process->ebl); + elf_end (process->core); + if (process->core_fd != -1) + close (process->core_fd); + Dwfl *dwfl = process->dwfl; + assert (dwfl->framestatelist == process); + dwfl->framestatelist = process->next; + free (process); +} + +Dwfl_Frame_State_Process * +internal_function +__libdwfl_process_alloc (Dwfl *dwfl, dwfl_frame_memory_read_t *memory_read, + void *memory_read_user_data) +{ + Dwfl_Frame_State_Process *process = malloc (sizeof (*process)); + if (process == NULL) + return NULL; + process->dwfl = dwfl; + process->ebl = NULL; + process->ebl_close = NULL; + process->memory_read = memory_read; + process->memory_read_user_data = memory_read_user_data; + process->core = NULL; + process->core_fd = -1; + process->thread = NULL; + process->next = dwfl->framestatelist; + dwfl->framestatelist = process; + return process; +} diff --git a/libdwfl/dwfl_frame_state_core.c b/libdwfl/dwfl_frame_state_core.c new file mode 100644 index 0000000..737bcbb --- /dev/null +++ b/libdwfl/dwfl_frame_state_core.c @@ -0,0 +1,286 @@ +/* Get Dwarf Frame state for target core file. + Copyright (C) 2012 Red Hat, Inc. + This file is part of elfutils. + + This file is free software; you can redistribute it and/or modify + it under the terms of either + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at + your option) any later version + + or + + * the GNU General Public License as published by the Free + Software Foundation; either version 2 of the License, or (at + your option) any later version + + or both in parallel, as here. + + elfutils is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received copies of the GNU General Public License and + the GNU Lesser General Public License along with this program. If + not, see http://www.gnu.org/licenses/. */ + +#include "libdwflP.h" +#include <fcntl.h> +#include "system.h" + +#ifndef MIN +# define MIN(a, b) ((a) < (b) ? (a) : (b)) +#endif + +static bool +dwfl_frame_state_core_memory_read (Dwarf_Addr addr, Dwarf_Addr *result, + void *user_data) +{ + Dwfl_Frame_State_Process *process = user_data; + Elf *core = process->core; + assert (core != NULL); + Dwfl *dwfl = process->dwfl; + static size_t phnum; + if (elf_getphdrnum (core, &phnum) < 0) + { + __libdwfl_seterrno (DWFL_E_LIBELF); + return false; + } + for (size_t cnt = 0; cnt < phnum; ++cnt) + { + GElf_Phdr phdr_mem, *phdr = gelf_getphdr (core, cnt, &phdr_mem); + if (phdr == NULL || phdr->p_type != PT_LOAD) + continue; + /* Bias is zero here, a core file itself has no bias. */ + GElf_Addr start = __libdwfl_segment_start (dwfl, phdr->p_vaddr); + GElf_Addr end = __libdwfl_segment_end (dwfl, + phdr->p_vaddr + phdr->p_memsz); + unsigned bytes = process->ebl->class == ELFCLASS64 ? 8 : 4; + if (addr < start || addr + bytes > end) + continue; + Elf_Data *data; + data = elf_getdata_rawchunk (core, phdr->p_offset + addr - start, + bytes, ELF_T_ADDR); + if (data == NULL) + { + __libdwfl_seterrno (DWFL_E_LIBELF); + return false; + } + assert (data->d_size == bytes); + /* FIXME: Currently any arch supported for unwinding supports + unaligned access. */ + if (bytes == 8) + *result = *(const uint64_t *) data->d_buf; + else + *result = *(const uint32_t *) data->d_buf; + return true; + } + __libdwfl_seterrno (DWFL_E_CORE_MISSING); + return false; +} + +Dwfl_Frame_State * +dwfl_frame_state_core (Dwfl *dwfl, const char *corefile) +{ + Dwfl_Frame_State_Process *process; + process = __libdwfl_process_alloc (dwfl, dwfl_frame_state_core_memory_read, + NULL); + if (process == NULL) + return NULL; + process->memory_read_user_data = process; + int core_fd = open64 (corefile, O_RDONLY); + if (core_fd < 0) + { + __libdwfl_process_free (process); + __libdwfl_seterrno (DWFL_E_BADELF); + return NULL; + } + process->core_fd = core_fd; + Elf *core; + Dwfl_Error err = __libdw_open_file (&core_fd, &core, true, false); + if (err != DWFL_E_NOERROR) + { + __libdwfl_process_free (process); + __libdwfl_seterrno (err); + return NULL; + } + process->core = core; + Ebl *ebl = ebl_openbackend (core); + if (ebl == NULL) + { + __libdwfl_process_free (process); + __libdwfl_seterrno (DWFL_E_LIBEBL); + return NULL; + } + process->ebl = ebl; + process->ebl_close = true; + size_t nregs = ebl_frame_state_nregs (ebl); + if (nregs == 0) + { + /* We do not support unwinding this CORE file EBL. */ + __libdwfl_process_free (process); + __libdwfl_seterrno (DWFL_E_LIBEBL); + return NULL; + } + GElf_Ehdr ehdr_mem, *ehdr = gelf_getehdr (core, &ehdr_mem); + if (ehdr == NULL) + { + __libdwfl_process_free (process); + __libdwfl_seterrno (DWFL_E_LIBELF); + return NULL; + } + assert (ehdr->e_type == ET_CORE); + size_t phnum; + if (elf_getphdrnum (core, &phnum) < 0) + { + __libdwfl_process_free (process); + __libdwfl_seterrno (DWFL_E_LIBELF); + return NULL; + } + Dwfl_Frame_State_Thread *thread = NULL; + for (size_t cnt = 0; cnt < phnum; ++cnt) + { + GElf_Phdr phdr_mem, *phdr = gelf_getphdr (core, cnt, &phdr_mem); + if (phdr == NULL || phdr->p_type != PT_NOTE) + continue; + Elf_Data *data = elf_getdata_rawchunk (core, phdr->p_offset, + phdr->p_filesz, ELF_T_NHDR); + if (data == NULL) + { + __libdwfl_process_free (process); + __libdwfl_seterrno (DWFL_E_LIBELF); + return NULL; + } + size_t offset = 0; + GElf_Nhdr nhdr; + size_t name_offset; + size_t desc_offset; + while (offset < data->d_size + && (offset = gelf_getnote (data, offset, + &nhdr, &name_offset, &desc_offset)) > 0) + { + /* Do not check NAME for now, help broken Linux kernels. */ + const char *name = data->d_buf + name_offset; + const char *desc = data->d_buf + desc_offset; + GElf_Word regs_offset; + size_t nregloc; + const Ebl_Register_Location *reglocs; + size_t nitems; + const Ebl_Core_Item *items; + if (! ebl_core_note (ebl, &nhdr, name, + ®s_offset, &nregloc, ®locs, &nitems, &items)) + { + /* This note may be just not recognized, skip it. */ + continue; + } + if (nhdr.n_type == NT_PRSTATUS) + { + const Ebl_Core_Item *item; + for (item = items; item < items + nitems; item++) + if (strcmp (item->name, "pid") == 0) + break; + if (item == items + nitems) + { + __libdwfl_process_free (process); + __libdwfl_seterrno (DWFL_E_BADELF); + return NULL; + } + uint32_t val32 = *(const uint32_t *) (desc + item->offset); + val32 = (elf_getident (core, NULL)[EI_DATA] == ELFDATA2MSB + ? be32toh (val32) : le32toh (val32)); + pid_t tid = (int32_t) val32; + eu_static_assert (sizeof val32 <= sizeof tid); + if (thread) + { + /* Delay initialization of THREAD till all notes for it have + been read in. */ + Dwfl_Frame_State *state = thread->unwound; + if (! ebl_frame_state (state) + || ! __libdwfl_state_fetch_pc (state)) + { + __libdwfl_thread_free (thread); + thread = NULL; + continue; + } + } + thread = __libdwfl_thread_alloc (process, tid); + if (thread == NULL) + { + __libdwfl_process_free (process); + __libdwfl_seterrno (DWFL_E_NOMEM); + return NULL; + } + } + if (thread == NULL) + { + /* Ignore notes before first PR_NTSTATUS. */ + continue; + } + Dwfl_Frame_State *state = thread->unwound; + desc += regs_offset; + for (size_t regloci = 0; regloci < nregloc; regloci++) + { + const Ebl_Register_Location *regloc = reglocs + regloci; + if (regloc->regno >= nregs) + continue; + assert (regloc->bits == 32 || regloc->bits == 64); + const char *reg_desc = desc + regloc->offset; + for (unsigned regno = regloc->regno; + regno < MIN (regloc->regno + (regloc->count ?: 1U), nregs); + regno++) + { + /* PPC provides DWARF register 65 irrelevant for + CFI which clashes with register 108 (LR) we need. + LR (108) is provided earlier (in NT_PRSTATUS) than the # 65. + FIXME: It depends now on their order in core notes. */ + if (dwfl_frame_state_reg_get (state, regno, NULL)) + continue; + Dwarf_Addr val; + switch (regloc->bits) + { + case 32:; + uint32_t val32 = *(const uint32_t *) reg_desc; + reg_desc += sizeof val32; + val32 = (elf_getident (core, NULL)[EI_DATA] == ELFDATA2MSB + ? be32toh (val32) : le32toh (val32)); + /* Do a host width conversion. */ + val = val32; + break; + case 64:; + uint64_t val64 = *(const uint64_t *) reg_desc; + reg_desc += sizeof val64; + val64 = (elf_getident (core, NULL)[EI_DATA] == ELFDATA2MSB + ? be64toh (val64) : le64toh (val64)); + assert (sizeof (*state->regs) == sizeof val64); + val = val64; + break; + default: + abort (); + } + /* Registers not valid for CFI are just ignored. */ + dwfl_frame_state_reg_set (state, regno, val); + reg_desc += regloc->pad; + } + } + } + } + if (thread) + { + /* Delay initialization of THREAD till all notes for it have been read + in. */ + Dwfl_Frame_State *state = thread->unwound; + if (! ebl_frame_state (state) || ! __libdwfl_state_fetch_pc (state)) + __libdwfl_thread_free (thread); + } + if (process->thread == NULL) + { + /* No valid threads recognized in this CORE. */ + __libdwfl_process_free (process); + __libdwfl_seterrno (DWFL_E_BADELF); + return NULL; + } + return process->thread->unwound; +} +INTDEF (dwfl_frame_state_core) diff --git a/libdwfl/dwfl_frame_state_data.c b/libdwfl/dwfl_frame_state_data.c new file mode 100644 index 0000000..9adfa7c --- /dev/null +++ b/libdwfl/dwfl_frame_state_data.c @@ -0,0 +1,91 @@ +/* Get Dwarf Frame state from modules present in DWFL. + Copyright (C) 2012 Red Hat, Inc. + This file is part of elfutils. + + This file is free software; you can redistribute it and/or modify + it under the terms of either + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at + your option) any later version + + or + + * the GNU General Public License as published by the Free + Software Foundation; either version 2 of the License, or (at + your option) any later version + + or both in parallel, as here. + + elfutils is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received copies of the GNU General Public License and + the GNU Lesser General Public License along with this program. If + not, see http://www.gnu.org/licenses/. */ + +#include "libdwflP.h" + +Dwfl_Frame_State * +dwfl_frame_state_data (Dwfl *dwfl, bool pc_set, Dwarf_Addr pc, unsigned nregs, + const uint64_t *regs_set, const Dwarf_Addr *regs, + dwfl_frame_memory_read_t *memory_read, + void *memory_read_user_data) +{ + Ebl *ebl = NULL; + for (Dwfl_Module *mod = dwfl->modulelist; mod != NULL; mod = mod->next) + { + Dwfl_Error error = __libdwfl_module_getebl (mod); + if (error != DWFL_E_NOERROR) + continue; + ebl = mod->ebl; + } + if (ebl == NULL || nregs > ebl_frame_state_nregs (ebl)) + { + __libdwfl_seterrno (DWFL_E_LIBEBL_BAD); + return NULL; + } + Dwfl_Frame_State_Process *process; + process = __libdwfl_process_alloc (dwfl, memory_read, memory_read_user_data); + if (process == NULL) + return NULL; + process->ebl = ebl; + Dwfl_Frame_State_Thread *thread = __libdwfl_thread_alloc (process, 0); + if (thread == NULL) + { + __libdwfl_process_free (process); + __libdwfl_seterrno (DWFL_E_NOMEM); + return NULL; + } + Dwfl_Frame_State *state = thread->unwound; + state->pc_state = DWFL_FRAME_STATE_ERROR; + if (pc_set) + { + state->pc = pc; + state->pc_state = DWFL_FRAME_STATE_PC_SET; + } + for (unsigned regno = 0; regno < nregs; regno++) + if ((regs_set[regno / sizeof (*regs_set) / 8] + & (1U << (regno % (sizeof (*regs_set) * 8)))) != 0 + && ! dwfl_frame_state_reg_set (state, regno, regs[regno])) + { + __libdwfl_process_free (process); + __libdwfl_seterrno (DWFL_E_INVALID_REGISTER); + return NULL; + } + if (! ebl_frame_state (state)) + { + __libdwfl_process_free (process); + __libdwfl_seterrno (DWFL_E_LIBEBL); + return NULL; + } + if (! __libdwfl_state_fetch_pc (state)) + { + __libdwfl_process_free (process); + return NULL; + } + return process->thread->unwound; +} +INTDEF (dwfl_frame_state_data) diff --git a/libdwfl/dwfl_frame_state_pc.c b/libdwfl/dwfl_frame_state_pc.c new file mode 100644 index 0000000..6e9928b --- /dev/null +++ b/libdwfl/dwfl_frame_state_pc.c @@ -0,0 +1,56 @@ +/* Get return address register value for frame. + Copyright (C) 2012 Red Hat, Inc. + This file is part of elfutils. + + This file is free software; you can redistribute it and/or modify + it under the terms of either + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at + your option) any later version + + or + + * the GNU General Public License as published by the Free + Software Foundation; either version 2 of the License, or (at + your option) any later version + + or both in parallel, as here. + + elfutils is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received copies of the GNU General Public License and + the GNU Lesser General Public License along with this program. If + not, see http://www.gnu.org/licenses/. */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include "libdwflP.h" + +bool +dwfl_frame_state_pc (Dwfl_Frame_State *state, Dwarf_Addr *pc, bool *minusone) +{ + assert (state->pc_state == DWFL_FRAME_STATE_PC_SET); + *pc = state->pc; + if (minusone) + { + /* Bottom frame? */ + if (state == state->thread->unwound) + *minusone = false; + /* *MINUSONE is logical or of both current and previous frame state. */ + else if (state->signal_frame) + *minusone = false; + /* Not affected by unsuccessfully unwound frame. */ + else if (! INTUSE(dwfl_frame_unwind) (&state) || state == NULL) + *minusone = true; + else + *minusone = ! state->signal_frame; + } + return true; +} +INTDEF (dwfl_frame_state_pc) diff --git a/libdwfl/dwfl_frame_state_pid.c b/libdwfl/dwfl_frame_state_pid.c new file mode 100644 index 0000000..89fbee2 --- /dev/null +++ b/libdwfl/dwfl_frame_state_pid.c @@ -0,0 +1,220 @@ +/* Get Dwarf Frame state for target live PID process. + Copyright (C) 2012 Red Hat, Inc. + This file is part of elfutils. + + This file is free software; you can redistribute it and/or modify + it under the terms of either + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at + your option) any later version + + or + + * the GNU General Public License as published by the Free + Software Foundation; either version 2 of the License, or (at + your option) any later version + + or both in parallel, as here. + + elfutils is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received copies of the GNU General Public License and + the GNU Lesser General Public License along with this program. If + not, see http://www.gnu.org/licenses/. */ + +#include "libdwflP.h" +#include <sys/ptrace.h> +#include <sys/wait.h> +#include <dirent.h> + +static bool +tid_is_attached (Dwfl *dwfl, pid_t tid) +{ + for (Dwfl_Frame_State_Process *process = dwfl->framestatelist; process; + process = process->next) + for (Dwfl_Frame_State_Thread *thread = process->thread; thread; + thread = thread->next) + if (thread->tid_attached && thread->tid == tid) + return true; + return false; +} + +static bool +ptrace_attach (pid_t tid) +{ + if (ptrace (PTRACE_ATTACH, tid, NULL, NULL) != 0) + return false; + /* FIXME: Handle missing SIGSTOP on old Linux kernels. */ + for (;;) + { + int status; + if (waitpid (tid, &status, __WALL) != tid || !WIFSTOPPED (status)) + { + ptrace (PTRACE_DETACH, tid, NULL, NULL); + return false; + } + if (WSTOPSIG (status) == SIGSTOP) + break; + if (ptrace (PTRACE_CONT, tid, NULL, + (void *) (uintptr_t) WSTOPSIG (status)) != 0) + { + ptrace (PTRACE_DETACH, tid, NULL, NULL); + return false; + } + } + return true; +} + +static bool +dwfl_frame_state_pid_memory_read (Dwarf_Addr addr, Dwarf_Addr *result, + void *user_data) +{ + Dwfl_Frame_State_Process *process = user_data; + assert (process->core == NULL && process->thread->tid); + if (process->ebl->class == ELFCLASS64) + { + errno = 0; + *result = ptrace (PTRACE_PEEKDATA, process->thread->tid, + (void *) (uintptr_t) addr, NULL); + if (errno != 0) + { + __libdwfl_seterrno (DWFL_E_PROCESS_MEMORY_READ); + return false; + } + return true; + } +#if SIZEOF_LONG == 8 + /* We do not care about reads unaliged to 4 bytes boundary. + But 0x...ffc read of 8 bytes could overrun a page. */ + bool lowered = (addr & 4) != 0; + if (lowered) + addr -= 4; +#endif /* SIZEOF_LONG == 8 */ + errno = 0; + *result = ptrace (PTRACE_PEEKDATA, process->thread->tid, + (void *) (uintptr_t) addr, NULL); + if (errno != 0) + { + __libdwfl_seterrno (DWFL_E_PROCESS_MEMORY_READ); + return false; + } +#if SIZEOF_LONG == 8 +# if BYTE_ORDER == BIG_ENDIAN + if (! lowered) + *result >>= 32; +# else + if (lowered) + *result >>= 32; +# endif +#endif /* SIZEOF_LONG == 8 */ + *result &= 0xffffffff; + return true; +} + +Dwfl_Frame_State * +dwfl_frame_state_pid (Dwfl *dwfl, pid_t pid) +{ + char dirname[64]; + int i = snprintf (dirname, sizeof (dirname), "/proc/%ld/task", (long) pid); + assert (i > 0 && i < (ssize_t) sizeof (dirname) - 1); + Dwfl_Frame_State_Process *process; + process = __libdwfl_process_alloc (dwfl, dwfl_frame_state_pid_memory_read, + NULL); + if (process == NULL) + return NULL; + process->memory_read_user_data = process; + for (Dwfl_Module *mod = dwfl->modulelist; mod != NULL; mod = mod->next) + { + Dwfl_Error error = __libdwfl_module_getebl (mod); + if (error != DWFL_E_NOERROR) + continue; + process->ebl = mod->ebl; + } + if (process->ebl == NULL) + { + /* Not identified EBL from any of the modules. */ + __libdwfl_process_free (process); + __libdwfl_seterrno (DWFL_E_PROCESS_NO_ARCH); + return NULL; + } + DIR *dir = opendir (dirname); + if (dir == NULL) + { + __libdwfl_process_free (process); + __libdwfl_seterrno (DWFL_E_PARSE_PROC); + return NULL; + } + for (;;) + { + errno = 0; + struct dirent *dirent = readdir (dir); + if (dirent == NULL) + { + if (errno == 0) + break; + __libdwfl_process_free (process); + __libdwfl_seterrno (DWFL_E_PARSE_PROC); + return NULL; + } + if (strcmp (dirent->d_name, ".") == 0 + || strcmp (dirent->d_name, "..") == 0) + continue; + char *end; + errno = 0; + long tidl = strtol (dirent->d_name, &end, 10); + if (errno != 0) + { + __libdwfl_process_free (process); + __libdwfl_seterrno (DWFL_E_PARSE_PROC); + return NULL; + } + pid_t tid = tidl; + if (tidl <= 0 || (end && *end) || tid != tidl) + { + __libdwfl_process_free (process); + __libdwfl_seterrno (DWFL_E_PARSE_PROC); + return NULL; + } + Dwfl_Frame_State_Thread *thread = __libdwfl_thread_alloc (process, tid); + if (thread == NULL) + { + __libdwfl_process_free (process); + __libdwfl_seterrno (DWFL_E_NOMEM); + return NULL; + } + if (! tid_is_attached (dwfl, thread->tid)) + { + if (! ptrace_attach (thread->tid)) + { + __libdwfl_thread_free (thread); + continue; + } + thread->tid_attached = true; + } + Dwfl_Frame_State *state = thread->unwound; + if (! ebl_frame_state (state) || ! __libdwfl_state_fetch_pc (state)) + { + __libdwfl_thread_free (thread); + continue; + } + } + if (closedir (dir) != 0) + { + __libdwfl_process_free (process); + __libdwfl_seterrno (DWFL_E_PARSE_PROC); + return NULL; + } + if (process->thread == NULL) + { + /* No valid threads recognized. */ + __libdwfl_process_free (process); + __libdwfl_seterrno (DWFL_E_NO_THREAD); + return NULL; + } + return process->thread->unwound; +} +INTDEF (dwfl_frame_state_pid) diff --git a/libdwfl/dwfl_frame_thread_next.c b/libdwfl/dwfl_frame_thread_next.c new file mode 100644 index 0000000..df4aebb --- /dev/null +++ b/libdwfl/dwfl_frame_thread_next.c @@ -0,0 +1,37 @@ +/* Get innermost frame of the next thread from state. + Copyright (C) 2012 Red Hat, Inc. + This file is part of elfutils. + + This file is free software; you can redistribute it and/or modify + it under the terms of either + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at + your option) any later version + + or + + * the GNU General Public License as published by the Free + Software Foundation; either version 2 of the License, or (at + your option) any later version + + or both in parallel, as here. + + elfutils is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received copies of the GNU General Public License and + the GNU Lesser General Public License along with this program. If + not, see http://www.gnu.org/licenses/. */ + +#include "libdwflP.h" + +Dwfl_Frame_State * +dwfl_frame_thread_next (Dwfl_Frame_State *state) +{ + Dwfl_Frame_State_Thread *thread_next = state->thread->next; + return thread_next ? thread_next->unwound : NULL; +} +INTDEF (dwfl_frame_thread_next) diff --git a/libdwfl/dwfl_frame_tid_get.c b/libdwfl/dwfl_frame_tid_get.c new file mode 100644 index 0000000..7883b5e --- /dev/null +++ b/libdwfl/dwfl_frame_tid_get.c @@ -0,0 +1,36 @@ +/* Get Task ID of the thread of state. + Copyright (C) 2012 Red Hat, Inc. + This file is part of elfutils. + + This file is free software; you can redistribute it and/or modify + it under the terms of either + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at + your option) any later version + + or + + * the GNU General Public License as published by the Free + Software Foundation; either version 2 of the License, or (at + your option) any later version + + or both in parallel, as here. + + elfutils is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received copies of the GNU General Public License and + the GNU Lesser General Public License along with this program. If + not, see http://www.gnu.org/licenses/. */ + +#include "libdwflP.h" + +pid_t +dwfl_frame_tid_get (Dwfl_Frame_State *state) +{ + return state->thread->tid; +} +INTDEF (dwfl_frame_tid_get) diff --git a/libdwfl/dwfl_frame_unwind.c b/libdwfl/dwfl_frame_unwind.c new file mode 100644 index 0000000..d329b35 --- /dev/null +++ b/libdwfl/dwfl_frame_unwind.c @@ -0,0 +1,450 @@ +/* Get previous frame state for an existing frame state. + Copyright (C) 2012 Red Hat, Inc. + This file is part of elfutils. + + This file is free software; you can redistribute it and/or modify + it under the terms of either + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at + your option) any later version + + or + + * the GNU General Public License as published by the Free + Software Foundation; either version 2 of the License, or (at + your option) any later version + + or both in parallel, as here. + + elfutils is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received copies of the GNU General Public License and + the GNU Lesser General Public License along with this program. If + not, see http://www.gnu.org/licenses/. */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include "cfi.h" +#include <stdlib.h> +#include "libdwflP.h" +#include "../libdw/dwarf.h" +#include <sys/ptrace.h> + +#ifndef MAX +# define MAX(a, b) ((a) > (b) ? (a) : (b)) +#endif + +static bool +state_get_reg (Dwfl_Frame_State *state, unsigned regno, Dwarf_Addr *val) +{ + if (! dwfl_frame_state_reg_get (state, regno, val)) + { + __libdwfl_seterrno (DWFL_E_INVALID_REGISTER); + return false; + } + return true; +} + +static int +bra_compar (const void *key_voidp, const void *elem_voidp) +{ + Dwarf_Word offset = (uintptr_t) key_voidp; + const Dwarf_Op *op = elem_voidp; + return (offset > op->offset) - (offset < op->offset); +} + +/* FIXME: Handle bytecode deadlocks and overflows. */ + +static bool +expr_eval (Dwfl_Frame_State *state, Dwarf_Frame *frame, const Dwarf_Op *ops, + size_t nops, Dwarf_Addr *result) +{ + Dwfl_Frame_State_Process *process = state->thread->process; + if (nops == 0) + { + __libdwfl_seterrno (DWFL_E_INVALID_DWARF); + return false; + } + Dwarf_Addr *stack = NULL; + size_t stack_used = 0, stack_allocated = 0; + bool + push (Dwarf_Addr val) + { + if (stack_used == stack_allocated) + { + stack_allocated = MAX (stack_allocated * 2, 32); + Dwarf_Addr *stack_new = realloc (stack, stack_allocated * sizeof (*stack)); + if (stack_new == NULL) + { + __libdwfl_seterrno (DWFL_E_NOMEM); + return false; + } + stack = stack_new; + } + stack[stack_used++] = val; + return true; + } + bool + pop (Dwarf_Addr *val) + { + if (stack_used == 0) + { + __libdwfl_seterrno (DWFL_E_INVALID_DWARF); + return false; + } + *val = stack[--stack_used]; + return true; + } + Dwarf_Addr val1, val2; + bool is_location = false; + for (const Dwarf_Op *op = ops; op < ops + nops; op++) + switch (op->atom) + { + case DW_OP_reg0 ... DW_OP_reg31: + if (! state_get_reg (state, op->atom - DW_OP_reg0, &val1) + || ! push (val1)) + { + free (stack); + return false; + } + break; + case DW_OP_regx: + if (! state_get_reg (state, op->number, &val1) || ! push (val1)) + { + free (stack); + return false; + } + break; + case DW_OP_breg0 ... DW_OP_breg31: + if (! state_get_reg (state, op->atom - DW_OP_breg0, &val1)) + { + free (stack); + return false; + } + val1 += op->number; + if (! push (val1)) + { + free (stack); + return false; + } + break; + case DW_OP_bregx: + if (! state_get_reg (state, op->number, &val1)) + { + free (stack); + return false; + } + val1 += op->number2; + if (! push (val1)) + { + free (stack); + return false; + } + break; + case DW_OP_lit0 ... DW_OP_lit31: + if (! push (op->atom - DW_OP_lit0)) + { + free (stack); + return false; + } + break; + case DW_OP_plus_uconst: + if (! pop (&val1) || ! push (val1 + op->number)) + { + free (stack); + return false; + } + break; + case DW_OP_call_frame_cfa:; + Dwarf_Op *cfa_ops; + size_t cfa_nops; + Dwarf_Addr cfa; + if (dwarf_frame_cfa (frame, &cfa_ops, &cfa_nops) != 0 + || ! expr_eval (state, frame, cfa_ops, cfa_nops, &cfa) + || ! push (cfa)) + { + __libdwfl_seterrno (DWFL_E_LIBDW); + free (stack); + return false; + } + is_location = true; + break; + case DW_OP_stack_value: + is_location = false; + break; + case DW_OP_deref: + if (! pop (&val1) + || ! process->memory_read (val1, &val1, + process->memory_read_user_data) + || ! push (val1)) + { + free (stack); + return false; + } + break; + case DW_OP_nop: + break; + case DW_OP_dup: + if (! pop (&val1) || ! push (val1) || ! push (val1)) + { + free (stack); + return false; + } + break; + case DW_OP_const1u: + case DW_OP_const1s: + case DW_OP_const2u: + case DW_OP_const2s: + case DW_OP_const4u: + case DW_OP_const4s: + case DW_OP_const8u: + case DW_OP_const8s: + case DW_OP_constu: + case DW_OP_consts: + if (! push (op->number)) + { + free (stack); + return false; + } + break; + case DW_OP_bra: + if (! pop (&val1)) + { + free (stack); + return false; + } + if (val1 == 0) + break; + /* FALLTHRU */ + case DW_OP_skip:; + Dwarf_Word offset = op->offset + 1 + 2 + (int16_t) op->number; + const Dwarf_Op *found = bsearch ((void *) (uintptr_t) offset, ops, nops, + sizeof (*ops), bra_compar); + if (found == NULL) + { + free (stack); + /* PPC32 vDSO has such invalid operations. */ + __libdwfl_seterrno (DWFL_E_INVALID_DWARF); + return false; + } + /* Undo the 'for' statement increment. */ + op = found - 1; + break; + case DW_OP_drop: + if (! pop (&val1)) + { + free (stack); + return false; + } + break; +#define BINOP(atom, op) \ + case atom: \ + if (! pop (&val2) || ! pop (&val1) || ! push (val1 op val2)) \ + { \ + free (stack); \ + return false; \ + } \ + break; + BINOP (DW_OP_and, &) + BINOP (DW_OP_shl, <<) + BINOP (DW_OP_plus, +) + BINOP (DW_OP_mul, *) +#undef BINOP +#define BINOP_SIGNED(atom, op) \ + case atom: \ + if (! pop (&val2) || ! pop (&val1) \ + || ! push ((int64_t) val1 op (int64_t) val2)) \ + { \ + free (stack); \ + return false; \ + } \ + break; + BINOP_SIGNED (DW_OP_le, <=) + BINOP_SIGNED (DW_OP_ge, >=) + BINOP_SIGNED (DW_OP_eq, ==) + BINOP_SIGNED (DW_OP_lt, <) + BINOP_SIGNED (DW_OP_gt, >) + BINOP_SIGNED (DW_OP_ne, !=) +#undef BINOP_SIGNED + default: + __libdwfl_seterrno (DWFL_E_UNSUPPORTED_DWARF); + return false; + } + if (! pop (result)) + { + free (stack); + return false; + } + free (stack); + if (is_location && ! process->memory_read (*result, result, + process->memory_read_user_data)) + return false; + return true; +} + +/* Return TRUE and update *STATEP for the unwound frame for successful unwind. + Return TRUE and set *STATEP to NULL for the outermost frame. Return FALSE + (and call __libdwfl_seterrno) otherwise. */ + +static bool +have_unwound (Dwfl_Frame_State **statep) +{ + Dwfl_Frame_State *state = *statep, *unwound = state->unwound; + switch (unwound->pc_state) + { + case DWFL_FRAME_STATE_ERROR: + __libdwfl_seterrno (DWFL_E_INVALID_DWARF); + *statep = NULL; + return false; + case DWFL_FRAME_STATE_PC_SET: + *statep = unwound; + return true; + case DWFL_FRAME_STATE_PC_UNDEFINED: + *statep = NULL; + return true; + } + abort (); +} + +/* The logic is to call __libdwfl_seterrno for any CFI bytecode interpretation + error so one can easily catch the problem with a debugger. Still there are + archs with invalid CFI for some registers where the registers are never used + later. Therefore we continue unwinding leaving the registers undefined. + + The only exception is PC itself, when there is an error unwinding PC we + return false. Otherwise we would return successful end of backtrace seeing + an undefined PC register (due to an error unwinding it). */ + +static bool +handle_cfi (Dwfl_Frame_State **statep, Dwarf_Addr pc, Dwarf_CFI *cfi) +{ + Dwfl_Frame_State *state = *statep; + Dwarf_Frame *frame; + if (INTUSE(dwarf_cfi_addrframe) (cfi, pc, &frame) != 0) + { + __libdwfl_seterrno (DWFL_E_LIBDW); + return false; + } + Dwfl_Frame_State_Thread *thread = state->thread; + Dwfl_Frame_State_Process *process = thread->process; + Ebl *ebl = process->ebl; + size_t nregs = ebl_frame_state_nregs (ebl); + Dwfl_Frame_State *unwound; + unwound = malloc (sizeof (*unwound) + sizeof (*unwound->regs) * nregs); + state->unwound = unwound; + unwound->thread = thread; + unwound->unwound = NULL; + unwound->signal_frame = frame->fde->cie->signal_frame; + unwound->pc_state = DWFL_FRAME_STATE_ERROR; + memset (unwound->regs_set, 0, sizeof (unwound->regs_set)); + for (unsigned regno = 0; regno < nregs; regno++) + { + Dwarf_Op reg_ops_mem[3], *reg_ops; + size_t reg_nops; + if (dwarf_frame_register (frame, regno, reg_ops_mem, ®_ops, + ®_nops) != 0) + { + __libdwfl_seterrno (DWFL_E_LIBDW); + continue; + } + Dwarf_Addr regval; + if (reg_nops == 0) + { + if (reg_ops == reg_ops_mem) + { + /* REGNO is undefined. */ + unsigned ra = frame->fde->cie->return_address_register; + if (regno == ra) + unwound->pc_state = DWFL_FRAME_STATE_PC_UNDEFINED; + continue; + } + else if (reg_ops == NULL) + { + /* REGNO is same-value. */ + if (! state_get_reg (state, regno, ®val)) + continue; + } + else + { + __libdwfl_seterrno (DWFL_E_INVALID_DWARF); + continue; + } + } + else if (! expr_eval (state, frame, reg_ops, reg_nops, ®val)) + { + /* PPC32 vDSO has various invalid operations, ignore them. The + register will look as unset causing an error later, if used. + But PPC32 does not use such registers. */ + continue; + } + if (! dwfl_frame_state_reg_set (unwound, regno, regval)) + { + __libdwfl_seterrno (DWFL_E_INVALID_REGISTER); + continue; + } + } + if (unwound->pc_state == DWFL_FRAME_STATE_ERROR + && dwfl_frame_state_reg_get (unwound, + frame->fde->cie->return_address_register, + &unwound->pc)) + { + /* PPC32 __libc_start_main properly CFI-unwinds PC as zero. Currently + none of the archs supported for unwinding have zero as a valid PC. */ + if (unwound->pc == 0) + unwound->pc_state = DWFL_FRAME_STATE_PC_UNDEFINED; + else + unwound->pc_state = DWFL_FRAME_STATE_PC_SET; + } + return have_unwound (statep); +} + +bool +dwfl_frame_unwind (Dwfl_Frame_State **statep) +{ + Dwfl_Frame_State *state = *statep; + if (state->unwound) + return have_unwound (statep); + Dwarf_Addr pc; + bool ok = INTUSE(dwfl_frame_state_pc) (state, &pc, NULL); + assert (ok); + /* Do not ask for MINUSONE dwfl_frame_state_pc, it would try to unwind STATE + which would deadlock us. */ + if (state != state->thread->unwound && ! state->signal_frame) + pc--; + Dwfl_Module *mod = INTUSE(dwfl_addrmodule) (state->thread->process->dwfl, pc); + if (mod != NULL) + { + Dwarf_Addr bias; + Dwarf_CFI *cfi_eh = INTUSE(dwfl_module_eh_cfi) (mod, &bias); + if (cfi_eh) + { + if (handle_cfi (statep, pc - bias, cfi_eh)) + return true; + if (state->unwound) + { + assert (state->unwound->pc_state == DWFL_FRAME_STATE_ERROR); + return false; + } + } + Dwarf_CFI *cfi_dwarf = INTUSE(dwfl_module_dwarf_cfi) (mod, &bias); + if (cfi_dwarf) + { + if (handle_cfi (statep, pc - bias, cfi_dwarf) && state->unwound) + return true; + if (state->unwound) + { + assert (state->unwound->pc_state == DWFL_FRAME_STATE_ERROR); + return false; + } + } + } + __libdwfl_seterrno (DWFL_E_NO_DWARF); + return false; +} +INTDEF(dwfl_frame_unwind) diff --git a/libdwfl/libdwfl.h b/libdwfl/libdwfl.h index 2b70e28..8998886 100644 --- a/libdwfl/libdwfl.h +++ b/libdwfl/libdwfl.h @@ -41,6 +41,10 @@ typedef struct Dwfl_Module Dwfl_Module; /* Handle describing a line record. */ typedef struct Dwfl_Line Dwfl_Line;
+/* This holds everything we know about the state of the frame at a particular + PC location described by an FDE. */ +typedef struct Dwfl_Frame_State Dwfl_Frame_State; + /* Callbacks. */ typedef struct { @@ -564,6 +568,46 @@ extern int dwfl_module_register_names (Dwfl_Module *mod, extern Dwarf_CFI *dwfl_module_dwarf_cfi (Dwfl_Module *mod, Dwarf_Addr *bias); extern Dwarf_CFI *dwfl_module_eh_cfi (Dwfl_Module *mod, Dwarf_Addr *bias);
+/* Get innermost frame of first thread of live process PID. Returns NULL on + failure. */ +extern Dwfl_Frame_State *dwfl_frame_state_pid (Dwfl *dwfl, pid_t pid); + +/* Get innermost frame of first thread of core file COREFILE. Returns NULL on + failure. */ +extern Dwfl_Frame_State *dwfl_frame_state_core (Dwfl *dwfl, + const char *corefile); + +/* Fetch inferior registers from a caller supplied storage. */ +typedef bool dwfl_frame_memory_read_t (Dwarf_Addr addr, Dwarf_Addr *result, + void *user_data); +extern Dwfl_Frame_State *dwfl_frame_state_data (Dwfl *dwfl, bool pc_set, + Dwarf_Addr pc, unsigned nregs, + const uint64_t *regs_set, + const Dwarf_Addr *regs, + dwfl_frame_memory_read_t + *memory_read, + void *memory_read_user_data); + +/* Return TRUE and update *STATEP for the unwound frame for successful unwind. + Return TRUE and set *STATEP to NULL for the outermost frame. Return FALSE + (and call __libdwfl_seterrno) otherwise. */ +extern bool dwfl_frame_unwind (Dwfl_Frame_State **statep); + +/* Get return address register value for frame. Return TRUE if *PC set and + optionally *MINUSONE is also set, if MINUSONE is not NULL. Return FALSE + (and call __libdw_seterrno) otherwise. *MINUSONE is TRUE for normal calls + where *PC should be decremented by one to get the call instruction, it is + FALSE if this frame was interrupted by a signal handler. */ +extern bool dwfl_frame_state_pc (Dwfl_Frame_State *state, Dwarf_Addr *pc, + bool *minusone); + +/* Get innermost frame of the next thread from STATE. STATE can be any frame + of (the previous) thread. */ +extern Dwfl_Frame_State *dwfl_frame_thread_next (Dwfl_Frame_State *state); + +/* Get Task ID of the thread of STATE. This is PID for the thread started by + function main and gettid () for the other threads. */ +extern pid_t dwfl_frame_tid_get (Dwfl_Frame_State *state);
#ifdef __cplusplus } diff --git a/libdwfl/libdwflP.h b/libdwfl/libdwflP.h index 69e6e12..fff621d 100644 --- a/libdwfl/libdwflP.h +++ b/libdwfl/libdwflP.h @@ -41,6 +41,10 @@ #include <string.h>
#include "../libdw/libdwP.h" /* We need its INTDECLs. */ +#include "libeblP.h" + +typedef struct Dwfl_Frame_State_Process Dwfl_Frame_State_Process; +typedef struct Dwfl_Frame_State_Thread Dwfl_Frame_State_Thread;
/* gettext helper macros. */ #define _(Str) dgettext ("elfutils", Str) @@ -74,7 +78,16 @@ DWFL_ERROR (BADELF, N_("not a valid ELF file")) \ DWFL_ERROR (WEIRD_TYPE, N_("cannot handle DWARF type description")) \ DWFL_ERROR (WRONG_ID_ELF, N_("ELF file does not match build ID")) \ - DWFL_ERROR (BAD_PRELINK, N_("corrupt .gnu.prelink_undo section data")) + DWFL_ERROR (BAD_PRELINK, N_("corrupt .gnu.prelink_undo section data")) \ + DWFL_ERROR (LIBEBL_BAD, N_("Internal error due to ebl")) \ + DWFL_ERROR (CORE_MISSING, N_("Missing data in core file")) \ + DWFL_ERROR (INVALID_REGISTER, N_("Invalid register")) \ + DWFL_ERROR (PROCESS_MEMORY_READ, N_("Error reading process memory")) \ + DWFL_ERROR (PROCESS_NO_ARCH, N_("Have not found ELF module in a process")) \ + DWFL_ERROR (PARSE_PROC, N_("Error parsing /proc filesystem")) \ + DWFL_ERROR (NO_THREAD, N_("No thread found")) \ + DWFL_ERROR (INVALID_DWARF, N_("Invalid DWARF")) \ + DWFL_ERROR (UNSUPPORTED_DWARF, N_("Unsupported DWARF"))
#define DWFL_ERROR(name, text) DWFL_E_##name, typedef enum { DWFL_ERRORS DWFL_E_NUM } Dwfl_Error; @@ -92,6 +105,8 @@ struct Dwfl
Dwfl_Module *modulelist; /* List in order used by full traversals. */
+ Dwfl_Frame_State_Process *framestatelist; + GElf_Addr offline_next_address;
GElf_Addr segment_align; /* Smallest granularity of segments. */ @@ -189,7 +204,102 @@ struct Dwfl_Module bool gc; /* Mark/sweep flag. */ };
+/* This holds information common for all the threads/tasks/TIDs of one process + for backtraces. */ + +struct Dwfl_Frame_State_Process +{ + Dwfl_Frame_State_Process *next; + struct Dwfl *dwfl; + struct ebl *ebl; + /* If it is false we share EBL with one of DWFL's Dwfl_Module->ebl. */ + bool ebl_close : 1; + dwfl_frame_memory_read_t *memory_read; + void *memory_read_user_data; + /* If there is no core file both CORE is NULL and CORE_FD is -1. */ + Elf *core; + int core_fd; + Dwfl_Frame_State_Thread *thread; +}; + +/* This holds information common for all the frames of one backtrace for + a partical thread/task/TID. */ + +struct Dwfl_Frame_State_Thread +{ + Dwfl_Frame_State_Process *process; + Dwfl_Frame_State_Thread *next; + /* If there is no TID it is 0. */ + pid_t tid; + bool tid_attached : 1; + /* Bottom frame. */ + Dwfl_Frame_State *unwound; +}; + +/* See its typedef in libdwfl.h. */ + +struct Dwfl_Frame_State +{ + Dwfl_Frame_State_Thread *thread; + /* Previous (outer) frame. */ + Dwfl_Frame_State *unwound; + bool signal_frame : 1; + enum + { + /* This structure is still being initialized or there was an error + initializing it. */ + DWFL_FRAME_STATE_ERROR, + /* PC field is valid. */ + DWFL_FRAME_STATE_PC_SET, + /* PC field is undefined, this means the next (inner) frame was the + outermost frame. */ + DWFL_FRAME_STATE_PC_UNDEFINED + } pc_state; + /* Either initialized from appropriate REGS element or on some archs + initialized separately as the return address has no DWARF register. */ + Dwarf_Addr pc; + /* (1 << X) bitmask where 0 <= X < ebl_frame_state_nregs. */ + uint64_t regs_set[3]; + /* REGS array size is ebl_frame_state_nregs. */ + Dwarf_Addr regs[]; +}; + +/* Fetch value from Dwfl_Frame_State->regs indexed by DWARF REGNO. + No error code is set if the function returns FALSE. */ + +static inline bool +dwfl_frame_state_reg_get (Dwfl_Frame_State *state, unsigned regno, + Dwarf_Addr *val) +{ + Ebl *ebl = state->thread->process->ebl; + if (regno >= ebl->frame_state_nregs) + return false; + if ((state->regs_set[regno / sizeof (*state->regs_set) / 8] + & (1U << (regno % (sizeof (*state->regs_set) * 8)))) == 0) + return false; + if (val) + *val = state->regs[regno]; + return true; +} + +/* Store value to Dwfl_Frame_State->regs indexed by DWARF REGNO. + No error code is set if the function returns FALSE. */
+static inline bool +dwfl_frame_state_reg_set (Dwfl_Frame_State *state, unsigned regno, + Dwarf_Addr val) +{ + Ebl *ebl = state->thread->process->ebl; + if (regno >= ebl->frame_state_nregs) + return false; + /* For example i386 user_regs_struct has signed fields. */ + if (ebl->class == ELFCLASS32) + val &= 0xffffffff; + state->regs_set[regno / sizeof (*state->regs_set) / 8] |= + (1U << (regno % (sizeof (*state->regs_set) * 8))); + state->regs[regno] = val; + return true; +}
/* Information cached about each CU in Dwfl_Module.dw. */ struct dwfl_cu @@ -404,6 +514,38 @@ extern Dwfl_Module *__libdwfl_report_offline (Dwfl *dwfl, const char *name, const char *)) internal_function;
+/* Set STATE->pc_set from STATE->regs according to the backend. Return true on + success, false on error. */ +extern bool __libdwfl_state_fetch_pc (Dwfl_Frame_State *state) + internal_function; + +/* Free and unlink THREAD from the internal lists. */ +extern void __libdwfl_thread_free (Dwfl_Frame_State_Thread *thread) + internal_function; + +/* Allocate new Dwfl_Frame_State_Thread for PID and link it to PROCESS. + Automatically create and link in also the first Dwfl_Frame_State. */ +extern Dwfl_Frame_State_Thread * + __libdwfl_thread_alloc (Dwfl_Frame_State_Process *process, pid_t tid) + internal_function; + +/* Free PROCESS. Unlink and free also any structures it references. */ +extern void __libdwfl_process_free (Dwfl_Frame_State_Process *process) + internal_function; + +/* Allocate new Dwfl_Frame_State_Process for DWFL with callback MEMORY_READ + (which is passed MEMORY_READ_USER_DATA). */ +extern Dwfl_Frame_State_Process * + __libdwfl_process_alloc (Dwfl *dwfl, dwfl_frame_memory_read_t *memory_read, + void *memory_read_user_data) + internal_function; + +/* Align segment START downwards or END upwards addresses according to DWFL. */ +extern GElf_Addr __libdwfl_segment_start (Dwfl *dwfl, GElf_Addr start) + internal_function; +extern GElf_Addr __libdwfl_segment_end (Dwfl *dwfl, GElf_Addr end) + internal_function; + /* Decompression wrappers: decompress whole file into memory. */ extern Dwfl_Error __libdw_gunzip (int fd, off64_t start_offset, void *mapped, size_t mapped_size, @@ -538,6 +680,10 @@ INTDECL (dwfl_offline_section_address) INTDECL (dwfl_module_relocate_address) INTDECL (dwfl_module_dwarf_cfi) INTDECL (dwfl_module_eh_cfi) +INTDECL (dwfl_frame_state_pid) +INTDECL (dwfl_frame_state_core) +INTDECL (dwfl_frame_unwind) +INTDECL (dwfl_frame_state_pc)
/* Leading arguments standard to callbacks passed a Dwfl_Module. */ #define MODCB_ARGS(mod) (mod), &(mod)->userdata, (mod)->name, (mod)->low_addr diff --git a/libdwfl/segment.c b/libdwfl/segment.c index 496b4fd..3a2f349 100644 --- a/libdwfl/segment.c +++ b/libdwfl/segment.c @@ -28,16 +28,18 @@
#include "libdwflP.h"
-static GElf_Addr -segment_start (Dwfl *dwfl, GElf_Addr start) +GElf_Addr +internal_function +__libdwfl_segment_start (Dwfl *dwfl, GElf_Addr start) { if (dwfl->segment_align > 1) start &= -dwfl->segment_align; return start; }
-static GElf_Addr -segment_end (Dwfl *dwfl, GElf_Addr end) +GElf_Addr +internal_function +__libdwfl_segment_end (Dwfl *dwfl, GElf_Addr end) { if (dwfl->segment_align > 1) end = (end + dwfl->segment_align - 1) & -dwfl->segment_align; @@ -156,8 +158,8 @@ reify_segments (Dwfl *dwfl) for (Dwfl_Module *mod = dwfl->modulelist; mod != NULL; mod = mod->next) if (! mod->gc) { - const GElf_Addr start = segment_start (dwfl, mod->low_addr); - const GElf_Addr end = segment_end (dwfl, mod->high_addr); + const GElf_Addr start = __libdwfl_segment_start (dwfl, mod->low_addr); + const GElf_Addr end = __libdwfl_segment_end (dwfl, mod->high_addr); bool resized = false;
int idx = lookup (dwfl, start, hint); @@ -296,8 +298,9 @@ dwfl_report_segment (Dwfl *dwfl, int ndx, const GElf_Phdr *phdr, GElf_Addr bias, dwfl->lookup_module = NULL; }
- GElf_Addr start = segment_start (dwfl, bias + phdr->p_vaddr); - GElf_Addr end = segment_end (dwfl, bias + phdr->p_vaddr + phdr->p_memsz); + GElf_Addr start = __libdwfl_segment_start (dwfl, bias + phdr->p_vaddr); + GElf_Addr end = __libdwfl_segment_end (dwfl, + bias + phdr->p_vaddr + phdr->p_memsz);
/* Coalesce into the last one if contiguous and matching. */ if (ndx != dwfl->lookup_tail_ndx diff --git a/libebl/Makefile.am b/libebl/Makefile.am index 4d62fad..a3186bb 100644 --- a/libebl/Makefile.am +++ b/libebl/Makefile.am @@ -29,7 +29,8 @@ ## include $(top_srcdir)/config/eu.am AM_CFLAGS += -fpic -AM_CPPFLAGS += -I$(srcdir)/../libelf -I$(srcdir)/../libdw -I$(srcdir)/../libasm +AM_CPPFLAGS += -I$(srcdir)/../libelf -I$(srcdir)/../libdw -I$(srcdir)/../libasm \ + -I$(srcdir)/../libdwfl VERSION = 1 LIBEBL_SUBDIR = @LIBEBL_SUBDIR@
@@ -54,7 +55,7 @@ gen_SOURCES = eblopenbackend.c eblclosebackend.c eblstrtab.c \ eblreginfo.c eblnonerelocp.c eblrelativerelocp.c \ eblsysvhashentrysize.c eblauxvinfo.c eblcheckobjattr.c \ ebl_check_special_section.c ebl_syscall_abi.c eblabicfi.c \ - eblstother.c + eblstother.c eblframestate.c
libebl_a_SOURCES = $(gen_SOURCES)
diff --git a/libebl/ebl-hooks.h b/libebl/ebl-hooks.h index d3cf3e6..b7e1499 100644 --- a/libebl/ebl-hooks.h +++ b/libebl/ebl-hooks.h @@ -155,5 +155,14 @@ int EBLHOOK(disasm) (const uint8_t **startp, const uint8_t *end, Function returns 0 on success and -1 on error. */ int EBLHOOK(abi_cfi) (Ebl *ebl, Dwarf_CIE *abi_info);
+/* *SYM must be STT_FUNC. Then if it describes a function descriptor (PPC64) + convert in-place its data and return a possibly different new name for it. + The name is valid as long as EBL is valid. */ +const char *EBLHOOK(get_func_pc) (Ebl *ebl, struct Dwfl_Module *mod, + GElf_Sym *sym); + +/* Fetch process data from STATE->base->pid or STATE->base->core. */ +bool EBLHOOK(frame_state) (struct Dwfl_Frame_State *state); + /* Destructor for ELF backend handle. */ void EBLHOOK(destr) (struct ebl *); diff --git a/libebl/eblframestate.c b/libebl/eblframestate.c new file mode 100644 index 0000000..a7cc13c --- /dev/null +++ b/libebl/eblframestate.c @@ -0,0 +1,52 @@ +/* Fetch live process Dwfl_Frame_State from PID. + Copyright (C) 2012 Red Hat, Inc. + This file is part of elfutils. + + This file is free software; you can redistribute it and/or modify + it under the terms of either + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at + your option) any later version + + or + + * the GNU General Public License as published by the Free + Software Foundation; either version 2 of the License, or (at + your option) any later version + + or both in parallel, as here. + + elfutils is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received copies of the GNU General Public License and + the GNU Lesser General Public License along with this program. If + not, see http://www.gnu.org/licenses/. */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include <libeblP.h> +#include <assert.h> +#include "libdwflP.h" + +bool +ebl_frame_state (Dwfl_Frame_State *state) +{ + Dwfl_Frame_State_Process *process = state->thread->process; + Ebl *ebl = process->ebl; + /* Otherwise caller could not allocate STATE of proper size. If FRAME_STATE + is unsupported then FRAME_STATE_NREGS is zero. */ + assert (ebl->frame_state != NULL); + return ebl->frame_state (state); +} + +size_t +ebl_frame_state_nregs (Ebl *ebl) +{ + return ebl == NULL ? 0 : ebl->frame_state_nregs; +} diff --git a/libebl/libebl.h b/libebl/libebl.h index cae31c9..09b7bfb 100644 --- a/libebl/libebl.h +++ b/libebl/libebl.h @@ -378,6 +378,20 @@ extern int ebl_auxv_info (Ebl *ebl, GElf_Xword a_type, const char **name, const char **format) __nonnull_attribute__ (1, 3, 4);
+/* Convert function descriptor SYM to the function PC value in-place. */ +struct Dwfl_Module; +extern const char *ebl_get_func_pc (Ebl *ebl, struct Dwfl_Module *mod, + GElf_Sym *sym) + __nonnull_attribute__ (1, 2, 3); + +/* Fetch process data from STATE->base->pid or STATE->base->core. */ +struct Dwfl_Frame_State; +extern bool ebl_frame_state (struct Dwfl_Frame_State *state) + __nonnull_attribute__ (1); + +/* Number of registers to allocate for STATE of ebl_frame_state. */ +extern size_t ebl_frame_state_nregs (Ebl *ebl) + __nonnull_attribute__ (1);
#ifdef __cplusplus } diff --git a/libebl/libeblP.h b/libebl/libeblP.h index 5ec26a4..d553c8c 100644 --- a/libebl/libeblP.h +++ b/libebl/libeblP.h @@ -60,6 +60,10 @@ struct ebl /* Size of entry in Sysv-style hash table. */ int sysvhash_entrysize;
+ /* Number of Dwarf_Frame_State->regs entries to allocate for frame_state + above. */ + size_t frame_state_nregs; + /* Internal data. */ void *dlhandle; }; diff --git a/m4/biarch.m4 b/m4/biarch.m4 new file mode 100644 index 0000000..a15323e --- /dev/null +++ b/m4/biarch.m4 @@ -0,0 +1,45 @@ +AC_DEFUN([utrace_CC_m32], [dnl +AC_CACHE_CHECK([$CC option for 32-bit word size], utrace_cv_CC_m32, [dnl +save_CC="$CC" +utrace_cv_CC_m32=none +for ut_try in -m32 -m31; do + [CC=`echo "$save_CC" | sed 's/ -m[36][241]//'`" $ut_try"] + AC_COMPILE_IFELSE([AC_LANG_SOURCE([[int foo (void) { return 1; }]])], + [utrace_cv_CC_m32=$ut_try]) + test x$utrace_cv_CC_m32 = xnone || break +done +CC="$save_CC"])]) + +AC_DEFUN([utrace_HOST64], [AC_REQUIRE([utrace_CC_m32]) +AS_IF([test x$utrace_cv_CC_m32 != xnone], [dnl +AC_CACHE_CHECK([for 64-bit host], utrace_cv_host64, [dnl +AC_EGREP_CPP([@utrace_host64@], [#include <stdint.h> +#if (UINTPTR_MAX > 0xffffffffUL) +@utrace_host64@ +#endif], + utrace_cv_host64=yes, utrace_cv_host64=no)]) +AS_IF([test $utrace_cv_host64 = no], + [utrace_biarch=-m64 utrace_thisarch=$utrace_cv_CC_m32], + [utrace_biarch=$utrace_cv_CC_m32 utrace_thisarch=-m64]) + +biarch_CC=`echo "$CC" | sed "s/ *${utrace_thisarch}//"` +biarch_CC="$biarch_CC $utrace_biarch"])]) + +AC_DEFUN([utrace_BIARCH], [AC_REQUIRE([utrace_HOST64]) +utrace_biarch_forced=no +AC_ARG_WITH([biarch], + AC_HELP_STRING([--with-biarch], + [enable biarch tests despite build problems]), + [AS_IF([test "x$with_biarch" != xno], [utrace_biarch_forced=yes])]) +AS_IF([test $utrace_biarch_forced = yes], [dnl +utrace_cv_cc_biarch=yes +AC_MSG_NOTICE([enabling biarch tests regardless using $biarch_CC])], [dnl +AS_IF([test x$utrace_cv_CC_m32 != xnone], [dnl +AC_CACHE_CHECK([whether $biarch_CC makes executables we can run], + utrace_cv_cc_biarch, [dnl +save_CC="$CC" +CC="$biarch_CC" +AC_RUN_IFELSE([AC_LANG_PROGRAM([], [])], + utrace_cv_cc_biarch=yes, utrace_cv_cc_biarch=no) +CC="$save_CC"])], [utrace_cv_cc_biarch=no])]) +AM_CONDITIONAL(BIARCH, [test $utrace_cv_cc_biarch = yes])]) diff --git a/src/Makefile.am b/src/Makefile.am index 674846d..1f2041f 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -37,7 +37,7 @@ native_ld = @native_ld@ base_cpu = @base_cpu@
bin_PROGRAMS = readelf nm size strip ld elflint findtextrel addr2line \ - elfcmp objdump ranlib strings ar unstrip + elfcmp objdump ranlib strings ar unstrip stack
ld_dsos = libld_elf_i386_pic.a @@ -115,6 +115,7 @@ ranlib_LDADD = libar.a $(libelf) $(libeu) $(libmudflap) strings_LDADD = $(libelf) $(libeu) $(libmudflap) ar_LDADD = libar.a $(libelf) $(libeu) $(libmudflap) unstrip_LDADD = $(libebl) $(libelf) $(libdw) $(libeu) $(libmudflap) -ldl +stack_LDADD = $(libebl) $(libelf) $(libdw) $(libeu) $(libmudflap) -ldl
ldlex.o: ldscript.c ldlex_no_Werror = yes diff --git a/src/stack.c b/src/stack.c new file mode 100644 index 0000000..dd7be04 --- /dev/null +++ b/src/stack.c @@ -0,0 +1,145 @@ +/* Unwinding of frames like gstack/pstack. + Copyright (C) 2012 Red Hat, Inc. + This file is part of elfutils. + + This file is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + elfutils is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see http://www.gnu.org/licenses/. */ + +#include <config.h> +#include <assert.h> +#include <argp.h> +#include <error.h> +#include <stdlib.h> +#include <inttypes.h> +#include <stdio.h> +#include <stdio_ext.h> +#include <locale.h> +#include ELFUTILS_HEADER(dwfl) + +/* libdwfl/argp-std.c */ +#define OPT_COREFILE 0x101 + +static void +report_pid (Dwfl *dwfl, pid_t pid) +{ + int result = dwfl_linux_proc_report (dwfl, pid); + if (result < 0) + error (2, 0, "dwfl_linux_proc_report: %s", dwfl_errmsg (-1)); + else if (result > 0) + error (2, result, "dwfl_linux_proc_report"); + + if (dwfl_report_end (dwfl, NULL, NULL) != 0) + error (2, 0, "dwfl_report_end: %s", dwfl_errmsg (-1)); +} + +static void +dump (Dwfl *dwfl, pid_t pid, const char *corefile) +{ + if (pid) + report_pid (dwfl, pid); + Dwfl_Frame_State *state; + if (pid) + state = dwfl_frame_state_pid (dwfl, pid); + else if (corefile) + state = dwfl_frame_state_core (dwfl, corefile); + else + abort (); + if (state == NULL) + error (2, 0, "dwfl_frame_state: %s", dwfl_errmsg (-1)); + do + { + Dwfl_Frame_State *thread = state; + pid_t tid = dwfl_frame_tid_get (thread); + printf ("TID %ld:\n", (long) tid); + unsigned frameno; + for (frameno = 0; state; frameno++) + { + Dwarf_Addr pc; + bool minusone; + if (! dwfl_frame_state_pc (state, &pc, &minusone)) + { + fprintf (stderr, "%s\n", dwfl_errmsg (-1)); + break; + } + Dwarf_Addr pc_adjusted = pc - (minusone ? 1 : 0); + + /* Get PC->SYMNAME. */ + Dwfl_Module *mod = dwfl_addrmodule (dwfl, pc_adjusted); + const char *symname = NULL; + if (mod) + symname = dwfl_module_addrname (mod, pc_adjusted); + + printf ("#%2u %#" PRIx64 "%4s\t%s\n", frameno, (uint64_t) pc, + minusone ? "- 1" : "", symname); + if (! dwfl_frame_unwind (&state)) + { + fprintf (stderr, "%s\n", dwfl_errmsg (-1)); + break; + } + } + state = dwfl_frame_thread_next (thread); + } + while (state); + + dwfl_end (dwfl); +} + +static argp_parser_t parse_opt_orig; +static pid_t pid; +static const char *corefile; + +static error_t +parse_opt (int key, char *arg, struct argp_state *state) +{ + switch (key) + { + case 'p': + pid = atoi (arg); + break; + case OPT_COREFILE: + corefile = arg; + break; + } + return parse_opt_orig (key, arg, state); +} + +int +main (int argc, char **argv) +{ + /* We use no threads here which can interfere with handling a stream. */ + __fsetlocking (stdin, FSETLOCKING_BYCALLER); + __fsetlocking (stdout, FSETLOCKING_BYCALLER); + __fsetlocking (stderr, FSETLOCKING_BYCALLER); + + /* Set locale. */ + (void) setlocale (LC_ALL, ""); + + struct argp argp = *dwfl_standard_argp (); + parse_opt_orig = argp.parser; + argp.parser = parse_opt; + int remaining; + Dwfl *dwfl = NULL; + argp_parse (&argp, argc, argv, 0, &remaining, &dwfl); + assert (dwfl != NULL); + assert (remaining == argc); + + if (pid && !corefile) + dump (dwfl, pid, NULL); + else if (corefile && !pid) + dump (dwfl, 0, corefile); + else + error (2, 0, "eu-stack [--debuginfo-path=<path>] {-p <process id>|" + "--core=<file> [--executable=<file>]|--help}"); + + return 0; +} diff --git a/tests/Makefile.am b/tests/Makefile.am index 2d819c5..647b002 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -52,10 +52,24 @@ check_PROGRAMS = arextract arsymtest newfile saridx scnnames sectiondump \ test-flag-nobits dwarf-getstring rerequest_tag \ alldts md5-sha1-test typeiter low_high_pc \ test-elf_cntl_gelf_getshdr dwflsyms dwfllines \ - dwfl-report-elf-align + dwfl-report-elf-align backtrace backtrace-child \ + backtrace-data asm_TESTS = asm-tst1 asm-tst2 asm-tst3 asm-tst4 asm-tst5 \ asm-tst6 asm-tst7 asm-tst8 asm-tst9
+BUILT_SOURCES = backtrace-child-biarch + +clean-local: + $(RM) backtrace-child-biarch + +# Substitute $(COMPILE). +backtrace-child-biarch: backtrace-child.c + $(CC_BIARCH) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \ + $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) $(backtrace_child_CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) $(backtrace_child_LDFLAGS) \ + -o $@ $< + TESTS = run-arextract.sh run-arsymtest.sh newfile test-nlist \ update1 update2 update3 update4 \ run-show-die-info.sh run-get-files.sh run-get-lines.sh \ @@ -88,7 +102,7 @@ TESTS = run-arextract.sh run-arsymtest.sh newfile test-nlist \ run-low_high_pc.sh run-macro-test.sh run-elf_cntl_gelf_getshdr.sh \ run-test-archive64.sh run-readelf-vmcoreinfo.sh \ run-readelf-mixed-corenote.sh run-dwfllines.sh \ - run-dwfl-report-elf-align.sh + run-dwfl-report-elf-align.sh run-backtrace.sh
if !STANDALONE check_PROGRAMS += msg_tst md5-sha1-test @@ -200,7 +214,7 @@ EXTRA_DIST = run-arextract.sh run-arsymtest.sh \ run-dwfllines.sh run-dwfl-report-elf-align.sh \ testfile-dwfl-report-elf-align-shlib.so.bz2 \ testfilenolines test-core-lib.so.bz2 test-core.core.bz2 \ - test-core.exec.bz2 + test-core.exec.bz2 run-backtrace.sh
if USE_VALGRIND valgrind_cmd='valgrind -q --trace-children=yes --error-exitcode=1 --run-libc-freeres=no' @@ -325,6 +339,10 @@ test_elf_cntl_gelf_getshdr_LDADD = $(libelf) $(libmudflap) dwflsyms_LDADD = $(libdw) $(libelf) $(libmudflap) dwfllines_LDADD = $(libdw) $(libelf) $(libmudflap) dwfl_report_elf_align_LDADD = $(libdw) $(libmudflap) +backtrace_LDADD = $(libdw) $(libelf) $(libmudflap) +backtrace_child_CFLAGS = -fPIE +backtrace_child_LDFLAGS = -pie -pthread +backtrace_data_LDADD = $(libdw) $(libelf) $(libmudflap)
if GCOV check: check-am coverage diff --git a/tests/backtrace-child.c b/tests/backtrace-child.c new file mode 100644 index 0000000..68ea3a7 --- /dev/null +++ b/tests/backtrace-child.c @@ -0,0 +1,147 @@ +/* Test child for parent backtrace test. + Copyright (C) 2012 Red Hat, Inc. + This file is part of elfutils. + + This file is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + elfutils is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see http://www.gnu.org/licenses/. */ + +#include <config.h> +#include <assert.h> +#include <stdlib.h> +#include <signal.h> +#include <errno.h> +#include <sys/ptrace.h> +#include <string.h> +#include <pthread.h> +#include <stdio.h> +#include <unistd.h> + +static int ptraceme, gencore; + +/* Execution will arrive here from jmp by an artificial ptrace-spawn signal. */ + +static void +sigusr2 (int signo) +{ + assert (signo == SIGUSR2); + if (! gencore) + raise (SIGUSR1); + + /* Catch the .plt jump, it will come from this abort call. */ + abort (); +} + +static __attribute__ ((noinline, noclone)) void +dummy1 (void) +{ + asm volatile (""); +} + +#ifdef __x86_64__ +static __attribute__ ((noinline, noclone, used)) void +jmp (void) +{ + /* Not reached, signal will get ptrace-spawn to jump into sigusr2. */ + abort (); +} +#endif + +static __attribute__ ((noinline, noclone)) void +dummy2 (void) +{ + asm volatile (""); +} + +static __attribute__ ((noinline, noclone, noreturn)) void +stdarg (int f __attribute__ ((unused)), ...) +{ + sighandler_t sigusr2_orig = signal (SIGUSR2, sigusr2); + assert (sigusr2_orig == SIG_DFL); + errno = 0; + if (ptraceme) + { + long l = ptrace (PTRACE_TRACEME, 0, NULL, NULL); + assert_perror (errno); + assert (l == 0); + } +#ifdef __x86_64__ + if (! gencore) + { + /* Execution will get PC patched into function jmp. */ + raise (SIGUSR1); + } +#endif + sigusr2 (SIGUSR2); + abort (); +} + +static __attribute__ ((noinline, noclone)) void +dummy3 (void) +{ + asm volatile (""); +} + +static __attribute__ ((noinline, noclone)) void +backtracegen (void) +{ + stdarg (1); + /* Here should be no instruction after the stdarg call as it is noreturn + function. It must be stdarg so that it is a call and not jump (jump as + a tail-call). */ +} + +static __attribute__ ((noinline, noclone)) void +dummy4 (void) +{ + asm volatile (""); +} + +static void * +start (void *arg __attribute__ ((unused))) +{ + backtracegen (); + abort (); +} + +int +main (int argc __attribute__ ((unused)), char **argv) +{ + assert (*argv++); + ptraceme = (*argv && strcmp (*argv, "--ptraceme") == 0); + argv += ptraceme; + gencore = (*argv && strcmp (*argv, "--gencore") == 0); + argv += gencore; + assert (*argv && strcmp (*argv, "--run") == 0); + dummy1 (); + dummy2 (); + dummy3 (); + dummy4 (); + if (gencore) + printf ("%ld\n", (long) getpid ()); + errno = 0; + pthread_t thread; + int i = pthread_create (&thread, NULL, start, NULL); + assert_perror (errno); + assert (i == 0); + if (ptraceme) + { + long l = ptrace (PTRACE_TRACEME, 0, NULL, NULL); + assert_perror (errno); + assert (l == 0); + } + if (gencore) + pthread_join (thread, NULL); + else + raise (SIGUSR2); + abort (); +} diff --git a/tests/backtrace-data.c b/tests/backtrace-data.c new file mode 100644 index 0000000..e2c1a80 --- /dev/null +++ b/tests/backtrace-data.c @@ -0,0 +1,255 @@ +/* Test program for unwinding of frames. + Copyright (C) 2012 Red Hat, Inc. + This file is part of elfutils. + + This file is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + elfutils is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see http://www.gnu.org/licenses/. */ + +#include <config.h> +#include <assert.h> +#include <inttypes.h> +#include <stdio.h> +#include <stdio_ext.h> +#include <locale.h> +#include <dirent.h> +#include <stdlib.h> +#include <errno.h> +#include <error.h> +#include <unistd.h> +#include <dwarf.h> +#include <sys/resource.h> +#include <sys/ptrace.h> +#include <signal.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/user.h> +#include <fcntl.h> +#include <string.h> +#include ELFUTILS_HEADER(dwfl) + +#ifndef __x86_64__ + +int +main (void) +{ + return 77; +} + +#else /* __x86_64__ */ + +static int +find_elf (Dwfl_Module *mod __attribute__ ((unused)), + void **userdata __attribute__ ((unused)), + const char *modname __attribute__ ((unused)), + Dwarf_Addr base __attribute__ ((unused)), + char **file_name __attribute__ ((unused)), + Elf **elfp __attribute__ ((unused))) +{ + /* Not used as modules are reported explicitly. */ + assert (0); +} + +static bool +memory_read (Dwarf_Addr addr, Dwarf_Addr *result, void *user_data) +{ + pid_t child = (uintptr_t) user_data; + + errno = 0; + long l = ptrace (PTRACE_PEEKDATA, child, (void *) (uintptr_t) addr, NULL); + assert_perror (errno); + *result = l; + + /* We could also return false for failed ptrace. */ + return true; +} + +/* Return filename and VMA address *BASEP where its mapping starts which + contains ADDR. */ + +static char * +maps_lookup (pid_t pid, Dwarf_Addr addr, GElf_Addr *basep) +{ + char *fname; + int i = asprintf (&fname, "/proc/%ld/maps", (long) pid); + assert_perror (errno); + assert (i > 0); + FILE *f = fopen (fname, "r"); + assert_perror (errno); + assert (f); + free (fname); + for (;;) + { + // 37e3c22000-37e3c23000 rw-p 00022000 00:11 49532 /lib64/ld-2.14.90.so */ + unsigned long start, end, offset; + i = fscanf (f, "%lx-%lx %*s %lx %*x:%*x %*x", &start, &end, &offset); + assert_perror (errno); + assert (i == 3); + char *filename = strdup (""); + assert (filename); + size_t filename_len = 0; + for (;;) + { + int c = fgetc (f); + assert (c != EOF); + if (c == '\n') + break; + if (c == ' ' && *filename == '\0') + continue; + filename = realloc (filename, filename_len + 2); + assert (filename); + filename[filename_len++] = c; + filename[filename_len] = '\0'; + } + if (start <= addr && addr < end) + { + i = fclose (f); + assert_perror (errno); + assert (i == 0); + + *basep = start - offset; + return filename; + } + free (filename); + } +} + +/* Add module containing ADDR to the DWFL address space. */ + +static void +report_module (Dwfl *dwfl, pid_t child, Dwarf_Addr addr) +{ + GElf_Addr base; + char *long_name = maps_lookup (child, addr, &base); + Dwfl_Module *mod = dwfl_report_elf (dwfl, long_name, long_name, -1, + base, false /* add_p_vaddr */); + assert (mod); + free (long_name); + assert (dwfl_addrmodule (dwfl, addr) == mod); +} + +int +main (int argc __attribute__ ((unused)), char **argv __attribute__ ((unused))) +{ + /* We use no threads here which can interfere with handling a stream. */ + __fsetlocking (stdin, FSETLOCKING_BYCALLER); + __fsetlocking (stdout, FSETLOCKING_BYCALLER); + __fsetlocking (stderr, FSETLOCKING_BYCALLER); + + /* Set locale. */ + (void) setlocale (LC_ALL, ""); + + pid_t child = fork (); + switch (child) + { + case -1: + assert_perror (errno); + assert (0); + case 0:; + long l = ptrace (PTRACE_TRACEME, 0, NULL, NULL); + assert_perror (errno); + assert (l == 0); + raise (SIGUSR1); + assert (0); + default: + break; + } + + int status; + pid_t pid = waitpid (child, &status, 0); + assert_perror (errno); + assert (pid == child); + assert (WIFSTOPPED (status)); + assert (WSTOPSIG (status) == SIGUSR1); + + struct user_regs_struct user_regs; + long l = ptrace (PTRACE_GETREGS, child, NULL, &user_regs); + assert_perror (errno); + assert (l == 0); + + const unsigned nregs = 17; + const uint64_t regs_set = (1U << nregs) - 1; + Dwarf_Addr regs[17]; + regs[0] = user_regs.rax; + regs[1] = user_regs.rdx; + regs[2] = user_regs.rcx; + regs[3] = user_regs.rbx; + regs[4] = user_regs.rsi; + regs[5] = user_regs.rdi; + regs[6] = user_regs.rbp; + regs[7] = user_regs.rsp; + regs[8] = user_regs.r8; + regs[9] = user_regs.r9; + regs[10] = user_regs.r10; + regs[11] = user_regs.r11; + regs[12] = user_regs.r12; + regs[13] = user_regs.r13; + regs[14] = user_regs.r14; + regs[15] = user_regs.r15; + regs[16] = user_regs.rip; + + /* x86_64 has PC contained in its CFI subset of DWARF register set so + elfutils will figure out the real PC value from REGS. */ + const bool pc_set = false; + Dwarf_Addr pc = 0; + + void *memory_read_user_data = (void *) (uintptr_t) child; + + static char *debuginfo_path; + static const Dwfl_Callbacks offline_callbacks = + { + .find_debuginfo = dwfl_standard_find_debuginfo, + .debuginfo_path = &debuginfo_path, + .section_address = dwfl_offline_section_address, + .find_elf = find_elf, + }; + Dwfl *dwfl = dwfl_begin (&offline_callbacks); + assert (dwfl); + + report_module (dwfl, child, user_regs.rip); + + Dwfl_Frame_State *state; + state = dwfl_frame_state_data (dwfl, pc_set, pc, nregs, ®s_set, regs, + memory_read, memory_read_user_data); + assert (state != NULL); + + /* Multiple threads are not handled here. */ + do + { + bool minusone; + if (! dwfl_frame_state_pc (state, &pc, &minusone)) + error (1, 0, "dwfl_frame_state_pc: %s", dwfl_errmsg (-1)); + Dwarf_Addr pc_adjusted = pc - (minusone ? 1 : 0); + + printf ("%#" PRIx64 "\n", (uint64_t) pc); + + Dwfl_Module *mod = dwfl_addrmodule (dwfl, pc_adjusted); + if (mod == NULL) + report_module (dwfl, child, pc_adjusted); + + if (! dwfl_frame_unwind (&state)) + error (1, 0, "dwfl_frame_unwind: %s", dwfl_errmsg (-1)); + } + while (state); + + dwfl_end (dwfl); + kill (child, SIGKILL); + pid = waitpid (child, &status, 0); + assert_perror (errno); + assert (pid == child); + assert (WIFSIGNALED (status)); + assert (WTERMSIG (status) == SIGKILL); + + return EXIT_SUCCESS; +} + +#endif /* x86_64 */ diff --git a/tests/backtrace.c b/tests/backtrace.c new file mode 100644 index 0000000..ca847cc --- /dev/null +++ b/tests/backtrace.c @@ -0,0 +1,513 @@ +/* Test program for unwinding of frames. + Copyright (C) 2012 Red Hat, Inc. + This file is part of elfutils. + + This file is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + elfutils is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see http://www.gnu.org/licenses/. */ + +#include <config.h> +#include <assert.h> +#include <inttypes.h> +#include <stdio.h> +#include <stdio_ext.h> +#include <locale.h> +#include <dirent.h> +#include <stdlib.h> +#include <errno.h> +#include <error.h> +#include <unistd.h> +#include <dwarf.h> +#include <sys/resource.h> +#include <sys/ptrace.h> +#include <signal.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/user.h> +#include <fcntl.h> +#include <string.h> +#include ELFUTILS_HEADER(dwfl) + +static void +report_pid (Dwfl *dwfl, pid_t pid) +{ + int result = dwfl_linux_proc_report (dwfl, pid); + if (result < 0) + error (2, 0, "dwfl_linux_proc_report: %s", dwfl_errmsg (-1)); + else if (result > 0) + error (2, result, "dwfl_linux_proc_report"); + + if (dwfl_report_end (dwfl, NULL, NULL) != 0) + error (2, 0, "dwfl_report_end: %s", dwfl_errmsg (-1)); +} + +static Dwfl * +dwfl_pid (pid_t pid) +{ + static char *debuginfo_path; + static const Dwfl_Callbacks proc_callbacks = + { + .find_debuginfo = dwfl_standard_find_debuginfo, + .debuginfo_path = &debuginfo_path, + + .find_elf = dwfl_linux_proc_find_elf, + }; + Dwfl *dwfl = dwfl_begin (&proc_callbacks); + if (dwfl == NULL) + error (2, 0, "dwfl_begin: %s", dwfl_errmsg (-1)); + report_pid (dwfl, pid); + return dwfl; +} + +static const char *executable; + +static int +find_elf (Dwfl_Module *mod, void **userdata, const char *modname, + Dwarf_Addr base, char **file_name, Elf **elfp) +{ + if (executable && modname != NULL + && (strcmp (modname, "[exe]") == 0 || strcmp (modname, "[pie]") == 0)) + { + char *executable_dup = strdup (executable); + if (executable_dup) + { + free (*file_name); + *file_name = executable_dup; + return -1; + } + } + return dwfl_build_id_find_elf (mod, userdata, modname, base, file_name, elfp); +} + +static Dwfl * +dwfl_offline (void) +{ + static char *debuginfo_path; + static const Dwfl_Callbacks offline_callbacks = + { + .find_debuginfo = dwfl_standard_find_debuginfo, + .debuginfo_path = &debuginfo_path, + + .section_address = dwfl_offline_section_address, + + /* We use this table for core files too. */ + .find_elf = find_elf, + }; + Dwfl *dwfl = dwfl_begin (&offline_callbacks); + if (dwfl == NULL) + error (2, 0, "dwfl_begin: %s", dwfl_errmsg (-1)); + return dwfl; +} + +static Dwfl * +dwfl_core (const char *corefile) +{ + Dwfl *dwfl = dwfl_offline (); + Elf *elf; + int fd; + + fd = open64 (corefile, O_RDONLY); + if (fd == -1) + error (2, 0, "open64: %m"); + elf = elf_begin (fd, ELF_C_READ_MMAP, NULL); + if (elf == NULL) + error (2, 0, "elf_begin: %s", elf_errmsg (-1)); + if (dwfl_core_file_report (dwfl, elf) < 0) + error (2, 0, "dwfl_core_file_report: %s", dwfl_errmsg (-1)); + if (dwfl_report_end (dwfl, NULL, NULL) != 0) + error (2, 0, "dwfl_report_end: %s", dwfl_errmsg (-1)); + if (elf_end (elf) != 0) + error (2, 0, "elf_end: %s", elf_errmsg (-1)); + if (close (fd) != 0) + error (2, 0, "close: %m"); + return dwfl; +} + +static int +dump_modules (Dwfl_Module *mod, void **userdata __attribute__ ((unused)), + const char *name, Dwarf_Addr start, + void *arg __attribute__ ((unused))) +{ + Dwarf_Addr end; + dwfl_module_info (mod, NULL, NULL, &end, NULL, NULL, NULL, NULL); + printf ("%#" PRIx64 "\t%#" PRIx64 "\t%s\n", (uint64_t) start, (uint64_t) end, + name); + return DWARF_CB_OK; +} + +static void +dump (pid_t pid, const char *corefile, + void (*callback) (pid_t tid, unsigned frameno, Dwarf_Addr pc, + const char *symname, Dwfl *dwfl, void *data), + void *data) +{ + Dwfl *dwfl; + Dwfl_Frame_State *state; + if (pid && !corefile) + { + dwfl = dwfl_pid (pid); + state = dwfl_frame_state_pid (dwfl, pid); + } + else if (corefile && !pid) + { + dwfl = dwfl_core (corefile); + state = dwfl_frame_state_core (dwfl, corefile); + } + else + abort (); + if (state == NULL) + error (2, 0, "dwfl_frame_state: %s", dwfl_errmsg (-1)); + ptrdiff_t ptrdiff = dwfl_getmodules (dwfl, dump_modules, NULL, 0); + assert (ptrdiff == 0); + int err = 0; + do + { + Dwfl_Frame_State *thread = state; + pid_t tid = dwfl_frame_tid_get (thread); + printf ("TID %ld:\n", (long) tid); + unsigned frameno; + for (frameno = 0; state; frameno++) + { + Dwarf_Addr pc; + bool minusone; + if (! dwfl_frame_state_pc (state, &pc, &minusone)) + { + fprintf (stderr, "%s\n", dwfl_errmsg (-1)); + err = 1; + break; + } + Dwarf_Addr pc_adjusted = pc - (minusone ? 1 : 0); + + /* Get PC->SYMNAME. */ + Dwfl_Module *mod = dwfl_addrmodule (dwfl, pc_adjusted); + const char *symname = NULL; + if (mod) + symname = dwfl_module_addrname (mod, pc_adjusted); + + printf ("#%2u %#" PRIx64 "%4s\t%s\n", frameno, (uint64_t) pc, + minusone ? "- 1" : "", symname); + if (callback) + callback (tid, frameno, pc, symname, dwfl, data); + if (! dwfl_frame_unwind (&state)) + { + fprintf (stderr, "%s\n", dwfl_errmsg (-1)); + err = 1; + break; + } + } + state = dwfl_frame_thread_next (thread); + } + while (state); + if (callback) + callback (0, 0, 0, NULL, dwfl, data); + dwfl_end (dwfl); + if (err) + exit (EXIT_FAILURE); +} + +struct see_exec_module +{ + Dwfl_Module *mod; + char selfpath[PATH_MAX + 1]; +}; + +static int +see_exec_module (Dwfl_Module *mod, void **userdata __attribute__ ((unused)), + const char *name __attribute__ ((unused)), + Dwarf_Addr start __attribute__ ((unused)), void *arg) +{ + struct see_exec_module *data = arg; + if (strcmp (name, data->selfpath) != 0) + return DWARF_CB_OK; + assert (data->mod == NULL); + data->mod = mod; + return DWARF_CB_OK; +} + +static void +selfdump_callback (pid_t tid, unsigned frameno, Dwarf_Addr pc, + const char *symname, Dwfl *dwfl, void *data) +{ + pid_t check_tid = (intptr_t) data; + bool disable = check_tid < 0; + if (disable) + check_tid = -check_tid; + static bool seen_main = false; + if (symname && strcmp (symname, "main") == 0) + seen_main = true; + if (pc == 0) + { + assert (seen_main); + return; + } + if (disable || tid != check_tid) + return; + Dwfl_Module *mod; + const char *symname2 = NULL; + switch (frameno) + { + case 0: + /* .plt has no symbols. */ + assert (symname == NULL); + break; + case 1: + assert (symname != NULL && strcmp (symname, "sigusr2") == 0); + break; + case 2: + /* __restore_rt - glibc maybe does not have to have this symbol. */ + break; + case 3: + /* Verify we trapped on the very first instruction of jmp. */ + assert (symname != NULL && strcmp (symname, "jmp") == 0); + mod = dwfl_addrmodule (dwfl, pc - 1); + if (mod) + symname2 = dwfl_module_addrname (mod, pc - 1); + assert (symname2 == NULL || strcmp (symname2, "jmp") != 0); + break; + case 4: + assert (symname != NULL && strcmp (symname, "stdarg") == 0); + break; + case 5: + /* Verify we trapped on the very last instruction of child. */ + assert (symname != NULL && strcmp (symname, "backtracegen") == 0); + mod = dwfl_addrmodule (dwfl, pc); + if (mod) + symname2 = dwfl_module_addrname (mod, pc); + assert (symname2 == NULL || strcmp (symname2, "backtracegen") != 0); + break; + } +} + +#ifdef __x86_64__ +static void +prepare_thread (pid_t pid2, Dwarf_Addr plt_start, Dwarf_Addr plt_end, + void (*jmp) (void)) +{ + long l; + errno = 0; + l = ptrace (PTRACE_POKEUSER, pid2, + (void *) (intptr_t) offsetof (struct user_regs_struct, rip), jmp); + assert_perror (errno); + assert (l == 0); + l = ptrace (PTRACE_CONT, pid2, NULL, (void *) (intptr_t) SIGUSR2); + int status; + pid_t got = waitpid (pid2, &status, __WALL); + assert_perror (errno); + assert (got == pid2); + assert (WIFSTOPPED (status)); + assert (WSTOPSIG (status) == SIGUSR1); + for (;;) + { + errno = 0; + l = ptrace (PTRACE_PEEKUSER, pid2, + (void *) (intptr_t) offsetof (struct user_regs_struct, rip), + NULL); + assert_perror (errno); + if ((unsigned long) l >= plt_start && (unsigned long) l < plt_end) + break; + l = ptrace (PTRACE_SINGLESTEP, pid2, NULL, NULL); + assert_perror (errno); + assert (l == 0); + got = waitpid (pid2, &status, __WALL); + assert_perror (errno); + assert (got == pid2); + assert (WIFSTOPPED (status)); + assert (WSTOPSIG (status) == SIGTRAP); + } +} +#endif /* __x86_64__ */ + +#include <asm/unistd.h> +#include <unistd.h> +#define tgkill(pid, tid, sig) syscall (__NR_tgkill, (pid), (tid), (sig)) + +static void +ptrace_detach_stopped (pid_t pid) +{ + errno = 0; + long l = ptrace (PTRACE_DETACH, pid, NULL, (void *) (intptr_t) SIGSTOP); + assert_perror (errno); + assert (l == 0); +} + +static void +selfdump (const char *exec) +{ + pid_t pid = fork (); + switch (pid) + { + case -1: + abort (); + case 0: + execl (exec, exec, "--ptraceme", "--run", NULL); + abort (); + default: + break; + } + + /* Catch the main thread. Catch it first otherwise the /proc evaluation of + PID may have caught still ourselves before executing execl above. */ + errno = 0; + int status; + pid_t got = waitpid (pid, &status, 0); + assert_perror (errno); + assert (got == pid); + assert (WIFSTOPPED (status)); + assert (WSTOPSIG (status) == SIGUSR2); + + /* Catch the spawned thread. Do not use __WCLONE as we could get racy + __WCLONE, probably despite pthread_create already had to be called the new + task is not yet alive enough for waitpid. */ + pid_t pid2 = waitpid (-1, &status, __WALL); + assert_perror (errno); + assert (pid2 > 0); + assert (pid2 != pid); + assert (WIFSTOPPED (status)); + assert (WSTOPSIG (status) == SIGUSR1); + + Dwfl *dwfl = dwfl_pid (pid); + char *selfpathname; + int i = asprintf (&selfpathname, "/proc/%ld/exe", (long) pid); + assert (i > 0); + struct see_exec_module data; + ssize_t ssize = readlink (selfpathname, data.selfpath, + sizeof (data.selfpath)); + free (selfpathname); + assert (ssize > 0 && ssize < (ssize_t) sizeof (data.selfpath)); + data.selfpath[ssize] = '\0'; + data.mod = NULL; + ptrdiff_t ptrdiff = dwfl_getmodules (dwfl, see_exec_module, &data, 0); + assert (ptrdiff == 0); + assert (data.mod != NULL); + GElf_Addr loadbase; + Elf *elf = dwfl_module_getelf (data.mod, &loadbase); + GElf_Ehdr ehdr_mem, *ehdr = gelf_getehdr (elf, &ehdr_mem); + assert (ehdr != NULL); + Elf_Scn *scn = NULL, *plt = NULL; + while ((scn = elf_nextscn (elf, scn)) != NULL) + { + GElf_Shdr scn_shdr_mem, *scn_shdr = gelf_getshdr (scn, &scn_shdr_mem); + assert (scn_shdr != NULL); + if (strcmp (elf_strptr (elf, ehdr->e_shstrndx, scn_shdr->sh_name), + ".plt") != 0) + continue; + assert (plt == NULL); + plt = scn; + } + assert (plt != NULL); + GElf_Shdr scn_shdr_mem, *scn_shdr = gelf_getshdr (plt, &scn_shdr_mem); + assert (scn_shdr != NULL); + /* Make it true on x86_64 with i386 inferior. */ + int disable = ehdr->e_ident[EI_CLASS] == ELFCLASS32; +#ifdef __x86_64__ + Dwarf_Addr plt_start = scn_shdr->sh_addr + loadbase; + Dwarf_Addr plt_end = plt_start + scn_shdr->sh_size; + void (*jmp) (void); + if (! disable) + { + int nsym = dwfl_module_getsymtab (data.mod); + int symi; + for (symi = 1; symi < nsym; ++symi) + { + GElf_Sym symbol; + const char *symbol_name = dwfl_module_getsym (data.mod, symi, &symbol, NULL); + if (symbol_name == NULL) + continue; + switch (GELF_ST_TYPE (symbol.st_info)) + { + case STT_SECTION: + case STT_FILE: + case STT_TLS: + continue; + default: + if (strcmp (symbol_name, "jmp") != 0) + continue; + break; + } + /* LOADBASE is already applied here. */ + jmp = (void (*) (void)) (uintptr_t) symbol.st_value; + break; + } + assert (symi < nsym); + prepare_thread (pid2, plt_start, plt_end, jmp); + } +#endif + dwfl_end (dwfl); + ptrace_detach_stopped (pid); + ptrace_detach_stopped (pid2); + dump (pid, NULL, selfdump_callback, + (void *) (intptr_t) (disable ? -pid2 : pid2)); +} + +static bool +is_core (const char *corefile) +{ + Dwfl *dwfl = dwfl_offline (); + Dwfl_Module *mod = dwfl_report_elf (dwfl, "core", corefile, -1, 0 /* base */, + false /* add_p_vaddr */); + assert (mod != NULL); + GElf_Addr loadbase_ignore; + Elf *core = dwfl_module_getelf (mod, &loadbase_ignore); + assert (core != NULL); + GElf_Ehdr ehdr_mem, *ehdr = gelf_getehdr (core, &ehdr_mem); + assert (ehdr != NULL); + assert (ehdr->e_type == ET_CORE || ehdr->e_type == ET_EXEC + || ehdr->e_type == ET_DYN); + bool retval = ehdr->e_type == ET_CORE; + dwfl_end (dwfl); + return retval; +} + +int +main (int argc __attribute__ ((unused)), char **argv) +{ + /* We use no threads here which can interfere with handling a stream. */ + __fsetlocking (stdin, FSETLOCKING_BYCALLER); + __fsetlocking (stdout, FSETLOCKING_BYCALLER); + __fsetlocking (stderr, FSETLOCKING_BYCALLER); + + /* Set locale. */ + (void) setlocale (LC_ALL, ""); + + if (argc == 1) + { + selfdump ("./backtrace-child"); + return 0; + } + argv++; + if (argc == 2) + { + if (strcmp (*argv, "--help") == 0) + error (2, 0, "backtrace {{no args for ./backtrace-child}|<pid>|<core>|" + "<executable>|<executable core>}"); + char *end; + long l = strtol (*argv, &end, 10); + if (**argv && !*end) + dump (l, NULL, NULL, NULL); + else if (is_core (*argv)) + dump (0, *argv, NULL, NULL); + else + selfdump (*argv); + return 0; + } + if (argc == 3) + { + assert (! is_core (argv[0])); + assert (is_core (argv[1])); + executable = argv[0]; + dump (0, argv[1], NULL, NULL); + return 0; + } + assert (0); + + return 0; +} diff --git a/tests/run-backtrace.sh b/tests/run-backtrace.sh new file mode 100755 index 0000000..be3c6f4 --- /dev/null +++ b/tests/run-backtrace.sh @@ -0,0 +1,83 @@ +#! /bin/bash +# Copyright (C) 2013 Red Hat, Inc. +# This file is part of elfutils. +# +# This file is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# elfutils is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see http://www.gnu.org/licenses/. + +. $srcdir/test-subr.sh + +if [ -z "$VERBOSE" ]; then + exec >/dev/null +else + set -x +fi + +mytestrun() +{ + echo "$*" + testrun "$@" +} + +check_main() +{ + if grep -w main $1; then + return + fi + cat >&2 $1 $3 + echo >&2 $2: no main + false +} + +check_gsignal() +{ + # Without proper ELF symbols resolution we could get inappropriate weak + # symbol "gsignal" with the same address as the correct symbol "raise". + if ! grep -w gsignal $1; then + return + fi + cat >&2 $1 + echo >&2 $2: found gsignal + false +} + +check_err() +{ + if test ! -s $1; then + return + fi + # In some cases we cannot reliably find out we got behind _start. + if cmp -s <(echo "No DWARF information found") <(uniq <$1); then + return + fi + cat >&2 $1 + echo >&2 $2: neither empty nor just out of DWARF + false +} + +for child in backtrace-child{,-biarch}; do + tempfiles $child{.bt,.err} + (set +ex; testrun ${abs_builddir}/backtrace ${abs_builddir}/$child 1>$child.bt 2>$child.err; true) + check_main $child.bt $child $child.err + check_gsignal $child.bt $child + check_err $child.err $child + core="core.`ulimit -c unlimited; set +ex; testrun ${abs_builddir}/$child --gencore --run; true`" + tempfiles $core{,.bt,.err} + (set +ex; testrun ${abs_builddir}/backtrace ${abs_builddir}/$child $core 1>$core.bt 2>$core.err; true) + cat $core.{bt,err} + check_main $core.bt $child-$core $core.err + check_gsignal $core.bt $child-$core + check_err $core.err $child-$core +done + +exit 0
On Thu, 2013-05-30 at 15:26 +0200, Jan Kratochvil wrote:
made a new branch: jankratochvil/unwindx86
It is a single commit with all its pre-requisites already checked in.
It is fairly large :) I read through most of it. I have some nits about what I think are "reverse dependencies" between libebl and libdwfl. But I think the general framework for unwinding is all here. Nice job, you have been very thorough. But before going into details of the code lets first concentrate on the new public interfaces, since if we get those wrong we are stuck with them, while we can fix bugs in the implementation more easily. Also it will help me review things more concretely by going "down" from the public entry points.
diff --git a/libdw/libdw.map b/libdw/libdw.map index d38a8ef..54ee7f2 100644 --- a/libdw/libdw.map +++ b/libdw/libdw.map @@ -259,4 +259,11 @@ ELFUTILS_0.156 { global: # Replaced ELFUTILS_0.122 version, which has a wrapper without add_p_vaddr. dwfl_report_elf;
- dwfl_frame_state_pid;
- dwfl_frame_state_core;
- dwfl_frame_state_data;
- dwfl_frame_unwind;
- dwfl_frame_state_pc;
- dwfl_frame_tid_get;
- dwfl_frame_thread_next;
} ELFUTILS_0.149; [...] diff --git a/libdwfl/libdwfl.h b/libdwfl/libdwfl.h index 2b70e28..8998886 100644 --- a/libdwfl/libdwfl.h +++ b/libdwfl/libdwfl.h @@ -41,6 +41,10 @@ typedef struct Dwfl_Module Dwfl_Module; /* Handle describing a line record. */ typedef struct Dwfl_Line Dwfl_Line;
+/* This holds everything we know about the state of the frame at a particular
- PC location described by an FDE. */
+typedef struct Dwfl_Frame_State Dwfl_Frame_State;
This is also for a particular thread. I found that at first a little confusing. That you can "jump" from thread to thread while unwinding. It might be nice/cleaner to make the thread concept and how to get the initial frame state for a thread a bit more explicit.
@@ -564,6 +568,46 @@ extern int dwfl_module_register_names (Dwfl_Module *mod, extern Dwarf_CFI *dwfl_module_dwarf_cfi (Dwfl_Module *mod, Dwarf_Addr *bias); extern Dwarf_CFI *dwfl_module_eh_cfi (Dwfl_Module *mod, Dwarf_Addr *bias);
+/* Get innermost frame of first thread of live process PID. Returns NULL on
- failure. */
+extern Dwfl_Frame_State *dwfl_frame_state_pid (Dwfl *dwfl, pid_t pid);
This has the side effect, on success, of stopping the process. Which is good of course, or we wouldn't be able to get consistent unwinds. But it isn't clear how to "unstop" the process again. IMHO it would be nicer to have an explicit Process concept, so that we can start observing a process (which would freeze it) and end observing a process (which would set it free again).
And internally you already use the Process concept.
+/* Get innermost frame of first thread of core file COREFILE. Returns NULL on
- failure. */
+extern Dwfl_Frame_State *dwfl_frame_state_core (Dwfl *dwfl,
const char *corefile);
The user might have the *Elf core still around. Which brings up the point that the Dwfl pid or core file really should match with the given corefile or pid when the Dwfl was populated by dwfl_linux_proc_report or dwfl_core_file_report. We could record that (pid or core *Elf) and just do the right thing without the user possibly messing things up with the wrong pid or core file.
+/* Fetch inferior registers from a caller supplied storage. */ +typedef bool dwfl_frame_memory_read_t (Dwarf_Addr addr, Dwarf_Addr *result,
void *user_data);
+extern Dwfl_Frame_State *dwfl_frame_state_data (Dwfl *dwfl, bool pc_set,
Dwarf_Addr pc, unsigned nregs,
const uint64_t *regs_set,
const Dwarf_Addr *regs,
dwfl_frame_memory_read_t
*memory_read,
void *memory_read_user_data);
So this is the generic variant. It could use a bit more documentation. But I get the idea. Expressing the regs as Dwarf_Words instead of uint64_t might be more in line with existing usage in the libdwfl interface. pc_set might be redundant since the backend should know whether PC is part of the regs or not.
Since we have a memory_read callback it makes sense to me to also have an (initial) frame register set callback (and maybe expose dwfl_frame_state_reg_set to make the callback as flexible as possible). That callback would then be called whenever you switch to the next thread of a process to get the initial frame. Using the DWARF register numbering here seems a smart choice since that is the best documented.
+/* Return TRUE and update *STATEP for the unwound frame for successful unwind.
- Return TRUE and set *STATEP to NULL for the outermost frame. Return FALSE
- (and call __libdwfl_seterrno) otherwise. */
+extern bool dwfl_frame_unwind (Dwfl_Frame_State **statep);
Another way to express this would be with a callback that walks over the frames as long as DWARF_CB_OK is returned. Like what dwfl_getmodules () does.
+/* Get return address register value for frame. Return TRUE if *PC set and
- optionally *MINUSONE is also set, if MINUSONE is not NULL. Return FALSE
- (and call __libdw_seterrno) otherwise. *MINUSONE is TRUE for normal calls
- where *PC should be decremented by one to get the call instruction, it is
- FALSE if this frame was interrupted by a signal handler. */
+extern bool dwfl_frame_state_pc (Dwfl_Frame_State *state, Dwarf_Addr *pc,
bool *minusone);
I wish there was a better word for MINUSONE. Maybe RETURNADDRESS? To indicate that the PC is the return address (and to get to call address of this frame you need to substract at least one)?
We should think about how we would like to expose the other registers of the frame. Though that doesn't have priority. Just walking the PCs is what is most interesting now.
+/* Get innermost frame of the next thread from STATE. STATE can be any frame
- of (the previous) thread. */
+extern Dwfl_Frame_State *dwfl_frame_thread_next (Dwfl_Frame_State *state);
This I think should be an operation on the Process (calling the initial register callback), to get the next threads initial frame (starting with null for the first one). Returning at the same time the tid. So you don't need the next function.
+/* Get Task ID of the thread of STATE. This is PID for the thread started by
- function main and gettid () for the other threads. */
+extern pid_t dwfl_frame_tid_get (Dwfl_Frame_State *state);
I like to try and rewrite this patch a little to have a public interface that includes a begin/end_dwfl_process() with which you can explicitly set the callbacks for memory and initial registers as outlined above. It might show that is a silly idea, but at least it will force me to go through the code for a full review.
Cheers,
Mark
On Fri, 31 May 2013 23:28:14 +0200, Mark Wielaard wrote:
I have some nits about what I think are "reverse dependencies" between libebl and libdwfl.
I also did not like it much but I do not know about a better solution (without making the code worse than it is).
you have been very thorough.
The CFI operators set is not complete, there is no CFI code deadlock prevention etc. but those parts were not on the critical path.
+/* This holds everything we know about the state of the frame at a particular
- PC location described by an FDE. */
+typedef struct Dwfl_Frame_State Dwfl_Frame_State;
This is also for a particular thread. I found that at first a little confusing. That you can "jump" from thread to thread while unwinding. It might be nice/cleaner to make the thread concept and how to get the initial frame state for a thread a bit more explicit.
I made some API I am fine with. If you see it better a different way express the API you want, please. I do not mind much, I just chose something I saw as the most simple one.
+/* Get innermost frame of first thread of live process PID. Returns NULL on
- failure. */
+extern Dwfl_Frame_State *dwfl_frame_state_pid (Dwfl *dwfl, pid_t pid);
This has the side effect, on success, of stopping the process. Which is good of course, or we wouldn't be able to get consistent unwinds. But it isn't clear how to "unstop" the process again.
dwfl_end
IMHO it would be nicer to have an explicit Process concept, so that we can start observing a process (which would freeze it) and end observing a process (which would set it free again).
During the current redesign of the API I will do not see a place for Process, now Dwfl is used intead of Process. It does not make sense to have multiple processes linked to single Dwfl as each process has different memory map layout.
+/* Get innermost frame of first thread of core file COREFILE. Returns NULL on
- failure. */
+extern Dwfl_Frame_State *dwfl_frame_state_core (Dwfl *dwfl,
const char *corefile);
The user might have the *Elf core still around. Which brings up the point that the Dwfl pid or core file really should match with the given corefile or pid when the Dwfl was populated by dwfl_linux_proc_report or dwfl_core_file_report. We could record that (pid or core *Elf) and just do the right thing without the user possibly messing things up with the wrong pid or core file.
OK, application can open *Elf core on its own.
+/* Fetch inferior registers from a caller supplied storage. */ +typedef bool dwfl_frame_memory_read_t (Dwarf_Addr addr, Dwarf_Addr *result,
void *user_data);
+extern Dwfl_Frame_State *dwfl_frame_state_data (Dwfl *dwfl, bool pc_set,
Dwarf_Addr pc, unsigned nregs,
const uint64_t *regs_set,
const Dwarf_Addr *regs,
dwfl_frame_memory_read_t
*memory_read,
void *memory_read_user_data);
So this is the generic variant.
Not completely, you can emulate neither dwfl_frame_state_core () nor dwfl_frame_state_pid () on top of it. This is because dwfl_frame_state_data's produced Dwfl_Frame_State will always have only one thread.
Supporting multiple threads using dwfl_frame_state_data means just calling it for every TID separately.
pc_set might be redundant since the backend should know whether PC is part of the regs or not.
I find it as a sanity check to verify application vs. backend agree on whether PC_SET is needed or not.
Since we have a memory_read callback it makes sense to me to also have an (initial) frame register set callback
According to the design "one dwfl_frame_state_data call per each TID" I find a registers callback superfluous. Such callback will be called exactly once at the beginning and never more; that only brings additional code complexity with no benefits.
That callback would then be called whenever you switch to the next thread of a process to get the initial frame.
One cannot, as described above.
Using the DWARF register numbering here seems a smart choice since that is the best documented.
Unfortunately it becames a hell due to PowerPC sparse DWARF numbering; but that is waiting for us in the non-x86* extensions in futher patches.
+/* Return TRUE and update *STATEP for the unwound frame for successful unwind.
- Return TRUE and set *STATEP to NULL for the outermost frame. Return FALSE
- (and call __libdwfl_seterrno) otherwise. */
+extern bool dwfl_frame_unwind (Dwfl_Frame_State **statep);
Another way to express this would be with a callback that walks over the frames as long as DWARF_CB_OK is returned. Like what dwfl_getmodules () does.
dwfl_getmodules supports both iterative calls via OFFSET and also a callback, TBH I do not find why, it seems overcomplicated to me. Callbacks make sense when the iteration requires some additional context but neither dwfl_getmodules nor dwfl_frame_unwind need any.
I can provide the callback if you still ask for it although I would prefer to know the advantages; disadvantages are clear I hope, it requires another function in the caller and passing context between caller and the callback.
+/* Get return address register value for frame. Return TRUE if *PC set and
- optionally *MINUSONE is also set, if MINUSONE is not NULL. Return FALSE
- (and call __libdw_seterrno) otherwise. *MINUSONE is TRUE for normal calls
- where *PC should be decremented by one to get the call instruction, it is
- FALSE if this frame was interrupted by a signal handler. */
+extern bool dwfl_frame_state_pc (Dwfl_Frame_State *state, Dwarf_Addr *pc,
bool *minusone);
I wish there was a better word for MINUSONE. Maybe RETURNADDRESS? To indicate that the PC is the return address (and to get to call address of this frame you need to substract at least one)?
As I find the rules where MINUSONE is set relatively complicated ( libdwfl/dwfl_frame_state_pc.c ) I find all the other namings confusing. The name RETURNADDRESS is for example weird why to be set for the bottom frame.
We should think about how we would like to expose the other registers of the frame. Though that doesn't have priority. Just walking the PCs is what is most interesting now.
Exactly, other registers were not on the critical path.
Is the API provided below OK?
Thanks, Jan
/* This holds everything we know about state of a frame at a particular thread at a particular PC location described by an FDE. */ typedef struct Dwfl_Frame_State Dwfl_Frame_State;
/* Attach (and stop) all threads of PID. PID will be detached by callind dwfl_end on returned DWFL. Returns NULL on failure. */ extern Dwfl *dwfl_begin_unwind_pid (pid_t pid);
/* Initialize Dwfl * from an already opened core file CORE. Caller has to close CORE after calling dwfl_end. Returns NULL on failure. */ extern Dwfl *dwfl_begin_unwind_core (Elf *core);
/* Initialize unwinder for DWFL from a caller supplied storage. Returns innermost frame or NULL on failure. This function does not support multiple threads, call it for each thread of the same process (DWFL). */ typedef bool dwfl_frame_memory_read_t (Dwarf_Addr addr, Dwarf_Addr *result, void *user_data); extern Dwfl_Frame_State *dwfl_begin_unwind (Dwfl *dwfl, bool pc_set, Dwarf_Addr pc, unsigned nregs, const Dwarf_Word *regs_set, const Dwarf_Addr *regs, dwfl_frame_memory_read_t * memory_read, void *memory_read_user_data);
/* Return TRUE and update *STATEP for the unwound frame for successful unwind. Return TRUE and set *STATEP to NULL for the outermost frame. Return FALSE (and set dwfl_errno) otherwise. */ extern bool dwfl_frame_unwind (Dwfl_Frame_State **statep);
/* Get return address register value for frame. Return TRUE if *PC set and optionally *MINUSONE is also set, if MINUSONE is not NULL. Return FALSE (and set dwfl_errno) otherwise. *MINUSONE is TRUE for normal calls where *PC should be decremented by one to get the call instruction, it is FALSE if this frame was interrupted by a signal handler. */ extern bool dwfl_frame_state_pc (Dwfl_Frame_State *state, Dwarf_Addr *pc, bool *minusone);
/* Get innermost frame of the next thread from DWFL. Return also Task ID of the returned thread in *TID_RETURN. TID_RETURN can be NULL. Returns NULL if there are no more threads in DWFL (in such case *TID_RETURN is left uninitialized. */ extern Dwfl_Frame_State *dwfl_get_next_thread (Dwfl *dwfl, pid_t *tid_return);
Hi Jan,
Sorry, I thought I sent feedback last week, but apparently I never finished that email.
On Sat, 2013-06-01 at 20:30 +0200, Jan Kratochvil wrote:
I made some API I am fine with. If you see it better a different way express the API you want, please. I do not mind much, I just chose something I saw as the most simple one.
I don't know if what I am suggesting is better, but it is slightly different, just so it is a little more generic and matches the existing interface a bit more. I just want to make sure we understand it completely before committing to it, so we don't have to break something in the future.
+/* Get innermost frame of first thread of live process PID. Returns NULL on
- failure. */
+extern Dwfl_Frame_State *dwfl_frame_state_pid (Dwfl *dwfl, pid_t pid);
This has the side effect, on success, of stopping the process. Which is good of course, or we wouldn't be able to get consistent unwinds. But it isn't clear how to "unstop" the process again.
dwfl_end
IMHO it would be nicer to have an explicit Process concept, so that we can start observing a process (which would freeze it) and end observing a process (which would set it free again).
During the current redesign of the API I will do not see a place for Process, now Dwfl is used intead of Process. It does not make sense to have multiple processes linked to single Dwfl as each process has different memory map layout.
Good points. Thanks. Identifying Dwfl with Process is much simpler than trying to introduce a new Process concept.
The only issue I have with dwfl_end to mark the process "unfrozen" again is that it means you have to reconstruct the whole Dwfl again for the next backtrace. Something which is a bit expensive if you like to use the unwinder for something like a profiler that is periodically taking backtraces of a process it is monitoring. I think we must convince ourselves that we can introduce something a bit more subtle that preserves the Dwfl in the future for that use case. But we can go with the "freeze at begin", "unfreeze at end" policy for now.
+/* Get innermost frame of first thread of core file COREFILE. Returns NULL on
- failure. */
+extern Dwfl_Frame_State *dwfl_frame_state_core (Dwfl *dwfl,
const char *corefile);
The user might have the *Elf core still around. Which brings up the point that the Dwfl pid or core file really should match with the given corefile or pid when the Dwfl was populated by dwfl_linux_proc_report or dwfl_core_file_report. We could record that (pid or core *Elf) and just do the right thing without the user possibly messing things up with the wrong pid or core file.
OK, application can open *Elf core on its own.
Yeah, this was really the clue that Dwfl == Process. But I missed it till you pointed it out. Thanks.
+/* Fetch inferior registers from a caller supplied storage. */ +typedef bool dwfl_frame_memory_read_t (Dwarf_Addr addr, Dwarf_Addr *result,
void *user_data);
+extern Dwfl_Frame_State *dwfl_frame_state_data (Dwfl *dwfl, bool pc_set,
Dwarf_Addr pc, unsigned nregs,
const uint64_t *regs_set,
const Dwarf_Addr *regs,
dwfl_frame_memory_read_t
*memory_read,
void *memory_read_user_data);
So this is the generic variant.
Not completely, you can emulate neither dwfl_frame_state_core () nor dwfl_frame_state_pid () on top of it. This is because dwfl_frame_state_data's produced Dwfl_Frame_State will always have only one thread.
Supporting multiple threads using dwfl_frame_state_data means just calling it for every TID separately.
Since we have a memory_read callback it makes sense to me to also have an (initial) frame register set callback
According to the design "one dwfl_frame_state_data call per each TID" I find a registers callback superfluous. Such callback will be called exactly once at the beginning and never more; that only brings additional code complexity with no benefits.
I think that is a bit of a bug in the design/interface though. If we introduce a register callback that is called when inspecting a different thread the user can (in theory) emulate either a core, pid or data backed Dwfl. And the other frame/state function would just work similarly without the user having to adjust code depending on how things were initialized.
That callback would then be called whenever you switch to the next thread of a process to get the initial frame.
One cannot, as described above.
If there is no data/registers for other threads then the callback can just say so, and the users will just see one thread.
Using the DWARF register numbering here seems a smart choice since that is the best documented.
Unfortunately it becames a hell due to PowerPC sparse DWARF numbering; but that is waiting for us in the non-x86* extensions in futher patches.
Sigh, I forgot that PowerPC uses "sparse" DWARF numbering (and even a little ambiguous). Still, it might be the best we have. It might just not be the most efficient way to pass things through for the ppc backend. Having a register callback might help a little with that though. Then the callback can report the registers in known ranges.
+/* Return TRUE and update *STATEP for the unwound frame for successful unwind.
- Return TRUE and set *STATEP to NULL for the outermost frame. Return FALSE
- (and call __libdwfl_seterrno) otherwise. */
+extern bool dwfl_frame_unwind (Dwfl_Frame_State **statep);
Another way to express this would be with a callback that walks over the frames as long as DWARF_CB_OK is returned. Like what dwfl_getmodules () does.
dwfl_getmodules supports both iterative calls via OFFSET and also a callback, TBH I do not find why, it seems overcomplicated to me. Callbacks make sense when the iteration requires some additional context but neither dwfl_getmodules nor dwfl_frame_unwind need any.
The main advantage is that it is consistent with other such iterators in libdw/libdwfl. And consistency in the interface is somewhat nice to have. The context would be the Dwfl_Frame_State, so you don't have to hold on that yourself and the lifetime of the frame state is clear, just during that callback (though that is admittedly a somewhat minor advantage, maybe not offset by the more complicated use case).
I can provide the callback if you still ask for it although I would prefer to know the advantages; disadvantages are clear I hope, it requires another function in the caller and passing context between caller and the callback.
No worries, I'll try and hack something up. If it is rubbish we can just revert it again. But it will give me some experience with the new code for review. And it will make things more concrete than my current "hand waving".
+/* Get return address register value for frame. Return TRUE if *PC set and
- optionally *MINUSONE is also set, if MINUSONE is not NULL. Return FALSE
- (and call __libdw_seterrno) otherwise. *MINUSONE is TRUE for normal calls
- where *PC should be decremented by one to get the call instruction, it is
- FALSE if this frame was interrupted by a signal handler. */
+extern bool dwfl_frame_state_pc (Dwfl_Frame_State *state, Dwarf_Addr *pc,
bool *minusone);
I wish there was a better word for MINUSONE. Maybe RETURNADDRESS? To indicate that the PC is the return address (and to get to call address of this frame you need to substract at least one)?
As I find the rules where MINUSONE is set relatively complicated ( libdwfl/dwfl_frame_state_pc.c ) I find all the other namings confusing. The name RETURNADDRESS is for example weird why to be set for the bottom frame.
Yes, you are right that it is relatively complicated. And RETURNADDRESS is also not really the correct way to express the concept. When unwinding we always get the address that is being returned to. But it might not be the address that the call was made from or the frame was interrupted in (what the DWARF spec call the activation address). When the previous frame was an asynchronous interrupt (signal) then the return address is in the actual context of the current frame. Otherwise the return address might be outside the actual context of the current frame (and in that case we have to substract at least one to get there, but not otherwise). I don't like the name MINUSONE since it is somewhat architecture specific (it isn't exactly 1, and on some architectures you might know the instruction length and get an exact answer) and doesn't express why you might want to substract 1. Maybe ISACTIVATION might be more descriptive (which would be the inverse of MINUSONE, it shows the return pc is equal to the frame "call" address)?
Sorry for discussing the naming at length. We do know technically what it represents. Just hope to find something a bit intuitive. Maybe that is just impossible with such a technical subject.
Let me just hack up an example of the interface as proposed above to make it more concrete (or to show me why it shouldn't/cannot be done).
Thanks,
Mark
Hi Mark,
On Mon, 10 Jun 2013 11:24:00 +0200, Mark Wielaard wrote:
On Sat, 2013-06-01 at 20:30 +0200, Jan Kratochvil wrote: The only issue I have with dwfl_end to mark the process "unfrozen" again is that it means you have to reconstruct the whole Dwfl again for the next backtrace. Something which is a bit expensive if you like to use the unwinder for something like a profiler that is periodically taking backtraces of a process it is monitoring. I think we must convince ourselves that we can introduce something a bit more subtle that preserves the Dwfl in the future for that use case. But we can go with the "freeze at begin", "unfreeze at end" policy for now.
OK, understood. But some dwfl_detach() can be introduced later as a simple extension.
I think that is a bit of a bug in the design/interface though. If we introduce a register callback that is called when inspecting a different thread the user can (in theory) emulate either a core, pid or data backed Dwfl. And the other frame/state function would just work similarly without the user having to adjust code depending on how things were initialized.
That makes sense to unify access to multiple TIDs for core/PID/callbacks in the caller.
Maybe ISACTIVATION might be more descriptive (which would be the inverse of MINUSONE, it shows the return pc is equal to the frame "call" address)?
OK, fine with ISACTIVATION as it conforms to the DWARF terminology.
Let me just hack up an example of the interface as proposed above to make it more concrete (or to show me why it shouldn't/cannot be done).
Do what you want to but I am fine with for example editing just the proposed API and I can refactor the code in a moment.
Thanks, Jan
Hi Jan,
On Mon, 2013-06-10 at 13:07 +0200, Jan Kratochvil wrote:
Let me just hack up an example of the interface as proposed above to make it more concrete (or to show me why it shouldn't/cannot be done).
Do what you want to but I am fine with for example editing just the proposed API and I can refactor the code in a moment.
I started on the simplest part, renaming MINUSONE to ISACTIVATION (and flipping the meaning). BTW. Did you mean to use dwfl_frame_unwind () in dwfl_frame_state_pc (), would have_unwound () work?
While doing that I added a few small things:
- Need to include sys/types.h before sys/user.h on older systems. On RHEL6 x86_64 at least. - GCC version check attributes, noclone was introduced with GCC 4.5 That is for the testcase. But I might have misunderstood. I never could make the selfdump tests work since it tries to find the "jmp" symbol (which seems to never be used?). - tests/backtrace.c: Drop out of dwfl_getmodules when done. Just to show that you can return from a callback early and don't have to iterate over all modules once you found your match. - src/stack.c: Print usage also when number of arguments wrong. The abort was a little too strong IMHO when someone (me) made a typo.
Those are all on the mjw/unwind branch I just pushed. Please take a look if any of those make sense (they can just be squashed into the actual commit, no need to make them separate, just did that so you can cherrypick and drop those that are not correct).
Then I thought I could just write up the rest of the interface with extra callbacks for dwfl_begin when you want state (threads, registers and memory). Since I had said attaching such state callbacks would "freeze" the process (with dwfl_end) "releasing" it. But I now think that was a wrong suggestion.
It makes the interface too heavy weight. We could later add a "unfreeze" api, but that means we would always stop the whole process first. Even if later someone decides that is not really what they are interested in. They might just want to get the backtrace of one thread, while they expect the rest of the process to keep running.
So now I am thinking we should do "the opposite". Only while starting an iteration over the frames to unwind for one thread should we ptrace attach, and after we are done with unwinding for that thread we should detach it again. That would make things much lighter weight.
What do you think? I am struggling a bit with this because I can see the user might want both (full freeze on begin or minimal thread freeze at use only).
Cheers,
Mark
Hi Mark,
On Thu, 13 Jun 2013 00:03:21 +0200, Mark Wielaard wrote:
- Need to include sys/types.h before sys/user.h on older systems. On RHEL6 x86_64 at least.
OK.
- GCC version check attributes, noclone was introduced with GCC 4.5
I would prefer "NORETURN", at least sourceware tree uses: src/include/ansidecl.h:#define ATTRIBUTE_NORETURN __attribute__ ((__noreturn__))
That is for the testcase. But I might have misunderstood. I never could make the selfdump tests work since it tries to find the "jmp" symbol (which seems to never be used?).
make check will not PASS for run-backtrace.sh? It PASSes for me.
$ nm tests/backtrace-child|grep -w jmp 0000000000000acb t jmp
Tested on Fedora 18 x86_64 and wit mjw/unwind now also on CentOS 6.3 x86_64.
- tests/backtrace.c: Drop out of dwfl_getmodules when done. Just to show that you can return from a callback early and don't have to iterate over all modules once you found your match.
I do not agree with this part, one of secondary goals was to verify exactly one of the modules patches the filename. Multiple modules of the same name will not be caught by your change.
- src/stack.c: Print usage also when number of arguments wrong. The abort was a little too strong IMHO when someone (me) made a typo.
OK.
Then I thought I could just write up the rest of the interface with extra callbacks for dwfl_begin when you want state (threads, registers and memory).
It should be enough to support registers + memory callback. registers are fetched only initially for a new thread so they in fact define a thread.
The current unwinder codebase does not implement 'lazy registers'. Currently every unwound frame contains everything what we could unwind. For x86* there are so few registers I guess the overhead of lazy-fetching may be higher than the saved cycles. Whether in reality it is worth it or not I have no idea.
I moved lazy registers feature into future incremental improvements although they would affect API. Still with the symbols versioning I do not think it is such a problem, we could be improving initial commit indefinitely to have every future extension in the initial API definition.
So now I am thinking we should do "the opposite". Only while starting an iteration over the frames to unwind for one thread should we ptrace attach, and after we are done with unwinding for that thread we should detach it again. That would make things much lighter weight.
What do you think? I am struggling a bit with this because I can see the user might want both (full freeze on begin or minimal thread freeze at use only).
Besides dwfl_end (which automatically detaches all attached TIDs) there could be also dwfl_detach_thread (Dwfl_Frame_State *state); although it would be thread-specific, not frame-specific, so you would like more: typedef struct Dwfl_Thread Dwfl_Thread; dwfl_detach_thread (Dwfl_Thread *thread); So with some iterator across threads one can detach each thread after unwinding it.
Thanks, Jan
On Thu, 2013-06-13 at 14:45 +0200, Jan Kratochvil wrote:
- GCC version check attributes, noclone was introduced with GCC 4.5
I would prefer "NORETURN", at least sourceware tree uses: src/include/ansidecl.h:#define ATTRIBUTE_NORETURN __attribute__ ((__noreturn__))
Ah, OK, I think that makes sense, but I might just be missing something because...
That is for the testcase. But I might have misunderstood. I never could make the selfdump tests work since it tries to find the "jmp" symbol (which seems to never be used?).
make check will not PASS for run-backtrace.sh? It PASSes for me.
$ nm tests/backtrace-child|grep -w jmp 0000000000000acb t jmp
Tested on Fedora 18 x86_64 and wit mjw/unwind now also on CentOS 6.3 x86_64.
That is indeed strange, those are similar to the platforms (f18 and rhel6.4 x86_64) that I also tested on. I assume I had something weird in my tree. I'll try again with clean trees. But I don't fully understand why/where jmp is actually used in the test case.
- tests/backtrace.c: Drop out of dwfl_getmodules when done. Just to show that you can return from a callback early and don't have to iterate over all modules once you found your match.
I do not agree with this part, one of secondary goals was to verify exactly one of the modules patches the filename. Multiple modules of the same name will not be caught by your change.
s/patches/matches/ I assume. OK. I just included it because it looked like it was doing too much work. But since it is a test case it makes sense to be paranoid and double check everything.
Then I thought I could just write up the rest of the interface with extra callbacks for dwfl_begin when you want state (threads, registers and memory).
It should be enough to support registers + memory callback. registers are fetched only initially for a new thread so they in fact define a thread. [...] Besides dwfl_end (which automatically detaches all attached TIDs) there could be also dwfl_detach_thread (Dwfl_Frame_State *state); although it would be thread-specific, not frame-specific, so you would like more: typedef struct Dwfl_Thread Dwfl_Thread; dwfl_detach_thread (Dwfl_Thread *thread); So with some iterator across threads one can detach each thread after unwinding it.
Yes, and one could inspect the thread (well just look at the tid I guess) before deciding to "commit" to unwind it. That does mean there is an extra step going from "process" to "thread" to "unwind", but then you can delay freezing/attaching to threads you are not interested in. See a suggested interface that has the above in my other email that crossed this one.
Cheers,
Mark
On Thu, 2013-06-13 at 15:48 +0200, Mark Wielaard wrote:
That is for the testcase. But I might have misunderstood. I never could make the selfdump tests work since it tries to find the "jmp" symbol (which seems to never be used?).
make check will not PASS for run-backtrace.sh? It PASSes for me.
$ nm tests/backtrace-child|grep -w jmp 0000000000000acb t jmp
Tested on Fedora 18 x86_64 and wit mjw/unwind now also on CentOS 6.3 x86_64.
That is indeed strange, those are similar to the platforms (f18 and rhel6.4 x86_64) that I also tested on. I assume I had something weird in my tree. I'll try again with clean trees. But I don't fully understand why/where jmp is actually used in the test case.
OK, on f18 x86_64 it does pass with a cleanly build tree. So I must have messed something up earlier, sorry.
But on rhel6.4 it does fail because nm tests/backtrace-child|grep -w jmp is empty, and so you get:
FAIL: run-backtrace.sh (exit: 1) ================================
backtrace: /home/mark/src/elfutils/tests/backtrace.c:439: selfdump: Assertion `symi < nsym' failed. /home/mark/src/elfutils/tests/test-subr.sh: line 84: 17144 Aborted (core dumped) LD_LIBRARY_PATH="${built_library_path}${LD_LIBRARY_PATH:+:}$LD_LIBRARY_PATH" $VALGRIND_CMD "$@"
I seem to need to build with CFLAGS="-g -O0" to keep the jmp function around. Then backtrace-child seems to succeed, but backtrace-child-biarch fails with:
FAIL: run-backtrace.sh (exit: 1) ================================
0x219000 0x21c000 /home/mark/build/elfutils-obj/tests/backtrace-child-biarch 0x21c000 0x3b0000 /lib/libc-2.12.so 0xbfd000 0xc16000 /lib/libpthread-2.12.so 0xf0b000 0xf0c000 [vdso: 28062] 0xf89000 0xfa9000 /lib/ld-2.12.so TID 28063: # 0 0xf0b430 (null) TID 28062: # 0 0xf0b430 (null) No DWARF information found No DWARF information found backtrace: /home/mark/src/elfutils/tests/backtrace.c:249: selfdump_callback: Assertion `seen_main' failed. /home/mark/src/elfutils/tests/test-subr.sh: line 84: 28061 Aborted (core dumped) LD_LIBRARY_PATH="${built_library_path}${LD_LIBRARY_PATH:+:}$LD_LIBRARY_PATH" $VALGRIND_CMD "$@" backtrace-child-biarch: no main
How odd. I'll try and see what is really going on here.
Cheers,
Mark
On Thu, 13 Jun 2013 16:41:55 +0200, Mark Wielaard wrote:
But on rhel6.4 it does fail because nm tests/backtrace-child|grep -w jmp is empty, and so you get:
FAIL: run-backtrace.sh (exit: 1)
backtrace: /home/mark/src/elfutils/tests/backtrace.c:439: selfdump: Assertion `symi < nsym' failed. /home/mark/src/elfutils/tests/test-subr.sh: line 84: 17144 Aborted (core dumped) LD_LIBRARY_PATH="${built_library_path}${LD_LIBRARY_PATH:+:}$LD_LIBRARY_PATH" $VALGRIND_CMD "$@"
I seem to need to build with CFLAGS="-g -O0" to keep the jmp function around. Then backtrace-child seems to succeed, but backtrace-child-biarch fails with:
OK, with CentOS-6.3 && -O2 build I also get FAIL.
This is because you broke it.
commit f4e6062e0e4eb89e349458802b40e7315e514fd6 Author: Mark Wielaard mjw@redhat.com GCC version check attributes, noclone was introduced with GCC 4.5
+#define NO_INLINE_CLONE __attribute__ ((noinline, noclone)) +#define NO_INLINE_CLONE __attribute__ ((noinline)) -static __attribute__ ((noinline, noclone, used)) void +static NO_INLINE_CLONE void
Thanks, Jan
On Thu, 2013-06-13 at 16:57 +0200, Jan Kratochvil wrote:
OK, with CentOS-6.3 && -O2 build I also get FAIL.
This is because you broke it.
commit f4e6062e0e4eb89e349458802b40e7315e514fd6 Author: Mark Wielaard mjw@redhat.com GCC version check attributes, noclone was introduced with GCC 4.5
+#define NO_INLINE_CLONE __attribute__ ((noinline, noclone)) +#define NO_INLINE_CLONE __attribute__ ((noinline)) -static __attribute__ ((noinline, noclone, used)) void +static NO_INLINE_CLONE void
Doh. So sorry. I fixed it on my branch now.
On Thu, 13 Jun 2013 16:41:55 +0200, Mark Wielaard wrote:
backtrace-child-biarch fails with:
FAIL: run-backtrace.sh (exit: 1)
[...]
TID 28063: # 0 0xf0b430 (null) TID 28062: # 0 0xf0b430 (null) No DWARF information found No DWARF information found
[...]
How odd. I'll try and see what is really going on here.
I got it reproduced on RHEL-6.3 x86_64. It is about 32-bit vDSO but it has .eh_frame there and the offset 0x430 is covered; I do not understand now why it happens, it needs some more investigation.
Thanks, Jan
On Thu, 13 Jun 2013 15:48:22 +0200, Mark Wielaard wrote:
But I don't fully understand why/where jmp is actually used in the test case.
It tests the unwinder can properly unwind even if we stop at the very first instruction of a function. Therefore that ISACTIVATION is true for the bottom frame.
Jan
On Thu, 2013-06-13 at 19:30 +0200, Jan Kratochvil wrote:
On Thu, 13 Jun 2013 15:48:22 +0200, Mark Wielaard wrote:
But I don't fully understand why/where jmp is actually used in the test case.
It tests the unwinder can properly unwind even if we stop at the very first instruction of a function. Therefore that ISACTIVATION is true for the bottom frame.
O, I finally see how it works. When the child raises SIGUSR1 then the backtrace.c testcase explicitly sets the PC to the first instruction of jmp in backtrace-child to check this condition. How tricky and crafty :)
On Thu, 2013-06-13 at 00:03 +0200, Mark Wielaard wrote:
What do you think? I am struggling a bit with this because I can see the user might want both (full freeze on begin or minimal thread freeze at use only).
What about this interface? Besides the dwarf_report_begin point, there are explicit actions for, attaching "state", iterating through threads, starting and ending a frame unwind. That seems to give the most flexibility. You can attach/freeze at any point, when starting to report Dwfl modules, when attaching the "state", when iterating through the threads or only when starting an unwind.
We can then provide an implementation that does attach on report dwfl_linux_proc_report_state (Dwfl *dwfl, pid_t pid) or one that only attaches on the start of a frame unwind (and detaches after the unwind finishes). That one could just be the existing dwfl_linux_proc_report (Dwfl *dwfl, pid_t pid) because it would only differ to the user when the other new interfaces are used. For core files we could just make dwfl_core_file_report (Dwfl *dwfl, Elf *elf) do the right thing.
So on the "backend" side the interface could be:
typedef struct Dwfl_Thread;
typedef struct { /* Called to iterate through threads. Starting with previous *ptid set to -1. Returns next thread id on success, a negative number on failure and zero if there are no more threads. Should call dwfl_thread_state () on nthread if thread can be unwound with initial register and memory accessors. */ pid_t (*next_thread) (Dwfl *dwfl, pid_t pid, pid_t *ptid, Dwfl_Thread *nthread, void *arg);
/* Called by dwfl_end (). */ void (*detach) (Dwfl *dwfl, pid_t pid, void *arg);
} Dwfl_State_Callbacks;
/* pid is the process id associated with the Dwfl state, arg is the callback backend state. Both will be provided to the callbacks. */ dwfl_attach_state (Dwfl *dwfl, pid_t pid, Dwfl_State_Callbacks *state, void *arg);
/* probably needed... */ Dwfl *dwfl_thread_dwfl (Dwfl_Thread *thread); pid_t dwfl_thread_pid (Dwfl_Thread *thread);
typedef struct { /* Called on initial unwind to get the initial register state of the first frame. Should call dwfl_thread_state_registers (), possibly multiple times for different ranges, to fill in initial (DWARF) register values. After this call, till at least thread_detach is called, the thread is assumed to be frozen, so that it is safe to unwind. Returns true on success or false and sets dwfl_err() on failure. */ bool (*set_initial_registers) (Dwfl_Thread *thread, pid_t tid, void *arg);
/* Called during unwinding to access memory (stack) state. */ bool (*memory_read) (Dwfl_Thread *thread, pid_t tid, Dwarf_Addr addr, Dwarf_Word *result, void *arg);
/* Called when unwinding is done. No callback will be called after this has been called. */ void (*thread_detach) (Dwfl_Thread *thread, pid_t tid, void *arg); } Dwfl_Thread_Callbacks;
dwfl_thread_state (Dwfl_Thread *thread, Dwfl_Thread_Callbacks *state);
pid_t dwfl_thread_tid (Dwfl_Thread *thread);
dwfl_thread_state_registers (Dwfl_Thread *thread, const int firstreg, unsigned nregs, const Dwarf_Word *regs);
Then (the existing?) dwfl_linux_proc_report and dwfl_core_file_report could setup the above callbacks. Or they could be setup by someone by hand if they can provide registers and memory accessors in some other way.
For the user/usage side we could then have:
/* Gets the next known thread, if any. To get the initial thread provide NULL as previous thread. */ Dwfl_Thread *dwfl_next_thread (Dwfl *dwfl, Dwfl_Thread *pthread);
/* Iterate through the frames for a thread. Returns zero if all frames have been processed by the callback, returns -1 on error, or the value of the callback when not DWARF_CB_OK. Keeps calling the callback with the next frame while the callback returns DWARF_CB_OK, till there are no more frames. On start will call the set_initial_registers callback and on return will call the detach_thread callback of the Dwfl_Thread. */ int dwfl_getdwarf (Dwfl_Thread *thread, int (*callback) (Dwfl_Frame *, void *), void *arg);
bool dwfl_frame_state_pc (Dwfl_Frame_State *state, Dwarf_Addr *pc, bool *isactivation)
Note the above is just a first suggestion, not even tried to compile yet. But unless I forgot something I think this is flexible enough to support different backends and policies for "freezing" a process.
Cheers,
Mark
On Thu, 13 Jun 2013 15:38:33 +0200, Mark Wielaard wrote:
typedef struct Dwfl_Thread;
typedef struct Dwfl_Thread Dwfl_Thread;
typedef struct { /* Called to iterate through threads. Starting with previous *ptid set to -1. Returns next thread id on success, a negative number on failure and zero if there are no more threads. Should call dwfl_thread_state () on nthread if thread can be unwound with initial register and memory accessors. */ pid_t (*next_thread) (Dwfl *dwfl, pid_t pid, pid_t *ptid, Dwfl_Thread *nthread, void *arg);
/* Called by dwfl_end (). */ void (*detach) (Dwfl *dwfl, pid_t pid, void *arg);
} Dwfl_State_Callbacks;
Couldn't it be merged together with Dwfl_Thread_Callbacks? This would also remove the dwfl_thread_state function. Dwfl_Thread_Callbacks->memory_read is a PID-related function, not TID-related.
Maybe rather Dwfl_Frame_Callbacks, I no longer remember much why I called it dwfl_frame_state and not just dwfl_frame.
/* pid is the process id associated with the Dwfl state, arg is the callback backend state. Both will be provided to the callbacks. */ dwfl_attach_state (Dwfl *dwfl, pid_t pid,
void?
Dwfl_State_Callbacks *state, void *arg);
Dwfl_State_Callbacks *state_callbacks, void *arg);
/* probably needed... */ Dwfl *dwfl_thread_dwfl (Dwfl_Thread *thread);
pid_t dwfl_thread_pid (Dwfl_Thread *thread);
PID or TID? You have declared dwfl_thread_tid below. But if it should be PID then it should be:
pid_t dwfl_pid (Dwfl *dwfl);
typedef struct { /* Called on initial unwind to get the initial register state of the first frame. Should call dwfl_thread_state_registers (), possibly multiple times for different ranges, to fill in initial (DWARF) register values. After this call, till at least thread_detach is called, the thread is assumed to be frozen, so that it is safe to unwind. Returns true on success or false and sets dwfl_err() on failure. */ bool (*set_initial_registers) (Dwfl_Thread *thread, pid_t tid, void *arg);
I think it should integrate dwfl_thread_state_registers, all the dwfl_thread_state_registers parameters should be here as return-references.
/* Called during unwinding to access memory (stack) state. */ bool (*memory_read) (Dwfl_Thread *thread, pid_t tid, Dwarf_Addr addr, Dwarf_Word *result, void *arg);
/* Called when unwinding is done. No callback will be called after this has been called. */ void (*thread_detach) (Dwfl_Thread *thread, pid_t tid, void *arg); } Dwfl_Thread_Callbacks;
dwfl_thread_state (Dwfl_Thread *thread, Dwfl_Thread_Callbacks *state);
Proposing to be removed above.
pid_t dwfl_thread_tid (Dwfl_Thread *thread);
dwfl_thread_state_registers (Dwfl_Thread *thread,
void?
const int firstreg, unsigned nregs, const Dwarf_Word *regs);
I would delete this function, see above. wrt its parameters:
This is apparently derived from dwfl_frame_state_data. But I do not see what is firstreg, where is pc/pc_set and where is regs_set.
PC_SET is true if PC contains a valid value. On some archs PC is not present in REGS. NREGS is number of valid registers in REGS and REGS_SET. REGS_SET is a bitfield specifying which REGS are valid.
extern Dwfl_Frame_State *dwfl_frame_state_data (Dwfl *dwfl, bool pc_set, Dwarf_Addr pc, unsigned nregs, const uint64_t *regs_set, const Dwarf_Addr *regs, dwfl_frame_memory_read_t *memory_read, void *memory_read_user_data);
Then (the existing?) dwfl_linux_proc_report and dwfl_core_file_report could setup the above callbacks. Or they could be setup by someone by hand if they can provide registers and memory accessors in some other way.
Great idea.
For the user/usage side we could then have:
/* Gets the next known thread, if any. To get the initial thread provide NULL as previous thread. */ Dwfl_Thread *dwfl_next_thread (Dwfl *dwfl, Dwfl_Thread *pthread);
/* Iterate through the frames for a thread. Returns zero if all frames have been processed by the callback, returns -1 on error, or the value of the callback when not DWARF_CB_OK. Keeps calling the callback with the next frame while the callback returns DWARF_CB_OK, till there are no more frames. On start will call the set_initial_registers callback and on return will call the detach_thread callback of the Dwfl_Thread. */ int dwfl_getdwarf (Dwfl_Thread *thread,
dwfl_getframes? I do not understand the name "getdwarf".
int (*callback) (Dwfl_Frame *, void *),
Dwfl_Frame_State * Although maybe we could rename it to Dwfl_Frame anyway.
void *arg);
bool dwfl_frame_state_pc (Dwfl_Frame_State *state, Dwarf_Addr *pc, bool *isactivation)
Thanks, Jan
On Thu, 2013-06-13 at 19:26 +0200, Jan Kratochvil wrote:
On Thu, 13 Jun 2013 15:38:33 +0200, Mark Wielaard wrote:
typedef struct Dwfl_Thread;
typedef struct Dwfl_Thread Dwfl_Thread;
typedef struct { /* Called to iterate through threads. Starting with previous *ptid set to -1. Returns next thread id on success, a negative number on failure and zero if there are no more threads. Should call dwfl_thread_state () on nthread if thread can be unwound with initial register and memory accessors. */ pid_t (*next_thread) (Dwfl *dwfl, pid_t pid, pid_t *ptid, Dwfl_Thread *nthread, void *arg);
/* Called by dwfl_end (). */ void (*detach) (Dwfl *dwfl, pid_t pid, void *arg);
} Dwfl_State_Callbacks;
Couldn't it be merged together with Dwfl_Thread_Callbacks?
Yes they probably could/should. I imagined you might want to have totally different thread callbacks per thread, but that is kind of silly I guess. As long as the callbacks do get the thread identifier it should be flexible enough.
This would also remove the dwfl_thread_state function. Dwfl_Thread_Callbacks->memory_read is a PID-related function, not TID-related.
Yes, but I imagined a callback could depend on the requested tid (to make sure it is attached) or have stored memory hunks (stacks) of the different threads separately, so might like to know for which tid this memory request is.
Maybe rather Dwfl_Frame_Callbacks, I no longer remember much why I called it dwfl_frame_state and not just dwfl_frame.
I also like Dwfl_Thread_Callbacks (if we have just one callback struct. But any name will do.
/* pid is the process id associated with the Dwfl state, arg is the callback backend state. Both will be provided to the callbacks. */ dwfl_attach_state (Dwfl *dwfl, pid_t pid,
void?
Yes, I cannot imagine any return value making sense, it just sets some state on the Dwfl, which cannot ever fail. Can it?
Dwfl_State_Callbacks *state, void *arg);
Dwfl_State_Callbacks *state_callbacks, void *arg);
/* probably needed... */ Dwfl *dwfl_thread_dwfl (Dwfl_Thread *thread);
pid_t dwfl_thread_pid (Dwfl_Thread *thread);
PID or TID? You have declared dwfl_thread_tid below. But if it should be PID then it should be: pid_t dwfl_pid (Dwfl *dwfl);
ah, yes, it should be dwfl_pid and dwfl_thread_tid. With dwfl_pid () returning -1 if dwfl_attach_state has not been called.
typedef struct { /* Called on initial unwind to get the initial register state of the first frame. Should call dwfl_thread_state_registers (), possibly multiple times for different ranges, to fill in initial (DWARF) register values. After this call, till at least thread_detach is called, the thread is assumed to be frozen, so that it is safe to unwind. Returns true on success or false and sets dwfl_err() on failure. */ bool (*set_initial_registers) (Dwfl_Thread *thread, pid_t tid, void *arg);
I think it should integrate dwfl_thread_state_registers, all the dwfl_thread_state_registers parameters should be here as return-references.
I thought you might want to call dwfl_thread_state_registers multiple times from this callback. See below.
/* Called during unwinding to access memory (stack) state. */ bool (*memory_read) (Dwfl_Thread *thread, pid_t tid, Dwarf_Addr addr, Dwarf_Word *result, void *arg);
/* Called when unwinding is done. No callback will be called after this has been called. */ void (*thread_detach) (Dwfl_Thread *thread, pid_t tid, void *arg); } Dwfl_Thread_Callbacks;
dwfl_thread_state (Dwfl_Thread *thread, Dwfl_Thread_Callbacks *state);
Proposing to be removed above.
Yes if we merge them into one.
pid_t dwfl_thread_tid (Dwfl_Thread *thread);
dwfl_thread_state_registers (Dwfl_Thread *thread,
void?
const int firstreg, unsigned nregs, const Dwarf_Word *regs);
I would delete this function, see above. wrt its parameters:
This is apparently derived from dwfl_frame_state_data. But I do not see what is firstreg, where is pc/pc_set and where is regs_set.
PC_SET is true if PC contains a valid value. On some archs PC is not present in REGS.
ah, I thought it was always defined as a dwarf register number, even if it was just a "fake" return register. But I might misremember.
NREGS is number of valid registers in REGS and REGS_SET. REGS_SET is a bitfield specifying which REGS are valid.
Yes, I should have explained. When you reminded me of the messy powerpc DWARF register numbering I thought it would be more flexible if you could call dwfl_thread_state_registers multiple times for valid ranges of (DWARF) registers. So instead of having a valid registers bitfield mask you would just call it only for ranges of registers that are valid (firstregnr..firstregnr+nregs). Then you don't need to have pass around very large (sparse) arrays.
/* Iterate through the frames for a thread. Returns zero if all frames have been processed by the callback, returns -1 on error, or the value of the callback when not DWARF_CB_OK. Keeps calling the callback with the next frame while the callback returns DWARF_CB_OK, till there are no more frames. On start will call the set_initial_registers callback and on return will call the detach_thread callback of the Dwfl_Thread. */ int dwfl_getdwarf (Dwfl_Thread *thread,
dwfl_getframes?
I do not understand the name "getdwarf".
Grin, I also don't understand it. I thought I had called it dwfl_thread_getframes.
int (*callback) (Dwfl_Frame *, void *),
Dwfl_Frame_State *
Although maybe we could rename it to Dwfl_Frame anyway.
I like the shorter name (although it was a typo).
Cheers,
Mark
On Thu, 13 Jun 2013 22:47:10 +0200, Mark Wielaard wrote:
On Thu, 2013-06-13 at 19:26 +0200, Jan Kratochvil wrote:
/* pid is the process id associated with the Dwfl state, arg is the callback backend state. Both will be provided to the callbacks. */ dwfl_attach_state (Dwfl *dwfl, pid_t pid,
void?
Yes, I cannot imagine any return value making sense, it just sets some state on the Dwfl, which cannot ever fail. Can it?
What if one does two dwfl_attach_state to the same DWFL? Maybe the second one will override the first one. Although it could rather fail and we would also need to provide dwfl_detach_state. But for now I put there 'void'.
PC_SET is true if PC contains a valid value. On some archs PC is not present in REGS.
ah, I thought it was always defined as a dwarf register number, even if it was just a "fake" return register. But I might misremember.
ppc has LR register which is somehow both 108 and 65. http://en.wikipedia.org/wiki/Link_register
But this is not PC, PC IMO does not have any DWARF register. DWARF never needs to address PC itself, DWARF only needs to address "return address" and that is LR. But during the "activation" we need to set both PC and LR, those are two different registers.
Maybe one of 65 and 108 should have been LR and the other one PC. But in reality there are some bugs and both 65 and 108 are used interchangeable for LR, therefore I aliased 108 to 65 (ppc_frame_dwarf_to_regno).
s390 PC (called PSWA) also does not seem to have any DWARF register number. Link register is called r14 there.
Therefore along the proposed dwfl_thread_state_registers function I have put there also:
void dwfl_thread_state_register_pc (Dwfl_Thread *thread, Dwarf_Word pc);
Then you don't need to have pass around very large (sparse) arrays.
FYI you did not have to deal with any large/sparse arrays. The unwinder maintains "compressed" registers as remapped by:
ppc_frame_dwarf_to_regno (Ebl *ebl __attribute__ ((unused)), unsigned *regno) case 1200 ... 1231: *regno = *regno - 1200 + (114 - 1);
And dwfl_frame_state_data is passed this "compressed" form; the disadvantage is that the numbering is no longer pure DWARF so I agree dwfl_thread_state_registers approach may be easier.
I have removed some duplicities - like if DWFL is passed then we do not need to also pass PID, because we have pid_t dwfl_pid (Dwfl *dwfl);
Thanks, Jan
typedef int bool; typedef int pid_t; typedef struct Dwfl Dwfl; typedef struct Dwarf_Addr Dwarf_Addr; typedef struct Dwarf_Word Dwarf_Word; typedef struct Dwfl_Thread Dwfl_Thread; typedef struct Dwfl_Frame Dwfl_Frame;
typedef struct { /* Called to iterate through threads. Returns next TID (tthread ID) on success, a negative number on failure and zero if there are no more threads. For successful results the implementation must call dwfl_thread_state on NTHREAD. This method must not be NULL. */ pid_t (*next_thread) (Dwfl *dwfl, Dwfl_Thread *nthread, void *arg);
/* Called by dwfl_end. All thread_detach method calls have been already done. This method may be NULL. */ void (*detach) (Dwfl *dwfl, void *arg);
/* Called during unwinding to access memory (stack) state. Returns true for successfully read *RESULT or false and sets dwfl_err () on failure. This method must not be NULL. */ bool (*memory_read) (Dwfl *dwfl, Dwarf_Addr addr, Dwarf_Word *result, void *arg);
/* Called on initial unwind to get the initial register state of the first frame. Should call dwfl_thread_state_registers, possibly multiple times for different ranges and possibly also dwfl_thread_state_register_pc, to fill in initial (DWARF) register values. After this call, till at least thread_detach is called, the thread is assumed to be frozen, so that it is safe to unwind. Returns true on success or false and sets dwfl_err () on failure. This method must not be NULL. */ bool (*set_initial_registers) (Dwfl_Thread *thread, void *arg);
/* Called when unwinding is done. No callback will be called after this method has been called. Iff set_initial_registers was called for a TID thread_detach will be called before the detach method above. This method may be NULL. */ void (*thread_detach) (Dwfl_Thread *thread, void *arg); } Dwfl_Thread_Callbacks;
/* PID is the process id associated with the DWFL state, ARG is the callback backend state. ARG will be provided to the callbacks. */ void dwfl_attach_state (Dwfl *dwfl, pid_t pid, Dwfl_Thread_Callbacks *thread_callbacks, void *arg);
/* Return PID for the process associated with DWFL. Function returns -1 if dwfl_attach_state was not called for DWFL. */ pid_t dwfl_pid (Dwfl *dwfl);
/* Return DWFL from which THREAD was created using dwfl_next_thread. */ Dwfl *dwfl_thread_dwfl (Dwfl_Thread *thread);
/* Return positive TID (thread ID) for THREAD. This function never fails. */ pid_t dwfl_thread_tid (Dwfl_Thread *thread);
/* Called by Dwfl_Thread_Callbacks.set_initial_registers implementation. For every known continuous block of registers <FIRSTREG..FIRSTREG+NREGS) (inclusive..exclusive) set their content to REGS (array of NREGS items). */ void dwfl_thread_state_registers (Dwfl_Thread *thread, const int firstreg, unsigned nregs, const Dwarf_Word *regs);
/* Called by Dwfl_Thread_Callbacks.set_initial_registers implementation. If PC is not contained among DWARF registers passed by dwfl_thread_state_registers on the target architecture pass the PC value here. */ void dwfl_thread_state_register_pc (Dwfl_Thread *thread, Dwarf_Word pc);
/* Gets the next known thread, if any. To get the initial thread provide NULL as previous thread PREV_THREAD. */ Dwfl_Thread *dwfl_next_thread (Dwfl *dwfl, Dwfl_Thread *prev_thread);
/* Iterate through the frames for a thread. Returns zero if all frames have been processed by the callback, returns -1 on error, or the value of the callback when not DWARF_CB_OK. Keeps calling the callback with the next frame while the callback returns DWARF_CB_OK, till there are no more frames. On start will call the set_initial_registers callback and on return will call the detach_thread callback of the Dwfl_Thread. */ int dwfl_thread_getframes (Dwfl_Thread *thread, int (*callback) (Dwfl_Frame *, void *));
/* Return *PC (program counter) for thread-specific frame STATE. Set *ISACTIVATION according to DWARF frame "activation" definition. Typically you need to substract 1 from *PC if *ACTIVATION is false to safely find function of the caller. ACTIVATION may be NULL. PC must not be NULL. Function returns false if it failed to find *PC. */ bool Dwfl_Frame_pc (Dwfl_Frame *state, Dwarf_Addr *pc, bool *isactivation);
On Sat, 2013-06-15 at 18:27 +0200, Jan Kratochvil wrote:
On Thu, 13 Jun 2013 22:47:10 +0200, Mark Wielaard wrote:
On Thu, 2013-06-13 at 19:26 +0200, Jan Kratochvil wrote:
/* pid is the process id associated with the Dwfl state, arg is the callback backend state. Both will be provided to the callbacks. */ dwfl_attach_state (Dwfl *dwfl, pid_t pid,
void?
Yes, I cannot imagine any return value making sense, it just sets some state on the Dwfl, which cannot ever fail. Can it?
What if one does two dwfl_attach_state to the same DWFL? Maybe the second one will override the first one. Although it could rather fail and we would also need to provide dwfl_detach_state. But for now I put there 'void'.
Good point. Maybe in the future we want to support "reattaching" (maybe switching from fully detached state to fully attached/frozen state). Even if we don't support that now. Lets add a return value just in case, even if for now we always succeed when not yet set and always fail when trying to reset.
PC_SET is true if PC contains a valid value. On some archs PC is not present in REGS.
ah, I thought it was always defined as a dwarf register number, even if it was just a "fake" return register. But I might misremember.
ppc has LR register which is somehow both 108 and 65. http://en.wikipedia.org/wiki/Link_register
But this is not PC, PC IMO does not have any DWARF register. DWARF never needs to address PC itself, DWARF only needs to address "return address" and that is LR. But during the "activation" we need to set both PC and LR, those are two different registers.
Maybe one of 65 and 108 should have been LR and the other one PC. But in reality there are some bugs and both 65 and 108 are used interchangeable for LR, therefore I aliased 108 to 65 (ppc_frame_dwarf_to_regno).
ah, forgot about PPC again (or link registers in general). I was thinking of x86 where the Return Address is a "fake" DWARF register, but can in practice be used as the actual IP register. You are right, we need to handle PC explicitly.
I have removed some duplicities - like if DWFL is passed then we do not need to also pass PID, because we have pid_t dwfl_pid (Dwfl *dwfl);
Yeah, that was more "convenience", but clutters the interface.
typedef struct { /* Called to iterate through threads. Returns next TID (tthread ID) on success, a negative number on failure and zero if there are no more threads. For successful results the implementation must call dwfl_thread_state on NTHREAD. This method must not be NULL. */ pid_t (*next_thread) (Dwfl *dwfl, Dwfl_Thread *nthread, void *arg);
I think this should get Dwfl_Thread *pthread also as argument, indicating the previous thread (NULL on first call).
/* Called by dwfl_end. All thread_detach method calls have been already done. This method may be NULL. */ void (*detach) (Dwfl *dwfl, void *arg);
/* Called during unwinding to access memory (stack) state. Returns true for successfully read *RESULT or false and sets dwfl_err () on failure. This method must not be NULL. */ bool (*memory_read) (Dwfl *dwfl, Dwarf_Addr addr, Dwarf_Word *result, void *arg);
If NULL, then unwinding is not supported. Which might not be fatal, someone could just be interested in the initial state/PC of the thread and don't care if they cannot unwind (fully).
/* Called on initial unwind to get the initial register state of the first frame. Should call dwfl_thread_state_registers, possibly multiple times for different ranges and possibly also dwfl_thread_state_register_pc, to fill in initial (DWARF) register values. After this call, till at least thread_detach is called, the thread is assumed to be frozen, so that it is safe to unwind. Returns true on success or false and sets dwfl_err () on failure. This method must not be NULL. */ bool (*set_initial_registers) (Dwfl_Thread *thread, void *arg);
/* Called when unwinding is done. No callback will be called after this method has been called. Iff set_initial_registers was called for a TID thread_detach will be called before the detach method above. This method may be NULL. */ void (*thread_detach) (Dwfl_Thread *thread, void *arg); } Dwfl_Thread_Callbacks;
/* PID is the process id associated with the DWFL state, ARG is the callback backend state. ARG will be provided to the callbacks. */ void dwfl_attach_state (Dwfl *dwfl, pid_t pid, Dwfl_Thread_Callbacks *thread_callbacks, void *arg);
Lets do add a return value to indicate success or failure as discussed above.
/* Return PID for the process associated with DWFL. Function returns -1 if dwfl_attach_state was not called for DWFL. */ pid_t dwfl_pid (Dwfl *dwfl);
/* Return DWFL from which THREAD was created using dwfl_next_thread. */ Dwfl *dwfl_thread_dwfl (Dwfl_Thread *thread);
/* Return positive TID (thread ID) for THREAD. This function never fails. */ pid_t dwfl_thread_tid (Dwfl_Thread *thread);
/* Called by Dwfl_Thread_Callbacks.set_initial_registers implementation. For every known continuous block of registers <FIRSTREG..FIRSTREG+NREGS) (inclusive..exclusive) set their content to REGS (array of NREGS items). */ void dwfl_thread_state_registers (Dwfl_Thread *thread, const int firstreg, unsigned nregs, const Dwarf_Word *regs);
/* Called by Dwfl_Thread_Callbacks.set_initial_registers implementation. If PC is not contained among DWARF registers passed by dwfl_thread_state_registers on the target architecture pass the PC value here. */ void dwfl_thread_state_register_pc (Dwfl_Thread *thread, Dwarf_Word pc);
/* Gets the next known thread, if any. To get the initial thread provide NULL as previous thread PREV_THREAD. */ Dwfl_Thread *dwfl_next_thread (Dwfl *dwfl, Dwfl_Thread *prev_thread);
/* Iterate through the frames for a thread. Returns zero if all frames have been processed by the callback, returns -1 on error, or the value of the callback when not DWARF_CB_OK. Keeps calling the callback with the next frame while the callback returns DWARF_CB_OK, till there are no more frames. On start will call the set_initial_registers callback and on return will call the detach_thread callback of the Dwfl_Thread. */ int dwfl_thread_getframes (Dwfl_Thread *thread, int (*callback) (Dwfl_Frame *, void *));
Needs an extra (possibly NULL) void *arg argument that will be passed to the callback.
/* Return *PC (program counter) for thread-specific frame STATE. Set *ISACTIVATION according to DWARF frame "activation" definition. Typically you need to substract 1 from *PC if *ACTIVATION is false to safely find function of the caller. ACTIVATION may be NULL. PC must not be NULL. Function returns false if it failed to find *PC. */ bool Dwfl_Frame_pc (Dwfl_Frame *state, Dwarf_Addr *pc, bool *isactivation);
lower case dwfl_frame_pc I presume.
Thanks,
Mark
On Mon, 17 Jun 2013 10:57:57 +0200, Mark Wielaard wrote:
On Sat, 2013-06-15 at 18:27 +0200, Jan Kratochvil wrote:
typedef struct { /* Called to iterate through threads. Returns next TID (tthread ID) on success, a negative number on failure and zero if there are no more threads. For successful results the implementation must call dwfl_thread_state on NTHREAD. This method must not be NULL. */ pid_t (*next_thread) (Dwfl *dwfl, Dwfl_Thread *nthread, void *arg);
I think this should get Dwfl_Thread *pthread also as argument, indicating the previous thread (NULL on first call).
I do not see it useful. In callbacks I wrote I need some internal iterator stored in ARG. What PTHREAD would be useful for? At most for TID but naively linearly looking up TID in the callback's data structures would typically lead to n^2 time complexity, with some better data structures to n*log(n). Still an iterator stored in ARG gives normal linear n time complexity.
Otherwise agreed, API updated. I will refactor the code as we were talking off-list.
Thanks, Jan
typedef int bool; typedef int pid_t; typedef struct Dwfl Dwfl; typedef struct Dwarf_Addr Dwarf_Addr; typedef struct Dwarf_Word Dwarf_Word; typedef struct Dwfl_Thread Dwfl_Thread; typedef struct Dwfl_Frame Dwfl_Frame;
typedef struct { /* Called to iterate through threads. Returns next TID (tthread ID) on success, a negative number on failure and zero if there are no more threads. For successful results the implementation must call dwfl_thread_state on NTHREAD. This method must not be NULL. */ pid_t (*next_thread) (Dwfl *dwfl, Dwfl_Thread *nthread, void *arg);
/* Called by dwfl_end. All thread_detach method calls have been already done. This method may be NULL. */ void (*detach) (Dwfl *dwfl, void *arg);
/* Called during unwinding to access memory (stack) state. Returns true for successfully read *RESULT or false and sets dwfl_err () on failure. This method may be NULL - in such case dwfl_thread_getframes will return only the initial frame. */ bool (*memory_read) (Dwfl *dwfl, Dwarf_Addr addr, Dwarf_Word *result, void *arg);
/* Called on initial unwind to get the initial register state of the first frame. Should call dwfl_thread_state_registers, possibly multiple times for different ranges and possibly also dwfl_thread_state_register_pc, to fill in initial (DWARF) register values. After this call, till at least thread_detach is called, the thread is assumed to be frozen, so that it is safe to unwind. Returns true on success or false and sets dwfl_err () on failure. This method must not be NULL. */ bool (*set_initial_registers) (Dwfl_Thread *thread, void *arg);
/* Called when unwinding is done. No callback will be called after this method has been called. Iff set_initial_registers was called for a TID thread_detach will be called before the detach method above. This method may be NULL. */ void (*thread_detach) (Dwfl_Thread *thread, void *arg); } Dwfl_Thread_Callbacks;
/* PID is the process id associated with the DWFL state, ARG is the callback backend state. ARG will be provided to the callbacks. Function returns true on success, false otherwise. */ bool dwfl_attach_state (Dwfl *dwfl, pid_t pid, Dwfl_Thread_Callbacks *thread_callbacks, void *arg);
/* Return PID for the process associated with DWFL. Function returns -1 if dwfl_attach_state was not called for DWFL. */ pid_t dwfl_pid (Dwfl *dwfl);
/* Return DWFL from which THREAD was created using dwfl_next_thread. */ Dwfl *dwfl_thread_dwfl (Dwfl_Thread *thread);
/* Return positive TID (thread ID) for THREAD. This function never fails. */ pid_t dwfl_thread_tid (Dwfl_Thread *thread);
/* Called by Dwfl_Thread_Callbacks.set_initial_registers implementation. For every known continuous block of registers <FIRSTREG..FIRSTREG+NREGS) (inclusive..exclusive) set their content to REGS (array of NREGS items). */ void dwfl_thread_state_registers (Dwfl_Thread *thread, const int firstreg, unsigned nregs, const Dwarf_Word *regs);
/* Called by Dwfl_Thread_Callbacks.set_initial_registers implementation. If PC is not contained among DWARF registers passed by dwfl_thread_state_registers on the target architecture pass the PC value here. */ void dwfl_thread_state_register_pc (Dwfl_Thread *thread, Dwarf_Word pc);
/* Gets the next known thread, if any. To get the initial thread provide NULL as previous thread PREV_THREAD. */ Dwfl_Thread *dwfl_next_thread (Dwfl *dwfl, Dwfl_Thread *prev_thread);
/* Iterate through the frames for a thread. Returns zero if all frames have been processed by the callback, returns -1 on error, or the value of the callback when not DWARF_CB_OK. Keeps calling the callback with the next frame while the callback returns DWARF_CB_OK, till there are no more frames. On start will call the set_initial_registers callback and on return will call the detach_thread callback of the Dwfl_Thread. */ int dwfl_thread_getframes (Dwfl_Thread *thread, int (*callback) (Dwfl_Frame *state, void *arg), void *arg);
/* Return *PC (program counter) for thread-specific frame STATE. Set *ISACTIVATION according to DWARF frame "activation" definition. Typically you need to substract 1 from *PC if *ACTIVATION is false to safely find function of the caller. ACTIVATION may be NULL. PC must not be NULL. Function returns false if it failed to find *PC. */ bool dwfl_frame_pc (Dwfl_Frame *state, Dwarf_Addr *pc, bool *isactivation);
elfutils-devel@lists.fedorahosted.org