/*
 * @(#)hprof_init.c	1.92 05/12/06
 * 
 * Copyright (c) 2006 Sun Microsystems, Inc. All Rights Reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 * -Redistribution of source code must retain the above copyright notice, this
 *  list of conditions and the following disclaimer.
 * 
 * -Redistribution in binary form must reproduce the above copyright notice, 
 *  this list of conditions and the following disclaimer in the documentation
 *  and/or other materials provided with the distribution.
 * 
 * Neither the name of Sun Microsystems, Inc. or the names of contributors may 
 * be used to endorse or promote products derived from this software without 
 * specific prior written permission.
 * 
 * This software is provided "AS IS," without a warranty of any kind. ALL 
 * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
 * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
 * OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN")
 * AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
 * AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
 * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST 
 * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, 
 * INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY 
 * OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, 
 * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
 * 
 * You acknowledge that this software is not designed, licensed or intended
 * for use in the design, construction, operation or maintenance of any
 * nuclear facility.
 */

/* Main source file, the basic JVMTI connection/startup code. */

#include "hprof.h"

#include "java_crw_demo.h"

/*
 * This file contains all the startup logic (Agent_Onload) and
 *   connection to the JVMTI interface.
 * All JVMTI Event callbacks are in this file.
 * All setting of global data (gdata) is done here.
 * Options are parsed here.
 * Option help messages are here.
 * Termination handled here (VM_DEATH) and shutdown (Agent_OnUnload).
 * Spawning of the cpu sample loop thread and listener thread is done here.
 *
 * Use of private 'static' data has been limited, most shared static data
 *    should be found in the GlobalData structure pointed to by gdata
 *    (see hprof.h).
 *
 */

/* The default output filenames. */

#define DEFAULT_TXT_SUFFIX	".txt"
#define DEFAULT_OUTPUTFILE	"java.hprof"
#define DEFAULT_OUTPUTTEMP	"java.hprof.temp"

/* The only global variable, defined by this library */
GlobalData *gdata;

/* Experimental options */
#define EXPERIMENT_NO_EARLY_HOOK 0x1

/* Default trace depth */
#define DEFAULT_TRACE_DEPTH 4

/* Default sample interval */
#define DEFAULT_SAMPLE_INTERVAL 10

/* Default cutoff */
#define DEFAULT_CUTOFF_POINT 0.0001

/* Stringize macros for help. */
#define _TO_STR(a) #a
#define TO_STR(a) _TO_STR(a)

/* Macros to surround callback code (non-VM_DEATH callbacks).
 *   Note that this just keeps a count of the non-VM_DEATH callbacks that
 *   are currently active, it does not prevent these callbacks from
 *   operating in parallel. It's the VM_DEATH callback that will wait
 *   for all these callbacks to either complete and block, or just block.
 *   We need to hold back these threads so they don't die during the final
 *   VM_DEATH processing.
 *   If the VM_DEATH callback is active in the beginning, then this callback
 *   just blocks to prevent further execution of the thread.
 *   If the VM_DEATH callback is active at the end, then this callback
 *   will notify the VM_DEATH callback if it's the last one.
 *   In all cases, the last thing they do is Enter/Exit the monitor
 *   gdata->callbackBlock, which will block this callback if VM_DEATH
 *   is running.
 *
 *   WARNING: No not 'return' or 'goto' out of the BEGIN_CALLBACK/END_CALLBACK
 *            block, this will mess up the count.
 */

#define BEGIN_CALLBACK()                                            \
{ /* BEGIN OF CALLBACK */                                           \
    jboolean bypass;                                                \
    rawMonitorEnter(gdata->callbackLock);                           \
    if (gdata->vm_death_callback_active) {                          \
	/* VM_DEATH is active, we will bypass the CALLBACK CODE */  \
	bypass = JNI_TRUE;                                          \
	rawMonitorExit(gdata->callbackLock);                        \
	/* Bypassed CALLBACKS block here until VM_DEATH done */     \
	rawMonitorEnter(gdata->callbackBlock);                      \
	rawMonitorExit(gdata->callbackBlock);                       \
    } else {                                                        \
	/* We will be executing the CALLBACK CODE in this case */   \
	gdata->active_callbacks++;                                  \
	bypass = JNI_FALSE;                                         \
	rawMonitorExit(gdata->callbackLock);                        \
    }                                                               \
    if ( !bypass ) {                                                \
        /* BODY OF CALLBACK CODE (with no callback locks held) */

#define END_CALLBACK() /* Part of bypass if body */                 \
        rawMonitorEnter(gdata->callbackLock);                       \
	gdata->active_callbacks--;                                  \
	/* If VM_DEATH is active, and last one, send notify. */     \
	if (gdata->vm_death_callback_active) {                      \
	    if (gdata->active_callbacks == 0) {                     \
		rawMonitorNotifyAll(gdata->callbackLock);           \
	    }                                                       \
	}                                                           \
	rawMonitorExit(gdata->callbackLock);                        \
	/* Non-Bypassed CALLBACKS block here until VM_DEATH done */ \
	rawMonitorEnter(gdata->callbackBlock);                      \
	rawMonitorExit(gdata->callbackBlock);                       \
    }                                                               \
} /* END OF CALLBACK */

/* Forward declarations */
static void set_callbacks(jboolean on);

/* ------------------------------------------------------------------- */
/* Global data initialization */

/* Get initialized global data area */
static GlobalData *
get_gdata(void)
{
    static GlobalData data;

    /* Create initial default values */
    (void)memset(&data, 0, sizeof(GlobalData));
    
    data.fd                             = -1; /* Non-zero file or socket. */
    data.heap_fd                        = -1; /* For heap=dump, see hprof_io */
    data.check_fd                       = -1; /* For heap=dump, see hprof_io */
    data.max_trace_depth                = DEFAULT_TRACE_DEPTH;
    data.prof_trace_depth               = DEFAULT_TRACE_DEPTH;
    data.sample_interval                = DEFAULT_SAMPLE_INTERVAL;
    data.lineno_in_traces               = JNI_TRUE;
    data.output_format                  = 'a';      /* 'b' for binary */
    data.cutoff_point                   = DEFAULT_CUTOFF_POINT;
    data.dump_on_exit                   = JNI_TRUE;
    data.gc_start_time                  = -1L;
#ifdef DEBUG
    data.debug			        = JNI_TRUE;
    data.coredump		        = JNI_TRUE;
#endif
    data.micro_state_accounting        	= JNI_FALSE;
    data.force_output        	        = JNI_TRUE;
    data.verbose		        = JNI_TRUE;
    data.primfields		        = JNI_TRUE;
    data.primarrays		        = JNI_TRUE;
    
    data.table_serial_number_start    = 1;
    data.class_serial_number_start    = 100000;
    data.thread_serial_number_start   = 200000;
    data.trace_serial_number_start    = 300000;
    data.object_serial_number_start   = 400000;
    data.frame_serial_number_start    = 500000;
    data.gref_serial_number_start     = 1;
    
    data.table_serial_number_counter  = data.table_serial_number_start;
    data.class_serial_number_counter  = data.class_serial_number_start;
    data.thread_serial_number_counter = data.thread_serial_number_start;
    data.trace_serial_number_counter  = data.trace_serial_number_start;
    data.object_serial_number_counter = data.object_serial_number_start;
    data.frame_serial_number_counter  = data.frame_serial_number_start;
    data.gref_serial_number_counter   = data.gref_serial_number_start;
   
    data.unknown_thread_serial_num    = data.thread_serial_number_counter++;
    return &data;
}

/* ------------------------------------------------------------------- */
/* Error handler callback for the java_crw_demo (classfile read write) functions. */

static void
my_crw_fatal_error_handler(const char * msg, const char *file, int line)
{
    char errmsg[256];
    
    (void)md_snprintf(errmsg, sizeof(errmsg), 
		"%s [%s:%d]", msg, file, line);
    errmsg[sizeof(errmsg)-1] = 0;
    HPROF_ERROR(JNI_TRUE, errmsg);
}

static void
list_all_tables(void)
{
    string_list();
    class_list();
    frame_list();
    site_list();
    object_list();
    trace_list();
    monitor_list();
    tls_list();
    loader_list();
}

/* ------------------------------------------------------------------- */
/* Option Parsing support */

/**
 * Socket connection
 */

/*
 * Return a socket  connect()ed to a "hostname" that is
 * accept()ing heap profile data on "port." Return a value <= 0 if
 * such a connection can't be made.
 */
static int 
connect_to_socket(char *hostname, unsigned short port)
{
    int fd;
    
    if (port == 0 || port > 65535) {
        HPROF_ERROR(JNI_FALSE, "invalid port number");
        return -1;
    }
    if (hostname == NULL) {
        HPROF_ERROR(JNI_FALSE, "hostname is NULL");
        return -1;
    }

    /* create a socket */
    fd = md_connect(hostname, port);
    return fd;
}

