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]

[tests patchv2] FYI unwinder: tests/ update


Hi Mark,

jankratochvil/unwindx86

I do not include the full git format-patch as it contains huge .bz2 files.

Thanks for the exhaustive review.


Besides changes described below I had to make some small libdwfl/ extension to
keep "Unwinding not supported for this architecture" error from
__libdwfl_attach_state_for_core() call in dwfl_core_file_report()
till the unwinding request later.  One can for example comment out from
backends/i386_init.c
  eh->frame_nregs = 9;
  HOOK (eh, set_initial_registers_tid);
to test such behavior.

__libdwfl_attach_state_for_core() should not report error even if
dwfl_core_file_report() fails, as the caller may not want to unwind the core
at all.  Still the error has to be kept somewhere.


On Tue, 12 Nov 2013 21:11:43 +0100, Mark Wielaard wrote:
> As a side note, I see it depends on new conditional BIARCH.
> It would probably be a good idea if configure warned when detecting a
> 64bit host, that didn't support biarch. So the user knows some tests
> won't be run.

Done.
	m4/biarch.m4
	AC_MSG_WARN([not running biarch tests, $biarch_CC does not work])


> Although if I am reading the patch correct the backtrace-child-biarch
> test program is still being build in that case. But then it is just the
> host arch? Yes, it is, there is a comment in run-backtrace.sh that says
> so.

This has been fixed now:

Currently the *-biarch.sh tests report SKIP if not supported:
	SKIP: run-backtrace-native-biarch.sh
	PASS: run-backtrace-native.sh
	SKIP: run-backtrace-native-core-biarch.sh
	PASS: run-backtrace-native-core.sh
Using a bit ugly
	if !BIARCH
	export ELFUTILS_DISABLE_BIARCH = 1
	endif

One could just omit those unsupported testcases but that does not seem right
to me.

One could also SKIP them by some less ugly hack than the environment variable.


> A little explanation of the general design of the test(s) would be
> appreciated. Just something in run-backtrace.sh that explains what
> program is called to do what, which signals are send to trigger which
> actions and which parts are generic and which are x86 specific.

TBH I do not put so high quality + documentation requirements on testcases,
hey are just the testcases...


> Also in general I think this is a somewhat giant testcase. Although good
> to test the whole system, it is somewhat hard to figure out why/where
> something fails. For example on my current system run-backtrace.sh
> sometimes FAILs. But I cannot easily figure out why,

It prints hopefully a descriptive error message into tests/*.log file.


> I don't really understand the need for backtrace-data.

I had to write it as an example code anyway so I included it therein.  Also
this public part of API would not be really tested without this testcase.
But sure it is not a real test, it does not test anything much interesting.

But I have put there run-backtrace-data.sh and added it also to TESTS.


> Does CC_BIARCH also take care of any include or library flags for 32bit?
> Or is it assumed that all it takes is adding -m32?
> 
> OK, testing on at least fedora and debian seems to confirm you don't
> need anything else as long as you have to right multilib/32bit
> compiler/libs installed (gcc-multilib and libc6-dev-i386 on debian).

m4/biarch.m4 has been written by Roland.

But for example recent Ubuntu includes support for x32 which is not handled by
elfutils at all I think.


> > +backtrace_LDADD = $(libdw) $(libelf) $(libmudflap)
> > +backtrace_child_CFLAGS = -fPIE
> > +backtrace_child_LDFLAGS = -pie -pthread
> > +backtrace_data_LDADD = $(libdw) $(libelf) $(libmudflap)
> 
> Is there a reason for building backtrace_child pie? Just another test factor?

Yes.


> Why not build backtrace-child-biarch pie?

It is built as PIE.  I have put there just a new comment:

# backtrace-child-biarch also uses those *_CFLAGS and *_LDLAGS variables:
backtrace_child_CFLAGS = -fPIE
backtrace_child_LDFLAGS = -pie -pthread


> backtrace-child.c will be generated once or twice. A native-host version
> (likely 64bit) and a biarch version (likely 32bit, but maybe also
> 64bit).

It has changed now, there is BIARCH Makefile.am conditional.


> It can be called with three options: --ptraceme, --gencore or --run.
> 
> It will always first call three dummy functions that are empty no clone,
> no inline asm volatile ("") functions. (why?)

noinline is probably clear, Jakub Jelinek always also puts there noclone,
which in fact is also clear why.  That asm volatile ("") is needed otherwise
static noinline noclone empty function is completely omitted from its caller.


> Then it will create a new
> thread that calls a function "start" which will call a function
> "backtracegen" which will call a vararg no-return function "stdarg" that
> will install a SIGUSR2 signal handler ("sigusr2").
> 
> If --ptraceme is given both the main and start thread will call ptrace
> traceme.
> 
> If --gencore is given the main thread call pthread_join on the start
> thread. The start thread will call the sigusr2 handler (directly, not
> through raising a signal), which will then call abort().
> 
> If --run is given the main thread will raise (SIGUSR2) and the start
> thread will, depending on whether it is build on x86_64 either raise
> SIGUSR1 or call the sigusr2 handler directly (without raising the
> signal). The sigusr2 handler will then raise SIGUSR1 itself.

I have put into backtrace-child.c this comment:

/* Command line syntax: ./backtrace-child [--ptraceme|--gencore] --run
   --ptraceme will call ptrace (PTRACE_TRACEME) in the two threads.
   --gencore will call abort () at its end.
   --run is always required.
   Main thread will signal SIGUSR2.  Other thread will signal SIGUSR1.
   On x86_64 only:
     PC will get changed to function 'jmp' by backtrace.c function
     prepare_thread.  Then SIGUSR2 will be signalled to backtrace-child
     which will invoke function sigusr2.
   On non-x86_64:
     sigusr2 gets called by normal function call from function stdarg.
   On any arch then sigusr2 calls raise (SIGUSR1) for --ptraceme.
   abort () is called otherwise, expected for --gencore core dump.

   Expected x86_64 output:
   TID 10276:
   # 0 0x7f7ab61e9e6b      raise
   # 1 0x7f7ab661af47 - 1  main
   # 2 0x7f7ab5e3bb45 - 1  __libc_start_main
   # 3 0x7f7ab661aa09 - 1  _start
   TID 10278:
   # 0 0x7f7ab61e9e6b      raise
   # 1 0x7f7ab661ab3c - 1  sigusr2
   # 2 0x7f7ab5e4fa60      __restore_rt
   # 3 0x7f7ab661ab47      jmp
   # 4 0x7f7ab661ac92 - 1  stdarg
   # 5 0x7f7ab661acba - 1  backtracegen
   # 6 0x7f7ab661acd1 - 1  start
   # 7 0x7f7ab61e2c53 - 1  start_thread
   # 8 0x7f7ab5f0fdbd - 1  __clone

   Expected non-x86_64 (i386) output; __kernel_vsyscall are skipped if found:
   TID 10408:
   # 0 0xf779f430          __kernel_vsyscall
   # 1 0xf7771466 - 1      raise
   # 2 0xf77c1d07 - 1      main
   # 3 0xf75bd963 - 1      __libc_start_main
   # 4 0xf77c1761 - 1      _start
   TID 10412:
   # 0 0xf779f430          __kernel_vsyscall
   # 1 0xf7771466 - 1      raise
   # 2 0xf77c18f4 - 1      sigusr2
   # 3 0xf77c1a10 - 1      stdarg
   # 4 0xf77c1a2c - 1      backtracegen
   # 5 0xf77c1a48 - 1      start
   # 6 0xf77699da - 1      start_thread
   # 7 0xf769bbfe - 1      __clone
   */


I am aware of too many possible improvements but ... it is just a test.


> (This is really confusing, why the difference between x86_64 and the
> rest?)

1. Changing inferior PC is arch-dependent (PTRACE_POKEUSER on x86_64).
2. Some of the arch-independent tests like this one I find sufficient to run
   only on one arch.  And everyone has x86_64.
3. I found it good enough.  One could port it too all the other archs but see
   item 2.


> There is also an assumption that if the process is build on x86_64 and
> ptraced and a SIGUSR1 signal is caught by the tracee that the PC will be
> adjusted to call the sigusr2 handler (this is also makes this test case
> somewhat hard to follow IMHO).

Yes.


> There are a couple of aborts in this code which I am unsure of whether
> they are meant to be ever called/reached or not. A comment would be nice
> to tell apart the abort () calls that are meant to actually generate the
> core file and those that are there to make sure the test case fails in
> an erroneous state.

I have extended the comment at:
  /* This abort () call is reached to dump core for --gencore.
     Without --gencore it should not be reached.  */
  abort ();

There was already this comment
  /* Not reached, signal will get ptrace-spawn to jump into sigusr2.  */
  abort ();

