1 /*
2  *    Stack-less Just-In-Time compiler
3  *
4  *    Copyright Zoltan Herczeg (hzmester@freemail.hu). All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without modification, are
7  * permitted provided that the following conditions are met:
8  *
9  *   1. Redistributions of source code must retain the above copyright notice, this list of
10  *      conditions and the following disclaimer.
11  *
12  *   2. Redistributions in binary form must reproduce the above copyright notice, this list
13  *      of conditions and the following disclaimer in the documentation and/or other materials
14  *      provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND CONTRIBUTORS ``AS IS'' AND ANY
17  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
19  * SHALL THE COPYRIGHT HOLDER(S) OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
20  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
21  * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
22  * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
23  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
24  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25  */
26 
27 /*
28    This file contains a simple executable memory allocator
29 
30    It is assumed, that executable code blocks are usually medium (or sometimes
31    large) memory blocks, and the allocator is not too frequently called (less
32    optimized than other allocators). Thus, using it as a generic allocator is
33    not suggested.
34 
35    How does it work:
36      Memory is allocated in continuous memory areas called chunks by alloc_chunk()
37      Chunk format:
38      [ block ][ block ] ... [ block ][ block terminator ]
39 
40    All blocks and the block terminator is started with block_header. The block
41    header contains the size of the previous and the next block. These sizes
42    can also contain special values.
43      Block size:
44        0 - The block is a free_block, with a different size member.
45        1 - The block is a block terminator.
46        n - The block is used at the moment, and the value contains its size.
47      Previous block size:
48        0 - This is the first block of the memory chunk.
49        n - The size of the previous block.
50 
51    Using these size values we can go forward or backward on the block chain.
52    The unused blocks are stored in a chain list pointed by free_blocks. This
53    list is useful if we need to find a suitable memory area when the allocator
54    is called.
55 
56    When a block is freed, the new free block is connected to its adjacent free
57    blocks if possible.
58 
59      [ free block ][ used block ][ free block ]
60    and "used block" is freed, the three blocks are connected together:
61      [           one big free block           ]
62 */
63 
64 /* --------------------------------------------------------------------- */
65 /*  System (OS) functions                                                */
66 /* --------------------------------------------------------------------- */
67 
68 /* 64 KByte. */
69 #define CHUNK_SIZE  0x10000
70 
71 struct chunk_header {
72           void *executable;
73           int fd;
74 };
75 
76 /*
77    alloc_chunk / free_chunk :
78      * allocate executable system memory chunks
79      * the size is always divisible by CHUNK_SIZE
80    allocator_grab_lock / allocator_release_lock :
81      * make the allocator thread safe
82      * can be empty if the OS (or the application) does not support threading
83      * only the allocator requires this lock, sljit is fully thread safe
84        as it only uses local variables
85 */
86 
87 #include <fcntl.h>
88 
89 #ifndef O_NOATIME
90 #define O_NOATIME 0
91 #endif
92 
93 #ifdef __O_TMPFILE
94 #ifndef O_TMPFILE
95 #define O_TMPFILE   (__O_TMPFILE | O_DIRECTORY)
96 #endif
97 #endif
98 
99 int mkostemp(char *template, int flags);
100 char *secure_getenv(const char *name);
101 
create_tempfile(void)102 static SLJIT_INLINE int create_tempfile(void)
103 {
104           int fd;
105 
106           char tmp_name[256];
107           size_t tmp_name_len;
108           char *dir;
109           size_t len;
110 
111 #ifdef P_tmpdir
112           len = (P_tmpdir != NULL) ? strlen(P_tmpdir) : 0;
113 
114           if (len > 0 && len < sizeof(tmp_name)) {
115                     strcpy(tmp_name, P_tmpdir);
116                     tmp_name_len = len;
117           }
118           else {
119                     strcpy(tmp_name, "/tmp");
120                     tmp_name_len = 4;
121           }
122 #else
123           strcpy(tmp_name, "/tmp");
124           tmp_name_len = 4;
125 #endif
126 
127           dir = secure_getenv("TMPDIR");
128           if (dir) {
129                     len = strlen(dir);
130                     if (len > 0 && len < sizeof(tmp_name)) {
131                               strcpy(tmp_name, dir);
132                               tmp_name_len = len;
133                     }
134           }
135 
136           SLJIT_ASSERT(tmp_name_len > 0 && tmp_name_len < sizeof(tmp_name));
137 
138           while (tmp_name_len > 0 && tmp_name[tmp_name_len - 1] == '/') {
139                     tmp_name_len--;
140                     tmp_name[tmp_name_len] = '\0';
141           }
142 
143 #ifdef O_TMPFILE
144           fd = open(tmp_name, O_TMPFILE | O_EXCL | O_RDWR | O_NOATIME | O_CLOEXEC, S_IRUSR | S_IWUSR);
145           if (fd != -1)
146                     return fd;
147 #endif
148 
149           if (tmp_name_len + 7 >= sizeof(tmp_name))
150           {
151                     return -1;
152           }
153 
154           strcpy(tmp_name + tmp_name_len, "/XXXXXX");
155           fd = mkostemp(tmp_name, O_CLOEXEC | O_NOATIME);
156 
157           if (fd == -1)
158                     return fd;
159 
160           if (unlink(tmp_name)) {
161                     close(fd);
162                     return -1;
163           }
164 
165           return fd;
166 }
167 
alloc_chunk(sljit_uw size)168 static SLJIT_INLINE struct chunk_header* alloc_chunk(sljit_uw size)
169 {
170           struct chunk_header *retval;
171           int fd;
172 
173           fd = create_tempfile();
174           if (fd == -1)
175                     return NULL;
176 
177           if (ftruncate(fd, size)) {
178                     close(fd);
179                     return NULL;
180           }
181 
182           retval = (struct chunk_header *)mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
183 
184           if (retval == MAP_FAILED) {
185                     close(fd);
186                     return NULL;
187           }
188 
189           retval->executable = mmap(NULL, size, PROT_READ | PROT_EXEC, MAP_SHARED, fd, 0);
190 
191           if (retval->executable == MAP_FAILED) {
192                     munmap(retval, size);
193                     close(fd);
194                     return NULL;
195           }
196 
197           retval->fd = fd;
198           return retval;
199 }
200 
free_chunk(void * chunk,sljit_uw size)201 static SLJIT_INLINE void free_chunk(void *chunk, sljit_uw size)
202 {
203           struct chunk_header *header = ((struct chunk_header *)chunk) - 1;
204 
205           int fd = header->fd;
206           munmap(header->executable, size);
207           munmap(header, size);
208           close(fd);
209 }
210 
211 /* --------------------------------------------------------------------- */
212 /*  Common functions                                                     */
213 /* --------------------------------------------------------------------- */
214 
215 #define CHUNK_MASK  (~(CHUNK_SIZE - 1))
216 
217 struct block_header {
218           sljit_uw size;
219           sljit_uw prev_size;
220           sljit_sw executable_offset;
221 };
222 
223 struct free_block {
224           struct block_header header;
225           struct free_block *next;
226           struct free_block *prev;
227           sljit_uw size;
228 };
229 
230 #define AS_BLOCK_HEADER(base, offset) \
231           ((struct block_header*)(((sljit_u8*)base) + offset))
232 #define AS_FREE_BLOCK(base, offset) \
233           ((struct free_block*)(((sljit_u8*)base) + offset))
234 #define MEM_START(base)                 ((void*)((base) + 1))
235 #define ALIGN_SIZE(size)      (((size) + sizeof(struct block_header) + 7) & ~7)
236 
237 static struct free_block* free_blocks;
238 static sljit_uw allocated_size;
239 static sljit_uw total_size;
240 
sljit_insert_free_block(struct free_block * free_block,sljit_uw size)241 static SLJIT_INLINE void sljit_insert_free_block(struct free_block *free_block, sljit_uw size)
242 {
243           free_block->header.size = 0;
244           free_block->size = size;
245 
246           free_block->next = free_blocks;
247           free_block->prev = NULL;
248           if (free_blocks)
249                     free_blocks->prev = free_block;
250           free_blocks = free_block;
251 }
252 
sljit_remove_free_block(struct free_block * free_block)253 static SLJIT_INLINE void sljit_remove_free_block(struct free_block *free_block)
254 {
255           if (free_block->next)
256                     free_block->next->prev = free_block->prev;
257 
258           if (free_block->prev)
259                     free_block->prev->next = free_block->next;
260           else {
261                     SLJIT_ASSERT(free_blocks == free_block);
262                     free_blocks = free_block->next;
263           }
264 }
265 
sljit_malloc_exec(sljit_uw size)266 SLJIT_API_FUNC_ATTRIBUTE void* sljit_malloc_exec(sljit_uw size)
267 {
268           struct chunk_header *chunk_header;
269           struct block_header *header;
270           struct block_header *next_header;
271           struct free_block *free_block;
272           sljit_uw chunk_size;
273           sljit_sw executable_offset;
274 
275           allocator_grab_lock();
276           if (size < (64 - sizeof(struct block_header)))
277                     size = (64 - sizeof(struct block_header));
278           size = ALIGN_SIZE(size);
279 
280           free_block = free_blocks;
281           while (free_block) {
282                     if (free_block->size >= size) {
283                               chunk_size = free_block->size;
284                               if (chunk_size > size + 64) {
285                                         /* We just cut a block from the end of the free block. */
286                                         chunk_size -= size;
287                                         free_block->size = chunk_size;
288                                         header = AS_BLOCK_HEADER(free_block, chunk_size);
289                                         header->prev_size = chunk_size;
290                                         header->executable_offset = free_block->header.executable_offset;
291                                         AS_BLOCK_HEADER(header, size)->prev_size = size;
292                               }
293                               else {
294                                         sljit_remove_free_block(free_block);
295                                         header = (struct block_header*)free_block;
296                                         size = chunk_size;
297                               }
298                               allocated_size += size;
299                               header->size = size;
300                               allocator_release_lock();
301                               return MEM_START(header);
302                     }
303                     free_block = free_block->next;
304           }
305 
306           chunk_size = sizeof(struct chunk_header) + sizeof(struct block_header);
307           chunk_size = (chunk_size + size + CHUNK_SIZE - 1) & CHUNK_MASK;
308 
309           chunk_header = alloc_chunk(chunk_size);
310           if (!chunk_header) {
311                     allocator_release_lock();
312                     return NULL;
313           }
314 
315           executable_offset = (sljit_sw)((sljit_u8*)chunk_header->executable - (sljit_u8*)chunk_header);
316 
317           chunk_size -= sizeof(struct chunk_header) + sizeof(struct block_header);
318           total_size += chunk_size;
319 
320           header = (struct block_header *)(chunk_header + 1);
321 
322           header->prev_size = 0;
323           header->executable_offset = executable_offset;
324           if (chunk_size > size + 64) {
325                     /* Cut the allocated space into a free and a used block. */
326                     allocated_size += size;
327                     header->size = size;
328                     chunk_size -= size;
329 
330                     free_block = AS_FREE_BLOCK(header, size);
331                     free_block->header.prev_size = size;
332                     free_block->header.executable_offset = executable_offset;
333                     sljit_insert_free_block(free_block, chunk_size);
334                     next_header = AS_BLOCK_HEADER(free_block, chunk_size);
335           }
336           else {
337                     /* All space belongs to this allocation. */
338                     allocated_size += chunk_size;
339                     header->size = chunk_size;
340                     next_header = AS_BLOCK_HEADER(header, chunk_size);
341           }
342           next_header->size = 1;
343           next_header->prev_size = chunk_size;
344           next_header->executable_offset = executable_offset;
345           allocator_release_lock();
346           return MEM_START(header);
347 }
348 
sljit_free_exec(void * ptr)349 SLJIT_API_FUNC_ATTRIBUTE void sljit_free_exec(void* ptr)
350 {
351           struct block_header *header;
352           struct free_block* free_block;
353 
354           allocator_grab_lock();
355           header = AS_BLOCK_HEADER(ptr, -(sljit_sw)sizeof(struct block_header));
356           header = AS_BLOCK_HEADER(header, -header->executable_offset);
357           allocated_size -= header->size;
358 
359           /* Connecting free blocks together if possible. */
360 
361           /* If header->prev_size == 0, free_block will equal to header.
362              In this case, free_block->header.size will be > 0. */
363           free_block = AS_FREE_BLOCK(header, -(sljit_sw)header->prev_size);
364           if (SLJIT_UNLIKELY(!free_block->header.size)) {
365                     free_block->size += header->size;
366                     header = AS_BLOCK_HEADER(free_block, free_block->size);
367                     header->prev_size = free_block->size;
368           }
369           else {
370                     free_block = (struct free_block*)header;
371                     sljit_insert_free_block(free_block, header->size);
372           }
373 
374           header = AS_BLOCK_HEADER(free_block, free_block->size);
375           if (SLJIT_UNLIKELY(!header->size)) {
376                     free_block->size += ((struct free_block*)header)->size;
377                     sljit_remove_free_block((struct free_block*)header);
378                     header = AS_BLOCK_HEADER(free_block, free_block->size);
379                     header->prev_size = free_block->size;
380           }
381 
382           /* The whole chunk is free. */
383           if (SLJIT_UNLIKELY(!free_block->header.prev_size && header->size == 1)) {
384                     /* If this block is freed, we still have (allocated_size / 2) free space. */
385                     if (total_size - free_block->size > (allocated_size * 3 / 2)) {
386                               total_size -= free_block->size;
387                               sljit_remove_free_block(free_block);
388                               free_chunk(free_block, free_block->size + sizeof(struct block_header));
389                     }
390           }
391 
392           allocator_release_lock();
393 }
394 
sljit_free_unused_memory_exec(void)395 SLJIT_API_FUNC_ATTRIBUTE void sljit_free_unused_memory_exec(void)
396 {
397           struct free_block* free_block;
398           struct free_block* next_free_block;
399 
400           allocator_grab_lock();
401 
402           free_block = free_blocks;
403           while (free_block) {
404                     next_free_block = free_block->next;
405                     if (!free_block->header.prev_size &&
406                                         AS_BLOCK_HEADER(free_block, free_block->size)->size == 1) {
407                               total_size -= free_block->size;
408                               sljit_remove_free_block(free_block);
409                               free_chunk(free_block, free_block->size + sizeof(struct block_header));
410                     }
411                     free_block = next_free_block;
412           }
413 
414           SLJIT_ASSERT((total_size && free_blocks) || (!total_size && !free_blocks));
415           allocator_release_lock();
416 }
417 
sljit_exec_offset(void * ptr)418 SLJIT_API_FUNC_ATTRIBUTE sljit_sw sljit_exec_offset(void* ptr)
419 {
420           return ((struct block_header *)(ptr))[-1].executable_offset;
421 }
422