Hi Mark,

jankratochvil/unwinds390

attached patch lacks the binary blobs.

On Mon, 18 Nov 2013 23:35:25 +0100, Mark Wielaard wrote:
> I understand what the new unwind hook is supposed to do.
> It gives the backend a fallback for unwinding if DWARF unwinding fails.
> Which seems a good thing to have in general. But why is it needed
> specifically for s390?

Because s390 does not provide CFI for its signal handler.

(gdb) up
#1  <signal handler called>
(gdb) x/i $pc
=> 0x3ffff9cea7c:       svc     119

This address is in stack, as GDB watchpoint does not catch it I guess it is
generated by Linux kernel.  Dynamically registering CFI for stack address may
not be safe.  They should rather put the instruction to some text segment.

I did not file it anywhere, though.


> OK. But see the core_pc_offset -> core_pc_regno suggestion in the ppc
> case.

This is reworked using 'pc_register', although for s390* in
Ebl_Register_Location instead of Ebl_Core_Item (as for ppc*).


> > --- /dev/null
> > +++ b/backends/s390_initreg.c
> > [...]
> > +bool
> > +s390_set_initial_registers_tid (pid_t tid __attribute__ ((unused)),
> > +                     ebl_tid_registers_t *setfunc __attribute__ ((unused)),
> > +                           void *arg __attribute__ ((unused)))
> > +{
> > +#ifndef __s390__
> > +  return false;
> > +#else /* __s390__ */
> > +  struct user user_regs;
> > +  ptrace_area parea;
> > +  parea.process_addr = (uintptr_t) &user_regs;
> > +  parea.kernel_addr = 0;
> > +  parea.len = sizeof (user_regs);
> > +  if (ptrace (PTRACE_PEEKUSR_AREA, tid, &parea, NULL) != 0)
> > +    return false;
> > +  /* If we run as s390x we get the 64-bit registers of tid.
> > +     But -m31 executable seems to use only the 32-bit parts of its
> > +     registers so we ignore the upper half.  */
> > +  Dwarf_Word dwarf_regs[16];
> > +  for (unsigned u = 0; u < 16; u++)
> > +    dwarf_regs[u] = user_regs.regs.gprs[u]);
> > +  return setfunc (0, 16, dwarf_regs, arg);
> 
> Should that be: if (! setfunc ...) return false?

Yes, fixed.


> > +  /* Avoid conversion double -> integer.  */
> > +  eu_static_assert (sizeof user_regs.regs.fp_regs.fprs[0]
> > +               == sizeof state->regs[0]);
> > +  for (unsigned u = 0; u < 16; u++)
> > +    dwarf_regs[u] = *((const __typeof (*state->regs) *)
> > +                 &user_regs.regs.fp_regs.fprs[u]));
> > +  if (! setfunc (0, 16, dwarf_regs, arg))
> > +    return false;

> > +  for (unsigned u = 0; u < 16; u++)
> > +    dwarf_frame_state_reg_set (state, 16 + u,
> > +                          *((const __typeof (*state->regs) *)
> > +                            &user_regs.regs.fp_regs.fprs[u]));
> 
> I don't understand this. What is the intention?

This second block is a leftover, deleted.  Sorry I apparently forgot to
re-test the patch on native s390* after the last changes.

Sorry if obvious but if you ask about the cast it is stated above:
        /* Avoid conversion double -> integer.  */
because:
        /usr/include/sys/user.h
        struct _user_fpregs_struct
        {
          unsigned int fpc;
          double fprs[16];
        };


