diff options
| -rw-r--r-- | runtime/openjdkjvmti/Android.bp | 1 | ||||
| -rw-r--r-- | runtime/openjdkjvmti/OpenjdkJvmTi.cc | 8 | ||||
| -rw-r--r-- | runtime/openjdkjvmti/ti_stack.cc | 195 | ||||
| -rw-r--r-- | runtime/openjdkjvmti/ti_stack.h | 51 | ||||
| -rw-r--r-- | runtime/stack.h | 3 | ||||
| -rw-r--r-- | runtime/thread.cc | 79 | ||||
| -rw-r--r-- | runtime/thread.h | 2 | ||||
| -rwxr-xr-x | test/911-get-stack-trace/build | 17 | ||||
| -rw-r--r-- | test/911-get-stack-trace/expected.txt | 208 | ||||
| -rw-r--r-- | test/911-get-stack-trace/info.txt | 1 | ||||
| -rwxr-xr-x | test/911-get-stack-trace/run | 43 | ||||
| -rw-r--r-- | test/911-get-stack-trace/src/Main.java | 178 | ||||
| -rw-r--r-- | test/911-get-stack-trace/stack_trace.cc | 96 | ||||
| -rw-r--r-- | test/911-get-stack-trace/stack_trace.h | 30 | ||||
| -rw-r--r-- | test/Android.bp | 1 | ||||
| -rw-r--r-- | test/ti-agent/common_load.cc | 2 |
16 files changed, 912 insertions, 3 deletions
diff --git a/runtime/openjdkjvmti/Android.bp b/runtime/openjdkjvmti/Android.bp index c8675a4339..8b6a8f95df 100644 --- a/runtime/openjdkjvmti/Android.bp +++ b/runtime/openjdkjvmti/Android.bp @@ -22,6 +22,7 @@ cc_defaults { "object_tagging.cc", "OpenjdkJvmTi.cc", "ti_method.cc", + "ti_stack.cc", "transform.cc"], include_dirs: ["art/runtime"], shared_libs: [ diff --git a/runtime/openjdkjvmti/OpenjdkJvmTi.cc b/runtime/openjdkjvmti/OpenjdkJvmTi.cc index ed99009b0f..10e877a62f 100644 --- a/runtime/openjdkjvmti/OpenjdkJvmTi.cc +++ b/runtime/openjdkjvmti/OpenjdkJvmTi.cc @@ -48,6 +48,7 @@ #include "thread_list.h" #include "thread-inl.h" #include "ti_method.h" +#include "ti_stack.h" #include "transform.h" // TODO Remove this at some point by annotating all the methods. It was put in to make the skeleton @@ -202,7 +203,12 @@ class JvmtiFunctions { jint max_frame_count, jvmtiFrameInfo* frame_buffer, jint* count_ptr) { - return ERR(NOT_IMPLEMENTED); + return StackUtil::GetStackTrace(env, + thread, + start_depth, + max_frame_count, + frame_buffer, + count_ptr); } static jvmtiError GetAllStackTraces(jvmtiEnv* env, diff --git a/runtime/openjdkjvmti/ti_stack.cc b/runtime/openjdkjvmti/ti_stack.cc new file mode 100644 index 0000000000..33e677f4b1 --- /dev/null +++ b/runtime/openjdkjvmti/ti_stack.cc @@ -0,0 +1,195 @@ +/* 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 "art_jvmti.h" +#include "art_method-inl.h" +#include "base/enums.h" +#include "dex_file.h" +#include "dex_file_annotations.h" +#include "jni_env_ext.h" +#include "mirror/class.h" +#include "mirror/dex_cache.h" +#include "scoped_thread_state_change-inl.h" +#include "stack.h" +#include "thread.h" +#include "thread_pool.h" + +namespace openjdkjvmti { + +struct GetStackTraceVisitor : public art::StackVisitor { + GetStackTraceVisitor(art::Thread* thread_in, + art::ScopedObjectAccessAlreadyRunnable& soa_, + size_t start_, + size_t stop_) + : StackVisitor(thread_in, nullptr, StackVisitor::StackWalkKind::kIncludeInlinedFrames), + soa(soa_), + 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 = soa.EncodeMethod(m); + + art::mirror::DexCache* dex_cache = m->GetDexCache(); + int32_t line_number = -1; + if (dex_cache != nullptr) { // be tolerant of bad input + const art::DexFile* dex_file = dex_cache->GetDexFile(); + line_number = art::annotations::GetLineNumFromPC(dex_file, m, GetDexPc(false)); + } + + jvmtiFrameInfo info = { id, static_cast<jlong>(line_number) }; + frames.push_back(info); + + if (stop == 1) { + return false; // We're done. + } else if (stop > 0) { + stop--; + } + } else { + start--; + } + + return true; + } + + art::ScopedObjectAccessAlreadyRunnable& soa; + 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 { + art::ScopedObjectAccess soa(art::Thread::Current()); + + GetStackTraceVisitor visitor(self, soa, 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; +}; + +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); + } + + 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); + + size_t collected_frames = closure.frames.size(); + + // Frames from the top. + if (start_depth >= 0) { + if (closure.start_result != 0) { + // Not enough frames. + return ERR(ILLEGAL_ARGUMENT); + } + DCHECK_LE(collected_frames, static_cast<size_t>(max_frame_count)); + if (closure.frames.size() > 0) { + memcpy(frame_buffer, closure.frames.data(), collected_frames * sizeof(jvmtiFrameInfo)); + } + *count_ptr = static_cast<jint>(closure.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, + &closure.frames.data()[collected_frames + start_depth], + count * sizeof(jvmtiFrameInfo)); + *count_ptr = static_cast<jint>(count); + return ERR(NONE); +} + +} // namespace openjdkjvmti diff --git a/runtime/openjdkjvmti/ti_stack.h b/runtime/openjdkjvmti/ti_stack.h new file mode 100644 index 0000000000..1931ed3113 --- /dev/null +++ b/runtime/openjdkjvmti/ti_stack.h @@ -0,0 +1,51 @@ +/* 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. + */ + +#ifndef ART_RUNTIME_OPENJDKJVMTI_TI_STACK_H_ +#define ART_RUNTIME_OPENJDKJVMTI_TI_STACK_H_ + +#include "jvmti.h" + +namespace openjdkjvmti { + +class StackUtil { + public: + static jvmtiError GetStackTrace(jvmtiEnv* env, + jthread thread, + jint start_depth, + jint max_frame_count, + jvmtiFrameInfo* frame_buffer, + jint* count_ptr); +}; + +} // namespace openjdkjvmti + +#endif // ART_RUNTIME_OPENJDKJVMTI_TI_STACK_H_ diff --git a/runtime/stack.h b/runtime/stack.h index e9ed497094..8a446ecfd4 100644 --- a/runtime/stack.h +++ b/runtime/stack.h @@ -572,8 +572,7 @@ class StackVisitor { }; protected: - StackVisitor(Thread* thread, Context* context, StackWalkKind walk_kind) - REQUIRES_SHARED(Locks::mutator_lock_); + StackVisitor(Thread* thread, Context* context, StackWalkKind walk_kind); bool GetRegisterIfAccessible(uint32_t reg, VRegKind kind, uint32_t* val) const REQUIRES_SHARED(Locks::mutator_lock_); diff --git a/runtime/thread.cc b/runtime/thread.cc index ef48b5d2dc..3f7d0868b0 100644 --- a/runtime/thread.cc +++ b/runtime/thread.cc @@ -1175,6 +1175,85 @@ bool Thread::RequestCheckpoint(Closure* function) { return success; } +class BarrierClosure : public Closure { + public: + explicit BarrierClosure(Closure* wrapped) : wrapped_(wrapped), barrier_(0) {} + + void Run(Thread* self) OVERRIDE { + wrapped_->Run(self); + barrier_.Pass(self); + } + + void Wait(Thread* self) { + barrier_.Increment(self, 1); + } + + private: + Closure* wrapped_; + Barrier barrier_; +}; + +void Thread::RequestSynchronousCheckpoint(Closure* function) { + if (this == Thread::Current()) { + // Asked to run on this thread. Just run. + function->Run(this); + return; + } + Thread* self = Thread::Current(); + + // The current thread is not this thread. + + for (;;) { + // If this thread is runnable, try to schedule a checkpoint. Do some gymnastics to not hold the + // suspend-count lock for too long. + if (GetState() == ThreadState::kRunnable) { + BarrierClosure barrier_closure(function); + bool installed = false; + { + MutexLock mu(self, *Locks::thread_suspend_count_lock_); + installed = RequestCheckpoint(&barrier_closure); + } + if (installed) { + barrier_closure.Wait(self); + return; + } + // Fall-through. + } + + // This thread is not runnable, make sure we stay suspended, then run the checkpoint. + // Note: ModifySuspendCountInternal also expects the thread_list_lock to be held in + // certain situations. + { + MutexLock mu(self, *Locks::thread_list_lock_); + MutexLock mu2(self, *Locks::thread_suspend_count_lock_); + + if (!ModifySuspendCount(self, +1, nullptr, false)) { + // Just retry the loop. + sched_yield(); + continue; + } + } + + while (GetState() == ThreadState::kRunnable) { + // We became runnable again. Wait till the suspend triggered in ModifySuspendCount + // moves us to suspended. + sched_yield(); + } + + function->Run(this); + + { + MutexLock mu(self, *Locks::thread_list_lock_); + MutexLock mu2(self, *Locks::thread_suspend_count_lock_); + + DCHECK_NE(GetState(), ThreadState::kRunnable); + CHECK(ModifySuspendCount(self, -1, nullptr, false)); + } + + return; // We're done, break out of the loop. + } +} + Closure* Thread::GetFlipFunction() { Atomic<Closure*>* atomic_func = reinterpret_cast<Atomic<Closure*>*>(&tlsPtr_.flip_function); Closure* func; diff --git a/runtime/thread.h b/runtime/thread.h index 07ed78b128..75b5b123da 100644 --- a/runtime/thread.h +++ b/runtime/thread.h @@ -237,6 +237,8 @@ class Thread { bool RequestCheckpoint(Closure* function) REQUIRES(Locks::thread_suspend_count_lock_); + void RequestSynchronousCheckpoint(Closure* function) + REQUIRES(!Locks::thread_suspend_count_lock_, !Locks::thread_list_lock_); void SetFlipFunction(Closure* function); Closure* GetFlipFunction(); diff --git a/test/911-get-stack-trace/build b/test/911-get-stack-trace/build new file mode 100755 index 0000000000..898e2e54a2 --- /dev/null +++ b/test/911-get-stack-trace/build @@ -0,0 +1,17 @@ +#!/bin/bash +# +# Copyright 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. + +./default-build "$@" --experimental agents diff --git a/test/911-get-stack-trace/expected.txt b/test/911-get-stack-trace/expected.txt new file mode 100644 index 0000000000..20bab7814e --- /dev/null +++ b/test/911-get-stack-trace/expected.txt @@ -0,0 +1,208 @@ +################### +### Same thread ### +################### +From top +--------- + getStackTrace (Ljava/lang/Thread;II)[Ljava/lang/String; + print (Ljava/lang/Thread;II)V + printOrWait (IILMain$ControlData;)V + baz (IIILMain$ControlData;)Ljava/lang/Object; + bar (IIILMain$ControlData;)J + foo (IIILMain$ControlData;)I + baz (IIILMain$ControlData;)Ljava/lang/Object; + bar (IIILMain$ControlData;)J + foo (IIILMain$ControlData;)I + baz (IIILMain$ControlData;)Ljava/lang/Object; + bar (IIILMain$ControlData;)J + foo (IIILMain$ControlData;)I + baz (IIILMain$ControlData;)Ljava/lang/Object; + bar (IIILMain$ControlData;)J + foo (IIILMain$ControlData;)I + baz (IIILMain$ControlData;)Ljava/lang/Object; + bar (IIILMain$ControlData;)J + foo (IIILMain$ControlData;)I + doTest ()V + main ([Ljava/lang/String;)V +--------- + print (Ljava/lang/Thread;II)V + printOrWait (IILMain$ControlData;)V + baz (IIILMain$ControlData;)Ljava/lang/Object; + bar (IIILMain$ControlData;)J + foo (IIILMain$ControlData;)I + baz (IIILMain$ControlData;)Ljava/lang/Object; + bar (IIILMain$ControlData;)J + foo (IIILMain$ControlData;)I + baz (IIILMain$ControlData;)Ljava/lang/Object; + bar (IIILMain$ControlData;)J + foo (IIILMain$ControlData;)I + baz (IIILMain$ControlData;)Ljava/lang/Object; + bar (IIILMain$ControlData;)J + foo (IIILMain$ControlData;)I + baz (IIILMain$ControlData;)Ljava/lang/Object; + bar (IIILMain$ControlData;)J + foo (IIILMain$ControlData;)I + doTest ()V + main ([Ljava/lang/String;)V +--------- + getStackTrace (Ljava/lang/Thread;II)[Ljava/lang/String; + print (Ljava/lang/Thread;II)V + printOrWait (IILMain$ControlData;)V + baz (IIILMain$ControlData;)Ljava/lang/Object; + bar (IIILMain$ControlData;)J +--------- + printOrWait (IILMain$ControlData;)V + baz (IIILMain$ControlData;)Ljava/lang/Object; + bar (IIILMain$ControlData;)J + foo (IIILMain$ControlData;)I + baz (IIILMain$ControlData;)Ljava/lang/Object; +From bottom +--------- + main ([Ljava/lang/String;)V +--------- + baz (IIILMain$ControlData;)Ljava/lang/Object; + bar (IIILMain$ControlData;)J + foo (IIILMain$ControlData;)I + doTest ()V + main ([Ljava/lang/String;)V +--------- + bar (IIILMain$ControlData;)J + foo (IIILMain$ControlData;)I + baz (IIILMain$ControlData;)Ljava/lang/Object; + bar (IIILMain$ControlData;)J + foo (IIILMain$ControlData;)I + +################################ +### Other thread (suspended) ### +################################ +From top +--------- + wait ()V + printOrWait (IILMain$ControlData;)V + baz (IIILMain$ControlData;)Ljava/lang/Object; + bar (IIILMain$ControlData;)J + foo (IIILMain$ControlData;)I + baz (IIILMain$ControlData;)Ljava/lang/Object; + bar (IIILMain$ControlData;)J + foo (IIILMain$ControlData;)I + baz (IIILMain$ControlData;)Ljava/lang/Object; + bar (IIILMain$ControlData;)J + foo (IIILMain$ControlData;)I + baz (IIILMain$ControlData;)Ljava/lang/Object; + bar (IIILMain$ControlData;)J + foo (IIILMain$ControlData;)I + baz (IIILMain$ControlData;)Ljava/lang/Object; + bar (IIILMain$ControlData;)J + foo (IIILMain$ControlData;)I + run ()V +--------- + printOrWait (IILMain$ControlData;)V + baz (IIILMain$ControlData;)Ljava/lang/Object; + bar (IIILMain$ControlData;)J + foo (IIILMain$ControlData;)I + baz (IIILMain$ControlData;)Ljava/lang/Object; + bar (IIILMain$ControlData;)J + foo (IIILMain$ControlData;)I + baz (IIILMain$ControlData;)Ljava/lang/Object; + bar (IIILMain$ControlData;)J + foo (IIILMain$ControlData;)I + baz (IIILMain$ControlData;)Ljava/lang/Object; + bar (IIILMain$ControlData;)J + foo (IIILMain$ControlData;)I + baz (IIILMain$ControlData;)Ljava/lang/Object; + bar (IIILMain$ControlData;)J + foo (IIILMain$ControlData;)I + run ()V +--------- + wait ()V + printOrWait (IILMain$ControlData;)V + baz (IIILMain$ControlData;)Ljava/lang/Object; + bar (IIILMain$ControlData;)J + foo (IIILMain$ControlData;)I +--------- + baz (IIILMain$ControlData;)Ljava/lang/Object; + bar (IIILMain$ControlData;)J + foo (IIILMain$ControlData;)I + baz (IIILMain$ControlData;)Ljava/lang/Object; + bar (IIILMain$ControlData;)J +From bottom +--------- + run ()V +--------- + foo (IIILMain$ControlData;)I + baz (IIILMain$ControlData;)Ljava/lang/Object; + bar (IIILMain$ControlData;)J + foo (IIILMain$ControlData;)I + run ()V +--------- + baz (IIILMain$ControlData;)Ljava/lang/Object; + bar (IIILMain$ControlData;)J + foo (IIILMain$ControlData;)I + baz (IIILMain$ControlData;)Ljava/lang/Object; + bar (IIILMain$ControlData;)J + +########################### +### Other thread (live) ### +########################### +From top +--------- + printOrWait (IILMain$ControlData;)V + baz (IIILMain$ControlData;)Ljava/lang/Object; + bar (IIILMain$ControlData;)J + foo (IIILMain$ControlData;)I + baz (IIILMain$ControlData;)Ljava/lang/Object; + bar (IIILMain$ControlData;)J + foo (IIILMain$ControlData;)I + baz (IIILMain$ControlData;)Ljava/lang/Object; + bar (IIILMain$ControlData;)J + foo (IIILMain$ControlData;)I + baz (IIILMain$ControlData;)Ljava/lang/Object; + bar (IIILMain$ControlData;)J + foo (IIILMain$ControlData;)I + baz (IIILMain$ControlData;)Ljava/lang/Object; + bar (IIILMain$ControlData;)J + foo (IIILMain$ControlData;)I + run ()V +--------- + baz (IIILMain$ControlData;)Ljava/lang/Object; + bar (IIILMain$ControlData;)J + foo (IIILMain$ControlData;)I + baz (IIILMain$ControlData;)Ljava/lang/Object; + bar (IIILMain$ControlData;)J + foo (IIILMain$ControlData;)I + baz (IIILMain$ControlData;)Ljava/lang/Object; + bar (IIILMain$ControlData;)J + foo (IIILMain$ControlData;)I + baz (IIILMain$ControlData;)Ljava/lang/Object; + bar (IIILMain$ControlData;)J + foo (IIILMain$ControlData;)I + baz (IIILMain$ControlData;)Ljava/lang/Object; + bar (IIILMain$ControlData;)J + foo (IIILMain$ControlData;)I + run ()V +--------- + printOrWait (IILMain$ControlData;)V + baz (IIILMain$ControlData;)Ljava/lang/Object; + bar (IIILMain$ControlData;)J + foo (IIILMain$ControlData;)I + baz (IIILMain$ControlData;)Ljava/lang/Object; +--------- + bar (IIILMain$ControlData;)J + foo (IIILMain$ControlData;)I + baz (IIILMain$ControlData;)Ljava/lang/Object; + bar (IIILMain$ControlData;)J + foo (IIILMain$ControlData;)I +From bottom +--------- + run ()V +--------- + foo (IIILMain$ControlData;)I + baz (IIILMain$ControlData;)Ljava/lang/Object; + bar (IIILMain$ControlData;)J + foo (IIILMain$ControlData;)I + run ()V +--------- + baz (IIILMain$ControlData;)Ljava/lang/Object; + bar (IIILMain$ControlData;)J + foo (IIILMain$ControlData;)I + baz (IIILMain$ControlData;)Ljava/lang/Object; + bar (IIILMain$ControlData;)J diff --git a/test/911-get-stack-trace/info.txt b/test/911-get-stack-trace/info.txt new file mode 100644 index 0000000000..875a5f6ec1 --- /dev/null +++ b/test/911-get-stack-trace/info.txt @@ -0,0 +1 @@ +Tests basic functions in the jvmti plugin. diff --git a/test/911-get-stack-trace/run b/test/911-get-stack-trace/run new file mode 100755 index 0000000000..43fc325363 --- /dev/null +++ b/test/911-get-stack-trace/run @@ -0,0 +1,43 @@ +#!/bin/bash +# +# Copyright 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. + +plugin=libopenjdkjvmtid.so +agent=libtiagentd.so +lib=tiagentd +if [[ "$@" == *"-O"* ]]; then + agent=libtiagent.so + plugin=libopenjdkjvmti.so + lib=tiagent +fi + +if [[ "$@" == *"--jvm"* ]]; then + arg="jvm" +else + arg="art" +fi + +if [[ "$@" != *"--debuggable"* ]]; then + other_args=" -Xcompiler-option --debuggable " +else + other_args="" +fi + +./default-run "$@" --experimental agents \ + --experimental runtime-plugins \ + --runtime-option -agentpath:${agent}=911-get-stack-trace,${arg} \ + --android-runtime-option -Xplugin:${plugin} \ + ${other_args} \ + --args ${lib} diff --git a/test/911-get-stack-trace/src/Main.java b/test/911-get-stack-trace/src/Main.java new file mode 100644 index 0000000000..df4501d338 --- /dev/null +++ b/test/911-get-stack-trace/src/Main.java @@ -0,0 +1,178 @@ +/* + * 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. + */ + +import java.util.Arrays; +import java.util.concurrent.CountDownLatch; + +public class Main { + public static void main(String[] args) throws Exception { + System.loadLibrary(args[1]); + + doTest(); + doTestOtherThreadWait(); + doTestOtherThreadBusyLoop(); + } + + public static void doTest() throws Exception { + System.out.println("###################"); + System.out.println("### Same thread ###"); + System.out.println("###################"); + System.out.println("From top"); + Recurse.foo(4, 0, 25, null); + Recurse.foo(4, 1, 25, null); + Recurse.foo(4, 0, 5, null); + Recurse.foo(4, 2, 5, null); + + System.out.println("From bottom"); + Recurse.foo(4, -1, 25, null); + Recurse.foo(4, -5, 5, null); + Recurse.foo(4, -7, 5, null); + } + + public static void doTestOtherThreadWait() throws Exception { + System.out.println(); + 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... + + System.out.println("From top"); + print(t, 0, 25); + print(t, 1, 25); + print(t, 0, 5); + print(t, 2, 5); + + System.out.println("From bottom"); + print(t, -1, 25); + print(t, -5, 5); + print(t, -7, 5); + + // 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("###########################"); + 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... + + System.out.println("From top"); + print(t, 0, 25); + print(t, 1, 25); + print(t, 0, 5); + print(t, 2, 5); + + System.out.println("From bottom"); + print(t, -1, 25); + print(t, -5, 5); + print(t, -7, 5); + + // Let the thread stop looping and die. + data.stop = true; + t.join(); + } + + public static void print(String[] stack) { + System.out.println("---------"); + for (int i = 0; i < stack.length; i += 2) { + System.out.print(' '); + System.out.print(stack[i]); + System.out.print(' '); + System.out.println(stack[i + 1]); + } + } + + public static void print(Thread t, int start, int max) { + print(getStackTrace(t, start, max)); + } + + // Wrap generated stack traces into a class to separate them nicely. + public static class Recurse { + + public static int foo(int x, int start, int max, ControlData data) { + bar(x, start, max, data); + return 0; + } + + private static long bar(int x, int start, int max, ControlData data) { + baz(x, start, max, data); + return 0; + } + + private static Object baz(int x, int start, int max, ControlData data) { + if (x == 0) { + printOrWait(start, max, data); + } else { + foo(x - 1, start, max, data); + } + return null; + } + + private static void printOrWait(int start, int max, ControlData data) { + if (data == null) { + print(Thread.currentThread(), start, max); + } else { + if (data.waitFor != null) { + synchronized (data.waitFor) { + data.reached.countDown(); + try { + data.waitFor.wait(); // Use wait() as it doesn't have a "hidden" Java call-graph. + } catch (Throwable t) { + throw new RuntimeException(t); + } + } + } else { + data.reached.countDown(); + while (!data.stop) { + // Busy-loop. + } + } + } + } + } + + public static class ControlData { + CountDownLatch reached = new CountDownLatch(1); + Object waitFor = null; + volatile boolean stop = false; + } + + public static native String[] getStackTrace(Thread thread, int start, int max); +} diff --git a/test/911-get-stack-trace/stack_trace.cc b/test/911-get-stack-trace/stack_trace.cc new file mode 100644 index 0000000000..da649cf8c9 --- /dev/null +++ b/test/911-get-stack-trace/stack_trace.cc @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2013 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. + */ + +#include "stack_trace.h" + +#include <memory> +#include <stdio.h> + +#include "base/logging.h" +#include "jni.h" +#include "openjdkjvmti/jvmti.h" +#include "ScopedLocalRef.h" +#include "ti-agent/common_load.h" + +namespace art { +namespace Test911GetStackTrace { + +extern "C" JNIEXPORT jobjectArray JNICALL Java_Main_getStackTrace( + JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jthread thread, jint start, jint max) { + std::unique_ptr<jvmtiFrameInfo[]> frames(new jvmtiFrameInfo[max]); + + 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); + return nullptr; + } + + ScopedLocalRef<jclass> obj_class(env, env->FindClass("java/lang/String")); + if (obj_class.get() == nullptr) { + return nullptr; + } + + jobjectArray ret = env->NewObjectArray(2 * count, obj_class.get(), nullptr); + if (ret == nullptr) { + return ret; + } + + for (size_t i = 0; i < static_cast<size_t>(count); ++i) { + char* name; + char* sig; + char* gen; + jvmtiError result2 = jvmti_env->GetMethodName(frames[i].method, &name, &sig, &gen); + if (result2 != JVMTI_ERROR_NONE) { + char* err; + jvmti_env->GetErrorName(result, &err); + printf("Failure running GetMethodName: %s\n", err); + return nullptr; + } + ScopedLocalRef<jstring> trace_name(env, name == nullptr ? nullptr : env->NewStringUTF(name)); + ScopedLocalRef<jstring> trace_sig(env, sig == nullptr ? nullptr : env->NewStringUTF(sig)); + env->SetObjectArrayElement(ret, static_cast<jint>(2 * i), trace_name.get()); + env->SetObjectArrayElement(ret, static_cast<jint>(2 * i + 1), trace_sig.get()); + + if (name != nullptr) { + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(name)); + } + if (sig != nullptr) { + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(sig)); + } + if (gen != nullptr) { + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(gen)); + } + } + + return ret; +} + +// Don't do anything +jint OnLoad(JavaVM* vm, + char* options ATTRIBUTE_UNUSED, + void* reserved ATTRIBUTE_UNUSED) { + if (vm->GetEnv(reinterpret_cast<void**>(&jvmti_env), JVMTI_VERSION_1_0)) { + printf("Unable to get jvmti env!\n"); + return 1; + } + return 0; +} + +} // namespace Test911GetStackTrace +} // namespace art diff --git a/test/911-get-stack-trace/stack_trace.h b/test/911-get-stack-trace/stack_trace.h new file mode 100644 index 0000000000..eba2a91da1 --- /dev/null +++ b/test/911-get-stack-trace/stack_trace.h @@ -0,0 +1,30 @@ +/* + * 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. + */ + +#ifndef ART_TEST_911_GET_STACK_TRACE_STACK_TRACE_H_ +#define ART_TEST_911_GET_STACK_TRACE_STACK_TRACE_H_ + +#include <jni.h> + +namespace art { +namespace Test911GetStackTrace { + +jint OnLoad(JavaVM* vm, char* options, void* reserved); + +} // namespace Test911GetStackTrace +} // namespace art + +#endif // ART_TEST_911_GET_STACK_TRACE_STACK_TRACE_H_ diff --git a/test/Android.bp b/test/Android.bp index 9675ef52b4..37e1fc0dd7 100644 --- a/test/Android.bp +++ b/test/Android.bp @@ -254,6 +254,7 @@ art_cc_defaults { "908-gc-start-finish/gc_callbacks.cc", "909-attach-agent/attach.cc", "910-methods/methods.cc", + "911-get-stack-trace/stack_trace.cc", ], shared_libs: [ "libbase", diff --git a/test/ti-agent/common_load.cc b/test/ti-agent/common_load.cc index 5e8ef94334..7faf700b6e 100644 --- a/test/ti-agent/common_load.cc +++ b/test/ti-agent/common_load.cc @@ -34,6 +34,7 @@ #include "908-gc-start-finish/gc_callbacks.h" #include "909-attach-agent/attach.h" #include "910-methods/methods.h" +#include "911-get-stack-trace/stack_trace.h" namespace art { @@ -60,6 +61,7 @@ AgentLib agents[] = { { "908-gc-start-finish", Test908GcStartFinish::OnLoad, nullptr }, { "909-attach-agent", nullptr, Test909AttachAgent::OnAttach }, { "910-methods", Test910Methods::OnLoad, nullptr }, + { "911-get-stack-trace", Test911GetStackTrace::OnLoad, nullptr }, }; static AgentLib* FindAgent(char* name) { |