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 v5] unwinder: The unwinder (x86* only)


Hi Mark,

On Tue, 08 Oct 2013 16:43:02 +0200, Mark Wielaard wrote:
> I don't really like that ebl/backends now depends on dwfl.
[...]
> This is needed because the new ebl_set_initial_registers_tid function is
> defined as:

It was not really needed as it was dealing with Dwfl stuff only in opaque
style.


> The advantage would be that things are a little bit more flexible for
> the caller and that more code could be shared between things that
> process registers in core notes and those that grab them from a process.

I do not see more PID/core sharing, struct user has different layout than core
files so that would be needed a new Ebl_Register_Location struct to describe
struct user -> DWARF numbering translation.

But even then you cannot make PTRACE_GETREGS arch-independent as it is done
(almost) on every arch differently.  Sometimes PTRACE_GETREGS, sometimes
PTRACE_PEEKUSER (older ppc), sometimes PTRACE_PEEKUSR_AREA (s390), sometimes
with swapped addr<->data parameters for PTRACE_GETREGS (sparc) etc.

So sending the patch without libebl/backends dependcy on libdwfl/ although
still with ptrace registers fetching + translation in arch-specific code.


> > +const char *EBLHOOK(get_func_pc) (Ebl *ebl, struct Dwfl_Module *mod,
> > +				  GElf_Sym *sym);
> 
> BTW. This hook isn't actually implemented in this patch.

Fixed.


Thanks,
Jan


commit aa5632c886646039c2f177799ee4637fbaa3bda7
Author: Jan Kratochvil <jan.kratochvil@redhat.com>
Date:   Thu May 30 14:37:38 2013 +0200

    Unwinder for x86*.
    
    backends/
    2013-06-23  Jan Kratochvil  <jan.kratochvil@redhat.com>
    
    	* Makefile.am (i386_SRCS): Add i386_initreg.c.
    	(x86_64_SRCS): Add x86_64_initreg.c.
    	* i386_initreg.c: New file.
    	* i386_init.c (i386_init): Initialize frame_nregs and
    	set_initial_registers_tid.
    	* x86_64_initreg.c: New file.
    	* x86_64_init.c (x86_64_init): Initialize frame_nregs and
    	set_initial_registers_tid.
    
    ./
    2013-06-23  Jan Kratochvil  <jan.kratochvil@redhat.com>
    
    	* configure.ac: New AC_CHECK_SIZEOF for long.  Call utrace_BIARCH, new
    	AC_SUBST for CC_BIARCH.
    
    libdw/
    2013-06-23  Jan Kratochvil  <jan.kratochvil@redhat.com>
    
    	* cfi.h (struct Dwarf_Frame_s): Make the comment more specific.
    	* libdw.map (ELFUTILS_0.156): Add dwfl_attach_state, dwfl_pid,
    	dwfl_thread_dwfl, dwfl_thread_tid, dwfl_frame_thread,
    	dwfl_thread_state_registers, dwfl_thread_state_register_pc,
    	dwfl_next_thread, dwfl_thread_getframes and dwfl_frame_pc.
    
    libdwfl/
    2013-09-02  Jan Kratochvil  <jan.kratochvil@redhat.com>
    	    Mark Wielaard  <mjw@redhat.com>
    
    	* Makefile.am (AM_CPPFLAGS): Add ../libasm.
    	(libdwfl_a_SOURCES): Add dwfl_frame.c, dwfl_frame_unwind.c,
    	dwfl_frame_pc.c, dwfl_frame_pid.c, dwfl_frame_core.c and
    	dwfl_frame_regs.c.
    	* core-file.c (dwfl_core_file_report): Call
    	__libdwfl_attach_state_for_core.
    	* dwfl_end.c (dwfl_end): Call __libdwfl_process_free.
    	* dwfl_frame.c: New file.
    	* dwfl_frame_unwind.c: New file.
    	* dwfl_frame_pc.c: New file.
    	* dwfl_frame_pid.c: New file.
    	* dwfl_frame_core.c: New file.
    	* dwfl_frame_regs.c: New file.
    	* libdwfl.h: Include libebl.h.
    	(Dwfl_Thread, Dwfl_Frame): New typedefs.
    	(dwfl_core_file_report, dwfl_linux_proc_report): Extend comments.
    	(Dwfl_Thread_Callbacks): New definition.
    	(dwfl_attach_state, dwfl_pid, dwfl_thread_dwfl, dwfl_thread_tid)
    	(dwfl_frame_thread, dwfl_thread_state_registers)
    	(dwfl_thread_state_register_pc, dwfl_next_thread, dwfl_thread_getframes)
    	(dwfl_frame_pc): New declarations.
    	* libdwflP.h: Include libeblP.h.
    	(Dwfl_Process): New typedef.
    	(LIBEBL_BAD, CORE_MISSING, INVALID_REGISTER, PROCESS_MEMORY_READ)
    	(PROCESS_NO_ARCH, PARSE_PROC, NO_THREAD, INVALID_DWARF)
    	(UNSUPPORTED_DWARF, NEXT_THREAD_FAIL, ATTACH_STATE_CONFLICT)
    	(NO_ATTACH_STATE): New DWFL_ERROR entries.
    	(struct Dwfl): New entry process.
    	(struct Dwfl_Process, struct Dwfl_Thread, struct Dwfl_Frame)
    	(dwfl_frame_reg_get, dwfl_frame_reg_set): New definitions.
    	(__libdwfl_process_free, __libdwfl_frame_unwind)
    	(__libdwfl_attach_state_for_pid, __libdwfl_attach_state_for_core)
    	(__libdwfl_segment_start, __libdwfl_segment_end): New declarations.
    	(dwfl_attach_state, dwfl_pid, dwfl_thread_dwfl, dwfl_thread_tid)
    	(dwfl_frame_thread, dwfl_thread_state_registers)
    	(dwfl_thread_state_register_pc, dwfl_next_thread, dwfl_thread_getframes)
    	(dwfl_frame_pc): New INTDECL entries.
    	* linux-proc-maps.c (dwfl_linux_proc_report): Call
    	__libdwfl_attach_state_for_pid.
    	* segment.c (segment_start): Rename to ...
    	(__libdwfl_segment_start): ... here and make it internal_function.
    	(segment_end): Rename to ...
    	(__libdwfl_segment_end): ... here and make it internal_function.
    	(reify_segments, dwfl_report_segment): Rename them at the callers.
    
    libebl/
    2013-06-23  Jan Kratochvil  <jan.kratochvil@redhat.com>
    
    	* Makefile.am (gen_SOURCES): Add eblinitreg.c.
    	* ebl-hooks.h (set_initial_registers_tid): New entry.
    	* eblinitreg.c: New file.
    	* libebl.h (dwfl_thread_state_registers_t): New definition.
    	(ebl_set_initial_registers_tid, ebl_frame_nregs): New declarations.
    	* libeblP.h (struct ebl): New entry frame_nregs.
    
    m4/
    2013-06-23  Jan Kratochvil  <jan.kratochvil@redhat.com>
    
    	* biarch.m4: New file.
    
    src/
    2013-06-23  Jan Kratochvil  <jan.kratochvil@redhat.com>
    	    Mark Wielaard  <mjw@redhat.com>
    
    	* Makefile.am (bin_PROGRAMS): Add stack.
    	(stack_LDADD): New.
    	* stack.c: New file.
    
    tests/
    2013-09-02  Jan Kratochvil  <jan.kratochvil@redhat.com>
    	    Mark Wielaard  <mjw@redhat.com>
    
    	* Makefile.am (check_PROGRAMS): Add backtrace, backtrace-child and
    	backtrace-data.
    	(BUILT_SOURCES, clean-local, backtrace-child-biarch): New.
    	(TESTS): Add run-backtrace.sh.
    	(backtrace_LDADD, backtrace_child_CFLAGS, backtrace_child_LDFLAGS)
    	(backtrace_data_LDADD): New.
    	* backtrace-child.c: New file.
    	* backtrace-data.c: New file.
    	* backtrace.c: New file.
    	* run-backtrace.sh: New file.
    
    Signed-off-by: Jan Kratochvil <jan.kratochvil@redhat.com>

diff --git a/backends/Makefile.am b/backends/Makefile.am
index 557ed87..5b5e067 100644
--- a/backends/Makefile.am
+++ b/backends/Makefile.am
@@ -50,7 +50,8 @@ libdw = ../libdw/libdw.so
 endif
 
 i386_SRCS = i386_init.c i386_symbol.c i386_corenote.c i386_cfi.c \
-	    i386_retval.c i386_regs.c i386_auxv.c i386_syscall.c
+	    i386_retval.c i386_regs.c i386_auxv.c i386_syscall.c \
+	    i386_initreg.c
 cpu_i386 = ../libcpu/libcpu_i386.a
 libebl_i386_pic_a_SOURCES = $(i386_SRCS)
 am_libebl_i386_pic_a_OBJECTS = $(i386_SRCS:.c=.os)
@@ -60,7 +61,8 @@ libebl_sh_pic_a_SOURCES = $(sh_SRCS)
 am_libebl_sh_pic_a_OBJECTS = $(sh_SRCS:.c=.os)
 
 x86_64_SRCS = x86_64_init.c x86_64_symbol.c x86_64_corenote.c x86_64_cfi.c \
-	      x86_64_retval.c x86_64_regs.c i386_auxv.c x86_64_syscall.c
+	      x86_64_retval.c x86_64_regs.c i386_auxv.c x86_64_syscall.c \
+	      x86_64_initreg.c
 cpu_x86_64 = ../libcpu/libcpu_x86_64.a
 libebl_x86_64_pic_a_SOURCES = $(x86_64_SRCS)
 am_libebl_x86_64_pic_a_OBJECTS = $(x86_64_SRCS:.c=.os)
diff --git a/backends/i386_init.c b/backends/i386_init.c
index cc9b2d7..1e0b486 100644
--- a/backends/i386_init.c
+++ b/backends/i386_init.c
@@ -1,5 +1,5 @@
 /* Initialization of i386 specific backend library.
-   Copyright (C) 2000-2009 Red Hat, Inc.
+   Copyright (C) 2000-2009, 2013 Red Hat, Inc.
    This file is part of elfutils.
    Written by Ulrich Drepper <drepper@redhat.com>, 2000.
 
@@ -63,6 +63,9 @@ i386_init (elf, machine, eh, ehlen)
   HOOK (eh, auxv_info);
   HOOK (eh, disasm);
   HOOK (eh, abi_cfi);
+  /* gcc/config/ #define DWARF_FRAME_REGISTERS.  For i386 it is 17, why?  */
+  eh->frame_nregs = 9;
+  HOOK (eh, set_initial_registers_tid);
 
   return MODVERSION;
 }
diff --git a/backends/i386_initreg.c b/backends/i386_initreg.c
new file mode 100644
index 0000000..9d904a4
--- /dev/null
+++ b/backends/i386_initreg.c
@@ -0,0 +1,79 @@
+/* Fetch process data from STATE->base->pid or STATE->base->core.
+   Copyright (C) 2013 Red Hat, Inc.
+   This file is part of elfutils.
+
+   This file is free software; you can redistribute it and/or modify
+   it under the terms of either
+
+     * the GNU Lesser General Public License as published by the Free
+       Software Foundation; either version 3 of the License, or (at
+       your option) any later version
+
+   or
+
+     * the GNU General Public License as published by the Free
+       Software Foundation; either version 2 of the License, or (at
+       your option) any later version
+
+   or both in parallel, as here.
+
+   elfutils is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received copies of the GNU General Public License and
+   the GNU Lesser General Public License along with this program.  If
+   not, see <http://www.gnu.org/licenses/>.  */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#if defined __i386__ || defined __x86_64__
+# include <sys/types.h>
+# include <sys/user.h>
+# include <sys/ptrace.h>
+#endif
+
+#define BACKEND i386_
+#include "libebl_CPU.h"
+
+bool
+i386_set_initial_registers_tid (struct Dwfl_Thread *thread, pid_t tid,
+				dwfl_thread_state_registers_t *setfunc)
+{
+#if !defined __i386__ && !defined __x86_64__
+  return false;
+#else /* __i386__ || __x86_64__ */
+  struct user_regs_struct user_regs;
+  if (ptrace (PTRACE_GETREGS, tid, NULL, &user_regs) != 0)
+    return false;
+  Dwarf_Word dwarf_regs[9];
+# if defined __i386__
+  dwarf_regs[0] = user_regs.eax;
+  dwarf_regs[1] = user_regs.ecx;
+  dwarf_regs[2] = user_regs.edx;
+  dwarf_regs[3] = user_regs.ebx;
+  dwarf_regs[4] = user_regs.esp;
+  dwarf_regs[5] = user_regs.ebp;
+  dwarf_regs[6] = user_regs.esi;
+  dwarf_regs[7] = user_regs.edi;
+  dwarf_regs[8] = user_regs.eip;
+# elif defined __x86_64__
+  dwarf_regs[0] = user_regs.rax;
+  dwarf_regs[1] = user_regs.rcx;
+  dwarf_regs[2] = user_regs.rdx;
+  dwarf_regs[3] = user_regs.rbx;
+  dwarf_regs[4] = user_regs.rsp;
+  dwarf_regs[5] = user_regs.rbp;
+  dwarf_regs[6] = user_regs.rsi;
+  dwarf_regs[7] = user_regs.rdi;
+  dwarf_regs[8] = user_regs.rip;
+# else /* (__i386__ || __x86_64__) && (!__i386__ && !__x86_64__) */
+#  error
+# endif /* (__i386__ || __x86_64__) && (!__i386__ && !__x86_64__) */
+  return setfunc (thread, 0, 9, dwarf_regs);
+#endif /* __i386__ || __x86_64__ */
+  return true;
+}
diff --git a/backends/x86_64_init.c b/backends/x86_64_init.c
index 67a5880..b885558 100644
--- a/backends/x86_64_init.c
+++ b/backends/x86_64_init.c
@@ -1,5 +1,5 @@
 /* Initialization of x86-64 specific backend library.
-   Copyright (C) 2002-2009 Red Hat, Inc.
+   Copyright (C) 2002-2009, 2013 Red Hat, Inc.
    This file is part of elfutils.
    Written by Ulrich Drepper <drepper@redhat.com>, 2002.
 
@@ -60,6 +60,9 @@ x86_64_init (elf, machine, eh, ehlen)
   HOOK (eh, auxv_info);
   HOOK (eh, disasm);
   HOOK (eh, abi_cfi);
+  /* gcc/config/ #define DWARF_FRAME_REGISTERS.  */
+  eh->frame_nregs = 17;
+  HOOK (eh, set_initial_registers_tid);
 
   return MODVERSION;
 }
