/*
 * @(#)hprof_util.c	1.59 10/03/23
 * 
 * Copyright (c) 2006, Oracle and/or its affiliates. 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 Oracle 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 MICROSYSTEMS, 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.
 */

/* General utility functions. */

/*
 * Wrappers over JVM, JNI, and JVMTI functions are placed here.
 *
 * All memory allocation and deallocation goes through jvmtiAllocate()
 *    and jvmtiDeallocate().
 *
 */


#include "hprof.h"

/* Macro to get JNI function pointer. */
#define JNI_FUNC_PTR(env,f) (*((*(env))->f))

/* Macro to get JVM function pointer. */
#define JVM_FUNC_PTR(env,f) (*((*(env))->f))

/* Macro to get JVMTI function pointer. */
#define JVMTI_FUNC_PTR(env,f) (*((*(env))->f))

/* ------------------------------------------------------------------- */
/* JVM functions */

JNIEnv *
getEnv(void)
{
    JNIEnv *env;
    jint    res;
    
    res = JVM_FUNC_PTR(gdata->jvm,GetEnv)
                     (gdata->jvm, (void **)&env, JNI_VERSION_1_2);
    if (res != JNI_OK) {
        char buf[256];

        (void)md_snprintf(buf, sizeof(buf),
                "Unable to access JNI Version 1.2 (0x%x),"
                " is your JDK a 5.0 or newer version?"
                " JNIEnv's GetEnv() returned %d",
               JNI_VERSION_1_2, res);
        buf[sizeof(buf)-1] = 0;
        HPROF_ERROR(JNI_FALSE, buf);
        error_exit_process(1); /* Kill entire process, no core dump */
    }
    return env;
}

/* ------------------------------------------------------------------- */
/* Memory Allocation */

void *
jvmtiAllocate(int size)
{
    jvmtiError error;
    unsigned char *ptr;
   
    HPROF_ASSERT(size>=0);
    ptr = NULL;
    if ( size == 0 ) {
        return ptr;
    }
    error = JVMTI_FUNC_PTR(gdata->jvmti,Allocate)
                (gdata->jvmti, (jlong)size, &ptr);
    if ( error != JVMTI_ERROR_NONE || ptr == NULL ) {
        HPROF_JVMTI_ERROR(error, "Cannot allocate jvmti memory");
    }
    return (void*)ptr;
}

void
jvmtiDeallocate(void *ptr)
{
    if ( ptr != NULL ) {
        jvmtiError error;

        error = JVMTI_FUNC_PTR(gdata->jvmti,Deallocate)
                    (gdata->jvmti, (unsigned char*)ptr);
        if ( error != JVMTI_ERROR_NONE ) {
            HPROF_JVMTI_ERROR(error, "Cannot deallocate jvmti memory");
        }
    }
}

#ifdef DEBUG

void *
hprof_debug_malloc(int size, char *file, int line)
{
    void *ptr;

    HPROF_ASSERT(size>0);
    
    rawMonitorEnter(gdata->debug_malloc_lock); {
        ptr = debug_malloc(size, file, line);
    } rawMonitorExit(gdata->debug_malloc_lock);
    
    if ( ptr == NULL ) {
        HPROF_ERROR(JNI_TRUE, "Cannot allocate malloc memory");
    }
    return ptr;
}

void
hprof_debug_free(void *ptr, char *file, int line)
{
    HPROF_ASSERT(ptr!=NULL);
    
    rawMonitorEnter(gdata->debug_malloc_lock); {
        (void)debug_free(ptr, file, line);
    } rawMonitorExit(gdata->debug_malloc_lock);
}

#endif

void *
hprof_malloc(int size)
{
    void *ptr;
    
    HPROF_ASSERT(size>0);
    ptr = malloc(size);
    if ( ptr == NULL ) {
        HPROF_ERROR(JNI_TRUE, "Cannot allocate malloc memory");
    }
    return ptr;
}

void
hprof_free(void *ptr)
{
    HPROF_ASSERT(ptr!=NULL);
    (void)free(ptr);
}

/* ------------------------------------------------------------------- */
/* JVMTI Version functions */

jint
jvmtiVersion(void)
{
    if (gdata->cachedJvmtiVersion == 0) {
        jvmtiError error;
        
        error = JVMTI_FUNC_PTR(gdata->jvmti,GetVersionNumber)
                        (gdata->jvmti, &(gdata->cachedJvmtiVersion));
        if (error != JVMTI_ERROR_NONE) {
            HPROF_JVMTI_ERROR(error, "Cannot get jvmti version number");
        }
    }
    return gdata->cachedJvmtiVersion;
}

static jint
jvmtiMajorVersion(void)
{
    return (jvmtiVersion() & JVMTI_VERSION_MASK_MAJOR)
                    >> JVMTI_VERSION_SHIFT_MAJOR;
}

static jint
jvmtiMinorVersion(void)
{
    return (jvmtiVersion() & JVMTI_VERSION_MASK_MINOR)
                    >> JVMTI_VERSION_SHIFT_MINOR;
}

static jint
jvmtiMicroVersion(void)
{
    return (jvmtiVersion() & JVMTI_VERSION_MASK_MICRO)
                    >> JVMTI_VERSION_SHIFT_MICRO;
}

/* Logic to determine JVMTI version compatibility */
static jboolean
compatible_versions(jint major_runtime,     jint minor_runtime,
                    jint major_compiletime, jint minor_compiletime)
{
    /* Runtime major version must match. */
    if ( major_runtime != major_compiletime ) {
        return JNI_FALSE;
    }
    /* Runtime minor version must be >= the version compiled with. */
    if ( minor_runtime < minor_compiletime ) {
        return JNI_FALSE;
    }
    /* Assumed compatible */
    return JNI_TRUE;
}

/* ------------------------------------------------------------------- */
/* JVMTI Raw Monitor support functions */

jrawMonitorID
createRawMonitor(const char *str)
{
    jvmtiError error;
    jrawMonitorID m;
    
    m = NULL;
    error = JVMTI_FUNC_PTR(gdata->jvmti,CreateRawMonitor)
                (gdata->jvmti, str, &m);
    if ( error != JVMTI_ERROR_NONE ) {
        HPROF_JVMTI_ERROR(error, "Cannot create raw monitor");
    }
    return m;
}

void
rawMonitorEnter(jrawMonitorID m)
{
    jvmtiError error;
        
    error = JVMTI_FUNC_PTR(gdata->jvmti,RawMonitorEnter)
                (gdata->jvmti, m);
    if ( error == JVMTI_ERROR_WRONG_PHASE ) {
        /* Treat this as ok, after agent shutdown CALLBACK code may call this */
        error = JVMTI_ERROR_NONE;
    }
    if ( error != JVMTI_ERROR_NONE ) {
        HPROF_JVMTI_ERROR(error, "Cannot enter with raw monitor");
    }
}

