/* This file is part of Darling. Copyright (C) 2015 Lubos Dolezel Darling is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Darling is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Darling. If not, see . */ #include "threads.h" #include #include #include #include #include #include typedef void (*thread_ep)(void**, int, ...); struct arg_struct { thread_ep entry_point; uintptr_t arg3; uintptr_t arg4; uintptr_t arg5; uintptr_t arg6; union { int (*thread_self_trap)(); int port; }; unsigned long pth_obj_size; void* pth; }; struct reaper_item { pthread_t thread; void* stack; size_t stacksize; }; static void* darling_thread_entry(void* p); static void start_reaper(); static sem_t reaper_sem; static pthread_mutex_t reaper_mutex = PTHREAD_MUTEX_INITIALIZER; static std::queue reaper_items; #ifndef PTHREAD_STACK_MIN # define PTHREAD_STACK_MIN 16384 #endif void* __darling_thread_create(unsigned long stack_size, unsigned long pth_obj_size, void* entry_point, uintptr_t arg3, uintptr_t arg4, uintptr_t arg5, uintptr_t arg6, int (*thread_self_trap)()) { static pthread_once_t reaper_once = PTHREAD_ONCE_INIT; arg_struct args = { (thread_ep) entry_point, arg3, arg4, arg5, arg6, thread_self_trap, pth_obj_size, nullptr }; pthread_attr_t attr; pthread_t nativeLibcThread; void* pth; pthread_once(&reaper_once, start_reaper); pthread_attr_init(&attr); // pthread_attr_setstacksize(&attr, stack_size); pth = ::mmap(nullptr, stack_size + pth_obj_size + 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); // pthread_attr_setstack is bugged. The documentation states we should provide the lowest // address of the stack, yet some versions regard it as the highest address instead. // Therefore it's better to just make the pthread stack as small as possible and then switch // to our own stack instead. //pthread_attr_setstack(&attr, ((char*)pth) + pth_obj_size, stack_size - pth_obj_size - 0x1000); //std::cout << "Allocated stack at " << pth << ", size " << stack_size << std::endl; pth = static_cast(pth) + stack_size + 0x1000; pthread_attr_setstacksize(&attr, 4096); args.pth = pth; pthread_create(&nativeLibcThread, &attr, darling_thread_entry, &args); pthread_attr_destroy(&attr); while (args.pth != nullptr) sched_yield(); return pth; } static void* darling_thread_entry(void* p) { arg_struct* in_args = static_cast(p); arg_struct args; memcpy(&args, in_args, sizeof(args)); args.port = args.thread_self_trap(); in_args->pth = nullptr; #ifdef __x86_64__ __asm__ __volatile__ ( "movq %1, %%rdi\n" "movq %%rdi, %%rsp\n" "movq 40(%0), %%rsi\n" "movq 8(%0), %%rdx\n" "testq %%rdx, %%rdx\n" "jnz 1f\n" "movq %%rsp, %%rdx\n" // wqthread hack: if 3rd arg is null, we pass sp "1:\n" "movq 16(%0), %%rcx\n" "movq 24(%0), %%r8\n" "movq 32(%0), %%r9\n" "movq %%rdi, 56(%0)\n" "movq (%0), %%rax\n" "andq $-0x10, %%rsp\n" "pushq $0\n" "pushq $0\n" "jmpq *%%rax\n" :: "a" (&args), "di" (args.pth)); #elif defined(__i386__) // TODO: args in eax, ebx, ecx, edx, edi, esi __asm__ __volatile__ ( "movl (%0), %%eax\n" "movl (%1), %%esp\n" "pushl %%eax\n" // address to be jumped to "movl %1, 28(%0)\n" "movl %1, %%eax\n" // 1st arg "movl 20(%0), %%ebx\n" // 2nd arg "movl 8(%0), %%edx\n" // 4th arg "movl 12(%0), %%edi\n" // 5th arg "movl 16(%0), %%esi\n" // 6th arg "movl 4(%0), %%ecx\n" // 3rd arg "testl %%ecx, %%ecx\n" // FIXME: clobbered ecx! "jnz 1f\n" "movl %%esp, %%ecx\n" "1:\n" "ret\n" // Jump to the address pushed at the beginning :: "c" (&args), "d" (args.pth)); #endif //args->entry_point(args->stack, args->port, args->arg3, // args->arg4, args->arg5, args->arg6); //memcpy(stack - sizeof(arg_struct) / sizeof(void*), // args, sizeof(arg_struct)); //delete args; #if 0 #ifdef __x86_64__ __asm__ ( "movq %%rsp, -8(%0)\n" // save original stack location "movq %0, %%rsp\n" // switch to Darwin libc stack "movq -56(%%rsp), %%rax\n" "movq %%rsp, %%rdi\n" // stack/pthread_self is 1st argument "movl -16(%%rsp), %%esi\n" // port is 2nd argument "movq -48(%%rsp), %%rdx\n" "movq -40(%%rsp), %%rcx\n" "movq -32(%%rsp), %%r8\n" "movq -24(%%rsp), %%r9\n" "subq $24, %%rsp\n" // protect orig_stack + alignment "callq *%%rax\n" "ud2\n" :: "r" (stack)); #elif defined(__i386__) # warning Missing assembly for this architecture! #else # warning Missing assembly for this architecture! #endif __builtin_unreachable(); #endif return nullptr; } int __darling_thread_terminate(void* stackaddr, unsigned long freesize, unsigned long pthobj_size) { #if 0 #ifdef __x86_64__ __asm__ ("movq %0, %%rdi\n" "movq %1, %%rsi\n" "movq -8(%2), %%rsp\n" // restore original glibc thread stack //"call munmap@PLT\n" "xorl %%edi, %%edi\n" "call pthread_exit@PLT\n" :: "a" (stackaddr), "c" (freesize), "b" (uintptr_t(stackaddr)+freesize-pthobj_size) ); #elif defined(__i386__) // TODO: PIC magic probably needed __asm__ ("movl (%0), %%esp\n" // restore original glibc thread stack "pushl %1\n" "pushl %0\n" "call munmap@PLT\n" "addl $8, %%esp\n" "pushl $0\n" "call pthread_exit@PLT\n" :: "r" (stackaddr), "r" (freesize) ); #else # warning Missing assembly for this architecture! #endif #endif pthread_mutex_lock(&reaper_mutex); reaper_items.push(reaper_item { pthread_self(), stackaddr, freesize }); pthread_mutex_unlock(&reaper_mutex); sem_post(&reaper_sem); pthread_exit(nullptr); __builtin_unreachable(); } void* __darling_thread_get_stack(void) { pthread_attr_t attr; void* stackaddr; size_t stacksize; pthread_getattr_np(pthread_self(), &attr); pthread_attr_getstack(&attr, &stackaddr, &stacksize); return ((char*)stackaddr) + stacksize - 0x2000; } static void* reaper_entry(void*) { while (true) { reaper_item item; sem_wait(&reaper_sem); pthread_mutex_lock(&reaper_mutex); item = reaper_items.front(); reaper_items.pop(); pthread_mutex_unlock(&reaper_mutex); // std::cout << "Reaping thread " << (void*)item.thread << "; Free stack at " << item.stack << ", " << item.stacksize << " bytes\n"; // Wait for thread to terminate pthread_join(item.thread, nullptr); // Free its stack in the extended requested by Darwin's libc munmap(item.stack, item.stacksize); } } static void start_reaper() { pthread_attr_t attr; pthread_t thread; pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); sem_init(&reaper_sem, 0, 0); pthread_create(&thread, &attr, reaper_entry, nullptr); pthread_attr_destroy(&attr); }