2009-12-01 Jeffrey Stedfast <fejj@novell.com>
[moon.git] / src / debug.cpp
blob26b8b975ce9f8cf22ab96bede4e77df9786f0e31
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3 * debug.cpp:
5 * Copyright 2007 Novell, Inc. (http://www.novell.com)
7 * See the LICENSE file included with the distribution for details.
8 *
9 */
11 #include <config.h>
13 #if DEBUG
14 #define INCLUDED_MONO_HEADERS 1
16 #include <mono/jit/jit.h>
17 #include <mono/metadata/appdomain.h>
18 #include <mono/metadata/assembly.h>
19 #include <mono/metadata/debug-helpers.h>
20 G_BEGIN_DECLS
21 /* because this header sucks */
22 #include <mono/metadata/mono-debug.h>
23 G_END_DECLS
24 #include <mono/metadata/mono-config.h>
25 #include <mono/metadata/threads.h>
27 #include "runtime.h"
28 #include "debug.h"
30 #include <signal.h>
31 #include <sys/syscall.h>
32 #include <sys/wait.h>
33 #include <execinfo.h>
34 #include <unistd.h>
35 #include <ctype.h>
37 #ifdef HAVE_UNWIND
38 #define UNW_LOCAL_ONLY
39 #include <libunwind.h>
40 #include <demangle.h>
41 #endif
43 static bool vm_stack_trace_enabled = false;
45 void
46 enable_vm_stack_trace (void)
48 vm_stack_trace_enabled = true;
51 #ifdef HAVE_UNWIND
52 static char*
53 get_method_name_from_ip (void *ip)
55 if (!vm_stack_trace_enabled)
56 return NULL;
58 MonoJitInfo *ji;
59 MonoMethod *mi;
60 char *method;
61 char *res;
62 MonoDomain *domain = mono_domain_get ();
64 ji = mono_jit_info_table_find (domain, (char*) ip);
65 if (!ji) {
66 return NULL;
68 mi = mono_jit_info_get_method (ji);
69 method = mono_method_full_name (mi, TRUE);
71 res = g_strdup_printf ("%s", method);
73 g_free (method);
75 return res;
77 #endif
79 static char*
80 get_method_from_ip (void *ip)
82 if (!vm_stack_trace_enabled)
83 return NULL;
85 MonoJitInfo *ji;
86 MonoMethod *mi;
87 char *method;
88 char *res;
89 gpointer jit_start;
90 int jit_size;
91 MonoDomain *domain = mono_domain_get ();
92 MonoDebugSourceLocation *location;
94 ji = mono_jit_info_table_find (domain, (char*) ip);
95 if (!ji) {
96 return NULL;
98 mi = mono_jit_info_get_method (ji);
99 jit_start = mono_jit_info_get_code_start (ji);
100 jit_size = mono_jit_info_get_code_size (ji);
101 method = mono_method_full_name (mi, TRUE);
103 location = mono_debug_lookup_source_location (mi, (guint32)((guint8*)ip - (guint8*)jit_start), domain);
105 if (location) {
106 res = g_strdup_printf (" %s in %s:%i,%i", method, location->source_file, location->row, location->column);
107 } else {
108 res = g_strdup_printf (" %s + 0x%x", method, (int)((char*)ip - (char*)jit_start));
110 mono_debug_free_source_location (location);
112 g_free (method);
114 return res;
117 int max_stack_trace_frames = -1;
119 static int
120 get_max_frames ()
122 if (max_stack_trace_frames == -1) {
123 const char *c = getenv ("MOONLIGHT_MAX_FRAMES");
124 if (c == NULL || c [0] == 0) {
125 max_stack_trace_frames = 10; // the default
126 } else {
127 max_stack_trace_frames = atoi (c);
128 if (max_stack_trace_frames <= 0)
129 max_stack_trace_frames = 10;
132 return max_stack_trace_frames;
135 char* get_stack_trace (void)
137 return get_stack_trace_prefix_n ("\t", get_max_frames ());
140 void print_stack_trace (void)
142 print_stack_trace_prefix_n ("\t", get_max_frames ());
145 static char tohex[16] = {
146 '0', '1', '2', '3', '4', '5', '6', '7',
147 '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
150 void
151 hexdump_addr (void *addr, size_t n)
153 const unsigned char *mem = (const unsigned char *) addr;
154 char outbuf[80], *outptr;
155 unsigned char c;
156 size_t i, j;
158 for (i = 0; i < n; i += 16) {
159 outptr = outbuf;
161 // write out the offset
162 *outptr++ = tohex[(i >> 28) & 0xf];
163 *outptr++ = tohex[(i >> 24) & 0xf];
164 *outptr++ = tohex[(i >> 20) & 0xf];
165 *outptr++ = tohex[(i >> 16) & 0xf];
166 *outptr++ = tohex[(i >> 12) & 0xf];
167 *outptr++ = tohex[(i >> 8) & 0xf];
168 *outptr++ = tohex[(i >> 4) & 0xf];
169 *outptr++ = tohex[i & 0xf];
171 // write out up to 16 hex-encoded octets
172 for (j = i; j < n && j < i + 16; j++) {
173 if ((j & 0x1) == 0)
174 *outptr++ = ' ';
176 c = mem[j];
178 *outptr++ = tohex[(c >> 4) & 0xf];
179 *outptr++ = tohex[c & 0xf];
182 // pad out to the expected column
183 for ( ; j < i + 16; j++) {
184 if ((j & 0x1) == 0)
185 *outptr++ = ' ';
187 *outptr++ = ' ';
188 *outptr++ = ' ';
191 *outptr++ = ' ';
192 *outptr++ = ' ';
193 *outptr++ = ' ';
194 *outptr++ = ' ';
196 // write out up to 16 raw octets
197 for (j = i; j < n && j < i + 16; j++) {
198 c = mem[j];
199 if (isprint ((int) c)) {
200 *outptr++ = (char) c;
201 } else {
202 *outptr++ = '.';
206 *outptr++ = '\n';
207 *outptr = '\0';
209 fputs (outbuf, stdout);
213 typedef struct Addr2LineData Addr2LineData;
215 struct Addr2LineData {
216 Addr2LineData *next;
217 FILE *pipein;
218 FILE *pipeout;
219 char *binary;
220 int child_pid;
221 gpointer base;
224 static __thread Addr2LineData *addr2line_pipes = NULL;
226 static char*
227 library_of_ip (gpointer ip, gpointer* base_address)
229 /* non-linux platforms will need different code here */
231 FILE* maps = fopen ("/proc/self/maps", "r");
232 char * buffer = NULL;
233 size_t buffer_length = 0;
234 char* result = NULL;
235 char* current_library = NULL;
236 gpointer current_base_address = NULL;
237 gpointer start, end;
238 char entire_line [2000];
240 while (true) {
241 gint64 buffer_read = getline (&buffer, &buffer_length, maps);
243 if (buffer_read < 0)
244 break;
246 memcpy (entire_line, buffer, buffer_read);
247 entire_line [buffer_read + 1] = 0;
249 if (buffer_read < 20)
250 continue;
252 buffer [buffer_read - 1] = 0; // Strip off the newline.
254 const char delimiters[] = " ";
255 char *saveptr = NULL;
256 char *range = strtok_r (buffer, delimiters, &saveptr);
257 char *a = strtok_r (NULL, delimiters, &saveptr);
258 char *b = strtok_r (NULL, delimiters, &saveptr);
259 char *c = strtok_r (NULL, delimiters, &saveptr);
260 char *d = strtok_r (NULL, delimiters, &saveptr);
261 char *lib = strtok_r (NULL, delimiters, &saveptr);
263 if (lib == NULL) {
264 current_library = NULL;
265 continue;
268 if (lib [0] != '/' && lib [0] != '[') {
269 printf ("Something's wrong, lib: %s\n", lib);
270 printf ("range: %s, a: %s, b: %s, c: %s, d: %s, lib: %s, line: %s",
271 range, a, b, c, d, lib, entire_line);
274 saveptr = NULL;
275 char* start_range = strtok_r (range, "-", &saveptr);
276 char* end_range = strtok_r (NULL, "-", &saveptr);
278 char* tail;
279 start = start_range ? (gpointer) strtoull (start_range, &tail, 16) : NULL;
280 end = end_range ? (gpointer) strtoull (end_range, &tail, 16) : NULL;
282 if (current_library == NULL || strcmp (lib, current_library) != 0) {
283 current_library = lib;
285 current_base_address = start;
287 if (start <= ip && end >= ip) {
288 result = g_strdup (lib);
289 *base_address = current_base_address;
290 // printf ("IP %p is in library %s\n", ip, result);
291 break;
295 free (buffer);
296 fclose (maps);
298 return result;
301 static char* addr2line_offset (gpointer ip, bool use_offset);
303 static char*
304 addr2line (gpointer ip)
306 char* result = addr2line_offset (ip, true);
307 if (result == NULL)
308 result = addr2line_offset (ip, false);
309 return result;
313 static char*
314 addr2line_offset (gpointer ip, bool use_offset)
316 char *res;
317 Addr2LineData *addr2line;
318 gpointer base_address;
320 char* binary = library_of_ip (ip, &base_address);
321 //printf ("library_of_ip (%p, %p): %s\n", ip, base_address, binary);
323 if (binary == NULL)
324 return NULL;
326 if (binary [0] == '[') {
327 g_free (binary);
328 return NULL;
331 for (addr2line = addr2line_pipes; addr2line; addr2line = addr2line->next) {
332 if (strcmp (binary, addr2line->binary) == 0)
333 break;
336 if (!addr2line) {
337 const char *addr_argv[] = {"addr2line", "-f", "-e", binary, "-C", NULL};
338 int child_pid;
339 int ch_in, ch_out;
341 if (!g_spawn_async_with_pipes (NULL, (char**)addr_argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL,
342 &child_pid, &ch_in, &ch_out, NULL, NULL)) {
343 g_free (binary);
344 return NULL;
347 addr2line = g_new0 (Addr2LineData, 1);
348 addr2line->base = base_address;
349 addr2line->child_pid = child_pid;
350 addr2line->binary = g_strdup (binary);
351 addr2line->pipein = fdopen (ch_in, "w");
352 addr2line->pipeout = fdopen (ch_out, "r");
353 addr2line->next = addr2line_pipes;
354 addr2line_pipes = addr2line;
357 g_free (binary);
359 gpointer offset;
360 if (use_offset)
361 offset = (gpointer) (((size_t) ip) - ((size_t) addr2line->base));
362 else
363 offset = ip;
365 // printf ("Checking ip: %p, offset: %p, base: %p\n", ip, offset, addr2line->base);
366 fprintf (addr2line->pipein, "%p\n", offset);
367 fflush (addr2line->pipein);
369 /* we first get the func name and then file:lineno in a second line */
370 char buf [1024];
371 char* first;
372 char* second;
373 char* result;
374 int result_length;
376 result = fgets (buf, sizeof (buf), addr2line->pipeout);
378 if (result == NULL)
379 return NULL;
381 if (result [0] == '?' || result [0] == 0)
382 return NULL;
384 result_length = strlen (result);
385 result [result_length - 1] = 0;
386 first = result;
388 result = fgets (buf + result_length, sizeof (buf) - result_length, addr2line->pipeout);
390 if (result == NULL)
391 return NULL;
393 result_length = strlen (result);
394 result [result_length - 1] = 0;
395 second = result;
397 res = g_strdup_printf ("%s [%p] %s %s", addr2line->binary, ip, first, second);
399 // printf ("Final result: %s\n", res);
401 return res;
404 char*
405 get_managed_frame (gpointer ip)
407 return get_method_from_ip (ip);
410 char*
411 get_stack_trace_prefix_n (const char* prefix, int maxframes)
413 int address_count;
414 gpointer ip;
415 int total_length = 0;
416 int prefix_length = strlen (prefix);
417 void *ips [maxframes];
418 char *frames [maxframes];
419 char **names;
421 address_count = backtrace (ips, maxframes);
423 for (int i = 2; i < address_count; i++) {
424 ip = ips [i];
426 char* frame = addr2line (ip);
428 if (frame == NULL && mono_domain_get ())
429 frame = get_managed_frame (ip);
431 if (frame == NULL || strlen (frame) == 0 || frame [0] == '?') {
432 g_free (frame);
433 names = backtrace_symbols (&ip, 1);
434 frame = g_strdup (names [0]);
435 free (names);
437 frames [i] = frame;
438 total_length += prefix_length + strlen (frame) + 1;
441 char* result = (char*) g_malloc0 (total_length + 1);
442 int position = 0;
443 for (int i = 2; i < address_count; i++) {
444 char* frame = frames [i];
445 size_t frame_length = strlen (frame);
447 memcpy (result + position, prefix, prefix_length);
448 position += prefix_length;
449 memcpy (result + position, frame, frame_length);
450 position += frame_length;
451 memcpy (result + position, "\n", 1);
452 position ++;
454 g_free (frame);
457 return result;
460 void
461 print_stack_trace_prefix (const char* prefix)
463 print_stack_trace_prefix_n (prefix, get_max_frames ());
466 void
467 print_stack_trace_prefix_n (const char* prefix, int maxframes)
469 char* st = get_stack_trace_prefix_n (prefix, maxframes);
470 printf (st);
471 g_free (st);
474 #ifdef HAVE_UNWIND
476 enum FrameType {
477 UKNOWN = 0,
478 INSTANCE = 1,
479 STATIC = 2,
480 MONO = 3,
481 C = 4
484 struct Frame : List::Node {
485 long ptr;
486 char *name;
487 FrameType type;
488 virtual ~Frame () {
489 g_free (name);
492 virtual char *ToString ()
494 return g_strdup_printf ("%d|%lx|%s", type, ptr, name);
498 struct Frames : List::Node {
499 List *list;
500 char *act;
501 char *typname;
502 int refcount;
503 virtual ~Frames () {
504 g_free (act);
505 g_free (typname);
506 delete (list);
510 List *allframes = NULL;
511 #endif
513 void
514 print_reftrace (const char * act, const char * typname, int refcount, bool keep)
516 #ifdef HAVE_UNWIND
517 unw_cursor_t cursor; unw_context_t uc;
518 unw_word_t ip, sp, bp;
520 unw_getcontext(&uc);
521 unw_init_local(&cursor, &uc);
523 char framename [1024];
524 Frame *frame;
525 List *frames = new List ();
527 int count = 0;
528 while (unw_step(&cursor) > 0 && count < get_max_frames ()) {
530 frame = new Frame ();
532 unw_get_reg(&cursor, UNW_REG_IP, &ip);
533 unw_get_reg(&cursor, UNW_REG_SP, &sp);
534 #if (__i386__)
535 unw_get_reg(&cursor, UNW_X86_EBP, &bp);
536 #elif (__amd64__)
537 unw_get_reg(&cursor, UNW_X86_64_RBP, &bp);
538 #endif
540 unw_get_proc_name (&cursor, framename, sizeof(framename), 0);
542 if (!*framename) {
543 if (mono_domain_get ()) {
544 frame->ptr = (long)ip;
545 char * ret = get_method_name_from_ip ((void*)ip);
546 frame->name = g_strdup (ret);
547 g_free (ret);
548 frame->type = MONO;
549 } else {
550 delete frame;
551 continue;
553 } else {
555 char * demangled = cplus_demangle (framename, 0);
556 if (demangled) {
557 if (strstr (demangled, ":") > 0) {
558 frame->ptr = *(int*)(bp+8);
559 frame->name = g_strdup (demangled);
560 frame->type = INSTANCE;
561 } else {
562 frame->ptr = (long)bp;
563 frame->name = g_strdup (demangled);
564 frame->type = STATIC;
566 } else {
567 frame->ptr = (long)bp;
568 frame->name = g_strdup (framename);
569 frame->type = C;
573 frames->Append (frame);
574 count++;
577 if (keep) {
578 if (allframes == NULL)
579 allframes = new List ();
580 if (allframes->Length() % 50)
581 dump_frames ();
582 Frames *f = new Frames ();
583 f->list = frames;
584 f->act = g_strdup (act);
585 f->typname = g_strdup (typname);
586 f->refcount = refcount;
587 allframes->Append (f);
588 } else {
590 printf("trace:%s|%s|%d;", act, typname,refcount);
591 frame = (Frame*)frames->First ();
592 while (frame != NULL) {
593 char *s = frame->ToString ();
594 printf ("%s;", s);
595 g_free(s);
596 frame = (Frame*)frame->next;
598 printf ("\n");
600 delete frames;
602 #endif
606 void dump_frames (void)
608 #if HAVE_UNWIND
609 Frames *frames = (Frames*)allframes->First ();
610 while (frames != NULL) {
611 printf("trace:%s|%s|%d;", frames->act, frames->typname, frames->refcount);
612 Frame *frame = (Frame*)frames->list->First ();
613 while (frame != NULL) {
614 char *s = frame->ToString ();
615 printf ("%s;", s);
616 g_free(s);
617 frame = (Frame*)frame->next;
619 printf ("\n");
620 frames = (Frames*)frames->next;
622 allframes->Clear (true);
623 //delete (frames);
624 //frames = NULL;
625 #endif
628 #if SANITY
631 * moonlight_handle_native_sigsegv:
632 * (this is a slightly modified version of mono_handle_native_sigsegv from mono/mono/mini/mini-exceptions.c)
637 static bool handlers_installed = false;
638 static bool handling_sigsegv = false;
640 static void
641 print_gdb_trace ()
643 /* Try to get more meaningful information using gdb */
644 #if !defined(PLATFORM_WIN32)
645 /* From g_spawn_command_line_sync () in eglib */
646 int res;
647 int stdout_pipe [2] = { -1, -1 };
648 pid_t pid;
649 const char *argv [16];
650 char buf1 [128];
651 int status;
652 char buffer [1024];
654 res = pipe (stdout_pipe);
655 g_assert (res != -1);
658 * glibc fork acquires some locks, so if the crash happened inside malloc/free,
659 * it will deadlock. Call the syscall directly instead.
661 pid = syscall (SYS_fork);
662 if (pid == 0) {
663 close (stdout_pipe [0]);
664 dup2 (stdout_pipe [1], STDOUT_FILENO);
666 for (int i = getdtablesize () - 1; i >= 3; i--)
667 close (i);
669 argv [0] = g_find_program_in_path ("gdb");
670 if (argv [0] == NULL) {
671 close (STDOUT_FILENO);
672 exit (1);
675 argv [1] = "-ex";
676 sprintf (buf1, "attach %ld", (long)getpid ());
677 argv [2] = buf1;
678 argv [3] = "--ex";
679 argv [4] = "info threads";
680 argv [5] = "--ex";
681 argv [6] = "thread apply all bt";
682 argv [7] = "--batch";
683 argv [8] = 0;
685 execv (argv [0], (char**)argv);
686 exit (1);
689 close (stdout_pipe [1]);
691 fprintf (stderr, "\nDebug info from gdb:\n\n");
693 while (1) {
694 int nread = read (stdout_pipe [0], buffer, 1024);
696 if (nread <= 0)
697 break;
698 write (STDERR_FILENO, buffer, nread);
701 waitpid (pid, &status, WNOHANG);
703 #endif
706 void
707 static moonlight_handle_native_sigsegv (int signal)
709 const char *signal_str;
711 switch (signal) {
712 case SIGSEGV: signal_str = "SIGSEGV"; break;
713 case SIGFPE: signal_str = "SIGFPE"; break;
714 case SIGABRT: signal_str = "SIGABRT"; break;
715 case SIGQUIT: signal_str = "SIGQUIT"; break;
716 default: signal_str = "UNKNOWN"; break;
719 if (handling_sigsegv) {
721 * In our normal sigsegv handling we do signal-unsafe things to provide better
722 * output to what actually happened. If we get another one, do only signal-safe
723 * things
725 _exit (1);
726 return;
729 /* To prevent infinite loops when the stack walk causes a crash */
730 handling_sigsegv = true;
733 * A SIGSEGV indicates something went very wrong so we can no longer depend
734 * on anything working. So try to print out lots of diagnostics, starting
735 * with ones which have a greater chance of working.
737 fprintf (stderr,
738 "\n"
739 "=============================================================\n"
740 "Got a %s while executing native code. \n"
741 " We'll first ask gdb for a stack trace, then try our own \n"
742 " stack walking method (usually not as good as gdb, but it \n"
743 " can do managed and native stack traces together) \n"
744 "=============================================================\n"
745 "\n", signal_str);
747 print_gdb_trace ();
749 fprintf (stderr, "\nDebug info from libmoon:\n\n");
750 print_stack_trace ();
752 if (signal != SIGQUIT) {
753 abort ();
754 } else {
755 handling_sigsegv = false;
759 void
760 moonlight_install_signal_handlers ()
762 struct sigaction sa;
764 if (handlers_installed)
765 return;
766 handlers_installed = true;
768 printf ("Moonlight: Installing signal handlers for crash reporting.\n");
770 sa.sa_handler = moonlight_handle_native_sigsegv;
771 sigemptyset (&sa.sa_mask);
772 sa.sa_flags = 0;
774 g_assert (sigaction (SIGSEGV, &sa, NULL) != -1);
775 g_assert (sigaction (SIGFPE, &sa, NULL) != -1);
776 g_assert (sigaction (SIGQUIT, &sa, NULL) != -1);
777 g_assert (sigaction (SIGABRT, &sa, NULL) != -1);
779 #endif /* SANITY */
781 #endif /* DEBUG */