void
rawMonitorWait(jrawMonitorID m, jlong pause_time)
{
    jvmtiError error;
    
    error = JVMTI_FUNC_PTR(gdata->jvmti,RawMonitorWait)
                (gdata->jvmti, m, pause_time);
    if ( error != JVMTI_ERROR_NONE ) {
        HPROF_JVMTI_ERROR(error, "Cannot wait with raw monitor");
    }
}

void
rawMonitorNotifyAll(jrawMonitorID m)
{
    jvmtiError error;
    
    error = JVMTI_FUNC_PTR(gdata->jvmti,RawMonitorNotifyAll)
                (gdata->jvmti, m);
    if ( error != JVMTI_ERROR_NONE ) {
        HPROF_JVMTI_ERROR(error, "Cannot notify all with raw monitor");
    }
}

void
rawMonitorExit(jrawMonitorID m)
{
    jvmtiError error;

    error = JVMTI_FUNC_PTR(gdata->jvmti,RawMonitorExit)
                (gdata->jvmti, m);
    if ( error == JVMTI_ERROR_WRONG_PHASE ) {
        /* Treat this as ok, after agent shutdown CALLBACK code may call this */
        error = JVMTI_ERROR_NONE;
    }
    if ( error != JVMTI_ERROR_NONE ) {
        HPROF_JVMTI_ERROR(error, "Cannot exit with raw monitor");
    }
}

void
destroyRawMonitor(jrawMonitorID m)
{
    jvmtiError error;

    error = JVMTI_FUNC_PTR(gdata->jvmti,DestroyRawMonitor)
                (gdata->jvmti, m);
    if ( error == JVMTI_ERROR_WRONG_PHASE ) {
        /* Treat this as ok */
        error = JVMTI_ERROR_NONE;
    }
    if ( error != JVMTI_ERROR_NONE ) {
        HPROF_JVMTI_ERROR(error, "Cannot destroy raw monitor");
    }
}

/* ------------------------------------------------------------------- */
/* JVMTI Event enabling/disabilin */

void
setEventNotificationMode(jvmtiEventMode mode, jvmtiEvent event, jthread thread)
{
    jvmtiError error;
    
    error = JVMTI_FUNC_PTR(gdata->jvmti,SetEventNotificationMode)
                (gdata->jvmti, mode, event, thread);
    if ( error != JVMTI_ERROR_NONE ) {
        HPROF_JVMTI_ERROR(error, "Cannot set event notification");
    }
}

/* ---------------------------------------------------------------------- */
/* JNI Support Functions */

jobject
exceptionOccurred(JNIEnv *env)
{
    return JNI_FUNC_PTR(env,ExceptionOccurred)(env);
}

void
exceptionDescribe(JNIEnv *env)
{
    JNI_FUNC_PTR(env,ExceptionDescribe)(env);
}

void
exceptionClear(JNIEnv *env)
{
    JNI_FUNC_PTR(env,ExceptionClear)(env);
}

jobject
newGlobalReference(JNIEnv *env, jobject object)
{
    jobject gref;
    
    HPROF_ASSERT(env!=NULL);
    HPROF_ASSERT(object!=NULL);
    gref = JNI_FUNC_PTR(env,NewGlobalRef)(env, object);
    HPROF_ASSERT(gref!=NULL);
    return gref;
}

jobject
newWeakGlobalReference(JNIEnv *env, jobject object)
{
    jobject gref;
    
    HPROF_ASSERT(env!=NULL);
    HPROF_ASSERT(object!=NULL);
    gref = JNI_FUNC_PTR(env,NewWeakGlobalRef)(env, object);
    HPROF_ASSERT(gref!=NULL);
    return gref;
}

void
deleteGlobalReference(JNIEnv *env, jobject object)
{
    HPROF_ASSERT(env!=NULL);
    HPROF_ASSERT(object!=NULL);
    JNI_FUNC_PTR(env,DeleteGlobalRef)(env, object);
}

jobject
newLocalReference(JNIEnv *env, jobject object)
{
    jobject lref;

    HPROF_ASSERT(env!=NULL);
    HPROF_ASSERT(object!=NULL);
    lref = JNI_FUNC_PTR(env,NewLocalRef)(env, object);
    /* Possible for a non-null weak reference to return a NULL localref */
    return lref;
}

void
deleteLocalReference(JNIEnv *env, jobject object)
{
    HPROF_ASSERT(env!=NULL);
    HPROF_ASSERT(object!=NULL);
    JNI_FUNC_PTR(env,DeleteLocalRef)(env, object);
}

void
deleteWeakGlobalReference(JNIEnv *env, jobject object)
{
    HPROF_ASSERT(env!=NULL);
    HPROF_ASSERT(object!=NULL);
    JNI_FUNC_PTR(env,DeleteWeakGlobalRef)(env, object);
}

jclass
getObjectClass(JNIEnv *env, jobject object)
/* WARNING: Must be called inside WITH_LOCAL_REFS */
{
    jclass clazz;
    
    HPROF_ASSERT(env!=NULL);
    HPROF_ASSERT(object!=NULL);
    clazz = JNI_FUNC_PTR(env,GetObjectClass)(env, object);
    HPROF_ASSERT(clazz!=NULL);
    return clazz;
}

jclass
getSuperclass(JNIEnv *env, jclass klass)
/* WARNING: Must be called inside WITH_LOCAL_REFS */
{
    jclass super_klass;
    
    HPROF_ASSERT(env!=NULL);
    HPROF_ASSERT(klass!=NULL);
    super_klass = JNI_FUNC_PTR(env,GetSuperclass)(env, klass);
    return super_klass;
}

jmethodID
getStaticMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig)
{
    jmethodID method;
    
    HPROF_ASSERT(env!=NULL);
    HPROF_ASSERT(clazz!=NULL);
    HPROF_ASSERT(name!=NULL);
    HPROF_ASSERT(sig!=NULL);
    CHECK_EXCEPTIONS(env) {
        method = JNI_FUNC_PTR(env,GetStaticMethodID)(env, clazz, name, sig);
    } END_CHECK_EXCEPTIONS;
    HPROF_ASSERT(method!=NULL);
    return method;
}

jmethodID
getMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig)
{
    jmethodID method;
    jobject exception;
    
    HPROF_ASSERT(env!=NULL);
    HPROF_ASSERT(clazz!=NULL);
    HPROF_ASSERT(name!=NULL);
    HPROF_ASSERT(sig!=NULL);
    method = JNI_FUNC_PTR(env,GetMethodID)(env, clazz, name, sig);
    /* Might be a static method */
    exception = JNI_FUNC_PTR(env,ExceptionOccurred)(env);
    if ( exception != NULL ) {
        JNI_FUNC_PTR(env,ExceptionClear)(env);
        method = getStaticMethodID(env, clazz, name, sig);
    }
    HPROF_ASSERT(method!=NULL);
    return method;
}