I have put new comment here:
  sigusr2 (SIGUSR2);
  /* Not reached.  */
  abort ();

here:
  backtracegen ();
  /* Not reached.  */
  abort ();

And here:
    raise (SIGUSR2);
  /* Not reached */
  abort ();


> > +/* Test child for parent backtrace test.
> > [...]
> > +/* 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 ();
> > +}
> 
> Could you explain the .plt part a bit more. This seems to be the most
> tricky part since it seems to rely on a fairly (2 years) recent
> binutils. Is it specific to how you call abort here?

I have removed the .plt testing code now.


> Or is it in general always an issue with how core files are generated that
> they will end up with the thread that caused the core to be generated to go
> through the .plt?

No.


> It is fine for a test to depend on a good toolchain and FAIL if the
> toolchain produces broken/missing frame data. But if at all possible
> lets split that out in its own test.

As a separate test it would be better to include the Jakub's gcc testcase for
DWARF expressions, which I have therefore did now.  The .plt testing is now
completely removed.

There are now backtrace-dwarf.c, cleanup-13.c and run-backtrace-dwarf.sh .


> > +++ b/tests/backtrace-data.c
> 
> I skipped this for now since I don't understand how it is run/tested.

It was not run.  I just had to write some testcase showing custom
Dwfl_Thread_Callbacks so I have also included the it.

I have put there new run-backtrace-data.sh and added it to TESTS.

But still it does not test anything much interesting.


> > +++ b/tests/backtrace.c
> > [...]
> > +/* Older systems (such as RHEL-5; fixed as binutils PR ld/12570 on 2011-06-20)
> > +   will fail this testcase as they do not provide CFI for the .plt section.
> > +   elfutils 'portable' patch/branch disables the .plt unwind test.  */
> 
> It would be nice to add how they fail. So the user can see if it is
> this .plt case or something else. And as mentioned above can we split
> out this .plt corner case in its own test?

As written above I have removed the .plt stuff as you seem too much concerned
about the obsolete systems, despite it was removed in the 'portable' branch.

At least it has forced me to include the more complete Jakub's test, thanks.


> > +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;
> > +}
> 
> OK. nitpick call this dwfl_from_pid.

Couldn't you choose a different name?  IMO dwfl_* names are reserved for
libdwfl.


> > +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);
> > +}
> 
> Is this still needed now that dwfl_core_file_report takes an executable?

Yes, unfortunately it is.  It predates the parameter.  It is planned to be
simplified but so far I would fine OK to keep it as is, the "wrong" code is
already checked in.


> > +static Dwfl *
> > +corefile_to_dwfl (const char *corefile)
> > +{
> > +  return report_corefile (dwfl_offline (), corefile);
> > +}
> 
> OK. nitpick call it dwfl_from_corefile?

Again, I find dwfl_* prefix as reserved, don't you?


> > +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;
> > +}
> 
> OK. I wonder if we could/should provide a helper function directly in
> libdwfl for this. I guess more people will try something like this and
> we can probably do this more efficiently/cache the result.

Maybe we could.  I am not too much concerned about it now.


> > +  if (disable || tid != check_tid)
> > +    return;
> 
> Maybe add a comment?
> 
> // For the main thread we are only interested if we can unwind till
> // we see the "main" symbol.

done.


> > +  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;
> > +  }
> > +}
> 
> This really could use a comment at the top showing the expected
> backtrace:
> 
> /*
>   0 (null)       // .plt
>   1 sigusr2      // in exe
>   2 __restore_rt // or null
>   3 jmp          // offset+0
>   4 stdarg       // in exe
>   5 backtracegen // last offset
>   ...
>     main         // in exe
>   ...
> */

done.


> > +#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__ */
> 
> Urgh. Somewhat unportable :{
> I see what it is doing.

Only the first half remains there now.


> Is this to get us into the .plt entry?
> It could use a comment to explain to porters what the intention is.

The PTRACE_SINGLESTEP part is done so it is no longer needed.


> > +#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);
> > +}
> 
> OK. To get the threads detached, but in a known stopped state.

Yes; it is later patched in the 'portable' branch.


> > +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;
> > +  }
> 
> Currently when we run tests under valgrind we use --trace-children=yes
> which will cause this to launch valgrind as main executable instead. But
> don't worry. I'll fix that when I get to fixing the other valgrind issue
> we found.

Yes, sorry, the valgrind run does not seem to be currently working anyway.


> > +  /* 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);
> 
> Aha, so SIGUSR1 is the pthread, SIGUSR2 is the main thread.
> That isn't immediately clear from the bracktrace_child source code.
> Might want to add a comment there.

It is now documented at the top of backtrace-child:
   Main thread will signal SIGUSR2.  Other thread will signal SIGUSR1.

I have put here the same comment.


> > +  /* Make it true on x86_64 with i386 inferior.  */
> > +  int disable = ehdr->e_ident[EI_CLASS] == ELFCLASS32;
> 
> This could use an explanation of "it". This doesn't give a clue of what
> is being disabled.

There is now instead:
  /* It is false also on x86_64 with i386 inferior.  */
  bool is_x86_64;


> > +#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
> 
> Like above, this could use a comment for porters to explain what address
> is looked up and why. I wonder if this could be done a little more
> portable by having the child just print the needed address and the
> parent reading that. Maybe even use a pipe between the two for
> synchronization.

Isn't it easier this dwfl_module_getsym way?  It is difficult to debug the
multi-process piping things IMO.

I have put there:
      // Find inferior symbol named "jmp".


> > +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;
> > +}
> 
> This works and tests dwfl_report_elf too, so it is fine. But you can
> also just use elf_begin () to open the core file and check the ehdr.

OK, you are right, replaced the is_core() code.


> > +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;
> > +}
> 
> This really could use some documentation or better argument parsing IMHO.
> Why not have simpler arguments:
> 
> --backtrace-child
> --backtrace-pid <pid>
> --backtrace-core <corefile>
> --backtrace-exe <exefile>
> --backtrace-exe-core <exefile> <corefile>
> 
> But if I read run-backtrace.sh correctly you only need the last two. It
> might make the testcase a little more readable/easier to follow to just
> drop the other cases.

TBH I use ./tests/backtrace as a general backtracing tool as src/stack is too
conformant / complicated to use / with less verbose output.

While it may be more nice for the testcase having to type --backtrace-exe etc.
etc. each time during debugging/troubleshooting I find very inconvenient.

Do you agree here?


> > +++ b/tests/run-backtrace.sh
[...]
> > +  # Backtrace core file.
> > +  core="core.`ulimit -c unlimited; set +ex; testrun ${abs_builddir}/$child --gencore --run; true`"
> 
> Why the true? Don't you want to fail early if the core file cannot be generated?

Without 'true' the script will fail due to 'set -e'.

After program dumps a core its exit code is not zero.  'true' overrides it
there.


> Generating real core files is good to test the whole system. But it
> would be good to also add a pregenerated exe and core file to the
> testsuite and run a test on that.

Done.


> It would be nice to split this in three separate tests (that use a
> common backtrace-subr.sh):
> 
> - run-backtrace-core Which would run on pregenerated executables and
>   core files from a known good system for which the backend support is
>   there to run a backtrace on a core. This test would be cross arch.
> 
> - run-backtrace-native Which would do the live process backtracing.
> 
> - run-backtrace-native-core Which would do the backtrace on a just
>   natively generated core file as sanity check that the local
>   toolchain/kernel is sane.
> 
> That would also help with porting new backends since it would show
> different parts of the backend (or local toolchain) that need to have
> support for success.

Done, now:
	tests/run-backtrace-core-i386.sh
	tests/run-backtrace-core-x86_64.sh
	tests/run-backtrace-data.sh
	tests/run-backtrace-dwarf.sh
	tests/run-backtrace-native-biarch.sh
	tests/run-backtrace-native-core-biarch.sh
	tests/run-backtrace-native-core.sh
	tests/run-backtrace-native.sh

I have added -$ARCH suffix and also -biarch suffix as if you do not find *.log
files sufficient to determine which sub-test failed it needs to be done this
way.


Thanks,
Jan


392e91a81d53eda3aec6f03667cd2744f73d173d Mon Sep 17 00:00:00 2001
From: Jan Kratochvil <jan.kratochvil@redhat.com>
Date: Thu, 30 May 2013 14:37:38 +0200
Subject: [PATCH] Unwinder for x86*.

./
2013-11-22  Jan Kratochvil  <jan.kratochvil@redhat.com>

	* configure.ac (CC_BIARCH): Remove AS_IF for it.

m4/
2013-11-22  Jan Kratochvil  <jan.kratochvil@redhat.com>

	* biarch.m4 (utrace_BIARCH): Call AC_MSG_WARN if !BIARCH.

