summaryrefslogtreecommitdiff
path: root/runtime/instrumentation.h
diff options
context:
space:
mode:
Diffstat (limited to 'runtime/instrumentation.h')
-rw-r--r--runtime/instrumentation.h259
1 files changed, 130 insertions, 129 deletions
diff --git a/runtime/instrumentation.h b/runtime/instrumentation.h
index c811935e9d..b31d0da671 100644
--- a/runtime/instrumentation.h
+++ b/runtime/instrumentation.h
@@ -23,6 +23,7 @@
#include <list>
#include <memory>
#include <optional>
+#include <queue>
#include <unordered_set>
#include "arch/instruction_set.h"
@@ -31,6 +32,7 @@
#include "base/macros.h"
#include "base/safe_map.h"
#include "gc_root.h"
+#include "jvalue.h"
#include "offsets.h"
namespace art {
@@ -45,6 +47,7 @@ template <typename T> class Handle;
template <typename T> class MutableHandle;
struct NthCallerVisitor;
union JValue;
+class OatQuickMethodHeader;
class SHARED_LOCKABLE ReaderWriterMutex;
class ShadowFrame;
class Thread;
@@ -92,7 +95,6 @@ struct InstrumentationListener {
// Call-back for when a method is popped due to an exception throw. A method will either cause a
// MethodExited call-back or a MethodUnwind call-back when its activation is removed.
virtual void MethodUnwind(Thread* thread,
- Handle<mirror::Object> this_object,
ArtMethod* method,
uint32_t dex_pc)
REQUIRES_SHARED(Locks::mutator_lock_) = 0;
@@ -192,20 +194,37 @@ class Instrumentation {
};
enum class InstrumentationLevel {
- kInstrumentNothing, // execute without instrumentation
- kInstrumentWithInstrumentationStubs, // execute with instrumentation entry/exit stubs
- kInstrumentWithInterpreter // execute with interpreter
+ kInstrumentNothing, // execute without instrumentation
+ kInstrumentWithEntryExitHooks, // execute with entry/exit hooks
+ kInstrumentWithInterpreter // execute with interpreter
};
Instrumentation();
- static constexpr MemberOffset NeedsEntryExitHooksOffset() {
- // Assert that instrumentation_stubs_installed_ is 8bits wide. If the size changes
+ static constexpr MemberOffset RunExitHooksOffset() {
+ // Assert that run_entry_exit_hooks_ is 8bits wide. If the size changes
+ // update the compare instructions in the code generator when generating checks for
+ // MethodEntryExitHooks.
+ static_assert(sizeof(run_exit_hooks_) == 1, "run_exit_hooks_ isn't expected size");
+ return MemberOffset(OFFSETOF_MEMBER(Instrumentation, run_exit_hooks_));
+ }
+
+ static constexpr MemberOffset HaveMethodEntryListenersOffset() {
+ // Assert that have_method_entry_listeners_ is 8bits wide. If the size changes
// update the compare instructions in the code generator when generating checks for
// MethodEntryExitHooks.
- static_assert(sizeof(instrumentation_stubs_installed_) == 1,
- "instrumentation_stubs_installed_ isn't expected size");
- return MemberOffset(OFFSETOF_MEMBER(Instrumentation, instrumentation_stubs_installed_));
+ static_assert(sizeof(have_method_entry_listeners_) == 1,
+ "have_method_entry_listeners_ isn't expected size");
+ return MemberOffset(OFFSETOF_MEMBER(Instrumentation, have_method_entry_listeners_));
+ }
+
+ static constexpr MemberOffset HaveMethodExitListenersOffset() {
+ // Assert that have_method_exit_listeners_ is 8bits wide. If the size changes
+ // update the compare instructions in the code generator when generating checks for
+ // MethodEntryExitHooks.
+ static_assert(sizeof(have_method_exit_listeners_) == 1,
+ "have_method_exit_listeners_ isn't expected size");
+ return MemberOffset(OFFSETOF_MEMBER(Instrumentation, have_method_exit_listeners_));
}
// Add a listener to be notified of the masked together sent of instrumentation events. This
@@ -215,14 +234,23 @@ class Instrumentation {
void AddListener(InstrumentationListener* listener, uint32_t events)
REQUIRES(Locks::mutator_lock_, !Locks::thread_list_lock_, !Locks::classlinker_classes_lock_);
- // Removes a listener possibly removing instrumentation stubs.
+ // Removes listeners for the specified events.
void RemoveListener(InstrumentationListener* listener, uint32_t events)
REQUIRES(Locks::mutator_lock_, !Locks::thread_list_lock_, !Locks::classlinker_classes_lock_);
// Calls UndeoptimizeEverything which may visit class linker classes through ConfigureStubs.
void DisableDeoptimization(const char* key)
- REQUIRES(Locks::mutator_lock_, Roles::uninterruptible_)
- REQUIRES(!GetDeoptimizedMethodsLock());
+ REQUIRES(Locks::mutator_lock_, Roles::uninterruptible_);
+
+ // Enables entry exit hooks support. This is called in preparation for debug requests that require
+ // calling method entry / exit hooks.
+ void EnableEntryExitHooks(const char* key)
+ REQUIRES(Locks::mutator_lock_, Roles::uninterruptible_);
+
+ // Switches the runtime state to non-java debuggable if entry / exit hooks are no longer required
+ // and the runtime did not start off as java debuggable.
+ void MaybeSwitchRuntimeDebugState(Thread* self)
+ REQUIRES(Locks::mutator_lock_, Roles::uninterruptible_);
bool AreAllMethodsDeoptimized() const {
return InterpreterStubsInstalled();
@@ -233,52 +261,44 @@ class Instrumentation {
void DeoptimizeEverything(const char* key)
REQUIRES(Locks::mutator_lock_, Roles::uninterruptible_)
REQUIRES(!Locks::thread_list_lock_,
- !Locks::classlinker_classes_lock_,
- !GetDeoptimizedMethodsLock());
+ !Locks::classlinker_classes_lock_);
// Executes everything with compiled code (or interpreter if there is no code). May visit class
// linker classes through ConfigureStubs.
void UndeoptimizeEverything(const char* key)
REQUIRES(Locks::mutator_lock_, Roles::uninterruptible_)
REQUIRES(!Locks::thread_list_lock_,
- !Locks::classlinker_classes_lock_,
- !GetDeoptimizedMethodsLock());
+ !Locks::classlinker_classes_lock_);
// Deoptimize a method by forcing its execution with the interpreter. Nevertheless, a static
// method (except a class initializer) set to the resolution trampoline will be deoptimized only
// once its declaring class is initialized.
- void Deoptimize(ArtMethod* method)
- REQUIRES(Locks::mutator_lock_, !Locks::thread_list_lock_, !GetDeoptimizedMethodsLock());
+ void Deoptimize(ArtMethod* method) REQUIRES(Locks::mutator_lock_, !Locks::thread_list_lock_);
// Undeoptimze the method by restoring its entrypoints. Nevertheless, a static method
// (except a class initializer) set to the resolution trampoline will be updated only once its
// declaring class is initialized.
- void Undeoptimize(ArtMethod* method)
- REQUIRES(Locks::mutator_lock_, !Locks::thread_list_lock_, !GetDeoptimizedMethodsLock());
+ void Undeoptimize(ArtMethod* method) REQUIRES(Locks::mutator_lock_, !Locks::thread_list_lock_);
// Indicates whether the method has been deoptimized so it is executed with the interpreter.
- bool IsDeoptimized(ArtMethod* method)
- REQUIRES(!GetDeoptimizedMethodsLock()) REQUIRES_SHARED(Locks::mutator_lock_);
+ bool IsDeoptimized(ArtMethod* method) REQUIRES_SHARED(Locks::mutator_lock_);
// Indicates if any method needs to be deoptimized. This is used to avoid walking the stack to
// determine if a deoptimization is required.
- bool IsDeoptimizedMethodsEmpty() const
- REQUIRES(!GetDeoptimizedMethodsLock()) REQUIRES_SHARED(Locks::mutator_lock_);
+ bool IsDeoptimizedMethodsEmpty() const REQUIRES_SHARED(Locks::mutator_lock_);
// Enable method tracing by installing instrumentation entry/exit stubs or interpreter.
void EnableMethodTracing(const char* key,
+ InstrumentationListener* listener,
bool needs_interpreter = kDeoptimizeForAccurateMethodEntryExitListeners)
REQUIRES(Locks::mutator_lock_, Roles::uninterruptible_)
- REQUIRES(!Locks::thread_list_lock_,
- !Locks::classlinker_classes_lock_,
- !GetDeoptimizedMethodsLock());
+ REQUIRES(!Locks::thread_list_lock_, !Locks::classlinker_classes_lock_);
// Disable method tracing by uninstalling instrumentation entry/exit stubs or interpreter.
void DisableMethodTracing(const char* key)
REQUIRES(Locks::mutator_lock_, Roles::uninterruptible_)
REQUIRES(!Locks::thread_list_lock_,
- !Locks::classlinker_classes_lock_,
- !GetDeoptimizedMethodsLock());
+ !Locks::classlinker_classes_lock_);
void InstrumentQuickAllocEntryPoints() REQUIRES(!Locks::instrument_entrypoints_lock_);
@@ -300,11 +320,11 @@ class Instrumentation {
// Update the code of a method respecting any installed stubs.
void UpdateMethodsCode(ArtMethod* method, const void* new_code)
- REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!GetDeoptimizedMethodsLock());
+ REQUIRES_SHARED(Locks::mutator_lock_);
// Update the code of a native method to a JITed stub.
void UpdateNativeMethodsCodeToJitCode(ArtMethod* method, const void* new_code)
- REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!GetDeoptimizedMethodsLock());
+ REQUIRES_SHARED(Locks::mutator_lock_);
// Return the code that we can execute for an invoke including from the JIT.
const void* GetCodeForInvoke(ArtMethod* method) REQUIRES_SHARED(Locks::mutator_lock_);
@@ -321,7 +341,7 @@ class Instrumentation {
}
bool EntryExitStubsInstalled() const {
- return instrumentation_level_ == InstrumentationLevel::kInstrumentWithInstrumentationStubs ||
+ return instrumentation_level_ == InstrumentationLevel::kInstrumentWithEntryExitHooks ||
instrumentation_level_ == InstrumentationLevel::kInstrumentWithInterpreter;
}
@@ -339,8 +359,8 @@ class Instrumentation {
return forced_interpret_only_;
}
- bool AreExitStubsInstalled() const {
- return instrumentation_stubs_installed_;
+ bool RunExitHooks() const {
+ return run_exit_hooks_;
}
bool HasMethodEntryListeners() const REQUIRES_SHARED(Locks::mutator_lock_) {
@@ -383,6 +403,20 @@ class Instrumentation {
return have_exception_handled_listeners_;
}
+ // Returns if dex pc events need to be reported for the specified method.
+ // These events are reported when DexPCListeners are installed and at least one of the
+ // following conditions hold:
+ // 1. The method is deoptimized. This is done when there is a breakpoint on method.
+ // 2. When the thread is deoptimized. This is used when single stepping a single thread.
+ // 3. When interpreter stubs are installed. In this case no additional information is maintained
+ // about which methods need dex pc move events. This is usually used for features which need
+ // them for several methods across threads or need expensive processing. So it is OK to not
+ // further optimize this case.
+ // DexPCListeners are installed when there is a breakpoint on any method / single stepping
+ // on any of thread. These are removed when the last breakpoint was removed. See AddListener and
+ // RemoveListener for more details.
+ bool NeedsDexPcEvents(ArtMethod* method, Thread* thread) REQUIRES_SHARED(Locks::mutator_lock_);
+
bool NeedsSlowInterpreterForListeners() const REQUIRES_SHARED(Locks::mutator_lock_) {
return have_field_read_listeners_ ||
have_field_write_listeners_ ||
@@ -413,7 +447,6 @@ class Instrumentation {
// Inform listeners that a method has been exited due to an exception.
void MethodUnwindEvent(Thread* thread,
- ObjPtr<mirror::Object> this_object,
ArtMethod* method,
uint32_t dex_pc) const
REQUIRES_SHARED(Locks::mutator_lock_);
@@ -479,50 +512,37 @@ class Instrumentation {
void ExceptionHandledEvent(Thread* thread, ObjPtr<mirror::Throwable> exception_object) const
REQUIRES_SHARED(Locks::mutator_lock_);
- JValue GetReturnValue(Thread* self,
- ArtMethod* method,
- bool* is_ref,
- uint64_t* gpr_result,
- uint64_t* fpr_result) REQUIRES_SHARED(Locks::mutator_lock_);
- bool ShouldDeoptimizeMethod(Thread* self, const NthCallerVisitor& visitor)
+ JValue GetReturnValue(ArtMethod* method, bool* is_ref, uint64_t* gpr_result, uint64_t* fpr_result)
REQUIRES_SHARED(Locks::mutator_lock_);
-
- // Called when an instrumented method is entered. The intended link register (lr) is saved so
- // that returning causes a branch to the method exit stub. Generates method enter events.
- void PushInstrumentationStackFrame(Thread* self,
- ObjPtr<mirror::Object> this_object,
- ArtMethod* method,
- uintptr_t stack_pointer,
- uintptr_t lr,
- bool interpreter_entry)
+ bool PushDeoptContextIfNeeded(Thread* self,
+ DeoptimizationMethodType deopt_type,
+ bool is_ref,
+ const JValue& result) REQUIRES_SHARED(Locks::mutator_lock_);
+ void DeoptimizeIfNeeded(Thread* self,
+ ArtMethod** sp,
+ DeoptimizationMethodType type,
+ JValue result,
+ bool is_ref) REQUIRES_SHARED(Locks::mutator_lock_);
+ // This returns if the caller of runtime method requires a deoptimization. This checks both if the
+ // method requires a deopt or if this particular frame needs a deopt because of a class
+ // redefinition.
+ bool ShouldDeoptimizeCaller(Thread* self, ArtMethod** sp) REQUIRES_SHARED(Locks::mutator_lock_);
+ bool ShouldDeoptimizeCaller(Thread* self, ArtMethod** sp, size_t frame_size)
REQUIRES_SHARED(Locks::mutator_lock_);
-
- DeoptimizationMethodType GetDeoptimizationMethodType(ArtMethod* method)
+ // This returns if the specified method requires a deoptimization. This doesn't account if a stack
+ // frame involving this method requires a deoptimization.
+ bool NeedsSlowInterpreterForMethod(Thread* self, ArtMethod* method)
REQUIRES_SHARED(Locks::mutator_lock_);
- // Called when an instrumented method is exited. Removes the pushed instrumentation frame
- // returning the intended link register. Generates method exit events. The gpr_result and
- // fpr_result pointers are pointers to the locations where the integer/pointer and floating point
- // result values of the function are stored. Both pointers must always be valid but the values
- // held there will only be meaningful if interpreted as the appropriate type given the function
- // being returned from.
- TwoWordReturn PopInstrumentationStackFrame(Thread* self,
- uintptr_t* return_pc_addr,
- uint64_t* gpr_result,
- uint64_t* fpr_result)
- REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!GetDeoptimizedMethodsLock());
-
- // Pops nframes instrumentation frames from the current thread. Returns the return pc for the last
- // instrumentation frame that's popped.
- uintptr_t PopFramesForDeoptimization(Thread* self, uintptr_t stack_pointer) const
+ DeoptimizationMethodType GetDeoptimizationMethodType(ArtMethod* method)
REQUIRES_SHARED(Locks::mutator_lock_);
// Call back for configure stubs.
- void InstallStubsForClass(ObjPtr<mirror::Class> klass) REQUIRES_SHARED(Locks::mutator_lock_)
- REQUIRES(!GetDeoptimizedMethodsLock());
+ void InstallStubsForClass(ObjPtr<mirror::Class> klass) REQUIRES_SHARED(Locks::mutator_lock_);
+
+ void InstallStubsForMethod(ArtMethod* method) REQUIRES_SHARED(Locks::mutator_lock_);
- void InstallStubsForMethod(ArtMethod* method)
- REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!GetDeoptimizedMethodsLock());
+ void UpdateEntrypointsForDebuggable() REQUIRES(art::Locks::mutator_lock_);
// Install instrumentation exit stub on every method of the stack of the given thread.
// This is used by:
@@ -532,6 +552,8 @@ class Instrumentation {
// the stack frame to run entry / exit hooks but we don't need to deoptimize.
// deopt_all_frames indicates whether the frames need to deoptimize or not.
void InstrumentThreadStack(Thread* thread, bool deopt_all_frames) REQUIRES(Locks::mutator_lock_);
+ void InstrumentAllThreadStacks(bool deopt_all_frames) REQUIRES(Locks::mutator_lock_)
+ REQUIRES(!Locks::thread_list_lock_);
// Force all currently running frames to be deoptimized back to interpreter. This should only be
// used in cases where basically all compiled code has been invalidated.
@@ -548,18 +570,21 @@ class Instrumentation {
return alloc_entrypoints_instrumented_;
}
+ bool ProcessMethodUnwindCallbacks(Thread* self,
+ std::queue<ArtMethod*>& methods,
+ MutableHandle<mirror::Throwable>& exception)
+ REQUIRES_SHARED(Locks::mutator_lock_);
+
InstrumentationLevel GetCurrentInstrumentationLevel() const;
+ bool MethodSupportsExitEvents(ArtMethod* method, const OatQuickMethodHeader* header)
+ REQUIRES_SHARED(Locks::mutator_lock_);
+
private:
// Returns true if moving to the given instrumentation level requires the installation of stubs.
// False otherwise.
bool RequiresInstrumentationInstallation(InstrumentationLevel new_level) const;
- // Returns true if we need entry exit stub to call entry hooks. JITed code
- // directly call entry / exit hooks and don't need the stub.
- static bool CodeNeedsEntryExitStub(const void* code, ArtMethod* method)
- REQUIRES_SHARED(Locks::mutator_lock_);
-
// Update the current instrumentation_level_.
void UpdateInstrumentationLevel(InstrumentationLevel level);
@@ -570,12 +595,10 @@ class Instrumentation {
// becomes the highest instrumentation level required by a client.
void ConfigureStubs(const char* key, InstrumentationLevel desired_instrumentation_level)
REQUIRES(Locks::mutator_lock_, Roles::uninterruptible_)
- REQUIRES(!GetDeoptimizedMethodsLock(),
- !Locks::thread_list_lock_,
+ REQUIRES(!Locks::thread_list_lock_,
!Locks::classlinker_classes_lock_);
void UpdateStubs() REQUIRES(Locks::mutator_lock_, Roles::uninterruptible_)
- REQUIRES(!GetDeoptimizedMethodsLock(),
- !Locks::thread_list_lock_,
+ REQUIRES(!Locks::thread_list_lock_,
!Locks::classlinker_classes_lock_);
// If there are no pending deoptimizations restores the stack to the normal state by updating the
@@ -619,34 +642,36 @@ class Instrumentation {
REQUIRES_SHARED(Locks::mutator_lock_);
// Read barrier-aware utility functions for accessing deoptimized_methods_
- bool AddDeoptimizedMethod(ArtMethod* method)
- REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(GetDeoptimizedMethodsLock());
- bool IsDeoptimizedMethod(ArtMethod* method)
- REQUIRES_SHARED(Locks::mutator_lock_, GetDeoptimizedMethodsLock());
- bool RemoveDeoptimizedMethod(ArtMethod* method)
- REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(GetDeoptimizedMethodsLock());
- ArtMethod* BeginDeoptimizedMethod()
- REQUIRES_SHARED(Locks::mutator_lock_, GetDeoptimizedMethodsLock());
- bool IsDeoptimizedMethodsEmptyLocked() const
- REQUIRES_SHARED(Locks::mutator_lock_, GetDeoptimizedMethodsLock());
+ bool AddDeoptimizedMethod(ArtMethod* method) REQUIRES(Locks::mutator_lock_);
+ bool IsDeoptimizedMethod(ArtMethod* method) REQUIRES_SHARED(Locks::mutator_lock_);
+ bool RemoveDeoptimizedMethod(ArtMethod* method) REQUIRES(Locks::mutator_lock_);
void UpdateMethodsCodeImpl(ArtMethod* method, const void* new_code)
- REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!GetDeoptimizedMethodsLock());
-
- ReaderWriterMutex* GetDeoptimizedMethodsLock() const {
- return deoptimized_methods_lock_.get();
- }
-
- // A counter that's incremented every time a DeoptimizeAllFrames. We check each
- // InstrumentationStackFrames creation id against this number and if they differ we deopt even if
- // we could otherwise continue running.
- uint64_t current_force_deopt_id_ GUARDED_BY(Locks::mutator_lock_);
+ REQUIRES_SHARED(Locks::mutator_lock_);
- // Have we hijacked ArtMethod::code_ so that it calls instrumentation/interpreter code?
- bool instrumentation_stubs_installed_;
+ // We need to run method exit hooks for two reasons:
+ // 1. When method exit listeners are installed
+ // 2. When we need to check if the caller of this method needs a deoptimization. This is needed
+ // only for deoptimizing the currently active invocations on stack when we deoptimize a method or
+ // invalidate the JITed code when redefining the classes. So future invocations don't need to do
+ // this check.
+ //
+ // For JITed code of non-native methods we already have a stack slot reserved for deoptimizing
+ // on demand and we use that stack slot to check if the caller needs a deoptimization. JITed code
+ // checks if there are any method exit listeners or if the stack slot is set to determine if
+ // method exit hooks need to be executed.
+ //
+ // For JITed JNI stubs there is no reserved stack slot for this and we just use this variable to
+ // check if we need to run method entry / exit hooks. This variable would be set when either of
+ // the above conditions are true. If we need method exit hooks only for case 2, we would call exit
+ // hooks for any future invocations which aren't necessary.
+ // QuickToInterpreterBridge and GenericJniStub also use this for same reasons.
+ // If calling entry / exit hooks becomes expensive we could do the same optimization we did for
+ // JITed code by having a reserved stack slot.
+ bool run_exit_hooks_;
// The required level of instrumentation. This could be one of the following values:
// kInstrumentNothing: no instrumentation support is needed
- // kInstrumentWithInstrumentationStubs: needs support to call method entry/exit stubs.
+ // kInstrumentWithEntryExitHooks: needs support to call method entry/exit stubs.
// kInstrumentWithInterpreter: only execute with interpreter
Instrumentation::InstrumentationLevel instrumentation_level_;
@@ -718,8 +743,7 @@ class Instrumentation {
// The set of methods being deoptimized (by the debugger) which must be executed with interpreter
// only.
- mutable std::unique_ptr<ReaderWriterMutex> deoptimized_methods_lock_ BOTTOM_MUTEX_ACQUIRED_AFTER;
- std::unordered_set<ArtMethod*> deoptimized_methods_ GUARDED_BY(GetDeoptimizedMethodsLock());
+ std::unordered_set<ArtMethod*> deoptimized_methods_ GUARDED_BY(Locks::mutator_lock_);
// Current interpreter handler table. This is updated each time the thread state flags are
// modified.
@@ -734,36 +758,13 @@ class Instrumentation {
friend class InstrumentationTest; // For GetCurrentInstrumentationLevel and ConfigureStubs.
friend class InstrumentationStackPopper; // For popping instrumentation frames.
- friend void InstrumentationInstallStack(Thread*, void*, bool);
+ friend void InstrumentationInstallStack(Thread*, bool);
DISALLOW_COPY_AND_ASSIGN(Instrumentation);
};
std::ostream& operator<<(std::ostream& os, Instrumentation::InstrumentationEvent rhs);
std::ostream& operator<<(std::ostream& os, Instrumentation::InstrumentationLevel rhs);
-// An element in the instrumentation side stack maintained in art::Thread.
-struct InstrumentationStackFrame {
- InstrumentationStackFrame(mirror::Object* this_object,
- ArtMethod* method,
- uintptr_t return_pc,
- bool interpreter_entry,
- uint64_t force_deopt_id)
- : this_object_(this_object),
- method_(method),
- return_pc_(return_pc),
- interpreter_entry_(interpreter_entry),
- force_deopt_id_(force_deopt_id) {
- }
-
- std::string Dump() const REQUIRES_SHARED(Locks::mutator_lock_);
-
- mirror::Object* this_object_;
- ArtMethod* method_;
- uintptr_t return_pc_;
- bool interpreter_entry_;
- uint64_t force_deopt_id_;
-};
-
} // namespace instrumentation
} // namespace art