jclass
findClass(JNIEnv *env, const char *name)
/* WARNING: Must be called inside WITH_LOCAL_REFS */
{
    jclass clazz;
    
    HPROF_ASSERT(env!=NULL);
    HPROF_ASSERT(name!=NULL);
    LOG2("FindClass", name);
    CHECK_EXCEPTIONS(env) {
        clazz = JNI_FUNC_PTR(env,FindClass)(env, name);
    } END_CHECK_EXCEPTIONS;
    HPROF_ASSERT(clazz!=NULL);
    return clazz;
}

jfieldID
getStaticFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig)
{
    jfieldID field;
    
    HPROF_ASSERT(env!=NULL);
    HPROF_ASSERT(clazz!=NULL);
    HPROF_ASSERT(name!=NULL);
    HPROF_ASSERT(sig!=NULL);
    CHECK_EXCEPTIONS(env) {
        field = JNI_FUNC_PTR(env,GetStaticFieldID)(env, clazz, name, sig);
    } END_CHECK_EXCEPTIONS;
    return field;
}

void
setStaticIntField(JNIEnv *env, jclass clazz, jfieldID field, jint value)
{
    HPROF_ASSERT(env!=NULL);
    HPROF_ASSERT(clazz!=NULL);
    HPROF_ASSERT(field!=NULL);
    CHECK_EXCEPTIONS(env) {
        JNI_FUNC_PTR(env,SetStaticIntField)(env, clazz, field, value);
    } END_CHECK_EXCEPTIONS;
}

static jobject
callStaticObjectMethod(JNIEnv *env, jclass klass, jmethodID method)
{
    jobject x;
    
    HPROF_ASSERT(env!=NULL);
    HPROF_ASSERT(klass!=NULL);
    HPROF_ASSERT(method!=NULL);
    CHECK_EXCEPTIONS(env) {
        x = JNI_FUNC_PTR(env,CallStaticObjectMethod)(env, klass, method);
    } END_CHECK_EXCEPTIONS;
    return x;
}

static jlong
callLongMethod(JNIEnv *env, jobject object, jmethodID method)
{
    jlong x;
    
    HPROF_ASSERT(env!=NULL);
    HPROF_ASSERT(object!=NULL);
    HPROF_ASSERT(method!=NULL);
    CHECK_EXCEPTIONS(env) {
        x = JNI_FUNC_PTR(env,CallLongMethod)(env, object, method);
    } END_CHECK_EXCEPTIONS;
    return x;
}

static void
callVoidMethod(JNIEnv *env, jobject object, jmethodID method, jboolean arg)
{
    HPROF_ASSERT(env!=NULL);
    HPROF_ASSERT(object!=NULL);
    HPROF_ASSERT(method!=NULL);
    CHECK_EXCEPTIONS(env) {
        JNI_FUNC_PTR(env,CallVoidMethod)(env, object, method, arg);
    } END_CHECK_EXCEPTIONS;
}

static jstring
newStringUTF(JNIEnv *env, const char *name)
/* WARNING: Must be called inside WITH_LOCAL_REFS */
{
    jstring string;
    
    HPROF_ASSERT(env!=NULL);
    HPROF_ASSERT(name!=NULL);
    CHECK_EXCEPTIONS(env) {
        string = JNI_FUNC_PTR(env,NewStringUTF)(env, name);
    } END_CHECK_EXCEPTIONS;
    HPROF_ASSERT(string!=NULL);
    return string;
}

static jobject
newThreadObject(JNIEnv *env, jclass clazz, jmethodID method, 
                jthreadGroup group, jstring name)
/* WARNING: Must be called inside WITH_LOCAL_REFS */
{
    jthread thread;
    
    HPROF_ASSERT(env!=NULL);
    HPROF_ASSERT(clazz!=NULL);
    HPROF_ASSERT(method!=NULL);
    CHECK_EXCEPTIONS(env) {
        thread = JNI_FUNC_PTR(env,NewObject)(env, clazz, method, group, name);
    } END_CHECK_EXCEPTIONS;
    HPROF_ASSERT(thread!=NULL);
    return thread;
}

jboolean
isSameObject(JNIEnv *env, jobject o1, jobject o2)
{
    HPROF_ASSERT(env!=NULL);
    if ( o1 == o2  || JNI_FUNC_PTR(env,IsSameObject)(env, o1, o2) ) {
        return JNI_TRUE;
    }
    return JNI_FALSE;
}

void 
pushLocalFrame(JNIEnv *env, jint capacity)
{
    HPROF_ASSERT(env!=NULL);
    CHECK_EXCEPTIONS(env) {
        jint ret;
        
        ret = JNI_FUNC_PTR(env,PushLocalFrame)(env, capacity);
        if ( ret != 0 ) {
            HPROF_ERROR(JNI_TRUE, "JNI PushLocalFrame returned non-zero");
        }
    } END_CHECK_EXCEPTIONS;
}

void 
popLocalFrame(JNIEnv *env, jobject result)
{
    jobject ret;
    
    HPROF_ASSERT(env!=NULL);
    ret = JNI_FUNC_PTR(env,PopLocalFrame)(env, result);
    if ( (result != NULL && ret == NULL) || (result == NULL && ret != NULL) ) {
        HPROF_ERROR(JNI_TRUE, "JNI PopLocalFrame returned wrong object");
    }
}

void
registerNatives(JNIEnv *env, jclass clazz, 
                        JNINativeMethod *methods, jint count)
{
    jint ret;
    
    HPROF_ASSERT(env!=NULL);
    HPROF_ASSERT(clazz!=NULL);
    HPROF_ASSERT(methods!=NULL);
    HPROF_ASSERT(count>0);
    ret = JNI_FUNC_PTR(env,RegisterNatives)(env, clazz, methods, count);
    if ( ret != 0 ) {
        HPROF_ERROR(JNI_TRUE, "JNI RegisterNatives returned non-zero");
    }
}

/* ---------------------------------------------------------------------- */
/* JVMTI Support Functions */

char *
getErrorName(jvmtiError error_number)
{
    char *error_name;
    
    error_name = NULL;
    (void)JVMTI_FUNC_PTR(gdata->jvmti,GetErrorName)
                        (gdata->jvmti, error_number, &error_name);
    return error_name;
}

jvmtiPhase
getPhase(void)
{
    jvmtiPhase phase;
    
    phase = 0;
    (void)JVMTI_FUNC_PTR(gdata->jvmti,GetPhase)(gdata->jvmti, &phase);
    return phase;
}

char *
phaseString(jvmtiPhase phase)
{
    switch ( phase ) {
        case JVMTI_PHASE_ONLOAD:
            return "onload";
        case JVMTI_PHASE_PRIMORDIAL:
            return "primordial";
        case JVMTI_PHASE_START:
            return "start";
        case JVMTI_PHASE_LIVE:
            return "live";
        case JVMTI_PHASE_DEAD:
            return "dead";
    }
    return "unknown";
}

void
disposeEnvironment(void)
{
    (void)JVMTI_FUNC_PTR(gdata->jvmti,DisposeEnvironment)
                        (gdata->jvmti);
}

