This is the mail archive of the elfutils-devel@sourceware.org mailing list for the elfutils project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

[patch 4/4] unwinder: The unwinder (x86* only)


Hi,

dependent on:
	[patch 3/4] unwinder: Provide dwfl_core_filename_report

present in:
	jankratochvil/forunwind-baseaddr-hookvars-corereport-unwindx86
but this branch is not suitable for merge as it is unclear in which form get
checked in the patches above.  The branch is usable only for "git checkout",
neither diff nor merge should be done on it.

ppc* and s390* extensions exist in
	 jankratochvil/forunwind-baseaddr-hookvars-corereport-unwindx86-nonx86
and they are not present here for simplicity, I may post them later.

Some comments were already posted, it supports natively core files and live
PIDs, "data" API interface for backtracing application-provided inferiors is
also present.

Testcases are provided, src/stack.c utility is also provided.


Thanks,
Jan


backends/
2012-11-13  Jan Kratochvil  <jan.kratochvil@redhat.com>

	* Makefile.am (i386_SRCS): Add i386_frame_state.c.
	(x86_64_SRCS): Add x86_64_frame_state.c.
	* i386_frame_state.c: New file.
	* i386_init.c (i386_init): Initialize frame_state_nregs and
	frame_state.
	* x86_64_frame_state.c: New file.
	* x86_64_init.c (x86_64_init): Initialize frame_state_nregs and
	frame_state.

./
2012-11-13  Jan Kratochvil  <jan.kratochvil@redhat.com>

	* configure.ac: New AC_CHECK_SIZEOF for long.  Call utrace_BIARCH, new
	AC_SUBST for CC_BIARCH.

libdw/
2012-11-13  Jan Kratochvil  <jan.kratochvil@redhat.com>

	* cfi.h (struct Dwarf_Frame_s): Make the comment more specific.
	* libdw.map (ELFUTILS_0.156): Add dwfl_report_elf_baseaddr,
	dwfl_frame_state_pid, dwfl_frame_state_core, dwfl_frame_state_data,
	dwfl_frame_unwind, dwfl_frame_state_pc, dwfl_frame_tid_get and
	dwfl_frame_thread_next.

libdwfl/
2012-11-13  Jan Kratochvil  <jan.kratochvil@redhat.com>

	* Makefile.am (INCLUDES): Add ../libasm.
	(libdwfl_a_SOURCES): Add dwfl_frame_state.c, dwfl_frame_unwind.c,
	dwfl_frame_state_pc.c, dwfl_frame_state_pid.c, dwfl_frame_state_core.c,
	dwfl_frame_state_data.c, dwfl_frame_thread_next.c and
	dwfl_frame_tid_get.c.
	* dwfl_end.c (dwfl_end): Call __libdwfl_process_free for
	dwfl->framestatelist.
	* dwfl_frame_state.c: New file.
	* dwfl_frame_state_core.c: New file.
	* dwfl_frame_state_data.c: New file.
	* dwfl_frame_state_pc.c: New file.
	* dwfl_frame_state_pid.c: New file.
	* dwfl_frame_thread_next.c: New file.
	* dwfl_frame_tid_get.c: New file.
	* dwfl_frame_unwind.c: New file.
	* libdwfl.h (Dwfl_Frame_State): New typedef.
	(dwfl_frame_state_pid, dwfl_frame_state_core, dwfl_frame_memory_read_t)
	(dwfl_frame_state_data, dwfl_frame_unwind, dwfl_frame_state_pc)
	(dwfl_frame_thread_next, dwfl_frame_tid_get): New declaration.
	* libdwflP.h: Include libeblP.h.
	(Dwfl_Frame_State_Process, Dwfl_Frame_State_Thread): New typedefs.
	(LIBEBL_BAD, CORE_MISSING, INVALID_REGISTER, PROCESS_MEMORY_READ)
	(PROCESS_NO_ARCH, PARSE_PROC, NO_THREAD, INVALID_DWARF)
	(UNSUPPORTED_DWARF): New DWFL_ERROR entries.
	(struct Dwfl): New entry framestatelist.
	(struct Dwfl_Frame_State_Process, struct Dwfl_Frame_State_Thread)
	(struct Dwfl_Frame_State, dwfl_frame_state_reg_get)
	(dwfl_frame_state_reg_set): New.
	(__libdwfl_state_fetch_pc, __libdwfl_thread_free)
	(__libdwfl_thread_alloc, __libdwfl_process_free)
	(__libdwfl_process_alloc, __libdwfl_segment_start)
	(__libdwfl_segment_end): New declarations.
	(dwfl_frame_state_pid, dwfl_frame_state_core, dwfl_frame_unwind)
	(dwfl_frame_state_pc): New INTDECL entries.
	* segment.c (segment_start): Rename to ...
	(__libdwfl_segment_start): ... here and make it internal_function.
	(segment_end): Rename to ...
	(__libdwfl_segment_end): ... here and make it internal_function.
	(reify_segments, dwfl_report_segment): Rename them at the callers.

libebl/
2012-11-13  Jan Kratochvil  <jan.kratochvil@redhat.com>

	* Makefile.am (INCLUDES): Add ../libdwfl.
	(gen_SOURCES): Add eblframestate.c.
	* ebl-hooks.h (frame_state, frame_state_nregs): New entries.
	* eblframestate.c: New file.
	* libebl.h (struct Dwfl_Frame_State, ebl_frame_state)
	(ebl_frame_state_nregs): New declarations.

m4/
2012-11-13  Jan Kratochvil  <jan.kratochvil@redhat.com>

	* biarch.m4: New file.

src/
2012-11-13  Jan Kratochvil  <jan.kratochvil@redhat.com>

	* Makefile.am (bin_PROGRAMS): Add stack.
	(stack_LDADD): New.
	* stack.c: New file.

tests/
2012-11-13  Jan Kratochvil  <jan.kratochvil@redhat.com>

	* Makefile.am (check_PROGRAMS): Add backtrace, backtrace-child and
	backtrace-data.
	(BUILT_SOURCES, clean-local, backtrace-child-biarch): New.
	(TESTS): Add run-backtrace.sh.
	(backtrace_LDADD, backtrace_child_CFLAGS, backtrace_child_LDFLAGS)
	(backtrace_data_LDADD): New.
	* backtrace-child.c: New file.
	* backtrace-data.c: New file.
	* backtrace.c: New file.
	* run-backtrace.sh: New file.

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

Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]