Hi Mark,
made a new branch:
jankratochvil/unwindx86
It is a single commit with all its pre-requisites already checked in.
Thanks,
Jan
commit b5f6f0ca766ccd33702ad810534aedab03c360bf
Author: Jan Kratochvil <jan.kratochvil(a)redhat.com>
Date: Thu May 30 14:37:38 2013 +0200
Unwinder for x86*.
backends/
2012-11-13 Jan Kratochvil <jan.kratochvil(a)redhat.com>
* Makefile.am (AM_CPPFLAGS): Add ../libdwfl.
(i386_SRCS): Add i386_frame_state.c.
(x86_64_SRCS): Add x86_64_frame_state.c.
* i386_frame_state.c: New file.
* i386_init.c (i386_init): Initialize frame_state_nregs and
frame_state.
* x86_64_frame_state.c: New file.
* x86_64_init.c (x86_64_init): Initialize frame_state_nregs and
frame_state.
./
2012-11-13 Jan Kratochvil <jan.kratochvil(a)redhat.com>
* configure.ac: New AC_CHECK_SIZEOF for long. Call utrace_BIARCH, new
AC_SUBST for CC_BIARCH.
libdw/
2012-11-13 Jan Kratochvil <jan.kratochvil(a)redhat.com>
* cfi.h (struct Dwarf_Frame_s): Make the comment more specific.
* libdw.map (ELFUTILS_0.156): Add dwfl_report_elf_baseaddr,
dwfl_frame_state_pid, dwfl_frame_state_core, dwfl_frame_state_data,
dwfl_frame_unwind, dwfl_frame_state_pc, dwfl_frame_tid_get and
dwfl_frame_thread_next.
libdwfl/
2012-11-13 Jan Kratochvil <jan.kratochvil(a)redhat.com>
* Makefile.am (AM_CPPFLAGS): Add ../libasm.
(libdwfl_a_SOURCES): Add dwfl_frame_state.c, dwfl_frame_unwind.c,
dwfl_frame_state_pc.c, dwfl_frame_state_pid.c, dwfl_frame_state_core.c,
dwfl_frame_state_data.c, dwfl_frame_thread_next.c and
dwfl_frame_tid_get.c.
* dwfl_end.c (dwfl_end): Call __libdwfl_process_free for
dwfl->framestatelist.
* dwfl_frame_state.c: New file.
* dwfl_frame_state_core.c: New file.
* dwfl_frame_state_data.c: New file.
* dwfl_frame_state_pc.c: New file.
* dwfl_frame_state_pid.c: New file.
* dwfl_frame_thread_next.c: New file.
* dwfl_frame_tid_get.c: New file.
* dwfl_frame_unwind.c: New file.
* libdwfl.h (Dwfl_Frame_State): New typedef.
(dwfl_frame_state_pid, dwfl_frame_state_core, dwfl_frame_memory_read_t)
(dwfl_frame_state_data, dwfl_frame_unwind, dwfl_frame_state_pc)
(dwfl_frame_thread_next, dwfl_frame_tid_get): New declaration.
* libdwflP.h: Include libeblP.h.
(Dwfl_Frame_State_Process, Dwfl_Frame_State_Thread): New typedefs.
(LIBEBL_BAD, CORE_MISSING, INVALID_REGISTER, PROCESS_MEMORY_READ)
(PROCESS_NO_ARCH, PARSE_PROC, NO_THREAD, INVALID_DWARF)
(UNSUPPORTED_DWARF): New DWFL_ERROR entries.
(struct Dwfl): New entry framestatelist.
(struct Dwfl_Frame_State_Process, struct Dwfl_Frame_State_Thread)
(struct Dwfl_Frame_State, dwfl_frame_state_reg_get)
(dwfl_frame_state_reg_set): New.
(__libdwfl_state_fetch_pc, __libdwfl_thread_free)
(__libdwfl_thread_alloc, __libdwfl_process_free)
(__libdwfl_process_alloc, __libdwfl_segment_start)
(__libdwfl_segment_end): New declarations.
(dwfl_frame_state_pid, dwfl_frame_state_core, dwfl_frame_unwind)
(dwfl_frame_state_pc): New INTDECL entries.
* segment.c (segment_start): Rename to ...
(__libdwfl_segment_start): ... here and make it internal_function.
(segment_end): Rename to ...
(__libdwfl_segment_end): ... here and make it internal_function.
(reify_segments, dwfl_report_segment): Rename them at the callers.
libebl/
2012-11-13 Jan Kratochvil <jan.kratochvil(a)redhat.com>
* Makefile.am (AM_CPPFLAGS): Add ../libdwfl.
(gen_SOURCES): Add eblframestate.c.
* ebl-hooks.h (frame_state): New entry.
* eblframestate.c: New file.
* libebl.h (struct Dwfl_Frame_State, ebl_frame_state)
(ebl_frame_state_nregs): New declarations.
* libeblP.h (frame_state_nregs): New entry.
m4/
2012-11-13 Jan Kratochvil <jan.kratochvil(a)redhat.com>
* biarch.m4: New file.
src/
2012-11-13 Jan Kratochvil <jan.kratochvil(a)redhat.com>
* Makefile.am (bin_PROGRAMS): Add stack.
(stack_LDADD): New.
* stack.c: New file.
tests/
2012-11-13 Jan Kratochvil <jan.kratochvil(a)redhat.com>
* Makefile.am (check_PROGRAMS): Add backtrace, backtrace-child and
backtrace-data.
(BUILT_SOURCES, clean-local, backtrace-child-biarch): New.
(TESTS): Add run-backtrace.sh.
(backtrace_LDADD, backtrace_child_CFLAGS, backtrace_child_LDFLAGS)
(backtrace_data_LDADD): New.
* backtrace-child.c: New file.
* backtrace-data.c: New file.
* backtrace.c: New file.
* run-backtrace.sh: New file.
Signed-off-by: Jan Kratochvil <jan.kratochvil(a)redhat.com>
diff --git a/backends/Makefile.am b/backends/Makefile.am
index 1923702..80c9d92 100644
--- a/backends/Makefile.am
+++ b/backends/Makefile.am
@@ -29,7 +29,8 @@
## not, see <
http://www.gnu.org/licenses/>.
include $(top_srcdir)/config/eu.am
AM_CPPFLAGS += -I$(top_srcdir)/libebl -I$(top_srcdir)/libasm \
- -I$(top_srcdir)/libelf -I$(top_srcdir)/libdw
+ -I$(top_srcdir)/libelf -I$(top_srcdir)/libdw \
+ -I$(top_srcdir)/libdwfl
modules = i386 sh x86_64 ia64 alpha arm sparc ppc ppc64 s390 tilegx
@@ -50,7 +51,8 @@ libdw = ../libdw/libdw.so
endif
i386_SRCS = i386_init.c i386_symbol.c i386_corenote.c i386_cfi.c \
- i386_retval.c i386_regs.c i386_auxv.c i386_syscall.c
+ i386_retval.c i386_regs.c i386_auxv.c i386_syscall.c \
+ i386_frame_state.c
cpu_i386 = ../libcpu/libcpu_i386.a
libebl_i386_pic_a_SOURCES = $(i386_SRCS)
am_libebl_i386_pic_a_OBJECTS = $(i386_SRCS:.c=.os)
@@ -60,7 +62,8 @@ libebl_sh_pic_a_SOURCES = $(sh_SRCS)
am_libebl_sh_pic_a_OBJECTS = $(sh_SRCS:.c=.os)
x86_64_SRCS = x86_64_init.c x86_64_symbol.c x86_64_corenote.c x86_64_cfi.c \
- x86_64_retval.c x86_64_regs.c i386_auxv.c x86_64_syscall.c
+ x86_64_retval.c x86_64_regs.c i386_auxv.c x86_64_syscall.c \
+ x86_64_frame_state.c
cpu_x86_64 = ../libcpu/libcpu_x86_64.a
libebl_x86_64_pic_a_SOURCES = $(x86_64_SRCS)
am_libebl_x86_64_pic_a_OBJECTS = $(x86_64_SRCS:.c=.os)
diff --git a/backends/i386_frame_state.c b/backends/i386_frame_state.c
new file mode 100644
index 0000000..b5d5439
--- /dev/null
+++ b/backends/i386_frame_state.c
@@ -0,0 +1,80 @@
+/* Fetch process data from STATE->base->pid or STATE->base->core.
+ Copyright (C) 2012 Red Hat, Inc.
+ This file is part of elfutils.
+
+ This file is free software; you can redistribute it and/or modify
+ it under the terms of either
+
+ * the GNU Lesser General Public License as published by the Free
+ Software Foundation; either version 3 of the License, or (at
+ your option) any later version
+
+ or
+
+ * the GNU General Public License as published by the Free
+ Software Foundation; either version 2 of the License, or (at
+ your option) any later version
+
+ or both in parallel, as here.
+
+ elfutils is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received copies of the GNU General Public License and
+ the GNU Lesser General Public License along with this program. If
+ not, see <
http://www.gnu.org/licenses/>. */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#if defined __i386__ || defined __x86_64__
+# include <sys/user.h>
+# include <sys/ptrace.h>
+#endif
+#include "libdwflP.h"
+
+#define BACKEND i386_
+#include "libebl_CPU.h"
+
+bool
+i386_frame_state (Dwfl_Frame_State *state)
+{
+ pid_t tid = state->thread->tid;
+ if (state->thread->process->core == NULL && tid)
+ {
+#if !defined __i386__ && !defined __x86_64__
+ return false;
+#else /* __i386__ || __x86_64__ */
+ struct user_regs_struct user_regs;
+ if (ptrace (PTRACE_GETREGS, tid, NULL, &user_regs) != 0)
+ return false;
+# if defined __i386__
+ dwfl_frame_state_reg_set (state, 0, user_regs.eax);
+ dwfl_frame_state_reg_set (state, 1, user_regs.ecx);
+ dwfl_frame_state_reg_set (state, 2, user_regs.edx);
+ dwfl_frame_state_reg_set (state, 3, user_regs.ebx);
+ dwfl_frame_state_reg_set (state, 4, user_regs.esp);
+ dwfl_frame_state_reg_set (state, 5, user_regs.ebp);
+ dwfl_frame_state_reg_set (state, 6, user_regs.esi);
+ dwfl_frame_state_reg_set (state, 7, user_regs.edi);
+ dwfl_frame_state_reg_set (state, 8, user_regs.eip);
+# elif defined __x86_64__
+ dwfl_frame_state_reg_set (state, 0, user_regs.rax);
+ dwfl_frame_state_reg_set (state, 1, user_regs.rcx);
+ dwfl_frame_state_reg_set (state, 2, user_regs.rdx);
+ dwfl_frame_state_reg_set (state, 3, user_regs.rbx);
+ dwfl_frame_state_reg_set (state, 4, user_regs.rsp);
+ dwfl_frame_state_reg_set (state, 5, user_regs.rbp);
+ dwfl_frame_state_reg_set (state, 6, user_regs.rsi);
+ dwfl_frame_state_reg_set (state, 7, user_regs.rdi);
+ dwfl_frame_state_reg_set (state, 8, user_regs.rip);
+# else /* (__i386__ || __x86_64__) && (!__i386__ && !__x86_64__) */
+# error
+# endif /* (__i386__ || __x86_64__) && (!__i386__ && !__x86_64__) */
+#endif /* __i386__ || __x86_64__ */
+ }
+ return true;
+}
diff --git a/backends/i386_init.c b/backends/i386_init.c
index cc9b2d7..4200f31 100644
--- a/backends/i386_init.c
+++ b/backends/i386_init.c
@@ -63,6 +63,9 @@ i386_init (elf, machine, eh, ehlen)
HOOK (eh, auxv_info);
HOOK (eh, disasm);
HOOK (eh, abi_cfi);
+ /* gcc/config/ #define DWARF_FRAME_REGISTERS. For i386 it is 17, why? */
+ eh->frame_state_nregs = 9;
+ HOOK (eh, frame_state);
return MODVERSION;
}
diff --git a/backends/x86_64_frame_state.c b/backends/x86_64_frame_state.c
new file mode 100644
index 0000000..b304250
--- /dev/null
+++ b/backends/x86_64_frame_state.c
@@ -0,0 +1,75 @@
+/* Fetch live process Dwfl_Frame_State from PID.
+ Copyright (C) 2012 Red Hat, Inc.
+ This file is part of elfutils.
+
+ This file is free software; you can redistribute it and/or modify
+ it under the terms of either
+
+ * the GNU Lesser General Public License as published by the Free
+ Software Foundation; either version 3 of the License, or (at
+ your option) any later version
+
+ or
+
+ * the GNU General Public License as published by the Free
+ Software Foundation; either version 2 of the License, or (at
+ your option) any later version
+
+ or both in parallel, as here.
+
+ elfutils is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received copies of the GNU General Public License and
+ the GNU Lesser General Public License along with this program. If
+ not, see <
http://www.gnu.org/licenses/>. */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdlib.h>
+#ifdef __x86_64__
+# include <sys/user.h>
+# include <sys/ptrace.h>
+#endif
+#include "libdwflP.h"
+
+#define BACKEND x86_64_
+#include "libebl_CPU.h"
+
+bool
+x86_64_frame_state (Dwfl_Frame_State *state)
+{
+ pid_t tid = state->thread->tid;
+ if (state->thread->process->core == NULL && tid)
+ {
+#ifndef __x86_64__
+ return false;
+#else /* __x86_64__ */
+ struct user_regs_struct user_regs;
+ if (ptrace (PTRACE_GETREGS, tid, NULL, &user_regs) != 0)
+ return false;
+ dwfl_frame_state_reg_set (state, 0, user_regs.rax);
+ dwfl_frame_state_reg_set (state, 1, user_regs.rdx);
+ dwfl_frame_state_reg_set (state, 2, user_regs.rcx);
+ dwfl_frame_state_reg_set (state, 3, user_regs.rbx);
+ dwfl_frame_state_reg_set (state, 4, user_regs.rsi);
+ dwfl_frame_state_reg_set (state, 5, user_regs.rdi);
+ dwfl_frame_state_reg_set (state, 6, user_regs.rbp);
+ dwfl_frame_state_reg_set (state, 7, user_regs.rsp);
+ dwfl_frame_state_reg_set (state, 8, user_regs.r8);
+ dwfl_frame_state_reg_set (state, 9, user_regs.r9);
+ dwfl_frame_state_reg_set (state, 10, user_regs.r10);
+ dwfl_frame_state_reg_set (state, 11, user_regs.r11);
+ dwfl_frame_state_reg_set (state, 12, user_regs.r12);
+ dwfl_frame_state_reg_set (state, 13, user_regs.r13);
+ dwfl_frame_state_reg_set (state, 14, user_regs.r14);
+ dwfl_frame_state_reg_set (state, 15, user_regs.r15);
+ dwfl_frame_state_reg_set (state, 16, user_regs.rip);
+#endif /* __x86_64__ */
+ }
+ return true;
+}
diff --git a/backends/x86_64_init.c b/backends/x86_64_init.c
index 67a5880..08c63f1 100644
--- a/backends/x86_64_init.c
+++ b/backends/x86_64_init.c
@@ -60,6 +60,9 @@ x86_64_init (elf, machine, eh, ehlen)
HOOK (eh, auxv_info);
HOOK (eh, disasm);
HOOK (eh, abi_cfi);
+ /* gcc/config/ #define DWARF_FRAME_REGISTERS. */
+ eh->frame_state_nregs = 17;
+ HOOK (eh, frame_state);
return MODVERSION;
}
diff --git a/configure.ac b/configure.ac
index d7c75ce..47cfaa3 100644
--- a/configure.ac
+++ b/configure.ac
@@ -317,4 +317,15 @@ esac
# Round up to the next release API (x.y) version.
eu_version=$(( (eu_version + 999) / 1000 ))
+AC_CHECK_SIZEOF(long)
+
+# On a 64-bit host where can can use $CC -m32, we'll run two sets of tests.
+# Likewise in a 32-bit build on a host where $CC -m64 works.
+utrace_BIARCH
+# `$utrace_biarch' will be `-m64' even on an uniarch i386 machine.
+AS_IF([test $utrace_cv_cc_biarch = yes],
+ [CC_BIARCH="$CC $utrace_biarch"],
+ [CC_BIARCH="$CC"])
+AC_SUBST([CC_BIARCH])
+
AC_OUTPUT
diff --git a/libdw/cfi.h b/libdw/cfi.h
index 8949833..44a84d4 100644
--- a/libdw/cfi.h
+++ b/libdw/cfi.h
@@ -150,8 +150,8 @@ struct dwarf_frame_register
Dwarf_Sword value:(sizeof (Dwarf_Sword) * 8 - 3);
};
-/* This holds everything we know about the state of the frame
- at a particular PC location described by an FDE. */
+/* This holds instructions for unwinding frame at a particular PC location
+ described by an FDE. */
struct Dwarf_Frame_s
{
/* This frame description covers PC values in [start, end). */
diff --git a/libdw/libdw.map b/libdw/libdw.map
index d38a8ef..54ee7f2 100644
--- a/libdw/libdw.map
+++ b/libdw/libdw.map
@@ -259,4 +259,11 @@ ELFUTILS_0.156 {
global:
# Replaced ELFUTILS_0.122 version, which has a wrapper without add_p_vaddr.
dwfl_report_elf;
+ dwfl_frame_state_pid;
+ dwfl_frame_state_core;
+ dwfl_frame_state_data;
+ dwfl_frame_unwind;
+ dwfl_frame_state_pc;
+ dwfl_frame_tid_get;
+ dwfl_frame_thread_next;
} ELFUTILS_0.149;
diff --git a/libdwfl/Makefile.am b/libdwfl/Makefile.am
index 3ef4dd6..d9dd5ab 100644
--- a/libdwfl/Makefile.am
+++ b/libdwfl/Makefile.am
@@ -31,7 +31,7 @@
##
include $(top_srcdir)/config/eu.am
AM_CPPFLAGS += -I$(srcdir) -I$(srcdir)/../libelf -I$(srcdir)/../libebl \
- -I$(srcdir)/../libdw
+ -I$(srcdir)/../libdw -I$(srcdir)/../libasm
VERSION = 1
noinst_LIBRARIES = libdwfl.a
@@ -68,7 +68,11 @@ libdwfl_a_SOURCES = dwfl_begin.c dwfl_end.c dwfl_error.c dwfl_version.c
\
dwfl_module_return_value_location.c \
dwfl_module_register_names.c \
dwfl_segment_report_module.c \
- link_map.c core-file.c open.c image-header.c
+ link_map.c core-file.c open.c image-header.c \
+ dwfl_frame_state.c dwfl_frame_unwind.c \
+ dwfl_frame_state_pc.c dwfl_frame_state_pid.c \
+ dwfl_frame_state_core.c dwfl_frame_state_data.c \
+ dwfl_frame_thread_next.c dwfl_frame_tid_get.c
if ZLIB
libdwfl_a_SOURCES += gzip.c
diff --git a/libdwfl/dwfl_end.c b/libdwfl/dwfl_end.c
index 94fcfc6..babcba7 100644
--- a/libdwfl/dwfl_end.c
+++ b/libdwfl/dwfl_end.c
@@ -34,6 +34,9 @@ dwfl_end (Dwfl *dwfl)
if (dwfl == NULL)
return;
+ while (dwfl->framestatelist)
+ __libdwfl_process_free (dwfl->framestatelist);
+
free (dwfl->lookup_addr);
free (dwfl->lookup_module);
free (dwfl->lookup_segndx);
diff --git a/libdwfl/dwfl_frame_state.c b/libdwfl/dwfl_frame_state.c
new file mode 100644
index 0000000..1229aa2
--- /dev/null
+++ b/libdwfl/dwfl_frame_state.c
@@ -0,0 +1,175 @@
+/* Get Dwarf Frame state for target PID or core file.
+ Copyright (C) 2012 Red Hat, Inc.
+ This file is part of elfutils.
+
+ This file is free software; you can redistribute it and/or modify
+ it under the terms of either
+
+ * the GNU Lesser General Public License as published by the Free
+ Software Foundation; either version 3 of the License, or (at
+ your option) any later version
+
+ or
+
+ * the GNU General Public License as published by the Free
+ Software Foundation; either version 2 of the License, or (at
+ your option) any later version
+
+ or both in parallel, as here.
+
+ elfutils is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received copies of the GNU General Public License and
+ the GNU Lesser General Public License along with this program. If
+ not, see <
http://www.gnu.org/licenses/>. */
+
+#include "libdwflP.h"
+#include <sys/ptrace.h>
+#include <unistd.h>
+
+#ifndef MIN
+# define MIN(a, b) ((a) < (b) ? (a) : (b))
+#endif
+
+bool
+internal_function
+__libdwfl_state_fetch_pc (Dwfl_Frame_State *state)
+{
+ switch (state->pc_state)
+ {
+ case DWFL_FRAME_STATE_PC_SET:
+ return true;
+ case DWFL_FRAME_STATE_PC_UNDEFINED:
+ abort ();
+ case DWFL_FRAME_STATE_ERROR:;
+ Ebl *ebl = state->thread->process->ebl;
+ Dwarf_CIE abi_info;
+ if (ebl_abi_cfi (ebl, &abi_info) != 0)
+ {
+ __libdwfl_seterrno (DWFL_E_LIBEBL);
+ return false;
+ }
+ unsigned ra = abi_info.return_address_register;
+ /* dwarf_frame_state_reg_is_set is not applied here. */
+ if (ra >= ebl_frame_state_nregs (ebl))
+ {
+ __libdwfl_seterrno (DWFL_E_LIBEBL_BAD);
+ return false;
+ }
+ state->pc = state->regs[ra];
+ state->pc_state = DWFL_FRAME_STATE_PC_SET;
+ return true;
+ }
+ abort ();
+}
+
+/* Do not call it on your own, to be used by thread_* functions only. */
+
+static void
+state_free (Dwfl_Frame_State *state)
+{
+ Dwfl_Frame_State_Thread *thread = state->thread;
+ assert (thread->unwound == state);
+ thread->unwound = state->unwound;
+ free (state);
+}
+
+/* Do not call it on your own, to be used by thread_* functions only. */
+
+static Dwfl_Frame_State *
+state_alloc (Dwfl_Frame_State_Thread *thread)
+{
+ assert (thread->unwound == NULL);
+ Ebl *ebl = thread->process->ebl;
+ size_t nregs = ebl_frame_state_nregs (ebl);
+ if (nregs == 0)
+ return NULL;
+ assert (nregs < sizeof (((Dwfl_Frame_State *) NULL)->regs_set) * 8);
+ Dwfl_Frame_State *state = malloc (sizeof (*state)
+ + sizeof (*state->regs) * nregs);
+ if (state == NULL)
+ return NULL;
+ state->thread = thread;
+ state->signal_frame = false;
+ state->pc_state = DWFL_FRAME_STATE_ERROR;
+ memset (state->regs_set, 0, sizeof (state->regs_set));
+ thread->unwound = state;
+ state->unwound = NULL;
+ return state;
+}
+
+void
+internal_function
+__libdwfl_thread_free (Dwfl_Frame_State_Thread *thread)
+{
+ while (thread->unwound)
+ state_free (thread->unwound);
+ if (thread->tid_attached)
+ ptrace (PTRACE_DETACH, thread->tid, NULL, NULL);
+ Dwfl_Frame_State_Process *process = thread->process;
+ assert (process->thread == thread);
+ process->thread = thread->next;
+ free (thread);
+}
+
+Dwfl_Frame_State_Thread *
+internal_function
+__libdwfl_thread_alloc (Dwfl_Frame_State_Process *process, pid_t tid)
+{
+ Dwfl_Frame_State_Thread *thread = malloc (sizeof (*thread));
+ if (thread == NULL)
+ return NULL;
+ thread->process = process;
+ thread->tid = tid;
+ thread->tid_attached = false;
+ thread->unwound = NULL;
+ thread->next = process->thread;
+ process->thread = thread;
+ if (state_alloc (thread) == NULL)
+ {
+ __libdwfl_thread_free (thread);
+ return NULL;
+ }
+ return thread;
+}
+
+void
+internal_function
+__libdwfl_process_free (Dwfl_Frame_State_Process *process)
+{
+ while (process->thread)
+ __libdwfl_thread_free (process->thread);
+ if (process->ebl_close)
+ ebl_closebackend (process->ebl);
+ elf_end (process->core);
+ if (process->core_fd != -1)
+ close (process->core_fd);
+ Dwfl *dwfl = process->dwfl;
+ assert (dwfl->framestatelist == process);
+ dwfl->framestatelist = process->next;
+ free (process);
+}
+
+Dwfl_Frame_State_Process *
+internal_function
+__libdwfl_process_alloc (Dwfl *dwfl, dwfl_frame_memory_read_t *memory_read,
+ void *memory_read_user_data)
+{
+ Dwfl_Frame_State_Process *process = malloc (sizeof (*process));
+ if (process == NULL)
+ return NULL;
+ process->dwfl = dwfl;
+ process->ebl = NULL;
+ process->ebl_close = NULL;
+ process->memory_read = memory_read;
+ process->memory_read_user_data = memory_read_user_data;
+ process->core = NULL;
+ process->core_fd = -1;
+ process->thread = NULL;
+ process->next = dwfl->framestatelist;
+ dwfl->framestatelist = process;
+ return process;
+}
diff --git a/libdwfl/dwfl_frame_state_core.c b/libdwfl/dwfl_frame_state_core.c
new file mode 100644
index 0000000..737bcbb
--- /dev/null
+++ b/libdwfl/dwfl_frame_state_core.c
@@ -0,0 +1,286 @@
+/* Get Dwarf Frame state for target core file.
+ Copyright (C) 2012 Red Hat, Inc.
+ This file is part of elfutils.
+
+ This file is free software; you can redistribute it and/or modify
+ it under the terms of either
+
+ * the GNU Lesser General Public License as published by the Free
+ Software Foundation; either version 3 of the License, or (at
+ your option) any later version
+
+ or
+
+ * the GNU General Public License as published by the Free
+ Software Foundation; either version 2 of the License, or (at
+ your option) any later version
+
+ or both in parallel, as here.
+
+ elfutils is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received copies of the GNU General Public License and
+ the GNU Lesser General Public License along with this program. If
+ not, see <
http://www.gnu.org/licenses/>. */
+
+#include "libdwflP.h"
+#include <fcntl.h>
+#include "system.h"
+
+#ifndef MIN
+# define MIN(a, b) ((a) < (b) ? (a) : (b))
+#endif
+
+static bool
+dwfl_frame_state_core_memory_read (Dwarf_Addr addr, Dwarf_Addr *result,
+ void *user_data)
+{
+ Dwfl_Frame_State_Process *process = user_data;
+ Elf *core = process->core;
+ assert (core != NULL);
+ Dwfl *dwfl = process->dwfl;
+ static size_t phnum;
+ if (elf_getphdrnum (core, &phnum) < 0)
+ {
+ __libdwfl_seterrno (DWFL_E_LIBELF);
+ return false;
+ }
+ for (size_t cnt = 0; cnt < phnum; ++cnt)
+ {
+ GElf_Phdr phdr_mem, *phdr = gelf_getphdr (core, cnt, &phdr_mem);
+ if (phdr == NULL || phdr->p_type != PT_LOAD)
+ continue;
+ /* Bias is zero here, a core file itself has no bias. */
+ GElf_Addr start = __libdwfl_segment_start (dwfl, phdr->p_vaddr);
+ GElf_Addr end = __libdwfl_segment_end (dwfl,
+ phdr->p_vaddr + phdr->p_memsz);
+ unsigned bytes = process->ebl->class == ELFCLASS64 ? 8 : 4;
+ if (addr < start || addr + bytes > end)
+ continue;
+ Elf_Data *data;
+ data = elf_getdata_rawchunk (core, phdr->p_offset + addr - start,
+ bytes, ELF_T_ADDR);
+ if (data == NULL)
+ {
+ __libdwfl_seterrno (DWFL_E_LIBELF);
+ return false;
+ }
+ assert (data->d_size == bytes);
+ /* FIXME: Currently any arch supported for unwinding supports
+ unaligned access. */
+ if (bytes == 8)
+ *result = *(const uint64_t *) data->d_buf;
+ else
+ *result = *(const uint32_t *) data->d_buf;
+ return true;
+ }
+ __libdwfl_seterrno (DWFL_E_CORE_MISSING);
+ return false;
+}
+
+Dwfl_Frame_State *
+dwfl_frame_state_core (Dwfl *dwfl, const char *corefile)
+{
+ Dwfl_Frame_State_Process *process;
+ process = __libdwfl_process_alloc (dwfl, dwfl_frame_state_core_memory_read,
+ NULL);
+ if (process == NULL)
+ return NULL;
+ process->memory_read_user_data = process;
+ int core_fd = open64 (corefile, O_RDONLY);
+ if (core_fd < 0)
+ {
+ __libdwfl_process_free (process);
+ __libdwfl_seterrno (DWFL_E_BADELF);
+ return NULL;
+ }
+ process->core_fd = core_fd;
+ Elf *core;
+ Dwfl_Error err = __libdw_open_file (&core_fd, &core, true, false);
+ if (err != DWFL_E_NOERROR)
+ {
+ __libdwfl_process_free (process);
+ __libdwfl_seterrno (err);
+ return NULL;
+ }
+ process->core = core;
+ Ebl *ebl = ebl_openbackend (core);
+ if (ebl == NULL)
+ {
+ __libdwfl_process_free (process);
+ __libdwfl_seterrno (DWFL_E_LIBEBL);
+ return NULL;
+ }
+ process->ebl = ebl;
+ process->ebl_close = true;
+ size_t nregs = ebl_frame_state_nregs (ebl);
+ if (nregs == 0)
+ {
+ /* We do not support unwinding this CORE file EBL. */
+ __libdwfl_process_free (process);
+ __libdwfl_seterrno (DWFL_E_LIBEBL);
+ return NULL;
+ }
+ GElf_Ehdr ehdr_mem, *ehdr = gelf_getehdr (core, &ehdr_mem);
+ if (ehdr == NULL)
+ {
+ __libdwfl_process_free (process);
+ __libdwfl_seterrno (DWFL_E_LIBELF);
+ return NULL;
+ }
+ assert (ehdr->e_type == ET_CORE);
+ size_t phnum;
+ if (elf_getphdrnum (core, &phnum) < 0)
+ {
+ __libdwfl_process_free (process);
+ __libdwfl_seterrno (DWFL_E_LIBELF);
+ return NULL;
+ }
+ Dwfl_Frame_State_Thread *thread = NULL;
+ for (size_t cnt = 0; cnt < phnum; ++cnt)
+ {
+ GElf_Phdr phdr_mem, *phdr = gelf_getphdr (core, cnt, &phdr_mem);
+ if (phdr == NULL || phdr->p_type != PT_NOTE)
+ continue;
+ Elf_Data *data = elf_getdata_rawchunk (core, phdr->p_offset,
+ phdr->p_filesz, ELF_T_NHDR);
+ if (data == NULL)
+ {
+ __libdwfl_process_free (process);
+ __libdwfl_seterrno (DWFL_E_LIBELF);
+ return NULL;
+ }
+ size_t offset = 0;
+ GElf_Nhdr nhdr;
+ size_t name_offset;
+ size_t desc_offset;
+ while (offset < data->d_size
+ && (offset = gelf_getnote (data, offset,
+ &nhdr, &name_offset, &desc_offset)) > 0)
+ {
+ /* Do not check NAME for now, help broken Linux kernels. */
+ const char *name = data->d_buf + name_offset;
+ const char *desc = data->d_buf + desc_offset;
+ GElf_Word regs_offset;
+ size_t nregloc;
+ const Ebl_Register_Location *reglocs;
+ size_t nitems;
+ const Ebl_Core_Item *items;
+ if (! ebl_core_note (ebl, &nhdr, name,
+ ®s_offset, &nregloc, ®locs, &nitems, &items))
+ {
+ /* This note may be just not recognized, skip it. */
+ continue;
+ }
+ if (nhdr.n_type == NT_PRSTATUS)
+ {
+ const Ebl_Core_Item *item;
+ for (item = items; item < items + nitems; item++)
+ if (strcmp (item->name, "pid") == 0)
+ break;
+ if (item == items + nitems)
+ {
+ __libdwfl_process_free (process);
+ __libdwfl_seterrno (DWFL_E_BADELF);
+ return NULL;
+ }
+ uint32_t val32 = *(const uint32_t *) (desc + item->offset);
+ val32 = (elf_getident (core, NULL)[EI_DATA] == ELFDATA2MSB
+ ? be32toh (val32) : le32toh (val32));
+ pid_t tid = (int32_t) val32;
+ eu_static_assert (sizeof val32 <= sizeof tid);
+ if (thread)
+ {
+ /* Delay initialization of THREAD till all notes for it have
+ been read in. */
+ Dwfl_Frame_State *state = thread->unwound;
+ if (! ebl_frame_state (state)
+ || ! __libdwfl_state_fetch_pc (state))
+ {
+ __libdwfl_thread_free (thread);
+ thread = NULL;
+ continue;
+ }
+ }
+ thread = __libdwfl_thread_alloc (process, tid);
+ if (thread == NULL)
+ {
+ __libdwfl_process_free (process);
+ __libdwfl_seterrno (DWFL_E_NOMEM);
+ return NULL;
+ }
+ }
+ if (thread == NULL)
+ {
+ /* Ignore notes before first PR_NTSTATUS. */
+ continue;
+ }
+ Dwfl_Frame_State *state = thread->unwound;
+ desc += regs_offset;
+ for (size_t regloci = 0; regloci < nregloc; regloci++)
+ {
+ const Ebl_Register_Location *regloc = reglocs + regloci;
+ if (regloc->regno >= nregs)
+ continue;
+ assert (regloc->bits == 32 || regloc->bits == 64);
+ const char *reg_desc = desc + regloc->offset;
+ for (unsigned regno = regloc->regno;
+ regno < MIN (regloc->regno + (regloc->count ?: 1U), nregs);
+ regno++)
+ {
+ /* PPC provides DWARF register 65 irrelevant for
+ CFI which clashes with register 108 (LR) we need.
+ LR (108) is provided earlier (in NT_PRSTATUS) than the # 65.
+ FIXME: It depends now on their order in core notes. */
+ if (dwfl_frame_state_reg_get (state, regno, NULL))
+ continue;
+ Dwarf_Addr val;
+ switch (regloc->bits)
+ {
+ case 32:;
+ uint32_t val32 = *(const uint32_t *) reg_desc;
+ reg_desc += sizeof val32;
+ val32 = (elf_getident (core, NULL)[EI_DATA] == ELFDATA2MSB
+ ? be32toh (val32) : le32toh (val32));
+ /* Do a host width conversion. */
+ val = val32;
+ break;
+ case 64:;
+ uint64_t val64 = *(const uint64_t *) reg_desc;
+ reg_desc += sizeof val64;
+ val64 = (elf_getident (core, NULL)[EI_DATA] == ELFDATA2MSB
+ ? be64toh (val64) : le64toh (val64));
+ assert (sizeof (*state->regs) == sizeof val64);
+ val = val64;
+ break;
+ default:
+ abort ();
+ }
+ /* Registers not valid for CFI are just ignored. */
+ dwfl_frame_state_reg_set (state, regno, val);
+ reg_desc += regloc->pad;
+ }
+ }
+ }
+ }
+ if (thread)
+ {
+ /* Delay initialization of THREAD till all notes for it have been read
+ in. */
+ Dwfl_Frame_State *state = thread->unwound;
+ if (! ebl_frame_state (state) || ! __libdwfl_state_fetch_pc (state))
+ __libdwfl_thread_free (thread);
+ }
+ if (process->thread == NULL)
+ {
+ /* No valid threads recognized in this CORE. */
+ __libdwfl_process_free (process);
+ __libdwfl_seterrno (DWFL_E_BADELF);
+ return NULL;
+ }
+ return process->thread->unwound;
+}
+INTDEF (dwfl_frame_state_core)
diff --git a/libdwfl/dwfl_frame_state_data.c b/libdwfl/dwfl_frame_state_data.c
new file mode 100644
index 0000000..9adfa7c
--- /dev/null
+++ b/libdwfl/dwfl_frame_state_data.c
@@ -0,0 +1,91 @@
+/* Get Dwarf Frame state from modules present in DWFL.
+ Copyright (C) 2012 Red Hat, Inc.
+ This file is part of elfutils.
+
+ This file is free software; you can redistribute it and/or modify
+ it under the terms of either
+
+ * the GNU Lesser General Public License as published by the Free
+ Software Foundation; either version 3 of the License, or (at
+ your option) any later version
+
+ or
+
+ * the GNU General Public License as published by the Free
+ Software Foundation; either version 2 of the License, or (at
+ your option) any later version
+
+ or both in parallel, as here.
+
+ elfutils is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received copies of the GNU General Public License and
+ the GNU Lesser General Public License along with this program. If
+ not, see <
http://www.gnu.org/licenses/>. */
+
+#include "libdwflP.h"
+
+Dwfl_Frame_State *
+dwfl_frame_state_data (Dwfl *dwfl, bool pc_set, Dwarf_Addr pc, unsigned nregs,
+ const uint64_t *regs_set, const Dwarf_Addr *regs,
+ dwfl_frame_memory_read_t *memory_read,
+ void *memory_read_user_data)
+{
+ Ebl *ebl = NULL;
+ for (Dwfl_Module *mod = dwfl->modulelist; mod != NULL; mod = mod->next)
+ {
+ Dwfl_Error error = __libdwfl_module_getebl (mod);
+ if (error != DWFL_E_NOERROR)
+ continue;
+ ebl = mod->ebl;
+ }
+ if (ebl == NULL || nregs > ebl_frame_state_nregs (ebl))
+ {
+ __libdwfl_seterrno (DWFL_E_LIBEBL_BAD);
+ return NULL;
+ }
+ Dwfl_Frame_State_Process *process;
+ process = __libdwfl_process_alloc (dwfl, memory_read, memory_read_user_data);
+ if (process == NULL)
+ return NULL;
+ process->ebl = ebl;
+ Dwfl_Frame_State_Thread *thread = __libdwfl_thread_alloc (process, 0);
+ if (thread == NULL)
+ {
+ __libdwfl_process_free (process);
+ __libdwfl_seterrno (DWFL_E_NOMEM);
+ return NULL;
+ }
+ Dwfl_Frame_State *state = thread->unwound;
+ state->pc_state = DWFL_FRAME_STATE_ERROR;
+ if (pc_set)
+ {
+ state->pc = pc;
+ state->pc_state = DWFL_FRAME_STATE_PC_SET;
+ }
+ for (unsigned regno = 0; regno < nregs; regno++)
+ if ((regs_set[regno / sizeof (*regs_set) / 8]
+ & (1U << (regno % (sizeof (*regs_set) * 8)))) != 0
+ && ! dwfl_frame_state_reg_set (state, regno, regs[regno]))
+ {
+ __libdwfl_process_free (process);
+ __libdwfl_seterrno (DWFL_E_INVALID_REGISTER);
+ return NULL;
+ }
+ if (! ebl_frame_state (state))
+ {
+ __libdwfl_process_free (process);
+ __libdwfl_seterrno (DWFL_E_LIBEBL);
+ return NULL;
+ }
+ if (! __libdwfl_state_fetch_pc (state))
+ {
+ __libdwfl_process_free (process);
+ return NULL;
+ }
+ return process->thread->unwound;
+}
+INTDEF (dwfl_frame_state_data)
diff --git a/libdwfl/dwfl_frame_state_pc.c b/libdwfl/dwfl_frame_state_pc.c
new file mode 100644
index 0000000..6e9928b
--- /dev/null
+++ b/libdwfl/dwfl_frame_state_pc.c
@@ -0,0 +1,56 @@
+/* Get return address register value for frame.
+ Copyright (C) 2012 Red Hat, Inc.
+ This file is part of elfutils.
+
+ This file is free software; you can redistribute it and/or modify
+ it under the terms of either
+
+ * the GNU Lesser General Public License as published by the Free
+ Software Foundation; either version 3 of the License, or (at
+ your option) any later version
+
+ or
+
+ * the GNU General Public License as published by the Free
+ Software Foundation; either version 2 of the License, or (at
+ your option) any later version
+
+ or both in parallel, as here.
+
+ elfutils is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received copies of the GNU General Public License and
+ the GNU Lesser General Public License along with this program. If
+ not, see <
http://www.gnu.org/licenses/>. */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include "libdwflP.h"
+
+bool
+dwfl_frame_state_pc (Dwfl_Frame_State *state, Dwarf_Addr *pc, bool *minusone)
+{
+ assert (state->pc_state == DWFL_FRAME_STATE_PC_SET);
+ *pc = state->pc;
+ if (minusone)
+ {
+ /* Bottom frame? */
+ if (state == state->thread->unwound)
+ *minusone = false;
+ /* *MINUSONE is logical or of both current and previous frame state. */
+ else if (state->signal_frame)
+ *minusone = false;
+ /* Not affected by unsuccessfully unwound frame. */
+ else if (! INTUSE(dwfl_frame_unwind) (&state) || state == NULL)
+ *minusone = true;
+ else
+ *minusone = ! state->signal_frame;
+ }
+ return true;
+}
+INTDEF (dwfl_frame_state_pc)
diff --git a/libdwfl/dwfl_frame_state_pid.c b/libdwfl/dwfl_frame_state_pid.c
new file mode 100644
index 0000000..89fbee2
--- /dev/null
+++ b/libdwfl/dwfl_frame_state_pid.c
@@ -0,0 +1,220 @@
+/* Get Dwarf Frame state for target live PID process.
+ Copyright (C) 2012 Red Hat, Inc.
+ This file is part of elfutils.
+
+ This file is free software; you can redistribute it and/or modify
+ it under the terms of either
+
+ * the GNU Lesser General Public License as published by the Free
+ Software Foundation; either version 3 of the License, or (at
+ your option) any later version
+
+ or
+
+ * the GNU General Public License as published by the Free
+ Software Foundation; either version 2 of the License, or (at
+ your option) any later version
+
+ or both in parallel, as here.
+
+ elfutils is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received copies of the GNU General Public License and
+ the GNU Lesser General Public License along with this program. If
+ not, see <
http://www.gnu.org/licenses/>. */
+
+#include "libdwflP.h"
+#include <sys/ptrace.h>
+#include <sys/wait.h>
+#include <dirent.h>
+
+static bool
+tid_is_attached (Dwfl *dwfl, pid_t tid)
+{
+ for (Dwfl_Frame_State_Process *process = dwfl->framestatelist; process;
+ process = process->next)
+ for (Dwfl_Frame_State_Thread *thread = process->thread; thread;
+ thread = thread->next)
+ if (thread->tid_attached && thread->tid == tid)
+ return true;
+ return false;
+}
+
+static bool
+ptrace_attach (pid_t tid)
+{
+ if (ptrace (PTRACE_ATTACH, tid, NULL, NULL) != 0)
+ return false;
+ /* FIXME: Handle missing SIGSTOP on old Linux kernels. */
+ for (;;)
+ {
+ int status;
+ if (waitpid (tid, &status, __WALL) != tid || !WIFSTOPPED (status))
+ {
+ ptrace (PTRACE_DETACH, tid, NULL, NULL);
+ return false;
+ }
+ if (WSTOPSIG (status) == SIGSTOP)
+ break;
+ if (ptrace (PTRACE_CONT, tid, NULL,
+ (void *) (uintptr_t) WSTOPSIG (status)) != 0)
+ {
+ ptrace (PTRACE_DETACH, tid, NULL, NULL);
+ return false;
+ }
+ }
+ return true;
+}
+
+static bool
+dwfl_frame_state_pid_memory_read (Dwarf_Addr addr, Dwarf_Addr *result,
+ void *user_data)
+{
+ Dwfl_Frame_State_Process *process = user_data;
+ assert (process->core == NULL && process->thread->tid);
+ if (process->ebl->class == ELFCLASS64)
+ {
+ errno = 0;
+ *result = ptrace (PTRACE_PEEKDATA, process->thread->tid,
+ (void *) (uintptr_t) addr, NULL);
+ if (errno != 0)
+ {
+ __libdwfl_seterrno (DWFL_E_PROCESS_MEMORY_READ);
+ return false;
+ }
+ return true;
+ }
+#if SIZEOF_LONG == 8
+ /* We do not care about reads unaliged to 4 bytes boundary.
+ But 0x...ffc read of 8 bytes could overrun a page. */
+ bool lowered = (addr & 4) != 0;
+ if (lowered)
+ addr -= 4;
+#endif /* SIZEOF_LONG == 8 */
+ errno = 0;
+ *result = ptrace (PTRACE_PEEKDATA, process->thread->tid,
+ (void *) (uintptr_t) addr, NULL);
+ if (errno != 0)
+ {
+ __libdwfl_seterrno (DWFL_E_PROCESS_MEMORY_READ);
+ return false;
+ }
+#if SIZEOF_LONG == 8
+# if BYTE_ORDER == BIG_ENDIAN
+ if (! lowered)
+ *result >>= 32;
+# else
+ if (lowered)
+ *result >>= 32;
+# endif
+#endif /* SIZEOF_LONG == 8 */
+ *result &= 0xffffffff;
+ return true;
+}
+
+Dwfl_Frame_State *
+dwfl_frame_state_pid (Dwfl *dwfl, pid_t pid)
+{
+ char dirname[64];
+ int i = snprintf (dirname, sizeof (dirname), "/proc/%ld/task", (long) pid);
+ assert (i > 0 && i < (ssize_t) sizeof (dirname) - 1);
+ Dwfl_Frame_State_Process *process;
+ process = __libdwfl_process_alloc (dwfl, dwfl_frame_state_pid_memory_read,
+ NULL);
+ if (process == NULL)
+ return NULL;
+ process->memory_read_user_data = process;
+ for (Dwfl_Module *mod = dwfl->modulelist; mod != NULL; mod = mod->next)
+ {
+ Dwfl_Error error = __libdwfl_module_getebl (mod);
+ if (error != DWFL_E_NOERROR)
+ continue;
+ process->ebl = mod->ebl;
+ }
+ if (process->ebl == NULL)
+ {
+ /* Not identified EBL from any of the modules. */
+ __libdwfl_process_free (process);
+ __libdwfl_seterrno (DWFL_E_PROCESS_NO_ARCH);
+ return NULL;
+ }
+ DIR *dir = opendir (dirname);
+ if (dir == NULL)
+ {
+ __libdwfl_process_free (process);
+ __libdwfl_seterrno (DWFL_E_PARSE_PROC);
+ return NULL;
+ }
+ for (;;)
+ {
+ errno = 0;
+ struct dirent *dirent = readdir (dir);
+ if (dirent == NULL)
+ {
+ if (errno == 0)
+ break;
+ __libdwfl_process_free (process);
+ __libdwfl_seterrno (DWFL_E_PARSE_PROC);
+ return NULL;
+ }
+ if (strcmp (dirent->d_name, ".") == 0
+ || strcmp (dirent->d_name, "..") == 0)
+ continue;
+ char *end;
+ errno = 0;
+ long tidl = strtol (dirent->d_name, &end, 10);
+ if (errno != 0)
+ {
+ __libdwfl_process_free (process);
+ __libdwfl_seterrno (DWFL_E_PARSE_PROC);
+ return NULL;
+ }
+ pid_t tid = tidl;
+ if (tidl <= 0 || (end && *end) || tid != tidl)
+ {
+ __libdwfl_process_free (process);
+ __libdwfl_seterrno (DWFL_E_PARSE_PROC);
+ return NULL;
+ }
+ Dwfl_Frame_State_Thread *thread = __libdwfl_thread_alloc (process, tid);
+ if (thread == NULL)
+ {
+ __libdwfl_process_free (process);
+ __libdwfl_seterrno (DWFL_E_NOMEM);
+ return NULL;
+ }
+ if (! tid_is_attached (dwfl, thread->tid))
+ {
+ if (! ptrace_attach (thread->tid))
+ {
+ __libdwfl_thread_free (thread);
+ continue;
+ }
+ thread->tid_attached = true;
+ }
+ Dwfl_Frame_State *state = thread->unwound;
+ if (! ebl_frame_state (state) || ! __libdwfl_state_fetch_pc (state))
+ {
+ __libdwfl_thread_free (thread);
+ continue;
+ }
+ }
+ if (closedir (dir) != 0)
+ {
+ __libdwfl_process_free (process);
+ __libdwfl_seterrno (DWFL_E_PARSE_PROC);
+ return NULL;
+ }
+ if (process->thread == NULL)
+ {
+ /* No valid threads recognized. */
+ __libdwfl_process_free (process);
+ __libdwfl_seterrno (DWFL_E_NO_THREAD);
+ return NULL;
+ }
+ return process->thread->unwound;
+}
+INTDEF (dwfl_frame_state_pid)
diff --git a/libdwfl/dwfl_frame_thread_next.c b/libdwfl/dwfl_frame_thread_next.c
new file mode 100644
index 0000000..df4aebb
--- /dev/null
+++ b/libdwfl/dwfl_frame_thread_next.c
@@ -0,0 +1,37 @@
+/* Get innermost frame of the next thread from state.
+ Copyright (C) 2012 Red Hat, Inc.
+ This file is part of elfutils.
+
+ This file is free software; you can redistribute it and/or modify
+ it under the terms of either
+
+ * the GNU Lesser General Public License as published by the Free
+ Software Foundation; either version 3 of the License, or (at
+ your option) any later version
+
+ or
+
+ * the GNU General Public License as published by the Free
+ Software Foundation; either version 2 of the License, or (at
+ your option) any later version
+
+ or both in parallel, as here.
+
+ elfutils is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received copies of the GNU General Public License and
+ the GNU Lesser General Public License along with this program. If
+ not, see <
http://www.gnu.org/licenses/>. */
+
+#include "libdwflP.h"
+
+Dwfl_Frame_State *
+dwfl_frame_thread_next (Dwfl_Frame_State *state)
+{
+ Dwfl_Frame_State_Thread *thread_next = state->thread->next;
+ return thread_next ? thread_next->unwound : NULL;
+}
+INTDEF (dwfl_frame_thread_next)
diff --git a/libdwfl/dwfl_frame_tid_get.c b/libdwfl/dwfl_frame_tid_get.c
new file mode 100644
index 0000000..7883b5e
--- /dev/null
+++ b/libdwfl/dwfl_frame_tid_get.c
@@ -0,0 +1,36 @@
+/* Get Task ID of the thread of state.
+ Copyright (C) 2012 Red Hat, Inc.
+ This file is part of elfutils.
+
+ This file is free software; you can redistribute it and/or modify
+ it under the terms of either
+
+ * the GNU Lesser General Public License as published by the Free
+ Software Foundation; either version 3 of the License, or (at
+ your option) any later version
+
+ or
+
+ * the GNU General Public License as published by the Free
+ Software Foundation; either version 2 of the License, or (at
+ your option) any later version
+
+ or both in parallel, as here.
+
+ elfutils is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received copies of the GNU General Public License and
+ the GNU Lesser General Public License along with this program. If
+ not, see <
http://www.gnu.org/licenses/>. */
+
+#include "libdwflP.h"
+
+pid_t
+dwfl_frame_tid_get (Dwfl_Frame_State *state)
+{
+ return state->thread->tid;
+}
+INTDEF (dwfl_frame_tid_get)
diff --git a/libdwfl/dwfl_frame_unwind.c b/libdwfl/dwfl_frame_unwind.c
new file mode 100644
index 0000000..d329b35
--- /dev/null
+++ b/libdwfl/dwfl_frame_unwind.c
@@ -0,0 +1,450 @@
+/* Get previous frame state for an existing frame state.
+ Copyright (C) 2012 Red Hat, Inc.
+ This file is part of elfutils.
+
+ This file is free software; you can redistribute it and/or modify
+ it under the terms of either
+
+ * the GNU Lesser General Public License as published by the Free
+ Software Foundation; either version 3 of the License, or (at
+ your option) any later version
+
+ or
+
+ * the GNU General Public License as published by the Free
+ Software Foundation; either version 2 of the License, or (at
+ your option) any later version
+
+ or both in parallel, as here.
+
+ elfutils is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received copies of the GNU General Public License and
+ the GNU Lesser General Public License along with this program. If
+ not, see <
http://www.gnu.org/licenses/>. */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include "cfi.h"
+#include <stdlib.h>
+#include "libdwflP.h"
+#include "../libdw/dwarf.h"
+#include <sys/ptrace.h>
+
+#ifndef MAX
+# define MAX(a, b) ((a) > (b) ? (a) : (b))
+#endif
+
+static bool
+state_get_reg (Dwfl_Frame_State *state, unsigned regno, Dwarf_Addr *val)
+{
+ if (! dwfl_frame_state_reg_get (state, regno, val))
+ {
+ __libdwfl_seterrno (DWFL_E_INVALID_REGISTER);
+ return false;
+ }
+ return true;
+}
+
+static int
+bra_compar (const void *key_voidp, const void *elem_voidp)
+{
+ Dwarf_Word offset = (uintptr_t) key_voidp;
+ const Dwarf_Op *op = elem_voidp;
+ return (offset > op->offset) - (offset < op->offset);
+}
+
+/* FIXME: Handle bytecode deadlocks and overflows. */
+
+static bool
+expr_eval (Dwfl_Frame_State *state, Dwarf_Frame *frame, const Dwarf_Op *ops,
+ size_t nops, Dwarf_Addr *result)
+{
+ Dwfl_Frame_State_Process *process = state->thread->process;
+ if (nops == 0)
+ {
+ __libdwfl_seterrno (DWFL_E_INVALID_DWARF);
+ return false;
+ }
+ Dwarf_Addr *stack = NULL;
+ size_t stack_used = 0, stack_allocated = 0;
+ bool
+ push (Dwarf_Addr val)
+ {
+ if (stack_used == stack_allocated)
+ {
+ stack_allocated = MAX (stack_allocated * 2, 32);
+ Dwarf_Addr *stack_new = realloc (stack, stack_allocated * sizeof (*stack));
+ if (stack_new == NULL)
+ {
+ __libdwfl_seterrno (DWFL_E_NOMEM);
+ return false;
+ }
+ stack = stack_new;
+ }
+ stack[stack_used++] = val;
+ return true;
+ }
+ bool
+ pop (Dwarf_Addr *val)
+ {
+ if (stack_used == 0)
+ {
+ __libdwfl_seterrno (DWFL_E_INVALID_DWARF);
+ return false;
+ }
+ *val = stack[--stack_used];
+ return true;
+ }
+ Dwarf_Addr val1, val2;
+ bool is_location = false;
+ for (const Dwarf_Op *op = ops; op < ops + nops; op++)
+ switch (op->atom)
+ {
+ case DW_OP_reg0 ... DW_OP_reg31:
+ if (! state_get_reg (state, op->atom - DW_OP_reg0, &val1)
+ || ! push (val1))
+ {
+ free (stack);
+ return false;
+ }
+ break;
+ case DW_OP_regx:
+ if (! state_get_reg (state, op->number, &val1) || ! push (val1))
+ {
+ free (stack);
+ return false;
+ }
+ break;
+ case DW_OP_breg0 ... DW_OP_breg31:
+ if (! state_get_reg (state, op->atom - DW_OP_breg0, &val1))
+ {
+ free (stack);
+ return false;
+ }
+ val1 += op->number;
+ if (! push (val1))
+ {
+ free (stack);
+ return false;
+ }
+ break;
+ case DW_OP_bregx:
+ if (! state_get_reg (state, op->number, &val1))
+ {
+ free (stack);
+ return false;
+ }
+ val1 += op->number2;
+ if (! push (val1))
+ {
+ free (stack);
+ return false;
+ }
+ break;
+ case DW_OP_lit0 ... DW_OP_lit31:
+ if (! push (op->atom - DW_OP_lit0))
+ {
+ free (stack);
+ return false;
+ }
+ break;
+ case DW_OP_plus_uconst:
+ if (! pop (&val1) || ! push (val1 + op->number))
+ {
+ free (stack);
+ return false;
+ }
+ break;
+ case DW_OP_call_frame_cfa:;
+ Dwarf_Op *cfa_ops;
+ size_t cfa_nops;
+ Dwarf_Addr cfa;
+ if (dwarf_frame_cfa (frame, &cfa_ops, &cfa_nops) != 0
+ || ! expr_eval (state, frame, cfa_ops, cfa_nops, &cfa)
+ || ! push (cfa))
+ {
+ __libdwfl_seterrno (DWFL_E_LIBDW);
+ free (stack);
+ return false;
+ }
+ is_location = true;
+ break;
+ case DW_OP_stack_value:
+ is_location = false;
+ break;
+ case DW_OP_deref:
+ if (! pop (&val1)
+ || ! process->memory_read (val1, &val1,
+ process->memory_read_user_data)
+ || ! push (val1))
+ {
+ free (stack);
+ return false;
+ }
+ break;
+ case DW_OP_nop:
+ break;
+ case DW_OP_dup:
+ if (! pop (&val1) || ! push (val1) || ! push (val1))
+ {
+ free (stack);
+ return false;
+ }
+ break;
+ case DW_OP_const1u:
+ case DW_OP_const1s:
+ case DW_OP_const2u:
+ case DW_OP_const2s:
+ case DW_OP_const4u:
+ case DW_OP_const4s:
+ case DW_OP_const8u:
+ case DW_OP_const8s:
+ case DW_OP_constu:
+ case DW_OP_consts:
+ if (! push (op->number))
+ {
+ free (stack);
+ return false;
+ }
+ break;
+ case DW_OP_bra:
+ if (! pop (&val1))
+ {
+ free (stack);
+ return false;
+ }
+ if (val1 == 0)
+ break;
+ /* FALLTHRU */
+ case DW_OP_skip:;
+ Dwarf_Word offset = op->offset + 1 + 2 + (int16_t) op->number;
+ const Dwarf_Op *found = bsearch ((void *) (uintptr_t) offset, ops, nops,
+ sizeof (*ops), bra_compar);
+ if (found == NULL)
+ {
+ free (stack);
+ /* PPC32 vDSO has such invalid operations. */
+ __libdwfl_seterrno (DWFL_E_INVALID_DWARF);
+ return false;
+ }
+ /* Undo the 'for' statement increment. */
+ op = found - 1;
+ break;
+ case DW_OP_drop:
+ if (! pop (&val1))
+ {
+ free (stack);
+ return false;
+ }
+ break;
+#define BINOP(atom, op) \
+ case atom: \
+ if (! pop (&val2) || ! pop (&val1) || ! push (val1 op val2)) \
+ { \
+ free (stack); \
+ return false; \
+ } \
+ break;
+ BINOP (DW_OP_and, &)
+ BINOP (DW_OP_shl, <<)
+ BINOP (DW_OP_plus, +)
+ BINOP (DW_OP_mul, *)
+#undef BINOP
+#define BINOP_SIGNED(atom, op) \
+ case atom: \
+ if (! pop (&val2) || ! pop (&val1) \
+ || ! push ((int64_t) val1 op (int64_t) val2)) \
+ { \
+ free (stack); \
+ return false; \
+ } \
+ break;
+ BINOP_SIGNED (DW_OP_le, <=)
+ BINOP_SIGNED (DW_OP_ge, >=)
+ BINOP_SIGNED (DW_OP_eq, ==)
+ BINOP_SIGNED (DW_OP_lt, <)
+ BINOP_SIGNED (DW_OP_gt, >)
+ BINOP_SIGNED (DW_OP_ne, !=)
+#undef BINOP_SIGNED
+ default:
+ __libdwfl_seterrno (DWFL_E_UNSUPPORTED_DWARF);
+ return false;
+ }
+ if (! pop (result))
+ {
+ free (stack);
+ return false;
+ }
+ free (stack);
+ if (is_location && ! process->memory_read (*result, result,
+ process->memory_read_user_data))
+ return false;
+ return true;
+}
+
+/* Return TRUE and update *STATEP for the unwound frame for successful unwind.
+ Return TRUE and set *STATEP to NULL for the outermost frame. Return FALSE
+ (and call __libdwfl_seterrno) otherwise. */
+
+static bool
+have_unwound (Dwfl_Frame_State **statep)
+{
+ Dwfl_Frame_State *state = *statep, *unwound = state->unwound;
+ switch (unwound->pc_state)
+ {
+ case DWFL_FRAME_STATE_ERROR:
+ __libdwfl_seterrno (DWFL_E_INVALID_DWARF);
+ *statep = NULL;
+ return false;
+ case DWFL_FRAME_STATE_PC_SET:
+ *statep = unwound;
+ return true;
+ case DWFL_FRAME_STATE_PC_UNDEFINED:
+ *statep = NULL;
+ return true;
+ }
+ abort ();
+}
+
+/* The logic is to call __libdwfl_seterrno for any CFI bytecode interpretation
+ error so one can easily catch the problem with a debugger. Still there are
+ archs with invalid CFI for some registers where the registers are never used
+ later. Therefore we continue unwinding leaving the registers undefined.
+
+ The only exception is PC itself, when there is an error unwinding PC we
+ return false. Otherwise we would return successful end of backtrace seeing
+ an undefined PC register (due to an error unwinding it). */
+
+static bool
+handle_cfi (Dwfl_Frame_State **statep, Dwarf_Addr pc, Dwarf_CFI *cfi)
+{
+ Dwfl_Frame_State *state = *statep;
+ Dwarf_Frame *frame;
+ if (INTUSE(dwarf_cfi_addrframe) (cfi, pc, &frame) != 0)
+ {
+ __libdwfl_seterrno (DWFL_E_LIBDW);
+ return false;
+ }
+ Dwfl_Frame_State_Thread *thread = state->thread;
+ Dwfl_Frame_State_Process *process = thread->process;
+ Ebl *ebl = process->ebl;
+ size_t nregs = ebl_frame_state_nregs (ebl);
+ Dwfl_Frame_State *unwound;
+ unwound = malloc (sizeof (*unwound) + sizeof (*unwound->regs) * nregs);
+ state->unwound = unwound;
+ unwound->thread = thread;
+ unwound->unwound = NULL;
+ unwound->signal_frame = frame->fde->cie->signal_frame;
+ unwound->pc_state = DWFL_FRAME_STATE_ERROR;
+ memset (unwound->regs_set, 0, sizeof (unwound->regs_set));
+ for (unsigned regno = 0; regno < nregs; regno++)
+ {
+ Dwarf_Op reg_ops_mem[3], *reg_ops;
+ size_t reg_nops;
+ if (dwarf_frame_register (frame, regno, reg_ops_mem, ®_ops,
+ ®_nops) != 0)
+ {
+ __libdwfl_seterrno (DWFL_E_LIBDW);
+ continue;
+ }
+ Dwarf_Addr regval;
+ if (reg_nops == 0)
+ {
+ if (reg_ops == reg_ops_mem)
+ {
+ /* REGNO is undefined. */
+ unsigned ra = frame->fde->cie->return_address_register;
+ if (regno == ra)
+ unwound->pc_state = DWFL_FRAME_STATE_PC_UNDEFINED;
+ continue;
+ }
+ else if (reg_ops == NULL)
+ {
+ /* REGNO is same-value. */
+ if (! state_get_reg (state, regno, ®val))
+ continue;
+ }
+ else
+ {
+ __libdwfl_seterrno (DWFL_E_INVALID_DWARF);
+ continue;
+ }
+ }
+ else if (! expr_eval (state, frame, reg_ops, reg_nops, ®val))
+ {
+ /* PPC32 vDSO has various invalid operations, ignore them. The
+ register will look as unset causing an error later, if used.
+ But PPC32 does not use such registers. */
+ continue;
+ }
+ if (! dwfl_frame_state_reg_set (unwound, regno, regval))
+ {
+ __libdwfl_seterrno (DWFL_E_INVALID_REGISTER);
+ continue;
+ }
+ }
+ if (unwound->pc_state == DWFL_FRAME_STATE_ERROR
+ && dwfl_frame_state_reg_get (unwound,
+ frame->fde->cie->return_address_register,
+ &unwound->pc))
+ {
+ /* PPC32 __libc_start_main properly CFI-unwinds PC as zero. Currently
+ none of the archs supported for unwinding have zero as a valid PC. */
+ if (unwound->pc == 0)
+ unwound->pc_state = DWFL_FRAME_STATE_PC_UNDEFINED;
+ else
+ unwound->pc_state = DWFL_FRAME_STATE_PC_SET;
+ }
+ return have_unwound (statep);
+}
+
+bool
+dwfl_frame_unwind (Dwfl_Frame_State **statep)
+{
+ Dwfl_Frame_State *state = *statep;
+ if (state->unwound)
+ return have_unwound (statep);
+ Dwarf_Addr pc;
+ bool ok = INTUSE(dwfl_frame_state_pc) (state, &pc, NULL);
+ assert (ok);
+ /* Do not ask for MINUSONE dwfl_frame_state_pc, it would try to unwind STATE
+ which would deadlock us. */
+ if (state != state->thread->unwound && ! state->signal_frame)
+ pc--;
+ Dwfl_Module *mod = INTUSE(dwfl_addrmodule) (state->thread->process->dwfl,
pc);
+ if (mod != NULL)
+ {
+ Dwarf_Addr bias;
+ Dwarf_CFI *cfi_eh = INTUSE(dwfl_module_eh_cfi) (mod, &bias);
+ if (cfi_eh)
+ {
+ if (handle_cfi (statep, pc - bias, cfi_eh))
+ return true;
+ if (state->unwound)
+ {
+ assert (state->unwound->pc_state == DWFL_FRAME_STATE_ERROR);
+ return false;
+ }
+ }
+ Dwarf_CFI *cfi_dwarf = INTUSE(dwfl_module_dwarf_cfi) (mod, &bias);
+ if (cfi_dwarf)
+ {
+ if (handle_cfi (statep, pc - bias, cfi_dwarf) && state->unwound)
+ return true;
+ if (state->unwound)
+ {
+ assert (state->unwound->pc_state == DWFL_FRAME_STATE_ERROR);
+ return false;
+ }
+ }
+ }
+ __libdwfl_seterrno (DWFL_E_NO_DWARF);
+ return false;
+}
+INTDEF(dwfl_frame_unwind)
diff --git a/libdwfl/libdwfl.h b/libdwfl/libdwfl.h
index 2b70e28..8998886 100644
--- a/libdwfl/libdwfl.h
+++ b/libdwfl/libdwfl.h
@@ -41,6 +41,10 @@ typedef struct Dwfl_Module Dwfl_Module;
/* Handle describing a line record. */
typedef struct Dwfl_Line Dwfl_Line;
+/* This holds everything we know about the state of the frame at a particular
+ PC location described by an FDE. */
+typedef struct Dwfl_Frame_State Dwfl_Frame_State;
+
/* Callbacks. */
typedef struct
{
@@ -564,6 +568,46 @@ extern int dwfl_module_register_names (Dwfl_Module *mod,
extern Dwarf_CFI *dwfl_module_dwarf_cfi (Dwfl_Module *mod, Dwarf_Addr *bias);
extern Dwarf_CFI *dwfl_module_eh_cfi (Dwfl_Module *mod, Dwarf_Addr *bias);
+/* Get innermost frame of first thread of live process PID. Returns NULL on
+ failure. */
+extern Dwfl_Frame_State *dwfl_frame_state_pid (Dwfl *dwfl, pid_t pid);
+
+/* Get innermost frame of first thread of core file COREFILE. Returns NULL on
+ failure. */
+extern Dwfl_Frame_State *dwfl_frame_state_core (Dwfl *dwfl,
+ const char *corefile);
+
+/* Fetch inferior registers from a caller supplied storage. */
+typedef bool dwfl_frame_memory_read_t (Dwarf_Addr addr, Dwarf_Addr *result,
+ void *user_data);
+extern Dwfl_Frame_State *dwfl_frame_state_data (Dwfl *dwfl, bool pc_set,
+ Dwarf_Addr pc, unsigned nregs,
+ const uint64_t *regs_set,
+ const Dwarf_Addr *regs,
+ dwfl_frame_memory_read_t
+ *memory_read,
+ void *memory_read_user_data);
+
+/* Return TRUE and update *STATEP for the unwound frame for successful unwind.
+ Return TRUE and set *STATEP to NULL for the outermost frame. Return FALSE
+ (and call __libdwfl_seterrno) otherwise. */
+extern bool dwfl_frame_unwind (Dwfl_Frame_State **statep);
+
+/* Get return address register value for frame. Return TRUE if *PC set and
+ optionally *MINUSONE is also set, if MINUSONE is not NULL. Return FALSE
+ (and call __libdw_seterrno) otherwise. *MINUSONE is TRUE for normal calls
+ where *PC should be decremented by one to get the call instruction, it is
+ FALSE if this frame was interrupted by a signal handler. */
+extern bool dwfl_frame_state_pc (Dwfl_Frame_State *state, Dwarf_Addr *pc,
+ bool *minusone);
+
+/* Get innermost frame of the next thread from STATE. STATE can be any frame
+ of (the previous) thread. */
+extern Dwfl_Frame_State *dwfl_frame_thread_next (Dwfl_Frame_State *state);
+
+/* Get Task ID of the thread of STATE. This is PID for the thread started by
+ function main and gettid () for the other threads. */
+extern pid_t dwfl_frame_tid_get (Dwfl_Frame_State *state);
#ifdef __cplusplus
}
diff --git a/libdwfl/libdwflP.h b/libdwfl/libdwflP.h
index 69e6e12..fff621d 100644
--- a/libdwfl/libdwflP.h
+++ b/libdwfl/libdwflP.h
@@ -41,6 +41,10 @@
#include <string.h>
#include "../libdw/libdwP.h" /* We need its INTDECLs. */
+#include "libeblP.h"
+
+typedef struct Dwfl_Frame_State_Process Dwfl_Frame_State_Process;
+typedef struct Dwfl_Frame_State_Thread Dwfl_Frame_State_Thread;
/* gettext helper macros. */
#define _(Str) dgettext ("elfutils", Str)
@@ -74,7 +78,16 @@
DWFL_ERROR (BADELF, N_("not a valid ELF file")) \
DWFL_ERROR (WEIRD_TYPE, N_("cannot handle DWARF type description")) \
DWFL_ERROR (WRONG_ID_ELF, N_("ELF file does not match build ID")) \
- DWFL_ERROR (BAD_PRELINK, N_("corrupt .gnu.prelink_undo section data"))
+ DWFL_ERROR (BAD_PRELINK, N_("corrupt .gnu.prelink_undo section data"))
\
+ DWFL_ERROR (LIBEBL_BAD, N_("Internal error due to ebl")) \
+ DWFL_ERROR (CORE_MISSING, N_("Missing data in core file")) \
+ DWFL_ERROR (INVALID_REGISTER, N_("Invalid register")) \
+ DWFL_ERROR (PROCESS_MEMORY_READ, N_("Error reading process memory")) \
+ DWFL_ERROR (PROCESS_NO_ARCH, N_("Have not found ELF module in a process"))
\
+ DWFL_ERROR (PARSE_PROC, N_("Error parsing /proc filesystem")) \
+ DWFL_ERROR (NO_THREAD, N_("No thread found")) \
+ DWFL_ERROR (INVALID_DWARF, N_("Invalid DWARF")) \
+ DWFL_ERROR (UNSUPPORTED_DWARF, N_("Unsupported DWARF"))
#define DWFL_ERROR(name, text) DWFL_E_##name,
typedef enum { DWFL_ERRORS DWFL_E_NUM } Dwfl_Error;
@@ -92,6 +105,8 @@ struct Dwfl
Dwfl_Module *modulelist; /* List in order used by full traversals. */
+ Dwfl_Frame_State_Process *framestatelist;
+
GElf_Addr offline_next_address;
GElf_Addr segment_align; /* Smallest granularity of segments. */
@@ -189,7 +204,102 @@ struct Dwfl_Module
bool gc; /* Mark/sweep flag. */
};
+/* This holds information common for all the threads/tasks/TIDs of one process
+ for backtraces. */
+
+struct Dwfl_Frame_State_Process
+{
+ Dwfl_Frame_State_Process *next;
+ struct Dwfl *dwfl;
+ struct ebl *ebl;
+ /* If it is false we share EBL with one of DWFL's Dwfl_Module->ebl. */
+ bool ebl_close : 1;
+ dwfl_frame_memory_read_t *memory_read;
+ void *memory_read_user_data;
+ /* If there is no core file both CORE is NULL and CORE_FD is -1. */
+ Elf *core;
+ int core_fd;
+ Dwfl_Frame_State_Thread *thread;
+};
+
+/* This holds information common for all the frames of one backtrace for
+ a partical thread/task/TID. */
+
+struct Dwfl_Frame_State_Thread
+{
+ Dwfl_Frame_State_Process *process;
+ Dwfl_Frame_State_Thread *next;
+ /* If there is no TID it is 0. */
+ pid_t tid;
+ bool tid_attached : 1;
+ /* Bottom frame. */
+ Dwfl_Frame_State *unwound;
+};
+
+/* See its typedef in libdwfl.h. */
+
+struct Dwfl_Frame_State
+{
+ Dwfl_Frame_State_Thread *thread;
+ /* Previous (outer) frame. */
+ Dwfl_Frame_State *unwound;
+ bool signal_frame : 1;
+ enum
+ {
+ /* This structure is still being initialized or there was an error
+ initializing it. */
+ DWFL_FRAME_STATE_ERROR,
+ /* PC field is valid. */
+ DWFL_FRAME_STATE_PC_SET,
+ /* PC field is undefined, this means the next (inner) frame was the
+ outermost frame. */
+ DWFL_FRAME_STATE_PC_UNDEFINED
+ } pc_state;
+ /* Either initialized from appropriate REGS element or on some archs
+ initialized separately as the return address has no DWARF register. */
+ Dwarf_Addr pc;
+ /* (1 << X) bitmask where 0 <= X < ebl_frame_state_nregs. */
+ uint64_t regs_set[3];
+ /* REGS array size is ebl_frame_state_nregs. */
+ Dwarf_Addr regs[];
+};
+
+/* Fetch value from Dwfl_Frame_State->regs indexed by DWARF REGNO.
+ No error code is set if the function returns FALSE. */
+
+static inline bool
+dwfl_frame_state_reg_get (Dwfl_Frame_State *state, unsigned regno,
+ Dwarf_Addr *val)
+{
+ Ebl *ebl = state->thread->process->ebl;
+ if (regno >= ebl->frame_state_nregs)
+ return false;
+ if ((state->regs_set[regno / sizeof (*state->regs_set) / 8]
+ & (1U << (regno % (sizeof (*state->regs_set) * 8)))) == 0)
+ return false;
+ if (val)
+ *val = state->regs[regno];
+ return true;
+}
+
+/* Store value to Dwfl_Frame_State->regs indexed by DWARF REGNO.
+ No error code is set if the function returns FALSE. */
+static inline bool
+dwfl_frame_state_reg_set (Dwfl_Frame_State *state, unsigned regno,
+ Dwarf_Addr val)
+{
+ Ebl *ebl = state->thread->process->ebl;
+ if (regno >= ebl->frame_state_nregs)
+ return false;
+ /* For example i386 user_regs_struct has signed fields. */
+ if (ebl->class == ELFCLASS32)
+ val &= 0xffffffff;
+ state->regs_set[regno / sizeof (*state->regs_set) / 8] |=
+ (1U << (regno % (sizeof (*state->regs_set) * 8)));
+ state->regs[regno] = val;
+ return true;
+}
/* Information cached about each CU in Dwfl_Module.dw. */
struct dwfl_cu
@@ -404,6 +514,38 @@ extern Dwfl_Module *__libdwfl_report_offline (Dwfl *dwfl, const char
*name,
const char *))
internal_function;
+/* Set STATE->pc_set from STATE->regs according to the backend. Return true on
+ success, false on error. */
+extern bool __libdwfl_state_fetch_pc (Dwfl_Frame_State *state)
+ internal_function;
+
+/* Free and unlink THREAD from the internal lists. */
+extern void __libdwfl_thread_free (Dwfl_Frame_State_Thread *thread)
+ internal_function;
+
+/* Allocate new Dwfl_Frame_State_Thread for PID and link it to PROCESS.
+ Automatically create and link in also the first Dwfl_Frame_State. */
+extern Dwfl_Frame_State_Thread *
+ __libdwfl_thread_alloc (Dwfl_Frame_State_Process *process, pid_t tid)
+ internal_function;
+
+/* Free PROCESS. Unlink and free also any structures it references. */
+extern void __libdwfl_process_free (Dwfl_Frame_State_Process *process)
+ internal_function;
+
+/* Allocate new Dwfl_Frame_State_Process for DWFL with callback MEMORY_READ
+ (which is passed MEMORY_READ_USER_DATA). */
+extern Dwfl_Frame_State_Process *
+ __libdwfl_process_alloc (Dwfl *dwfl, dwfl_frame_memory_read_t *memory_read,
+ void *memory_read_user_data)
+ internal_function;
+
+/* Align segment START downwards or END upwards addresses according to DWFL. */
+extern GElf_Addr __libdwfl_segment_start (Dwfl *dwfl, GElf_Addr start)
+ internal_function;
+extern GElf_Addr __libdwfl_segment_end (Dwfl *dwfl, GElf_Addr end)
+ internal_function;
+
/* Decompression wrappers: decompress whole file into memory. */
extern Dwfl_Error __libdw_gunzip (int fd, off64_t start_offset,
void *mapped, size_t mapped_size,
@@ -538,6 +680,10 @@ INTDECL (dwfl_offline_section_address)
INTDECL (dwfl_module_relocate_address)
INTDECL (dwfl_module_dwarf_cfi)
INTDECL (dwfl_module_eh_cfi)
+INTDECL (dwfl_frame_state_pid)
+INTDECL (dwfl_frame_state_core)
+INTDECL (dwfl_frame_unwind)
+INTDECL (dwfl_frame_state_pc)
/* Leading arguments standard to callbacks passed a Dwfl_Module. */
#define MODCB_ARGS(mod) (mod), &(mod)->userdata, (mod)->name,
(mod)->low_addr
diff --git a/libdwfl/segment.c b/libdwfl/segment.c
index 496b4fd..3a2f349 100644
--- a/libdwfl/segment.c
+++ b/libdwfl/segment.c
@@ -28,16 +28,18 @@
#include "libdwflP.h"
-static GElf_Addr
-segment_start (Dwfl *dwfl, GElf_Addr start)
+GElf_Addr
+internal_function
+__libdwfl_segment_start (Dwfl *dwfl, GElf_Addr start)
{
if (dwfl->segment_align > 1)
start &= -dwfl->segment_align;
return start;
}
-static GElf_Addr
-segment_end (Dwfl *dwfl, GElf_Addr end)
+GElf_Addr
+internal_function
+__libdwfl_segment_end (Dwfl *dwfl, GElf_Addr end)
{
if (dwfl->segment_align > 1)
end = (end + dwfl->segment_align - 1) & -dwfl->segment_align;
@@ -156,8 +158,8 @@ reify_segments (Dwfl *dwfl)
for (Dwfl_Module *mod = dwfl->modulelist; mod != NULL; mod = mod->next)
if (! mod->gc)
{
- const GElf_Addr start = segment_start (dwfl, mod->low_addr);
- const GElf_Addr end = segment_end (dwfl, mod->high_addr);
+ const GElf_Addr start = __libdwfl_segment_start (dwfl, mod->low_addr);
+ const GElf_Addr end = __libdwfl_segment_end (dwfl, mod->high_addr);
bool resized = false;
int idx = lookup (dwfl, start, hint);
@@ -296,8 +298,9 @@ dwfl_report_segment (Dwfl *dwfl, int ndx, const GElf_Phdr *phdr,
GElf_Addr bias,
dwfl->lookup_module = NULL;
}
- GElf_Addr start = segment_start (dwfl, bias + phdr->p_vaddr);
- GElf_Addr end = segment_end (dwfl, bias + phdr->p_vaddr + phdr->p_memsz);
+ GElf_Addr start = __libdwfl_segment_start (dwfl, bias + phdr->p_vaddr);
+ GElf_Addr end = __libdwfl_segment_end (dwfl,
+ bias + phdr->p_vaddr + phdr->p_memsz);
/* Coalesce into the last one if contiguous and matching. */
if (ndx != dwfl->lookup_tail_ndx
diff --git a/libebl/Makefile.am b/libebl/Makefile.am
index 4d62fad..a3186bb 100644
--- a/libebl/Makefile.am
+++ b/libebl/Makefile.am
@@ -29,7 +29,8 @@
##
include $(top_srcdir)/config/eu.am
AM_CFLAGS += -fpic
-AM_CPPFLAGS += -I$(srcdir)/../libelf -I$(srcdir)/../libdw -I$(srcdir)/../libasm
+AM_CPPFLAGS += -I$(srcdir)/../libelf -I$(srcdir)/../libdw -I$(srcdir)/../libasm \
+ -I$(srcdir)/../libdwfl
VERSION = 1
LIBEBL_SUBDIR = @LIBEBL_SUBDIR@
@@ -54,7 +55,7 @@ gen_SOURCES = eblopenbackend.c eblclosebackend.c eblstrtab.c \
eblreginfo.c eblnonerelocp.c eblrelativerelocp.c \
eblsysvhashentrysize.c eblauxvinfo.c eblcheckobjattr.c \
ebl_check_special_section.c ebl_syscall_abi.c eblabicfi.c \
- eblstother.c
+ eblstother.c eblframestate.c
libebl_a_SOURCES = $(gen_SOURCES)
diff --git a/libebl/ebl-hooks.h b/libebl/ebl-hooks.h
index d3cf3e6..b7e1499 100644
--- a/libebl/ebl-hooks.h
+++ b/libebl/ebl-hooks.h
@@ -155,5 +155,14 @@ int EBLHOOK(disasm) (const uint8_t **startp, const uint8_t *end,
Function returns 0 on success and -1 on error. */
int EBLHOOK(abi_cfi) (Ebl *ebl, Dwarf_CIE *abi_info);
+/* *SYM must be STT_FUNC. Then if it describes a function descriptor (PPC64)
+ convert in-place its data and return a possibly different new name for it.
+ The name is valid as long as EBL is valid. */
+const char *EBLHOOK(get_func_pc) (Ebl *ebl, struct Dwfl_Module *mod,
+ GElf_Sym *sym);
+
+/* Fetch process data from STATE->base->pid or STATE->base->core. */
+bool EBLHOOK(frame_state) (struct Dwfl_Frame_State *state);
+
/* Destructor for ELF backend handle. */
void EBLHOOK(destr) (struct ebl *);
diff --git a/libebl/eblframestate.c b/libebl/eblframestate.c
new file mode 100644
index 0000000..a7cc13c
--- /dev/null
+++ b/libebl/eblframestate.c
@@ -0,0 +1,52 @@
+/* Fetch live process Dwfl_Frame_State from PID.
+ Copyright (C) 2012 Red Hat, Inc.
+ This file is part of elfutils.
+
+ This file is free software; you can redistribute it and/or modify
+ it under the terms of either
+
+ * the GNU Lesser General Public License as published by the Free
+ Software Foundation; either version 3 of the License, or (at
+ your option) any later version
+
+ or
+
+ * the GNU General Public License as published by the Free
+ Software Foundation; either version 2 of the License, or (at
+ your option) any later version
+
+ or both in parallel, as here.
+
+ elfutils is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received copies of the GNU General Public License and
+ the GNU Lesser General Public License along with this program. If
+ not, see <
http://www.gnu.org/licenses/>. */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <libeblP.h>
+#include <assert.h>
+#include "libdwflP.h"
+
+bool
+ebl_frame_state (Dwfl_Frame_State *state)
+{
+ Dwfl_Frame_State_Process *process = state->thread->process;
+ Ebl *ebl = process->ebl;
+ /* Otherwise caller could not allocate STATE of proper size. If FRAME_STATE
+ is unsupported then FRAME_STATE_NREGS is zero. */
+ assert (ebl->frame_state != NULL);
+ return ebl->frame_state (state);
+}
+
+size_t
+ebl_frame_state_nregs (Ebl *ebl)
+{
+ return ebl == NULL ? 0 : ebl->frame_state_nregs;
+}
diff --git a/libebl/libebl.h b/libebl/libebl.h
index cae31c9..09b7bfb 100644
--- a/libebl/libebl.h
+++ b/libebl/libebl.h
@@ -378,6 +378,20 @@ extern int ebl_auxv_info (Ebl *ebl, GElf_Xword a_type,
const char **name, const char **format)
__nonnull_attribute__ (1, 3, 4);
+/* Convert function descriptor SYM to the function PC value in-place. */
+struct Dwfl_Module;
+extern const char *ebl_get_func_pc (Ebl *ebl, struct Dwfl_Module *mod,
+ GElf_Sym *sym)
+ __nonnull_attribute__ (1, 2, 3);
+
+/* Fetch process data from STATE->base->pid or STATE->base->core. */
+struct Dwfl_Frame_State;
+extern bool ebl_frame_state (struct Dwfl_Frame_State *state)
+ __nonnull_attribute__ (1);
+
+/* Number of registers to allocate for STATE of ebl_frame_state. */
+extern size_t ebl_frame_state_nregs (Ebl *ebl)
+ __nonnull_attribute__ (1);
#ifdef __cplusplus
}
diff --git a/libebl/libeblP.h b/libebl/libeblP.h
index 5ec26a4..d553c8c 100644
--- a/libebl/libeblP.h
+++ b/libebl/libeblP.h
@@ -60,6 +60,10 @@ struct ebl
/* Size of entry in Sysv-style hash table. */
int sysvhash_entrysize;
+ /* Number of Dwarf_Frame_State->regs entries to allocate for frame_state
+ above. */
+ size_t frame_state_nregs;
+
/* Internal data. */
void *dlhandle;
};
diff --git a/m4/biarch.m4 b/m4/biarch.m4
new file mode 100644
index 0000000..a15323e
--- /dev/null
+++ b/m4/biarch.m4
@@ -0,0 +1,45 @@
+AC_DEFUN([utrace_CC_m32], [dnl
+AC_CACHE_CHECK([$CC option for 32-bit word size], utrace_cv_CC_m32, [dnl
+save_CC="$CC"
+utrace_cv_CC_m32=none
+for ut_try in -m32 -m31; do
+ [CC=`echo "$save_CC" | sed 's/ -m[36][241]//'`" $ut_try"]
+ AC_COMPILE_IFELSE([AC_LANG_SOURCE([[int foo (void) { return 1; }]])],
+ [utrace_cv_CC_m32=$ut_try])
+ test x$utrace_cv_CC_m32 = xnone || break
+done
+CC="$save_CC"])])
+
+AC_DEFUN([utrace_HOST64], [AC_REQUIRE([utrace_CC_m32])
+AS_IF([test x$utrace_cv_CC_m32 != xnone], [dnl
+AC_CACHE_CHECK([for 64-bit host], utrace_cv_host64, [dnl
+AC_EGREP_CPP([@utrace_host64@], [#include <stdint.h>
+#if (UINTPTR_MAX > 0xffffffffUL)
+@utrace_host64@
+#endif],
+ utrace_cv_host64=yes, utrace_cv_host64=no)])
+AS_IF([test $utrace_cv_host64 = no],
+ [utrace_biarch=-m64 utrace_thisarch=$utrace_cv_CC_m32],
+ [utrace_biarch=$utrace_cv_CC_m32 utrace_thisarch=-m64])
+
+biarch_CC=`echo "$CC" | sed "s/ *${utrace_thisarch}//"`
+biarch_CC="$biarch_CC $utrace_biarch"])])
+
+AC_DEFUN([utrace_BIARCH], [AC_REQUIRE([utrace_HOST64])
+utrace_biarch_forced=no
+AC_ARG_WITH([biarch],
+ AC_HELP_STRING([--with-biarch],
+ [enable biarch tests despite build problems]),
+ [AS_IF([test "x$with_biarch" != xno], [utrace_biarch_forced=yes])])
+AS_IF([test $utrace_biarch_forced = yes], [dnl
+utrace_cv_cc_biarch=yes
+AC_MSG_NOTICE([enabling biarch tests regardless using $biarch_CC])], [dnl
+AS_IF([test x$utrace_cv_CC_m32 != xnone], [dnl
+AC_CACHE_CHECK([whether $biarch_CC makes executables we can run],
+ utrace_cv_cc_biarch, [dnl
+save_CC="$CC"
+CC="$biarch_CC"
+AC_RUN_IFELSE([AC_LANG_PROGRAM([], [])],
+ utrace_cv_cc_biarch=yes, utrace_cv_cc_biarch=no)
+CC="$save_CC"])], [utrace_cv_cc_biarch=no])])
+AM_CONDITIONAL(BIARCH, [test $utrace_cv_cc_biarch = yes])])
diff --git a/src/Makefile.am b/src/Makefile.am
index 674846d..1f2041f 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -37,7 +37,7 @@ native_ld = @native_ld@
base_cpu = @base_cpu@
bin_PROGRAMS = readelf nm size strip ld elflint findtextrel addr2line \
- elfcmp objdump ranlib strings ar unstrip
+ elfcmp objdump ranlib strings ar unstrip stack
ld_dsos = libld_elf_i386_pic.a
@@ -115,6 +115,7 @@ ranlib_LDADD = libar.a $(libelf) $(libeu) $(libmudflap)
strings_LDADD = $(libelf) $(libeu) $(libmudflap)
ar_LDADD = libar.a $(libelf) $(libeu) $(libmudflap)
unstrip_LDADD = $(libebl) $(libelf) $(libdw) $(libeu) $(libmudflap) -ldl
+stack_LDADD = $(libebl) $(libelf) $(libdw) $(libeu) $(libmudflap) -ldl
ldlex.o: ldscript.c
ldlex_no_Werror = yes
diff --git a/src/stack.c b/src/stack.c
new file mode 100644
index 0000000..dd7be04
--- /dev/null
+++ b/src/stack.c
@@ -0,0 +1,145 @@
+/* Unwinding of frames like gstack/pstack.
+ Copyright (C) 2012 Red Hat, Inc.
+ This file is part of elfutils.
+
+ This file is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ elfutils is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <
http://www.gnu.org/licenses/>. */
+
+#include <config.h>
+#include <assert.h>
+#include <argp.h>
+#include <error.h>
+#include <stdlib.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdio_ext.h>
+#include <locale.h>
+#include ELFUTILS_HEADER(dwfl)
+
+/* libdwfl/argp-std.c */
+#define OPT_COREFILE 0x101
+
+static void
+report_pid (Dwfl *dwfl, pid_t pid)
+{
+ int result = dwfl_linux_proc_report (dwfl, pid);
+ if (result < 0)
+ error (2, 0, "dwfl_linux_proc_report: %s", dwfl_errmsg (-1));
+ else if (result > 0)
+ error (2, result, "dwfl_linux_proc_report");
+
+ if (dwfl_report_end (dwfl, NULL, NULL) != 0)
+ error (2, 0, "dwfl_report_end: %s", dwfl_errmsg (-1));
+}
+
+static void
+dump (Dwfl *dwfl, pid_t pid, const char *corefile)
+{
+ if (pid)
+ report_pid (dwfl, pid);
+ Dwfl_Frame_State *state;
+ if (pid)
+ state = dwfl_frame_state_pid (dwfl, pid);
+ else if (corefile)
+ state = dwfl_frame_state_core (dwfl, corefile);
+ else
+ abort ();
+ if (state == NULL)
+ error (2, 0, "dwfl_frame_state: %s", dwfl_errmsg (-1));
+ do
+ {
+ Dwfl_Frame_State *thread = state;
+ pid_t tid = dwfl_frame_tid_get (thread);
+ printf ("TID %ld:\n", (long) tid);
+ unsigned frameno;
+ for (frameno = 0; state; frameno++)
+ {
+ Dwarf_Addr pc;
+ bool minusone;
+ if (! dwfl_frame_state_pc (state, &pc, &minusone))
+ {
+ fprintf (stderr, "%s\n", dwfl_errmsg (-1));
+ break;
+ }
+ Dwarf_Addr pc_adjusted = pc - (minusone ? 1 : 0);
+
+ /* Get PC->SYMNAME. */
+ Dwfl_Module *mod = dwfl_addrmodule (dwfl, pc_adjusted);
+ const char *symname = NULL;
+ if (mod)
+ symname = dwfl_module_addrname (mod, pc_adjusted);
+
+ printf ("#%2u %#" PRIx64 "%4s\t%s\n", frameno, (uint64_t) pc,
+ minusone ? "- 1" : "", symname);
+ if (! dwfl_frame_unwind (&state))
+ {
+ fprintf (stderr, "%s\n", dwfl_errmsg (-1));
+ break;
+ }
+ }
+ state = dwfl_frame_thread_next (thread);
+ }
+ while (state);
+
+ dwfl_end (dwfl);
+}
+
+static argp_parser_t parse_opt_orig;
+static pid_t pid;
+static const char *corefile;
+
+static error_t
+parse_opt (int key, char *arg, struct argp_state *state)
+{
+ switch (key)
+ {
+ case 'p':
+ pid = atoi (arg);
+ break;
+ case OPT_COREFILE:
+ corefile = arg;
+ break;
+ }
+ return parse_opt_orig (key, arg, state);
+}
+
+int
+main (int argc, char **argv)
+{
+ /* We use no threads here which can interfere with handling a stream. */
+ __fsetlocking (stdin, FSETLOCKING_BYCALLER);
+ __fsetlocking (stdout, FSETLOCKING_BYCALLER);
+ __fsetlocking (stderr, FSETLOCKING_BYCALLER);
+
+ /* Set locale. */
+ (void) setlocale (LC_ALL, "");
+
+ struct argp argp = *dwfl_standard_argp ();
+ parse_opt_orig = argp.parser;
+ argp.parser = parse_opt;
+ int remaining;
+ Dwfl *dwfl = NULL;
+ argp_parse (&argp, argc, argv, 0, &remaining, &dwfl);
+ assert (dwfl != NULL);
+ assert (remaining == argc);
+
+ if (pid && !corefile)
+ dump (dwfl, pid, NULL);
+ else if (corefile && !pid)
+ dump (dwfl, 0, corefile);
+ else
+ error (2, 0, "eu-stack [--debuginfo-path=<path>] {-p <process
id>|"
+ "--core=<file> [--executable=<file>]|--help}");
+
+ return 0;
+}
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 2d819c5..647b002 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -52,10 +52,24 @@ check_PROGRAMS = arextract arsymtest newfile saridx scnnames
sectiondump \
test-flag-nobits dwarf-getstring rerequest_tag \
alldts md5-sha1-test typeiter low_high_pc \
test-elf_cntl_gelf_getshdr dwflsyms dwfllines \
- dwfl-report-elf-align
+ dwfl-report-elf-align backtrace backtrace-child \
+ backtrace-data
asm_TESTS = asm-tst1 asm-tst2 asm-tst3 asm-tst4 asm-tst5 \
asm-tst6 asm-tst7 asm-tst8 asm-tst9
+BUILT_SOURCES = backtrace-child-biarch
+
+clean-local:
+ $(RM) backtrace-child-biarch
+
+# Substitute $(COMPILE).
+backtrace-child-biarch: backtrace-child.c
+ $(CC_BIARCH) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \
+ $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS) $(backtrace_child_CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) $(backtrace_child_LDFLAGS) \
+ -o $@ $<
+
TESTS = run-arextract.sh run-arsymtest.sh newfile test-nlist \
update1 update2 update3 update4 \
run-show-die-info.sh run-get-files.sh run-get-lines.sh \
@@ -88,7 +102,7 @@ TESTS = run-arextract.sh run-arsymtest.sh newfile test-nlist \
run-low_high_pc.sh run-macro-test.sh run-elf_cntl_gelf_getshdr.sh \
run-test-archive64.sh run-readelf-vmcoreinfo.sh \
run-readelf-mixed-corenote.sh run-dwfllines.sh \
- run-dwfl-report-elf-align.sh
+ run-dwfl-report-elf-align.sh run-backtrace.sh
if !STANDALONE
check_PROGRAMS += msg_tst md5-sha1-test
@@ -200,7 +214,7 @@ EXTRA_DIST = run-arextract.sh run-arsymtest.sh \
run-dwfllines.sh run-dwfl-report-elf-align.sh \
testfile-dwfl-report-elf-align-shlib.so.bz2 \
testfilenolines test-core-lib.so.bz2 test-core.core.bz2 \
- test-core.exec.bz2
+ test-core.exec.bz2 run-backtrace.sh
if USE_VALGRIND
valgrind_cmd='valgrind -q --trace-children=yes --error-exitcode=1
--run-libc-freeres=no'
@@ -325,6 +339,10 @@ test_elf_cntl_gelf_getshdr_LDADD = $(libelf) $(libmudflap)
dwflsyms_LDADD = $(libdw) $(libelf) $(libmudflap)
dwfllines_LDADD = $(libdw) $(libelf) $(libmudflap)
dwfl_report_elf_align_LDADD = $(libdw) $(libmudflap)
+backtrace_LDADD = $(libdw) $(libelf) $(libmudflap)
+backtrace_child_CFLAGS = -fPIE
+backtrace_child_LDFLAGS = -pie -pthread
+backtrace_data_LDADD = $(libdw) $(libelf) $(libmudflap)
if GCOV
check: check-am coverage
diff --git a/tests/backtrace-child.c b/tests/backtrace-child.c
new file mode 100644
index 0000000..68ea3a7
--- /dev/null
+++ b/tests/backtrace-child.c
@@ -0,0 +1,147 @@
+/* Test child for parent backtrace test.
+ Copyright (C) 2012 Red Hat, Inc.
+ This file is part of elfutils.
+
+ This file is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ elfutils is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <
http://www.gnu.org/licenses/>. */
+
+#include <config.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <errno.h>
+#include <sys/ptrace.h>
+#include <string.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <unistd.h>
+
+static int ptraceme, gencore;
+
+/* Execution will arrive here from jmp by an artificial ptrace-spawn signal. */
+
+static void
+sigusr2 (int signo)
+{
+ assert (signo == SIGUSR2);
+ if (! gencore)
+ raise (SIGUSR1);
+
+ /* Catch the .plt jump, it will come from this abort call. */
+ abort ();
+}
+
+static __attribute__ ((noinline, noclone)) void
+dummy1 (void)
+{
+ asm volatile ("");
+}
+
+#ifdef __x86_64__
+static __attribute__ ((noinline, noclone, used)) void
+jmp (void)
+{
+ /* Not reached, signal will get ptrace-spawn to jump into sigusr2. */
+ abort ();
+}
+#endif
+
+static __attribute__ ((noinline, noclone)) void
+dummy2 (void)
+{
+ asm volatile ("");
+}
+
+static __attribute__ ((noinline, noclone, noreturn)) void
+stdarg (int f __attribute__ ((unused)), ...)
+{
+ sighandler_t sigusr2_orig = signal (SIGUSR2, sigusr2);
+ assert (sigusr2_orig == SIG_DFL);
+ errno = 0;
+ if (ptraceme)
+ {
+ long l = ptrace (PTRACE_TRACEME, 0, NULL, NULL);
+ assert_perror (errno);
+ assert (l == 0);
+ }
+#ifdef __x86_64__
+ if (! gencore)
+ {
+ /* Execution will get PC patched into function jmp. */
+ raise (SIGUSR1);
+ }
+#endif
+ sigusr2 (SIGUSR2);
+ abort ();
+}
+
+static __attribute__ ((noinline, noclone)) void
+dummy3 (void)
+{
+ asm volatile ("");
+}
+
+static __attribute__ ((noinline, noclone)) void
+backtracegen (void)
+{
+ stdarg (1);
+ /* Here should be no instruction after the stdarg call as it is noreturn
+ function. It must be stdarg so that it is a call and not jump (jump as
+ a tail-call). */
+}
+
+static __attribute__ ((noinline, noclone)) void
+dummy4 (void)
+{
+ asm volatile ("");
+}
+
+static void *
+start (void *arg __attribute__ ((unused)))
+{
+ backtracegen ();
+ abort ();
+}
+
+int
+main (int argc __attribute__ ((unused)), char **argv)
+{
+ assert (*argv++);
+ ptraceme = (*argv && strcmp (*argv, "--ptraceme") == 0);
+ argv += ptraceme;
+ gencore = (*argv && strcmp (*argv, "--gencore") == 0);
+ argv += gencore;
+ assert (*argv && strcmp (*argv, "--run") == 0);
+ dummy1 ();
+ dummy2 ();
+ dummy3 ();
+ dummy4 ();
+ if (gencore)
+ printf ("%ld\n", (long) getpid ());
+ errno = 0;
+ pthread_t thread;
+ int i = pthread_create (&thread, NULL, start, NULL);
+ assert_perror (errno);
+ assert (i == 0);
+ if (ptraceme)
+ {
+ long l = ptrace (PTRACE_TRACEME, 0, NULL, NULL);
+ assert_perror (errno);
+ assert (l == 0);
+ }
+ if (gencore)
+ pthread_join (thread, NULL);
+ else
+ raise (SIGUSR2);
+ abort ();
+}
diff --git a/tests/backtrace-data.c b/tests/backtrace-data.c
new file mode 100644
index 0000000..e2c1a80
--- /dev/null
+++ b/tests/backtrace-data.c
@@ -0,0 +1,255 @@
+/* Test program for unwinding of frames.
+ Copyright (C) 2012 Red Hat, Inc.
+ This file is part of elfutils.
+
+ This file is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ elfutils is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <
http://www.gnu.org/licenses/>. */
+
+#include <config.h>
+#include <assert.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdio_ext.h>
+#include <locale.h>
+#include <dirent.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <error.h>
+#include <unistd.h>
+#include <dwarf.h>
+#include <sys/resource.h>
+#include <sys/ptrace.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/user.h>
+#include <fcntl.h>
+#include <string.h>
+#include ELFUTILS_HEADER(dwfl)
+
+#ifndef __x86_64__
+
+int
+main (void)
+{
+ return 77;
+}
+
+#else /* __x86_64__ */
+
+static int
+find_elf (Dwfl_Module *mod __attribute__ ((unused)),
+ void **userdata __attribute__ ((unused)),
+ const char *modname __attribute__ ((unused)),
+ Dwarf_Addr base __attribute__ ((unused)),
+ char **file_name __attribute__ ((unused)),
+ Elf **elfp __attribute__ ((unused)))
+{
+ /* Not used as modules are reported explicitly. */
+ assert (0);
+}
+
+static bool
+memory_read (Dwarf_Addr addr, Dwarf_Addr *result, void *user_data)
+{
+ pid_t child = (uintptr_t) user_data;
+
+ errno = 0;
+ long l = ptrace (PTRACE_PEEKDATA, child, (void *) (uintptr_t) addr, NULL);
+ assert_perror (errno);
+ *result = l;
+
+ /* We could also return false for failed ptrace. */
+ return true;
+}
+
+/* Return filename and VMA address *BASEP where its mapping starts which
+ contains ADDR. */
+
+static char *
+maps_lookup (pid_t pid, Dwarf_Addr addr, GElf_Addr *basep)
+{
+ char *fname;
+ int i = asprintf (&fname, "/proc/%ld/maps", (long) pid);
+ assert_perror (errno);
+ assert (i > 0);
+ FILE *f = fopen (fname, "r");
+ assert_perror (errno);
+ assert (f);
+ free (fname);
+ for (;;)
+ {
+ // 37e3c22000-37e3c23000 rw-p 00022000 00:11 49532 /lib64/ld-2.14.90.so */
+ unsigned long start, end, offset;
+ i = fscanf (f, "%lx-%lx %*s %lx %*x:%*x %*x", &start, &end,
&offset);
+ assert_perror (errno);
+ assert (i == 3);
+ char *filename = strdup ("");
+ assert (filename);
+ size_t filename_len = 0;
+ for (;;)
+ {
+ int c = fgetc (f);
+ assert (c != EOF);
+ if (c == '\n')
+ break;
+ if (c == ' ' && *filename == '\0')
+ continue;
+ filename = realloc (filename, filename_len + 2);
+ assert (filename);
+ filename[filename_len++] = c;
+ filename[filename_len] = '\0';
+ }
+ if (start <= addr && addr < end)
+ {
+ i = fclose (f);
+ assert_perror (errno);
+ assert (i == 0);
+
+ *basep = start - offset;
+ return filename;
+ }
+ free (filename);
+ }
+}
+
+/* Add module containing ADDR to the DWFL address space. */
+
+static void
+report_module (Dwfl *dwfl, pid_t child, Dwarf_Addr addr)
+{
+ GElf_Addr base;
+ char *long_name = maps_lookup (child, addr, &base);
+ Dwfl_Module *mod = dwfl_report_elf (dwfl, long_name, long_name, -1,
+ base, false /* add_p_vaddr */);
+ assert (mod);
+ free (long_name);
+ assert (dwfl_addrmodule (dwfl, addr) == mod);
+}
+
+int
+main (int argc __attribute__ ((unused)), char **argv __attribute__ ((unused)))
+{
+ /* We use no threads here which can interfere with handling a stream. */
+ __fsetlocking (stdin, FSETLOCKING_BYCALLER);
+ __fsetlocking (stdout, FSETLOCKING_BYCALLER);
+ __fsetlocking (stderr, FSETLOCKING_BYCALLER);
+
+ /* Set locale. */
+ (void) setlocale (LC_ALL, "");
+
+ pid_t child = fork ();
+ switch (child)
+ {
+ case -1:
+ assert_perror (errno);
+ assert (0);
+ case 0:;
+ long l = ptrace (PTRACE_TRACEME, 0, NULL, NULL);
+ assert_perror (errno);
+ assert (l == 0);
+ raise (SIGUSR1);
+ assert (0);
+ default:
+ break;
+ }
+
+ int status;
+ pid_t pid = waitpid (child, &status, 0);
+ assert_perror (errno);
+ assert (pid == child);
+ assert (WIFSTOPPED (status));
+ assert (WSTOPSIG (status) == SIGUSR1);
+
+ struct user_regs_struct user_regs;
+ long l = ptrace (PTRACE_GETREGS, child, NULL, &user_regs);
+ assert_perror (errno);
+ assert (l == 0);
+
+ const unsigned nregs = 17;
+ const uint64_t regs_set = (1U << nregs) - 1;
+ Dwarf_Addr regs[17];
+ regs[0] = user_regs.rax;
+ regs[1] = user_regs.rdx;
+ regs[2] = user_regs.rcx;
+ regs[3] = user_regs.rbx;
+ regs[4] = user_regs.rsi;
+ regs[5] = user_regs.rdi;
+ regs[6] = user_regs.rbp;
+ regs[7] = user_regs.rsp;
+ regs[8] = user_regs.r8;
+ regs[9] = user_regs.r9;
+ regs[10] = user_regs.r10;
+ regs[11] = user_regs.r11;
+ regs[12] = user_regs.r12;
+ regs[13] = user_regs.r13;
+ regs[14] = user_regs.r14;
+ regs[15] = user_regs.r15;
+ regs[16] = user_regs.rip;
+
+ /* x86_64 has PC contained in its CFI subset of DWARF register set so
+ elfutils will figure out the real PC value from REGS. */
+ const bool pc_set = false;
+ Dwarf_Addr pc = 0;
+
+ void *memory_read_user_data = (void *) (uintptr_t) child;
+
+ static char *debuginfo_path;
+ static const Dwfl_Callbacks offline_callbacks =
+ {
+ .find_debuginfo = dwfl_standard_find_debuginfo,
+ .debuginfo_path = &debuginfo_path,
+ .section_address = dwfl_offline_section_address,
+ .find_elf = find_elf,
+ };
+ Dwfl *dwfl = dwfl_begin (&offline_callbacks);
+ assert (dwfl);
+
+ report_module (dwfl, child, user_regs.rip);
+
+ Dwfl_Frame_State *state;
+ state = dwfl_frame_state_data (dwfl, pc_set, pc, nregs, ®s_set, regs,
+ memory_read, memory_read_user_data);
+ assert (state != NULL);
+
+ /* Multiple threads are not handled here. */
+ do
+ {
+ bool minusone;
+ if (! dwfl_frame_state_pc (state, &pc, &minusone))
+ error (1, 0, "dwfl_frame_state_pc: %s", dwfl_errmsg (-1));
+ Dwarf_Addr pc_adjusted = pc - (minusone ? 1 : 0);
+
+ printf ("%#" PRIx64 "\n", (uint64_t) pc);
+
+ Dwfl_Module *mod = dwfl_addrmodule (dwfl, pc_adjusted);
+ if (mod == NULL)
+ report_module (dwfl, child, pc_adjusted);
+
+ if (! dwfl_frame_unwind (&state))
+ error (1, 0, "dwfl_frame_unwind: %s", dwfl_errmsg (-1));
+ }
+ while (state);
+
+ dwfl_end (dwfl);
+ kill (child, SIGKILL);
+ pid = waitpid (child, &status, 0);
+ assert_perror (errno);
+ assert (pid == child);
+ assert (WIFSIGNALED (status));
+ assert (WTERMSIG (status) == SIGKILL);
+
+ return EXIT_SUCCESS;
+}
+
+#endif /* x86_64 */
diff --git a/tests/backtrace.c b/tests/backtrace.c
new file mode 100644
index 0000000..ca847cc
--- /dev/null
+++ b/tests/backtrace.c
@@ -0,0 +1,513 @@
+/* Test program for unwinding of frames.
+ Copyright (C) 2012 Red Hat, Inc.
+ This file is part of elfutils.
+
+ This file is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ elfutils is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <
http://www.gnu.org/licenses/>. */
+
+#include <config.h>
+#include <assert.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdio_ext.h>
+#include <locale.h>
+#include <dirent.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <error.h>
+#include <unistd.h>
+#include <dwarf.h>
+#include <sys/resource.h>
+#include <sys/ptrace.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/user.h>
+#include <fcntl.h>
+#include <string.h>
+#include ELFUTILS_HEADER(dwfl)
+
+static void
+report_pid (Dwfl *dwfl, pid_t pid)
+{
+ int result = dwfl_linux_proc_report (dwfl, pid);
+ if (result < 0)
+ error (2, 0, "dwfl_linux_proc_report: %s", dwfl_errmsg (-1));
+ else if (result > 0)
+ error (2, result, "dwfl_linux_proc_report");
+
+ if (dwfl_report_end (dwfl, NULL, NULL) != 0)
+ error (2, 0, "dwfl_report_end: %s", dwfl_errmsg (-1));
+}
+
+static Dwfl *
+dwfl_pid (pid_t pid)
+{
+ static char *debuginfo_path;
+ static const Dwfl_Callbacks proc_callbacks =
+ {
+ .find_debuginfo = dwfl_standard_find_debuginfo,
+ .debuginfo_path = &debuginfo_path,
+
+ .find_elf = dwfl_linux_proc_find_elf,
+ };
+ Dwfl *dwfl = dwfl_begin (&proc_callbacks);
+ if (dwfl == NULL)
+ error (2, 0, "dwfl_begin: %s", dwfl_errmsg (-1));
+ report_pid (dwfl, pid);
+ return dwfl;
+}
+
+static const char *executable;
+
+static int
+find_elf (Dwfl_Module *mod, void **userdata, const char *modname,
+ Dwarf_Addr base, char **file_name, Elf **elfp)
+{
+ if (executable && modname != NULL
+ && (strcmp (modname, "[exe]") == 0 || strcmp (modname,
"[pie]") == 0))
+ {
+ char *executable_dup = strdup (executable);
+ if (executable_dup)
+ {
+ free (*file_name);
+ *file_name = executable_dup;
+ return -1;
+ }
+ }
+ return dwfl_build_id_find_elf (mod, userdata, modname, base, file_name, elfp);
+}
+
+static Dwfl *
+dwfl_offline (void)
+{
+ static char *debuginfo_path;
+ static const Dwfl_Callbacks offline_callbacks =
+ {
+ .find_debuginfo = dwfl_standard_find_debuginfo,
+ .debuginfo_path = &debuginfo_path,
+
+ .section_address = dwfl_offline_section_address,
+
+ /* We use this table for core files too. */
+ .find_elf = find_elf,
+ };
+ Dwfl *dwfl = dwfl_begin (&offline_callbacks);
+ if (dwfl == NULL)
+ error (2, 0, "dwfl_begin: %s", dwfl_errmsg (-1));
+ return dwfl;
+}
+
+static Dwfl *
+dwfl_core (const char *corefile)
+{
+ Dwfl *dwfl = dwfl_offline ();
+ Elf *elf;
+ int fd;
+
+ fd = open64 (corefile, O_RDONLY);
+ if (fd == -1)
+ error (2, 0, "open64: %m");
+ elf = elf_begin (fd, ELF_C_READ_MMAP, NULL);
+ if (elf == NULL)
+ error (2, 0, "elf_begin: %s", elf_errmsg (-1));
+ if (dwfl_core_file_report (dwfl, elf) < 0)
+ error (2, 0, "dwfl_core_file_report: %s", dwfl_errmsg (-1));
+ if (dwfl_report_end (dwfl, NULL, NULL) != 0)
+ error (2, 0, "dwfl_report_end: %s", dwfl_errmsg (-1));
+ if (elf_end (elf) != 0)
+ error (2, 0, "elf_end: %s", elf_errmsg (-1));
+ if (close (fd) != 0)
+ error (2, 0, "close: %m");
+ return dwfl;
+}
+
+static int
+dump_modules (Dwfl_Module *mod, void **userdata __attribute__ ((unused)),
+ const char *name, Dwarf_Addr start,
+ void *arg __attribute__ ((unused)))
+{
+ Dwarf_Addr end;
+ dwfl_module_info (mod, NULL, NULL, &end, NULL, NULL, NULL, NULL);
+ printf ("%#" PRIx64 "\t%#" PRIx64 "\t%s\n", (uint64_t)
start, (uint64_t) end,
+ name);
+ return DWARF_CB_OK;
+}
+
+static void
+dump (pid_t pid, const char *corefile,
+ void (*callback) (pid_t tid, unsigned frameno, Dwarf_Addr pc,
+ const char *symname, Dwfl *dwfl, void *data),
+ void *data)
+{
+ Dwfl *dwfl;
+ Dwfl_Frame_State *state;
+ if (pid && !corefile)
+ {
+ dwfl = dwfl_pid (pid);
+ state = dwfl_frame_state_pid (dwfl, pid);
+ }
+ else if (corefile && !pid)
+ {
+ dwfl = dwfl_core (corefile);
+ state = dwfl_frame_state_core (dwfl, corefile);
+ }
+ else
+ abort ();
+ if (state == NULL)
+ error (2, 0, "dwfl_frame_state: %s", dwfl_errmsg (-1));
+ ptrdiff_t ptrdiff = dwfl_getmodules (dwfl, dump_modules, NULL, 0);
+ assert (ptrdiff == 0);
+ int err = 0;
+ do
+ {
+ Dwfl_Frame_State *thread = state;
+ pid_t tid = dwfl_frame_tid_get (thread);
+ printf ("TID %ld:\n", (long) tid);
+ unsigned frameno;
+ for (frameno = 0; state; frameno++)
+ {
+ Dwarf_Addr pc;
+ bool minusone;
+ if (! dwfl_frame_state_pc (state, &pc, &minusone))
+ {
+ fprintf (stderr, "%s\n", dwfl_errmsg (-1));
+ err = 1;
+ break;
+ }
+ Dwarf_Addr pc_adjusted = pc - (minusone ? 1 : 0);
+
+ /* Get PC->SYMNAME. */
+ Dwfl_Module *mod = dwfl_addrmodule (dwfl, pc_adjusted);
+ const char *symname = NULL;
+ if (mod)
+ symname = dwfl_module_addrname (mod, pc_adjusted);
+
+ printf ("#%2u %#" PRIx64 "%4s\t%s\n", frameno, (uint64_t) pc,
+ minusone ? "- 1" : "", symname);
+ if (callback)
+ callback (tid, frameno, pc, symname, dwfl, data);
+ if (! dwfl_frame_unwind (&state))
+ {
+ fprintf (stderr, "%s\n", dwfl_errmsg (-1));
+ err = 1;
+ break;
+ }
+ }
+ state = dwfl_frame_thread_next (thread);
+ }
+ while (state);
+ if (callback)
+ callback (0, 0, 0, NULL, dwfl, data);
+ dwfl_end (dwfl);
+ if (err)
+ exit (EXIT_FAILURE);
+}
+
+struct see_exec_module
+{
+ Dwfl_Module *mod;
+ char selfpath[PATH_MAX + 1];
+};
+
+static int
+see_exec_module (Dwfl_Module *mod, void **userdata __attribute__ ((unused)),
+ const char *name __attribute__ ((unused)),
+ Dwarf_Addr start __attribute__ ((unused)), void *arg)
+{
+ struct see_exec_module *data = arg;
+ if (strcmp (name, data->selfpath) != 0)
+ return DWARF_CB_OK;
+ assert (data->mod == NULL);
+ data->mod = mod;
+ return DWARF_CB_OK;
+}
+
+static void
+selfdump_callback (pid_t tid, unsigned frameno, Dwarf_Addr pc,
+ const char *symname, Dwfl *dwfl, void *data)
+{
+ pid_t check_tid = (intptr_t) data;
+ bool disable = check_tid < 0;
+ if (disable)
+ check_tid = -check_tid;
+ static bool seen_main = false;
+ if (symname && strcmp (symname, "main") == 0)
+ seen_main = true;
+ if (pc == 0)
+ {
+ assert (seen_main);
+ return;
+ }
+ if (disable || tid != check_tid)
+ return;
+ Dwfl_Module *mod;
+ const char *symname2 = NULL;
+ switch (frameno)
+ {
+ case 0:
+ /* .plt has no symbols. */
+ assert (symname == NULL);
+ break;
+ case 1:
+ assert (symname != NULL && strcmp (symname, "sigusr2") == 0);
+ break;
+ case 2:
+ /* __restore_rt - glibc maybe does not have to have this symbol. */
+ break;
+ case 3:
+ /* Verify we trapped on the very first instruction of jmp. */
+ assert (symname != NULL && strcmp (symname, "jmp") == 0);
+ mod = dwfl_addrmodule (dwfl, pc - 1);
+ if (mod)
+ symname2 = dwfl_module_addrname (mod, pc - 1);
+ assert (symname2 == NULL || strcmp (symname2, "jmp") != 0);
+ break;
+ case 4:
+ assert (symname != NULL && strcmp (symname, "stdarg") == 0);
+ break;
+ case 5:
+ /* Verify we trapped on the very last instruction of child. */
+ assert (symname != NULL && strcmp (symname, "backtracegen") ==
0);
+ mod = dwfl_addrmodule (dwfl, pc);
+ if (mod)
+ symname2 = dwfl_module_addrname (mod, pc);
+ assert (symname2 == NULL || strcmp (symname2, "backtracegen") != 0);
+ break;
+ }
+}
+
+#ifdef __x86_64__
+static void
+prepare_thread (pid_t pid2, Dwarf_Addr plt_start, Dwarf_Addr plt_end,
+ void (*jmp) (void))
+{
+ long l;
+ errno = 0;
+ l = ptrace (PTRACE_POKEUSER, pid2,
+ (void *) (intptr_t) offsetof (struct user_regs_struct, rip), jmp);
+ assert_perror (errno);
+ assert (l == 0);
+ l = ptrace (PTRACE_CONT, pid2, NULL, (void *) (intptr_t) SIGUSR2);
+ int status;
+ pid_t got = waitpid (pid2, &status, __WALL);
+ assert_perror (errno);
+ assert (got == pid2);
+ assert (WIFSTOPPED (status));
+ assert (WSTOPSIG (status) == SIGUSR1);
+ for (;;)
+ {
+ errno = 0;
+ l = ptrace (PTRACE_PEEKUSER, pid2,
+ (void *) (intptr_t) offsetof (struct user_regs_struct, rip),
+ NULL);
+ assert_perror (errno);
+ if ((unsigned long) l >= plt_start && (unsigned long) l < plt_end)
+ break;
+ l = ptrace (PTRACE_SINGLESTEP, pid2, NULL, NULL);
+ assert_perror (errno);
+ assert (l == 0);
+ got = waitpid (pid2, &status, __WALL);
+ assert_perror (errno);
+ assert (got == pid2);
+ assert (WIFSTOPPED (status));
+ assert (WSTOPSIG (status) == SIGTRAP);
+ }
+}
+#endif /* __x86_64__ */
+
+#include <asm/unistd.h>
+#include <unistd.h>
+#define tgkill(pid, tid, sig) syscall (__NR_tgkill, (pid), (tid), (sig))
+
+static void
+ptrace_detach_stopped (pid_t pid)
+{
+ errno = 0;
+ long l = ptrace (PTRACE_DETACH, pid, NULL, (void *) (intptr_t) SIGSTOP);
+ assert_perror (errno);
+ assert (l == 0);
+}
+
+static void
+selfdump (const char *exec)
+{
+ pid_t pid = fork ();
+ switch (pid)
+ {
+ case -1:
+ abort ();
+ case 0:
+ execl (exec, exec, "--ptraceme", "--run", NULL);
+ abort ();
+ default:
+ break;
+ }
+
+ /* Catch the main thread. Catch it first otherwise the /proc evaluation of
+ PID may have caught still ourselves before executing execl above. */
+ errno = 0;
+ int status;
+ pid_t got = waitpid (pid, &status, 0);
+ assert_perror (errno);
+ assert (got == pid);
+ assert (WIFSTOPPED (status));
+ assert (WSTOPSIG (status) == SIGUSR2);
+
+ /* Catch the spawned thread. Do not use __WCLONE as we could get racy
+ __WCLONE, probably despite pthread_create already had to be called the new
+ task is not yet alive enough for waitpid. */
+ pid_t pid2 = waitpid (-1, &status, __WALL);
+ assert_perror (errno);
+ assert (pid2 > 0);
+ assert (pid2 != pid);
+ assert (WIFSTOPPED (status));
+ assert (WSTOPSIG (status) == SIGUSR1);
+
+ Dwfl *dwfl = dwfl_pid (pid);
+ char *selfpathname;
+ int i = asprintf (&selfpathname, "/proc/%ld/exe", (long) pid);
+ assert (i > 0);
+ struct see_exec_module data;
+ ssize_t ssize = readlink (selfpathname, data.selfpath,
+ sizeof (data.selfpath));
+ free (selfpathname);
+ assert (ssize > 0 && ssize < (ssize_t) sizeof (data.selfpath));
+ data.selfpath[ssize] = '\0';
+ data.mod = NULL;
+ ptrdiff_t ptrdiff = dwfl_getmodules (dwfl, see_exec_module, &data, 0);
+ assert (ptrdiff == 0);
+ assert (data.mod != NULL);
+ GElf_Addr loadbase;
+ Elf *elf = dwfl_module_getelf (data.mod, &loadbase);
+ GElf_Ehdr ehdr_mem, *ehdr = gelf_getehdr (elf, &ehdr_mem);
+ assert (ehdr != NULL);
+ Elf_Scn *scn = NULL, *plt = NULL;
+ while ((scn = elf_nextscn (elf, scn)) != NULL)
+ {
+ GElf_Shdr scn_shdr_mem, *scn_shdr = gelf_getshdr (scn, &scn_shdr_mem);
+ assert (scn_shdr != NULL);
+ if (strcmp (elf_strptr (elf, ehdr->e_shstrndx, scn_shdr->sh_name),
+ ".plt") != 0)
+ continue;
+ assert (plt == NULL);
+ plt = scn;
+ }
+ assert (plt != NULL);
+ GElf_Shdr scn_shdr_mem, *scn_shdr = gelf_getshdr (plt, &scn_shdr_mem);
+ assert (scn_shdr != NULL);
+ /* Make it true on x86_64 with i386 inferior. */
+ int disable = ehdr->e_ident[EI_CLASS] == ELFCLASS32;
+#ifdef __x86_64__
+ Dwarf_Addr plt_start = scn_shdr->sh_addr + loadbase;
+ Dwarf_Addr plt_end = plt_start + scn_shdr->sh_size;
+ void (*jmp) (void);
+ if (! disable)
+ {
+ int nsym = dwfl_module_getsymtab (data.mod);
+ int symi;
+ for (symi = 1; symi < nsym; ++symi)
+ {
+ GElf_Sym symbol;
+ const char *symbol_name = dwfl_module_getsym (data.mod, symi, &symbol, NULL);
+ if (symbol_name == NULL)
+ continue;
+ switch (GELF_ST_TYPE (symbol.st_info))
+ {
+ case STT_SECTION:
+ case STT_FILE:
+ case STT_TLS:
+ continue;
+ default:
+ if (strcmp (symbol_name, "jmp") != 0)
+ continue;
+ break;
+ }
+ /* LOADBASE is already applied here. */
+ jmp = (void (*) (void)) (uintptr_t) symbol.st_value;
+ break;
+ }
+ assert (symi < nsym);
+ prepare_thread (pid2, plt_start, plt_end, jmp);
+ }
+#endif
+ dwfl_end (dwfl);
+ ptrace_detach_stopped (pid);
+ ptrace_detach_stopped (pid2);
+ dump (pid, NULL, selfdump_callback,
+ (void *) (intptr_t) (disable ? -pid2 : pid2));
+}
+
+static bool
+is_core (const char *corefile)
+{
+ Dwfl *dwfl = dwfl_offline ();
+ Dwfl_Module *mod = dwfl_report_elf (dwfl, "core", corefile, -1, 0 /* base
*/,
+ false /* add_p_vaddr */);
+ assert (mod != NULL);
+ GElf_Addr loadbase_ignore;
+ Elf *core = dwfl_module_getelf (mod, &loadbase_ignore);
+ assert (core != NULL);
+ GElf_Ehdr ehdr_mem, *ehdr = gelf_getehdr (core, &ehdr_mem);
+ assert (ehdr != NULL);
+ assert (ehdr->e_type == ET_CORE || ehdr->e_type == ET_EXEC
+ || ehdr->e_type == ET_DYN);
+ bool retval = ehdr->e_type == ET_CORE;
+ dwfl_end (dwfl);
+ return retval;
+}
+
+int
+main (int argc __attribute__ ((unused)), char **argv)
+{
+ /* We use no threads here which can interfere with handling a stream. */
+ __fsetlocking (stdin, FSETLOCKING_BYCALLER);
+ __fsetlocking (stdout, FSETLOCKING_BYCALLER);
+ __fsetlocking (stderr, FSETLOCKING_BYCALLER);
+
+ /* Set locale. */
+ (void) setlocale (LC_ALL, "");
+
+ if (argc == 1)
+ {
+ selfdump ("./backtrace-child");
+ return 0;
+ }
+ argv++;
+ if (argc == 2)
+ {
+ if (strcmp (*argv, "--help") == 0)
+ error (2, 0, "backtrace {{no args for
./backtrace-child}|<pid>|<core>|"
+ "<executable>|<executable core>}");
+ char *end;
+ long l = strtol (*argv, &end, 10);
+ if (**argv && !*end)
+ dump (l, NULL, NULL, NULL);
+ else if (is_core (*argv))
+ dump (0, *argv, NULL, NULL);
+ else
+ selfdump (*argv);
+ return 0;
+ }
+ if (argc == 3)
+ {
+ assert (! is_core (argv[0]));
+ assert (is_core (argv[1]));
+ executable = argv[0];
+ dump (0, argv[1], NULL, NULL);
+ return 0;
+ }
+ assert (0);
+
+ return 0;
+}
diff --git a/tests/run-backtrace.sh b/tests/run-backtrace.sh
new file mode 100755
index 0000000..be3c6f4
--- /dev/null
+++ b/tests/run-backtrace.sh
@@ -0,0 +1,83 @@
+#! /bin/bash
+# Copyright (C) 2013 Red Hat, Inc.
+# This file is part of elfutils.
+#
+# This file is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# elfutils is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <
http://www.gnu.org/licenses/>.
+
+. $srcdir/test-subr.sh
+
+if [ -z "$VERBOSE" ]; then
+ exec >/dev/null
+else
+ set -x
+fi
+
+mytestrun()
+{
+ echo "$*"
+ testrun "$@"
+}
+
+check_main()
+{
+ if grep -w main $1; then
+ return
+ fi
+ cat >&2 $1 $3
+ echo >&2 $2: no main
+ false
+}
+
+check_gsignal()
+{
+ # Without proper ELF symbols resolution we could get inappropriate weak
+ # symbol "gsignal" with the same address as the correct symbol
"raise".
+ if ! grep -w gsignal $1; then
+ return
+ fi
+ cat >&2 $1
+ echo >&2 $2: found gsignal
+ false
+}
+
+check_err()
+{
+ if test ! -s $1; then
+ return
+ fi
+ # In some cases we cannot reliably find out we got behind _start.
+ if cmp -s <(echo "No DWARF information found") <(uniq <$1); then
+ return
+ fi
+ cat >&2 $1
+ echo >&2 $2: neither empty nor just out of DWARF
+ false
+}
+
+for child in backtrace-child{,-biarch}; do
+ tempfiles $child{.bt,.err}
+ (set +ex; testrun ${abs_builddir}/backtrace ${abs_builddir}/$child 1>$child.bt
2>$child.err; true)
+ check_main $child.bt $child $child.err
+ check_gsignal $child.bt $child
+ check_err $child.err $child
+ core="core.`ulimit -c unlimited; set +ex; testrun ${abs_builddir}/$child --gencore
--run; true`"
+ tempfiles $core{,.bt,.err}
+ (set +ex; testrun ${abs_builddir}/backtrace ${abs_builddir}/$child $core 1>$core.bt
2>$core.err; true)
+ cat $core.{bt,err}
+ check_main $core.bt $child-$core $core.err
+ check_gsignal $core.bt $child-$core
+ check_err $core.err $child-$core
+done
+
+exit 0