Paranoid integer overflow check in my_mmap
[ghc.git] / rts / posix / OSMem.c
1 /* -----------------------------------------------------------------------------
2 *
3 * (c) The University of Glasgow 2006-2007
4 *
5 * OS-specific memory management
6 *
7 * ---------------------------------------------------------------------------*/
8
9 // This is non-posix compliant.
10 // #include "PosixSource.h"
11
12 #include "Rts.h"
13
14 #include "RtsUtils.h"
15 #include "sm/OSMem.h"
16
17 #ifdef HAVE_UNISTD_H
18 #include <unistd.h>
19 #endif
20 #ifdef HAVE_SYS_TYPES_H
21 #include <sys/types.h>
22 #endif
23 #ifdef HAVE_SYS_MMAN_H
24 #include <sys/mman.h>
25 #endif
26 #ifdef HAVE_STRING_H
27 #include <string.h>
28 #endif
29 #ifdef HAVE_FCNTL_H
30 #include <fcntl.h>
31 #endif
32
33 #include <errno.h>
34
35 #if darwin_HOST_OS
36 #include <mach/mach.h>
37 #include <mach/vm_map.h>
38 #endif
39
40 static caddr_t next_request = 0;
41
42 void osMemInit(void)
43 {
44 next_request = (caddr_t)RtsFlags.GcFlags.heapBase;
45 }
46
47 /* -----------------------------------------------------------------------------
48 The mmap() method
49
50 On Unix-like systems, we use mmap() to allocate our memory. We
51 want memory in chunks of MBLOCK_SIZE, and aligned on an MBLOCK_SIZE
52 boundary. The mmap() interface doesn't give us this level of
53 control, so we have to use some heuristics.
54
55 In the general case, if we want a block of n megablocks, then we
56 allocate n+1 and trim off the slop from either side (using
57 munmap()) to get an aligned chunk of size n. However, the next
58 time we'll try to allocate directly after the previously allocated
59 chunk, on the grounds that this is aligned and likely to be free.
60 If it turns out that we were wrong, we have to munmap() and try
61 again using the general method.
62
63 Note on posix_memalign(): this interface is available on recent
64 systems and appears to provide exactly what we want. However, it
65 turns out not to be as good as our mmap() implementation, because
66 it wastes extra space (using double the address space, in a test on
67 x86_64/Linux). The problem seems to be that posix_memalign()
68 returns memory that can be free()'d, so the library must store
69 extra information along with the allocated block, thus messing up
70 the alignment. Hence, we don't use posix_memalign() for now.
71
72 -------------------------------------------------------------------------- */
73
74 // A wrapper around mmap(), to abstract away from OS differences in
75 // the mmap() interface.
76
77 static void *
78 my_mmap (void *addr, W_ size)
79 {
80 void *ret;
81
82 if (size > (W_)SIZE_MAX)
83 barf("my_mmap: impossibly large allocation of %" FMT_Word " bytes; W_ larger than size_t?", size);
84
85 #if defined(solaris2_HOST_OS) || defined(irix_HOST_OS)
86 {
87 int fd = open("/dev/zero",O_RDONLY);
88 ret = mmap(addr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
89 close(fd);
90 }
91 #elif hpux_HOST_OS
92 ret = mmap(addr, size, PROT_READ | PROT_WRITE,
93 MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
94 #elif darwin_HOST_OS
95 // Without MAP_FIXED, Apple's mmap ignores addr.
96 // With MAP_FIXED, it overwrites already mapped regions, whic
97 // mmap(0, ... MAP_FIXED ...) is worst of all: It unmaps the program text
98 // and replaces it with zeroes, causing instant death.
99 // This behaviour seems to be conformant with IEEE Std 1003.1-2001.
100 // Let's just use the underlying Mach Microkernel calls directly,
101 // they're much nicer.
102
103 kern_return_t err = 0;
104 ret = addr;
105 if(addr) // try to allocate at address
106 err = vm_allocate(mach_task_self(),(vm_address_t*) &ret, size, FALSE);
107 if(!addr || err) // try to allocate anywhere
108 err = vm_allocate(mach_task_self(),(vm_address_t*) &ret, size, TRUE);
109
110 if(err) {
111 // don't know what the error codes mean exactly, assume it's
112 // not our problem though.
113 errorBelch("memory allocation failed (requested %" FMT_Word " bytes)", size);
114 stg_exit(EXIT_FAILURE);
115 } else {
116 vm_protect(mach_task_self(),(vm_address_t)ret,size,FALSE,VM_PROT_READ|VM_PROT_WRITE);
117 }
118 #else
119 ret = mmap(addr, size, PROT_READ | PROT_WRITE,
120 MAP_ANON | MAP_PRIVATE, -1, 0);
121 #endif
122
123 if (ret == (void *)-1) {
124 if (errno == ENOMEM ||
125 (errno == EINVAL && sizeof(void*)==4 && size >= 0xc0000000)) {
126 // If we request more than 3Gig, then we get EINVAL
127 // instead of ENOMEM (at least on Linux).
128 errorBelch("out of memory (requested %" FMT_Word " bytes)", size);
129 stg_exit(EXIT_FAILURE);
130 } else {
131 barf("getMBlock: mmap: %s", strerror(errno));
132 }
133 }
134
135 return ret;
136 }
137
138 // Implements the general case: allocate a chunk of memory of 'size'
139 // mblocks.
140
141 static void *
142 gen_map_mblocks (W_ size)
143 {
144 int slop;
145 StgWord8 *ret;
146
147 // Try to map a larger block, and take the aligned portion from
148 // it (unmap the rest).
149 size += MBLOCK_SIZE;
150 ret = my_mmap(0, size);
151
152 // unmap the slop bits around the chunk we allocated
153 slop = (W_)ret & MBLOCK_MASK;
154
155 if (munmap((void*)ret, MBLOCK_SIZE - slop) == -1) {
156 barf("gen_map_mblocks: munmap failed");
157 }
158 if (slop > 0 && munmap((void*)(ret+size-slop), slop) == -1) {
159 barf("gen_map_mblocks: munmap failed");
160 }
161
162 // ToDo: if we happened to get an aligned block, then don't
163 // unmap the excess, just use it. For this to work, you
164 // need to keep in mind the following:
165 // * Calling my_mmap() with an 'addr' arg pointing to
166 // already my_mmap()ed space is OK and won't fail.
167 // * If my_mmap() can't satisfy the request at the
168 // given 'next_request' address in getMBlocks(), that
169 // you unmap the extra mblock mmap()ed here (or simply
170 // satisfy yourself that the slop introduced isn't worth
171 // salvaging.)
172 //
173
174 // next time, try after the block we just got.
175 ret += MBLOCK_SIZE - slop;
176 return ret;
177 }
178
179 void *
180 osGetMBlocks(nat n)
181 {
182 caddr_t ret;
183 W_ size;
184
185 // Compute size = MBLOCK_SIZE * (W_)n,
186 // while testing for integer overflow.
187 // We assume that W_ is at least as large a type as nat.
188 if ((W_)n > ((W_)-1) / MBLOCK_SIZE) {
189 // We tried to allocate, say, 4 GB or more on a 32-bit system.
190 errorBelch("out of memory (requested %d MBlocks)", n);
191 stg_exit(EXIT_FAILURE);
192 } else {
193 size = MBLOCK_SIZE * (W_)n;
194 }
195
196 if (next_request == 0) {
197 // use gen_map_mblocks the first time.
198 ret = gen_map_mblocks(size);
199 } else {
200 ret = my_mmap(next_request, size);
201
202 if (((W_)ret & MBLOCK_MASK) != 0) {
203 // misaligned block!
204 #if 0 // defined(DEBUG)
205 errorBelch("warning: getMBlock: misaligned block %p returned when allocating %d megablock(s) at %p", ret, n, next_request);
206 #endif
207
208 // unmap this block...
209 if (munmap(ret, size) == -1) {
210 barf("getMBlock: munmap failed");
211 }
212 // and do it the hard way
213 ret = gen_map_mblocks(size);
214 }
215 }
216 // Next time, we'll try to allocate right after the block we just got.
217 // ToDo: check that we haven't already grabbed the memory at next_request
218 next_request = ret + size;
219
220 return ret;
221 }
222
223 void osFreeMBlocks(char *addr, nat n)
224 {
225 munmap(addr, n * MBLOCK_SIZE);
226 }
227
228 void osReleaseFreeMemory(void) {
229 /* Nothing to do on POSIX */
230 }
231
232 void osFreeAllMBlocks(void)
233 {
234 void *mblock;
235
236 for (mblock = getFirstMBlock();
237 mblock != NULL;
238 mblock = getNextMBlock(mblock)) {
239 munmap(mblock, MBLOCK_SIZE);
240 }
241 }
242
243 W_ getPageSize (void)
244 {
245 static W_ pageSize = 0;
246 if (pageSize) {
247 return pageSize;
248 } else {
249 long ret;
250 ret = sysconf(_SC_PAGESIZE);
251 if (ret == -1) {
252 barf("getPageSize: cannot get page size");
253 }
254 return ret;
255 }
256 }
257
258 void setExecutable (void *p, W_ len, rtsBool exec)
259 {
260 StgWord pageSize = getPageSize();
261
262 /* malloced memory isn't executable by default on OpenBSD */
263 StgWord mask = ~(pageSize - 1);
264 StgWord startOfFirstPage = ((StgWord)p ) & mask;
265 StgWord startOfLastPage = ((StgWord)p + len - 1) & mask;
266 StgWord size = startOfLastPage - startOfFirstPage + pageSize;
267 if (mprotect((void*)startOfFirstPage, (size_t)size,
268 (exec ? PROT_EXEC : 0) | PROT_READ | PROT_WRITE) != 0) {
269 barf("setExecutable: failed to protect 0x%p\n", p);
270 }
271 }