/* Accept a filename, and adjust the name so that it is unique for this PID */
static void
make_unique_filename(char **filename)
{
    int fd;

    /* Find a file that doesn't exist */
    fd = md_open(*filename);
    if ( fd >= 0 ) {
	int   pid;
	char *new_name;
	char *old_name;
	char *prefix;
	char  suffix[5];
	int   new_len;

	/* Close the file. */
	md_close(fd);
	
	/* Make filename name.PID[.txt] */
	pid = md_getpid();
	old_name = *filename;
	new_len = (int)strlen(old_name)+64;
	new_name = HPROF_MALLOC(new_len);
	prefix = old_name;
	suffix[0] = 0;

	/* Look for .txt suffix if not binary output */
	if (gdata->output_format != 'b') {
	    char *dot;
	    char *format_suffix;

	    format_suffix = DEFAULT_TXT_SUFFIX;
	    
	    (void)strcpy(suffix, format_suffix);
	    
	    dot = strrchr(old_name, '.');
	    if ( dot != NULL ) {
		int i;
		int slen;
		int match;

		slen = (int)strlen(format_suffix);
		match = 1;
		for ( i = 0; i < slen; i++ ) {
		    if ( dot[i]==0 ||
			 tolower(format_suffix[i]) != tolower(dot[i]) ) {
			match = 0;
			break;
		    }
		}
		if ( match ) {
		    (void)strcpy(suffix, dot);
		    *dot = 0; /* truncates prefix and old_name */
	        }
	    }
	}

	/* Construct the name */
	(void)md_snprintf(new_name, new_len, 
		   "%s.%d%s", prefix, pid, suffix); 
	*filename = new_name;
	HPROF_FREE(old_name);

	/* Odds are with Windows, this file may not be so unique. */
	(void)remove(gdata->output_filename);
    }
}

static int 
get_tok(char **src, char *buf, int buflen, int sep)
{
    int len;
    char *p;
    
    buf[0] = 0;
    if ( **src == 0 ) {
	return 0;
    }
    p = strchr(*src, sep);
    if ( p==NULL ) {
	len = (int)strlen(*src);
	p = (*src) + len;
    } else {
	/*LINTED*/
	len = (int)(p - (*src));
    }
    if ( (len+1) > buflen ) {
	return 0;
    }
    (void)memcpy(buf, *src, len);
    buf[len] = 0;
    if ( *p != 0 && *p == sep ) {
	(*src) = p+1;
    } else {
	(*src) = p;
    }
    return len;
}

static jboolean
setBinarySwitch(char **src, jboolean *ptr)
{
    char buf[80];
    
    if (!get_tok(src, buf, (int)sizeof(buf), ',')) {
        return JNI_FALSE;
    }
    if (strcmp(buf, "y") == 0) {
        *ptr = JNI_TRUE;
    } else if (strcmp(buf, "n") == 0) {
        *ptr = JNI_FALSE;
    } else {
        return JNI_FALSE;
    }
    return JNI_TRUE;
}

static void 
print_usage(void)
{
    
    (void)fprintf(stdout,
"\n"
"     HPROF: Heap and CPU Profiling Agent (JVMTI Demonstration Code)\n"
"\n"
AGENTNAME " usage: java " AGENTLIB "=[help]|[<option>=<value>, ...]\n"
"\n"
"Option Name and Value  Description                    Default\n"
"---------------------  -----------                    -------\n"
"heap=dump|sites|all    heap profiling                 all\n"
"cpu=samples|times|old  CPU usage                      off\n"
"monitor=y|n            monitor contention             n\n"
"format=a|b             text(txt) or binary output     a\n"
"file=<file>            write data to file             " DEFAULT_OUTPUTFILE "[{" DEFAULT_TXT_SUFFIX "}]\n"
"net=<host>:<port>      send data over a socket        off\n"
"depth=<size>           stack trace depth              " TO_STR(DEFAULT_TRACE_DEPTH) "\n"
"interval=<ms>          sample interval in ms          " TO_STR(DEFAULT_SAMPLE_INTERVAL) "\n"
"cutoff=<value>         output cutoff point            " TO_STR(DEFAULT_CUTOFF_POINT) "\n"
"lineno=y|n             line number in traces?         y\n"
"thread=y|n             thread in traces?              n\n"
"doe=y|n                dump on exit?                  y\n"
"msa=y|n                Solaris micro state accounting n\n"
"force=y|n              force output to <file>         y\n"
"verbose=y|n            print messages about dumps     y\n"
"\n"
"Obsolete Options\n"
"----------------\n"
"gc_okay=y|n\n"

#ifdef DEBUG
"\n"
"DEBUG Option           Description                    Default\n"
"------------           -----------                    -------\n"
"primfields=y|n         include primitive field values y\n"
"primarrays=y|n         include primitive array values y\n"
"debugflags=MASK        Various debug flags            0\n"
"                        0x01   Report refs in and of unprepared classes\n"
"logflags=MASK          Logging to stderr              0\n"
"                        " TO_STR(LOG_DUMP_MISC)    " Misc logging\n"
"                        " TO_STR(LOG_DUMP_LISTS)   " Dump out the tables\n"
"                        " TO_STR(LOG_CHECK_BINARY) " Verify & dump format=b\n"
"coredump=y|n           Core dump on fatal             n\n"
"errorexit=y|n          Exit on any error              n\n"
"pause=y|n              Pause on onload & echo PID     n\n"
"debug=y|n              Turn on all debug checking     n\n"
"X=MASK                 Internal use only              0\n"

"\n"
"Environment Variables\n"
"---------------------\n"
"_JAVA_HPROF_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

"\n"
"Examples\n"
"--------\n"
"  - Get sample cpu information every 20 millisec, with a stack depth of 3:\n"
"      java " AGENTLIB "=cpu=samples,interval=20,depth=3 classname\n"
"  - Get heap usage information based on the allocation sites:\n"
"      java " AGENTLIB "=heap=sites classname\n"

#ifdef DEBUG
"  - Using the external option addition with csh, log details on all runs:\n"
"      setenv _JAVA_HPROF_OPTIONS \"logflags=0xC\"\n"
"      java " AGENTLIB "=cpu=samples classname\n"
"    is the same as:\n"
"      java " AGENTLIB "=cpu=samples,logflags=0xC classname\n"
#endif

"\n"
"Notes\n"
"-----\n"
"  - The option format=b cannot be used with monitor=y.\n"
"  - The option format=b cannot be used with cpu=old|times.\n"
"  - Use of the " XRUN " interface can still be used, e.g.\n"
"       java " XRUN ":[help]|[<option>=<value>, ...]\n"
"    will behave exactly the same as:\n"
"       java " AGENTLIB "=[help]|[<option>=<value>, ...]\n"
    
#ifdef DEBUG
"  - The debug options and environment variables are available with both java\n"
"    and java_g versions.\n"
#endif

"\n"
"Warnings\n"
"--------\n"
"  - This is demonstration code for the JVMTI interface and use of BCI,\n"
"    it is not an official product or formal part of the JDK.\n"
"  - The " XRUN " interface will be removed in a future release.\n"
"  - The option format=b is considered experimental, this format may change\n"
"    in a future release.\n"
    
#ifdef DEBUG
"  - The obsolete options may be completely removed in a future release.\n"
"  - The debug options and environment variables are not considered public\n"
"    interfaces and can change or be removed with any type of update of\n"
"    " AGENTNAME ", including patches.\n"
#endif

        );
}

static void
option_error(char *description)
{
    char errmsg[FILENAME_MAX+80];

    (void)md_snprintf(errmsg, sizeof(errmsg),
	   "%s option error: %s (%s)", AGENTNAME, description, gdata->options);
    errmsg[sizeof(errmsg)-1] = 0;
    HPROF_ERROR(JNI_FALSE, errmsg);
    error_exit_process(1);
}