> > --- /dev/null
> > +++ b/backends/s390_unwind.c
> > [...]
> > +bool
> > +s390_unwind (Ebl *ebl, Dwarf_Addr pc, ebl_tid_registers_t *setfunc,
> > +        ebl_tid_registers_get_t *getfunc, ebl_pid_memory_read_t *readfunc,
> > +        void *arg, bool *signal_framep)
> > +{
> > +  /* Caller already assumed caller adjustment but S390 instructions are 4 
> > bytes
> > +     long.  Undo it.  */
> > +  if ((pc & 0x3) != 0x3)
> > +    return false;
> > +  pc++;
> > +  /* We can assume big-endian read here.  */
> > +  Dwarf_Word instr;
> > +  if (! readfunc (pc, &instr, arg))
> > +    return false;
> > +  /* Fetch only the very first two bytes.  */
> > +  instr = (instr >> (ebl->class == ELFCLASS64 ? 48 : 16)) & 0xffff;
> > +  /* See GDB s390_sigtramp_frame_sniffer.  */
> 
> That is not a good comment.
> You have to explain here what the code is trying to do.

OK, good to know that just the reference to GDB is not enough.
Still I believe to _also_ point to GDB is helpful.
In this case it is described what it does by the messages:
        /* Check for 'svc'.  */
        /* Check for 'sigreturn' or 'rt_sigreturn'.  */
I have improved them by:
        /* Check for 'svc' as the first instruction.  */
        /* Check for 'sigreturn' or 'rt_sigreturn' as the second instruction.  
*/


> > +  /* Check for 'svc'.  */
> > +  if (((instr >> 8) & 0xff) != 0x0a)
> > +    return false;
> > +  /* Check for 'sigreturn' or 'rt_sigreturn'.  */
> > +  if ((instr & 0xff) != 119 && (instr & 0xff) != 173)
> > +    return false;
> > +  /* See GDB s390_sigtramp_frame_unwind_cache.  */
> 
> Again.

I left there the comment, see above.  But I have added some more comments
below.


> > +# define S390_SP_REGNUM (0 + 15) /* S390_R15_REGNUM */
> > +  Dwarf_Word this_sp;
> > +  if (! getfunc (S390_SP_REGNUM, 1, &this_sp, arg))
> > +    return false;
> > +  unsigned word_size = ebl->class == ELFCLASS64 ? 8 : 4;
> > +  Dwarf_Addr next_cfa = this_sp + 16 * word_size + 32;
> > +  /* "New-style RT frame" is not supported,
> > +     assuming "Old-style RT frame and all non-RT frames".  */
> > +  Dwarf_Word sigreg_ptr;
> > +  if (! readfunc (next_cfa + 8, &sigreg_ptr, arg))
> > +    return false;
> > +  /* Skip PSW mask.  */
> > +  sigreg_ptr += word_size;
> > +  /* Read PSW address.  */
> > +  Dwarf_Word val;
> > +  if (! readfunc (sigreg_ptr, &val, arg))
> > +    return false;
> > +  if (! setfunc (-1, 1, &val, arg))
> > +    return false;
> > +  sigreg_ptr += word_size;
> > +  /* Then the GPRs.  */
> > +  Dwarf_Word gprs[16];
> > +  for (int i = 0; i < 16; i++)
> > +    {
> > +      if (! readfunc (sigreg_ptr, &gprs[i], arg))
> > +   return false;
> > +      sigreg_ptr += word_size;
> > +    }
> > +  /* Then the ACRs.  Skip them, they are not used in CFI.  */
> > +  for (int i = 0; i < 16; i++)
> > +    sigreg_ptr += 4;
> > +  /* The floating-point control word.  */
> > +  sigreg_ptr += 8;
> > +  /* And finally the FPRs.  */
> > +  Dwarf_Word fprs[16];
> > +  for (int i = 0; i < 16; i++)
> > +    {
> > +      if (! readfunc (sigreg_ptr, &val, arg))
> > +   return false;
> > +      if (ebl->class == ELFCLASS32)
> > +   {
> > +     Dwarf_Addr val_low;
> > +     if (! readfunc (sigreg_ptr + 4, &val_low, arg))
> > +       return false;
> > +     val = (val << 32) | val_low;
> > +   }
> > +      fprs[i] = val;
> > +      sigreg_ptr += 8;
> > +    }
> > +  /* If we have them, the GPR upper halves are appended at the end.  */
> > +  if (ebl->class == ELFCLASS32)
> > +    {
> > +      unsigned sigreg_high_off = 4;
> > +      sigreg_ptr += sigreg_high_off;
> > +      for (int i = 0; i < 16; i++)
> > +   {
> > +     if (! readfunc (sigreg_ptr, &val, arg))
> > +       return false;
> > +     Dwarf_Word val_low = gprs[i];
> > +     val = (val << 32) | val_low;
> > +     gprs[i] = val;
> > +     sigreg_ptr += 4;
> > +   }
> > +    }
> > +  if (! setfunc (0, 16, gprs, arg))
> > +    return false;
> > +  if (! setfunc (16, 16, fprs, arg))
> > +    return false;
> > +  *signal_framep = true;
> > +  return true;
> > +}
> 
> This code is a little too magic.
> Please add a comment at the top in which situations it works and how.

Added this function comment:

/* s390/s390x do not annotate signal handler frame by CFI.  It would be also
   difficult as PC points into a stub built on stock.  Function below is called
   only if unwinder could not find CFI.  Function then verifies the register
   state for this frame really belongs to a signal frame.  In such case it
   fetches original registers saved by the signal frame.  */


> > @@ -539,7 +553,7 @@ handle_cfi (Dwfl_Frame *state, Dwarf_Addr pc, Dwarf_CFI 
> > *cfi, Dwarf_Addr bias)
> >         {
> >           /* REGNO is undefined.  */
> >           unsigned ra = frame->fde->cie->return_address_register;
> > -         if (regno == ra)
> > +         if (ebl_dwarf_to_regno (ebl, &ra) && regno == ra)
> >             unwound->pc_state = DWFL_FRAME_STATE_PC_UNDEFINED;
> >           continue;
> >         }
> 
> OK. Shouldn't this have been part of the previous (ppc) patch?

Checked in by the mail:
        [obv] Fix forgotten call of ebl_dwarf_to_regno


> >  void
> >  internal_function
> >  __libdwfl_frame_unwind (Dwfl_Frame *state)
> > @@ -618,4 +684,20 @@ __libdwfl_frame_unwind (Dwfl_Frame *state)
> >         return;
> >     }
> >      }
> > +  assert (state->unwound == NULL);
> > +  Dwfl_Thread *thread = state->thread;
> > +  Dwfl_Process *process = thread->process;
> > +  Ebl *ebl = process->ebl;
> > +  new_unwound (state);
> > +  state->unwound->pc_state = DWFL_FRAME_STATE_PC_UNDEFINED;
> > +  // &Dwfl_Frame.signal_frame cannot be passed as it is a bitfield.
> > +  bool signal_frame = false;
> > +  if (! ebl_unwind (ebl, pc, setfunc, getfunc, readfunc, state, 
> > &signal_frame))
> > +    {
> > +      state->unwound->pc_state = DWFL_FRAME_STATE_ERROR;
> > +      // __libdwfl_seterrno has been called above.
> > +      return;
> > +    }
> > +  assert (state->unwound->pc_state == DWFL_FRAME_STATE_PC_SET);
> > +  state->unwound->signal_frame = signal_frame;
> >  }
> 
> Should this always be done?
> Or only if there is no dwarf or no cfi range for the given address?

Currently ebl_unwind is executed only if there is no module or no dwarf or no
cfi range.

For the s390* case it would be enough to execute ebl_unwind only if there is
no module but I find it a bit too narrowly bounded generic code to s390*.

If CFI/DWARF for PC has been found but handle_cfi unwinder failed for it
ebl_unwind should not be executed and it really is not executed.

Do we agree here?


> > +/* Get previous frame state for an existing frame state.  PC is for the
> > +   existing frame.  SETFUNC sets register in the previous frame.  GETFUNC 
> > gets
> > +   register from the existing frame.  Note that GETFUNC vs. SETFUNC act on
> > +   a disjunct set of registers.  READFUNC reads memory.  ARG has to be 
> > passed
> > +   for SETFUNC, GETFUNC and READFUNC.  *SIGNAL_FRAMEP is initialized to 
> > false,
> > +   it can be set to true if existing frame is a signal frame.  
> > SIGNAL_FRAMEP is
> > +   never NULL.  */
> > +bool EBLHOOK(unwind) (Ebl *ebl, Dwarf_Addr pc, ebl_tid_registers_t 
> > *setfunc,
> > +                 ebl_tid_registers_get_t *getfunc,
> > +                 ebl_pid_memory_read_t *readfunc, void *arg,
> > +                 bool *signal_framep);
> 
> This should explain when it is called.

