WvStreams
wvcrash.cc
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"
10
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>
18
19#ifndef _WIN32
20# include <sys/wait.h>
21# include <sys/syscall.h>
22#endif
23
24#ifndef WVCRASH_USE_SIGALTSTACK
25# define WVCRASH_USE_SIGALTSTACK 1
26#endif
27
28// FIXME: this file mostly only works in Linux
29#if 1
30
31# include <execinfo.h>
32#include <unistd.h>
33
34#ifdef __USE_GNU
35static const char *argv0 = program_invocation_short_name;
36#else
37static const char *argv0 = "UNKNOWN";
38#endif // __USE_GNU
39
40#if WVCRASH_USE_SIGALTSTACK
41static const size_t altstack_size = 1048576; // wvstreams can be a pig
42static char altstack[altstack_size];
43#endif
44
45// Reserve enough buffer for a screenful of programme.
46static const int buffer_size = 2048 + wvcrash_ring_buffer_size;
47
48static char desc[buffer_size];
49
50// write a string 'str' to fd
51static void wr(int fd, const char *str)
52{
53 write(fd, str, strlen(str));
54}
55
56
57// convert 'num' to a string and write it to fd.
58static void wrn(int fd, int num)
59{
60 int tmp;
61 char c;
62
63 if (num < 0)
64 {
65 wr(fd, "-");
66 num = -num;
67 }
68 else if (num == 0)
69 {
70 wr(fd, "0");
71 return;
72 }
73
74 tmp = 0;
75 while (num > 0)
76 {
77 tmp *= 10;
78 tmp += num%10;
79 num /= 10;
80 }
81
82 while (tmp > 0)
83 {
84 c = '0' + (tmp%10);
85 write(fd, &c, 1);
86 tmp /= 10;
87 }
88}
89
90
91// convert 'addr' to hex and write it to fd.
92static void wra(int fd, const void *addr)
93{
94 const unsigned int ptrbitsshift = (sizeof(ptrdiff_t) << 3) - 4;
95 char digits[] = "0123456789ABCDEF";
96
97 write(fd, "0x", 2);
98 for (int shift=ptrbitsshift; shift>=0; shift-=4)
99 write(fd, &digits[(((ptrdiff_t)addr)>>shift)&0xF], 1);
100}
101
102
103static void wvcrash_real(int sig, int fd, pid_t pid)
104{
105 static void *trace[64];
106 static char *signame = strsignal(sig);
107
108 wr(fd, argv0);
109 if (desc[0])
110 {
111 wr(fd, " (");
112 wr(fd, desc);
113 wr(fd, ")");
114 }
115 wr(fd, " dying on signal ");
116 wrn(fd, sig);
117 if (signame)
118 {
119 wr(fd, " (");
120 wr(fd, signame);
121 wr(fd, ")\n");
122 }
123
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");
135
136#if WVCRASH_USE_SIGALTSTACK
137 // Determine if this has likely been a stack overflow
138 const void *last_real_stack_frame;
139 for (;;)
140 {
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;
173 }
174 if (last_real_stack_frame != NULL)
175 {
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)
186 {
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");
193 }
194 wr(fd, "\n");
195 }
196#endif
197
198
199 // Write out the contents of the ring buffer
200 {
201 const char *ring;
202 bool first = true;
203 while ((ring = wvcrash_ring_buffer_get()) != NULL)
204 {
205 if (first)
206 {
207 first = false;
208 wr(fd, "\nRing buffer:\n");
209 }
210 wr(fd, ring);
211 }
212 }
213
214 // Write out the assertion message, as logged by __assert*_fail(), if any.
215 {
216 const char *assert_msg = wvcrash_read_assert();
217 if (assert_msg && assert_msg[0])
218 {
219 wr(fd, "\nAssert:\n");
220 wr(fd, assert_msg);
221 }
222 }
223
224 // Write out the note, if any.
225 {
226 const char *will_msg = wvcrash_read_will();
227 if (will_msg && will_msg[0])
228 {
229 wr(fd, "\nLast Will and Testament:\n");
230 wr(fd, will_msg);
231 wr(fd, "\n");
232 }
233 }
234
235 if (WvCrashInfo::in_stream_state != WvCrashInfo::UNUSED
236 && WvCrashInfo::in_stream)
237 {
238 const char *state = NULL;
239 switch (WvCrashInfo::in_stream_state)
240 {
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;
253 }
254
255 if (state)
256 {
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';
260
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");
267 }
268 }
269
270 wr(fd, "\nBacktrace:\n");
271 backtrace_symbols_fd(trace,
272 backtrace(trace, sizeof(trace)/sizeof(trace[0])), fd);
273
274 if (pid > 0)
275 {
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)
283 {
284 if (waitpid(pid, NULL, WNOHANG) == pid)
285 break;
286 nanosleep(&ts, NULL);
287 }
288 }
289
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;
297
298 signal(sig, SIG_DFL);
299 raise(sig);
300}
301
302
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.)
307//
308// We fork a subprogram to do the fancy stuff like sending email.
309//
310void wvcrash(int sig)
311{
312 int fds[2];
313 pid_t pid;
314
315 signal(sig, SIG_DFL);
316 wr(2, "\n\nwvcrash: crashing!\n");
317
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.
323 //
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);
332
333 if (pipe(fds))
334 wvcrash_real(sig, 2, 0); // just use stderr instead
335 else
336 {
337 pid = fork();
338 if (pid < 0)
339 wvcrash_real(sig, 2, 0); // just use stderr instead
340 else if (pid == 0) // child
341 {
342 close(fds[1]);
343 dup2(fds[0], 0); // make stdin read from pipe
344 fcntl(0, F_SETFD, 0);
345
346 execlp("wvcrash", "wvcrash", NULL);
347
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);
352
353 wr(2, "wvcrash: can't exec dd to write to wvcrash.txt!\n");
354 _exit(127);
355 }
356 else if (pid > 0) // parent
357 {
358 close(fds[0]);
359 wvcrash_real(sig, fds[1], pid);
360 }
361 }
362
363 // child (usually)
364 _exit(126);
365}
366
367
368static void wvcrash_setup_alt_stack()
369{
370#if WVCRASH_USE_SIGALTSTACK
371 stack_t ss;
372
373 ss.ss_sp = altstack;
374 ss.ss_flags = 0;
375 ss.ss_size = altstack_size;
376
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
381}
382
383void wvcrash_add_signal(int sig)
384{
385#if WVCRASH_USE_SIGALTSTACK
386 struct sigaction act;
387
388 memset(&act,0,sizeof(act));
389 act.sa_handler = wvcrash;
390 sigfillset(&act.sa_mask);
391 act.sa_flags = SA_ONSTACK | SA_RESTART;
392
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
397 signal(sig, wvcrash);
398#endif //WVCRASH_USE_SIGALTSTACK
399}
400
401// Secret symbol for initialising the will and assert buffers
402extern void __wvcrash_init_buffers(const char *program_name);
403
404void wvcrash_setup(const char *_argv0, const char *_desc)
405{
406 if (_argv0)
407 argv0 = basename(_argv0);
408 __wvcrash_init_buffers(argv0);
409 if (_desc)
410 {
411 strncpy(desc, _desc, buffer_size);
412 desc[buffer_size - 1] = '\0';
413 }
414 else
415 desc[0] = '\0';
416
417 wvcrash_setup_alt_stack();
418
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);
424}
425
426#elif defined(_WIN32)
427
428#include <windows.h>
429#include <stdio.h>
430#include <imagehlp.h>
431
432inline char* last_part(char* in)
433{
434 int len = strlen(in);
435 char* tmp = in+len;
436 while (tmp > in)
437 {
438 if (*tmp == '/' || *tmp == '\\')
439 return tmp+1;
440 tmp--;
441 }
442 return in;
443}
444
445
454int backtrace(CONTEXT &ctx)
455{
456 HANDLE hProcess = (HANDLE)GetCurrentProcess();
457 HANDLE hThread = (HANDLE)GetCurrentThread();
458
459 SymInitialize(hProcess, NULL, TRUE);
460
461 STACKFRAME sf;
462 memset(&sf, 0, sizeof(STACKFRAME));
463
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;
470
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))
483 {
484 if (sf.AddrPC.Offset == 0)
485 break;
486
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);
492
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);
501
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);
509
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");
518 }
519
520 SymCleanup(hProcess);
521
522 return 1;
523}
524
525
526static void exception_desc(FILE *file, unsigned exception,
527 unsigned data1, unsigned data2)
528{
529
530 switch (exception)
531 {
532 case 0xC0000005:
533 {
534 switch (data1)
535 {
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;
551 }
552 }
553 break;
554
555 case 0xC0000094:
556 fprintf(file, "integer division by zero");
557 break;
558
559 default:
560 fprintf(file, "unknown exception (data1=0x%08X, data2=0x%08X)");
561 break;
562 }
563}
564
565static LONG WINAPI ExceptionFilter( struct _EXCEPTION_POINTERS * pExceptionPointers )
566{
567 struct ExceptionInfo
568 {
569 unsigned exception;
570 unsigned unknown[2];
571 void *ip;
572 unsigned more_unknown;
573 unsigned data1;
574 unsigned data2;
575 };
576 ExceptionInfo *info = *(ExceptionInfo **)pExceptionPointers;
577
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)
582 {
583 fprintf(stderr, "Preparing to debug!\n");
584 return EXCEPTION_CONTINUE_SEARCH;
585 }
586
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");
593
594
595 return EXCEPTION_EXECUTE_HANDLER;
596}
597
598static bool have_global_exception_handler = false;
599void setup_console_crash()
600{
601 if (!have_global_exception_handler)
602 {
603 SetUnhandledExceptionFilter(ExceptionFilter);
604 have_global_exception_handler = true;
605 }
606}
607
608void wvcrash(int sig) {}
609void wvcrash_setup(const char *_argv0, const char *_desc) {}
610
611#else // Not Linux
612
613void wvcrash(int sig) {}
614void wvcrash_add_signal(int sig) {}
615void wvcrash_setup(const char *_argv0, const char *_desc) {}
616
617#endif // Not Linux