static void 
parse_options(char *command_line_options)
{
    int file_or_net_option_seen = JNI_FALSE;
    char *all_options;
    char *extra_options;
    char *options;
    char *default_filename;
    int   ulen;
    
    if (command_line_options == 0)
        command_line_options = "";
    
    if ((strcmp(command_line_options, "help")) == 0) {
        print_usage();
        error_exit_process(0);
    }

    extra_options = getenv("_JAVA_HPROF_OPTIONS");
    if ( extra_options == NULL ) {
	extra_options = "";
    }

    all_options = HPROF_MALLOC((int)strlen(command_line_options) + 
			        (int)strlen(extra_options) + 2);
    gdata->options = all_options;
    (void)strcpy(all_options, command_line_options);
    if ( extra_options[0] != 0 ) {
	if ( all_options[0] != 0 ) {
	    (void)strcat(all_options, ",");
        }
	(void)strcat(all_options, extra_options);
    }
    options = all_options;

    LOG2("parse_options()", all_options);
    
    while (*options) {
        char option[16];
        char suboption[FILENAME_MAX+1];
        char *endptr;

        if (!get_tok(&options, option, (int)sizeof(option), '=')) {
            option_error("general syntax error parsing options");
        }
        if (strcmp(option, "file") == 0) {
            if ( file_or_net_option_seen  ) {
                option_error("file or net options should only appear once");
            }
            if (!get_tok(&options, suboption, (int)sizeof(suboption), ',')) {
                option_error("syntax error parsing file=filename");
            }
            gdata->utf8_output_filename = HPROF_MALLOC((int)strlen(suboption)+1);
            (void)strcpy(gdata->utf8_output_filename, suboption);
            file_or_net_option_seen = JNI_TRUE;
        } else if (strcmp(option, "net") == 0) {
            char port_number[16];
            if (file_or_net_option_seen ) {
                option_error("file or net options should only appear once");
	    }
	    if (!get_tok(&options, suboption, (int)sizeof(suboption), ':')) {
                option_error("net option missing ':'");
	    }
	    if (!get_tok(&options, port_number, (int)sizeof(port_number), ',')) {
                option_error("net option missing port");
            }
            gdata->net_hostname = HPROF_MALLOC((int)strlen(suboption)+1);
            (void)strcpy(gdata->net_hostname, suboption);
            gdata->net_port = (int)strtol(port_number, NULL, 10);
            file_or_net_option_seen = JNI_TRUE;
        } else if (strcmp(option, "format") == 0) {
            if (!get_tok(&options, suboption, (int)sizeof(suboption), ',')) {
                option_error("syntax error parsing format=a|b");
            }
            if (strcmp(suboption, "a") == 0) {
                gdata->output_format = 'a';
            } else if (strcmp(suboption, "b") == 0) {
                gdata->output_format = 'b';
            } else {
                option_error("format option value must be a|b");
            }
        } else if (strcmp(option, "depth") == 0) {
            if (!get_tok(&options, suboption, (int)sizeof(suboption), ',')) {
                option_error("syntax error parsing depth=DECIMAL");
            }
            gdata->max_trace_depth = (int)strtol(suboption, &endptr, 10);
            if ((endptr != NULL && *endptr != 0) || gdata->max_trace_depth < 0) {
                option_error("depth option value must be decimal and >= 0");
            }
            gdata->prof_trace_depth = gdata->max_trace_depth;
        } else if (strcmp(option, "interval") == 0) {
            if (!get_tok(&options, suboption, (int)sizeof(suboption), ',')) {
                option_error("syntax error parsing interval=DECIMAL");
            }
            gdata->sample_interval = (int)strtol(suboption, &endptr, 10);
            if ((endptr != NULL && *endptr != 0) || gdata->sample_interval <= 0) {
                option_error("interval option value must be decimal and > 0");
            }
        } else if (strcmp(option, "cutoff") == 0) {
            if (!get_tok(&options, suboption, (int)sizeof(suboption), ',')) {
                option_error("syntax error parsing cutoff=DOUBLE");
            }
            gdata->cutoff_point = strtod(suboption, &endptr);
            if ((endptr != NULL && *endptr != 0) || gdata->cutoff_point < 0) {
                option_error("cutoff option value must be floating point and >= 0");
            }
        } else if (strcmp(option, "cpu") == 0) {
            if (!get_tok(&options, suboption, (int)sizeof(suboption), ',')) {
                option_error("syntax error parsing cpu=y|samples|times|old");
            }
            if ((strcmp(suboption, "samples") == 0) ||
                (strcmp(suboption, "y") == 0)) {
                gdata->cpu_sampling = JNI_TRUE;
            } else if (strcmp(suboption, "times") == 0) {
                gdata->cpu_timing = JNI_TRUE;
                gdata->old_timing_format = JNI_FALSE;
            } else if (strcmp(suboption, "old") == 0) {
                gdata->cpu_timing = JNI_TRUE;
                gdata->old_timing_format = JNI_TRUE;
            } else {
                option_error("cpu option value must be y|samples|times|old");
            }
        } else if (strcmp(option, "heap") == 0) {
            if (!get_tok(&options, suboption, (int)sizeof(suboption), ',')) {
                option_error("syntax error parsing heap=dump|sites|all");
            }
            if (strcmp(suboption, "dump") == 0) {
                gdata->heap_dump = JNI_TRUE;
            } else if (strcmp(suboption, "sites") == 0) {
                gdata->alloc_sites = JNI_TRUE;
            } else if (strcmp(suboption, "all") == 0) {
                gdata->heap_dump = JNI_TRUE;
                gdata->alloc_sites = JNI_TRUE;
            } else {
                option_error("heap option value must be dump|sites|all");
            }
        } else if( strcmp(option,"lineno") == 0) {
            if ( !setBinarySwitch(&options, &(gdata->lineno_in_traces)) ) {
                option_error("lineno option value must be y|n");
            }
        } else if( strcmp(option,"thread") == 0) {
            if ( !setBinarySwitch(&options, &(gdata->thread_in_traces)) ) {
                option_error("thread option value must be y|n");
            }
        } else if( strcmp(option,"doe") == 0) {
            if ( !setBinarySwitch(&options, &(gdata->dump_on_exit)) ) {
                option_error("doe option value must be y|n");
            }
        } else if( strcmp(option,"msa") == 0) {
            if ( !setBinarySwitch(&options, &(gdata->micro_state_accounting)) ) {
                option_error("msa option value must be y|n");
            }
        } else if( strcmp(option,"force") == 0) {
            if ( !setBinarySwitch(&options, &(gdata->force_output)) ) {
                option_error("force option value must be y|n");
            }
        } else if( strcmp(option,"verbose") == 0) {
            if ( !setBinarySwitch(&options, &(gdata->verbose)) ) {
                option_error("verbose option value must be y|n");
            }
        } else if( strcmp(option,"primfields") == 0) {
            if ( !setBinarySwitch(&options, &(gdata->primfields)) ) {
                option_error("primfields option value must be y|n");
            }
        } else if( strcmp(option,"primarrays") == 0) {
            if ( !setBinarySwitch(&options, &(gdata->primarrays)) ) {
                option_error("primarrays option value must be y|n");
            }
        } else if( strcmp(option,"monitor") == 0) {
            if ( !setBinarySwitch(&options, &(gdata->monitor_tracing)) ) {
                option_error("monitor option value must be y|n");
            }
        } else if( strcmp(option,"gc_okay") == 0) {
            if ( !setBinarySwitch(&options, &(gdata->gc_okay)) ) {
                option_error("gc_okay option value must be y|n");
            }
        } else if (strcmp(option, "logflags") == 0) {
            if (!get_tok(&options, suboption, (int)sizeof(suboption), ',')) {
                option_error("logflags option value must be numeric");
            }
            gdata->logflags = (int)strtol(suboption, NULL, 0);
        } else if (strcmp(option, "debugflags") == 0) {
            if (!get_tok(&options, suboption, (int)sizeof(suboption), ',')) {
                option_error("debugflags option value must be numeric");
            }
            gdata->debugflags = (int)strtol(suboption, NULL, 0);
        } else if (strcmp(option, "coredump") == 0) {
            if ( !setBinarySwitch(&options, &(gdata->coredump)) ) {
                option_error("coredump option value must be y|n");
            }
        } else if (strcmp(option, "exitpause") == 0) {
	    option_error("The exitpause option was removed, use -XX:OnError='cmd %%p'");
        } else if (strcmp(option, "errorexit") == 0) {
            if ( !setBinarySwitch(&options, &(gdata->errorexit)) ) {
                option_error("errorexit option value must be y|n");
            }
        } else if (strcmp(option, "pause") == 0) {
            if ( !setBinarySwitch(&options, &(gdata->pause)) ) {
                option_error("pause option value must be y|n");
            }
        } else if (strcmp(option, "debug") == 0) {
            if ( !setBinarySwitch(&options, &(gdata->debug)) ) {
                option_error("debug option value must be y|n");
            }
        } else if (strcmp(option, "precrash") == 0) {
	    option_error("The precrash option was removed, use -XX:OnError='precrash -p %%p'");
        } else if (strcmp(option, "X") == 0) {
            if (!get_tok(&options, suboption, (int)sizeof(suboption), ',')) {
                option_error("X option value must be numeric");
            }
            gdata->experiment = (int)strtol(suboption, NULL, 0);
        } else {
	    char errmsg[80];
	    (void)strcpy(errmsg, "Unknown option: ");
	    (void)strcat(errmsg, option);
	    option_error(errmsg);
        }
    }
   
    if (gdata->output_format == 'b') {
        if (gdata->cpu_timing) {
            option_error("cpu=times|old is not supported with format=b");
        }
        if (gdata->monitor_tracing) {
            option_error("monitor=y is not supported with format=b");
        }
    }
            
    if (gdata->old_timing_format) {
        gdata->prof_trace_depth = 2;
    }

    if (gdata->output_format == 'b') {
	default_filename = DEFAULT_OUTPUTFILE;
    } else {
        default_filename = DEFAULT_OUTPUTFILE DEFAULT_TXT_SUFFIX;
    }
    
    if (!file_or_net_option_seen) {
	gdata->utf8_output_filename = HPROF_MALLOC((int)strlen(default_filename)+1);
	(void)strcpy(gdata->utf8_output_filename, default_filename);
    }

    if ( gdata->utf8_output_filename != NULL ) {
	/* UTF-8 to platform encoding (fill in gdata->output_filename) */
	ulen = (int)strlen(gdata->utf8_output_filename);
	gdata->output_filename = (char*)HPROF_MALLOC(ulen*3+3);
#ifdef SKIP_NPT
	(void)strcpy(gdata->output_filename, gdata->utf8_output_filename);
#else
	(void)(gdata->npt->utf8ToPlatform)
	      (gdata->npt->utf, (jbyte*)gdata->utf8_output_filename, ulen, 
	       gdata->output_filename, ulen*3+3);
#endif
    }

    /* By default we turn on gdata->alloc_sites and gdata->heap_dump */
    if (     !gdata->cpu_timing && 
             !gdata->cpu_sampling && 
             !gdata->monitor_tracing && 
             !gdata->alloc_sites && 
             !gdata->heap_dump) {
        gdata->heap_dump = JNI_TRUE;
        gdata->alloc_sites = JNI_TRUE;
    }

    if ( gdata->alloc_sites || gdata->heap_dump ) {
	gdata->obj_watch = JNI_TRUE;
    }
    if ( gdata->obj_watch || gdata->cpu_timing ) {
        gdata->bci = JNI_TRUE;
    }

    /* Create files & sockets needed */
    if (gdata->heap_dump) {
	char *base;
	int   len;

	/* Get a fast tempfile for the heap information */
	base = gdata->output_filename;
	if ( base==NULL ) {
	    base = default_filename;
	}
	len = (int)strlen(base);
	gdata->heapfilename = HPROF_MALLOC(len + 5);
	(void)strcpy(gdata->heapfilename, base);
	(void)strcat(gdata->heapfilename, ".TMP");
	make_unique_filename(&(gdata->heapfilename));
	(void)remove(gdata->heapfilename);
	if (gdata->output_format == 'b') {
	    if ( gdata->logflags & LOG_CHECK_BINARY ) {
		char * check_suffix;

		check_suffix = ".check" DEFAULT_TXT_SUFFIX;
		gdata->checkfilename = 
		    HPROF_MALLOC((int)strlen(default_filename)+
				(int)strlen(check_suffix)+1);
		(void)strcpy(gdata->checkfilename, default_filename);
		(void)strcat(gdata->checkfilename, check_suffix);
		(void)remove(gdata->checkfilename);
	        gdata->check_fd = md_creat(gdata->checkfilename);
	    }
	    if ( gdata->debug ) {
	        gdata->logflags |= LOG_CHECK_BINARY;
	    }
	    gdata->heap_fd = md_creat_binary(gdata->heapfilename);
	} else {
	    gdata->heap_fd = md_creat(gdata->heapfilename);
	}
	if ( gdata->heap_fd < 0 ) {
	    char errmsg[FILENAME_MAX+80];

            (void)md_snprintf(errmsg, sizeof(errmsg),
                     "can't create temp heap file: %s", gdata->heapfilename);
                    errmsg[sizeof(errmsg)-1] = 0;
            HPROF_ERROR(JNI_TRUE, errmsg);
	}
    }
    
    if ( gdata->net_port > 0 ) {
        LOG2("Agent_OnLoad", "Connecting to socket");
        gdata->fd = connect_to_socket(gdata->net_hostname, (unsigned short)gdata->net_port);
        if (gdata->fd <= 0) {
	    char errmsg[120];
    
            (void)md_snprintf(errmsg, sizeof(errmsg),
                "can't connect to %s:%u", gdata->net_hostname, gdata->net_port);
            errmsg[sizeof(errmsg)-1] = 0;
            HPROF_ERROR(JNI_FALSE, errmsg);
            error_exit_process(1);
        }
        gdata->socket = JNI_TRUE;
    } else {
	/* If going out to a file, obey the force=y|n option */
	if ( !gdata->force_output ) {
            make_unique_filename(&(gdata->output_filename));
	}
	/* Make doubly sure this file does NOT exist */
	(void)remove(gdata->output_filename);
	/* Create the file */
	if (gdata->output_format == 'b') {
            gdata->fd = md_creat_binary(gdata->output_filename);
        } else {
            gdata->fd = md_creat(gdata->output_filename);
        }
        if (gdata->fd < 0) {
	    char errmsg[FILENAME_MAX+80];
    
            (void)md_snprintf(errmsg, sizeof(errmsg), 
		"can't create profile file: %s", gdata->output_filename);
            errmsg[sizeof(errmsg)-1] = 0;
	    HPROF_ERROR(JNI_FALSE, errmsg);
	    error_exit_process(1);
        }
    }

}