Added:

/* Get previous frame state for an existing frame state.  Method is called only 
   if unwinder could not find CFI for current PC.  PC is for the
   existing frame.  SETFUNC sets register in the previous frame.  GETFUNC gets
[...]


> > --- a/libebl/libebl.h
> > +++ b/libebl/libebl.h
> > @@ -433,6 +433,33 @@ extern const char *ebl_get_symbol (Ebl *ebl, size_t 
> > ndx, GElf_Sym *symp,
> >  extern bool ebl_dwarf_to_regno (Ebl *ebl, unsigned *regno)
> >    __nonnull_attribute__ (1, 2);
> >  
> > +/* Modify PC as fetched from inferior data into valid PC.  */
> > +extern void ebl_normalize_pc (Ebl *ebl, Dwarf_Addr *pc)
> > +  __nonnull_attribute__ (1, 2);
> > +
> > +/* Callback type for FIXME.
> > +   Register -1 is mapped to PC (if arch PC has no DWARF number).  */
> > +typedef bool (ebl_tid_registers_get_t) (int firstreg, unsigned nregs,
> > +                                   Dwarf_Word *regs, void *arg)
> > +  __nonnull_attribute__ (3);
> 
> FIXME should be ebl_unwind I assume.

Yes.  And the -1 mapping does not make sense, PC is already passed to
ebl/backend.  Therefore also deleted:

libdwfl/frame_unwind.c:
static bool
getfunc (int firstreg, unsigned nregs, Dwarf_Word *regs, void *arg)
-  if (firstreg < 0)
-    {
-      assert (firstreg == -1);
-      assert (nregs > 0);
-      assert (state->pc_state == DWFL_FRAME_STATE_PC_SET);
-      *regs = state->pc;
-      firstreg++;
-      nregs--;
-      regs++;
-    }
+  assert (firstreg >= 0);

The comment is now:
        /* Callback type for ebl_unwind's parameter getfunc.  */


     /* Callback type for ebl_unwind's parameter readfunc.  */
> > +typedef bool (ebl_pid_memory_read_t) (Dwarf_Addr addr, Dwarf_Word *data,
> > +                                 void *arg)
> > +  __nonnull_attribute__ (3);
> > +
> > +/* Get previous frame state for an existing frame state.  PC is for the
> > +   existing frame.  SETFUNC sets register in the previous frame.  GETFUNC 
> > gets
> > +   register from the existing frame.  Note that GETFUNC vs. SETFUNC act on
> > +   a disjunct set of registers.  READFUNC reads memory.  ARG has to be 
> > passed
> > +   for SETFUNC, GETFUNC and READFUNC.  *SIGNAL_FRAMEP is initialized to 
> > false,
> > +   it can be set to true if existing frame is a signal frame.  
> > SIGNAL_FRAMEP is
> > +   never NULL.  */
> > +extern bool ebl_unwind (Ebl *ebl, Dwarf_Addr pc, ebl_tid_registers_t 
> > *setfunc,
> > +                   ebl_tid_registers_get_t *getfunc,
> > +                   ebl_pid_memory_read_t *readfunc, void *arg,
> > +                   bool *signal_framep)
> > +  __nonnull_attribute__ (1, 3, 4, 5, 7);
> > +
> >  #ifdef __cplusplus
> >  }
> >  #endif
> 
> See above about comment explanation.

Copied there the comment with added:
        Method is called only if unwinder could not find CFI for current PC.


Thanks,
Jan
--- Begin Message ---
backends/
2013-12-17  Jan Kratochvil  <[email protected]>

        unwinder: s390 and s390x
        * Makefile.am (s390_SRCS): Add s390_initreg.c and s390_unwind.c.
        * s390_corenote.c (prstatus_regs): Set PC_REGISTER.  Reindent all the
        entries.
        * s390_init.c (s390_init): Initialize frame_nregs,
        set_initial_registers_tid, normalize_pc and unwind.
        * s390_initreg.c: New file.
        * s390_unwind.c: New file.

libdwfl/
2013-12-17  Jan Kratochvil  <[email protected]>

        unwinder: s390 and s390x
        * dwfl_frame_pc.c (dwfl_frame_pc): Call ebl_normalize_pc.
        * frame_unwind.c (new_unwound): New function from ...
        (handle_cfi): ... here.  Call it.
        (setfunc, getfunc, readfunc): New functions.
        (__libdwfl_frame_unwind): Call ebl_unwind with those functions.
        * linux-core-attach.c (core_set_initial_registers): Always iterate
        through the Ebl_Register_Location loop.  Call
        dwfl_thread_state_register_pc there.

libebl/
2013-12-17  Jan Kratochvil  <[email protected]>

        unwinder: s390 and s390x
        * Makefile.am (gen_SOURCES): Add eblnormalizepc.c and eblunwind.c.
        * ebl-hooks.h (normalize_pc, unwind): New.
        * eblnormalizepc.c: New file.
        * eblunwind.c: New file.
        * libebl.h (Ebl_Register_Location): Add field pc_register.
        (ebl_normalize_pc): New declaration.
        (ebl_tid_registers_get_t, ebl_pid_memory_read_t): New definitions.
        (ebl_unwind): New declaration.

