alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

Java example source code file (debugInit.c)

This example Java source code file (debugInit.c) is included in the alvinalexander.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Learn more about this Java project at its project page.

Java - Java tags/keywords

agentlib, error_message, exit_error, jdwp, jni_err, jni_false, jni_true, jnicall, jnienv, jvmti_error_none, jvmti_func_ptr, log_misc, null, transportspec

The debugInit.c Java example source code

/*
 * 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"

/* 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

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;
} 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;
}

/* 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);
    if (error != JNI_OK) {
        ERROR_MESSAGE(("JDWP unable to access JVMTI Version 1 (0x%x),"
                         " is your J2SE a 1.5 or newer version?"
                         " JNIEnv's GetEnv() returned %d",
                         JVMTI_VERSION_1, 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 */
    }

    JVMTI_FUNC_PTR(gdata->jvmti, GetSystemProperty)
        (gdata->jvmti, (const char *)"sun.boot.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;
    }

    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();

    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?)
         */
        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>=, ...]\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>=, ...]\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(0);  /* 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;

    /* 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;
            }
        } 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)
{
    int exit_code = 0;

    /* Pick an error code */
    if ( error != JVMTI_ERROR_NONE ) {
        exit_code = 1;
        if ( docoredump ) {
            finish_logging(exit_code);
            abort();
        }
    }
    if ( msg==NULL ) {
        msg = "";
    }

    LOG_MISC(("Exiting with error %s(%d): %s", jvmtiErrorText(error), error, msg));

    gdata->vmDead = JNI_TRUE;

    /* Let's try and cleanup the JVMTI, if we even have one */
    if ( gdata->jvmti != NULL ) {
        /* Dispose of jvmti (gdata->jvmti becomes NULL) */
        disposeEnvironment(gdata->jvmti);
    }

    /* Finish up logging. We reach here if JDWP is doing the exiting. */
    finish_logging(exit_code);  /* Only first call matters */

    /* Let's give the JNI a FatalError if non-exit 0, which is historic way */
    if ( exit_code != 0 ) {
        JNIEnv *env = NULL;
        jniFatalError(env, msg, error, exit_code);
    }

    /* Last chance to die, this kills the entire process. */
    forceExit(exit_code);
}

Other Java examples (source code examples)

Here is a short list of links related to this Java debugInit.c source code file:

... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.