WvStreams
|
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