tests/
2013-12-17  Jan Kratochvil  <[email protected]>

        unwinder: s390 and s390x
        * Makefile.am (TESTS): Add run-backtrace-core-s390x.sh and
        run-backtrace-core-s390.sh.
        (EXTRA_DIST): Add backtrace.s390x.core.bz2, backtrace.s390x.exec.bz2,
        backtrace.s390.core.bz2, backtrace.s390.exec.bz2,
        run-backtrace-core-s390x.sh and run-backtrace-core-s390.sh.
        * backtrace.s390.core.bz2: New file.
        * backtrace.s390.exec.bz2: New file.
        * backtrace.s390x.core.bz2: New file.
        * backtrace.s390x.exec.bz2: New file.
        * run-backtrace-core-s390.sh: New file.
        * run-backtrace-core-s390x.sh: New file.

Signed-off-by: Jan Kratochvil <[email protected]>
---
 backends/Makefile.am              |   3 +-
 backends/s390_corenote.c          |  10 +--
 backends/s390_init.c              |   9 +++
 backends/s390_initreg.c           |  87 ++++++++++++++++++++++++
 backends/s390_unwind.c            | 139 ++++++++++++++++++++++++++++++++++++++
 libdwfl/dwfl_frame_pc.c           |   1 +
 libdwfl/frame_unwind.c            |  93 ++++++++++++++++++++++---
 libdwfl/linux-core-attach.c       |  14 ++--
 libebl/Makefile.am                |   3 +-
 libebl/ebl-hooks.h                |  17 +++++
 libebl/eblnormalizepc.c           |  40 +++++++++++
 libebl/eblunwind.c                |  43 ++++++++++++
 libebl/libebl.h                   |  29 ++++++++
 tests/Makefile.am                 |   8 ++-
 tests/backtrace.s390.core.bz2     | Bin 0 -> 7375 bytes
 tests/backtrace.s390.exec.bz2     | Bin 0 -> 352692 bytes
 tests/backtrace.s390x.core.bz2    | Bin 0 -> 7740 bytes
 tests/backtrace.s390x.exec.bz2    | Bin 0 -> 347228 bytes
 tests/run-backtrace-core-s390.sh  |  20 ++++++
 tests/run-backtrace-core-s390x.sh |  20 ++++++
 20 files changed, 513 insertions(+), 23 deletions(-)
 create mode 100644 backends/s390_initreg.c
 create mode 100644 backends/s390_unwind.c
 create mode 100644 libebl/eblnormalizepc.c
 create mode 100644 libebl/eblunwind.c
 create mode 100644 tests/backtrace.s390.core.bz2
 create mode 100644 tests/backtrace.s390.exec.bz2
 create mode 100644 tests/backtrace.s390x.core.bz2
 create mode 100644 tests/backtrace.s390x.exec.bz2
 create mode 100755 tests/run-backtrace-core-s390.sh
 create mode 100755 tests/run-backtrace-core-s390x.sh

diff --git a/backends/Makefile.am b/backends/Makefile.am
index 4752a64..ec9e0a3 100644
--- a/backends/Makefile.am
+++ b/backends/Makefile.am
@@ -106,7 +106,8 @@ libebl_ppc64_pic_a_SOURCES = $(ppc64_SRCS)
 am_libebl_ppc64_pic_a_OBJECTS = $(ppc64_SRCS:.c=.os)
 
 s390_SRCS = s390_init.c s390_symbol.c s390_regs.c s390_retval.c \
-           s390_corenote.c s390x_corenote.c s390_cfi.c
+           s390_corenote.c s390x_corenote.c s390_cfi.c s390_initreg.c \
+           s390_unwind.c
 libebl_s390_pic_a_SOURCES = $(s390_SRCS)
 am_libebl_s390_pic_a_OBJECTS = $(s390_SRCS:.c=.os)
 
diff --git a/backends/s390_corenote.c b/backends/s390_corenote.c
index b88c05c..7ca3516 100644
--- a/backends/s390_corenote.c
+++ b/backends/s390_corenote.c
@@ -47,13 +47,13 @@
 
 static const Ebl_Register_Location prstatus_regs[] =
   {
-#define GR(at, n, dwreg, b)                                            \
+#define GR(at, n, dwreg, b...)                                         \
     { .offset = at * BITS/8, .regno = dwreg, .count = n, .bits = b }
 
-    GR ( 0,  1, 64, BITS),             /* pswm */
-    GR ( 1,  1, 65, BITS),             /* pswa */
-    GR ( 2, 16,  0, BITS),             /* r0-r15 */
-    GR (18, 16, 48,   32),             /* ar0-ar15 */
+    GR ( 0,  1, 64, BITS),                             /* pswm */
+    GR ( 1,  1, 65, BITS, .pc_register = true ),       /* pswa */
+    GR ( 2, 16,  0, BITS),                             /* r0-r15 */
+    GR (18, 16, 48,   32),                             /* ar0-ar15 */
 
 #undef GR
   };
diff --git a/backends/s390_init.c b/backends/s390_init.c
index 630a2ee..26b20b4 100644
--- a/backends/s390_init.c
+++ b/backends/s390_init.c
@@ -62,6 +62,15 @@ s390_init (elf, machine, eh, ehlen)
   else
     HOOK (eh, core_note);
   HOOK (eh, abi_cfi);
+  /* gcc/config/ #define DWARF_FRAME_REGISTERS 34.
+     But from the gcc/config/s390/s390.h "Register usage." comment it looks as
+     if #32 (Argument pointer) and #33 (Condition code) are not used for
+     unwinding.  */
+  eh->frame_nregs = 32;
+  HOOK (eh, set_initial_registers_tid);
+  if (eh->class == ELFCLASS32)
+    HOOK (eh, normalize_pc);
+  HOOK (eh, unwind);
 
   /* Only the 64-bit format uses the incorrect hash table entry size.  */
   if (eh->class == ELFCLASS64)
