/*************************************************************************
 *
 * stacktrace.c 1.2 1998/12/21
 *
 * Copyright (c) 1998 by Bjorn Reese <breese@imada.ou.dk>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
 * MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE AUTHORS AND
 * CONTRIBUTORS ACCEPT NO RESPONSIBILITY IN ANY CONCEIVABLE MANNER.
 *
 ************************************************************************
 *
 * 1998/12/21 - breese
 *  - Fixed include files and arguments for waitpid()
 *  - Made an AIX workaround for waitpid() exiting with EINTR
 *  - Added a missing 'quit' command for OSF dbx
 *
 ************************************************************************/

#if defined(_AIX) || defined(__xlC__)
# define PLATFORM_AIX
#elif defined(__FreeBSD__)
# define PLATFORM_FREEBSD
#elif defined(hpux) || defined(__hpux) || defined(_HPUX_SOURCE)
# define PLATFORM_HPUX
#elif defined(sgi) || defined(mips) || defined(_SGI_SOURCE)
# define PLATFORM_IRIX
#elif defined(__osf__)
# define PLATFORM_OSF
#elif defined(M_I386) || defined(_SCO_DS) || defined(_SCO_C_DIALECT)
# define PLATFORM_SCO
#elif defined(sun) || defined(__sun__) || defined(__SUNPRO_C)
# if defined(__SVR4) || defined(__svr4__)
#  define PLATFORM_SOLARIS
# endif
#endif

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
#if defined(PLATFORM_IRIX) && defined(USE_LIBEXC)
/*
 * Must be compiled with 'cc -DUSE_LIBEXC ... -lexc'
 */
# include <libexc.h>
#endif

#ifndef FALSE
# define FALSE (0 == 1)
# define TRUE (! FALSE)
#endif

#define SYS_ERROR -1

#ifndef EXIT_FAILURE
# define EXIT_FAILURE 1
#endif

#define MAX_BUFFER_SIZE 512

static char *globalProgName;
static int globalOutFD = STDOUT_FILENO;

/*************************************************************************
 * my_pclose [private]
 */
static void my_pclose(int fd)
{
  close(fd);
}

/*************************************************************************
 * my_popen [private]
 */
static int my_popen(const char *command, pid_t *pid)
{
  int rc;
  int pipefd[2];
  
  rc = pipe(pipefd);
  if (SYS_ERROR != rc)
    {
      *pid = fork();
      switch (*pid)
	{
	case SYS_ERROR:
	  rc = SYS_ERROR;
	  close(pipefd[0]);
	  close(pipefd[1]);
	  break;
	  
	case 0: /* Child */
	  close(pipefd[0]);
	  close(STDOUT_FILENO);
	  close(STDERR_FILENO);
	  dup2(pipefd[1], STDOUT_FILENO);
	  dup2(pipefd[1], STDERR_FILENO);
	  /*
	   * The System() call assumes that /bin/sh is
	   * always available, and so will we.
	   */
	  execl("/bin/sh", "/bin/sh", "-c", command, NULL);
	  _exit(EXIT_FAILURE);
	  break;
	  
	default: /* Parent */
	  close(pipefd[1]);
	  rc = pipefd[0];
	  break;
	} /* switch */
    }
  return rc;
}

/*************************************************************************
 * my_getline [private]
 */
static int my_getline(int fd, char *buffer, int max)
{
  char c;
  int i = 0;
  
  do {
    if (read(fd, &c, 1) < 1)
      return 0;
    if (i < max)
      buffer[i++] = c;
  } while (c != '\n');
  buffer[i] = (char)0;
  return i;
}

/*************************************************************************
 * DumpStack [private]
 */
