diff options
Diffstat (limited to 'vm/jdwp/JdwpMain.c')
-rw-r--r-- | vm/jdwp/JdwpMain.c | 415 |
1 files changed, 415 insertions, 0 deletions
diff --git a/vm/jdwp/JdwpMain.c b/vm/jdwp/JdwpMain.c new file mode 100644 index 0000000..24e5c6c --- /dev/null +++ b/vm/jdwp/JdwpMain.c @@ -0,0 +1,415 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * JDWP initialization. + */ +#include "jdwp/JdwpPriv.h" +#include "Dalvik.h" +#include "Atomic.h" + +#include <stdlib.h> +#include <unistd.h> +#include <sys/time.h> +#include <time.h> +#include <errno.h> + + +static void* jdwpThreadStart(void* arg); + + +/* + * Initialize JDWP. + * + * Does not return until JDWP thread is running, but may return before + * the thread is accepting network connections. + */ +JdwpState* dvmJdwpStartup(const JdwpStartupParams* pParams) +{ + JdwpState* state = NULL; + + /* comment this out when debugging JDWP itself */ + android_setMinPriority(LOG_TAG, ANDROID_LOG_DEBUG); + + state = (JdwpState*) calloc(1, sizeof(JdwpState)); + + state->params = *pParams; + + state->requestSerial = 0x10000000; + state->eventSerial = 0x20000000; + dvmDbgInitMutex(&state->threadStartLock); + dvmDbgInitMutex(&state->attachLock); + dvmDbgInitMutex(&state->serialLock); + dvmDbgInitMutex(&state->eventLock); + state->eventThreadId = 0; + dvmDbgInitMutex(&state->eventThreadLock); + dvmDbgInitCond(&state->threadStartCond); + dvmDbgInitCond(&state->attachCond); + dvmDbgInitCond(&state->eventThreadCond); + + switch (pParams->transport) { + case kJdwpTransportSocket: + // LOGD("prepping for JDWP over TCP\n"); + state->transport = dvmJdwpSocketTransport(); + break; + case kJdwpTransportAndroidAdb: + // LOGD("prepping for JDWP over ADB\n"); + state->transport = dvmJdwpAndroidAdbTransport(); + /* TODO */ + break; + default: + LOGE("Unknown transport %d\n", pParams->transport); + assert(false); + goto fail; + } + + if (!dvmJdwpNetStartup(state, pParams)) + goto fail; + + /* + * Grab a mutex or two before starting the thread. This ensures they + * won't signal the cond var before we're waiting. + */ + dvmDbgLockMutex(&state->threadStartLock); + if (pParams->suspend) + dvmDbgLockMutex(&state->attachLock); + + /* + * We have bound to a port, or are trying to connect outbound to a + * debugger. Create the JDWP thread and let it continue the mission. + */ + if (!dvmCreateInternalThread(&state->debugThreadHandle, "JDWP", + jdwpThreadStart, state)) + { + /* state is getting tossed, but unlock these anyway for cleanliness */ + dvmDbgUnlockMutex(&state->threadStartLock); + if (pParams->suspend) + dvmDbgUnlockMutex(&state->attachLock); + goto fail; + } + + /* + * Wait until the thread finishes basic initialization. + * TODO: cond vars should be waited upon in a loop + */ + dvmDbgCondWait(&state->threadStartCond, &state->threadStartLock); + dvmDbgUnlockMutex(&state->threadStartLock); + + + /* + * For suspend=y, wait for the debugger to connect to us or for us to + * connect to the debugger. + * + * The JDWP thread will signal us when it connects successfully or + * times out (for timeout=xxx), so we have to check to see what happened + * when we wake up. + */ + if (pParams->suspend) { + dvmChangeStatus(NULL, THREAD_VMWAIT); + dvmDbgCondWait(&state->attachCond, &state->attachLock); + dvmDbgUnlockMutex(&state->attachLock); + dvmChangeStatus(NULL, THREAD_RUNNING); + + if (!dvmJdwpIsActive(state)) { + LOGE("JDWP connection failed\n"); + goto fail; + } + + LOGI("JDWP connected\n"); + + /* + * Ordinarily we would pause briefly to allow the debugger to set + * breakpoints and so on, but for "suspend=y" the VM init code will + * pause the VM when it sends the VM_START message. + */ + } + + return state; + +fail: + dvmJdwpShutdown(state); // frees state + return NULL; +} + +/* + * Reset all session-related state. There should not be an active connection + * to the client at this point. The rest of the VM still thinks there is + * a debugger attached. + * + * This includes freeing up the debugger event list. + */ +void dvmJdwpResetState(JdwpState* state) +{ + /* could reset the serial numbers, but no need to */ + + dvmJdwpUnregisterAll(state); + assert(state->eventList == NULL); + + /* + * Should not have one of these in progress. If the debugger went away + * mid-request, though, we could see this. + */ + if (state->eventThreadId != 0) { + LOGW("WARNING: resetting state while event in progress\n"); + assert(false); + } +} + +/* + * Tell the JDWP thread to shut down. Frees "state". + */ +void dvmJdwpShutdown(JdwpState* state) +{ + void* threadReturn; + + if (state == NULL) + return; + + if (dvmJdwpIsTransportDefined(state)) { + if (dvmJdwpIsConnected(state)) + dvmJdwpPostVMDeath(state); + + /* + * Close down the network to inspire the thread to halt. + */ + if (gDvm.verboseShutdown) + LOGD("JDWP shutting down net...\n"); + dvmJdwpNetShutdown(state); + + if (state->debugThreadStarted) { + state->run = false; + if (pthread_join(state->debugThreadHandle, &threadReturn) != 0) { + LOGW("JDWP thread join failed\n"); + } + } + + if (gDvm.verboseShutdown) + LOGD("JDWP freeing netstate...\n"); + dvmJdwpNetFree(state); + state->netState = NULL; + } + assert(state->netState == NULL); + + dvmJdwpResetState(state); + free(state); +} + +/* + * Are we talking to a debugger? + */ +bool dvmJdwpIsActive(JdwpState* state) +{ + return dvmJdwpIsConnected(state); +} + +/* + * Entry point for JDWP thread. The thread was created through the VM + * mechanisms, so there is a java/lang/Thread associated with us. + */ +static void* jdwpThreadStart(void* arg) +{ + JdwpState* state = (JdwpState*) arg; + + LOGV("JDWP: thread running\n"); + + /* + * Finish initializing "state", then notify the creating thread that + * we're running. + */ + state->debugThreadHandle = dvmThreadSelf()->handle; + state->run = true; + android_atomic_release_store(true, &state->debugThreadStarted); + + dvmDbgLockMutex(&state->threadStartLock); + dvmDbgCondBroadcast(&state->threadStartCond); + dvmDbgUnlockMutex(&state->threadStartLock); + + /* set the thread state to VMWAIT so GCs don't wait for us */ + dvmDbgThreadWaiting(); + + /* + * Loop forever if we're in server mode, processing connections. In + * non-server mode, we bail out of the thread when the debugger drops + * us. + * + * We broadcast a notification when a debugger attaches, after we + * successfully process the handshake. + */ + while (state->run) { + bool first; + + if (state->params.server) { + /* + * Block forever, waiting for a connection. To support the + * "timeout=xxx" option we'll need to tweak this. + */ + if (!dvmJdwpAcceptConnection(state)) + break; + } else { + /* + * If we're not acting as a server, we need to connect out to the + * debugger. To support the "timeout=xxx" option we need to + * have a timeout if the handshake reply isn't received in a + * reasonable amount of time. + */ + if (!dvmJdwpEstablishConnection(state)) { + /* wake anybody who was waiting for us to succeed */ + dvmDbgLockMutex(&state->attachLock); + dvmDbgCondBroadcast(&state->attachCond); + dvmDbgUnlockMutex(&state->attachLock); + break; + } + } + + /* prep debug code to handle the new connection */ + dvmDbgConnected(); + + /* process requests until the debugger drops */ + first = true; + while (true) { + // sanity check -- shouldn't happen? + if (dvmThreadSelf()->status != THREAD_VMWAIT) { + LOGE("JDWP thread no longer in VMWAIT (now %d); resetting\n", + dvmThreadSelf()->status); + dvmDbgThreadWaiting(); + } + + if (!dvmJdwpProcessIncoming(state)) /* blocking read */ + break; + + if (first && !dvmJdwpAwaitingHandshake(state)) { + /* handshake worked, tell the interpreter that we're active */ + first = false; + + /* set thread ID; requires object registry to be active */ + state->debugThreadId = dvmDbgGetThreadSelfId(); + + /* wake anybody who's waiting for us */ + dvmDbgLockMutex(&state->attachLock); + dvmDbgCondBroadcast(&state->attachCond); + dvmDbgUnlockMutex(&state->attachLock); + } + } + + dvmJdwpCloseConnection(state); + + if (state->ddmActive) { + state->ddmActive = false; + + /* broadcast the disconnect; must be in RUNNING state */ + dvmDbgThreadRunning(); + dvmDbgDdmDisconnected(); + dvmDbgThreadWaiting(); + } + + /* release session state, e.g. remove breakpoint instructions */ + dvmJdwpResetState(state); + + /* tell the interpreter that the debugger is no longer around */ + dvmDbgDisconnected(); + + /* if we had threads suspended, resume them now */ + dvmUndoDebuggerSuspensions(); + + /* if we connected out, this was a one-shot deal */ + if (!state->params.server) + state->run = false; + } + + /* back to running, for thread shutdown */ + dvmDbgThreadRunning(); + + LOGV("JDWP: thread exiting\n"); + return NULL; +} + + +/* + * Return the thread handle, or (pthread_t)0 if the debugger isn't running. + */ +pthread_t dvmJdwpGetDebugThread(JdwpState* state) +{ + if (state == NULL) + return 0; + + return state->debugThreadHandle; +} + + +/* + * Support routines for waitForDebugger(). + * + * We can't have a trivial "waitForDebugger" function that returns the + * instant the debugger connects, because we run the risk of executing code + * before the debugger has had a chance to configure breakpoints or issue + * suspend calls. It would be nice to just sit in the suspended state, but + * most debuggers don't expect any threads to be suspended when they attach. + * + * There's no JDWP event we can post to tell the debugger, "we've stopped, + * and we like it that way". We could send a fake breakpoint, which should + * cause the debugger to immediately send a resume, but the debugger might + * send the resume immediately or might throw an exception of its own upon + * receiving a breakpoint event that it didn't ask for. + * + * What we really want is a "wait until the debugger is done configuring + * stuff" event. We can approximate this with a "wait until the debugger + * has been idle for a brief period". + */ + +/* + * Get a notion of the current time, in milliseconds. + */ +s8 dvmJdwpGetNowMsec(void) +{ +#ifdef HAVE_POSIX_CLOCKS + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + return now.tv_sec * 1000LL + now.tv_nsec / 1000000LL; +#else + struct timeval now; + gettimeofday(&now, NULL); + return now.tv_sec * 1000LL + now.tv_usec / 1000LL; +#endif +} + +/* + * Return the time, in milliseconds, since the last debugger activity. + * + * Returns -1 if no debugger is attached, or 0 if we're in the middle of + * processing a debugger request. + */ +s8 dvmJdwpLastDebuggerActivity(JdwpState* state) +{ + if (!gDvm.debuggerActive) { + LOGD("dvmJdwpLastDebuggerActivity: no active debugger\n"); + return -1; + } + + s8 last = dvmQuasiAtomicRead64(&state->lastActivityWhen); + + /* initializing or in the middle of something? */ + if (last == 0) { + LOGV("+++ last=busy\n"); + return 0; + } + + /* now get the current time */ + s8 now = dvmJdwpGetNowMsec(); + assert(now > last); + + LOGV("+++ debugger interval=%lld\n", now - last); + return now - last; +} |