/*************************************************************************
*
* 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