static int DumpStack(char *format, ...)
{
  int gotSomething = FALSE;
  int fd;
  pid_t pid;
  int status = EXIT_FAILURE;
  int rc;
  va_list args;
  char *buffer;
  char cmd[MAX_BUFFER_SIZE];
  char buf[MAX_BUFFER_SIZE];

  /*
   * Please note that vsprintf() is not ASync safe (ie. cannot safely
   * be used from a signal handler.) If this proves to be a problem
   * then the cmd string can be built by more basic functions such as
   * strcpy, strcat, and a homemade integer-to-ascii function.
   */
  va_start(args, format);
  vsprintf(cmd, format, args);
  va_end(args);

  fd = my_popen(cmd, &pid);
  if (SYS_ERROR != fd)
    {
      /*
       * Wait for the child to exit. This must be done
       * to make the debugger attach successfully.
       * The output from the debugger is buffered on
       * the pipe.
       *
       * AIX needs the looping hack
       */
      do
	{
	  rc = waitpid(pid, &status, 0);
	}
      while ((SYS_ERROR == rc) && (EINTR == errno));

      if ((WIFEXITED(status)) && (WEXITSTATUS(status) == EXIT_SUCCESS))
	{
	  while (my_getline(fd, buf, sizeof(buf)))
	    {
	      buffer = buf;
	      if (! gotSomething)
		{
		  write(globalOutFD, "Output from ",
			strlen("Output from "));
		  strtok(cmd, " ");
		  write(globalOutFD, cmd, strlen(cmd));
		  write(globalOutFD, "\n", strlen("\n"));
		  gotSomething = TRUE;
		}
	      if ('\n' == buf[strlen(buf)-1])
		{
		  buf[strlen(buf)-1] = (char)0;
		}
	      write(globalOutFD, buffer, strlen(buffer));
	      write(globalOutFD, "\n", strlen("\n"));
	    }
	}
      my_pclose(fd);
    }
  return gotSomething;
}

/*************************************************************************
 * StackTrace
 */
