JVMTI PopFrame support
Implement support for the JVMTI can_pop_frames capability. This works
by marking shadow-frames with a bit that forces it to be popped or an
instruction to be retried. When a PopFrame is requested the plugin
will deoptimize the targeted thread and force the interpreter to deal
with the frame pop. If the can_pop_frames capability is enabled the
runtime will be forced to handle all exceptions through the
interpreter. This is required to support PopFrame during some
exception events.
Test: ./test.py --host
Test: ./art/tools/run-libjdwp-tests.sh --mode=host
Bug: 73255278
Bug: 111357976
Change-Id: I62d6b1f4ff387c794ba45093c3d6773aaf642067
diff --git a/runtime/thread.cc b/runtime/thread.cc
index 4a3d8cb..afb2c28 100644
--- a/runtime/thread.cc
+++ b/runtime/thread.cc
@@ -3368,22 +3368,51 @@
HandleWrapperObjPtr<mirror::Throwable> h_exception(hs.NewHandleWrapper(&exception));
instrumentation->ExceptionThrownEvent(this, exception.Ptr());
}
- // Does instrumentation need to deoptimize the stack?
- // Note: we do this *after* reporting the exception to instrumentation in case it
- // now requires deoptimization. It may happen if a debugger is attached and requests
- // new events (single-step, breakpoint, ...) when the exception is reported.
- if (Dbg::IsForcedInterpreterNeededForException(this)) {
+ // Does instrumentation need to deoptimize the stack or otherwise go to interpreter for something?
+ // Note: we do this *after* reporting the exception to instrumentation in case it now requires
+ // deoptimization. It may happen if a debugger is attached and requests new events (single-step,
+ // breakpoint, ...) when the exception is reported.
+ ShadowFrame* cf;
+ bool force_frame_pop = false;
+ {
+ NthCallerVisitor visitor(this, 0, false);
+ visitor.WalkStack();
+ cf = visitor.GetCurrentShadowFrame();
+ if (cf == nullptr) {
+ cf = FindDebuggerShadowFrame(visitor.GetFrameId());
+ }
+ force_frame_pop = cf != nullptr && cf->GetForcePopFrame();
+ if (kIsDebugBuild && force_frame_pop) {
+ NthCallerVisitor penultimate_visitor(this, 1, false);
+ penultimate_visitor.WalkStack();
+ ShadowFrame* penultimate_frame = penultimate_visitor.GetCurrentShadowFrame();
+ if (penultimate_frame == nullptr) {
+ penultimate_frame = FindDebuggerShadowFrame(penultimate_visitor.GetFrameId());
+ }
+ DCHECK(penultimate_frame != nullptr &&
+ penultimate_frame->GetForceRetryInstruction())
+ << "Force pop frame without retry instruction found. penultimate frame is null: "
+ << (penultimate_frame == nullptr ? "true" : "false");
+ }
+ }
+ if (Dbg::IsForcedInterpreterNeededForException(this) || force_frame_pop) {
NthCallerVisitor visitor(this, 0, false);
visitor.WalkStack();
if (Runtime::Current()->IsAsyncDeoptimizeable(visitor.caller_pc)) {
+ VLOG(deopt) << "Deopting " << cf->GetMethod()->PrettyMethod() << " for frame-pop";
// method_type shouldn't matter due to exception handling.
const DeoptimizationMethodType method_type = DeoptimizationMethodType::kDefault;
// Save the exception into the deoptimization context so it can be restored
// before entering the interpreter.
+ if (force_frame_pop) {
+ DCHECK(Runtime::Current()->AreNonStandardExitsEnabled());
+ // Get rid of the exception since we are doing a framepop instead.
+ ClearException();
+ }
PushDeoptimizationContext(
JValue(),
false /* is_reference */,
- exception,
+ (force_frame_pop ? nullptr : exception),
false /* from_code */,
method_type);
artDeoptimize(this);