summaryrefslogtreecommitdiff
path: root/mono/metadata/lock-tracer.c
blob: 0f04817ced9d077f54b828f963f7947fd2da0a96 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
/*
 * lock-tracer.c: Runtime simple lock tracer
 *
 * Authors:
 *	Rodrigo Kumpera (rkumpera@novell.com)
 * 
 */

#include <config.h>
#include <stdio.h>
#include <string.h>

#include <sys/types.h>

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#ifdef HAVE_EXECINFO_H
#include <execinfo.h>
#endif

#include <mono/io-layer/io-layer.h>

#include "lock-tracer.h"


/*
 * This is a very simple lock trace implementation. It can be used to verify that the runtime is
 * correctly following all locking rules.
 * 
 * To log more kind of locks just do the following:
 * 	- add an entry into the RuntimeLocks enum
 *  - change EnterCriticalSection(mutex) to mono_locks_acquire (mutex, LockName)
 *  - change LeaveCriticalSection to mono_locks_release (mutex, LockName)
 *  - change the decoder to understand the new lock kind.
 *
 * TODO:
 * 	- Use unbuffered IO without fsync
 *  - Switch to a binary log format
 *  - Enable tracing of more runtime locks
 *  - Add lock check assertions (must_not_hold_any_lock_but, must_hold_lock, etc)
 *   This should be used to verify methods that expect that a given lock is held at entrypoint, for example.
 * 
 * To use the trace, define LOCK_TRACER in lock-trace.h and when running mono define MONO_ENABLE_LOCK_TRACER.
 * This will produce a locks.ZZZ where ZZZ is the pid of the mono process.
 * Use the decoder to verify the result.
 */

#ifdef LOCK_TRACER

#ifdef TARGET_OSX
#include <dlfcn.h>
#endif

static FILE *trace_file;
static CRITICAL_SECTION tracer_lock;
static size_t base_address;

typedef enum {
	RECORD_MUST_NOT_HOLD_ANY,
	RECORD_MUST_NOT_HOLD_ONE,
	RECORD_MUST_HOLD_ONE,
	RECORD_LOCK_ACQUIRED,
	RECORD_LOCK_RELEASED
} RecordType;

void
mono_locks_tracer_init (void)
{
	Dl_info info;
	int res;
	char *name;
	InitializeCriticalSection (&tracer_lock);
	if (!g_getenv ("MONO_ENABLE_LOCK_TRACER"))
		return;
	name = g_strdup_printf ("locks.%d", getpid ());
	trace_file = fopen (name, "w+");
	g_free (name);

#ifdef TARGET_OSX
	res = dladdr ((void*)&mono_locks_tracer_init, &info);
	/* The 0x1000 offset was found by empirically trying it. */
	if (res)
		base_address = (size_t)info.dli_fbase - 0x1000;
#endif
}


#ifdef HAVE_EXECINFO_H

static int
mono_backtrace (gpointer array[], int traces)
{
	return backtrace (array, traces);
}

#else

static int
mono_backtrace (gpointer array[], int traces)
{
	return 0;
}

#endif

static void
add_record (RecordType record_kind, RuntimeLocks kind, gpointer lock)
{
	int i = 0;
	gpointer frames[10];
	char *msg;
 	if (!trace_file)
		return;

	memset (frames, 0, sizeof (gpointer));
	mono_backtrace (frames, 6);
	for (i = 0; i < 6; ++i)
		frames [i] = (gpointer)((size_t)frames[i] - base_address);

	/*We only dump 5 frames, which should be more than enough to most analysis.*/
	msg = g_strdup_printf ("%x,%d,%d,%p,%p,%p,%p,%p,%p\n", (guint32)GetCurrentThreadId (), record_kind, kind, lock, frames [1], frames [2], frames [3], frames [4], frames [5]);
	fwrite (msg, strlen (msg), 1, trace_file);
	fflush (trace_file);
	g_free (msg);
}

void
mono_locks_lock_acquired (RuntimeLocks kind, gpointer lock)
{
	add_record (RECORD_LOCK_ACQUIRED, kind, lock);
}

void
mono_locks_lock_released (RuntimeLocks kind, gpointer lock)
{
	add_record (RECORD_LOCK_RELEASED, kind, lock);
}

#endif