/* ------------------------------------------------------------------- */
/* Data reset and dump functions */

static void 
reset_all_data(void)
{
    if (gdata->cpu_sampling || gdata->cpu_timing || gdata->monitor_tracing) {
        rawMonitorEnter(gdata->data_access_lock);
    }

    if (gdata->cpu_sampling || gdata->cpu_timing) {
        trace_clear_cost();
    }
    if (gdata->monitor_tracing) {
        monitor_clear();
    }

    if (gdata->cpu_sampling || gdata->cpu_timing || gdata->monitor_tracing) {
        rawMonitorExit(gdata->data_access_lock);
    }
}

static void reset_class_load_status(JNIEnv *env, jthread thread);

static void 
dump_all_data(JNIEnv *env)
{
    verbose_message("Dumping");
    if (gdata->monitor_tracing) {
        verbose_message(" contended monitor usage ...");
        tls_dump_monitor_state(env);
        monitor_write_contended_time(env, gdata->cutoff_point);
    }
    if (gdata->heap_dump) {
        verbose_message(" Java heap ...");
	/* Update the class table */
	reset_class_load_status(env, NULL);
        site_heapdump(env); 
    }
    if (gdata->alloc_sites) {
        verbose_message(" allocation sites ...");
        site_write(env, 0, gdata->cutoff_point);
    }
    if (gdata->cpu_sampling) {
        verbose_message(" CPU usage by sampling running threads ...");
        trace_output_cost(env, gdata->cutoff_point);
    }
    if (gdata->cpu_timing) {
        if (!gdata->old_timing_format) {
            verbose_message(" CPU usage by timing methods ...");
            trace_output_cost(env, gdata->cutoff_point);
        } else {
            verbose_message(" CPU usage in old prof format ...");
            trace_output_cost_in_prof_format(env);
        }
    }
    reset_all_data();
    io_flush();
    verbose_message(" done.\n");
}

/* ------------------------------------------------------------------- */
/* Dealing with class load and unload status */

static void
reset_class_load_status(JNIEnv *env, jthread thread)
{
    
    WITH_LOCAL_REFS(env, 1) {
	jint    class_count;
	jclass *classes;
	jint    i;

	/* Get all classes from JVMTI, make sure they are in the class table. */
	getLoadedClasses(&classes, &class_count);

	/* We don't know if the class list has changed really, so we
	 *    guess by the class count changing. Don't want to do
	 *    a bunch of work on classes when it's unnecessary.
	 *    I assume that even though we have global references on the
	 *    jclass object that the class is still considered unloaded.
	 *    (e.g. GC of jclass isn't required for it to be included
	 *    in the unloaded list, or not in the load list)
	 *    [Note: Use of Weak references was a performance problem.]
	 */
	if ( class_count != gdata->class_count ) {

            rawMonitorEnter(gdata->data_access_lock); {
		
		/* Unmark the classes in the load list */
		class_all_status_remove(CLASS_IN_LOAD_LIST);
		
		/* Pretend like it was a class load event */
		for ( i = 0 ; i < class_count ; i++ ) {
		    jobject loader;
		    
		    loader = getClassLoader(classes[i]);
		    event_class_load(env, thread, classes[i], loader);
		}

		/* Process the classes that have been unloaded */
		class_do_unloads(env);
            
	    } rawMonitorExit(gdata->data_access_lock);
	
	}

	/* Free the space and save the count. */
	jvmtiDeallocate(classes);
	gdata->class_count = class_count;
    
    } END_WITH_LOCAL_REFS;

}

/* A GC or Death event has happened, so do some cleanup */
static void
object_free_cleanup(JNIEnv *env, jboolean force_class_table_reset)
{
    Stack *stack;
    
    /* Then we process the ObjectFreeStack */
    rawMonitorEnter(gdata->object_free_lock); {
	stack = gdata->object_free_stack;
	gdata->object_free_stack = NULL; /* Will trigger new stack */
    } rawMonitorExit(gdata->object_free_lock);

    /* Notice we just grabbed the stack of freed objects so
     *    any object free events will create a new stack.
     */
    if ( stack != NULL ) {
	int count;
	int i;
	
	count = stack_depth(stack);

	/* If we saw something freed in this GC */
	if ( count > 0 ) {
	
	    for ( i = 0 ; i < count ; i++ ) {
		ObjectIndex object_index;
		jlong tag;
		
		tag = *(jlong*)stack_element(stack,i);
		    object_index = tag_extract(tag);

		(void)object_free(object_index);
	    }
	    
	    /* We reset the class load status (only do this once) */
	    reset_class_load_status(env, NULL);
	    force_class_table_reset = JNI_FALSE;
	
	}
	
	/* Just terminate this stack object */
	stack_term(stack);
    }
    
    /* We reset the class load status if we haven't and need to */
    if ( force_class_table_reset ) {
	reset_class_load_status(env, NULL);
    }
    
}

/* Main function for thread that watches for GC finish events */
static void JNICALL
gc_finish_watcher(jvmtiEnv *jvmti, JNIEnv *env, void *p)
{
    jboolean active;

    active = JNI_TRUE;
   
    /* Indicate the watcher thread is active */
    rawMonitorEnter(gdata->gc_finish_lock); {
        gdata->gc_finish_active = JNI_TRUE;
    } rawMonitorExit(gdata->gc_finish_lock);
   
    /* Loop while active */
    while ( active ) {
        jboolean do_cleanup;
	
	do_cleanup = JNI_FALSE;
	rawMonitorEnter(gdata->gc_finish_lock); {
	    /* Don't wait if VM_DEATH wants us to quit */
	    if ( gdata->gc_finish_stop_request ) {
		/* Time to terminate */
		active = JNI_FALSE;
	    } else {
		/* Wait for notification to do cleanup, or terminate */
		rawMonitorWait(gdata->gc_finish_lock, 0);
		/* After wait, check to see if VM_DEATH wants us to quit */
	        if ( gdata->gc_finish_stop_request ) {
		    /* Time to terminate */
		    active = JNI_FALSE;
		}
	    }
	    if ( active && gdata->gc_finish > 0 ) {
		/* Time to cleanup, reset count and prepare for cleanup */
	        gdata->gc_finish = 0;
		do_cleanup = JNI_TRUE;
	    }
	} rawMonitorExit(gdata->gc_finish_lock);

	/* Do the cleanup if requested outside gc_finish_lock */
	if ( do_cleanup ) {
	    /* Free up all freed objects, don't force class table reset 
	     *   We cannot let the VM_DEATH complete while we are doing
	     *   this cleanup. So if during this, VM_DEATH happens, 
	     *   the VM_DEATH callback should block waiting for this
	     *   loop to terminate, and send a notification to the
	     *   VM_DEATH thread.
	     */
	    object_free_cleanup(env, JNI_FALSE);

	    /* Cleanup the tls table where the Thread objects were GC'd */
	    tls_garbage_collect(env);
	}

    }
   
    /* Falling out means VM_DEATH is happening, we need to notify VM_DEATH
     *    that we are done doing the cleanup. VM_DEATH is waiting on this
     *    notify.
     */
    rawMonitorEnter(gdata->gc_finish_lock); {
        gdata->gc_finish_active = JNI_FALSE;
        rawMonitorNotifyAll(gdata->gc_finish_lock);
    } rawMonitorExit(gdata->gc_finish_lock);
}

/* ------------------------------------------------------------------- */
/* JVMTI Event callback functions */