libdwfl/
2013-11-22  Jan Kratochvil  <jan.kratochvil@redhat.com>

	* dwfl_frame.c (dwfl_pid, dwfl_getthreads): Use PROCESS_ATTACH_ERROR if
	PROCESS is NULL.
	* libdwflP.h (struct Dwfl): New field process_attach_error.
	* linux-core-attach.c (__libdwfl_attach_state_for_core): Rename to ...
	(attach_state_for_core): ... here, make it static, change return type,
	no longer use __libdwfl_seterrno.
	(__libdwfl_attach_state_for_core): New wrapper for it.

tests/
2013-11-22  Jan Kratochvil  <jan.kratochvil@redhat.com>

	* Makefile.am (check_PROGRAMS): Add backtrace, backtrace-child,
	backtrace-data and backtrace-dwarf.
	(BUILT_SOURCES, clean-local, backtrace-child-biarch): New.
	(TESTS): Add 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 and run-backtrace-core-i386.sh.
	<!BIARCH> Add export of ELFUTILS_DISABLE_BIARCH.
	(EXTRA_DIST): Add run-backtrace-data.sh, run-backtrace-dwarf.sh,
	cleanup-13.c, run-backtrace-native.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,
	backtrace-subr.sh, backtrace.i386.core.bz2, backtrace.i386.exec.bz2,
	backtrace.x86_64.core.bz2, backtrace.x86_64.exec.bz2.
	(backtrace_LDADD, backtrace_child_CFLAGS, backtrace_child_LDFLAGS)
	(backtrace_data_LDADD, backtrace_dwarf_CFLAGS, backtrace_dwarf_LDADD):
	New.
	* backtrace-child.c: New file.
	* backtrace-data.c: New file.
	* backtrace-dwarf.c: New file.
	* backtrace-subr.sh: New file.
	* backtrace.c: New file.
	* cleanup-13.c: New file.
	* backtrace.i386.core.bz2: New file.
	* backtrace.i386.exec.bz2: New file.
	* backtrace.x86_64.core.bz2: New file.
	* backtrace.x86_64.exec.bz2: New file.
	* run-backtrace-core-i386.sh: New file.
	* run-backtrace-core-x86_64.sh: New file.
	* run-backtrace-native-biarch.sh: New file.
	* run-backtrace-native-core-biarch.sh: New file.
	* run-backtrace-native-core.sh: New file.
	* run-backtrace-native.sh: New file.
	* run-backtrace-data.sh: New file.
	* run-backtrace-dwarf.sh: New file.

Signed-off-by: Jan Kratochvil <jan.kratochvil@redhat.com>
---
 configure.ac                              |   4 +-
 libdwfl/dwfl_frame.c                      |   4 +-
 libdwfl/libdwflP.h                        |   1 +
 libdwfl/linux-core-attach.c               |  50 +--
 m4/biarch.m4                              |   4 +-
 tests/Makefile.am                         |  42 ++-
 tests/backtrace-child.c                   | 210 ++++++++++++
 tests/backtrace-data.c                    | 314 +++++++++++++++++
 tests/backtrace-dwarf.c                   | 192 +++++++++++
 tests/backtrace-subr.sh                   |  88 +++++
 tests/backtrace.c                         | 549 ++++++++++++++++++++++++++++++
 tests/backtrace.i386.core.bz2             | Bin 0 -> 10284 bytes
 tests/backtrace.i386.exec.bz2             | Bin 0 -> 383189 bytes
 tests/backtrace.x86_64.core.bz2           | Bin 0 -> 10833 bytes
 tests/backtrace.x86_64.exec.bz2           | Bin 0 -> 401340 bytes
 tests/cleanup-13.c                        | 334 ++++++++++++++++++
 tests/run-backtrace-core-i386.sh          |  20 ++
 tests/run-backtrace-core-x86_64.sh        |  20 ++
 tests/run-backtrace-data.sh               |  20 ++
 tests/run-backtrace-dwarf.sh              |  20 ++
 tests/run-backtrace-native-biarch.sh      |  33 ++
 tests/run-backtrace-native-core-biarch.sh |  34 ++
 tests/run-backtrace-native-core.sh        |  30 ++
 tests/run-backtrace-native.sh             |  29 ++
 24 files changed, 1970 insertions(+), 28 deletions(-)
 create mode 100644 tests/backtrace-child.c
 create mode 100644 tests/backtrace-data.c
 create mode 100644 tests/backtrace-dwarf.c
 create mode 100644 tests/backtrace-subr.sh
 create mode 100644 tests/backtrace.c
 create mode 100644 tests/backtrace.i386.core.bz2
 create mode 100644 tests/backtrace.i386.exec.bz2
 create mode 100644 tests/backtrace.x86_64.core.bz2
 create mode 100644 tests/backtrace.x86_64.exec.bz2
 create mode 100644 tests/cleanup-13.c
 create mode 100755 tests/run-backtrace-core-i386.sh
 create mode 100755 tests/run-backtrace-core-x86_64.sh
 create mode 100755 tests/run-backtrace-data.sh
 create mode 100755 tests/run-backtrace-dwarf.sh
 create mode 100755 tests/run-backtrace-native-biarch.sh
 create mode 100755 tests/run-backtrace-native-core-biarch.sh
 create mode 100755 tests/run-backtrace-native-core.sh
 create mode 100755 tests/run-backtrace-native.sh

diff --git a/configure.ac b/configure.ac
index 99b74ae..72fb3e8 100644
--- a/configure.ac
+++ b/configure.ac
@@ -332,9 +332,7 @@ AC_CHECK_SIZEOF(long)
 # 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"])
+CC_BIARCH="$CC $utrace_biarch"
 AC_SUBST([CC_BIARCH])
 
 AC_OUTPUT
diff --git a/libdwfl/dwfl_frame.c b/libdwfl/dwfl_frame.c
index f286350..4a7b3cd 100644
--- a/libdwfl/dwfl_frame.c
+++ b/libdwfl/dwfl_frame.c
@@ -200,7 +200,7 @@ dwfl_pid (Dwfl *dwfl)
 {
   if (dwfl->process == NULL)
     {
-      __libdwfl_seterrno (DWFL_E_NO_ATTACH_STATE);
+      __libdwfl_seterrno (dwfl->process_attach_error);
       return -1;
     }
   return dwfl->process->pid;
@@ -235,7 +235,7 @@ dwfl_getthreads (Dwfl *dwfl, int (*callback) (Dwfl_Thread *thread, void *arg),
   Dwfl_Process *process = dwfl->process;
   if (process == NULL)
     {
-      __libdwfl_seterrno (DWFL_E_NO_ATTACH_STATE);
+      __libdwfl_seterrno (dwfl->process_attach_error);
       return -1;
     }
 
diff --git a/libdwfl/libdwflP.h b/libdwfl/libdwflP.h
index b8a64d8..7e73e9e 100644
--- a/libdwfl/libdwflP.h
+++ b/libdwfl/libdwflP.h
@@ -108,6 +108,7 @@ struct Dwfl
   Dwfl_Module *modulelist;    /* List in order used by full traversals.  */
 
   Dwfl_Process *process;
+  Dwfl_Error process_attach_error;
 
   GElf_Addr offline_next_address;
 
diff --git a/libdwfl/linux-core-attach.c b/libdwfl/linux-core-attach.c
index 971d495..b2d703f 100644
--- a/libdwfl/linux-core-attach.c
+++ b/libdwfl/linux-core-attach.c
@@ -264,37 +264,30 @@ static const Dwfl_Thread_Callbacks core_thread_callbacks =
   NULL, /* core_thread_detach */
 };
 
-bool
-internal_function
-__libdwfl_attach_state_for_core (Dwfl *dwfl, Elf *core)
+static Dwfl_Error
+attach_state_for_core (Dwfl *dwfl, Elf *core)
 {
   Ebl *ebl = ebl_openbackend (core);
   if (ebl == NULL)
-    {
-      __libdwfl_seterrno (DWFL_E_LIBEBL);
-      return false;
-    }
+    return DWFL_E_LIBEBL;
   size_t nregs = ebl_frame_nregs (ebl);
   if (nregs == 0)
     {
       ebl_closebackend (ebl);
-      __libdwfl_seterrno (DWFL_E_LIBEBL);
-      return false;
+      return DWFL_E_NO_UNWIND;
     }
   GElf_Ehdr ehdr_mem, *ehdr = gelf_getehdr (core, &ehdr_mem);
   if (ehdr == NULL)
     {
       ebl_closebackend (ebl);
-      __libdwfl_seterrno (DWFL_E_LIBELF);
-      return false;
+      return DWFL_E_LIBELF;
     }
   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;
+      return DWFL_E_LIBELF;
     }
   pid_t pid = -1;
   Elf_Data *note_data = NULL;
@@ -311,8 +304,7 @@ __libdwfl_attach_state_for_core (Dwfl *dwfl, Elf *core)
   if (note_data == NULL)
     {
       ebl_closebackend (ebl);
-      __libdwfl_seterrno (DWFL_E_LIBELF);
-      return NULL;
+      return DWFL_E_LIBELF;
     }
   size_t offset = 0;
   GElf_Nhdr nhdr;
@@ -355,15 +347,13 @@ __libdwfl_attach_state_for_core (Dwfl *dwfl, Elf *core)
     {
       /* No valid NT_PRPSINFO recognized in this CORE.  */
       ebl_closebackend (ebl);
-      __libdwfl_seterrno (DWFL_E_BADELF);
-      return false;
+      return DWFL_E_BADELF;
     }
   struct core_arg *core_arg = malloc (sizeof *core_arg);
   if (core_arg == NULL)
     {
       ebl_closebackend (ebl);
-      __libdwfl_seterrno (DWFL_E_NOMEM);
-      return false;
+      return DWFL_E_NOMEM;
     }
   core_arg->core = core;
   core_arg->note_data = note_data;
@@ -372,8 +362,30 @@ __libdwfl_attach_state_for_core (Dwfl *dwfl, Elf *core)
   if (! INTUSE(dwfl_attach_state) (dwfl, core, pid, &core_thread_callbacks,
 				   core_arg))
     {
+      Dwfl_Error error = dwfl_errno ();
+      assert (error != DWFL_E_NOERROR);
       free (core_arg);
       ebl_closebackend (ebl);
+      return error;
+    }
+  return DWFL_E_NOERROR;
+}
+
+bool
+internal_function
+__libdwfl_attach_state_for_core (Dwfl *dwfl, Elf *core)
+{
+  if (dwfl->process != NULL)
+    {
+      __libdwfl_seterrno (DWFL_E_ATTACH_STATE_CONFLICT);
+      return false;
+    }
+  Dwfl_Error error = attach_state_for_core (dwfl, core);
+  assert ((dwfl->process != NULL) == (error == DWFL_E_NOERROR));
+  if (error != DWFL_E_NOERROR)
+    {
+      dwfl->process_attach_error = error;
+      __libdwfl_seterrno (error);
       return false;
     }
   return true;
diff --git a/m4/biarch.m4 b/m4/biarch.m4
index a15323e..04c8dba 100644
--- a/m4/biarch.m4
+++ b/m4/biarch.m4
@@ -41,5 +41,7 @@ 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])])
+CC="$save_CC"])], [utrace_cv_cc_biarch=no])
+AS_IF([test $utrace_cv_cc_biarch != yes], [dnl
+AC_MSG_WARN([not running biarch tests, $biarch_CC does not work])])])
 AM_CONDITIONAL(BIARCH, [test $utrace_cv_cc_biarch = yes])])
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 4f8e9e4..15992ed 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -52,10 +52,26 @@ 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 backtrace-dwarf
 asm_TESTS = asm-tst1 asm-tst2 asm-tst3 asm-tst4 asm-tst5 \
 	    asm-tst6 asm-tst7 asm-tst8 asm-tst9
 
