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 <mach-o/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
typedef void (*TermFunc)(void*);
#if __has_feature(tls) || __arm64__ || __arm__
typedef struct {
size_t info_size; void * tlv_addr; size_t tlv_size; } dyld_tlv_info;
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;
}
__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);
}
if ( size == 0 )
return NULL;
void* buffer = malloc(size);
memcpy(buffer, start, size);
pthread_setspecific(key, 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 size_t count = sect->size / sizeof(uintptr_t);
for (size_t j=count; j > 0; --j) {
InitFunc func = funcs[j-1];
func();
}
}
}
}
cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize);
}
}
return buffer;
}
static void
tlv_free(void *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 void tlv_load_notification(const struct mach_header* mh, intptr_t slide)
{
if ( mh->flags & MH_HAS_TLV_DESCRIPTORS )
tlv_initialize_descriptors(mh);
}
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->useCount == list->allocCount ) {
uint32_t newAllocCount = list->allocCount * 2;
size_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_list(struct TLVTerminatorList* list) {
for(uint32_t i=list->useCount; i > 0 ; --i) {
struct TLVTerminatorListEntry* entry = &list->entries[i-1];
if ( entry->termFunc != NULL ) {
(*entry->termFunc)(entry->objAddr);
}
struct TLVTerminatorList* newlist = (struct TLVTerminatorList*)pthread_getspecific(tlv_terminators_key);
if ( newlist != NULL ) {
pthread_setspecific(tlv_terminators_key, NULL);
tlv_finalize_list(newlist);
}
}
free(list);
}
static void tlv_finalize(void* storage)
{
tlv_finalize_list((struct TLVTerminatorList*)storage);
}
void _tlv_exit()
{
void* termFuncs = pthread_getspecific(tlv_terminators_key);
if ( termFuncs != NULL ) {
pthread_setspecific(tlv_terminators_key, NULL);
tlv_finalize(termFuncs);
}
}
__attribute__((visibility("hidden")))
void tlv_initializer()
{
(void)pthread_key_create(&tlv_terminators_key, &tlv_finalize);
_dyld_register_func_for_add_image(tlv_load_notification);
}
void _tlv_bootstrap()
{
abort();
}
#else
void _tlv_exit()
{
}
void _tlv_atexit(TermFunc func, void* objAddr)
{
}
__attribute__((visibility("hidden")))
void tlv_initializer()
{
}
#endif // __has_feature(tls)