Merge "Quick: Fix DCE to rename SSA regs for degenerate Phis."
diff --git a/build/Android.gtest.mk b/build/Android.gtest.mk
index 5a3236d..730e61d 100644
--- a/build/Android.gtest.mk
+++ b/build/Android.gtest.mk
@@ -26,6 +26,7 @@
AllFields \
ExceptionHandle \
GetMethodSignature \
+ Instrumentation \
Interfaces \
Main \
MultiDex \
@@ -64,6 +65,7 @@
ART_GTEST_compiler_driver_test_DEX_DEPS := AbstractMethod StaticLeafMethods
ART_GTEST_dex_file_test_DEX_DEPS := GetMethodSignature Main Nested
ART_GTEST_exception_test_DEX_DEPS := ExceptionHandle
+ART_GTEST_instrumentation_test_DEX_DEPS := Instrumentation
ART_GTEST_jni_compiler_test_DEX_DEPS := MyClassNatives
ART_GTEST_jni_internal_test_DEX_DEPS := AllFields StaticLeafMethods
ART_GTEST_oat_file_assistant_test_DEX_DEPS := Main MainStripped MultiDex Nested
@@ -157,6 +159,7 @@
runtime/handle_scope_test.cc \
runtime/indenter_test.cc \
runtime/indirect_reference_table_test.cc \
+ runtime/instrumentation_test.cc \
runtime/intern_table_test.cc \
runtime/interpreter/safe_math_test.cc \
runtime/java_vm_ext_test.cc \
diff --git a/compiler/dex/compiler_enums.h b/compiler/dex/compiler_enums.h
index 0acdd42..b78b3d7 100644
--- a/compiler/dex/compiler_enums.h
+++ b/compiler/dex/compiler_enums.h
@@ -172,7 +172,6 @@
kMirOpRangeCheck,
kMirOpDivZeroCheck,
kMirOpCheck,
- kMirOpCheckPart2,
kMirOpSelect,
// Vector opcodes:
diff --git a/compiler/dex/mir_dataflow.cc b/compiler/dex/mir_dataflow.cc
index b4aec98..a7ba061 100644
--- a/compiler/dex/mir_dataflow.cc
+++ b/compiler/dex/mir_dataflow.cc
@@ -834,9 +834,6 @@
// 10B MIR_CHECK
0,
- // 10C MIR_CHECKPART2
- 0,
-
// 10D MIR_SELECT
DF_DA | DF_UB,
diff --git a/compiler/dex/mir_graph.cc b/compiler/dex/mir_graph.cc
index 9e3fbbc..1871f07 100644
--- a/compiler/dex/mir_graph.cc
+++ b/compiler/dex/mir_graph.cc
@@ -52,8 +52,7 @@
"OpNullCheck",
"OpRangeCheck",
"OpDivZeroCheck",
- "Check1",
- "Check2",
+ "Check",
"Select",
"ConstVector",
"MoveVector",
@@ -1508,7 +1507,7 @@
Instruction::Format dalvik_format = Instruction::k10x; // Default to no-operand format.
// Handle special cases that recover the original dalvik instruction.
- if ((opcode == kMirOpCheck) || (opcode == kMirOpCheckPart2)) {
+ if (opcode == kMirOpCheck) {
str.append(extended_mir_op_names_[opcode - kMirOpFirst]);
str.append(": ");
// Recover the original Dex instruction.
@@ -2517,8 +2516,6 @@
return Instruction::kContinue | Instruction::kThrow;
case kMirOpCheck:
return Instruction::kContinue | Instruction::kThrow;
- case kMirOpCheckPart2:
- return Instruction::kContinue;
case kMirOpSelect:
return Instruction::kContinue;
case kMirOpConstVector:
diff --git a/compiler/dex/quick/codegen_util.cc b/compiler/dex/quick/codegen_util.cc
index fb68335..86bb69d 100644
--- a/compiler/dex/quick/codegen_util.cc
+++ b/compiler/dex/quick/codegen_util.cc
@@ -1391,22 +1391,6 @@
}
}
}
- if (bb->block_type != kEntryBlock && bb->first_mir_insn != nullptr &&
- static_cast<int>(bb->first_mir_insn->dalvikInsn.opcode) == kMirOpCheckPart2) {
- // In Mir2Lir::MethodBlockCodeGen() we have artificially moved the throwing
- // instruction to the previous block. However, the MIRGraph data used above
- // doesn't reflect that, so we still need to process that MIR insn here.
- MIR* mir = nullptr;
- BasicBlock* pred_bb = bb;
- // Traverse empty blocks.
- while (mir == nullptr && pred_bb->predecessors.size() == 1u) {
- pred_bb = mir_graph_->GetBasicBlock(bb->predecessors[0]);
- DCHECK(pred_bb != nullptr);
- mir = pred_bb->last_mir_insn;
- }
- DCHECK(mir != nullptr);
- UpdateReferenceVRegsLocal(nullptr, mir, references);
- }
}
bool Mir2Lir::UpdateReferenceVRegsLocal(MIR* mir, MIR* prev_mir, BitVector* references) {
diff --git a/compiler/dex/quick/mir_to_lir.cc b/compiler/dex/quick/mir_to_lir.cc
index e9e9161..e3e87ec 100644
--- a/compiler/dex/quick/mir_to_lir.cc
+++ b/compiler/dex/quick/mir_to_lir.cc
@@ -1187,7 +1187,6 @@
case kMirOpRangeCheck:
case kMirOpDivZeroCheck:
case kMirOpCheck:
- case kMirOpCheckPart2:
// Ignore these known opcodes
break;
default:
@@ -1276,20 +1275,6 @@
head_lir->u.m.def_mask = &kEncodeAll;
}
- if (opcode == kMirOpCheck) {
- // Combine check and work halves of throwing instruction.
- MIR* work_half = mir->meta.throw_insn;
- mir->dalvikInsn = work_half->dalvikInsn;
- mir->optimization_flags = work_half->optimization_flags;
- mir->meta = work_half->meta; // Whatever the work_half had, we need to copy it.
- opcode = work_half->dalvikInsn.opcode;
- SSARepresentation* ssa_rep = work_half->ssa_rep;
- work_half->ssa_rep = mir->ssa_rep;
- mir->ssa_rep = ssa_rep;
- work_half->dalvikInsn.opcode = static_cast<Instruction::Code>(kMirOpCheckPart2);
- work_half->meta.throw_insn = mir;
- }
-
if (MIR::DecodedInstruction::IsPseudoMirOp(opcode)) {
HandleExtendedMethodMIR(bb, mir);
continue;
diff --git a/compiler/dex/quick/quick_compiler.cc b/compiler/dex/quick/quick_compiler.cc
index 73cfe92..7ca4382 100644
--- a/compiler/dex/quick/quick_compiler.cc
+++ b/compiler/dex/quick/quick_compiler.cc
@@ -403,7 +403,6 @@
kMirOpRangeCheck,
kMirOpDivZeroCheck,
kMirOpCheck,
- kMirOpCheckPart2,
kMirOpSelect,
};
diff --git a/runtime/asm_support.h b/runtime/asm_support.h
index a115fbe..de4783a 100644
--- a/runtime/asm_support.h
+++ b/runtime/asm_support.h
@@ -89,7 +89,7 @@
art::Thread::ThinLockIdOffset<__SIZEOF_POINTER__>().Int32Value())
// Offset of field Thread::tlsPtr_.card_table.
-#define THREAD_CARD_TABLE_OFFSET 120
+#define THREAD_CARD_TABLE_OFFSET 128
ADD_TEST_EQ(THREAD_CARD_TABLE_OFFSET,
art::Thread::CardTableOffset<__SIZEOF_POINTER__>().Int32Value())
diff --git a/runtime/debugger.cc b/runtime/debugger.cc
index dc1b4f1..9b33e50 100644
--- a/runtime/debugger.cc
+++ b/runtime/debugger.cc
@@ -57,6 +57,9 @@
namespace art {
+// The key identifying the debugger to update instrumentation.
+static constexpr const char* kDbgInstrumentationKey = "Debugger";
+
static const size_t kMaxAllocRecordStackDepth = 16; // Max 255.
static const size_t kDefaultNumAllocRecords = 64*1024; // Must be a power of 2. 2BE can hold 64k-1.
@@ -232,13 +235,29 @@
virtual ~DebugInstrumentationListener() {}
void MethodEntered(Thread* thread, mirror::Object* this_object, mirror::ArtMethod* method,
- uint32_t dex_pc ATTRIBUTE_UNUSED)
+ uint32_t dex_pc)
OVERRIDE SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
if (method->IsNative()) {
// TODO: post location events is a suspension point and native method entry stubs aren't.
return;
}
- Dbg::UpdateDebugger(thread, this_object, method, 0, Dbg::kMethodEntry, nullptr);
+ if (IsListeningToDexPcMoved()) {
+ // We also listen to kDexPcMoved instrumentation event so we know the DexPcMoved method is
+ // going to be called right after us. To avoid sending JDWP events twice for this location,
+ // we report the event in DexPcMoved. However, we must remind this is method entry so we
+ // send the METHOD_ENTRY event. And we can also group it with other events for this location
+ // like BREAKPOINT or SINGLE_STEP (or even METHOD_EXIT if this is a RETURN instruction).
+ thread->SetDebugMethodEntry();
+ } else if (IsListeningToMethodExit() && IsReturn(method, dex_pc)) {
+ // We also listen to kMethodExited instrumentation event and the current instruction is a
+ // RETURN so we know the MethodExited method is going to be called right after us. To avoid
+ // sending JDWP events twice for this location, we report the event(s) in MethodExited.
+ // However, we must remind this is method entry so we send the METHOD_ENTRY event. And we can
+ // also group it with other events for this location like BREAKPOINT or SINGLE_STEP.
+ thread->SetDebugMethodEntry();
+ } else {
+ Dbg::UpdateDebugger(thread, this_object, method, 0, Dbg::kMethodEntry, nullptr);
+ }
}
void MethodExited(Thread* thread, mirror::Object* this_object, mirror::ArtMethod* method,
@@ -248,14 +267,20 @@
// TODO: post location events is a suspension point and native method entry stubs aren't.
return;
}
- Dbg::UpdateDebugger(thread, this_object, method, dex_pc, Dbg::kMethodExit, &return_value);
+ uint32_t events = Dbg::kMethodExit;
+ if (thread->IsDebugMethodEntry()) {
+ // It is also the method entry.
+ DCHECK(IsReturn(method, dex_pc));
+ events |= Dbg::kMethodEntry;
+ thread->ClearDebugMethodEntry();
+ }
+ Dbg::UpdateDebugger(thread, this_object, method, dex_pc, events, &return_value);
}
- void MethodUnwind(Thread* thread, mirror::Object* this_object, mirror::ArtMethod* method,
- uint32_t dex_pc)
+ void MethodUnwind(Thread* thread ATTRIBUTE_UNUSED, mirror::Object* this_object ATTRIBUTE_UNUSED,
+ mirror::ArtMethod* method, uint32_t dex_pc)
OVERRIDE SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
// We're not recorded to listen to this kind of event, so complain.
- UNUSED(thread, this_object, method, dex_pc);
LOG(ERROR) << "Unexpected method unwind event in debugger " << PrettyMethod(method)
<< " " << dex_pc;
}
@@ -263,13 +288,27 @@
void DexPcMoved(Thread* thread, mirror::Object* this_object, mirror::ArtMethod* method,
uint32_t new_dex_pc)
OVERRIDE SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
- Dbg::UpdateDebugger(thread, this_object, method, new_dex_pc, 0, nullptr);
+ if (IsListeningToMethodExit() && IsReturn(method, new_dex_pc)) {
+ // We also listen to kMethodExited instrumentation event and the current instruction is a
+ // RETURN so we know the MethodExited method is going to be called right after us. Like in
+ // MethodEntered, we delegate event reporting to MethodExited.
+ // Besides, if this RETURN instruction is the only one in the method, we can send multiple
+ // JDWP events in the same packet: METHOD_ENTRY, METHOD_EXIT, BREAKPOINT and/or SINGLE_STEP.
+ // Therefore, we must not clear the debug method entry flag here.
+ } else {
+ uint32_t events = 0;
+ if (thread->IsDebugMethodEntry()) {
+ // It is also the method entry.
+ events = Dbg::kMethodEntry;
+ thread->ClearDebugMethodEntry();
+ }
+ Dbg::UpdateDebugger(thread, this_object, method, new_dex_pc, events, nullptr);
+ }
}
- void FieldRead(Thread* thread, mirror::Object* this_object, mirror::ArtMethod* method,
- uint32_t dex_pc, ArtField* field)
+ void FieldRead(Thread* thread ATTRIBUTE_UNUSED, mirror::Object* this_object,
+ mirror::ArtMethod* method, uint32_t dex_pc, ArtField* field)
OVERRIDE SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
- UNUSED(thread);
Dbg::PostFieldAccessEvent(method, dex_pc, this_object, field);
}
@@ -293,6 +332,26 @@
}
private:
+ static bool IsReturn(mirror::ArtMethod* method, uint32_t dex_pc)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ const DexFile::CodeItem* code_item = method->GetCodeItem();
+ const Instruction* instruction = Instruction::At(&code_item->insns_[dex_pc]);
+ return instruction->IsReturn();
+ }
+
+ static bool IsListeningToDexPcMoved() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ return IsListeningTo(instrumentation::Instrumentation::kDexPcMoved);
+ }
+
+ static bool IsListeningToMethodExit() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ return IsListeningTo(instrumentation::Instrumentation::kMethodExited);
+ }
+
+ static bool IsListeningTo(instrumentation::Instrumentation::InstrumentationEvent event)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ return (Dbg::GetInstrumentationEvents() & event) != 0;
+ }
+
DISALLOW_COPY_AND_ASSIGN(DebugInstrumentationListener);
} gDebugInstrumentationListener;
@@ -677,7 +736,7 @@
instrumentation_events_ = 0;
}
if (RequiresDeoptimization()) {
- runtime->GetInstrumentation()->DisableDeoptimization();
+ runtime->GetInstrumentation()->DisableDeoptimization(kDbgInstrumentationKey);
}
gDebuggerActive = false;
}
@@ -2998,12 +3057,12 @@
break;
case DeoptimizationRequest::kFullDeoptimization:
VLOG(jdwp) << "Deoptimize the world ...";
- instrumentation->DeoptimizeEverything();
+ instrumentation->DeoptimizeEverything(kDbgInstrumentationKey);
VLOG(jdwp) << "Deoptimize the world DONE";
break;
case DeoptimizationRequest::kFullUndeoptimization:
VLOG(jdwp) << "Undeoptimize the world ...";
- instrumentation->UndeoptimizeEverything();
+ instrumentation->UndeoptimizeEverything(kDbgInstrumentationKey);
VLOG(jdwp) << "Undeoptimize the world DONE";
break;
case DeoptimizationRequest::kSelectiveDeoptimization:
diff --git a/runtime/debugger.h b/runtime/debugger.h
index fe90eb6..789a0a4 100644
--- a/runtime/debugger.h
+++ b/runtime/debugger.h
@@ -714,6 +714,10 @@
static JDWP::JdwpState* GetJdwpState();
+ static uint32_t GetInstrumentationEvents() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ return instrumentation_events_;
+ }
+
private:
static JDWP::JdwpError GetLocalValue(const StackVisitor& visitor,
ScopedObjectAccessUnchecked& soa, int slot,
diff --git a/runtime/instrumentation.cc b/runtime/instrumentation.cc
index e6c333d..f810bc8 100644
--- a/runtime/instrumentation.cc
+++ b/runtime/instrumentation.cc
@@ -16,13 +16,10 @@
#include "instrumentation.h"
-#include <sys/uio.h>
-
#include <sstream>
#include "arch/context.h"
#include "atomic.h"
-#include "base/unix_file/fd_file.h"
#include "class_linker.h"
#include "debugger.h"
#include "dex_file-inl.h"
@@ -39,16 +36,13 @@
#include "mirror/object_array-inl.h"
#include "mirror/object-inl.h"
#include "nth_caller_visitor.h"
-#include "os.h"
-#include "scoped_thread_state_change.h"
#include "thread.h"
#include "thread_list.h"
namespace art {
-
namespace instrumentation {
-const bool kVerboseInstrumentation = false;
+constexpr bool kVerboseInstrumentation = false;
static bool InstallStubsClassVisitor(mirror::Class* klass, void* arg)
EXCLUSIVE_LOCKS_REQUIRED(Locks::mutator_lock_) {
@@ -64,7 +58,7 @@
have_method_entry_listeners_(false), have_method_exit_listeners_(false),
have_method_unwind_listeners_(false), have_dex_pc_listeners_(false),
have_field_read_listeners_(false), have_field_write_listeners_(false),
- have_exception_caught_listeners_(false),
+ have_exception_caught_listeners_(false), have_backward_branch_listeners_(false),
deoptimized_methods_lock_("deoptimized methods lock"),
deoptimization_enabled_(false),
interpreter_handler_table_(kMainHandlerTable),
@@ -166,7 +160,7 @@
// existing instrumentation frames.
static void InstrumentationInstallStack(Thread* thread, void* arg)
SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
- struct InstallStackVisitor : public StackVisitor {
+ struct InstallStackVisitor FINAL : public StackVisitor {
InstallStackVisitor(Thread* thread_in, Context* context, uintptr_t instrumentation_exit_pc)
: StackVisitor(thread_in, context),
instrumentation_stack_(thread_in->GetInstrumentationStack()),
@@ -175,7 +169,7 @@
last_return_pc_(0) {
}
- virtual bool VisitFrame() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ bool VisitFrame() OVERRIDE SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
mirror::ArtMethod* m = GetMethod();
if (m == nullptr) {
if (kVerboseInstrumentation) {
@@ -306,7 +300,7 @@
// Removes the instrumentation exit pc as the return PC for every quick frame.
static void InstrumentationRestoreStack(Thread* thread, void* arg)
SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
- struct RestoreStackVisitor : public StackVisitor {
+ struct RestoreStackVisitor FINAL : public StackVisitor {
RestoreStackVisitor(Thread* thread_in, uintptr_t instrumentation_exit_pc,
Instrumentation* instrumentation)
: StackVisitor(thread_in, nullptr), thread_(thread_in),
@@ -315,7 +309,7 @@
instrumentation_stack_(thread_in->GetInstrumentationStack()),
frames_removed_(0) {}
- virtual bool VisitFrame() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ bool VisitFrame() OVERRIDE SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
if (instrumentation_stack_->size() == 0) {
return false; // Stop.
}
@@ -390,25 +384,29 @@
}
}
+static bool HasEvent(Instrumentation::InstrumentationEvent expected, uint32_t events) {
+ return (events & expected) != 0;
+}
+
void Instrumentation::AddListener(InstrumentationListener* listener, uint32_t events) {
Locks::mutator_lock_->AssertExclusiveHeld(Thread::Current());
- if ((events & kMethodEntered) != 0) {
+ if (HasEvent(kMethodEntered, events)) {
method_entry_listeners_.push_back(listener);
have_method_entry_listeners_ = true;
}
- if ((events & kMethodExited) != 0) {
+ if (HasEvent(kMethodExited, events)) {
method_exit_listeners_.push_back(listener);
have_method_exit_listeners_ = true;
}
- if ((events & kMethodUnwind) != 0) {
+ if (HasEvent(kMethodUnwind, events)) {
method_unwind_listeners_.push_back(listener);
have_method_unwind_listeners_ = true;
}
- if ((events & kBackwardBranch) != 0) {
+ if (HasEvent(kBackwardBranch, events)) {
backward_branch_listeners_.push_back(listener);
have_backward_branch_listeners_ = true;
}
- if ((events & kDexPcMoved) != 0) {
+ if (HasEvent(kDexPcMoved, events)) {
std::list<InstrumentationListener*>* modified;
if (have_dex_pc_listeners_) {
modified = new std::list<InstrumentationListener*>(*dex_pc_listeners_.get());
@@ -419,7 +417,7 @@
dex_pc_listeners_.reset(modified);
have_dex_pc_listeners_ = true;
}
- if ((events & kFieldRead) != 0) {
+ if (HasEvent(kFieldRead, events)) {
std::list<InstrumentationListener*>* modified;
if (have_field_read_listeners_) {
modified = new std::list<InstrumentationListener*>(*field_read_listeners_.get());
@@ -430,7 +428,7 @@
field_read_listeners_.reset(modified);
have_field_read_listeners_ = true;
}
- if ((events & kFieldWritten) != 0) {
+ if (HasEvent(kFieldWritten, events)) {
std::list<InstrumentationListener*>* modified;
if (have_field_write_listeners_) {
modified = new std::list<InstrumentationListener*>(*field_write_listeners_.get());
@@ -441,7 +439,7 @@
field_write_listeners_.reset(modified);
have_field_write_listeners_ = true;
}
- if ((events & kExceptionCaught) != 0) {
+ if (HasEvent(kExceptionCaught, events)) {
std::list<InstrumentationListener*>* modified;
if (have_exception_caught_listeners_) {
modified = new std::list<InstrumentationListener*>(*exception_caught_listeners_.get());
@@ -458,102 +456,104 @@
void Instrumentation::RemoveListener(InstrumentationListener* listener, uint32_t events) {
Locks::mutator_lock_->AssertExclusiveHeld(Thread::Current());
- if ((events & kMethodEntered) != 0) {
- if (have_method_entry_listeners_) {
- method_entry_listeners_.remove(listener);
- have_method_entry_listeners_ = !method_entry_listeners_.empty();
- }
+ if (HasEvent(kMethodEntered, events) && have_method_entry_listeners_) {
+ method_entry_listeners_.remove(listener);
+ have_method_entry_listeners_ = !method_entry_listeners_.empty();
}
- if ((events & kMethodExited) != 0) {
- if (have_method_exit_listeners_) {
- method_exit_listeners_.remove(listener);
- have_method_exit_listeners_ = !method_exit_listeners_.empty();
- }
+ if (HasEvent(kMethodExited, events) && have_method_exit_listeners_) {
+ method_exit_listeners_.remove(listener);
+ have_method_exit_listeners_ = !method_exit_listeners_.empty();
}
- if ((events & kMethodUnwind) != 0) {
- if (have_method_unwind_listeners_) {
+ if (HasEvent(kMethodUnwind, events) && have_method_unwind_listeners_) {
method_unwind_listeners_.remove(listener);
have_method_unwind_listeners_ = !method_unwind_listeners_.empty();
- }
}
- if ((events & kDexPcMoved) != 0) {
+ if (HasEvent(kBackwardBranch, events) && have_backward_branch_listeners_) {
+ backward_branch_listeners_.remove(listener);
+ have_backward_branch_listeners_ = !backward_branch_listeners_.empty();
+ }
+ if (HasEvent(kDexPcMoved, events) && have_dex_pc_listeners_) {
+ std::list<InstrumentationListener*>* modified =
+ new std::list<InstrumentationListener*>(*dex_pc_listeners_.get());
+ modified->remove(listener);
+ have_dex_pc_listeners_ = !modified->empty();
if (have_dex_pc_listeners_) {
- std::list<InstrumentationListener*>* modified =
- new std::list<InstrumentationListener*>(*dex_pc_listeners_.get());
- modified->remove(listener);
- have_dex_pc_listeners_ = !modified->empty();
- if (have_dex_pc_listeners_) {
- dex_pc_listeners_.reset(modified);
- } else {
- dex_pc_listeners_.reset();
- delete modified;
- }
+ dex_pc_listeners_.reset(modified);
+ } else {
+ dex_pc_listeners_.reset();
+ delete modified;
}
}
- if ((events & kFieldRead) != 0) {
+ if (HasEvent(kFieldRead, events) && have_field_read_listeners_) {
+ std::list<InstrumentationListener*>* modified =
+ new std::list<InstrumentationListener*>(*field_read_listeners_.get());
+ modified->remove(listener);
+ have_field_read_listeners_ = !modified->empty();
if (have_field_read_listeners_) {
- std::list<InstrumentationListener*>* modified =
- new std::list<InstrumentationListener*>(*field_read_listeners_.get());
- modified->remove(listener);
- have_field_read_listeners_ = !modified->empty();
- if (have_field_read_listeners_) {
- field_read_listeners_.reset(modified);
- } else {
- field_read_listeners_.reset();
- delete modified;
- }
+ field_read_listeners_.reset(modified);
+ } else {
+ field_read_listeners_.reset();
+ delete modified;
}
}
- if ((events & kFieldWritten) != 0) {
+ if (HasEvent(kFieldWritten, events) && have_field_write_listeners_) {
+ std::list<InstrumentationListener*>* modified =
+ new std::list<InstrumentationListener*>(*field_write_listeners_.get());
+ modified->remove(listener);
+ have_field_write_listeners_ = !modified->empty();
if (have_field_write_listeners_) {
- std::list<InstrumentationListener*>* modified =
- new std::list<InstrumentationListener*>(*field_write_listeners_.get());
- modified->remove(listener);
- have_field_write_listeners_ = !modified->empty();
- if (have_field_write_listeners_) {
- field_write_listeners_.reset(modified);
- } else {
- field_write_listeners_.reset();
- delete modified;
- }
+ field_write_listeners_.reset(modified);
+ } else {
+ field_write_listeners_.reset();
+ delete modified;
}
}
- if ((events & kExceptionCaught) != 0) {
+ if (HasEvent(kExceptionCaught, events) && have_exception_caught_listeners_) {
+ std::list<InstrumentationListener*>* modified =
+ new std::list<InstrumentationListener*>(*exception_caught_listeners_.get());
+ modified->remove(listener);
+ have_exception_caught_listeners_ = !modified->empty();
if (have_exception_caught_listeners_) {
- std::list<InstrumentationListener*>* modified =
- new std::list<InstrumentationListener*>(*exception_caught_listeners_.get());
- modified->remove(listener);
- have_exception_caught_listeners_ = !modified->empty();
- if (have_exception_caught_listeners_) {
- exception_caught_listeners_.reset(modified);
- } else {
- exception_caught_listeners_.reset();
- delete modified;
- }
+ exception_caught_listeners_.reset(modified);
+ } else {
+ exception_caught_listeners_.reset();
+ delete modified;
}
}
UpdateInterpreterHandlerTable();
}
-void Instrumentation::ConfigureStubs(bool require_entry_exit_stubs, bool require_interpreter) {
- interpret_only_ = require_interpreter || forced_interpret_only_;
- // Compute what level of instrumentation is required and compare to current.
- int desired_level, current_level;
- if (require_interpreter) {
- desired_level = 2;
- } else if (require_entry_exit_stubs) {
- desired_level = 1;
- } else {
- desired_level = 0;
- }
+Instrumentation::InstrumentationLevel Instrumentation::GetCurrentInstrumentationLevel() const {
if (interpreter_stubs_installed_) {
- current_level = 2;
+ return InstrumentationLevel::kInstrumentWithInterpreter;
} else if (entry_exit_stubs_installed_) {
- current_level = 1;
+ return InstrumentationLevel::kInstrumentWithInstrumentationStubs;
} else {
- current_level = 0;
+ return InstrumentationLevel::kInstrumentNothing;
}
- if (desired_level == current_level) {
+}
+
+void Instrumentation::ConfigureStubs(const char* key, InstrumentationLevel desired_level) {
+ // Store the instrumentation level for this key or remove it.
+ if (desired_level == InstrumentationLevel::kInstrumentNothing) {
+ // The client no longer needs instrumentation.
+ requested_instrumentation_levels_.erase(key);
+ } else {
+ // The client needs instrumentation.
+ requested_instrumentation_levels_.Overwrite(key, desired_level);
+ }
+
+ // Look for the highest required instrumentation level.
+ InstrumentationLevel requested_level = InstrumentationLevel::kInstrumentNothing;
+ for (const auto& v : requested_instrumentation_levels_) {
+ requested_level = std::max(requested_level, v.second);
+ }
+
+ interpret_only_ = (requested_level == InstrumentationLevel::kInstrumentWithInterpreter) ||
+ forced_interpret_only_;
+
+ InstrumentationLevel current_level = GetCurrentInstrumentationLevel();
+ if (requested_level == current_level) {
// We're already set.
return;
}
@@ -561,12 +561,14 @@
Runtime* runtime = Runtime::Current();
Locks::mutator_lock_->AssertExclusiveHeld(self);
Locks::thread_list_lock_->AssertNotHeld(self);
- if (desired_level > 0) {
- if (require_interpreter) {
+ if (requested_level > InstrumentationLevel::kInstrumentNothing) {
+ if (requested_level == InstrumentationLevel::kInstrumentWithInterpreter) {
interpreter_stubs_installed_ = true;
- } else {
- CHECK(require_entry_exit_stubs);
entry_exit_stubs_installed_ = true;
+ } else {
+ CHECK_EQ(requested_level, InstrumentationLevel::kInstrumentWithInstrumentationStubs);
+ entry_exit_stubs_installed_ = true;
+ interpreter_stubs_installed_ = false;
}
runtime->GetClassLinker()->VisitClasses(InstallStubsClassVisitor, this);
instrumentation_stubs_installed_ = true;
@@ -590,8 +592,7 @@
}
}
-static void ResetQuickAllocEntryPointsForThread(Thread* thread, void* arg) {
- UNUSED(arg);
+static void ResetQuickAllocEntryPointsForThread(Thread* thread, void* arg ATTRIBUTE_UNUSED) {
thread->ResetQuickAllocEntryPointsForThread();
}
@@ -804,11 +805,11 @@
deoptimization_enabled_ = true;
}
-void Instrumentation::DisableDeoptimization() {
+void Instrumentation::DisableDeoptimization(const char* key) {
CHECK_EQ(deoptimization_enabled_, true);
// If we deoptimized everything, undo it.
if (interpreter_stubs_installed_) {
- UndeoptimizeEverything();
+ UndeoptimizeEverything(key);
}
// Undeoptimized selected methods.
while (true) {
@@ -828,25 +829,35 @@
// Indicates if instrumentation should notify method enter/exit events to the listeners.
bool Instrumentation::ShouldNotifyMethodEnterExitEvents() const {
+ if (!HasMethodEntryListeners() && !HasMethodExitListeners()) {
+ return false;
+ }
return !deoptimization_enabled_ && !interpreter_stubs_installed_;
}
-void Instrumentation::DeoptimizeEverything() {
- CHECK(!interpreter_stubs_installed_);
- ConfigureStubs(false, true);
+void Instrumentation::DeoptimizeEverything(const char* key) {
+ CHECK(deoptimization_enabled_);
+ ConfigureStubs(key, InstrumentationLevel::kInstrumentWithInterpreter);
}
-void Instrumentation::UndeoptimizeEverything() {
+void Instrumentation::UndeoptimizeEverything(const char* key) {
CHECK(interpreter_stubs_installed_);
- ConfigureStubs(false, false);
+ CHECK(deoptimization_enabled_);
+ ConfigureStubs(key, InstrumentationLevel::kInstrumentNothing);
}
-void Instrumentation::EnableMethodTracing(bool require_interpreter) {
- ConfigureStubs(!require_interpreter, require_interpreter);
+void Instrumentation::EnableMethodTracing(const char* key, bool needs_interpreter) {
+ InstrumentationLevel level;
+ if (needs_interpreter) {
+ level = InstrumentationLevel::kInstrumentWithInterpreter;
+ } else {
+ level = InstrumentationLevel::kInstrumentWithInstrumentationStubs;
+ }
+ ConfigureStubs(key, level);
}
-void Instrumentation::DisableMethodTracing() {
- ConfigureStubs(false, false);
+void Instrumentation::DisableMethodTracing(const char* key) {
+ ConfigureStubs(key, InstrumentationLevel::kInstrumentNothing);
}
const void* Instrumentation::GetQuickCodeFor(mirror::ArtMethod* method, size_t pointer_size) const {
@@ -896,7 +907,7 @@
void Instrumentation::MethodUnwindEvent(Thread* thread, mirror::Object* this_object,
mirror::ArtMethod* method,
uint32_t dex_pc) const {
- if (have_method_unwind_listeners_) {
+ if (HasMethodUnwindListeners()) {
for (InstrumentationListener* listener : method_unwind_listeners_) {
listener->MethodUnwind(thread, this_object, method, dex_pc);
}
@@ -906,11 +917,9 @@
void Instrumentation::DexPcMovedEventImpl(Thread* thread, mirror::Object* this_object,
mirror::ArtMethod* method,
uint32_t dex_pc) const {
- if (HasDexPcListeners()) {
- std::shared_ptr<std::list<InstrumentationListener*>> original(dex_pc_listeners_);
- for (InstrumentationListener* listener : *original.get()) {
- listener->DexPcMoved(thread, this_object, method, dex_pc);
- }
+ std::shared_ptr<std::list<InstrumentationListener*>> original(dex_pc_listeners_);
+ for (InstrumentationListener* listener : *original.get()) {
+ listener->DexPcMoved(thread, this_object, method, dex_pc);
}
}
@@ -924,22 +933,18 @@
void Instrumentation::FieldReadEventImpl(Thread* thread, mirror::Object* this_object,
mirror::ArtMethod* method, uint32_t dex_pc,
ArtField* field) const {
- if (HasFieldReadListeners()) {
- std::shared_ptr<std::list<InstrumentationListener*>> original(field_read_listeners_);
- for (InstrumentationListener* listener : *original.get()) {
- listener->FieldRead(thread, this_object, method, dex_pc, field);
- }
+ std::shared_ptr<std::list<InstrumentationListener*>> original(field_read_listeners_);
+ for (InstrumentationListener* listener : *original.get()) {
+ listener->FieldRead(thread, this_object, method, dex_pc, field);
}
}
void Instrumentation::FieldWriteEventImpl(Thread* thread, mirror::Object* this_object,
mirror::ArtMethod* method, uint32_t dex_pc,
ArtField* field, const JValue& field_value) const {
- if (HasFieldWriteListeners()) {
- std::shared_ptr<std::list<InstrumentationListener*>> original(field_write_listeners_);
- for (InstrumentationListener* listener : *original.get()) {
- listener->FieldWritten(thread, this_object, method, dex_pc, field, field_value);
- }
+ std::shared_ptr<std::list<InstrumentationListener*>> original(field_write_listeners_);
+ for (InstrumentationListener* listener : *original.get()) {
+ listener->FieldWritten(thread, this_object, method, dex_pc, field, field_value);
}
}
diff --git a/runtime/instrumentation.h b/runtime/instrumentation.h
index 8b7fcca..7d70d21 100644
--- a/runtime/instrumentation.h
+++ b/runtime/instrumentation.h
@@ -22,11 +22,10 @@
#include <map>
#include "arch/instruction_set.h"
-#include "atomic.h"
#include "base/macros.h"
#include "base/mutex.h"
#include "gc_root.h"
-#include "object_callbacks.h"
+#include "safe_map.h"
namespace art {
namespace mirror {
@@ -67,8 +66,6 @@
uint32_t dex_pc) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) = 0;
// Call-back for when a method is exited.
- // TODO: its likely passing the return value would be useful, however, we may need to get and
- // parse the shorty to determine what kind of register holds the result.
virtual void MethodExited(Thread* thread, mirror::Object* this_object,
mirror::ArtMethod* method, uint32_t dex_pc,
const JValue& return_value)
@@ -119,6 +116,12 @@
kBackwardBranch = 0x80,
};
+ enum class InstrumentationLevel {
+ kInstrumentNothing, // execute without instrumentation
+ kInstrumentWithInstrumentationStubs, // execute with instrumentation entry/exit stubs
+ kInstrumentWithInterpreter // execute with interpreter
+ };
+
Instrumentation();
// Add a listener to be notified of the masked together sent of instrumentation events. This
@@ -138,7 +141,7 @@
void EnableDeoptimization()
EXCLUSIVE_LOCKS_REQUIRED(Locks::mutator_lock_)
LOCKS_EXCLUDED(deoptimized_methods_lock_);
- void DisableDeoptimization()
+ void DisableDeoptimization(const char* key)
EXCLUSIVE_LOCKS_REQUIRED(Locks::mutator_lock_)
LOCKS_EXCLUDED(deoptimized_methods_lock_);
bool AreAllMethodsDeoptimized() const {
@@ -147,12 +150,12 @@
bool ShouldNotifyMethodEnterExitEvents() const SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
// Executes everything with interpreter.
- void DeoptimizeEverything()
+ void DeoptimizeEverything(const char* key)
EXCLUSIVE_LOCKS_REQUIRED(Locks::mutator_lock_)
LOCKS_EXCLUDED(Locks::thread_list_lock_, Locks::classlinker_classes_lock_);
// Executes everything with compiled code (or interpreter if there is no code).
- void UndeoptimizeEverything()
+ void UndeoptimizeEverything(const char* key)
EXCLUSIVE_LOCKS_REQUIRED(Locks::mutator_lock_)
LOCKS_EXCLUDED(Locks::thread_list_lock_, Locks::classlinker_classes_lock_);
@@ -170,18 +173,19 @@
LOCKS_EXCLUDED(Locks::thread_list_lock_, deoptimized_methods_lock_)
EXCLUSIVE_LOCKS_REQUIRED(Locks::mutator_lock_);
+ // Indicates whether the method has been deoptimized so it is executed with the interpreter.
bool IsDeoptimized(mirror::ArtMethod* method)
LOCKS_EXCLUDED(deoptimized_methods_lock_)
SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
- // Enable method tracing by installing instrumentation entry/exit stubs.
- void EnableMethodTracing(
- bool require_interpreter = kDeoptimizeForAccurateMethodEntryExitListeners)
+ // Enable method tracing by installing instrumentation entry/exit stubs or interpreter.
+ void EnableMethodTracing(const char* key,
+ bool needs_interpreter = kDeoptimizeForAccurateMethodEntryExitListeners)
EXCLUSIVE_LOCKS_REQUIRED(Locks::mutator_lock_)
LOCKS_EXCLUDED(Locks::thread_list_lock_, Locks::classlinker_classes_lock_);
- // Disable method tracing by uninstalling instrumentation entry/exit stubs.
- void DisableMethodTracing()
+ // Disable method tracing by uninstalling instrumentation entry/exit stubs or interpreter.
+ void DisableMethodTracing(const char* key)
EXCLUSIVE_LOCKS_REQUIRED(Locks::mutator_lock_)
LOCKS_EXCLUDED(Locks::thread_list_lock_, Locks::classlinker_classes_lock_);
@@ -236,6 +240,10 @@
return have_method_exit_listeners_;
}
+ bool HasMethodUnwindListeners() const SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ return have_method_unwind_listeners_;
+ }
+
bool HasDexPcListeners() const SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
return have_dex_pc_listeners_;
}
@@ -355,8 +363,14 @@
LOCKS_EXCLUDED(deoptimized_methods_lock_);
private:
+ InstrumentationLevel GetCurrentInstrumentationLevel() const;
+
// Does the job of installing or removing instrumentation code within methods.
- void ConfigureStubs(bool require_entry_exit_stubs, bool require_interpreter)
+ // In order to support multiple clients using instrumentation at the same time,
+ // the caller must pass a unique key (a string) identifying it so we remind which
+ // instrumentation level it needs. Therefore the current instrumentation level
+ // becomes the highest instrumentation level required by a client.
+ void ConfigureStubs(const char* key, InstrumentationLevel desired_instrumentation_level)
EXCLUSIVE_LOCKS_REQUIRED(Locks::mutator_lock_)
LOCKS_EXCLUDED(Locks::thread_list_lock_, Locks::classlinker_classes_lock_,
deoptimized_methods_lock_);
@@ -452,6 +466,11 @@
// Do we have any backward branch listeners? Short-cut to avoid taking the instrumentation_lock_.
bool have_backward_branch_listeners_ GUARDED_BY(Locks::mutator_lock_);
+ // Contains the instrumentation level required by each client of the instrumentation identified
+ // by a string key.
+ typedef SafeMap<const char*, InstrumentationLevel> InstrumentationLevelTable;
+ InstrumentationLevelTable requested_instrumentation_levels_ GUARDED_BY(Locks::mutator_lock_);
+
// The event listeners, written to with the mutator_lock_ exclusively held.
std::list<InstrumentationListener*> method_entry_listeners_ GUARDED_BY(Locks::mutator_lock_);
std::list<InstrumentationListener*> method_exit_listeners_ GUARDED_BY(Locks::mutator_lock_);
@@ -481,9 +500,12 @@
size_t quick_alloc_entry_points_instrumentation_counter_
GUARDED_BY(Locks::instrument_entrypoints_lock_);
+ friend class InstrumentationTest; // For GetCurrentInstrumentationLevel and ConfigureStubs.
+
DISALLOW_COPY_AND_ASSIGN(Instrumentation);
};
std::ostream& operator<<(std::ostream& os, const Instrumentation::InstrumentationEvent& rhs);
+std::ostream& operator<<(std::ostream& os, const Instrumentation::InstrumentationLevel& rhs);
// An element in the instrumentation side stack maintained in art::Thread.
struct InstrumentationStackFrame {
diff --git a/runtime/instrumentation_test.cc b/runtime/instrumentation_test.cc
new file mode 100644
index 0000000..5afacb8
--- /dev/null
+++ b/runtime/instrumentation_test.cc
@@ -0,0 +1,791 @@
+/*
+ * Copyright (C) 2015 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 "instrumentation.h"
+
+#include "common_runtime_test.h"
+#include "common_throws.h"
+#include "class_linker-inl.h"
+#include "dex_file.h"
+#include "handle_scope-inl.h"
+#include "jvalue.h"
+#include "runtime.h"
+#include "scoped_thread_state_change.h"
+#include "thread_list.h"
+#include "thread-inl.h"
+
+namespace art {
+namespace instrumentation {
+
+class TestInstrumentationListener FINAL : public instrumentation::InstrumentationListener {
+ public:
+ TestInstrumentationListener()
+ : received_method_enter_event(false), received_method_exit_event(false),
+ received_method_unwind_event(false), received_dex_pc_moved_event(false),
+ received_field_read_event(false), received_field_written_event(false),
+ received_exception_caught_event(false), received_backward_branch_event(false) {}
+
+ virtual ~TestInstrumentationListener() {}
+
+ void MethodEntered(Thread* thread ATTRIBUTE_UNUSED,
+ mirror::Object* this_object ATTRIBUTE_UNUSED,
+ mirror::ArtMethod* method ATTRIBUTE_UNUSED,
+ uint32_t dex_pc ATTRIBUTE_UNUSED)
+ OVERRIDE SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ received_method_enter_event = true;
+ }
+
+ void MethodExited(Thread* thread ATTRIBUTE_UNUSED,
+ mirror::Object* this_object ATTRIBUTE_UNUSED,
+ mirror::ArtMethod* method ATTRIBUTE_UNUSED,
+ uint32_t dex_pc ATTRIBUTE_UNUSED,
+ const JValue& return_value ATTRIBUTE_UNUSED)
+ OVERRIDE SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ received_method_exit_event = true;
+ }
+
+ void MethodUnwind(Thread* thread ATTRIBUTE_UNUSED,
+ mirror::Object* this_object ATTRIBUTE_UNUSED,
+ mirror::ArtMethod* method ATTRIBUTE_UNUSED,
+ uint32_t dex_pc ATTRIBUTE_UNUSED)
+ OVERRIDE SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ received_method_unwind_event = true;
+ }
+
+ void DexPcMoved(Thread* thread ATTRIBUTE_UNUSED,
+ mirror::Object* this_object ATTRIBUTE_UNUSED,
+ mirror::ArtMethod* method ATTRIBUTE_UNUSED,
+ uint32_t new_dex_pc ATTRIBUTE_UNUSED)
+ OVERRIDE SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ received_dex_pc_moved_event = true;
+ }
+
+ void FieldRead(Thread* thread ATTRIBUTE_UNUSED,
+ mirror::Object* this_object ATTRIBUTE_UNUSED,
+ mirror::ArtMethod* method ATTRIBUTE_UNUSED,
+ uint32_t dex_pc ATTRIBUTE_UNUSED,
+ ArtField* field ATTRIBUTE_UNUSED)
+ OVERRIDE SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ received_field_read_event = true;
+ }
+
+ void FieldWritten(Thread* thread ATTRIBUTE_UNUSED,
+ mirror::Object* this_object ATTRIBUTE_UNUSED,
+ mirror::ArtMethod* method ATTRIBUTE_UNUSED,
+ uint32_t dex_pc ATTRIBUTE_UNUSED,
+ ArtField* field ATTRIBUTE_UNUSED,
+ const JValue& field_value ATTRIBUTE_UNUSED)
+ OVERRIDE SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ received_field_written_event = true;
+ }
+
+ void ExceptionCaught(Thread* thread ATTRIBUTE_UNUSED,
+ mirror::Throwable* exception_object ATTRIBUTE_UNUSED)
+ OVERRIDE SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ received_exception_caught_event = true;
+ }
+
+ void BackwardBranch(Thread* thread ATTRIBUTE_UNUSED,
+ mirror::ArtMethod* method ATTRIBUTE_UNUSED,
+ int32_t dex_pc_offset ATTRIBUTE_UNUSED)
+ OVERRIDE SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ received_backward_branch_event = true;
+ }
+
+ void Reset() {
+ received_method_enter_event = false;
+ received_method_exit_event = false;
+ received_method_unwind_event = false;
+ received_dex_pc_moved_event = false;
+ received_field_read_event = false;
+ received_field_written_event = false;
+ received_exception_caught_event = false;
+ received_backward_branch_event = false;
+ }
+
+ bool received_method_enter_event;
+ bool received_method_exit_event;
+ bool received_method_unwind_event;
+ bool received_dex_pc_moved_event;
+ bool received_field_read_event;
+ bool received_field_written_event;
+ bool received_exception_caught_event;
+ bool received_backward_branch_event;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TestInstrumentationListener);
+};
+
+class InstrumentationTest : public CommonRuntimeTest {
+ public:
+ // Unique keys used to test Instrumentation::ConfigureStubs.
+ static constexpr const char* kClientOneKey = "TestClient1";
+ static constexpr const char* kClientTwoKey = "TestClient2";
+
+ void CheckConfigureStubs(const char* key, Instrumentation::InstrumentationLevel level) {
+ ScopedObjectAccess soa(Thread::Current());
+ instrumentation::Instrumentation* instr = Runtime::Current()->GetInstrumentation();
+ {
+ soa.Self()->TransitionFromRunnableToSuspended(kSuspended);
+ Runtime* runtime = Runtime::Current();
+ runtime->GetThreadList()->SuspendAll("Instrumentation::ConfigureStubs");
+ instr->ConfigureStubs(key, level);
+ runtime->GetThreadList()->ResumeAll();
+ soa.Self()->TransitionFromSuspendedToRunnable();
+ }
+ }
+
+ Instrumentation::InstrumentationLevel GetCurrentInstrumentationLevel() {
+ return Runtime::Current()->GetInstrumentation()->GetCurrentInstrumentationLevel();
+ }
+
+ size_t GetInstrumentationUserCount() {
+ ScopedObjectAccess soa(Thread::Current());
+ return Runtime::Current()->GetInstrumentation()->requested_instrumentation_levels_.size();
+ }
+
+ void TestEvent(uint32_t instrumentation_event) {
+ ScopedObjectAccess soa(Thread::Current());
+ instrumentation::Instrumentation* instr = Runtime::Current()->GetInstrumentation();
+ TestInstrumentationListener listener;
+ {
+ soa.Self()->TransitionFromRunnableToSuspended(kSuspended);
+ Runtime* runtime = Runtime::Current();
+ runtime->GetThreadList()->SuspendAll("Add instrumentation listener");
+ instr->AddListener(&listener, instrumentation_event);
+ runtime->GetThreadList()->ResumeAll();
+ soa.Self()->TransitionFromSuspendedToRunnable();
+ }
+
+ mirror::ArtMethod* const event_method = nullptr;
+ mirror::Object* const event_obj = nullptr;
+ const uint32_t event_dex_pc = 0;
+
+ // Check the listener is registered and is notified of the event.
+ EXPECT_TRUE(HasEventListener(instr, instrumentation_event));
+ EXPECT_FALSE(DidListenerReceiveEvent(listener, instrumentation_event));
+ ReportEvent(instr, instrumentation_event, soa.Self(), event_method, event_obj, event_dex_pc);
+ EXPECT_TRUE(DidListenerReceiveEvent(listener, instrumentation_event));
+
+ listener.Reset();
+ {
+ soa.Self()->TransitionFromRunnableToSuspended(kSuspended);
+ Runtime* runtime = Runtime::Current();
+ runtime->GetThreadList()->SuspendAll("Remove instrumentation listener");
+ instr->RemoveListener(&listener, instrumentation_event);
+ runtime->GetThreadList()->ResumeAll();
+ soa.Self()->TransitionFromSuspendedToRunnable();
+ }
+
+ // Check the listener is not registered and is not notified of the event.
+ EXPECT_FALSE(HasEventListener(instr, instrumentation_event));
+ EXPECT_FALSE(DidListenerReceiveEvent(listener, instrumentation_event));
+ ReportEvent(instr, instrumentation_event, soa.Self(), event_method, event_obj, event_dex_pc);
+ EXPECT_FALSE(DidListenerReceiveEvent(listener, instrumentation_event));
+ }
+
+ void DeoptimizeMethod(Thread* self, Handle<mirror::ArtMethod> method,
+ bool enable_deoptimization)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ Runtime* runtime = Runtime::Current();
+ instrumentation::Instrumentation* instrumentation = runtime->GetInstrumentation();
+ self->TransitionFromRunnableToSuspended(kSuspended);
+ runtime->GetThreadList()->SuspendAll("Single method deoptimization");
+ if (enable_deoptimization) {
+ instrumentation->EnableDeoptimization();
+ }
+ instrumentation->Deoptimize(method.Get());
+ runtime->GetThreadList()->ResumeAll();
+ self->TransitionFromSuspendedToRunnable();
+ }
+
+ void UndeoptimizeMethod(Thread* self, Handle<mirror::ArtMethod> method,
+ const char* key, bool disable_deoptimization)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ Runtime* runtime = Runtime::Current();
+ instrumentation::Instrumentation* instrumentation = runtime->GetInstrumentation();
+ self->TransitionFromRunnableToSuspended(kSuspended);
+ runtime->GetThreadList()->SuspendAll("Single method undeoptimization");
+ instrumentation->Undeoptimize(method.Get());
+ if (disable_deoptimization) {
+ instrumentation->DisableDeoptimization(key);
+ }
+ runtime->GetThreadList()->ResumeAll();
+ self->TransitionFromSuspendedToRunnable();
+ }
+
+ void DeoptimizeEverything(Thread* self, const char* key, bool enable_deoptimization)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ Runtime* runtime = Runtime::Current();
+ instrumentation::Instrumentation* instrumentation = runtime->GetInstrumentation();
+ self->TransitionFromRunnableToSuspended(kSuspended);
+ runtime->GetThreadList()->SuspendAll("Full deoptimization");
+ if (enable_deoptimization) {
+ instrumentation->EnableDeoptimization();
+ }
+ instrumentation->DeoptimizeEverything(key);
+ runtime->GetThreadList()->ResumeAll();
+ self->TransitionFromSuspendedToRunnable();
+ }
+
+ void UndeoptimizeEverything(Thread* self, const char* key, bool disable_deoptimization)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ Runtime* runtime = Runtime::Current();
+ instrumentation::Instrumentation* instrumentation = runtime->GetInstrumentation();
+ self->TransitionFromRunnableToSuspended(kSuspended);
+ runtime->GetThreadList()->SuspendAll("Full undeoptimization");
+ instrumentation->UndeoptimizeEverything(key);
+ if (disable_deoptimization) {
+ instrumentation->DisableDeoptimization(key);
+ }
+ runtime->GetThreadList()->ResumeAll();
+ self->TransitionFromSuspendedToRunnable();
+ }
+
+ void EnableMethodTracing(Thread* self, const char* key, bool needs_interpreter)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ Runtime* runtime = Runtime::Current();
+ instrumentation::Instrumentation* instrumentation = runtime->GetInstrumentation();
+ self->TransitionFromRunnableToSuspended(kSuspended);
+ runtime->GetThreadList()->SuspendAll("EnableMethodTracing");
+ instrumentation->EnableMethodTracing(key, needs_interpreter);
+ runtime->GetThreadList()->ResumeAll();
+ self->TransitionFromSuspendedToRunnable();
+ }
+
+ void DisableMethodTracing(Thread* self, const char* key)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ Runtime* runtime = Runtime::Current();
+ instrumentation::Instrumentation* instrumentation = runtime->GetInstrumentation();
+ self->TransitionFromRunnableToSuspended(kSuspended);
+ runtime->GetThreadList()->SuspendAll("EnableMethodTracing");
+ instrumentation->DisableMethodTracing(key);
+ runtime->GetThreadList()->ResumeAll();
+ self->TransitionFromSuspendedToRunnable();
+ }
+
+ private:
+ static bool HasEventListener(const instrumentation::Instrumentation* instr, uint32_t event_type)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ switch (event_type) {
+ case instrumentation::Instrumentation::kMethodEntered:
+ return instr->HasMethodEntryListeners();
+ case instrumentation::Instrumentation::kMethodExited:
+ return instr->HasMethodExitListeners();
+ case instrumentation::Instrumentation::kMethodUnwind:
+ return instr->HasMethodUnwindListeners();
+ case instrumentation::Instrumentation::kDexPcMoved:
+ return instr->HasDexPcListeners();
+ case instrumentation::Instrumentation::kFieldRead:
+ return instr->HasFieldReadListeners();
+ case instrumentation::Instrumentation::kFieldWritten:
+ return instr->HasFieldWriteListeners();
+ case instrumentation::Instrumentation::kExceptionCaught:
+ return instr->HasExceptionCaughtListeners();
+ case instrumentation::Instrumentation::kBackwardBranch:
+ return instr->HasBackwardBranchListeners();
+ default:
+ LOG(FATAL) << "Unknown instrumentation event " << event_type;
+ UNREACHABLE();
+ }
+ }
+
+ static void ReportEvent(const instrumentation::Instrumentation* instr, uint32_t event_type,
+ Thread* self, mirror::ArtMethod* method, mirror::Object* obj,
+ uint32_t dex_pc)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ switch (event_type) {
+ case instrumentation::Instrumentation::kMethodEntered:
+ instr->MethodEnterEvent(self, obj, method, dex_pc);
+ break;
+ case instrumentation::Instrumentation::kMethodExited: {
+ JValue value;
+ instr->MethodExitEvent(self, obj, method, dex_pc, value);
+ break;
+ }
+ case instrumentation::Instrumentation::kMethodUnwind:
+ instr->MethodUnwindEvent(self, obj, method, dex_pc);
+ break;
+ case instrumentation::Instrumentation::kDexPcMoved:
+ instr->DexPcMovedEvent(self, obj, method, dex_pc);
+ break;
+ case instrumentation::Instrumentation::kFieldRead:
+ instr->FieldReadEvent(self, obj, method, dex_pc, nullptr);
+ break;
+ case instrumentation::Instrumentation::kFieldWritten: {
+ JValue value;
+ instr->FieldWriteEvent(self, obj, method, dex_pc, nullptr, value);
+ break;
+ }
+ case instrumentation::Instrumentation::kExceptionCaught: {
+ ThrowArithmeticExceptionDivideByZero();
+ mirror::Throwable* event_exception = self->GetException();
+ instr->ExceptionCaughtEvent(self, event_exception);
+ self->ClearException();
+ break;
+ }
+ case instrumentation::Instrumentation::kBackwardBranch:
+ instr->BackwardBranch(self, method, dex_pc);
+ break;
+ default:
+ LOG(FATAL) << "Unknown instrumentation event " << event_type;
+ UNREACHABLE();
+ }
+ }
+
+ static bool DidListenerReceiveEvent(const TestInstrumentationListener& listener,
+ uint32_t event_type) {
+ switch (event_type) {
+ case instrumentation::Instrumentation::kMethodEntered:
+ return listener.received_method_enter_event;
+ case instrumentation::Instrumentation::kMethodExited:
+ return listener.received_method_exit_event;
+ case instrumentation::Instrumentation::kMethodUnwind:
+ return listener.received_method_unwind_event;
+ case instrumentation::Instrumentation::kDexPcMoved:
+ return listener.received_dex_pc_moved_event;
+ case instrumentation::Instrumentation::kFieldRead:
+ return listener.received_field_read_event;
+ case instrumentation::Instrumentation::kFieldWritten:
+ return listener.received_field_written_event;
+ case instrumentation::Instrumentation::kExceptionCaught:
+ return listener.received_exception_caught_event;
+ case instrumentation::Instrumentation::kBackwardBranch:
+ return listener.received_backward_branch_event;
+ default:
+ LOG(FATAL) << "Unknown instrumentation event " << event_type;
+ UNREACHABLE();
+ }
+ }
+};
+
+TEST_F(InstrumentationTest, NoInstrumentation) {
+ ScopedObjectAccess soa(Thread::Current());
+ instrumentation::Instrumentation* instr = Runtime::Current()->GetInstrumentation();
+ ASSERT_NE(instr, nullptr);
+
+ EXPECT_FALSE(instr->AreExitStubsInstalled());
+ EXPECT_FALSE(instr->AreAllMethodsDeoptimized());
+ EXPECT_FALSE(instr->IsActive());
+ EXPECT_FALSE(instr->ShouldNotifyMethodEnterExitEvents());
+
+ // Test interpreter table is the default one.
+ EXPECT_EQ(instrumentation::kMainHandlerTable, instr->GetInterpreterHandlerTable());
+
+ // Check there is no registered listener.
+ EXPECT_FALSE(instr->HasDexPcListeners());
+ EXPECT_FALSE(instr->HasExceptionCaughtListeners());
+ EXPECT_FALSE(instr->HasFieldReadListeners());
+ EXPECT_FALSE(instr->HasFieldWriteListeners());
+ EXPECT_FALSE(instr->HasMethodEntryListeners());
+ EXPECT_FALSE(instr->HasMethodExitListeners());
+ EXPECT_FALSE(instr->IsActive());
+}
+
+// Test instrumentation listeners for each event.
+TEST_F(InstrumentationTest, MethodEntryEvent) {
+ TestEvent(instrumentation::Instrumentation::kMethodEntered);
+}
+
+TEST_F(InstrumentationTest, MethodExitEvent) {
+ TestEvent(instrumentation::Instrumentation::kMethodExited);
+}
+
+TEST_F(InstrumentationTest, MethodUnwindEvent) {
+ TestEvent(instrumentation::Instrumentation::kMethodUnwind);
+}
+
+TEST_F(InstrumentationTest, DexPcMovedEvent) {
+ TestEvent(instrumentation::Instrumentation::kDexPcMoved);
+}
+
+TEST_F(InstrumentationTest, FieldReadEvent) {
+ TestEvent(instrumentation::Instrumentation::kFieldRead);
+}
+
+TEST_F(InstrumentationTest, FieldWriteEvent) {
+ TestEvent(instrumentation::Instrumentation::kFieldWritten);
+}
+
+TEST_F(InstrumentationTest, ExceptionCaughtEvent) {
+ TestEvent(instrumentation::Instrumentation::kExceptionCaught);
+}
+
+TEST_F(InstrumentationTest, BackwardBranchEvent) {
+ TestEvent(instrumentation::Instrumentation::kBackwardBranch);
+}
+
+TEST_F(InstrumentationTest, DeoptimizeDirectMethod) {
+ ScopedObjectAccess soa(Thread::Current());
+ jobject class_loader = LoadDex("Instrumentation");
+ Runtime* const runtime = Runtime::Current();
+ instrumentation::Instrumentation* instr = runtime->GetInstrumentation();
+ ClassLinker* class_linker = runtime->GetClassLinker();
+ StackHandleScope<2> hs(soa.Self());
+ Handle<mirror::ClassLoader> loader(hs.NewHandle(soa.Decode<mirror::ClassLoader*>(class_loader)));
+ mirror::Class* klass = class_linker->FindClass(soa.Self(), "LInstrumentation;", loader);
+ ASSERT_TRUE(klass != nullptr);
+ Handle<mirror::ArtMethod> method_to_deoptimize(
+ hs.NewHandle(klass->FindDeclaredDirectMethod("instanceMethod", "()V")));
+ ASSERT_TRUE(method_to_deoptimize.Get() != nullptr);
+
+ EXPECT_FALSE(instr->AreAllMethodsDeoptimized());
+ EXPECT_FALSE(instr->IsDeoptimized(method_to_deoptimize.Get()));
+
+ DeoptimizeMethod(soa.Self(), method_to_deoptimize, true);
+
+ EXPECT_FALSE(instr->AreAllMethodsDeoptimized());
+ EXPECT_TRUE(instr->AreExitStubsInstalled());
+ EXPECT_TRUE(instr->IsDeoptimized(method_to_deoptimize.Get()));
+
+ constexpr const char* instrumentation_key = "DeoptimizeDirectMethod";
+ UndeoptimizeMethod(soa.Self(), method_to_deoptimize, instrumentation_key, true);
+
+ EXPECT_FALSE(instr->AreAllMethodsDeoptimized());
+ EXPECT_FALSE(instr->IsDeoptimized(method_to_deoptimize.Get()));
+}
+
+TEST_F(InstrumentationTest, FullDeoptimization) {
+ ScopedObjectAccess soa(Thread::Current());
+ Runtime* const runtime = Runtime::Current();
+ instrumentation::Instrumentation* instr = runtime->GetInstrumentation();
+ EXPECT_FALSE(instr->AreAllMethodsDeoptimized());
+
+ constexpr const char* instrumentation_key = "FullDeoptimization";
+ DeoptimizeEverything(soa.Self(), instrumentation_key, true);
+
+ EXPECT_TRUE(instr->AreAllMethodsDeoptimized());
+ EXPECT_TRUE(instr->AreExitStubsInstalled());
+
+ UndeoptimizeEverything(soa.Self(), instrumentation_key, true);
+
+ EXPECT_FALSE(instr->AreAllMethodsDeoptimized());
+}
+
+TEST_F(InstrumentationTest, MixedDeoptimization) {
+ ScopedObjectAccess soa(Thread::Current());
+ jobject class_loader = LoadDex("Instrumentation");
+ Runtime* const runtime = Runtime::Current();
+ instrumentation::Instrumentation* instr = runtime->GetInstrumentation();
+ ClassLinker* class_linker = runtime->GetClassLinker();
+ StackHandleScope<2> hs(soa.Self());
+ Handle<mirror::ClassLoader> loader(hs.NewHandle(soa.Decode<mirror::ClassLoader*>(class_loader)));
+ mirror::Class* klass = class_linker->FindClass(soa.Self(), "LInstrumentation;", loader);
+ ASSERT_TRUE(klass != nullptr);
+ Handle<mirror::ArtMethod> method_to_deoptimize(
+ hs.NewHandle(klass->FindDeclaredDirectMethod("instanceMethod", "()V")));
+ ASSERT_TRUE(method_to_deoptimize.Get() != nullptr);
+
+ EXPECT_FALSE(instr->AreAllMethodsDeoptimized());
+ EXPECT_FALSE(instr->IsDeoptimized(method_to_deoptimize.Get()));
+
+ DeoptimizeMethod(soa.Self(), method_to_deoptimize, true);
+ // Deoptimizing a method does not change instrumentation level.
+ EXPECT_EQ(Instrumentation::InstrumentationLevel::kInstrumentNothing,
+ GetCurrentInstrumentationLevel());
+ EXPECT_FALSE(instr->AreAllMethodsDeoptimized());
+ EXPECT_TRUE(instr->AreExitStubsInstalled());
+ EXPECT_TRUE(instr->IsDeoptimized(method_to_deoptimize.Get()));
+
+ constexpr const char* instrumentation_key = "MixedDeoptimization";
+ DeoptimizeEverything(soa.Self(), instrumentation_key, false);
+ EXPECT_EQ(Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter,
+ GetCurrentInstrumentationLevel());
+ EXPECT_TRUE(instr->AreAllMethodsDeoptimized());
+ EXPECT_TRUE(instr->AreExitStubsInstalled());
+ EXPECT_TRUE(instr->IsDeoptimized(method_to_deoptimize.Get()));
+
+ UndeoptimizeEverything(soa.Self(), instrumentation_key, false);
+ EXPECT_EQ(Instrumentation::InstrumentationLevel::kInstrumentNothing,
+ GetCurrentInstrumentationLevel());
+ EXPECT_FALSE(instr->AreAllMethodsDeoptimized());
+ EXPECT_TRUE(instr->AreExitStubsInstalled());
+ EXPECT_TRUE(instr->IsDeoptimized(method_to_deoptimize.Get()));
+
+ UndeoptimizeMethod(soa.Self(), method_to_deoptimize, instrumentation_key, true);
+ EXPECT_EQ(Instrumentation::InstrumentationLevel::kInstrumentNothing,
+ GetCurrentInstrumentationLevel());
+ EXPECT_FALSE(instr->AreAllMethodsDeoptimized());
+ EXPECT_FALSE(instr->IsDeoptimized(method_to_deoptimize.Get()));
+}
+
+TEST_F(InstrumentationTest, MethodTracing_Interpreter) {
+ ScopedObjectAccess soa(Thread::Current());
+ Runtime* const runtime = Runtime::Current();
+ instrumentation::Instrumentation* instr = runtime->GetInstrumentation();
+ EXPECT_FALSE(instr->AreAllMethodsDeoptimized());
+
+ constexpr const char* instrumentation_key = "MethodTracing";
+ EnableMethodTracing(soa.Self(), instrumentation_key, true);
+ EXPECT_EQ(Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter,
+ GetCurrentInstrumentationLevel());
+ EXPECT_TRUE(instr->AreAllMethodsDeoptimized());
+ EXPECT_TRUE(instr->AreExitStubsInstalled());
+
+ DisableMethodTracing(soa.Self(), instrumentation_key);
+ EXPECT_EQ(Instrumentation::InstrumentationLevel::kInstrumentNothing,
+ GetCurrentInstrumentationLevel());
+ EXPECT_FALSE(instr->AreAllMethodsDeoptimized());
+}
+
+TEST_F(InstrumentationTest, MethodTracing_InstrumentationEntryExitStubs) {
+ ScopedObjectAccess soa(Thread::Current());
+ Runtime* const runtime = Runtime::Current();
+ instrumentation::Instrumentation* instr = runtime->GetInstrumentation();
+ EXPECT_FALSE(instr->AreAllMethodsDeoptimized());
+
+ constexpr const char* instrumentation_key = "MethodTracing";
+ EnableMethodTracing(soa.Self(), instrumentation_key, false);
+ EXPECT_EQ(Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs,
+ GetCurrentInstrumentationLevel());
+ EXPECT_FALSE(instr->AreAllMethodsDeoptimized());
+ EXPECT_TRUE(instr->AreExitStubsInstalled());
+
+ DisableMethodTracing(soa.Self(), instrumentation_key);
+ EXPECT_EQ(Instrumentation::InstrumentationLevel::kInstrumentNothing,
+ GetCurrentInstrumentationLevel());
+ EXPECT_FALSE(instr->AreAllMethodsDeoptimized());
+}
+
+// We use a macro to print the line number where the test is failing.
+#define CHECK_INSTRUMENTATION(_level, _user_count) \
+ do { \
+ Instrumentation* const instr = Runtime::Current()->GetInstrumentation(); \
+ bool interpreter = \
+ (_level == Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter); \
+ EXPECT_EQ(_level, GetCurrentInstrumentationLevel()); \
+ EXPECT_EQ(_user_count, GetInstrumentationUserCount()); \
+ if (instr->IsForcedInterpretOnly()) { \
+ EXPECT_TRUE(instr->InterpretOnly()); \
+ } else if (interpreter) { \
+ EXPECT_TRUE(instr->InterpretOnly()); \
+ } else { \
+ EXPECT_FALSE(instr->InterpretOnly()); \
+ } \
+ if (interpreter) { \
+ EXPECT_TRUE(instr->AreAllMethodsDeoptimized()); \
+ } else { \
+ EXPECT_FALSE(instr->AreAllMethodsDeoptimized()); \
+ } \
+ } while (false)
+
+TEST_F(InstrumentationTest, ConfigureStubs_Nothing) {
+ CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentNothing, 0U);
+
+ // Check no-op.
+ CheckConfigureStubs(kClientOneKey, Instrumentation::InstrumentationLevel::kInstrumentNothing);
+ CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentNothing, 0U);
+}
+
+TEST_F(InstrumentationTest, ConfigureStubs_InstrumentationStubs) {
+ CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentNothing, 0U);
+
+ // Check we can switch to instrumentation stubs
+ CheckConfigureStubs(kClientOneKey,
+ Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs);
+ CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs,
+ 1U);
+
+ // Check we can disable instrumentation.
+ CheckConfigureStubs(kClientOneKey, Instrumentation::InstrumentationLevel::kInstrumentNothing);
+ CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentNothing, 0U);
+}
+
+TEST_F(InstrumentationTest, ConfigureStubs_Interpreter) {
+ CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentNothing, 0U);
+
+ // Check we can switch to interpreter
+ CheckConfigureStubs(kClientOneKey,
+ Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter);
+ CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter, 1U);
+
+ // Check we can disable instrumentation.
+ CheckConfigureStubs(kClientOneKey, Instrumentation::InstrumentationLevel::kInstrumentNothing);
+ CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentNothing, 0U);
+}
+
+TEST_F(InstrumentationTest, ConfigureStubs_InstrumentationStubsToInterpreter) {
+ CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentNothing, 0U);
+
+ // Configure stubs with instrumentation stubs.
+ CheckConfigureStubs(kClientOneKey,
+ Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs);
+ CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs,
+ 1U);
+
+ // Configure stubs with interpreter.
+ CheckConfigureStubs(kClientOneKey,
+ Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter);
+ CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter, 1U);
+
+ // Check we can disable instrumentation.
+ CheckConfigureStubs(kClientOneKey, Instrumentation::InstrumentationLevel::kInstrumentNothing);
+ CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentNothing, 0U);
+}
+
+TEST_F(InstrumentationTest, ConfigureStubs_InterpreterToInstrumentationStubs) {
+ CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentNothing, 0U);
+
+ // Configure stubs with interpreter.
+ CheckConfigureStubs(kClientOneKey,
+ Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter);
+ CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter, 1U);
+
+ // Configure stubs with instrumentation stubs.
+ CheckConfigureStubs(kClientOneKey,
+ Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs);
+ CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs,
+ 1U);
+
+ // Check we can disable instrumentation.
+ CheckConfigureStubs(kClientOneKey, Instrumentation::InstrumentationLevel::kInstrumentNothing);
+ CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentNothing, 0U);
+}
+
+TEST_F(InstrumentationTest,
+ ConfigureStubs_InstrumentationStubsToInterpreterToInstrumentationStubs) {
+ CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentNothing, 0U);
+
+ // Configure stubs with instrumentation stubs.
+ CheckConfigureStubs(kClientOneKey,
+ Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs);
+ CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs,
+ 1U);
+
+ // Configure stubs with interpreter.
+ CheckConfigureStubs(kClientOneKey,
+ Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter);
+ CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter, 1U);
+
+ // Configure stubs with instrumentation stubs again.
+ CheckConfigureStubs(kClientOneKey,
+ Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs);
+ CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs,
+ 1U);
+
+ // Check we can disable instrumentation.
+ CheckConfigureStubs(kClientOneKey, Instrumentation::InstrumentationLevel::kInstrumentNothing);
+ CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentNothing, 0U);
+}
+
+TEST_F(InstrumentationTest, MultiConfigureStubs_Nothing) {
+ CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentNothing, 0U);
+
+ // Check kInstrumentNothing with two clients.
+ CheckConfigureStubs(kClientOneKey, Instrumentation::InstrumentationLevel::kInstrumentNothing);
+ CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentNothing, 0U);
+
+ CheckConfigureStubs(kClientTwoKey, Instrumentation::InstrumentationLevel::kInstrumentNothing);
+ CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentNothing, 0U);
+}
+
+TEST_F(InstrumentationTest, MultiConfigureStubs_InstrumentationStubs) {
+ CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentNothing, 0U);
+
+ // Configure stubs with instrumentation stubs for 1st client.
+ CheckConfigureStubs(kClientOneKey,
+ Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs);
+ CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs,
+ 1U);
+
+ // Configure stubs with instrumentation stubs for 2nd client.
+ CheckConfigureStubs(kClientTwoKey,
+ Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs);
+ CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs,
+ 2U);
+
+ // 1st client requests instrumentation deactivation but 2nd client still needs
+ // instrumentation stubs.
+ CheckConfigureStubs(kClientOneKey, Instrumentation::InstrumentationLevel::kInstrumentNothing);
+ CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs,
+ 1U);
+
+ // 2nd client requests instrumentation deactivation
+ CheckConfigureStubs(kClientTwoKey, Instrumentation::InstrumentationLevel::kInstrumentNothing);
+ CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentNothing, 0U);
+}
+
+TEST_F(InstrumentationTest, MultiConfigureStubs_Interpreter) {
+ CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentNothing, 0U);
+
+ // Configure stubs with interpreter for 1st client.
+ CheckConfigureStubs(kClientOneKey,
+ Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter);
+ CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter, 1U);
+
+ // Configure stubs with interpreter for 2nd client.
+ CheckConfigureStubs(kClientTwoKey,
+ Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter);
+ CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter, 2U);
+
+ // 1st client requests instrumentation deactivation but 2nd client still needs interpreter.
+ CheckConfigureStubs(kClientOneKey, Instrumentation::InstrumentationLevel::kInstrumentNothing);
+ CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter, 1U);
+
+ // 2nd client requests instrumentation deactivation
+ CheckConfigureStubs(kClientTwoKey, Instrumentation::InstrumentationLevel::kInstrumentNothing);
+ CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentNothing, 0U);
+}
+
+TEST_F(InstrumentationTest, MultiConfigureStubs_InstrumentationStubsThenInterpreter) {
+ CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentNothing, 0U);
+
+ // Configure stubs with instrumentation stubs for 1st client.
+ CheckConfigureStubs(kClientOneKey,
+ Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs);
+ CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs,
+ 1U);
+
+ // Configure stubs with interpreter for 2nd client.
+ CheckConfigureStubs(kClientTwoKey,
+ Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter);
+ CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter, 2U);
+
+ // 1st client requests instrumentation deactivation but 2nd client still needs interpreter.
+ CheckConfigureStubs(kClientOneKey, Instrumentation::InstrumentationLevel::kInstrumentNothing);
+ CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter, 1U);
+
+ // 2nd client requests instrumentation deactivation
+ CheckConfigureStubs(kClientTwoKey, Instrumentation::InstrumentationLevel::kInstrumentNothing);
+ CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentNothing, 0U);
+}
+
+TEST_F(InstrumentationTest, MultiConfigureStubs_InterpreterThenInstrumentationStubs) {
+ CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentNothing, 0U);
+
+ // Configure stubs with interpreter for 1st client.
+ CheckConfigureStubs(kClientOneKey,
+ Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter);
+ CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter, 1U);
+
+ // Configure stubs with instrumentation stubs for 2nd client.
+ CheckConfigureStubs(kClientTwoKey,
+ Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs);
+ CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter, 2U);
+
+ // 1st client requests instrumentation deactivation but 2nd client still needs
+ // instrumentation stubs.
+ CheckConfigureStubs(kClientOneKey, Instrumentation::InstrumentationLevel::kInstrumentNothing);
+ CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs,
+ 1U);
+
+ // 2nd client requests instrumentation deactivation
+ CheckConfigureStubs(kClientTwoKey, Instrumentation::InstrumentationLevel::kInstrumentNothing);
+ CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentNothing, 0U);
+}
+
+} // namespace instrumentation
+} // namespace art
diff --git a/runtime/interpreter/interpreter_goto_table_impl.cc b/runtime/interpreter/interpreter_goto_table_impl.cc
index 878efba..dd1f55e 100644
--- a/runtime/interpreter/interpreter_goto_table_impl.cc
+++ b/runtime/interpreter/interpreter_goto_table_impl.cc
@@ -156,7 +156,6 @@
const Instruction* inst = Instruction::At(code_item->insns_ + dex_pc);
uint16_t inst_data;
const void* const* currentHandlersTable;
- bool notified_method_entry_event = false;
UPDATE_HANDLER_TABLE();
if (LIKELY(dex_pc == 0)) { // We are entering the method as opposed to deoptimizing.
if (kIsDebugBuild) {
@@ -166,7 +165,6 @@
if (UNLIKELY(instrumentation->HasMethodEntryListeners())) {
instrumentation->MethodEnterEvent(self, shadow_frame.GetThisObject(code_item->ins_size_),
shadow_frame.GetMethod(), 0);
- notified_method_entry_event = true;
}
}
@@ -264,9 +262,6 @@
instrumentation->MethodExitEvent(self, shadow_frame.GetThisObject(code_item->ins_size_),
shadow_frame.GetMethod(), dex_pc,
result);
- } else if (UNLIKELY(instrumentation->HasDexPcListeners())) {
- instrumentation->DexPcMovedEvent(self, shadow_frame.GetThisObject(code_item->ins_size_),
- shadow_frame.GetMethod(), dex_pc);
}
return result;
}
@@ -281,9 +276,6 @@
instrumentation->MethodExitEvent(self, shadow_frame.GetThisObject(code_item->ins_size_),
shadow_frame.GetMethod(), dex_pc,
result);
- } else if (UNLIKELY(instrumentation->HasDexPcListeners())) {
- instrumentation->DexPcMovedEvent(self, shadow_frame.GetThisObject(code_item->ins_size_),
- shadow_frame.GetMethod(), dex_pc);
}
return result;
}
@@ -299,9 +291,6 @@
instrumentation->MethodExitEvent(self, shadow_frame.GetThisObject(code_item->ins_size_),
shadow_frame.GetMethod(), dex_pc,
result);
- } else if (UNLIKELY(instrumentation->HasDexPcListeners())) {
- instrumentation->DexPcMovedEvent(self, shadow_frame.GetThisObject(code_item->ins_size_),
- shadow_frame.GetMethod(), dex_pc);
}
return result;
}
@@ -316,9 +305,6 @@
instrumentation->MethodExitEvent(self, shadow_frame.GetThisObject(code_item->ins_size_),
shadow_frame.GetMethod(), dex_pc,
result);
- } else if (UNLIKELY(instrumentation->HasDexPcListeners())) {
- instrumentation->DexPcMovedEvent(self, shadow_frame.GetThisObject(code_item->ins_size_),
- shadow_frame.GetMethod(), dex_pc);
}
return result;
}
@@ -352,9 +338,6 @@
instrumentation->MethodExitEvent(self, shadow_frame.GetThisObject(code_item->ins_size_),
shadow_frame.GetMethod(), dex_pc,
result);
- } else if (UNLIKELY(instrumentation->HasDexPcListeners())) {
- instrumentation->DexPcMovedEvent(self, shadow_frame.GetThisObject(code_item->ins_size_),
- shadow_frame.GetMethod(), dex_pc);
}
return result;
}
@@ -2510,26 +2493,16 @@
// Note: we do not use the kReturn instruction flag here (to test the instruction is a return). The
// compiler seems to not evaluate "(Instruction::FlagsOf(Instruction::code) & kReturn) != 0" to
// a constant condition that would remove the "if" statement so the test is free.
-#define INSTRUMENTATION_INSTRUCTION_HANDLER(o, code, n, f, r, i, a, v) \
- alt_op_##code: { \
- if (Instruction::code != Instruction::RETURN_VOID && \
- Instruction::code != Instruction::RETURN_VOID_NO_BARRIER && \
- Instruction::code != Instruction::RETURN && \
- Instruction::code != Instruction::RETURN_WIDE && \
- Instruction::code != Instruction::RETURN_OBJECT) { \
- if (LIKELY(!notified_method_entry_event)) { \
- Runtime* runtime = Runtime::Current(); \
- const instrumentation::Instrumentation* instrumentation = runtime->GetInstrumentation(); \
- if (UNLIKELY(instrumentation->HasDexPcListeners())) { \
- Object* this_object = shadow_frame.GetThisObject(code_item->ins_size_); \
- instrumentation->DexPcMovedEvent(self, this_object, shadow_frame.GetMethod(), dex_pc); \
- } \
- } else { \
- notified_method_entry_event = false; \
- } \
- } \
- UPDATE_HANDLER_TABLE(); \
- goto *handlersTable[instrumentation::kMainHandlerTable][Instruction::code]; \
+#define INSTRUMENTATION_INSTRUCTION_HANDLER(o, code, n, f, r, i, a, v) \
+ alt_op_##code: { \
+ Runtime* const runtime = Runtime::Current(); \
+ const instrumentation::Instrumentation* instrumentation = runtime->GetInstrumentation(); \
+ if (UNLIKELY(instrumentation->HasDexPcListeners())) { \
+ Object* this_object = shadow_frame.GetThisObject(code_item->ins_size_); \
+ instrumentation->DexPcMovedEvent(self, this_object, shadow_frame.GetMethod(), dex_pc); \
+ } \
+ UPDATE_HANDLER_TABLE(); \
+ goto *handlersTable[instrumentation::kMainHandlerTable][Instruction::code]; \
}
#include "dex_instruction_list.h"
DEX_INSTRUCTION_LIST(INSTRUMENTATION_INSTRUCTION_HANDLER)
diff --git a/runtime/interpreter/interpreter_switch_impl.cc b/runtime/interpreter/interpreter_switch_impl.cc
index a5e5299..0e3420f 100644
--- a/runtime/interpreter/interpreter_switch_impl.cc
+++ b/runtime/interpreter/interpreter_switch_impl.cc
@@ -47,10 +47,7 @@
// Code to run before each dex instruction.
#define PREAMBLE() \
do { \
- DCHECK(!inst->IsReturn()); \
- if (UNLIKELY(notified_method_entry_event)) { \
- notified_method_entry_event = false; \
- } else if (UNLIKELY(instrumentation->HasDexPcListeners())) { \
+ if (UNLIKELY(instrumentation->HasDexPcListeners())) { \
instrumentation->DexPcMovedEvent(self, shadow_frame.GetThisObject(code_item->ins_size_), \
shadow_frame.GetMethod(), dex_pc); \
} \
@@ -67,7 +64,6 @@
self->VerifyStack();
uint32_t dex_pc = shadow_frame.GetDexPC();
- bool notified_method_entry_event = false;
const auto* const instrumentation = Runtime::Current()->GetInstrumentation();
if (LIKELY(dex_pc == 0)) { // We are entering the method as opposed to deoptimizing.
if (kIsDebugBuild) {
@@ -76,7 +72,6 @@
if (UNLIKELY(instrumentation->HasMethodEntryListeners())) {
instrumentation->MethodEnterEvent(self, shadow_frame.GetThisObject(code_item->ins_size_),
shadow_frame.GetMethod(), 0);
- notified_method_entry_event = true;
}
}
const uint16_t* const insns = code_item->insns_;
@@ -171,19 +166,18 @@
break;
}
case Instruction::RETURN_VOID_NO_BARRIER: {
+ PREAMBLE();
JValue result;
self->AllowThreadSuspension();
if (UNLIKELY(instrumentation->HasMethodExitListeners())) {
instrumentation->MethodExitEvent(self, shadow_frame.GetThisObject(code_item->ins_size_),
shadow_frame.GetMethod(), inst->GetDexPc(insns),
result);
- } else if (UNLIKELY(instrumentation->HasDexPcListeners())) {
- instrumentation->DexPcMovedEvent(self, shadow_frame.GetThisObject(code_item->ins_size_),
- shadow_frame.GetMethod(), dex_pc);
}
return result;
}
case Instruction::RETURN_VOID: {
+ PREAMBLE();
QuasiAtomic::ThreadFenceForConstructor();
JValue result;
self->AllowThreadSuspension();
@@ -191,13 +185,11 @@
instrumentation->MethodExitEvent(self, shadow_frame.GetThisObject(code_item->ins_size_),
shadow_frame.GetMethod(), inst->GetDexPc(insns),
result);
- } else if (UNLIKELY(instrumentation->HasDexPcListeners())) {
- instrumentation->DexPcMovedEvent(self, shadow_frame.GetThisObject(code_item->ins_size_),
- shadow_frame.GetMethod(), dex_pc);
}
return result;
}
case Instruction::RETURN: {
+ PREAMBLE();
JValue result;
result.SetJ(0);
result.SetI(shadow_frame.GetVReg(inst->VRegA_11x(inst_data)));
@@ -206,13 +198,11 @@
instrumentation->MethodExitEvent(self, shadow_frame.GetThisObject(code_item->ins_size_),
shadow_frame.GetMethod(), inst->GetDexPc(insns),
result);
- } else if (UNLIKELY(instrumentation->HasDexPcListeners())) {
- instrumentation->DexPcMovedEvent(self, shadow_frame.GetThisObject(code_item->ins_size_),
- shadow_frame.GetMethod(), dex_pc);
}
return result;
}
case Instruction::RETURN_WIDE: {
+ PREAMBLE();
JValue result;
result.SetJ(shadow_frame.GetVRegLong(inst->VRegA_11x(inst_data)));
self->AllowThreadSuspension();
@@ -220,13 +210,11 @@
instrumentation->MethodExitEvent(self, shadow_frame.GetThisObject(code_item->ins_size_),
shadow_frame.GetMethod(), inst->GetDexPc(insns),
result);
- } else if (UNLIKELY(instrumentation->HasDexPcListeners())) {
- instrumentation->DexPcMovedEvent(self, shadow_frame.GetThisObject(code_item->ins_size_),
- shadow_frame.GetMethod(), dex_pc);
}
return result;
}
case Instruction::RETURN_OBJECT: {
+ PREAMBLE();
JValue result;
self->AllowThreadSuspension();
const size_t ref_idx = inst->VRegA_11x(inst_data);
@@ -254,9 +242,6 @@
instrumentation->MethodExitEvent(self, shadow_frame.GetThisObject(code_item->ins_size_),
shadow_frame.GetMethod(), inst->GetDexPc(insns),
result);
- } else if (UNLIKELY(instrumentation->HasDexPcListeners())) {
- instrumentation->DexPcMovedEvent(self, shadow_frame.GetThisObject(code_item->ins_size_),
- shadow_frame.GetMethod(), dex_pc);
}
return result;
}
diff --git a/runtime/jdwp/jdwp_event.cc b/runtime/jdwp/jdwp_event.cc
index 1ec800f..ab3f2e4 100644
--- a/runtime/jdwp/jdwp_event.cc
+++ b/runtime/jdwp/jdwp_event.cc
@@ -141,6 +141,8 @@
}
}
+// Returns the instrumentation event the DebugInstrumentationListener must
+// listen to in order to properly report the given JDWP event to the debugger.
static uint32_t GetInstrumentationEventFor(JdwpEventKind eventKind) {
switch (eventKind) {
case EK_BREAKPOINT:
diff --git a/runtime/jit/jit.h b/runtime/jit/jit.h
index f5ad8b8..c698cfc 100644
--- a/runtime/jit/jit.h
+++ b/runtime/jit/jit.h
@@ -19,8 +19,6 @@
#include <unordered_map>
-#include "instrumentation.h"
-
#include "atomic.h"
#include "base/macros.h"
#include "base/mutex.h"
diff --git a/runtime/oat.h b/runtime/oat.h
index a31e09a..aaf442a 100644
--- a/runtime/oat.h
+++ b/runtime/oat.h
@@ -32,7 +32,7 @@
class PACKED(4) OatHeader {
public:
static constexpr uint8_t kOatMagic[] = { 'o', 'a', 't', '\n' };
- static constexpr uint8_t kOatVersion[] = { '0', '6', '1', '\0' };
+ static constexpr uint8_t kOatVersion[] = { '0', '6', '2', '\0' };
static constexpr const char* kImageLocationKey = "image-location";
static constexpr const char* kDex2OatCmdLineKey = "dex2oat-cmdline";
diff --git a/runtime/reflection.cc b/runtime/reflection.cc
index 329ceb5..49e1b8e 100644
--- a/runtime/reflection.cc
+++ b/runtime/reflection.cc
@@ -520,23 +520,6 @@
return result;
}
-void InvokeWithShadowFrame(Thread* self, ShadowFrame* shadow_frame, uint16_t arg_offset,
- JValue* result) {
- // We want to make sure that the stack is not within a small distance from the
- // protected region in case we are calling into a leaf function whose stack
- // check has been elided.
- if (UNLIKELY(__builtin_frame_address(0) < self->GetStackEnd())) {
- ThrowStackOverflowError(self);
- return;
- }
- uint32_t shorty_len;
- const char* shorty = shadow_frame->GetMethod()->GetShorty(&shorty_len);
- ArgArray arg_array(shorty, shorty_len);
- arg_array.BuildArgArrayFromFrame(shadow_frame, arg_offset);
- shadow_frame->GetMethod()->Invoke(self, arg_array.GetArray(), arg_array.GetNumBytes(), result,
- shorty);
-}
-
jobject InvokeMethod(const ScopedObjectAccessAlreadyRunnable& soa, jobject javaMethod,
jobject javaReceiver, jobject javaArgs, size_t num_frames) {
// We want to make sure that the stack is not within a small distance from the
diff --git a/runtime/reflection.h b/runtime/reflection.h
index 6305d68..37f8a6a 100644
--- a/runtime/reflection.h
+++ b/runtime/reflection.h
@@ -61,10 +61,6 @@
jobject obj, jmethodID mid, va_list args)
SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
-void InvokeWithShadowFrame(Thread* self, ShadowFrame* shadow_frame, uint16_t arg_offset,
- JValue* result)
- SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
-
// num_frames is number of frames we look up for access check.
jobject InvokeMethod(const ScopedObjectAccessAlreadyRunnable& soa, jobject method, jobject receiver,
jobject args, size_t num_frames = 1)
diff --git a/runtime/thread.h b/runtime/thread.h
index e766daa..9346813 100644
--- a/runtime/thread.h
+++ b/runtime/thread.h
@@ -752,6 +752,18 @@
tls32_.ready_for_debug_invoke = ready;
}
+ bool IsDebugMethodEntry() const {
+ return tls32_.debug_method_entry_;
+ }
+
+ void SetDebugMethodEntry() {
+ tls32_.debug_method_entry_ = true;
+ }
+
+ void ClearDebugMethodEntry() {
+ tls32_.debug_method_entry_ = false;
+ }
+
// Activates single step control for debugging. The thread takes the
// ownership of the given SingleStepControl*. It is deleted by a call
// to DeactivateSingleStepControl or upon thread destruction.
@@ -1028,7 +1040,7 @@
suspend_count(0), debug_suspend_count(0), thin_lock_thread_id(0), tid(0),
daemon(is_daemon), throwing_OutOfMemoryError(false), no_thread_suspension(0),
thread_exit_check_count(0), handling_signal_(false), suspended_at_suspend_check(false),
- ready_for_debug_invoke(false) {
+ ready_for_debug_invoke(false), debug_method_entry_(false) {
}
union StateAndFlags state_and_flags;
@@ -1077,6 +1089,10 @@
// used to invoke method from the debugger which is only allowed when
// the thread is suspended by an event.
bool32_t ready_for_debug_invoke;
+
+ // True if the thread enters a method. This is used to detect method entry
+ // event for the debugger.
+ bool32_t debug_method_entry_;
} tls32_;
struct PACKED(8) tls_64bit_sized_values {
diff --git a/runtime/trace.cc b/runtime/trace.cc
index 9eca517..3b8feda 100644
--- a/runtime/trace.cc
+++ b/runtime/trace.cc
@@ -126,6 +126,9 @@
pthread_t Trace::sampling_pthread_ = 0U;
std::unique_ptr<std::vector<mirror::ArtMethod*>> Trace::temp_stack_trace_;
+// The key identifying the tracer to update instrumentation.
+static constexpr const char* kTracerInstrumentationKey = "Tracer";
+
static mirror::ArtMethod* DecodeTraceMethodId(uint32_t tmid) {
return reinterpret_cast<mirror::ArtMethod*>(tmid & ~kTraceMethodActionMask);
}
@@ -393,7 +396,7 @@
instrumentation::Instrumentation::kMethodExited |
instrumentation::Instrumentation::kMethodUnwind);
// TODO: In full-PIC mode, we don't need to fully deopt.
- runtime->GetInstrumentation()->EnableMethodTracing();
+ runtime->GetInstrumentation()->EnableMethodTracing(kTracerInstrumentationKey);
}
}
}
@@ -440,7 +443,7 @@
MutexLock mu(Thread::Current(), *Locks::thread_list_lock_);
runtime->GetThreadList()->ForEach(ClearThreadStackTraceAndClockBase, nullptr);
} else {
- runtime->GetInstrumentation()->DisableMethodTracing();
+ runtime->GetInstrumentation()->DisableMethodTracing(kTracerInstrumentationKey);
runtime->GetInstrumentation()->RemoveListener(
the_trace, instrumentation::Instrumentation::kMethodEntered |
instrumentation::Instrumentation::kMethodExited |
@@ -522,7 +525,7 @@
MutexLock mu(Thread::Current(), *Locks::thread_list_lock_);
runtime->GetThreadList()->ForEach(ClearThreadStackTraceAndClockBase, nullptr);
} else {
- runtime->GetInstrumentation()->DisableMethodTracing();
+ runtime->GetInstrumentation()->DisableMethodTracing(kTracerInstrumentationKey);
runtime->GetInstrumentation()->RemoveListener(the_trace,
instrumentation::Instrumentation::kMethodEntered |
instrumentation::Instrumentation::kMethodExited |
@@ -566,7 +569,7 @@
instrumentation::Instrumentation::kMethodExited |
instrumentation::Instrumentation::kMethodUnwind);
// TODO: In full-PIC mode, we don't need to fully deopt.
- runtime->GetInstrumentation()->EnableMethodTracing();
+ runtime->GetInstrumentation()->EnableMethodTracing(kTracerInstrumentationKey);
}
runtime->GetThreadList()->ResumeAll();
diff --git a/runtime/utf.h b/runtime/utf.h
index dd38afa..7f05248 100644
--- a/runtime/utf.h
+++ b/runtime/utf.h
@@ -87,9 +87,9 @@
/*
* Retrieve the next UTF-16 character or surrogate pair from a UTF-8 string.
* single byte, 2-byte and 3-byte UTF-8 sequences result in a single UTF-16
- * character whereas 4-byte UTF-8 sequences result in a surrogate pair. Use
- * GetLeadingUtf16Char and GetTrailingUtf16Char to process the return value
- * of this function.
+ * character (possibly one half of a surrogate) whereas 4-byte UTF-8 sequences
+ * result in a surrogate pair. Use GetLeadingUtf16Char and GetTrailingUtf16Char
+ * to process the return value of this function.
*
* Advances "*utf8_data_in" to the start of the next character.
*
diff --git a/runtime/utils.cc b/runtime/utils.cc
index 650214f..7986cdc 100644
--- a/runtime/utils.cc
+++ b/runtime/utils.cc
@@ -827,14 +827,21 @@
*/
const uint32_t pair = GetUtf16FromUtf8(pUtf8Ptr);
-
const uint16_t leading = GetLeadingUtf16Char(pair);
- const uint32_t trailing = GetTrailingUtf16Char(pair);
- if (trailing == 0) {
- // Perform follow-up tests based on the high 8 bits of the
- // lower surrogate.
- switch (leading >> 8) {
+ // We have a surrogate pair resulting from a valid 4 byte UTF sequence.
+ // No further checks are necessary because 4 byte sequences span code
+ // points [U+10000, U+1FFFFF], which are valid codepoints in a dex
+ // identifier. Furthermore, GetUtf16FromUtf8 guarantees that each of
+ // the surrogate halves are valid and well formed in this instance.
+ if (GetTrailingUtf16Char(pair) != 0) {
+ return true;
+ }
+
+
+ // We've encountered a one, two or three byte UTF-8 sequence. The
+ // three byte UTF-8 sequence could be one half of a surrogate pair.
+ switch (leading >> 8) {
case 0x00:
// It's only valid if it's above the ISO-8859-1 high space (0xa0).
return (leading > 0x00a0);
@@ -842,9 +849,14 @@
case 0xd9:
case 0xda:
case 0xdb:
- // It looks like a leading surrogate but we didn't find a trailing
- // surrogate if we're here.
- return false;
+ {
+ // We found a three byte sequence encoding one half of a surrogate.
+ // Look for the other half.
+ const uint32_t pair2 = GetUtf16FromUtf8(pUtf8Ptr);
+ const uint16_t trailing = GetLeadingUtf16Char(pair2);
+
+ return (GetTrailingUtf16Char(pair2) == 0) && (0xdc00 <= trailing && trailing <= 0xdfff);
+ }
case 0xdc:
case 0xdd:
case 0xde:
@@ -855,21 +867,19 @@
case 0xff:
// It's in the range that has spaces, controls, and specials.
switch (leading & 0xfff8) {
- case 0x2000:
- case 0x2008:
- case 0x2028:
- case 0xfff0:
- case 0xfff8:
- return false;
+ case 0x2000:
+ case 0x2008:
+ case 0x2028:
+ case 0xfff0:
+ case 0xfff8:
+ return false;
}
- break;
- }
-
- return true;
+ return true;
+ default:
+ return true;
}
- // We have a surrogate pair. Check that trailing surrogate is well formed.
- return (trailing >= 0xdc00 && trailing <= 0xdfff);
+ UNREACHABLE();
}
/* Return whether the pointed-at modified-UTF-8 encoded character is
diff --git a/runtime/utils_test.cc b/runtime/utils_test.cc
index 195de0c..869d305 100644
--- a/runtime/utils_test.cc
+++ b/runtime/utils_test.cc
@@ -521,4 +521,27 @@
EXPECT_GT(NanoTime() - start, MsToNs(1000));
}
+TEST_F(UtilsTest, IsValidDescriptor) {
+ std::vector<uint8_t> descriptor(
+ { 'L', 'a', '/', 'b', '$', 0xed, 0xa0, 0x80, 0xed, 0xb0, 0x80, ';', 0x00 });
+ EXPECT_TRUE(IsValidDescriptor(reinterpret_cast<char*>(&descriptor[0])));
+
+ std::vector<uint8_t> unpaired_surrogate(
+ { 'L', 'a', '/', 'b', '$', 0xed, 0xa0, 0x80, ';', 0x00 });
+ EXPECT_FALSE(IsValidDescriptor(reinterpret_cast<char*>(&unpaired_surrogate[0])));
+
+ std::vector<uint8_t> unpaired_surrogate_at_end(
+ { 'L', 'a', '/', 'b', '$', 0xed, 0xa0, 0x80, 0x00 });
+ EXPECT_FALSE(IsValidDescriptor(reinterpret_cast<char*>(&unpaired_surrogate_at_end[0])));
+
+ std::vector<uint8_t> invalid_surrogate(
+ { 'L', 'a', '/', 'b', '$', 0xed, 0xb0, 0x80, ';', 0x00 });
+ EXPECT_FALSE(IsValidDescriptor(reinterpret_cast<char*>(&invalid_surrogate[0])));
+
+ std::vector<uint8_t> unpaired_surrogate_with_multibyte_sequence(
+ { 'L', 'a', '/', 'b', '$', 0xed, 0xb0, 0x80, 0xf0, 0x9f, 0x8f, 0xa0, ';', 0x00 });
+ EXPECT_FALSE(
+ IsValidDescriptor(reinterpret_cast<char*>(&unpaired_surrogate_with_multibyte_sequence[0])));
+}
+
} // namespace art
diff --git a/test/Instrumentation/Instrumentation.java b/test/Instrumentation/Instrumentation.java
new file mode 100644
index 0000000..09d4342
--- /dev/null
+++ b/test/Instrumentation/Instrumentation.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2015 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 Instrumentation {
+ // Direct method
+ private void instanceMethod() {
+ System.out.println("instanceMethod");
+ }
+}