| /* Copyright (C) 2016 The Android Open Source Project |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This file implements interfaces from the file jvmti.h. This implementation |
| * is licensed under the same terms as the file jvmti.h. The |
| * copyright and license information for the file jvmti.h follows. |
| * |
| * Copyright (c) 2003, 2011, 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. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle in the LICENSE file that accompanied this code. |
| * |
| * 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 "ti_stack.h" |
| |
| #include <algorithm> |
| #include <list> |
| #include <unordered_map> |
| #include <vector> |
| |
| #include "art_field-inl.h" |
| #include "art_method-inl.h" |
| #include "art_jvmti.h" |
| #include "base/bit_utils.h" |
| #include "base/enums.h" |
| #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" |
| #include "mirror/dex_cache.h" |
| #include "scoped_thread_state_change-inl.h" |
| #include "ScopedLocalRef.h" |
| #include "stack.h" |
| #include "thread-inl.h" |
| #include "thread_list.h" |
| #include "thread_pool.h" |
| #include "well_known_classes.h" |
| |
| namespace openjdkjvmti { |
| |
| struct GetStackTraceVisitor : public art::StackVisitor { |
| GetStackTraceVisitor(art::Thread* thread_in, |
| size_t start_, |
| size_t stop_) |
| : StackVisitor(thread_in, nullptr, StackVisitor::StackWalkKind::kIncludeInlinedFrames), |
| start(start_), |
| stop(stop_) {} |
| |
| bool VisitFrame() REQUIRES_SHARED(art::Locks::mutator_lock_) { |
| art::ArtMethod* m = GetMethod(); |
| if (m->IsRuntimeMethod()) { |
| return true; |
| } |
| |
| if (start == 0) { |
| m = m->GetInterfaceMethodIfProxy(art::kRuntimePointerSize); |
| jmethodID id = art::jni::EncodeArtMethod(m); |
| |
| uint32_t dex_pc = GetDexPc(false); |
| jlong dex_location = (dex_pc == art::DexFile::kDexNoIndex) ? -1 : static_cast<jlong>(dex_pc); |
| |
| jvmtiFrameInfo info = { id, dex_location }; |
| frames.push_back(info); |
| |
| if (stop == 1) { |
| return false; // We're done. |
| } else if (stop > 0) { |
| stop--; |
| } |
| } else { |
| start--; |
| } |
| |
| return true; |
| } |
| |
| std::vector<jvmtiFrameInfo> frames; |
| size_t start; |
| size_t stop; |
| }; |
| |
| struct GetStackTraceClosure : public art::Closure { |
| public: |
| GetStackTraceClosure(size_t start, size_t stop) |
| : start_input(start), |
| stop_input(stop), |
| start_result(0), |
| stop_result(0) {} |
| |
| void Run(art::Thread* self) OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) { |
| GetStackTraceVisitor visitor(self, start_input, stop_input); |
| visitor.WalkStack(false); |
| |
| frames.swap(visitor.frames); |
| start_result = visitor.start; |
| stop_result = visitor.stop; |
| } |
| |
| const size_t start_input; |
| const size_t stop_input; |
| |
| std::vector<jvmtiFrameInfo> frames; |
| size_t start_result; |
| size_t stop_result; |
| }; |
| |
| static jvmtiError TranslateFrameVector(const std::vector<jvmtiFrameInfo>& frames, |
| jint start_depth, |
| size_t start_result, |
| jint max_frame_count, |
| jvmtiFrameInfo* frame_buffer, |
| jint* count_ptr) { |
| size_t collected_frames = frames.size(); |
| |
| // Assume we're here having collected something. |
| DCHECK_GT(max_frame_count, 0); |
| |
| // Frames from the top. |
| if (start_depth >= 0) { |
| if (start_result != 0) { |
| // Not enough frames. |
| return ERR(ILLEGAL_ARGUMENT); |
| } |
| DCHECK_LE(collected_frames, static_cast<size_t>(max_frame_count)); |
| if (frames.size() > 0) { |
| memcpy(frame_buffer, frames.data(), collected_frames * sizeof(jvmtiFrameInfo)); |
| } |
| *count_ptr = static_cast<jint>(frames.size()); |
| return ERR(NONE); |
| } |
| |
| // Frames from the bottom. |
| if (collected_frames < static_cast<size_t>(-start_depth)) { |
| return ERR(ILLEGAL_ARGUMENT); |
| } |
| |
| size_t count = std::min(static_cast<size_t>(-start_depth), static_cast<size_t>(max_frame_count)); |
| memcpy(frame_buffer, |
| &frames.data()[collected_frames + start_depth], |
| count * sizeof(jvmtiFrameInfo)); |
| *count_ptr = static_cast<jint>(count); |
| 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) { |
| 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); |
| |
| art::ThreadState state = thread->GetState(); |
| if (state == art::ThreadState::kStarting || |
| state == art::ThreadState::kTerminated || |
| thread->IsStillStarting()) { |
| return ERR(THREAD_NOT_ALIVE); |
| } |
| |
| if (max_frame_count < 0) { |
| return ERR(ILLEGAL_ARGUMENT); |
| } |
| if (frame_buffer == nullptr || count_ptr == nullptr) { |
| return ERR(NULL_POINTER); |
| } |
| |
| if (max_frame_count == 0) { |
| *count_ptr = 0; |
| return ERR(NONE); |
| } |
| |
| GetStackTraceClosure closure(start_depth >= 0 ? static_cast<size_t>(start_depth) : 0, |
| start_depth >= 0 ? static_cast<size_t>(max_frame_count) : 0); |
| thread->RequestSynchronousCheckpoint(&closure); |
| |
| return TranslateFrameVector(closure.frames, |
| start_depth, |
| closure.start_result, |
| max_frame_count, |
| frame_buffer, |
| count_ptr); |
| } |
| |
| struct GetAllStackTraceClosure : public art::Closure { |
| public: |
| explicit GetAllStackTraceClosure(size_t stop) |
| : start_input(0), |
| stop_input(stop), |
| frames_lock("GetAllStackTraceGuard", art::LockLevel::kAbortLock), |
| start_result(0), |
| stop_result(0) {} |
| |
| void Run(art::Thread* self) |
| OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) REQUIRES(!frames_lock) { |
| // self should be live here (so it could be suspended). No need to filter. |
| |
| art::Thread* current = art::Thread::Current(); |
| std::vector<jvmtiFrameInfo> self_frames; |
| |
| GetStackTraceVisitor visitor(self, start_input, stop_input); |
| visitor.WalkStack(false); |
| |
| self_frames.swap(visitor.frames); |
| |
| art::MutexLock mu(current, frames_lock); |
| frames.emplace(self, self_frames); |
| } |
| |
| const size_t start_input; |
| const size_t stop_input; |
| |
| art::Mutex frames_lock; |
| std::unordered_map<art::Thread*, std::vector<jvmtiFrameInfo>> frames GUARDED_BY(frames_lock); |
| size_t start_result; |
| size_t stop_result; |
| }; |
| |
| |
| |
| jvmtiError StackUtil::GetAllStackTraces(jvmtiEnv* env, |
| jint max_frame_count, |
| jvmtiStackInfo** stack_info_ptr, |
| jint* thread_count_ptr) { |
| if (max_frame_count < 0) { |
| return ERR(ILLEGAL_ARGUMENT); |
| } |
| if (stack_info_ptr == nullptr || thread_count_ptr == nullptr) { |
| return ERR(NULL_POINTER); |
| } |
| |
| |
| art::Thread* current = art::Thread::Current(); |
| art::ScopedObjectAccess soa(current); // Now we know we have the shared lock. |
| art::ScopedThreadSuspension sts(current, art::kWaitingForDebuggerSuspension); |
| art::ScopedSuspendAll ssa("GetAllStackTraces"); |
| |
| std::vector<art::Thread*> threads; |
| std::vector<std::vector<jvmtiFrameInfo>> frames; |
| { |
| std::list<art::Thread*> thread_list; |
| { |
| art::MutexLock mu(current, *art::Locks::thread_list_lock_); |
| thread_list = art::Runtime::Current()->GetThreadList()->GetList(); |
| } |
| |
| for (art::Thread* thread : thread_list) { |
| // Skip threads that are still starting. |
| if (thread->IsStillStarting()) { |
| continue; |
| } |
| |
| GetStackTraceClosure closure(0u, static_cast<size_t>(max_frame_count)); |
| thread->RequestSynchronousCheckpoint(&closure); |
| |
| threads.push_back(thread); |
| frames.emplace_back(); |
| frames.back().swap(closure.frames); |
| } |
| } |
| |
| // Convert the data into our output format. Note: we need to keep the threads suspended, |
| // as we need to access them for their peers. |
| |
| // 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->GetPeerFromOtherThread() != 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. |
| size_t rounded_stack_info_size = art::RoundUp(sizeof(jvmtiStackInfo) * frames.size(), |
| 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); |
| // First copy in all the basic data. |
| memcpy(stack_info, stack_info_array.get(), sizeof(jvmtiStackInfo) * frames.size()); |
| |
| // Now copy the frames and fix up the pointers. |
| jvmtiFrameInfo* frame_info = reinterpret_cast<jvmtiFrameInfo*>( |
| chunk_data + rounded_stack_info_size); |
| for (size_t i = 0; i < frames.size(); ++i) { |
| jvmtiStackInfo& old_stack_info = stack_info_array.get()[i]; |
| jvmtiStackInfo& new_stack_info = stack_info[i]; |
| |
| jthread thread_peer = current->GetJniEnv()->AddLocalReference<jthread>( |
| threads[i]->GetPeerFromOtherThread()); |
| new_stack_info.thread = thread_peer; |
| |
| 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; |
| *thread_count_ptr = static_cast<jint>(frames.size()); |
| |
| 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->GetPeerFromOtherThread(); |
| 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->GetPeerFromOtherThread() != 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); |
| } |
| |
| // 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<size_t>(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<jlocation>(closure.dex_pc); |
| } |
| |
| return ERR(NONE); |
| } |
| |
| } // namespace openjdkjvmti |