| //===---------- ctthread.cc - Thread implementation for VMKit -------------===// |
| // |
| // The VMKit project |
| // |
| // This file is distributed under the University of Illinois Open Source |
| // License. See LICENSE.TXT for details. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "debug.h" |
| |
| #include "VmkitGC.h" |
| #include "vmkit/MethodInfo.h" |
| #include "vmkit/VirtualMachine.h" |
| #include "vmkit/Cond.h" |
| #include "vmkit/Locks.h" |
| #include "vmkit/Thread.h" |
| |
| #include <cassert> |
| #include <cstdio> |
| #include <errno.h> |
| #include <pthread.h> |
| #include <sys/mman.h> |
| #include <sys/syscall.h> |
| #include <sched.h> |
| #include <signal.h> |
| #include <unistd.h> |
| |
| using namespace vmkit; |
| |
| int Thread::kill(void* tid, int signo) { |
| return pthread_kill((pthread_t)tid, signo); |
| } |
| |
| int Thread::kill(int signo) { |
| return pthread_kill((pthread_t)internalThreadID, signo); |
| } |
| |
| void Thread::exit(int value) { |
| pthread_exit((void*)(intptr_t)value); |
| } |
| |
| void Thread::yield(void) { |
| Thread* th = vmkit::Thread::get(); |
| if (th->isVmkitThread()) { |
| if (th->doYield && !th->inRV) { |
| th->MyVM->rendezvous.join(); |
| } |
| } |
| sched_yield(); |
| } |
| |
| void Thread::joinRVBeforeEnter() { |
| MyVM->rendezvous.joinBeforeUncooperative(); |
| } |
| |
| void Thread::joinRVAfterLeave(word_t savedSP) { |
| MyVM->rendezvous.joinAfterUncooperative(savedSP); |
| } |
| |
| void Thread::startKnownFrame(KnownFrame& F) { |
| // Get the caller of this function |
| word_t cur = System::GetCallerAddress(); |
| F.previousFrame = lastKnownFrame; |
| F.currentFP = cur; |
| // This is used as a marker. |
| F.currentIP = 0; |
| lastKnownFrame = &F; |
| } |
| |
| void Thread::endKnownFrame() { |
| assert(lastKnownFrame->currentIP == 0); |
| lastKnownFrame = lastKnownFrame->previousFrame; |
| } |
| |
| void Thread::startUnknownFrame(KnownFrame& F) { |
| // Get the caller of this function |
| word_t cur = System::GetCallerAddress(); |
| // Get the caller of the caller. |
| cur = System::GetCallerOfAddress(cur); |
| F.previousFrame = lastKnownFrame; |
| F.currentFP = cur; |
| F.currentIP = System::GetIPFromCallerAddress(cur); |
| lastKnownFrame = &F; |
| } |
| |
| void Thread::endUnknownFrame() { |
| assert(lastKnownFrame->currentIP != 0); |
| lastKnownFrame = lastKnownFrame->previousFrame; |
| } |
| |
| void Thread::internalThrowException() { |
| LONGJMP(lastExceptionBuffer->buffer, 1); |
| } |
| |
| void Thread::printBacktrace() { |
| StackWalker Walker(this); |
| |
| while (FrameInfo* FI = Walker.get()) { |
| MyVM->printMethod(FI, Walker.ip, Walker.addr); |
| ++Walker; |
| } |
| } |
| |
| void Thread::getFrameContext(word_t* buffer) { |
| vmkit::StackWalker Walker(this); |
| uint32_t i = 0; |
| |
| while (word_t ip = *Walker) { |
| buffer[i++] = ip; |
| ++Walker; |
| } |
| } |
| |
| uint32_t Thread::getFrameContextLength() { |
| vmkit::StackWalker Walker(this); |
| uint32_t i = 0; |
| |
| while (*Walker) { |
| ++i; |
| ++Walker; |
| } |
| return i; |
| } |
| |
| FrameInfo* StackWalker::get() { |
| if (addr == thread->baseSP) return 0; |
| ip = System::GetIPFromCallerAddress(addr); |
| return thread->MyVM->IPToFrameInfo(ip); |
| } |
| |
| word_t StackWalker::operator*() { |
| if (addr == thread->baseSP) return 0; |
| ip = System::GetIPFromCallerAddress(addr); |
| return ip; |
| } |
| |
| void StackWalker::operator++() { |
| if (addr != thread->baseSP) { |
| assert((addr < thread->baseSP) && "Corrupted stack"); |
| assert((addr < System::GetCallerOfAddress(addr)) && "Corrupted stack"); |
| if ((frame != NULL) && (addr == frame->currentFP)) { |
| assert(frame->currentIP == 0); |
| frame = frame->previousFrame; |
| assert(frame != NULL); |
| assert(frame->currentIP != 0); |
| addr = frame->currentFP; |
| frame = frame->previousFrame; |
| } else { |
| addr = System::GetCallerOfAddress(addr); |
| } |
| } |
| } |
| |
| StackWalker::StackWalker(vmkit::Thread* th) { |
| thread = th; |
| frame = th->lastKnownFrame; |
| if (vmkit::Thread::get() == th) { |
| addr = System::GetCallerAddress(); |
| addr = System::GetCallerOfAddress(addr); |
| } else { |
| addr = th->waitOnSP(); |
| if (frame) { |
| // if (frame->currentFP < addr) { |
| // fprintf(stderr, "Error in thread with pointer %p because %x < %x\n", th, frame->currentFP, addr); |
| // addr = frame->currentFP; |
| // } |
| |
| |
| assert(frame->currentFP >= addr); |
| } |
| if (frame && (addr == frame->currentFP)) { |
| frame = frame->previousFrame; |
| // Let this be called from JNI, as in |
| // OpenJDK's JVM_FillInStackTrace: |
| if (frame && frame->currentIP != 0) |
| frame = frame->previousFrame; |
| assert((frame == NULL) || (frame->currentIP == 0)); |
| } |
| } |
| assert(addr && "No address to start with"); |
| } |
| |
| |
| void Thread::scanStack(word_t closure) { |
| StackWalker Walker(this); |
| while (FrameInfo* MI = Walker.get()) { |
| MethodInfoHelper::scan(closure, MI, Walker.ip, Walker.addr); |
| ++Walker; |
| } |
| } |
| |
| void Thread::enterUncooperativeCode(uint16_t level) { |
| if (isVmkitThread()) { |
| if (!inRV) { |
| assert(!lastSP && "SP already set when entering uncooperative code"); |
| // Get the caller. |
| word_t temp = System::GetCallerAddress(); |
| // Make sure to at least get the caller of the caller. |
| ++level; |
| while (level--) |
| temp = System::GetCallerOfAddress(temp); |
| // The cas is not necessary, but it does a memory barrier. |
| __sync_bool_compare_and_swap(&lastSP, 0, temp); |
| if (doYield) joinRVBeforeEnter(); |
| assert(lastSP && "No last SP when entering uncooperative code"); |
| } |
| } |
| } |
| |
| void Thread::enterUncooperativeCode(word_t SP) { |
| if (isVmkitThread()) { |
| if (!inRV) { |
| assert(!lastSP && "SP already set when entering uncooperative code"); |
| // The cas is not necessary, but it does a memory barrier. |
| __sync_bool_compare_and_swap(&lastSP, 0, SP); |
| if (doYield) joinRVBeforeEnter(); |
| assert(lastSP && "No last SP when entering uncooperative code"); |
| } |
| } |
| } |
| |
| void Thread::leaveUncooperativeCode() { |
| if (isVmkitThread()) { |
| if (!inRV) { |
| assert(lastSP && "No last SP when leaving uncooperative code"); |
| word_t savedSP = lastSP; |
| // The cas is not necessary, but it does a memory barrier. |
| __sync_bool_compare_and_swap(&lastSP, lastSP, 0); |
| // A rendezvous has just been initiated, join it. |
| if (doYield) joinRVAfterLeave(savedSP); |
| assert(!lastSP && "SP has a value after leaving uncooperative code"); |
| } |
| } |
| } |
| |
| word_t Thread::waitOnSP() { |
| // First see if we can get lastSP directly. |
| word_t sp = lastSP; |
| if (sp) return sp; |
| |
| // Then loop a fixed number of iterations to get lastSP. |
| for (uint32 count = 0; count < 1000; ++count) { |
| sp = lastSP; |
| if (sp) return sp; |
| } |
| |
| // Finally, yield until lastSP is not set. |
| while ((sp = lastSP) == 0) vmkit::Thread::yield(); |
| |
| assert(sp != 0 && "Still no sp"); |
| return sp; |
| } |
| |
| |
| word_t Thread::baseAddr = 0; |
| |
| // These could be set at runtime. |
| #define STACK_SIZE 0x100000 |
| #define NR_THREADS 255 |
| |
| /// StackThreadManager - This class allocates all stacks for threads. Because |
| /// we want fast access to thread local data, and can not rely on platform |
| /// dependent thread local storage (eg pthread keys are inefficient, tls is |
| /// specific to Linux), we put thread local data at the bottom of the |
| /// stack. A simple mask computes the thread local data , based on the current |
| /// stack pointer. |
| // |
| /// The stacks are allocated at boot time. They must all be in the memory range |
| /// 0x?0000000 and Ox(?+1)0000000, so that the thread local data can be computed |
| /// and threads have a unique ID. |
| /// |
| class StackThreadManager { |
| public: |
| word_t baseAddr; |
| uint32 allocPtr; |
| uint32 used[NR_THREADS]; |
| LockNormal stackLock; |
| |
| StackThreadManager() { |
| baseAddr = 0; |
| word_t ptr = kThreadStart; |
| |
| uint32 flags = MAP_PRIVATE | MAP_ANON | MAP_FIXED; |
| baseAddr = (word_t)mmap((void*)ptr, STACK_SIZE * NR_THREADS, |
| PROT_READ | PROT_WRITE, flags, -1, 0); |
| |
| if (baseAddr == (word_t) MAP_FAILED) { |
| fprintf(stderr, "Can not allocate thread memory\n"); |
| abort(); |
| } |
| |
| // Protect the page after the alternative stack. |
| uint32 pagesize = System::GetPageSize(); |
| for (uint32 i = 0; i < NR_THREADS; ++i) { |
| word_t addr = baseAddr + (i * STACK_SIZE) + pagesize |
| + vmkit::System::GetAlternativeStackSize(); |
| mprotect((void*)addr, pagesize, PROT_NONE); |
| } |
| |
| memset((void*)used, 0, NR_THREADS * sizeof(uint32)); |
| allocPtr = 0; |
| vmkit::Thread::baseAddr = baseAddr; |
| } |
| |
| word_t allocate() { |
| stackLock.lock(); |
| uint32 myIndex = 0; |
| do { |
| if (!used[myIndex]) { |
| used[myIndex] = 1; |
| break; |
| } |
| ++myIndex; |
| } while (myIndex != NR_THREADS); |
| |
| stackLock.unlock(); |
| |
| if (myIndex != NR_THREADS) |
| return baseAddr + myIndex * STACK_SIZE; |
| |
| return 0; |
| } |
| |
| }; |
| |
| |
| /// Static allocate a stack manager. In the future, this should be virtual |
| /// machine specific. |
| StackThreadManager TheStackManager; |
| |
| extern void sigsegvHandler(int, siginfo_t*, void*); |
| extern void sigsTermHandler(int n, siginfo_t *info, void *context); |
| |
| /// internalThreadStart - The initial function called by a thread. Sets some |
| /// thread specific data, registers the thread to the GC and calls the |
| /// given routine of th. |
| /// |
| void Thread::internalThreadStart(vmkit::Thread* th) { |
| th->baseSP = System::GetCallerAddress(); |
| |
| // Set the alternate stack as the second page of the thread's |
| // stack. |
| stack_t st; |
| st.ss_sp = (void*)th->GetAlternativeStackEnd(); |
| st.ss_flags = 0; |
| st.ss_size = System::GetAlternativeStackSize(); |
| sigaltstack(&st, NULL); |
| |
| // Set the SIGSEGV handler to diagnose errors. |
| struct sigaction sa; |
| sigset_t mask; |
| sigfillset(&mask); |
| sa.sa_flags = SA_SIGINFO | SA_ONSTACK; |
| sa.sa_mask = mask; |
| sa.sa_sigaction = sigsegvHandler; |
| sigaction(SIGSEGV, &sa, NULL); |
| sigaction(SIGBUS, &sa, NULL); |
| // to handle termination |
| st.ss_sp = (void*)th->GetAlternativeStackEnd(); |
| st.ss_flags = 0; |
| st.ss_size = System::GetAlternativeStackSize(); |
| sigaltstack(&st, NULL); |
| sigfillset(&mask); |
| sa.sa_flags = SA_SIGINFO | SA_ONSTACK; |
| sa.sa_mask = mask; |
| sa.sa_sigaction = sigsTermHandler; |
| sigaction(SIGHUP, &sa, NULL); |
| sigaction(SIGINT, &sa, NULL); |
| //sigaction(SIGTERM, &sa, NULL); |
| |
| assert(th->MyVM && "VM not set in a thread"); |
| // fprintf(stderr, "Thread %p has TID %ld\n", th,syscall(SYS_gettid) ); |
| th->MyVM->rendezvous.addThread(th); |
| th->routine(th); |
| th->MyVM->removeThread(th); |
| } |
| |
| |
| |
| /// start - Called by the creator of the thread to run the new thread. |
| int Thread::start(void (*fct)(vmkit::Thread*)) { |
| pthread_attr_t attributs; |
| pthread_attr_init(&attributs); |
| pthread_attr_setstack(&attributs, this, STACK_SIZE); |
| routine = fct; |
| // Make sure to add it in the list of threads before leaving this function: |
| // the garbage collector wants to trace this thread. |
| MyVM->addThread(this); |
| int res = pthread_create((pthread_t*)(void*)(&internalThreadID), &attributs, |
| (void* (*)(void *))internalThreadStart, this); |
| pthread_attr_destroy(&attributs); |
| return res; |
| } |
| |
| |
| /// operator new - Get a stack from the stack manager. The Thread object |
| /// will be placed in the first page at the bottom of the stack. Hence |
| /// Thread objects can not exceed a page. |
| void* Thread::operator new(size_t sz) { |
| assert(sz < (size_t)getpagesize() && "Thread local data too big"); |
| void* res = (void*)TheStackManager.allocate(); |
| // Make sure the thread information is cleared. |
| if (res != NULL) memset(res, 0, sz); |
| assert(res && "Thread cannot be allocated because there is no room available"); |
| return res; |
| } |
| |
| /// releaseThread - Remove the stack of the thread from the list of stacks |
| /// in use. |
| void Thread::releaseThread(vmkit::Thread* th) { |
| // It seems like the pthread implementation in Linux is clearing with NULL |
| // the stack of the thread. So we have to get the thread id before |
| // calling pthread_join. |
| void* thread_id = th->internalThreadID; |
| if (thread_id != NULL) { |
| // Wait for the thread to die. |
| pthread_join((pthread_t)thread_id, NULL); |
| } |
| word_t index = ((word_t)th & System::GetThreadIDMask()); |
| index = (index & ~TheStackManager.baseAddr) >> 20; |
| TheStackManager.used[index] = 0; |
| } |
| |
| void Thread::throwNullPointerException(word_t methodIP) |
| { |
| vmkit::FrameInfo* FI = MyVM->IPToFrameInfo(methodIP); |
| if (FI->Metadata == NULL) { |
| fprintf(stderr, "Thread %p received a SIGSEGV: either the VM code or an external\n" |
| "native method is bogus. Aborting...\n", (void*)this); |
| abort(); |
| } |
| |
| MyVM->nullPointerException(); |
| UNREACHABLE(); |
| } |