WvStreams
wvcrash.cc
00001 /*
00002  * Worldvisions Weaver Software:
00003  *   Copyright (C) 1997-2002 Net Integration Technologies, Inc.
00004  * 
00005  * Routines to generate a stack backtrace automatically when a program
00006  * crashes.
00007  */
00008 #include "wvcrash.h"
00009 #include "wvtask.h"
00010 
00011 #include <errno.h>
00012 #include <fcntl.h>
00013 #include <signal.h>
00014 #include <stdio.h>
00015 #include <string.h>
00016 #include <time.h>
00017 #include <sys/types.h>
00018 
00019 #ifndef _WIN32
00020 # include <sys/wait.h>
00021 # include <sys/syscall.h>
00022 #endif
00023 
00024 #ifndef WVCRASH_USE_SIGALTSTACK
00025 # define WVCRASH_USE_SIGALTSTACK 1
00026 #endif
00027 
00028 // FIXME: this file mostly only works in Linux
00029 #ifdef __linux
00030 
00031 # include <execinfo.h>
00032 #include <unistd.h>
00033 
00034 #ifdef __USE_GNU
00035 static const char *argv0 = program_invocation_short_name;
00036 #else
00037 static const char *argv0 = "UNKNOWN";
00038 #endif // __USE_GNU
00039 
00040 #if WVCRASH_USE_SIGALTSTACK
00041 static const size_t altstack_size = 1048576; // wvstreams can be a pig
00042 static char altstack[altstack_size];
00043 #endif
00044 
00045 // Reserve enough buffer for a screenful of programme.
00046 static const int buffer_size = 2048 + wvcrash_ring_buffer_size;
00047 
00048 static char desc[buffer_size];
00049 
00050 // write a string 'str' to fd
00051 static void wr(int fd, const char *str)
00052 {
00053     write(fd, str, strlen(str));
00054 }
00055 
00056 
00057 // convert 'num' to a string and write it to fd.
00058 static void wrn(int fd, int num)
00059 {
00060     int tmp;
00061     char c;
00062     
00063     if (num < 0)
00064     {
00065         wr(fd, "-");
00066         num = -num;
00067     } 
00068     else if (num == 0)
00069     {
00070         wr(fd, "0");
00071         return;
00072     }
00073     
00074     tmp = 0;
00075     while (num > 0)
00076     {
00077         tmp *= 10;
00078         tmp += num%10;
00079         num /= 10;
00080     }
00081     
00082     while (tmp > 0)
00083     {
00084         c = '0' + (tmp%10);
00085         write(fd, &c, 1);
00086         tmp /= 10;
00087     }
00088 }
00089 
00090 
00091 // convert 'addr' to hex and write it to fd.
00092 static void wra(int fd, const void *addr)
00093 {
00094     const unsigned int ptrbitsshift = (sizeof(ptrdiff_t) << 3) - 4;
00095     char digits[] = "0123456789ABCDEF";
00096 
00097     write(fd, "0x", 2);
00098     for (int shift=ptrbitsshift; shift>=0; shift-=4)
00099         write(fd, &digits[(((ptrdiff_t)addr)>>shift)&0xF], 1);
00100 }
00101 
00102 
00103 static void wvcrash_real(int sig, int fd, pid_t pid)
00104 {
00105     static void *trace[64];
00106     static char *signame = strsignal(sig);
00107     
00108     wr(fd, argv0);
00109     if (desc[0])
00110     {
00111         wr(fd, " (");
00112         wr(fd, desc);
00113         wr(fd, ")");
00114     }
00115     wr(fd, " dying on signal ");
00116     wrn(fd, sig);
00117     if (signame)
00118     {
00119         wr(fd, " (");
00120         wr(fd, signame);
00121         wr(fd, ")\n");
00122     }
00123 
00124     // Write out the PID and PPID.
00125     static char pid_str[32];
00126     wr(fd, "\nProcess ID: ");
00127     snprintf(pid_str, sizeof(pid_str), "%d", getpid());
00128     pid_str[31] = '\0';
00129     wr(fd, pid_str);
00130     wr(fd, "\nParent's process ID: ");
00131     snprintf(pid_str, sizeof(pid_str), "%d", getppid());
00132     pid_str[31] = '\0';
00133     wr(fd, pid_str);
00134     wr(fd, "\n");
00135 
00136 #if WVCRASH_USE_SIGALTSTACK
00137     // Determine if this has likely been a stack overflow
00138     const void *last_real_stack_frame;
00139     for (;;)
00140     {
00141         last_real_stack_frame = __builtin_frame_address(0);
00142         if (last_real_stack_frame == NULL
00143                 || last_real_stack_frame < &altstack[0]
00144                 || last_real_stack_frame >= &altstack[altstack_size])
00145             break;
00146         last_real_stack_frame = __builtin_frame_address(1);
00147         if (last_real_stack_frame == NULL
00148                 || last_real_stack_frame < &altstack[0]
00149                 || last_real_stack_frame >= &altstack[altstack_size])
00150             break;
00151         last_real_stack_frame = __builtin_frame_address(2);
00152         if (last_real_stack_frame == NULL
00153                 || last_real_stack_frame < &altstack[0]
00154                 || last_real_stack_frame >= &altstack[altstack_size])
00155             break;
00156         last_real_stack_frame = __builtin_frame_address(3);
00157         if (last_real_stack_frame == NULL
00158                 || last_real_stack_frame < &altstack[0]
00159                 || last_real_stack_frame >= &altstack[altstack_size])
00160             break;
00161         last_real_stack_frame = __builtin_frame_address(4);
00162         if (last_real_stack_frame == NULL
00163                 || last_real_stack_frame < &altstack[0]
00164                 || last_real_stack_frame >= &altstack[altstack_size])
00165             break;
00166         last_real_stack_frame = __builtin_frame_address(5);
00167         if (last_real_stack_frame == NULL
00168                 || last_real_stack_frame < &altstack[0]
00169                 || last_real_stack_frame >= &altstack[altstack_size])
00170             break;
00171         last_real_stack_frame = NULL;
00172         break;
00173     }
00174     if (last_real_stack_frame != NULL)
00175     {
00176         wr(fd, "\nLast real stack frame: ");
00177         wra(fd, last_real_stack_frame);
00178         const void *top_of_stack = WvTaskMan::current_top_of_stack();
00179         wr(fd, "\nTop of stack: ");
00180         wra(fd, top_of_stack);
00181         size_t stack_size = size_t(top_of_stack) - size_t(last_real_stack_frame);
00182         wr(fd, "\nStack size: ");
00183         wrn(fd, int(stack_size));
00184         size_t stack_size_limit = WvTaskMan::current_stacksize_limit();
00185         if (stack_size_limit > 0)
00186         {
00187             wr(fd, "\nStack size rlimit: ");
00188             wrn(fd, int(stack_size_limit));
00189             if (stack_size > stack_size_limit)
00190                 wr(fd, "  DEFINITE STACK OVERFLOW");
00191             else if (stack_size + 16384 > stack_size_limit)
00192                 wr(fd, "  PROBABLE STACK OVERFLOW");
00193         }
00194         wr(fd, "\n");
00195     }
00196 #endif
00197                 
00198 
00199     // Write out the contents of the ring buffer
00200     {
00201         const char *ring;
00202         bool first = true;
00203         while ((ring = wvcrash_ring_buffer_get()) != NULL)
00204         {
00205             if (first)
00206             {
00207                 first = false;
00208                 wr(fd, "\nRing buffer:\n");
00209             }
00210             wr(fd, ring);
00211         }
00212     }
00213     
00214     // Write out the assertion message, as logged by __assert*_fail(), if any.
00215     {
00216         const char *assert_msg = wvcrash_read_assert();
00217         if (assert_msg && assert_msg[0])
00218         {
00219             wr(fd, "\nAssert:\n");
00220             wr(fd, assert_msg);
00221         }
00222     }
00223 
00224     // Write out the note, if any.
00225     {
00226         const char *will_msg = wvcrash_read_will();
00227         if (will_msg && will_msg[0])
00228         {
00229             wr(fd, "\nLast Will and Testament:\n");
00230             wr(fd, will_msg);
00231             wr(fd, "\n");
00232         }
00233     }
00234 
00235     if (WvCrashInfo::in_stream_state != WvCrashInfo::UNUSED
00236         && WvCrashInfo::in_stream)
00237     {
00238         const char *state = NULL;
00239         switch (WvCrashInfo::in_stream_state)
00240         {
00241         case WvCrashInfo::UNUSED:
00242             // Can't possibly get here.
00243             break;
00244         case WvCrashInfo::PRE_SELECT:
00245             state = "\nStream in pre_select: ";
00246             break;
00247         case WvCrashInfo::POST_SELECT:
00248             state = "\nStream in post_select: ";
00249             break;
00250         case WvCrashInfo::EXECUTE:
00251             state = "\nStream in execute: ";
00252             break;
00253         }
00254 
00255         if (state)
00256         {
00257             static char ptr_str[32];
00258             snprintf(ptr_str, sizeof(ptr_str), "%p", WvCrashInfo::in_stream);
00259             ptr_str[sizeof(ptr_str) - 1] = '\0';
00260 
00261             wr(fd, state);
00262             wr(fd, WvCrashInfo::in_stream_id && WvCrashInfo::in_stream_id[0]
00263                ? WvCrashInfo::in_stream_id : "unknown stream");
00264             wr(fd, " (");
00265             wr(fd, ptr_str);
00266             wr(fd, ")\n");
00267         }
00268     }
00269 
00270     wr(fd, "\nBacktrace:\n");
00271     backtrace_symbols_fd(trace,
00272                  backtrace(trace, sizeof(trace)/sizeof(trace[0])), fd);
00273     
00274     if (pid > 0)
00275     {
00276         // Wait up to 10 seconds for child to write wvcrash file in case there
00277         // is limited space availible on the device; wvcrash file is more
00278         // useful than core dump
00279         int i;
00280         struct timespec ts = { 0, 100*1000*1000 };
00281         close(fd);
00282         for (i=0; i < 100; ++i)
00283         {
00284             if (waitpid(pid, NULL, WNOHANG) == pid)
00285                 break;
00286             nanosleep(&ts, NULL);
00287         }
00288     }
00289 
00290     // we want to create a coredump, and the kernel seems to not want to do
00291     // that if we send ourselves the same signal that we're already in.
00292     // Whatever... just send a different one :)
00293     if (sig == SIGABRT)
00294         sig = SIGBUS;
00295     else if (sig != 0)
00296         sig = SIGABRT;
00297    
00298     signal(sig, SIG_DFL);
00299     raise(sig);
00300 }
00301 
00302 
00303 // Hint: we can't do anything really difficult here, because the program is
00304 // probably really confused.  So we should try to limit this to straight
00305 // kernel syscalls (ie. don't fiddle with FILE* or streams or lists, just
00306 // use straight file descriptors.)
00307 // 
00308 // We fork a subprogram to do the fancy stuff like sending email.
00309 // 
00310 void wvcrash(int sig)
00311 {
00312     int fds[2];
00313     pid_t pid;
00314 
00315     signal(sig, SIG_DFL);
00316     wr(2, "\n\nwvcrash: crashing!\n");
00317     
00318     // close some fds, just in case the reason we're crashing is fd
00319     // exhaustion!  Otherwise we won't be able to create our pipe to a
00320     // subprocess.  Probably only closing two fds is possible, but the
00321     // subproc could get confused if all the fds are non-close-on-exec and
00322     // it needs to open a few files.
00323     // 
00324     // Don't close fd 0, 1, or 2, however, since those might be useful to
00325     // the child wvcrash script.  Also, let's skip 3 and 4, in case someone
00326     // uses them for something.  But don't close fd numbers that are *too*
00327     // big; if someone ulimits the number of fds we can use, and *that's*
00328     // why we're crashing, there's no guarantee that high fd numbers are in
00329     // use even if we've run out.
00330     for (int count = 5; count < 15; count++)
00331         close(count);
00332     
00333     if (pipe(fds))
00334         wvcrash_real(sig, 2, 0); // just use stderr instead
00335     else
00336     {
00337         pid = fork();
00338         if (pid < 0)
00339             wvcrash_real(sig, 2, 0); // just use stderr instead
00340         else if (pid == 0) // child
00341         {
00342             close(fds[1]);
00343             dup2(fds[0], 0); // make stdin read from pipe
00344             fcntl(0, F_SETFD, 0);
00345             
00346             execlp("wvcrash", "wvcrash", NULL);
00347             
00348             // if we get here, we couldn't exec wvcrash
00349             wr(2, "wvcrash: can't exec wvcrash binary "
00350                "- writing to wvcrash.txt!\n");
00351             execlp("dd", "dd", "of=wvcrash.txt", NULL);
00352             
00353             wr(2, "wvcrash: can't exec dd to write to wvcrash.txt!\n");
00354             _exit(127);
00355         }
00356         else if (pid > 0) // parent
00357         {
00358             close(fds[0]);
00359             wvcrash_real(sig, fds[1], pid);
00360         }
00361     }
00362     
00363     // child (usually)
00364     _exit(126);
00365 }
00366 
00367 
00368 static void wvcrash_setup_alt_stack()
00369 {
00370 #if WVCRASH_USE_SIGALTSTACK
00371     stack_t ss;
00372     
00373     ss.ss_sp = altstack;
00374     ss.ss_flags = 0;
00375     ss.ss_size = altstack_size;
00376     
00377     if (ss.ss_sp == NULL || sigaltstack(&ss, NULL))
00378         fprintf(stderr, "Failed to setup sigaltstack for wvcrash: %s\n",
00379                 strerror(errno)); 
00380 #endif //WVCRASH_USE_SIGALTSTACK
00381 }
00382 
00383 void wvcrash_add_signal(int sig)
00384 {
00385 #if WVCRASH_USE_SIGALTSTACK
00386     struct sigaction act;
00387     
00388     memset(&act,0,sizeof(act));
00389     act.sa_handler = wvcrash;
00390     sigfillset(&act.sa_mask);
00391     act.sa_flags = SA_ONSTACK | SA_RESTART;
00392     
00393     if (sigaction(sig, &act, NULL))
00394         fprintf(stderr, "Failed to setup wvcrash handler for signal %d: %s\n",
00395                 sig, strerror(errno));
00396 #else //!WVCRASH_USE_SIGALTSTACK
00397     signal(sig, wvcrash);
00398 #endif //WVCRASH_USE_SIGALTSTACK
00399 }
00400 
00401 // Secret symbol for initialising the will and assert buffers
00402 extern void __wvcrash_init_buffers(const char *program_name);
00403 
00404 void wvcrash_setup(const char *_argv0, const char *_desc)
00405 {
00406     if (_argv0)
00407         argv0 = basename(_argv0);
00408     __wvcrash_init_buffers(argv0);
00409     if (_desc)
00410     {
00411         strncpy(desc, _desc, buffer_size);
00412         desc[buffer_size - 1] = '\0';
00413     }
00414     else
00415         desc[0] = '\0';
00416     
00417     wvcrash_setup_alt_stack();
00418     
00419     wvcrash_add_signal(SIGSEGV);
00420     wvcrash_add_signal(SIGBUS);
00421     wvcrash_add_signal(SIGABRT);
00422     wvcrash_add_signal(SIGFPE);
00423     wvcrash_add_signal(SIGILL);
00424 }
00425 
00426 #elif defined(_WIN32)
00427 
00428 #include <windows.h>
00429 #include <stdio.h>
00430 #include <imagehlp.h>
00431 
00432 inline char* last_part(char* in)
00433 {
00434     int len = strlen(in);
00435     char* tmp = in+len;
00436     while (tmp > in)
00437     {
00438         if (*tmp == '/' || *tmp == '\\')
00439             return tmp+1;
00440         tmp--;
00441     }
00442     return in;
00443 }
00444 
00445 
00454 int backtrace(CONTEXT &ctx)
00455 {
00456     HANDLE hProcess = (HANDLE)GetCurrentProcess();
00457     HANDLE hThread = (HANDLE)GetCurrentThread();
00458 
00459     SymInitialize(hProcess, NULL, TRUE);
00460 
00461     STACKFRAME sf;
00462     memset(&sf, 0, sizeof(STACKFRAME));
00463 
00464     sf.AddrPC.Offset = ctx.Eip;
00465     sf.AddrPC.Mode = AddrModeFlat;
00466     sf.AddrFrame.Offset = ctx.Ebp;
00467     sf.AddrFrame.Mode = AddrModeFlat;
00468     sf.AddrStack.Offset = ctx.Esp;
00469     sf.AddrStack.Mode = AddrModeFlat;
00470 
00471     fprintf(stderr, "Generating stack trace......\n");
00472     fprintf(stderr, "%3s  %16s:%-10s %32s:%3s %s\n", "Num", "Module", "Addr", "Filename", "Line", "Function Name");
00473     int i = 0;
00474     while (StackWalk(IMAGE_FILE_MACHINE_I386,
00475         hProcess,
00476         hThread,
00477         &sf,
00478         &ctx,
00479         NULL,
00480         SymFunctionTableAccess,
00481         SymGetModuleBase,
00482         NULL))
00483     {
00484         if (sf.AddrPC.Offset == 0)
00485             break;
00486 
00487         // info about module
00488         IMAGEHLP_MODULE modinfo;
00489         memset(&modinfo, 0, sizeof(IMAGEHLP_MODULE));
00490         modinfo.SizeOfStruct = sizeof(IMAGEHLP_MODULE);
00491         SymGetModuleInfo(hProcess, sf.AddrPC.Offset, &modinfo);
00492 
00493         // get some symbols
00494         BYTE buffer[1024];
00495         DWORD disp = 0;
00496         memset(buffer, 0, sizeof(buffer));
00497         PIMAGEHLP_SYMBOL sym = (PIMAGEHLP_SYMBOL)buffer;
00498         sym->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL);
00499         sym->MaxNameLength = sizeof(buffer) - sizeof(IMAGEHLP_SYMBOL) + 1;
00500         SymGetSymFromAddr(hProcess, sf.AddrPC.Offset, &disp, sym);
00501 
00502         // line numbers anyone?
00503         IMAGEHLP_LINE line;
00504         SymSetOptions(SYMOPT_LOAD_LINES);
00505         DWORD disp2 = 0;
00506         memset(&line, 0, sizeof(IMAGEHLP_LINE));
00507         line.SizeOfStruct = sizeof(IMAGEHLP_LINE);
00508         SymGetLineFromAddr(hProcess, sf.AddrPC.Offset, &disp2, &line);
00509 
00510         // output some info now then
00511         fprintf(stderr, "%3d. %16s:0x%08X %32s:%-3d %s\n",
00512                 ++i,
00513                 modinfo.LoadedImageName[0]?modinfo.LoadedImageName:"unknown",
00514                 (DWORD)sf.AddrPC.Offset,
00515                 (line.FileName && line.FileName[0])?last_part(line.FileName):"unknown",
00516                 (line.FileName && line.FileName[0])?line.LineNumber:0,
00517                 sym->Name[0]?sym->Name:"unknown");
00518     }
00519 
00520     SymCleanup(hProcess);
00521 
00522     return 1;
00523 }
00524 
00525 
00526 static void exception_desc(FILE *file, unsigned exception,
00527         unsigned data1, unsigned data2)
00528 {
00529 
00530     switch (exception)
00531     {
00532         case 0xC0000005:
00533         {
00534             switch (data1)
00535             {
00536                 case 0:
00537                     fprintf(file,
00538                             "invalid memory read from address 0x%08X",
00539                             data2);
00540                     break;
00541                 case 1:
00542                     fprintf(file,
00543                             "invalid memory write to address 0x%08X",
00544                             data2);
00545                     break;
00546                 default:
00547                     fprintf(file,
00548                             "invalid memory access (unknown type %d) at address 0x%08X",
00549                             data1, data2);
00550                     break;
00551             }
00552         }
00553         break;
00554 
00555         case 0xC0000094:
00556             fprintf(file, "integer division by zero");
00557             break;
00558 
00559         default:
00560             fprintf(file, "unknown exception (data1=0x%08X, data2=0x%08X)");
00561             break;
00562     }
00563 }
00564 
00565 static LONG WINAPI ExceptionFilter( struct _EXCEPTION_POINTERS * pExceptionPointers )
00566 {
00567     struct ExceptionInfo
00568     {
00569         unsigned exception;
00570         unsigned unknown[2];
00571         void *ip;
00572         unsigned more_unknown;
00573         unsigned data1;
00574         unsigned data2;
00575     };
00576     ExceptionInfo *info = *(ExceptionInfo **)pExceptionPointers;
00577 
00578     // handle a special exception.  Number 3 = forced breakpoint
00579     // having __asm int 3; in code will cause windows to ask if
00580     // you want to debug the application nicely.
00581     if (info->exception==0x80000003)
00582     {
00583         fprintf(stderr, "Preparing to debug!\n");
00584         return EXCEPTION_CONTINUE_SEARCH;
00585     }
00586     
00587     fprintf(stderr, "--------------------------------------------------------\n");
00588     fprintf(stderr, "Exception 0x%08X:\n  ", info->exception);
00589     exception_desc(stderr, info->exception, info->data1, info->data2);
00590     fprintf(stderr, "\n  at instruction 0x%08X in thread 0x%08X\n", info->ip, GetCurrentThreadId());
00591     backtrace(*pExceptionPointers->ContextRecord);
00592     fprintf(stderr, "--------------------------------------------------------\n");
00593 
00594                 
00595     return EXCEPTION_EXECUTE_HANDLER;
00596 }
00597 
00598 static bool have_global_exception_handler = false;
00599 void setup_console_crash()
00600 {
00601     if (!have_global_exception_handler)
00602     {
00603         SetUnhandledExceptionFilter(ExceptionFilter);
00604         have_global_exception_handler = true;
00605     }
00606 }
00607 
00608 void wvcrash(int sig) {}
00609 void wvcrash_setup(const char *_argv0, const char *_desc) {}
00610 
00611 #else // Not Linux
00612 
00613 void wvcrash(int sig) {}
00614 void wvcrash_add_signal(int sig) {}
00615 void wvcrash_setup(const char *_argv0, const char *_desc) {}
00616 
00617 #endif // Not Linux