You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

339 lines
9.5 KiB
C

// On some platforms, we need _GNU_SOURCE to expose asprintf()
#ifndef _GNU_SOURCE
#define _GNU_SOURCE 1
#endif
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <ctype.h>
#ifndef _WIN32
#include <unistd.h>
#include <sys/types.h>
#include <sys/mman.h>
#else
#include "safewindows.h"
#endif
#include "objc/runtime.h"
#include "objc/blocks_runtime.h"
#include "blocks_runtime.h"
#include "lock.h"
#include "visibility.h"
#include "asmconstants.h" // For PAGE_SIZE
#ifndef __has_builtin
#define __has_builtin(x) 0
#endif
#if defined(_WIN32) && (defined(__arm__) || defined(__aarch64__))
static inline void __clear_cache(void* start, void* end) {
FlushInstructionCache(GetCurrentProcess(), start, end - start);
}
#define clear_cache __clear_cache
#elif __has_builtin(__builtin___clear_cache)
#define clear_cache __builtin___clear_cache
#else
void __clear_cache(void* start, void* end);
#define clear_cache __clear_cache
#endif
/* QNX needs a special header for asprintf() */
#ifdef __QNXNTO__
#include <nbutil.h>
#endif
#ifdef _WIN32
#if defined(WINAPI_FAMILY) && WINAPI_FAMILY != WINAPI_FAMILY_DESKTOP_APP && _WIN32_WINNT >= 0x0A00
// Prefer the *FromApp versions when we're being built in a Windows Store App context on
// Windows >= 10. *FromApp require the application to be manifested for "codeGeneration".
#define VirtualAlloc VirtualAllocFromApp
#define VirtualProtect VirtualProtectFromApp
#endif // App family partition
#ifndef PROT_READ
#define PROT_READ 0x4
#endif
#ifndef PROT_WRITE
#define PROT_WRITE 0x2
#endif
#ifndef PROT_EXEC
#define PROT_EXEC 0x1
#endif
static int mprotect(void *buffer, size_t len, int prot)
{
DWORD oldProt = 0, newProt = PAGE_NOACCESS;
// Windows doesn't offer values that can be ORed together...
if ((prot & PROT_WRITE))
{
// promote to readwrite as there's no writeonly protection constant
newProt = PAGE_READWRITE;
}
else if ((prot & PROT_READ))
{
newProt = PAGE_READONLY;
}
if ((prot & PROT_EXEC))
{
switch (newProt)
{
case PAGE_NOACCESS: newProt = PAGE_EXECUTE; break;
case PAGE_READONLY: newProt = PAGE_EXECUTE_READ; break;
case PAGE_READWRITE: newProt = PAGE_EXECUTE_READWRITE; break;
}
}
return 0 != VirtualProtect(buffer, len, newProt, &oldProt);
}
#else
# ifndef MAP_ANONYMOUS
# define MAP_ANONYMOUS MAP_ANON
# endif
#endif
struct block_header
{
void *block;
void(*fnptr)(void);
/**
* On 64-bit platforms, we have 16 bytes for instructions, which ought to
* be enough without padding.
* Note: If we add too much padding, then we waste space but have no other
* ill effects. If we get this too small, then the assert in
* `init_trampolines` will fire on library load.
*
* PowerPC: We need INSTR_CNT * INSTR_LEN = 7*4 = 28 bytes
* for instruction. sizeof(block_header) must be a divisor of
* PAGE_SIZE, so we need to pad block_header to 32 bytes.
* On PowerPC 64-bit where sizeof(void *) = 8 bytes, we
* add 16 bytes of padding.
*/
#if defined(__i386__) || (defined(__mips__) && !defined(__mips_n64)) || (defined(__powerpc__) && !defined(__powerpc64__))
uint64_t padding[3];
#elif defined(__mips__) || defined(__powerpc64__)
uint64_t padding[2];
#elif defined(__arm__)
uint64_t padding;
#endif
};
#define HEADERS_PER_PAGE (PAGE_SIZE/sizeof(struct block_header))
/**
* Structure containing a two pages of block trampolines. Each trampoline
* loads its block and target method address from the corresponding
* block_header (one page before the start of the block structure).
*/
struct trampoline_buffers
{
struct block_header headers[HEADERS_PER_PAGE];
char rx_buffer[PAGE_SIZE];
};
_Static_assert(__builtin_offsetof(struct trampoline_buffers, rx_buffer) == PAGE_SIZE,
"Incorrect offset for read-execute buffer");
_Static_assert(sizeof(struct trampoline_buffers) == 2*PAGE_SIZE,
"Incorrect size for trampoline buffers");
struct trampoline_set
{
struct trampoline_buffers *buffers;
struct trampoline_set *next;
int first_free;
};
static mutex_t trampoline_lock;
struct wx_buffer
{
void *w;
void *x;
};
extern char __objc_block_trampoline;
extern char __objc_block_trampoline_end;
extern char __objc_block_trampoline_sret;
extern char __objc_block_trampoline_end_sret;
PRIVATE void init_trampolines(void)
{
assert(&__objc_block_trampoline_end - &__objc_block_trampoline <= sizeof(struct block_header));
assert(&__objc_block_trampoline_end_sret - &__objc_block_trampoline_sret <= sizeof(struct block_header));
INIT_LOCK(trampoline_lock);
}
static id invalid(id self, SEL _cmd)
{
fprintf(stderr, "Invalid block method called for [%s %s]\n",
class_getName(object_getClass(self)), sel_getName(_cmd));
return nil;
}
static struct trampoline_set *alloc_trampolines(char *start, char *end)
{
struct trampoline_set *metadata = calloc(1, sizeof(struct trampoline_set));
#if _WIN32
metadata->buffers = VirtualAlloc(NULL, sizeof(struct trampoline_buffers), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
#else
metadata->buffers = mmap(NULL, sizeof(struct trampoline_buffers), PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
#endif
for (int i=0 ; i<HEADERS_PER_PAGE ; i++)
{
metadata->buffers->headers[i].fnptr = (void(*)(void))invalid;
metadata->buffers->headers[i].block = &metadata->buffers->headers[i+1].block;
char *block = metadata->buffers->rx_buffer + (i * sizeof(struct block_header));
memcpy(block, start, end-start);
}
metadata->buffers->headers[HEADERS_PER_PAGE-1].block = NULL;
mprotect(metadata->buffers->rx_buffer, PAGE_SIZE, PROT_READ | PROT_EXEC);
clear_cache(metadata->buffers->rx_buffer, &metadata->buffers->rx_buffer[PAGE_SIZE]);
return metadata;
}
static struct trampoline_set *sret_trampolines;
static struct trampoline_set *trampolines;
IMP imp_implementationWithBlock(id block)
{
struct Block_layout *b = (struct Block_layout *)block;
void *start;
void *end;
LOCK_FOR_SCOPE(&trampoline_lock);
struct trampoline_set **setptr;
if ((b->flags & BLOCK_USE_SRET) == BLOCK_USE_SRET)
{
setptr = &sret_trampolines;
start = &__objc_block_trampoline_sret;
end = &__objc_block_trampoline_end_sret;
}
else
{
setptr = &trampolines;
start = &__objc_block_trampoline;
end = &__objc_block_trampoline_end;
}
size_t trampolineSize = end - start;
// If we don't have a trampoline intrinsic for this architecture, return a
// null IMP.
if (0 >= trampolineSize) { return 0; }
block = Block_copy(block);
// Allocate some trampolines if this is the first time that we need to do this.
if (*setptr == NULL)
{
*setptr = alloc_trampolines(start, end);
}
for (struct trampoline_set *set=*setptr ; set!=NULL ; set=set->next)
{
if (set->first_free != -1)
{
int i = set->first_free;
struct block_header *h = &set->buffers->headers[i];
struct block_header *next = h->block;
set->first_free = next ? (next - set->buffers->headers) : -1;
assert(set->first_free < HEADERS_PER_PAGE);
assert(set->first_free >= -1);
h->fnptr = (void(*)(void))b->invoke;
h->block = b;
uintptr_t addr = (uintptr_t)&set->buffers->rx_buffer[i*sizeof(struct block_header)];
#if (__ARM_ARCH_ISA_THUMB == 2)
// If the trampoline is Thumb-2 code, then we must set the low bit
// to 1 so that b[l]x instructions put the CPU in the correct mode.
addr |= 1;
#endif
return (IMP)addr;
}
}
UNREACHABLE("Failed to allocate block");
}
static int indexForIMP(IMP anIMP, struct trampoline_set **setptr)
{
for (struct trampoline_set *set=*setptr ; set!=NULL ; set=set->next)
{
if (((char*)anIMP >= set->buffers->rx_buffer) &&
((char*)anIMP < &set->buffers->rx_buffer[PAGE_SIZE]))
{
*setptr = set;
ptrdiff_t offset = (char*)anIMP - set->buffers->rx_buffer;
return offset / sizeof(struct block_header);
}
}
return -1;
}
id imp_getBlock(IMP anImp)
{
LOCK_FOR_SCOPE(&trampoline_lock);
struct trampoline_set *set = trampolines;
int idx = indexForIMP(anImp, &set);
if (idx == -1)
{
set = sret_trampolines;
indexForIMP(anImp, &set);
}
if (idx == -1)
{
return NULL;
}
return set->buffers->headers[idx].block;
}
BOOL imp_removeBlock(IMP anImp)
{
LOCK_FOR_SCOPE(&trampoline_lock);
struct trampoline_set *set = trampolines;
int idx = indexForIMP(anImp, &set);
if (idx == -1)
{
set = sret_trampolines;
indexForIMP(anImp, &set);
}
if (idx == -1)
{
return NO;
}
struct block_header *h = &set->buffers->headers[idx];
Block_release(h->block);
h->fnptr = (void(*)(void))invalid;
h->block = set->first_free == -1 ? NULL : &set->buffers->headers[set->first_free];
set->first_free = h - set->buffers->headers;
return YES;
}
PRIVATE size_t lengthOfTypeEncoding(const char *types);
char *block_copyIMPTypeEncoding_np(id block)
{
char *buffer = strdup(block_getType_np(block));
if (NULL == buffer) { return NULL; }
char *replace = buffer;
// Skip the return type
replace += lengthOfTypeEncoding(replace);
while (isdigit(*replace)) { replace++; }
// The first argument type should be @? (block), and we need to transform
// it to @, so we have to delete the ?. Assert here because this isn't a
// block encoding at all if the first argument is not a block, and since we
// got it from block_getType_np(), this means something is badly wrong.
assert('@' == *replace);
replace++;
assert('?' == *replace);
// Use strlen(replace) not replace+1, because we want to copy the NULL
// terminator as well.
memmove(replace, replace+1, strlen(replace));
// The next argument should be an object, and we want to replace it with a
// selector
while (isdigit(*replace)) { replace++; }
if ('@' != *replace)
{
free(buffer);
return NULL;
}
*replace = ':';
return buffer;
}