Replace instrumention id with stack pointers.
Replace instrumentation ids for instrumentation frames, which are
inherently broken (see b/72608560), and use stack addresses instead
to properly identify which frames to pop / unwind.
Bug: 72608560
Bug: 148166031
Test: ./art/test/testrunner/testrunner.py --trace --debuggable --ndebuggable --optimizing --interpreter --jit --debug --ndebug -j32
Test: run-libjdwp-tests.sh
Test: 2011-stack-walk-concurrent-instrument
Test: ./art/test/run-test --host --dev --runtime-option -verbose:deopt,plugin --prebuild --compact-dex-level fast --jit --no-relocate --create-runner --runtime-option -Xcheck:jni 1965-get-set-local-primitive-no-tables
art/tools/parallel_run.py -j80 /tmp/path/to/runit.sh --out failure.txt
Change-Id: I71f6e55b9da608796cd3142b147f7b50bbd292ec
diff --git a/openjdkjvmti/deopt_manager.cc b/openjdkjvmti/deopt_manager.cc
index 3b04ed8..3e3691a 100644
--- a/openjdkjvmti/deopt_manager.cc
+++ b/openjdkjvmti/deopt_manager.cc
@@ -487,9 +487,11 @@
void DeoptManager::DeoptimizeThread(art::Thread* target) {
// We might or might not be running on the target thread (self) so get Thread::Current
// directly.
+ art::ScopedThreadSuspension sts(art::Thread::Current(), art::kSuspended);
art::gc::ScopedGCCriticalSection sgccs(art::Thread::Current(),
art::gc::GcCause::kGcCauseDebugger,
art::gc::CollectorType::kCollectorTypeDebugger);
+ art::ScopedSuspendAll ssa("Instrument thread stack");
art::Runtime::Current()->GetInstrumentation()->InstrumentThreadStack(target);
}
diff --git a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc
index 1049c5d..1304c0d 100644
--- a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc
+++ b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc
@@ -358,12 +358,16 @@
}
}
- // For the given quick ref and args quick frame, return the caller's PC.
- static uintptr_t GetCallingPc(ArtMethod** sp) REQUIRES_SHARED(Locks::mutator_lock_) {
+ static uint8_t* GetCallingPcAddr(ArtMethod** sp) REQUIRES_SHARED(Locks::mutator_lock_) {
DCHECK((*sp)->IsCalleeSaveMethod());
uint8_t* return_adress_spill =
reinterpret_cast<uint8_t*>(sp) + kQuickCalleeSaveFrame_RefAndArgs_ReturnPcOffset;
- return *reinterpret_cast<uintptr_t*>(return_adress_spill);
+ return return_adress_spill;
+ }
+
+ // For the given quick ref and args quick frame, return the caller's PC.
+ static uintptr_t GetCallingPc(ArtMethod** sp) REQUIRES_SHARED(Locks::mutator_lock_) {
+ return *reinterpret_cast<uintptr_t*>(GetCallingPcAddr(sp));
}
QuickArgumentVisitor(ArtMethod** sp, bool is_static, const char* shorty,
@@ -1156,6 +1160,8 @@
instrumentation->PushInstrumentationStackFrame(self,
is_static ? nullptr : this_object,
method,
+ reinterpret_cast<uintptr_t>(
+ QuickArgumentVisitor::GetCallingPcAddr(sp)),
QuickArgumentVisitor::GetCallingPc(sp),
interpreter_entry);
@@ -1181,9 +1187,9 @@
// Compute address of return PC and sanity check that it currently holds 0.
constexpr size_t return_pc_offset =
RuntimeCalleeSaveFrame::GetReturnPcOffset(CalleeSaveType::kSaveEverything);
- uintptr_t* return_pc = reinterpret_cast<uintptr_t*>(reinterpret_cast<uint8_t*>(sp) +
- return_pc_offset);
- CHECK_EQ(*return_pc, 0U);
+ uintptr_t* return_pc_addr = reinterpret_cast<uintptr_t*>(reinterpret_cast<uint8_t*>(sp) +
+ return_pc_offset);
+ CHECK_EQ(*return_pc_addr, 0U);
// Pop the frame filling in the return pc. The low half of the return value is 0 when
// deoptimization shouldn't be performed with the high-half having the return address. When
@@ -1191,7 +1197,7 @@
// deoptimization entry point.
instrumentation::Instrumentation* instrumentation = Runtime::Current()->GetInstrumentation();
TwoWordReturn return_or_deoptimize_pc = instrumentation->PopInstrumentationStackFrame(
- self, return_pc, gpr_result, fpr_result);
+ self, return_pc_addr, gpr_result, fpr_result);
if (self->IsExceptionPending() || self->ObserveAsyncException()) {
return GetTwoWordFailureValue();
}
diff --git a/runtime/instrumentation.cc b/runtime/instrumentation.cc
index 011d947..60e7c9c 100644
--- a/runtime/instrumentation.cc
+++ b/runtime/instrumentation.cc
@@ -107,23 +107,23 @@
InstrumentationStackPopper::InstrumentationStackPopper(Thread* self)
: self_(self),
instrumentation_(Runtime::Current()->GetInstrumentation()),
- frames_to_remove_(0) {}
+ pop_until_(0u) {}
InstrumentationStackPopper::~InstrumentationStackPopper() {
- std::deque<instrumentation::InstrumentationStackFrame>* stack = self_->GetInstrumentationStack();
- for (size_t i = 0; i < frames_to_remove_; i++) {
- stack->pop_front();
+ std::map<uintptr_t, instrumentation::InstrumentationStackFrame>* stack =
+ self_->GetInstrumentationStack();
+ for (auto i = stack->begin(); i != stack->end() && i->first <= pop_until_;) {
+ i = stack->erase(i);
}
}
-bool InstrumentationStackPopper::PopFramesTo(uint32_t desired_pops,
+bool InstrumentationStackPopper::PopFramesTo(uintptr_t stack_pointer,
MutableHandle<mirror::Throwable>& exception) {
- std::deque<instrumentation::InstrumentationStackFrame>* stack = self_->GetInstrumentationStack();
- DCHECK_LE(frames_to_remove_, desired_pops);
- DCHECK_GE(stack->size(), desired_pops);
+ std::map<uintptr_t, instrumentation::InstrumentationStackFrame>* stack =
+ self_->GetInstrumentationStack();
DCHECK(!self_->IsExceptionPending());
if (!instrumentation_->HasMethodUnwindListeners()) {
- frames_to_remove_ = desired_pops;
+ pop_until_ = stack_pointer;
return true;
}
if (kVerboseInstrumentation) {
@@ -132,8 +132,14 @@
// The instrumentation events expect the exception to be set.
self_->SetException(exception.Get());
bool new_exception_thrown = false;
- for (; frames_to_remove_ < desired_pops && !new_exception_thrown; frames_to_remove_++) {
- InstrumentationStackFrame frame = stack->at(frames_to_remove_);
+ auto i = stack->upper_bound(pop_until_);
+
+ // Now pop all frames until reaching stack_pointer, or a new exception is
+ // thrown. Note that `stack_pointer` doesn't need to be a return PC address
+ // (in fact the exception handling code passes the start of the frame where
+ // the catch handler is).
+ for (; i != stack->end() && i->first <= stack_pointer; i++) {
+ const InstrumentationStackFrame& frame = i->second;
ArtMethod* method = frame.method_;
// Notify listeners of method unwind.
// TODO: improve the dex_pc information here.
@@ -144,13 +150,19 @@
if (!method->IsRuntimeMethod() && !frame.interpreter_entry_) {
instrumentation_->MethodUnwindEvent(self_, frame.this_object_, method, dex_pc);
new_exception_thrown = self_->GetException() != exception.Get();
+ if (new_exception_thrown) {
+ pop_until_ = i->first;
+ break;
+ }
}
}
+ if (!new_exception_thrown) {
+ pop_until_ = stack_pointer;
+ }
exception.Assign(self_->GetException());
self_->ClearException();
if (kVerboseInstrumentation && new_exception_thrown) {
- LOG(INFO) << "Failed to pop " << (desired_pops - frames_to_remove_)
- << " frames due to new exception";
+ LOG(INFO) << "Did partial pop of frames due to new exception";
}
return !new_exception_thrown;
}
@@ -284,7 +296,8 @@
// Since we may already have done this previously, we need to push new instrumentation frame before
// existing instrumentation frames.
void InstrumentationInstallStack(Thread* thread, void* arg)
- REQUIRES_SHARED(Locks::mutator_lock_) {
+ REQUIRES(Locks::mutator_lock_) {
+ Locks::mutator_lock_->AssertExclusiveHeld(Thread::Current());
struct InstallStackVisitor final : public StackVisitor {
InstallStackVisitor(Thread* thread_in,
Context* context,
@@ -294,7 +307,6 @@
instrumentation_stack_(thread_in->GetInstrumentationStack()),
instrumentation_exit_pc_(instrumentation_exit_pc),
reached_existing_instrumentation_frames_(false),
- instrumentation_stack_depth_(0),
last_return_pc_(0),
force_deopt_id_(force_deopt_id) {}
@@ -326,11 +338,10 @@
LOG(INFO) << " Installing exit stub in " << DescribeLocation();
}
if (return_pc == instrumentation_exit_pc_) {
- CHECK_LT(instrumentation_stack_depth_, instrumentation_stack_->size());
-
+ auto it = instrumentation_stack_->find(GetReturnPcAddr());
+ CHECK(it != instrumentation_stack_->end());
+ const InstrumentationStackFrame& frame = it->second;
if (m->IsRuntimeMethod()) {
- const InstrumentationStackFrame& frame =
- (*instrumentation_stack_)[instrumentation_stack_depth_];
if (frame.interpreter_entry_) {
// This instrumentation frame is for an interpreter bridge and is
// pushed when executing the instrumented interpreter bridge. So method
@@ -339,7 +350,6 @@
uint32_t dex_pc = dex::kDexNoIndex;
dex_pcs_.push_back(dex_pc);
last_return_pc_ = frame.return_pc_;
- ++instrumentation_stack_depth_;
return true;
}
}
@@ -348,8 +358,6 @@
// We should have already installed instrumentation or be interpreter on previous frames.
reached_existing_instrumentation_frames_ = true;
- const InstrumentationStackFrame& frame =
- (*instrumentation_stack_)[instrumentation_stack_depth_];
CHECK_EQ(m->GetNonObsoleteMethod(), frame.method_->GetNonObsoleteMethod())
<< "Expected " << ArtMethod::PrettyMethod(m)
<< ", Found " << ArtMethod::PrettyMethod(frame.method_);
@@ -387,16 +395,7 @@
LOG(INFO) << "Pushing frame " << instrumentation_frame.Dump();
}
- // Insert frame at the right position so we do not corrupt the instrumentation stack.
- // Instrumentation stack frames are in descending frame id order.
- auto it = instrumentation_stack_->begin();
- for (auto end = instrumentation_stack_->end(); it != end; ++it) {
- const InstrumentationStackFrame& current = *it;
- if (instrumentation_frame.frame_id_ >= current.frame_id_) {
- break;
- }
- }
- instrumentation_stack_->insert(it, instrumentation_frame);
+ instrumentation_stack_->insert({GetReturnPcAddr(), instrumentation_frame});
SetReturnPc(instrumentation_exit_pc_);
}
uint32_t dex_pc = dex::kDexNoIndex;
@@ -405,15 +404,13 @@
}
dex_pcs_.push_back(dex_pc);
last_return_pc_ = return_pc;
- ++instrumentation_stack_depth_;
return true; // Continue.
}
- std::deque<InstrumentationStackFrame>* const instrumentation_stack_;
+ std::map<uintptr_t, InstrumentationStackFrame>* const instrumentation_stack_;
std::vector<InstrumentationStackFrame> shadow_stack_;
std::vector<uint32_t> dex_pcs_;
const uintptr_t instrumentation_exit_pc_;
bool reached_existing_instrumentation_frames_;
- size_t instrumentation_stack_depth_;
uintptr_t last_return_pc_;
uint64_t force_deopt_id_;
};
@@ -434,17 +431,20 @@
if (instrumentation->ShouldNotifyMethodEnterExitEvents()) {
// Create method enter events for all methods currently on the thread's stack. We only do this
// if no debugger is attached to prevent from posting events twice.
+ // TODO: This is the only place we make use of frame_id_. We should create a
+ // std::vector instead and populate it as we walk the stack.
auto ssi = visitor.shadow_stack_.rbegin();
for (auto isi = thread->GetInstrumentationStack()->rbegin(),
end = thread->GetInstrumentationStack()->rend(); isi != end; ++isi) {
- while (ssi != visitor.shadow_stack_.rend() && (*ssi).frame_id_ < (*isi).frame_id_) {
+ while (ssi != visitor.shadow_stack_.rend() && (*ssi).frame_id_ < isi->second.frame_id_) {
instrumentation->MethodEnterEvent(thread, (*ssi).this_object_, (*ssi).method_, 0);
++ssi;
}
uint32_t dex_pc = visitor.dex_pcs_.back();
visitor.dex_pcs_.pop_back();
- if (!isi->interpreter_entry_ && !isi->method_->IsRuntimeMethod()) {
- instrumentation->MethodEnterEvent(thread, (*isi).this_object_, (*isi).method_, dex_pc);
+ if (!isi->second.interpreter_entry_ && !isi->second.method_->IsRuntimeMethod()) {
+ instrumentation->MethodEnterEvent(
+ thread, isi->second.this_object_, isi->second.method_, dex_pc);
}
}
}
@@ -489,36 +489,31 @@
}
return true; // Ignore upcalls.
}
- bool removed_stub = false;
- // TODO: make this search more efficient?
- const size_t frameId = GetFrameId();
- for (const InstrumentationStackFrame& instrumentation_frame : *instrumentation_stack_) {
- if (instrumentation_frame.frame_id_ == frameId) {
- if (kVerboseInstrumentation) {
- LOG(INFO) << " Removing exit stub in " << DescribeLocation();
- }
- if (instrumentation_frame.interpreter_entry_) {
- CHECK(m == Runtime::Current()->GetCalleeSaveMethod(CalleeSaveType::kSaveRefsAndArgs));
- } else {
- CHECK_EQ(m->GetNonObsoleteMethod(),
- instrumentation_frame.method_->GetNonObsoleteMethod())
- << ArtMethod::PrettyMethod(m);
- }
- SetReturnPc(instrumentation_frame.return_pc_);
- if (instrumentation_->ShouldNotifyMethodEnterExitEvents() &&
- !m->IsRuntimeMethod()) {
- // Create the method exit events. As the methods didn't really exit the result is 0.
- // We only do this if no debugger is attached to prevent from posting events twice.
- JValue val;
- instrumentation_->MethodExitEvent(thread_, instrumentation_frame.this_object_, m,
- GetDexPc(), OptionalFrame{}, val);
- }
- frames_removed_++;
- removed_stub = true;
- break;
+ auto it = instrumentation_stack_->find(GetReturnPcAddr());
+ if (it != instrumentation_stack_->end()) {
+ const InstrumentationStackFrame& instrumentation_frame = it->second;
+ if (kVerboseInstrumentation) {
+ LOG(INFO) << " Removing exit stub in " << DescribeLocation();
}
- }
- if (!removed_stub) {
+ if (instrumentation_frame.interpreter_entry_) {
+ CHECK(m == Runtime::Current()->GetCalleeSaveMethod(CalleeSaveType::kSaveRefsAndArgs));
+ } else {
+ CHECK_EQ(m->GetNonObsoleteMethod(),
+ instrumentation_frame.method_->GetNonObsoleteMethod())
+ << ArtMethod::PrettyMethod(m)
+ << " and " << instrumentation_frame.method_->GetNonObsoleteMethod()->PrettyMethod();
+ }
+ SetReturnPc(instrumentation_frame.return_pc_);
+ if (instrumentation_->ShouldNotifyMethodEnterExitEvents() &&
+ !m->IsRuntimeMethod()) {
+ // Create the method exit events. As the methods didn't really exit the result is 0.
+ // We only do this if no debugger is attached to prevent from posting events twice.
+ JValue val;
+ instrumentation_->MethodExitEvent(thread_, instrumentation_frame.this_object_, m,
+ GetDexPc(), OptionalFrame{}, val);
+ }
+ frames_removed_++;
+ } else {
if (kVerboseInstrumentation) {
LOG(INFO) << " No exit stub in " << DescribeLocation();
}
@@ -528,7 +523,7 @@
Thread* const thread_;
const uintptr_t instrumentation_exit_pc_;
Instrumentation* const instrumentation_;
- std::deque<instrumentation::InstrumentationStackFrame>* const instrumentation_stack_;
+ std::map<uintptr_t, instrumentation::InstrumentationStackFrame>* const instrumentation_stack_;
size_t frames_removed_;
};
if (kVerboseInstrumentation) {
@@ -536,7 +531,8 @@
thread->GetThreadName(thread_name);
LOG(INFO) << "Removing exit stubs in " << thread_name;
}
- std::deque<instrumentation::InstrumentationStackFrame>* stack = thread->GetInstrumentationStack();
+ std::map<uintptr_t, instrumentation::InstrumentationStackFrame>* stack =
+ thread->GetInstrumentationStack();
if (stack->size() > 0) {
Instrumentation* instrumentation = reinterpret_cast<Instrumentation*>(arg);
uintptr_t instrumentation_exit_pc =
@@ -544,9 +540,7 @@
RestoreStackVisitor visitor(thread, instrumentation_exit_pc, instrumentation);
visitor.WalkStack(true);
CHECK_EQ(visitor.frames_removed_, stack->size());
- while (stack->size() > 0) {
- stack->pop_front();
- }
+ stack->clear();
}
}
@@ -825,13 +819,17 @@
bool no_remaining_deopts = true;
// Check that there are no other forced deoptimizations. Do it here so we only need to lock
// thread_list_lock once.
- runtime->GetThreadList()->ForEach([&](Thread* t) {
+ // The compiler gets confused on the thread annotations, so use
+ // NO_THREAD_SAFETY_ANALYSIS. Note that we hold the mutator lock
+ // exclusively at this point.
+ Locks::mutator_lock_->AssertExclusiveHeld(self);
+ runtime->GetThreadList()->ForEach([&](Thread* t) NO_THREAD_SAFETY_ANALYSIS {
no_remaining_deopts =
no_remaining_deopts && !t->IsForceInterpreter() &&
std::all_of(t->GetInstrumentationStack()->cbegin(),
t->GetInstrumentationStack()->cend(),
[&](const auto& frame) REQUIRES_SHARED(Locks::mutator_lock_) {
- return frame.force_deopt_id_ == current_force_deopt_id_;
+ return frame.second.force_deopt_id_ == current_force_deopt_id_;
});
});
if (no_remaining_deopts) {
@@ -1385,34 +1383,15 @@
}
}
-// Computes a frame ID by ignoring inlined frames.
-size_t Instrumentation::ComputeFrameId(Thread* self,
- size_t frame_depth,
- size_t inlined_frames_before_frame) {
- CHECK_GE(frame_depth, inlined_frames_before_frame);
- size_t no_inline_depth = frame_depth - inlined_frames_before_frame;
- return StackVisitor::ComputeNumFrames(self, kInstrumentationStackWalk) - no_inline_depth;
-}
-
-static void CheckStackDepth(Thread* self, const InstrumentationStackFrame& instrumentation_frame,
- int delta)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- size_t frame_id = StackVisitor::ComputeNumFrames(self, kInstrumentationStackWalk) + delta;
- if (frame_id != instrumentation_frame.frame_id_) {
- LOG(ERROR) << "Expected frame_id=" << frame_id << " but found "
- << instrumentation_frame.frame_id_;
- StackVisitor::DescribeStack(self);
- CHECK_EQ(frame_id, instrumentation_frame.frame_id_);
- }
-}
-
void Instrumentation::PushInstrumentationStackFrame(Thread* self,
ObjPtr<mirror::Object> this_object,
ArtMethod* method,
+ uintptr_t stack_ptr,
uintptr_t lr,
bool interpreter_entry) {
DCHECK(!self->IsExceptionPending());
- std::deque<instrumentation::InstrumentationStackFrame>* stack = self->GetInstrumentationStack();
+ std::map<uintptr_t, instrumentation::InstrumentationStackFrame>* stack =
+ self->GetInstrumentationStack();
if (kVerboseInstrumentation) {
LOG(INFO) << "Entering " << ArtMethod::PrettyMethod(method) << " from PC "
<< reinterpret_cast<void*>(lr);
@@ -1436,7 +1415,7 @@
instrumentation::InstrumentationStackFrame instrumentation_frame(
h_this.Get(), method, lr, frame_id, interpreter_entry, current_force_deopt_id_);
- stack->push_front(instrumentation_frame);
+ stack->insert({stack_ptr, instrumentation_frame});
}
DeoptimizationMethodType Instrumentation::GetDeoptimizationMethodType(ArtMethod* method) {
@@ -1518,20 +1497,24 @@
}
TwoWordReturn Instrumentation::PopInstrumentationStackFrame(Thread* self,
- uintptr_t* return_pc,
+ uintptr_t* return_pc_addr,
uint64_t* gpr_result,
uint64_t* fpr_result) {
DCHECK(gpr_result != nullptr);
DCHECK(fpr_result != nullptr);
// Do the pop.
- std::deque<instrumentation::InstrumentationStackFrame>* stack = self->GetInstrumentationStack();
+ std::map<uintptr_t, instrumentation::InstrumentationStackFrame>* stack =
+ self->GetInstrumentationStack();
CHECK_GT(stack->size(), 0U);
- InstrumentationStackFrame instrumentation_frame = stack->front();
- stack->pop_front();
+ auto it = stack->find(reinterpret_cast<uintptr_t>(return_pc_addr));
+ CHECK(it != stack->end());
+ InstrumentationStackFrame instrumentation_frame = it->second;
+ stack->erase(it);
// Set return PC and check the sanity of the stack.
- *return_pc = instrumentation_frame.return_pc_;
- CheckStackDepth(self, instrumentation_frame, 0);
+ // We don't cache the return pc value in a local as it may change after
+ // sending a method exit event.
+ *return_pc_addr = instrumentation_frame.return_pc_;
self->VerifyStack();
ArtMethod* method = instrumentation_frame.method_;
@@ -1585,6 +1568,7 @@
uint32_t dex_pc = dex::kDexNoIndex;
if (!method->IsRuntimeMethod() && !instrumentation_frame.interpreter_entry_) {
ObjPtr<mirror::Object> this_object = instrumentation_frame.this_object_;
+ // Note that sending the event may change the contents of *return_pc_addr.
MethodExitEvent(
self, this_object, instrumentation_frame.method_, dex_pc, OptionalFrame{}, return_value);
}
@@ -1608,7 +1592,7 @@
// Restore the return value if it's a reference since it might have moved.
*reinterpret_cast<mirror::Object**>(gpr_result) = res.Get();
}
- if (deoptimize && Runtime::Current()->IsAsyncDeoptimizeable(*return_pc)) {
+ if (deoptimize && Runtime::Current()->IsAsyncDeoptimizeable(*return_pc_addr)) {
if (kVerboseInstrumentation) {
LOG(INFO) << "Deoptimizing "
<< visitor.caller->PrettyMethod()
@@ -1625,43 +1609,35 @@
/* exception= */ nullptr ,
/* from_code= */ false,
deopt_method_type);
- return GetTwoWordSuccessValue(*return_pc,
+ return GetTwoWordSuccessValue(*return_pc_addr,
reinterpret_cast<uintptr_t>(GetQuickDeoptimizationEntryPoint()));
} else {
- if (deoptimize && !Runtime::Current()->IsAsyncDeoptimizeable(*return_pc)) {
+ if (deoptimize && !Runtime::Current()->IsAsyncDeoptimizeable(*return_pc_addr)) {
VLOG(deopt) << "Got a deoptimization request on un-deoptimizable " << method->PrettyMethod()
- << " at PC " << reinterpret_cast<void*>(*return_pc);
+ << " at PC " << reinterpret_cast<void*>(*return_pc_addr);
}
if (kVerboseInstrumentation) {
LOG(INFO) << "Returning from " << method->PrettyMethod()
- << " to PC " << reinterpret_cast<void*>(*return_pc);
+ << " to PC " << reinterpret_cast<void*>(*return_pc_addr);
}
- return GetTwoWordSuccessValue(0, *return_pc);
+ return GetTwoWordSuccessValue(0, *return_pc_addr);
}
}
-uintptr_t Instrumentation::PopFramesForDeoptimization(Thread* self, size_t nframes) const {
- std::deque<instrumentation::InstrumentationStackFrame>* stack = self->GetInstrumentationStack();
- CHECK_GE(stack->size(), nframes);
- if (nframes == 0) {
- return 0u;
- }
- // Only need to send instrumentation events if it's not for deopt (do give the log messages if we
- // have verbose-instrumentation anyway though).
- if (kVerboseInstrumentation) {
- for (size_t i = 0; i < nframes; i++) {
- LOG(INFO) << "Popping for deoptimization " << stack->at(i).method_->PrettyMethod();
+uintptr_t Instrumentation::PopFramesForDeoptimization(Thread* self, uintptr_t pop_until) const {
+ std::map<uintptr_t, instrumentation::InstrumentationStackFrame>* stack =
+ self->GetInstrumentationStack();
+ // Pop all instrumentation frames below `pop_until`.
+ uintptr_t return_pc = 0u;
+ for (auto i = stack->begin(); i != stack->end() && i->first <= pop_until;) {
+ auto e = i;
+ ++i;
+ if (kVerboseInstrumentation) {
+ LOG(INFO) << "Popping for deoptimization " << e->second.method_->PrettyMethod();
}
+ return_pc = e->second.return_pc_;
+ stack->erase(e);
}
- // Now that we've sent all the instrumentation events we can actually modify the
- // instrumentation-stack. We cannot do this earlier since MethodUnwindEvent can re-enter java and
- // do other things that require the instrumentation stack to be in a consistent state with the
- // actual stack.
- for (size_t i = 0; i < nframes - 1; i++) {
- stack->pop_front();
- }
- uintptr_t return_pc = stack->front().return_pc_;
- stack->pop_front();
return return_pc;
}
diff --git a/runtime/instrumentation.h b/runtime/instrumentation.h
index 82e1a13..e30fc9a 100644
--- a/runtime/instrumentation.h
+++ b/runtime/instrumentation.h
@@ -169,16 +169,17 @@
explicit InstrumentationStackPopper(Thread* self);
~InstrumentationStackPopper() REQUIRES_SHARED(Locks::mutator_lock_);
- // Increase the number of frames being popped to 'desired_pops' return true if the frames were
- // popped without any exceptions, false otherwise. The exception that caused the pop is
- // 'exception'.
- bool PopFramesTo(uint32_t desired_pops, /*in-out*/MutableHandle<mirror::Throwable>& exception)
+ // Increase the number of frames being popped up to `stack_pointer`. Return true if the
+ // frames were popped without any exceptions, false otherwise. The exception that caused
+ // the pop is 'exception'.
+ bool PopFramesTo(uintptr_t stack_pointer, /*in-out*/MutableHandle<mirror::Throwable>& exception)
REQUIRES_SHARED(Locks::mutator_lock_);
private:
Thread* self_;
Instrumentation* instrumentation_;
- uint32_t frames_to_remove_;
+ // The stack pointer limit for frames to pop.
+ uintptr_t pop_until_;
};
// Instrumentation is a catch-all for when extra information is required from the runtime. The
@@ -494,6 +495,7 @@
void PushInstrumentationStackFrame(Thread* self,
ObjPtr<mirror::Object> this_object,
ArtMethod* method,
+ uintptr_t stack_pointer,
uintptr_t lr,
bool interpreter_entry)
REQUIRES_SHARED(Locks::mutator_lock_);
@@ -507,13 +509,15 @@
// 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,
- uint64_t* gpr_result, uint64_t* fpr_result)
+ 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, size_t nframes) const
+ uintptr_t PopFramesForDeoptimization(Thread* self, uintptr_t stack_pointer) const
REQUIRES_SHARED(Locks::mutator_lock_);
// Call back for configure stubs.
@@ -534,7 +538,7 @@
// This is used by the debugger to cause a deoptimization of the thread's stack after updating
// local variable(s).
void InstrumentThreadStack(Thread* thread)
- REQUIRES_SHARED(Locks::mutator_lock_);
+ REQUIRES(Locks::mutator_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.
diff --git a/runtime/jit/jit_code_cache.cc b/runtime/jit/jit_code_cache.cc
index 299a3d3..717b2a3 100644
--- a/runtime/jit/jit_code_cache.cc
+++ b/runtime/jit/jit_code_cache.cc
@@ -1002,13 +1002,12 @@
// The stack walking code queries the side instrumentation stack if it
// sees an instrumentation exit pc, so the JIT code of methods in that stack
// must have been seen. We sanity check this below.
- for (const instrumentation::InstrumentationStackFrame& frame
- : *thread->GetInstrumentationStack()) {
+ for (const auto& it : *thread->GetInstrumentationStack()) {
// The 'method_' in InstrumentationStackFrame is the one that has return_pc_ in
// its stack frame, it is not the method owning return_pc_. We just pass null to
// LookupMethodHeader: the method is only checked against in debug builds.
OatQuickMethodHeader* method_header =
- code_cache_->LookupMethodHeader(frame.return_pc_, /* method= */ nullptr);
+ code_cache_->LookupMethodHeader(it.second.return_pc_, /* method= */ nullptr);
if (method_header != nullptr) {
const void* code = method_header->GetCode();
CHECK(bitmap_->Test(FromCodeToAllocation(code)));
diff --git a/runtime/quick_exception_handler.cc b/runtime/quick_exception_handler.cc
index 910b389..bd69aa4 100644
--- a/runtime/quick_exception_handler.cc
+++ b/runtime/quick_exception_handler.cc
@@ -156,37 +156,6 @@
DISALLOW_COPY_AND_ASSIGN(CatchBlockStackVisitor);
};
-static size_t GetInstrumentationFramesToPop(Thread* self, size_t frame_depth)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- CHECK_NE(frame_depth, kInvalidFrameDepth);
- size_t instrumentation_frames_to_pop = 0;
- StackVisitor::WalkStack(
- [&](art::StackVisitor* stack_visitor) REQUIRES_SHARED(Locks::mutator_lock_) {
- size_t current_frame_depth = stack_visitor->GetFrameDepth();
- if (current_frame_depth < frame_depth) {
- CHECK(stack_visitor->GetMethod() != nullptr);
- if (UNLIKELY(reinterpret_cast<uintptr_t>(GetQuickInstrumentationExitPc()) ==
- stack_visitor->GetReturnPc())) {
- if (!stack_visitor->IsInInlinedFrame()) {
- // We do not count inlined frames, because we do not instrument them. The reason we
- // include them in the stack walking is the check against `frame_depth_`, which is
- // given to us by a visitor that visits inlined frames.
- ++instrumentation_frames_to_pop;
- }
- }
- return true;
- }
- // We reached the frame of the catch handler or the upcall.
- return false;
- },
- self,
- /* context= */ nullptr,
- art::StackVisitor::StackWalkKind::kIncludeInlinedFrames,
- /* check_suspended */ true,
- /* include_transitions */ true);
- return instrumentation_frames_to_pop;
-}
-
// Finds the appropriate exception catch after calling all method exit instrumentation functions.
// Note that this might change the exception being thrown.
void QuickExceptionHandler::FindCatch(ObjPtr<mirror::Throwable> exception) {
@@ -219,11 +188,6 @@
DCHECK_GE(new_pop_count, already_popped);
already_popped = new_pop_count;
- // Figure out how many of those frames have instrumentation we need to remove (Should be the
- // exact same as number of new_pop_count if there aren't inlined frames).
- size_t instrumentation_frames_to_pop =
- GetInstrumentationFramesToPop(self_, handler_frame_depth_);
-
if (kDebugExceptionDelivery) {
if (*handler_quick_frame_ == nullptr) {
LOG(INFO) << "Handler is upcall";
@@ -234,8 +198,6 @@
LOG(INFO) << "Handler: " << handler_method_->PrettyMethod() << " (line: "
<< line_number << ")";
}
- LOG(INFO) << "Will attempt to pop " << instrumentation_frames_to_pop
- << " off of the instrumentation stack";
}
// Exception was cleared as part of delivery.
DCHECK(!self_->IsExceptionPending());
@@ -245,7 +207,8 @@
handler_method_header_->IsOptimized()) {
SetCatchEnvironmentForOptimizedHandler(&visitor);
}
- popped_to_top = popper.PopFramesTo(instrumentation_frames_to_pop, exception_ref);
+ popped_to_top =
+ popper.PopFramesTo(reinterpret_cast<uintptr_t>(handler_quick_frame_), exception_ref);
} while (!popped_to_top);
if (!clear_exception_) {
// Put exception back in root set with clear throw location.
@@ -679,10 +642,9 @@
DCHECK(is_deoptimization_) << "Non-deoptimization handlers should use FindCatch";
uintptr_t return_pc = 0;
if (method_tracing_active_) {
- size_t instrumentation_frames_to_pop =
- GetInstrumentationFramesToPop(self_, handler_frame_depth_);
instrumentation::Instrumentation* instrumentation = Runtime::Current()->GetInstrumentation();
- return_pc = instrumentation->PopFramesForDeoptimization(self_, instrumentation_frames_to_pop);
+ return_pc = instrumentation->PopFramesForDeoptimization(
+ self_, reinterpret_cast<uintptr_t>(handler_quick_frame_));
}
return return_pc;
}
diff --git a/runtime/stack.cc b/runtime/stack.cc
index 2c76db5..4148dc7 100644
--- a/runtime/stack.cc
+++ b/runtime/stack.cc
@@ -556,18 +556,18 @@
return context_->GetFPR(reg);
}
+uintptr_t StackVisitor::GetReturnPcAddr() const {
+ uintptr_t sp = reinterpret_cast<uintptr_t>(GetCurrentQuickFrame());
+ DCHECK_NE(sp, 0u);
+ return sp + GetCurrentQuickFrameInfo().GetReturnPcOffset();
+}
+
uintptr_t StackVisitor::GetReturnPc() const {
- uint8_t* sp = reinterpret_cast<uint8_t*>(GetCurrentQuickFrame());
- DCHECK(sp != nullptr);
- uint8_t* pc_addr = sp + GetCurrentQuickFrameInfo().GetReturnPcOffset();
- return *reinterpret_cast<uintptr_t*>(pc_addr);
+ return *reinterpret_cast<uintptr_t*>(GetReturnPcAddr());
}
void StackVisitor::SetReturnPc(uintptr_t new_ret_pc) {
- uint8_t* sp = reinterpret_cast<uint8_t*>(GetCurrentQuickFrame());
- CHECK(sp != nullptr);
- uint8_t* pc_addr = sp + GetCurrentQuickFrameInfo().GetReturnPcOffset();
- *reinterpret_cast<uintptr_t*>(pc_addr) = new_ret_pc;
+ *reinterpret_cast<uintptr_t*>(GetReturnPcAddr()) = new_ret_pc;
}
size_t StackVisitor::ComputeNumFrames(Thread* thread, StackWalkKind walk_kind) {
@@ -851,8 +851,6 @@
DCHECK(thread_ == Thread::Current() || thread_->IsSuspended());
}
CHECK_EQ(cur_depth_, 0U);
- bool exit_stubs_installed = Runtime::Current()->GetInstrumentation()->AreExitStubsInstalled();
- uint32_t instrumentation_stack_depth = 0;
size_t inlined_frames_count = 0;
for (const ManagedStack* current_fragment = thread_->GetManagedStack();
@@ -943,47 +941,35 @@
}
// Compute PC for next stack frame from return PC.
size_t frame_size = frame_info.FrameSizeInBytes();
- size_t return_pc_offset = frame_size - sizeof(void*);
- uint8_t* return_pc_addr = reinterpret_cast<uint8_t*>(cur_quick_frame_) + return_pc_offset;
+ uintptr_t return_pc_addr = GetReturnPcAddr();
uintptr_t return_pc = *reinterpret_cast<uintptr_t*>(return_pc_addr);
- if (UNLIKELY(exit_stubs_installed ||
- reinterpret_cast<uintptr_t>(GetQuickInstrumentationExitPc()) == return_pc)) {
+ if (UNLIKELY(reinterpret_cast<uintptr_t>(GetQuickInstrumentationExitPc()) == return_pc)) {
// While profiling, the return pc is restored from the side stack, except when walking
// the stack for an exception where the side stack will be unwound in VisitFrame.
- if (reinterpret_cast<uintptr_t>(GetQuickInstrumentationExitPc()) == return_pc) {
- CHECK_LT(instrumentation_stack_depth, thread_->GetInstrumentationStack()->size());
- const instrumentation::InstrumentationStackFrame& instrumentation_frame =
- (*thread_->GetInstrumentationStack())[instrumentation_stack_depth];
- instrumentation_stack_depth++;
- if (GetMethod() ==
- Runtime::Current()->GetCalleeSaveMethod(CalleeSaveType::kSaveAllCalleeSaves)) {
- // Skip runtime save all callee frames which are used to deliver exceptions.
- } else if (instrumentation_frame.interpreter_entry_) {
- ArtMethod* callee =
- Runtime::Current()->GetCalleeSaveMethod(CalleeSaveType::kSaveRefsAndArgs);
- CHECK_EQ(GetMethod(), callee) << "Expected: " << ArtMethod::PrettyMethod(callee)
- << " Found: " << ArtMethod::PrettyMethod(GetMethod());
- } else {
- // Instrumentation generally doesn't distinguish between a method's obsolete and
- // non-obsolete version.
- CHECK_EQ(instrumentation_frame.method_->GetNonObsoleteMethod(),
- GetMethod()->GetNonObsoleteMethod())
- << "Expected: "
- << ArtMethod::PrettyMethod(instrumentation_frame.method_->GetNonObsoleteMethod())
- << " Found: " << ArtMethod::PrettyMethod(GetMethod()->GetNonObsoleteMethod());
- }
- if (num_frames_ != 0) {
- // Check agreement of frame Ids only if num_frames_ is computed to avoid infinite
- // recursion.
- size_t frame_id = instrumentation::Instrumentation::ComputeFrameId(
- thread_,
- cur_depth_,
- inlined_frames_count);
- CHECK_EQ(instrumentation_frame.frame_id_, frame_id);
- }
- return_pc = instrumentation_frame.return_pc_;
+ const std::map<uintptr_t, instrumentation::InstrumentationStackFrame>&
+ instrumentation_stack = *thread_->GetInstrumentationStack();
+ auto it = instrumentation_stack.find(return_pc_addr);
+ CHECK(it != instrumentation_stack.end());
+ const instrumentation::InstrumentationStackFrame& instrumentation_frame = it->second;
+ if (GetMethod() ==
+ Runtime::Current()->GetCalleeSaveMethod(CalleeSaveType::kSaveAllCalleeSaves)) {
+ // Skip runtime save all callee frames which are used to deliver exceptions.
+ } else if (instrumentation_frame.interpreter_entry_) {
+ ArtMethod* callee =
+ Runtime::Current()->GetCalleeSaveMethod(CalleeSaveType::kSaveRefsAndArgs);
+ CHECK_EQ(GetMethod(), callee) << "Expected: " << ArtMethod::PrettyMethod(callee)
+ << " Found: " << ArtMethod::PrettyMethod(GetMethod());
+ } else {
+ // Instrumentation generally doesn't distinguish between a method's obsolete and
+ // non-obsolete version.
+ CHECK_EQ(instrumentation_frame.method_->GetNonObsoleteMethod(),
+ GetMethod()->GetNonObsoleteMethod())
+ << "Expected: "
+ << ArtMethod::PrettyMethod(instrumentation_frame.method_->GetNonObsoleteMethod())
+ << " Found: " << ArtMethod::PrettyMethod(GetMethod()->GetNonObsoleteMethod());
}
+ return_pc = instrumentation_frame.return_pc_;
}
cur_quick_frame_pc_ = return_pc;
diff --git a/runtime/stack.h b/runtime/stack.h
index ad73e75..af33e6c 100644
--- a/runtime/stack.h
+++ b/runtime/stack.h
@@ -258,6 +258,7 @@
uintptr_t* GetGPRAddress(uint32_t reg) const;
uintptr_t GetReturnPc() const REQUIRES_SHARED(Locks::mutator_lock_);
+ uintptr_t GetReturnPcAddr() const REQUIRES_SHARED(Locks::mutator_lock_);
void SetReturnPc(uintptr_t new_ret_pc) REQUIRES_SHARED(Locks::mutator_lock_);
diff --git a/runtime/thread.cc b/runtime/thread.cc
index a996bcc..3add372 100644
--- a/runtime/thread.cc
+++ b/runtime/thread.cc
@@ -2298,7 +2298,8 @@
is_runtime_thread_(false) {
wait_mutex_ = new Mutex("a thread wait mutex", LockLevel::kThreadWaitLock);
wait_cond_ = new ConditionVariable("a thread wait condition variable", *wait_mutex_);
- tlsPtr_.instrumentation_stack = new std::deque<instrumentation::InstrumentationStackFrame>;
+ tlsPtr_.instrumentation_stack =
+ new std::map<uintptr_t, instrumentation::InstrumentationStackFrame>;
tlsPtr_.name = new std::string(kThreadNameDuringStartup);
static_assert((sizeof(Thread) % 4) == 0U,
@@ -3621,7 +3622,7 @@
method_type);
artDeoptimize(this);
UNREACHABLE();
- } else {
+ } else if (visitor.caller != nullptr) {
LOG(WARNING) << "Got a deoptimization request on un-deoptimizable method "
<< visitor.caller->PrettyMethod();
}
@@ -4065,8 +4066,8 @@
RootCallbackVisitor visitor_to_callback(visitor, thread_id);
ReferenceMapVisitor<RootCallbackVisitor, kPrecise> mapper(this, &context, visitor_to_callback);
mapper.template WalkStack<StackVisitor::CountTransitions::kNo>(false);
- for (instrumentation::InstrumentationStackFrame& frame : *GetInstrumentationStack()) {
- visitor->VisitRootIfNonNull(&frame.this_object_, RootInfo(kRootVMInternal, thread_id));
+ for (auto& entry : *GetInstrumentationStack()) {
+ visitor->VisitRootIfNonNull(&entry.second.this_object_, RootInfo(kRootVMInternal, thread_id));
}
}
diff --git a/runtime/thread.h b/runtime/thread.h
index 483191e..7129526 100644
--- a/runtime/thread.h
+++ b/runtime/thread.h
@@ -1064,7 +1064,19 @@
void RemoveDebuggerShadowFrameMapping(size_t frame_id)
REQUIRES_SHARED(Locks::mutator_lock_);
- std::deque<instrumentation::InstrumentationStackFrame>* GetInstrumentationStack() {
+ // While getting this map requires shared the mutator lock, manipulating it
+ // should actually follow these rules:
+ // (1) The owner of this map (the thread) can change it with its mutator lock.
+ // (2) Other threads can read this map when the owner is suspended and they
+ // hold the mutator lock.
+ // (3) Other threads can change this map when owning the mutator lock exclusively.
+ //
+ // The reason why (3) needs the mutator lock exclusively (and not just having
+ // the owner suspended) is that we don't want other threads to concurrently read the map.
+ //
+ // TODO: Add a class abstraction to express these rules.
+ std::map<uintptr_t, instrumentation::InstrumentationStackFrame>* GetInstrumentationStack()
+ REQUIRES_SHARED(Locks::mutator_lock_) {
return tlsPtr_.instrumentation_stack;
}
@@ -1746,8 +1758,12 @@
Context* long_jump_context;
// Additional stack used by method instrumentation to store method and return pc values.
- // Stored as a pointer since std::deque is not PACKED.
- std::deque<instrumentation::InstrumentationStackFrame>* instrumentation_stack;
+ // Stored as a pointer since std::map is not PACKED.
+ // !DO NOT CHANGE! to std::unordered_map: the users of this map require an
+ // ordered iteration on the keys (which are stack addresses).
+ // Also see Thread::GetInstrumentationStack for the requirements on
+ // manipulating and reading this map.
+ std::map<uintptr_t, instrumentation::InstrumentationStackFrame>* instrumentation_stack;
// For gc purpose, a shadow frame record stack that keeps track of:
// 1) shadow frames under construction.
diff --git a/test/2011-stack-walk-concurrent-instrument/expected.txt b/test/2011-stack-walk-concurrent-instrument/expected.txt
new file mode 100644
index 0000000..77a1486
--- /dev/null
+++ b/test/2011-stack-walk-concurrent-instrument/expected.txt
@@ -0,0 +1,2 @@
+JNI_OnLoad called
+Done
diff --git a/test/2011-stack-walk-concurrent-instrument/info.txt b/test/2011-stack-walk-concurrent-instrument/info.txt
new file mode 100644
index 0000000..91f0106
--- /dev/null
+++ b/test/2011-stack-walk-concurrent-instrument/info.txt
@@ -0,0 +1,3 @@
+Tests concurrently instrumenting a thread while walking a stack doesn't crash/break.
+
+Bug: 72608560
diff --git a/test/2011-stack-walk-concurrent-instrument/src/Main.java b/test/2011-stack-walk-concurrent-instrument/src/Main.java
new file mode 100644
index 0000000..8f96f93
--- /dev/null
+++ b/test/2011-stack-walk-concurrent-instrument/src/Main.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.util.concurrent.*;
+
+public class Main {
+ public Main() {
+ }
+
+ void $noinline$f(Runnable r) throws Exception {
+ $noinline$g(r);
+ }
+
+ void $noinline$g(Runnable r) {
+ $noinline$h(r);
+ }
+
+ void $noinline$h(Runnable r) {
+ r.run();
+ }
+
+ public native void resetTest();
+ public native void waitAndDeopt(Thread t);
+ public native void doSelfStackWalk();
+
+ void testConcurrent() throws Exception {
+ resetTest();
+ final Thread current = Thread.currentThread();
+ Thread t = new Thread(() -> {
+ try {
+ this.waitAndDeopt(current);
+ } catch (Exception e) {
+ throw new Error("Fail!", e);
+ }
+ });
+ t.start();
+ $noinline$f(() -> {
+ try {
+ this.doSelfStackWalk();
+ } catch (Exception e) {
+ throw new Error("Fail!", e);
+ }
+ });
+ t.join();
+ }
+
+ public static void main(String[] args) throws Exception {
+ System.loadLibrary(args[0]);
+ Main st = new Main();
+ st.testConcurrent();
+ System.out.println("Done");
+ }
+}
diff --git a/test/2011-stack-walk-concurrent-instrument/stack_walk_concurrent.cc b/test/2011-stack-walk-concurrent-instrument/stack_walk_concurrent.cc
new file mode 100644
index 0000000..a185446
--- /dev/null
+++ b/test/2011-stack-walk-concurrent-instrument/stack_walk_concurrent.cc
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2020 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 <atomic>
+#include <string_view>
+
+#include "arch/context.h"
+#include "art_method-inl.h"
+#include "jni.h"
+#include "scoped_thread_state_change.h"
+#include "stack.h"
+#include "thread.h"
+
+namespace art {
+namespace StackWalkConcurrentInstrument {
+
+std::atomic<bool> instrument_waiting = false;
+std::atomic<bool> instrumented = false;
+
+// Spin lock.
+static void WaitForInstrument() REQUIRES_SHARED(Locks::mutator_lock_) {
+ ScopedThreadSuspension sts(Thread::Current(), ThreadState::kWaitingForDeoptimization);
+ instrument_waiting = true;
+ while (!instrumented) {
+ }
+}
+
+class SelfStackWalkVisitor : public StackVisitor {
+ public:
+ explicit SelfStackWalkVisitor(Thread* thread) REQUIRES_SHARED(Locks::mutator_lock_)
+ : StackVisitor(thread, Context::Create(), StackWalkKind::kIncludeInlinedFrames) {}
+
+ bool VisitFrame() override REQUIRES_SHARED(Locks::mutator_lock_) {
+ if (GetMethod()->GetNameView() == "$noinline$f") {
+ CHECK(!found_f_);
+ found_f_ = true;
+ } else if (GetMethod()->GetNameView() == "$noinline$g") {
+ CHECK(!found_g_);
+ found_g_ = true;
+ WaitForInstrument();
+ } else if (GetMethod()->GetNameView() == "$noinline$h") {
+ CHECK(!found_h_);
+ found_h_ = true;
+ }
+ return true;
+ }
+
+ bool found_f_ = false;
+ bool found_g_ = false;
+ bool found_h_ = false;
+};
+
+extern "C" JNIEXPORT void JNICALL Java_Main_resetTest(JNIEnv*, jobject) {
+ instrument_waiting = false;
+ instrumented = false;
+}
+
+extern "C" JNIEXPORT void JNICALL Java_Main_doSelfStackWalk(JNIEnv*, jobject) {
+ ScopedObjectAccess soa(Thread::Current());
+ SelfStackWalkVisitor sswv(Thread::Current());
+ sswv.WalkStack();
+ CHECK(sswv.found_f_);
+ CHECK(sswv.found_g_);
+ CHECK(sswv.found_h_);
+}
+extern "C" JNIEXPORT void JNICALL Java_Main_waitAndDeopt(JNIEnv*, jobject, jobject target) {
+ while (!instrument_waiting) {
+ }
+ bool timed_out = false;
+ Thread* other = Runtime::Current()->GetThreadList()->SuspendThreadByPeer(
+ target, true, SuspendReason::kInternal, &timed_out);
+ CHECK(!timed_out);
+ CHECK(other != nullptr);
+ ScopedSuspendAll ssa(__FUNCTION__);
+ Runtime::Current()->GetInstrumentation()->InstrumentThreadStack(other);
+ MutexLock mu(Thread::Current(), *Locks::thread_suspend_count_lock_);
+ bool updated = other->ModifySuspendCount(Thread::Current(), -1, nullptr, SuspendReason::kInternal);
+ CHECK(updated);
+ instrumented = true;
+ return;
+}
+
+} // namespace StackWalkConcurrentInstrument
+} // namespace art
diff --git a/test/Android.bp b/test/Android.bp
index de3f516..7182513 100644
--- a/test/Android.bp
+++ b/test/Android.bp
@@ -600,6 +600,7 @@
"1947-breakpoint-redefine-deopt/check_deopt.cc",
"1972-jni-id-swap-indices/jni_id.cc",
"1985-structural-redefine-stack-scope/stack_scope.cc",
+ "2011-stack-walk-concurrent-instrument/stack_walk_concurrent.cc",
"common/runtime_state.cc",
"common/stack_inspect.cc",
],