Fix compilation with old g++ 3.3.5 and debian-sarge.
[wvstreams.git] / utils / wvcrash.cc
blob2f56d32f4d9d3bdfcb4d08c706d0f24d80e71bf1
1 /*
2 * Worldvisions Weaver Software:
3 * Copyright (C) 1997-2002 Net Integration Technologies, Inc.
4 *
5 * Routines to generate a stack backtrace automatically when a program
6 * crashes.
7 */
8 #include "wvcrash.h"
9 #include "wvtask.h"
11 #include <errno.h>
12 #include <fcntl.h>
13 #include <signal.h>
14 #include <stdio.h>
15 #include <string.h>
16 #include <time.h>
17 #include <sys/types.h>
19 #ifndef _WIN32
20 # include <sys/wait.h>
21 # include <sys/syscall.h>
22 #endif
24 #ifndef WVCRASH_USE_SIGALTSTACK
25 # define WVCRASH_USE_SIGALTSTACK 1
26 #endif
28 // FIXME: this file mostly only works in Linux
29 #ifdef __linux
31 # include <execinfo.h>
32 #include <unistd.h>
34 #ifdef __USE_GNU
35 static const char *argv0 = program_invocation_short_name;
36 #else
37 static const char *argv0 = "UNKNOWN";
38 #endif // __USE_GNU
40 #if WVCRASH_USE_SIGALTSTACK
41 static const size_t altstack_size = 1048576; // wvstreams can be a pig
42 static char altstack[altstack_size];
43 #endif
45 // Reserve enough buffer for a screenful of programme.
46 static const int buffer_size = 2048 + wvcrash_ring_buffer_size;
48 static char desc[buffer_size];
50 // write a string 'str' to fd
51 static void wr(int fd, const char *str)
53 write(fd, str, strlen(str));
57 // convert 'num' to a string and write it to fd.
58 static void wrn(int fd, int num)
60 int tmp;
61 char c;
63 if (num < 0)
65 wr(fd, "-");
66 num = -num;
68 else if (num == 0)
70 wr(fd, "0");
71 return;
74 tmp = 0;
75 while (num > 0)
77 tmp *= 10;
78 tmp += num%10;
79 num /= 10;
82 while (tmp > 0)
84 c = '0' + (tmp%10);
85 write(fd, &c, 1);
86 tmp /= 10;
91 // convert 'addr' to hex and write it to fd.
92 static void wra(int fd, const void *addr)
94 const unsigned int ptrbitsshift = (sizeof(ptrdiff_t) << 3) - 4;
95 char digits[] = "0123456789ABCDEF";
97 write(fd, "0x", 2);
98 for (int shift=ptrbitsshift; shift>=0; shift-=4)
99 write(fd, &digits[(((ptrdiff_t)addr)>>shift)&0xF], 1);
103 static void wvcrash_real(int sig, int fd, pid_t pid)
105 static void *trace[64];
106 static char *signame = strsignal(sig);
108 wr(fd, argv0);
109 if (desc[0])
111 wr(fd, " (");
112 wr(fd, desc);
113 wr(fd, ")");
115 wr(fd, " dying on signal ");
116 wrn(fd, sig);
117 if (signame)
119 wr(fd, " (");
120 wr(fd, signame);
121 wr(fd, ")\n");
124 // Write out the PID and PPID.
125 static char pid_str[32];
126 wr(fd, "\nProcess ID: ");
127 snprintf(pid_str, sizeof(pid_str), "%d", getpid());
128 pid_str[31] = '\0';
129 wr(fd, pid_str);
130 wr(fd, "\nParent's process ID: ");
131 snprintf(pid_str, sizeof(pid_str), "%d", getppid());
132 pid_str[31] = '\0';
133 wr(fd, pid_str);
134 wr(fd, "\n");
136 #if WVCRASH_USE_SIGALTSTACK
137 // Determine if this has likely been a stack overflow
138 const void *last_real_stack_frame;
139 for (;;)
141 last_real_stack_frame = __builtin_frame_address(0);
142 if (last_real_stack_frame == NULL
143 || last_real_stack_frame < &altstack[0]
144 || last_real_stack_frame >= &altstack[altstack_size])
145 break;
146 last_real_stack_frame = __builtin_frame_address(1);
147 if (last_real_stack_frame == NULL
148 || last_real_stack_frame < &altstack[0]
149 || last_real_stack_frame >= &altstack[altstack_size])
150 break;
151 last_real_stack_frame = __builtin_frame_address(2);
152 if (last_real_stack_frame == NULL
153 || last_real_stack_frame < &altstack[0]
154 || last_real_stack_frame >= &altstack[altstack_size])
155 break;
156 last_real_stack_frame = __builtin_frame_address(3);
157 if (last_real_stack_frame == NULL
158 || last_real_stack_frame < &altstack[0]
159 || last_real_stack_frame >= &altstack[altstack_size])
160 break;
161 last_real_stack_frame = __builtin_frame_address(4);
162 if (last_real_stack_frame == NULL
163 || last_real_stack_frame < &altstack[0]
164 || last_real_stack_frame >= &altstack[altstack_size])
165 break;
166 last_real_stack_frame = __builtin_frame_address(5);
167 if (last_real_stack_frame == NULL
168 || last_real_stack_frame < &altstack[0]
169 || last_real_stack_frame >= &altstack[altstack_size])
170 break;
171 last_real_stack_frame = NULL;
172 break;
174 if (last_real_stack_frame != NULL)
176 wr(fd, "\nLast real stack frame: ");
177 wra(fd, last_real_stack_frame);
178 const void *top_of_stack = WvTaskMan::current_top_of_stack();
179 wr(fd, "\nTop of stack: ");
180 wra(fd, top_of_stack);
181 size_t stack_size = size_t(top_of_stack) - size_t(last_real_stack_frame);
182 wr(fd, "\nStack size: ");
183 wrn(fd, int(stack_size));
184 size_t stack_size_limit = WvTaskMan::current_stacksize_limit();
185 if (stack_size_limit > 0)
187 wr(fd, "\nStack size rlimit: ");
188 wrn(fd, int(stack_size_limit));
189 if (stack_size > stack_size_limit)
190 wr(fd, " DEFINITE STACK OVERFLOW");
191 else if (stack_size + 16384 > stack_size_limit)
192 wr(fd, " PROBABLE STACK OVERFLOW");
194 wr(fd, "\n");
196 #endif
199 // Write out the contents of the ring buffer
201 const char *ring;
202 bool first = true;
203 while ((ring = wvcrash_ring_buffer_get()) != NULL)
205 if (first)
207 first = false;
208 wr(fd, "\nRing buffer:\n");
210 wr(fd, ring);
214 // Write out the assertion message, as logged by __assert*_fail(), if any.
216 const char *assert_msg = wvcrash_read_assert();
217 if (assert_msg && assert_msg[0])
219 wr(fd, "\nAssert:\n");
220 wr(fd, assert_msg);
224 // Write out the note, if any.
226 const char *will_msg = wvcrash_read_will();
227 if (will_msg && will_msg[0])
229 wr(fd, "\nLast Will and Testament:\n");
230 wr(fd, will_msg);
231 wr(fd, "\n");
235 if (WvCrashInfo::in_stream_state != WvCrashInfo::UNUSED
236 && WvCrashInfo::in_stream)
238 const char *state = NULL;
239 switch (WvCrashInfo::in_stream_state)
241 case WvCrashInfo::UNUSED:
242 // Can't possibly get here.
243 break;
244 case WvCrashInfo::PRE_SELECT:
245 state = "\nStream in pre_select: ";
246 break;
247 case WvCrashInfo::POST_SELECT:
248 state = "\nStream in post_select: ";
249 break;
250 case WvCrashInfo::EXECUTE:
251 state = "\nStream in execute: ";
252 break;
255 if (state)
257 static char ptr_str[32];
258 snprintf(ptr_str, sizeof(ptr_str), "%p", WvCrashInfo::in_stream);
259 ptr_str[sizeof(ptr_str) - 1] = '\0';
261 wr(fd, state);
262 wr(fd, WvCrashInfo::in_stream_id && WvCrashInfo::in_stream_id[0]
263 ? WvCrashInfo::in_stream_id : "unknown stream");
264 wr(fd, " (");
265 wr(fd, ptr_str);
266 wr(fd, ")\n");
270 wr(fd, "\nBacktrace:\n");
271 backtrace_symbols_fd(trace,
272 backtrace(trace, sizeof(trace)/sizeof(trace[0])), fd);
274 if (pid > 0)
276 // Wait up to 10 seconds for child to write wvcrash file in case there
277 // is limited space availible on the device; wvcrash file is more
278 // useful than core dump
279 int i;
280 struct timespec ts = { 0, 100*1000*1000 };
281 close(fd);
282 for (i=0; i < 100; ++i)
284 if (waitpid(pid, NULL, WNOHANG) == pid)
285 break;
286 nanosleep(&ts, NULL);
290 // we want to create a coredump, and the kernel seems to not want to do
291 // that if we send ourselves the same signal that we're already in.
292 // Whatever... just send a different one :)
293 if (sig == SIGABRT)
294 sig = SIGBUS;
295 else if (sig != 0)
296 sig = SIGABRT;
298 signal(sig, SIG_DFL);
299 raise(sig);
303 // Hint: we can't do anything really difficult here, because the program is
304 // probably really confused. So we should try to limit this to straight
305 // kernel syscalls (ie. don't fiddle with FILE* or streams or lists, just
306 // use straight file descriptors.)
308 // We fork a subprogram to do the fancy stuff like sending email.
310 void wvcrash(int sig)
312 int fds[2];
313 pid_t pid;
315 signal(sig, SIG_DFL);
316 wr(2, "\n\nwvcrash: crashing!\n");
318 // close some fds, just in case the reason we're crashing is fd
319 // exhaustion! Otherwise we won't be able to create our pipe to a
320 // subprocess. Probably only closing two fds is possible, but the
321 // subproc could get confused if all the fds are non-close-on-exec and
322 // it needs to open a few files.
324 // Don't close fd 0, 1, or 2, however, since those might be useful to
325 // the child wvcrash script. Also, let's skip 3 and 4, in case someone
326 // uses them for something. But don't close fd numbers that are *too*
327 // big; if someone ulimits the number of fds we can use, and *that's*
328 // why we're crashing, there's no guarantee that high fd numbers are in
329 // use even if we've run out.
330 for (int count = 5; count < 15; count++)
331 close(count);
333 if (pipe(fds))
334 wvcrash_real(sig, 2, 0); // just use stderr instead
335 else
337 pid = fork();
338 if (pid < 0)
339 wvcrash_real(sig, 2, 0); // just use stderr instead
340 else if (pid == 0) // child
342 close(fds[1]);
343 dup2(fds[0], 0); // make stdin read from pipe
344 fcntl(0, F_SETFD, 0);
346 execlp("wvcrash", "wvcrash", NULL);
348 // if we get here, we couldn't exec wvcrash
349 wr(2, "wvcrash: can't exec wvcrash binary "
350 "- writing to wvcrash.txt!\n");
351 execlp("dd", "dd", "of=wvcrash.txt", NULL);
353 wr(2, "wvcrash: can't exec dd to write to wvcrash.txt!\n");
354 _exit(127);
356 else if (pid > 0) // parent
358 close(fds[0]);
359 wvcrash_real(sig, fds[1], pid);
363 // child (usually)
364 _exit(126);
368 static void wvcrash_setup_alt_stack()
370 #if WVCRASH_USE_SIGALTSTACK
371 stack_t ss;
373 ss.ss_sp = altstack;
374 ss.ss_flags = 0;
375 ss.ss_size = altstack_size;
377 if (ss.ss_sp == NULL || sigaltstack(&ss, NULL))
378 fprintf(stderr, "Failed to setup sigaltstack for wvcrash: %s\n",
379 strerror(errno));
380 #endif //WVCRASH_USE_SIGALTSTACK
383 void wvcrash_add_signal(int sig)
385 #if WVCRASH_USE_SIGALTSTACK
386 struct sigaction act;
388 act.sa_handler = wvcrash;
389 sigfillset(&act.sa_mask);
390 act.sa_flags = SA_ONSTACK | SA_RESTART;
391 act.sa_restorer = NULL;
393 if (sigaction(sig, &act, NULL))
394 fprintf(stderr, "Failed to setup wvcrash handler for signal %d: %s\n",
395 sig, strerror(errno));
396 #else //!WVCRASH_USE_SIGALTSTACK
397 signal(sig, wvcrash);
398 #endif //WVCRASH_USE_SIGALTSTACK
401 // Secret symbol for initialising the will and assert buffers
402 extern void __wvcrash_init_buffers(const char *program_name);
404 void wvcrash_setup(const char *_argv0, const char *_desc)
406 if (_argv0)
407 argv0 = basename(_argv0);
408 __wvcrash_init_buffers(argv0);
409 if (_desc)
411 strncpy(desc, _desc, buffer_size);
412 desc[buffer_size - 1] = '\0';
414 else
415 desc[0] = '\0';
417 wvcrash_setup_alt_stack();
419 wvcrash_add_signal(SIGSEGV);
420 wvcrash_add_signal(SIGBUS);
421 wvcrash_add_signal(SIGABRT);
422 wvcrash_add_signal(SIGFPE);
423 wvcrash_add_signal(SIGILL);
426 #elif defined(_WIN32)
428 #include <windows.h>
429 #include <stdio.h>
430 #include <imagehlp.h>
432 inline char* last_part(char* in)
434 int len = strlen(in);
435 char* tmp = in+len;
436 while (tmp > in)
438 if (*tmp == '/' || *tmp == '\\')
439 return tmp+1;
440 tmp--;
442 return in;
447 * Call this with a thread context to get a nice callstack. You can get a
448 * thread context either from an exception or by using this code:
449 * CONTEXT ctx;
450 * memset(&ctx, 0, sizeof(CONTEXT));
451 * ctx.ContextFlags = CONTEXT_FULL;
452 * GetThreadContext(hThread, &ctx);
454 int backtrace(CONTEXT &ctx)
456 HANDLE hProcess = (HANDLE)GetCurrentProcess();
457 HANDLE hThread = (HANDLE)GetCurrentThread();
459 SymInitialize(hProcess, NULL, TRUE);
461 STACKFRAME sf;
462 memset(&sf, 0, sizeof(STACKFRAME));
464 sf.AddrPC.Offset = ctx.Eip;
465 sf.AddrPC.Mode = AddrModeFlat;
466 sf.AddrFrame.Offset = ctx.Ebp;
467 sf.AddrFrame.Mode = AddrModeFlat;
468 sf.AddrStack.Offset = ctx.Esp;
469 sf.AddrStack.Mode = AddrModeFlat;
471 fprintf(stderr, "Generating stack trace......\n");
472 fprintf(stderr, "%3s %16s:%-10s %32s:%3s %s\n", "Num", "Module", "Addr", "Filename", "Line", "Function Name");
473 int i = 0;
474 while (StackWalk(IMAGE_FILE_MACHINE_I386,
475 hProcess,
476 hThread,
477 &sf,
478 &ctx,
479 NULL,
480 SymFunctionTableAccess,
481 SymGetModuleBase,
482 NULL))
484 if (sf.AddrPC.Offset == 0)
485 break;
487 // info about module
488 IMAGEHLP_MODULE modinfo;
489 memset(&modinfo, 0, sizeof(IMAGEHLP_MODULE));
490 modinfo.SizeOfStruct = sizeof(IMAGEHLP_MODULE);
491 SymGetModuleInfo(hProcess, sf.AddrPC.Offset, &modinfo);
493 // get some symbols
494 BYTE buffer[1024];
495 DWORD disp = 0;
496 memset(buffer, 0, sizeof(buffer));
497 PIMAGEHLP_SYMBOL sym = (PIMAGEHLP_SYMBOL)buffer;
498 sym->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL);
499 sym->MaxNameLength = sizeof(buffer) - sizeof(IMAGEHLP_SYMBOL) + 1;
500 SymGetSymFromAddr(hProcess, sf.AddrPC.Offset, &disp, sym);
502 // line numbers anyone?
503 IMAGEHLP_LINE line;
504 SymSetOptions(SYMOPT_LOAD_LINES);
505 DWORD disp2 = 0;
506 memset(&line, 0, sizeof(IMAGEHLP_LINE));
507 line.SizeOfStruct = sizeof(IMAGEHLP_LINE);
508 SymGetLineFromAddr(hProcess, sf.AddrPC.Offset, &disp2, &line);
510 // output some info now then
511 fprintf(stderr, "%3d. %16s:0x%08X %32s:%-3d %s\n",
512 ++i,
513 modinfo.LoadedImageName[0]?modinfo.LoadedImageName:"unknown",
514 (DWORD)sf.AddrPC.Offset,
515 (line.FileName && line.FileName[0])?last_part(line.FileName):"unknown",
516 (line.FileName && line.FileName[0])?line.LineNumber:0,
517 sym->Name[0]?sym->Name:"unknown");
520 SymCleanup(hProcess);
522 return 1;
526 static void exception_desc(FILE *file, unsigned exception,
527 unsigned data1, unsigned data2)
530 switch (exception)
532 case 0xC0000005:
534 switch (data1)
536 case 0:
537 fprintf(file,
538 "invalid memory read from address 0x%08X",
539 data2);
540 break;
541 case 1:
542 fprintf(file,
543 "invalid memory write to address 0x%08X",
544 data2);
545 break;
546 default:
547 fprintf(file,
548 "invalid memory access (unknown type %d) at address 0x%08X",
549 data1, data2);
550 break;
553 break;
555 case 0xC0000094:
556 fprintf(file, "integer division by zero");
557 break;
559 default:
560 fprintf(file, "unknown exception (data1=0x%08X, data2=0x%08X)");
561 break;
565 static LONG WINAPI ExceptionFilter( struct _EXCEPTION_POINTERS * pExceptionPointers )
567 struct ExceptionInfo
569 unsigned exception;
570 unsigned unknown[2];
571 void *ip;
572 unsigned more_unknown;
573 unsigned data1;
574 unsigned data2;
576 ExceptionInfo *info = *(ExceptionInfo **)pExceptionPointers;
578 // handle a special exception. Number 3 = forced breakpoint
579 // having __asm int 3; in code will cause windows to ask if
580 // you want to debug the application nicely.
581 if (info->exception==0x80000003)
583 fprintf(stderr, "Preparing to debug!\n");
584 return EXCEPTION_CONTINUE_SEARCH;
587 fprintf(stderr, "--------------------------------------------------------\n");
588 fprintf(stderr, "Exception 0x%08X:\n ", info->exception);
589 exception_desc(stderr, info->exception, info->data1, info->data2);
590 fprintf(stderr, "\n at instruction 0x%08X in thread 0x%08X\n", info->ip, GetCurrentThreadId());
591 backtrace(*pExceptionPointers->ContextRecord);
592 fprintf(stderr, "--------------------------------------------------------\n");
595 return EXCEPTION_EXECUTE_HANDLER;
598 static bool have_global_exception_handler = false;
599 void setup_console_crash()
601 if (!have_global_exception_handler)
603 SetUnhandledExceptionFilter(ExceptionFilter);
604 have_global_exception_handler = true;
608 void wvcrash(int sig) {}
609 void wvcrash_setup(const char *_argv0, const char *_desc) {}
611 #else // Not Linux
613 void wvcrash(int sig) {}
614 void wvcrash_add_signal(int sig) {}
615 void wvcrash_setup(const char *_argv0, const char *_desc) {}
617 #endif // Not Linux