| /* | |
| * Copyright (c) 1998, 2024, 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" | |
| /* How the options get to OnLoad: */ | |
| #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 | |
| static jboolean vmInitialized; | |
| static jrawMonitorID initMonitor; | |
| static jboolean initComplete; | |
| static jbyte currentSessionID; | |
| /* | |
| * 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; | |
| char *allow; | |
| } 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, EventInfo *opt_info); | |
| 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; | |
| } | |
| /* Logic to determine JVMTI version compatibility */ | |
| static jboolean | |
| compatible_versions(jint major_runtime, jint minor_runtime, | |
| jint major_compiletime, jint minor_compiletime) | |
| { | |
| return major_runtime == major_compiletime && | |
| minor_runtime >= minor_compiletime; | |
| } | |
| /* OnLoad startup: | |
| * Returning JNI_ERR will cause the java_g VM to core dump, be careful. | |
| */ | |
| JNIEXPORT jint JNICALL | |
| DEF_Agent_OnLoad(JavaVM *vm, char *options, void *reserved) | |
| { | |
| jvmtiError error; | |
| jvmtiCapabilities needed_capabilities; | |
| jvmtiCapabilities potential_capabilities; | |
| jint jvmtiCompileTimeMajorVersion; | |
| jint jvmtiCompileTimeMinorVersion; | |
| jint jvmtiCompileTimeMicroVersion; | |
| /* 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; | |
| 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; | |
| /* Get the JVMTI Env, IMPORTANT: Do this first! For jvmtiAllocate(). */ | |
| error = JVM_FUNC_PTR(vm,GetEnv) | |
| (vm, (void **)&(gdata->jvmti), JVMTI_VERSION); | |
| if (error != JNI_OK) { | |
| ERROR_MESSAGE(("JDWP unable to access JVMTI Version %d.%d.%d (0x%x)." | |
| " JNIEnv's GetEnv() returned %d.", | |
| jvmtiCompileTimeMajorVersion, jvmtiCompileTimeMinorVersion, | |
| jvmtiCompileTimeMicroVersion, JVMTI_VERSION, error)); | |
| forceExit(1); /* Kill entire process, no core dump */ | |
| } | |
| /* Check that the JVMTI compile and runtime versions are compatibile. */ | |
| 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 */ | |
| } | |
| /* 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; | |
| if (gdata->vthreadsSupported) { | |
| needed_capabilities.can_support_virtual_threads = 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; | |
| } | |
| LOG_MISC(("OnLoad: DONE")); | |
| return JNI_OK; | |
| } | |
| JNIEXPORT void JNICALL | |
| DEF_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, NULL); | |
| 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; | |
| } | |
| EventInfo info; | |
| info.ei = EI_EXCEPTION; | |
| info.thread = thread; | |
| info.clazz = getMethodClass(jvmti_env, method); | |
| info.method = method; | |
| info.location = location; | |
| info.object = exception; | |
| if (gdata->vthreadsSupported) { | |
| info.is_vthread = isVThread(thread); | |
| } | |
| info.u.exception.catch_clazz = getMethodClass(jvmti_env, catch_method); | |
| info.u.exception.catch_method = catch_method; | |
| info.u.exception.catch_location = catch_location; | |
| /* | |
| * 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, &info); | |
| } else if (initOnException != NULL) { | |
| jclass exception_clazz = JNI_FUNC_PTR(env, GetObjectClass)(env, exception); | |
| /* check class of exception thrown */ | |
| if ( exception_clazz != NULL ) { | |
| char *signature = NULL; | |
| /* initing on throw, check */ | |
| error = classSignature(exception_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, &info); | |
| } 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, | |
| transport->allow); | |
| 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", msg); | |
| } | |
| 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 | |
| * | |
| * @param opt_info optional event info to use, might be null | |
| */ | |
| static void | |
| initialize(JNIEnv *env, jthread thread, EventIndex triggering_ei, EventInfo *opt_info) | |
| { | |
| 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(); | |
| 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); | |
| 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?) | |
| */ | |
| struct bag *initEventBag; | |
| LOG_MISC(("triggering_ei == EI_EXCEPTION")); | |
| JDI_ASSERT(triggering_ei == EI_EXCEPTION); | |
| JDI_ASSERT(opt_info != NULL); | |
| initEventBag = eventHelper_createEventBag(); | |
| threadControl_onEventHandlerEntry(currentSessionID, opt_info, NULL); | |
| eventHelper_recordEvent(opt_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); | |
| /* | |
| * 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 the \"VM Invocation Options\" section of the JPDA\n" | |
| " \"Connection and Invocation Details\" document 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" | |
| "allow=<IP|IP-list> If server=y, allows connections only from the IP addresses/subnets specified.\n" | |
| " A list of multiple IP address/subnet entries must be separated by \'+\'.\n" | |
| " * (allows connection from any address)\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" | |
| "includevirtualthreads=y|n List of all threads includes virtual threads as well as platform threads.\n" | |
| " n\n" | |
| "mutf8=y|n output modified utf-8 n\n" | |
| "quiet=y|n control over terminal messages n\n")); | |
| TTY_MESSAGE(( | |
| "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 " 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")); | |
| TTY_MESSAGE(( | |
| "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; | |
| /* Set vthread debugging level. */ | |
| gdata->vthreadsSupported = JNI_TRUE; | |
| gdata->includeVThreads = JNI_FALSE; | |
| gdata->rememberVThreadsWhenDisconnected = JNI_FALSE; | |
| gdata->jvmti_data_dump = 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; | |
| currentTransport->address = NULL; | |
| currentTransport->allow = NULL; | |
| currentTransport->timeout = 0L; | |
| 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, "allow") == 0) { | |
| if (currentTransport == NULL) { | |
| errmsg = "allow specified without transport"; | |
| goto bad_option_with_errmsg; | |
| } | |
| /*LINTED*/ | |
| if (!get_tok(&str, current, (int)(end - current), ',')) { | |
| goto syntax_error; | |
| } | |
| currentTransport->allow = 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, "includevirtualthreads") == 0) { | |
| if (!get_tok(&str, current, (int)(end - current), ',')) { | |
| goto syntax_error; | |
| } | |
| if (strcmp(current, "y") == 0) { | |
| gdata->includeVThreads = JNI_TRUE; | |
| } else if (strcmp(current, "n") == 0) { | |
| gdata->includeVThreads = JNI_FALSE; | |
| } else { | |
| goto syntax_error; | |
| } | |
| // These two flags always set the same for now. | |
| gdata->rememberVThreadsWhenDisconnected = gdata->includeVThreads; | |
| 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, "datadump") == 0) { | |
| // Enable JVMTI DATA_DUMP_REQUEST support. | |
| // This is not a documented flag. This feature is experimental and is only intended | |
| // to be used by debug agent developers. See comment for cbDataDump() for more details. | |
| if ( !get_boolean(&str, &(gdata->jvmti_data_dump)) ) { | |
| goto syntax_error; | |
| } | |
| } 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; | |
| } | |
| } 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 }; | |
| // Release commandLoop vmDeathLock if necessary | |
| commandLoop_exitVmDeathLockOnError(); | |
| // 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 initialized. | |
| // 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); | |
| } |