jlong
getObjectSize(jobject object)
{
    jlong size;
    jvmtiError error;
    
    HPROF_ASSERT(object!=NULL);
    size = 0;
    error = JVMTI_FUNC_PTR(gdata->jvmti,GetObjectSize)
                        (gdata->jvmti, object, &size);
    if ( error != JVMTI_ERROR_NONE ) {
        HPROF_JVMTI_ERROR(error, "Cannot get object size");
    }
    return size;
}

static jboolean 
isInterface(jclass klass)
{
    jvmtiError error;
    jboolean   answer;
    
    HPROF_ASSERT(klass!=NULL);
    answer = JNI_FALSE;
    error = JVMTI_FUNC_PTR(gdata->jvmti,IsInterface)
                        (gdata->jvmti, klass, &answer);
    if ( error != JVMTI_ERROR_NONE ) {
        HPROF_JVMTI_ERROR(error, "Cannot call IsInterface");
    }
    return answer;
}

jint
getClassStatus(jclass klass)
{
    jvmtiError error;
    jint       status;
    
    HPROF_ASSERT(klass!=NULL);
    status = 0;
    error = JVMTI_FUNC_PTR(gdata->jvmti,GetClassStatus)
                        (gdata->jvmti, klass, &status);
    if ( error == JVMTI_ERROR_WRONG_PHASE ) {
        /* Treat this as ok */
        error = JVMTI_ERROR_NONE;
        status = 0;
    }
    if ( error != JVMTI_ERROR_NONE ) {
        HPROF_JVMTI_ERROR(error, "Cannot get class status");
    }
    return status;
}

jobject
getClassLoader(jclass klass)
/* WARNING: Must be called inside WITH_LOCAL_REFS */
{
    jvmtiError error;
    jobject    loader;
    
    HPROF_ASSERT(klass!=NULL);
    loader = NULL;
    error = JVMTI_FUNC_PTR(gdata->jvmti,GetClassLoader)
                        (gdata->jvmti, klass, &loader);
    if ( error != JVMTI_ERROR_NONE ) {
        HPROF_JVMTI_ERROR(error, "Cannot get class loader");
    }
    return loader;
}

jlong
getTag(jobject object)
{
    jlong tag;
    jvmtiError error;
    
    HPROF_ASSERT(object!=NULL);
    tag = 0;
    error = JVMTI_FUNC_PTR(gdata->jvmti,GetTag)
                        (gdata->jvmti, object, &tag);
    if ( error != JVMTI_ERROR_NONE ) {
        HPROF_JVMTI_ERROR(error, "Cannot get object tag");
    }
    return tag;
}

void
setTag(jobject object, jlong tag)
{
    jvmtiError error;
    
    HPROF_ASSERT(object!=NULL);
    error = JVMTI_FUNC_PTR(gdata->jvmti,SetTag)
                        (gdata->jvmti, object, tag);
    if ( error != JVMTI_ERROR_NONE ) {
        HPROF_JVMTI_ERROR(error, "Cannot set object tag");
    }
}

void      
getObjectMonitorUsage(jobject object, jvmtiMonitorUsage *uinfo)
{
    jvmtiError error;
    
    HPROF_ASSERT(object!=NULL);
    error = JVMTI_FUNC_PTR(gdata->jvmti,GetObjectMonitorUsage)
                        (gdata->jvmti, object, uinfo);
    if ( error != JVMTI_ERROR_NONE ) {
        HPROF_JVMTI_ERROR(error, "Cannot get monitor usage info");
    }
}

void
getOwnedMonitorInfo(jthread thread, jobject **ppobjects, jint *pcount)
/* WARNING: Must be called inside WITH_LOCAL_REFS */
{
    jvmtiError error;
   
    HPROF_ASSERT(thread!=NULL);
    HPROF_ASSERT(ppobjects!=NULL);
    HPROF_ASSERT(pcount!=NULL);
    *pcount = 0;
    *ppobjects = NULL;
    error = JVMTI_FUNC_PTR(gdata->jvmti,GetOwnedMonitorInfo)
                        (gdata->jvmti, thread, pcount, ppobjects);
    if ( error == JVMTI_ERROR_THREAD_NOT_ALIVE ) {
        *pcount = 0;
        error = JVMTI_ERROR_NONE;
    }
    if ( error != JVMTI_ERROR_NONE ) {
        HPROF_JVMTI_ERROR(error, "Cannot get thread owned monitor info");
    }
}

void      
getSystemProperty(const char *name, char **value)
{
    jvmtiError error;
    
    HPROF_ASSERT(name!=NULL);
    *value = NULL;
    error = JVMTI_FUNC_PTR(gdata->jvmti,GetSystemProperty)
                        (gdata->jvmti, name, value);
    if ( error != JVMTI_ERROR_NONE ) {
        HPROF_JVMTI_ERROR(error, "Cannot get system property");
    }
}

void 
getClassSignature(jclass klass, char** psignature, char **pgeneric_signature)
{
    jvmtiError error;
    char *generic_signature;
    
    HPROF_ASSERT(klass!=NULL);
    *psignature = NULL;
    error = JVMTI_FUNC_PTR(gdata->jvmti,GetClassSignature)
                        (gdata->jvmti, klass, psignature, &generic_signature);
    if ( error != JVMTI_ERROR_NONE ) {
        HPROF_JVMTI_ERROR(error, "Cannot get class signature");
    }
    if ( pgeneric_signature != NULL ) {
        *pgeneric_signature = generic_signature;
    } else {
        jvmtiDeallocate(generic_signature);
    }
}

void 
getSourceFileName(jclass klass, char** pname)
{
    jvmtiError error;
    
    HPROF_ASSERT(klass!=NULL);
    *pname = NULL;
    error = JVMTI_FUNC_PTR(gdata->jvmti,GetSourceFileName)
                        (gdata->jvmti, klass, pname);
    if ( error == JVMTI_ERROR_ABSENT_INFORMATION ) {
        error = JVMTI_ERROR_NONE;
        *pname = NULL;
    }
    if ( error != JVMTI_ERROR_NONE ) {
        HPROF_JVMTI_ERROR(error, "Cannot get source file name");
    }
}

static void 
getClassFields(jclass klass, jint* pn_fields, jfieldID** pfields)
{
    jvmtiError error;
    jint       status;
   
    HPROF_ASSERT(klass!=NULL);
    *pn_fields = 0;
    *pfields      = NULL;
    
    /* Get class status */
    status = getClassStatus(klass);
    
    /* Arrays have no fields */
    if ( status & JVMTI_CLASS_STATUS_ARRAY ) {
        return;
    }

    /* Primitives have no fields */
    if ( status & JVMTI_CLASS_STATUS_PRIMITIVE ) {
        return;
    }
   
    /* If the class is not prepared, we have a problem? */
    if ( !(status & JVMTI_CLASS_STATUS_PREPARED) ) {
        HPROF_ERROR(JNI_FALSE, "Class not prepared when needing fields");
        return;
    }

    /* Now try and get all the fields */
    error         = JVMTI_FUNC_PTR(gdata->jvmti,GetClassFields)
                        (gdata->jvmti, klass, pn_fields, pfields);
    if ( error != JVMTI_ERROR_NONE ) {
        HPROF_JVMTI_ERROR(error, "Cannot get class field list");
    }
}

