| /* ----------------------------------------------------------------------- | |
| tramp.c - Copyright (c) 2020 Madhavan T. Venkataraman | |
| API and support functions for managing statically defined closure | |
| trampolines. | |
| Permission is hereby granted, free of charge, to any person obtaining | |
| a copy of this software and associated documentation files (the | |
| ``Software''), to deal in the Software without restriction, including | |
| without limitation the rights to use, copy, modify, merge, publish, | |
| distribute, sublicense, and/or sell copies of the Software, and to | |
| permit persons to whom the Software is furnished to do so, subject to | |
| the following conditions: | |
| The above copyright notice and this permission notice shall be included | |
| in all copies or substantial portions of the Software. | |
| THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND, | |
| EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
| MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |
| NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT | |
| HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | |
| WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER | |
| DEALINGS IN THE SOFTWARE. | |
| ----------------------------------------------------------------------- */ | |
| #include <fficonfig.h> | |
| #ifdef FFI_EXEC_STATIC_TRAMP | |
| /* -------------------------- Headers and Definitions ---------------------*/ | |
| /* | |
| * Add support for other OSes later. For now, it is just Linux. | |
| */ | |
| #if defined __linux__ | |
| #ifdef __linux__ | |
| #define _GNU_SOURCE 1 | |
| #endif | |
| #include <stdio.h> | |
| #include <unistd.h> | |
| #include <stdlib.h> | |
| #include <stdint.h> | |
| #include <fcntl.h> | |
| #include <pthread.h> | |
| #include <sys/mman.h> | |
| #include <tramp.h> | |
| #ifdef __linux__ | |
| #include <linux/limits.h> | |
| #include <linux/types.h> | |
| #endif | |
| #endif /* __linux__ */ | |
| /* | |
| * Each architecture defines static code for a trampoline code table. The | |
| * trampoline code table is mapped into the address space of a process. | |
| * | |
| * The following architecture specific function returns: | |
| * | |
| * - the address of the trampoline code table in the text segment | |
| * - the size of each trampoline in the trampoline code table | |
| * - the size of the mapping for the whole trampoline code table | |
| */ | |
| void __attribute__((weak)) *ffi_tramp_arch (size_t *tramp_size, | |
| size_t *map_size); | |
| /* ------------------------- Trampoline Data Structures --------------------*/ | |
| struct tramp; | |
| /* | |
| * Trampoline table. Manages one trampoline code table and one trampoline | |
| * parameter table. | |
| * | |
| * prev, next Links in the global trampoline table list. | |
| * code_table Trampoline code table mapping. | |
| * parm_table Trampoline parameter table mapping. | |
| * array Array of trampolines malloced. | |
| * free List of free trampolines. | |
| * nfree Number of free trampolines. | |
| */ | |
| struct tramp_table | |
| { | |
| struct tramp_table *prev; | |
| struct tramp_table *next; | |
| void *code_table; | |
| void *parm_table; | |
| struct tramp *array; | |
| struct tramp *free; | |
| int nfree; | |
| }; | |
| /* | |
| * Parameters for each trampoline. | |
| * | |
| * data | |
| * Data for the target code that the trampoline jumps to. | |
| * target | |
| * Target code that the trampoline jumps to. | |
| */ | |
| struct tramp_parm | |
| { | |
| void *data; | |
| void *target; | |
| }; | |
| /* | |
| * Trampoline structure for each trampoline. | |
| * | |
| * prev, next Links in the trampoline free list of a trampoline table. | |
| * table Trampoline table to which this trampoline belongs. | |
| * code Address of this trampoline in the code table mapping. | |
| * parm Address of this trampoline's parameters in the parameter | |
| * table mapping. | |
| */ | |
| struct tramp | |
| { | |
| struct tramp *prev; | |
| struct tramp *next; | |
| struct tramp_table *table; | |
| void *code; | |
| struct tramp_parm *parm; | |
| }; | |
| enum tramp_globals_status { | |
| TRAMP_GLOBALS_UNINITIALIZED = 0, | |
| TRAMP_GLOBALS_PASSED, | |
| TRAMP_GLOBALS_FAILED, | |
| }; | |
| /* | |
| * Trampoline globals. | |
| * | |
| * fd | |
| * File descriptor of binary file that contains the trampoline code table. | |
| * offset | |
| * Offset of the trampoline code table in that file. | |
| * text | |
| * Address of the trampoline code table in the text segment. | |
| * map_size | |
| * Size of the trampoline code table mapping. | |
| * size | |
| * Size of one trampoline in the trampoline code table. | |
| * ntramp | |
| * Total number of trampolines in the trampoline code table. | |
| * free_tables | |
| * List of trampoline tables that contain free trampolines. | |
| * nfree_tables | |
| * Number of trampoline tables that contain free trampolines. | |
| * status | |
| * Initialization status. | |
| */ | |
| struct tramp_globals | |
| { | |
| int fd; | |
| off_t offset; | |
| void *text; | |
| size_t map_size; | |
| size_t size; | |
| int ntramp; | |
| struct tramp_table *free_tables; | |
| int nfree_tables; | |
| enum tramp_globals_status status; | |
| }; | |
| static struct tramp_globals tramp_globals; | |
| /* --------------------- Trampoline File Initialization --------------------*/ | |
| /* | |
| * The trampoline file is the file used to map the trampoline code table into | |
| * the address space of a process. There are two ways to get this file: | |
| * | |
| * - From the OS. E.g., on Linux, /proc/<pid>/maps lists all the memory | |
| * mappings for <pid>. For file-backed mappings, maps supplies the file name | |
| * and the file offset. Using this, we can locate the mapping that maps | |
| * libffi and get the path to the libffi binary. And, we can compute the | |
| * offset of the trampoline code table within that binary. | |
| * | |
| * - Else, if we can create a temporary file, we can write the trampoline code | |
| * table from the text segment into the temporary file. | |
| * | |
| * The first method is the preferred one. If the OS security subsystem | |
| * disallows mapping unsigned files with PROT_EXEC, then the second method | |
| * will fail. | |
| * | |
| * If an OS allows the trampoline code table in the text segment to be | |
| * directly remapped (e.g., MACH vm_remap ()), then we don't need the | |
| * trampoline file. | |
| */ | |
| static int tramp_table_alloc (void); | |
| #if defined __linux__ | |
| static int | |
| ffi_tramp_get_libffi (void) | |
| { | |
| FILE *fp; | |
| char file[PATH_MAX], line[PATH_MAX+100], perm[10], dev[10]; | |
| unsigned long start, end, offset, inode; | |
| uintptr_t addr = (uintptr_t) tramp_globals.text; | |
| int nfields, found; | |
| snprintf (file, PATH_MAX, "/proc/%d/maps", getpid()); | |
| fp = fopen (file, "r"); | |
| if (fp == NULL) | |
| return 0; | |
| found = 0; | |
| while (feof (fp) == 0) { | |
| if (fgets (line, sizeof (line), fp) == 0) | |
| break; | |
| nfields = sscanf (line, "%lx-%lx %9s %lx %9s %ld %s", | |
| &start, &end, perm, &offset, dev, &inode, file); | |
| if (nfields != 7) | |
| continue; | |
| if (addr >= start && addr < end) { | |
| tramp_globals.offset = offset + (addr - start); | |
| found = 1; | |
| break; | |
| } | |
| } | |
| fclose (fp); | |
| if (!found) | |
| return 0; | |
| tramp_globals.fd = open (file, O_RDONLY); | |
| if (tramp_globals.fd == -1) | |
| return 0; | |
| /* | |
| * Allocate a trampoline table just to make sure that the trampoline code | |
| * table can be mapped. | |
| */ | |
| if (!tramp_table_alloc ()) | |
| { | |
| close (tramp_globals.fd); | |
| tramp_globals.fd = -1; | |
| return 0; | |
| } | |
| return 1; | |
| } | |
| #endif /* __linux__ */ | |
| #if defined __linux__ | |
| #if defined HAVE_MKSTEMP | |
| static int | |
| ffi_tramp_get_temp_file (void) | |
| { | |
| char template[12] = "/tmp/XXXXXX"; | |
| ssize_t count; | |
| tramp_globals.offset = 0; | |
| tramp_globals.fd = mkstemp (template); | |
| if (tramp_globals.fd == -1) | |
| return 0; | |
| unlink (template); | |
| /* | |
| * Write the trampoline code table into the temporary file and allocate a | |
| * trampoline table to make sure that the temporary file can be mapped. | |
| */ | |
| count = write(tramp_globals.fd, tramp_globals.text, tramp_globals.map_size); | |
| if (count == tramp_globals.map_size && tramp_table_alloc ()) | |
| return 1; | |
| close (tramp_globals.fd); | |
| tramp_globals.fd = -1; | |
| return 0; | |
| } | |
| #else /* !defined HAVE_MKSTEMP */ | |
| /* | |
| * TODO: | |
| * src/closures.c contains code for finding temp file that has EXEC | |
| * permissions. May be, some of that code can be shared with static | |
| * trampolines. | |
| */ | |
| static int | |
| ffi_tramp_get_temp_file (void) | |
| { | |
| tramp_globals.offset = 0; | |
| tramp_globals.fd = -1; | |
| return 0; | |
| } | |
| #endif /* defined HAVE_MKSTEMP */ | |
| #endif /* __linux__ */ | |
| /* ------------------------ OS-specific Initialization ----------------------*/ | |
| #if defined __linux__ | |
| static int | |
| ffi_tramp_init_os (void) | |
| { | |
| if (ffi_tramp_get_libffi ()) | |
| return 1; | |
| return ffi_tramp_get_temp_file (); | |
| } | |
| #endif /* __linux__ */ | |
| /* --------------------------- OS-specific Locking -------------------------*/ | |
| #if defined __linux__ | |
| static pthread_mutex_t tramp_globals_mutex = PTHREAD_MUTEX_INITIALIZER; | |
| static void | |
| ffi_tramp_lock(void) | |
| { | |
| pthread_mutex_lock (&tramp_globals_mutex); | |
| } | |
| static void | |
| ffi_tramp_unlock() | |
| { | |
| pthread_mutex_unlock (&tramp_globals_mutex); | |
| } | |
| #endif /* __linux__ */ | |
| /* ------------------------ OS-specific Memory Mapping ----------------------*/ | |
| /* | |
| * Create a trampoline code table mapping and a trampoline parameter table | |
| * mapping. The two mappings must be adjacent to each other for PC-relative | |
| * access. | |
| * | |
| * For each trampoline in the code table, there is a corresponding parameter | |
| * block in the parameter table. The size of the parameter block is the same | |
| * as the size of the trampoline. This means that the parameter block is at | |
| * a fixed offset from its trampoline making it easy for a trampoline to find | |
| * its parameters using PC-relative access. | |
| * | |
| * The parameter block will contain a struct tramp_parm. This means that | |
| * sizeof (struct tramp_parm) cannot exceed the size of a parameter block. | |
| */ | |
| #if defined __linux__ | |
| static int | |
| tramp_table_map (struct tramp_table *table) | |
| { | |
| char *addr; | |
| /* | |
| * Create an anonymous mapping twice the map size. The top half will be used | |
| * for the code table. The bottom half will be used for the parameter table. | |
| */ | |
| addr = mmap (NULL, tramp_globals.map_size * 2, PROT_READ | PROT_WRITE, | |
| MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); | |
| if (addr == MAP_FAILED) | |
| return 0; | |
| /* | |
| * Replace the top half of the anonymous mapping with the code table mapping. | |
| */ | |
| table->code_table = mmap (addr, tramp_globals.map_size, PROT_READ | PROT_EXEC, | |
| MAP_PRIVATE | MAP_FIXED, tramp_globals.fd, tramp_globals.offset); | |
| if (table->code_table == MAP_FAILED) | |
| { | |
| (void) munmap (addr, tramp_globals.map_size * 2); | |
| return 0; | |
| } | |
| table->parm_table = table->code_table + tramp_globals.map_size; | |
| return 1; | |
| } | |
| static void | |
| tramp_table_unmap (struct tramp_table *table) | |
| { | |
| (void) munmap (table->code_table, tramp_globals.map_size); | |
| (void) munmap (table->parm_table, tramp_globals.map_size); | |
| } | |
| #endif /* __linux__ */ | |
| /* ------------------------ Trampoline Initialization ----------------------*/ | |
| /* | |
| * Initialize the static trampoline feature. | |
| */ | |
| static int | |
| ffi_tramp_init (void) | |
| { | |
| if (tramp_globals.status == TRAMP_GLOBALS_PASSED) | |
| return 1; | |
| if (tramp_globals.status == TRAMP_GLOBALS_FAILED) | |
| return 0; | |
| if (ffi_tramp_arch == NULL) | |
| { | |
| tramp_globals.status = TRAMP_GLOBALS_FAILED; | |
| return 0; | |
| } | |
| tramp_globals.free_tables = NULL; | |
| tramp_globals.nfree_tables = 0; | |
| /* | |
| * Get trampoline code table information from the architecture. | |
| */ | |
| tramp_globals.text = ffi_tramp_arch (&tramp_globals.size, | |
| &tramp_globals.map_size); | |
| tramp_globals.ntramp = tramp_globals.map_size / tramp_globals.size; | |
| if (sysconf (_SC_PAGESIZE) > tramp_globals.map_size) | |
| return 0; | |
| if (ffi_tramp_init_os ()) | |
| { | |
| tramp_globals.status = TRAMP_GLOBALS_PASSED; | |
| return 1; | |
| } | |
| tramp_globals.status = TRAMP_GLOBALS_FAILED; | |
| return 0; | |
| } | |
| /* ---------------------- Trampoline Table functions ---------------------- */ | |
| /* This code assumes that malloc () is available on all OSes. */ | |
| static void tramp_add (struct tramp *tramp); | |
| /* | |
| * Allocate and initialize a trampoline table. | |
| */ | |
| static int | |
| tramp_table_alloc (void) | |
| { | |
| struct tramp_table *table; | |
| struct tramp *tramp_array, *tramp; | |
| size_t size; | |
| char *code, *parm; | |
| int i; | |
| /* | |
| * If we already have tables with free trampolines, there is no need to | |
| * allocate a new table. | |
| */ | |
| if (tramp_globals.nfree_tables > 0) | |
| return 1; | |
| /* | |
| * Allocate a new trampoline table structure. | |
| */ | |
| table = malloc (sizeof (*table)); | |
| if (table == NULL) | |
| return 0; | |
| /* | |
| * Allocate new trampoline structures. | |
| */ | |
| tramp_array = malloc (sizeof (*tramp) * tramp_globals.ntramp); | |
| if (tramp_array == NULL) | |
| goto free_table; | |
| /* | |
| * Map a code table and a parameter table into the caller's address space. | |
| */ | |
| if (!tramp_table_map (table)) | |
| { | |
| /* | |
| * Failed to map the code and parameter tables. | |
| */ | |
| goto free_tramp_array; | |
| } | |
| /* | |
| * Initialize the trampoline table. | |
| */ | |
| table->array = tramp_array; | |
| table->free = NULL; | |
| table->nfree = 0; | |
| /* | |
| * Populate the trampoline table free list. This will also add the trampoline | |
| * table to the global list of trampoline tables. | |
| */ | |
| size = tramp_globals.size; | |
| code = table->code_table; | |
| parm = table->parm_table; | |
| for (i = 0; i < tramp_globals.ntramp; i++) | |
| { | |
| tramp = &tramp_array[i]; | |
| tramp->table = table; | |
| tramp->code = code; | |
| tramp->parm = (struct tramp_parm *) parm; | |
| tramp_add (tramp); | |
| code += size; | |
| parm += size; | |
| } | |
| /* Success */ | |
| return 1; | |
| /* Failure */ | |
| free_tramp_array: | |
| free (tramp_array); | |
| free_table: | |
| free (table); | |
| return 0; | |
| } | |
| /* | |
| * Free a trampoline table. | |
| */ | |
| static void | |
| tramp_table_free (struct tramp_table *table) | |
| { | |
| tramp_table_unmap (table); | |
| free (table->array); | |
| free (table); | |
| } | |
| /* | |
| * Add a new trampoline table to the global table list. | |
| */ | |
| static void | |
| tramp_table_add (struct tramp_table *table) | |
| { | |
| table->next = tramp_globals.free_tables; | |
| table->prev = NULL; | |
| if (tramp_globals.free_tables != NULL) | |
| tramp_globals.free_tables->prev = table; | |
| tramp_globals.free_tables = table; | |
| tramp_globals.nfree_tables++; | |
| } | |
| /* | |
| * Delete a trampoline table from the global table list. | |
| */ | |
| static void | |
| tramp_table_del (struct tramp_table *table) | |
| { | |
| tramp_globals.nfree_tables--; | |
| if (table->prev != NULL) | |
| table->prev->next = table->next; | |
| if (table->next != NULL) | |
| table->next->prev = table->prev; | |
| if (tramp_globals.free_tables == table) | |
| tramp_globals.free_tables = table->next; | |
| } | |
| /* ------------------------- Trampoline functions ------------------------- */ | |
| /* | |
| * Add a trampoline to its trampoline table. | |
| */ | |
| static void | |
| tramp_add (struct tramp *tramp) | |
| { | |
| struct tramp_table *table = tramp->table; | |
| tramp->next = table->free; | |
| tramp->prev = NULL; | |
| if (table->free != NULL) | |
| table->free->prev = tramp; | |
| table->free = tramp; | |
| table->nfree++; | |
| if (table->nfree == 1) | |
| tramp_table_add (table); | |
| /* | |
| * We don't want to keep too many free trampoline tables lying around. | |
| */ | |
| if (table->nfree == tramp_globals.ntramp && | |
| tramp_globals.nfree_tables > 1) | |
| { | |
| tramp_table_del (table); | |
| tramp_table_free (table); | |
| } | |
| } | |
| /* | |
| * Remove a trampoline from its trampoline table. | |
| */ | |
| static void | |
| tramp_del (struct tramp *tramp) | |
| { | |
| struct tramp_table *table = tramp->table; | |
| table->nfree--; | |
| if (tramp->prev != NULL) | |
| tramp->prev->next = tramp->next; | |
| if (tramp->next != NULL) | |
| tramp->next->prev = tramp->prev; | |
| if (table->free == tramp) | |
| table->free = tramp->next; | |
| if (table->nfree == 0) | |
| tramp_table_del (table); | |
| } | |
| /* ------------------------ Trampoline API functions ------------------------ */ | |
| int | |
| ffi_tramp_is_supported(void) | |
| { | |
| int ret; | |
| ffi_tramp_lock(); | |
| ret = ffi_tramp_init (); | |
| ffi_tramp_unlock(); | |
| return ret; | |
| } | |
| /* | |
| * Allocate a trampoline and return its opaque address. | |
| */ | |
| void * | |
| ffi_tramp_alloc (int flags) | |
| { | |
| struct tramp *tramp; | |
| ffi_tramp_lock(); | |
| if (!ffi_tramp_init () || flags != 0) | |
| { | |
| ffi_tramp_unlock(); | |
| return NULL; | |
| } | |
| if (!tramp_table_alloc ()) | |
| { | |
| ffi_tramp_unlock(); | |
| return NULL; | |
| } | |
| tramp = tramp_globals.free_tables->free; | |
| tramp_del (tramp); | |
| ffi_tramp_unlock(); | |
| return tramp; | |
| } | |
| /* | |
| * Set the parameters for a trampoline. | |
| */ | |
| void | |
| ffi_tramp_set_parms (void *arg, void *target, void *data) | |
| { | |
| struct tramp *tramp = arg; | |
| ffi_tramp_lock(); | |
| tramp->parm->target = target; | |
| tramp->parm->data = data; | |
| ffi_tramp_unlock(); | |
| } | |
| /* | |
| * Get the invocation address of a trampoline. | |
| */ | |
| void * | |
| ffi_tramp_get_addr (void *arg) | |
| { | |
| struct tramp *tramp = arg; | |
| void *addr; | |
| ffi_tramp_lock(); | |
| addr = tramp->code; | |
| ffi_tramp_unlock(); | |
| return addr; | |
| } | |
| /* | |
| * Free a trampoline. | |
| */ | |
| void | |
| ffi_tramp_free (void *arg) | |
| { | |
| struct tramp *tramp = arg; | |
| ffi_tramp_lock(); | |
| tramp_add (tramp); | |
| ffi_tramp_unlock(); | |
| } | |
| /* ------------------------------------------------------------------------- */ | |
| #else /* !FFI_EXEC_STATIC_TRAMP */ | |
| #include <stddef.h> | |
| int | |
| ffi_tramp_is_supported(void) | |
| { | |
| return 0; | |
| } | |
| void * | |
| ffi_tramp_alloc (int flags) | |
| { | |
| return NULL; | |
| } | |
| void | |
| ffi_tramp_set_parms (void *arg, void *target, void *data) | |
| { | |
| } | |
| void * | |
| ffi_tramp_get_addr (void *arg) | |
| { | |
| return NULL; | |
| } | |
| void | |
| ffi_tramp_free (void *arg) | |
| { | |
| } | |
| #endif /* FFI_EXEC_STATIC_TRAMP */ |