/* 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]
   --ptraceme will call ptrace (PTRACE_TRACEME) in the two threads.
   --gencore will call abort () at its end.
   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.
     This is all done so that signal interrupts execution of the very first
     instruction of a function.  Properly handled unwind should not slip into
     the previous unrelated function.
     The tested functionality is arch-independent but the code reproducing it
     has to be arch-specific.
   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>

#ifndef __linux__

int
main (int argc __attribute__ ((unused)), char **argv)
{
  fprintf (stderr, "%s: Unwinding not supported for this architecture\n",
           argv[0]);
  return 77;
}

#else /* __linux__ */

#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 NOINLINE_NOCLONE void
sigusr2 (int signo)
{
  assert (signo == SIGUSR2);
  if (! gencore)
    {
      raise (SIGUSR1);
      /* Do not return as stack may be invalid due to ptrace-patched PC to the
	 jmp function.  */
      pthread_exit (NULL);
      /* Not reached.  */
      abort ();
    }
  /* Here we dump the core for --gencore.  */
  raise (SIGABRT);
  /* Avoid tail call optimization for the raise call.  */
  asm volatile ("");
}

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 (errno == 0);
      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)
{
  setbuf (stdout, NULL);
  assert (*argv++);
  ptraceme = (*argv && strcmp (*argv, "--ptraceme") == 0);
  argv += ptraceme;
  gencore = (*argv && strcmp (*argv, "--gencore") == 0);
  argv += gencore;
  assert (!*argv);
  /* These dummy* functions are there so that each of their surrounding
     functions has some unrelated code around.  The purpose of some of the
     tests is verify unwinding the very first / after the very last instruction
     does not inappropriately slip into the unrelated code around.  */
  dummy1 ();
  dummy2 ();
  dummy3 ();
  dummy4 ();
  if (gencore)
    printf ("%ld\n", (long) getpid ());
  pthread_t thread;
  int i = pthread_create (&thread, NULL, start, NULL);
  // pthread_* functions do not set errno.
  assert (i == 0);
  if (ptraceme)
    {
      errno = 0;
      long l = ptrace (PTRACE_TRACEME, 0, NULL, NULL);
      assert (errno == 0);
      assert (l == 0);
    }
  if (gencore)
    pthread_join (thread, NULL);
  else
    raise (SIGUSR2);
  return 0;
}

#endif /* ! __linux__ */