static jint 
getFieldModifiers(jclass klass, jfieldID field)
{
    jvmtiError error;
    jint       modifiers;
    
    HPROF_ASSERT(klass!=NULL);
    HPROF_ASSERT(field!=NULL);
    modifiers = 0;
    error = JVMTI_FUNC_PTR(gdata->jvmti,GetFieldModifiers)
            (gdata->jvmti, klass, field, &modifiers);
    if ( error != JVMTI_ERROR_NONE ) {
        HPROF_JVMTI_ERROR(error, "Cannot get field modifiers");
    }
    return modifiers;
}

static void 
getFieldName(jclass klass, jfieldID field, char** pname, char** psignature,
                        char **pgeneric_signature)
{
    jvmtiError error;
    char *generic_signature;
    
    generic_signature = NULL;
    *pname = NULL;
    *psignature = NULL;
    error = JVMTI_FUNC_PTR(gdata->jvmti,GetFieldName)
            (gdata->jvmti, klass, field, pname, psignature, &generic_signature);
    if ( error != JVMTI_ERROR_NONE ) {
        HPROF_JVMTI_ERROR(error, "Cannot get field name");
    }
    if ( pgeneric_signature != NULL ) {
        *pgeneric_signature = generic_signature;
    } else {
        jvmtiDeallocate(generic_signature);
    }
}

static void      
getImplementedInterfaces(jclass klass, jint* pn_interfaces,
                        jclass** pinterfaces)
/* WARNING: Must be called inside WITH_LOCAL_REFS */
{
    jvmtiError error;
   
    *pn_interfaces = 0;
    *pinterfaces   = NULL;
    error          = JVMTI_FUNC_PTR(gdata->jvmti,GetImplementedInterfaces)
                        (gdata->jvmti, klass, pn_interfaces, pinterfaces);
    if ( error != JVMTI_ERROR_NONE ) {
        HPROF_JVMTI_ERROR(error, "Cannot get class interface list");
    }
}

static ClassIndex
get_cnum(JNIEnv *env, jclass klass)
/* WARNING: Must be called inside WITH_LOCAL_REFS */
{
    ClassIndex  cnum;
    LoaderIndex loader_index;
    char       *sig;
    jobject     loader;

    loader       = getClassLoader(klass);
    loader_index = loader_find_or_create(env, loader);
    getClassSignature(klass, &sig, NULL);
    cnum = class_find_or_create(sig, loader_index);
    jvmtiDeallocate(sig);
    (void)class_new_classref(env, cnum, klass);
    return cnum;
}

/* From primitive type, get signature letter */
char
primTypeToSigChar(jvmtiPrimitiveType primType)
{
    char sig_ch;

    sig_ch = 0;
    switch ( primType ) {
        case JVMTI_PRIMITIVE_TYPE_BYTE:
            sig_ch = JVM_SIGNATURE_BYTE;
            break;
        case JVMTI_PRIMITIVE_TYPE_CHAR:
            sig_ch = JVM_SIGNATURE_CHAR;
            break;
        case JVMTI_PRIMITIVE_TYPE_FLOAT:
            sig_ch = JVM_SIGNATURE_FLOAT;
            break;
        case JVMTI_PRIMITIVE_TYPE_DOUBLE:
            sig_ch = JVM_SIGNATURE_DOUBLE;
            break;
        case JVMTI_PRIMITIVE_TYPE_INT:
            sig_ch = JVM_SIGNATURE_INT;
            break;
        case JVMTI_PRIMITIVE_TYPE_LONG:
            sig_ch = JVM_SIGNATURE_LONG;
            break;
        case JVMTI_PRIMITIVE_TYPE_SHORT:
            sig_ch = JVM_SIGNATURE_SHORT;
            break;
        case JVMTI_PRIMITIVE_TYPE_BOOLEAN:
            sig_ch = JVM_SIGNATURE_BOOLEAN;
            break;
        default:
            sig_ch = 0;
            break;
    }
    return sig_ch;
}

/* From signature, get primitive type */
jvmtiPrimitiveType
sigToPrimType(char *sig)
{
    jvmtiPrimitiveType primType;
    
    primType = 0;
    if ( sig == NULL || sig[0] == 0 ) {
        return primType;
    }
    switch ( sig[0] ) {
        case JVM_SIGNATURE_BYTE:
            primType =  JVMTI_PRIMITIVE_TYPE_BYTE;
            break;
        case JVM_SIGNATURE_CHAR:
            primType =  JVMTI_PRIMITIVE_TYPE_CHAR;
            break;
        case JVM_SIGNATURE_FLOAT:
            primType =  JVMTI_PRIMITIVE_TYPE_FLOAT;
            break;
        case JVM_SIGNATURE_DOUBLE:
            primType =  JVMTI_PRIMITIVE_TYPE_DOUBLE;
            break;
        case JVM_SIGNATURE_INT:
            primType =  JVMTI_PRIMITIVE_TYPE_INT;
            break;
        case JVM_SIGNATURE_LONG:
            primType =  JVMTI_PRIMITIVE_TYPE_LONG;
            break;
        case JVM_SIGNATURE_SHORT:
            primType =  JVMTI_PRIMITIVE_TYPE_SHORT;
            break;
        case JVM_SIGNATURE_BOOLEAN:
            primType =  JVMTI_PRIMITIVE_TYPE_BOOLEAN;
            break;
    }
    return primType;
}

/* From signature, get primitive size */
int
sigToPrimSize(char *sig)
{
    unsigned size;
    
    size = 0;
    if ( sig == NULL || sig[0] == 0 ) {
        return size;
    }
    switch ( sig[0] ) {
        case JVM_SIGNATURE_BYTE:
        case JVM_SIGNATURE_BOOLEAN:
            size =  1;
            break;
        case JVM_SIGNATURE_CHAR:
        case JVM_SIGNATURE_SHORT:
            size =  2;
            break;
        case JVM_SIGNATURE_FLOAT:
        case JVM_SIGNATURE_INT:
            size =  4;
            break;
        case JVM_SIGNATURE_DOUBLE:
        case JVM_SIGNATURE_LONG:
            size =  8;
            break;
    }
    return size;
}

static void 
add_class_fields(JNIEnv *env, ClassIndex top_cnum, ClassIndex cnum, 
                jclass klass, Stack *field_list, Stack *class_list)