static void
setup_event_mode(jboolean onload_set_only, jvmtiEventMode state)
{
    if ( onload_set_only ) {
	setEventNotificationMode(state, 
			JVMTI_EVENT_VM_INIT,                   NULL);
	setEventNotificationMode(state, 
			JVMTI_EVENT_VM_DEATH,                  NULL);
	if (gdata->bci) {
	    setEventNotificationMode(state, 
			JVMTI_EVENT_CLASS_FILE_LOAD_HOOK,      NULL);
	}
    } else { 
        /* Enable all other JVMTI events of interest now. */
	setEventNotificationMode(state, 
			JVMTI_EVENT_THREAD_START,              NULL);
	setEventNotificationMode(state, 
			JVMTI_EVENT_THREAD_END,                NULL);
	setEventNotificationMode(state, 
		        JVMTI_EVENT_CLASS_LOAD,                NULL);
	setEventNotificationMode(state, 
		        JVMTI_EVENT_CLASS_PREPARE,             NULL);
	setEventNotificationMode(state, 
			JVMTI_EVENT_DATA_DUMP_REQUEST,         NULL);
	if (gdata->cpu_timing) {
	    setEventNotificationMode(state, 
			JVMTI_EVENT_EXCEPTION_CATCH,           NULL);
	}
	if (gdata->monitor_tracing) {
	    setEventNotificationMode(state, 
			JVMTI_EVENT_MONITOR_WAIT,              NULL);
	    setEventNotificationMode(state, 
			JVMTI_EVENT_MONITOR_WAITED,            NULL);
	    setEventNotificationMode(state, 
			JVMTI_EVENT_MONITOR_CONTENDED_ENTER,   NULL);
	    setEventNotificationMode(state, 
			JVMTI_EVENT_MONITOR_CONTENDED_ENTERED, NULL);
	}
	if (gdata->obj_watch) {
	    setEventNotificationMode(state, 
			JVMTI_EVENT_OBJECT_FREE,               NULL);
	}
	setEventNotificationMode(state, 
			JVMTI_EVENT_GARBAGE_COLLECTION_START,  NULL);
	setEventNotificationMode(state, 
			JVMTI_EVENT_GARBAGE_COLLECTION_FINISH, NULL);
    }       
}    

/* JVMTI_EVENT_VM_INIT */
static void JNICALL
cbVMInit(jvmtiEnv *jvmti, JNIEnv *env, jthread thread)
{
    rawMonitorEnter(gdata->data_access_lock); {

	LoaderIndex loader_index;
	ClassIndex  cnum;
	TlsIndex    tls_index;

        gdata->jvm_initializing = JNI_TRUE;
    
	/* Header to use in heap dumps */
	gdata->header    = "JAVA PROFILE 1.0.1";
	gdata->segmented = JNI_FALSE;
	if (gdata->output_format == 'b') {
	    /* We need JNI here to call in and get the current maximum memory */
	    gdata->maxMemory      = getMaxMemory(env);
	    gdata->maxHeapSegment = (jlong)2000000000;
	    /* More than 2Gig triggers segments and 1.0.2 */ 
	    if ( gdata->maxMemory >= gdata->maxHeapSegment ) {
	        gdata->header    = "JAVA PROFILE 1.0.2";
	        gdata->segmented = JNI_TRUE; /* 1.0.2 */
	    }
	}

	/* We write the initial header after the VM initializes now
	 *    because we needed to use JNI to get maxMemory and determine if
	 *    a 1.0.1 or a 1.0.2 header will be used.
	 *    This used to be done in Agent_OnLoad.
	 */
	io_write_file_header();
	
	LOG("cbVMInit begin");
     
	/* Create a system loader entry first */
	loader_index 	        = loader_find_or_create(NULL,NULL);

        /* Find the thread jclass (does JNI calls) */
        gdata->thread_cnum = class_find_or_create("Ljava/lang/Thread;", 
			loader_index);
	class_add_status(gdata->thread_cnum, CLASS_SYSTEM);
        
	/* Issue fake system thread start */
	tls_index = tls_find_or_create(env, thread);
	
	/* Setup the Tracker class (should be first class in table) */
	tracker_setup_class();
	
	/* Find selected system classes to keep track of */
	gdata->system_class_size = 0;
	cnum = class_find_or_create("Ljava/lang/Object;", loader_index);

	gdata->system_trace_index = tls_get_trace(tls_index, env,
				gdata->max_trace_depth, JNI_FALSE);
	gdata->system_object_site_index = site_find_or_create(
		    cnum, gdata->system_trace_index);
	
	/* Used to ID HPROF generated items */
	gdata->hprof_trace_index = tls_get_trace(tls_index, env,
				gdata->max_trace_depth, JNI_FALSE);
	gdata->hprof_site_index	= site_find_or_create(
		    cnum, gdata->hprof_trace_index);

	if ( gdata->logflags & LOG_DUMP_LISTS ) {
	    list_all_tables();
	}
	
	/* Prime the class table */
	reset_class_load_status(env, thread);
	
	/* Find the tracker jclass and jmethodID's (does JNI calls) */
        if ( gdata->bci ) {
            tracker_setup_methods(env);
        }
   
        /* Start any agent threads (does JNI, JVMTI, and Java calls) */

	/* Thread to watch for gc_finish events */
	rawMonitorEnter(gdata->gc_finish_lock); {
	    createAgentThread(env, "HPROF gc_finish watcher", 
			      &gc_finish_watcher);
	} rawMonitorExit(gdata->gc_finish_lock);

        /* Start up listener thread if we need it */
        if ( gdata->socket ) {
	    listener_init(env);
	}
	
        /* Start up cpu sampling thread if we need it */
        if ( gdata->cpu_sampling ) {
	    /* Note: this could also get started later (see cpu) */
	    cpu_sample_init(env);
        }

	/* Setup event modes */
	setup_event_mode(JNI_FALSE, JVMTI_ENABLE);

	/* Engage tracking (sets Java Tracker field so injections call into
	 *     agent library).
	 */
	if ( gdata->bci ) {
	    tracker_engage(env); 
	}
        
        /* Indicate the VM is initialized now */
        gdata->jvm_initialized = JNI_TRUE;
        gdata->jvm_initializing = JNI_FALSE;
       
	LOG("cbVMInit end");
    
    } rawMonitorExit(gdata->data_access_lock);
}

/* JVMTI_EVENT_VM_DEATH */
static void JNICALL
cbVMDeath(jvmtiEnv *jvmti, JNIEnv *env)
{
    /*
     * Use local flag to minimize gdata->dump_lock hold time.
     */
    jboolean need_to_dump = JNI_FALSE;

    LOG("cbVMDeath");
   
    /* Shutdown thread watching gc_finish, outside CALLBACK locks.
     *   We need to make sure the watcher thread is done doing any cleanup
     *   work before we continue here.
     */
    rawMonitorEnter(gdata->gc_finish_lock); {
	/* Notify watcher thread to finish up, it will send 
	 *   another notify when done. If the watcher thread is busy
	 *   cleaning up, it will detect gc_finish_stop_request when it's done.
	 *   Then it sets gc_finish_active to JNI_FALSE and will notify us. 
	 *   If the watcher thread is waiting to be notified, then the
	 *   notification wakes it up.
	 *   We do not want to do the VM_DEATH while the gc_finish
	 *   watcher thread is in the middle of a cleanup.
	 */
	gdata->gc_finish_stop_request = JNI_TRUE;
	rawMonitorNotifyAll(gdata->gc_finish_lock);
	/* Wait for the gc_finish watcher thread to notify us it's done */
	while ( gdata->gc_finish_active ) {
	    rawMonitorWait(gdata->gc_finish_lock,0);
	}
    } rawMonitorExit(gdata->gc_finish_lock);
    
    /* The gc_finish watcher thread should be done now, or done shortly. */


    /* BEGIN_CALLBACK/END_CALLBACK handling. */
   
    /* The callbackBlock prevents any active callbacks from returning
     *   back to the VM, and also blocks all new callbacks.
     *   We want to prevent any threads from premature death, so
     *   that we don't have worry about that during thread queries
     *   in this final dump process.
     */
    rawMonitorEnter(gdata->callbackBlock); {
	
	/* We need to wait for all callbacks actively executing to block
	 *   on exit, and new ones will block on entry.
	 *   The BEGIN_CALLBACK/END_CALLBACK macros keep track of callbacks
	 *   that are active.
	 *   Once the last active callback is done, it will notify this 
	 *   thread and block.
	 */
     
	rawMonitorEnter(gdata->callbackLock); {
	    /* Turn off native calls */
	    if ( gdata->bci ) {
		tracker_disengage(env);
	    }
	    gdata->vm_death_callback_active = JNI_TRUE;
	    while (gdata->active_callbacks > 0) {
		rawMonitorWait(gdata->callbackLock, 0);
	    }
	} rawMonitorExit(gdata->callbackLock);

	/* Now we know that no threads will die on us, being blocked
	 *   on some event callback, at a minimum ThreadEnd.
	 */

	/* Make some basic checks. */
	rawMonitorEnter(gdata->data_access_lock); {
	    if ( gdata->jvm_initializing ) {
		HPROF_ERROR(JNI_TRUE, "VM Death during VM Init");
		return;
	    }
	    if ( !gdata->jvm_initialized ) {
		HPROF_ERROR(JNI_TRUE, "VM Death before VM Init");
		return;
	    }
	    if (gdata->jvm_shut_down) {
		HPROF_ERROR(JNI_TRUE, "VM Death more than once?");
		return;
	    }
	} rawMonitorExit(gdata->data_access_lock);
	
	/* Shutdown the cpu loop thread */
	if ( gdata->cpu_sampling ) {
	    cpu_sample_term(env);	
	}

	/* Time to dump the final data */
	rawMonitorEnter(gdata->dump_lock); {

	    gdata->jvm_shut_down = JNI_TRUE;

	    if (!gdata->dump_in_process) {
		need_to_dump    = JNI_TRUE;
		gdata->dump_in_process = JNI_TRUE;
		/*
		 * Setting gdata->dump_in_process will cause cpu sampling to pause
		 * (if we are sampling). We don't resume sampling after the
		 * dump_all_data() call below because the VM is shutting
		 * down.
		 */
	    }
	
	} rawMonitorExit(gdata->dump_lock);
       
	/* Dump everything if we need to */ 
	if (gdata->dump_on_exit && need_to_dump) {
	
	    dump_all_data(env);
	}

	/* Disable all events and callbacks now, all of them.
	 *   NOTE: It's important that this be done after the dump
	 *         it prevents other threads from messing up the data
	 *         because they will block on ThreadStart and ThreadEnd
	 *         events due to the CALLBACK block.
	 */
	set_callbacks(JNI_FALSE);
	setup_event_mode(JNI_FALSE, JVMTI_DISABLE);
	setup_event_mode(JNI_TRUE, JVMTI_DISABLE);
    
	/* Write tail of file */
        io_write_file_footer();
	
    } rawMonitorExit(gdata->callbackBlock);
	
    /* Shutdown the listener thread and socket, or flush I/O buffers */
    if (gdata->socket) {
	listener_term(env);
    } else {
	io_flush();
    }

    /* Close the file descriptors down */
    if ( gdata->fd  >= 0 ) {
	(void)md_close(gdata->fd);
	gdata->fd = -1;
	if ( gdata->logflags & LOG_CHECK_BINARY ) {
	    if (gdata->output_format == 'b' && gdata->output_filename != NULL) {
		check_binary_file(gdata->output_filename);
	    }
	}
    }
    if ( gdata->heap_fd  >= 0 ) {
	(void)md_close(gdata->heap_fd);
	gdata->heap_fd = -1;
    }
    
    if ( gdata->check_fd  >= 0 ) {
	(void)md_close(gdata->check_fd);
	gdata->check_fd = -1;
    }
   
    /* Remove the temporary heap file */
    if (gdata->heap_dump) {
	(void)remove(gdata->heapfilename);
    }
    
    /* If logging, dump the tables */
    if ( gdata->logflags & LOG_DUMP_LISTS ) {
	list_all_tables();
    }

    /* Make sure all global references are deleted */
    class_delete_global_references(env);
    loader_delete_global_references(env);
    tls_delete_global_references(env);
    
}  

