1 /**
2  * \file
3  * Runtime simple lock tracer
4  *
5  * Authors:
6  *	Rodrigo Kumpera (rkumpera@novell.com)
7  *
8  */
9 
10 #include <config.h>
11 #include <stdio.h>
12 #include <string.h>
13 
14 #include <sys/types.h>
15 
16 #ifdef HAVE_UNISTD_H
17 #include <unistd.h>
18 #endif
19 
20 #ifdef HAVE_EXECINFO_H
21 #include <execinfo.h>
22 #endif
23 
24 #include <mono/utils/mono-compiler.h>
25 
26 #include "lock-tracer.h"
27 
28 /*
29  * This is a very simple lock trace implementation. It can be used to verify that the runtime is
30  * correctly following all locking rules.
31  *
32  * To log more kind of locks just do the following:
33  * 	- add an entry into the RuntimeLocks enum
34  *  - change mono_os_mutex_lock(mutex) to mono_locks_os_acquire (mutex, LockName)
35  *  - change mono_os_mutex_unlock(mutex) to mono_locks_os_release (mutex, LockName)
36  *  - change mono_coop_mutex_lock(mutex) to mono_locks_coop_acquire (mutex, LockName)
37  *  - change mono_coop_mutex_unlock(mutex) to mono_locks_coop_release (mutex, LockName)
38  *  - change the decoder to understand the new lock kind.
39  *
40  * TODO:
41  * 	- Use unbuffered IO without fsync
42  *  - Switch to a binary log format
43  *  - Enable tracing of more runtime locks
44  *  - Add lock check assertions (must_not_hold_any_lock_but, must_hold_lock, etc)
45  *   This should be used to verify methods that expect that a given lock is held at entrypoint, for example.
46  *
47  * To use the trace, define LOCK_TRACER in lock-trace.h and when running mono define MONO_ENABLE_LOCK_TRACER.
48  * This will produce a locks.ZZZ where ZZZ is the pid of the mono process.
49  * Use the decoder to verify the result.
50  */
51 
52 #ifdef LOCK_TRACER
53 
54 #ifdef TARGET_OSX
55 #include <dlfcn.h>
56 #endif
57 
58 static FILE *trace_file;
59 static mono_mutex_t tracer_lock;
60 static size_t base_address;
61 
62 typedef enum {
63 	RECORD_MUST_NOT_HOLD_ANY,
64 	RECORD_MUST_NOT_HOLD_ONE,
65 	RECORD_MUST_HOLD_ONE,
66 	RECORD_LOCK_ACQUIRED,
67 	RECORD_LOCK_RELEASED
68 } RecordType;
69 
70 void
mono_locks_tracer_init(void)71 mono_locks_tracer_init (void)
72 {
73 	Dl_info info;
74 	int res;
75 	char *name;
76 	mono_os_mutex_init_recursive (&tracer_lock);
77 
78 	if (!g_hasenv ("MONO_ENABLE_LOCK_TRACER"))
79 		return;
80 
81 	name = g_strdup_printf ("locks.%d", getpid ());
82 	trace_file = fopen (name, "w+");
83 	g_free (name);
84 
85 #ifdef TARGET_OSX
86 	res = dladdr ((void*)&mono_locks_tracer_init, &info);
87 	/* The 0x1000 offset was found by empirically trying it. */
88 	if (res)
89 		base_address = (size_t)info.dli_fbase - 0x1000;
90 #endif
91 }
92 
93 
94 #ifdef HAVE_EXECINFO_H
95 
96 static int
mono_backtrace(gpointer array[],int traces)97 mono_backtrace (gpointer array[], int traces)
98 {
99 	return backtrace (array, traces);
100 }
101 
102 #else
103 
104 static int
mono_backtrace(gpointer array[],int traces)105 mono_backtrace (gpointer array[], int traces)
106 {
107 	return 0;
108 }
109 
110 #endif
111 
112 static void
add_record(RecordType record_kind,RuntimeLocks kind,gpointer lock)113 add_record (RecordType record_kind, RuntimeLocks kind, gpointer lock)
114 {
115 	int i = 0;
116 	const int no_frames = 6;
117 	gpointer frames[no_frames];
118 
119 	char *msg;
120  	if (!trace_file)
121 		return;
122 
123 	memset (frames, 0, sizeof (gpointer) * no_frames);
124 	mono_backtrace (frames, no_frames);
125 	for (i = 0; i < no_frames; ++i)
126 		frames [i] = (gpointer)((size_t)frames[i] - base_address);
127 
128 	/*We only dump 5 frames, which should be more than enough to most analysis.*/
129 	msg = g_strdup_printf ("%x,%d,%d,%p,%p,%p,%p,%p,%p\n", (guint32)mono_native_thread_id_get (), record_kind, kind, lock, frames [1], frames [2], frames [3], frames [4], frames [5]);
130 	fwrite (msg, strlen (msg), 1, trace_file);
131 	fflush (trace_file);
132 	g_free (msg);
133 }
134 
135 void
mono_locks_lock_acquired(RuntimeLocks kind,gpointer lock)136 mono_locks_lock_acquired (RuntimeLocks kind, gpointer lock)
137 {
138 	add_record (RECORD_LOCK_ACQUIRED, kind, lock);
139 }
140 
141 void
mono_locks_lock_released(RuntimeLocks kind,gpointer lock)142 mono_locks_lock_released (RuntimeLocks kind, gpointer lock)
143 {
144 	add_record (RECORD_LOCK_RELEASED, kind, lock);
145 }
146 #else
147 
148 MONO_EMPTY_SOURCE_FILE (lock_tracer);
149 #endif /* LOCK_TRACER */
150