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