/* JVMTI_EVENT_THREAD_START */
static void JNICALL
cbThreadStart(jvmtiEnv *jvmti, JNIEnv *env, jthread thread)
{
    LOG3("cbThreadStart", "thread is", (int)(long)(ptrdiff_t)thread);
    
    BEGIN_CALLBACK() {
        event_thread_start(env, thread);
    } END_CALLBACK();
}

/* JVMTI_EVENT_THREAD_END */
static void JNICALL
cbThreadEnd(jvmtiEnv *jvmti, JNIEnv *env, jthread thread)
{
    LOG3("cbThreadEnd", "thread is", (int)(long)(ptrdiff_t)thread);
    
    BEGIN_CALLBACK() {
        event_thread_end(env, thread);
    } END_CALLBACK();
}

/* JVMTI_EVENT_CLASS_FILE_LOAD_HOOK */
static void JNICALL
cbClassFileLoadHook(jvmtiEnv *jvmti_env, JNIEnv* env,
                jclass class_being_redefined, jobject loader, 
                const char* name, jobject protection_domain, 
                jint class_data_len, const unsigned char* class_data,
                jint* new_class_data_len, unsigned char** new_class_data)
{

    /* WARNING: This will be called before VM_INIT. */

    LOG2("cbClassFileLoadHook:",(name==NULL?"Unknown":name));
    
    if (!gdata->bci) {
        return;
    }
    
    BEGIN_CALLBACK() {
	rawMonitorEnter(gdata->data_access_lock); {
	    const char *classname;
	    
	    if ( gdata->bci_counter == 0 ) {
		/* Prime the system classes */
		class_prime_system_classes();
	    }
	    
	    gdata->bci_counter++;
	    
	    *new_class_data_len = 0;
	    *new_class_data     = NULL;

	    /* Name could be NULL */
	    if ( name == NULL ) {
		classname = ((JavaCrwDemoClassname)
			     (gdata->java_crw_demo_classname_function))
		    (class_data, class_data_len, &my_crw_fatal_error_handler);
		if ( classname == NULL ) {
		    HPROF_ERROR(JNI_TRUE, "No classname in classfile");
		}
	    } else {
		classname = strdup(name);
		if ( classname == NULL ) {
		    HPROF_ERROR(JNI_TRUE, "Ran out of malloc() space");
		}
	    }
	    
	    /* The tracker class itself? */
	    if ( strcmp(classname, TRACKER_CLASS_NAME) != 0 ) {
		ClassIndex            cnum;
		int                   system_class;
		unsigned char *       new_image;
		long                  new_length;
		int                   len;
                char                 *signature;
		LoaderIndex           loader_index;
		
		LOG2("cbClassFileLoadHook injecting class" , classname);
		
		/* Define a unique class number for this class */
		len              = (int)strlen(classname);
		signature        = HPROF_MALLOC(len+3);
		signature[0]     = JVM_SIGNATURE_CLASS;
		(void)memcpy(signature+1, classname, len);
		signature[len+1] = JVM_SIGNATURE_ENDCLASS;
		signature[len+2] = 0;
		loader_index = loader_find_or_create(env,loader);
		if ( class_being_redefined != NULL ) {
		    cnum  = class_find_or_create(signature, loader_index);
		} else {
		    cnum  = class_create(signature, loader_index);
		}
		HPROF_FREE(signature);
		signature        = NULL;

		/* Make sure class doesn't get unloaded by accident */
		class_add_status(cnum, CLASS_IN_LOAD_LIST);
		
		/* Is it a system class? */
		system_class = 0;
		if (    (!gdata->jvm_initialized) 
		     && (!gdata->jvm_initializing) 
		     && ( ( class_get_status(cnum) & CLASS_SYSTEM) != 0
			    || gdata->bci_counter < 8 ) ) {
		    system_class = 1;
		    LOG2(classname, " is a system class");
		}

		new_image = NULL;
		new_length = 0;
	
		/* Call the class file reader/write demo code */
		((JavaCrwDemo)(gdata->java_crw_demo_function))(
		    cnum, 
		    classname, 
		    class_data, 
		    class_data_len, 
	            system_class,
 		    TRACKER_CLASS_NAME,
 		    TRACKER_CLASS_SIG,
 		    (gdata->cpu_timing)?TRACKER_CALL_NAME:NULL,
 		    (gdata->cpu_timing)?TRACKER_CALL_SIG:NULL,
 		    (gdata->cpu_timing)?TRACKER_RETURN_NAME:NULL,
 		    (gdata->cpu_timing)?TRACKER_RETURN_SIG:NULL,
 		    (gdata->obj_watch)?TRACKER_OBJECT_INIT_NAME:NULL,
 		    (gdata->obj_watch)?TRACKER_OBJECT_INIT_SIG:NULL,
 		    (gdata->obj_watch)?TRACKER_NEWARRAY_NAME:NULL,
 		    (gdata->obj_watch)?TRACKER_NEWARRAY_SIG:NULL,
		    &new_image,
		    &new_length,
		    &my_crw_fatal_error_handler, 
		    &class_set_methods);

		if ( new_length > 0 ) {
		    unsigned char *jvmti_space;
		    
		    LOG2("cbClassFileLoadHook DID inject this class", classname);
		    jvmti_space = (unsigned char *)jvmtiAllocate((jint)new_length);
		    (void)memcpy((void*)jvmti_space, (void*)new_image, (int)new_length);
		    *new_class_data_len = (jint)new_length;
		    *new_class_data     = jvmti_space; /* VM will deallocate */
		} else {
		    LOG2("cbClassFileLoadHook DID NOT inject this class", classname);
		    *new_class_data_len = 0;
		    *new_class_data     = NULL;
	        }
		if ( new_image != NULL ) {
		    (void)free((void*)new_image); /* Free malloc() space with free() */
		}
	    }
	    (void)free((void*)classname);
	} rawMonitorExit(gdata->data_access_lock);
    } END_CALLBACK();
}

/* JVMTI_EVENT_CLASS_LOAD */
static void JNICALL
cbClassLoad(jvmtiEnv *jvmti, JNIEnv *env, jthread thread, jclass klass)
{

    /* WARNING: This MAY be called before VM_INIT. */

    LOG("cbClassLoad");
    
    BEGIN_CALLBACK() {
	rawMonitorEnter(gdata->data_access_lock); {
	    
	    WITH_LOCAL_REFS(env, 1) {
		jobject loader;
		
		loader = getClassLoader(klass);
		event_class_load(env, thread, klass, loader);
	    } END_WITH_LOCAL_REFS;

	} rawMonitorExit(gdata->data_access_lock);
    } END_CALLBACK();
}

/* JVMTI_EVENT_CLASS_PREPARE */
static void JNICALL
cbClassPrepare(jvmtiEnv *jvmti, JNIEnv *env, jthread thread, jclass klass)
{

    /* WARNING: This will be called before VM_INIT. */

    LOG("cbClassPrepare");
    
    BEGIN_CALLBACK() {
	rawMonitorEnter(gdata->data_access_lock); {
	    
	    WITH_LOCAL_REFS(env, 1) {
		jobject loader;
		
		loader = NULL;
		loader = getClassLoader(klass);
		event_class_prepare(env, thread, klass, loader);
	    } END_WITH_LOCAL_REFS;
	
	} rawMonitorExit(gdata->data_access_lock);
    } END_CALLBACK();

}