diff --git a/backends/s390_initreg.c b/backends/s390_initreg.c
new file mode 100644
index 0000000..62a1531
--- /dev/null
+++ b/backends/s390_initreg.c
@@ -0,0 +1,87 @@
+/* Fetch live process registers from TID.
+   Copyright (C) 2013 Red Hat, Inc.
+   This file is part of elfutils.
+
+   This file is free software; you can redistribute it and/or modify
+   it under the terms of either
+
+     * the GNU Lesser General Public License as published by the Free
+       Software Foundation; either version 3 of the License, or (at
+       your option) any later version
+
+   or
+
+     * the GNU General Public License as published by the Free
+       Software Foundation; either version 2 of the License, or (at
+       your option) any later version
+
+   or both in parallel, as here.
+
+   elfutils is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received copies of the GNU General Public License and
+   the GNU Lesser General Public License along with this program.  If
+   not, see <http://www.gnu.org/licenses/>.  */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include "system.h"
+#include <assert.h>
+#ifdef __s390__
+# include <sys/user.h>
+# include <asm/ptrace.h>
+# include <sys/ptrace.h>
+#endif
+
+#define BACKEND s390_
+#include "libebl_CPU.h"
+
+bool
+s390_set_initial_registers_tid (pid_t tid __attribute__ ((unused)),
+                         ebl_tid_registers_t *setfunc __attribute__ ((unused)),
+                               void *arg __attribute__ ((unused)))
+{
+#ifndef __s390__
+  return false;
+#else /* __s390__ */
+  struct user user_regs;
+  ptrace_area parea;
+  parea.process_addr = (uintptr_t) &user_regs;
+  parea.kernel_addr = 0;
+  parea.len = sizeof (user_regs);
+  if (ptrace (PTRACE_PEEKUSR_AREA, tid, &parea, NULL) != 0)
+    return false;
+  /* If we run as s390x we get the 64-bit registers of tid.
+     But -m31 executable seems to use only the 32-bit parts of its
+     registers so we ignore the upper half.  */
+  Dwarf_Word dwarf_regs[16];
+  for (unsigned u = 0; u < 16; u++)
+    dwarf_regs[u] = user_regs.regs.gprs[u];
+  if (! setfunc (0, 16, dwarf_regs, arg))
+    return false;
+  /* Avoid conversion double -> integer.  */
+  eu_static_assert (sizeof user_regs.regs.fp_regs.fprs[0]
+                   == sizeof dwarf_regs[0]);
+  for (unsigned u = 0; u < 16; u++)
+    dwarf_regs[u] = *((const __typeof (dwarf_regs[0]) *)
+                     &user_regs.regs.fp_regs.fprs[u]);
+  if (! setfunc (16, 16, dwarf_regs, arg))
+    return false;
+  dwarf_regs[0] = user_regs.regs.psw.addr;
+  return setfunc (-1, 1, dwarf_regs, arg);
+#endif /* __s390__ */
+}
+
+void
+s390_normalize_pc (Ebl *ebl __attribute__ ((unused)), Dwarf_Addr *pc)
+{
+  assert (ebl->class == ELFCLASS32);
+
+  /* Clear S390 bit 31.  */
+  *pc &= (1U << 31) - 1;
+}
diff --git a/backends/s390_unwind.c b/backends/s390_unwind.c
new file mode 100644
index 0000000..6286ee5
--- /dev/null
+++ b/backends/s390_unwind.c
@@ -0,0 +1,139 @@
+/* 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 <stdlib.h>
+#include <assert.h>
+
+#define BACKEND s390_
+#include "libebl_CPU.h"
+
+/* s390/s390x do not annotate signal handler frame by CFI.  It would be also
+   difficult as PC points into a stub built on stock.  Function below is called
+   only if unwinder could not find CFI.  Function then verifies the register
+   state for this frame really belongs to a signal frame.  In such case it
+   fetches original registers saved by the signal frame.  */
+
+bool
+s390_unwind (Ebl *ebl, Dwarf_Addr pc, ebl_tid_registers_t *setfunc,
+            ebl_tid_registers_get_t *getfunc, ebl_pid_memory_read_t *readfunc,
+            void *arg, bool *signal_framep)
+{
+  /* Caller already assumed caller adjustment but S390 instructions are 4 bytes
+     long.  Undo it.  */
+  if ((pc & 0x3) != 0x3)
+    return false;
+  pc++;
+  /* We can assume big-endian read here.  */
+  Dwarf_Word instr;
+  if (! readfunc (pc, &instr, arg))
+    return false;
+  /* Fetch only the very first two bytes.  */
+  instr = (instr >> (ebl->class == ELFCLASS64 ? 48 : 16)) & 0xffff;
+  /* See GDB s390_sigtramp_frame_sniffer.  */
+  /* Check for 'svc' as the first instruction.  */
+  if (((instr >> 8) & 0xff) != 0x0a)
+    return false;
+  /* Check for 'sigreturn' or 'rt_sigreturn' as the second instruction.  */
+  if ((instr & 0xff) != 119 && (instr & 0xff) != 173)
+    return false;
+  /* See GDB s390_sigtramp_frame_unwind_cache.  */
+  Dwarf_Word this_sp;
+  if (! getfunc (0 + 15, 1, &this_sp, arg))
+    return false;
+  unsigned word_size = ebl->class == ELFCLASS64 ? 8 : 4;
+  Dwarf_Addr next_cfa = this_sp + 16 * word_size + 32;
+  /* "New-style RT frame" is not supported,
+     assuming "Old-style RT frame and all non-RT frames".
+     Pointer to the array of saved registers is at NEXT_CFA + 8.  */
+  Dwarf_Word sigreg_ptr;
+  if (! readfunc (next_cfa + 8, &sigreg_ptr, arg))
+    return false;
+  /* Skip PSW mask.  */
+  sigreg_ptr += word_size;
+  /* Read PSW address.  */
+  Dwarf_Word val;
+  if (! readfunc (sigreg_ptr, &val, arg))
+    return false;
+  if (! setfunc (-1, 1, &val, arg))
+    return false;
+  sigreg_ptr += word_size;
+  /* Then the GPRs.  */
+  Dwarf_Word gprs[16];
+  for (int i = 0; i < 16; i++)
+    {
+      if (! readfunc (sigreg_ptr, &gprs[i], arg))
+       return false;
+      sigreg_ptr += word_size;
+    }
+  /* Then the ACRs.  Skip them, they are not used in CFI.  */
+  for (int i = 0; i < 16; i++)
+    sigreg_ptr += 4;
+  /* The floating-point control word.  */
+  sigreg_ptr += 8;
+  /* And finally the FPRs.  */
+  Dwarf_Word fprs[16];
+  for (int i = 0; i < 16; i++)
+    {
+      if (! readfunc (sigreg_ptr, &val, arg))
+       return false;
+      if (ebl->class == ELFCLASS32)
+       {
+         Dwarf_Addr val_low;
+         if (! readfunc (sigreg_ptr + 4, &val_low, arg))
+           return false;
+         val = (val << 32) | val_low;
+       }
+      fprs[i] = val;
+      sigreg_ptr += 8;
+    }
+  /* If we have them, the GPR upper halves are appended at the end.  */
+  if (ebl->class == ELFCLASS32)
+    {
+      /* Skip signal number.  */
+      sigreg_ptr += 4;
+      for (int i = 0; i < 16; i++)
+       {
+         if (! readfunc (sigreg_ptr, &val, arg))
+           return false;
+         Dwarf_Word val_low = gprs[i];
+         val = (val << 32) | val_low;
+         gprs[i] = val;
+         sigreg_ptr += 4;
+       }
+    }
+  if (! setfunc (0, 16, gprs, arg))
+    return false;
+  if (! setfunc (16, 16, fprs, arg))
+    return false;
+  *signal_framep = true;
+  return true;
+}
diff --git a/libdwfl/dwfl_frame_pc.c b/libdwfl/dwfl_frame_pc.c
index 5462e4c..296c815 100644
--- a/libdwfl/dwfl_frame_pc.c
+++ b/libdwfl/dwfl_frame_pc.c
@@ -37,6 +37,7 @@ dwfl_frame_pc (Dwfl_Frame *state, Dwarf_Addr *pc, bool 
*isactivation)
 {
   assert (state->pc_state == DWFL_FRAME_STATE_PC_SET);
   *pc = state->pc;
+  ebl_normalize_pc (state->thread->process->ebl, pc);
   if (isactivation)
     {
       /* Bottom frame?  */
diff --git a/libdwfl/frame_unwind.c b/libdwfl/frame_unwind.c
index 3635df1..671c6d1 100644
--- a/libdwfl/frame_unwind.c
+++ b/libdwfl/frame_unwind.c
@@ -494,6 +494,26 @@ expr_eval (Dwfl_Frame *state, Dwarf_Frame *frame, const 
Dwarf_Op *ops,
   return true;
 }
 
+static void
+new_unwound (Dwfl_Frame *state)
+{
+  assert (state->unwound == NULL);
+  Dwfl_Thread *thread = state->thread;
+  Dwfl_Process *process = thread->process;
+  Ebl *ebl = process->ebl;
+  size_t nregs = ebl_frame_nregs (ebl);
+  assert (nregs > 0);
+  Dwfl_Frame *unwound;
+  unwound = malloc (sizeof (*unwound) + sizeof (*unwound->regs) * nregs);
+  state->unwound = unwound;
+  unwound->thread = thread;
+  unwound->unwound = NULL;
+  unwound->signal_frame = false;
+  unwound->initial_frame = false;
+  unwound->pc_state = DWFL_FRAME_STATE_ERROR;
+  memset (unwound->regs_set, 0, sizeof (unwound->regs_set));
+}
+
 /* 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
@@ -508,20 +528,14 @@ handle_cfi (Dwfl_Frame *state, Dwarf_Addr pc, Dwarf_CFI 
*cfi, Dwarf_Addr bias)
       __libdwfl_seterrno (DWFL_E_LIBDW);
       return;
     }
+  new_unwound (state);
+  Dwfl_Frame *unwound = state->unwound;
+  unwound->signal_frame = frame->fde->cie->signal_frame;
   Dwfl_Thread *thread = state->thread;
   Dwfl_Process *process = thread->process;
   Ebl *ebl = process->ebl;
   size_t nregs = ebl_frame_nregs (ebl);
   assert (nregs > 0);
-  Dwfl_Frame *unwound;
-  unwound = malloc (sizeof (*unwound) + sizeof (*unwound->regs) * nregs);
-  state->unwound = unwound;
-  unwound->thread = thread;
-  unwound->unwound = NULL;
-  unwound->signal_frame = frame->fde->cie->signal_frame;
-  unwound->initial_frame = false;
-  unwound->pc_state = DWFL_FRAME_STATE_ERROR;
-  memset (unwound->regs_set, 0, sizeof (unwound->regs_set));
   for (unsigned regno = 0; regno < nregs; regno++)
     {
       Dwarf_Op reg_ops_mem[3], *reg_ops;
@@ -583,6 +597,47 @@ handle_cfi (Dwfl_Frame *state, Dwarf_Addr pc, Dwarf_CFI 
*cfi, Dwarf_Addr bias)
   free (frame);
 }
 
+static bool
+setfunc (int firstreg, unsigned nregs, const Dwarf_Word *regs, void *arg)
+{
+  Dwfl_Frame *state = arg;
+  Dwfl_Frame *unwound = state->unwound;
+  if (firstreg < 0)
+    {
+      assert (firstreg == -1);
+      assert (nregs == 1);
+      assert (unwound->pc_state == DWFL_FRAME_STATE_PC_UNDEFINED);
+      unwound->pc = *regs;
+      unwound->pc_state = DWFL_FRAME_STATE_PC_SET;
+      return true;
+    }
+  while (nregs--)
+    if (! __libdwfl_frame_reg_set (unwound, firstreg++, *regs++))
+      return false;
+  return true;
+}
+
+static bool
+getfunc (int firstreg, unsigned nregs, Dwarf_Word *regs, void *arg)
+{
+  Dwfl_Frame *state = arg;
+  assert (firstreg >= 0);
+  while (nregs--)
+    if (! __libdwfl_frame_reg_get (state, firstreg++, regs++))
+      return false;
+  return true;
+}
+
+static bool
+readfunc (Dwarf_Addr addr, Dwarf_Word *datap, void *arg)
+{
+  Dwfl_Frame *state = arg;
+  Dwfl_Thread *thread = state->thread;
+  Dwfl_Process *process = thread->process;
+  return process->callbacks->memory_read (process->dwfl, addr, datap,
+                                         process->callbacks_arg);
+}
+
 void
 internal_function
 __libdwfl_frame_unwind (Dwfl_Frame *state)
@@ -619,4 +674,24 @@ __libdwfl_frame_unwind (Dwfl_Frame *state)
            return;
        }
     }
+  assert (state->unwound == NULL);
+  Dwfl_Thread *thread = state->thread;
+  Dwfl_Process *process = thread->process;
+  Ebl *ebl = process->ebl;
+  new_unwound (state);
+  state->unwound->pc_state = DWFL_FRAME_STATE_PC_UNDEFINED;
+  // &Dwfl_Frame.signal_frame cannot be passed as it is a bitfield.
+  bool signal_frame = false;
+  if (! ebl_unwind (ebl, pc, setfunc, getfunc, readfunc, state, &signal_frame))
+    {
+      // Discard the unwind attempt.  During next __libdwfl_frame_unwind call
+      // we may have for example the appropriate Dwfl_Module already mapped.
+      assert (state->unwound->unwound == NULL);
+      free (state->unwound);
+      state->unwound = NULL;
+      // __libdwfl_seterrno has been called above.
+      return;
+    }
+  assert (state->unwound->pc_state == DWFL_FRAME_STATE_PC_SET);
+  state->unwound->signal_frame = signal_frame;
 }
diff --git a/libdwfl/linux-core-attach.c b/libdwfl/linux-core-attach.c
index 11379ba..f55faf7 100644
--- a/libdwfl/linux-core-attach.c
+++ b/libdwfl/linux-core-attach.c
@@ -228,12 +228,12 @@ core_set_initial_registers (Dwfl_Thread *thread, void 
*thread_arg_voidp)
   for (size_t regloci = 0; regloci < nregloc; regloci++)
     {
       const Ebl_Register_Location *regloc = reglocs + regloci;
-      if (regloc->regno >= nregs)
+      // Iterate even regs out of NREGS range so that we can find pc_register.
+      if (regloc->bits != 32 && regloc->bits != 64)
        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 < regloc->regno + (regloc->count ?: 1U);
           regno++)
        {
          /* PPC provides DWARF register 65 irrelevant for
@@ -241,7 +241,8 @@ core_set_initial_registers (Dwfl_Thread *thread, void 
*thread_arg_voidp)
             LR (108) is provided earlier (in NT_PRSTATUS) than the # 65.
             FIXME: It depends now on their order in core notes.
             FIXME: It uses private function.  */
-         if (__libdwfl_frame_reg_get (thread->unwound, regno, NULL))
+         if (regno < nregs
+             && __libdwfl_frame_reg_get (thread->unwound, regno, NULL))
            continue;
          Dwarf_Word val;
          switch (regloc->bits)
@@ -266,7 +267,10 @@ core_set_initial_registers (Dwfl_Thread *thread, void 
*thread_arg_voidp)
              abort ();
          }
          /* Registers not valid for CFI are just ignored.  */
-         INTUSE(dwfl_thread_state_registers) (thread, regno, 1, &val);
+         if (regno < nregs)
+           INTUSE(dwfl_thread_state_registers) (thread, regno, 1, &val);
+         if (regloc->pc_register)
+           INTUSE(dwfl_thread_state_register_pc) (thread, val);
          reg_desc += regloc->pad;
        }
     }
