/************************************************************************* * * $Id: stacktrace.c,v 1.1 2004-10-08 20:30:25 schoenebeck Exp $ * * Copyright (c) 1998 by Bjorn Reese * * 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. * ************************************************************************ * * 1999/08/19 - breese * - Cleaned up the interface * * 1999/08/06 - breese * - Added U_STACK_TRACE for HP/UX * * 1999/01/24 - breese * - Added GCC_DumpStack * * 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(unix) || defined(__unix) || defined(__xlC__) # define PLATFORM_UNIX #elif defined(WIN32) || defined(_WIN32) # define PLATFORM_WIN32 #endif #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 /* ANSI C includes */ #include #include #include #include #include #include #include #if defined(PLATFORM_UNIX) # include # include # include # if defined(PLATFORM_IRIX) && defined(USE_BUILTIN) /* Compile with -DUSE_BUILTIN and -lexc */ # include # elif defined(PLATFORM_HPUX) && defined(USE_BUILTIN) /* Compile with -DUSE_BUILTIN and -lcl */ extern void U_STACK_TRACE(void); # endif #endif #include "stacktrace.h" #ifndef FALSE # define FALSE (0 == 1) # define TRUE (! FALSE) #endif #define SYS_ERROR -1 #ifndef EXIT_SUCCESS # define EXIT_SUCCESS 0 #endif #ifndef EXIT_FAILURE # define EXIT_FAILURE 1 #endif #define MAX_BUFFER_SIZE 512 #if defined(__GNUC__) /* Change the code if ADDRESSLIST_SIZE is increased */ # define ADDRESSLIST_SIZE 20 #endif /************************************************************************* * Globals * * We cannot pass custom arguments to signal handlers so we store * them as global variables (but limit their scope to this file.) */ static const char *global_progname; static int global_output = STDOUT_FILENO; #if defined(PLATFORM_UNIX) /************************************************************************* * my_pclose [private] */ static void my_pclose(int fd, int pid) { close(fd); /* Make sure the the child process has terminated */ (void)kill(pid, SIGTERM); } /************************************************************************* * my_popen [private] */ static int my_popen(const char *command, pid_t *pid) { int rc; //int pipefd[2]; //FIXME: deactivated separate piping of debugger output, as it caused problems in conjunction with threads //rc = pipe(pipefd); rc = SYS_ERROR; //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; } /************************************************************************* * GCC_DumpStack [private] * * * This code is still experimental. * * Stackbased arrays are used to prevent allocating from the heap. * Warning: sprintf/sscanf are not ASync safe. They were used for * convenience. * * 'nm' is used because it is most widespread * GNU: nm [-B] * Solaris: nm -x -p * IRIX: nm -x -B (but __builtin_return_address() always returns NULL) * AIX: nm -x -B * OSF/1: nm -B * SCO/OpenServer: nm -x -p * HP/UX nm -x -p */ #if defined(__GNUC__) && defined(USE_BUILTIN) typedef struct { unsigned long realAddress; unsigned long closestAddress; char name[MAX_BUFFER_SIZE + 1]; char type; } address_T; static void GCC_DumpStack(void) { int i; void *p = &p; /* dummy start value */ address_T syms[ADDRESSLIST_SIZE + 1]; char buffer[MAX_BUFFER_SIZE]; int fd; pid_t pid; unsigned long addr; unsigned long highestAddress; unsigned long lowestAddress; char type; char *pname; char name[MAX_BUFFER_SIZE]; int number; for (i = 0; p; i++) { /* * This is based on code by Steve Coleman * * __builtin_return_address() only accepts a constant as argument. */ switch (i) { case 0: p = __builtin_return_address(0); break; case 1: p = __builtin_return_address(1); break; case 2: p = __builtin_return_address(2); break; case 3: p = __builtin_return_address(3); break; case 4: p = __builtin_return_address(4); break; case 5: p = __builtin_return_address(5); break; case 6: p = __builtin_return_address(6); break; case 7: p = __builtin_return_address(7); break; case 8: p = __builtin_return_address(8); break; case 9: p = __builtin_return_address(9); break; case 10: p = __builtin_return_address(10); break; case 11: p = __builtin_return_address(11); break; case 12: p = __builtin_return_address(12); break; case 13: p = __builtin_return_address(13); break; case 14: p = __builtin_return_address(14); break; case 15: p = __builtin_return_address(15); break; case 16: p = __builtin_return_address(16); break; case 17: p = __builtin_return_address(17); break; case 18: p = __builtin_return_address(18); break; case 19: p = __builtin_return_address(19); break; default: /* Change ADDRESSLIST_SIZE if more are added */ p = NULL; break; } if ((p) && (i < ADDRESSLIST_SIZE)) { syms[i].realAddress = (unsigned long)p; syms[i].closestAddress = 0; syms[i].name[0] = (char)0; syms[i].type = ' '; } else { syms[i].realAddress = 0; break; /* for */ } } /* for */ /* First find out if we are using GNU or vendor nm */ number = 0; strcpy(buffer, "nm -V 2>/dev/null | grep GNU | wc -l"); fd = my_popen(buffer, &pid); if (SYS_ERROR != fd) { if (my_getline(fd, buffer, sizeof(buffer))) { sscanf(buffer, "%d", &number); } my_pclose(fd, pid); } if (number == 0) /* vendor nm */ { # if defined(PLATFORM_SOLARIS) || defined(PLATFORM_SCO) || defined(PLATFORM_HPUX) strcpy(buffer, "nm -x -p "); # elif defined(PLATFORM_AIX) || defined(PLATFORM_IRIX) || defined(PLATFORM_OSF) strcpy(buffer, "nm -x -B "); # else strcpy(buffer, "nm -B "); # endif } else /* GNU nm */ strcpy(buffer, "nm -B "); strcat(buffer, global_progname); lowestAddress = ULONG_MAX; highestAddress = 0; fd = my_popen(buffer, &pid); if (SYS_ERROR != fd) { while (my_getline(fd, buffer, sizeof(buffer))) { if (buffer[0] == '\n') continue; if (3 == sscanf(buffer, "%lx %c %s", &addr, &type, name)) { if ((type == 't') || type == 'T') { if (addr == 0) continue; /* while */ if (addr < lowestAddress) lowestAddress = addr; if (addr > highestAddress) highestAddress = addr; for (i = 0; syms[i].realAddress != 0; i++) { if ((addr <= syms[i].realAddress) && (addr > syms[i].closestAddress)) { syms[i].closestAddress = addr; strncpy(syms[i].name, name, MAX_BUFFER_SIZE); syms[i].name[MAX_BUFFER_SIZE] = (char)0; syms[i].type = type; } } } } } my_pclose(fd, pid); for (i = 0; syms[i].realAddress != 0; i++) { if ((syms[i].name[0] == (char)0) || (syms[i].realAddress <= lowestAddress) || (syms[i].realAddress >= highestAddress)) { sprintf(buffer, "[%d] 0x%08lx ???\n", i, syms[i].realAddress); } else { sprintf(buffer, "[%d] 0x%08lx <%s + 0x%lx> %c\n", i, syms[i].realAddress, syms[i].name, syms[i].realAddress - syms[i].closestAddress, syms[i].type); } write(global_output, buffer, strlen(buffer)); } } } #endif /************************************************************************* * 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(global_output, "Output from ", strlen("Output from ")); strtok(cmd, " "); write(global_output, cmd, strlen(cmd)); write(global_output, "\n", strlen("\n")); gotSomething = TRUE; } if ('\n' == buf[strlen(buf)-1]) { buf[strlen(buf)-1] = (char)0; } write(global_output, buffer, strlen(buffer)); write(global_output, "\n", strlen("\n")); } } my_pclose(fd, pid); } return gotSomething; } #endif /* PLATFORM_UNIX */ /************************************************************************* * StackTrace */ void StackTrace(void) { #if defined(PLATFORM_UNIX) /* * 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 </dev/null </dev/null <&1 </dev/null </dev/null </dev/null </dev/null </dev/null </dev/null </dev/null </dev/null </dev/null <&1 < </dev/null </dev/null <