/* JVMTI_EVENT_DATA_DUMP_REQUEST */
static void JNICALL
cbDataDumpRequest(jvmtiEnv *jvmti)
{
    jboolean need_to_dump;
    
    LOG("cbDataDumpRequest");
    
    BEGIN_CALLBACK() {
	need_to_dump = JNI_FALSE;
	rawMonitorEnter(gdata->dump_lock); {
	    if (!gdata->dump_in_process) {
		need_to_dump    = JNI_TRUE;
		gdata->dump_in_process = JNI_TRUE;
	    }
	} rawMonitorExit(gdata->dump_lock);
	
	if (need_to_dump) {
	    dump_all_data(getEnv());

	    rawMonitorEnter(gdata->dump_lock); {
		gdata->dump_in_process = JNI_FALSE;
	    } rawMonitorExit(gdata->dump_lock);

	    if (gdata->cpu_sampling && !gdata->jvm_shut_down) {
		cpu_sample_on(NULL, 0); /* resume sampling */
	    }
	}
    } END_CALLBACK();

}

/* JVMTI_EVENT_EXCEPTION_CATCH */
static void JNICALL
cbExceptionCatch(jvmtiEnv *jvmti, JNIEnv* env,
                jthread thread, jmethodID method, jlocation location,
		jobject exception)
{
    LOG("cbExceptionCatch");
    
    BEGIN_CALLBACK() {
	event_exception_catch(env, thread, method, location, exception);
    } END_CALLBACK();
}

/* JVMTI_EVENT_MONITOR_WAIT */
static void JNICALL
cbMonitorWait(jvmtiEnv *jvmti, JNIEnv* env,
                jthread thread, jobject object, jlong timeout)
{
    LOG("cbMonitorWait");
    
    BEGIN_CALLBACK() {
	monitor_wait_event(env, thread, object, timeout);
    } END_CALLBACK();
}

/* JVMTI_EVENT_MONITOR_WAITED */
static void JNICALL
cbMonitorWaited(jvmtiEnv *jvmti, JNIEnv* env,
                jthread thread, jobject object, jboolean timed_out)
{
    LOG("cbMonitorWaited");
    
    BEGIN_CALLBACK() {
	monitor_waited_event(env, thread, object, timed_out);
    } END_CALLBACK();
}

/* JVMTI_EVENT_MONITOR_CONTENDED_ENTER */
static void JNICALL
cbMonitorContendedEnter(jvmtiEnv *jvmti, JNIEnv* env,
                jthread thread, jobject object)
{
    LOG("cbMonitorContendedEnter");
    
    BEGIN_CALLBACK() {
	monitor_contended_enter_event(env, thread, object);
    } END_CALLBACK();
}

/* JVMTI_EVENT_MONITOR_CONTENDED_ENTERED */
static void JNICALL
cbMonitorContendedEntered(jvmtiEnv *jvmti, JNIEnv* env,
                jthread thread, jobject object)
{
    LOG("cbMonitorContendedEntered");
    
    BEGIN_CALLBACK() {
	monitor_contended_entered_event(env, thread, object);
    } END_CALLBACK();
}

/* JVMTI_EVENT_GARBAGE_COLLECTION_START */
static void JNICALL
cbGarbageCollectionStart(jvmtiEnv *jvmti)
{
    LOG("cbGarbageCollectionStart");
    
    /* Only calls to Allocate, Deallocate, RawMonitorEnter & RawMonitorExit
     *   are allowed here (see the JVMTI Spec).
     */

    gdata->gc_start_time = md_get_timemillis();
}

/* JVMTI_EVENT_GARBAGE_COLLECTION_FINISH */
static void JNICALL
cbGarbageCollectionFinish(jvmtiEnv *jvmti)
{
    LOG("cbGarbageCollectionFinish");
   
    /* Only calls to Allocate, Deallocate, RawMonitorEnter & RawMonitorExit
     *   are allowed here (see the JVMTI Spec).
     */

    if ( gdata->gc_start_time != -1L ) {
	gdata->time_in_gc += (md_get_timemillis() - gdata->gc_start_time);
	gdata->gc_start_time = -1L;
    }
   
    /* Increment gc_finish counter, notify watcher thread */
    rawMonitorEnter(gdata->gc_finish_lock); {
	/* If VM_DEATH is trying to shut it down, don't do anything at all.
	 *    Never send notify if VM_DEATH wants the watcher thread to quit.
	 */
	if ( gdata->gc_finish_active ) {
	    gdata->gc_finish++;
	    rawMonitorNotifyAll(gdata->gc_finish_lock);
	}
    } rawMonitorExit(gdata->gc_finish_lock);
}

/* JVMTI_EVENT_OBJECT_FREE */
static void JNICALL
cbObjectFree(jvmtiEnv *jvmti, jlong tag)
{
    LOG3("cbObjectFree", "tag", (int)tag);
   
    /* Only calls to Allocate, Deallocate, RawMonitorEnter & RawMonitorExit
     *   are allowed here (see the JVMTI Spec).
     */

    HPROF_ASSERT(tag!=(jlong)0);
    rawMonitorEnter(gdata->object_free_lock); {
	if ( !gdata->jvm_shut_down ) {
	    Stack *stack;
	    
	    stack = gdata->object_free_stack;
	    if ( stack == NULL ) {
		gdata->object_free_stack = stack_init(512, 512, sizeof(jlong));
		stack = gdata->object_free_stack;
	    }
	    stack_push(stack, (void*)&tag);
	}
    } rawMonitorExit(gdata->object_free_lock);
}

static void 
set_callbacks(jboolean on) 
{
    jvmtiEventCallbacks callbacks;
    
    (void)memset(&callbacks,0,sizeof(callbacks));
    if ( ! on ) {
        setEventCallbacks(&callbacks);
	return;
    }
    
    /* JVMTI_EVENT_VM_INIT */
    callbacks.VMInit                     = &cbVMInit;
    /* JVMTI_EVENT_VM_DEATH */
    callbacks.VMDeath                    = &cbVMDeath;
    /* JVMTI_EVENT_THREAD_START */
    callbacks.ThreadStart                = &cbThreadStart;
    /* JVMTI_EVENT_THREAD_END */
    callbacks.ThreadEnd                  = &cbThreadEnd;
    /* JVMTI_EVENT_CLASS_FILE_LOAD_HOOK */
    callbacks.ClassFileLoadHook          = &cbClassFileLoadHook;
    /* JVMTI_EVENT_CLASS_LOAD */
    callbacks.ClassLoad                  = &cbClassLoad;
    /* JVMTI_EVENT_CLASS_PREPARE */
    callbacks.ClassPrepare               = &cbClassPrepare;
    /* JVMTI_EVENT_DATA_DUMP_REQUEST */
    callbacks.DataDumpRequest		 = &cbDataDumpRequest;
    /* JVMTI_EVENT_EXCEPTION_CATCH */
    callbacks.ExceptionCatch             = &cbExceptionCatch;
    /* JVMTI_EVENT_MONITOR_WAIT */
    callbacks.MonitorWait                = &cbMonitorWait;
    /* JVMTI_EVENT_MONITOR_WAITED */
    callbacks.MonitorWaited              = &cbMonitorWaited;
    /* JVMTI_EVENT_MONITOR_CONTENDED_ENTER */
    callbacks.MonitorContendedEnter      = &cbMonitorContendedEnter;
    /* JVMTI_EVENT_MONITOR_CONTENDED_ENTERED */
    callbacks.MonitorContendedEntered    = &cbMonitorContendedEntered;
    /* JVMTI_EVENT_GARBAGE_COLLECTION_START */
    callbacks.GarbageCollectionStart     = &cbGarbageCollectionStart;
    /* JVMTI_EVENT_GARBAGE_COLLECTION_FINISH */
    callbacks.GarbageCollectionFinish    = &cbGarbageCollectionFinish;
    /* JVMTI_EVENT_OBJECT_FREE */
    callbacks.ObjectFree                 = &cbObjectFree;

    setEventCallbacks(&callbacks);
    
}

static void 
getCapabilities(void)
{
    jvmtiCapabilities needed_capabilities;
    jvmtiCapabilities potential_capabilities;

    /* Fill in ones that we must have */
    (void)memset(&needed_capabilities,0,sizeof(needed_capabilities));
    needed_capabilities.can_generate_garbage_collection_events   = 1;
    needed_capabilities.can_tag_objects                          = 1;
    if (gdata->bci) {
        needed_capabilities.can_generate_all_class_hook_events   = 1;
    }
    if (gdata->obj_watch) {
        needed_capabilities.can_generate_object_free_events      = 1;
    }
    if (gdata->cpu_timing || gdata->cpu_sampling) {
	#if 0 /* Not needed until we call JVMTI for CpuTime */
        needed_capabilities.can_get_thread_cpu_time              = 1;
        needed_capabilities.can_get_current_thread_cpu_time      = 1;
	#endif
        needed_capabilities.can_generate_exception_events        = 1;
    }
    if (gdata->monitor_tracing) {
	#if 0 /* Not needed until we call JVMTI for CpuTime */
        needed_capabilities.can_get_thread_cpu_time              = 1;
        needed_capabilities.can_get_current_thread_cpu_time      = 1;
	#endif
        needed_capabilities.can_get_owned_monitor_info           = 1;
        needed_capabilities.can_get_current_contended_monitor    = 1;
        needed_capabilities.can_get_monitor_info                 = 1;
        needed_capabilities.can_generate_monitor_events          = 1;
    }

    /* Get potential capabilities */
    getPotentialCapabilities(&potential_capabilities);
   
    /* Some capabilities would be nicer to have */ 
    needed_capabilities.can_get_source_file_name        = 
        potential_capabilities.can_get_source_file_name;
    needed_capabilities.can_get_line_numbers    = 
        potential_capabilities.can_get_line_numbers;

    /* Add the capabilities */
    addCapabilities(&needed_capabilities);

}