diff --git a/libebl/Makefile.am b/libebl/Makefile.am
index 2f3b730..fc4f1b6 100644
--- a/libebl/Makefile.am
+++ b/libebl/Makefile.am
@@ -54,7 +54,8 @@ 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 eblinitreg.c ebldwarftoregno.c
+             eblstother.c eblinitreg.c ebldwarftoregno.c eblnormalizepc.c \
+             eblunwind.c
 
 libebl_a_SOURCES = $(gen_SOURCES)
 
diff --git a/libebl/ebl-hooks.h b/libebl/ebl-hooks.h
index f721bc4..2243895 100644
--- a/libebl/ebl-hooks.h
+++ b/libebl/ebl-hooks.h
@@ -166,5 +166,22 @@ bool EBLHOOK(set_initial_registers_tid) (pid_t tid,
    Dwarf_Frame->REGS indexing.  */
 bool EBLHOOK(dwarf_to_regno) (Ebl *ebl, unsigned *regno);
 
+/* Optionally modify *PC as fetched from inferior data into valid PC
+   instruction pointer.  */
+void EBLHOOK(normalize_pc) (Ebl *ebl, Dwarf_Addr *pc);
+
+/* Get previous frame state for an existing frame state.  Method is called only
+   if unwinder could not find CFI for current PC.  PC is for the
+   existing frame.  SETFUNC sets register in the previous frame.  GETFUNC gets
+   register from the existing frame.  Note that GETFUNC vs. SETFUNC act on
+   a disjunct set of registers.  READFUNC reads memory.  ARG has to be passed
+   for SETFUNC, GETFUNC and READFUNC.  *SIGNAL_FRAMEP is initialized to false,
+   it can be set to true if existing frame is a signal frame.  SIGNAL_FRAMEP is
+   never NULL.  */
+bool EBLHOOK(unwind) (Ebl *ebl, Dwarf_Addr pc, ebl_tid_registers_t *setfunc,
+                     ebl_tid_registers_get_t *getfunc,
+                     ebl_pid_memory_read_t *readfunc, void *arg,
+                     bool *signal_framep);
+
 /* Destructor for ELF backend handle.  */
 void EBLHOOK(destr) (struct ebl *);
diff --git a/libebl/eblnormalizepc.c b/libebl/eblnormalizepc.c
new file mode 100644
index 0000000..a5fea77
--- /dev/null
+++ b/libebl/eblnormalizepc.c
@@ -0,0 +1,40 @@
+/* Modify PC as fetched from inferior data into valid PC.
+   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>
+
+void
+ebl_normalize_pc (Ebl *ebl, Dwarf_Addr *pc)
+{
+  if (ebl != NULL && ebl->normalize_pc != NULL)
+    ebl->normalize_pc (ebl, pc);
+}
diff --git a/libebl/eblunwind.c b/libebl/eblunwind.c
new file mode 100644
index 0000000..1251c1b
--- /dev/null
+++ b/libebl/eblunwind.c
@@ -0,0 +1,43 @@
+/* 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 <libeblP.h>
+
+bool
+ebl_unwind (Ebl *ebl, Dwarf_Addr pc, ebl_tid_registers_t *setfunc,
+           ebl_tid_registers_get_t *getfunc, ebl_pid_memory_read_t *readfunc,
+           void *arg, bool *signal_framep)
+{
+  if (ebl == NULL || ebl->unwind == NULL)
+    return false;
+  return ebl->unwind (ebl, pc, setfunc, getfunc, readfunc, arg, signal_framep);
+}
diff --git a/libebl/libebl.h b/libebl/libebl.h
index 7080b4a..84c2f4c 100644
--- a/libebl/libebl.h
+++ b/libebl/libebl.h
@@ -356,6 +356,7 @@ typedef struct
   uint8_t bits;                        /* Bits of data for one register.  */
   uint8_t pad;                 /* Bytes of padding after register's data.  */
   Dwarf_Half count;            /* Consecutive register numbers here.  */
+  bool pc_register;
 } Ebl_Register_Location;
 
 /* Non-register data items in core notes.  */
