diff options
author | Jan Kratochvil <jan.kratochvil@redhat.com> | 2013-05-30 14:37:38 +0200 |
---|---|---|
committer | Jan Kratochvil <jan.kratochvil@redhat.com> | 2013-11-07 21:39:00 +0100 |
commit | 0b867460075c9f02cb305abc91a0e12b90017583 (patch) | |
tree | 28f799db9b0eef70d0b4b5e81163b2fc62903470 | |
parent | 22f867adcc1258aeb88560fa8591ef071353bb22 (diff) | |
download | android_external_elfutils-0b867460075c9f02cb305abc91a0e12b90017583.tar.gz android_external_elfutils-0b867460075c9f02cb305abc91a0e12b90017583.tar.bz2 android_external_elfutils-0b867460075c9f02cb305abc91a0e12b90017583.zip |
Unwinder for x86*.
Signed-off-by: Jan Kratochvil <jan.kratochvil@redhat.com>
Signed-off-by: Mark Wielaard <mjw@redhat.com>
36 files changed, 2623 insertions, 32 deletions
@@ -1,3 +1,8 @@ +2013-11-07 Jan Kratochvil <jan.kratochvil@redhat.com> + + * configure.ac: New AC_CHECK_SIZEOF for long. Call utrace_BIARCH, new + AC_SUBST for CC_BIARCH. + 2013-11-06 Mark Wielaard <mjw@redhat.com> * configure.ac (--enable-dwz): Add AC_MSG_WARN when disabled but diff --git a/backends/ChangeLog b/backends/ChangeLog index 28c807a9..3c57f8c3 100644 --- a/backends/ChangeLog +++ b/backends/ChangeLog @@ -1,3 +1,15 @@ +2013-11-07 Jan Kratochvil <jan.kratochvil@redhat.com> + Mark Wielaard <mjw@redhat.com> + + * Makefile.am (i386_SRCS): Add i386_initreg.c. + (x86_64_SRCS): Add x86_64_initreg.c. + * i386_initreg.c: New file. + * i386_init.c (i386_init): Initialize frame_nregs and + set_initial_registers_tid. + * x86_64_initreg.c: New file. + * x86_64_init.c (x86_64_init): Initialize frame_nregs and + set_initial_registers_tid. + 2013-10-06 Mark Wielaard <mjw@redhat.com> * ppc_cfi.c (ppc_abi_cfi): Use DW_CFA_val_offset for reg1, not diff --git a/backends/Makefile.am b/backends/Makefile.am index 557ed879..5b5e0676 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_initreg.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_initreg.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_init.c b/backends/i386_init.c index cc9b2d7f..1e0b4863 100644 --- a/backends/i386_init.c +++ b/backends/i386_init.c @@ -1,5 +1,5 @@ /* Initialization of i386 specific backend library. - Copyright (C) 2000-2009 Red Hat, Inc. + Copyright (C) 2000-2009, 2013 Red Hat, Inc. This file is part of elfutils. Written by Ulrich Drepper <drepper@redhat.com>, 2000. @@ -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_nregs = 9; + HOOK (eh, set_initial_registers_tid); return MODVERSION; } diff --git a/backends/i386_initreg.c b/backends/i386_initreg.c new file mode 100644 index 00000000..9e819a47 --- /dev/null +++ b/backends/i386_initreg.c @@ -0,0 +1,79 @@ +/* Fetch live process registers from TID. + 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 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/types.h> +# include <sys/user.h> +# include <sys/ptrace.h> +#endif + +#define BACKEND i386_ +#include "libebl_CPU.h" + +bool +i386_set_initial_registers_tid (pid_t tid __attribute__ ((unused)), + ebl_tid_registers_t *setfunc __attribute__ ((unused)), + void *arg __attribute__ ((unused))) +{ +#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; + Dwarf_Word dwarf_regs[9]; +# if defined __i386__ + dwarf_regs[0] = user_regs.eax; + dwarf_regs[1] = user_regs.ecx; + dwarf_regs[2] = user_regs.edx; + dwarf_regs[3] = user_regs.ebx; + dwarf_regs[4] = user_regs.esp; + dwarf_regs[5] = user_regs.ebp; + dwarf_regs[6] = user_regs.esi; + dwarf_regs[7] = user_regs.edi; + dwarf_regs[8] = user_regs.eip; +# elif defined __x86_64__ + dwarf_regs[0] = user_regs.rax; + dwarf_regs[1] = user_regs.rcx; + dwarf_regs[2] = user_regs.rdx; + dwarf_regs[3] = user_regs.rbx; + dwarf_regs[4] = user_regs.rsp; + dwarf_regs[5] = user_regs.rbp; + dwarf_regs[6] = user_regs.rsi; + dwarf_regs[7] = user_regs.rdi; + dwarf_regs[8] = user_regs.rip; +# else /* (__i386__ || __x86_64__) && (!__i386__ && !__x86_64__) */ +# error "source file error, it cannot happen" +# endif /* (__i386__ || __x86_64__) && (!__i386__ && !__x86_64__) */ + return setfunc (0, 9, dwarf_regs, arg); +#endif /* __i386__ || __x86_64__ */ +} diff --git a/backends/x86_64_init.c b/backends/x86_64_init.c index 67a58804..b885558b 100644 --- a/backends/x86_64_init.c +++ b/backends/x86_64_init.c @@ -1,5 +1,5 @@ /* Initialization of x86-64 specific backend library. - Copyright (C) 2002-2009 Red Hat, Inc. + Copyright (C) 2002-2009, 2013 Red Hat, Inc. This file is part of elfutils. Written by Ulrich Drepper <drepper@redhat.com>, 2002. @@ -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_nregs = 17; + HOOK (eh, set_initial_registers_tid); return MODVERSION; } diff --git a/backends/x86_64_initreg.c b/backends/x86_64_initreg.c new file mode 100644 index 00000000..0c493640 --- /dev/null +++ b/backends/x86_64_initreg.c @@ -0,0 +1,73 @@ +/* Fetch live process registers from TID. + 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 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 + +#define BACKEND x86_64_ +#include "libebl_CPU.h" + +bool +x86_64_set_initial_registers_tid (pid_t tid __attribute__ ((unused)), + ebl_tid_registers_t *setfunc __attribute__ ((unused)), + void *arg __attribute__ ((unused))) +{ +#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; + Dwarf_Word dwarf_regs[17]; + dwarf_regs[0] = user_regs.rax; + dwarf_regs[1] = user_regs.rdx; + dwarf_regs[2] = user_regs.rcx; + dwarf_regs[3] = user_regs.rbx; + dwarf_regs[4] = user_regs.rsi; + dwarf_regs[5] = user_regs.rdi; + dwarf_regs[6] = user_regs.rbp; + dwarf_regs[7] = user_regs.rsp; + dwarf_regs[8] = user_regs.r8; + dwarf_regs[9] = user_regs.r9; + dwarf_regs[10] = user_regs.r10; + dwarf_regs[11] = user_regs.r11; + dwarf_regs[12] = user_regs.r12; + dwarf_regs[13] = user_regs.r13; + dwarf_regs[14] = user_regs.r14; + dwarf_regs[15] = user_regs.r15; + dwarf_regs[16] = user_regs.rip; + return setfunc (0, 17, dwarf_regs, arg); +#endif /* __x86_64__ */ +} diff --git a/configure.ac b/configure.ac index 03d5756f..99b74ae5 100644 --- a/configure.ac +++ b/configure.ac @@ -326,4 +326,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/ChangeLog b/libdw/ChangeLog index d4c1049d..c355c306 100644 --- a/libdw/ChangeLog +++ b/libdw/ChangeLog @@ -1,3 +1,11 @@ +2013-11-07 Jan Kratochvil <jan.kratochvil@redhat.com> + + * cfi.h (struct Dwarf_Frame_s): Make the comment more specific. + * libdw.map (ELFUTILS_0.156): Add dwfl_attach_state, dwfl_pid, + dwfl_thread_dwfl, dwfl_thread_tid, dwfl_frame_thread, + dwfl_thread_state_registers, dwfl_thread_state_register_pc, + dwfl_getthreads, dwfl_thread_getframes and dwfl_frame_pc. + 2013-11-01 Michael Forney <mforney@mforney.org> * Makefile.am (libdwfl_objects): New definition. diff --git a/libdw/cfi.h b/libdw/cfi.h index 89498335..98ac6cfa 100644 --- a/libdw/cfi.h +++ b/libdw/cfi.h @@ -1,5 +1,5 @@ /* Internal definitions for libdw CFI interpreter. - Copyright (C) 2009-2010 Red Hat, Inc. + Copyright (C) 2009-2010, 2013 Red Hat, Inc. This file is part of elfutils. This file is free software; you can redistribute it and/or modify @@ -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 5fb6660e..922608ac 100644 --- a/libdw/libdw.map +++ b/libdw/libdw.map @@ -259,6 +259,16 @@ ELFUTILS_0.156 { global: # Replaced ELFUTILS_0.122 version, which has a wrapper without add_p_vaddr. dwfl_report_elf; + dwfl_attach_state; + dwfl_pid; + dwfl_thread_dwfl; + dwfl_thread_tid; + dwfl_frame_thread; + dwfl_thread_state_registers; + dwfl_thread_state_register_pc; + dwfl_getthreads; + dwfl_thread_getframes; + dwfl_frame_pc; } ELFUTILS_0.149; ELFUTILS_0.157 { diff --git a/libdwfl/ChangeLog b/libdwfl/ChangeLog index dc599210..467b4869 100644 --- a/libdwfl/ChangeLog +++ b/libdwfl/ChangeLog @@ -1,4 +1,49 @@ 2013-11-07 Jan Kratochvil <jan.kratochvil@redhat.com> + Mark Wielaard <mjw@redhat.com> + + * Makefile.am (libdwfl_a_SOURCES): Add dwfl_frame.c, frame_unwind.c, + dwfl_frame_pc.c, linux-pid-attach.c, linux-core-attach.c and + dwfl_frame_regs.c. + * core-file.c (dwfl_core_file_report): Call + __libdwfl_attach_state_for_core. + * dwfl_end.c (dwfl_end): Call __libdwfl_process_free. + * dwfl_frame.c: New file. + * frame_unwind.c: New file. + * dwfl_frame_pc.c: New file. + * linux-pid-attach.c: New file. + * linux-core-attach.c: New file. + * dwfl_frame_regs.c: New file. + * libdwfl.h (Dwfl_Thread, Dwfl_Frame): New typedefs. + (dwfl_core_file_report, dwfl_linux_proc_report): Extend comments. + (Dwfl_Thread_Callbacks): New definition. + (struct ebl, dwfl_attach_state, dwfl_pid, dwfl_thread_dwfl) + (dwfl_thread_tid, dwfl_frame_thread, dwfl_thread_state_registers) + (dwfl_thread_state_register_pc, dwfl_getthreads, dwfl_thread_getframes) + (dwfl_frame_pc): New declarations. + * libdwflP.h (Dwfl_Process): New typedef. + (LIBEBL_BAD, CORE_MISSING, INVALID_REGISTER, PROCESS_MEMORY_READ) + (PROCESS_NO_ARCH, PARSE_PROC, INVALID_DWARF, UNSUPPORTED_DWARF) + (NEXT_THREAD_FAIL, ATTACH_STATE_CONFLICT, NO_ATTACH_STATE, NO_UNWIND) + (INVALID_ARGUMENT): New DWFL_ERROR entries. + (struct Dwfl): New entry process. + (struct Dwfl_Process, struct Dwfl_Thread, struct Dwfl_Frame) + (__libdwfl_frame_reg_get, __libdwfl_frame_reg_set) + (__libdwfl_process_free, __libdwfl_frame_unwind) + (__libdwfl_attach_state_for_pid, __libdwfl_attach_state_for_core) + (__libdwfl_segment_start, __libdwfl_segment_end): New declarations. + (dwfl_attach_state, dwfl_pid, dwfl_thread_dwfl, dwfl_thread_tid) + (dwfl_frame_thread, dwfl_thread_state_registers) + (dwfl_thread_state_register_pc, dwfl_getthreads, dwfl_thread_getframes) + (dwfl_frame_pc): New INTDECL entries. + * linux-proc-maps.c (dwfl_linux_proc_report): Call + __libdwfl_attach_state_for_pid. + * 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. + +2013-11-07 Jan Kratochvil <jan.kratochvil@redhat.com> * core-file.c (dwfl_core_file_report): Remove the use of MAX. diff --git a/libdwfl/Makefile.am b/libdwfl/Makefile.am index 3ef4dd6b..ce590dac 100644 --- a/libdwfl/Makefile.am +++ b/libdwfl/Makefile.am @@ -2,7 +2,7 @@ ## ## Process this file with automake to create Makefile.in ## -## Copyright (C) 2005-2010 Red Hat, Inc. +## Copyright (C) 2005-2010, 2013 Red Hat, Inc. ## This file is part of elfutils. ## ## This file is free software; you can redistribute it and/or modify @@ -68,7 +68,9 @@ 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.c frame_unwind.c dwfl_frame_pc.c \ + linux-pid-attach.c linux-core-attach.c dwfl_frame_regs.c if ZLIB libdwfl_a_SOURCES += gzip.c diff --git a/libdwfl/core-file.c b/libdwfl/core-file.c index 4f669834..f9120ca9 100644 --- a/libdwfl/core-file.c +++ b/libdwfl/core-file.c @@ -1,5 +1,5 @@ /* Core file handling. - Copyright (C) 2008-2010 Red Hat, Inc. + Copyright (C) 2008-2010, 2013 Red Hat, Inc. This file is part of elfutils. This file is free software; you can redistribute it and/or modify @@ -558,6 +558,13 @@ dwfl_core_file_report (Dwfl *dwfl, Elf *elf, const char *executable) clear_r_debug_info (&r_debug_info); + if (listed > 0) + { + /* Possible error is ignored, DWFL still may be useful for non-unwinding + operations. */ + __libdwfl_attach_state_for_core (dwfl, elf); + } + /* We return the number of modules we found if we found any. If we found none, we return -1 instead of 0 if there was an error rather than just nothing found. */ diff --git a/libdwfl/dwfl_end.c b/libdwfl/dwfl_end.c index 94fcfc6e..33cae48c 100644 --- a/libdwfl/dwfl_end.c +++ b/libdwfl/dwfl_end.c @@ -1,5 +1,5 @@ /* Finish a session using libdwfl. - Copyright (C) 2005, 2008, 2012 Red Hat, Inc. + Copyright (C) 2005, 2008, 2012-2013 Red Hat, Inc. This file is part of elfutils. This file is free software; you can redistribute it and/or modify @@ -34,6 +34,9 @@ dwfl_end (Dwfl *dwfl) if (dwfl == NULL) return; + if (dwfl->process) + __libdwfl_process_free (dwfl->process); + free (dwfl->lookup_addr); free (dwfl->lookup_module); free (dwfl->lookup_segndx); diff --git a/libdwfl/dwfl_frame.c b/libdwfl/dwfl_frame.c new file mode 100644 index 00000000..11376c6e --- /dev/null +++ b/libdwfl/dwfl_frame.c @@ -0,0 +1,345 @@ +/* Get Dwarf Frame state for target PID or core file. + 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 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> + +/* Set STATE->pc_set from STATE->regs according to the backend. Return true on + success, false on error. */ +static bool +state_fetch_pc (Dwfl_Frame *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_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) +{ + Dwfl_Thread *thread = state->thread; + assert (thread->unwound == state); + thread->unwound = state->unwound; + free (state); +} + +static void +thread_free_all_states (Dwfl_Thread *thread) +{ + while (thread->unwound) + state_free (thread->unwound); +} + +static Dwfl_Frame * +state_alloc (Dwfl_Thread *thread) +{ + assert (thread->unwound == NULL); + Ebl *ebl = thread->process->ebl; + size_t nregs = ebl_frame_nregs (ebl); + if (nregs == 0) + return NULL; + assert (nregs < sizeof (((Dwfl_Frame *) NULL)->regs_set) * 8); + Dwfl_Frame *state = malloc (sizeof (*state) + sizeof (*state->regs) * nregs); + if (state == NULL) + return NULL; + state->thread = thread; + state->signal_frame = false; + state->initial_frame = true; + 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_process_free (Dwfl_Process *process) +{ + Dwfl *dwfl = process->dwfl; + if (process->callbacks->detach != NULL) + process->callbacks->detach (dwfl, process->callbacks_arg); + assert (dwfl->process == process); + dwfl->process = NULL; + if (process->ebl_close) + ebl_closebackend (process->ebl); + free (process); +} + +/* Allocate new Dwfl_Process for DWFL. */ +static void +process_alloc (Dwfl *dwfl) +{ + Dwfl_Process *process = malloc (sizeof (*process)); + if (process == NULL) + return; + process->dwfl = dwfl; + dwfl->process = process; +} + +bool +dwfl_attach_state (Dwfl *dwfl, int machine, pid_t pid, + const Dwfl_Thread_Callbacks *thread_callbacks, void *arg) +{ + if (thread_callbacks == NULL || thread_callbacks->next_thread == NULL + || thread_callbacks->set_initial_registers == NULL) + { + __libdwfl_seterrno (DWFL_E_INVALID_ARGUMENT); + return false; + } + if (dwfl->process != NULL) + { + __libdwfl_seterrno (DWFL_E_ATTACH_STATE_CONFLICT); + return false; + } + Ebl *ebl; + bool ebl_close; + if (machine != EM_NONE) + { + ebl = ebl_openbackend_machine (machine); + ebl_close = true; + } + else + { + ebl = NULL; + for (Dwfl_Module *mod = dwfl->modulelist; mod != NULL; mod = mod->next) + { + /* Reading of the vDSO module may fail as /proc/PID/mem is unreadable + without PTRACE_ATTACH and we may not be PTRACE_ATTACH-ed now. + MOD would not be re-read later to unwind it when we are already + PTRACE_ATTACH-ed to PID. */ + if (strncmp (mod->name, "[vdso: ", 7) == 0) + continue; + Dwfl_Error error = __libdwfl_module_getebl (mod); + if (error != DWFL_E_NOERROR) + continue; + ebl = mod->ebl; + break; + } + ebl_close = false; + } + if (ebl == NULL) + { + /* Not identified EBL from any of the modules. */ + __libdwfl_seterrno (DWFL_E_PROCESS_NO_ARCH); + return false; + } + process_alloc (dwfl); + Dwfl_Process *process = dwfl->process; + if (process == NULL) + { + if (ebl_close) + ebl_closebackend (ebl); + __libdwfl_seterrno (DWFL_E_NOMEM); + return false; + } + process->ebl = ebl; + process->ebl_close = ebl_close; + process->pid = pid; + process->callbacks = thread_callbacks; + process->callbacks_arg = arg; + return true; +} +INTDEF(dwfl_attach_state) + +pid_t +dwfl_pid (Dwfl *dwfl) +{ + if (dwfl->process == NULL) + { + __libdwfl_seterrno (DWFL_E_NO_ATTACH_STATE); + return -1; + } + return dwfl->process->pid; +} +INTDEF(dwfl_pid) + +Dwfl * +dwfl_thread_dwfl (Dwfl_Thread *thread) +{ + return thread->process->dwfl; +} +INTDEF(dwfl_thread_dwfl) + +pid_t +dwfl_thread_tid (Dwfl_Thread *thread) +{ + return thread->tid; +} +INTDEF(dwfl_thread_tid) + +Dwfl_Thread * +dwfl_frame_thread (Dwfl_Frame *state) +{ + return state->thread; +} +INTDEF(dwfl_frame_thread) + +int +dwfl_getthreads (Dwfl *dwfl, int (*callback) (Dwfl_Thread *thread, void *arg), + void *arg) +{ + Dwfl_Process *process = dwfl->process; + if (process == NULL) + { + __libdwfl_seterrno (DWFL_E_NO_ATTACH_STATE); + return -1; + } + + Dwfl_Thread thread; + thread.process = process; + thread.unwound = NULL; + thread.callbacks_arg = NULL; + for (;;) + { + thread.tid = process->callbacks->next_thread (dwfl, + process->callbacks_arg, + &thread.callbacks_arg); + if (thread.tid < 0) + { + Dwfl_Error saved_errno = dwfl_errno (); + thread_free_all_states (&thread); + __libdwfl_seterrno (saved_errno); + return -1; + } + if (thread.tid == 0) + { + thread_free_all_states (&thread); + __libdwfl_seterrno (DWFL_E_NOERROR); + return 0; + } + int err = callback (&thread, arg); + if (err != DWARF_CB_OK) + { + thread_free_all_states (&thread); + return err; + } + assert (thread.unwound == NULL); + } + /* NOTREACHED */ +} +INTDEF(dwfl_getthreads) + +int +dwfl_thread_getframes (Dwfl_Thread *thread, + int (*callback) (Dwfl_Frame *state, void *arg), + void *arg) +{ + if (thread->unwound != NULL) + { + /* We had to be called from inside CALLBACK. */ + __libdwfl_seterrno (DWFL_E_ATTACH_STATE_CONFLICT); + return -1; + } + Ebl *ebl = thread->process->ebl; + if (ebl_frame_nregs (ebl) == 0) + { + __libdwfl_seterrno (DWFL_E_NO_UNWIND); + return -1; + } + if (state_alloc (thread) == NULL) + { + __libdwfl_seterrno (DWFL_E_NOMEM); + return -1; + } + Dwfl_Process *process = thread->process; + if (! process->callbacks->set_initial_registers (thread, + thread->callbacks_arg)) + { + thread_free_all_states (thread); + return -1; + } + if (! state_fetch_pc (thread->unwound)) + { + if (process->callbacks->thread_detach) + process->callbacks->thread_detach (thread, thread->callbacks_arg); + thread_free_all_states (thread); + return -1; + } + + Dwfl_Frame *state; + do + { + state = thread->unwound; + int err = callback (state, arg); + if (err != DWARF_CB_OK) + { + if (process->callbacks->thread_detach) + process->callbacks->thread_detach (thread, thread->callbacks_arg); + thread_free_all_states (thread); + return err; + } + __libdwfl_frame_unwind (state); + /* The old frame is no longer needed. */ + state_free (thread->unwound); + state = thread->unwound; + } + while (state && state->pc_state == DWFL_FRAME_STATE_PC_SET); + + Dwfl_Error err = dwfl_errno (); + if (process->callbacks->thread_detach) + process->callbacks->thread_detach (thread, thread->callbacks_arg); + if (state == NULL || state->pc_state == DWFL_FRAME_STATE_ERROR) + { + thread_free_all_states (thread); + __libdwfl_seterrno (err); + return -1; + } + assert (state->pc_state == DWFL_FRAME_STATE_PC_UNDEFINED); + thread_free_all_states (thread); + return 0; +} +INTDEF(dwfl_thread_getframes) diff --git a/libdwfl/dwfl_frame_pc.c b/libdwfl/dwfl_frame_pc.c new file mode 100644 index 00000000..5462e4c7 --- /dev/null +++ b/libdwfl/dwfl_frame_pc.c @@ -0,0 +1,63 @@ +/* Get return address register value for frame. + 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 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_pc (Dwfl_Frame *state, Dwarf_Addr *pc, bool *isactivation) +{ + assert (state->pc_state == DWFL_FRAME_STATE_PC_SET); + *pc = state->pc; + if (isactivation) + { + /* Bottom frame? */ + if (state->initial_frame) + *isactivation = true; + /* *ISACTIVATION is logical union of whether current or previous frame + state is SIGNAL_FRAME. */ + else if (state->signal_frame) + *isactivation = true; + else + { + /* If the previous frame has unwound unsuccessfully just silently do + not consider it could be a SIGNAL_FRAME. */ + __libdwfl_frame_unwind (state); + if (state->unwound == NULL + || state->unwound->pc_state != DWFL_FRAME_STATE_PC_SET) + *isactivation = false; + else + *isactivation = state->unwound->signal_frame; + } + } + return true; +} +INTDEF (dwfl_frame_pc) diff --git a/libdwfl/dwfl_frame_regs.c b/libdwfl/dwfl_frame_regs.c new file mode 100644 index 00000000..927bc15b --- /dev/null +++ b/libdwfl/dwfl_frame_regs.c @@ -0,0 +1,57 @@ +/* Get Dwarf Frame state from modules present in DWFL. + 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 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" + +bool +dwfl_thread_state_registers (Dwfl_Thread *thread, const int firstreg, + unsigned nregs, const Dwarf_Word *regs) +{ + Dwfl_Frame *state = thread->unwound; + assert (state && state->unwound == NULL); + assert (state->initial_frame); + for (unsigned regno = firstreg; regno < firstreg + nregs; regno++) + if (! __libdwfl_frame_reg_set (state, regno, regs[regno - firstreg])) + { + __libdwfl_seterrno (DWFL_E_INVALID_REGISTER); + return false; + } + return true; +} +INTDEF(dwfl_thread_state_registers) + +void +dwfl_thread_state_register_pc (Dwfl_Thread *thread, Dwarf_Word pc) +{ + Dwfl_Frame *state = thread->unwound; + assert (state && state->unwound == NULL); + assert (state->initial_frame); + state->pc = pc; + state->pc_state = DWFL_FRAME_STATE_PC_SET; +} +INTDEF(dwfl_thread_state_register_pc) diff --git a/libdwfl/frame_unwind.c b/libdwfl/frame_unwind.c new file mode 100644 index 00000000..1aed8cb5 --- /dev/null +++ b/libdwfl/frame_unwind.c @@ -0,0 +1,617 @@ +/* Get previous frame state for an existing frame state. + 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 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> + +/* Maximum number of DWARF expression stack slots before returning an error. */ +#define DWARF_EXPR_STACK_MAX 0x100 + +/* Maximum number of DWARF expression executed operations before returning an + error. */ +#define DWARF_EXPR_STEPS_MAX 0x1000 + +#ifndef MAX +# define MAX(a, b) ((a) > (b) ? (a) : (b)) +#endif + +bool +internal_function +__libdwfl_frame_reg_get (Dwfl_Frame *state, unsigned regno, Dwarf_Addr *val) +{ + Ebl *ebl = state->thread->process->ebl; + if (regno >= ebl_frame_nregs (ebl)) + 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; +} + +bool +internal_function +__libdwfl_frame_reg_set (Dwfl_Frame *state, unsigned regno, Dwarf_Addr val) +{ + Ebl *ebl = state->thread->process->ebl; + if (regno >= ebl_frame_nregs (ebl)) + return false; + /* For example i386 user_regs_struct has signed fields. */ + if (ebl_get_elfclass (ebl) == 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; +} + +static bool +state_get_reg (Dwfl_Frame *state, unsigned regno, Dwarf_Addr *val) +{ + if (! __libdwfl_frame_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); +} + +/* If FRAME is NULL is are computing CFI frame base. In such case another + DW_OP_call_frame_cfa is no longer permitted. */ + +static bool +expr_eval (Dwfl_Frame *state, Dwarf_Frame *frame, const Dwarf_Op *ops, + size_t nops, Dwarf_Addr *result, Dwarf_Addr bias) +{ + Dwfl_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 >= DWARF_EXPR_STACK_MAX) + { + __libdwfl_seterrno (DWFL_E_INVALID_DWARF); + return false; + } + 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; + size_t steps_count = 0; + for (const Dwarf_Op *op = ops; op < ops + nops; op++) + { + if (++steps_count > DWARF_EXPR_STEPS_MAX) + { + __libdwfl_seterrno (DWFL_E_INVALID_DWARF); + return false; + } + switch (op->atom) + { + /* DW_OP_* order matches libgcc/unwind-dw2.c execute_stack_op: */ + case DW_OP_lit0 ... DW_OP_lit31: + if (! push (op->atom - DW_OP_lit0)) + { + free (stack); + return false; + } + break; + case DW_OP_addr: + if (! push (op->number + bias)) + { + free (stack); + return false; + } + break; + case DW_OP_GNU_encoded_addr: + /* Missing support in the rest of elfutils. */ + __libdwfl_seterrno (DWFL_E_UNSUPPORTED_DWARF); + return false; + 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_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_dup: + if (! pop (&val1) || ! push (val1) || ! push (val1)) + { + free (stack); + return false; + } + break; + case DW_OP_drop: + if (! pop (&val1)) + { + free (stack); + return false; + } + break; + case DW_OP_pick: + if (stack_used <= op->number) + { + free (stack); + __libdwfl_seterrno (DWFL_E_INVALID_DWARF); + return false; + } + if (! push (stack[stack_used - 1 - op->number])) + { + free (stack); + return false; + } + break; + case DW_OP_over: + if (! pop (&val1) || ! pop (&val2) + || ! push (val2) || ! push (val1) || ! push (val2)) + { + free (stack); + return false; + } + break; + case DW_OP_swap: + if (! pop (&val1) || ! pop (&val2) || ! push (val1) || ! push (val2)) + { + free (stack); + return false; + } + break; + case DW_OP_rot: + { + Dwarf_Addr val3; + if (! pop (&val1) || ! pop (&val2) || ! pop (&val3) + || ! push (val1) || ! push (val3) || ! push (val2)) + { + free (stack); + return false; + } + } + break; + case DW_OP_deref: + case DW_OP_deref_size: + if (process->callbacks->memory_read == NULL) + { + free (stack); + __libdwfl_seterrno (DWFL_E_INVALID_ARGUMENT); + return false; + } + if (! pop (&val1) + || ! process->callbacks->memory_read (process->dwfl, val1, &val1, + process->callbacks_arg)) + { + free (stack); + return false; + } + if (op->atom == DW_OP_deref_size) + { + const int elfclass = frame->cache->e_ident[EI_CLASS]; + const unsigned addr_bytes = elfclass == ELFCLASS32 ? 4 : 8; + if (op->number > addr_bytes) + { + free (stack); + __libdwfl_seterrno (DWFL_E_INVALID_DWARF); + return false; + } +#if BYTE_ORDER == BIG_ENDIAN + if (op->number == 0) + val1 = 0; + else + val1 >>= (addr_bytes - op->number) * 8; +#else + if (op->number < 8) + val1 &= (1 << (op->number * 8)) - 1; +#endif + } + if (! push (val1)) + { + free (stack); + return false; + } + break; +#define UNOP(atom, expr) \ + case atom: \ + if (! pop (&val1) || ! push (expr)) \ + { \ + free (stack); \ + return false; \ + } \ + break; + UNOP (DW_OP_abs, abs ((int64_t) val1)) + UNOP (DW_OP_neg, -(int64_t) val1) + UNOP (DW_OP_not, ~val1) +#undef UNOP + case DW_OP_plus_uconst: + if (! pop (&val1) || ! push (val1 + op->number)) + { + 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; +#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 (DW_OP_and, &) + case DW_OP_div: + if (! pop (&val2) || ! pop (&val1)) + { + free (stack); + return false; + } + if (val2 == 0) + { + free (stack); + __libdwfl_seterrno (DWFL_E_INVALID_DWARF); + return false; + } + if (! push ((int64_t) val1 / (int64_t) val2)) + { + free (stack); + return false; + } + break; + BINOP (DW_OP_minus, -) + case DW_OP_mod: + if (! pop (&val2) || ! pop (&val1)) + { + free (stack); + return false; + } + if (val2 == 0) + { + free (stack); + __libdwfl_seterrno (DWFL_E_INVALID_DWARF); + return false; + } + if (! push (val1 % val2)) + { + free (stack); + return false; + } + break; + BINOP (DW_OP_mul, *) + BINOP (DW_OP_or, |) + BINOP (DW_OP_plus, +) + BINOP (DW_OP_shl, <<) + BINOP (DW_OP_shr, >>) + BINOP_SIGNED (DW_OP_shra, >>) + BINOP (DW_OP_xor, ^) + 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 +#undef BINOP_SIGNED + 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_nop: + break; + /* DW_OP_* not listed in libgcc/unwind-dw2.c execute_stack_op: */ + case DW_OP_call_frame_cfa:; + // Not used by CFI itself but it is synthetized by elfutils internation. + Dwarf_Op *cfa_ops; + size_t cfa_nops; + Dwarf_Addr cfa; + if (frame == NULL + || dwarf_frame_cfa (frame, &cfa_ops, &cfa_nops) != 0 + || ! expr_eval (state, NULL, cfa_ops, cfa_nops, &cfa, bias) + || ! push (cfa)) + { + __libdwfl_seterrno (DWFL_E_LIBDW); + free (stack); + return false; + } + is_location = true; + break; + case DW_OP_stack_value: + // Not used by CFI itself but it is synthetized by elfutils internation. + is_location = false; + break; + default: + __libdwfl_seterrno (DWFL_E_INVALID_DWARF); + return false; + } + } + if (! pop (result)) + { + free (stack); + return false; + } + free (stack); + if (is_location) + { + if (process->callbacks->memory_read == NULL) + { + __libdwfl_seterrno (DWFL_E_INVALID_ARGUMENT); + return false; + } + if (! process->callbacks->memory_read (process->dwfl, *result, result, + process->callbacks_arg)) + 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. */ + +static void +handle_cfi (Dwfl_Frame *state, Dwarf_Addr pc, Dwarf_CFI *cfi, Dwarf_Addr bias) +{ + Dwarf_Frame *frame; + if (INTUSE(dwarf_cfi_addrframe) (cfi, pc, &frame) != 0) + { + __libdwfl_seterrno (DWFL_E_LIBDW); + return; + } + Dwfl_Thread *thread = state->thread; + Dwfl_Process *process = thread->process; + Ebl *ebl = process->ebl; + size_t nregs = ebl_frame_nregs (ebl); + assert (nregs > 0); + Dwfl_Frame *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->initial_frame = false; + 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, bias)) + { + /* 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 (! __libdwfl_frame_reg_set (unwound, regno, regval)) + { + __libdwfl_seterrno (DWFL_E_INVALID_REGISTER); + continue; + } + } + if (unwound->pc_state == DWFL_FRAME_STATE_ERROR + && __libdwfl_frame_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; + } +} + +void +internal_function +__libdwfl_frame_unwind (Dwfl_Frame *state) +{ + if (state->unwound) + return; + /* Do not ask dwfl_frame_pc for ISACTIVATION, it would try to unwind STATE + which would deadlock us. */ + Dwarf_Addr pc; + bool ok = INTUSE(dwfl_frame_pc) (state, &pc, NULL); + assert (ok); + /* Check whether this is the initial frame or a signal frame. + Then we need to unwind from the original, unadjusted PC. */ + if (! state->initial_frame && ! state->signal_frame) + pc--; + Dwfl_Module *mod = INTUSE(dwfl_addrmodule) (state->thread->process->dwfl, pc); + if (mod == NULL) + __libdwfl_seterrno (DWFL_E_NO_DWARF); + else + { + Dwarf_Addr bias; + Dwarf_CFI *cfi_eh = INTUSE(dwfl_module_eh_cfi) (mod, &bias); + if (cfi_eh) + { + handle_cfi (state, pc - bias, cfi_eh, bias); + if (state->unwound) + return; + } + Dwarf_CFI *cfi_dwarf = INTUSE(dwfl_module_dwarf_cfi) (mod, &bias); + if (cfi_dwarf) + { + handle_cfi (state, pc - bias, cfi_dwarf, bias); + if (state->unwound) + return; + } + } +} diff --git a/libdwfl/libdwfl.h b/libdwfl/libdwfl.h index 2ba82343..bbabb708 100644 --- a/libdwfl/libdwfl.h +++ b/libdwfl/libdwfl.h @@ -1,5 +1,5 @@ /* Interfaces for libdwfl. - Copyright (C) 2005-2010 Red Hat, Inc. + Copyright (C) 2005-2010, 2013 Red Hat, Inc. This file is part of elfutils. This file is free software; you can redistribute it and/or modify @@ -41,6 +41,14 @@ typedef struct Dwfl_Module Dwfl_Module; /* Handle describing a line record. */ typedef struct Dwfl_Line Dwfl_Line; +/* This holds information common for all the frames of one backtrace for + a partical thread/task/TID. Several threads belong to one Dwfl. */ +typedef struct Dwfl_Thread Dwfl_Thread; + +/* This holds everything we know about the state of the frame at a particular + PC location described by an FDE belonging to Dwfl_Thread. */ +typedef struct Dwfl_Frame Dwfl_Frame; + /* Callbacks. */ typedef struct { @@ -353,11 +361,14 @@ extern int dwfl_linux_kernel_report_offline (Dwfl *dwfl, const char *release, supply non-NULL EXECUTABLE, otherwise dynamic libraries will not be loaded into the DWFL map. This might call dwfl_report_elf on file names found in the dump if reading some link_map files is the only way to ascertain those - modules' addresses. + modules' addresses. dwfl_attach_state is also called for DWFL, + dwfl_core_file_report does not fail if the dwfl_attach_state call has failed. Returns the number of modules reported, or -1 for errors. */ extern int dwfl_core_file_report (Dwfl *dwfl, Elf *elf, const char *executable); /* Call dwfl_report_module for each file mapped into the address space of PID. + dwfl_attach_state is also called for DWFL, dwfl_linux_proc_report does + not fail if the dwfl_attach_state call has failed. Returns zero on success, -1 if dwfl_report_module failed, or an errno code if opening the kernel binary failed. */ extern int dwfl_linux_proc_report (Dwfl *dwfl, pid_t pid); @@ -567,6 +578,128 @@ 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); +typedef struct +{ + /* Called to iterate through threads. Returns next TID (thread ID) on + success, a negative number on failure and zero if there are no more + threads. dwfl_errno () should be set if negative number has been + returned. *THREAD_ARGP is NULL on first call, and may be optionally + set by the implementation. The value set by the implementation will + be passed in on the next call to NEXT_THREAD. THREAD_ARGP is never + NULL. *THREAD_ARGP will be passed to set_initial_registers or + thread_detach callbacks together with Dwfl_Thread *thread. This + method must not be NULL. */ + pid_t (*next_thread) (Dwfl *dwfl, void *dwfl_arg, void **thread_argp) + __nonnull_attribute__ (1); + + /* Called during unwinding to access memory (stack) state. Returns true for + successfully read *RESULT or false and sets dwfl_errno () 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 *dwfl_arg) + __nonnull_attribute__ (1, 3); + + /* 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_errno () + on failure. In the case of a failure thread_detach will not be called. + This method must not be NULL. */ + bool (*set_initial_registers) (Dwfl_Thread *thread, void *thread_arg) + __nonnull_attribute__ (1); + + /* Called by dwfl_end. All thread_detach method calls have been already + done. This method may be NULL. */ + void (*detach) (Dwfl *dwfl, void *dwfl_arg) + __nonnull_attribute__ (1); + + /* 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 and it returned success thread_detach will be called before the + detach method above. This method may be NULL. */ + void (*thread_detach) (Dwfl_Thread *thread, void *thread_arg) + __nonnull_attribute__ (1); +} Dwfl_Thread_Callbacks; + +/* PID is the process id associated with the DWFL state. Architecture of DWFL + modules is specified by MACHINE. Use EM_NONE to detect architecture from + DWFL. If EBL is NULL the function will detect it from arbitrary Dwfl_Module + of DWFL. DWFL_ARG is the callback backend state. DWFL_ARG will be provided + to the callbacks. *THREAD_CALLBACKS function pointers must remain valid + during lifetime of DWFL. Function returns true on success, + false otherwise. */ +bool dwfl_attach_state (Dwfl *dwfl, int machine, pid_t pid, + const Dwfl_Thread_Callbacks *thread_callbacks, + void *dwfl_arg) + __nonnull_attribute__ (1, 4); + +/* 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) + __nonnull_attribute__ (1); + +/* Return DWFL from which THREAD was created using dwfl_getthreads. */ +Dwfl *dwfl_thread_dwfl (Dwfl_Thread *thread) + __nonnull_attribute__ (1); + +/* Return positive TID (thread ID) for THREAD. This function never fails. */ +pid_t dwfl_thread_tid (Dwfl_Thread *thread) + __nonnull_attribute__ (1); + +/* Return thread for frame STATE. This function never fails. */ +Dwfl_Thread *dwfl_frame_thread (Dwfl_Frame *state) + __nonnull_attribute__ (1); + +/* 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). + Function returns false if any of the registers has invalid number. */ +bool dwfl_thread_state_registers (Dwfl_Thread *thread, const int firstreg, + unsigned nregs, const Dwarf_Word *regs) + __nonnull_attribute__ (1, 4); + +/* 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) + __nonnull_attribute__ (1); + +/* Iterate through the threads for a process. Returns zero if all threads have + been processed by the callback, returns -1 on error, or the value of the + callback when not DWARF_CB_OK. -1 returned on error will set dwfl_errno (). + Keeps calling the callback with the next thread while the callback returns + DWARF_CB_OK, till there are no more threads. */ +int dwfl_getthreads (Dwfl *dwfl, + int (*callback) (Dwfl_Thread *thread, void *arg), + void *arg) + __nonnull_attribute__ (1, 2); + +/* 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. -1 returned on error will + set dwfl_errno (). Some systems return error instead of zero on end of the + backtrace, for cross-platform compatibility callers should consider error as + a zero. 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) + __nonnull_attribute__ (1, 2); + +/* 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) + __nonnull_attribute__ (1, 2); + #ifdef __cplusplus } #endif diff --git a/libdwfl/libdwflP.h b/libdwfl/libdwflP.h index 7e46478d..ff488132 100644 --- a/libdwfl/libdwflP.h +++ b/libdwfl/libdwflP.h @@ -1,5 +1,5 @@ /* Internal definitions for libdwfl. - Copyright (C) 2005-2012 Red Hat, Inc. + Copyright (C) 2005-2013 Red Hat, Inc. This file is part of elfutils. This file is free software; you can redistribute it and/or modify @@ -42,6 +42,8 @@ #include "../libdw/libdwP.h" /* We need its INTDECLs. */ +typedef struct Dwfl_Process Dwfl_Process; + /* gettext helper macros. */ #define _(Str) dgettext ("elfutils", Str) @@ -74,7 +76,20 @@ 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_("Couldn't find architecture of any ELF")) \ + DWFL_ERROR (PARSE_PROC, N_("Error parsing /proc filesystem")) \ + DWFL_ERROR (INVALID_DWARF, N_("Invalid DWARF")) \ + DWFL_ERROR (UNSUPPORTED_DWARF, N_("Unsupported DWARF")) \ + DWFL_ERROR (NEXT_THREAD_FAIL, N_("Unable to find more threads")) \ + DWFL_ERROR (ATTACH_STATE_CONFLICT, N_("Dwfl already has attached state")) \ + DWFL_ERROR (NO_ATTACH_STATE, N_("Dwfl has no attached state")) \ + DWFL_ERROR (NO_UNWIND, N_("Unwinding not supported for this architecture")) \ + DWFL_ERROR (INVALID_ARGUMENT, N_("Invalid argument")) #define DWFL_ERROR(name, text) DWFL_E_##name, typedef enum { DWFL_ERRORS DWFL_E_NUM } Dwfl_Error; @@ -92,6 +107,8 @@ struct Dwfl Dwfl_Module *modulelist; /* List in order used by full traversals. */ + Dwfl_Process *process; + GElf_Addr offline_next_address; GElf_Addr segment_align; /* Smallest granularity of segments. */ @@ -189,7 +206,72 @@ 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_Process +{ + struct Dwfl *dwfl; + pid_t pid; + const Dwfl_Thread_Callbacks *callbacks; + void *callbacks_arg; + struct ebl *ebl; + bool ebl_close:1; +}; + +/* See its typedef in libdwfl.h. */ + +struct Dwfl_Thread +{ + Dwfl_Process *process; + pid_t tid; + /* The current frame being unwound. Initially it is the bottom frame. + Later the processed frames get freed and this pointer is updated. */ + Dwfl_Frame *unwound; + void *callbacks_arg; +}; + +/* See its typedef in libdwfl.h. */ + +struct Dwfl_Frame +{ + Dwfl_Thread *thread; + /* Previous (outer) frame. */ + Dwfl_Frame *unwound; + bool signal_frame : 1; + bool initial_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_nregs. */ + uint64_t regs_set[3]; + /* REGS array size is ebl_frame_nregs. + REGS_SET tells which of the REGS are valid. */ + Dwarf_Addr regs[]; +}; + +/* Fetch value from Dwfl_Frame->regs indexed by DWARF REGNO. + No error code is set if the function returns FALSE. */ +bool __libdwfl_frame_reg_get (Dwfl_Frame *state, unsigned regno, + Dwarf_Addr *val) + internal_function; +/* Store value to Dwfl_Frame->regs indexed by DWARF REGNO. + No error code is set if the function returns FALSE. */ +bool __libdwfl_frame_reg_set (Dwfl_Frame *state, unsigned regno, + Dwarf_Addr val) + internal_function; /* Information cached about each CU in Dwfl_Module.dw. */ struct dwfl_cu @@ -415,6 +497,33 @@ extern Dwfl_Module *__libdwfl_report_offline (Dwfl *dwfl, const char *name, const char *)) internal_function; +/* Free PROCESS. Unlink and free also any structures it references. */ +extern void __libdwfl_process_free (Dwfl_Process *process) + internal_function; + +/* Update STATE->unwound for the unwound frame. + On error STATE->unwound == NULL + or STATE->unwound->pc_state == DWFL_FRAME_STATE_ERROR; + in such case dwfl_errno () is set. + If STATE->unwound->pc_state == DWFL_FRAME_STATE_PC_UNDEFINED + then STATE was the last valid frame. */ +extern void __libdwfl_frame_unwind (Dwfl_Frame *state) + internal_function; + +/* Call dwfl_attach_state for PID, return true if successful. */ +extern bool __libdwfl_attach_state_for_pid (Dwfl *dwfl, pid_t pid) + internal_function; + +/* Call dwfl_attach_state for CORE, return true if successful. */ +extern bool __libdwfl_attach_state_for_core (Dwfl *dwfl, Elf *core) + 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, @@ -557,6 +666,16 @@ INTDECL (dwfl_offline_section_address) INTDECL (dwfl_module_relocate_address) INTDECL (dwfl_module_dwarf_cfi) INTDECL (dwfl_module_eh_cfi) +INTDECL (dwfl_attach_state) +INTDECL (dwfl_pid) +INTDECL (dwfl_thread_dwfl) +INTDECL (dwfl_thread_tid) +INTDECL (dwfl_frame_thread) +INTDECL (dwfl_thread_state_registers) +INTDECL (dwfl_thread_state_register_pc) +INTDECL (dwfl_getthreads) +INTDECL (dwfl_thread_getframes) +INTDECL (dwfl_frame_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/linux-core-attach.c b/libdwfl/linux-core-attach.c new file mode 100644 index 00000000..106d7649 --- /dev/null +++ b/libdwfl/linux-core-attach.c @@ -0,0 +1,380 @@ +/* Get Dwarf Frame state for target core file. + 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 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 + +struct core_arg +{ + Elf *core; + Elf_Data *note_data; + size_t thread_note_offset; + Ebl *ebl; +}; + +struct thread_arg +{ + struct core_arg *core_arg; + size_t note_offset; +}; + +static bool +core_memory_read (Dwfl *dwfl, Dwarf_Addr addr, Dwarf_Word *result, + void *dwfl_arg) +{ + Dwfl_Process *process = dwfl->process; + struct core_arg *core_arg = dwfl_arg; + Elf *core = core_arg->core; + assert (core != NULL); + 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 = ebl_get_elfclass (process->ebl) == 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_ADDR_OUTOFRANGE); + return false; +} + +static pid_t +core_next_thread (Dwfl *dwfl __attribute__ ((unused)), void *dwfl_arg, + void **thread_argp) +{ + struct core_arg *core_arg = dwfl_arg; + Elf *core = core_arg->core; + GElf_Nhdr nhdr; + size_t name_offset; + size_t desc_offset; + Elf_Data *note_data = core_arg->note_data; + size_t offset; + while (offset = core_arg->thread_note_offset, offset < note_data->d_size + && (core_arg->thread_note_offset = gelf_getnote (note_data, offset, + &nhdr, &name_offset, + &desc_offset)) > 0) + { + /* Do not check NAME for now, help broken Linux kernels. */ + const char *name = note_data->d_buf + name_offset; + const char *desc = note_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 (core_arg->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) + continue; + const Ebl_Core_Item *item; + for (item = items; item < items + nitems; item++) + if (strcmp (item->name, "pid") == 0) + break; + if (item == items + nitems) + continue; + 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); + struct thread_arg *thread_arg = malloc (sizeof (*thread_arg)); + if (thread_arg == NULL) + { + __libdwfl_seterrno (DWFL_E_NOMEM); + return -1; + } + thread_arg->core_arg = core_arg; + thread_arg->note_offset = offset; + *thread_argp = thread_arg; + return tid; + } + return 0; +} + +static bool +core_set_initial_registers (Dwfl_Thread *thread, void *thread_arg_voidp) +{ + struct thread_arg *thread_arg = thread_arg_voidp; + struct core_arg *core_arg = thread_arg->core_arg; + Elf *core = core_arg->core; + size_t offset = thread_arg->note_offset; + GElf_Nhdr nhdr; + size_t name_offset; + size_t desc_offset; + Elf_Data *note_data = core_arg->note_data; + size_t nregs = ebl_frame_nregs (core_arg->ebl); + assert (nregs > 0); + assert (offset < note_data->d_size); + size_t getnote_err = gelf_getnote (note_data, offset, &nhdr, &name_offset, + &desc_offset); + /* __libdwfl_attach_state_for_core already verified the note is there. */ + assert (getnote_err != 0); + /* Do not check NAME for now, help broken Linux kernels. */ + const char *name = note_data->d_buf + name_offset; + const char *desc = note_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; + int core_note_err = ebl_core_note (core_arg->ebl, &nhdr, name, ®s_offset, + &nregloc, ®locs, &nitems, &items); + /* __libdwfl_attach_state_for_core already verified the note is there. */ + assert (core_note_err != 0); + assert (nhdr.n_type == NT_PRSTATUS); + const Ebl_Core_Item *item; + for (item = items; item < items + nitems; item++) + if (strcmp (item->name, "pid") == 0) + break; + assert (item < items + nitems); + pid_t tid; + { + uint32_t val32 = *(const uint32_t *) (desc + item->offset); + val32 = (elf_getident (core, NULL)[EI_DATA] == ELFDATA2MSB + ? be32toh (val32) : le32toh (val32)); + tid = (int32_t) val32; + eu_static_assert (sizeof val32 <= sizeof tid); + } + /* core_next_thread already found this TID there. */ + assert (tid == INTUSE(dwfl_thread_tid) (thread)); + 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. + FIXME: It uses private function. */ + if (__libdwfl_frame_reg_get (thread->unwound, regno, NULL)) + continue; + Dwarf_Word 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 (*thread->unwound->regs) == sizeof val64); + val = val64; + break; + default: + abort (); + } + /* Registers not valid for CFI are just ignored. */ + INTUSE(dwfl_thread_state_registers) (thread, regno, 1, &val); + reg_desc += regloc->pad; + } + } + return true; +} + +static void +core_detach (Dwfl *dwfl __attribute__ ((unused)), void *dwfl_arg) +{ + struct core_arg *core_arg = dwfl_arg; + ebl_closebackend (core_arg->ebl); + free (core_arg); +} + +static const Dwfl_Thread_Callbacks core_thread_callbacks = +{ + core_next_thread, + core_memory_read, + core_set_initial_registers, + core_detach, + NULL, /* core_thread_detach */ +}; + +bool +internal_function +__libdwfl_attach_state_for_core (Dwfl *dwfl, Elf *core) +{ + Ebl *ebl = ebl_openbackend (core); + if (ebl == NULL) + { + __libdwfl_seterrno (DWFL_E_LIBEBL); + return false; + } + size_t nregs = ebl_frame_nregs (ebl); + if (nregs == 0) + { + ebl_closebackend (ebl); + __libdwfl_seterrno (DWFL_E_LIBEBL); + return false; + } + GElf_Ehdr ehdr_mem, *ehdr = gelf_getehdr (core, &ehdr_mem); + if (ehdr == NULL) + { + ebl_closebackend (ebl); + __libdwfl_seterrno (DWFL_E_LIBELF); + return false; + } + assert (ehdr->e_type == ET_CORE); + size_t phnum; + if (elf_getphdrnum (core, &phnum) < 0) + { + ebl_closebackend (ebl); + __libdwfl_seterrno (DWFL_E_LIBELF); + return false; + } + pid_t pid = -1; + Elf_Data *note_data = 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) + { + note_data = elf_getdata_rawchunk (core, phdr->p_offset, + phdr->p_filesz, ELF_T_NHDR); + break; + } + } + if (note_data == NULL) + { + ebl_closebackend (ebl); + __libdwfl_seterrno (DWFL_E_LIBELF); + return NULL; + } + size_t offset = 0; + GElf_Nhdr nhdr; + size_t name_offset; + size_t desc_offset; + while (offset < note_data->d_size + && (offset = gelf_getnote (note_data, offset, + &nhdr, &name_offset, &desc_offset)) > 0) + { + /* Do not check NAME for now, help broken Linux kernels. */ + const char *name = note_data->d_buf + name_offset; + const char *desc = note_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_PRPSINFO) + continue; + const Ebl_Core_Item *item; + for (item = items; item < items + nitems; item++) + if (strcmp (item->name, "pid") == 0) + break; + if (item == items + nitems) + continue; + uint32_t val32 = *(const uint32_t *) (desc + item->offset); + val32 = (elf_getident (core, NULL)[EI_DATA] == ELFDATA2MSB + ? be32toh (val32) : le32toh (val32)); + pid = (int32_t) val32; + eu_static_assert (sizeof val32 <= sizeof pid); + break; + } + if (pid == -1) + { + /* No valid NT_PRPSINFO recognized in this CORE. */ + ebl_closebackend (ebl); + __libdwfl_seterrno (DWFL_E_BADELF); + return false; + } + struct core_arg *core_arg = malloc (sizeof *core_arg); + if (core_arg == NULL) + { + ebl_closebackend (ebl); + __libdwfl_seterrno (DWFL_E_NOMEM); + return false; + } + core_arg->core = core; + core_arg->note_data = note_data; + core_arg->thread_note_offset = 0; + core_arg->ebl = ebl; + if (! INTUSE(dwfl_attach_state) (dwfl, ebl_get_elfmachine (ebl), pid, + &core_thread_callbacks, core_arg)) + { + free (core_arg); + ebl_closebackend (ebl); + return false; + } + return true; +} diff --git a/libdwfl/linux-pid-attach.c b/libdwfl/linux-pid-attach.c new file mode 100644 index 00000000..5ad58f6b --- /dev/null +++ b/libdwfl/linux-pid-attach.c @@ -0,0 +1,282 @@ +/* Get Dwarf Frame state for target live PID process. + 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 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> +#include <sys/syscall.h> +#include <unistd.h> + +#ifndef MAX +# define MAX(a, b) ((a) > (b) ? (a) : (b)) +#endif + +struct pid_arg +{ + DIR *dir; + /* It is 0 if not used. */ + pid_t tid_attached; +}; + +static bool +linux_proc_pid_is_stopped (pid_t pid) +{ + char buffer[64]; + FILE *procfile; + bool retval, have_state; + + snprintf (buffer, sizeof (buffer), "/proc/%ld/status", (long) pid); + procfile = fopen (buffer, "r"); + if (procfile == NULL) + return false; + + have_state = false; + while (fgets (buffer, sizeof (buffer), procfile) != NULL) + if (strncmp (buffer, "State:", 6) == 0) + { + have_state = true; + break; + } + retval = (have_state && strstr (buffer, "T (stopped)") != NULL); + fclose (procfile); + return retval; +} + +static bool +ptrace_attach (pid_t tid) +{ + if (ptrace (PTRACE_ATTACH, tid, NULL, NULL) != 0) + { + __libdwfl_seterrno (DWFL_E_ERRNO); + return false; + } + if (linux_proc_pid_is_stopped (tid)) + { + /* Make sure there is a SIGSTOP signal pending even when the process is + already State: T (stopped). Older kernels might fail to generate + a SIGSTOP notification in that case in response to our PTRACE_ATTACH + above. Which would make the waitpid below wait forever. So emulate + it. Since there can only be one SIGSTOP notification pending this is + safe. See also gdb/linux-nat.c linux_nat_post_attach_wait. */ + syscall (__NR_tkill, tid, SIGSTOP); + ptrace (PTRACE_CONT, tid, NULL, NULL); + } + for (;;) + { + int status; + if (waitpid (tid, &status, __WALL) != tid || !WIFSTOPPED (status)) + { + int saved_errno = errno; + ptrace (PTRACE_DETACH, tid, NULL, NULL); + errno = saved_errno; + __libdwfl_seterrno (DWFL_E_ERRNO); + return false; + } + if (WSTOPSIG (status) == SIGSTOP) + break; + if (ptrace (PTRACE_CONT, tid, NULL, + (void *) (uintptr_t) WSTOPSIG (status)) != 0) + { + int saved_errno = errno; + ptrace (PTRACE_DETACH, tid, NULL, NULL); + errno = saved_errno; + __libdwfl_seterrno (DWFL_E_ERRNO); + return false; + } + } + return true; +} + +static bool +pid_memory_read (Dwfl *dwfl, Dwarf_Addr addr, Dwarf_Word *result, void *arg) +{ + struct pid_arg *pid_arg = arg; + pid_t tid = pid_arg->tid_attached; + assert (tid > 0); + Dwfl_Process *process = dwfl->process; + if (ebl_get_elfclass (process->ebl) == ELFCLASS64) + { +#if SIZEOF_LONG == 8 + errno = 0; + *result = ptrace (PTRACE_PEEKDATA, tid, (void *) (uintptr_t) addr, NULL); + return errno == 0; +#else /* SIZEOF_LONG != 8 */ + /* This should not happen. */ + return false; +#endif /* SIZEOF_LONG != 8 */ + } +#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, tid, (void *) (uintptr_t) addr, NULL); + if (errno != 0) + 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; +} + +static pid_t +pid_next_thread (Dwfl *dwfl __attribute__ ((unused)), void *dwfl_arg, + void **thread_argp) +{ + struct pid_arg *pid_arg = dwfl_arg; + struct dirent *dirent; + do + { + errno = 0; + dirent = readdir (pid_arg->dir); + if (dirent == NULL) + { + if (errno != 0) + { + __libdwfl_seterrno (DWFL_E_ERRNO); + return -1; + } + return 0; + } + } + while (strcmp (dirent->d_name, ".") == 0 + || strcmp (dirent->d_name, "..") == 0); + char *end; + errno = 0; + long tidl = strtol (dirent->d_name, &end, 10); + if (errno != 0) + { + __libdwfl_seterrno (DWFL_E_ERRNO); + return -1; + } + pid_t tid = tidl; + if (tidl <= 0 || (end && *end) || tid != tidl) + { + __libdwfl_seterrno (DWFL_E_PARSE_PROC); + return -1; + } + *thread_argp = dwfl_arg; + return tid; +} + +/* Implement the ebl_set_initial_registers_tid setfunc callback. */ + +static bool +pid_thread_state_registers_cb (const int firstreg, + unsigned nregs, + const Dwarf_Word *regs, + void *arg) +{ + Dwfl_Thread *thread = (Dwfl_Thread *) arg; + return INTUSE(dwfl_thread_state_registers) (thread, firstreg, nregs, regs); +} + +static bool +pid_set_initial_registers (Dwfl_Thread *thread, void *thread_arg) +{ + struct pid_arg *pid_arg = thread_arg; + assert (pid_arg->tid_attached == 0); + pid_t tid = INTUSE(dwfl_thread_tid) (thread); + if (! ptrace_attach (tid)) + return false; + pid_arg->tid_attached = tid; + Dwfl_Process *process = thread->process; + Ebl *ebl = process->ebl; + return ebl_set_initial_registers_tid (ebl, tid, + pid_thread_state_registers_cb, thread); +} + +static void +pid_detach (Dwfl *dwfl __attribute__ ((unused)), void *dwfl_arg) +{ + struct pid_arg *pid_arg = dwfl_arg; + closedir (pid_arg->dir); + free (pid_arg); +} + +static void +pid_thread_detach (Dwfl_Thread *thread, void *thread_arg) +{ + struct pid_arg *pid_arg = thread_arg; + pid_t tid = INTUSE(dwfl_thread_tid) (thread); + assert (pid_arg->tid_attached == tid); + pid_arg->tid_attached = 0; + ptrace (PTRACE_DETACH, tid, NULL, NULL); +} + +static const Dwfl_Thread_Callbacks pid_thread_callbacks = +{ + pid_next_thread, + pid_memory_read, + pid_set_initial_registers, + pid_detach, + pid_thread_detach, +}; + +bool +internal_function +__libdwfl_attach_state_for_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); + DIR *dir = opendir (dirname); + if (dir == NULL) + { + __libdwfl_seterrno (DWFL_E_ERRNO); + return false; + } + struct pid_arg *pid_arg = malloc (sizeof *pid_arg); + if (pid_arg == NULL) + { + closedir (dir); + __libdwfl_seterrno (DWFL_E_NOMEM); + return false; + } + pid_arg->dir = dir; + pid_arg->tid_attached = 0; + if (! INTUSE(dwfl_attach_state) (dwfl, EM_NONE, pid, &pid_thread_callbacks, + pid_arg)) + { + closedir (dir); + free (pid_arg); + return false; + } + return true; +} diff --git a/libdwfl/linux-proc-maps.c b/libdwfl/linux-proc-maps.c index 4eaccdba..8863cc88 100644 --- a/libdwfl/linux-proc-maps.c +++ b/libdwfl/linux-proc-maps.c @@ -1,5 +1,5 @@ /* Standard libdwfl callbacks for debugging a live Linux process. - Copyright (C) 2005-2010 Red Hat, Inc. + Copyright (C) 2005-2010, 2013 Red Hat, Inc. This file is part of elfutils. This file is free software; you can redistribute it and/or modify @@ -300,6 +300,13 @@ dwfl_linux_proc_report (Dwfl *dwfl, pid_t pid) fclose (f); + if (result == 0) + { + /* Possible error is ignored, DWFL still may be useful for non-unwinding + operations. */ + __libdwfl_attach_state_for_pid (dwfl, pid); + } + return result; } INTDEF (dwfl_linux_proc_report) diff --git a/libdwfl/segment.c b/libdwfl/segment.c index 496b4fdc..92769174 100644 --- a/libdwfl/segment.c +++ b/libdwfl/segment.c @@ -1,5 +1,5 @@ /* Manage address space lookup table for libdwfl. - Copyright (C) 2008, 2009, 2010 Red Hat, Inc. + Copyright (C) 2008, 2009, 2010, 2013 Red Hat, Inc. This file is part of elfutils. This file is free software; you can redistribute it and/or modify @@ -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/ChangeLog b/libebl/ChangeLog index 1bc08689..57f9f46d 100644 --- a/libebl/ChangeLog +++ b/libebl/ChangeLog @@ -1,3 +1,13 @@ +2013-11-07 Jan Kratochvil <jan.kratochvil@redhat.com> + Mark Wielaard <mjw@redhat.com> + + * Makefile.am (gen_SOURCES): Add eblinitreg.c. + * ebl-hooks.h (set_initial_registers_tid): New entry. + * eblinitreg.c: New file. + * libebl.h (ebl_tid_registers_t): New definition. + (ebl_set_initial_registers_tid, ebl_frame_nregs): New declarations. + * libeblP.h (struct ebl): New entry frame_nregs. + 2013-10-06 Mark Wielaard <mjw@redhat.com> * libebl.h (ebl_abi_cfi): Document restrictions using register diff --git a/libebl/Makefile.am b/libebl/Makefile.am index 4d62fad1..4487c5f9 100644 --- a/libebl/Makefile.am +++ b/libebl/Makefile.am @@ -1,6 +1,6 @@ ## Process this file with automake to create Makefile.in ## -## Copyright (C) 2000-2010 Red Hat, Inc. +## Copyright (C) 2000-2010, 2013 Red Hat, Inc. ## This file is part of elfutils. ## ## This file is free software; you can redistribute it and/or modify @@ -54,7 +54,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 eblinitreg.c libebl_a_SOURCES = $(gen_SOURCES) diff --git a/libebl/ebl-hooks.h b/libebl/ebl-hooks.h index d3cf3e62..cb52fee4 100644 --- a/libebl/ebl-hooks.h +++ b/libebl/ebl-hooks.h @@ -1,5 +1,5 @@ /* Backend hook signatures internal interface for libebl. - Copyright (C) 2000-2011 Red Hat, Inc. + Copyright (C) 2000-2011, 2013 Red Hat, Inc. This file is part of elfutils. This file is free software; you can redistribute it and/or modify @@ -155,5 +155,12 @@ 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); +/* Fetch process data from live TID and call SETFUNC one or more times. + Method should be present only when EBL_FRAME_NREGS > 0, otherwise the + backend doesn't support unwinding. */ +bool EBLHOOK(set_initial_registers_tid) (pid_t tid, + ebl_tid_registers_t *setfunc, + void *arg); + /* Destructor for ELF backend handle. */ void EBLHOOK(destr) (struct ebl *); diff --git a/libebl/eblinitreg.c b/libebl/eblinitreg.c new file mode 100644 index 00000000..8909c500 --- /dev/null +++ b/libebl/eblinitreg.c @@ -0,0 +1,51 @@ +/* Fetch live process Dwfl_Frame from PID. + 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 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> + +bool +ebl_set_initial_registers_tid (Ebl *ebl, pid_t tid, + ebl_tid_registers_t *setfunc, + void *arg) +{ + /* Otherwise caller could not allocate THREAD frame of proper size. + If set_initial_registers_tid is unsupported then FRAME_NREGS is zero. */ + assert (ebl->set_initial_registers_tid != NULL); + return ebl->set_initial_registers_tid (tid, setfunc, arg); +} + +size_t +ebl_frame_nregs (Ebl *ebl) +{ + return ebl == NULL ? 0 : ebl->frame_nregs; +} diff --git a/libebl/libebl.h b/libebl/libebl.h index 990167a5..622f9e8d 100644 --- a/libebl/libebl.h +++ b/libebl/libebl.h @@ -1,5 +1,5 @@ /* Interface for libebl. - Copyright (C) 2000-2010 Red Hat, Inc. + Copyright (C) 2000-2010, 2013 Red Hat, Inc. This file is part of elfutils. This file is free software; you can redistribute it and/or modify @@ -383,6 +383,26 @@ extern int ebl_auxv_info (Ebl *ebl, GElf_Xword a_type, const char **name, const char **format) __nonnull_attribute__ (1, 3, 4); +/* Callback type for ebl_set_initial_registers_tid. */ +typedef bool (ebl_tid_registers_t) (const int firstreg, + unsigned nregs, + const Dwarf_Word *regs, + void *arg) + __nonnull_attribute__ (3); + +/* Callback to fetch process data from live TID. + EBL architecture has to have EBL_FRAME_NREGS > 0, otherwise the + backend doesn't support unwinding and this function call may crash. */ +extern bool ebl_set_initial_registers_tid (Ebl *ebl, + pid_t tid, + ebl_tid_registers_t *setfunc, + void *arg) + __nonnull_attribute__ (1, 3); + +/* Number of registers to allocate for ebl_set_initial_registers_tid. + EBL architecture can unwind iff EBL_FRAME_NREGS > 0. */ +extern size_t ebl_frame_nregs (Ebl *ebl) + __nonnull_attribute__ (1); #ifdef __cplusplus } diff --git a/libebl/libeblP.h b/libebl/libeblP.h index 5ec26a4b..4f4137d5 100644 --- a/libebl/libeblP.h +++ b/libebl/libeblP.h @@ -1,5 +1,5 @@ /* Internal definitions for interface for libebl. - Copyright (C) 2000-2009 Red Hat, Inc. + Copyright (C) 2000-2009, 2013 Red Hat, Inc. This file is part of elfutils. This file is free software; you can redistribute it and/or modify @@ -60,6 +60,10 @@ struct ebl /* Size of entry in Sysv-style hash table. */ int sysvhash_entrysize; + /* Number of registers to allocate for ebl_set_initial_registers_tid. + Ebl architecture can unwind iff FRAME_NREGS > 0. */ + size_t frame_nregs; + /* Internal data. */ void *dlhandle; }; diff --git a/m4/ChangeLog b/m4/ChangeLog index f82b0ca7..98de943b 100644 --- a/m4/ChangeLog +++ b/m4/ChangeLog @@ -1,3 +1,7 @@ +2013-11-07 Jan Kratochvil <jan.kratochvil@redhat.com> + + * biarch.m4: New file. + 2013-04-24 Mark Wielaard <mjw@redhat.com> * gettext.m4: Upgrade to gettext-0.18.2. diff --git a/m4/biarch.m4 b/m4/biarch.m4 new file mode 100644 index 00000000..a15323e2 --- /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/ChangeLog b/src/ChangeLog index 3012bf43..aebcb2ff 100644 --- a/src/ChangeLog +++ b/src/ChangeLog @@ -1,3 +1,9 @@ +2013-11-07 Jan Kratochvil <jan.kratochvil@redhat.com> + + * Makefile.am (bin_PROGRAMS): Add stack. + (stack_LDADD): New. + * stack.c: New file. + 2013-11-05 Mark Wielaard <mjw@redhat.com> * readelf.c (print_debug_ranges_section): Cast address to size_t diff --git a/src/Makefile.am b/src/Makefile.am index 674846d8..954a14ba 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,6 +1,6 @@ ## Process this file with automake to create Makefile.in ## -## Copyright (C) 1996-2012 Red Hat, Inc. +## Copyright (C) 1996-2013 Red Hat, Inc. ## This file is part of elfutils. ## ## This file is free software; you can redistribute it and/or modify @@ -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 00000000..948325d4 --- /dev/null +++ b/src/stack.c @@ -0,0 +1,174 @@ +/* Unwinding of frames like gstack/pstack. + 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/>. */ + +#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 <fcntl.h> +#include ELFUTILS_HEADER(dwfl) + +static bool verbose = false; + +static int +frame_callback (Dwfl_Frame *state, void *arg) +{ + unsigned *framenop = arg; + Dwarf_Addr pc; + bool isactivation; + if (! dwfl_frame_pc (state, &pc, &isactivation)) + { + error (0, 0, "%s", dwfl_errmsg (-1)); + return DWARF_CB_ABORT; + } + Dwarf_Addr pc_adjusted = pc - (isactivation ? 0 : 1); + + /* Get PC->SYMNAME. */ + Dwfl *dwfl = dwfl_thread_dwfl (dwfl_frame_thread (state)); + Dwfl_Module *mod = dwfl_addrmodule (dwfl, pc_adjusted); + const char *symname = NULL; + if (mod) + symname = dwfl_module_addrname (mod, pc_adjusted); + + // Try to find the address wide if possible. + static int width = 0; + if (width == 0 && mod) + { + Dwarf_Addr bias; + Elf *elf = dwfl_module_getelf (mod, &bias); + if (elf) + { + GElf_Ehdr ehdr_mem; + GElf_Ehdr *ehdr = gelf_getehdr (elf, &ehdr_mem); + if (ehdr) + width = ehdr->e_ident[EI_CLASS] == ELFCLASS32 ? 8 : 16; + } + } + if (width == 0) + width = 16; + + printf ("#%-2u 0x%0*" PRIx64, (*framenop)++, width, (uint64_t) pc); + if (verbose) + printf ("%4s", ! isactivation ? "- 1" : ""); + printf (" %s\n", symname); + return DWARF_CB_OK; +} + +static int +thread_callback (Dwfl_Thread *thread, void *thread_arg __attribute__ ((unused))) +{ + printf ("TID %ld:\n", (long) dwfl_thread_tid (thread)); + unsigned frameno = 0; + switch (dwfl_thread_getframes (thread, frame_callback, &frameno)) + { + case DWARF_CB_OK: + case DWARF_CB_ABORT: + break; + case -1: + error (0, 0, "dwfl_thread_getframes: %s", dwfl_errmsg (-1)); + break; + default: + abort (); + } + return DWARF_CB_OK; +} + +static error_t +parse_opt (int key, char *arg __attribute__ ((unused)), + struct argp_state *state) +{ + switch (key) + { + case ARGP_KEY_INIT: + state->child_inputs[0] = state->input; + break; + + case 'v': + verbose = true; + break; + + default: + return ARGP_ERR_UNKNOWN; + } + return 0; +} + +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, ""); + + const struct argp_option options[] = + { + { "verbose", 'v', NULL, 0, N_("Additionally show frames activation"), 0 }, + { NULL, 0, NULL, 0, NULL, 0 } + }; + + const struct argp_child children[] = + { + { .argp = dwfl_standard_argp () }, + { .argp = NULL }, + }; + + const struct argp argp = + { + .options = options, + .parser = parse_opt, + .doc = N_("\ +Print a stack for each thread in a process or core file.\n\ +Only real user processes are supported, no kernel or process maps."), + .children = children + }; + + int remaining; + Dwfl *dwfl = NULL; + argp_parse (&argp, argc, argv, 0, &remaining, &dwfl); + assert (dwfl != NULL); + if (remaining != argc) + error (2, 0, "eu-stack [--debuginfo-path=<path>] {-p <process id>|" + "--core=<file> [--executable=<file>]|--help}"); + + /* dwfl_linux_proc_report has been already called from dwfl_standard_argp's + parse_opt function. */ + if (dwfl_report_end (dwfl, NULL, NULL) != 0) + error (2, 0, "dwfl_report_end: %s", dwfl_errmsg (-1)); + + switch (dwfl_getthreads (dwfl, thread_callback, NULL)) + { + case DWARF_CB_OK: + break; + case -1: + error (0, 0, "dwfl_getthreads: %s", dwfl_errmsg (-1)); + break; + default: + abort (); + } + dwfl_end (dwfl); + + return 0; +} |