void StackTrace(void)
{
  /*
   * In general dbx seems to do a better job than gdb.
   *
   * Different dbx implementations require different flags/commands.
   */

#if defined(PLATFORM_AIX)

  if (DumpStack("dbx -a %d 2>/dev/null <<EOF\n"
		"where\n"
		"detach\n"
		"EOF\n",
		(int)getpid()))
    return;

  if (DumpStack("gdb -q %s %d 2>/dev/null <<EOF\n"
		"set prompt\n"
		"where\n"
		"detach\n"
		"quit\n"
		"EOF\n",
		globalProgName, (int)getpid()))
    return;
  
#elif defined(PLATFORM_FREEBSD)

  /*
   * FreeBSD insists on sending a SIGSTOP to the process we
   * attach to, so we let the debugger send a SIGCONT to that
   * process after we have detached.
   */
  if (DumpStack("gdb -q %s %d 2>/dev/null <<EOF\n"
		"set prompt\n"
		"where\n"
		"detach\n"
		"shell kill -CONT %d\n"
		"quit\n"
		"EOF\n",
		globalProgName, (int)getpid(), (int)getpid()))
    return;

#elif defined(PLATFORM_HPUX)

  /*
   * HP decided to call their debugger xdb.
   *
   * This does not seem to work properly yet. The debugger says
   * "Note: Stack traces may not be possible until you are
   *  stopped in user code." on HP-UX 09.01
   *
   * -L = line-oriented interface.
   * "T [depth]" gives a stacktrace with local variables.
   * The final "y" is confirmation to the quit command.
   */
  if (DumpStack("xdb -P %d -L %s 2>&1 <<EOF\n"
		"T 50\n"
		"q\ny\n"
		"EOF\n",
		(int)getpid(), globalProgName))
    return;

  if (DumpStack("gdb -q %s %d 2>/dev/null <<EOF\n"
		"set prompt\n"
		"where\n"
		"detach\n"
		"quit\n"
		"EOF\n",
		globalProgName, (int)getpid()))
    return;
  
#elif defined(PLATFORM_IRIX)

  /*
   * "set $page=0" drops hold mode
   * "dump ." displays the contents of the variables
   */
  if (DumpStack("dbx -p %d 2>/dev/null <<EOF\n"
		"set \\$page=0\n"
		"where\n"
# if !defined(__GNUC__)
		/* gcc does not generate this information */
		"dump .\n"
# endif
		"detach\n"
		"EOF\n",
		(int)getpid()))
    return;

# if defined(USE_LIBEXC)
  if (trace_back_stack_and_print())
    return;
# endif
  
  if (DumpStack("gdb -q %s %d 2>/dev/null <<EOF\n"
		"set prompt\n"
		"where\n"
		"echo ---\\n\n"
		"frame 5\n"      /* Skip signal handler frames */
		"set \\$x = 50\n"
		"while (\\$x)\n" /* Print local variables for each frame */
		"info locals\n"
		"up\n"
		"set \\$x--\n"
		"end\n"
		"echo ---\\n\n"
		"detach\n"
		"quit\n"
		"EOF\n",
		globalProgName, (int)getpid()))
    return;

#elif defined(PLATFORM_OSF)
  
  if (DumpStack("dbx -pid %d %s 2>/dev/null <<EOF\n"
		"where\n"
		"detach\n"
		"quit\n"
		"EOF\n",
		(int)getpid(), globalProgName))
    return;

  if (DumpStack("gdb -q %s %d 2>/dev/null <<EOF\n"
		"set prompt\n"
		"where\n"
		"detach\n"
		"quit\n"
		"EOF\n",
		globalProgName, (int)getpid()))
    return;
  
#elif defined(PLATFORM_SCO)
  
  /*
   * SCO OpenServer dbx is like a catch-22. The 'detach' command
   * depends on whether ptrace(S) support detaching or not. If it
   * is supported then 'detach' must be used, otherwise the process
   * will be killed upon dbx exit. If it isn't supported then 'detach'
   * will cause the process to be killed. We do not want it to be
   * killed.
   *
   * Out of two evils, the omission of 'detach' was chosen because
   * it worked on our system.
   */
  if (DumpStack("dbx %s %d 2>/dev/null <<EOF\n"
		"where\n"
		"quit\nEOF\n",
		globalProgName, (int)getpid()))
    return;

  if (DumpStack("gdb -q %s %d 2>/dev/null <<EOF\n"
		"set prompt\n"
		"where\n"
		"detach\n"
		"quit\n"
		"EOF\n",
		globalProgName, (int)getpid()))
    return;

#elif defined(PLATFORM_SOLARIS)
  
  if (DumpStack("dbx %s %d 2>/dev/null <<EOF\n"
		"where\n"
		"detach\n"
		"EOF\n",
		globalProgName, (int)getpid()))
    return;

  if (DumpStack("gdb -q %s %d 2>/dev/null <<EOF\n"
		"set prompt\n"
		"where\n"
		"echo ---\\n\n"
		"frame 5\n"      /* Skip signal handler frames */
		"set \\$x = 50\n"
		"while (\\$x)\n" /* Print local variables for each frame */
		"info locals\n"
		"up\n"
		"set \\$x--\n"
		"end\n"
		"echo ---\\n\n"
		"detach\n"
		"quit\n"
		"EOF\n",
		globalProgName, (int)getpid()))
    return;

  if (DumpStack("/usr/proc/bin/pstack %d",
		(int)getpid()))
    return;

  /*
   * Other Unices (AIX, HPUX, SCO) also have adb, but
   * they seem unable to attach to a running process.)
   */
  if (DumpStack("adb %s 2>&1 <<EOF\n"
		"0t%d:A\n" /* Attach to pid */
		"\\$c\n"   /* print stacktrace */
		":R\n"     /* Detach */
		"\\$q\n"   /* Quit */
		"EOF\n",
		globalProgName, (int)getpid()))
    return;

#else /* All other platforms */

  /*
   * TODO: SCO/UnixWare 7 must be something like (not tested)
   *  debug -i c <pid> <<EOF\nstack -f 4\nquit\nEOF\n
   */
  
# if !defined(__GNUC__)
  if (DumpStack("dbx %s %d 2>/dev/null <<EOF\n"
		"where\n"
		"detach\n"
		"EOF\n",
		globalProgName, (int)getpid()))
    return;
# endif

  if (DumpStack("gdb -q %s %d 2>/dev/null <<EOF\n"
		"set prompt\n"
		"where\n"
		"echo ---\\n\n"
		"frame 4\n"
		"set \\$x = 50\n"
		"while (\\$x)\n" /* Print local variables for each frame */
		"info locals\n"
		"up\n"
		"set \\$x--\n"
		"end\n"
		"echo ---\\n\n"
		"detach\n"
		"quit\n"
		"EOF\n",
		globalProgName, (int)getpid()))
    return;
  
#endif

  write(globalOutFD,
	"No debugger found\n", strlen("No debugger found\n"));
}

#if defined(STANDALONE)

#include <signal.h>

void CrashHandler(int sig)
{
  /* Reinstall default handler to prevent race conditions */
  signal(sig, SIG_DFL);
  /* Print the stack trace */
  StackTrace();
  /* And exit because we may have corrupted the internal
   * memory allocation lists. */
  _exit(EXIT_FAILURE);
}


void Crash(void)
{
  /* Force a crash */
  strcpy(NULL, "");
}

int main(int argc, char *argv[])
{
  struct sigaction sact;

  globalProgName = argv[0];
  
  sigemptyset(&sact.sa_mask);
  sact.sa_flags = 0;
  sact.sa_handler = CrashHandler;
  sigaction(SIGSEGV, &sact, NULL);
  sigaction(SIGBUS, &sact, NULL);

  Crash();
  return EXIT_SUCCESS;
}
#endif


syntax highlighted by Code2HTML, v. 0.9.1