+if BIARCH
+BUILT_SOURCES = backtrace-child-biarch
+endif
+
+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 +105,14 @@ 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-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
+if !BIARCH
+export ELFUTILS_DISABLE_BIARCH = 1
+endif
 
 if !STANDALONE
 check_PROGRAMS += msg_tst md5-sha1-test
@@ -216,7 +239,13 @@ 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-data.sh run-backtrace-dwarf.sh cleanup-13.c \
+	     run-backtrace-native.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 \
+	     backtrace-subr.sh backtrace.i386.core.bz2 backtrace.i386.exec.bz2 \
+	     backtrace.x86_64.core.bz2 backtrace.x86_64.exec.bz2
 
 if USE_VALGRIND
 valgrind_cmd='valgrind -q --trace-children=yes --error-exitcode=1 --run-libc-freeres=no'
@@ -345,6 +374,13 @@ 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-biarch also uses those *_CFLAGS and *_LDLAGS variables:
+backtrace_child_CFLAGS = -fPIE
+backtrace_child_LDFLAGS = -pie -pthread
+backtrace_data_LDADD = $(libdw) $(libelf) $(libmudflap)
+backtrace_dwarf_CFLAGS = -Wno-unused-parameter
+backtrace_dwarf_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..3d78fd3
--- /dev/null
+++ b/tests/backtrace-child.c
@@ -0,0 +1,210 @@
+/* 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/>.  */
+
+/* Command line syntax: ./backtrace-child [--ptraceme|--gencore] --run
+   --ptraceme will call ptrace (PTRACE_TRACEME) in the two threads.
+   --gencore will call abort () at its end.
+   --run is always required.
+   Main thread will signal SIGUSR2.  Other thread will signal SIGUSR1.
+   On x86_64 only:
+     PC will get changed to function 'jmp' by backtrace.c function
+     prepare_thread.  Then SIGUSR2 will be signalled to backtrace-child
+     which will invoke function sigusr2.
+   On non-x86_64:
+     sigusr2 gets called by normal function call from function stdarg.
+   On any arch then sigusr2 calls raise (SIGUSR1) for --ptraceme.
+   abort () is called otherwise, expected for --gencore core dump.
+
+   Expected x86_64 output:
+   TID 10276:
+   # 0 0x7f7ab61e9e6b      raise
+   # 1 0x7f7ab661af47 - 1  main
+   # 2 0x7f7ab5e3bb45 - 1  __libc_start_main
+   # 3 0x7f7ab661aa09 - 1  _start
+   TID 10278:
+   # 0 0x7f7ab61e9e6b      raise
+   # 1 0x7f7ab661ab3c - 1  sigusr2
+   # 2 0x7f7ab5e4fa60      __restore_rt
+   # 3 0x7f7ab661ab47      jmp
+   # 4 0x7f7ab661ac92 - 1  stdarg
+   # 5 0x7f7ab661acba - 1  backtracegen
+   # 6 0x7f7ab661acd1 - 1  start
+   # 7 0x7f7ab61e2c53 - 1  start_thread
+   # 8 0x7f7ab5f0fdbd - 1  __clone
+
+   Expected non-x86_64 (i386) output; __kernel_vsyscall are skipped if found:
+   TID 10408:
+   # 0 0xf779f430          __kernel_vsyscall
+   # 1 0xf7771466 - 1      raise
+   # 2 0xf77c1d07 - 1      main
+   # 3 0xf75bd963 - 1      __libc_start_main
+   # 4 0xf77c1761 - 1      _start
+   TID 10412:
+   # 0 0xf779f430          __kernel_vsyscall
+   # 1 0xf7771466 - 1      raise
+   # 2 0xf77c18f4 - 1      sigusr2
+   # 3 0xf77c1a10 - 1      stdarg
+   # 4 0xf77c1a2c - 1      backtracegen
+   # 5 0xf77c1a48 - 1      start
+   # 6 0xf77699da - 1      start_thread
+   # 7 0xf769bbfe - 1      __clone
+   */
+
+#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);
+
+  /* This abort () call is reached to dump core for --gencore.
+     Without --gencore it should not be reached.  */
+  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);
+  /* Not reached.  */
+  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 ();
+  /* Not reached.  */
+  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);
+  /* Not reached.  */
+  abort ();
+}
diff --git a/tests/backtrace-data.c b/tests/backtrace-data.c
new file mode 100644
index 0000000..804f836
--- /dev/null
+++ b/tests/backtrace-data.c
@@ -0,0 +1,314 @@
+/* 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, void *dwfl_arg __attribute__ ((unused)),
+	     void **thread_argp)
+{
+  if (*thread_argp != NULL)
+    return 0;
+  /* Put arbitrary non-NULL value into *THREAD_ARGP.  */
+  *thread_argp = thread_argp;
+  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;
+}
+
+static int
+thread_callback (Dwfl_Thread *thread, void *thread_arg __attribute__ ((unused)))
+{
+  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 ();
+    }
+  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, "");
+
+  elf_version (EV_CURRENT);
+
+  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, EM_NONE, child, &callbacks, NULL);
+  assert (ok);
+
+  /* Multiple threads are not handled here.  */
+  int err = dwfl_getthreads (dwfl, thread_callback, NULL);
+  assert (! err);
+
+  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-dwarf.c b/tests/backtrace-dwarf.c
new file mode 100644
index 0000000..eddc7a5
--- /dev/null
+++ b/tests/backtrace-dwarf.c
@@ -0,0 +1,192 @@
+/* Test program for unwinding of complicated DWARF expressions.
+   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 <signal.h>
+#include <inttypes.h>
+#include <stdio_ext.h>
+#include <locale.h>
+#include <errno.h>
+#include <sys/ptrace.h>
+#include ELFUTILS_HEADER(dwfl)
+
+static void cleanup_13_abort (void);
+#define main cleanup_13_main
+#include "cleanup-13.c"
+#undef main
+
+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 = "/proc/self/exe";
+
+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 int
+frame_callback (Dwfl_Frame *state, void *frame_arg)
+{
+  Dwarf_Addr pc;
+  bool isactivation;
+  if (! dwfl_frame_pc (state, &pc, &isactivation))
+    {
+      error (0, 0, "%s", dwfl_errmsg (-1));
+      return DWARF_CB_ABORT;
+    }
+  Dwarf_Addr pc_adjusted = pc - (isactivation ? 0 : 1);
+
+  /* Get PC->SYMNAME.  */
+  Dwfl_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 ("%#" PRIx64 "\t%s\n", (uint64_t) pc, symname);
+
+  if (symname && strcmp (symname, "main") == 0)
+    {
+      kill (dwfl_pid (dwfl), SIGKILL);
+      exit (0);
+    }
+
+  return DWARF_CB_OK;
+}
+
+static int
+thread_callback (Dwfl_Thread *thread, void *thread_arg)
+{
+  dwfl_thread_getframes (thread, frame_callback, NULL);
+  error (1, 0, "dwfl_thread_getframes: %s", dwfl_errmsg (-1));
+}
+
+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);
+}
+
+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, "");
+
+  elf_version (EV_CURRENT);
+
+  pid_t pid = fork ();
+  switch (pid)
+  {
+    case -1:
+      abort ();
+    case 0:;
+      long l = ptrace (PTRACE_TRACEME, 0, NULL, NULL);
+      assert_perror (errno);
+      assert (l == 0);
+      cleanup_13_main ();
+      abort ();
+    default:
+      break;
+  }
+
+  errno = 0;
+  int status;
+  pid_t got = waitpid (pid, &status, 0);
+  assert_perror (errno);
+  assert (got == pid);
+  assert (WIFSTOPPED (status));
+  assert (WSTOPSIG (status) == SIGABRT);
+
+  ptrace_detach_stopped (pid);
+
+  Dwfl *dwfl = pid_to_dwfl (pid);
+  dwfl_getthreads (dwfl, thread_callback, NULL);
+  error (1, 0, "dwfl_getthreads: %s", dwfl_errmsg (-1));
+}
diff --git a/tests/backtrace-subr.sh b/tests/backtrace-subr.sh
new file mode 100644
index 0000000..06403af
--- /dev/null
+++ b/tests/backtrace-subr.sh
@@ -0,0 +1,88 @@
+# 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
+
+# Verify one of the backtraced threads contains function 'main'.
+check_main()
+{
+  if grep -w main $1; then
+    return
+  fi
+  echo >&2 $2: no main
+  false
+}
+
+# Without proper ELF symbols resolution we could get inappropriate weak
+# symbol "gsignal" with the same address as the correct symbol "raise".
+# It was fixed by GIT commit 78dec228b3cfb2f9300cd0b682ebf416c9674c91 .
+# [patch] Improve ELF symbols preference (global > weak)
+# https://lists.fedorahosted.org/pipermail/elfutils-devel/2012-October/002624.html
+check_gsignal()
+{
+  if ! grep -w gsignal $1; then
+    return
+  fi
+  echo >&2 $2: found gsignal
+  false
+}
+
+# Verify the STDERR output does not contain unexpected errors.
+# In some cases we cannot reliably find out we got behind _start as some
+# operating system do not properly terminate CFI by undefined PC.
+# Ignore it here as it is a bug of OS, not a bug of elfutils.
+check_err()
+{
+  if [ $(egrep -v <$1 '/backtrace: dwfl_thread_getframes: (No DWARF information found|no matching address range)$' \
+         | wc -c) \
+       -eq 0 ]
+  then
+    return
+  fi
+  echo >&2 $2: neither empty nor just out of DWARF
+  false
+}
+
+check_all()
+{
+  bt=$1
+  err=$2
+  testname=$3
+  check_main $bt $testname
+  check_gsignal $bt $testname
+  check_err $err $testname
+}
+
+check_unsupported()
+{
+  err=$1
+  testname=$2
+  if grep -q ': Unwinding not supported for this architecture$' $err; then
+    echo >&2 $testname: arch not supported
+    exit 77
+  fi
+}
+
+check_core()
+{
+  arch=$1
+  testfiles backtrace.$arch.{exec,core}
+  tempfiles backtrace.$arch.{bt,err}
+  echo ./backtrace ./backtrace.$arch.{exec,core}
+  testrun ${abs_builddir}/backtrace ./backtrace.$arch.{exec,core} 1>backtrace.$arch.bt 2>backtrace.$arch.err || true
+  cat backtrace.$arch.{bt,err}
+  check_all backtrace.$arch.{bt,err} backtrace.$arch.core
+}
diff --git a/tests/backtrace.c b/tests/backtrace.c
new file mode 100644
index 0000000..1be7bbf
--- /dev/null
+++ b/tests/backtrace.c
@@ -0,0 +1,549 @@
+/* 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_PRIVATE, NULL);
+  if (elf == NULL)
+    error (2, 0, "elf_begin: %s", elf_errmsg (-1));
+  if (dwfl_core_file_report (dwfl, elf, executable) < 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));
+  /* FD and ELF 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 *frame_arg)
+{
+  struct frame_callback *data = frame_arg;
+  Dwarf_Addr pc;
+  bool isactivation;
+  if (! dwfl_frame_pc (state, &pc, &isactivation))
+    {
+      error (0, 0, "%s", dwfl_errmsg (-1));
+      return DWARF_CB_ABORT;
+    }
+  Dwarf_Addr pc_adjusted = pc - (isactivation ? 0 : 1);
+
+  /* Get PC->SYMNAME.  */
+  Dwfl_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;
+}
+
+struct thread_callback
+{
+  callback_t *callback;
+  void *callback_data;
+};
+
+static int
+thread_callback (Dwfl_Thread *thread, void *thread_arg)
+{
+  struct thread_callback *data = thread_arg;
+  printf ("TID %ld:\n", (long) dwfl_thread_tid (thread));
+  struct frame_callback frame_callback_data;
+  frame_callback_data.frameno = 0;
+  frame_callback_data.callback = data->callback;
+  frame_callback_data.callback_data = data->callback_data;
+  switch (dwfl_thread_getframes (thread, frame_callback,
+				 &frame_callback_data))
+    {
+    case 0:
+      break;
+    case DWARF_CB_ABORT:
+      return DWARF_CB_ABORT;
+    case -1:
+      error (0, 0, "dwfl_thread_getframes: %s", dwfl_errmsg (-1));
+      /* All platforms do not have yet proper unwind termination.  */
+      break;
+    default:
+      abort ();
+    }
+  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);
+  struct thread_callback thread_callback_data;
+  thread_callback_data.callback = callback;
+  thread_callback_data.callback_data = callback_data;
+  bool err = false;
+  switch (dwfl_getthreads (dwfl, thread_callback, &thread_callback_data))
+    {
+    case 0:
+      break;
+    case DWARF_CB_ABORT:
+      err = true;
+      break;
+    case -1:
+      error (0, 0, "dwfl_getthreads: %s", dwfl_errmsg (-1));
+      err = true;
+      break;
+    default:
+      abort ();
+    }
+  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 is_x86_64 = check_tid < 0;
+  if (is_x86_64)
+    check_tid = -check_tid;
+  static bool seen_main = false;
+  if (symname && *symname == '.')
+    symname++;
+  if (symname && strcmp (symname, "main") == 0)
+    seen_main = true;
+  if (pc == 0)
+    {
+      assert (seen_main);
+      return;
+    }
+  if (tid != check_tid)
+    {
+      // For the main thread we are only interested if we can unwind till
+      // we see the "main" symbol.
+      return;
+    }
+  Dwfl_Module *mod;
+  static bool reduce_frameno = false;
+  if (reduce_frameno)
+    frameno--;
+  if (! is_x86_64 && frameno >= 2)
+    frameno += 2;
+  const char *symname2 = NULL;
+  switch (frameno)
+  {
+    case 0:
+      if (! reduce_frameno && symname
+	       && strcmp (symname, "__kernel_vsyscall") == 0)
+	reduce_frameno = true;
+      else
+	assert (symname && strcmp (symname, "raise") == 0);
+      break;
+    case 1:
+      assert (symname != NULL && strcmp (symname, "sigusr2") == 0);
+      break;
+    case 2: // x86_64 only
+      /* __restore_rt - glibc maybe does not have to have this symbol.  */
+      break;
+    case 3: // x86_64 only
+      if (is_x86_64)
+	{
+	  /* 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;
+	}
+      /* PASSTHRU */
+    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;
+  }
+}
+
+static void
+prepare_thread (pid_t pid2 __attribute__ ((unused)),
+		void (*jmp) (void) __attribute__ ((unused)))
+{
+#ifndef __x86_64__
+  abort ();
+#else /* x86_64 */
+  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);
+#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));
+  // Main thread will signal SIGUSR2.  Other thread will signal SIGUSR1.
+  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));
+  // Main thread will signal SIGUSR2.  Other thread will signal SIGUSR1.
+  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);
+  /* It is false also on x86_64 with i386 inferior.  */
+  bool is_x86_64;
+#ifndef __x86_64__
+  is_x86_64 = false;
+#else /* __x86_64__ */
+  is_x86_64 = ehdr->e_ident[EI_CLASS] == ELFCLASS64;
+#endif /* __x86_64__ */
+  void (*jmp) (void);
+  if (is_x86_64)
+    {
+      // Find inferior symbol named "jmp".
+      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, jmp);
+    }
+  dwfl_end (dwfl);
+  ptrace_detach_stopped (pid);
+  ptrace_detach_stopped (pid2);
+  dump (pid, NULL, selfdump_callback,
+	(void *) (intptr_t) (is_x86_64 ? -pid2 : pid2));
+}
+
+static bool
+is_core (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_PRIVATE, NULL);
+  if (elf == NULL)
+    error (2, 0, "elf_begin: %s", elf_errmsg (-1));
+  GElf_Ehdr ehdr_mem, *ehdr = gelf_getehdr (elf, &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;
+  if (elf_end (elf) != 0)
+    error (2, 0, "elf_end: %s", elf_errmsg (-1));
+  if (close (fd) != 0)
+    error (2, errno, "close");
+  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, "");
+
+  elf_version (EV_CURRENT);
+
+  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/cleanup-13.c b/tests/cleanup-13.c
new file mode 100644
index 0000000..b87c696
--- /dev/null
+++ b/tests/cleanup-13.c
@@ -0,0 +1,334 @@
+// http://gcc.gnu.org/viewcvs/gcc/trunk/gcc/testsuite/gcc.dg/cleanup-13.c?view=co&content-type=text%2Fplain
+
+/* HP-UX libunwind.so doesn't provide _UA_END_OF_STACK */
+/* { dg-do run } */
+/* { dg-options "-fexceptions" } */
+/* { dg-skip-if "" { "ia64-*-hpux11.*" }  { "*" } { "" } } */
+/* Verify DW_OP_* handling in the unwinder.  */
+
+#include <unwind.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* #define OP_addr(x) 0x06, ... */
+#define OP_deref 0x06,
+#define SLEB128(x) (x)&0x7f	/* Assume here the value is -0x40 ... 0x3f.  */
+#define ULEB128(x) (x)&0x7f	/* Assume here the value is 0 ... 0x7f.  */
+#define VAL1(x) (x)&0xff
+#if defined (__BIG_ENDIAN__)
+#define VAL2(x) ((x)>>8)&0xff,(x)&0xff
+#define VAL4(x) ((x)>>24)&0xff,((x)>>16)&0xff,((x)>>8)&0xff,(x)&0xff
+#define VAL8(x) ((x)>>56)&0xff,((x)>>48)&0xff,((x)>>40)&0xff,((x)>>32)&0xff,((x)>>24)&0xff,((x)>>16)&0xff,((x)>>8)&0xff,(x)&0xff
+#elif defined(__LITTLE_ENDIAN__) || defined(__x86_64__) || defined(__i386__)
+#define VAL2(x) (x)&0xff,((x)>>8)&0xff
+#define VAL4(x) (x)&0xff,((x)>>8)&0xff,((x)>>16)&0xff,((x)>>24)&0xff
+#define VAL8(x) (x)&0xff,((x)>>8)&0xff,((x)>>16)&0xff,((x)>>24)&0xff,((x)>>32)&0xff,((x)>>40)&0xff,((x)>>48)&0xff,((x)>>56)&0xff
+#endif
+#define OP_const1u(x) 0x08,VAL1(x),
+#define OP_const1s(x) 0x09,VAL1(x),
+#define OP_const2u(x) 0x0a,VAL2(x),
+#define OP_const2s(x) 0x0b,VAL2(x),
+#define OP_const4u(x) 0x0c,VAL4(x),
+#define OP_const4s(x) 0x0d,VAL4(x),
+#define OP_const8u(x) 0x0e,VAL8(x),
+#define OP_const8s(x) 0x0f,VAL8(x),
+#define OP_constu(x) 0x10,ULEB128(x),
+#define OP_consts(x) 0x11,SLEB128(x),
+#define OP_dup 0x12,
+#define OP_drop 0x13,
+#define OP_over 0x14,
+#define OP_pick(x) 0x15,VAL1(x),
+#define OP_swap 0x16,
+#define OP_rot 0x17,
+#define OP_xderef 0x18,
+#define OP_abs 0x19,
+#define OP_and 0x1a,
+#define OP_div 0x1b,
+#define OP_minus 0x1c,
+#define OP_mod 0x1d,
+#define OP_mul 0x1e,
+#define OP_neg 0x1f,
+#define OP_not 0x20,
+#define OP_or 0x21,
+#define OP_plus 0x22,
+#define OP_plus_uconst(x) 0x23,ULEB128(x),
+#define OP_shl 0x24,
+#define OP_shr 0x25,
+#define OP_shra 0x26,
+#define OP_xor 0x27,
+#define OP_bra(x) 0x28,VAL2(x),
+#define OP_eq 0x29,
+#define OP_ge 0x2a,
+#define OP_gt 0x2b,
+#define OP_le 0x2c,
+#define OP_lt 0x2d,
+#define OP_ne 0x2e,
+#define OP_skip(x) 0x2f,VAL2(x),
+#define OP_lit0 0x30,
+#define OP_lit1 0x31,
+#define OP_lit2 0x32,
+#define OP_lit3 0x33,
+#define OP_lit4 0x34,
+#define OP_lit5 0x35,
+#define OP_lit6 0x36,
+#define OP_lit7 0x37,
+#define OP_lit8 0x38,
+#define OP_lit9 0x39,
+#define OP_lit10 0x3a,
+#define OP_lit11 0x3b,
+#define OP_lit12 0x3c,
+#define OP_lit13 0x3d,
+#define OP_lit14 0x3e,
+#define OP_lit15 0x3f,
+#define OP_lit16 0x40,
+#define OP_lit17 0x41,
+#define OP_lit18 0x42,
+#define OP_lit19 0x43,
+#define OP_lit20 0x44,
+#define OP_lit21 0x45,
+#define OP_lit22 0x46,
+#define OP_lit23 0x47,
+#define OP_lit24 0x48,
+#define OP_lit25 0x49,
+#define OP_lit26 0x4a,
+#define OP_lit27 0x4b,
+#define OP_lit28 0x4c,
+#define OP_lit29 0x4d,
+#define OP_lit30 0x4e,
+#define OP_lit31 0x4f,
+#define OP_reg0 0x50,
+#define OP_reg1 0x51,
+#define OP_reg2 0x52,
+#define OP_reg3 0x53,
+#define OP_reg4 0x54,
+#define OP_reg5 0x55,
+#define OP_reg6 0x56,
+#define OP_reg7 0x57,
+#define OP_reg8 0x58,
+#define OP_reg9 0x59,
+#define OP_reg10 0x5a,
+#define OP_reg11 0x5b,
+#define OP_reg12 0x5c,
+#define OP_reg13 0x5d,
+#define OP_reg14 0x5e,
+#define OP_reg15 0x5f,
+#define OP_reg16 0x60,
+#define OP_reg17 0x61,
+#define OP_reg18 0x62,
+#define OP_reg19 0x63,
+#define OP_reg20 0x64,
+#define OP_reg21 0x65,
+#define OP_reg22 0x66,
+#define OP_reg23 0x67,
+#define OP_reg24 0x68,
+#define OP_reg25 0x69,
+#define OP_reg26 0x6a,
+#define OP_reg27 0x6b,
+#define OP_reg28 0x6c,
+#define OP_reg29 0x6d,
+#define OP_reg30 0x6e,
+#define OP_reg31 0x6f,
+#define OP_breg0(x) 0x70,SLEB128(x),
+#define OP_breg1(x) 0x71,SLEB128(x),
+#define OP_breg2(x) 0x72,SLEB128(x),
+#define OP_breg3(x) 0x73,SLEB128(x),
+#define OP_breg4(x) 0x74,SLEB128(x),
+#define OP_breg5(x) 0x75,SLEB128(x),
+#define OP_breg6(x) 0x76,SLEB128(x),
+#define OP_breg7(x) 0x77,SLEB128(x),
+#define OP_breg8(x) 0x78,SLEB128(x),
+#define OP_breg9(x) 0x79,SLEB128(x),
+#define OP_breg10(x) 0x7a,SLEB128(x),
+#define OP_breg11(x) 0x7b,SLEB128(x),
+#define OP_breg12(x) 0x7c,SLEB128(x),
+#define OP_breg13(x) 0x7d,SLEB128(x),
+#define OP_breg14(x) 0x7e,SLEB128(x),
+#define OP_breg15(x) 0x7f,SLEB128(x),
+#define OP_breg16(x) 0x80,SLEB128(x),
+#define OP_breg17(x) 0x81,SLEB128(x),
+#define OP_breg18(x) 0x82,SLEB128(x),
+#define OP_breg19(x) 0x83,SLEB128(x),
+#define OP_breg20(x) 0x84,SLEB128(x),
+#define OP_breg21(x) 0x85,SLEB128(x),
+#define OP_breg22(x) 0x86,SLEB128(x),
+#define OP_breg23(x) 0x87,SLEB128(x),
+#define OP_breg24(x) 0x88,SLEB128(x),
+#define OP_breg25(x) 0x89,SLEB128(x),
+#define OP_breg26(x) 0x8a,SLEB128(x),
+#define OP_breg27(x) 0x8b,SLEB128(x),
+#define OP_breg28(x) 0x8c,SLEB128(x),
+#define OP_breg29(x) 0x8d,SLEB128(x),
+#define OP_breg30(x) 0x8e,SLEB128(x),
+#define OP_breg31(x) 0x8f,SLEB128(x),
+#define OP_regx(x) 0x90,SLEB128(x),
+#define OP_fbreg(x) 0x91,SLEB128(x),
+#define OP_bregx(x,y) 0x92,ULEB128(x),SLEB128(y),
+#define OP_piece(x) 0x93,ULEB128(x),
+#define OP_deref_size(x) 0x94,VAL1(x),
+#define OP_xderef_size(x) 0x95,VAL1(x),
+#define OP_nop 0x96,
+#define OP_nop_termination 0x96
+#define OP_push_object_address 0x97,
+#define OP_call2(x) 0x98,VAL2(x),
+#define OP_call4(x) 0x99,VAL4(x),
+/* #define OP_call_ref(x) 0x9a,... */
+#define OP_form_tls_address(x) 0x9b,
+#define OP_call_frame_cfa 0x9c,
+#define OP_bit_piece(x) 0x9d,ULEB128(x),
+/* #define OP_implicit_value(x...) 0x9e,... */
+#define OP_stack_value 0x9f,
+#define OP_GNU_push_tls_address 0xe0,
+/* #define OP_GNU_encoded_addr(x...) 0xf1, */
+
+#define ASSERT_TOS_NON0 OP_bra(3) OP_skip(-3)
+#define ASSERT_TOS_0 OP_lit0 OP_eq ASSERT_TOS_NON0
+
+/* Initially there is CFA value on the stack, we want to
+   keep it there at the end.  */
+#define CFI_PROGRAM \
+OP_lit0 OP_nop ASSERT_TOS_0						\
+OP_lit1 ASSERT_TOS_NON0							\
+OP_lit1 OP_const1u(1) OP_eq ASSERT_TOS_NON0				\
+OP_lit16 OP_const2u(16) OP_eq ASSERT_TOS_NON0				\
+OP_lit31 OP_const4u(31) OP_ne ASSERT_TOS_0				\
+OP_lit1 OP_neg OP_const1s(-1) OP_eq ASSERT_TOS_NON0			\
+OP_lit16 OP_neg OP_const2s(-16) OP_ne ASSERT_TOS_0			\
+OP_lit31 OP_const4s(-31) OP_neg OP_ne ASSERT_TOS_0			\
+OP_lit7 OP_dup OP_plus_uconst(2) OP_lit9 OP_eq ASSERT_TOS_NON0		\
+  OP_lit7 OP_eq ASSERT_TOS_NON0						\
+OP_lit20 OP_lit1 OP_drop OP_lit20 OP_eq ASSERT_TOS_NON0			\
+OP_lit17 OP_lit19 OP_over OP_lit17 OP_eq ASSERT_TOS_NON0		\
+  OP_lit19 OP_eq ASSERT_TOS_NON0 OP_lit17 OP_eq ASSERT_TOS_NON0		\
+OP_lit1 OP_lit2 OP_lit3 OP_lit4 OP_pick(2) OP_lit2 OP_eq ASSERT_TOS_NON0\
+  OP_lit4 OP_eq ASSERT_TOS_NON0 OP_lit3 OP_eq ASSERT_TOS_NON0		\
+  OP_pick(0) OP_lit2 OP_eq ASSERT_TOS_NON0				\
+  OP_lit2 OP_eq ASSERT_TOS_NON0 OP_lit1 OP_eq ASSERT_TOS_NON0		\
+OP_lit6 OP_lit12 OP_swap OP_lit6 OP_eq ASSERT_TOS_NON0			\
+  OP_lit12 OP_eq ASSERT_TOS_NON0					\
+OP_lit7 OP_lit8 OP_lit9 OP_rot OP_lit8 OP_eq ASSERT_TOS_NON0		\
+  OP_lit7 OP_eq ASSERT_TOS_NON0 OP_lit9 OP_eq ASSERT_TOS_NON0		\
+OP_lit7 OP_abs OP_lit7 OP_eq ASSERT_TOS_NON0				\
+OP_const1s(-123) OP_abs OP_const1u(123) OP_eq ASSERT_TOS_NON0		\
+OP_lit3 OP_lit6 OP_and OP_lit2 OP_eq ASSERT_TOS_NON0			\
+OP_lit3 OP_lit6 OP_or OP_lit7 OP_eq ASSERT_TOS_NON0			\
+OP_lit17 OP_lit2 OP_minus OP_lit15 OP_eq ASSERT_TOS_NON0		\
+/* Divide is signed truncating toward zero.  */				\
+OP_const1s(-6) OP_const1s(-2) OP_div OP_lit3 OP_eq ASSERT_TOS_NON0	\
+OP_const1s(-7) OP_const1s(3) OP_div OP_const1s(-2)			\
+  OP_eq ASSERT_TOS_NON0							\
+/* Modulo is unsigned.  */						\
+OP_const1s(-6) OP_const1s(-4) OP_mod OP_const1s(-6)			\
+  OP_eq ASSERT_TOS_NON0							\
+OP_const1s(-6) OP_lit4 OP_mod OP_lit2 OP_eq ASSERT_TOS_NON0		\
+OP_lit6 OP_const1s(-4) OP_mod OP_lit6 OP_eq ASSERT_TOS_NON0		\
+/* Signed modulo can be implemented using "over over div mul minus".  */\
+OP_const1s(-6) OP_const1s(-4) OP_over OP_over OP_div OP_mul OP_minus	\
+  OP_const1s(-2) OP_eq ASSERT_TOS_NON0					\
+OP_const1s(-7) OP_lit3 OP_over OP_over OP_div OP_mul OP_minus		\
+  OP_const1s(-1) OP_eq ASSERT_TOS_NON0					\
+OP_lit7 OP_const1s(-3) OP_over OP_over OP_div OP_mul OP_minus		\
+  OP_lit1 OP_eq ASSERT_TOS_NON0						\
+OP_lit16 OP_lit31 OP_plus_uconst(1) OP_mul OP_const2u(512)		\
+  OP_eq ASSERT_TOS_NON0							\
+OP_lit5 OP_not OP_lit31 OP_and OP_lit26 OP_eq ASSERT_TOS_NON0		\
+OP_lit12 OP_lit31 OP_plus OP_const1u(43) OP_eq ASSERT_TOS_NON0		\
+OP_const1s(-6) OP_lit2 OP_plus OP_const1s(-4) OP_eq ASSERT_TOS_NON0	\
+OP_const1s(-6) OP_plus_uconst(3) OP_const1s(-3) OP_eq ASSERT_TOS_NON0	\
+OP_lit16 OP_lit4 OP_shl OP_const2u(256) OP_eq ASSERT_TOS_NON0		\
+OP_lit16 OP_lit3 OP_shr OP_lit2 OP_eq ASSERT_TOS_NON0			\
+OP_const1s(-16) OP_lit3 OP_shra OP_const1s(-2) OP_eq ASSERT_TOS_NON0	\
+OP_lit3 OP_lit6 OP_xor OP_lit5 OP_eq ASSERT_TOS_NON0			\
+OP_lit3 OP_lit6 OP_le ASSERT_TOS_NON0					\
+OP_lit3 OP_lit3 OP_le ASSERT_TOS_NON0					\
+OP_lit6 OP_lit3 OP_le ASSERT_TOS_0					\
+OP_lit3 OP_lit6 OP_lt ASSERT_TOS_NON0					\
+OP_lit3 OP_lit3 OP_lt ASSERT_TOS_0					\
+OP_lit6 OP_lit3 OP_lt ASSERT_TOS_0					\
+OP_lit3 OP_lit6 OP_ge ASSERT_TOS_0					\
+OP_lit3 OP_lit3 OP_ge ASSERT_TOS_NON0					\
+OP_lit6 OP_lit3 OP_ge ASSERT_TOS_NON0					\
+OP_lit3 OP_lit6 OP_gt ASSERT_TOS_0					\
+OP_lit3 OP_lit3 OP_gt ASSERT_TOS_0					\
+OP_lit6 OP_lit3 OP_gt ASSERT_TOS_NON0					\
+OP_const1s(-6) OP_lit1 OP_shr OP_lit0 OP_gt ASSERT_TOS_NON0		\
+OP_const1s(-6) OP_lit1 OP_shra OP_lit0 OP_lt ASSERT_TOS_NON0
+
+#define CFI_ESCAPE_VAL_2(VALUES...) #VALUES
+#define CFI_ESCAPE_VAL_1(VALUES...) CFI_ESCAPE_VAL_2(VALUES)
+#define CFI_ESCAPE_VAL(VALUES...) CFI_ESCAPE_VAL_1(VALUES)
+#define CFI_ESCAPE do { } while (0)
+#define CFI_ARCH_PROGRAM OP_nop_termination
+#ifdef __GCC_HAVE_DWARF2_CFI_ASM
+#if defined (__x86_64__)
+#undef CFI_ESCAPE
+#undef CFI_ARCH_PROGRAM
+#define CFI_ARCH_PROGRAM CFI_PROGRAM OP_lit8 OP_minus OP_nop_termination
+unsigned char cfi_arch_program[] = { CFI_ARCH_PROGRAM };
+extern char verify_it[sizeof (cfi_arch_program) - 0x80 < 0x3f80 ? 1 : -1];
+/* DW_CFA_expression %rip, uleb128(l2-l1), l1: program DW_OP_lit8 DW_OP_minus DW_OP_nop l2: */
+#define CFI_ESCAPE \
+  asm volatile (".cfi_escape 0x10, 0x10, (%P0&0x7f)+0x80, %P0>>7, " \
+		CFI_ESCAPE_VAL (CFI_ARCH_PROGRAM) \
+		: : "i" (sizeof (cfi_arch_program)))
+#elif defined (__i386__)
+#undef CFI_ESCAPE
+#undef CFI_ARCH_PROGRAM
+#define CFI_ARCH_PROGRAM CFI_PROGRAM OP_lit4 OP_minus OP_nop_termination
+unsigned char cfi_arch_program[] = { CFI_ARCH_PROGRAM };
+extern char verify_it[sizeof (cfi_arch_program) - 0x80 < 0x3f80 ? 1 : -1];
+/* DW_CFA_expression %eip, uleb128(l2-l1), l1: program DW_OP_lit4 DW_OP_minus DW_OP_nop l2: */
+#define CFI_ESCAPE \
+  asm volatile (".cfi_escape 0x10, 8, (%P0&0x7f)+0x80, %P0>>7, " \
+		CFI_ESCAPE_VAL (CFI_ARCH_PROGRAM) \
+		: : "i" (sizeof (cfi_arch_program)))
+#endif
+#endif
+static _Unwind_Reason_Code
+force_unwind_stop (int version, _Unwind_Action actions,
+		   _Unwind_Exception_Class exc_class,
+		   struct _Unwind_Exception *exc_obj,
+		   struct _Unwind_Context *context,
+		   void *stop_parameter)
+{
+  if (actions & _UA_END_OF_STACK)
+    abort ();
+  return _URC_NO_REASON;
+}
+
+static void force_unwind ()
+{
+  struct _Unwind_Exception *exc = malloc (sizeof (*exc));
+  memset (&exc->exception_class, 0, sizeof (exc->exception_class));
+  exc->exception_cleanup = 0;
+
+#ifndef __USING_SJLJ_EXCEPTIONS__
+  _Unwind_ForcedUnwind (exc, force_unwind_stop, 0);
+#else
+  _Unwind_SjLj_ForcedUnwind (exc, force_unwind_stop, 0);
+#endif
+
+  abort ();
+}
+
+static void handler (void *p __attribute__((unused)))
+{
+  exit (0);
+}
+
+__attribute__((noinline)) static void callme ()
+{
+  CFI_ESCAPE;
+  force_unwind ();
+}
+
+__attribute__((noinline)) static void doit ()
+{
+  char dummy __attribute__((cleanup (handler)));
+  callme ();
+}
+
+int main()
+{ 
+  doit ();
+  abort ();
+}
diff --git a/tests/run-backtrace-core-i386.sh b/tests/run-backtrace-core-i386.sh
new file mode 100755
index 0000000..7294ec3
--- /dev/null
+++ b/tests/run-backtrace-core-i386.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 i386
diff --git a/tests/run-backtrace-core-x86_64.sh b/tests/run-backtrace-core-x86_64.sh
new file mode 100755
index 0000000..d00cd6d
--- /dev/null
+++ b/tests/run-backtrace-core-x86_64.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 x86_64
diff --git a/tests/run-backtrace-data.sh b/tests/run-backtrace-data.sh
new file mode 100755
index 0000000..a43d08f
--- /dev/null
+++ b/tests/run-backtrace-data.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/test-subr.sh
+
+testrun ${abs_builddir}/backtrace-data
diff --git a/tests/run-backtrace-dwarf.sh b/tests/run-backtrace-dwarf.sh
new file mode 100755
index 0000000..b24215e
--- /dev/null
+++ b/tests/run-backtrace-dwarf.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/test-subr.sh
+
+testrun ${abs_builddir}/backtrace-dwarf
diff --git a/tests/run-backtrace-native-biarch.sh b/tests/run-backtrace-native-biarch.sh
new file mode 100755
index 0000000..6d76336
--- /dev/null
+++ b/tests/run-backtrace-native-biarch.sh
@@ -0,0 +1,33 @@
+#! /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/>.
+
+if test -n "$ELFUTILS_DISABLE_BIARCH"; then
+  exit 77
+fi
+
+. $srcdir/backtrace-subr.sh
+
+child=backtrace-child-biarch
+
+# Backtrace live process.
+# Do not abort on non-zero exit code due to some warnings of ./backtrace
+# - see function check_err.
+tempfiles $child.{bt,err}
+(set +ex; testrun ${abs_builddir}/backtrace ${abs_builddir}/$child 1>$child.bt 2>$child.err; true)
+cat $child.{bt,err}
+check_unsupported $child.err $child
+check_all $child.{bt,err} $child
diff --git a/tests/run-backtrace-native-core-biarch.sh b/tests/run-backtrace-native-core-biarch.sh
new file mode 100755
index 0000000..f8f49f9
--- /dev/null
+++ b/tests/run-backtrace-native-core-biarch.sh
@@ -0,0 +1,34 @@
+#! /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/>.
+
+if test -n "$ELFUTILS_DISABLE_BIARCH"; then
+  exit 77
+fi
+
+. $srcdir/backtrace-subr.sh
+
+child=backtrace-child-biarch
+
+# Backtrace core file.
+core="core.`ulimit -c unlimited; set +ex; testrun ${abs_builddir}/$child --gencore --run; true`"
+# Do not abort on non-zero exit code due to some warnings of ./backtrace
+# - see function check_err.
+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_unsupported $core.err $child-$core
+check_all $core.{bt,err} $child-$core
diff --git a/tests/run-backtrace-native-core.sh b/tests/run-backtrace-native-core.sh
new file mode 100755
index 0000000..f4d341f
--- /dev/null
+++ b/tests/run-backtrace-native-core.sh
@@ -0,0 +1,30 @@
+#! /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
+
+child=backtrace-child
+
+# Backtrace core file.
+core="core.`ulimit -c unlimited; set +ex; testrun ${abs_builddir}/$child --gencore --run; true`"
+# Do not abort on non-zero exit code due to some warnings of ./backtrace
+# - see function check_err.
+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_unsupported $core.err $child-$core
+check_all $core.{bt,err} $child-$core
diff --git a/tests/run-backtrace-native.sh b/tests/run-backtrace-native.sh
new file mode 100755
index 0000000..d1b3eb7
--- /dev/null
+++ b/tests/run-backtrace-native.sh
@@ -0,0 +1,29 @@
+#! /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
+
+child=backtrace-child
+
+# Backtrace live process.
+# Do not abort on non-zero exit code due to some warnings of ./backtrace
+# - see function check_err.
+tempfiles $child.{bt,err}
+(set +ex; testrun ${abs_builddir}/backtrace ${abs_builddir}/$child 1>$child.bt 2>$child.err; true)
+cat $child.{bt,err}
+check_unsupported $child.err $child
+check_all $child.{bt,err} $child
-- 
1.8.3.1


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