threadLocalVariables.c [plain text]
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <stddef.h>
#include <stdio.h>
#include <pthread.h>
#include <Block.h>
#include <malloc/malloc.h>
#include <mach-o/loader.h>
#include <libkern/OSAtomic.h>
#include "dyld_priv.h"
#if __LP64__
typedef struct mach_header_64 macho_header;
#define LC_SEGMENT_COMMAND LC_SEGMENT_64
typedef struct segment_command_64 macho_segment_command;
typedef struct section_64 macho_section;
#else
typedef struct mach_header macho_header;
#define LC_SEGMENT_COMMAND LC_SEGMENT
typedef struct segment_command macho_segment_command;
typedef struct section macho_section;
#endif
#ifndef S_THREAD_LOCAL_REGULAR
#define S_THREAD_LOCAL_REGULAR 0x11
#endif
#ifndef S_THREAD_LOCAL_ZEROFILL
#define S_THREAD_LOCAL_ZEROFILL 0x12
#endif
#ifndef S_THREAD_LOCAL_VARIABLES
#define S_THREAD_LOCAL_VARIABLES 0x13
#endif
#ifndef S_THREAD_LOCAL_VARIABLE_POINTERS
#define S_THREAD_LOCAL_VARIABLE_POINTERS 0x14
#endif
#ifndef S_THREAD_LOCAL_INIT_FUNCTION_POINTERS
#define S_THREAD_LOCAL_INIT_FUNCTION_POINTERS 0x15
#endif
#ifndef MH_HAS_TLV_DESCRIPTORS
#define MH_HAS_TLV_DESCRIPTORS 0x800000
#endif
typedef void (*TermFunc)(void*);
#if __has_feature(tls)
typedef struct TLVHandler {
struct TLVHandler *next;
dyld_tlv_state_change_handler handler;
enum dyld_tlv_states state;
} TLVHandler;
static TLVHandler * volatile tlv_handlers = NULL;
struct TLVDescriptor
{
void* (*thunk)(struct TLVDescriptor*);
unsigned long key;
unsigned long offset;
};
typedef struct TLVDescriptor TLVDescriptor;
extern void* tlv_get_addr(TLVDescriptor*);
struct TLVImageInfo
{
pthread_key_t key;
const struct mach_header* mh;
};
typedef struct TLVImageInfo TLVImageInfo;
static TLVImageInfo* tlv_live_images = NULL;
static unsigned int tlv_live_image_alloc_count = 0;
static unsigned int tlv_live_image_used_count = 0;
static pthread_mutex_t tlv_live_image_lock = PTHREAD_MUTEX_INITIALIZER;
static void tlv_set_key_for_image(const struct mach_header* mh, pthread_key_t key)
{
pthread_mutex_lock(&tlv_live_image_lock);
if ( tlv_live_image_used_count == tlv_live_image_alloc_count ) {
unsigned int newCount = (tlv_live_images == NULL) ? 8 : 2*tlv_live_image_alloc_count;
struct TLVImageInfo* newBuffer = malloc(sizeof(TLVImageInfo)*newCount);
if ( tlv_live_images != NULL ) {
memcpy(newBuffer, tlv_live_images, sizeof(TLVImageInfo)*tlv_live_image_used_count);
free(tlv_live_images);
}
tlv_live_images = newBuffer;
tlv_live_image_alloc_count = newCount;
}
tlv_live_images[tlv_live_image_used_count].key = key;
tlv_live_images[tlv_live_image_used_count].mh = mh;
++tlv_live_image_used_count;
pthread_mutex_unlock(&tlv_live_image_lock);
}
static const struct mach_header* tlv_get_image_for_key(pthread_key_t key)
{
const struct mach_header* result = NULL;
pthread_mutex_lock(&tlv_live_image_lock);
for(unsigned int i=0; i < tlv_live_image_used_count; ++i) {
if ( tlv_live_images[i].key == key ) {
result = tlv_live_images[i].mh;
break;
}
}
pthread_mutex_unlock(&tlv_live_image_lock);
return result;
}
static void
tlv_notify(enum dyld_tlv_states state, void *buffer)
{
if (!tlv_handlers) return;
dyld_tlv_info info = { sizeof(info), buffer, malloc_size(buffer) };
for (TLVHandler *h = tlv_handlers; h != NULL; h = h->next) {
if (h->state == state && h->handler) {
h->handler(h->state, &info);
}
}
}
__attribute__((visibility("hidden")))
void* tlv_allocate_and_initialize_for_key(pthread_key_t key)
{
const struct mach_header* mh = tlv_get_image_for_key(key);
if ( mh == NULL )
return NULL;
uint8_t* start = NULL;
unsigned long size = 0;
intptr_t slide = 0;
bool slideComputed = false;
bool hasInitializers = false;
const uint32_t cmd_count = mh->ncmds;
const struct load_command* const cmds = (struct load_command*)(((uint8_t*)mh) + sizeof(macho_header));
const struct load_command* cmd = cmds;
for (uint32_t i = 0; i < cmd_count; ++i) {
if ( cmd->cmd == LC_SEGMENT_COMMAND) {
const macho_segment_command* seg = (macho_segment_command*)cmd;
if ( !slideComputed && (seg->filesize != 0) ) {
slide = (uintptr_t)mh - seg->vmaddr;
slideComputed = true;
}
const macho_section* const sectionsStart = (macho_section*)((char*)seg + sizeof(macho_segment_command));
const macho_section* const sectionsEnd = §ionsStart[seg->nsects];
for (const macho_section* sect=sectionsStart; sect < sectionsEnd; ++sect) {
switch ( sect->flags & SECTION_TYPE ) {
case S_THREAD_LOCAL_INIT_FUNCTION_POINTERS:
hasInitializers = true;
break;
case S_THREAD_LOCAL_ZEROFILL:
case S_THREAD_LOCAL_REGULAR:
if ( start == NULL ) {
start = (uint8_t*)(sect->addr + slide);
size = sect->size;
}
else {
const uint8_t* newEnd = (uint8_t*)(sect->addr + slide + sect->size);
size = newEnd - start;
}
break;
}
}
}
cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize);
}
void* buffer = malloc(size);
memcpy(buffer, start, size);
pthread_setspecific(key, buffer);
tlv_notify(dyld_tlv_state_allocated, buffer);
if ( hasInitializers ) {
cmd = cmds;
for (uint32_t i = 0; i < cmd_count; ++i) {
if ( cmd->cmd == LC_SEGMENT_COMMAND) {
const macho_segment_command* seg = (macho_segment_command*)cmd;
const macho_section* const sectionsStart = (macho_section*)((char*)seg + sizeof(macho_segment_command));
const macho_section* const sectionsEnd = §ionsStart[seg->nsects];
for (const macho_section* sect=sectionsStart; sect < sectionsEnd; ++sect) {
if ( (sect->flags & SECTION_TYPE) == S_THREAD_LOCAL_INIT_FUNCTION_POINTERS ) {
typedef void (*InitFunc)(void);
InitFunc* funcs = (InitFunc*)(sect->addr + slide);
const uint32_t count = sect->size / sizeof(uintptr_t);
for (uint32_t i=count; i > 0; --i) {
InitFunc func = funcs[i-1];
func();
}
}
}
}
cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize);
}
}
return buffer;
}
static void
tlv_free(void *storage)
{
tlv_notify(dyld_tlv_state_deallocated, storage);
free(storage);
}
static void tlv_initialize_descriptors(const struct mach_header* mh)
{
pthread_key_t key = 0;
intptr_t slide = 0;
bool slideComputed = false;
const uint32_t cmd_count = mh->ncmds;
const struct load_command* const cmds = (struct load_command*)(((uint8_t*)mh) + sizeof(macho_header));
const struct load_command* cmd = cmds;
for (uint32_t i = 0; i < cmd_count; ++i) {
if ( cmd->cmd == LC_SEGMENT_COMMAND) {
const macho_segment_command* seg = (macho_segment_command*)cmd;
if ( !slideComputed && (seg->filesize != 0) ) {
slide = (uintptr_t)mh - seg->vmaddr;
slideComputed = true;
}
const macho_section* const sectionsStart = (macho_section*)((char*)seg + sizeof(macho_segment_command));
const macho_section* const sectionsEnd = §ionsStart[seg->nsects];
for (const macho_section* sect=sectionsStart; sect < sectionsEnd; ++sect) {
if ( (sect->flags & SECTION_TYPE) == S_THREAD_LOCAL_VARIABLES ) {
if ( sect->size != 0 ) {
if ( key == 0 ) {
int result = pthread_key_create(&key, &tlv_free);
if ( result != 0 )
abort();
tlv_set_key_for_image(mh, key);
}
TLVDescriptor* start = (TLVDescriptor*)(sect->addr + slide);
TLVDescriptor* end = (TLVDescriptor*)(sect->addr + sect->size + slide);
for (TLVDescriptor* d=start; d < end; ++d) {
d->thunk = tlv_get_addr;
d->key = key;
}
}
}
}
}
cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize);
}
}
static const char* tlv_load_notification(enum dyld_image_states state, uint32_t infoCount, const struct dyld_image_info info[])
{
for (uint32_t i=0; i < infoCount; ++i) {
if ( info[i].imageLoadAddress->flags & MH_HAS_TLV_DESCRIPTORS )
tlv_initialize_descriptors(info[i].imageLoadAddress);
}
return NULL;
}
void dyld_register_tlv_state_change_handler(enum dyld_tlv_states state, dyld_tlv_state_change_handler handler)
{
TLVHandler *h = malloc(sizeof(TLVHandler));
h->state = state;
h->handler = Block_copy(handler);
TLVHandler *old;
do {
old = tlv_handlers;
h->next = old;
} while (! OSAtomicCompareAndSwapPtrBarrier(old, h, (void * volatile *)&tlv_handlers));
}
void dyld_enumerate_tlv_storage(dyld_tlv_state_change_handler handler)
{
pthread_mutex_lock(&tlv_live_image_lock);
unsigned int count = tlv_live_image_used_count;
void *list[count];
for (unsigned int i = 0; i < count; ++i) {
list[i] = pthread_getspecific(tlv_live_images[i].key);
}
pthread_mutex_unlock(&tlv_live_image_lock);
for (unsigned int i = 0; i < count; ++i) {
if (list[i]) {
dyld_tlv_info info = { sizeof(info), list[i], malloc_size(list[i]) };
handler(dyld_tlv_state_allocated, &info);
}
}
}
struct TLVTerminatorListEntry
{
TermFunc termFunc;
void* objAddr;
};
struct TLVTerminatorList
{
uint32_t allocCount;
uint32_t useCount;
struct TLVTerminatorListEntry entries[1]; };
static pthread_key_t tlv_terminators_key = 0;
void _tlv_atexit(TermFunc func, void* objAddr)
{
struct TLVTerminatorList* list = (struct TLVTerminatorList*)pthread_getspecific(tlv_terminators_key);
if ( list == NULL ) {
list = (struct TLVTerminatorList*)malloc(offsetof(struct TLVTerminatorList, entries[1]));
list->allocCount = 1;
list->useCount = 1;
list->entries[0].termFunc = func;
list->entries[0].objAddr = objAddr;
pthread_setspecific(tlv_terminators_key, list);
}
else {
if ( list->allocCount == list->allocCount ) {
uint32_t newAllocCount = list->allocCount * 2;
uint32_t newAllocSize = offsetof(struct TLVTerminatorList, entries[newAllocCount]);
struct TLVTerminatorList* newlist = (struct TLVTerminatorList*)malloc(newAllocSize);
newlist->allocCount = newAllocCount;
newlist->useCount = list->useCount;
for(uint32_t i=0; i < list->useCount; ++i)
newlist->entries[i] = list->entries[i];
pthread_setspecific(tlv_terminators_key, newlist);
free(list);
list = newlist;
}
list->entries[list->useCount].termFunc = func;
list->entries[list->useCount].objAddr = objAddr;
list->useCount += 1;
}
}
static void tlv_finalize(void* storage)
{
struct TLVTerminatorList* list = (struct TLVTerminatorList*)storage;
for(uint32_t i=list->useCount; i > 0 ; --i) {
struct TLVTerminatorListEntry* entry = &list->entries[i-1];
if ( entry->termFunc != NULL ) {
(*entry->termFunc)(entry->objAddr);
}
}
free(storage);
}
void _tlv_exit()
{
void* termFuncs = pthread_getspecific(tlv_terminators_key);
if ( termFuncs != NULL )
tlv_finalize(termFuncs);
}
__attribute__((visibility("hidden")))
void tlv_initializer()
{
(void)pthread_key_create(&tlv_terminators_key, &tlv_finalize);
dyld_register_image_state_change_handler(dyld_image_state_bound, true, tlv_load_notification);
}
void _tlv_bootstrap()
{
abort();
}
#else
void dyld_register_tlv_state_change_handler(enum dyld_tlv_states state, dyld_tlv_state_change_handler handler)
{
}
void dyld_enumerate_tlv_storage(dyld_tlv_state_change_handler handler)
{
}
void _tlv_exit()
{
}
void _tlv_atexit(TermFunc func, void* objAddr)
{
}
__attribute__((visibility("hidden")))
void tlv_initializer()
{
}
#endif // __has_feature(tls)