diff options
| -rw-r--r-- | runtime/openjdkjvmti/OpenjdkJvmTi.cc | 6 | ||||
| -rw-r--r-- | runtime/openjdkjvmti/ti_stack.cc | 189 | ||||
| -rw-r--r-- | runtime/openjdkjvmti/ti_stack.h | 6 | ||||
| -rw-r--r-- | test/911-get-stack-trace/expected.txt | 181 | ||||
| -rw-r--r-- | test/911-get-stack-trace/src/Main.java | 4 | ||||
| -rw-r--r-- | test/911-get-stack-trace/src/ThreadListTraces.java | 71 | ||||
| -rw-r--r-- | test/911-get-stack-trace/stack_trace.cc | 56 |
7 files changed, 495 insertions, 18 deletions
diff --git a/runtime/openjdkjvmti/OpenjdkJvmTi.cc b/runtime/openjdkjvmti/OpenjdkJvmTi.cc index b3c56a1a05..ff0fba882a 100644 --- a/runtime/openjdkjvmti/OpenjdkJvmTi.cc +++ b/runtime/openjdkjvmti/OpenjdkJvmTi.cc @@ -246,7 +246,11 @@ class JvmtiFunctions { const jthread* thread_list, jint max_frame_count, jvmtiStackInfo** stack_info_ptr) { - return ERR(NOT_IMPLEMENTED); + return StackUtil::GetThreadListStackTraces(env, + thread_count, + thread_list, + max_frame_count, + stack_info_ptr); } static jvmtiError GetFrameCount(jvmtiEnv* env, jthread thread, jint* count_ptr) { diff --git a/runtime/openjdkjvmti/ti_stack.cc b/runtime/openjdkjvmti/ti_stack.cc index 098cedbffa..8bd8d09b5e 100644 --- a/runtime/openjdkjvmti/ti_stack.cc +++ b/runtime/openjdkjvmti/ti_stack.cc @@ -31,6 +31,7 @@ #include "ti_stack.h" +#include <algorithm> #include <list> #include <unordered_map> #include <vector> @@ -42,6 +43,7 @@ #include "base/mutex.h" #include "dex_file.h" #include "dex_file_annotations.h" +#include "handle_scope-inl.h" #include "jni_env_ext.h" #include "jni_internal.h" #include "mirror/class.h" @@ -374,4 +376,191 @@ jvmtiError StackUtil::GetAllStackTraces(jvmtiEnv* env, return ERR(NONE); } +jvmtiError StackUtil::GetThreadListStackTraces(jvmtiEnv* env, + jint thread_count, + const jthread* thread_list, + jint max_frame_count, + jvmtiStackInfo** stack_info_ptr) { + if (max_frame_count < 0) { + return ERR(ILLEGAL_ARGUMENT); + } + if (thread_count < 0) { + return ERR(ILLEGAL_ARGUMENT); + } + if (thread_count == 0) { + *stack_info_ptr = nullptr; + return ERR(NONE); + } + if (stack_info_ptr == nullptr || stack_info_ptr == nullptr) { + return ERR(NULL_POINTER); + } + + art::Thread* current = art::Thread::Current(); + art::ScopedObjectAccess soa(current); // Now we know we have the shared lock. + + // Decode all threads to raw pointers. Put them into a handle scope to avoid any moving GC bugs. + art::VariableSizedHandleScope hs(current); + std::vector<art::Handle<art::mirror::Object>> handles; + for (jint i = 0; i != thread_count; ++i) { + if (thread_list[i] == nullptr) { + return ERR(INVALID_THREAD); + } + if (!soa.Env()->IsInstanceOf(thread_list[i], art::WellKnownClasses::java_lang_Thread)) { + return ERR(INVALID_THREAD); + } + handles.push_back(hs.NewHandle(soa.Decode<art::mirror::Object>(thread_list[i]))); + } + + std::vector<art::Thread*> threads; + std::vector<size_t> thread_list_indices; + std::vector<std::vector<jvmtiFrameInfo>> frames; + + { + art::ScopedThreadSuspension sts(current, art::kWaitingForDebuggerSuspension); + art::ScopedSuspendAll ssa("GetThreadListStackTraces"); + + { + std::list<art::Thread*> art_thread_list; + { + art::MutexLock mu(current, *art::Locks::thread_list_lock_); + art_thread_list = art::Runtime::Current()->GetThreadList()->GetList(); + } + + for (art::Thread* thread : art_thread_list) { + if (thread->IsStillStarting()) { + // Skip this. We can't get the jpeer, and if it is for a thread in the thread_list, + // we'll just report STARTING. + continue; + } + + // Get the peer, and check whether we know it. + art::ObjPtr<art::mirror::Object> peer = thread->GetPeer(); + for (size_t index = 0; index != handles.size(); ++index) { + if (peer == handles[index].Get()) { + // Found the thread. + GetStackTraceClosure closure(0u, static_cast<size_t>(max_frame_count)); + thread->RequestSynchronousCheckpoint(&closure); + + threads.push_back(thread); + thread_list_indices.push_back(index); + frames.emplace_back(); + frames.back().swap(closure.frames); + + continue; + } + } + + // Must be not started, or dead. We'll deal with it at the end. + } + } + } + + // Convert the data into our output format. + + // Note: we use an array of jvmtiStackInfo for convenience. The spec says we need to + // allocate one big chunk for this and the actual frames, which means we need + // to either be conservative or rearrange things later (the latter is implemented). + std::unique_ptr<jvmtiStackInfo[]> stack_info_array(new jvmtiStackInfo[frames.size()]); + std::vector<std::unique_ptr<jvmtiFrameInfo[]>> frame_infos; + frame_infos.reserve(frames.size()); + + // Now run through and add data for each thread. + size_t sum_frames = 0; + for (size_t index = 0; index < frames.size(); ++index) { + jvmtiStackInfo& stack_info = stack_info_array.get()[index]; + memset(&stack_info, 0, sizeof(jvmtiStackInfo)); + + art::Thread* self = threads[index]; + const std::vector<jvmtiFrameInfo>& thread_frames = frames[index]; + + // For the time being, set the thread to null. We don't have good ScopedLocalRef + // infrastructure. + DCHECK(self->GetPeer() != nullptr); + stack_info.thread = nullptr; + stack_info.state = JVMTI_THREAD_STATE_SUSPENDED; + + size_t collected_frames = thread_frames.size(); + if (max_frame_count == 0 || collected_frames == 0) { + stack_info.frame_count = 0; + stack_info.frame_buffer = nullptr; + continue; + } + DCHECK_LE(collected_frames, static_cast<size_t>(max_frame_count)); + + jvmtiFrameInfo* frame_info = new jvmtiFrameInfo[collected_frames]; + frame_infos.emplace_back(frame_info); + + jint count; + jvmtiError translate_result = TranslateFrameVector(thread_frames, + 0, + 0, + static_cast<jint>(collected_frames), + frame_info, + &count); + DCHECK(translate_result == JVMTI_ERROR_NONE); + stack_info.frame_count = static_cast<jint>(collected_frames); + stack_info.frame_buffer = frame_info; + sum_frames += static_cast<size_t>(count); + } + + // No errors, yet. Now put it all into an output buffer. Note that this is not frames.size(), + // potentially. + size_t rounded_stack_info_size = art::RoundUp(sizeof(jvmtiStackInfo) * thread_count, + alignof(jvmtiFrameInfo)); + size_t chunk_size = rounded_stack_info_size + sum_frames * sizeof(jvmtiFrameInfo); + unsigned char* chunk_data; + jvmtiError alloc_result = env->Allocate(chunk_size, &chunk_data); + if (alloc_result != ERR(NONE)) { + return alloc_result; + } + + jvmtiStackInfo* stack_info = reinterpret_cast<jvmtiStackInfo*>(chunk_data); + jvmtiFrameInfo* frame_info = reinterpret_cast<jvmtiFrameInfo*>( + chunk_data + rounded_stack_info_size); + + for (size_t i = 0; i < static_cast<size_t>(thread_count); ++i) { + // Check whether we found a running thread for this. + // Note: For simplicity, and with the expectation that the list is usually small, use a simple + // search. (The list is *not* sorted!) + auto it = std::find(thread_list_indices.begin(), thread_list_indices.end(), i); + if (it == thread_list_indices.end()) { + // No native thread. Must be new or dead. We need to fill out the stack info now. + // (Need to read the Java "started" field to know whether this is starting or terminated.) + art::ObjPtr<art::mirror::Object> peer = soa.Decode<art::mirror::Object>(thread_list[i]); + art::ObjPtr<art::mirror::Class> klass = peer->GetClass(); + art::ArtField* started_field = klass->FindDeclaredInstanceField("started", "Z"); + CHECK(started_field != nullptr); + bool started = started_field->GetBoolean(peer) != 0; + constexpr jint kStartedState = JVMTI_JAVA_LANG_THREAD_STATE_NEW; + constexpr jint kTerminatedState = JVMTI_THREAD_STATE_TERMINATED | + JVMTI_JAVA_LANG_THREAD_STATE_TERMINATED; + stack_info[i].thread = reinterpret_cast<JNIEnv*>(soa.Env())->NewLocalRef(thread_list[i]); + stack_info[i].state = started ? kTerminatedState : kStartedState; + stack_info[i].frame_count = 0; + stack_info[i].frame_buffer = nullptr; + } else { + // Had a native thread and frames. + size_t f_index = it - thread_list_indices.begin(); + + jvmtiStackInfo& old_stack_info = stack_info_array.get()[f_index]; + jvmtiStackInfo& new_stack_info = stack_info[i]; + + memcpy(&new_stack_info, &old_stack_info, sizeof(jvmtiStackInfo)); + new_stack_info.thread = reinterpret_cast<JNIEnv*>(soa.Env())->NewLocalRef(thread_list[i]); + if (old_stack_info.frame_count > 0) { + // Only copy when there's data - leave the nullptr alone. + size_t frames_size = + static_cast<size_t>(old_stack_info.frame_count) * sizeof(jvmtiFrameInfo); + memcpy(frame_info, old_stack_info.frame_buffer, frames_size); + new_stack_info.frame_buffer = frame_info; + frame_info += old_stack_info.frame_count; + } + } + } + + * stack_info_ptr = stack_info; + + return ERR(NONE); +} + } // namespace openjdkjvmti diff --git a/runtime/openjdkjvmti/ti_stack.h b/runtime/openjdkjvmti/ti_stack.h index 7619f98daf..a5b391c5d2 100644 --- a/runtime/openjdkjvmti/ti_stack.h +++ b/runtime/openjdkjvmti/ti_stack.h @@ -53,6 +53,12 @@ class StackUtil { jint max_frame_count, jvmtiFrameInfo* frame_buffer, jint* count_ptr); + + static jvmtiError GetThreadListStackTraces(jvmtiEnv* env, + jint thread_count, + const jthread* thread_list, + jint max_frame_count, + jvmtiStackInfo** stack_info_ptr); }; } // namespace openjdkjvmti diff --git a/test/911-get-stack-trace/expected.txt b/test/911-get-stack-trace/expected.txt index f0f92eaec9..061101c02f 100644 --- a/test/911-get-stack-trace/expected.txt +++ b/test/911-get-stack-trace/expected.txt @@ -592,4 +592,185 @@ main doTest ()V 107 59 main ([Ljava/lang/String;)V 30 33 + +######################################## +### Other select threads (suspended) ### +######################################## +--------- +Thread-14 + +--------- +Thread-16 + +--------- +Thread-18 + +--------- +Thread-20 + +--------- +Thread-22 + +--------- +main + +--------- +Thread-14 + wait ()V -1 -2 + printOrWait (IILControlData;)V 24 45 + baz (IIILControlData;)Ljava/lang/Object; 2 30 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + +--------- +Thread-16 + wait ()V -1 -2 + printOrWait (IILControlData;)V 24 45 + baz (IIILControlData;)Ljava/lang/Object; 2 30 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + +--------- +Thread-18 + wait ()V -1 -2 + printOrWait (IILControlData;)V 24 45 + baz (IIILControlData;)Ljava/lang/Object; 2 30 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + +--------- +Thread-20 + wait ()V -1 -2 + printOrWait (IILControlData;)V 24 45 + baz (IIILControlData;)Ljava/lang/Object; 2 30 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + +--------- +Thread-22 + wait ()V -1 -2 + printOrWait (IILControlData;)V 24 45 + baz (IIILControlData;)Ljava/lang/Object; 2 30 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + +--------- +main + getThreadListStackTraces ([Ljava/lang/Thread;I)[[Ljava/lang/Object; -1 -2 + printList ([Ljava/lang/Thread;I)V 0 66 + doTest ()V 96 52 + main ([Ljava/lang/String;)V 38 37 + +--------- +Thread-14 + wait ()V -1 -2 + printOrWait (IILControlData;)V 24 45 + baz (IIILControlData;)Ljava/lang/Object; 2 30 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + run ()V 4 35 + +--------- +Thread-16 + wait ()V -1 -2 + printOrWait (IILControlData;)V 24 45 + baz (IIILControlData;)Ljava/lang/Object; 2 30 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + run ()V 4 35 + +--------- +Thread-18 + wait ()V -1 -2 + printOrWait (IILControlData;)V 24 45 + baz (IIILControlData;)Ljava/lang/Object; 2 30 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + run ()V 4 35 + +--------- +Thread-20 + wait ()V -1 -2 + printOrWait (IILControlData;)V 24 45 + baz (IIILControlData;)Ljava/lang/Object; 2 30 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + run ()V 4 35 + +--------- +Thread-22 + wait ()V -1 -2 + printOrWait (IILControlData;)V 24 45 + baz (IIILControlData;)Ljava/lang/Object; 2 30 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + baz (IIILControlData;)Ljava/lang/Object; 9 32 + bar (IIILControlData;)J 0 24 + foo (IIILControlData;)I 0 19 + run ()V 4 35 + +--------- +main + getThreadListStackTraces ([Ljava/lang/Thread;I)[[Ljava/lang/Object; -1 -2 + printList ([Ljava/lang/Thread;I)V 0 66 + doTest ()V 101 54 + main ([Ljava/lang/String;)V 38 37 + Done diff --git a/test/911-get-stack-trace/src/Main.java b/test/911-get-stack-trace/src/Main.java index 7b85c720b8..2df5c53907 100644 --- a/test/911-get-stack-trace/src/Main.java +++ b/test/911-get-stack-trace/src/Main.java @@ -32,6 +32,10 @@ public class Main { AllTraces.doTest(); + System.out.println(); + + ThreadListTraces.doTest(); + System.out.println("Done"); } } diff --git a/test/911-get-stack-trace/src/ThreadListTraces.java b/test/911-get-stack-trace/src/ThreadListTraces.java new file mode 100644 index 0000000000..f66557f3bd --- /dev/null +++ b/test/911-get-stack-trace/src/ThreadListTraces.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2016 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. + */ + +public class ThreadListTraces { + public static void doTest() throws Exception { + System.out.println("########################################"); + System.out.println("### Other select threads (suspended) ###"); + System.out.println("########################################"); + + final int N = 10; + + final ControlData data = new ControlData(N); + data.waitFor = new Object(); + + Thread threads[] = new Thread[N]; + + Thread list[] = new Thread[N/2 + 1]; + + for (int i = 0; i < N; i++) { + Thread t = new Thread() { + public void run() { + Recurse.foo(4, 0, 0, data); + } + }; + t.start(); + threads[i] = t; + if (i % 2 == 0) { + list[i/2] = t; + } + } + list[list.length - 1] = Thread.currentThread(); + + data.reached.await(); + Thread.yield(); + Thread.sleep(500); // A little bit of time... + + printList(list, 0); + + printList(list, 5); + + printList(list, 25); + + // Let the thread make progress and die. + synchronized(data.waitFor) { + data.waitFor.notifyAll(); + } + for (int i = 0; i < N; i++) { + threads[i].join(); + } + } + + public static void printList(Thread[] threads, int max) { + PrintThread.printAll(getThreadListStackTraces(threads, max)); + } + + // Similar to getAllStackTraces, but restricted to the given threads. + public static native Object[][] getThreadListStackTraces(Thread threads[], int max); +} diff --git a/test/911-get-stack-trace/stack_trace.cc b/test/911-get-stack-trace/stack_trace.cc index 8fc0af4732..f853387180 100644 --- a/test/911-get-stack-trace/stack_trace.cc +++ b/test/911-get-stack-trace/stack_trace.cc @@ -59,11 +59,7 @@ static jobjectArray TranslateJvmtiFrameInfoArray(JNIEnv* env, char* gen; { jvmtiError result2 = jvmti_env->GetMethodName(frames[method_index].method, &name, &sig, &gen); - if (result2 != JVMTI_ERROR_NONE) { - char* err; - jvmti_env->GetErrorName(result2, &err); - printf("Failure running GetMethodName: %s\n", err); - jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(err)); + if (JvmtiErrorToException(env, result2)) { return nullptr; } } @@ -134,11 +130,7 @@ extern "C" JNIEXPORT jobjectArray JNICALL Java_PrintThread_getStackTrace( jint count; { jvmtiError result = jvmti_env->GetStackTrace(thread, start, max, frames.get(), &count); - if (result != JVMTI_ERROR_NONE) { - char* err; - jvmti_env->GetErrorName(result, &err); - printf("Failure running GetStackTrace: %s\n", err); - jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(err)); + if (JvmtiErrorToException(env, result)) { return nullptr; } } @@ -148,17 +140,47 @@ extern "C" JNIEXPORT jobjectArray JNICALL Java_PrintThread_getStackTrace( extern "C" JNIEXPORT jobjectArray JNICALL Java_AllTraces_getAllStackTraces( JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jint max) { - std::unique_ptr<jvmtiFrameInfo[]> frames(new jvmtiFrameInfo[max]); - jint thread_count; jvmtiStackInfo* stack_infos; { jvmtiError result = jvmti_env->GetAllStackTraces(max, &stack_infos, &thread_count); - if (result != JVMTI_ERROR_NONE) { - char* err; - jvmti_env->GetErrorName(result, &err); - printf("Failure running GetAllStackTraces: %s\n", err); - jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(err)); + if (JvmtiErrorToException(env, result)) { + return nullptr; + } + } + + auto callback = [&](jint thread_index) -> jobject { + auto inner_callback = [&](jint index) -> jobject { + if (index == 0) { + return stack_infos[thread_index].thread; + } else { + return TranslateJvmtiFrameInfoArray(env, + stack_infos[thread_index].frame_buffer, + stack_infos[thread_index].frame_count); + } + }; + return CreateObjectArray(env, 2, "java/lang/Object", inner_callback); + }; + jobjectArray ret = CreateObjectArray(env, thread_count, "[Ljava/lang/Object;", callback); + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(stack_infos)); + return ret; +} + +extern "C" JNIEXPORT jobjectArray JNICALL Java_ThreadListTraces_getThreadListStackTraces( + JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jobjectArray jthreads, jint max) { + jint thread_count = env->GetArrayLength(jthreads); + std::unique_ptr<jthread[]> threads(new jthread[thread_count]); + for (jint i = 0; i != thread_count; ++i) { + threads[i] = env->GetObjectArrayElement(jthreads, i); + } + + jvmtiStackInfo* stack_infos; + { + jvmtiError result = jvmti_env->GetThreadListStackTraces(thread_count, + threads.get(), + max, + &stack_infos); + if (JvmtiErrorToException(env, result)) { return nullptr; } } |