/* * Copyright (c) 1998, 2012, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code 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 * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ #include <ctype.h> #include "util.h" #include "commonRef.h" #include "debugDispatch.h" #include "eventHandler.h" #include "eventHelper.h" #include "threadControl.h" #include "stepControl.h" #include "transport.h" #include "classTrack.h" #include "debugLoop.h" #include "bag.h" #include "invoker.h" #include "sys.h" // ANDROID-CHANGED: Allow us to initialize VMDebug & ddms apis. #include "vmDebug.h" #include "DDMImpl.h" /* How the options get to OnLoad: */ #define XDEBUG "-Xdebug" #define XRUN "-Xrunjdwp" #define AGENTLIB "-agentlib:jdwp" /* Debug version defaults */ #ifdef DEBUG #define DEFAULT_ASSERT_ON JNI_TRUE #define DEFAULT_ASSERT_FATAL JNI_TRUE #define DEFAULT_LOGFILE "jdwp.log" #else #define DEFAULT_ASSERT_ON JNI_FALSE #define DEFAULT_ASSERT_FATAL JNI_FALSE #define DEFAULT_LOGFILE NULL #endif // ANDROID-CHANGED: Special Art Version to get an ArtTiEnv. This has the same basic api as a // jvmtiEnv but generally has a caveat that everything is best effort. #define ART_TI_VERSION_1_2 (JVMTI_VERSION_1_2 | 0x40000000) static jboolean vmInitialized; static jrawMonitorID initMonitor; static jboolean initComplete; static jbyte currentSessionID; // ANDROID-CHANGED: We need to support OnAttach for android so use this to let other parts know that // we aren't fully initialized yet. static jboolean isInAttach = JNI_FALSE; /* * Options set through the OnLoad options string. All of these values * are set once at VM startup and never reset. */ static jboolean isServer = JNI_FALSE; /* Listens for connecting debuggers? */ static jboolean isStrict = JNI_FALSE; /* Unused */ static jboolean useStandardAlloc = JNI_FALSE; /* Use standard malloc/free? */ static struct bag *transports; /* of TransportSpec */ static jboolean initOnStartup = JNI_TRUE; /* init immediately */ static char *initOnException = NULL; /* init when this exception thrown */ static jboolean initOnUncaught = JNI_FALSE; /* init when uncaught exc thrown */ static char *launchOnInit = NULL; /* launch this app during init */ static jboolean suspendOnInit = JNI_TRUE; /* suspend all app threads after init */ static jboolean dopause = JNI_FALSE; /* pause for debugger attach */ static jboolean docoredump = JNI_FALSE; /* core dump on exit */ static char *logfile = NULL; /* Name of logfile (if logging) */ static unsigned logflags = 0; /* Log flags */ static char *names; /* strings derived from OnLoad options */ /* * Elements of the transports bag */ typedef struct TransportSpec { char *name; char *address; long timeout; } TransportSpec; /* * Forward Refs */ static void JNICALL cbEarlyVMInit(jvmtiEnv*, JNIEnv *, jthread); static void JNICALL cbEarlyVMDeath(jvmtiEnv*, JNIEnv *); static void JNICALL cbEarlyException(jvmtiEnv*, JNIEnv *, jthread, jmethodID, jlocation, jobject, jmethodID, jlocation); static void initialize(JNIEnv *env, jthread thread, EventIndex triggering_ei); static jboolean parseOptions(char *str); /* * Phase 1: Initial load. * * OnLoad is called by the VM immediately after the back-end * library is loaded. We can do very little in this function since * the VM has not completed initialization. So, we parse the JDWP * options and set up a simple initial event callbacks for JVMTI events. * When a triggering event occurs, that callback will begin debugger initialization. */ /* Get a static area to hold the Global Data */ static BackendGlobalData * get_gdata(void) { static BackendGlobalData s; (void)memset(&s, 0, sizeof(BackendGlobalData)); return &s; } static jvmtiError set_event_notification(jvmtiEventMode mode, EventIndex ei) { jvmtiError error; error = JVMTI_FUNC_PTR(gdata->jvmti,SetEventNotificationMode) (gdata->jvmti, mode, eventIndex2jvmti(ei), NULL); if (error != JVMTI_ERROR_NONE) { ERROR_MESSAGE(("JDWP unable to configure initial JVMTI event %s: %s(%d)", eventText(ei), jvmtiErrorText(error), error)); } return error; } typedef struct { int major; int minor; } version_type; typedef struct { version_type runtime; version_type compiletime; } compatible_versions_type; /* * List of explicitly compatible JVMTI versions, specified as * { runtime version, compile-time version } pairs. -1 is a wildcard. */ static int nof_compatible_versions = 3; static compatible_versions_type compatible_versions_list[] = { /* * FIXUP: Allow version 0 to be compatible with anything * Special check for FCS of 1.0. */ { { 0, -1 }, { -1, -1 } }, { { -1, -1 }, { 0, -1 } }, /* * 1.2 is runtime compatible with 1.1 -- just make sure to check the * version before using any new 1.2 features */ { { 1, 1 }, { 1, 2 } } }; /* Logic to determine JVMTI version compatibility */ static jboolean compatible_versions(jint major_runtime, jint minor_runtime, jint major_compiletime, jint minor_compiletime) { /* * First check to see if versions are explicitly compatible via the * list specified above. */ int i; for (i = 0; i < nof_compatible_versions; ++i) { version_type runtime = compatible_versions_list[i].runtime; version_type comptime = compatible_versions_list[i].compiletime; if ((major_runtime == runtime.major || runtime.major == -1) && (minor_runtime == runtime.minor || runtime.minor == -1) && (major_compiletime == comptime.major || comptime.major == -1) && (minor_compiletime == comptime.minor || comptime.minor == -1)) { return JNI_TRUE; } } return major_runtime == major_compiletime && minor_runtime >= minor_compiletime; } // ANDROID-CHANGED: Function to get and set the com.android.art.internal.ddm.process_chunk extension // function. This returns JNI_ERR if something went wrong with searching. If the extension is not // found we return JNI_OK and don't bother updating the gdata pointer. static jint find_ddm_process_chunk() { jvmtiError error; jvmtiExtensionFunctionInfo* extension_info; jint num_extensions; jboolean found; int i; int j; found = JNI_FALSE; error = JVMTI_FUNC_PTR(gdata->jvmti,GetExtensionFunctions) (gdata->jvmti, &num_extensions, &extension_info); if (error != JVMTI_ERROR_NONE) { ERROR_MESSAGE(("JDWP Unable to get jvmti extension functions: %s(%d)", jvmtiErrorText(error), error)); return JNI_ERR; } // We iterate through every extension function even once we found the one we want in order to // clean them all up as we go. for (i = 0; i < num_extensions; i++) { if (strcmp("com.android.art.internal.ddm.process_chunk", extension_info[i].id) == 0) { gdata->ddm_process_chunk = (DdmProcessChunk) extension_info[i].func; } jvmtiDeallocate(extension_info[i].id); jvmtiDeallocate(extension_info[i].short_description); for (j = 0; j < extension_info[i].param_count; j++) { jvmtiDeallocate(extension_info[i].params[j].name); } jvmtiDeallocate(extension_info[i].params); jvmtiDeallocate(extension_info[i].errors); } jvmtiDeallocate(extension_info); return JNI_OK; } /* OnLoad startup: * Returning JNI_ERR will cause the java_g VM to core dump, be careful. */ JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm, char *options, void *reserved) { jvmtiError error; jvmtiCapabilities needed_capabilities; jvmtiCapabilities potential_capabilities; jint jvmtiCompileTimeMajorVersion; jint jvmtiCompileTimeMinorVersion; jint jvmtiCompileTimeMicroVersion; char *boot_path = NULL; char npt_lib[MAXPATHLEN]; /* See if it's already loaded */ if ( gdata!=NULL && gdata->isLoaded==JNI_TRUE ) { ERROR_MESSAGE(("Cannot load this JVM TI agent twice, check your java command line for duplicate jdwp options.")); return JNI_ERR; } /* If gdata is defined and the VM died, why are we here? */ if ( gdata!=NULL && gdata->vmDead ) { ERROR_MESSAGE(("JDWP unable to load, VM died")); return JNI_ERR; } /* Get global data area */ gdata = get_gdata(); if (gdata == NULL) { ERROR_MESSAGE(("JDWP unable to allocate memory")); return JNI_ERR; } gdata->isLoaded = JNI_TRUE; /* Start filling in gdata */ gdata->jvm = vm; vmInitialized = JNI_FALSE; gdata->vmDead = JNI_FALSE; /* Get the JVMTI Env, IMPORTANT: Do this first! For jvmtiAllocate(). */ error = JVM_FUNC_PTR(vm,GetEnv) (vm, (void **)&(gdata->jvmti), JVMTI_VERSION_1); // ANDROID-CHANGED: Check for ART_TI_VERSION_1_2 if we cannot get real JVMTI. This is done only // to support the userdebug debug-anything behavior. if (error != JNI_OK) { ERROR_MESSAGE(("JDWP unable to access JVMTI Version 1 (0x%x)," " retrying using ART_TI instead since this might be a userdebug device." " JNIEnv's GetEnv() returned %d", JVMTI_VERSION_1, error)); // Try to get an ArtTiEnv instead error = JVM_FUNC_PTR(vm,GetEnv) (vm, (void **)&(gdata->jvmti), ART_TI_VERSION_1_2); } if (error != JNI_OK) { ERROR_MESSAGE(("JDWP unable to access either JVMTI Version 1 (0x%x)" " or ART_TI_VERSION_1_2 (0x%x)," " is your J2SE a 1.5 or newer version?" " JNIEnv's GetEnv() returned %d", JVMTI_VERSION_1, ART_TI_VERSION_1_2, error)); forceExit(1); /* Kill entire process, no core dump */ } /* Check to make sure the version of jvmti.h we compiled with * matches the runtime version we are using. */ jvmtiCompileTimeMajorVersion = ( JVMTI_VERSION & JVMTI_VERSION_MASK_MAJOR ) >> JVMTI_VERSION_SHIFT_MAJOR; jvmtiCompileTimeMinorVersion = ( JVMTI_VERSION & JVMTI_VERSION_MASK_MINOR ) >> JVMTI_VERSION_SHIFT_MINOR; jvmtiCompileTimeMicroVersion = ( JVMTI_VERSION & JVMTI_VERSION_MASK_MICRO ) >> JVMTI_VERSION_SHIFT_MICRO; /* Check for compatibility */ if ( !compatible_versions(jvmtiMajorVersion(), jvmtiMinorVersion(), jvmtiCompileTimeMajorVersion, jvmtiCompileTimeMinorVersion) ) { ERROR_MESSAGE(("This jdwp native library will not work with this VM's " "version of JVMTI (%d.%d.%d), it needs JVMTI %d.%d[.%d].", jvmtiMajorVersion(), jvmtiMinorVersion(), jvmtiMicroVersion(), jvmtiCompileTimeMajorVersion, jvmtiCompileTimeMinorVersion, jvmtiCompileTimeMicroVersion)); /* Do not let VM get a fatal error, we don't want a core dump here. */ forceExit(1); /* Kill entire process, no core dump wanted */ } // ANDROID-CHANGED: Android uses java.library.path to store all library path information. JVMTI_FUNC_PTR(gdata->jvmti, GetSystemProperty) (gdata->jvmti, (const char *)"java.library.path", &boot_path); dbgsysBuildLibName(npt_lib, sizeof(npt_lib), boot_path, NPT_LIBNAME); /* Npt and Utf function init */ NPT_INITIALIZE(npt_lib, &(gdata->npt), NPT_VERSION, NULL); jvmtiDeallocate(boot_path); if (gdata->npt == NULL) { ERROR_MESSAGE(("JDWP: unable to initialize NPT library")); return JNI_ERR; } gdata->npt->utf = (gdata->npt->utfInitialize)(NULL); if (gdata->npt->utf == NULL) { ERROR_MESSAGE(("JDWP: UTF function initialization failed")); return JNI_ERR; } /* Parse input options */ if (!parseOptions(options)) { /* No message necessary, should have been printed out already */ /* Do not let VM get a fatal error, we don't want a core dump here. */ forceExit(1); /* Kill entire process, no core dump wanted */ } LOG_MISC(("Onload: %s", options)); /* Get potential capabilities */ (void)memset(&potential_capabilities,0,sizeof(potential_capabilities)); error = JVMTI_FUNC_PTR(gdata->jvmti,GetPotentialCapabilities) (gdata->jvmti, &potential_capabilities); if (error != JVMTI_ERROR_NONE) { ERROR_MESSAGE(("JDWP unable to get potential JVMTI capabilities: %s(%d)", jvmtiErrorText(error), error)); return JNI_ERR; } /* Fill in ones that we must have */ (void)memset(&needed_capabilities,0,sizeof(needed_capabilities)); needed_capabilities.can_access_local_variables = 1; needed_capabilities.can_generate_single_step_events = 1; needed_capabilities.can_generate_exception_events = 1; needed_capabilities.can_generate_frame_pop_events = 1; needed_capabilities.can_generate_breakpoint_events = 1; needed_capabilities.can_suspend = 1; needed_capabilities.can_generate_method_entry_events = 1; needed_capabilities.can_generate_method_exit_events = 1; needed_capabilities.can_generate_garbage_collection_events = 1; needed_capabilities.can_maintain_original_method_order = 1; needed_capabilities.can_generate_monitor_events = 1; needed_capabilities.can_tag_objects = 1; /* And what potential ones that would be nice to have */ needed_capabilities.can_force_early_return = potential_capabilities.can_force_early_return; needed_capabilities.can_generate_field_modification_events = potential_capabilities.can_generate_field_modification_events; needed_capabilities.can_generate_field_access_events = potential_capabilities.can_generate_field_access_events; needed_capabilities.can_get_bytecodes = potential_capabilities.can_get_bytecodes; needed_capabilities.can_get_synthetic_attribute = potential_capabilities.can_get_synthetic_attribute; needed_capabilities.can_get_owned_monitor_info = potential_capabilities.can_get_owned_monitor_info; needed_capabilities.can_get_current_contended_monitor = potential_capabilities.can_get_current_contended_monitor; needed_capabilities.can_get_monitor_info = potential_capabilities.can_get_monitor_info; needed_capabilities.can_pop_frame = potential_capabilities.can_pop_frame; needed_capabilities.can_redefine_classes = potential_capabilities.can_redefine_classes; needed_capabilities.can_redefine_any_class = potential_capabilities.can_redefine_any_class; needed_capabilities.can_get_owned_monitor_stack_depth_info = potential_capabilities.can_get_owned_monitor_stack_depth_info; needed_capabilities.can_get_constant_pool = potential_capabilities.can_get_constant_pool; { needed_capabilities.can_get_source_debug_extension = 1; needed_capabilities.can_get_source_file_name = 1; needed_capabilities.can_get_line_numbers = 1; needed_capabilities.can_signal_thread = potential_capabilities.can_signal_thread; } /* Add the capabilities */ error = JVMTI_FUNC_PTR(gdata->jvmti,AddCapabilities) (gdata->jvmti, &needed_capabilities); if (error != JVMTI_ERROR_NONE) { ERROR_MESSAGE(("JDWP unable to get necessary JVMTI capabilities.")); forceExit(1); /* Kill entire process, no core dump wanted */ } /* Initialize event number mapping tables */ eventIndexInit(); /* Set the initial JVMTI event notifications */ error = set_event_notification(JVMTI_ENABLE, EI_VM_DEATH); if (error != JVMTI_ERROR_NONE) { return JNI_ERR; } error = set_event_notification(JVMTI_ENABLE, EI_VM_INIT); if (error != JVMTI_ERROR_NONE) { return JNI_ERR; } if (initOnUncaught || (initOnException != NULL)) { error = set_event_notification(JVMTI_ENABLE, EI_EXCEPTION); if (error != JVMTI_ERROR_NONE) { return JNI_ERR; } } /* Set callbacks just for 3 functions */ (void)memset(&(gdata->callbacks),0,sizeof(gdata->callbacks)); gdata->callbacks.VMInit = &cbEarlyVMInit; gdata->callbacks.VMDeath = &cbEarlyVMDeath; gdata->callbacks.Exception = &cbEarlyException; error = JVMTI_FUNC_PTR(gdata->jvmti,SetEventCallbacks) (gdata->jvmti, &(gdata->callbacks), sizeof(gdata->callbacks)); if (error != JVMTI_ERROR_NONE) { ERROR_MESSAGE(("JDWP unable to set JVMTI event callbacks: %s(%d)", jvmtiErrorText(error), error)); return JNI_ERR; } // ANDROID-CHANGED: Find com.android.art.internal.ddm.process_chunk function if it exists. if (find_ddm_process_chunk() != JNI_OK) { ERROR_MESSAGE(("Fatal error while attempting to find the " "com.android.art.internal.ddm.process_chunk extension function")); return JNI_ERR; } LOG_MISC(("OnLoad: DONE")); return JNI_OK; } JNIEXPORT void JNICALL Agent_OnUnload(JavaVM *vm) { gdata->isLoaded = JNI_FALSE; /* Cleanup, but make sure VM is alive before using JNI, and * make sure JVMTI environment is ok before deallocating * memory allocated through JVMTI, which all of it is. */ /* * Close transport before exit */ if (transport_is_open()) { transport_close(); } } /* * Phase 2: Initial events. Phase 2 consists of waiting for the * event that triggers full initialization. Under normal circumstances * (initOnStartup == TRUE) this is the JVMTI_EVENT_VM_INIT event. * Otherwise, we delay initialization until the app throws a * particular exception. The triggering event invokes * the bulk of the initialization, including creation of threads and * monitors, transport setup, and installation of a new event callback which * handles the complete set of events. * * Since the triggering event comes in on an application thread, some of the * initialization is difficult to do here. Specifically, this thread along * with all other app threads may need to be suspended until a debugger * connects. These kinds of tasks are left to the third phase which is * invoked by one of the spawned debugger threads, the event handler. */ /* * Wait for a triggering event; then kick off debugger * initialization. A different event callback will be installed by * debugger initialization, and this function will not be called * again. */ /* * TO DO: Decide whether we need to protect this code with * a lock. It might be too early to create a monitor safely (?). */ static void JNICALL cbEarlyVMInit(jvmtiEnv *jvmti_env, JNIEnv *env, jthread thread) { LOG_CB(("cbEarlyVMInit")); if ( gdata->vmDead ) { EXIT_ERROR(AGENT_ERROR_INTERNAL,"VM dead at VM_INIT time"); } if (initOnStartup) initialize(env, thread, EI_VM_INIT); vmInitialized = JNI_TRUE; LOG_MISC(("END cbEarlyVMInit")); } static void disposeEnvironment(jvmtiEnv *jvmti_env) { jvmtiError error; error = JVMTI_FUNC_PTR(jvmti_env,DisposeEnvironment)(jvmti_env); if ( error == JVMTI_ERROR_MUST_POSSESS_CAPABILITY ) error = JVMTI_ERROR_NONE; /* Hack! FIXUP when JVMTI has disposeEnv */ /* What should error return say? */ if (error != JVMTI_ERROR_NONE) { ERROR_MESSAGE(("JDWP unable to dispose of JVMTI environment: %s(%d)", jvmtiErrorText(error), error)); } gdata->jvmti = NULL; } static void JNICALL cbEarlyVMDeath(jvmtiEnv *jvmti_env, JNIEnv *env) { LOG_CB(("cbEarlyVMDeath")); if ( gdata->vmDead ) { EXIT_ERROR(AGENT_ERROR_INTERNAL,"VM died more than once"); } disposeEnvironment(jvmti_env); gdata->jvmti = NULL; gdata->jvm = NULL; gdata->vmDead = JNI_TRUE; LOG_MISC(("END cbEarlyVMDeath")); } static void JNICALL cbEarlyException(jvmtiEnv *jvmti_env, JNIEnv *env, jthread thread, jmethodID method, jlocation location, jobject exception, jmethodID catch_method, jlocation catch_location) { jvmtiError error; jthrowable currentException; LOG_CB(("cbEarlyException: thread=%p", thread)); if ( gdata->vmDead ) { EXIT_ERROR(AGENT_ERROR_INTERNAL,"VM dead at initial Exception event"); } if (!vmInitialized) { LOG_MISC(("VM is not initialized yet")); return; } /* * We want to preserve any current exception that might get wiped * out during event handling (e.g. JNI calls). We have to rely on * space for the local reference on the current frame because * doing a PushLocalFrame here might itself generate an exception. */ currentException = JNI_FUNC_PTR(env,ExceptionOccurred)(env); JNI_FUNC_PTR(env,ExceptionClear)(env); if (initOnUncaught && catch_method == NULL) { LOG_MISC(("Initializing on uncaught exception")); initialize(env, thread, EI_EXCEPTION); } else if (initOnException != NULL) { jclass clazz; /* Get class of exception thrown */ clazz = JNI_FUNC_PTR(env,GetObjectClass)(env, exception); if ( clazz != NULL ) { char *signature = NULL; /* initing on throw, check */ error = classSignature(clazz, &signature, NULL); LOG_MISC(("Checking specific exception: looking for %s, got %s", initOnException, signature)); if ( (error==JVMTI_ERROR_NONE) && (strcmp(signature, initOnException) == 0)) { LOG_MISC(("Initializing on specific exception")); initialize(env, thread, EI_EXCEPTION); } else { error = AGENT_ERROR_INTERNAL; /* Just to cause restore */ } if ( signature != NULL ) { jvmtiDeallocate(signature); } } else { error = AGENT_ERROR_INTERNAL; /* Just to cause restore */ } /* If initialize didn't happen, we need to restore things */ if ( error != JVMTI_ERROR_NONE ) { /* * Restore exception state from before callback call */ LOG_MISC(("No initialization, didn't find right exception")); if (currentException != NULL) { JNI_FUNC_PTR(env,Throw)(env, currentException); } else { JNI_FUNC_PTR(env,ExceptionClear)(env); } } } LOG_MISC(("END cbEarlyException")); } typedef struct EnumerateArg { jboolean isServer; jdwpError error; jint startCount; } EnumerateArg; static jboolean startTransport(void *item, void *arg) { TransportSpec *transport = item; EnumerateArg *enumArg = arg; jdwpError serror; LOG_MISC(("Begin startTransport")); serror = transport_startTransport(enumArg->isServer, transport->name, transport->address, transport->timeout); if (serror != JDWP_ERROR(NONE)) { ERROR_MESSAGE(("JDWP Transport %s failed to initialize, %s(%d)", transport->name, jdwpErrorText(serror), serror)); enumArg->error = serror; } else { /* (Don't overwrite any previous error) */ enumArg->startCount++; } LOG_MISC(("End startTransport")); return JNI_TRUE; /* Always continue, even if there was an error */ } static void signalInitComplete(void) { /* * Initialization is complete */ LOG_MISC(("signal initialization complete")); debugMonitorEnter(initMonitor); initComplete = JNI_TRUE; debugMonitorNotifyAll(initMonitor); debugMonitorExit(initMonitor); } /* * Determine if initialization is complete. */ jboolean debugInit_isInitComplete(void) { return initComplete; } /* * Wait for all initialization to complete. */ void debugInit_waitInitComplete(void) { debugMonitorEnter(initMonitor); while (!initComplete) { debugMonitorWait(initMonitor); } debugMonitorExit(initMonitor); } /* All process exit() calls come from here */ void forceExit(int exit_code) { /* make sure the transport is closed down before we exit() */ transport_close(); exit(exit_code); } /* All JVM fatal error exits lead here (e.g. we need to kill the VM). */ static void jniFatalError(JNIEnv *env, const char *msg, jvmtiError error, int exit_code) { JavaVM *vm; char buf[512]; gdata->vmDead = JNI_TRUE; if ( msg==NULL ) msg = "UNKNOWN REASON"; vm = gdata->jvm; if ( env==NULL && vm!=NULL ) { jint rc = (*((*vm)->GetEnv))(vm, (void **)&env, JNI_VERSION_1_2); if (rc != JNI_OK ) { env = NULL; } } if ( error != JVMTI_ERROR_NONE ) { (void)snprintf(buf, sizeof(buf), "JDWP %s, jvmtiError=%s(%d)", msg, jvmtiErrorText(error), error); } else { (void)snprintf(buf, sizeof(buf), "JDWP %s", buf); } if (env != NULL) { (*((*env)->FatalError))(env, buf); } else { /* Should rarely ever reach here, means VM is really dead */ print_message(stderr, "ERROR: JDWP: ", "\n", "Can't call JNI FatalError(NULL, \"%s\")", buf); } forceExit(exit_code); } /* * Initialize debugger back end modules */ static void initialize(JNIEnv *env, jthread thread, EventIndex triggering_ei) { jvmtiError error; EnumerateArg arg; jbyte suspendPolicy; LOG_MISC(("Begin initialize()")); currentSessionID = 0; initComplete = JNI_FALSE; if ( gdata->vmDead ) { EXIT_ERROR(AGENT_ERROR_INTERNAL,"VM dead at initialize() time"); } /* Turn off the initial JVMTI event notifications */ error = set_event_notification(JVMTI_DISABLE, EI_EXCEPTION); if (error != JVMTI_ERROR_NONE) { EXIT_ERROR(error, "unable to disable JVMTI event notification"); } error = set_event_notification(JVMTI_DISABLE, EI_VM_INIT); if (error != JVMTI_ERROR_NONE) { EXIT_ERROR(error, "unable to disable JVMTI event notification"); } error = set_event_notification(JVMTI_DISABLE, EI_VM_DEATH); if (error != JVMTI_ERROR_NONE) { EXIT_ERROR(error, "unable to disable JVMTI event notification"); } /* Remove initial event callbacks */ (void)memset(&(gdata->callbacks),0,sizeof(gdata->callbacks)); error = JVMTI_FUNC_PTR(gdata->jvmti,SetEventCallbacks) (gdata->jvmti, &(gdata->callbacks), sizeof(gdata->callbacks)); if (error != JVMTI_ERROR_NONE) { EXIT_ERROR(error, "unable to clear JVMTI callbacks"); } commonRef_initialize(); util_initialize(env); threadControl_initialize(); stepControl_initialize(); invoker_initialize(); debugDispatch_initialize(); classTrack_initialize(env); debugLoop_initialize(); // ANDROID-CHANGED: Set up DDM DDM_initialize(); // ANDROID-CHANGED: Take over relevant VMDebug APIs. vmDebug_initalize(env); initMonitor = debugMonitorCreate("JDWP Initialization Monitor"); /* * Initialize transports */ arg.isServer = isServer; arg.error = JDWP_ERROR(NONE); arg.startCount = 0; transport_initialize(); (void)bagEnumerateOver(transports, startTransport, &arg); /* * Exit with an error only if * 1) none of the transports was successfully started, and * 2) the application has not yet started running */ if ((arg.error != JDWP_ERROR(NONE)) && (arg.startCount == 0) && initOnStartup) { EXIT_ERROR(map2jvmtiError(arg.error), "No transports initialized"); } eventHandler_initialize(currentSessionID); signalInitComplete(); transport_waitForConnection(); suspendPolicy = suspendOnInit ? JDWP_SUSPEND_POLICY(ALL) : JDWP_SUSPEND_POLICY(NONE); // ANDROID-CHANGED: Don't send any event if we are actually in Agent_OnAttach. if (isInAttach) { // Do Nothing. } else if (triggering_ei == EI_VM_INIT) { LOG_MISC(("triggering_ei == EI_VM_INIT")); eventHelper_reportVMInit(env, currentSessionID, thread, suspendPolicy); } else { /* * TO DO: Kludgy way of getting the triggering event to the * just-attached debugger. It would be nice to make this a little * cleaner. There is also a race condition where other events * can get in the queue (from other not-yet-suspended threads) * before this one does. (Also need to handle allocation error below?) */ EventInfo info; struct bag *initEventBag; LOG_MISC(("triggering_ei != EI_VM_INIT")); initEventBag = eventHelper_createEventBag(); (void)memset(&info,0,sizeof(info)); info.ei = triggering_ei; eventHelper_recordEvent(&info, 0, suspendPolicy, initEventBag); (void)eventHelper_reportEvents(currentSessionID, initEventBag); bagDestroyBag(initEventBag); } if ( gdata->vmDead ) { EXIT_ERROR(AGENT_ERROR_INTERNAL,"VM dead before initialize() completes"); } LOG_MISC(("End initialize()")); } /* * Restore all static data to the initialized state so that another * debugger can connect properly later. */ void debugInit_reset(JNIEnv *env) { EnumerateArg arg; LOG_MISC(("debugInit_reset() beginning")); currentSessionID++; initComplete = JNI_FALSE; eventHandler_reset(currentSessionID); transport_reset(); debugDispatch_reset(); invoker_reset(); stepControl_reset(); threadControl_reset(); util_reset(); commonRef_reset(env); classTrack_reset(); /* * If this is a server, we are now ready to accept another connection. * If it's a client, then we've cleaned up some (more should be added * later) and we're done. */ if (isServer) { arg.isServer = JNI_TRUE; arg.error = JDWP_ERROR(NONE); arg.startCount = 0; (void)bagEnumerateOver(transports, startTransport, &arg); signalInitComplete(); transport_waitForConnection(); } else { signalInitComplete(); /* Why? */ } LOG_MISC(("debugInit_reset() completed.")); } char * debugInit_launchOnInit(void) { return launchOnInit; } jboolean debugInit_suspendOnInit(void) { return suspendOnInit; } /* * code below is shamelessly swiped from hprof. */ static int get_tok(char **src, char *buf, int buflen, char sep) { int i; char *p = *src; for (i = 0; i < buflen; i++) { if (p[i] == 0 || p[i] == sep) { buf[i] = 0; if (p[i] == sep) { i++; } *src += i; return i; } buf[i] = p[i]; } /* overflow */ return 0; } static void printUsage(void) { TTY_MESSAGE(( " Java Debugger JDWP Agent Library\n" " --------------------------------\n" "\n" " (see http://java.sun.com/products/jpda for more information)\n" "\n" "jdwp usage: java " AGENTLIB "=[help]|[<option>=<value>, ...]\n" "\n" "Option Name and Value Description Default\n" "--------------------- ----------- -------\n" "suspend=y|n wait on startup? y\n" "transport=<name> transport spec none\n" "address=<listen/attach address> transport spec \"\"\n" "server=y|n listen for debugger? n\n" "launch=<command line> run debugger on event none\n" "onthrow=<exception name> debug on throw none\n" "onuncaught=y|n debug on any uncaught? n\n" "timeout=<timeout value> for listen/attach in milliseconds n\n" "mutf8=y|n output modified utf-8 n\n" "quiet=y|n control over terminal messages n\n" "\n" "Obsolete Options\n" "----------------\n" "strict=y|n\n" "stdalloc=y|n\n" "\n" "Examples\n" "--------\n" " - Using sockets connect to a debugger at a specific address:\n" " java " AGENTLIB "=transport=dt_socket,address=localhost:8000 ...\n" " - Using sockets listen for a debugger to attach:\n" " java " AGENTLIB "=transport=dt_socket,server=y,suspend=y ...\n" "\n" "Notes\n" "-----\n" " - A timeout value of 0 (the default) is no timeout.\n" "\n" "Warnings\n" "--------\n" " - The older " XRUN " interface can still be used, but will be removed in\n" " a future release, for example:\n" " java " XDEBUG " " XRUN ":[help]|[<option>=<value>, ...]\n" )); #ifdef DEBUG TTY_MESSAGE(( "\n" "Debugging Options Description Default\n" "----------------- ----------- -------\n" "pause=y|n pause to debug PID n\n" "coredump=y|n coredump at exit n\n" "errorexit=y|n exit on any error n\n" "logfile=filename name of log file none\n" "logflags=flags log flags (bitmask) none\n" " JVM calls = 0x001\n" " JNI calls = 0x002\n" " JVMTI calls = 0x004\n" " misc events = 0x008\n" " step logs = 0x010\n" " locations = 0x020\n" " callbacks = 0x040\n" " errors = 0x080\n" " everything = 0xfff\n" "debugflags=flags debug flags (bitmask) none\n" " USE_ITERATE_THROUGH_HEAP 0x01\n" "\n" "Environment Variables\n" "---------------------\n" "_JAVA_JDWP_OPTIONS\n" " Options can be added externally via this environment variable.\n" " Anything contained in it will get a comma prepended to it (if needed),\n" " then it will be added to the end of the options supplied via the\n" " " XRUN " or " AGENTLIB " command line option.\n" )); #endif } static jboolean checkAddress(void *bagItem, void *arg) { TransportSpec *spec = (TransportSpec *)bagItem; if (spec->address == NULL) { ERROR_MESSAGE(("JDWP Non-server transport %s must have a connection " "address specified through the 'address=' option", spec->name)); return JNI_FALSE; } else { return JNI_TRUE; } } static char * add_to_options(char *options, char *new_options) { size_t originalLength; char *combinedOptions; /* * Allocate enough space for both strings and * comma in between. */ originalLength = strlen(options); combinedOptions = jvmtiAllocate((jint)originalLength + 1 + (jint)strlen(new_options) + 1); if (combinedOptions == NULL) { return NULL; } (void)strcpy(combinedOptions, options); (void)strcat(combinedOptions, ","); (void)strcat(combinedOptions, new_options); return combinedOptions; } static jboolean get_boolean(char **pstr, jboolean *answer) { char buf[80]; *answer = JNI_FALSE; /*LINTED*/ if (get_tok(pstr, buf, (int)sizeof(buf), ',')) { if (strcmp(buf, "y") == 0) { *answer = JNI_TRUE; return JNI_TRUE; } else if (strcmp(buf, "n") == 0) { *answer = JNI_FALSE; return JNI_TRUE; } } return JNI_FALSE; } /* atexit() callback */ static void atexit_finish_logging(void) { /* Normal exit(0) (not _exit()) may only reach here */ finish_logging(); /* Only first call matters */ } static jboolean parseOptions(char *options) { TransportSpec *currentTransport = NULL; char *end; char *current; int length; char *str; char *errmsg; /* Set defaults */ gdata->assertOn = DEFAULT_ASSERT_ON; gdata->assertFatal = DEFAULT_ASSERT_FATAL; logfile = DEFAULT_LOGFILE; // ANDROID-CHANGED: By default we assume ddms is off initially. gdata->ddmInitiallyActive = JNI_FALSE; /* Options being NULL will end up being an error. */ if (options == NULL) { options = ""; } /* Check for "help" BEFORE we add any environmental settings */ if ((strcmp(options, "help")) == 0) { printUsage(); forceExit(0); /* Kill entire process, no core dump wanted */ } /* These buffers are never freed */ { char *envOptions; /* * Add environmentally specified options. */ envOptions = getenv("_JAVA_JDWP_OPTIONS"); if (envOptions != NULL) { options = add_to_options(options, envOptions); if ( options==NULL ) { EXIT_ERROR(AGENT_ERROR_OUT_OF_MEMORY,"options"); } } /* * Allocate a buffer for names derived from option strings. It should * never be longer than the original options string itself. * Also keep a copy of the options in gdata->options. */ length = (int)strlen(options); gdata->options = jvmtiAllocate(length + 1); if (gdata->options == NULL) { EXIT_ERROR(AGENT_ERROR_OUT_OF_MEMORY,"options"); } (void)strcpy(gdata->options, options); names = jvmtiAllocate(length + 1); if (names == NULL) { EXIT_ERROR(AGENT_ERROR_OUT_OF_MEMORY,"options"); } transports = bagCreateBag(sizeof(TransportSpec), 3); if (transports == NULL) { EXIT_ERROR(AGENT_ERROR_OUT_OF_MEMORY,"transports"); } } current = names; end = names + length; str = options; while (*str) { char buf[100]; /*LINTED*/ if (!get_tok(&str, buf, (int)sizeof(buf), '=')) { goto syntax_error; } if (strcmp(buf, "transport") == 0) { currentTransport = bagAdd(transports); /*LINTED*/ if (!get_tok(&str, current, (int)(end - current), ',')) { goto syntax_error; } currentTransport->name = current; current += strlen(current) + 1; } else if (strcmp(buf, "address") == 0) { if (currentTransport == NULL) { errmsg = "address specified without transport"; goto bad_option_with_errmsg; } /*LINTED*/ if (!get_tok(&str, current, (int)(end - current), ',')) { goto syntax_error; } currentTransport->address = current; current += strlen(current) + 1; } else if (strcmp(buf, "timeout") == 0) { if (currentTransport == NULL) { errmsg = "timeout specified without transport"; goto bad_option_with_errmsg; } /*LINTED*/ if (!get_tok(&str, current, (int)(end - current), ',')) { goto syntax_error; } currentTransport->timeout = atol(current); current += strlen(current) + 1; } else if (strcmp(buf, "launch") == 0) { /*LINTED*/ if (!get_tok(&str, current, (int)(end - current), ',')) { goto syntax_error; } launchOnInit = current; current += strlen(current) + 1; } else if (strcmp(buf, "onthrow") == 0) { /* Read class name and convert in place to a signature */ *current = 'L'; /*LINTED*/ if (!get_tok(&str, current + 1, (int)(end - current - 1), ',')) { goto syntax_error; } initOnException = current; while (*current != '\0') { if (*current == '.') { *current = '/'; } current++; } *current++ = ';'; *current++ = '\0'; } else if (strcmp(buf, "assert") == 0) { /*LINTED*/ if (!get_tok(&str, current, (int)(end - current), ',')) { goto syntax_error; } if (strcmp(current, "y") == 0) { gdata->assertOn = JNI_TRUE; gdata->assertFatal = JNI_FALSE; } else if (strcmp(current, "fatal") == 0) { gdata->assertOn = JNI_TRUE; gdata->assertFatal = JNI_TRUE; } else if (strcmp(current, "n") == 0) { gdata->assertOn = JNI_FALSE; gdata->assertFatal = JNI_FALSE; } else { goto syntax_error; } current += strlen(current) + 1; } else if (strcmp(buf, "pause") == 0) { if ( !get_boolean(&str, &dopause) ) { goto syntax_error; } if ( dopause ) { do_pause(); } } else if (strcmp(buf, "coredump") == 0) { if ( !get_boolean(&str, &docoredump) ) { goto syntax_error; } } else if (strcmp(buf, "errorexit") == 0) { if ( !get_boolean(&str, &(gdata->doerrorexit)) ) { goto syntax_error; } } else if (strcmp(buf, "exitpause") == 0) { errmsg = "The exitpause option removed, use -XX:OnError"; goto bad_option_with_errmsg; } else if (strcmp(buf, "precrash") == 0) { errmsg = "The precrash option removed, use -XX:OnError"; goto bad_option_with_errmsg; } else if (strcmp(buf, "logfile") == 0) { /*LINTED*/ if (!get_tok(&str, current, (int)(end - current), ',')) { goto syntax_error; } logfile = current; current += strlen(current) + 1; } else if (strcmp(buf, "logflags") == 0) { /*LINTED*/ if (!get_tok(&str, current, (int)(end - current), ',')) { goto syntax_error; } /*LINTED*/ logflags = (unsigned)strtol(current, NULL, 0); } else if (strcmp(buf, "debugflags") == 0) { /*LINTED*/ if (!get_tok(&str, current, (int)(end - current), ',')) { goto syntax_error; } /*LINTED*/ gdata->debugflags = (unsigned)strtol(current, NULL, 0); } else if ( strcmp(buf, "suspend")==0 ) { if ( !get_boolean(&str, &suspendOnInit) ) { goto syntax_error; } } else if ( strcmp(buf, "server")==0 ) { if ( !get_boolean(&str, &isServer) ) { goto syntax_error; } } else if ( strcmp(buf, "strict")==0 ) { /* Obsolete, but accept it */ if ( !get_boolean(&str, &isStrict) ) { goto syntax_error; } } else if ( strcmp(buf, "quiet")==0 ) { if ( !get_boolean(&str, &(gdata->quiet)) ) { goto syntax_error; } } else if ( strcmp(buf, "onuncaught")==0 ) { if ( !get_boolean(&str, &initOnUncaught) ) { goto syntax_error; } } else if ( strcmp(buf, "mutf8")==0 ) { if ( !get_boolean(&str, &(gdata->modifiedUtf8)) ) { goto syntax_error; } } else if ( strcmp(buf, "stdalloc")==0 ) { /* Obsolete, but accept it */ if ( !get_boolean(&str, &useStandardAlloc) ) { goto syntax_error; } // ANDROID-CHANGED: Need to be able to tell if ddm is initially running. } else if ( strcmp(buf, "ddm_already_active")==0 ) { if ( !get_boolean(&str, &(gdata->ddmInitiallyActive)) ) { goto syntax_error; } } else { goto syntax_error; } } /* Setup logging now */ if ( logfile!=NULL ) { setup_logging(logfile, logflags); (void)atexit(&atexit_finish_logging); } if (bagSize(transports) == 0) { errmsg = "no transport specified"; goto bad_option_with_errmsg; } /* * TO DO: Remove when multiple transports are allowed. (replace with * check below. */ if (bagSize(transports) > 1) { errmsg = "multiple transports are not supported in this release"; goto bad_option_with_errmsg; } if (!isServer) { jboolean specified = bagEnumerateOver(transports, checkAddress, NULL); if (!specified) { /* message already printed */ goto bad_option_no_msg; } } /* * The user has selected to wait for an exception before init happens */ if ((initOnException != NULL) || (initOnUncaught)) { initOnStartup = JNI_FALSE; if (launchOnInit == NULL) { /* * These rely on the launch=/usr/bin/foo * suboption, so it is an error if user did not * provide one. */ errmsg = "Specify launch=<command line> when using onthrow or onuncaught suboption"; goto bad_option_with_errmsg; } } return JNI_TRUE; syntax_error: ERROR_MESSAGE(("JDWP option syntax error: %s=%s", AGENTLIB, options)); return JNI_FALSE; bad_option_with_errmsg: ERROR_MESSAGE(("JDWP %s: %s=%s", errmsg, AGENTLIB, options)); return JNI_FALSE; bad_option_no_msg: ERROR_MESSAGE(("JDWP %s: %s=%s", "invalid option", AGENTLIB, options)); return JNI_FALSE; } /* All normal exit doors lead here */ void debugInit_exit(jvmtiError error, const char *msg) { enum exit_codes { EXIT_NO_ERRORS = 0, EXIT_JVMTI_ERROR = 1, EXIT_TRANSPORT_ERROR = 2 }; // Prepare to exit. Log error and finish logging LOG_MISC(("Exiting with error %s(%d): %s", jvmtiErrorText(error), error, ((msg == NULL) ? "" : msg))); // coredump requested by command line. Keep JVMTI data dirty if (error != JVMTI_ERROR_NONE && docoredump) { LOG_MISC(("Dumping core as requested by command line")); finish_logging(); abort(); } finish_logging(); // Cleanup the JVMTI if we have one if (gdata != NULL) { gdata->vmDead = JNI_TRUE; if (gdata->jvmti != NULL) { // Dispose of jvmti (gdata->jvmti becomes NULL) disposeEnvironment(gdata->jvmti); } } // We are here with no errors. Kill entire process and exit with zero exit code if (error == JVMTI_ERROR_NONE) { forceExit(EXIT_NO_ERRORS); return; } // No transport initilized. // As we don't have any details here exiting with separate exit code if (error == AGENT_ERROR_TRANSPORT_INIT) { forceExit(EXIT_TRANSPORT_ERROR); return; } // We have JVMTI error. Call hotspot jni_FatalError handler jniFatalError(NULL, msg, error, EXIT_JVMTI_ERROR); // hotspot calls os:abort() so we should never reach code below, // but guard against possible hotspot changes // Last chance to die, this kills the entire process. forceExit(EXIT_JVMTI_ERROR); } // ANDROID-CHANGED: Support jdwp loading with OnAttach. static jint doInitializeOnAttach(JavaVM* vm) { JNIEnv* jnienv = NULL; jvmtiError error = JVM_FUNC_PTR(vm,GetEnv) (vm, (void **)&(jnienv), JNI_VERSION_1_6); if (error != JNI_OK) { ERROR_MESSAGE(("JDWP unable to access jni (0x%x)," " is your J2SE a 1.6 or newer version?" " JNIEnv's GetEnv() returned %d", JNI_VERSION_1_6, error)); return JNI_ERR; } jthread currentThread; error = JVMTI_FUNC_PTR(gdata->jvmti,GetCurrentThread) (gdata->jvmti, ¤tThread); if (error != JVMTI_ERROR_NONE) { ERROR_MESSAGE(("JDWP unable to get current thread during agent attach: %s(%d)", jvmtiErrorText(error), error)); return JNI_ERR; } // Pretend to send the VM_INIT event. cbEarlyVMInit(gdata->jvmti, jnienv, currentThread); return JNI_OK; } /* OnAttach startup: * ANDROID-CHANGED: We need this to support the way android normally uses debuggers. */ JNIEXPORT jint JNICALL Agent_OnAttach(JavaVM* vm, char* options, void* reserved) { isInAttach = JNI_TRUE; // SuspendOnInit should default to false in late-attach scenario since it is not supported. suspendOnInit = JNI_FALSE; if (Agent_OnLoad(vm, options, reserved) != JNI_OK) { return JNI_ERR; } jint res; if (suspendOnInit) { ERROR_MESSAGE(("JDWP cannot suspend all threads when performing late-attach.")); return JNI_ERR; } else if (!initOnUncaught && (initOnException == NULL)) { res = doInitializeOnAttach(vm); } else { res = JNI_OK; } isInAttach = JNI_FALSE; return res; }