From: Andreas Gampe Date: Fri, 13 Jan 2017 17:21:42 +0000 (-0800) Subject: ART: Add GetFrameCount and GetFrameLocation X-Git-Url: http://git.osdn.net/view?a=commitdiff_plain;h=f6f3b5f115f712fe4238f6ac8c367399e0cd9c8b;p=android-x86%2Fart.git ART: Add GetFrameCount and GetFrameLocation Add support for GetFrameCount and GetFrameLocation. Add tests. Bug: 31684812 Test: m test-art-host-run-test-911-get-stack-trace Change-Id: I7656e243f614eb0ceb5fcd6841128119fad89968 --- diff --git a/runtime/openjdkjvmti/OpenjdkJvmTi.cc b/runtime/openjdkjvmti/OpenjdkJvmTi.cc index 4334ca350..03903841e 100644 --- a/runtime/openjdkjvmti/OpenjdkJvmTi.cc +++ b/runtime/openjdkjvmti/OpenjdkJvmTi.cc @@ -260,7 +260,7 @@ class JvmtiFunctions { } static jvmtiError GetFrameCount(jvmtiEnv* env, jthread thread, jint* count_ptr) { - return ERR(NOT_IMPLEMENTED); + return StackUtil::GetFrameCount(env, thread, count_ptr); } static jvmtiError PopFrame(jvmtiEnv* env, jthread thread) { @@ -272,7 +272,7 @@ class JvmtiFunctions { jint depth, jmethodID* method_ptr, jlocation* location_ptr) { - return ERR(NOT_IMPLEMENTED); + return StackUtil::GetFrameLocation(env, thread, depth, method_ptr, location_ptr); } static jvmtiError NotifyFramePop(jvmtiEnv* env, jthread thread, jint depth) { diff --git a/runtime/openjdkjvmti/ti_stack.cc b/runtime/openjdkjvmti/ti_stack.cc index 8bd8d09b5..4cf55a6a9 100644 --- a/runtime/openjdkjvmti/ti_stack.cc +++ b/runtime/openjdkjvmti/ti_stack.cc @@ -161,24 +161,43 @@ static jvmtiError TranslateFrameVector(const std::vector& frames return ERR(NONE); } +static jvmtiError GetThread(JNIEnv* env, jthread java_thread, art::Thread** thread) { + if (java_thread == nullptr) { + *thread = art::Thread::Current(); + if (*thread == nullptr) { + // GetStackTrace can only be run during the live phase, so the current thread should be + // attached and thus available. Getting a null for current means we're starting up or + // dying. + return ERR(WRONG_PHASE); + } + } else { + if (!env->IsInstanceOf(java_thread, art::WellKnownClasses::java_lang_Thread)) { + return ERR(INVALID_THREAD); + } + + // TODO: Need non-aborting call here, to return JVMTI_ERROR_INVALID_THREAD. + art::ScopedObjectAccess soa(art::Thread::Current()); + art::MutexLock mu(soa.Self(), *art::Locks::thread_list_lock_); + *thread = art::Thread::FromManagedThread(soa, java_thread); + if (*thread == nullptr) { + return ERR(THREAD_NOT_ALIVE); + } + } + return ERR(NONE); +} + jvmtiError StackUtil::GetStackTrace(jvmtiEnv* jvmti_env ATTRIBUTE_UNUSED, jthread java_thread, jint start_depth, jint max_frame_count, jvmtiFrameInfo* frame_buffer, jint* count_ptr) { - if (java_thread == nullptr) { - return ERR(INVALID_THREAD); - } - art::Thread* thread; - { - // TODO: Need non-aborting call here, to return JVMTI_ERROR_INVALID_THREAD. - art::ScopedObjectAccess soa(art::Thread::Current()); - art::MutexLock mu(soa.Self(), *art::Locks::thread_list_lock_); - thread = art::Thread::FromManagedThread(soa, java_thread); - DCHECK(thread != nullptr); + jvmtiError thread_error = GetThread(art::Thread::Current()->GetJniEnv(), java_thread, &thread); + if (thread_error != ERR(NONE)) { + return thread_error; } + DCHECK(thread != nullptr); art::ThreadState state = thread->GetState(); if (state == art::ThreadState::kStarting || @@ -563,4 +582,144 @@ jvmtiError StackUtil::GetThreadListStackTraces(jvmtiEnv* env, return ERR(NONE); } +// Walks up the stack counting Java frames. This is not StackVisitor::ComputeNumFrames, as +// runtime methods and transitions must not be counted. +struct GetFrameCountVisitor : public art::StackVisitor { + explicit GetFrameCountVisitor(art::Thread* thread) + : art::StackVisitor(thread, nullptr, art::StackVisitor::StackWalkKind::kIncludeInlinedFrames), + count(0) {} + + bool VisitFrame() REQUIRES_SHARED(art::Locks::mutator_lock_) { + art::ArtMethod* m = GetMethod(); + const bool do_count = !(m == nullptr || m->IsRuntimeMethod()); + if (do_count) { + count++; + } + return true; + } + + size_t count; +}; + +struct GetFrameCountClosure : public art::Closure { + public: + GetFrameCountClosure() : count(0) {} + + void Run(art::Thread* self) OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) { + GetFrameCountVisitor visitor(self); + visitor.WalkStack(false); + + count = visitor.count; + } + + size_t count; +}; + +jvmtiError StackUtil::GetFrameCount(jvmtiEnv* env ATTRIBUTE_UNUSED, + jthread java_thread, + jint* count_ptr) { + art::Thread* thread; + jvmtiError thread_error = GetThread(art::Thread::Current()->GetJniEnv(), java_thread, &thread); + if (thread_error != ERR(NONE)) { + return thread_error; + } + DCHECK(thread != nullptr); + + if (count_ptr == nullptr) { + return ERR(NULL_POINTER); + } + + GetFrameCountClosure closure; + thread->RequestSynchronousCheckpoint(&closure); + + *count_ptr = closure.count; + return ERR(NONE); +} + +// Walks up the stack 'n' callers, when used with Thread::WalkStack. +struct GetLocationVisitor : public art::StackVisitor { + GetLocationVisitor(art::Thread* thread, size_t n_in) + : art::StackVisitor(thread, nullptr, art::StackVisitor::StackWalkKind::kIncludeInlinedFrames), + n(n_in), + count(0), + caller(nullptr), + caller_dex_pc(0) {} + + bool VisitFrame() REQUIRES_SHARED(art::Locks::mutator_lock_) { + art::ArtMethod* m = GetMethod(); + const bool do_count = !(m == nullptr || m->IsRuntimeMethod()); + if (do_count) { + DCHECK(caller == nullptr); + if (count == n) { + caller = m; + caller_dex_pc = GetDexPc(false); + return false; + } + count++; + } + return true; + } + + const size_t n; + size_t count; + art::ArtMethod* caller; + uint32_t caller_dex_pc; +}; + +struct GetLocationClosure : public art::Closure { + public: + explicit GetLocationClosure(size_t n_in) : n(n_in), method(nullptr), dex_pc(0) {} + + void Run(art::Thread* self) OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) { + GetLocationVisitor visitor(self, n); + visitor.WalkStack(false); + + method = visitor.caller; + dex_pc = visitor.caller_dex_pc; + } + + const size_t n; + art::ArtMethod* method; + uint32_t dex_pc; +}; + +jvmtiError StackUtil::GetFrameLocation(jvmtiEnv* env ATTRIBUTE_UNUSED, + jthread java_thread, + jint depth, + jmethodID* method_ptr, + jlocation* location_ptr) { + art::Thread* thread; + jvmtiError thread_error = GetThread(art::Thread::Current()->GetJniEnv(), java_thread, &thread); + if (thread_error != ERR(NONE)) { + return thread_error; + } + DCHECK(thread != nullptr); + + if (depth < 0) { + return ERR(ILLEGAL_ARGUMENT); + } + if (method_ptr == nullptr || location_ptr == nullptr) { + return ERR(NULL_POINTER); + } + + GetLocationClosure closure(static_cast(depth)); + thread->RequestSynchronousCheckpoint(&closure); + + if (closure.method == nullptr) { + return ERR(NO_MORE_FRAMES); + } + + *method_ptr = art::jni::EncodeArtMethod(closure.method); + if (closure.method->IsNative()) { + *location_ptr = -1; + } else { + if (closure.dex_pc == art::DexFile::kDexNoIndex) { + return ERR(INTERNAL); + } + *location_ptr = static_cast(closure.dex_pc); + } + + return ERR(NONE); +} + } // namespace openjdkjvmti diff --git a/runtime/openjdkjvmti/ti_stack.h b/runtime/openjdkjvmti/ti_stack.h index a5b391c5d..6a593cfa9 100644 --- a/runtime/openjdkjvmti/ti_stack.h +++ b/runtime/openjdkjvmti/ti_stack.h @@ -47,6 +47,14 @@ class StackUtil { jint* thread_count_ptr) REQUIRES(!art::Locks::thread_list_lock_); + static jvmtiError GetFrameCount(jvmtiEnv* env, jthread thread, jint* count_ptr); + + static jvmtiError GetFrameLocation(jvmtiEnv* env, + jthread thread, + jint depth, + jmethodID* method_ptr, + jlocation* location_ptr); + static jvmtiError GetStackTrace(jvmtiEnv* env, jthread thread, jint start_depth, diff --git a/test/911-get-stack-trace/expected.txt b/test/911-get-stack-trace/expected.txt index 061101c02..dad08c9f9 100644 --- a/test/911-get-stack-trace/expected.txt +++ b/test/911-get-stack-trace/expected.txt @@ -773,4 +773,64 @@ main doTest ()V 101 54 main ([Ljava/lang/String;)V 38 37 + +################### +### Same thread ### +################### +4 +JVMTI_ERROR_ILLEGAL_ARGUMENT +[public static native java.lang.Object[] Frames.getFrameLocation(java.lang.Thread,int), ffffffff] +[public static void Frames.doTestSameThread(), 38] +[public static void Frames.doTest() throws java.lang.Exception, 0] +[public static void Main.main(java.lang.String[]) throws java.lang.Exception, 2e] +JVMTI_ERROR_NO_MORE_FRAMES + +################################ +### Other thread (suspended) ### +################################ +18 +JVMTI_ERROR_ILLEGAL_ARGUMENT +[public final native void java.lang.Object.wait() throws java.lang.InterruptedException, ffffffff] +[private static void Recurse.printOrWait(int,int,ControlData), 18] +[private static java.lang.Object Recurse.baz(int,int,int,ControlData), 2] +[private static long Recurse.bar(int,int,int,ControlData), 0] +[public static int Recurse.foo(int,int,int,ControlData), 0] +[private static java.lang.Object Recurse.baz(int,int,int,ControlData), 9] +[private static long Recurse.bar(int,int,int,ControlData), 0] +[public static int Recurse.foo(int,int,int,ControlData), 0] +[private static java.lang.Object Recurse.baz(int,int,int,ControlData), 9] +[private static long Recurse.bar(int,int,int,ControlData), 0] +[public static int Recurse.foo(int,int,int,ControlData), 0] +[private static java.lang.Object Recurse.baz(int,int,int,ControlData), 9] +[private static long Recurse.bar(int,int,int,ControlData), 0] +[public static int Recurse.foo(int,int,int,ControlData), 0] +[private static java.lang.Object Recurse.baz(int,int,int,ControlData), 9] +[private static long Recurse.bar(int,int,int,ControlData), 0] +[public static int Recurse.foo(int,int,int,ControlData), 0] +[public void Frames$1.run(), 4] +JVMTI_ERROR_NO_MORE_FRAMES + +########################### +### Other thread (live) ### +########################### +17 +JVMTI_ERROR_ILLEGAL_ARGUMENT +[private static void Recurse.printOrWait(int,int,ControlData), 2c] +[private static java.lang.Object Recurse.baz(int,int,int,ControlData), 2] +[private static long Recurse.bar(int,int,int,ControlData), 0] +[public static int Recurse.foo(int,int,int,ControlData), 0] +[private static java.lang.Object Recurse.baz(int,int,int,ControlData), 9] +[private static long Recurse.bar(int,int,int,ControlData), 0] +[public static int Recurse.foo(int,int,int,ControlData), 0] +[private static java.lang.Object Recurse.baz(int,int,int,ControlData), 9] +[private static long Recurse.bar(int,int,int,ControlData), 0] +[public static int Recurse.foo(int,int,int,ControlData), 0] +[private static java.lang.Object Recurse.baz(int,int,int,ControlData), 9] +[private static long Recurse.bar(int,int,int,ControlData), 0] +[public static int Recurse.foo(int,int,int,ControlData), 0] +[private static java.lang.Object Recurse.baz(int,int,int,ControlData), 9] +[private static long Recurse.bar(int,int,int,ControlData), 0] +[public static int Recurse.foo(int,int,int,ControlData), 0] +[public void Frames$2.run(), 4] +JVMTI_ERROR_NO_MORE_FRAMES Done diff --git a/test/911-get-stack-trace/src/Frames.java b/test/911-get-stack-trace/src/Frames.java new file mode 100644 index 000000000..a1a11c378 --- /dev/null +++ b/test/911-get-stack-trace/src/Frames.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.util.Arrays; + +public class Frames { + public static void doTest() throws Exception { + doTestSameThread(); + + System.out.println(); + + doTestOtherThreadWait(); + + System.out.println(); + + doTestOtherThreadBusyLoop(); + } + + public static void doTestSameThread() { + System.out.println("###################"); + System.out.println("### Same thread ###"); + System.out.println("###################"); + + Thread t = Thread.currentThread(); + + int count = getFrameCount(t); + System.out.println(count); + try { + System.out.println(Arrays.toString(getFrameLocation(t, -1))); + } catch (RuntimeException e) { + System.out.println(e.getMessage()); + } + for (int i = 0; i < count; i++) { + System.out.println(Arrays.toString(getFrameLocation(t, i))); + } + try { + System.out.println(Arrays.toString(getFrameLocation(t, count))); + } catch (RuntimeException e) { + System.out.println(e.getMessage()); + } + } + + public static void doTestOtherThreadWait() throws Exception { + System.out.println("################################"); + System.out.println("### Other thread (suspended) ###"); + System.out.println("################################"); + final ControlData data = new ControlData(); + data.waitFor = new Object(); + Thread t = new Thread() { + public void run() { + Recurse.foo(4, 0, 0, data); + } + }; + t.start(); + data.reached.await(); + Thread.yield(); + Thread.sleep(500); // A little bit of time... + + int count = getFrameCount(t); + System.out.println(count); + try { + System.out.println(Arrays.toString(getFrameLocation(t, -1))); + } catch (RuntimeException e) { + System.out.println(e.getMessage()); + } + for (int i = 0; i < count; i++) { + System.out.println(Arrays.toString(getFrameLocation(t, i))); + } + try { + System.out.println(Arrays.toString(getFrameLocation(t, count))); + } catch (RuntimeException e) { + System.out.println(e.getMessage()); + } + + // Let the thread make progress and die. + synchronized(data.waitFor) { + data.waitFor.notifyAll(); + } + t.join(); + } + + public static void doTestOtherThreadBusyLoop() throws Exception { + System.out.println("###########################"); + System.out.println("### Other thread (live) ###"); + System.out.println("###########################"); + final ControlData data = new ControlData(); + Thread t = new Thread() { + public void run() { + Recurse.foo(4, 0, 0, data); + } + }; + t.start(); + data.reached.await(); + Thread.yield(); + Thread.sleep(500); // A little bit of time... + + int count = getFrameCount(t); + System.out.println(count); + try { + System.out.println(Arrays.toString(getFrameLocation(t, -1))); + } catch (RuntimeException e) { + System.out.println(e.getMessage()); + } + for (int i = 0; i < count; i++) { + System.out.println(Arrays.toString(getFrameLocation(t, i))); + } + try { + System.out.println(Arrays.toString(getFrameLocation(t, count))); + } catch (RuntimeException e) { + System.out.println(e.getMessage()); + } + + // Let the thread stop looping and die. + data.stop = true; + t.join(); + } + + public static native int getFrameCount(Thread thread); + public static native Object[] getFrameLocation(Thread thread, int depth); +} diff --git a/test/911-get-stack-trace/src/Main.java b/test/911-get-stack-trace/src/Main.java index 2df5c5390..b199033c3 100644 --- a/test/911-get-stack-trace/src/Main.java +++ b/test/911-get-stack-trace/src/Main.java @@ -36,6 +36,10 @@ public class Main { ThreadListTraces.doTest(); + System.out.println(); + + Frames.doTest(); + System.out.println("Done"); } } diff --git a/test/911-get-stack-trace/stack_trace.cc b/test/911-get-stack-trace/stack_trace.cc index f85338718..d162e8a16 100644 --- a/test/911-get-stack-trace/stack_trace.cc +++ b/test/911-get-stack-trace/stack_trace.cc @@ -20,6 +20,7 @@ #include "android-base/stringprintf.h" +#include "android-base/stringprintf.h" #include "base/logging.h" #include "base/macros.h" #include "jni.h" @@ -202,5 +203,55 @@ extern "C" JNIEXPORT jobjectArray JNICALL Java_ThreadListTraces_getThreadListSta return ret; } +extern "C" JNIEXPORT jint JNICALL Java_Frames_getFrameCount( + JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jthread thread) { + jint count; + jvmtiError result = jvmti_env->GetFrameCount(thread, &count); + if (JvmtiErrorToException(env, result)) { + return -1; + } + return count; +} + +extern "C" JNIEXPORT jobjectArray JNICALL Java_Frames_getFrameLocation( + JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jthread thread, jint depth) { + jmethodID method; + jlocation location; + + jvmtiError result = jvmti_env->GetFrameLocation(thread, depth, &method, &location); + if (JvmtiErrorToException(env, result)) { + return nullptr; + } + + auto callback = [&](jint index) -> jobject { + switch (index) { + case 0: + { + jclass decl_class; + jvmtiError class_result = jvmti_env->GetMethodDeclaringClass(method, &decl_class); + if (JvmtiErrorToException(env, class_result)) { + return nullptr; + } + jint modifiers; + jvmtiError mod_result = jvmti_env->GetMethodModifiers(method, &modifiers); + if (JvmtiErrorToException(env, mod_result)) { + return nullptr; + } + constexpr jint kStatic = 0x8; + return env->ToReflectedMethod(decl_class, + method, + (modifiers & kStatic) != 0 ? JNI_TRUE : JNI_FALSE); + } + case 1: + return env->NewStringUTF( + android::base::StringPrintf("%x", static_cast(location)).c_str()); + } + LOG(FATAL) << "Unreachable"; + UNREACHABLE(); + }; + jobjectArray ret = CreateObjectArray(env, 2, "java/lang/Object", callback); + return ret; +} + } // namespace Test911GetStackTrace } // namespace art