2005-12-23 Dick Porter <dick@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 #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 static void *
93 mono_raw_buffer_load_mmap (int fd, int is_writable, guint32 base, size_t size)
94 {
95 #ifdef USE_WIN32_API
96         /* FileMapping implementation */
97
98         DWORD start, end;
99         int prot, access;
100         void *ptr;
101         HANDLE file, mapping;
102
103         start = ROUND_DOWN (base, alignment);
104         end = base + size;
105         
106         if (is_writable) {
107                 prot = PAGE_WRITECOPY;
108                 access = FILE_MAP_COPY;
109         }
110         else {
111                 prot = PAGE_READONLY;
112                 access = FILE_MAP_READ;
113         }
114
115         file = (HANDLE) _get_osfhandle (fd);
116         mapping = CreateFileMapping (file, NULL, prot, 0, 0, NULL);
117         if (mapping == NULL)
118                 return 0;
119
120         ptr = MapViewOfFile (mapping, access, 0, start, end - start);
121         if (ptr == NULL) {
122                 CloseHandle (mapping);
123                 return 0;
124         }
125
126         mono_mmap_lock ();
127         g_hash_table_insert (mmap_map, ptr, GINT_TO_POINTER (mapping));
128         mono_mmap_unlock ();
129         
130         return ((char *)ptr) + (base - start);
131
132 #else
133         /* mmap implementation */
134
135
136         size_t start, end;
137         int prot = PROT_READ;
138         int flags = 0;
139         void *ptr;
140
141         start = ROUND_DOWN (base, alignment);
142         end = ROUND_UP (base + size, alignment);
143
144         if (is_writable){
145                 prot |= PROT_WRITE;
146                 flags = MAP_SHARED;
147         } else {
148                 flags = MAP_PRIVATE;
149         }
150
151         ptr = mmap (0, end - start, prot, flags, fd, start);
152
153         if (ptr == (void *) -1)
154                 return 0;
155
156         /* 
157          * This seems to prevent segmentation faults on Fedora Linux, no
158          * idea why :). See
159          * http://bugzilla.ximian.com/show_bug.cgi?id=49499
160          * for more info.
161          */
162         if (mprotect (ptr, end - start, prot | PROT_EXEC) != 0)
163                 g_warning (G_GNUC_PRETTY_FUNCTION
164                                    ": mprotect failed: %s", g_strerror (errno));
165
166         if (make_unreadable) {
167                 int res = mprotect (ptr, end - start, 0);
168                 g_assert (res == 0);
169         }
170
171         mono_mmap_lock ();
172         g_hash_table_insert (mmap_map, ptr, GINT_TO_POINTER (size));
173         mono_mmap_unlock ();
174
175         return ((char *)ptr) + (base - start);
176 #endif
177 }
178
179 static void
180 mono_raw_buffer_free_mmap (void *base)
181 {
182         int value;
183
184         mono_mmap_lock ();
185         value = GPOINTER_TO_INT (g_hash_table_lookup (mmap_map, base));
186         mono_mmap_unlock ();
187
188 #ifdef USE_WIN32_API
189         UnmapViewOfFile (base);
190         CloseHandle ((HANDLE) value);
191 #else
192         munmap (base, value);
193 #endif
194 }
195
196 static void
197 mono_raw_buffer_update_mmap (void *base, size_t size)
198 {
199 #ifdef USE_WIN32_API
200         FlushViewOfFile (base, size);
201 #else
202         msync (base, size, MS_SYNC);
203 #endif
204 }
205
206 void *
207 mono_raw_buffer_load (int fd, int is_writable, guint32 base, size_t size)
208 {
209         void *ptr;
210
211         ptr = mono_raw_buffer_load_mmap (fd, is_writable, base, size);
212         if (ptr == 0)
213                 ptr = mono_raw_buffer_load_malloc (fd, is_writable, base, size);
214         
215         return ptr;
216 }
217
218 void
219 mono_raw_buffer_update (void *buffer, size_t size)
220 {
221         char *mmap_base;
222         gboolean exists;
223
224         mmap_base =  (gpointer)(ROUND_DOWN ((UINTPTR_TYPE) (buffer), alignment));
225
226         mono_mmap_lock ();
227         exists = g_hash_table_lookup (mmap_map, mmap_base) != NULL;
228         mono_mmap_unlock ();
229         if (exists)
230                 mono_raw_buffer_update_mmap (mmap_base, size);
231 }
232
233 void
234 mono_raw_buffer_free (void *buffer)
235 {
236         char *mmap_base;
237         gboolean exists;
238
239         mmap_base = (gpointer)(ROUND_DOWN ((UINTPTR_TYPE) (buffer), alignment));
240         
241         exists = g_hash_table_lookup (mmap_map, mmap_base) != NULL;
242         if (exists)
243                 mono_raw_buffer_free_mmap (mmap_base);
244         else
245                 mono_raw_buffer_free_malloc (buffer);
246 }
247
248 /*
249  * mono_raw_buffer_set_make_unreadable:
250  *
251  *   Set whenever to make all mmaped memory unreadable. In conjuction with a
252  * SIGSEGV handler, this is useful to find out which pages the runtime tries to read.
253  */
254 void
255 mono_raw_buffer_set_make_unreadable (gboolean unreadable)
256 {
257         make_unreadable = unreadable;
258 }
259
260 typedef struct {
261         gboolean found;
262         void *ptr;
263 } FindMapUserData;
264
265 static void
266 find_map (void *start, guint32 size, gpointer user_data)
267 {
268         FindMapUserData *data = (FindMapUserData*)user_data;
269
270         if (!data->found)
271                 if (((guint8*)data->ptr >= (guint8*)start) && ((guint8*)data->ptr < (guint8*)start + size))
272                         data->found = TRUE;
273 }
274
275 /*
276  * mono_raw_buffer_is_pagefault:
277  *
278  *   Should be called from a SIGSEGV signal handler to find out whenever @ptr is
279  * within memory allocated by this module.
280  */
281 gboolean
282 mono_raw_buffer_is_pagefault (void *ptr)
283 {
284         FindMapUserData data;
285
286         if (!make_unreadable)
287                 return FALSE;
288
289         data.found = FALSE;
290         data.ptr = ptr;
291
292         mono_mmap_lock ();
293         g_hash_table_foreach (mmap_map, (GHFunc)find_map, &data);
294         mono_mmap_unlock ();
295
296         return data.found;
297 }
298
299 /*
300  * mono_raw_buffer_handle_pagefault:
301  *
302  *   Handle a pagefault caused by an unreadable page by making it readable again.
303  */
304 void
305 mono_raw_buffer_handle_pagefault (void *ptr)
306 {
307 #ifndef PLATFORM_WIN32
308         guint8* start = (guint8*)ROUND_DOWN (((gssize)ptr), alignment);
309         int res;
310
311         mono_mmap_lock ();
312         res = mprotect (start, alignment, PROT_READ);
313         g_assert (res == 0);
314
315         n_pagefaults ++;
316         mono_mmap_unlock ();
317 #endif
318 }
319
320 /*
321  * mono_raw_buffer_get_n_pagefaults:
322  *
323  *   Return the number of times handle_pagefault is called.
324  * To count the number of pagefaults caused by a block of code use code like this:
325  * 
326  *  int prev_pagefaults = mono_raw_buffer_get_n_pagefaults ();
327  *  <CODE>
328  *  int new_pagefaults = mono_raw_buffer_get_n_pagefaults () - prev_pagefaults;
329  */
330 guint32
331 mono_raw_buffer_get_n_pagefaults (void)
332 {
333         return n_pagefaults;
334 }