/* WARNING: Must be called inside WITH_LOCAL_REFS */
{
    jclass     *interfaces;
    jint        n_interfaces;
    jfieldID   *idlist;
    jint        n_fields;
    int         i;
    int         depth;
    int         skip_static_field_names;
    jint        status;

    HPROF_ASSERT(env!=NULL);
    HPROF_ASSERT(klass!=NULL);
    HPROF_ASSERT(field_list!=NULL);
    HPROF_ASSERT(class_list!=NULL);

    /* If not the initial class, we can skip the static fields (perf issue) */
    skip_static_field_names = (cnum != top_cnum);

    /* Get class status */
    status = getClassStatus(klass);
    
    /* Arrays have no fields */
    if ( status & JVMTI_CLASS_STATUS_ARRAY ) {
        return;
    }

    /* Primitives have no fields */
    if ( status & JVMTI_CLASS_STATUS_PRIMITIVE ) {
        return;
    }

    /* If the class is not prepared, we have a problem? */
    if ( !(status & JVMTI_CLASS_STATUS_PREPARED) ) {
        char *sig;
        
        getClassSignature(klass, &sig, NULL);
        debug_message("Class signature is: %s\n", sig);
        HPROF_ERROR(JNI_FALSE, "Class not prepared when needing all fields");
        jvmtiDeallocate(sig);
        return;
    }

    /* See if class already processed */
    depth = stack_depth(class_list);
    for ( i = depth-1 ; i >= 0 ; i-- ) {
        if ( isSameObject(env, klass, *(jclass*)stack_element(class_list, i)) ) {
            return;
        }
    }
    
    /* Class or Interface, do implemented interfaces recursively */
    getImplementedInterfaces(klass, &n_interfaces, &interfaces);
    for ( i = 0 ; i < n_interfaces ; i++ ) {
        add_class_fields(env, top_cnum,
                         get_cnum(env, interfaces[i]), interfaces[i], 
                         field_list, class_list);
    }
    jvmtiDeallocate(interfaces);
    
    /* Begin graph traversal, go up super chain recursively */
    if ( !isInterface(klass) ) {
        jclass super_klass;
        
        super_klass = getSuperclass(env, klass);
        if ( super_klass != NULL ) {
            add_class_fields(env, top_cnum,
                             get_cnum(env, super_klass), super_klass, 
                             field_list, class_list);
        }
    }
    

    /* Only now we add klass to list so we don't repeat it later */
    stack_push(class_list, &klass);

    /* Now actually add the fields for this klass */
    getClassFields(klass, &n_fields, &idlist);
    for ( i = 0 ; i < n_fields ; i++ ) {
        FieldInfo        finfo;
        static FieldInfo empty_finfo;

        finfo           = empty_finfo;
        finfo.cnum      = cnum;
        finfo.modifiers = getFieldModifiers(klass, idlist[i]);
        if ( ( finfo.modifiers & JVM_ACC_STATIC ) == 0 ||
             !skip_static_field_names ) {
            char *field_name;
            char *field_sig;
            
            getFieldName(klass, idlist[i], &field_name, &field_sig, NULL);
            finfo.name_index = string_find_or_create(field_name);
            finfo.sig_index  = string_find_or_create(field_sig);
            finfo.primType   = sigToPrimType(field_sig);
            finfo.primSize   = sigToPrimSize(field_sig);
            jvmtiDeallocate(field_name);
            jvmtiDeallocate(field_sig);
        }
        stack_push(field_list, &finfo);
    }
    jvmtiDeallocate(idlist);
}

void 
getAllClassFieldInfo(JNIEnv *env, jclass klass, 
                jint* pn_fields, FieldInfo** pfields)
{
    ClassIndex cnum;
    
    *pfields      = NULL;
    *pn_fields    = 0;
    
    WITH_LOCAL_REFS(env, 1) {
        Stack *class_list;
        Stack *field_list;
        int    nbytes;

        cnum          = get_cnum(env, klass);
        class_list    = stack_init( 16,  16, (int)sizeof(jclass));
        field_list    = stack_init(128, 128, (int)sizeof(FieldInfo));
        add_class_fields(env, cnum, cnum, klass, field_list, class_list);
        *pn_fields    = stack_depth(field_list);
        if ( (*pn_fields) > 0 ) {
            nbytes        = (*pn_fields) * (int)sizeof(FieldInfo);
            *pfields      = (FieldInfo*)HPROF_MALLOC(nbytes);
            (void)memcpy(*pfields, stack_element(field_list, 0), nbytes);
        }
        stack_term(field_list);
        stack_term(class_list);
    } END_WITH_LOCAL_REFS;
}

void 
getMethodClass(jmethodID method, jclass *pclazz)
/* WARNING: Must be called inside WITH_LOCAL_REFS */
{
    jvmtiError error;
    
    HPROF_ASSERT(method!=NULL);
    *pclazz = NULL;
    error = JVMTI_FUNC_PTR(gdata->jvmti,GetMethodDeclaringClass)
                (gdata->jvmti, method, pclazz);
    if ( error != JVMTI_ERROR_NONE ) {
        HPROF_JVMTI_ERROR(error, "Cannot get method class");
    }
}

jboolean 
isMethodNative(jmethodID method)
{
    jvmtiError error;
    jboolean   isNative;
    
    HPROF_ASSERT(method!=NULL);
    error = JVMTI_FUNC_PTR(gdata->jvmti,IsMethodNative)
                (gdata->jvmti, method, &isNative);
    if ( error != JVMTI_ERROR_NONE ) {
        HPROF_JVMTI_ERROR(error, "Cannot check is method native");
    }
    return isNative;
}

void 
getMethodName(jmethodID method, char** pname, char** psignature)
{
    jvmtiError error;
    char *generic_signature;
    
    HPROF_ASSERT(method!=NULL);
    generic_signature = NULL;
    *pname = NULL;
    *psignature = NULL;
    error = JVMTI_FUNC_PTR(gdata->jvmti,GetMethodName)
                (gdata->jvmti, method, pname, psignature, &generic_signature);
    if ( error != JVMTI_ERROR_NONE ) {
        HPROF_JVMTI_ERROR(error, "Cannot get method name");
    }
    jvmtiDeallocate(generic_signature);
}

void
getPotentialCapabilities(jvmtiCapabilities *pcapabilities)
{
    jvmtiError error;

    (void)memset(pcapabilities,0,sizeof(jvmtiCapabilities));
    error = JVMTI_FUNC_PTR(gdata->jvmti,GetPotentialCapabilities)
                (gdata->jvmti, pcapabilities);
    if (error != JVMTI_ERROR_NONE) {
        HPROF_ERROR(JNI_FALSE, "Unable to get potential JVMTI capabilities.");
        error_exit_process(1); /* Kill entire process, no core dump wanted */
    }
}

void
addCapabilities(jvmtiCapabilities *pcapabilities)
{
    jvmtiError error;

    error = JVMTI_FUNC_PTR(gdata->jvmti,AddCapabilities)
                (gdata->jvmti, pcapabilities);
    if (error != JVMTI_ERROR_NONE) {
        HPROF_ERROR(JNI_FALSE, "Unable to get necessary JVMTI capabilities.");
        error_exit_process(1); /* Kill entire process, no core dump wanted */
    }
}

void
setEventCallbacks(jvmtiEventCallbacks *pcallbacks)
{
    jvmtiError error;

    error = JVMTI_FUNC_PTR(gdata->jvmti,SetEventCallbacks)
                (gdata->jvmti, pcallbacks, (int)sizeof(jvmtiEventCallbacks));
    if (error != JVMTI_ERROR_NONE) {
        HPROF_JVMTI_ERROR(error, "Cannot set jvmti callbacks");
    }

}