/* Dynamic library loading */
static void *
load_library(char *name)
{
    char  lname[FILENAME_MAX+1];
    char  err_buf[256+FILENAME_MAX+1];
    char *boot_path;
    void *handle;

    handle = NULL;

    /* The library may be located in different ways, try both, but
     *   if it comes from outside the SDK/jre it isn't ours.
     */
    getSystemProperty("sun.boot.library.path", &boot_path);
    md_build_library_name(lname, FILENAME_MAX, boot_path, name);
    handle = md_load_library(lname, err_buf, (int)sizeof(err_buf));
    if ( handle == NULL ) {
	/* This may be necessary on Windows. */
	md_build_library_name(lname, FILENAME_MAX, "", name);
	handle = md_load_library(lname, err_buf, (int)sizeof(err_buf));
	if ( handle == NULL ) {
	    HPROF_ERROR(JNI_TRUE, err_buf);
	}
    }
    return handle;
}

/* Lookup dynamic function pointer in shared library */
static void *
lookup_library_symbol(void *library, char **symbols, int nsymbols)
{
    void *addr;
    int   i;

    addr = NULL;
    for( i = 0 ; i < nsymbols; i++ ) {
	addr = md_find_library_entry(library, symbols[i]);
	if ( addr != NULL ) {
	    break;
	}
    }
    if ( addr == NULL ) {
	char errmsg[256];
	
	(void)md_snprintf(errmsg, sizeof(errmsg), 
		    "Cannot find library symbol '%s'", symbols[0]);
	HPROF_ERROR(JNI_TRUE, errmsg);
    }
    return addr;
}

/* ------------------------------------------------------------------- */
/* The OnLoad interface */

JNIEXPORT jint JNICALL 
Agent_OnLoad(JavaVM *vm, char *options, void *reserved)
{
    /* See if it's already loaded */
    if ( gdata!=NULL && gdata->isLoaded==JNI_TRUE ) {
        HPROF_ERROR(JNI_TRUE, "Cannot load this JVM TI agent twice, check your java command line for duplicate hprof options.");
        return JNI_ERR;
    }
    
    gdata = get_gdata();

    gdata->isLoaded = JNI_TRUE;

    error_setup();

    LOG2("Agent_OnLoad", "gdata setup");
    
    gdata->jvm = vm;

#ifndef SKIP_NPT
    /* Load in NPT library for character conversions */
    NPT_INITIALIZE(&(gdata->npt), NPT_VERSION, NULL);
    if ( gdata->npt == NULL ) {
        HPROF_ERROR(JNI_TRUE, "Cannot load npt library");
    }
    gdata->npt->utf = (gdata->npt->utfInitialize)(NULL);
    if ( gdata->npt->utf == NULL ) {
        HPROF_ERROR(JNI_TRUE, "Cannot initialize npt utf functions");
    }
#endif

    /* Get the JVMTI environment */
    getJvmti();
   
    /* Lock needed to protect debug_malloc() code, which is not MT safe */
    #ifdef DEBUG
	gdata->debug_malloc_lock = createRawMonitor("HPROF debug_malloc lock");
    #endif

    parse_options(options);

    LOG2("Agent_OnLoad", "Has jvmtiEnv and options parsed");

    /* Initialize machine dependent code (micro state accounting) */
    md_init();

    string_init();	/* Table index values look like: 0x10000000 */
    
    class_init();	/* Table index values look like: 0x20000000 */
    tls_init();		/* Table index values look like: 0x30000000 */
    trace_init();	/* Table index values look like: 0x40000000 */
    object_init();	/* Table index values look like: 0x50000000 */
    
    site_init();	/* Table index values look like: 0x60000000 */
    frame_init();	/* Table index values look like: 0x70000000 */
    monitor_init();	/* Table index values look like: 0x80000000 */
    loader_init();	/* Table index values look like: 0x90000000 */

    LOG2("Agent_OnLoad", "Tables initialized");
    
    if ( gdata->pause ) {
        error_do_pause();
    }
    
    getCapabilities();
   
    /* Set the JVMTI callback functions  (do this only once)*/
    set_callbacks(JNI_TRUE);
  
    /* Create basic locks */
    gdata->dump_lock          = createRawMonitor("HPROF dump lock");
    gdata->data_access_lock   = createRawMonitor("HPROF data access lock");
    gdata->callbackLock       = createRawMonitor("HPROF callback lock");
    gdata->callbackBlock      = createRawMonitor("HPROF callback block");
    gdata->object_free_lock   = createRawMonitor("HPROF object free lock");
    gdata->gc_finish_lock     = createRawMonitor("HPROF gc_finish lock");
    
    /* Set Onload events mode. */
    setup_event_mode(JNI_TRUE, JVMTI_ENABLE);
    
    LOG2("Agent_OnLoad", "JVMTI capabilities, callbacks and initial notifications setup");
    
    /* Used in VM_DEATH to wait for callbacks to complete */
    gdata->jvm_initializing 		= JNI_FALSE;
    gdata->jvm_initialized 		= JNI_FALSE;
    gdata->vm_death_callback_active 	= JNI_FALSE;
    gdata->active_callbacks         	= 0;

    /* Write the header information */
    io_setup();

    /* We sample the start time now so that the time increments can be
     *    placed in the various heap dump segments in micro seconds.
     */
    gdata->micro_sec_ticks = md_get_microsecs();

    /* Load java_crw_demo library and find function "java_crw_demo" */
    if ( gdata->bci ) {

	/* Load the library or get the handle to it */
	gdata->java_crw_demo_library = load_library("java_crw_demo"); 
	
	{ /* "java_crw_demo" */
	    static char *symbols[]  = JAVA_CRW_DEMO_SYMBOLS;
	    gdata->java_crw_demo_function = 
	           lookup_library_symbol(gdata->java_crw_demo_library, 
			      symbols, (int)(sizeof(symbols)/sizeof(char*)));
	}
	{ /* "java_crw_demo_classname" */
	    static char *symbols[] = JAVA_CRW_DEMO_CLASSNAME_SYMBOLS;
	    gdata->java_crw_demo_classname_function = 
	           lookup_library_symbol(gdata->java_crw_demo_library, 
			      symbols, (int)(sizeof(symbols)/sizeof(char*)));
        }
    }
    
    return JNI_OK;
}

JNIEXPORT void JNICALL 
Agent_OnUnload(JavaVM *vm)
{
    Stack *stack;
    
    LOG("Agent_OnUnload");
    
    gdata->isLoaded = JNI_FALSE;
    
    stack = gdata->object_free_stack;
    gdata->object_free_stack = NULL;
    if ( stack != NULL ) {
	stack_term(stack);
    }
    
    io_cleanup();
    loader_cleanup();
    tls_cleanup();
    monitor_cleanup();
    trace_cleanup();
    site_cleanup();
    object_cleanup();
    frame_cleanup();
    class_cleanup();
    string_cleanup();
 
    /* Deallocate any memory in gdata */
    if ( gdata->net_hostname != NULL ) {
	HPROF_FREE(gdata->net_hostname);
    }
    if ( gdata->utf8_output_filename != NULL ) {
        HPROF_FREE(gdata->utf8_output_filename);
    }
    if ( gdata->output_filename != NULL ) {
        HPROF_FREE(gdata->output_filename);
    }
    if ( gdata->heapfilename != NULL ) {
        HPROF_FREE(gdata->heapfilename);
    }
    if ( gdata->checkfilename != NULL ) {
        HPROF_FREE(gdata->checkfilename);
    }
    if ( gdata->options != NULL ) {
	HPROF_FREE(gdata->options);
    }

    /* Verify all allocated memory has been taken care of. */
    malloc_police();

    /* Cleanup is hard to do when other threads might still be running
     *  so we skip destroying some raw monitors which still might be in use
     *  and we skip disposal of the jvmtiEnv* which might still be needed.
     *  Only raw monitors that could be held by other threads are left
     *  alone. So we explicitly do NOT do this:
     *      destroyRawMonitor(gdata->callbackLock);
     *      destroyRawMonitor(gdata->callbackBlock);
     *      destroyRawMonitor(gdata->gc_finish_lock);
     *      destroyRawMonitor(gdata->object_free_lock);
     *      destroyRawMonitor(gdata->listener_loop_lock);
     *      destroyRawMonitor(gdata->cpu_loop_lock);
     *      disposeEnvironment();
     *      gdata->jvmti = NULL;
     */

    /* Destroy basic locks */
    destroyRawMonitor(gdata->dump_lock);
    gdata->dump_lock = NULL;
    destroyRawMonitor(gdata->data_access_lock);
    gdata->data_access_lock = NULL;
    if ( gdata->cpu_sample_lock != NULL ) {
	destroyRawMonitor(gdata->cpu_sample_lock);
	gdata->cpu_sample_lock = NULL;
    }
    #ifdef DEBUG
	destroyRawMonitor(gdata->debug_malloc_lock);
	gdata->debug_malloc_lock = NULL;
    #endif

    /* Unload java_crw_demo library */
    if ( gdata->bci && gdata->java_crw_demo_library != NULL ) {
	md_unload_library(gdata->java_crw_demo_library);
	gdata->java_crw_demo_library = NULL;
    }

    /* You would think you could clear out gdata and set it to NULL, but
     *   turns out that isn't a good idea.  Some of the threads could be
     *   blocked inside the CALLBACK*() macros, where they got blocked up
     *   waiting for the VM_DEATH callback to complete. They only have
     *   some raw monitor actions to do, but they need access to gdata to do it.
     *   So do not do this:
     *       (void)memset(gdata, 0, sizeof(GlobalData));
     *       gdata = NULL;
     */
}