1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
5 * Copyright 2007 Novell, Inc. (http://www.novell.com)
7 * See the LICENSE file included with the distribution for details.
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>
21 /* because this header sucks */
22 #include <mono/metadata/mono-debug.h>
24 #include <mono/metadata/mono-config.h>
25 #include <mono/metadata/threads.h>
31 #include <sys/syscall.h>
38 #define UNW_LOCAL_ONLY
39 #include <libunwind.h>
43 static bool vm_stack_trace_enabled
= false;
46 enable_vm_stack_trace (void)
48 vm_stack_trace_enabled
= true;
53 get_method_name_from_ip (void *ip
)
55 if (!vm_stack_trace_enabled
)
62 MonoDomain
*domain
= mono_domain_get ();
64 ji
= mono_jit_info_table_find (domain
, (char*) ip
);
68 mi
= mono_jit_info_get_method (ji
);
69 method
= mono_method_full_name (mi
, TRUE
);
71 res
= g_strdup_printf ("%s", method
);
80 get_method_from_ip (void *ip
)
82 if (!vm_stack_trace_enabled
)
91 MonoDomain
*domain
= mono_domain_get ();
92 MonoDebugSourceLocation
*location
;
94 ji
= mono_jit_info_table_find (domain
, (char*) ip
);
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
);
106 res
= g_strdup_printf (" %s in %s:%i,%i", method
, location
->source_file
, location
->row
, location
->column
);
108 res
= g_strdup_printf (" %s + 0x%x", method
, (int)((char*)ip
- (char*)jit_start
));
110 mono_debug_free_source_location (location
);
117 int max_stack_trace_frames
= -1;
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
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'
151 hexdump_addr (void *addr
, size_t n
)
153 const unsigned char *mem
= (const unsigned char *) addr
;
154 char outbuf
[80], *outptr
;
158 for (i
= 0; i
< n
; i
+= 16) {
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
++) {
178 *outptr
++ = tohex
[(c
>> 4) & 0xf];
179 *outptr
++ = tohex
[c
& 0xf];
182 // pad out to the expected column
183 for ( ; j
< i
+ 16; j
++) {
196 // write out up to 16 raw octets
197 for (j
= i
; j
< n
&& j
< i
+ 16; j
++) {
199 if (isprint ((int) c
)) {
200 *outptr
++ = (char) c
;
209 fputs (outbuf
, stdout
);
213 typedef struct Addr2LineData Addr2LineData
;
215 struct Addr2LineData
{
224 static __thread Addr2LineData
*addr2line_pipes
= NULL
;
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;
235 char* current_library
= NULL
;
236 gpointer current_base_address
= NULL
;
238 char entire_line
[2000];
241 gint64 buffer_read
= getline (&buffer
, &buffer_length
, maps
);
246 memcpy (entire_line
, buffer
, buffer_read
);
247 entire_line
[buffer_read
+ 1] = 0;
249 if (buffer_read
< 20)
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
);
264 current_library
= NULL
;
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
);
275 char* start_range
= strtok_r (range
, "-", &saveptr
);
276 char* end_range
= strtok_r (NULL
, "-", &saveptr
);
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);
301 static char* addr2line_offset (gpointer ip
, bool use_offset
);
304 addr2line (gpointer ip
)
306 char* result
= addr2line_offset (ip
, true);
308 result
= addr2line_offset (ip
, false);
314 addr2line_offset (gpointer ip
, bool use_offset
)
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);
326 if (binary
[0] == '[') {
331 for (addr2line
= addr2line_pipes
; addr2line
; addr2line
= addr2line
->next
) {
332 if (strcmp (binary
, addr2line
->binary
) == 0)
337 const char *addr_argv
[] = {"addr2line", "-f", "-e", binary
, "-C", NULL
};
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
)) {
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
;
361 offset
= (gpointer
) (((size_t) ip
) - ((size_t) addr2line
->base
));
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 */
376 result
= fgets (buf
, sizeof (buf
), addr2line
->pipeout
);
381 if (result
[0] == '?' || result
[0] == 0)
384 result_length
= strlen (result
);
385 result
[result_length
- 1] = 0;
388 result
= fgets (buf
+ result_length
, sizeof (buf
) - result_length
, addr2line
->pipeout
);
393 result_length
= strlen (result
);
394 result
[result_length
- 1] = 0;
397 res
= g_strdup_printf ("%s [%p] %s %s", addr2line
->binary
, ip
, first
, second
);
399 // printf ("Final result: %s\n", res);
405 get_managed_frame (gpointer ip
)
407 return get_method_from_ip (ip
);
411 get_stack_trace_prefix_n (const char* prefix
, int maxframes
)
415 int total_length
= 0;
416 int prefix_length
= strlen (prefix
);
417 void *ips
[maxframes
];
418 char *frames
[maxframes
];
421 address_count
= backtrace (ips
, maxframes
);
423 for (int i
= 2; i
< address_count
; 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] == '?') {
433 names
= backtrace_symbols (&ip
, 1);
434 frame
= g_strdup (names
[0]);
438 total_length
+= prefix_length
+ strlen (frame
) + 1;
441 char* result
= (char*) g_malloc0 (total_length
+ 1);
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);
461 print_stack_trace_prefix (const char* prefix
)
463 print_stack_trace_prefix_n (prefix
, get_max_frames ());
467 print_stack_trace_prefix_n (const char* prefix
, int maxframes
)
469 char* st
= get_stack_trace_prefix_n (prefix
, maxframes
);
484 struct Frame
: List::Node
{
492 virtual char *ToString ()
494 return g_strdup_printf ("%d|%lx|%s", type
, ptr
, name
);
498 struct Frames
: List::Node
{
510 List
*allframes
= NULL
;
514 print_reftrace (const char * act
, const char * typname
, int refcount
, bool keep
)
517 unw_cursor_t cursor
; unw_context_t uc
;
518 unw_word_t ip
, sp
, bp
;
521 unw_init_local(&cursor
, &uc
);
523 char framename
[1024];
525 List
*frames
= new List ();
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
);
535 unw_get_reg(&cursor
, UNW_X86_EBP
, &bp
);
537 unw_get_reg(&cursor
, UNW_X86_64_RBP
, &bp
);
540 unw_get_proc_name (&cursor
, framename
, sizeof(framename
), 0);
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
);
555 char * demangled
= cplus_demangle (framename
, 0);
557 if (strstr (demangled
, ":") > 0) {
558 frame
->ptr
= *(int*)(bp
+8);
559 frame
->name
= g_strdup (demangled
);
560 frame
->type
= INSTANCE
;
562 frame
->ptr
= (long)bp
;
563 frame
->name
= g_strdup (demangled
);
564 frame
->type
= STATIC
;
567 frame
->ptr
= (long)bp
;
568 frame
->name
= g_strdup (framename
);
573 frames
->Append (frame
);
578 if (allframes
== NULL
)
579 allframes
= new List ();
580 if (allframes
->Length() % 50)
582 Frames
*f
= new Frames ();
584 f
->act
= g_strdup (act
);
585 f
->typname
= g_strdup (typname
);
586 f
->refcount
= refcount
;
587 allframes
->Append (f
);
590 printf("trace:%s|%s|%d;", act
, typname
,refcount
);
591 frame
= (Frame
*)frames
->First ();
592 while (frame
!= NULL
) {
593 char *s
= frame
->ToString ();
596 frame
= (Frame
*)frame
->next
;
606 void dump_frames (void)
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 ();
617 frame
= (Frame
*)frame
->next
;
620 frames
= (Frames
*)frames
->next
;
622 allframes
->Clear (true);
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;
643 /* Try to get more meaningful information using gdb */
644 #if !defined(PLATFORM_WIN32)
645 /* From g_spawn_command_line_sync () in eglib */
647 int stdout_pipe
[2] = { -1, -1 };
649 const char *argv
[16];
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
);
663 close (stdout_pipe
[0]);
664 dup2 (stdout_pipe
[1], STDOUT_FILENO
);
666 for (int i
= getdtablesize () - 1; i
>= 3; i
--)
669 argv
[0] = g_find_program_in_path ("gdb");
670 if (argv
[0] == NULL
) {
671 close (STDOUT_FILENO
);
676 sprintf (buf1
, "attach %ld", (long)getpid ());
679 argv
[4] = "info threads";
681 argv
[6] = "thread apply all bt";
682 argv
[7] = "--batch";
685 execv (argv
[0], (char**)argv
);
689 close (stdout_pipe
[1]);
691 fprintf (stderr
, "\nDebug info from gdb:\n\n");
694 int nread
= read (stdout_pipe
[0], buffer
, 1024);
698 write (STDERR_FILENO
, buffer
, nread
);
701 waitpid (pid
, &status
, WNOHANG
);
707 static moonlight_handle_native_sigsegv (int signal
)
709 const char *signal_str
;
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
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.
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"
749 fprintf (stderr
, "\nDebug info from libmoon:\n\n");
750 print_stack_trace ();
752 if (signal
!= SIGQUIT
) {
755 handling_sigsegv
= false;
760 moonlight_install_signal_handlers ()
764 if (handlers_installed
)
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
);
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);