void *
getThreadLocalStorage(jthread thread)
{
    jvmtiError error;
    void *ptr;
    
    HPROF_ASSERT(thread!=NULL);
    ptr = NULL;
    error = JVMTI_FUNC_PTR(gdata->jvmti,GetThreadLocalStorage)
                (gdata->jvmti, thread, &ptr);
    if ( error == JVMTI_ERROR_WRONG_PHASE ) {
        /* Treat this as ok */
        error = JVMTI_ERROR_NONE;
        ptr = NULL;
    }
    if ( error != JVMTI_ERROR_NONE ) {
        HPROF_JVMTI_ERROR(error, "Cannot get thread local storage");
    }
    return ptr;
}

void
setThreadLocalStorage(jthread thread, void *ptr)
{
    jvmtiError error;
    
    HPROF_ASSERT(thread!=NULL);
    error = JVMTI_FUNC_PTR(gdata->jvmti,SetThreadLocalStorage)
                (gdata->jvmti, thread, (const void *)ptr);
    if ( error == JVMTI_ERROR_WRONG_PHASE ) {
        /* Treat this as ok */
        error = JVMTI_ERROR_NONE;
    }
    if ( error != JVMTI_ERROR_NONE ) {
        HPROF_JVMTI_ERROR(error, "Cannot set thread local storage");
    }
}

void
getThreadState(jthread thread, jint *threadState)
{
    jvmtiError error;

    HPROF_ASSERT(thread!=NULL);
    HPROF_ASSERT(threadState!=NULL);
    *threadState = 0;
    error = JVMTI_FUNC_PTR(gdata->jvmti,GetThreadState)
                (gdata->jvmti, thread, threadState);
    if ( error != JVMTI_ERROR_NONE ) {
        HPROF_JVMTI_ERROR(error, "Cannot get thread state");
    }
}

void
getThreadInfo(jthread thread, jvmtiThreadInfo *info)
/* WARNING: Must be called inside WITH_LOCAL_REFS */
{
    jvmtiError error;

    HPROF_ASSERT(thread!=NULL);
    HPROF_ASSERT(info!=NULL);
    (void)memset((void*)info, 0, sizeof(jvmtiThreadInfo));
    error = JVMTI_FUNC_PTR(gdata->jvmti,GetThreadInfo)
                (gdata->jvmti, thread, info);
    if ( error != JVMTI_ERROR_NONE ) {
        HPROF_JVMTI_ERROR(error, "Cannot get thread info");
    }
}

void
getThreadGroupInfo(jthreadGroup thread_group, jvmtiThreadGroupInfo *info)
/* WARNING: Must be called inside WITH_LOCAL_REFS */
{
    jvmtiError error;

    HPROF_ASSERT(info!=NULL);
    (void)memset((void*)info, 0, sizeof(jvmtiThreadGroupInfo));
    error = JVMTI_FUNC_PTR(gdata->jvmti,GetThreadGroupInfo)
                (gdata->jvmti, thread_group, info);
    if ( error != JVMTI_ERROR_NONE ) {
        HPROF_JVMTI_ERROR(error, "Cannot get thread group info");
    }
}

void
getLoadedClasses(jclass **ppclasses, jint *pcount)
/* WARNING: Must be called inside WITH_LOCAL_REFS */
{
    jvmtiError error;
    
    *ppclasses = NULL;
    *pcount = 0;
    error = JVMTI_FUNC_PTR(gdata->jvmti,GetLoadedClasses)
                (gdata->jvmti, pcount, ppclasses);
    if ( error != JVMTI_ERROR_NONE ) {
        HPROF_JVMTI_ERROR(error, "Cannot get all loaded class list");
    }
}

static void
getLineNumberTable(jmethodID method, jvmtiLineNumberEntry **ppentries,
                jint *pcount)
{
    jvmtiError error;
    
    HPROF_ASSERT(method!=NULL);
    *ppentries = NULL;
    *pcount    = 0;
    error = JVMTI_FUNC_PTR(gdata->jvmti,GetLineNumberTable)
                (gdata->jvmti, method, pcount, ppentries);
    if ( error == JVMTI_ERROR_ABSENT_INFORMATION ) {
        error = JVMTI_ERROR_NONE;
        *ppentries = NULL;
        *pcount    = 0;
    }
    if ( error != JVMTI_ERROR_NONE ) {
        HPROF_JVMTI_ERROR(error, "Cannot get source line numbers");
    }
}

static jint
map_loc2line(jlocation location, jvmtiLineNumberEntry *table, jint count)
{
    jint line_number;
    int i;
    int start;
    int half;

    HPROF_ASSERT(location>=0);
    HPROF_ASSERT(count>=0);

    line_number = -1;
    if ( count == 0 ) {
        return line_number;
    }
    
    /* Do a binary search */
    half = count >> 1;
    start = 0;
    while ( half > 0 ) {
        jlocation start_location;

        start_location = table[start + half].start_location;
        if ( location > start_location ) {
            start = start + half;
        } else if ( location == start_location ) {
            start = start + half;
            break;
        }
        half = half >> 1;
    }

    HPROF_ASSERT(start < count);

    /* Now start the table search */
    for ( i = start ; i < count ; i++ ) {
        if ( location < table[i].start_location ) {
            HPROF_ASSERT( ((int)location) < ((int)table[i].start_location) );
            break;
        }
        line_number = table[i].line_number;
    }
    HPROF_ASSERT(line_number > 0);
    return line_number;
}

jint
getLineNumber(jmethodID method, jlocation location)
{
    jvmtiLineNumberEntry *line_table;
    jint                  line_count;
    jint                  lineno;
    
    HPROF_ASSERT(method!=NULL);
    if ( location < 0 ) {
        HPROF_ASSERT(location > -4);
        return (jint)location;
    }
    lineno = -1;

    getLineNumberTable(method, &line_table, &line_count);
    lineno = map_loc2line(location, line_table, line_count);
    jvmtiDeallocate(line_table);
    
    return lineno;
}

jlong
getMaxMemory(JNIEnv *env)
{
    jlong max;
    
    HPROF_ASSERT(env!=NULL);
    
    max = (jlong)0;
    WITH_LOCAL_REFS(env, 1) {
        jclass          clazz;
        jmethodID       getRuntime;
        jobject         runtime;
        jmethodID       maxMemory;
        
        clazz      = findClass(env, "java/lang/Runtime");
        getRuntime = getStaticMethodID(env, clazz, "getRuntime", 
                                       "()Ljava/lang/Runtime;");
        runtime    = callStaticObjectMethod(env, clazz, getRuntime);
        maxMemory  = getMethodID(env, clazz, "maxMemory", "()J");
        max        = callLongMethod(env, runtime, maxMemory);
    } END_WITH_LOCAL_REFS;
    return max;    
}