diff --git a/backends/x86_64_initreg.c b/backends/x86_64_initreg.c
new file mode 100644
index 0000000..9f7c119
--- /dev/null
+++ b/backends/x86_64_initreg.c
@@ -0,0 +1,72 @@
+/* Fetch live process Dwfl_Frame from PID.
+   Copyright (C) 2013 Red Hat, Inc.
+   This file is part of elfutils.
+
+   This file is free software; you can redistribute it and/or modify
+   it under the terms of either
+
+     * the GNU Lesser General Public License as published by the Free
+       Software Foundation; either version 3 of the License, or (at
+       your option) any later version
+
+   or
+
+     * the GNU General Public License as published by the Free
+       Software Foundation; either version 2 of the License, or (at
+       your option) any later version
+
+   or both in parallel, as here.
+
+   elfutils is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received copies of the GNU General Public License and
+   the GNU Lesser General Public License along with this program.  If
+   not, see <http://www.gnu.org/licenses/>.  */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdlib.h>
+#ifdef __x86_64__
+# include <sys/user.h>
+# include <sys/ptrace.h>
+#endif
+
+#define BACKEND x86_64_
+#include "libebl_CPU.h"
+
+bool
+x86_64_set_initial_registers_tid (struct Dwfl_Thread *thread, pid_t tid,
+				  dwfl_thread_state_registers_t *setfunc)
+{
+#ifndef __x86_64__
+  return false;
+#else /* __x86_64__ */
+  struct user_regs_struct user_regs;
+  if (ptrace (PTRACE_GETREGS, tid, NULL, &user_regs) != 0)
+    return false;
+  Dwarf_Word dwarf_regs[17];
+  dwarf_regs[0] = user_regs.rax;
+  dwarf_regs[1] = user_regs.rdx;
+  dwarf_regs[2] = user_regs.rcx;
+  dwarf_regs[3] = user_regs.rbx;
+  dwarf_regs[4] = user_regs.rsi;
+  dwarf_regs[5] = user_regs.rdi;
+  dwarf_regs[6] = user_regs.rbp;
+  dwarf_regs[7] = user_regs.rsp;
+  dwarf_regs[8] = user_regs.r8;
+  dwarf_regs[9] = user_regs.r9;
+  dwarf_regs[10] = user_regs.r10;
+  dwarf_regs[11] = user_regs.r11;
+  dwarf_regs[12] = user_regs.r12;
+  dwarf_regs[13] = user_regs.r13;
+  dwarf_regs[14] = user_regs.r14;
+  dwarf_regs[15] = user_regs.r15;
+  dwarf_regs[16] = user_regs.rip;
+  return setfunc (thread, 0, 17, dwarf_regs);
+#endif /* __x86_64__ */
+}
diff --git a/configure.ac b/configure.ac
index b4c249c..54e761c 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..98ac6cf 100644
--- a/libdw/cfi.h
+++ b/libdw/cfi.h
@@ -1,5 +1,5 @@
 /* Internal definitions for libdw CFI interpreter.
-   Copyright (C) 2009-2010 Red Hat, Inc.
+   Copyright (C) 2009-2010, 2013 Red Hat, Inc.
    This file is part of elfutils.
 
    This file is free software; you can redistribute it and/or modify
@@ -150,8 +150,8 @@ struct dwarf_frame_register
   Dwarf_Sword value:(sizeof (Dwarf_Sword) * 8 - 3);
 };
 
-/* This holds everything we know about the state of the frame
-   at a particular PC location described by an FDE.  */
+/* This holds instructions for unwinding frame at a particular PC location
+   described by an FDE.  */
 struct Dwarf_Frame_s
 {
   /* This frame description covers PC values in [start, end).  */
diff --git a/libdw/libdw.map b/libdw/libdw.map
index 09eae6a..0130ca0 100644
--- a/libdw/libdw.map
+++ b/libdw/libdw.map
@@ -259,6 +259,16 @@ ELFUTILS_0.156 {
   global:
     # Replaced ELFUTILS_0.122 version, which has a wrapper without add_p_vaddr.
     dwfl_report_elf;
+    dwfl_attach_state;
+    dwfl_pid;
+    dwfl_thread_dwfl;
+    dwfl_thread_tid;
+    dwfl_frame_thread;
+    dwfl_thread_state_registers;
+    dwfl_thread_state_register_pc;
+    dwfl_next_thread;
+    dwfl_thread_getframes;
+    dwfl_frame_pc;
 } ELFUTILS_0.149;
 
 ELFUTILS_0.157 {
diff --git a/libdwfl/Makefile.am b/libdwfl/Makefile.am
index 3ef4dd6..0ba2542 100644
--- a/libdwfl/Makefile.am
+++ b/libdwfl/Makefile.am
@@ -2,7 +2,7 @@
 ##
 ## Process this file with automake to create Makefile.in
 ##
-## Copyright (C) 2005-2010 Red Hat, Inc.
+## Copyright (C) 2005-2010, 2013 Red Hat, Inc.
 ## This file is part of elfutils.
 ##
 ## This file is free software; you can redistribute it and/or modify
@@ -31,7 +31,7 @@
 ##
 include $(top_srcdir)/config/eu.am
 AM_CPPFLAGS += -I$(srcdir) -I$(srcdir)/../libelf -I$(srcdir)/../libebl \
-	   -I$(srcdir)/../libdw
+	    -I$(srcdir)/../libdw -I$(srcdir)/../libasm
 VERSION = 1
 
 noinst_LIBRARIES = libdwfl.a
@@ -68,7 +68,9 @@ libdwfl_a_SOURCES = dwfl_begin.c dwfl_end.c dwfl_error.c dwfl_version.c \
 		    dwfl_module_return_value_location.c \
 		    dwfl_module_register_names.c \
 		    dwfl_segment_report_module.c \
-		    link_map.c core-file.c open.c image-header.c
+		    link_map.c core-file.c open.c image-header.c \
+		    dwfl_frame.c dwfl_frame_unwind.c dwfl_frame_pc.c \
+		    dwfl_frame_pid.c dwfl_frame_core.c dwfl_frame_regs.c
 
 if ZLIB
 libdwfl_a_SOURCES += gzip.c
diff --git a/libdwfl/core-file.c b/libdwfl/core-file.c
index 7207591..cc1da6c 100644
--- a/libdwfl/core-file.c
+++ b/libdwfl/core-file.c
@@ -1,5 +1,5 @@
 /* Core file handling.
-   Copyright (C) 2008-2010 Red Hat, Inc.
+   Copyright (C) 2008-2010, 2013 Red Hat, Inc.
    This file is part of elfutils.
 
    This file is free software; you can redistribute it and/or modify
@@ -521,6 +521,14 @@ dwfl_core_file_report (Dwfl *dwfl, Elf *elf)
   /* We return the number of modules we found if we found any.
      If we found none, we return -1 instead of 0 if there was an
      error rather than just nothing found.  */
-  return sniffed || listed >= 0 ? listed + sniffed : listed;
+  int retval = sniffed || listed >= 0 ? listed + sniffed : listed;
+  if (retval > 0)
+    {
+      /* Possible error is ignored, DWFL still may be useful for non-unwinding
+	 operations.  */
+      __libdwfl_attach_state_for_core (dwfl, elf);
+    }
+
+  return retval;
 }
 INTDEF (dwfl_core_file_report)
diff --git a/libdwfl/dwfl_end.c b/libdwfl/dwfl_end.c
index 94fcfc6..33cae48 100644
--- a/libdwfl/dwfl_end.c
+++ b/libdwfl/dwfl_end.c
@@ -1,5 +1,5 @@
 /* Finish a session using libdwfl.
-   Copyright (C) 2005, 2008, 2012 Red Hat, Inc.
+   Copyright (C) 2005, 2008, 2012-2013 Red Hat, Inc.
    This file is part of elfutils.
 
    This file is free software; you can redistribute it and/or modify
@@ -34,6 +34,9 @@ dwfl_end (Dwfl *dwfl)
   if (dwfl == NULL)
     return;
 
+  if (dwfl->process)
+    __libdwfl_process_free (dwfl->process);
+
   free (dwfl->lookup_addr);
   free (dwfl->lookup_module);
   free (dwfl->lookup_segndx);
diff --git a/libdwfl/dwfl_frame.c b/libdwfl/dwfl_frame.c
new file mode 100644
index 0000000..58878f9
--- /dev/null
+++ b/libdwfl/dwfl_frame.c
@@ -0,0 +1,357 @@
+/* Get Dwarf Frame state for target PID or core file.
+   Copyright (C) 2013 Red Hat, Inc.
+   This file is part of elfutils.
+
+   This file is free software; you can redistribute it and/or modify
+   it under the terms of either
+
+     * the GNU Lesser General Public License as published by the Free
+       Software Foundation; either version 3 of the License, or (at
+       your option) any later version
+
+   or
+
+     * the GNU General Public License as published by the Free
+       Software Foundation; either version 2 of the License, or (at
+       your option) any later version
+
+   or both in parallel, as here.
+
+   elfutils is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received copies of the GNU General Public License and
+   the GNU Lesser General Public License along with this program.  If
+   not, see <http://www.gnu.org/licenses/>.  */
+
+#include "libdwflP.h"
+#include <sys/ptrace.h>
+#include <unistd.h>
+
+#ifndef MIN
+# define MIN(a, b) ((a) < (b) ? (a) : (b))
+#endif
+
+/* Set STATE->pc_set from STATE->regs according to the backend.  Return true on
+   success, false on error.  */
+static bool
+state_fetch_pc (Dwfl_Frame *state)
+{
+  switch (state->pc_state)
+    {
+    case DWFL_FRAME_STATE_PC_SET:
+      return true;
+    case DWFL_FRAME_STATE_PC_UNDEFINED:
+      abort ();
+    case DWFL_FRAME_STATE_ERROR:;
+      Ebl *ebl = state->thread->process->ebl;
+      Dwarf_CIE abi_info;
+      if (ebl_abi_cfi (ebl, &abi_info) != 0)
+	{
+	  __libdwfl_seterrno (DWFL_E_LIBEBL);
+	  return false;
+	}
+      unsigned ra = abi_info.return_address_register;
+      /* dwarf_frame_state_reg_is_set is not applied here.  */
+      if (ra >= ebl_frame_nregs (ebl))
+	{
+	  __libdwfl_seterrno (DWFL_E_LIBEBL_BAD);
+	  return false;
+	}
+      state->pc = state->regs[ra];
+      state->pc_state = DWFL_FRAME_STATE_PC_SET;
+      return true;
+    }
+  abort ();
+}
+
+/* Do not call it on your own, to be used by thread_* functions only.  */
+
+static void
+state_free (Dwfl_Frame *state)
+{
+  Dwfl_Thread *thread = state->thread;
+  assert (thread->unwound == state);
+  thread->unwound = state->unwound;
+  free (state);
+}
+
+/* Do not call it on your own, to be used by thread_* functions only.  */
+
+static Dwfl_Frame *
+state_alloc (Dwfl_Thread *thread)
+{
+  assert (thread->unwound == NULL);
+  Ebl *ebl = thread->process->ebl;
+  size_t nregs = ebl_frame_nregs (ebl);
+  if (nregs == 0)
+    return NULL;
+  assert (nregs < sizeof (((Dwfl_Frame *) NULL)->regs_set) * 8);
+  Dwfl_Frame *state = malloc (sizeof (*state) + sizeof (*state->regs) * nregs);
+  if (state == NULL)
+    return NULL;
+  state->thread = thread;
+  state->signal_frame = false;
+  state->pc_state = DWFL_FRAME_STATE_ERROR;
+  memset (state->regs_set, 0, sizeof (state->regs_set));
+  thread->unwound = state;
+  state->unwound = NULL;
+  return state;
+}
+
+/* Free and unlink THREAD from the internal lists.  PREV_THREAD must be NULL if
+   THREAD was the first one or PREV_THREAD must be the preceding thread.  */
+static void
+thread_free (Dwfl_Thread *thread, Dwfl_Thread *prev_thread)
+{
+  Dwfl_Process *process = thread->process;
+  assert (prev_thread == NULL || prev_thread->process == process);
+  assert (prev_thread != NULL || process->thread == thread);
+  assert (prev_thread == NULL || prev_thread->next == thread);
+  if (thread->thread_detach_needed)
+    {
+      assert (thread->tid > 0);
+      if (process->callbacks->thread_detach)
+	process->callbacks->thread_detach (thread, thread->callbacks_arg);
+    }
+  while (thread->unwound)
+    state_free (thread->unwound);
+  if (prev_thread == NULL)
+    process->thread = thread->next;
+  else
+    prev_thread->next = thread->next;
+  free (thread);
+}
+
+/* Allocate new Dwfl_Thread and link it to PROCESS.  PREV_THREAD must be NULL
+   if this is the first thread for PROCESS, otherwise PREV_THREAD must be the
+   last thread of PROCESS.  */
+static Dwfl_Thread *
+thread_alloc (Dwfl_Process *process, Dwfl_Thread *prev_thread)
+{
+  assert (prev_thread == NULL || prev_thread->process == process);
+  assert (prev_thread != NULL || process->thread == NULL);
+  assert (prev_thread == NULL || prev_thread->next == NULL);
+  Dwfl_Thread *thread = malloc (sizeof (*thread));
+  if (thread == NULL)
+    return NULL;
+  thread->process = process;
+  thread->unwound = NULL;
+  thread->tid = 0;
+  thread->next = NULL;
+  if (prev_thread == NULL)
+    process->thread = thread;
+  else
+    prev_thread->next = thread;
+  return thread;
+}
+
+void
+internal_function
+__libdwfl_process_free (Dwfl_Process *process)
+{
+  Dwfl *dwfl = process->dwfl;
+  if (process->callbacks->detach != NULL)
+    process->callbacks->detach (dwfl, process->callbacks_arg);
+  while (process->thread)
+    thread_free (process->thread, NULL);
+  assert (dwfl->process == process);
+  dwfl->process = NULL;
+  free (process);
+}
+
+/* Allocate new Dwfl_Process for DWFL.  */
+static void
+process_alloc (Dwfl *dwfl)
+{
+  Dwfl_Process *process = malloc (sizeof (*process));
+  if (process == NULL)
+    return;
+  process->dwfl = dwfl;
+  process->thread = NULL;
+  dwfl->process = process;
+}
+
+bool
+dwfl_attach_state (Dwfl *dwfl, Ebl *ebl, pid_t pid,
+		   const Dwfl_Thread_Callbacks *thread_callbacks, void *arg)
+{
+  if (thread_callbacks == NULL || thread_callbacks->next_thread == NULL
+      || thread_callbacks->set_initial_registers == NULL)
+    {
+      __libdwfl_seterrno (DWFL_E_UNKNOWN_ERROR);
+      return false;
+    }
+  if (dwfl->process != NULL)
+    {
+      __libdwfl_seterrno (DWFL_E_ATTACH_STATE_CONFLICT);
+      return false;
+    }
+  if (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;
+	  break;
+	}
+      if (ebl == NULL)
+	{
+	  /* Not identified EBL from any of the modules.  */
+	  __libdwfl_seterrno (DWFL_E_PROCESS_NO_ARCH);
+	  return false;
+	}
+    }
+  process_alloc (dwfl);
+  Dwfl_Process *process = dwfl->process;
+  if (process == NULL)
+    {
+      __libdwfl_seterrno (DWFL_E_NOMEM);
+      return false;
+    }
+  process->ebl = ebl;
+  process->pid = pid;
+  process->callbacks = thread_callbacks;
+  process->callbacks_arg = arg;
+  return true;
+}
+INTDEF(dwfl_attach_state)
+
+pid_t
+dwfl_pid (Dwfl *dwfl)
+{
+  if (dwfl->process == NULL)
+    {
+      __libdwfl_seterrno (DWFL_E_NO_ATTACH_STATE);
+      return -1;
+    }
+  return dwfl->process->pid;
+}
+INTDEF(dwfl_pid)
+
+Dwfl *
+dwfl_thread_dwfl (Dwfl_Thread *thread)
+{
+  return thread->process->dwfl;
+}
+INTDEF(dwfl_thread_dwfl)
+
+pid_t
+dwfl_thread_tid (Dwfl_Thread *thread)
+{
+  return thread->tid;
+}
+INTDEF(dwfl_thread_tid)
+
+Dwfl_Thread *
+dwfl_frame_thread (Dwfl_Frame *state)
+{
+  return state->thread;
+}
+INTDEF(dwfl_frame_thread)
+
+Dwfl_Thread *
+dwfl_next_thread (Dwfl *dwfl, Dwfl_Thread *prev_thread)
+{
+  assert (prev_thread == NULL || prev_thread->process->dwfl == dwfl);
+  if (prev_thread != NULL && prev_thread->next != NULL)
+    return prev_thread->next;
+  Dwfl_Process *process = dwfl->process;
+  if (process == NULL)
+    {
+      __libdwfl_seterrno (DWFL_E_NO_ATTACH_STATE);
+      return NULL;
+    }
+  Dwfl_Thread *nthread = thread_alloc (process, prev_thread);
+  if (nthread == NULL)
+    {
+      __libdwfl_seterrno (DWFL_E_NOMEM);
+      return NULL;
+    }
+  nthread->tid = process->callbacks->next_thread (dwfl, nthread,
+						  process->callbacks_arg,
+						  &nthread->callbacks_arg);
+  if (nthread->tid < 0)
+    {
+      thread_free (nthread, prev_thread);
+      __libdwfl_seterrno (DWFL_E_NEXT_THREAD_FAIL);
+      return NULL;
+    }
+  if (nthread->tid == 0)
+    {
+      thread_free (nthread, prev_thread);
+      __libdwfl_seterrno (DWFL_E_NOERROR);
+      return NULL;
+    }
+  return nthread;
+}
+INTDEF(dwfl_next_thread)
+
+int
+dwfl_thread_getframes (Dwfl_Thread *thread,
+		       int (*callback) (Dwfl_Frame *state, void *arg),
+		       void *arg)
+{
+  assert (thread->unwound == NULL);
+  if (state_alloc (thread) == NULL)
+    {
+      __libdwfl_seterrno (DWFL_E_NOMEM);
+      return -1;
+    }
+  Dwfl_Process *process = thread->process;
+  if (! process->callbacks->set_initial_registers (thread,
+						   thread->callbacks_arg))
+    {
+      while (thread->unwound)
+	state_free (thread->unwound);
+      return -1;
+    }
+  thread->thread_detach_needed = true;
+  if (! state_fetch_pc (thread->unwound))
+    {
+      assert (thread->thread_detach_needed);
+      if (process->callbacks->thread_detach)
+	process->callbacks->thread_detach (thread, thread->callbacks_arg);
+      thread->thread_detach_needed = false;
+      while (thread->unwound)
+	state_free (thread->unwound);
+      return -1;
+    }
+
+  Dwfl_Frame *state = thread->unwound;
+  do
+    {
+      int err = callback (state, arg);
+      if (err != DWARF_CB_OK)
+	{
+	  assert (thread->thread_detach_needed);
+	  if (process->callbacks->thread_detach)
+	    process->callbacks->thread_detach (thread, thread->callbacks_arg);
+	  thread->thread_detach_needed = false;
+	  while (thread->unwound)
+	    state_free (thread->unwound);
+	  return err;
+	}
+      __libdwfl_frame_unwind (state);
+      state = state->unwound;
+    }
+  while (state && state->pc_state == DWFL_FRAME_STATE_PC_SET);
+
+  Dwfl_Error err = dwfl_errno ();
+  assert (thread->thread_detach_needed);
+  if (process->callbacks->thread_detach)
+    process->callbacks->thread_detach (thread, thread->callbacks_arg);
+  thread->thread_detach_needed = false;
+  if (state == NULL || state->pc_state == DWFL_FRAME_STATE_ERROR)
+    {
+      __libdwfl_seterrno (err);
+      return -1;
+    }
+  assert (state->pc_state == DWFL_FRAME_STATE_PC_UNDEFINED);
+  return 0;
+}
+INTDEF(dwfl_thread_getframes)
diff --git a/libdwfl/dwfl_frame_core.c b/libdwfl/dwfl_frame_core.c
new file mode 100644
index 0000000..f9f8a41
--- /dev/null
+++ b/libdwfl/dwfl_frame_core.c
@@ -0,0 +1,369 @@
+/* Get Dwarf Frame state for target core file.
+   Copyright (C) 2013 Red Hat, Inc.
+   This file is part of elfutils.
+
+   This file is free software; you can redistribute it and/or modify
+   it under the terms of either
+
+     * the GNU Lesser General Public License as published by the Free
+       Software Foundation; either version 3 of the License, or (at
+       your option) any later version
+
+   or
+
+     * the GNU General Public License as published by the Free
+       Software Foundation; either version 2 of the License, or (at
+       your option) any later version
+
+   or both in parallel, as here.
+
+   elfutils is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received copies of the GNU General Public License and
+   the GNU Lesser General Public License along with this program.  If
+   not, see <http://www.gnu.org/licenses/>.  */
+
+#include "libdwflP.h"
+#include <fcntl.h>
+#include "system.h"
+
+#ifndef MIN
+# define MIN(a, b) ((a) < (b) ? (a) : (b))
+#endif
+
+struct core_arg
+{
+  Elf *core;
+  Elf_Data *note_data;
+  size_t thread_note_offset;
+  Ebl *ebl;
+};
+
+struct thread_arg
+{
+  struct core_arg *core_arg;
+  size_t note_offset;
+};
+
+static bool
+core_memory_read (Dwfl *dwfl, Dwarf_Addr addr, Dwarf_Word *result,
+		  void *dwfl_arg)
+{
+  Dwfl_Process *process = dwfl->process;
+  struct core_arg *core_arg = dwfl_arg;
+  Elf *core = core_arg->core;
+  assert (core != NULL);
+  static size_t phnum;
+  if (elf_getphdrnum (core, &phnum) < 0)
+    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)
+	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;
+    }
+  return false;
+}
+
+static pid_t
+core_next_thread (Dwfl *dwfl __attribute__ ((unused)),
+		  Dwfl_Thread *nthread __attribute__ ((unused)), void *dwfl_arg,
+		  void **thread_argp)
+{
+  struct core_arg *core_arg = dwfl_arg;
+  Elf *core = core_arg->core;
+  GElf_Nhdr nhdr;
+  size_t name_offset;
+  size_t desc_offset;
+  Elf_Data *note_data = core_arg->note_data;
+  size_t offset;
+  while (offset = core_arg->thread_note_offset, offset < note_data->d_size
+	 && (core_arg->thread_note_offset = gelf_getnote (note_data, offset,
+							  &nhdr, &name_offset,
+							  &desc_offset)) > 0)
+    {
+      /* Do not check NAME for now, help broken Linux kernels.  */
+      const char *name = note_data->d_buf + name_offset;
+      const char *desc = note_data->d_buf + desc_offset;
+      GElf_Word regs_offset;
+      size_t nregloc;
+      const Ebl_Register_Location *reglocs;
+      size_t nitems;
+      const Ebl_Core_Item *items;
+      if (! ebl_core_note (core_arg->ebl, &nhdr, name,
+			   &regs_offset, &nregloc, &reglocs, &nitems, &items))
+	{
+	  /* This note may be just not recognized, skip it.  */
+	  continue;
+	}
+      if (nhdr.n_type != NT_PRSTATUS)
+	continue;
+      const Ebl_Core_Item *item;
+      for (item = items; item < items + nitems; item++)
+	if (strcmp (item->name, "pid") == 0)
+	  break;
+      if (item == items + nitems)
+	continue;
+      uint32_t val32 = *(const uint32_t *) (desc + item->offset);
+      val32 = (elf_getident (core, NULL)[EI_DATA] == ELFDATA2MSB
+		? be32toh (val32) : le32toh (val32));
+      pid_t tid = (int32_t) val32;
+      eu_static_assert (sizeof val32 <= sizeof tid);
+      struct thread_arg *thread_arg = malloc (sizeof (*thread_arg));
+      if (thread_arg == NULL)
+	{
+	  __libdwfl_seterrno (DWFL_E_NOMEM);
+	  return 0;
+	}
+      thread_arg->core_arg = core_arg;
+      thread_arg->note_offset = offset;
+      *thread_argp = thread_arg;
+      return tid;
+    }
+  return 0;
+}
+
+static bool
+core_set_initial_registers (Dwfl_Thread *thread, void *thread_arg_voidp)
+{
+  struct thread_arg *thread_arg = thread_arg_voidp;
+  struct core_arg *core_arg = thread_arg->core_arg;
+  Elf *core = core_arg->core;
+  size_t offset = thread_arg->note_offset;
+  GElf_Nhdr nhdr;
+  size_t name_offset;
+  size_t desc_offset;
+  Elf_Data *note_data = core_arg->note_data;
+  size_t nregs = ebl_frame_nregs (core_arg->ebl);
+  assert (offset < note_data->d_size);
+  size_t getnote_err = gelf_getnote (note_data, offset, &nhdr, &name_offset,
+				     &desc_offset);
+  assert (getnote_err != 0);
+  /* Do not check NAME for now, help broken Linux kernels.  */
+  const char *name = note_data->d_buf + name_offset;
+  const char *desc = note_data->d_buf + desc_offset;
+  GElf_Word regs_offset;
+  size_t nregloc;
+  const Ebl_Register_Location *reglocs;
+  size_t nitems;
+  const Ebl_Core_Item *items;
+  int core_note_err = ebl_core_note (core_arg->ebl, &nhdr, name, &regs_offset,
+				     &nregloc, &reglocs, &nitems, &items);
+  assert (core_note_err != 0);
+  assert (nhdr.n_type == NT_PRSTATUS);
+  const Ebl_Core_Item *item;
+  for (item = items; item < items + nitems; item++)
+    if (strcmp (item->name, "pid") == 0)
+      break;
+  assert (item < items + nitems);
+  pid_t tid;
+  {
+    uint32_t val32 = *(const uint32_t *) (desc + item->offset);
+    val32 = (elf_getident (core, NULL)[EI_DATA] == ELFDATA2MSB
+	     ? be32toh (val32) : le32toh (val32));
+    tid = (int32_t) val32;
+    eu_static_assert (sizeof val32 <= sizeof tid);
+  }
+  assert (tid == INTUSE(dwfl_thread_tid) (thread));
+  desc += regs_offset;
+  for (size_t regloci = 0; regloci < nregloc; regloci++)
+    {
+      const Ebl_Register_Location *regloc = reglocs + regloci;
+      if (regloc->regno >= nregs)
+	continue;
+      assert (regloc->bits == 32 || regloc->bits == 64);
+      const char *reg_desc = desc + regloc->offset;
+      for (unsigned regno = regloc->regno;
+	   regno < MIN (regloc->regno + (regloc->count ?: 1U), nregs);
+	   regno++)
+	{
+	  /* PPC provides DWARF register 65 irrelevant for
+	     CFI which clashes with register 108 (LR) we need.
+	     LR (108) is provided earlier (in NT_PRSTATUS) than the # 65.
+	     FIXME: It depends now on their order in core notes.
+	     FIXME: It uses private function.  */
+	  if (dwfl_frame_reg_get (thread->unwound, regno, NULL))
+	    continue;
+	  Dwarf_Word val;
+	  switch (regloc->bits)
+	  {
+	    case 32:;
+	      uint32_t val32 = *(const uint32_t *) reg_desc;
+	      reg_desc += sizeof val32;
+	      val32 = (elf_getident (core, NULL)[EI_DATA] == ELFDATA2MSB
+		       ? be32toh (val32) : le32toh (val32));
+	      /* Do a host width conversion.  */
+	      val = val32;
+	      break;
+	    case 64:;
+	      uint64_t val64 = *(const uint64_t *) reg_desc;
+	      reg_desc += sizeof val64;
+	      val64 = (elf_getident (core, NULL)[EI_DATA] == ELFDATA2MSB
+		       ? be64toh (val64) : le64toh (val64));
+	      assert (sizeof (*thread->unwound->regs) == sizeof val64);
+	      val = val64;
+	      break;
+	    default:
+	      abort ();
+	  }
+	  /* Registers not valid for CFI are just ignored.  */
+	  INTUSE(dwfl_thread_state_registers) (thread, regno, 1, &val);
+	  reg_desc += regloc->pad;
+	}
+    }
+  return true;
+}
+
+static void
+core_detach (Dwfl *dwfl __attribute__ ((unused)), void *dwfl_arg)
+{
+  struct core_arg *core_arg = dwfl_arg;
+  ebl_closebackend (core_arg->ebl);
+  free (core_arg);
+}
+
+static const Dwfl_Thread_Callbacks core_thread_callbacks = 
+{
+  core_next_thread,
+  core_memory_read,
+  core_set_initial_registers,
+  core_detach,
+  NULL, /* core_thread_detach */
+};
+
+bool
+internal_function
+__libdwfl_attach_state_for_core (Dwfl *dwfl, Elf *core)
+{
+  Ebl *ebl = ebl_openbackend (core);
+  if (ebl == NULL)
+    {
+      __libdwfl_seterrno (DWFL_E_LIBEBL);
+      return false;
+    }
+  size_t nregs = ebl_frame_nregs (ebl);
+  if (nregs == 0)
+    {
+      ebl_closebackend (ebl);
+      __libdwfl_seterrno (DWFL_E_LIBEBL);
+      return false;
+    }
+  GElf_Ehdr ehdr_mem, *ehdr = gelf_getehdr (core, &ehdr_mem);
+  if (ehdr == NULL)
+    {
+      ebl_closebackend (ebl);
+      __libdwfl_seterrno (DWFL_E_LIBELF);
+      return false;
+    }
+  assert (ehdr->e_type == ET_CORE);
+  size_t phnum;
+  if (elf_getphdrnum (core, &phnum) < 0)
+    {
+      ebl_closebackend (ebl);
+      __libdwfl_seterrno (DWFL_E_LIBELF);
+      return false;
+    }
+  pid_t pid = -1;
+  Elf_Data *note_data = NULL;
+  for (size_t cnt = 0; cnt < phnum; ++cnt)
+    {
+      GElf_Phdr phdr_mem, *phdr = gelf_getphdr (core, cnt, &phdr_mem);
+      if (phdr != NULL && phdr->p_type == PT_NOTE)
+	{
+	  note_data = elf_getdata_rawchunk (core, phdr->p_offset,
+					    phdr->p_filesz, ELF_T_NHDR);
+	  break;
+	}
+    }
+  if (note_data == NULL)
+    {
+      ebl_closebackend (ebl);
+      __libdwfl_seterrno (DWFL_E_LIBELF);
+      return NULL;
+    }
+  size_t offset = 0;
+  GElf_Nhdr nhdr;
+  size_t name_offset;
+  size_t desc_offset;
+  while (offset < note_data->d_size
+	 && (offset = gelf_getnote (note_data, offset,
+				    &nhdr, &name_offset, &desc_offset)) > 0)
+    {
+      /* Do not check NAME for now, help broken Linux kernels.  */
+      const char *name = note_data->d_buf + name_offset;
+      const char *desc = note_data->d_buf + desc_offset;
+      GElf_Word regs_offset;
+      size_t nregloc;
+      const Ebl_Register_Location *reglocs;
+      size_t nitems;
+      const Ebl_Core_Item *items;
+      if (! ebl_core_note (ebl, &nhdr, name,
+			   &regs_offset, &nregloc, &reglocs, &nitems, &items))
+	{
+	  /* This note may be just not recognized, skip it.  */
+	  continue;
+	}
+      if (nhdr.n_type != NT_PRPSINFO)
+	continue;
+      const Ebl_Core_Item *item;
+      for (item = items; item < items + nitems; item++)
+	if (strcmp (item->name, "pid") == 0)
+	  break;
+      if (item == items + nitems)
+	continue;
+      uint32_t val32 = *(const uint32_t *) (desc + item->offset);
+      val32 = (elf_getident (core, NULL)[EI_DATA] == ELFDATA2MSB
+		? be32toh (val32) : le32toh (val32));
+      pid = (int32_t) val32;
+      eu_static_assert (sizeof val32 <= sizeof pid);
+      break;
+    }
+  if (pid == -1)
+    {
+      /* No valid NT_PRPSINFO recognized in this CORE.  */
+      ebl_closebackend (ebl);
+      __libdwfl_seterrno (DWFL_E_BADELF);
+      return false;
+    }
+  struct core_arg *core_arg = malloc (sizeof *core_arg);
+  if (core_arg == NULL)
+    {
+      ebl_closebackend (ebl);
+      __libdwfl_seterrno (DWFL_E_NOMEM);
+      return false;
+    }
+  core_arg->core = core;
+  core_arg->note_data = note_data;
+  core_arg->thread_note_offset = 0;
+  core_arg->ebl = ebl;
+  if (! INTUSE(dwfl_attach_state) (dwfl, ebl, pid, &core_thread_callbacks, core_arg))
+    {
+      free (core_arg);
+      ebl_closebackend (ebl);
+      return false;
+    }
+  return true;
+}
diff --git a/libdwfl/dwfl_frame_pc.c b/libdwfl/dwfl_frame_pc.c
new file mode 100644
index 0000000..9781777
--- /dev/null
+++ b/libdwfl/dwfl_frame_pc.c
@@ -0,0 +1,61 @@
+/* Get return address register value for frame.
+   Copyright (C) 2013 Red Hat, Inc.
+   This file is part of elfutils.
+
+   This file is free software; you can redistribute it and/or modify
+   it under the terms of either
+
+     * the GNU Lesser General Public License as published by the Free
+       Software Foundation; either version 3 of the License, or (at
+       your option) any later version
+
+   or
+
+     * the GNU General Public License as published by the Free
+       Software Foundation; either version 2 of the License, or (at
+       your option) any later version
+
+   or both in parallel, as here.
+
+   elfutils is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received copies of the GNU General Public License and
+   the GNU Lesser General Public License along with this program.  If
+   not, see <http://www.gnu.org/licenses/>.  */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include "libdwflP.h"
+
+bool
+dwfl_frame_pc (Dwfl_Frame *state, Dwarf_Addr *pc, bool *isactivation)
+{
+  assert (state->pc_state == DWFL_FRAME_STATE_PC_SET);
+  *pc = state->pc;
+  if (isactivation)
+    {
+      /* Bottom frame?  */
+      if (state == state->thread->unwound)
+	*isactivation = true;
+      /* *ISACTIVATION is logical or of current and previous frame state.  */
+      else if (state->signal_frame)
+	*isactivation = true;
+      else
+	{
+	  /* Not affected by unsuccessfully unwound frame.  */
+	  __libdwfl_frame_unwind (state);
+	  if (state->unwound == NULL
+	      || state->unwound->pc_state != DWFL_FRAME_STATE_PC_SET)
+	    *isactivation = false;
+	  else
+	    *isactivation = state->unwound->signal_frame;
+	}
+    }
+  return true;
+}
+INTDEF (dwfl_frame_pc)
diff --git a/libdwfl/dwfl_frame_pid.c b/libdwfl/dwfl_frame_pid.c
new file mode 100644
index 0000000..e91e82f
--- /dev/null
+++ b/libdwfl/dwfl_frame_pid.c
@@ -0,0 +1,221 @@
+/* Get Dwarf Frame state for target live PID process.
+   Copyright (C) 2013 Red Hat, Inc.
+   This file is part of elfutils.
+
+   This file is free software; you can redistribute it and/or modify
+   it under the terms of either
+
+     * the GNU Lesser General Public License as published by the Free
+       Software Foundation; either version 3 of the License, or (at
+       your option) any later version
+
+   or
+
+     * the GNU General Public License as published by the Free
+       Software Foundation; either version 2 of the License, or (at
+       your option) any later version
+
+   or both in parallel, as here.
+
+   elfutils is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received copies of the GNU General Public License and
+   the GNU Lesser General Public License along with this program.  If
+   not, see <http://www.gnu.org/licenses/>.  */
+
+#include "libdwflP.h"
+#include <sys/ptrace.h>
+#include <sys/wait.h>
+#include <dirent.h>
+
+#ifndef MAX
+# define MAX(a, b) ((a) > (b) ? (a) : (b))
+#endif
+
+struct pid_arg
+{
+  DIR *dir;
+  size_t tids_attached_size, tids_attached_used;
+  pid_t *tids_attached;
+};
+
+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
+pid_memory_read (Dwfl *dwfl, Dwarf_Addr addr, Dwarf_Word *result, void *arg)
+{
+  struct pid_arg *pid_arg = arg;
+  assert (pid_arg->tids_attached_used > 0);
+  pid_t tid = pid_arg->tids_attached[0];
+  Dwfl_Process *process = dwfl->process;
+  if (process->ebl->class == ELFCLASS64)
+    {
+      errno = 0;
+      *result = ptrace (PTRACE_PEEKDATA, tid, (void *) (uintptr_t) addr, NULL);
+      return errno == 0;
+    }
+#if SIZEOF_LONG == 8
+  /* We do not care about reads unaliged to 4 bytes boundary.
+     But 0x...ffc read of 8 bytes could overrun a page.  */
+  bool lowered = (addr & 4) != 0;
+  if (lowered)
+    addr -= 4;
+#endif /* SIZEOF_LONG == 8 */
+  errno = 0;
+  *result = ptrace (PTRACE_PEEKDATA, tid, (void *) (uintptr_t) addr, NULL);
+  if (errno != 0)
+    return false;
+#if SIZEOF_LONG == 8
+# if BYTE_ORDER == BIG_ENDIAN
+  if (! lowered)
+    *result >>= 32;
+# else
+  if (lowered)
+    *result >>= 32;
+# endif
+#endif /* SIZEOF_LONG == 8 */
+  *result &= 0xffffffff;
+  return true;
+}
+
+static pid_t
+pid_next_thread (Dwfl *dwfl __attribute__ ((unused)),
+		 Dwfl_Thread *nthread __attribute__ ((unused)), void *dwfl_arg,
+		 void **thread_argp)
+{
+  struct pid_arg *pid_arg = dwfl_arg;
+  struct dirent *dirent;
+  do
+    {
+      errno = 0;
+      dirent = readdir (pid_arg->dir);
+      if (dirent == NULL)
+	return errno == 0 ? 0 : -1;
+    }
+  while (strcmp (dirent->d_name, ".") == 0
+	 || strcmp (dirent->d_name, "..") == 0);
+  char *end;
+  errno = 0;
+  long tidl = strtol (dirent->d_name, &end, 10);
+  if (errno != 0)
+    return -1;
+  pid_t tid = tidl;
+  if (tidl <= 0 || (end && *end) || tid != tidl)
+    return -1;
+  *thread_argp = dwfl_arg;
+  return tid;
+}
+
+static bool
+pid_set_initial_registers (Dwfl_Thread *thread, void *thread_arg)
+{
+  struct pid_arg *pid_arg = thread_arg;
+  pid_t tid = INTUSE(dwfl_thread_tid) (thread);
+  if (! ptrace_attach (tid))
+    return false;
+  if (pid_arg->tids_attached_used == pid_arg->tids_attached_size)
+    {
+      pid_arg->tids_attached_size *= 2;
+      pid_arg->tids_attached_size = MAX (64, pid_arg->tids_attached_size);
+      pid_arg->tids_attached = realloc (pid_arg->tids_attached,
+					(pid_arg->tids_attached_size
+					 * sizeof *pid_arg->tids_attached));
+    }
+  pid_arg->tids_attached[pid_arg->tids_attached_used++] = tid;
+  Dwfl_Process *process = thread->process;
+  Ebl *ebl = process->ebl;
+  return ebl_set_initial_registers_tid (ebl, thread, tid,
+					INTUSE(dwfl_thread_state_registers));
+}
+
+static void
+pid_detach (Dwfl *dwfl __attribute__ ((unused)), void *dwfl_arg)
+{
+  struct pid_arg *pid_arg = dwfl_arg;
+  closedir (pid_arg->dir);
+  free (pid_arg->tids_attached);
+  free (pid_arg);
+}
+
+static void
+pid_thread_detach (Dwfl_Thread *thread, void *thread_arg)
+{
+  struct pid_arg *pid_arg = thread_arg;
+  pid_t tid = INTUSE(dwfl_thread_tid) (thread);
+  size_t ix;
+  for (ix = 0; ix < pid_arg->tids_attached_used; ix++)
+    if (pid_arg->tids_attached[ix] == tid)
+      break;
+  assert (ix < pid_arg->tids_attached_used);
+  pid_arg->tids_attached[ix]
+    = pid_arg->tids_attached[--pid_arg->tids_attached_used];
+  ptrace (PTRACE_DETACH, tid, NULL, NULL);
+}
+
+static const Dwfl_Thread_Callbacks pid_thread_callbacks = 
+{
+  pid_next_thread,
+  pid_memory_read,
+  pid_set_initial_registers,
+  pid_detach,
+  pid_thread_detach,
+};
+
+bool
+internal_function
+__libdwfl_attach_state_for_pid (Dwfl *dwfl, pid_t pid)
+{
+  char dirname[64];
+  int i = snprintf (dirname, sizeof (dirname), "/proc/%ld/task", (long) pid);
+  assert (i > 0 && i < (ssize_t) sizeof (dirname) - 1);
+  DIR *dir = opendir (dirname);
+  if (dir == NULL)
+    {
+      __libdwfl_seterrno (DWFL_E_PARSE_PROC);
+      return NULL;
+    }
+  struct pid_arg *pid_arg = malloc (sizeof *pid_arg);
+  if (pid_arg == NULL)
+    {
+      closedir (dir);
+      __libdwfl_seterrno (DWFL_E_NOMEM);
+      return false;
+    }
+  pid_arg->dir = dir;
+  pid_arg->tids_attached_size = 0;
+  pid_arg->tids_attached_used = 0;
+  pid_arg->tids_attached = NULL;
+  if (! INTUSE(dwfl_attach_state) (dwfl, NULL, pid, &pid_thread_callbacks, pid_arg))
+    {
+      free (pid_arg);
+      return false;
+    }
+  return true;
+}
diff --git a/libdwfl/dwfl_frame_regs.c b/libdwfl/dwfl_frame_regs.c
new file mode 100644
index 0000000..c1a08ba
--- /dev/null
+++ b/libdwfl/dwfl_frame_regs.c
@@ -0,0 +1,55 @@
+/* Get Dwarf Frame state from modules present in DWFL.
+   Copyright (C) 2013 Red Hat, Inc.
+   This file is part of elfutils.
+
+   This file is free software; you can redistribute it and/or modify
+   it under the terms of either
+
+     * the GNU Lesser General Public License as published by the Free
+       Software Foundation; either version 3 of the License, or (at
+       your option) any later version
+
+   or
+
+     * the GNU General Public License as published by the Free
+       Software Foundation; either version 2 of the License, or (at
+       your option) any later version
+
+   or both in parallel, as here.
+
+   elfutils is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received copies of the GNU General Public License and
+   the GNU Lesser General Public License along with this program.  If
+   not, see <http://www.gnu.org/licenses/>.  */
+
+#include "libdwflP.h"
+
+bool
+dwfl_thread_state_registers (Dwfl_Thread *thread, const int firstreg,
+			     unsigned nregs, const Dwarf_Word *regs)
+{
+  Dwfl_Frame *state = thread->unwound;
+  assert (state && state->unwound == NULL);
+  for (unsigned regno = firstreg; regno < firstreg + nregs; regno++)
+    if (! dwfl_frame_reg_set (state, regno, regs[regno - firstreg]))
+      {
+	__libdwfl_seterrno (DWFL_E_INVALID_REGISTER);
+	return false;
+      }
+  return true;
+}
+INTDEF(dwfl_thread_state_registers)
+
+void
+dwfl_thread_state_register_pc (Dwfl_Thread *thread, Dwarf_Word pc)
+{
+  Dwfl_Frame *state = thread->unwound;
+  assert (state && state->unwound == NULL);
+  state->pc = pc;
+  state->pc_state = DWFL_FRAME_STATE_PC_SET;
+}
+INTDEF(dwfl_thread_state_register_pc)
diff --git a/libdwfl/dwfl_frame_unwind.c b/libdwfl/dwfl_frame_unwind.c
new file mode 100644
index 0000000..d1119fe
--- /dev/null
+++ b/libdwfl/dwfl_frame_unwind.c
@@ -0,0 +1,416 @@
+/* Get previous frame state for an existing frame state.
+   Copyright (C) 2013 Red Hat, Inc.
+   This file is part of elfutils.
+
+   This file is free software; you can redistribute it and/or modify
+   it under the terms of either
+
+     * the GNU Lesser General Public License as published by the Free
+       Software Foundation; either version 3 of the License, or (at
+       your option) any later version
+
+   or
+
+     * the GNU General Public License as published by the Free
+       Software Foundation; either version 2 of the License, or (at
+       your option) any later version
+
+   or both in parallel, as here.
+
+   elfutils is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received copies of the GNU General Public License and
+   the GNU Lesser General Public License along with this program.  If
+   not, see <http://www.gnu.org/licenses/>.  */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include "cfi.h"
+#include <stdlib.h>
+#include "libdwflP.h"
+#include "../libdw/dwarf.h"
+#include <sys/ptrace.h>
+
+#ifndef MAX
+# define MAX(a, b) ((a) > (b) ? (a) : (b))
+#endif
+
+static bool
+state_get_reg (Dwfl_Frame *state, unsigned regno, Dwarf_Addr *val)
+{
+  if (! dwfl_frame_reg_get (state, regno, val))
+    {
+      __libdwfl_seterrno (DWFL_E_INVALID_REGISTER);
+      return false;
+    }
+  return true;
+}
+
+static int
+bra_compar (const void *key_voidp, const void *elem_voidp)
+{
+  Dwarf_Word offset = (uintptr_t) key_voidp;
+  const Dwarf_Op *op = elem_voidp;
+  return (offset > op->offset) - (offset < op->offset);
+}
+
+/* FIXME: Handle bytecode deadlocks and overflows.  */
+
+static bool
+expr_eval (Dwfl_Frame *state, Dwarf_Frame *frame, const Dwarf_Op *ops,
+	   size_t nops, Dwarf_Addr *result)
+{
+  Dwfl_Process *process = state->thread->process;
+  if (nops == 0)
+    {
+      __libdwfl_seterrno (DWFL_E_INVALID_DWARF);
+      return false;
+    }
+  Dwarf_Addr *stack = NULL;
+  size_t stack_used = 0, stack_allocated = 0;
+  bool
+  push (Dwarf_Addr val)
+  {
+    if (stack_used == 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->callbacks->memory_read == NULL
+	    || ! process->callbacks->memory_read (process->dwfl, val1, &val1,
+						  process->callbacks_arg)
+	    || ! 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->callbacks->memory_read == NULL
+	  || ! process->callbacks->memory_read (process->dwfl, *result, result,
+						process->callbacks_arg)))
+    return false;
+  return true;
+}
+
+/* The logic is to call __libdwfl_seterrno for any CFI bytecode interpretation
+   error so one can easily catch the problem with a debugger.  Still there are
+   archs with invalid CFI for some registers where the registers are never used
+   later.  Therefore we continue unwinding leaving the registers undefined.
+
+   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 void
+handle_cfi (Dwfl_Frame *state, Dwarf_Addr pc, Dwarf_CFI *cfi)
+{
+  Dwarf_Frame *frame;
+  if (INTUSE(dwarf_cfi_addrframe) (cfi, pc, &frame) != 0)
+    {
+      __libdwfl_seterrno (DWFL_E_LIBDW);
+      return;
+    }
+  Dwfl_Thread *thread = state->thread;
+  Dwfl_Process *process = thread->process;
+  Ebl *ebl = process->ebl;
+  size_t nregs = ebl_frame_nregs (ebl);
+  Dwfl_Frame *unwound;
+  unwound = malloc (sizeof (*unwound) + sizeof (*unwound->regs) * nregs);
+  state->unwound = unwound;
+  unwound->thread = thread;
+  unwound->unwound = NULL;
+  unwound->signal_frame = frame->fde->cie->signal_frame;
+  unwound->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_reg_set (unwound, regno, regval))
+	{
+	  __libdwfl_seterrno (DWFL_E_INVALID_REGISTER);
+	  continue;
+	}
+    }
+  if (unwound->pc_state == DWFL_FRAME_STATE_ERROR
+      && dwfl_frame_reg_get (unwound, frame->fde->cie->return_address_register,
+			     &unwound->pc))
+    {
+      /* PPC32 __libc_start_main properly CFI-unwinds PC as zero.  Currently
+	 none of the archs supported for unwinding have zero as a valid PC.  */
+      if (unwound->pc == 0)
+	unwound->pc_state = DWFL_FRAME_STATE_PC_UNDEFINED;
+      else
+	unwound->pc_state = DWFL_FRAME_STATE_PC_SET;
+    }
+}
+
+void
+internal_function
+__libdwfl_frame_unwind (Dwfl_Frame *state)
+{
+  if (state->unwound)
+    return;
+  Dwarf_Addr pc;
+  bool ok = INTUSE(dwfl_frame_pc) (state, &pc, NULL);
+  assert (ok);
+  /* Do not ask dwfl_frame_pc for ISACTIVATION, 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)
+	{
+	  handle_cfi (state, pc - bias, cfi_eh);
+	  if (state->unwound)
+	    return;
+	}
+      Dwarf_CFI *cfi_dwarf = INTUSE(dwfl_module_dwarf_cfi) (mod, &bias);
+      if (cfi_dwarf)
+	{
+	  handle_cfi (state, pc - bias, cfi_dwarf);
+	  if (state->unwound)
+	    return;
+	}
+    }
+  __libdwfl_seterrno (DWFL_E_NO_DWARF);
+}
diff --git a/libdwfl/libdwfl.h b/libdwfl/libdwfl.h
index 2b70e28..511f172 100644
--- a/libdwfl/libdwfl.h
+++ b/libdwfl/libdwfl.h
@@ -1,5 +1,5 @@
 /* Interfaces for libdwfl.
-   Copyright (C) 2005-2010 Red Hat, Inc.
+   Copyright (C) 2005-2010, 2013 Red Hat, Inc.
    This file is part of elfutils.
 
    This file is free software; you can redistribute it and/or modify
@@ -30,6 +30,7 @@
 #define _LIBDWFL_H	1
 
 #include "libdw.h"
+#include "libebl.h"
 #include <stdio.h>
 
 /* Handle for a session using the library.  */
@@ -41,6 +42,14 @@ typedef struct Dwfl_Module Dwfl_Module;
 /* Handle describing a line record.  */
 typedef struct Dwfl_Line Dwfl_Line;
 
+/* This holds information common for all the frames of one backtrace for
+   a partical thread/task/TID.  Several threads belong to one Dwfl.  */
+typedef struct Dwfl_Thread Dwfl_Thread;
+
+/* This holds everything we know about the state of the frame at a particular
+   PC location described by an FDE belonging to Dwfl_Thread.  */
+typedef struct Dwfl_Frame Dwfl_Frame;
+
 /* Callbacks.  */
 typedef struct
 {
@@ -352,10 +361,14 @@ extern int dwfl_linux_kernel_report_offline (Dwfl *dwfl, const char *release,
    segment to locate its PT_DYNAMIC in the dump.  This might call
    dwfl_report_elf on file names found in the dump if reading some
    link_map files is the only way to ascertain those modules' addresses.
+   dwfl_attach_state is also called for DWFL, dwfl_core_file_report does
+   not fail if the dwfl_attach_state call has failed.
    Returns the number of modules reported, or -1 for errors.  */
 extern int dwfl_core_file_report (Dwfl *dwfl, Elf *elf);
 
 /* Call dwfl_report_module for each file mapped into the address space of PID.
+   dwfl_attach_state is also called for DWFL, dwfl_linux_proc_report does
+   not fail if the dwfl_attach_state call has failed.
    Returns zero on success, -1 if dwfl_report_module failed,
    or an errno code if opening the kernel binary failed.  */
 extern int dwfl_linux_proc_report (Dwfl *dwfl, pid_t pid);
@@ -565,6 +578,119 @@ extern Dwarf_CFI *dwfl_module_dwarf_cfi (Dwfl_Module *mod, Dwarf_Addr *bias);
 extern Dwarf_CFI *dwfl_module_eh_cfi (Dwfl_Module *mod, Dwarf_Addr *bias);
 
 
+typedef struct
+{
+  /* Called to iterate through threads.  Returns next TID (thread ID) on
+     success, a negative number on failure and zero if there are no more
+     threads.  NTHREAD is the new thread being created.  *THREAD_ARGP may be
+     optionally set by the implementation, THREAD_ARGP is never NULL.
+     This method must not be NULL.  */ 
+  pid_t (*next_thread) (Dwfl *dwfl, Dwfl_Thread *nthread, void *dwfl_arg,
+			void **thread_argp)
+    __nonnull_attribute__ (1, 2);
+
+  /* Called during unwinding to access memory (stack) state.  Returns true for
+     successfully read *RESULT or false and sets dwfl_errno () on failure.
+     This method may be NULL - in such case dwfl_thread_getframes will return
+     only the initial frame.  */
+  bool (*memory_read) (Dwfl *dwfl, Dwarf_Addr addr, Dwarf_Word *result,
+                       void *dwfl_arg)
+    __nonnull_attribute__ (1, 3);
+
+  /* Called on initial unwind to get the initial register state of the first
+     frame.  Should call dwfl_thread_state_registers, possibly multiple times
+     for different ranges and possibly also dwfl_thread_state_register_pc, to
+     fill in initial (DWARF) register values.  After this call, till at least
+     thread_detach is called, the thread is assumed to be frozen, so that it is
+     safe to unwind.  Returns true on success or false and sets dwfl_err () on
+     failure.  This method must not be NULL.  */ 
+  bool (*set_initial_registers) (Dwfl_Thread *thread, void *thread_arg)
+    __nonnull_attribute__ (1);
+
+  /* Called by dwfl_end.  All thread_detach method calls have been already
+     done.  This method may be NULL.  */
+  void (*detach) (Dwfl *dwfl, void *dwfl_arg)
+    __nonnull_attribute__ (1);
+
+  /* Called when unwinding is done.  No callback will be called after
+     this method has been called.  Iff set_initial_registers was called for
+     a TID thread_detach will be called before the detach method above.
+     This method may be NULL.  */
+  void (*thread_detach) (Dwfl_Thread *thread, void *thread_arg)
+    __nonnull_attribute__ (1);
+} Dwfl_Thread_Callbacks;
+
+/* PID is the process id associated with the DWFL state.  Architecture of DWFL
+   modules is specified by EBL.  If EBL is NULL the function will detect it
+   from arbitrary Dwfl_Module of DWFL.  DWFL_ARG is the callback backend state.
+   DWFL_ARG will be provided to the callbacks.  *THREAD_CALLBACKS function
+   pointers must remain valid during lifetime of DWFL.  Function returns true
+   on success, false otherwise.  */
+bool dwfl_attach_state (Dwfl *dwfl, Ebl *ebl, pid_t pid,
+                        const Dwfl_Thread_Callbacks *thread_callbacks,
+			void *dwfl_arg)
+  __nonnull_attribute__ (1, 4);
+
+/* Return PID for the process associated with DWFL.  Function returns -1 if
+   dwfl_attach_state was not called for DWFL.  */
+pid_t dwfl_pid (Dwfl *dwfl)
+  __nonnull_attribute__ (1);
+
+/* Return DWFL from which THREAD was created using dwfl_next_thread.  */
+Dwfl *dwfl_thread_dwfl (Dwfl_Thread *thread)
+  __nonnull_attribute__ (1);
+
+/* Return positive TID (thread ID) for THREAD.  This function never fails.  */
+pid_t dwfl_thread_tid (Dwfl_Thread *thread)
+  __nonnull_attribute__ (1);
+
+/* Return thread for frame STATE.  This function never fails.  */
+Dwfl_Thread *dwfl_frame_thread (Dwfl_Frame *state)
+  __nonnull_attribute__ (1);
+
+/* Called by Dwfl_Thread_Callbacks.set_initial_registers implementation.
+   For every known continuous block of registers <FIRSTREG..FIRSTREG+NREGS)
+   (inclusive..exclusive) set their content to REGS (array of NREGS items).
+   Function returns false if any of the registers has invalid number.  */
+bool dwfl_thread_state_registers (Dwfl_Thread *thread, const int firstreg,
+                                  unsigned nregs, const Dwarf_Word *regs)
+  __nonnull_attribute__ (1, 4);
+dwfl_thread_state_registers_t dwfl_thread_state_registers;
+
+/* Called by Dwfl_Thread_Callbacks.set_initial_registers implementation.
+   If PC is not contained among DWARF registers passed by
+   dwfl_thread_state_registers on the target architecture pass the PC value
+   here.  */
+void dwfl_thread_state_register_pc (Dwfl_Thread *thread, Dwarf_Word pc)
+  __nonnull_attribute__ (1);
+
+/* Gets the next known thread, if any.  To get the initial thread
+   provide NULL as previous thread PREV_THREAD.  On error function returns NULL
+   and sets dwfl_errno ().  When no more threads are found function returns
+   NULL and dwfl_errno () is set to 0 - dwfl_errmsg (0) returns NULL then.  */
+Dwfl_Thread *dwfl_next_thread (Dwfl *dwfl, Dwfl_Thread *prev_thread)
+  __nonnull_attribute__ (1);
+
+/* Iterate through the frames for a thread.  Returns zero if all frames
+   have been processed by the callback, returns -1 on error, or the
+   value of the callback when not DWARF_CB_OK.  Keeps calling the
+   callback with the next frame while the callback returns
+   DWARF_CB_OK, till there are no more frames.  On start will call the
+   set_initial_registers callback and on return will call the
+   detach_thread callback of the Dwfl_Thread.  */
+int dwfl_thread_getframes (Dwfl_Thread *thread,
+                           int (*callback) (Dwfl_Frame *state, void *arg),
+                           void *arg)
+  __nonnull_attribute__ (1, 2);
+
+/* Return *PC (program counter) for thread-specific frame STATE.
+   Set *ISACTIVATION according to DWARF frame "activation" definition.
+   Typically you need to substract 1 from *PC if *ACTIVATION is false to safely
+   find function of the caller.  ACTIVATION may be NULL.  PC must not be NULL.
+   Function returns false if it failed to find *PC.  */
+bool dwfl_frame_pc (Dwfl_Frame *state, Dwarf_Addr *pc, bool *isactivation)
+  __nonnull_attribute__ (1, 2);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/libdwfl/libdwflP.h b/libdwfl/libdwflP.h
index 1d4899b..b73d4f3 100644
--- a/libdwfl/libdwflP.h
+++ b/libdwfl/libdwflP.h
@@ -1,5 +1,5 @@
 /* Internal definitions for libdwfl.
-   Copyright (C) 2005-2012 Red Hat, Inc.
+   Copyright (C) 2005-2013 Red Hat, Inc.
    This file is part of elfutils.
 
    This file is free software; you can redistribute it and/or modify
@@ -41,6 +41,9 @@
 #include <string.h>
 
 #include "../libdw/libdwP.h"	/* We need its INTDECLs.  */
+#include "libeblP.h"
+
+typedef struct Dwfl_Process Dwfl_Process;
 
 /* gettext helper macros.  */
 #define _(Str) dgettext ("elfutils", Str)
@@ -74,7 +77,19 @@
   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"))		      \
+  DWFL_ERROR (NEXT_THREAD_FAIL, N_("Unable to find more threads"))	      \
+  DWFL_ERROR (ATTACH_STATE_CONFLICT, N_("Dwfl already has attached state"))   \
+  DWFL_ERROR (NO_ATTACH_STATE, N_("Dwfl has no attached state"))
 
 #define DWFL_ERROR(name, text) DWFL_E_##name,
 typedef enum { DWFL_ERRORS DWFL_E_NUM } Dwfl_Error;
@@ -92,6 +107,8 @@ struct Dwfl
 
   Dwfl_Module *modulelist;    /* List in order used by full traversals.  */
 
+  Dwfl_Process *process;
+
   GElf_Addr offline_next_address;
 
   GElf_Addr segment_align;	/* Smallest granularity of segments.  */
@@ -189,7 +206,94 @@ struct Dwfl_Module
   bool gc;			/* Mark/sweep flag.  */
 };
 
+/* This holds information common for all the threads/tasks/TIDs of one process
+   for backtraces.  */
+
+struct Dwfl_Process
+{
+  struct Dwfl *dwfl;
+  pid_t pid;
+  const Dwfl_Thread_Callbacks *callbacks;
+  void *callbacks_arg;
+  struct ebl *ebl;
+  Dwfl_Thread *thread;
+};
+
+/* See its typedef in libdwfl.h.  */
+
+struct Dwfl_Thread
+{
+  Dwfl_Process *process;
+  Dwfl_Thread *next;
+  pid_t tid;
+  bool thread_detach_needed : 1;
+  /* Bottom frame.  */
+  Dwfl_Frame *unwound;
+  void *callbacks_arg;
+};
+
+/* See its typedef in libdwfl.h.  */
+
+struct Dwfl_Frame
+{
+  Dwfl_Thread *thread;
+  /* Previous (outer) frame.  */
+  Dwfl_Frame *unwound;
+  bool signal_frame : 1;
+  enum
+  {
+    /* This structure is still being initialized or there was an error
+       initializing it.  */
+    DWFL_FRAME_STATE_ERROR,
+    /* PC field is valid.  */
+    DWFL_FRAME_STATE_PC_SET,
+    /* PC field is undefined, this means the next (inner) frame was the
+       outermost frame.  */
+    DWFL_FRAME_STATE_PC_UNDEFINED
+  } pc_state;
+  /* Either initialized from appropriate REGS element or on some archs
+     initialized separately as the return address has no DWARF register.  */
+  Dwarf_Addr pc;
+  /* (1 << X) bitmask where 0 <= X < ebl_frame_nregs.  */
+  uint64_t regs_set[3];
+  /* REGS array size is ebl_frame_nregs.  */
+  Dwarf_Addr regs[];
+};
+
+/* Fetch value from Dwfl_Frame->regs indexed by DWARF REGNO.
+   No error code is set if the function returns FALSE.  */
+
+static inline bool
+dwfl_frame_reg_get (Dwfl_Frame *state, unsigned regno, Dwarf_Addr *val)
+{
+  Ebl *ebl = state->thread->process->ebl;
+  if (regno >= ebl->frame_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->regs indexed by DWARF REGNO.
+   No error code is set if the function returns FALSE.  */
+
+static inline bool
+dwfl_frame_reg_set (Dwfl_Frame *state, unsigned regno, Dwarf_Addr val)
+{
+  Ebl *ebl = state->thread->process->ebl;
+  if (regno >= ebl->frame_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
@@ -415,6 +519,29 @@ extern Dwfl_Module *__libdwfl_report_offline (Dwfl *dwfl, const char *name,
 								const char *))
   internal_function;
 
+/* Free PROCESS.  Unlink and free also any structures it references.  */
+extern void __libdwfl_process_free (Dwfl_Process *process)
+  internal_function;
+
+/* Update STATE->UNWOUND for the unwound frame.
+   Functions sets dwfl_errno ().  */
+extern void __libdwfl_frame_unwind (Dwfl_Frame *state)
+  internal_function;
+
+/* Call dwfl_attach_state for PID, return true if successful.  */
+extern bool __libdwfl_attach_state_for_pid (Dwfl *dwfl, pid_t pid)
+  internal_function;
+
+/* Call dwfl_attach_state for CORE, return true if successful.  */
+extern bool __libdwfl_attach_state_for_core (Dwfl *dwfl, Elf *core)
+  internal_function;
+
+/* Align segment START downwards or END upwards addresses according to DWFL.  */
+extern GElf_Addr __libdwfl_segment_start (Dwfl *dwfl, GElf_Addr start)
+  internal_function;
+extern GElf_Addr __libdwfl_segment_end (Dwfl *dwfl, GElf_Addr end)
+  internal_function;
+
 /* Decompression wrappers: decompress whole file into memory.  */
 extern Dwfl_Error __libdw_gunzip  (int fd, off64_t start_offset,
 				   void *mapped, size_t mapped_size,
@@ -557,6 +684,16 @@ INTDECL (dwfl_offline_section_address)
 INTDECL (dwfl_module_relocate_address)
 INTDECL (dwfl_module_dwarf_cfi)
 INTDECL (dwfl_module_eh_cfi)
+INTDECL (dwfl_attach_state)
+INTDECL (dwfl_pid)
+INTDECL (dwfl_thread_dwfl)
+INTDECL (dwfl_thread_tid)
+INTDECL (dwfl_frame_thread)
+INTDECL (dwfl_thread_state_registers)
+INTDECL (dwfl_thread_state_register_pc)
+INTDECL (dwfl_next_thread)
+INTDECL (dwfl_thread_getframes)
+INTDECL (dwfl_frame_pc)
 
 /* Leading arguments standard to callbacks passed a Dwfl_Module.  */
 #define MODCB_ARGS(mod)	(mod), &(mod)->userdata, (mod)->name, (mod)->low_addr
diff --git a/libdwfl/linux-proc-maps.c b/libdwfl/linux-proc-maps.c
index 10946b9..3a29463 100644
--- a/libdwfl/linux-proc-maps.c
+++ b/libdwfl/linux-proc-maps.c
@@ -1,5 +1,5 @@
 /* Standard libdwfl callbacks for debugging a live Linux process.
-   Copyright (C) 2005-2010 Red Hat, Inc.
+   Copyright (C) 2005-2010, 2013 Red Hat, Inc.
    This file is part of elfutils.
 
    This file is free software; you can redistribute it and/or modify
@@ -300,6 +300,13 @@ dwfl_linux_proc_report (Dwfl *dwfl, pid_t pid)
 
   fclose (f);
 
+  if (result == 0)
+    {
+      /* Possible error is ignored, DWFL still may be useful for non-unwinding
+	 operations.  */
+      __libdwfl_attach_state_for_pid (dwfl, pid);
+    }
+
   return result;
 }
 INTDEF (dwfl_linux_proc_report)
diff --git a/libdwfl/segment.c b/libdwfl/segment.c
index 496b4fd..9276917 100644
--- a/libdwfl/segment.c
+++ b/libdwfl/segment.c
@@ -1,5 +1,5 @@
 /* Manage address space lookup table for libdwfl.
-   Copyright (C) 2008, 2009, 2010 Red Hat, Inc.
+   Copyright (C) 2008, 2009, 2010, 2013 Red Hat, Inc.
    This file is part of elfutils.
 
    This file is free software; you can redistribute it and/or modify
@@ -28,16 +28,18 @@
 
 #include "libdwflP.h"
 
-static GElf_Addr
-segment_start (Dwfl *dwfl, GElf_Addr start)
+GElf_Addr
+internal_function
+__libdwfl_segment_start (Dwfl *dwfl, GElf_Addr start)
 {
   if (dwfl->segment_align > 1)
     start &= -dwfl->segment_align;
   return start;
 }
 
-static GElf_Addr
-segment_end (Dwfl *dwfl, GElf_Addr end)
+GElf_Addr
+internal_function
+__libdwfl_segment_end (Dwfl *dwfl, GElf_Addr end)
 {
   if (dwfl->segment_align > 1)
     end = (end + dwfl->segment_align - 1) & -dwfl->segment_align;
@@ -156,8 +158,8 @@ reify_segments (Dwfl *dwfl)
   for (Dwfl_Module *mod = dwfl->modulelist; mod != NULL; mod = mod->next)
     if (! mod->gc)
       {
-	const GElf_Addr start = segment_start (dwfl, mod->low_addr);
-	const GElf_Addr end = segment_end (dwfl, mod->high_addr);
+	const GElf_Addr start = __libdwfl_segment_start (dwfl, mod->low_addr);
+	const GElf_Addr end = __libdwfl_segment_end (dwfl, mod->high_addr);
 	bool resized = false;
 
 	int idx = lookup (dwfl, start, hint);
@@ -296,8 +298,9 @@ dwfl_report_segment (Dwfl *dwfl, int ndx, const GElf_Phdr *phdr, GElf_Addr bias,
       dwfl->lookup_module = NULL;
     }
 
-  GElf_Addr start = segment_start (dwfl, bias + phdr->p_vaddr);
-  GElf_Addr end = segment_end (dwfl, bias + phdr->p_vaddr + phdr->p_memsz);
+  GElf_Addr start = __libdwfl_segment_start (dwfl, bias + phdr->p_vaddr);
+  GElf_Addr end = __libdwfl_segment_end (dwfl,
+					 bias + phdr->p_vaddr + phdr->p_memsz);
 
   /* Coalesce into the last one if contiguous and matching.  */
   if (ndx != dwfl->lookup_tail_ndx
diff --git a/libebl/Makefile.am b/libebl/Makefile.am
index 4d62fad..4487c5f 100644
--- a/libebl/Makefile.am
+++ b/libebl/Makefile.am
@@ -1,6 +1,6 @@
 ## Process this file with automake to create Makefile.in
 ##
-## Copyright (C) 2000-2010 Red Hat, Inc.
+## Copyright (C) 2000-2010, 2013 Red Hat, Inc.
 ## This file is part of elfutils.
 ##
 ## This file is free software; you can redistribute it and/or modify
@@ -54,7 +54,7 @@ gen_SOURCES = eblopenbackend.c eblclosebackend.c eblstrtab.c \
 	      eblreginfo.c eblnonerelocp.c eblrelativerelocp.c \
 	      eblsysvhashentrysize.c eblauxvinfo.c eblcheckobjattr.c \
 	      ebl_check_special_section.c ebl_syscall_abi.c eblabicfi.c \
-	      eblstother.c
+	      eblstother.c eblinitreg.c
 
 libebl_a_SOURCES = $(gen_SOURCES)
 
diff --git a/libebl/ebl-hooks.h b/libebl/ebl-hooks.h
index d3cf3e6..05ca18b 100644
--- a/libebl/ebl-hooks.h
+++ b/libebl/ebl-hooks.h
@@ -1,5 +1,5 @@
 /* Backend hook signatures internal interface for libebl.
-   Copyright (C) 2000-2011 Red Hat, Inc.
+   Copyright (C) 2000-2011, 2013 Red Hat, Inc.
    This file is part of elfutils.
 
    This file is free software; you can redistribute it and/or modify
@@ -155,5 +155,9 @@ int EBLHOOK(disasm) (const uint8_t **startp, const uint8_t *end,
    Function returns 0 on success and -1 on error.  */
 int EBLHOOK(abi_cfi) (Ebl *ebl, Dwarf_CIE *abi_info);
 
+/* Fetch process data from live TID into THREAD->unwound.  */
+bool EBLHOOK(set_initial_registers_tid) (struct Dwfl_Thread *thread, pid_t tid,
+					dwfl_thread_state_registers_t *setfunc);
+
 /* Destructor for ELF backend handle.  */
 void EBLHOOK(destr) (struct ebl *);
diff --git a/libebl/eblinitreg.c b/libebl/eblinitreg.c
new file mode 100644
index 0000000..4aa9d9b
--- /dev/null
+++ b/libebl/eblinitreg.c
@@ -0,0 +1,50 @@
+/* Fetch live process Dwfl_Frame from PID.
+   Copyright (C) 2013 Red Hat, Inc.
+   This file is part of elfutils.
+
+   This file is free software; you can redistribute it and/or modify
+   it under the terms of either
+
+     * the GNU Lesser General Public License as published by the Free
+       Software Foundation; either version 3 of the License, or (at
+       your option) any later version
+
+   or
+
+     * the GNU General Public License as published by the Free
+       Software Foundation; either version 2 of the License, or (at
+       your option) any later version
+
+   or both in parallel, as here.
+
+   elfutils is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received copies of the GNU General Public License and
+   the GNU Lesser General Public License along with this program.  If
+   not, see <http://www.gnu.org/licenses/>.  */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <libeblP.h>
+#include <assert.h>
+
+bool
+ebl_set_initial_registers_tid (Ebl *ebl, struct Dwfl_Thread *thread, pid_t tid,
+			       dwfl_thread_state_registers_t *setfunc)
+{
+  /* Otherwise caller could not allocate THREAD frame of proper size.
+     If set_initial_registers_tid is unsupported then FRAME_NREGS is zero.  */
+  assert (ebl->set_initial_registers_tid != NULL);
+  return ebl->set_initial_registers_tid (thread, tid, setfunc);
+}
+
+size_t
+ebl_frame_nregs (Ebl *ebl)
+{
+  return ebl == NULL ? 0 : ebl->frame_nregs;
+}
diff --git a/libebl/libebl.h b/libebl/libebl.h
index 990167a..e3185b4 100644
--- a/libebl/libebl.h
+++ b/libebl/libebl.h
@@ -1,5 +1,5 @@
 /* Interface for libebl.
-   Copyright (C) 2000-2010 Red Hat, Inc.
+   Copyright (C) 2000-2010, 2013 Red Hat, Inc.
    This file is part of elfutils.
 
    This file is free software; you can redistribute it and/or modify
@@ -383,6 +383,22 @@ extern int ebl_auxv_info (Ebl *ebl, GElf_Xword a_type,
 			  const char **name, const char **format)
   __nonnull_attribute__ (1, 3, 4);
 
+/* Fetch process data from live TID into THREAD->unwound.  */
+struct Dwfl_Thread;
+typedef bool (dwfl_thread_state_registers_t) (struct Dwfl_Thread *thread,
+					      const int firstreg,
+					      unsigned nregs,
+					      const Dwarf_Word *regs)
+  __nonnull_attribute__ (1, 4);
+extern bool ebl_set_initial_registers_tid (Ebl *ebl, struct Dwfl_Thread *thread,
+					   pid_t tid,
+					 dwfl_thread_state_registers_t *setfunc)
+  __nonnull_attribute__ (1);
+
+/* Number of registers to allocate
+   for STATE of ebl_set_initial_registers_tid.  */
+extern size_t ebl_frame_nregs (Ebl *ebl)
+  __nonnull_attribute__ (1);
 
 #ifdef __cplusplus
 }
diff --git a/libebl/libeblP.h b/libebl/libeblP.h
index 5ec26a4..6a06131 100644
--- a/libebl/libeblP.h
+++ b/libebl/libeblP.h
@@ -1,5 +1,5 @@
 /* Internal definitions for interface for libebl.
-   Copyright (C) 2000-2009 Red Hat, Inc.
+   Copyright (C) 2000-2009, 2013 Red Hat, Inc.
    This file is part of elfutils.
 
    This file is free software; you can redistribute it and/or modify
@@ -60,6 +60,9 @@ struct ebl
   /* Size of entry in Sysv-style hash table.  */
   int sysvhash_entrysize;
 
+  /* Number of Dwfl_Frame->regs entries to allocate.  */
+  size_t frame_nregs;
+
   /* Internal data.  */
   void *dlhandle;
 };
diff --git a/m4/biarch.m4 b/m4/biarch.m4
new file mode 100644
index 0000000..a15323e
--- /dev/null
+++ b/m4/biarch.m4
@@ -0,0 +1,45 @@
+AC_DEFUN([utrace_CC_m32], [dnl
+AC_CACHE_CHECK([$CC option for 32-bit word size], utrace_cv_CC_m32, [dnl
+save_CC="$CC"
+utrace_cv_CC_m32=none
+for ut_try in -m32 -m31; do
+  [CC=`echo "$save_CC" | sed 's/ -m[36][241]//'`" $ut_try"]
+  AC_COMPILE_IFELSE([AC_LANG_SOURCE([[int foo (void) { return 1; }]])],
+		    [utrace_cv_CC_m32=$ut_try])
+  test x$utrace_cv_CC_m32 = xnone || break
+done
+CC="$save_CC"])])
+
+AC_DEFUN([utrace_HOST64], [AC_REQUIRE([utrace_CC_m32])
+AS_IF([test x$utrace_cv_CC_m32 != xnone], [dnl
+AC_CACHE_CHECK([for 64-bit host], utrace_cv_host64, [dnl
+AC_EGREP_CPP([@utrace_host64@], [#include <stdint.h>
+#if (UINTPTR_MAX > 0xffffffffUL)
+(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 674846d..954a14b 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,6 +1,6 @@
 ## Process this file with automake to create Makefile.in
 ##
-## Copyright (C) 1996-2012 Red Hat, Inc.
+## Copyright (C) 1996-2013 Red Hat, Inc.
 ## This file is part of elfutils.
 ##
 ## This file is free software; you can redistribute it and/or modify
@@ -37,7 +37,7 @@ native_ld = @native_ld@
 base_cpu = @base_cpu@
 
 bin_PROGRAMS = readelf nm size strip ld elflint findtextrel addr2line \
-	       elfcmp objdump ranlib strings ar unstrip
+	       elfcmp objdump ranlib strings ar unstrip stack
 
 
 ld_dsos = libld_elf_i386_pic.a
@@ -115,6 +115,7 @@ ranlib_LDADD = libar.a $(libelf) $(libeu) $(libmudflap)
 strings_LDADD = $(libelf) $(libeu) $(libmudflap)
 ar_LDADD = libar.a $(libelf) $(libeu) $(libmudflap)
 unstrip_LDADD = $(libebl) $(libelf) $(libdw) $(libeu) $(libmudflap) -ldl
+stack_LDADD = $(libebl) $(libelf) $(libdw) $(libeu) $(libmudflap) -ldl
 
 ldlex.o: ldscript.c
 ldlex_no_Werror = yes
diff --git a/src/stack.c b/src/stack.c
new file mode 100644
index 0000000..ed6e791
--- /dev/null
+++ b/src/stack.c
@@ -0,0 +1,180 @@
+/* Unwinding of frames like gstack/pstack.
+   Copyright (C) 2013 Red Hat, Inc.
+   This file is part of elfutils.
+
+   This file is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   elfutils is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+#include <assert.h>
+#include <argp.h>
+#include <error.h>
+#include <stdlib.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdio_ext.h>
+#include <locale.h>
+#include <fcntl.h>
+#include ELFUTILS_HEADER(dwfl)
+
+/* 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 Dwfl *
+report_corefile (Dwfl *dwfl, const char *corefile)
+{
+  int fd = open64 (corefile, O_RDONLY);
+  if (fd == -1)
+    error (2, 0, "open64: %m");
+  Elf *elf = elf_begin (fd, ELF_C_READ_MMAP, NULL);
+  if (elf == NULL)
+    error (2, 0, "elf_begin: %s", elf_errmsg (-1));
+  if (dwfl_core_file_report (dwfl, elf) < 0)
+    error (2, 0, "dwfl_core_file_report: %s", dwfl_errmsg (-1));
+  if (dwfl_report_end (dwfl, NULL, NULL) != 0)
+    error (2, 0, "dwfl_report_end: %s", dwfl_errmsg (-1));
+  /* ELF and CORE are leaked.  */
+  return dwfl;
+}
+
+static int
+frame_callback (Dwfl_Frame *state, void *arg)
+{
+  unsigned *framenop = arg;
+  Dwarf_Addr pc;
+  bool isactivation;
+  if (! dwfl_frame_pc (state, &pc, &isactivation))
+    {
+      error (0, 0, "%s", dwfl_errmsg (-1));
+      return 1;
+    }
+  Dwarf_Addr pc_adjusted = pc - (isactivation ? 0 : 1);
+
+  /* Get PC->SYMNAME.  */
+  Dwfl *dwfl = dwfl_thread_dwfl (dwfl_frame_thread (state));
+  Dwfl_Module *mod = dwfl_addrmodule (dwfl, pc_adjusted);
+  const char *symname = NULL;
+  if (mod)
+    symname = dwfl_module_addrname (mod, pc_adjusted);
+
+  printf ("#%2u %#" PRIx64 "%4s\t%s\n", (*framenop)++, (uint64_t) pc,
+	  ! isactivation ? "- 1" : "", symname);
+  return DWARF_CB_OK;
+}
+
+static void
+dump (Dwfl *dwfl, pid_t pid, const char *corefile)
+{
+  if (pid)
+    report_pid (dwfl, pid);
+  else if (corefile)
+    report_corefile (dwfl, corefile);
+  else
+    abort ();
+  Dwfl_Thread *thread = NULL;
+  for (;;)
+    {
+      thread = dwfl_next_thread (dwfl, thread);
+      if (thread == NULL)
+	{
+	  const char *msg = dwfl_errmsg (0);
+	  if (msg == NULL)
+	    break;
+	  error (2, 0, "dwfl_next_thread: %s", msg);
+	}
+      printf ("TID %ld:\n", (long) dwfl_thread_tid (thread));
+      unsigned frameno = 0;
+      switch (dwfl_thread_getframes (thread, frame_callback, &frameno))
+	{
+	case 0:
+	case 1:
+	  break;
+	case -1:
+	  error (0, 0, "dwfl_thread_getframes: %s", dwfl_errmsg (-1));
+	  break;
+	default:
+	  abort ();
+	}
+    }
+  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);
+}
+
+static void
+usage (void)
+{
+  error (2, 0, "eu-stack [--debuginfo-path=<path>] {-p <process id>|"
+               "--core=<file> [--executable=<file>]|--help}");
+}
+
+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);
+  if (remaining != argc)
+    usage ();
+
+  if (pid && !corefile)
+    dump (dwfl, pid, NULL);
+  else if (corefile && !pid)
+    dump (dwfl, 0, corefile);
+  else
+    usage ();
+
+  return 0;
+}
diff --git a/tests/Makefile.am b/tests/Makefile.am
index de98e45..423afec 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -52,10 +52,24 @@ check_PROGRAMS = arextract arsymtest newfile saridx scnnames sectiondump \
 		  test-flag-nobits dwarf-getstring rerequest_tag \
 		  alldts md5-sha1-test typeiter typeiter2 low_high_pc \
 		  test-elf_cntl_gelf_getshdr dwflsyms dwfllines \
-		  dwfl-report-elf-align varlocs
+		  dwfl-report-elf-align varlocs 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 \
@@ -89,7 +103,7 @@ TESTS = run-arextract.sh run-arsymtest.sh newfile test-nlist \
 	run-test-archive64.sh run-readelf-vmcoreinfo.sh \
 	run-readelf-mixed-corenote.sh run-dwfllines.sh \
 	run-dwfl-report-elf-align.sh run-addr2line-test.sh \
-	run-addr2line-i-test.sh run-varlocs.sh
+	run-addr2line-i-test.sh run-varlocs.sh run-backtrace.sh
 
 if !STANDALONE
 check_PROGRAMS += msg_tst md5-sha1-test
@@ -212,7 +226,8 @@ EXTRA_DIST = run-arextract.sh run-arsymtest.sh \
 	     testfile_implicit_pointer.c testfile_implicit_pointer.bz2 \
 	     testfile_parameter_ref.c testfile_parameter_ref.bz2 \
 	     testfile_entry_value.c testfile_entry_value.bz2 \
-	     testfile_implicit_value.c testfile_implicit_value.bz2
+	     testfile_implicit_value.c testfile_implicit_value.bz2 \
+	     run-backtrace.sh
 
 if USE_VALGRIND
 valgrind_cmd='valgrind -q --trace-children=yes --error-exitcode=1 --run-libc-freeres=no'
@@ -339,6 +354,10 @@ dwflsyms_LDADD = $(libdw) $(libelf) $(libmudflap)
 dwfllines_LDADD = $(libdw) $(libelf) $(libmudflap)
 dwfl_report_elf_align_LDADD = $(libdw) $(libmudflap)
 varlocs_LDADD = $(libdw) $(libelf) $(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..e556a7d
--- /dev/null
+++ b/tests/backtrace-child.c
@@ -0,0 +1,157 @@
+/* Test child for parent backtrace test.
+   Copyright (C) 2013 Red Hat, Inc.
+   This file is part of elfutils.
+
+   This file is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   elfutils is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <errno.h>
+#include <sys/ptrace.h>
+#include <string.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5)
+#define NOINLINE_NOCLONE __attribute__ ((noinline, noclone))
+#else
+#define NOINLINE_NOCLONE __attribute__ ((noinline))
+#endif
+
+#define NORETURN __attribute__ ((noreturn))
+#define UNUSED __attribute__ ((unused))
+#define USED __attribute__ ((used))
+
+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 NOINLINE_NOCLONE void
+dummy1 (void)
+{
+  asm volatile ("");
+}
+
+#ifdef __x86_64__
+static NOINLINE_NOCLONE USED void
+jmp (void)
+{
+  /* Not reached, signal will get ptrace-spawn to jump into sigusr2.  */
+  abort ();
+}
+#endif
+
+static NOINLINE_NOCLONE void
+dummy2 (void)
+{
+  asm volatile ("");
+}
+
+static NOINLINE_NOCLONE NORETURN void
+stdarg (int f 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 NOINLINE_NOCLONE void
+dummy3 (void)
+{
+  asm volatile ("");
+}
+
+static 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 NOINLINE_NOCLONE void
+dummy4 (void)
+{
+  asm volatile ("");
+}
+
+static void *
+start (void *arg UNUSED)
+{
+  backtracegen ();
+  abort ();
+}
+
+int
+main (int argc 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..8f3a405
--- /dev/null
+++ b/tests/backtrace-data.c
@@ -0,0 +1,304 @@
+/* Test program for unwinding of frames.
+   Copyright (C) 2013 Red Hat, Inc.
+   This file is part of elfutils.
+
+   This file is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   elfutils is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+#include <assert.h>
+#include <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 (Dwfl *dwfl, Dwarf_Addr addr, Dwarf_Word *result,
+	     void *dwfl_arg __attribute__ ((unused)))
+{
+  pid_t child = dwfl_pid (dwfl);
+
+  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 Dwfl_Module *
+report_module (Dwfl *dwfl, pid_t child, Dwarf_Addr addr)
+{
+  GElf_Addr base;
+  char *long_name = maps_lookup (child, addr, &base);
+  Dwfl_Module *mod = dwfl_report_elf (dwfl, long_name, long_name, -1,
+				      base, false /* add_p_vaddr */);
+  assert (mod);
+  free (long_name);
+  assert (dwfl_addrmodule (dwfl, addr) == mod);
+  return mod;
+}
+
+static pid_t
+next_thread (Dwfl *dwfl, Dwfl_Thread *nthread __attribute__ ((unused)),
+	     void *dwfl_arg __attribute__ ((unused)),
+	     void **thread_argp __attribute__ ((unused)))
+{
+  return dwfl_pid (dwfl);
+}
+
+static bool
+set_initial_registers (Dwfl_Thread *thread,
+		       void *thread_arg __attribute__ ((unused)))
+{
+  pid_t child = dwfl_pid (dwfl_thread_dwfl (thread));
+
+  struct user_regs_struct user_regs;
+  long l = ptrace (PTRACE_GETREGS, child, NULL, &user_regs);
+  assert_perror (errno);
+  assert (l == 0);
+
+  Dwarf_Word dwarf_regs[17];
+  dwarf_regs[0] = user_regs.rax;
+  dwarf_regs[1] = user_regs.rdx;
+  dwarf_regs[2] = user_regs.rcx;
+  dwarf_regs[3] = user_regs.rbx;
+  dwarf_regs[4] = user_regs.rsi;
+  dwarf_regs[5] = user_regs.rdi;
+  dwarf_regs[6] = user_regs.rbp;
+  dwarf_regs[7] = user_regs.rsp;
+  dwarf_regs[8] = user_regs.r8;
+  dwarf_regs[9] = user_regs.r9;
+  dwarf_regs[10] = user_regs.r10;
+  dwarf_regs[11] = user_regs.r11;
+  dwarf_regs[12] = user_regs.r12;
+  dwarf_regs[13] = user_regs.r13;
+  dwarf_regs[14] = user_regs.r14;
+  dwarf_regs[15] = user_regs.r15;
+  dwarf_regs[16] = user_regs.rip;
+  bool ok = dwfl_thread_state_registers (thread, 0, 17, dwarf_regs);
+  assert (ok);
+
+  /* x86_64 has PC contained in its CFI subset of DWARF register set so
+     elfutils will figure out the real PC value from REGS.
+     So no need to explicitly call dwfl_thread_state_register_pc.  */
+
+  return true;
+}
+
+static const Dwfl_Thread_Callbacks callbacks =
+{
+  next_thread,
+  memory_read,
+  set_initial_registers,
+  NULL, /* detach */
+  NULL, /* thread_detach */
+};
+
+static int
+frame_callback (Dwfl_Frame *state, void *arg)
+{
+  unsigned *framenop = arg;
+  Dwarf_Addr pc;
+  bool isactivation;
+  if (! dwfl_frame_pc (state, &pc, &isactivation))
+    {
+      error (1, 0, "%s", dwfl_errmsg (-1));
+      return 1;
+    }
+  Dwarf_Addr pc_adjusted = pc - (isactivation ? 0 : 1);
+
+  /* Get PC->SYMNAME.  */
+  Dwfl *dwfl = dwfl_thread_dwfl (dwfl_frame_thread (state));
+  Dwfl_Module *mod = dwfl_addrmodule (dwfl, pc_adjusted);
+  if (mod == NULL)
+    mod = report_module (dwfl, dwfl_pid (dwfl), pc_adjusted);
+  const char *symname = NULL;
+  symname = dwfl_module_addrname (mod, pc_adjusted);
+
+  printf ("#%2u %#" PRIx64 "%4s\t%s\n", (*framenop)++, (uint64_t) pc,
+	  ! isactivation ? "- 1" : "", symname);
+  return DWARF_CB_OK;
+}
+
+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);
+
+  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);
+
+  struct user_regs_struct user_regs;
+  long l = ptrace (PTRACE_GETREGS, child, NULL, &user_regs);
+  assert_perror (errno);
+  assert (l == 0);
+  report_module (dwfl, child, user_regs.rip);
+
+  bool ok = dwfl_attach_state (dwfl, NULL, child, &callbacks, NULL);
+  assert (ok);
+
+  /* Multiple threads are not handled here.  */
+  Dwfl_Thread *thread = dwfl_next_thread (dwfl, NULL);
+  assert (thread);
+
+  unsigned frameno = 0;
+  switch (dwfl_thread_getframes (thread, frame_callback, &frameno))
+    {
+    case 0:
+      break;
+    case -1:
+      error (1, 0, "dwfl_thread_getframes: %s", dwfl_errmsg (-1));
+    default:
+      abort ();
+    }
+
+  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..243b51c
--- /dev/null
+++ b/tests/backtrace.c
@@ -0,0 +1,538 @@
+/* Test program for unwinding of frames.
+   Copyright (C) 2013 Red Hat, Inc.
+   This file is part of elfutils.
+
+   This file is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   elfutils is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+#include <assert.h>
+#include <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 *
+pid_to_dwfl (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 *
+report_corefile (Dwfl *dwfl, const char *corefile)
+{
+  int fd = open64 (corefile, O_RDONLY);
+  if (fd == -1)
+    error (2, 0, "open64: %m");
+  Elf *elf = elf_begin (fd, ELF_C_READ_MMAP, NULL);
+  if (elf == NULL)
+    error (2, 0, "elf_begin: %s", elf_errmsg (-1));
+  if (dwfl_core_file_report (dwfl, elf) < 0)
+    error (2, 0, "dwfl_core_file_report: %s", dwfl_errmsg (-1));
+  if (dwfl_report_end (dwfl, NULL, NULL) != 0)
+    error (2, 0, "dwfl_report_end: %s", dwfl_errmsg (-1));
+  /* ELF and CORE are leaked.  */
+  return dwfl;
+}
+
+static Dwfl *
+corefile_to_dwfl (const char *corefile)
+{
+  return report_corefile (dwfl_offline (), corefile);
+}
+
+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;
+}
+
+typedef void (callback_t) (pid_t tid, unsigned frameno, Dwarf_Addr pc,
+			   const char *symname, Dwfl *dwfl, void *data);
+
+struct frame_callback
+{
+  unsigned frameno;
+  callback_t *callback;
+  void *callback_data;
+};
+
+static int
+frame_callback (Dwfl_Frame *state, void *arg)
+{
+  struct frame_callback *data = arg;
+  Dwarf_Addr pc;
+  bool isactivation;
+  if (! dwfl_frame_pc (state, &pc, &isactivation))
+    {
+      error (0, 0, "%s", dwfl_errmsg (-1));
+      return 1;
+    }
+  Dwarf_Addr pc_adjusted = pc - (isactivation ? 0 : 1);
+
+  /* Get PC->SYMNAME.  */
+  Dwfl_Thread *thread = dwfl_frame_thread (state);
+  Dwfl *dwfl = dwfl_thread_dwfl (thread);
+  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", data->frameno, (uint64_t) pc,
+	  ! isactivation ? "- 1" : "", symname);
+  pid_t tid = dwfl_thread_tid (thread);
+  if (data->callback)
+    data->callback (tid, data->frameno, pc, symname, dwfl, data->callback_data);
+  data->frameno++;
+
+  return DWARF_CB_OK;
+}
+
+
+static void
+dump (pid_t pid, const char *corefile, callback_t *callback,
+      void *callback_data)
+{
+  Dwfl *dwfl;
+  if (pid && !corefile)
+    dwfl = pid_to_dwfl (pid);
+  else if (corefile && !pid)
+    dwfl = corefile_to_dwfl (corefile);
+  else
+    abort ();
+  ptrdiff_t ptrdiff = dwfl_getmodules (dwfl, dump_modules, NULL, 0);
+  assert (ptrdiff == 0);
+  Dwfl_Thread *thread = NULL;
+  int err = 0;
+  for (;;)
+    {
+      thread = dwfl_next_thread (dwfl, thread);
+      if (thread == NULL)
+	{
+	  const char *msg = dwfl_errmsg (0);
+	  if (msg == NULL)
+	    break;
+	  error (2, 0, "dwfl_next_thread: %s", msg);
+	}
+      printf ("TID %ld:\n", (long) dwfl_thread_tid (thread));
+      struct frame_callback frame_callback_data;
+      frame_callback_data.frameno = 0;
+      frame_callback_data.callback = callback;
+      frame_callback_data.callback_data = callback_data;
+      switch (dwfl_thread_getframes (thread, frame_callback,
+				     &frame_callback_data))
+	{
+	case 0:
+	  break;
+	case 1:
+	  err = 1;
+	  break;
+	case -1:
+	  error (0, 0, "dwfl_thread_getframes: %s", dwfl_errmsg (-1));
+	  err = 1;
+	  break;
+	default:
+	  abort ();
+	}
+    }
+  while (0);
+  if (callback)
+    callback (0, 0, 0, NULL, dwfl, callback_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 = pid_to_dwfl (pid);
+  char *selfpathname;
+  int i = asprintf (&selfpathname, "/proc/%ld/exe", (long) pid);
+  assert (i > 0);
+  struct see_exec_module data;
+  ssize_t ssize = readlink (selfpathname, data.selfpath,
+			    sizeof (data.selfpath));
+  free (selfpathname);
+  assert (ssize > 0 && ssize < (ssize_t) sizeof (data.selfpath));
+  data.selfpath[ssize] = '\0';
+  data.mod = NULL;
+  ptrdiff_t ptrdiff = dwfl_getmodules (dwfl, see_exec_module, &data, 0);
+  assert (ptrdiff == 0);
+  assert (data.mod != NULL);
+  GElf_Addr loadbase;
+  Elf *elf = dwfl_module_getelf (data.mod, &loadbase);
+  GElf_Ehdr ehdr_mem, *ehdr = gelf_getehdr (elf, &ehdr_mem);
+  assert (ehdr != NULL);
+  Elf_Scn *scn = NULL, *plt = NULL;
+  while ((scn = elf_nextscn (elf, scn)) != NULL)
+    {
+      GElf_Shdr scn_shdr_mem, *scn_shdr = gelf_getshdr (scn, &scn_shdr_mem);
+      assert (scn_shdr != NULL);
+      if (strcmp (elf_strptr (elf, ehdr->e_shstrndx, scn_shdr->sh_name),
+		  ".plt") != 0)
+	continue;
+      assert (plt == NULL);
+      plt = scn;
+    }
+  assert (plt != NULL);
+  GElf_Shdr scn_shdr_mem, *scn_shdr = gelf_getshdr (plt, &scn_shdr_mem);
+  assert (scn_shdr != NULL);
+  /* Make it true on x86_64 with i386 inferior.  */
+  int disable = ehdr->e_ident[EI_CLASS] == ELFCLASS32;
+#ifdef __x86_64__
+  Dwarf_Addr plt_start = scn_shdr->sh_addr + loadbase;
+  Dwarf_Addr plt_end = plt_start + scn_shdr->sh_size;
+  void (*jmp) (void);
+  if (! disable)
+    {
+      int nsym = dwfl_module_getsymtab (data.mod);
+      int symi;
+      for (symi = 1; symi < nsym; ++symi)
+	{
+	  GElf_Sym symbol;
+	  const char *symbol_name = dwfl_module_getsym (data.mod, symi, &symbol, NULL);
+	  if (symbol_name == NULL)
+	    continue;
+	  switch (GELF_ST_TYPE (symbol.st_info))
+	    {
+	    case STT_SECTION:
+	    case STT_FILE:
+	    case STT_TLS:
+	      continue;
+	    default:
+	      if (strcmp (symbol_name, "jmp") != 0)
+		continue;
+	      break;
+	    }
+	  /* LOADBASE is already applied here.  */
+	  jmp = (void (*) (void)) (uintptr_t) symbol.st_value;
+	  break;
+	}
+      assert (symi < nsym);
+    prepare_thread (pid2, plt_start, plt_end, jmp);
+    }
+#endif
+  dwfl_end (dwfl);
+  ptrace_detach_stopped (pid);
+  ptrace_detach_stopped (pid2);
+  dump (pid, NULL, selfdump_callback,
+	(void *) (intptr_t) (disable ? -pid2 : pid2));
+}
+
+static bool
+is_core (const char *corefile)
+{
+  Dwfl *dwfl = dwfl_offline ();
+  Dwfl_Module *mod = dwfl_report_elf (dwfl, "core", corefile, -1, 0 /* base */,
+				      false /* add_p_vaddr */);
+  assert (mod != NULL);
+  GElf_Addr loadbase_ignore;
+  Elf *core = dwfl_module_getelf (mod, &loadbase_ignore);
+  assert (core != NULL);
+  GElf_Ehdr ehdr_mem, *ehdr = gelf_getehdr (core, &ehdr_mem);
+  assert (ehdr != NULL);
+  assert (ehdr->e_type == ET_CORE || ehdr->e_type == ET_EXEC
+	  || ehdr->e_type == ET_DYN);
+  bool retval = ehdr->e_type == ET_CORE;
+  dwfl_end (dwfl);
+  return retval;
+}
+
+int
+main (int argc __attribute__ ((unused)), char **argv)
+{
+  /* We use no threads here which can interfere with handling a stream.  */
+  __fsetlocking (stdin, FSETLOCKING_BYCALLER);
+  __fsetlocking (stdout, FSETLOCKING_BYCALLER);
+  __fsetlocking (stderr, FSETLOCKING_BYCALLER);
+
+  /* Set locale.  */
+  (void) setlocale (LC_ALL, "");
+
+  if (argc == 1)
+    {
+      selfdump ("./backtrace-child");
+      return 0;
+    }
+  argv++;
+  if (argc == 2)
+    {
+      if (strcmp (*argv, "--help") == 0)
+	error (2, 0, "backtrace {{no args for ./backtrace-child}|<pid>|<core>|"
+		     "<executable>|<executable core>}");
+      char *end;
+      long l = strtol (*argv, &end, 10);
+      if (**argv && !*end)
+	dump (l, NULL, NULL, NULL);
+      else if (is_core (*argv))
+	dump (0, *argv, NULL, NULL);
+      else
+	selfdump (*argv);
+      return 0;
+    }
+  if (argc == 3)
+    {
+      assert (! is_core (argv[0]));
+      assert (is_core (argv[1]));
+      executable = argv[0];
+      dump (0, argv[1], NULL, NULL);
+      return 0;
+    }
+  assert (0);
+
+  return 0;
+}
diff --git a/tests/run-backtrace.sh b/tests/run-backtrace.sh
new file mode 100755
index 0000000..f09e772
--- /dev/null
+++ b/tests/run-backtrace.sh
@@ -0,0 +1,83 @@
+#! /bin/bash
+# Copyright (C) 2013 Red Hat, Inc.
+# This file is part of elfutils.
+#
+# This file is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# elfutils is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+. $srcdir/test-subr.sh
+
+if [ -z "$VERBOSE" ]; then
+  exec >/dev/null
+else
+  set -x
+fi
+
+mytestrun()
+{
+  echo "$*"
+  testrun "$@"
+}
+
+check_main()
+{
+  if grep -w main $1; then
+    return
+  fi
+  cat >&2 $1 $3
+  echo >&2 $2: no main
+  false
+}
+
+check_gsignal()
+{
+  # Without proper ELF symbols resolution we could get inappropriate weak
+  # symbol "gsignal" with the same address as the correct symbol "raise".
+  if ! grep -w gsignal $1; then
+    return
+  fi
+  cat >&2 $1
+  echo >&2 $2: found gsignal
+  false
+}
+
+check_err()
+{
+  if test ! -s $1; then
+    return
+  fi
+  # In some cases we cannot reliably find out we got behind _start.
+  if cmp -s <(echo "${abs_builddir}/backtrace: dwfl_thread_getframes: No DWARF information found") <(uniq <$1); then
+    return
+  fi
+  cat >&2 $1
+  echo >&2 $2: neither empty nor just out of DWARF
+  false
+}
+
+for child in backtrace-child{,-biarch}; do
+  tempfiles $child{.bt,.err}
+  (set +ex; testrun ${abs_builddir}/backtrace ${abs_builddir}/$child 1>$child.bt 2>$child.err; true)
+  check_main $child.bt $child $child.err
+  check_gsignal $child.bt $child
+  check_err $child.err $child
+  core="core.`ulimit -c unlimited; set +ex; testrun ${abs_builddir}/$child --gencore --run; true`"
+  tempfiles $core{,.bt,.err}
+  (set +ex; testrun ${abs_builddir}/backtrace ${abs_builddir}/$child $core 1>$core.bt 2>$core.err; true)
+  cat $core.{bt,err}
+  check_main $core.bt $child-$core $core.err
+  check_gsignal $core.bt $child-$core
+  check_err $core.err $child-$core
+done
+
+exit 0

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