@@ -410,6 +411,34 @@ extern size_t ebl_frame_nregs (Ebl *ebl)
 extern bool ebl_dwarf_to_regno (Ebl *ebl, unsigned *regno)
   __nonnull_attribute__ (1, 2);
 
+/* Modify PC as fetched from inferior data into valid PC.  */
+extern void ebl_normalize_pc (Ebl *ebl, Dwarf_Addr *pc)
+  __nonnull_attribute__ (1, 2);
+
+/* Callback type for ebl_unwind's parameter getfunc.  */
+typedef bool (ebl_tid_registers_get_t) (int firstreg, unsigned nregs,
+                                       Dwarf_Word *regs, void *arg)
+  __nonnull_attribute__ (3);
+
+/* Callback type for ebl_unwind's parameter readfunc.  */
+typedef bool (ebl_pid_memory_read_t) (Dwarf_Addr addr, Dwarf_Word *data,
+                                     void *arg)
+  __nonnull_attribute__ (3);
+
+/* Get previous frame state for an existing frame state.  Method is called only
+   if unwinder could not find CFI for current PC.  PC is for the
+   existing frame.  SETFUNC sets register in the previous frame.  GETFUNC gets
+   register from the existing frame.  Note that GETFUNC vs. SETFUNC act on
+   a disjunct set of registers.  READFUNC reads memory.  ARG has to be passed
+   for SETFUNC, GETFUNC and READFUNC.  *SIGNAL_FRAMEP is initialized to false,
+   it can be set to true if existing frame is a signal frame.  SIGNAL_FRAMEP is
+   never NULL.  */
+extern bool ebl_unwind (Ebl *ebl, Dwarf_Addr pc, ebl_tid_registers_t *setfunc,
+                       ebl_tid_registers_get_t *getfunc,
+                       ebl_pid_memory_read_t *readfunc, void *arg,
+                       bool *signal_framep)
+  __nonnull_attribute__ (1, 3, 4, 5, 7);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 7739623..b58e0f5 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -106,7 +106,8 @@ TESTS = run-arextract.sh run-arsymtest.sh newfile 
test-nlist \
        run-backtrace-native.sh run-backtrace-data.sh run-backtrace-dwarf.sh \
        run-backtrace-native-biarch.sh run-backtrace-native-core.sh \
        run-backtrace-native-core-biarch.sh run-backtrace-core-x86_64.sh \
