blob: bdd1f498b2a16917a93a11aa1dc79834f0f3d193 [file] [log] [blame]
/*
* Copyright (c) 2003, 2018, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "jvmti.h"
#include "jni_tools.h"
#include "agent_common.h"
#include "JVMTITools.h"
#ifdef __cplusplus
extern "C" {
#endif
#ifndef JNI_ENV_ARG
#ifdef __cplusplus
#define JNI_ENV_ARG(x, y) y
#define JNI_ENV_PTR(x) x
#else
#define JNI_ENV_ARG(x,y) x, y
#define JNI_ENV_PTR(x) (*x)
#endif
#endif
#define PASSED 0
#define STATUS_FAILED 2
static jvmtiEnv *jvmti = NULL;
static jint result = PASSED;
static jboolean printdump = JNI_FALSE;
static jvmtiCapabilities jvmti_caps;
static jint dummy_user_data = 0;
static jboolean user_data_error_flag = JNI_FALSE;
#define HEAP_ROOT_REF_KIND_BASE 100
#define MISSED_REF_KIND_BASE 300
typedef enum {
rthread,
rclass,
rother,
rmark
} refKind;
struct _refLink;
typedef struct _myTag {
refKind kind;
const struct _myTag* class_tag;
jlong size;
jlong sequence;
jboolean visited;
const char* name;
struct _refLink *ref;
} MyTag;
typedef struct _refLink {
MyTag* tag;
int reference_kind;
struct _refLink *next;
} refLink;
static MyTag *fakeRoot = NULL;
static MyTag *missed = NULL;
static void breakpoint() {
printf("Continuing from BREAKPOINT\n");
}
static MyTag *newTag(refKind kind,
const MyTag* class_tag,
jlong size,
const char* name) {
static jlong seq_num = 0;
MyTag* new_tag = NULL;
new_tag = malloc(sizeof(MyTag));
if (NULL == new_tag) {
printf("Error (newTag malloc): failed\n");
result = STATUS_FAILED;
}
new_tag->kind = kind;
new_tag->class_tag = class_tag;
new_tag->size = size;
new_tag->sequence = ++seq_num;
new_tag->visited = JNI_FALSE;
new_tag->name = name;
new_tag->ref = NULL;
return new_tag;
}
static void setTag(JNIEnv *env,
jobject obj,
refKind kind,
const char* name) {
MyTag *new_tag = NULL;
MyTag *class_tag = NULL;
jvmtiError err;
jlong size = 0;
jclass obj_class = NULL;
jlong haba = 0;
err = (*jvmti)->GetObjectSize(jvmti, obj, &size);
if (err != JVMTI_ERROR_NONE) {
printf("Error (ObjectSize): %s (%d)\n", TranslateError(err), err);
result = STATUS_FAILED;
}
obj_class = (*env)->GetObjectClass(env, obj);
err = (*jvmti)->GetTag(jvmti, obj_class, &haba);
class_tag = (MyTag*)(intptr_t)haba;
if (err != JVMTI_ERROR_NONE) {
printf("Error (GetTag): %s (%d)\n", TranslateError(err), err);
result = STATUS_FAILED;
}
if (class_tag != NULL && class_tag->kind != rclass) {
printf("Error class tag which is not a class\n");
result = STATUS_FAILED;
}
new_tag = newTag(kind, class_tag, size, name);
err = (*jvmti)->SetTag(jvmti, obj, (intptr_t)new_tag);
if (err != JVMTI_ERROR_NONE) {
printf("Error (SetTag): %s (%d)\n", TranslateError(err), err);
result = STATUS_FAILED;
}
}
static void addRef(MyTag *from, int reference_kind, MyTag *to) {
refLink *new_ref;
new_ref = malloc(sizeof(refLink));
if (NULL == new_ref) {
printf("Error (addRef malloc): failed\n");
result = STATUS_FAILED;
}
new_ref->tag = to;
new_ref->reference_kind = reference_kind;
new_ref->next = from->ref;
from->ref = new_ref;
}
static const char* reference_label(refLink *link) {
int reference_kind = link->reference_kind;
const char *name = "<font color=\"red\">**unknown**</font>";
switch (reference_kind) {
case JVMTI_REFERENCE_CLASS:
name = "<font color=\"black\">class</font>";
break;
case JVMTI_REFERENCE_FIELD:
name = "<font color=\"black\">field</font>";
break;
case JVMTI_REFERENCE_ARRAY_ELEMENT:
name = "<font color=\"green\">array_element</font>";
break;
case JVMTI_REFERENCE_CLASS_LOADER:
name = "<font color=\"purple\">class_loader</font>";
break;
case JVMTI_REFERENCE_SIGNERS:
name = "<font color=\"purple\">signers</font>";
break;
case JVMTI_REFERENCE_PROTECTION_DOMAIN:
name = "<font color=\"purple\">protection_domain</font>";
break;
case JVMTI_REFERENCE_INTERFACE:
name = "<font color=\"purple\">interface</font>";
break;
case JVMTI_REFERENCE_STATIC_FIELD:
name = "<font color=\"black\">static_field</font>";
break;
case HEAP_ROOT_REF_KIND_BASE+JVMTI_HEAP_ROOT_JNI_GLOBAL:
name = "<font color=\"orange\">root::jni_global</font>";
break;
case HEAP_ROOT_REF_KIND_BASE+JVMTI_HEAP_ROOT_SYSTEM_CLASS:
name = "<font color=\"orange\">root::system_class</font>";
break;
case HEAP_ROOT_REF_KIND_BASE+JVMTI_HEAP_ROOT_MONITOR:
name = "<font color=\"orange\">root::monitor</font>";
break;
case HEAP_ROOT_REF_KIND_BASE+JVMTI_HEAP_ROOT_STACK_LOCAL:
name = "<font color=\"orange\">root::local_var</font>";
break;
case HEAP_ROOT_REF_KIND_BASE+JVMTI_HEAP_ROOT_JNI_LOCAL:
name = "<font color=\"orange\">root::jni_local</font>";
break;
case HEAP_ROOT_REF_KIND_BASE+JVMTI_HEAP_ROOT_THREAD:
name = "<font color=\"orange\">root::thread</font>";
break;
case HEAP_ROOT_REF_KIND_BASE+JVMTI_HEAP_ROOT_OTHER:
name = "<font color=\"orange\">root::other</font>";
break;
default:
printf("Error: Unexpected reference kind %d\n", reference_kind);
result = STATUS_FAILED;
break;
}
return name;
}
static void walk(MyTag* tag, jint depth, const char* ref_label) {
static const char* const spaces = ". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ";
static const int len = 86;
const char *indent = spaces + (len - 2 * depth);
const MyTag* const ctag = tag->class_tag;
const char* const cname = ctag != NULL ? ctag->name : "";
printf("%s", indent);
if (tag->visited) {
printf("<a href=\"#%"LL"d\">", tag->sequence);
} else {
printf("<a name=\"%"LL"d\">", tag->sequence);
}
if (tag->name) {
printf("<b>%s(%s)</b>", cname, tag->name);
} else {
printf("%s(%"LL"d)", cname, tag->sequence);
}
printf("</a> -- ");
printf("%s\n", ref_label);
if (!tag->visited) {
refLink *ref;
tag->visited = JNI_TRUE;
for (ref = tag->ref; ref; ref = ref->next) {
walk(ref->tag, depth + 1, reference_label(ref));
}
}
}
#ifdef STATIC_BUILD
JNIEXPORT jint JNICALL Agent_OnLoad_heapref(JavaVM *jvm, char *options, void *reserved) {
return Agent_Initialize(jvm, options, reserved);
}
JNIEXPORT jint JNICALL Agent_OnAttach_heapref(JavaVM *jvm, char *options, void *reserved) {
return Agent_Initialize(jvm, options, reserved);
}
JNIEXPORT jint JNI_OnLoad_heapref(JavaVM *jvm, char *options, void *reserved) {
return JNI_VERSION_1_8;
}
#endif
jint Agent_Initialize(JavaVM *jvm, char *options, void *reserved) {
jint res;
jvmtiError err;
if (options != NULL && strcmp(options, "printdump") == 0) {
printdump = JNI_TRUE;
}
res = JNI_ENV_PTR(jvm)->GetEnv(JNI_ENV_ARG(jvm, (void **) &jvmti),
JVMTI_VERSION_1_1);
if (res != JNI_OK || jvmti == NULL) {
printf("Wrong result of a valid call to GetEnv!\n");
return JNI_ERR;
}
memset((void*)&jvmti_caps, 0, sizeof(jvmtiCapabilities));
jvmti_caps.can_tag_objects = 1;
err = (*jvmti)->AddCapabilities(jvmti, &jvmti_caps);
if (err != JVMTI_ERROR_NONE) {
printf("Error (AddCapabilities): %s (%d)\n", TranslateError(err), err);
return JNI_ERR;
}
return JNI_OK;
}
jvmtiIterationControl JNICALL
heapMarkCallback(jlong class_tag, jlong size, jlong* tag_ptr, void* user_data) {
const MyTag* const tag = newTag(rmark, (const MyTag*)(intptr_t)class_tag, size, NULL);
*tag_ptr = (intptr_t)tag;
if (user_data != &dummy_user_data && user_data_error_flag == JNI_FALSE) {
user_data_error_flag = JNI_TRUE;
printf("Error (heapMarkCallback): unexpected value of user_data\n");
result = STATUS_FAILED;
}
return JVMTI_ITERATION_CONTINUE;
}
jvmtiIterationControl JNICALL
heapRootCallback(jvmtiHeapRootKind root_kind,
jlong class_tag, jlong size,
jlong* tag_ptr, void* user_data) {
refKind kind = rother;
if (0 == *tag_ptr) {
/* new tag */
MyTag* tag = newTag(kind, (MyTag*)(intptr_t)class_tag, size, NULL);
addRef(fakeRoot, HEAP_ROOT_REF_KIND_BASE+root_kind, tag);
*tag_ptr = (intptr_t)tag;
} else {
/* existing tag */
addRef(fakeRoot, HEAP_ROOT_REF_KIND_BASE+root_kind, (MyTag*)(intptr_t)*tag_ptr);
}
if (user_data != &dummy_user_data && user_data_error_flag == JNI_FALSE) {
user_data_error_flag = JNI_TRUE;
printf("Error (heapRootCallback): unexpected value of user_data\n");
result = STATUS_FAILED;
}
return JVMTI_ITERATION_CONTINUE;
}
jvmtiIterationControl JNICALL
stackReferenceCallback(jvmtiHeapRootKind root_kind,
jlong class_tag, jlong size,
jlong* tag_ptr, jlong thread_tag,
jint depth, jmethodID method,
jint slot, void* user_data) {
refKind kind = rother;
if (0 == *tag_ptr) {
/* new tag */
MyTag* tag = newTag(kind, (MyTag*)(intptr_t)class_tag, size, NULL);
addRef(fakeRoot, HEAP_ROOT_REF_KIND_BASE+root_kind, tag);
*tag_ptr = (intptr_t)tag;
} else {
/* existing tag */
addRef(fakeRoot, HEAP_ROOT_REF_KIND_BASE+root_kind, (MyTag*)(intptr_t)*tag_ptr);
}
if (user_data != &dummy_user_data && user_data_error_flag == JNI_FALSE) {
user_data_error_flag = JNI_TRUE;
printf("Error (stackReferenceCallback): unexpected value of user_data\n");
result = STATUS_FAILED;
}
return JVMTI_ITERATION_CONTINUE;
}
jvmtiIterationControl JNICALL
objectReferenceCallback(jvmtiObjectReferenceKind reference_kind,
jlong class_tag, jlong size,
jlong* tag_ptr, jlong referrer_tag,
jint referrer_index, void* user_data) {
refKind kind = rother;
MyTag* referrer = NULL;
if (0 == referrer_tag) {
referrer = missed;
} else {
referrer = (MyTag *)(intptr_t)referrer_tag;
}
if (0 == *tag_ptr) {
/* new tag */
MyTag* tag = newTag(kind, (MyTag*)(intptr_t)class_tag, size, NULL);
addRef(referrer, reference_kind, tag);
*tag_ptr = (intptr_t) tag;
} else {
/* existing tag */
MyTag* tag = (MyTag*)(intptr_t)*tag_ptr;
addRef(referrer, reference_kind, tag);
}
if (user_data != &dummy_user_data && user_data_error_flag == JNI_FALSE) {
user_data_error_flag = JNI_TRUE;
printf("Error (objectReferenceCallback): unexpected value of user_data\n");
result = STATUS_FAILED;
}
return JVMTI_ITERATION_CONTINUE;
}
JNIEXPORT jint JNICALL
Java_nsk_jvmti_unit_heapref_check(JNIEnv *env, jclass cls) {
jvmtiError err;
jclass *classes;
jint classCount = 0;
jthread *threads;
jint threadCount = 0;
jint i;
if (jvmti == NULL) {
printf("JVMTI client was not properly loaded!\n");
return STATUS_FAILED;
}
fakeRoot = newTag(rother, (const MyTag *)NULL, 0, "FAKE_ROOT");
missed = newTag(rother, (const MyTag *)NULL, 0, "MISSED");
if ((*env)->PushLocalFrame(env, 500) != 0) {
printf("Error (PushLocalFrame): failed\n");
result = STATUS_FAILED;
}
err = (*jvmti)->GetLoadedClasses(jvmti, &classCount, &classes);
if (err != JVMTI_ERROR_NONE) {
printf("Error (GetLoadedClasses): %s (%d)\n", TranslateError(err), err);
result = STATUS_FAILED;
}
for (i = 0; i < classCount; ++i) {
char *classSig;
jclass k = classes[i];
err = (*jvmti)->GetClassSignature(jvmti, k, &classSig, NULL);
if (err != JVMTI_ERROR_NONE) {
printf("Error (getClassSignature): %s (%d)\n", TranslateError(err), err);
result = STATUS_FAILED;
} else {
char* slash = strrchr(classSig, '/');
const size_t len = strlen(classSig);
if (classSig[len-1] == ';') {
classSig[len-1] = 0;
}
if (*classSig == 'L' && slash != NULL) {
classSig = slash + 1;
}
setTag(env, k, rclass, (const char*)classSig);
}
}
err = (*jvmti)->GetAllThreads(jvmti, &threadCount, &threads);
if (err != JVMTI_ERROR_NONE) {
printf("Error (GetAllThreads): %s (%d)\n", TranslateError(err), err);
result = STATUS_FAILED;
}
for (i = 0; i < threadCount; ++i) {
jvmtiThreadInfo info;
jthread t = threads[i];
err = (*jvmti)->GetThreadInfo(jvmti, t, &info);
if (err != JVMTI_ERROR_NONE) {
printf("Error (GetThreadInfo): %s (%d)\n", TranslateError(err), err);
result = STATUS_FAILED;
} else {
setTag(env, t, rthread, (const char*)info.name);
}
}
(*env)->PopLocalFrame(env, NULL);
user_data_error_flag = JNI_FALSE;
err = (*jvmti)->IterateOverHeap(jvmti,
JVMTI_HEAP_OBJECT_UNTAGGED,
heapMarkCallback,
&dummy_user_data);
if (err != JVMTI_ERROR_NONE) {
printf("Error (IterateOverHeap): %s (%d)\n", TranslateError(err), err);
result = STATUS_FAILED;
}
user_data_error_flag = JNI_FALSE;
err = (*jvmti)->IterateOverReachableObjects(jvmti,
heapRootCallback,
stackReferenceCallback,
objectReferenceCallback,
&dummy_user_data);
if (err != JVMTI_ERROR_NONE) {
printf("Error (IterateOverReachableObjects): %s (%d)\n", TranslateError(err), err);
result = STATUS_FAILED;
}
if (printdump == JNI_TRUE) {
printf("<html><head><title>Heap Dump</title></head><body><pre>\n");
walk(fakeRoot, 0, "roots");
printf("\n------------------- MISSED ------------------\n\n");
walk(missed, 0, "missed");
printf("</pre></body></html>\n");
}
return result;
}
#ifdef __cplusplus
}
#endif