#include <fficonfig.h>
#ifdef FFI_EXEC_STATIC_TRAMP
#if defined (__linux__) || defined (__CYGWIN__)
#ifdef __linux__
#define _GNU_SOURCE 1
#endif
#include <ffi.h>
#include <ffi_common.h>
#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
#ifdef __CYGWIN__
#include <limits.h>
#endif
#endif
void __attribute__((weak)) *ffi_tramp_arch (size_t *tramp_size,
size_t *map_size);
struct tramp;
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;
};
struct tramp_parm
{
void *data;
void *target;
};
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,
};
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;
static int tramp_table_alloc (void);
#if defined (__linux__) || defined (__CYGWIN__)
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;
int open_flags = O_RDONLY;
#ifdef O_CLOEXEC
open_flags |= O_CLOEXEC;
#endif
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, open_flags);
if (tramp_globals.fd == -1)
return 0;
if (!tramp_table_alloc ())
{
close (tramp_globals.fd);
tramp_globals.fd = -1;
return 0;
}
return 1;
}
#endif
#if defined (__linux__) || defined (__CYGWIN__)
static int
ffi_tramp_get_temp_file (void)
{
ssize_t count;
tramp_globals.offset = 0;
tramp_globals.fd = open_temp_exec_file ();
count = write(tramp_globals.fd, tramp_globals.text, tramp_globals.map_size);
if (count >=0 && (size_t)count == tramp_globals.map_size && tramp_table_alloc ())
return 1;
close (tramp_globals.fd);
tramp_globals.fd = -1;
return 0;
}
#endif
#if defined (__linux__) || defined (__CYGWIN__)
static int
ffi_tramp_init_os (void)
{
if (ffi_tramp_get_libffi ())
return 1;
return ffi_tramp_get_temp_file ();
}
#endif
#if defined (__linux__) || defined (__CYGWIN__)
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(void)
{
pthread_mutex_unlock (&tramp_globals_mutex);
}
#endif
#if defined (__linux__) || defined (__CYGWIN__)
static int
tramp_table_map (struct tramp_table *table)
{
char *addr;
addr = mmap (NULL, tramp_globals.map_size * 2, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (addr == MAP_FAILED)
return 0;
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
static int
ffi_tramp_init (void)
{
long page_size;
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;
tramp_globals.text = ffi_tramp_arch (&tramp_globals.size,
&tramp_globals.map_size);
tramp_globals.ntramp = tramp_globals.map_size / tramp_globals.size;
page_size = sysconf (_SC_PAGESIZE);
if (page_size >= 0 && (size_t)page_size > 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;
}
static void tramp_add (struct tramp *tramp);
static int
tramp_table_alloc (void)
{
struct tramp_table *table;
struct tramp *tramp_array, *tramp;
size_t size;
char *code, *parm;
int i;
if (tramp_globals.nfree_tables > 0)
return 1;
table = malloc (sizeof (*table));
if (table == NULL)
return 0;
tramp_array = malloc (sizeof (*tramp) * tramp_globals.ntramp);
if (tramp_array == NULL)
goto free_table;
if (!tramp_table_map (table))
{
goto free_tramp_array;
}
table->array = tramp_array;
table->free = NULL;
table->nfree = 0;
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;
}
return 1;
free_tramp_array:
free (tramp_array);
free_table:
free (table);
return 0;
}
static void
tramp_table_free (struct tramp_table *table)
{
tramp_table_unmap (table);
free (table->array);
free (table);
}
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++;
}
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;
}
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);
if (table->nfree == tramp_globals.ntramp &&
tramp_globals.nfree_tables > 1)
{
tramp_table_del (table);
tramp_table_free (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);
}
int
ffi_tramp_is_supported(void)
{
int ret;
ffi_tramp_lock();
ret = ffi_tramp_init ();
ffi_tramp_unlock();
return ret;
}
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;
}
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();
}
void *
ffi_tramp_get_addr (void *arg)
{
struct tramp *tramp = arg;
void *addr;
ffi_tramp_lock();
addr = tramp->code;
ffi_tramp_unlock();
return addr;
}
void
ffi_tramp_free (void *arg)
{
struct tramp *tramp = arg;
ffi_tramp_lock();
tramp_add (tramp);
ffi_tramp_unlock();
}
#else
#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