2009-10-09 Chris Toshok <toshok@ximian.com>
[moon.git] / src / debug.cpp
blob05e144d0cc8244e4462f7ccae61e594194a89811
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 #include "runtime.h"
14 #include "debug.h"
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 <signal.h>
28 #include <sys/syscall.h>
29 #include <sys/wait.h>
30 #include <execinfo.h>
31 #include <unistd.h>
32 #include <ctype.h>
34 #ifdef HAVE_UNWIND
35 #define UNW_LOCAL_ONLY
36 #include <libunwind.h>
37 #include <demangle.h>
38 #endif
40 #if DEBUG
42 #if SL_2_0
43 // Define to enable stack traces for managed frames.
44 #define MONO_STACK_ENABLED 1
45 #endif
47 static bool vm_stack_trace_enabled = false;
49 void
50 enable_vm_stack_trace (void)
52 vm_stack_trace_enabled = true;
55 static char*
56 get_method_name_from_ip (void *ip)
58 if (!vm_stack_trace_enabled)
59 return NULL;
61 #if MONO_STACK_ENABLED
62 MonoJitInfo *ji;
63 MonoMethod *mi;
64 char *method;
65 char *res;
66 MonoDomain *domain = mono_domain_get ();
68 ji = mono_jit_info_table_find (domain, (char*) ip);
69 if (!ji) {
70 return NULL;
72 mi = mono_jit_info_get_method (ji);
73 method = mono_method_full_name (mi, TRUE);
75 res = g_strdup_printf ("%s", method);
77 g_free (method);
79 return res;
80 #else
81 return NULL;
82 #endif
85 static char*
86 get_method_from_ip (void *ip)
88 if (!vm_stack_trace_enabled)
89 return NULL;
91 #if MONO_STACK_ENABLED
92 MonoJitInfo *ji;
93 MonoMethod *mi;
94 char *method;
95 char *res;
96 gpointer jit_start;
97 int jit_size;
98 MonoDomain *domain = mono_domain_get ();
99 MonoDebugSourceLocation *location;
101 ji = mono_jit_info_table_find (domain, (char*) ip);
102 if (!ji) {
103 return NULL;
105 mi = mono_jit_info_get_method (ji);
106 jit_start = mono_jit_info_get_code_start (ji);
107 jit_size = mono_jit_info_get_code_size (ji);
108 method = mono_method_full_name (mi, TRUE);
110 location = mono_debug_lookup_source_location (mi, (guint32)((guint8*)ip - (guint8*)jit_start), domain);
112 if (location) {
113 res = g_strdup_printf (" %s in %s:%i,%i", method, location->source_file, location->row, location->column);
114 } else {
115 res = g_strdup_printf (" %s + 0x%x", method, (int)((char*)ip - (char*)jit_start));
117 mono_debug_free_source_location (location);
119 g_free (method);
121 return res;
122 #else
123 return NULL;
124 #endif
127 char* get_stack_trace (void)
129 return get_stack_trace_prefix ("\t");
132 void print_stack_trace (void)
134 print_stack_trace_prefix ("\t");
137 static char tohex[16] = {
138 '0', '1', '2', '3', '4', '5', '6', '7',
139 '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
142 void
143 hexdump_addr (void *addr, size_t n)
145 const unsigned char *mem = (const unsigned char *) addr;
146 char outbuf[80], *outptr;
147 unsigned char c;
148 size_t i, j;
150 for (i = 0; i < n; i += 16) {
151 outptr = outbuf;
153 // write out the offset
154 *outptr++ = tohex[(i >> 28) & 0xf];
155 *outptr++ = tohex[(i >> 24) & 0xf];
156 *outptr++ = tohex[(i >> 20) & 0xf];
157 *outptr++ = tohex[(i >> 16) & 0xf];
158 *outptr++ = tohex[(i >> 12) & 0xf];
159 *outptr++ = tohex[(i >> 8) & 0xf];
160 *outptr++ = tohex[(i >> 4) & 0xf];
161 *outptr++ = tohex[i & 0xf];
163 // write out up to 16 hex-encoded octets
164 for (j = i; j < n && j < i + 16; j++) {
165 if ((j & 0x1) == 0)
166 *outptr++ = ' ';
168 c = mem[j];
170 *outptr++ = tohex[(c >> 4) & 0xf];
171 *outptr++ = tohex[c & 0xf];
174 // pad out to the expected column
175 for ( ; j < i + 16; j++) {
176 if ((j & 0x1) == 0)
177 *outptr++ = ' ';
179 *outptr++ = ' ';
180 *outptr++ = ' ';
183 *outptr++ = ' ';
184 *outptr++ = ' ';
185 *outptr++ = ' ';
186 *outptr++ = ' ';
188 // write out up to 16 raw octets
189 for (j = i; j < n && j < i + 16; j++) {
190 c = mem[j];
191 if (isprint ((int) c)) {
192 *outptr++ = (char) c;
193 } else {
194 *outptr++ = '.';
198 *outptr++ = '\n';
199 *outptr = '\0';
201 fputs (outbuf, stdout);
205 typedef struct Addr2LineData Addr2LineData;
207 struct Addr2LineData {
208 Addr2LineData *next;
209 FILE *pipein;
210 FILE *pipeout;
211 char *binary;
212 int child_pid;
213 gpointer base;
216 static __thread Addr2LineData *addr2line_pipes = NULL;
218 static char*
219 library_of_ip (gpointer ip, gpointer* base_address)
221 /* non-linux platforms will need different code here */
223 FILE* maps = fopen ("/proc/self/maps", "r");
224 char * buffer = NULL;
225 size_t buffer_length = 0;
226 char* result = NULL;
227 char* current_library = NULL;
228 gpointer current_base_address = NULL;
229 gpointer start, end;
230 char entire_line [2000];
232 while (true) {
233 gint64 buffer_read = getline (&buffer, &buffer_length, maps);
235 if (buffer_read < 0)
236 break;
238 memcpy (entire_line, buffer, buffer_read);
239 entire_line [buffer_read + 1] = 0;
241 if (buffer_read < 20)
242 continue;
244 buffer [buffer_read - 1] = 0; // Strip off the newline.
246 const char delimiters[] = " ";
247 char *saveptr = NULL;
248 char *range = strtok_r (buffer, delimiters, &saveptr);
249 char *a = strtok_r (NULL, delimiters, &saveptr);
250 char *b = strtok_r (NULL, delimiters, &saveptr);
251 char *c = strtok_r (NULL, delimiters, &saveptr);
252 char *d = strtok_r (NULL, delimiters, &saveptr);
253 char *lib = strtok_r (NULL, delimiters, &saveptr);
255 if (lib == NULL) {
256 current_library = NULL;
257 continue;
260 if (lib [0] != '/' && lib [0] != '[') {
261 printf ("Something's wrong, lib: %s\n", lib);
262 printf ("range: %s, a: %s, b: %s, c: %s, d: %s, lib: %s, line: %s",
263 range, a, b, c, d, lib, entire_line);
266 saveptr = NULL;
267 char* start_range = strtok_r (range, "-", &saveptr);
268 char* end_range = strtok_r (NULL, "-", &saveptr);
270 char* tail;
271 start = start_range ? (gpointer) strtoull (start_range, &tail, 16) : NULL;
272 end = end_range ? (gpointer) strtoull (end_range, &tail, 16) : NULL;
274 if (current_library == NULL || strcmp (lib, current_library) != 0) {
275 current_library = lib;
277 current_base_address = start;
279 if (start <= ip && end >= ip) {
280 result = g_strdup (lib);
281 *base_address = current_base_address;
282 // printf ("IP %p is in library %s\n", ip, result);
283 break;
287 free (buffer);
288 fclose (maps);
290 return result;
293 static char* addr2line_offset (gpointer ip, bool use_offset);
295 static char*
296 addr2line (gpointer ip)
298 char* result = addr2line_offset (ip, true);
299 if (result == NULL)
300 result = addr2line_offset (ip, false);
301 return result;
305 static char*
306 addr2line_offset (gpointer ip, bool use_offset)
308 char *res;
309 Addr2LineData *addr2line;
310 gpointer base_address;
312 char* binary = library_of_ip (ip, &base_address);
313 //printf ("library_of_ip (%p, %p): %s\n", ip, base_address, binary);
315 if (binary == NULL)
316 return NULL;
318 if (binary [0] == '[') {
319 g_free (binary);
320 return NULL;
323 for (addr2line = addr2line_pipes; addr2line; addr2line = addr2line->next) {
324 if (strcmp (binary, addr2line->binary) == 0)
325 break;
328 if (!addr2line) {
329 const char *addr_argv[] = {"addr2line", "-f", "-e", binary, "-C", NULL};
330 int child_pid;
331 int ch_in, ch_out;
333 if (!g_spawn_async_with_pipes (NULL, (char**)addr_argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL,
334 &child_pid, &ch_in, &ch_out, NULL, NULL)) {
335 g_free (binary);
336 return NULL;
339 addr2line = g_new0 (Addr2LineData, 1);
340 addr2line->base = base_address;
341 addr2line->child_pid = child_pid;
342 addr2line->binary = g_strdup (binary);
343 addr2line->pipein = fdopen (ch_in, "w");
344 addr2line->pipeout = fdopen (ch_out, "r");
345 addr2line->next = addr2line_pipes;
346 addr2line_pipes = addr2line;
349 g_free (binary);
351 gpointer offset;
352 if (use_offset)
353 offset = (gpointer) (((size_t) ip) - ((size_t) addr2line->base));
354 else
355 offset = ip;
357 // printf ("Checking ip: %p, offset: %p, base: %p\n", ip, offset, addr2line->base);
358 fprintf (addr2line->pipein, "%p\n", offset);
359 fflush (addr2line->pipein);
361 /* we first get the func name and then file:lineno in a second line */
362 char buf [1024];
363 char* first;
364 char* second;
365 char* result;
366 int result_length;
368 result = fgets (buf, sizeof (buf), addr2line->pipeout);
370 if (result == NULL)
371 return NULL;
373 if (result [0] == '?' || result [0] == 0)
374 return NULL;
376 result_length = strlen (result);
377 result [result_length - 1] = 0;
378 first = result;
380 result = fgets (buf + result_length, sizeof (buf) - result_length, addr2line->pipeout);
382 if (result == NULL)
383 return NULL;
385 result_length = strlen (result);
386 result [result_length - 1] = 0;
387 second = result;
389 res = g_strdup_printf ("%s [%p] %s %s", addr2line->binary, ip, first, second);
391 // printf ("Final result: %s\n", res);
393 return res;
396 char*
397 get_managed_frame (gpointer ip)
399 return get_method_from_ip (ip);
402 char*
403 get_stack_trace_prefix (const char* prefix, int maxframes)
405 int address_count;
406 gpointer ip;
407 int total_length = 0;
408 int prefix_length = strlen (prefix);
409 void *ips [maxframes];
410 char *frames [maxframes];
411 char **names;
413 address_count = backtrace (ips, maxframes);
415 for (int i = 2; i < address_count; i++) {
416 ip = ips [i];
418 char* frame = addr2line (ip);
420 if (frame == NULL && mono_domain_get ())
421 frame = get_managed_frame (ip);
423 if (frame == NULL || strlen (frame) == 0 || frame [0] == '?') {
424 g_free (frame);
425 names = backtrace_symbols (&ip, 1);
426 frame = g_strdup (names [0]);
427 free (names);
429 frames [i] = frame;
430 total_length += prefix_length + strlen (frame) + 1;
433 char* result = (char*) g_malloc0 (total_length + 1);
434 int position = 0;
435 for (int i = 2; i < address_count; i++) {
436 char* frame = frames [i];
437 size_t frame_length = strlen (frame);
439 memcpy (result + position, prefix, prefix_length);
440 position += prefix_length;
441 memcpy (result + position, frame, frame_length);
442 position += frame_length;
443 memcpy (result + position, "\n", 1);
444 position ++;
446 g_free (frame);
449 return result;
452 void
453 print_stack_trace_prefix (const char* prefix, int maxframes)
455 char* st = get_stack_trace_prefix (prefix, maxframes);
456 printf (st);
457 g_free (st);
460 #ifdef HAVE_UNWIND
462 enum FrameType {
463 UKNOWN = 0,
464 INSTANCE = 1,
465 STATIC = 2,
466 MONO = 3,
467 C = 4
470 struct Frame : List::Node {
471 long ptr;
472 char *name;
473 FrameType type;
474 virtual ~Frame () {
475 g_free (name);
478 virtual char *ToString ()
480 return g_strdup_printf ("%d|%lx|%s", type, ptr, name);
484 struct Frames : List::Node {
485 List *list;
486 char *act;
487 char *typname;
488 int refcount;
489 virtual ~Frames () {
490 g_free (act);
491 g_free (typname);
492 delete (list);
496 List *allframes = NULL;
497 #endif
499 void
500 print_reftrace (const char * act, const char * typname, int refcount, bool keep)
502 #ifdef HAVE_UNWIND
503 unw_cursor_t cursor; unw_context_t uc;
504 unw_word_t ip, sp, bp;
506 unw_getcontext(&uc);
507 unw_init_local(&cursor, &uc);
509 char framename [1024];
510 Frame *frame;
511 List *frames = new List ();
513 int count = 0;
514 while (unw_step(&cursor) > 0 && count < MAX_STACK_FRAMES) {
516 frame = new Frame ();
518 unw_get_reg(&cursor, UNW_REG_IP, &ip);
519 unw_get_reg(&cursor, UNW_REG_SP, &sp);
520 #if (__i386__)
521 unw_get_reg(&cursor, UNW_X86_EBP, &bp);
522 #elif (__amd64__)
523 unw_get_reg(&cursor, UNW_X86_64_RBP, &bp);
524 #endif
526 unw_get_proc_name (&cursor, framename, sizeof(framename), 0);
528 if (!*framename) {
529 if (mono_domain_get ()) {
530 frame->ptr = (long)ip;
531 char * ret = get_method_name_from_ip ((void*)ip);
532 frame->name = g_strdup (ret);
533 g_free (ret);
534 frame->type = MONO;
535 } else {
536 delete frame;
537 continue;
539 } else {
541 char * demangled = cplus_demangle (framename, 0);
542 if (demangled) {
543 if (strstr (demangled, ":") > 0) {
544 frame->ptr = *(int*)(bp+8);
545 frame->name = g_strdup (demangled);
546 frame->type = INSTANCE;
547 } else {
548 frame->ptr = (long)bp;
549 frame->name = g_strdup (demangled);
550 frame->type = STATIC;
552 } else {
553 frame->ptr = (long)bp;
554 frame->name = g_strdup (framename);
555 frame->type = C;
559 frames->Append (frame);
560 count++;
563 if (keep) {
564 if (allframes == NULL)
565 allframes = new List ();
566 if (allframes->Length() % 50)
567 dump_frames ();
568 Frames *f = new Frames ();
569 f->list = frames;
570 f->act = g_strdup (act);
571 f->typname = g_strdup (typname);
572 f->refcount = refcount;
573 allframes->Append (f);
574 } else {
576 printf("trace:%s|%s|%d;", act, typname,refcount);
577 frame = (Frame*)frames->First ();
578 while (frame != NULL) {
579 char *s = frame->ToString ();
580 printf ("%s;", s);
581 g_free(s);
582 frame = (Frame*)frame->next;
584 printf ("\n");
586 delete frames;
588 #endif
592 void dump_frames (void)
594 #if HAVE_UNWIND
595 Frames *frames = (Frames*)allframes->First ();
596 while (frames != NULL) {
597 printf("trace:%s|%s|%d;", frames->act, frames->typname, frames->refcount);
598 Frame *frame = (Frame*)frames->list->First ();
599 while (frame != NULL) {
600 char *s = frame->ToString ();
601 printf ("%s;", s);
602 g_free(s);
603 frame = (Frame*)frame->next;
605 printf ("\n");
606 frames = (Frames*)frames->next;
608 allframes->Clear (true);
609 //delete (frames);
610 //frames = NULL;
611 #endif
614 #if SANITY
617 * moonlight_handle_native_sigsegv:
618 * (this is a slightly modified version of mono_handle_native_sigsegv from mono/mono/mini/mini-exceptions.c)
623 static bool handlers_installed = false;
624 static bool handling_sigsegv = false;
626 static void
627 print_gdb_trace ()
629 /* Try to get more meaningful information using gdb */
630 #if !defined(PLATFORM_WIN32)
631 /* From g_spawn_command_line_sync () in eglib */
632 int res;
633 int stdout_pipe [2] = { -1, -1 };
634 pid_t pid;
635 const char *argv [16];
636 char buf1 [128];
637 int status;
638 char buffer [1024];
640 res = pipe (stdout_pipe);
641 g_assert (res != -1);
644 * glibc fork acquires some locks, so if the crash happened inside malloc/free,
645 * it will deadlock. Call the syscall directly instead.
647 pid = syscall (SYS_fork);
648 if (pid == 0) {
649 close (stdout_pipe [0]);
650 dup2 (stdout_pipe [1], STDOUT_FILENO);
652 for (int i = getdtablesize () - 1; i >= 3; i--)
653 close (i);
655 argv [0] = g_find_program_in_path ("gdb");
656 if (argv [0] == NULL) {
657 close (STDOUT_FILENO);
658 exit (1);
661 argv [1] = "-ex";
662 sprintf (buf1, "attach %ld", (long)getpid ());
663 argv [2] = buf1;
664 argv [3] = "--ex";
665 argv [4] = "info threads";
666 argv [5] = "--ex";
667 argv [6] = "thread apply all bt";
668 argv [7] = "--batch";
669 argv [8] = 0;
671 execv (argv [0], (char**)argv);
672 exit (1);
675 close (stdout_pipe [1]);
677 fprintf (stderr, "\nDebug info from gdb:\n\n");
679 while (1) {
680 int nread = read (stdout_pipe [0], buffer, 1024);
682 if (nread <= 0)
683 break;
684 write (STDERR_FILENO, buffer, nread);
687 waitpid (pid, &status, WNOHANG);
689 #endif
692 void
693 static moonlight_handle_native_sigsegv (int signal)
695 const char *signal_str;
697 switch (signal) {
698 case SIGSEGV: signal_str = "SIGSEGV"; break;
699 case SIGFPE: signal_str = "SIGFPE"; break;
700 case SIGABRT: signal_str = "SIGABRT"; break;
701 case SIGQUIT: signal_str = "SIGQUIT"; break;
702 default: signal_str = "UNKNOWN"; break;
705 if (handling_sigsegv) {
707 * In our normal sigsegv handling we do signal-unsafe things to provide better
708 * output to what actually happened. If we get another one, do only signal-safe
709 * things
711 _exit (1);
712 return;
715 /* To prevent infinite loops when the stack walk causes a crash */
716 handling_sigsegv = true;
719 * A SIGSEGV indicates something went very wrong so we can no longer depend
720 * on anything working. So try to print out lots of diagnostics, starting
721 * with ones which have a greater chance of working.
723 fprintf (stderr,
724 "\n"
725 "=============================================================\n"
726 "Got a %s while executing native code. \n"
727 " We'll first ask gdb for a stack trace, then try our own \n"
728 " stack walking method (usually not as good as gdb, but it \n"
729 " can do managed and native stack traces together) \n"
730 "=============================================================\n"
731 "\n", signal_str);
733 print_gdb_trace ();
735 fprintf (stderr, "\nDebug info from libmoon:\n\n");
736 print_stack_trace ();
738 if (signal != SIGQUIT) {
739 abort ();
740 } else {
741 handling_sigsegv = false;
745 void
746 moonlight_install_signal_handlers ()
748 struct sigaction sa;
750 if (handlers_installed)
751 return;
752 handlers_installed = true;
754 printf ("Moonlight: Installing signal handlers for crash reporting.\n");
756 sa.sa_handler = moonlight_handle_native_sigsegv;
757 sigemptyset (&sa.sa_mask);
758 sa.sa_flags = 0;
760 g_assert (sigaction (SIGSEGV, &sa, NULL) != -1);
761 g_assert (sigaction (SIGFPE, &sa, NULL) != -1);
762 g_assert (sigaction (SIGQUIT, &sa, NULL) != -1);
764 #endif /* SANITY */
766 #endif /* DEBUG */