Files
sdk/runtime/vm/constants_kbc.cc
Tess Strickland df48d89796 [vm,dyn_modules] Fix single stepping at breakpoints.
In compiled code, a block of instructions that calls the single step
handler in the runtime is generated for any pause points.  Enabling a
breakpoint at a pause point then patches those instructions to instead
call a stub. The stub first calls the breakpoint handler in the runtime.
The runtime's breakpoint handler returns the original call target for
the breakpoint, which the stub then calls.

Since the debugger ignores any pause points with the same source
location after hitting a breakpoint, this means that setting a
breakpoint at a pause point only generates a breakpoint hit event,
ensuring that a request to single step after hitting a breakpoint steps
to the next location, not the breakpoint location.

Similar to compiled code, enabling a breakpoint in interpreted code
replaces the original instruction with a special instruction that first
calls the breakpoint handler and then performs the original
instruction's implementation.

However, there is no specific instruction for pause points in
interpreted code. Instead, the dispatch loop in the interpreter has two
labels for each instruction: one for the instruction's implementation,
and another that is used instead when single stepping is enabled.  All
the single step labels point at the same code block, which first calls
the single step handler and then dispatches to the code for the
instruction's implementation.

This difference in handling breakpoints and single step checks means
that pause events are prioritized, not breakpoint hit events, which
means single stepping into a breakpoint generates first a pause event
and then a breakpoint hit event, which differs from the behavior
for compiled code.

To fix this, refactor single step handling in the interpreter so that
the single step label for breakpoint instructions point to a different
code block which first calls the breakpoint handler and then calls the
single step handler before dispatching to the original instruction's
implementation.

TEST=ci

Cq-Include-Trybots: luci.dart.try:vm-dyn-linux-debug-x64-try,vm-dyn-mac-debug-arm64-try
Change-Id: Id33132bbecc5d0cbd9e92bc5a13d8d8a80155344
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/481782
Reviewed-by: Alexander Markov <alexmarkov@google.com>
Commit-Queue: Tess Strickland <sstrickl@google.com>
Auto-Submit: Tess Strickland <sstrickl@google.com>
2026-02-19 09:43:52 -08:00

90 lines
3.0 KiB
C++

// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
#include "vm/constants_kbc.h"
namespace dart {
static const intptr_t kInstructionSize0 = 1;
static const intptr_t kInstructionSizeA = 2;
static const intptr_t kInstructionSizeD = 2;
static const intptr_t kInstructionSizeWideD = 5;
static const intptr_t kInstructionSizeX = 2;
static const intptr_t kInstructionSizeWideX = 5;
static const intptr_t kInstructionSizeT = 2;
static const intptr_t kInstructionSizeWideT = 4;
static const intptr_t kInstructionSizeA_E = 3;
static const intptr_t kInstructionSizeWideA_E = 6;
static const intptr_t kInstructionSizeA_Y = 3;
static const intptr_t kInstructionSizeWideA_Y = 6;
static const intptr_t kInstructionSizeD_F = 3;
static const intptr_t kInstructionSizeWideD_F = 6;
static const intptr_t kInstructionSizeA_B_C = 4;
const intptr_t KernelBytecode::kInstructionSize[] = {
#define SIZE_ORDN(encoding) kInstructionSize##encoding
#define SIZE_WIDE(encoding) kInstructionSizeWide##encoding
#define SIZE_RESV(encoding) SIZE_ORDN(encoding)
#define SIZE(name, encoding, kind, op1, op2, op3) SIZE_##kind(encoding),
KERNEL_BYTECODES_LIST(SIZE)
#undef SIZE_ORDN
#undef SIZE_WIDE
#undef SIZE_RESV
#undef SIZE
};
static const KBCInstr kVMInternal_ImplicitConstructorClosureInstructions[] = {
KernelBytecode::kVMInternal_ImplicitConstructorClosure,
0,
0,
KernelBytecode::kPush,
0,
KernelBytecode::kReturnTOS,
};
static const KBCInstr
kVMInternal_ImplicitConstructorClosure_WideInstructions[] = {
KernelBytecode::kTrap,
};
static const KBCInstr kVMInternal_ImplicitInstanceClosureInstructions[] = {
KernelBytecode::kVMInternal_ImplicitInstanceClosure,
0,
0,
KernelBytecode::kReturnTOS,
};
static const KBCInstr kVMInternal_ImplicitInstanceClosure_WideInstructions[] = {
KernelBytecode::kTrap,
};
#define DECLARE_INSTRUCTIONS(name, fmt, kind, fmta, fmtb, fmtc) \
static const KBCInstr k##name##Instructions[] = { \
KernelBytecode::k##name, \
KernelBytecode::kReturnTOS, \
};
INTERNAL_KERNEL_BYTECODES_WITH_DEFAULT_CODE(DECLARE_INSTRUCTIONS)
#undef DECLARE_INSTRUCTIONS
void KernelBytecode::GetVMInternalBytecodeInstructions(
Opcode opcode,
const KBCInstr** instructions,
intptr_t* instructions_size) {
switch (opcode) {
#define CASE(name, fmt, kind, fmta, fmtb, fmtc) \
case k##name: \
*instructions = k##name##Instructions; \
*instructions_size = sizeof(k##name##Instructions); \
return;
INTERNAL_KERNEL_BYTECODES_LIST_WITH_NO_BREAKPOINTS(CASE)
#undef CASE
default:
UNREACHABLE();
}
}
} // namespace dart