void
createAgentThread(JNIEnv *env, const char *name, jvmtiStartFunction func)
{
    jvmtiError          error;
    
    HPROF_ASSERT(name!=NULL);
    HPROF_ASSERT(func!=NULL);
    
    WITH_LOCAL_REFS(env, 1) {
        jclass          clazz;
        jmethodID       threadConstructor;
        jmethodID       threadSetDaemon;
        jthread         thread;
        jstring         nameString;
        jthreadGroup    systemThreadGroup;
        jthreadGroup *  groups;
        jint            groupCount;
        
        thread                  = NULL;
        systemThreadGroup       = NULL;
        groups                  = NULL;
        clazz                   = class_get_class(env, gdata->thread_cnum);
        HPROF_ASSERT(clazz!=NULL);
        threadConstructor       = getMethodID(env, clazz, "<init>", 
                        "(Ljava/lang/ThreadGroup;Ljava/lang/String;)V");
        threadSetDaemon         = getMethodID(env, clazz, "setDaemon", 
                        "(Z)V");
        
        error = JVMTI_FUNC_PTR(gdata->jvmti,GetTopThreadGroups)
                    (gdata->jvmti, &groupCount, &groups);
        if ( error == JVMTI_ERROR_NONE ) {
            if ( groupCount > 0 ) {
                systemThreadGroup = groups[0];
            }
            jvmtiDeallocate(groups);
            
            nameString  = newStringUTF(env, name);
            HPROF_ASSERT(nameString!=NULL);
            thread      = newThreadObject(env, clazz, threadConstructor, 
                                        systemThreadGroup, nameString);
            HPROF_ASSERT(thread!=NULL);
            callVoidMethod(env, thread, threadSetDaemon, JNI_TRUE);
           
            error = JVMTI_FUNC_PTR(gdata->jvmti,RunAgentThread)
                (gdata->jvmti, thread, func, NULL, JVMTI_THREAD_MAX_PRIORITY);
        
            /* After the thread is running... */

            /* Make sure the TLS table has this thread as an agent thread */
            tls_agent_thread(env, thread);
        }
    } END_WITH_LOCAL_REFS;
    
    if ( error != JVMTI_ERROR_NONE ) {
        HPROF_JVMTI_ERROR(error, "Cannot create agent thread");
    }
}

jlong
getThreadCpuTime(jthread thread)
{
    jvmtiError error;
    jlong cpuTime;
    
    HPROF_ASSERT(thread!=NULL);
    cpuTime = -1;
    error = JVMTI_FUNC_PTR(gdata->jvmti,GetThreadCpuTime)
                (gdata->jvmti, thread, &cpuTime);
    if ( error != JVMTI_ERROR_NONE ) {
        HPROF_JVMTI_ERROR(error, "Cannot get cpu time");
    }
    return cpuTime;
}

/* Get frame count */
void 
getFrameCount(jthread thread, jint *pcount)
{
    jvmtiError error;

    HPROF_ASSERT(thread!=NULL);
    HPROF_ASSERT(pcount!=NULL);
    *pcount = 0;
    error = JVMTI_FUNC_PTR(gdata->jvmti,GetFrameCount)
            (gdata->jvmti, thread, pcount);
    if ( error != JVMTI_ERROR_NONE ) {
        *pcount = 0;
    }
}

/* Get call trace */
void 
getStackTrace(jthread thread, jvmtiFrameInfo *pframes, jint depth, jint *pcount)
{
    jvmtiError error;

    HPROF_ASSERT(thread!=NULL);
    HPROF_ASSERT(pframes!=NULL);
    HPROF_ASSERT(depth >= 0);
    HPROF_ASSERT(pcount!=NULL);
    *pcount = 0;
    error = JVMTI_FUNC_PTR(gdata->jvmti,GetStackTrace)
            (gdata->jvmti, thread, 0, depth, pframes, pcount);
    if ( error != JVMTI_ERROR_NONE ) {
        *pcount = 0;
    }
}

void      
getThreadListStackTraces(jint count, jthread *threads, 
                        jint depth, jvmtiStackInfo **stack_info)
{
    jvmtiError error;

    HPROF_ASSERT(threads!=NULL);
    HPROF_ASSERT(stack_info!=NULL);
    HPROF_ASSERT(depth >= 0);
    HPROF_ASSERT(count > 0);
    *stack_info = NULL;
    error = JVMTI_FUNC_PTR(gdata->jvmti,GetThreadListStackTraces)
            (gdata->jvmti, count, threads, depth, stack_info);
    if ( error != JVMTI_ERROR_NONE ) {
        HPROF_JVMTI_ERROR(error, "Cannot get thread list stack info");
    }
}

void      
followReferences(jvmtiHeapCallbacks *pHeapCallbacks, void *user_data)
{
    jvmtiError error;

    error = JVMTI_FUNC_PTR(gdata->jvmti,FollowReferences)
            (gdata->jvmti, 0, NULL, NULL, pHeapCallbacks, user_data);
    if ( error != JVMTI_ERROR_NONE ) {
        HPROF_JVMTI_ERROR(error, "Cannot follow references");
    }
}

/* GC control */
void 
runGC(void)
{
    jvmtiError error;
    error = JVMTI_FUNC_PTR(gdata->jvmti,ForceGarbageCollection)
                (gdata->jvmti);
    if ( error != JVMTI_ERROR_NONE ) {
        HPROF_JVMTI_ERROR(error, "Cannot force garbage collection");
    }
}

/* ------------------------------------------------------------------- */
/* Getting the initial JVMTI environment */

void
getJvmti(void)
{
    jvmtiEnv         *jvmti = NULL;
    jint              res;
    jint              jvmtiCompileTimeMajorVersion;
    jint              jvmtiCompileTimeMinorVersion;
    jint              jvmtiCompileTimeMicroVersion;

    res = JVM_FUNC_PTR(gdata->jvm,GetEnv)
                (gdata->jvm, (void **)&jvmti, JVMTI_VERSION_1);
    if (res != JNI_OK) {
        char buf[256];

        (void)md_snprintf(buf, sizeof(buf),
                "Unable to access JVMTI Version 1 (0x%x),"
                " is your JDK a 5.0 or newer version?"
                " JNIEnv's GetEnv() returned %d",
               JVMTI_VERSION_1, res);
        buf[sizeof(buf)-1] = 0;
        HPROF_ERROR(JNI_FALSE, buf);
        error_exit_process(1); /* Kill entire process, no core dump */
    }
    gdata->jvmti = jvmti;

    /* 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;
    if ( !compatible_versions(jvmtiMajorVersion(), jvmtiMinorVersion(),
                jvmtiCompileTimeMajorVersion, jvmtiCompileTimeMinorVersion) ) {
        char buf[256];

        (void)md_snprintf(buf, sizeof(buf), 
               "This " AGENTNAME " 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);
        buf[sizeof(buf)-1] = 0;
        HPROF_ERROR(JNI_FALSE, buf);
        error_exit_process(1); /* Kill entire process, no core dump wanted */
    }
}