-       run-backtrace-core-i386.sh run-backtrace-core-ppc.sh
+       run-backtrace-core-i386.sh run-backtrace-core-ppc.sh \
+       run-backtrace-core-s390x.sh run-backtrace-core-s390.sh
 
 if !BIARCH
 export ELFUTILS_DISABLE_BIARCH = 1
@@ -247,7 +248,10 @@ EXTRA_DIST = run-arextract.sh run-arsymtest.sh \
             backtrace-subr.sh backtrace.i386.core.bz2 backtrace.i386.exec.bz2 \
             backtrace.x86_64.core.bz2 backtrace.x86_64.exec.bz2 \
             backtrace.ppc.core.bz2 backtrace.ppc.exec.bz2 \
-            run-backtrace-core-ppc.sh
+            run-backtrace-core-ppc.sh \
+            backtrace.s390x.core.bz2 backtrace.s390x.exec.bz2 \
+            backtrace.s390.core.bz2 backtrace.s390.exec.bz2 \
+            run-backtrace-core-s390x.sh run-backtrace-core-s390.sh
 
 if USE_VALGRIND
 valgrind_cmd='valgrind -q --error-exitcode=1 --run-libc-freeres=no'
diff --git a/tests/run-backtrace-core-s390.sh b/tests/run-backtrace-core-s390.sh
new file mode 100755
index 0000000..d3b6dc9
--- /dev/null
+++ b/tests/run-backtrace-core-s390.sh
@@ -0,0 +1,20 @@
+#! /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/backtrace-subr.sh
+
+check_core s390
diff --git a/tests/run-backtrace-core-s390x.sh 
b/tests/run-backtrace-core-s390x.sh
new file mode 100755
index 0000000..c3e236d
--- /dev/null
+++ b/tests/run-backtrace-core-s390x.sh
@@ -0,0 +1,20 @@
+#! /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/backtrace-subr.sh
+
+check_core s390x
-- 
1.8.3.1

--- End Message ---

Reply via email to