art::Thread::SelfOffset<__SIZEOF_POINTER__>().Int32Value())
// Offset of field Thread::tlsPtr_.thread_local_pos.
-#define THREAD_LOCAL_POS_OFFSET (THREAD_CARD_TABLE_OFFSET + 151 * __SIZEOF_POINTER__)
+#define THREAD_LOCAL_POS_OFFSET (THREAD_CARD_TABLE_OFFSET + 152 * __SIZEOF_POINTER__)
ADD_TEST_EQ(THREAD_LOCAL_POS_OFFSET,
art::Thread::ThreadLocalPosOffset<__SIZEOF_POINTER__>().Int32Value())
// Offset of field Thread::tlsPtr_.thread_local_end.
uint64_t value = request->ReadValue(width);
VLOG(jdwp) << " --> slot " << slot << " " << sigByte << " " << value;
- error = Dbg::SetLocalValue(visitor, slot, sigByte, value, width);
+ error = Dbg::SetLocalValue(thread, visitor, slot, sigByte, value, width);
if (error != JDWP::ERR_NONE) {
return error;
}
return kStackFrameLocalAccessError;
}
-JDWP::JdwpError Dbg::SetLocalValue(StackVisitor& visitor, int slot, JDWP::JdwpTag tag,
- uint64_t value, size_t width) {
+JDWP::JdwpError Dbg::SetLocalValue(Thread* thread, StackVisitor& visitor, int slot,
+ JDWP::JdwpTag tag, uint64_t value, size_t width) {
ArtMethod* m = visitor.GetMethod();
JDWP::JdwpError error = JDWP::ERR_NONE;
uint16_t vreg = DemangleSlot(slot, m, &error);
case JDWP::JT_BOOLEAN:
case JDWP::JT_BYTE:
CHECK_EQ(width, 1U);
- if (!visitor.SetVReg(m, vreg, static_cast<uint32_t>(value), kIntVReg)) {
+ if (!visitor.SetVRegFromDebugger(m, vreg, static_cast<uint32_t>(value), kIntVReg)) {
return FailSetLocalValue(visitor, vreg, tag, static_cast<uint32_t>(value));
}
break;
case JDWP::JT_SHORT:
case JDWP::JT_CHAR:
CHECK_EQ(width, 2U);
- if (!visitor.SetVReg(m, vreg, static_cast<uint32_t>(value), kIntVReg)) {
+ if (!visitor.SetVRegFromDebugger(m, vreg, static_cast<uint32_t>(value), kIntVReg)) {
return FailSetLocalValue(visitor, vreg, tag, static_cast<uint32_t>(value));
}
break;
case JDWP::JT_INT:
CHECK_EQ(width, 4U);
- if (!visitor.SetVReg(m, vreg, static_cast<uint32_t>(value), kIntVReg)) {
+ if (!visitor.SetVRegFromDebugger(m, vreg, static_cast<uint32_t>(value), kIntVReg)) {
return FailSetLocalValue(visitor, vreg, tag, static_cast<uint32_t>(value));
}
break;
case JDWP::JT_FLOAT:
CHECK_EQ(width, 4U);
- if (!visitor.SetVReg(m, vreg, static_cast<uint32_t>(value), kFloatVReg)) {
+ if (!visitor.SetVRegFromDebugger(m, vreg, static_cast<uint32_t>(value), kFloatVReg)) {
return FailSetLocalValue(visitor, vreg, tag, static_cast<uint32_t>(value));
}
break;
VLOG(jdwp) << tag << " object " << o << " is an invalid object";
return JDWP::ERR_INVALID_OBJECT;
}
- if (!visitor.SetVReg(m, vreg, static_cast<uint32_t>(reinterpret_cast<uintptr_t>(o)),
+ if (!visitor.SetVRegFromDebugger(m, vreg, static_cast<uint32_t>(reinterpret_cast<uintptr_t>(o)),
kReferenceVReg)) {
return FailSetLocalValue(visitor, vreg, tag, reinterpret_cast<uintptr_t>(o));
}
}
case JDWP::JT_DOUBLE: {
CHECK_EQ(width, 8U);
- if (!visitor.SetVRegPair(m, vreg, value, kDoubleLoVReg, kDoubleHiVReg)) {
+ if (!visitor.SetVRegPairFromDebugger(m, vreg, value, kDoubleLoVReg, kDoubleHiVReg)) {
return FailSetLocalValue(visitor, vreg, tag, value);
}
break;
}
case JDWP::JT_LONG: {
CHECK_EQ(width, 8U);
- if (!visitor.SetVRegPair(m, vreg, value, kLongLoVReg, kLongHiVReg)) {
+ if (!visitor.SetVRegPairFromDebugger(m, vreg, value, kLongLoVReg, kLongHiVReg)) {
return FailSetLocalValue(visitor, vreg, tag, value);
}
break;
LOG(FATAL) << "Unknown tag " << tag;
UNREACHABLE();
}
+
+ // If we set the local variable in a compiled frame, we need to trigger a deoptimization of
+ // the stack so we continue execution with the interpreter using the new value(s) of the updated
+ // local variable(s). To achieve this, we install instrumentation exit stub on each method of the
+ // thread's stack. The stub will cause the deoptimization to happen.
+ if (!visitor.IsShadowFrame() && thread->HasDebuggerShadowFrames()) {
+ Runtime::Current()->GetInstrumentation()->InstrumentThreadStack(thread);
+ }
+
return JDWP::ERR_NONE;
}
return true;
}
}
+ if (thread->HasDebuggerShadowFrames()) {
+ // We need to deoptimize the stack for the exception handling flow so that
+ // we don't miss any deoptimization that should be done when there are
+ // debugger shadow frames.
+ return true;
+ }
// We have to require stack deoptimization if the upcall is deoptimized.
return instrumentation->IsDeoptimized(m);
}
-struct NeedsDeoptimizationVisitor : public StackVisitor {
+class NeedsDeoptimizationVisitor : public StackVisitor {
public:
explicit NeedsDeoptimizationVisitor(Thread* self)
SHARED_REQUIRES(Locks::mutator_lock_)
needs_deoptimization_ = true;
return false;
}
+ ShadowFrame* frame = GetThread()->FindDebuggerShadowFrame(GetFrameId());
+ if (frame != nullptr) {
+ // The debugger allocated a ShadowFrame to update a variable in the stack: we need to
+ // deoptimize the stack to execute (and deallocate) this frame.
+ needs_deoptimization_ = true;
+ return false;
+ }
return true;
}
#include "jdwp/jdwp.h"
#include "jni.h"
#include "jvalue.h"
+#include "thread.h"
#include "thread_state.h"
namespace art {
// execution with interpreter for debugging.
static bool IsForcedInterpreterNeededForUpcall(Thread* thread, ArtMethod* m)
SHARED_REQUIRES(Locks::mutator_lock_) {
- if (!IsDebuggerActive()) {
+ if (!IsDebuggerActive() && !thread->HasDebuggerShadowFrames()) {
return false;
}
return IsForcedInterpreterNeededForUpcallImpl(thread, m);
// the deoptimized frames.
static bool IsForcedInterpreterNeededForException(Thread* thread)
SHARED_REQUIRES(Locks::mutator_lock_) {
- if (!IsDebuggerActive()) {
+ if (!IsDebuggerActive() && !thread->HasDebuggerShadowFrames()) {
return false;
}
return IsForcedInterpreterNeededForExceptionImpl(thread);
ScopedObjectAccessUnchecked& soa, int slot,
JDWP::JdwpTag tag, uint8_t* buf, size_t width)
REQUIRES(!Locks::thread_list_lock_) SHARED_REQUIRES(Locks::mutator_lock_);
- static JDWP::JdwpError SetLocalValue(StackVisitor& visitor, int slot, JDWP::JdwpTag tag,
- uint64_t value, size_t width)
+ static JDWP::JdwpError SetLocalValue(Thread* thread, StackVisitor& visitor, int slot,
+ JDWP::JdwpTag tag, uint64_t value, size_t width)
REQUIRES(!Locks::thread_list_lock_) SHARED_REQUIRES(Locks::mutator_lock_);
static void DdmBroadcast(bool connect) SHARED_REQUIRES(Locks::mutator_lock_);
sizeof(void*));
EXPECT_OFFSET_DIFFP(Thread, tlsPtr_, stacked_shadow_frame_record,
deoptimization_context_stack, sizeof(void*));
- EXPECT_OFFSET_DIFFP(Thread, tlsPtr_, deoptimization_context_stack, name, sizeof(void*));
+ EXPECT_OFFSET_DIFFP(Thread, tlsPtr_, deoptimization_context_stack,
+ frame_id_to_shadow_frame, sizeof(void*));
+ EXPECT_OFFSET_DIFFP(Thread, tlsPtr_, frame_id_to_shadow_frame, name, sizeof(void*));
EXPECT_OFFSET_DIFFP(Thread, tlsPtr_, name, pthread_self, sizeof(void*));
EXPECT_OFFSET_DIFFP(Thread, tlsPtr_, pthread_self, last_no_thread_suspension_cause,
sizeof(void*));
thread->VerifyStack();
}
+void Instrumentation::InstrumentThreadStack(Thread* thread) {
+ instrumentation_stubs_installed_ = true;
+ InstrumentationInstallStack(thread, this);
+}
+
// Removes the instrumentation exit pc as the return PC for every quick frame.
static void InstrumentationRestoreStack(Thread* thread, void* arg)
SHARED_REQUIRES(Locks::mutator_lock_) {
ArtMethod* method = instrumentation_frame.method_;
uint32_t length;
- char return_shorty = method->GetShorty(&length)[0];
+ const size_t pointer_size = Runtime::Current()->GetClassLinker()->GetImagePointerSize();
+ char return_shorty = method->GetInterfaceMethodIfProxy(pointer_size)->GetShorty(&length)[0];
JValue return_value;
if (return_shorty == 'V') {
return_value.SetJ(0);
void InstallStubsForMethod(ArtMethod* method)
SHARED_REQUIRES(Locks::mutator_lock_) REQUIRES(!deoptimized_methods_lock_);
+ // Install instrumentation exit stub on every method of the stack of the given thread.
+ // This is used by the debugger to cause a deoptimization of the thread's stack after updating
+ // local variable(s).
+ void InstrumentThreadStack(Thread* thread)
+ SHARED_REQUIRES(Locks::mutator_lock_)
+ REQUIRES(!Locks::thread_list_lock_);
+
private:
InstrumentationLevel GetCurrentInstrumentationLevel() const;
class PACKED(4) OatHeader {
public:
static constexpr uint8_t kOatMagic[] = { 'o', 'a', 't', '\n' };
- static constexpr uint8_t kOatVersion[] = { '0', '7', '0', '\0' };
+ static constexpr uint8_t kOatVersion[] = { '0', '7', '1', '\0' };
static constexpr const char* kImageLocationKey = "image-location";
static constexpr const char* kDex2OatCmdLineKey = "dex2oat-cmdline";
method->ToNativeQuickPc(found_dex_pc, /* is_catch_handler */ true));
exception_handler_->SetHandlerQuickFrame(GetCurrentQuickFrame());
return false; // End stack walk.
+ } else if (UNLIKELY(GetThread()->HasDebuggerShadowFrames())) {
+ // We are going to unwind this frame. Did we prepare a shadow frame for debugging?
+ size_t frame_id = GetFrameId();
+ ShadowFrame* frame = GetThread()->FindDebuggerShadowFrame(frame_id);
+ if (frame != nullptr) {
+ // We will not execute this shadow frame so we can safely deallocate it.
+ GetThread()->RemoveDebuggerShadowFrameMapping(frame_id);
+ ShadowFrame::DeleteDeoptimizedFrame(frame);
+ }
}
}
return true; // Continue stack walk.
true, true);
bool verifier_success = verifier.Verify();
CHECK(verifier_success) << PrettyMethod(m);
- ShadowFrame* new_frame = ShadowFrame::CreateDeoptimizedFrame(num_regs, nullptr, m, dex_pc);
+ // Check if a shadow frame already exists for debugger's set-local-value purpose.
+ const size_t frame_id = GetFrameId();
+ ShadowFrame* new_frame = GetThread()->FindDebuggerShadowFrame(frame_id);
+ const bool* updated_vregs;
+ if (new_frame == nullptr) {
+ new_frame = ShadowFrame::CreateDeoptimizedFrame(num_regs, nullptr, m, dex_pc);
+ updated_vregs = nullptr;
+ } else {
+ updated_vregs = GetThread()->GetUpdatedVRegFlags(frame_id);
+ DCHECK(updated_vregs != nullptr);
+ }
{
ScopedStackedShadowFramePusher pusher(GetThread(), new_frame,
StackedShadowFrameType::kShadowFrameUnderConstruction);
static constexpr uint32_t kDeadValue = 0xEBADDE09;
static constexpr uint64_t kLongDeadValue = 0xEBADDE09EBADDE09;
for (uint16_t reg = 0; reg < num_regs; ++reg) {
+ if (updated_vregs != nullptr && updated_vregs[reg]) {
+ // Keep the value set by debugger.
+ continue;
+ }
VRegKind kind = GetVRegKind(reg, kinds);
switch (kind) {
case kUndefined:
}
}
}
+ if (updated_vregs != nullptr) {
+ // Calling Thread::RemoveDebuggerShadowFrameMapping will also delete the updated_vregs
+ // array so this must come after we processed the frame.
+ GetThread()->RemoveDebuggerShadowFrameMapping(frame_id);
+ DCHECK(GetThread()->FindDebuggerShadowFrame(frame_id) == nullptr);
+ }
if (prev_shadow_frame_ != nullptr) {
prev_shadow_frame_->SetLink(new_frame);
} else {
return vreg < num_regs && TestBitmap(vreg, reg_bitmap);
}
+bool StackVisitor::GetVRegFromDebuggerShadowFrame(uint16_t vreg,
+ VRegKind kind,
+ uint32_t* val) const {
+ size_t frame_id = const_cast<StackVisitor*>(this)->GetFrameId();
+ ShadowFrame* shadow_frame = thread_->FindDebuggerShadowFrame(frame_id);
+ if (shadow_frame != nullptr) {
+ bool* updated_vreg_flags = thread_->GetUpdatedVRegFlags(frame_id);
+ DCHECK(updated_vreg_flags != nullptr);
+ if (updated_vreg_flags[vreg]) {
+ // Value is set by the debugger.
+ if (kind == kReferenceVReg) {
+ *val = static_cast<uint32_t>(reinterpret_cast<uintptr_t>(
+ shadow_frame->GetVRegReference(vreg)));
+ } else {
+ *val = shadow_frame->GetVReg(vreg);
+ }
+ return true;
+ }
+ }
+ // No value is set by the debugger.
+ return false;
+}
+
bool StackVisitor::GetVReg(ArtMethod* m, uint16_t vreg, VRegKind kind, uint32_t* val) const {
if (cur_quick_frame_ != nullptr) {
DCHECK(context_ != nullptr); // You can't reliably read registers without a context.
DCHECK(m == GetMethod());
+ // Check if there is value set by the debugger.
+ if (GetVRegFromDebuggerShadowFrame(vreg, kind, val)) {
+ return true;
+ }
if (m->IsOptimized(sizeof(void*))) {
return GetVRegFromOptimizedCode(m, vreg, kind, val);
} else {
// its instructions?
uint16_t number_of_dex_registers = code_item->registers_size_;
DCHECK_LT(vreg, code_item->registers_size_);
-
ArtMethod* outer_method = *GetCurrentQuickFrame();
const void* code_pointer = outer_method->GetQuickOatCodePointer(sizeof(void*));
DCHECK(code_pointer != nullptr);
return true;
}
+bool StackVisitor::GetVRegPairFromDebuggerShadowFrame(uint16_t vreg,
+ VRegKind kind_lo,
+ VRegKind kind_hi,
+ uint64_t* val) const {
+ uint32_t low_32bits;
+ uint32_t high_32bits;
+ bool success = GetVRegFromDebuggerShadowFrame(vreg, kind_lo, &low_32bits);
+ success &= GetVRegFromDebuggerShadowFrame(vreg + 1, kind_hi, &high_32bits);
+ if (success) {
+ *val = (static_cast<uint64_t>(high_32bits) << 32) | static_cast<uint64_t>(low_32bits);
+ }
+ return success;
+}
+
bool StackVisitor::GetVRegPair(ArtMethod* m, uint16_t vreg, VRegKind kind_lo,
VRegKind kind_hi, uint64_t* val) const {
if (kind_lo == kLongLoVReg) {
LOG(FATAL) << "Expected long or double: kind_lo=" << kind_lo << ", kind_hi=" << kind_hi;
UNREACHABLE();
}
+ // Check if there is value set by the debugger.
+ if (GetVRegPairFromDebuggerShadowFrame(vreg, kind_lo, kind_hi, val)) {
+ return true;
+ }
if (cur_quick_frame_ != nullptr) {
DCHECK(context_ != nullptr); // You can't reliably read registers without a context.
DCHECK(m == GetMethod());
bool StackVisitor::SetVReg(ArtMethod* m, uint16_t vreg, uint32_t new_value,
VRegKind kind) {
if (cur_quick_frame_ != nullptr) {
- DCHECK(context_ != nullptr); // You can't reliably write registers without a context.
- DCHECK(m == GetMethod());
- if (m->IsOptimized(sizeof(void*))) {
- return false;
- } else {
- return SetVRegFromQuickCode(m, vreg, new_value, kind);
- }
+ DCHECK(context_ != nullptr); // You can't reliably write registers without a context.
+ DCHECK(m == GetMethod());
+ if (m->IsOptimized(sizeof(void*))) {
+ return false;
} else {
- cur_shadow_frame_->SetVReg(vreg, new_value);
- return true;
+ return SetVRegFromQuickCode(m, vreg, new_value, kind);
}
+ } else {
+ cur_shadow_frame_->SetVReg(vreg, new_value);
+ return true;
+ }
}
bool StackVisitor::SetVRegFromQuickCode(ArtMethod* m, uint16_t vreg, uint32_t new_value,
}
}
+bool StackVisitor::SetVRegFromDebugger(ArtMethod* m,
+ uint16_t vreg,
+ uint32_t new_value,
+ VRegKind kind) {
+ const DexFile::CodeItem* code_item = m->GetCodeItem();
+ if (code_item == nullptr) {
+ return false;
+ }
+ ShadowFrame* shadow_frame = GetCurrentShadowFrame();
+ if (shadow_frame == nullptr) {
+ // This is a compiled frame: we must prepare and update a shadow frame that will
+ // be executed by the interpreter after deoptimization of the stack.
+ const size_t frame_id = GetFrameId();
+ const uint16_t num_regs = code_item->registers_size_;
+ shadow_frame = thread_->FindOrCreateDebuggerShadowFrame(frame_id, num_regs, m, GetDexPc());
+ CHECK(shadow_frame != nullptr);
+ // Remember the vreg has been set for debugging and must not be overwritten by the
+ // original value during deoptimization of the stack.
+ thread_->GetUpdatedVRegFlags(frame_id)[vreg] = true;
+ }
+ if (kind == kReferenceVReg) {
+ shadow_frame->SetVRegReference(vreg, reinterpret_cast<mirror::Object*>(new_value));
+ } else {
+ shadow_frame->SetVReg(vreg, new_value);
+ }
+ return true;
+}
+
bool StackVisitor::SetRegisterIfAccessible(uint32_t reg, uint32_t new_value, VRegKind kind) {
const bool is_float = (kind == kFloatVReg) || (kind == kDoubleLoVReg) || (kind == kDoubleHiVReg);
if (!IsAccessibleRegister(reg, is_float)) {
}
}
+bool StackVisitor::SetVRegPairFromDebugger(ArtMethod* m,
+ uint16_t vreg,
+ uint64_t new_value,
+ VRegKind kind_lo,
+ VRegKind kind_hi) {
+ if (kind_lo == kLongLoVReg) {
+ DCHECK_EQ(kind_hi, kLongHiVReg);
+ } else if (kind_lo == kDoubleLoVReg) {
+ DCHECK_EQ(kind_hi, kDoubleHiVReg);
+ } else {
+ LOG(FATAL) << "Expected long or double: kind_lo=" << kind_lo << ", kind_hi=" << kind_hi;
+ UNREACHABLE();
+ }
+ const DexFile::CodeItem* code_item = m->GetCodeItem();
+ if (code_item == nullptr) {
+ return false;
+ }
+ ShadowFrame* shadow_frame = GetCurrentShadowFrame();
+ if (shadow_frame == nullptr) {
+ // This is a compiled frame: we must prepare for deoptimization (see SetVRegFromDebugger).
+ const size_t frame_id = GetFrameId();
+ const uint16_t num_regs = code_item->registers_size_;
+ shadow_frame = thread_->FindOrCreateDebuggerShadowFrame(frame_id, num_regs, m, GetDexPc());
+ CHECK(shadow_frame != nullptr);
+ // Remember the vreg pair has been set for debugging and must not be overwritten by the
+ // original value during deoptimization of the stack.
+ thread_->GetUpdatedVRegFlags(frame_id)[vreg] = true;
+ thread_->GetUpdatedVRegFlags(frame_id)[vreg + 1] = true;
+ }
+ shadow_frame->SetVRegLong(vreg, new_value);
+ return true;
+}
+
bool StackVisitor::SetRegisterPairIfAccessible(uint32_t reg_lo, uint32_t reg_hi,
uint64_t new_value, bool is_float) {
if (!IsAccessibleRegister(reg_lo, is_float) || !IsAccessibleRegister(reg_hi, is_float)) {
bool SetVReg(ArtMethod* m, uint16_t vreg, uint32_t new_value, VRegKind kind)
SHARED_REQUIRES(Locks::mutator_lock_);
+ // Values will be set in debugger shadow frames. Debugger will make sure deoptimization
+ // is triggered to make the values effective.
+ bool SetVRegFromDebugger(ArtMethod* m, uint16_t vreg, uint32_t new_value, VRegKind kind)
+ SHARED_REQUIRES(Locks::mutator_lock_);
+
bool SetVRegPair(ArtMethod* m, uint16_t vreg, uint64_t new_value,
VRegKind kind_lo, VRegKind kind_hi)
SHARED_REQUIRES(Locks::mutator_lock_);
+ // Values will be set in debugger shadow frames. Debugger will make sure deoptimization
+ // is triggered to make the values effective.
+ bool SetVRegPairFromDebugger(ArtMethod* m, uint16_t vreg, uint64_t new_value,
+ VRegKind kind_lo, VRegKind kind_hi)
+ SHARED_REQUIRES(Locks::mutator_lock_);
+
uintptr_t* GetGPRAddress(uint32_t reg) const;
// This is a fast-path for getting/setting values in a quick frame.
uintptr_t GetFPR(uint32_t reg) const;
void SetFPR(uint32_t reg, uintptr_t value);
+ bool GetVRegFromDebuggerShadowFrame(uint16_t vreg, VRegKind kind, uint32_t* val) const
+ SHARED_REQUIRES(Locks::mutator_lock_);
bool GetVRegFromQuickCode(ArtMethod* m, uint16_t vreg, VRegKind kind,
uint32_t* val) const
SHARED_REQUIRES(Locks::mutator_lock_);
bool GetRegisterIfAccessible(uint32_t reg, VRegKind kind, uint32_t* val) const
SHARED_REQUIRES(Locks::mutator_lock_);
+ bool GetVRegPairFromDebuggerShadowFrame(uint16_t vreg, VRegKind kind_lo, VRegKind kind_hi,
+ uint64_t* val) const
+ SHARED_REQUIRES(Locks::mutator_lock_);
bool GetVRegPairFromQuickCode(ArtMethod* m, uint16_t vreg, VRegKind kind_lo,
VRegKind kind_hi, uint64_t* val) const
SHARED_REQUIRES(Locks::mutator_lock_);
return shadow_frame;
}
+class FrameIdToShadowFrame {
+ public:
+ static FrameIdToShadowFrame* Create(size_t frame_id,
+ ShadowFrame* shadow_frame,
+ FrameIdToShadowFrame* next,
+ size_t num_vregs) {
+ // Append a bool array at the end to keep track of what vregs are updated by the debugger.
+ uint8_t* memory = new uint8_t[sizeof(FrameIdToShadowFrame) + sizeof(bool) * num_vregs];
+ return new (memory) FrameIdToShadowFrame(frame_id, shadow_frame, next);
+ }
+
+ static void Delete(FrameIdToShadowFrame* f) {
+ uint8_t* memory = reinterpret_cast<uint8_t*>(f);
+ delete[] memory;
+ }
+
+ size_t GetFrameId() const { return frame_id_; }
+ ShadowFrame* GetShadowFrame() const { return shadow_frame_; }
+ FrameIdToShadowFrame* GetNext() const { return next_; }
+ void SetNext(FrameIdToShadowFrame* next) { next_ = next; }
+ bool* GetUpdatedVRegFlags() {
+ return updated_vreg_flags_;
+ }
+
+ private:
+ FrameIdToShadowFrame(size_t frame_id,
+ ShadowFrame* shadow_frame,
+ FrameIdToShadowFrame* next)
+ : frame_id_(frame_id),
+ shadow_frame_(shadow_frame),
+ next_(next) {}
+
+ const size_t frame_id_;
+ ShadowFrame* const shadow_frame_;
+ FrameIdToShadowFrame* next_;
+ bool updated_vreg_flags_[0];
+
+ DISALLOW_COPY_AND_ASSIGN(FrameIdToShadowFrame);
+};
+
+static FrameIdToShadowFrame* FindFrameIdToShadowFrame(FrameIdToShadowFrame* head,
+ size_t frame_id) {
+ FrameIdToShadowFrame* found = nullptr;
+ for (FrameIdToShadowFrame* record = head; record != nullptr; record = record->GetNext()) {
+ if (record->GetFrameId() == frame_id) {
+ if (kIsDebugBuild) {
+ // Sanity check we have at most one record for this frame.
+ CHECK(found == nullptr) << "Multiple records for the frame " << frame_id;
+ found = record;
+ } else {
+ return record;
+ }
+ }
+ }
+ return found;
+}
+
+ShadowFrame* Thread::FindDebuggerShadowFrame(size_t frame_id) {
+ FrameIdToShadowFrame* record = FindFrameIdToShadowFrame(
+ tlsPtr_.frame_id_to_shadow_frame, frame_id);
+ if (record != nullptr) {
+ return record->GetShadowFrame();
+ }
+ return nullptr;
+}
+
+// Must only be called when FindDebuggerShadowFrame(frame_id) returns non-nullptr.
+bool* Thread::GetUpdatedVRegFlags(size_t frame_id) {
+ FrameIdToShadowFrame* record = FindFrameIdToShadowFrame(
+ tlsPtr_.frame_id_to_shadow_frame, frame_id);
+ CHECK(record != nullptr);
+ return record->GetUpdatedVRegFlags();
+}
+
+ShadowFrame* Thread::FindOrCreateDebuggerShadowFrame(size_t frame_id,
+ uint32_t num_vregs,
+ ArtMethod* method,
+ uint32_t dex_pc) {
+ ShadowFrame* shadow_frame = FindDebuggerShadowFrame(frame_id);
+ if (shadow_frame != nullptr) {
+ return shadow_frame;
+ }
+ VLOG(deopt) << "Create pre-deopted ShadowFrame for " << PrettyMethod(method);
+ shadow_frame = ShadowFrame::CreateDeoptimizedFrame(num_vregs, nullptr, method, dex_pc);
+ FrameIdToShadowFrame* record = FrameIdToShadowFrame::Create(frame_id,
+ shadow_frame,
+ tlsPtr_.frame_id_to_shadow_frame,
+ num_vregs);
+ for (uint32_t i = 0; i < num_vregs; i++) {
+ // Do this to clear all references for root visitors.
+ shadow_frame->SetVRegReference(i, nullptr);
+ // This flag will be changed to true if the debugger modifies the value.
+ record->GetUpdatedVRegFlags()[i] = false;
+ }
+ tlsPtr_.frame_id_to_shadow_frame = record;
+ return shadow_frame;
+}
+
+void Thread::RemoveDebuggerShadowFrameMapping(size_t frame_id) {
+ FrameIdToShadowFrame* head = tlsPtr_.frame_id_to_shadow_frame;
+ if (head->GetFrameId() == frame_id) {
+ tlsPtr_.frame_id_to_shadow_frame = head->GetNext();
+ FrameIdToShadowFrame::Delete(head);
+ return;
+ }
+ FrameIdToShadowFrame* prev = head;
+ for (FrameIdToShadowFrame* record = head->GetNext();
+ record != nullptr;
+ prev = record, record = record->GetNext()) {
+ if (record->GetFrameId() == frame_id) {
+ prev->SetNext(record->GetNext());
+ FrameIdToShadowFrame::Delete(record);
+ return;
+ }
+ }
+ LOG(FATAL) << "No shadow frame for frame " << frame_id;
+ UNREACHABLE();
+}
+
void Thread::InitTid() {
tls32_.tid = ::art::GetTid();
}
// Make sure we processed all deoptimization requests.
CHECK(tlsPtr_.deoptimization_context_stack == nullptr) << "Missed deoptimization";
+ CHECK(tlsPtr_.frame_id_to_shadow_frame == nullptr) <<
+ "Not all deoptimized frames have been consumed by the debugger.";
// We may be deleting a still born thread.
SetStateUnsafe(kTerminated);
}
}
}
- if (tlsPtr_.deoptimization_context_stack != nullptr) {
- for (DeoptimizationContextRecord* record = tlsPtr_.deoptimization_context_stack;
- record != nullptr;
- record = record->GetLink()) {
- if (record->IsReference()) {
- visitor->VisitRootIfNonNull(record->GetReturnValueAsGCRoot(),
- RootInfo(kRootThreadObject, thread_id));
- }
- visitor->VisitRootIfNonNull(record->GetPendingExceptionAsGCRoot(),
+ for (DeoptimizationContextRecord* record = tlsPtr_.deoptimization_context_stack;
+ record != nullptr;
+ record = record->GetLink()) {
+ if (record->IsReference()) {
+ visitor->VisitRootIfNonNull(record->GetReturnValueAsGCRoot(),
RootInfo(kRootThreadObject, thread_id));
}
+ visitor->VisitRootIfNonNull(record->GetPendingExceptionAsGCRoot(),
+ RootInfo(kRootThreadObject, thread_id));
+ }
+ if (tlsPtr_.frame_id_to_shadow_frame != nullptr) {
+ RootCallbackVisitor visitor_to_callback(visitor, thread_id);
+ ReferenceMapVisitor<RootCallbackVisitor> mapper(this, nullptr, visitor_to_callback);
+ for (FrameIdToShadowFrame* record = tlsPtr_.frame_id_to_shadow_frame;
+ record != nullptr;
+ record = record->GetNext()) {
+ mapper.VisitShadowFrame(record->GetShadowFrame());
+ }
}
for (auto* verifier = tlsPtr_.method_verifier; verifier != nullptr; verifier = verifier->link_) {
verifier->VisitRoots(visitor, RootInfo(kRootNativeStack, thread_id));
struct DebugInvokeReq;
class DeoptimizationContextRecord;
class DexFile;
+class FrameIdToShadowFrame;
class JavaVMExt;
struct JNIEnvExt;
class Monitor;
jobjectArray output_array = nullptr, int* stack_depth = nullptr)
SHARED_REQUIRES(Locks::mutator_lock_);
+ bool HasDebuggerShadowFrames() const {
+ return tlsPtr_.frame_id_to_shadow_frame != nullptr;
+ }
+
void VisitRoots(RootVisitor* visitor) SHARED_REQUIRES(Locks::mutator_lock_);
ALWAYS_INLINE void VerifyStack() SHARED_REQUIRES(Locks::mutator_lock_);
void PushStackedShadowFrame(ShadowFrame* sf, StackedShadowFrameType type);
ShadowFrame* PopStackedShadowFrame(StackedShadowFrameType type);
+ // For debugger, find the shadow frame that corresponds to a frame id.
+ // Or return null if there is none.
+ ShadowFrame* FindDebuggerShadowFrame(size_t frame_id)
+ SHARED_REQUIRES(Locks::mutator_lock_);
+ // For debugger, find the bool array that keeps track of the updated vreg set
+ // for a frame id.
+ bool* GetUpdatedVRegFlags(size_t frame_id) SHARED_REQUIRES(Locks::mutator_lock_);
+ // For debugger, find the shadow frame that corresponds to a frame id. If
+ // one doesn't exist yet, create one and track it in frame_id_to_shadow_frame.
+ ShadowFrame* FindOrCreateDebuggerShadowFrame(size_t frame_id,
+ uint32_t num_vregs,
+ ArtMethod* method,
+ uint32_t dex_pc)
+ SHARED_REQUIRES(Locks::mutator_lock_);
+
+ // Delete the entry that maps from frame_id to shadow_frame.
+ void RemoveDebuggerShadowFrameMapping(size_t frame_id)
+ SHARED_REQUIRES(Locks::mutator_lock_);
+
std::deque<instrumentation::InstrumentationStackFrame>* GetInstrumentationStack() {
return tlsPtr_.instrumentation_stack;
}
top_handle_scope(nullptr), class_loader_override(nullptr), long_jump_context(nullptr),
instrumentation_stack(nullptr), debug_invoke_req(nullptr), single_step_control(nullptr),
stacked_shadow_frame_record(nullptr), deoptimization_context_stack(nullptr),
- name(nullptr), pthread_self(0),
+ frame_id_to_shadow_frame(nullptr), name(nullptr), pthread_self(0),
last_no_thread_suspension_cause(nullptr), thread_local_start(nullptr),
thread_local_pos(nullptr), thread_local_end(nullptr), thread_local_objects(0),
thread_local_alloc_stack_top(nullptr), thread_local_alloc_stack_end(nullptr),
// Deoptimization return value record stack.
DeoptimizationContextRecord* deoptimization_context_stack;
+ // For debugger, a linked list that keeps the mapping from frame_id to shadow frame.
+ // Shadow frames may be created before deoptimization happens so that the debugger can
+ // set local values there first.
+ FrameIdToShadowFrame* frame_id_to_shadow_frame;
+
// A cached copy of the java.lang.Thread's name.
std::string* name;