[vm/debugger] Implement debug breaks in interpreter without bytecode patching.

The list of bytecodes that are checked for debug breaks and single stepping
can be tuned later (as well as performance if needed).

Fix identification of Dart top activation frame in debugger.

Change-Id: Ieab804ba25f84efe173531431c6d311005163433
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/104922
Commit-Queue: Régis Crelier <regis@google.com>
Reviewed-by: Alexander Markov <alexmarkov@google.com>
This commit is contained in:
Régis Crelier 2019-06-05 17:39:04 +00:00 committed by commit-bot@chromium.org
parent 487326eef4
commit ffea2b3245
7 changed files with 183 additions and 86 deletions

View file

@ -995,6 +995,22 @@ class KernelBytecode {
}
}
// The interpreter and this function must agree on the opcodes.
DART_FORCE_INLINE static bool IsDebugBreakCheckedOpcode(
const KBCInstr* instr) {
switch (DecodeOpcode(instr)) {
case KernelBytecode::kCheckStack:
case KernelBytecode::kDirectCall:
case KernelBytecode::kInterfaceCall:
case KernelBytecode::kUncheckedInterfaceCall:
case KernelBytecode::kDynamicCall:
case KernelBytecode::kReturnTOS:
return true;
default:
return false;
}
}
static const uint8_t kNativeCallToGrowableListArgc = 2;
DART_FORCE_INLINE static uint8_t DecodeArgc_Old(const KBCInstr* ret_addr) {

View file

@ -16,6 +16,7 @@
#include "vm/deopt_instructions.h"
#include "vm/flags.h"
#include "vm/globals.h"
#include "vm/interpreter.h"
#include "vm/json_stream.h"
#include "vm/kernel.h"
#include "vm/longjump.h"
@ -1484,7 +1485,7 @@ void ActivationFrame::PrintToJSONObjectRegular(JSONObject* jsobj) {
jsobj->AddLocation(script, pos);
jsobj->AddProperty("function", function());
if (IsInterpreted()) {
jsobj->AddProperty("bytecode", bytecode());
jsobj->AddProperty("code", bytecode());
} else {
jsobj->AddProperty("code", code());
}
@ -1528,7 +1529,7 @@ void ActivationFrame::PrintToJSONObjectAsyncCausal(JSONObject* jsobj) {
jsobj->AddLocation(script, pos);
jsobj->AddProperty("function", function());
if (IsInterpreted()) {
jsobj->AddProperty("bytecode", bytecode());
jsobj->AddProperty("code", bytecode());
} else {
jsobj->AddProperty("code", code());
}
@ -1549,7 +1550,7 @@ void ActivationFrame::PrintToJSONObjectAsyncActivation(JSONObject* jsobj) {
jsobj->AddLocation(script, pos);
jsobj->AddProperty("function", function());
if (IsInterpreted()) {
jsobj->AddProperty("bytecode", bytecode());
jsobj->AddProperty("code", bytecode());
} else {
jsobj->AddProperty("code", code());
}
@ -1683,7 +1684,7 @@ void CodeBreakpoint::Enable() {
if (!is_enabled_) {
if (IsInterpreted()) {
#if !defined(DART_PRECOMPILED_RUNTIME)
SetBytecodeBreak();
SetBytecodeBreakpoint();
#else
UNREACHABLE();
#endif // !defined(DART_PRECOMPILED_RUNTIME)
@ -1698,7 +1699,7 @@ void CodeBreakpoint::Disable() {
if (is_enabled_) {
if (IsInterpreted()) {
#if !defined(DART_PRECOMPILED_RUNTIME)
UnsetBytecodeBreak();
UnsetBytecodeBreakpoint();
#else
UNREACHABLE();
#endif // !defined(DART_PRECOMPILED_RUNTIME)
@ -1910,6 +1911,18 @@ void Debugger::DeoptimizeWorld() {
}
}
void Debugger::NotifySingleStepping(bool value) const {
isolate_->set_single_step(value);
#if !defined(DART_PRECOMPILED_RUNTIME)
// Do not call Interpreter::Current(), which may allocate an interpreter.
Interpreter* interpreter = Thread::Current()->interpreter();
if (interpreter != nullptr) {
// Do not reset is_debugging to false if bytecode debug breaks are enabled.
interpreter->set_is_debugging(value || HasEnabledBytecodeBreakpoints());
}
#endif // !defined(DART_PRECOMPILED_RUNTIME)
}
ActivationFrame* Debugger::CollectDartFrame(Isolate* isolate,
uword pc,
StackFrame* frame,
@ -2406,23 +2419,29 @@ ActivationFrame* Debugger::TopDartFrame() const {
StackFrameIterator iterator(ValidationPolicy::kDontValidateFrames,
Thread::Current(),
StackFrameIterator::kNoCrossThreadIteration);
StackFrame* frame = iterator.NextFrame();
while ((frame != NULL) && !frame->IsDartFrame()) {
StackFrame* frame;
while (true) {
frame = iterator.NextFrame();
}
ASSERT(frame != NULL);
RELEASE_ASSERT(frame != nullptr);
if (!frame->IsDartFrame()) {
continue;
}
#if !defined(DART_PRECOMPILED_RUNTIME)
if (frame->is_interpreted()) {
Bytecode& bytecode = Bytecode::Handle(frame->LookupDartBytecode());
ActivationFrame* activation =
new ActivationFrame(frame->pc(), frame->fp(), frame->sp(), bytecode);
if (frame->is_interpreted()) {
Bytecode& bytecode = Bytecode::Handle(frame->LookupDartBytecode());
if (bytecode.function() == Function::null()) {
continue; // Skip bytecode stub frame.
}
ActivationFrame* activation =
new ActivationFrame(frame->pc(), frame->fp(), frame->sp(), bytecode);
return activation;
}
#endif // !defined(DART_PRECOMPILED_RUNTIME)
Code& code = Code::Handle(frame->LookupDartCode());
ActivationFrame* activation = new ActivationFrame(
frame->pc(), frame->fp(), frame->sp(), code, Object::null_array(), 0);
return activation;
}
#endif // !defined(DART_PRECOMPILED_RUNTIME)
Code& code = Code::Handle(frame->LookupDartCode());
ActivationFrame* activation = new ActivationFrame(
frame->pc(), frame->fp(), frame->sp(), code, Object::null_array(), 0);
return activation;
}
DebuggerStackTrace* Debugger::StackTrace() {
@ -2885,6 +2904,24 @@ TokenPosition Debugger::ResolveBreakpointPos(bool in_bytecode,
return TokenPosition::kNoSource;
}
#if !defined(DART_PRECOMPILED_RUNTIME)
// Find a 'debug break checked' bytecode in the range [pc..end_pc[ and return
// the pc after it or nullptr.
static const KBCInstr* FindBreakpointCheckedInstr(const KBCInstr* pc,
const KBCInstr* end_pc) {
while ((pc < end_pc) && !KernelBytecode::IsDebugBreakCheckedOpcode(pc)) {
pc = KernelBytecode::Next(pc);
}
if (pc < end_pc) {
ASSERT(KernelBytecode::IsDebugBreakCheckedOpcode(pc));
// The checked debug break pc must point to the next bytecode.
return KernelBytecode::Next(pc);
}
// No 'debug break checked' bytecode in the range.
return nullptr;
}
#endif // !defined(DART_PRECOMPILED_RUNTIME)
void Debugger::MakeCodeBreakpointAt(const Function& func,
BreakpointLocation* loc) {
ASSERT(loc->token_pos_.IsReal());
@ -2895,24 +2932,45 @@ void Debugger::MakeCodeBreakpointAt(const Function& func,
if (func.HasBytecode()) {
Bytecode& bytecode = Bytecode::Handle(func.bytecode());
ASSERT(!bytecode.IsNull());
uword lowest_pc_offset = kUwordMax;
const KBCInstr* pc = nullptr;
if (bytecode.HasSourcePositions()) {
kernel::BytecodeSourcePositionsIterator iter(Thread::Current()->zone(),
bytecode);
bool check_range = false;
while (iter.MoveNext()) {
if (iter.TokenPos() == loc->token_pos_) {
if (iter.PcOffset() < lowest_pc_offset) {
lowest_pc_offset = iter.PcOffset();
if (check_range) {
const KBCInstr* end_pc =
reinterpret_cast<const KBCInstr*>(bytecode.PayloadStart()) +
iter.PcOffset();
check_range = false;
// Find a 'debug break checked' bytecode in the range [pc..end_pc[.
pc = FindBreakpointCheckedInstr(pc, end_pc);
if (pc != nullptr) {
// TODO(regis): We may want to find all PCs for a token position,
// e.g. in the case of duplicated bytecode in finally clauses.
break;
}
}
if (iter.TokenPos() == loc->token_pos_) {
pc = reinterpret_cast<const KBCInstr*>(bytecode.PayloadStart()) +
iter.PcOffset();
check_range = true;
}
}
if (check_range) {
ASSERT(pc != nullptr);
// Use the end of the bytecode as the end of the range to check.
pc = FindBreakpointCheckedInstr(
pc, reinterpret_cast<const KBCInstr*>(bytecode.PayloadStart()) +
bytecode.Size());
}
}
if (lowest_pc_offset != kUwordMax) {
uword lowest_pc = bytecode.PayloadStart() + lowest_pc_offset;
CodeBreakpoint* code_bpt = GetCodeBreakpoint(lowest_pc);
if (pc != nullptr) {
CodeBreakpoint* code_bpt = GetCodeBreakpoint(reinterpret_cast<uword>(pc));
if (code_bpt == NULL) {
// No code breakpoint for this code exists; create one.
code_bpt = new CodeBreakpoint(bytecode, loc->token_pos_, lowest_pc);
code_bpt = new CodeBreakpoint(bytecode, loc->token_pos_,
reinterpret_cast<uword>(pc));
RegisterCodeBreakpoint(code_bpt);
}
code_bpt->set_bpt_location(loc);
@ -3575,7 +3633,7 @@ void Debugger::Pause(ServiceEvent* event) {
void Debugger::EnterSingleStepMode() {
ResetSteppingFramePointers();
DeoptimizeWorld();
isolate_->set_single_step(true);
NotifySingleStepping(true);
}
void Debugger::ResetSteppingFramePointers() {
@ -3637,7 +3695,7 @@ void Debugger::HandleSteppingRequest(DebuggerStackTrace* stack_trace,
// as well. We need to deoptimize the world in case we are about
// to call an optimized function.
DeoptimizeWorld();
isolate_->set_single_step(true);
NotifySingleStepping(true);
skip_next_step_ = skip_next_step;
SetAsyncSteppingFramePointer(stack_trace);
if (FLAG_verbose_debug) {
@ -3645,7 +3703,7 @@ void Debugger::HandleSteppingRequest(DebuggerStackTrace* stack_trace,
}
} else if (resume_action_ == kStepOver) {
DeoptimizeWorld();
isolate_->set_single_step(true);
NotifySingleStepping(true);
skip_next_step_ = skip_next_step;
SetSyncSteppingFramePointer(stack_trace);
SetAsyncSteppingFramePointer(stack_trace);
@ -3669,7 +3727,7 @@ void Debugger::HandleSteppingRequest(DebuggerStackTrace* stack_trace,
}
// Fall through to synchronous stepping.
DeoptimizeWorld();
isolate_->set_single_step(true);
NotifySingleStepping(true);
// Find topmost caller that is debuggable.
for (intptr_t i = 1; i < stack_trace->Length(); i++) {
ActivationFrame* frame = stack_trace->FrameAt(i);
@ -3959,7 +4017,7 @@ bool Debugger::IsDebugging(Thread* thread, const Function& func) {
void Debugger::SignalPausedEvent(ActivationFrame* top_frame, Breakpoint* bpt) {
resume_action_ = kContinue;
ResetSteppingFramePointers();
isolate_->set_single_step(false);
NotifySingleStepping(false);
ASSERT(!IsPaused());
if ((bpt != NULL) && bpt->IsSingleShot()) {
RemoveBreakpoint(bpt->id());
@ -4695,7 +4753,7 @@ void Debugger::Continue() {
SetResumeAction(kContinue);
stepping_fp_ = 0;
async_stepping_fp_ = 0;
isolate_->set_single_step(false);
NotifySingleStepping(false);
}
BreakpointLocation* Debugger::GetLatentBreakpoint(const String& url,

View file

@ -230,8 +230,8 @@ class CodeBreakpoint {
void PatchCode();
void RestoreCode();
void SetBytecodeBreak();
void UnsetBytecodeBreak();
void SetBytecodeBreakpoint();
void UnsetBytecodeBreakpoint();
RawCode* code_;
RawBytecode* bytecode_;
@ -552,6 +552,10 @@ class Debugger {
ignore_breakpoints_ = ignore_breakpoints;
}
bool HasEnabledBytecodeBreakpoints() const;
// Called from the interpreter. Note that pc already points to next bytecode.
bool HasBytecodeBreakpointAt(const KBCInstr* next_pc) const;
// Put the isolate into single stepping mode when Dart code next runs.
//
// This is used by the vm service to allow the user to step while
@ -671,6 +675,7 @@ class Debugger {
intptr_t requested_column,
TokenPosition exact_token_pos);
void DeoptimizeWorld();
void NotifySingleStepping(bool value) const;
BreakpointLocation* SetCodeBreakpoints(bool in_bytecode,
BreakpointLocation* loc,
const Script& script,

View file

@ -7,24 +7,47 @@
#include "vm/debugger.h"
#include "vm/instructions_kbc.h"
#include "vm/interpreter.h"
namespace dart {
#ifndef PRODUCT
void CodeBreakpoint::SetBytecodeBreak() {
void CodeBreakpoint::SetBytecodeBreakpoint() {
ASSERT(!is_enabled_);
ASSERT(!Isolate::Current()->is_using_old_bytecode_instructions());
// TODO(regis): Register pc_ (or the token pos range including pc_) with the
// interpreter as a debug break address.
is_enabled_ = true;
Interpreter::Current()->set_is_debugging(true);
}
void CodeBreakpoint::UnsetBytecodeBreak() {
void CodeBreakpoint::UnsetBytecodeBreakpoint() {
ASSERT(is_enabled_);
// TODO(regis): Unregister pc_ (or the token pos range including pc_) with the
// interpreter as a debug break address.
is_enabled_ = false;
if (!Isolate::Current()->single_step() &&
!Isolate::Current()->debugger()->HasEnabledBytecodeBreakpoints()) {
Interpreter::Current()->set_is_debugging(false);
}
}
bool Debugger::HasEnabledBytecodeBreakpoints() const {
CodeBreakpoint* cbpt = code_breakpoints_;
while (cbpt != nullptr) {
if (cbpt->IsEnabled() && cbpt->IsInterpreted()) {
return true;
}
cbpt = cbpt->next();
}
return false;
}
bool Debugger::HasBytecodeBreakpointAt(const KBCInstr* next_pc) const {
CodeBreakpoint* cbpt = code_breakpoints_;
while (cbpt != nullptr) {
if ((reinterpret_cast<uword>(next_pc)) == cbpt->pc_ && cbpt->IsEnabled()) {
ASSERT(cbpt->IsInterpreted());
return true;
}
cbpt = cbpt->next();
}
return false;
}
#endif // !PRODUCT

View file

@ -381,7 +381,7 @@ Interpreter::~Interpreter() {
Interpreter* Interpreter::Current() {
Thread* thread = Thread::Current();
Interpreter* interpreter = thread->interpreter();
if (interpreter == NULL) {
if (interpreter == nullptr) {
TransitionGeneratedToVM transition(thread);
interpreter = new Interpreter();
Thread::Current()->set_interpreter(interpreter);
@ -1167,6 +1167,25 @@ static_assert(KernelBytecode::kMinSupportedBytecodeFormatVersion < 7,
} \
}
#ifdef PRODUCT
#define DEBUG_CHECK
#else
#define DEBUG_CHECK \
if (is_debugging()) { \
/* Check for debug breakpoint or if single stepping. */ \
if (thread->isolate()->debugger()->HasBytecodeBreakpointAt(pc)) { \
SP[1] = null_value; \
Exit(thread, FP, SP + 2, pc); \
NativeArguments args(thread, 0, NULL, SP + 1); \
INVOKE_RUNTIME(DRT_BreakpointRuntimeHandler, args) \
} else if (thread->isolate()->single_step()) { \
Exit(thread, FP, SP + 1, pc); \
NativeArguments args(thread, 0, NULL, NULL); \
INVOKE_RUNTIME(DRT_SingleStepHandler, args); \
} \
}
#endif // PRODUCT
bool Interpreter::AssertAssignable(Thread* thread,
const KBCInstr* pc,
RawObject** FP,
@ -1762,6 +1781,7 @@ SwitchDispatch:
{
BYTECODE(CheckStack, A);
DEBUG_CHECK;
{
// Check the interpreter's own stack limit for actual interpreter's stack
// overflows, and also the thread's stack limit for scheduled interrupts.
@ -1956,16 +1976,7 @@ SwitchDispatch:
{
BYTECODE(DirectCall, D_F);
#ifndef PRODUCT
// Check if single stepping.
if (thread->isolate()->single_step()) {
Exit(thread, FP, SP + 1, pc);
NativeArguments args(thread, 0, NULL, NULL);
INVOKE_RUNTIME(DRT_SingleStepHandler, args);
}
#endif // !PRODUCT
DEBUG_CHECK;
// Invoke target function.
{
const uint32_t argc = rF;
@ -1986,16 +1997,7 @@ SwitchDispatch:
{
BYTECODE(InterfaceCall, D_F);
#ifndef PRODUCT
// Check if single stepping.
if (thread->isolate()->single_step()) {
Exit(thread, FP, SP + 1, pc);
NativeArguments args(thread, 0, NULL, NULL);
INVOKE_RUNTIME(DRT_SingleStepHandler, args);
}
#endif // !PRODUCT
DEBUG_CHECK;
{
const uint32_t argc = rF;
const uint32_t kidx = rD;
@ -2018,16 +2020,7 @@ SwitchDispatch:
{
BYTECODE(UncheckedInterfaceCall, D_F);
#ifndef PRODUCT
// Check if single stepping.
if (thread->isolate()->single_step()) {
Exit(thread, FP, SP + 1, pc);
NativeArguments args(thread, 0, NULL, NULL);
INVOKE_RUNTIME(DRT_SingleStepHandler, args);
}
#endif // !PRODUCT
DEBUG_CHECK;
{
const uint32_t argc = rF;
const uint32_t kidx = rD;
@ -2050,16 +2043,7 @@ SwitchDispatch:
{
BYTECODE(DynamicCall, D_F);
#ifndef PRODUCT
// Check if single stepping.
if (thread->isolate()->single_step()) {
Exit(thread, FP, SP + 1, pc);
NativeArguments args(thread, 0, NULL, NULL);
INVOKE_RUNTIME(DRT_SingleStepHandler, args);
}
#endif // !PRODUCT
DEBUG_CHECK;
{
const uint32_t argc = rF;
const uint32_t kidx = rD;
@ -2275,6 +2259,7 @@ SwitchDispatch:
RawObject* result; // result to return to the caller.
BYTECODE(ReturnTOS, 0);
DEBUG_CHECK;
result = *SP;
// Restore caller PC.
pc = SavedCallerPC(FP);

View file

@ -122,6 +122,11 @@ class Interpreter {
void VisitObjectPointers(ObjectPointerVisitor* visitor);
void MajorGC() { lookup_cache_.Clear(); }
#ifndef PRODUCT
void set_is_debugging(bool value) { is_debugging_ = value; }
bool is_debugging() const { return is_debugging_; }
#endif // !PRODUCT
private:
uintptr_t* stack_;
uword stack_base_;
@ -274,6 +279,10 @@ class Interpreter {
last_setjmp_buffer_ = buffer;
}
#ifndef PRODUCT
bool is_debugging_;
#endif // !PRODUCT
friend class InterpreterSetjmpBuffer;
DISALLOW_COPY_AND_ASSIGN(Interpreter);
};

View file

@ -976,9 +976,10 @@ DEFINE_RUNTIME_ENTRY(BreakpointRuntimeHandler, 0) {
StackFrameIterator::kNoCrossThreadIteration);
StackFrame* caller_frame = iterator.NextFrame();
ASSERT(caller_frame != NULL);
ASSERT(!caller_frame->is_interpreted());
const Code& orig_stub = Code::Handle(
zone, isolate->debugger()->GetPatchedStubAddress(caller_frame->pc()));
Code& orig_stub = Code::Handle(zone);
if (!caller_frame->is_interpreted()) {
orig_stub = isolate->debugger()->GetPatchedStubAddress(caller_frame->pc());
}
const Error& error =
Error::Handle(zone, isolate->debugger()->PauseBreakpoint());
ThrowIfError(error);