1 /* -----------------------------------------------------------------------
2 tramp.c - Copyright (c) 2020 Madhavan T. Venkataraman
4 API and support functions for managing statically defined closure
7 Permission is hereby granted, free of charge, to any person obtaining
8 a copy of this software and associated documentation files (the
9 ``Software''), to deal in the Software without restriction, including
10 without limitation the rights to use, copy, modify, merge, publish,
11 distribute, sublicense, and/or sell copies of the Software, and to
12 permit persons to whom the Software is furnished to do so, subject to
13 the following conditions:
15 The above copyright notice and this permission notice shall be included
16 in all copies or substantial portions of the Software.
18 THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND,
19 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
22 HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
23 WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
25 DEALINGS IN THE SOFTWARE.
26 ----------------------------------------------------------------------- */
28 #include <fficonfig.h>
30 #ifdef FFI_EXEC_STATIC_TRAMP
32 /* -------------------------- Headers and Definitions ---------------------*/
34 * Add support for other OSes later. For now, it is just Linux.
50 #include <linux/limits.h>
51 #include <linux/types.h>
53 #endif /* __linux__ */
56 * Each architecture defines static code for a trampoline code table. The
57 * trampoline code table is mapped into the address space of a process.
59 * The following architecture specific function returns:
61 * - the address of the trampoline code table in the text segment
62 * - the size of each trampoline in the trampoline code table
63 * - the size of the mapping for the whole trampoline code table
65 void __attribute__((weak
)) *ffi_tramp_arch (size_t *tramp_size
,
68 /* ------------------------- Trampoline Data Structures --------------------*/
73 * Trampoline table. Manages one trampoline code table and one trampoline
76 * prev, next Links in the global trampoline table list.
77 * code_table Trampoline code table mapping.
78 * parm_table Trampoline parameter table mapping.
79 * array Array of trampolines malloced.
80 * free List of free trampolines.
81 * nfree Number of free trampolines.
85 struct tramp_table
*prev
;
86 struct tramp_table
*next
;
95 * Parameters for each trampoline.
98 * Data for the target code that the trampoline jumps to.
100 * Target code that the trampoline jumps to.
109 * Trampoline structure for each trampoline.
111 * prev, next Links in the trampoline free list of a trampoline table.
112 * table Trampoline table to which this trampoline belongs.
113 * code Address of this trampoline in the code table mapping.
114 * parm Address of this trampoline's parameters in the parameter
121 struct tramp_table
*table
;
123 struct tramp_parm
*parm
;
126 enum tramp_globals_status
{
127 TRAMP_GLOBALS_UNINITIALIZED
= 0,
128 TRAMP_GLOBALS_PASSED
,
129 TRAMP_GLOBALS_FAILED
,
133 * Trampoline globals.
136 * File descriptor of binary file that contains the trampoline code table.
138 * Offset of the trampoline code table in that file.
140 * Address of the trampoline code table in the text segment.
142 * Size of the trampoline code table mapping.
144 * Size of one trampoline in the trampoline code table.
146 * Total number of trampolines in the trampoline code table.
148 * List of trampoline tables that contain free trampolines.
150 * Number of trampoline tables that contain free trampolines.
152 * Initialization status.
162 struct tramp_table
*free_tables
;
164 enum tramp_globals_status status
;
167 static struct tramp_globals tramp_globals
;
169 /* --------------------- Trampoline File Initialization --------------------*/
172 * The trampoline file is the file used to map the trampoline code table into
173 * the address space of a process. There are two ways to get this file:
175 * - From the OS. E.g., on Linux, /proc/<pid>/maps lists all the memory
176 * mappings for <pid>. For file-backed mappings, maps supplies the file name
177 * and the file offset. Using this, we can locate the mapping that maps
178 * libffi and get the path to the libffi binary. And, we can compute the
179 * offset of the trampoline code table within that binary.
181 * - Else, if we can create a temporary file, we can write the trampoline code
182 * table from the text segment into the temporary file.
184 * The first method is the preferred one. If the OS security subsystem
185 * disallows mapping unsigned files with PROT_EXEC, then the second method
188 * If an OS allows the trampoline code table in the text segment to be
189 * directly remapped (e.g., MACH vm_remap ()), then we don't need the
192 static int tramp_table_alloc (void);
194 #if defined __linux__
197 ffi_tramp_get_libffi (void)
200 char file
[PATH_MAX
], line
[PATH_MAX
+100], perm
[10], dev
[10];
201 unsigned long start
, end
, offset
, inode
;
202 uintptr_t addr
= (uintptr_t) tramp_globals
.text
;
205 snprintf (file
, PATH_MAX
, "/proc/%d/maps", getpid());
206 fp
= fopen (file
, "r");
211 while (feof (fp
) == 0) {
212 if (fgets (line
, sizeof (line
), fp
) == 0)
215 nfields
= sscanf (line
, "%lx-%lx %9s %lx %9s %ld %s",
216 &start
, &end
, perm
, &offset
, dev
, &inode
, file
);
220 if (addr
>= start
&& addr
< end
) {
221 tramp_globals
.offset
= offset
+ (addr
- start
);
231 tramp_globals
.fd
= open (file
, O_RDONLY
);
232 if (tramp_globals
.fd
== -1)
236 * Allocate a trampoline table just to make sure that the trampoline code
237 * table can be mapped.
239 if (!tramp_table_alloc ())
241 close (tramp_globals
.fd
);
242 tramp_globals
.fd
= -1;
248 #endif /* __linux__ */
250 #if defined __linux__
252 #if defined HAVE_MKSTEMP
255 ffi_tramp_get_temp_file (void)
257 char template[12] = "/tmp/XXXXXX";
260 tramp_globals
.offset
= 0;
261 tramp_globals
.fd
= mkstemp (template);
262 if (tramp_globals
.fd
== -1)
267 * Write the trampoline code table into the temporary file and allocate a
268 * trampoline table to make sure that the temporary file can be mapped.
270 count
= write(tramp_globals
.fd
, tramp_globals
.text
, tramp_globals
.map_size
);
271 if (count
== tramp_globals
.map_size
&& tramp_table_alloc ())
274 close (tramp_globals
.fd
);
275 tramp_globals
.fd
= -1;
279 #else /* !defined HAVE_MKSTEMP */
283 * src/closures.c contains code for finding temp file that has EXEC
284 * permissions. May be, some of that code can be shared with static
288 ffi_tramp_get_temp_file (void)
290 tramp_globals
.offset
= 0;
291 tramp_globals
.fd
= -1;
295 #endif /* defined HAVE_MKSTEMP */
297 #endif /* __linux__ */
299 /* ------------------------ OS-specific Initialization ----------------------*/
301 #if defined __linux__
304 ffi_tramp_init_os (void)
306 if (ffi_tramp_get_libffi ())
308 return ffi_tramp_get_temp_file ();
311 #endif /* __linux__ */
313 /* --------------------------- OS-specific Locking -------------------------*/
315 #if defined __linux__
317 static pthread_mutex_t tramp_globals_mutex
= PTHREAD_MUTEX_INITIALIZER
;
322 pthread_mutex_lock (&tramp_globals_mutex
);
328 pthread_mutex_unlock (&tramp_globals_mutex
);
331 #endif /* __linux__ */
333 /* ------------------------ OS-specific Memory Mapping ----------------------*/
336 * Create a trampoline code table mapping and a trampoline parameter table
337 * mapping. The two mappings must be adjacent to each other for PC-relative
340 * For each trampoline in the code table, there is a corresponding parameter
341 * block in the parameter table. The size of the parameter block is the same
342 * as the size of the trampoline. This means that the parameter block is at
343 * a fixed offset from its trampoline making it easy for a trampoline to find
344 * its parameters using PC-relative access.
346 * The parameter block will contain a struct tramp_parm. This means that
347 * sizeof (struct tramp_parm) cannot exceed the size of a parameter block.
350 #if defined __linux__
353 tramp_table_map (struct tramp_table
*table
)
358 * Create an anonymous mapping twice the map size. The top half will be used
359 * for the code table. The bottom half will be used for the parameter table.
361 addr
= mmap (NULL
, tramp_globals
.map_size
* 2, PROT_READ
| PROT_WRITE
,
362 MAP_PRIVATE
| MAP_ANONYMOUS
, -1, 0);
363 if (addr
== MAP_FAILED
)
367 * Replace the top half of the anonymous mapping with the code table mapping.
369 table
->code_table
= mmap (addr
, tramp_globals
.map_size
, PROT_READ
| PROT_EXEC
,
370 MAP_PRIVATE
| MAP_FIXED
, tramp_globals
.fd
, tramp_globals
.offset
);
371 if (table
->code_table
== MAP_FAILED
)
373 (void) munmap (addr
, tramp_globals
.map_size
* 2);
376 table
->parm_table
= table
->code_table
+ tramp_globals
.map_size
;
381 tramp_table_unmap (struct tramp_table
*table
)
383 (void) munmap (table
->code_table
, tramp_globals
.map_size
);
384 (void) munmap (table
->parm_table
, tramp_globals
.map_size
);
387 #endif /* __linux__ */
389 /* ------------------------ Trampoline Initialization ----------------------*/
392 * Initialize the static trampoline feature.
395 ffi_tramp_init (void)
397 if (tramp_globals
.status
== TRAMP_GLOBALS_PASSED
)
400 if (tramp_globals
.status
== TRAMP_GLOBALS_FAILED
)
403 if (ffi_tramp_arch
== NULL
)
405 tramp_globals
.status
= TRAMP_GLOBALS_FAILED
;
409 tramp_globals
.free_tables
= NULL
;
410 tramp_globals
.nfree_tables
= 0;
413 * Get trampoline code table information from the architecture.
415 tramp_globals
.text
= ffi_tramp_arch (&tramp_globals
.size
,
416 &tramp_globals
.map_size
);
417 tramp_globals
.ntramp
= tramp_globals
.map_size
/ tramp_globals
.size
;
419 if (sysconf (_SC_PAGESIZE
) > tramp_globals
.map_size
)
422 if (ffi_tramp_init_os ())
424 tramp_globals
.status
= TRAMP_GLOBALS_PASSED
;
428 tramp_globals
.status
= TRAMP_GLOBALS_FAILED
;
432 /* ---------------------- Trampoline Table functions ---------------------- */
434 /* This code assumes that malloc () is available on all OSes. */
436 static void tramp_add (struct tramp
*tramp
);
439 * Allocate and initialize a trampoline table.
442 tramp_table_alloc (void)
444 struct tramp_table
*table
;
445 struct tramp
*tramp_array
, *tramp
;
451 * If we already have tables with free trampolines, there is no need to
452 * allocate a new table.
454 if (tramp_globals
.nfree_tables
> 0)
458 * Allocate a new trampoline table structure.
460 table
= malloc (sizeof (*table
));
465 * Allocate new trampoline structures.
467 tramp_array
= malloc (sizeof (*tramp
) * tramp_globals
.ntramp
);
468 if (tramp_array
== NULL
)
472 * Map a code table and a parameter table into the caller's address space.
474 if (!tramp_table_map (table
))
477 * Failed to map the code and parameter tables.
479 goto free_tramp_array
;
483 * Initialize the trampoline table.
485 table
->array
= tramp_array
;
490 * Populate the trampoline table free list. This will also add the trampoline
491 * table to the global list of trampoline tables.
493 size
= tramp_globals
.size
;
494 code
= table
->code_table
;
495 parm
= table
->parm_table
;
496 for (i
= 0; i
< tramp_globals
.ntramp
; i
++)
498 tramp
= &tramp_array
[i
];
499 tramp
->table
= table
;
501 tramp
->parm
= (struct tramp_parm
*) parm
;
519 * Free a trampoline table.
522 tramp_table_free (struct tramp_table
*table
)
524 tramp_table_unmap (table
);
530 * Add a new trampoline table to the global table list.
533 tramp_table_add (struct tramp_table
*table
)
535 table
->next
= tramp_globals
.free_tables
;
537 if (tramp_globals
.free_tables
!= NULL
)
538 tramp_globals
.free_tables
->prev
= table
;
539 tramp_globals
.free_tables
= table
;
540 tramp_globals
.nfree_tables
++;
544 * Delete a trampoline table from the global table list.
547 tramp_table_del (struct tramp_table
*table
)
549 tramp_globals
.nfree_tables
--;
550 if (table
->prev
!= NULL
)
551 table
->prev
->next
= table
->next
;
552 if (table
->next
!= NULL
)
553 table
->next
->prev
= table
->prev
;
554 if (tramp_globals
.free_tables
== table
)
555 tramp_globals
.free_tables
= table
->next
;
558 /* ------------------------- Trampoline functions ------------------------- */
561 * Add a trampoline to its trampoline table.
564 tramp_add (struct tramp
*tramp
)
566 struct tramp_table
*table
= tramp
->table
;
568 tramp
->next
= table
->free
;
570 if (table
->free
!= NULL
)
571 table
->free
->prev
= tramp
;
575 if (table
->nfree
== 1)
576 tramp_table_add (table
);
579 * We don't want to keep too many free trampoline tables lying around.
581 if (table
->nfree
== tramp_globals
.ntramp
&&
582 tramp_globals
.nfree_tables
> 1)
584 tramp_table_del (table
);
585 tramp_table_free (table
);
590 * Remove a trampoline from its trampoline table.
593 tramp_del (struct tramp
*tramp
)
595 struct tramp_table
*table
= tramp
->table
;
598 if (tramp
->prev
!= NULL
)
599 tramp
->prev
->next
= tramp
->next
;
600 if (tramp
->next
!= NULL
)
601 tramp
->next
->prev
= tramp
->prev
;
602 if (table
->free
== tramp
)
603 table
->free
= tramp
->next
;
605 if (table
->nfree
== 0)
606 tramp_table_del (table
);
609 /* ------------------------ Trampoline API functions ------------------------ */
612 ffi_tramp_is_supported(void)
617 ret
= ffi_tramp_init ();
623 * Allocate a trampoline and return its opaque address.
626 ffi_tramp_alloc (int flags
)
632 if (!ffi_tramp_init () || flags
!= 0)
638 if (!tramp_table_alloc ())
644 tramp
= tramp_globals
.free_tables
->free
;
653 * Set the parameters for a trampoline.
656 ffi_tramp_set_parms (void *arg
, void *target
, void *data
)
658 struct tramp
*tramp
= arg
;
661 tramp
->parm
->target
= target
;
662 tramp
->parm
->data
= data
;
667 * Get the invocation address of a trampoline.
670 ffi_tramp_get_addr (void *arg
)
672 struct tramp
*tramp
= arg
;
686 ffi_tramp_free (void *arg
)
688 struct tramp
*tramp
= arg
;
695 /* ------------------------------------------------------------------------- */
697 #else /* !FFI_EXEC_STATIC_TRAMP */
702 ffi_tramp_is_supported(void)
708 ffi_tramp_alloc (int flags
)
714 ffi_tramp_set_parms (void *arg
, void *target
, void *data
)
719 ffi_tramp_get_addr (void *arg
)
725 ffi_tramp_free (void *arg
)
729 #endif /* FFI_EXEC_STATIC_TRAMP */