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 #ifdef __linux
30 
31 # include <execinfo.h>
32 #include <unistd.h>
33 
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
39 
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
44 
45 // Reserve enough buffer for a screenful of programme.
46 static const int buffer_size = 2048 + wvcrash_ring_buffer_size;
47 
48 static char desc[buffer_size];
49 
50 // write a string 'str' to fd
51 static 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.
58 static 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.
92 static 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 
103 static 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 //
310 void 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 
368 static 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 
383 void 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
402 extern void __wvcrash_init_buffers(const char *program_name);
403 
404 void 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 
432 inline 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 
454 int 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 
526 static 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 
565 static 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 
598 static bool have_global_exception_handler = false;
599 void setup_console_crash()
600 {
601  if (!have_global_exception_handler)
602  {
603  SetUnhandledExceptionFilter(ExceptionFilter);
604  have_global_exception_handler = true;
605  }
606 }
607 
608 void wvcrash(int sig) {}
609 void wvcrash_setup(const char *_argv0, const char *_desc) {}
610 
611 #else // Not Linux
612 
613 void wvcrash(int sig) {}
614 void wvcrash_add_signal(int sig) {}
615 void wvcrash_setup(const char *_argv0, const char *_desc) {}
616 
617 #endif // Not Linux