Tue Sep 11 16:15:53 CEST 2007 Paolo Molaro <lupus@ximian.com>
[mono.git] / mono / metadata / rawbuffer.c
1 /*
2  * rawbuffer.c: Manages buffers that might have been mmapped or malloced
3  *
4  * Author:
5  *   Miguel de Icaza (miguel@ximian.com)
6  *
7  * (C) 2001 Ximian, Inc.
8  */
9 #include <config.h>
10 #if defined(PLATFORM_WIN32)
11 #define USE_WIN32_API           1
12 #endif
13
14 #ifdef HAVE_UNISTD_H
15 #include <unistd.h>
16 #endif
17 #include <errno.h>
18 #ifdef USE_WIN32_API
19 #include <winsock2.h>
20 #include <windows.h>
21 #include <io.h>
22 #else
23 #include <sys/mman.h>
24 #endif
25 #include <sys/types.h>
26 #include <glib.h>
27 #include "rawbuffer.h"
28
29 #include <mono/io-layer/io-layer.h>
30
31 #define ROUND_DOWN(VALUE,SIZE)  ((VALUE) & ~((SIZE) - 1))
32 #define ROUND_UP(VALUE,SIZE)    (ROUND_DOWN((VALUE) + (SIZE) - 1, (SIZE)))
33 #if SIZEOF_VOID_P == 8
34 #define UINTPTR_TYPE guint64
35 #else
36 #define UINTPTR_TYPE guint32
37 #endif
38
39 static GHashTable *mmap_map = NULL;
40 static size_t alignment = 0;
41 #define mono_mmap_lock() EnterCriticalSection (&mmap_mutex)
42 #define mono_mmap_unlock() LeaveCriticalSection (&mmap_mutex)
43 static CRITICAL_SECTION mmap_mutex;
44 static gboolean make_unreadable = FALSE;
45 static guint32 n_pagefaults = 0;
46
47 static void
48 get_alignment (void)
49 {
50 #ifdef USE_WIN32_API
51         SYSTEM_INFO info;
52
53         GetSystemInfo (&info);
54         alignment = info.dwAllocationGranularity;
55 #else
56         alignment = getpagesize ();
57 #endif
58 }
59
60 static void *
61 mono_raw_buffer_load_malloc (int fd, int is_writable, guint32 base, size_t size)
62 {
63         void *ptr;
64
65         ptr = g_malloc (size);
66         if (ptr == NULL)
67                 return NULL;
68
69         if (lseek (fd, base, 0) == (off_t) -1) {
70                 g_free (ptr);
71                 return NULL;
72         }
73
74         read (fd, ptr, size);
75         return ptr;
76 }
77
78 static void
79 mono_raw_buffer_free_malloc (void *base)
80 {
81         g_free (base);
82 }
83
84 void
85 mono_raw_buffer_init (void)
86 {
87         InitializeCriticalSection (&mmap_mutex);
88
89         get_alignment ();
90
91         mmap_map = g_hash_table_new (NULL, NULL);
92 }
93
94 void
95 mono_raw_buffer_cleanup (void)
96 {
97         g_hash_table_destroy (mmap_map);
98
99         DeleteCriticalSection (&mmap_mutex);
100 }
101
102 static void *
103 mono_raw_buffer_load_mmap (int fd, int is_writable, guint32 base, size_t size)
104 {
105 #ifdef USE_WIN32_API
106         /* FileMapping implementation */
107
108         DWORD start, end;
109         int prot, access;
110         void *ptr;
111         HANDLE file, mapping;
112
113         start = ROUND_DOWN (base, alignment);
114         end = base + size;
115         
116         if (is_writable) {
117                 prot = PAGE_WRITECOPY;
118                 access = FILE_MAP_COPY;
119         }
120         else {
121                 prot = PAGE_READONLY;
122                 access = FILE_MAP_READ;
123         }
124
125         file = (HANDLE) _get_osfhandle (fd);
126         mapping = CreateFileMapping (file, NULL, prot, 0, 0, NULL);
127         if (mapping == NULL)
128                 return 0;
129
130         ptr = MapViewOfFile (mapping, access, 0, start, end - start);
131         if (ptr == NULL) {
132                 CloseHandle (mapping);
133                 return 0;
134         }
135
136         mono_mmap_lock ();
137         g_hash_table_insert (mmap_map, ptr, GINT_TO_POINTER (mapping));
138         mono_mmap_unlock ();
139         
140         return ((char *)ptr) + (base - start);
141
142 #else
143         /* mmap implementation */
144
145
146         size_t start, end;
147         int prot = PROT_READ;
148         int flags = 0;
149         void *ptr;
150
151         start = ROUND_DOWN (base, alignment);
152         end = ROUND_UP (base + size, alignment);
153
154         if (is_writable){
155                 prot |= PROT_WRITE;
156                 flags = MAP_SHARED;
157         } else {
158                 flags = MAP_PRIVATE;
159         }
160
161         ptr = mmap (0, end - start, prot, flags, fd, start);
162
163         if (ptr == (void *) -1)
164                 return 0;
165
166         /* 
167          * This seems to prevent segmentation faults on Fedora Linux, no
168          * idea why :). See
169          * http://bugzilla.ximian.com/show_bug.cgi?id=49499
170          * for more info.
171          */
172         if (mprotect (ptr, end - start, prot | PROT_EXEC) != 0)
173                 g_warning (G_GNUC_PRETTY_FUNCTION
174                                    ": mprotect failed: %s", g_strerror (errno));
175
176         if (make_unreadable) {
177                 int res = mprotect (ptr, end - start, 0);
178                 g_assert (res == 0);
179         }
180
181         mono_mmap_lock ();
182         g_hash_table_insert (mmap_map, ptr, GINT_TO_POINTER (size));
183         mono_mmap_unlock ();
184
185         return ((char *)ptr) + (base - start);
186 #endif
187 }
188
189 static void
190 mono_raw_buffer_free_mmap (void *base)
191 {
192         int value;
193
194         mono_mmap_lock ();
195         value = GPOINTER_TO_INT (g_hash_table_lookup (mmap_map, base));
196         mono_mmap_unlock ();
197
198 #ifdef USE_WIN32_API
199         UnmapViewOfFile (base);
200         CloseHandle ((HANDLE) value);
201 #else
202         munmap (base, value);
203 #endif
204 }
205
206 static void
207 mono_raw_buffer_update_mmap (void *base, size_t size)
208 {
209 #ifdef USE_WIN32_API
210         FlushViewOfFile (base, size);
211 #else
212         msync (base, size, MS_SYNC);
213 #endif
214 }
215
216 void *
217 mono_raw_buffer_load (int fd, int is_writable, guint32 base, size_t size)
218 {
219         void *ptr;
220
221         ptr = mono_raw_buffer_load_mmap (fd, is_writable, base, size);
222         if (ptr == 0)
223                 ptr = mono_raw_buffer_load_malloc (fd, is_writable, base, size);
224         
225         return ptr;
226 }
227
228 void
229 mono_raw_buffer_update (void *buffer, size_t size)
230 {
231         char *mmap_base;
232         gboolean exists;
233
234         mmap_base =  (gpointer)(ROUND_DOWN ((UINTPTR_TYPE) (buffer), alignment));
235
236         mono_mmap_lock ();
237         exists = g_hash_table_lookup (mmap_map, mmap_base) != NULL;
238         mono_mmap_unlock ();
239         if (exists)
240                 mono_raw_buffer_update_mmap (mmap_base, size);
241 }
242
243 void
244 mono_raw_buffer_free (void *buffer)
245 {
246         char *mmap_base;
247         gboolean exists;
248
249         mmap_base = (gpointer)(ROUND_DOWN ((UINTPTR_TYPE) (buffer), alignment));
250         
251         exists = g_hash_table_lookup (mmap_map, mmap_base) != NULL;
252         if (exists)
253                 mono_raw_buffer_free_mmap (mmap_base);
254         else
255                 mono_raw_buffer_free_malloc (buffer);
256 }
257
258 /*
259  * mono_raw_buffer_set_make_unreadable:
260  *
261  *   Set whenever to make all mmaped memory unreadable. In conjuction with a
262  * SIGSEGV handler, this is useful to find out which pages the runtime tries to read.
263  */
264 void
265 mono_raw_buffer_set_make_unreadable (gboolean unreadable)
266 {
267         make_unreadable = unreadable;
268 }
269
270 typedef struct {
271         gboolean found;
272         void *ptr;
273 } FindMapUserData;
274
275 static void
276 find_map (void *start, guint32 size, gpointer user_data)
277 {
278         FindMapUserData *data = (FindMapUserData*)user_data;
279
280         if (!data->found)
281                 if (((guint8*)data->ptr >= (guint8*)start) && ((guint8*)data->ptr < (guint8*)start + size))
282                         data->found = TRUE;
283 }
284
285 /*
286  * mono_raw_buffer_is_pagefault:
287  *
288  *   Should be called from a SIGSEGV signal handler to find out whenever @ptr is
289  * within memory allocated by this module.
290  */
291 gboolean
292 mono_raw_buffer_is_pagefault (void *ptr)
293 {
294         FindMapUserData data;
295
296         if (!make_unreadable)
297                 return FALSE;
298
299         data.found = FALSE;
300         data.ptr = ptr;
301
302         mono_mmap_lock ();
303         g_hash_table_foreach (mmap_map, (GHFunc)find_map, &data);
304         mono_mmap_unlock ();
305
306         return data.found;
307 }
308
309 /*
310  * mono_raw_buffer_handle_pagefault:
311  *
312  *   Handle a pagefault caused by an unreadable page by making it readable again.
313  */
314 void
315 mono_raw_buffer_handle_pagefault (void *ptr)
316 {
317 #ifndef PLATFORM_WIN32
318         guint8* start = (guint8*)ROUND_DOWN (((gssize)ptr), alignment);
319         int res;
320
321         mono_mmap_lock ();
322         res = mprotect (start, alignment, PROT_READ);
323         g_assert (res == 0);
324
325         n_pagefaults ++;
326         mono_mmap_unlock ();
327 #endif
328 }
329
330 /*
331  * mono_raw_buffer_get_n_pagefaults:
332  *
333  *   Return the number of times handle_pagefault is called.
334  * To count the number of pagefaults caused by a block of code use code like this:
335  * 
336  *  int prev_pagefaults = mono_raw_buffer_get_n_pagefaults ();
337  *  <CODE>
338  *  int new_pagefaults = mono_raw_buffer_get_n_pagefaults () - prev_pagefaults;
339  */
340 guint32
341 mono_raw_buffer_get_n_pagefaults (void)
342 {
343         return n_pagefaults;
344 }