From becd6addfe1cb8326cc8ada5828c83f201124797 Mon Sep 17 00:00:00 2001 From: Andreas Gampe Date: Wed, 22 Feb 2017 19:20:37 -0800 Subject: [PATCH] ART: Add primitive array reporting Add support for array_primitive_value_callback. Bug: 31385354 Test: m test-art-host-run-test-906-iterate-heap Test: m test-art-host-run-test-913-heaps Change-Id: I2d615295bc5ad3d2579170d152e5778f56bf12cb --- runtime/openjdkjvmti/ti_heap.cc | 95 +++++++++++++++++++++++++++---- test/906-iterate-heap/expected.txt | 18 +++++- test/906-iterate-heap/iterate_heap.cc | 102 ++++++++++++++++++++++++++++++++-- test/906-iterate-heap/src/Main.java | 41 ++++++++++++++ test/913-heaps/expected.txt | 11 +++- test/913-heaps/heaps.cc | 93 ++++++++++++++++++++++++++++++- test/913-heaps/src/Main.java | 49 ++++++++++++++++ 7 files changed, 389 insertions(+), 20 deletions(-) diff --git a/runtime/openjdkjvmti/ti_heap.cc b/runtime/openjdkjvmti/ti_heap.cc index 7efeea7bb..2fbc12bb3 100644 --- a/runtime/openjdkjvmti/ti_heap.cc +++ b/runtime/openjdkjvmti/ti_heap.cc @@ -31,6 +31,7 @@ #include "object_callbacks.h" #include "object_tagging.h" #include "obj_ptr-inl.h" +#include "primitive.h" #include "runtime.h" #include "scoped_thread_state_change-inl.h" #include "thread-inl.h" @@ -89,6 +90,75 @@ jint ReportString(art::ObjPtr obj, return 0; } +// Report the contents of a primitive array, if a callback is set. +jint ReportPrimitiveArray(art::ObjPtr obj, + jvmtiEnv* env, + ObjectTagTable* tag_table, + const jvmtiHeapCallbacks* cb, + const void* user_data) REQUIRES_SHARED(art::Locks::mutator_lock_) { + if (UNLIKELY(cb->array_primitive_value_callback != nullptr) && + obj->IsArrayInstance() && + !obj->IsObjectArray()) { + art::ObjPtr array = obj->AsArray(); + int32_t array_length = array->GetLength(); + size_t component_size = array->GetClass()->GetComponentSize(); + art::Primitive::Type art_prim_type = array->GetClass()->GetComponentType()->GetPrimitiveType(); + jvmtiPrimitiveType prim_type = + static_cast(art::Primitive::Descriptor(art_prim_type)[0]); + DCHECK(prim_type == JVMTI_PRIMITIVE_TYPE_BOOLEAN || + prim_type == JVMTI_PRIMITIVE_TYPE_BYTE || + prim_type == JVMTI_PRIMITIVE_TYPE_CHAR || + prim_type == JVMTI_PRIMITIVE_TYPE_SHORT || + prim_type == JVMTI_PRIMITIVE_TYPE_INT || + prim_type == JVMTI_PRIMITIVE_TYPE_LONG || + prim_type == JVMTI_PRIMITIVE_TYPE_FLOAT || + prim_type == JVMTI_PRIMITIVE_TYPE_DOUBLE); + + const jlong class_tag = tag_table->GetTagOrZero(obj->GetClass()); + jlong array_tag = tag_table->GetTagOrZero(obj.Ptr()); + const jlong saved_array_tag = array_tag; + + jint result; + if (array_length == 0) { + result = cb->array_primitive_value_callback(class_tag, + obj->SizeOf(), + &array_tag, + 0, + prim_type, + nullptr, + const_cast(user_data)); + } else { + jvmtiError alloc_error; + JvmtiUniquePtr data = AllocJvmtiUniquePtr(env, + array_length * component_size, + &alloc_error); + if (data == nullptr) { + // TODO: Not really sure what to do here. Should we abort the iteration and go all the way + // back? For now just warn. + LOG(WARNING) << "Unable to allocate buffer for array reporting! Silently dropping value."; + return 0; + } + + memcpy(data.get(), array->GetRawData(component_size, 0), array_length * component_size); + + result = cb->array_primitive_value_callback(class_tag, + obj->SizeOf(), + &array_tag, + array_length, + prim_type, + data.get(), + const_cast(user_data)); + } + + if (array_tag != saved_array_tag) { + tag_table->Set(obj.Ptr(), array_tag); + } + + return result; + } + return 0; +} + } // namespace struct IterateThroughHeapData { @@ -202,7 +272,15 @@ static void IterateThroughHeapObjectCallback(art::mirror::Object* obj, void* arg ithd->stop_reports = (string_ret & JVMTI_VISIT_ABORT) != 0; } - // TODO Implement array primitive callback. + if (!ithd->stop_reports) { + jint array_ret = ReportPrimitiveArray(obj, + ithd->env, + ithd->heap_util->GetTags(), + ithd->callbacks, + ithd->user_data); + ithd->stop_reports = (array_ret & JVMTI_VISIT_ABORT) != 0; + } + // TODO Implement primitive field callback. } @@ -215,11 +293,6 @@ jvmtiError HeapUtil::IterateThroughHeap(jvmtiEnv* env, return ERR(NULL_POINTER); } - if (callbacks->array_primitive_value_callback != nullptr) { - // TODO: Implement. - return ERR(NOT_IMPLEMENTED); - } - art::Thread* self = art::Thread::Current(); art::ScopedObjectAccess soa(self); // Now we know we have the shared lock. @@ -569,6 +642,11 @@ class FollowReferencesHelper FINAL { } } } + } else { + if (!stop_reports_) { + jint array_ret = ReportPrimitiveArray(array, env, tag_table_, callbacks_, user_data_); + stop_reports_ = (array_ret & JVMTI_VISIT_ABORT) != 0; + } } } @@ -753,11 +831,6 @@ jvmtiError HeapUtil::FollowReferences(jvmtiEnv* env, return ERR(NULL_POINTER); } - if (callbacks->array_primitive_value_callback != nullptr) { - // TODO: Implement. - return ERR(NOT_IMPLEMENTED); - } - art::Thread* self = art::Thread::Current(); art::gc::Heap* heap = art::Runtime::Current()->GetHeap(); diff --git a/test/906-iterate-heap/expected.txt b/test/906-iterate-heap/expected.txt index d636286af..c8228d677 100644 --- a/test/906-iterate-heap/expected.txt +++ b/test/906-iterate-heap/expected.txt @@ -1,4 +1,20 @@ [{tag=1, class-tag=0, size=8, length=-1}, {tag=2, class-tag=100, size=8, length=-1}, {tag=3, class-tag=100, size=8, length=-1}, {tag=4, class-tag=0, size=32, length=5}, {tag=5, class-tag=0, size=40, length=-1}, {tag=100, class-tag=0, size=, length=-1}] [{tag=11, class-tag=0, size=8, length=-1}, {tag=12, class-tag=110, size=8, length=-1}, {tag=13, class-tag=110, size=8, length=-1}, {tag=14, class-tag=0, size=32, length=5}, {tag=15, class-tag=0, size=40, length=-1}, {tag=110, class-tag=0, size=, length=-1}] -15@0 ( 40, 'Hello World') +15@0 (40, 'Hello World') 16 +1@0 (14, 2xZ '0001') +2 +1@0 (15, 3xB '010203') +2 +1@0 (16, 2xC '41005a00') +2 +1@0 (18, 3xS '010002000300') +2 +1@0 (24, 3xI '010000000200000003000000') +2 +1@0 (20, 2xF '000000000000803f') +2 +1@0 (40, 3xJ '010000000000000002000000000000000300000000000000') +2 +1@0 (32, 2xD '0000000000000000000000000000f03f') +2 diff --git a/test/906-iterate-heap/iterate_heap.cc b/test/906-iterate-heap/iterate_heap.cc index 0a0c68a2e..890220ee8 100644 --- a/test/906-iterate-heap/iterate_heap.cc +++ b/test/906-iterate-heap/iterate_heap.cc @@ -16,8 +16,10 @@ #include "inttypes.h" +#include #include #include +#include #include #include @@ -181,11 +183,11 @@ extern "C" JNIEXPORT jstring JNICALL Java_Main_iterateThroughHeapString( struct FindStringCallbacks { explicit FindStringCallbacks(jlong t) : tag_to_find(t) {} - static jint JNICALL HeapIterationCallback(jlong class_tag ATTRIBUTE_UNUSED, - jlong size ATTRIBUTE_UNUSED, - jlong* tag_ptr ATTRIBUTE_UNUSED, - jint length ATTRIBUTE_UNUSED, - void* user_data ATTRIBUTE_UNUSED) { + static jint JNICALL HeapIterationCallback(jlong class_tag ATTRIBUTE_UNUSED, + jlong size ATTRIBUTE_UNUSED, + jlong* tag_ptr ATTRIBUTE_UNUSED, + jint length ATTRIBUTE_UNUSED, + void* user_data ATTRIBUTE_UNUSED) { return 0; } @@ -204,7 +206,7 @@ extern "C" JNIEXPORT jstring JNICALL Java_Main_iterateThroughHeapString( if (!p->data.empty()) { p->data += "\n"; } - p->data += android::base::StringPrintf("%" PRId64 "@%" PRId64 " (% " PRId64 ", '%s')", + p->data += android::base::StringPrintf("%" PRId64 "@%" PRId64 " (%" PRId64 ", '%s')", *tag_ptr, class_tag, size, @@ -232,5 +234,93 @@ extern "C" JNIEXPORT jstring JNICALL Java_Main_iterateThroughHeapString( return env->NewStringUTF(fsc.data.c_str()); } +extern "C" JNIEXPORT jstring JNICALL Java_Main_iterateThroughHeapPrimitiveArray( + JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jlong tag) { + struct FindArrayCallbacks { + explicit FindArrayCallbacks(jlong t) : tag_to_find(t) {} + + static jint JNICALL HeapIterationCallback(jlong class_tag ATTRIBUTE_UNUSED, + jlong size ATTRIBUTE_UNUSED, + jlong* tag_ptr ATTRIBUTE_UNUSED, + jint length ATTRIBUTE_UNUSED, + void* user_data ATTRIBUTE_UNUSED) { + return 0; + } + + static jint JNICALL ArrayValueCallback(jlong class_tag, + jlong size, + jlong* tag_ptr, + jint element_count, + jvmtiPrimitiveType element_type, + const void* elements, + void* user_data) { + FindArrayCallbacks* p = reinterpret_cast(user_data); + if (*tag_ptr == p->tag_to_find) { + std::ostringstream oss; + oss << *tag_ptr + << '@' + << class_tag + << " (" + << size + << ", " + << element_count + << "x" + << static_cast(element_type) + << " '"; + size_t element_size; + switch (element_type) { + case JVMTI_PRIMITIVE_TYPE_BOOLEAN: + case JVMTI_PRIMITIVE_TYPE_BYTE: + element_size = 1; + break; + case JVMTI_PRIMITIVE_TYPE_CHAR: + case JVMTI_PRIMITIVE_TYPE_SHORT: + element_size = 2; + break; + case JVMTI_PRIMITIVE_TYPE_INT: + case JVMTI_PRIMITIVE_TYPE_FLOAT: + element_size = 4; + break; + case JVMTI_PRIMITIVE_TYPE_LONG: + case JVMTI_PRIMITIVE_TYPE_DOUBLE: + element_size = 8; + break; + default: + LOG(FATAL) << "Unknown type " << static_cast(element_type); + UNREACHABLE(); + } + const uint8_t* data = reinterpret_cast(elements); + for (size_t i = 0; i != element_size * element_count; ++i) { + oss << android::base::StringPrintf("%02x", data[i]); + } + oss << "')"; + + if (!p->data.empty()) { + p->data += "\n"; + } + p->data += oss.str(); + // Update the tag to test whether that works. + *tag_ptr = *tag_ptr + 1; + } + return 0; + } + + std::string data; + const jlong tag_to_find; + }; + + jvmtiHeapCallbacks callbacks; + memset(&callbacks, 0, sizeof(jvmtiHeapCallbacks)); + callbacks.heap_iteration_callback = FindArrayCallbacks::HeapIterationCallback; + callbacks.array_primitive_value_callback = FindArrayCallbacks::ArrayValueCallback; + + FindArrayCallbacks fac(tag); + jvmtiError ret = jvmti_env->IterateThroughHeap(0, nullptr, &callbacks, &fac); + if (JvmtiErrorToException(env, ret)) { + return nullptr; + } + return env->NewStringUTF(fac.data.c_str()); +} + } // namespace Test906IterateHeap } // namespace art diff --git a/test/906-iterate-heap/src/Main.java b/test/906-iterate-heap/src/Main.java index 755d23c0d..d4998865b 100644 --- a/test/906-iterate-heap/src/Main.java +++ b/test/906-iterate-heap/src/Main.java @@ -79,6 +79,46 @@ public class Main { System.out.println(iterateThroughHeapString(getTag(s))); System.out.println(getTag(s)); + + boolean[] zArray = new boolean[] { false, true }; + setTag(zArray, 1); + System.out.println(iterateThroughHeapPrimitiveArray(getTag(zArray))); + System.out.println(getTag(zArray)); + + byte[] bArray = new byte[] { 1, 2, 3 }; + setTag(bArray, 1); + System.out.println(iterateThroughHeapPrimitiveArray(getTag(bArray))); + System.out.println(getTag(bArray)); + + char[] cArray = new char[] { 'A', 'Z' }; + setTag(cArray, 1); + System.out.println(iterateThroughHeapPrimitiveArray(getTag(cArray))); + System.out.println(getTag(cArray)); + + short[] sArray = new short[] { 1, 2, 3 }; + setTag(sArray, 1); + System.out.println(iterateThroughHeapPrimitiveArray(getTag(sArray))); + System.out.println(getTag(sArray)); + + int[] iArray = new int[] { 1, 2, 3 }; + setTag(iArray, 1); + System.out.println(iterateThroughHeapPrimitiveArray(getTag(iArray))); + System.out.println(getTag(iArray)); + + float[] fArray = new float[] { 0.0f, 1.0f }; + setTag(fArray, 1); + System.out.println(iterateThroughHeapPrimitiveArray(getTag(fArray))); + System.out.println(getTag(fArray)); + + long[] lArray = new long[] { 1, 2, 3 }; + setTag(lArray, 1); + System.out.println(iterateThroughHeapPrimitiveArray(getTag(lArray))); + System.out.println(getTag(lArray)); + + double[] dArray = new double[] { 0.0, 1.0 }; + setTag(dArray, 1); + System.out.println(iterateThroughHeapPrimitiveArray(getTag(dArray))); + System.out.println(getTag(dArray)); } static class A { @@ -147,4 +187,5 @@ public class Main { private static native int iterateThroughHeapAdd(int heapFilter, Class klassFilter); private static native String iterateThroughHeapString(long tag); + private static native String iterateThroughHeapPrimitiveArray(long tag); } diff --git a/test/913-heaps/expected.txt b/test/913-heaps/expected.txt index 3125d2bd2..6432172e2 100644 --- a/test/913-heaps/expected.txt +++ b/test/913-heaps/expected.txt @@ -79,5 +79,14 @@ root@root --(thread)--> 3000@0 [size=132, length=-1] 5@1002 --(field@28)--> 1@1000 [size=16, length=-1] 6@1000 --(class)--> 1000@0 [size=123, length=-1] --- -[1@0 ( 40, 'HelloWorld')] +[1@0 (40, 'HelloWorld')] 2 +2@0 (15, 3xB '010203') +3@0 (16, 2xC '41005a00') +8@0 (32, 2xD '0000000000000000000000000000f03f') +6@0 (20, 2xF '000000000000803f') +5@0 (24, 3xI '010000000200000003000000') +7@0 (40, 3xJ '010000000000000002000000000000000300000000000000') +4@0 (18, 3xS '010002000300') +1@0 (14, 2xZ '0001') +23456789 diff --git a/test/913-heaps/heaps.cc b/test/913-heaps/heaps.cc index 0c2361a3b..1de1a695c 100644 --- a/test/913-heaps/heaps.cc +++ b/test/913-heaps/heaps.cc @@ -521,7 +521,7 @@ extern "C" JNIEXPORT jobjectArray JNICALL Java_Main_followReferencesString( std::unique_ptr mod_utf(new char[utf_byte_count + 1]); memset(mod_utf.get(), 0, utf_byte_count + 1); ConvertUtf16ToModifiedUtf8(mod_utf.get(), utf_byte_count, value, value_length); - p->data.push_back(android::base::StringPrintf("%" PRId64 "@%" PRId64 " (% " PRId64 ", '%s')", + p->data.push_back(android::base::StringPrintf("%" PRId64 "@%" PRId64 " (%" PRId64 ", '%s')", *tag_ptr, class_tag, size, @@ -555,5 +555,96 @@ extern "C" JNIEXPORT jobjectArray JNICALL Java_Main_followReferencesString( return retArray; } + +extern "C" JNIEXPORT jstring JNICALL Java_Main_followReferencesPrimitiveArray( + JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jobject initial_object) { + struct FindArrayCallbacks { + static jint JNICALL FollowReferencesCallback( + jvmtiHeapReferenceKind reference_kind ATTRIBUTE_UNUSED, + const jvmtiHeapReferenceInfo* reference_info ATTRIBUTE_UNUSED, + jlong class_tag ATTRIBUTE_UNUSED, + jlong referrer_class_tag ATTRIBUTE_UNUSED, + jlong size ATTRIBUTE_UNUSED, + jlong* tag_ptr ATTRIBUTE_UNUSED, + jlong* referrer_tag_ptr ATTRIBUTE_UNUSED, + jint length ATTRIBUTE_UNUSED, + void* user_data ATTRIBUTE_UNUSED) { + return JVMTI_VISIT_OBJECTS; // Continue visiting. + } + + static jint JNICALL ArrayValueCallback(jlong class_tag, + jlong size, + jlong* tag_ptr, + jint element_count, + jvmtiPrimitiveType element_type, + const void* elements, + void* user_data) { + FindArrayCallbacks* p = reinterpret_cast(user_data); + if (*tag_ptr != 0) { + std::ostringstream oss; + oss << *tag_ptr + << '@' + << class_tag + << " (" + << size + << ", " + << element_count + << "x" + << static_cast(element_type) + << " '"; + size_t element_size; + switch (element_type) { + case JVMTI_PRIMITIVE_TYPE_BOOLEAN: + case JVMTI_PRIMITIVE_TYPE_BYTE: + element_size = 1; + break; + case JVMTI_PRIMITIVE_TYPE_CHAR: + case JVMTI_PRIMITIVE_TYPE_SHORT: + element_size = 2; + break; + case JVMTI_PRIMITIVE_TYPE_INT: + case JVMTI_PRIMITIVE_TYPE_FLOAT: + element_size = 4; + break; + case JVMTI_PRIMITIVE_TYPE_LONG: + case JVMTI_PRIMITIVE_TYPE_DOUBLE: + element_size = 8; + break; + default: + LOG(FATAL) << "Unknown type " << static_cast(element_type); + UNREACHABLE(); + } + const uint8_t* data = reinterpret_cast(elements); + for (size_t i = 0; i != element_size * element_count; ++i) { + oss << android::base::StringPrintf("%02x", data[i]); + } + oss << "')"; + + if (!p->data.empty()) { + p->data += "\n"; + } + p->data += oss.str(); + // Update the tag to test whether that works. + *tag_ptr = *tag_ptr + 1; + } + return 0; + } + + std::string data; + }; + + jvmtiHeapCallbacks callbacks; + memset(&callbacks, 0, sizeof(jvmtiHeapCallbacks)); + callbacks.heap_reference_callback = FindArrayCallbacks::FollowReferencesCallback; + callbacks.array_primitive_value_callback = FindArrayCallbacks::ArrayValueCallback; + + FindArrayCallbacks fac; + jvmtiError ret = jvmti_env->FollowReferences(0, nullptr, initial_object, &callbacks, &fac); + if (JvmtiErrorToException(env, ret)) { + return nullptr; + } + return env->NewStringUTF(fac.data.c_str()); +} + } // namespace Test913Heaps } // namespace art diff --git a/test/913-heaps/src/Main.java b/test/913-heaps/src/Main.java index 440207264..2767d89b9 100644 --- a/test/913-heaps/src/Main.java +++ b/test/913-heaps/src/Main.java @@ -26,6 +26,7 @@ public class Main { new TestConfig().doFollowReferencesTest(); doStringTest(); + doPrimitiveArrayTest(); } public static void doTest() throws Exception { @@ -47,6 +48,53 @@ public class Main { System.out.println(getTag(str)); } + public static void doPrimitiveArrayTest() throws Exception { + final boolean[] zArray = new boolean[] { false, true }; + setTag(zArray, 1); + + final byte[] bArray = new byte[] { 1, 2, 3 }; + setTag(bArray, 2); + + final char[] cArray = new char[] { 'A', 'Z' }; + setTag(cArray, 3); + + final short[] sArray = new short[] { 1, 2, 3 }; + setTag(sArray, 4); + + final int[] iArray = new int[] { 1, 2, 3 }; + setTag(iArray, 5); + + final float[] fArray = new float[] { 0.0f, 1.0f }; + setTag(fArray, 6); + + final long[] lArray = new long[] { 1, 2, 3 }; + setTag(lArray, 7); + + final double[] dArray = new double[] { 0.0, 1.0 }; + setTag(dArray, 8); + + Object o = new Object() { + Object z = zArray; + Object b = bArray; + Object c = cArray; + Object s = sArray; + Object i = iArray; + Object f = fArray; + Object l = lArray; + Object d = dArray; + }; + + System.out.println(followReferencesPrimitiveArray(o)); + System.out.print(getTag(zArray)); + System.out.print(getTag(bArray)); + System.out.print(getTag(cArray)); + System.out.print(getTag(sArray)); + System.out.print(getTag(iArray)); + System.out.print(getTag(fArray)); + System.out.print(getTag(lArray)); + System.out.println(getTag(dArray)); + } + private static void run() { clearStats(); forceGarbageCollection(); @@ -424,4 +472,5 @@ public class Main { public static native String[] followReferences(int heapFilter, Class klassFilter, Object initialObject, int stopAfter, int followSet, Object jniRef); public static native String[] followReferencesString(Object initialObject); + public static native String followReferencesPrimitiveArray(Object initialObject); } -- 2.11.0