[vm,dyn_modules] More work on bytecode debugger support.
Detects yield points in Debugger::IsAtAsyncJump for bytecode by seeing if the currently executing instruction is a direct call to an await or yield compiled stub. Adds a ResumptionBreakpointHandler runtime entry that is called during Interpreter::Resume() if the current isolate has resumption breakpoints. Similarly, all the places where a DebugCheck could be emitted if debugging stops are requested now include an explicit source position emission when source positions are requested but debugger stops are not, to ensure the debugger has appropriate information. Fixes CompareTopDartFrameTo returning kSelf for non-top frames when the top frame was interpreted but the stepping frame was not or vice versa. TEST=pkg/vm_service/test Change-Id: I88cdc37cf745f30e8dfb6b14c19fc9b2c4cbaf2d Cq-Include-Trybots: luci.dart.try:vm-dyn-linux-debug-x64-try,vm-aot-dyn-linux-debug-x64-try,vm-aot-dyn-linux-product-x64-try,vm-dyn-mac-debug-arm64-try Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/446300 Commit-Queue: Tess Strickland <sstrickl@google.com> Reviewed-by: Alexander Markov <alexmarkov@google.com>
This commit is contained in:
committed by
Commit Queue
parent
0908a80195
commit
74f753f32d
@@ -1479,12 +1479,8 @@ class BytecodeGenerator extends RecursiveVisitor {
|
||||
}
|
||||
if (condition is EqualsNull) {
|
||||
_generateNode(condition.expression);
|
||||
if (options.emitDebuggerStops &&
|
||||
condition.fileOffset != TreeNode.noOffset) {
|
||||
final savedSourcePosition = asm.currentSourcePosition;
|
||||
_recordSourcePosition(condition.fileOffset);
|
||||
asm.emitDebugCheck();
|
||||
asm.currentSourcePosition = savedSourcePosition;
|
||||
if (condition.fileOffset != TreeNode.noOffset) {
|
||||
_emitLocalSourcePosition(condition.fileOffset);
|
||||
}
|
||||
if (value) {
|
||||
asm.emitJumpIfNull(dest);
|
||||
@@ -1631,23 +1627,20 @@ class BytecodeGenerator extends RecursiveVisitor {
|
||||
savedAssemblers = null;
|
||||
currentLoopDepth = 0;
|
||||
savedMaxSourcePositions = <int>[];
|
||||
maxSourcePosition = node.fileOffset;
|
||||
if (node is Procedure) {
|
||||
maxSourcePosition = node.fileStartOffset;
|
||||
} else if (node is Constructor) {
|
||||
maxSourcePosition = node.startFileOffset;
|
||||
} else {
|
||||
maxSourcePosition = node.fileOffset;
|
||||
}
|
||||
|
||||
locals = new LocalVariables(node, options, staticTypeContext);
|
||||
locals.enterScope(node);
|
||||
|
||||
int position;
|
||||
if (node is Procedure) {
|
||||
position = node.fileStartOffset;
|
||||
} else if (node is Constructor) {
|
||||
position = node.startFileOffset;
|
||||
} else {
|
||||
position = node.fileOffset;
|
||||
}
|
||||
_recordSourcePosition(position);
|
||||
_genPrologue(node, node.function);
|
||||
_setupInitialContext(node.function);
|
||||
_emitFirstDebugCheck(node.function);
|
||||
_emitFirstDebugCheck(node, node.function);
|
||||
_genEqualsOperatorNullHandling(node);
|
||||
if (node is Procedure && node.isInstanceMember) {
|
||||
_checkArguments(node.function);
|
||||
@@ -1864,7 +1857,7 @@ class BytecodeGenerator extends RecursiveVisitor {
|
||||
return localVariables;
|
||||
}
|
||||
|
||||
void _genPrologue(Node node, FunctionNode? function) {
|
||||
void _genPrologue(TreeNode node, FunctionNode? function) {
|
||||
if (locals.makesCopyOfParameters) {
|
||||
final int numOptionalPositional = function!.positionalParameters.length -
|
||||
function.requiredParameterCount;
|
||||
@@ -1957,6 +1950,7 @@ class BytecodeGenerator extends RecursiveVisitor {
|
||||
|
||||
// CheckStack must see a properly initialized context when stress-testing
|
||||
// stack trace collection.
|
||||
_recordInitialSourcePositionForFunction(node, function);
|
||||
asm.emitCheckStack(0);
|
||||
|
||||
if (locals.hasFunctionTypeArgsVar && isClosure) {
|
||||
@@ -2047,25 +2041,31 @@ class BytecodeGenerator extends RecursiveVisitor {
|
||||
}
|
||||
}
|
||||
|
||||
void _emitFirstDebugCheck(FunctionNode? function) {
|
||||
int _initialSourcePositionForFunction(TreeNode node, FunctionNode? function) {
|
||||
// The debugger expects the initial source position to correspond to the
|
||||
// declaration position of the last parameter, if any, or of the function.
|
||||
if (function?.namedParameters.isNotEmpty ?? false) {
|
||||
return function!.namedParameters.last.fileOffset;
|
||||
} else if (function?.positionalParameters.isNotEmpty ?? false) {
|
||||
return function!.positionalParameters.last.fileOffset;
|
||||
} else if (function != null) {
|
||||
return function.fileOffset;
|
||||
} else {
|
||||
return node.fileOffset;
|
||||
}
|
||||
}
|
||||
|
||||
void _recordInitialSourcePositionForFunction(
|
||||
TreeNode node, FunctionNode? function) {
|
||||
_recordSourcePosition(_initialSourcePositionForFunction(node, function));
|
||||
}
|
||||
|
||||
void _emitFirstDebugCheck(TreeNode node, FunctionNode? function) {
|
||||
if (options.emitDebuggerStops) {
|
||||
// DebugCheck instruction should be emitted after parameter variables
|
||||
// are declared and copied into context.
|
||||
// The debugger expects the source position to correspond to the
|
||||
// declaration position of the last parameter, if any, or of the function.
|
||||
// The DebugCheck must be encountered each time an async op is reentered.
|
||||
if (options.emitSourcePositions && function != null) {
|
||||
var pos = TreeNode.noOffset;
|
||||
if (function.namedParameters.isNotEmpty) {
|
||||
pos = function.namedParameters.last.fileOffset;
|
||||
} else if (function.positionalParameters.isNotEmpty) {
|
||||
pos = function.positionalParameters.last.fileOffset;
|
||||
}
|
||||
if (pos == TreeNode.noOffset) {
|
||||
pos = function.fileOffset;
|
||||
}
|
||||
_recordSourcePosition(pos);
|
||||
}
|
||||
_recordInitialSourcePositionForFunction(node, function);
|
||||
asm.emitDebugCheck();
|
||||
}
|
||||
}
|
||||
@@ -2411,11 +2411,10 @@ class BytecodeGenerator extends RecursiveVisitor {
|
||||
|
||||
final int closureFunctionIndex = cp.addClosureFunction(closureIndex);
|
||||
|
||||
_recordSourcePosition(function.fileOffset);
|
||||
maxSourcePosition = math.max(maxSourcePosition, function.fileOffset);
|
||||
_genPrologue(node, function);
|
||||
|
||||
_setupInitialContext(function);
|
||||
_emitFirstDebugCheck(function);
|
||||
_emitFirstDebugCheck(node, function);
|
||||
_checkArguments(function);
|
||||
_initSuspendableFunction(function);
|
||||
|
||||
@@ -2685,6 +2684,26 @@ class BytecodeGenerator extends RecursiveVisitor {
|
||||
continuation();
|
||||
}
|
||||
|
||||
// Emits a source position entry and/or debugger stop as appropriate.
|
||||
void _emitSourcePosition() {
|
||||
if (options.emitSourcePositions) {
|
||||
asm.emitSourcePosition();
|
||||
}
|
||||
if (options.emitDebuggerStops) {
|
||||
asm.emitDebugCheck();
|
||||
}
|
||||
}
|
||||
|
||||
// Records the given file offset as the current source position and
|
||||
// emits a source position entry and/or debugger stop as appropriate,
|
||||
// restoring the current source position afterwards.
|
||||
void _emitLocalSourcePosition(int fileOffset) {
|
||||
final savedSourcePosition = asm.currentSourcePosition;
|
||||
_recordSourcePosition(fileOffset);
|
||||
_emitSourcePosition();
|
||||
asm.currentSourcePosition = savedSourcePosition;
|
||||
}
|
||||
|
||||
/// Generates non-local transfer from inner node [from] into the outer
|
||||
/// node, executing finally blocks on the way out. [to] can be null,
|
||||
/// in such case all enclosing finally blocks are executed.
|
||||
@@ -2692,9 +2711,7 @@ class BytecodeGenerator extends RecursiveVisitor {
|
||||
/// the last finally block.
|
||||
void _generateNonLocalControlTransfer(
|
||||
TreeNode from, TreeNode to, GenerateContinuation continuation) {
|
||||
if (options.emitDebuggerStops && from.fileOffset != TreeNode.noOffset) {
|
||||
asm.emitDebugCheck(); // Before context is unwound.
|
||||
}
|
||||
_emitLocalSourcePosition(from.fileOffset);
|
||||
List<TryFinally> tryFinallyBlocks = _getEnclosingTryFinallyBlocks(from, to);
|
||||
_addFinallyBlocks(tryFinallyBlocks, continuation);
|
||||
}
|
||||
@@ -3389,9 +3406,8 @@ class BytecodeGenerator extends RecursiveVisitor {
|
||||
}
|
||||
tryCatches![tryCatch]!.needsStackTrace = true;
|
||||
|
||||
if (options.emitDebuggerStops) {
|
||||
asm.emitDebugCheck(); // Allow breakpoint on explicit rethrow statement.
|
||||
}
|
||||
// Allow breakpoint on explicit rethrow statement.
|
||||
_emitSourcePosition();
|
||||
_genRethrow(tryCatch);
|
||||
}
|
||||
|
||||
@@ -3505,9 +3521,8 @@ class BytecodeGenerator extends RecursiveVisitor {
|
||||
|
||||
final target = node.target;
|
||||
if (target is Field && !_needsSetter(target)) {
|
||||
if (options.emitDebuggerStops &&
|
||||
_variableSetNeedsDebugCheck(node.value)) {
|
||||
asm.emitDebugCheck();
|
||||
if (_variableSetNeedsDebugCheck(node.value)) {
|
||||
_emitSourcePosition();
|
||||
}
|
||||
int cpIndex = cp.addStaticField(target);
|
||||
asm.emitStoreStaticTOS(cpIndex);
|
||||
@@ -3562,9 +3577,7 @@ class BytecodeGenerator extends RecursiveVisitor {
|
||||
void visitThrow(Throw node) {
|
||||
_generateNode(node.expression);
|
||||
|
||||
if (options.emitDebuggerStops) {
|
||||
asm.emitDebugCheck();
|
||||
}
|
||||
_emitSourcePosition();
|
||||
asm.emitThrow(0);
|
||||
}
|
||||
|
||||
@@ -3649,8 +3662,8 @@ class BytecodeGenerator extends RecursiveVisitor {
|
||||
|
||||
_generateNode(node.value);
|
||||
|
||||
if (options.emitDebuggerStops && _variableSetNeedsDebugCheck(node.value)) {
|
||||
asm.emitDebugCheck();
|
||||
if (_variableSetNeedsDebugCheck(node.value)) {
|
||||
_emitSourcePosition();
|
||||
}
|
||||
|
||||
if (isLateFinal) {
|
||||
@@ -3902,9 +3915,7 @@ class BytecodeGenerator extends RecursiveVisitor {
|
||||
|
||||
@override
|
||||
void visitFunctionDeclaration(ast.FunctionDeclaration node) {
|
||||
if (options.emitDebuggerStops) {
|
||||
asm.emitDebugCheck();
|
||||
}
|
||||
_emitSourcePosition();
|
||||
_genPushContextIfCaptured(node.variable);
|
||||
_genClosure(node, node.variable.name!, node.function);
|
||||
_genStoreVar(node.variable);
|
||||
@@ -4311,14 +4322,12 @@ class BytecodeGenerator extends RecursiveVisitor {
|
||||
}
|
||||
}
|
||||
|
||||
if (options.emitDebuggerStops &&
|
||||
(initializer == null || _variableSetNeedsDebugCheck(initializer))) {
|
||||
final savedSourcePosition = asm.currentSourcePosition;
|
||||
if (node.fileEqualsOffset != TreeNode.noOffset) {
|
||||
_recordSourcePosition(node.fileEqualsOffset);
|
||||
}
|
||||
asm.emitDebugCheck();
|
||||
asm.currentSourcePosition = savedSourcePosition;
|
||||
// Emit a source position only as part of a DebugCheck instruction or
|
||||
// if there's a store instruction to be emitted for the current PC offset.
|
||||
if ((initializer == null || _variableSetNeedsDebugCheck(initializer)) &&
|
||||
(options.emitDebuggerStops || emitStore)) {
|
||||
_recordSourcePosition(node.fileEqualsOffset);
|
||||
_emitSourcePosition();
|
||||
}
|
||||
|
||||
if (options.emitLocalVarInfo && !asm.isUnreachable && node.name != null) {
|
||||
|
||||
@@ -24,10 +24,15 @@ class SourcePositions extends BytecodeDeclaration {
|
||||
|
||||
SourcePositions();
|
||||
|
||||
// Maps the given PC to the given file offset.
|
||||
void add(int pc, int fileOffset) {
|
||||
assert(pc > _lastPc);
|
||||
assert((fileOffset >= 0) || (fileOffset == syntheticCodeMarker));
|
||||
if (fileOffset != _lastOffset) {
|
||||
if (pc <= _lastPc) {
|
||||
throw ArgumentError(
|
||||
'$pc <= $_lastPc for change in source position $fileOffset != $_lastOffset',
|
||||
'pc');
|
||||
}
|
||||
_positions.add(pc);
|
||||
_positions.add(fileOffset);
|
||||
_lastPc = pc;
|
||||
|
||||
@@ -471,14 +471,22 @@ Future<String> _locationToString(
|
||||
IsolateRef isolateRef,
|
||||
Frame frame,
|
||||
) async {
|
||||
final buffer = StringBuffer();
|
||||
final location = frame.location!;
|
||||
final Script script =
|
||||
final script =
|
||||
await service.getObject(isolateRef.id!, location.script!.id!) as Script;
|
||||
final scriptName = p.basename(script.uri!);
|
||||
buffer.write(scriptName);
|
||||
final tokenPos = location.tokenPos!;
|
||||
final line = script.getLineNumberFromTokenPos(tokenPos);
|
||||
final column = script.getColumnNumberFromTokenPos(tokenPos);
|
||||
return '$scriptName:$line:$column';
|
||||
if (line != null) {
|
||||
buffer.write(':$line');
|
||||
final column = script.getColumnNumberFromTokenPos(tokenPos);
|
||||
if (column != null) {
|
||||
buffer.write(':$column');
|
||||
}
|
||||
}
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
IsolateTest runStepThroughProgramRecordingStops(List<String> recordStops) {
|
||||
|
||||
@@ -35,9 +35,17 @@
|
||||
namespace dart {
|
||||
|
||||
DEFINE_FLAG(bool, dump_kernel_bytecode, false, "Dump kernel bytecode");
|
||||
DEFINE_FLAG(charp,
|
||||
dump_kernel_bytecode_filter,
|
||||
nullptr,
|
||||
"Dump only kernel bytecode of functions with matching names");
|
||||
|
||||
namespace bytecode {
|
||||
|
||||
static bool ShouldPrint(const Function& function) {
|
||||
return function.NamePassesFilter(FLAG_dump_kernel_bytecode_filter);
|
||||
}
|
||||
|
||||
class BytecodeOffsetsMapTraits {
|
||||
public:
|
||||
static const char* Name() { return "BytecodeOffsetsMapTraits"; }
|
||||
@@ -256,7 +264,9 @@ void BytecodeReaderHelper::ReadCode(const Function& function,
|
||||
ReadLocalVariables(bytecode, has_local_variables);
|
||||
|
||||
if (FLAG_dump_kernel_bytecode) {
|
||||
KernelBytecodeDisassembler::Disassemble(function);
|
||||
if (ShouldPrint(function)) {
|
||||
KernelBytecodeDisassembler::Disassemble(function);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialization of fields with null literal is elided from bytecode.
|
||||
@@ -303,7 +313,9 @@ void BytecodeReaderHelper::ReadCode(const Function& function,
|
||||
ReadLocalVariables(closure_bytecode, has_local_variables);
|
||||
|
||||
if (FLAG_dump_kernel_bytecode) {
|
||||
KernelBytecodeDisassembler::Disassemble(closure);
|
||||
if (ShouldPrint(closure)) {
|
||||
KernelBytecodeDisassembler::Disassemble(closure);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,24 +87,12 @@ void BytecodePatcher::RemoveBreakpointAt(uword return_address,
|
||||
});
|
||||
}
|
||||
|
||||
KBCInstr* GetInstructionBefore(const Bytecode& bytecode, uword return_address) {
|
||||
ASSERT(bytecode.ContainsInstructionAt(return_address));
|
||||
ASSERT(return_address != bytecode.PayloadStart());
|
||||
uword prev = bytecode.PayloadStart();
|
||||
uword current = KernelBytecode::Next(prev);
|
||||
while (current < return_address) {
|
||||
prev = current;
|
||||
current = KernelBytecode::Next(prev);
|
||||
}
|
||||
ASSERT_EQUAL(current, return_address);
|
||||
return reinterpret_cast<KBCInstr*>(prev);
|
||||
}
|
||||
|
||||
uint32_t BytecodePatcher::AddBreakpointAtWithMutatorsStopped(
|
||||
Thread* thread,
|
||||
uword return_address,
|
||||
const Bytecode& bytecode) {
|
||||
auto* const instr = GetInstructionBefore(bytecode, return_address);
|
||||
auto* const instr = reinterpret_cast<KBCInstr*>(
|
||||
bytecode.GetInstructionBefore(return_address));
|
||||
uint32_t old_opcode = *instr;
|
||||
*instr = KernelBytecode::BreakpointOpcode(instr);
|
||||
return old_opcode;
|
||||
@@ -115,7 +103,8 @@ void BytecodePatcher::RemoveBreakpointAtWithMutatorsStopped(
|
||||
uword return_address,
|
||||
const Bytecode& bytecode,
|
||||
uint32_t opcode) {
|
||||
auto* const instr = GetInstructionBefore(bytecode, return_address);
|
||||
auto* const instr = reinterpret_cast<KBCInstr*>(
|
||||
bytecode.GetInstructionBefore(return_address));
|
||||
// Must be previously enabled and not yet removed.
|
||||
ASSERT(*instr == KernelBytecode::BreakpointOpcode(
|
||||
static_cast<KernelBytecode::Opcode>(opcode)));
|
||||
|
||||
@@ -47,38 +47,7 @@ static bool PassesFilter(const char* filter,
|
||||
}
|
||||
#endif
|
||||
|
||||
char* save_ptr; // Needed for strtok_r.
|
||||
const char* scrubbed_name = function.QualifiedScrubbedNameCString();
|
||||
const char* function_name = function.ToFullyQualifiedCString();
|
||||
intptr_t function_name_len = strlen(function_name);
|
||||
|
||||
intptr_t len = strlen(filter) + 1; // Length with \0.
|
||||
char* filter_buffer = new char[len];
|
||||
strncpy(filter_buffer, filter, len); // strtok modifies arg 1.
|
||||
char* token = strtok_r(filter_buffer, ",", &save_ptr);
|
||||
bool found = false;
|
||||
while (token != nullptr) {
|
||||
if ((strstr(function_name, token) != nullptr) ||
|
||||
(strstr(scrubbed_name, token) != nullptr)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
const intptr_t token_len = strlen(token);
|
||||
if (token[token_len - 1] == '%') {
|
||||
if (function_name_len > token_len) {
|
||||
const char* suffix =
|
||||
function_name + (function_name_len - token_len + 1);
|
||||
if (strncmp(suffix, token, token_len - 1) == 0) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
token = strtok_r(nullptr, ",", &save_ptr);
|
||||
}
|
||||
delete[] filter_buffer;
|
||||
|
||||
return found;
|
||||
return function.NamePassesFilter(filter);
|
||||
}
|
||||
|
||||
bool PrintFilter::ShouldPrint(const Function& function,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -427,71 +427,22 @@ class KernelBytecode {
|
||||
return DecodeOpcode(instr) == KernelBytecode::kSetFrame;
|
||||
}
|
||||
|
||||
DART_FORCE_INLINE static bool IsDebugCheckOpcode(const KBCInstr* instr) {
|
||||
return DecodeOpcode(instr) == KernelBytecode::kDebugCheck;
|
||||
}
|
||||
|
||||
// The interpreter, the bytecode generator, the bytecode compiler, and this
|
||||
// function must agree on this list of opcodes.
|
||||
// For each instruction with listed opcode:
|
||||
// - The interpreter checks for a debug break.
|
||||
// - The bytecode generator emits a source position.
|
||||
// - The bytecode compiler may emit a DebugStepCheck call.
|
||||
DART_FORCE_INLINE static bool IsDebugCheckedOpcode(const KBCInstr* instr) {
|
||||
return IsDebugCheckedOpcode(DecodeOpcode(instr));
|
||||
}
|
||||
|
||||
DART_FORCE_INLINE static bool IsDebugCheckedOpcode(Opcode op) {
|
||||
switch (op) {
|
||||
case KernelBytecode::kDebugCheck:
|
||||
DART_FORCE_INLINE static bool IsDirectCallOpcode(const KBCInstr* instr) {
|
||||
switch (DecodeOpcode(instr)) {
|
||||
case KernelBytecode::kDirectCall:
|
||||
case KernelBytecode::kDirectCall_Wide:
|
||||
case KernelBytecode::kUncheckedDirectCall:
|
||||
case KernelBytecode::kUncheckedDirectCall_Wide:
|
||||
case KernelBytecode::kInterfaceCall:
|
||||
case KernelBytecode::kInterfaceCall_Wide:
|
||||
case KernelBytecode::kInstantiatedInterfaceCall:
|
||||
case KernelBytecode::kInstantiatedInterfaceCall_Wide:
|
||||
case KernelBytecode::kUncheckedClosureCall:
|
||||
case KernelBytecode::kUncheckedClosureCall_Wide:
|
||||
case KernelBytecode::kUncheckedInterfaceCall:
|
||||
case KernelBytecode::kUncheckedInterfaceCall_Wide:
|
||||
case KernelBytecode::kDynamicCall:
|
||||
case KernelBytecode::kDynamicCall_Wide:
|
||||
case KernelBytecode::kReturnTOS:
|
||||
case KernelBytecode::kEqualsNull:
|
||||
case KernelBytecode::kNegateInt:
|
||||
case KernelBytecode::kNegateDouble:
|
||||
case KernelBytecode::kAddInt:
|
||||
case KernelBytecode::kSubInt:
|
||||
case KernelBytecode::kMulInt:
|
||||
case KernelBytecode::kTruncDivInt:
|
||||
case KernelBytecode::kModInt:
|
||||
case KernelBytecode::kBitAndInt:
|
||||
case KernelBytecode::kBitOrInt:
|
||||
case KernelBytecode::kBitXorInt:
|
||||
case KernelBytecode::kShlInt:
|
||||
case KernelBytecode::kShrInt:
|
||||
case KernelBytecode::kCompareIntEq:
|
||||
case KernelBytecode::kCompareIntGt:
|
||||
case KernelBytecode::kCompareIntLt:
|
||||
case KernelBytecode::kCompareIntGe:
|
||||
case KernelBytecode::kCompareIntLe:
|
||||
case KernelBytecode::kAddDouble:
|
||||
case KernelBytecode::kSubDouble:
|
||||
case KernelBytecode::kMulDouble:
|
||||
case KernelBytecode::kDivDouble:
|
||||
case KernelBytecode::kCompareDoubleEq:
|
||||
case KernelBytecode::kCompareDoubleGt:
|
||||
case KernelBytecode::kCompareDoubleLt:
|
||||
case KernelBytecode::kCompareDoubleGe:
|
||||
case KernelBytecode::kCompareDoubleLe:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
DART_FORCE_INLINE static bool IsReturnOpcode(const KBCInstr* instr) {
|
||||
return DecodeOpcode(instr) == KernelBytecode::kReturnTOS;
|
||||
}
|
||||
|
||||
DART_FORCE_INLINE static uint8_t DecodeArgc(const KBCInstr* ret_addr) {
|
||||
// All call instructions have DF encoding, with argc being the last byte
|
||||
// regardless of whether the wide variant is used or not.
|
||||
|
||||
+126
-44
@@ -535,24 +535,24 @@ void ActivationFrame::GetPcDescriptors() {
|
||||
// if not IsInterpreted(), also compute deopt_id_.
|
||||
TokenPosition ActivationFrame::TokenPos() {
|
||||
if (!token_pos_initialized_) {
|
||||
token_pos_initialized_ = true;
|
||||
token_pos_ = TokenPosition::kNoSource;
|
||||
if (IsInterpreted()) {
|
||||
token_pos_ = bytecode().GetTokenIndexOfPC(pc_);
|
||||
try_index_ = bytecode().GetTryIndexAtPc(pc_);
|
||||
return token_pos_;
|
||||
}
|
||||
token_pos_ = TokenPosition::kNoSource;
|
||||
GetPcDescriptors();
|
||||
PcDescriptors::Iterator iter(pc_desc_, UntaggedPcDescriptors::kAnyKind);
|
||||
const uword pc_offset = pc_ - code().PayloadStart();
|
||||
while (iter.MoveNext()) {
|
||||
if (iter.PcOffset() == pc_offset) {
|
||||
try_index_ = iter.TryIndex();
|
||||
token_pos_ = iter.TokenPos();
|
||||
deopt_id_ = iter.DeoptId();
|
||||
break;
|
||||
} else {
|
||||
GetPcDescriptors();
|
||||
PcDescriptors::Iterator iter(pc_desc_, UntaggedPcDescriptors::kAnyKind);
|
||||
const uword pc_offset = pc_ - code().PayloadStart();
|
||||
while (iter.MoveNext()) {
|
||||
if (iter.PcOffset() == pc_offset) {
|
||||
try_index_ = iter.TryIndex();
|
||||
token_pos_ = iter.TokenPos();
|
||||
deopt_id_ = iter.DeoptId();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
token_pos_initialized_ = true;
|
||||
}
|
||||
return token_pos_;
|
||||
}
|
||||
@@ -2257,21 +2257,18 @@ static TokenPosition ResolveBreakpointPos(const Function& func,
|
||||
|
||||
if (func.HasBytecode()) {
|
||||
#if defined(DART_DYNAMIC_MODULES)
|
||||
// Only compiled code has synthetic token positions.
|
||||
ASSERT(!requested_token_pos.IsSynthetic());
|
||||
bytecode::BytecodeSourcePositionsIterator iter(zone, bytecode);
|
||||
while (iter.MoveNext()) {
|
||||
const TokenPosition& pos = iter.TokenPos();
|
||||
if (pos.IsSynthetic() && pos == requested_token_pos) {
|
||||
// if there's a safepoint for a synthetic function start and the start
|
||||
// was requested, we're done.
|
||||
return pos;
|
||||
}
|
||||
if (!pos.IsWithin(requested_token_pos, last_token_pos)) {
|
||||
// Token is not in the target range.
|
||||
continue;
|
||||
}
|
||||
TokenPosition next_closest_token_position = TokenPosition::kMaxSource;
|
||||
if (requested_column >= 0) {
|
||||
// Find next closest safepoint
|
||||
// Find next closest emitted source position.
|
||||
bytecode::BytecodeSourcePositionsIterator iter2(zone, bytecode);
|
||||
while (iter2.MoveNext()) {
|
||||
const TokenPosition& next = iter2.TokenPos();
|
||||
@@ -2352,11 +2349,6 @@ static TokenPosition ResolveBreakpointPos(const Function& func,
|
||||
// Token is not on same line as best fit.
|
||||
continue;
|
||||
}
|
||||
// To match the PCDescriptors version, adjust the offset to be
|
||||
// the "return address" (e.g., the next instuction).
|
||||
auto* const pc = reinterpret_cast<const KBCInstr*>(
|
||||
pc_offset + bytecode.PayloadStart());
|
||||
pc_offset += KernelBytecode::kInstructionSize[*pc];
|
||||
// Prefer the lowest pc offset.
|
||||
if (pc_offset < lowest_pc_offset) {
|
||||
lowest_pc_offset = pc_offset;
|
||||
@@ -2457,12 +2449,11 @@ void GroupDebugger::MakeCodeBreakpointAtUnsafe(Thread* thread,
|
||||
bytecode::BytecodeSourcePositionsIterator iter(zone, bytecode);
|
||||
while (iter.MoveNext()) {
|
||||
if (iter.TokenPos() == loc->token_pos_) {
|
||||
// To match the PCDescriptors version, adjust the offset to be
|
||||
// the "return address" (e.g., the next instuction).
|
||||
uword pc_offset = iter.PcOffset();
|
||||
auto* const pc = reinterpret_cast<const KBCInstr*>(
|
||||
pc_offset + bytecode.PayloadStart());
|
||||
pc_offset += KernelBytecode::kInstructionSize[*pc];
|
||||
// Breakpoints are set and located using the address of the instruction
|
||||
// following the breakpoint instruction, since frames contain
|
||||
// return addresses.
|
||||
const uword pc_offset =
|
||||
KernelBytecode::Next(start + iter.PcOffset()) - start;
|
||||
if (pc_offset < lowest_pc_offset) {
|
||||
lowest_pc_offset = pc_offset;
|
||||
}
|
||||
@@ -3844,27 +3835,107 @@ void Debugger::SignalPausedEvent(ActivationFrame* top_frame, Breakpoint* bpt) {
|
||||
}
|
||||
|
||||
static bool IsAtAsyncJump(ActivationFrame* top_frame) {
|
||||
Zone* zone = Thread::Current()->zone();
|
||||
Thread* const thread = Thread::Current();
|
||||
Zone* const zone = thread->zone();
|
||||
if (!top_frame->function().IsAsyncFunction() &&
|
||||
!top_frame->function().IsAsyncGenerator()) {
|
||||
return false;
|
||||
}
|
||||
const auto& pc_descriptors =
|
||||
PcDescriptors::Handle(zone, top_frame->code().pc_descriptors());
|
||||
if (pc_descriptors.IsNull()) {
|
||||
return false;
|
||||
}
|
||||
const TokenPosition looking_for = top_frame->TokenPos();
|
||||
PcDescriptors::Iterator it(pc_descriptors, UntaggedPcDescriptors::kOther);
|
||||
while (it.MoveNext()) {
|
||||
if (it.TokenPos() == looking_for &&
|
||||
it.YieldIndex() != UntaggedPcDescriptors::kInvalidYieldIndex) {
|
||||
return true;
|
||||
if (top_frame->IsInterpreted()) {
|
||||
#if defined(DART_DYNAMIC_MODULES)
|
||||
const auto& bytecode = top_frame->bytecode();
|
||||
const uword prev = bytecode.GetInstructionBefore(top_frame->pc());
|
||||
if (prev == 0) {
|
||||
// Async awaiter frames have an PC offset of 0 or 1.
|
||||
ASSERT(top_frame->pc() == bytecode.PayloadStart() ||
|
||||
top_frame->pc() == bytecode.PayloadStart() +
|
||||
StackTraceUtils::kFutureListenerPcOffset);
|
||||
return false;
|
||||
}
|
||||
auto* const instr = reinterpret_cast<const KBCInstr*>(prev);
|
||||
// Async jumps in bytecode are implemented via direct calls to the
|
||||
// appropriate Dart method.
|
||||
if (!KernelBytecode::IsDirectCallOpcode(instr)) {
|
||||
return false;
|
||||
}
|
||||
const auto& object_pool = ObjectPool::Handle(zone, bytecode.object_pool());
|
||||
auto const index = KernelBytecode::DecodeD(instr);
|
||||
const auto& obj = Object::Handle(zone, object_pool.ObjectAt(index));
|
||||
if (obj.IsNull() || !obj.IsFunction()) {
|
||||
return false;
|
||||
}
|
||||
const auto& target = Function::Cast(obj);
|
||||
auto* const object_store = thread->isolate_group()->object_store();
|
||||
return target.ptr() == object_store->suspend_state_await() ||
|
||||
target.ptr() ==
|
||||
object_store->suspend_state_await_with_type_check() ||
|
||||
target.ptr() == object_store->suspend_state_yield_async_star() ||
|
||||
target.ptr() ==
|
||||
object_store->suspend_state_suspend_sync_star_at_start();
|
||||
#else
|
||||
UNREACHABLE();
|
||||
#endif
|
||||
} else {
|
||||
const TokenPosition looking_for = top_frame->TokenPos();
|
||||
const auto& pc_descriptors =
|
||||
PcDescriptors::Handle(zone, top_frame->code().pc_descriptors());
|
||||
if (!pc_descriptors.IsNull()) {
|
||||
PcDescriptors::Iterator it(pc_descriptors, UntaggedPcDescriptors::kOther);
|
||||
while (it.MoveNext()) {
|
||||
if (it.TokenPos() == looking_for &&
|
||||
it.YieldIndex() != UntaggedPcDescriptors::kInvalidYieldIndex) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool IsAtBytecodeAsyncReturn(ActivationFrame* top_frame) {
|
||||
#if defined(DART_DYNAMIC_MODULES)
|
||||
if (!top_frame->IsInterpreted()) return false;
|
||||
|
||||
Thread* const thread = Thread::Current();
|
||||
Zone* const zone = thread->zone();
|
||||
const auto& bytecode = top_frame->bytecode();
|
||||
uword prev = bytecode.GetInstructionBefore(top_frame->pc());
|
||||
if (prev == 0) {
|
||||
// Async awaiter frames have an PC offset of 0 or 1.
|
||||
ASSERT(top_frame->pc() == bytecode.PayloadStart() ||
|
||||
top_frame->pc() == bytecode.PayloadStart() +
|
||||
StackTraceUtils::kFutureListenerPcOffset);
|
||||
return false;
|
||||
}
|
||||
auto* instr = reinterpret_cast<const KBCInstr*>(prev);
|
||||
// Async returns in bytecode are implemented via direct calls to
|
||||
// the appropriate Dart async return method followed by a return
|
||||
// instruction.
|
||||
if (KernelBytecode::IsReturnOpcode(instr)) {
|
||||
prev = bytecode.GetInstructionBefore(prev);
|
||||
ASSERT(prev != 0);
|
||||
instr = reinterpret_cast<const KBCInstr*>(prev);
|
||||
}
|
||||
if (!KernelBytecode::IsDirectCallOpcode(instr)) {
|
||||
return false;
|
||||
}
|
||||
const auto& object_pool = ObjectPool::Handle(zone, bytecode.object_pool());
|
||||
auto const index = KernelBytecode::DecodeD(instr);
|
||||
const auto& obj = Object::Handle(zone, object_pool.ObjectAt(index));
|
||||
if (obj.IsNull() || !obj.IsFunction()) {
|
||||
return false;
|
||||
}
|
||||
const auto& target = Function::Cast(obj);
|
||||
auto* const object_store = thread->isolate_group()->object_store();
|
||||
return target.ptr() == object_store->suspend_state_return_async() ||
|
||||
target.ptr() ==
|
||||
object_store->suspend_state_return_async_not_future() ||
|
||||
target.ptr() == object_store->suspend_state_return_async_star();
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(DART_DYNAMIC_MODULES)
|
||||
static ActivationFrame::Relation CompareTopDartFrameTo(uword other_fp,
|
||||
bool is_interpreted) {
|
||||
@@ -3876,7 +3947,15 @@ static ActivationFrame::Relation CompareTopDartFrameTo(uword other_fp,
|
||||
if (frame->IsDartFrame() && (frame->is_interpreted() == is_interpreted)) {
|
||||
// The current frame's FP can be directly compared to the provided FP to
|
||||
// provide an answer, since they're using the same stack.
|
||||
return ActivationFrame::CompareTo(is_interpreted, frame->fp(), other_fp);
|
||||
//
|
||||
// Since this function is only called if the top Dart frame is interpreted
|
||||
// but the stepping frame is not or vice versa, the current frame is not
|
||||
// the top Dart frame, so a result of kSelf means the top Dart frame
|
||||
// is a callee.
|
||||
return ActivationFrame::CompareTo(is_interpreted, frame->fp(),
|
||||
other_fp) == ActivationFrame::kCaller
|
||||
? ActivationFrame::kCaller
|
||||
: ActivationFrame::kCallee;
|
||||
}
|
||||
}
|
||||
// If there were no frames of the same type on the stack, this must have been
|
||||
@@ -3949,7 +4028,10 @@ ErrorPtr Debugger::PauseStepping() {
|
||||
// with regular function wrt the first stop in the function prologue.
|
||||
if ((frame->function().IsAsyncFunction() ||
|
||||
frame->function().IsAsyncGenerator()) &&
|
||||
frame->GetSuspendStateVar() == Object::null()) {
|
||||
frame->GetSuspendStateVar() == Object::null() &&
|
||||
// The bytecode generator sets the suspend state var to null prior
|
||||
// to returning.
|
||||
!IsAtBytecodeAsyncReturn(frame)) {
|
||||
return Error::null();
|
||||
}
|
||||
|
||||
|
||||
+102
-79
@@ -848,8 +848,30 @@ DART_FORCE_INLINE bool Interpreter::InstanceCall(Thread* thread,
|
||||
WriteInstructionToTrace(pc); \
|
||||
} \
|
||||
icount_++;
|
||||
#define BREAKPOINT_TRACE_ORIGINAL_INSTRUCTION \
|
||||
do { \
|
||||
if (IsTracingExecution()) { \
|
||||
/* Use the original instruction count. */ \
|
||||
auto const icount = icount_ - 1; \
|
||||
auto const instr_size = KernelBytecode::kInstructionSize[op]; \
|
||||
THR_Print("%" Pu64 " ", icount); \
|
||||
THR_Print("dispatching to original instruction\n"); \
|
||||
THR_Print("%" Pu64 " ", icount); \
|
||||
if (FLAG_support_disassembler) { \
|
||||
KBCInstr temp[6]; \
|
||||
*temp = op; \
|
||||
memmove(temp + 1, pc + 1, instr_size - 1); \
|
||||
KernelBytecodeDisassembler::Disassemble( \
|
||||
reinterpret_cast<uword>(temp), \
|
||||
reinterpret_cast<uword>(temp + instr_size)); \
|
||||
} else { \
|
||||
THR_Print("Disassembler not supported in this mode.\n"); \
|
||||
} \
|
||||
} \
|
||||
} while (0)
|
||||
#else
|
||||
#define TRACE_INSTRUCTION
|
||||
#define BREAKPOINT_TRACE_ORIGINAL_INSTRUCTION
|
||||
#endif // defined(DEBUG)
|
||||
|
||||
#if !defined(PRODUCT)
|
||||
@@ -865,13 +887,27 @@ DART_FORCE_INLINE bool Interpreter::InstanceCall(Thread* thread,
|
||||
|
||||
// Decode opcode and A part of the given value and dispatch to the
|
||||
// corresponding bytecode handler.
|
||||
#ifdef DART_HAS_COMPUTED_GOTO
|
||||
#if defined(DART_HAS_COMPUTED_GOTO)
|
||||
#define DISPATCH_OP(val) \
|
||||
do { \
|
||||
op = (val); \
|
||||
TRACE_INSTRUCTION \
|
||||
goto* dispatch[ADJUST_FOR_SINGLE_STEPPING(op)]; \
|
||||
} while (0)
|
||||
#if !defined(PRODUCT)
|
||||
// The breakpoint should dispatch to the single step handler, if any, in case
|
||||
// the breakpoint was set on a call that should then be stepped into or over
|
||||
// appropriately. Note that op has already been set to the original opcode
|
||||
// that had been replaced with the breakpoint opcode during patching.
|
||||
#define BREAKPOINT_DISPATCH \
|
||||
do { \
|
||||
BREAKPOINT_TRACE_ORIGINAL_INSTRUCTION; \
|
||||
goto* dispatch[ADJUST_FOR_SINGLE_STEPPING(op)]; \
|
||||
} while (0)
|
||||
// The dispatch from a single step check back to the original instruction
|
||||
// implementation should ignore single_stepping_offset.
|
||||
#define DISPATCH_ORIGINAL_OPCODE goto* dispatch[op]
|
||||
#endif // !defined(PRODUCT)
|
||||
#else
|
||||
#define DISPATCH_OP(val) \
|
||||
do { \
|
||||
@@ -879,6 +915,20 @@ DART_FORCE_INLINE bool Interpreter::InstanceCall(Thread* thread,
|
||||
TRACE_INSTRUCTION \
|
||||
goto SwitchDispatch; \
|
||||
} while (0)
|
||||
#if !defined(PRODUCT)
|
||||
// The breakpoint should dispatch to the single step handler, if any, in case
|
||||
// the breakpoint was set on a call that should then be stepped into or over
|
||||
// appropriately. Note that op has already been set to the original opcode
|
||||
// that had been replaced with the breakpoint opcode during patching.
|
||||
#define BREAKPOINT_DISPATCH \
|
||||
do { \
|
||||
BREAKPOINT_TRACE_ORIGINAL_INSTRUCTION; \
|
||||
goto SwitchDispatch; \
|
||||
} while (0)
|
||||
// The dispatch from a single step check back to the original instruction
|
||||
// implementation should ignore single_stepping_offset.
|
||||
#define DISPATCH_ORIGINAL_OPCODE goto SwitchDispatchNoSingleStep
|
||||
#endif // !defined(PRODUCT)
|
||||
#endif // defined(DART_HAS_COMPUTED_GOTO)
|
||||
|
||||
// Fetch next operation from PC and dispatch.
|
||||
@@ -1800,6 +1850,16 @@ ObjectPtr Interpreter::Resume(Thread* thread,
|
||||
pp_ = bytecode->untag()->object_pool();
|
||||
fp_ = FP;
|
||||
|
||||
#if !defined(PRODUCT)
|
||||
if (auto* const isolate = thread->isolate()) {
|
||||
if (isolate->has_resumption_breakpoints()) {
|
||||
Exit(thread, FP, SP + 1, pc_);
|
||||
InvokeRuntime(thread, this, DRT_ResumptionBreakpointHandler,
|
||||
NativeArguments(thread, 0, nullptr, nullptr));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return Run(thread, SP, rethrow_exception);
|
||||
}
|
||||
|
||||
@@ -1849,7 +1909,7 @@ ObjectPtr Interpreter::Run(Thread* thread,
|
||||
goto RethrowException;
|
||||
}
|
||||
|
||||
#ifdef DART_HAS_COMPUTED_GOTO
|
||||
#if defined(DART_HAS_COMPUTED_GOTO)
|
||||
static const void* dispatch[] = {
|
||||
#define TARGET(name, fmt, kind, fmta, fmtb, fmtc) &&bc##name,
|
||||
KERNEL_BYTECODES_LIST(TARGET)
|
||||
@@ -1880,7 +1940,19 @@ SwitchDispatch:
|
||||
default:
|
||||
FATAL1("Undefined opcode: %d\n", op);
|
||||
}
|
||||
#endif
|
||||
#if !defined(PRODUCT)
|
||||
SwitchDispatchNoSingleStep:
|
||||
switch (op & 0xFF) {
|
||||
#define TARGET(name, fmt, kind, fmta, fmtb, fmtc) \
|
||||
case KernelBytecode::k##name: \
|
||||
goto bc##name;
|
||||
KERNEL_BYTECODES_LIST(TARGET)
|
||||
#undef TARGET
|
||||
default:
|
||||
FATAL1("Undefined opcode: %d\n", op);
|
||||
}
|
||||
#endif // !defined(PRODUCT)
|
||||
#endif // defined(DART_HAS_COMPUTED_GOTO)
|
||||
|
||||
// KernelBytecode handlers (see constants_kbc.h for bytecode descriptions).
|
||||
{
|
||||
@@ -4074,80 +4146,6 @@ SwitchDispatch:
|
||||
DISPATCH();
|
||||
}
|
||||
|
||||
#if !defined(PRODUCT)
|
||||
#if defined(DEBUG)
|
||||
#define BREAKPOINT_TRACE_ORIGINAL_INSTRUCTION \
|
||||
do { \
|
||||
if (IsTracingExecution()) { \
|
||||
/* Use the original instruction count. */ \
|
||||
auto const icount = icount_ - 1; \
|
||||
auto const instr_size = KernelBytecode::kInstructionSize[op]; \
|
||||
THR_Print("%" Pu64 " ", icount); \
|
||||
THR_Print("dispatching to original instruction\n"); \
|
||||
THR_Print("%" Pu64 " ", icount); \
|
||||
if (FLAG_support_disassembler) { \
|
||||
KBCInstr temp[6]; \
|
||||
*temp = op; \
|
||||
memmove(temp + 1, pc + 1, instr_size - 1); \
|
||||
KernelBytecodeDisassembler::Disassemble( \
|
||||
reinterpret_cast<uword>(temp), \
|
||||
reinterpret_cast<uword>(temp + instr_size)); \
|
||||
} else { \
|
||||
THR_Print("Disassembler not supported in this mode.\n"); \
|
||||
} \
|
||||
} \
|
||||
} while (0)
|
||||
#else
|
||||
#define BREAKPOINT_TRACE_ORIGINAL_INSTRUCTION
|
||||
#endif
|
||||
|
||||
// The dispatch from a single step check or a breakpoint back to the original
|
||||
// patched instruction should ignore single_stepping_offset.
|
||||
#if defined(DART_HAS_COMPUTED_GOTO)
|
||||
#define DISPATCH_ORIGINAL_OPCODE goto* dispatch[op]
|
||||
#else
|
||||
#define DISPATCH_ORIGINAL_OPCODE goto SwitchDispatchNoSingleStep
|
||||
{
|
||||
SwitchDispatchNoSingleStep:
|
||||
switch (op & 0xFF) {
|
||||
#define TARGET(name, fmt, kind, fmta, fmtb, fmtc) \
|
||||
case KernelBytecode::k##name: \
|
||||
goto bc##name;
|
||||
KERNEL_BYTECODES_LIST(TARGET)
|
||||
#undef TARGET
|
||||
default:
|
||||
FATAL1("Undefined opcode: %d\n", op);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#define DEFINE_BREAKPOINT(Format) \
|
||||
{ \
|
||||
BYTECODE(VMInternal_Breakpoint_##Format, Format) \
|
||||
SP[1] = 0; /* Smi containing the original opcode. */ \
|
||||
Exit(thread, FP, SP + 2, pc); \
|
||||
INVOKE_RUNTIME(DRT_BreakpointRuntimeHandler, \
|
||||
NativeArguments(thread, 0, nullptr, SP + 1)); \
|
||||
uint32_t old_op = RawSmiValue(Smi::RawCast(SP[1])); \
|
||||
ASSERT_EQUAL(KernelBytecode::BreakpointOpcode( \
|
||||
static_cast<KernelBytecode::Opcode>(old_op)), \
|
||||
op); \
|
||||
op = old_op; \
|
||||
/* The pc is moved to the next instruction during the dispatch to */ \
|
||||
/* the original instruction's implementation, so re-adjust it to */ \
|
||||
/* before the breakpoint/original instruction prior to dispatch. */ \
|
||||
pc -= KernelBytecode::kInstructionSize[op]; \
|
||||
BREAKPOINT_TRACE_ORIGINAL_INSTRUCTION; \
|
||||
DISPATCH_ORIGINAL_OPCODE; \
|
||||
}
|
||||
DEFINE_BREAKPOINT(0) // size 1
|
||||
DEFINE_BREAKPOINT(D) // size 2 and 5
|
||||
DEFINE_BREAKPOINT(A_E) // size 3 and 6
|
||||
DEFINE_BREAKPOINT(A_B_C) // size 4
|
||||
#undef DEFINE_BREAKPOINT
|
||||
#undef BREAKPOINT_TRACE_ORIGINAL_INSTRUCTION
|
||||
#endif // !defined(PRODUCT)
|
||||
|
||||
{
|
||||
TailCallSP1:
|
||||
FunctionPtr function = Function::RawCast(SP[1]);
|
||||
@@ -4329,6 +4327,30 @@ SwitchDispatch:
|
||||
}
|
||||
|
||||
#if !defined(PRODUCT)
|
||||
#define DEFINE_BREAKPOINT(Format) \
|
||||
{ \
|
||||
BYTECODE(VMInternal_Breakpoint_##Format, Format) \
|
||||
SP[1] = 0; /* Smi containing the original opcode. */ \
|
||||
Exit(thread, FP, SP + 2, pc); \
|
||||
INVOKE_RUNTIME(DRT_BreakpointRuntimeHandler, \
|
||||
NativeArguments(thread, 0, nullptr, SP + 1)); \
|
||||
uint32_t old_op = RawSmiValue(Smi::RawCast(SP[1])); \
|
||||
ASSERT_EQUAL(KernelBytecode::BreakpointOpcode( \
|
||||
static_cast<KernelBytecode::Opcode>(old_op)), \
|
||||
op); \
|
||||
op = old_op; \
|
||||
/* The pc is moved to the next instruction during the dispatch to */ \
|
||||
/* the original instruction's implementation, so re-adjust it to */ \
|
||||
/* before the breakpoint/original instruction prior to dispatch. */ \
|
||||
pc -= KernelBytecode::kInstructionSize[op]; \
|
||||
BREAKPOINT_DISPATCH; \
|
||||
}
|
||||
DEFINE_BREAKPOINT(0) // size 1
|
||||
DEFINE_BREAKPOINT(D) // size 2 and 5
|
||||
DEFINE_BREAKPOINT(A_E) // size 3 and 6
|
||||
DEFINE_BREAKPOINT(A_B_C) // size 4
|
||||
#undef DEFINE_BREAKPOINT
|
||||
|
||||
{
|
||||
#define SINGLE_STEP_HANDLER_ENTRY(Name, __, ___, ____, _____, ______) \
|
||||
bc##Name##_SingleStep:
|
||||
@@ -4343,8 +4365,9 @@ SwitchDispatch:
|
||||
}
|
||||
#endif
|
||||
|
||||
/* The frame should include the PC of the to-be-executed instruction. */
|
||||
Exit(thread, FP, SP + 1, pc);
|
||||
// The debugger expects return addresses in the frames when retrieving
|
||||
// source positions, so use the next instruction's address.
|
||||
Exit(thread, FP, SP + 1, KernelBytecode::Next(pc));
|
||||
INVOKE_RUNTIME(DRT_SingleStepHandler,
|
||||
NativeArguments(thread, 0, nullptr, nullptr));
|
||||
DISPATCH_ORIGINAL_OPCODE;
|
||||
|
||||
+51
-29
@@ -11657,6 +11657,43 @@ void Function::PrintName(const NameFormattingParams& params,
|
||||
FunctionPrintNameHelper(fun, params, printer);
|
||||
}
|
||||
|
||||
bool Function::NamePassesFilter(const char* name_filter) const {
|
||||
if (name_filter == nullptr) return true;
|
||||
|
||||
char* save_ptr; // Needed for strtok_r.
|
||||
const char* scrubbed_name = QualifiedScrubbedNameCString();
|
||||
const char* function_name = ToFullyQualifiedCString();
|
||||
intptr_t function_name_len = strlen(function_name);
|
||||
|
||||
intptr_t len = strlen(name_filter) + 1; // Length with \0.
|
||||
char* filter_buffer = new char[len];
|
||||
strncpy(filter_buffer, name_filter, len); // strtok modifies arg 1.
|
||||
char* token = strtok_r(filter_buffer, ",", &save_ptr);
|
||||
bool found = false;
|
||||
while (token != nullptr) {
|
||||
if ((strstr(function_name, token) != nullptr) ||
|
||||
(strstr(scrubbed_name, token) != nullptr)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
const intptr_t token_len = strlen(token);
|
||||
if (token[token_len - 1] == '%') {
|
||||
if (function_name_len > token_len) {
|
||||
const char* suffix =
|
||||
function_name + (function_name_len - token_len + 1);
|
||||
if (strncmp(suffix, token, token_len - 1) == 0) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
token = strtok_r(nullptr, ",", &save_ptr);
|
||||
}
|
||||
delete[] filter_buffer;
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
StringPtr Function::GetSource() const {
|
||||
if (IsImplicitConstructor() || is_synthetic()) {
|
||||
// We may need to handle more cases when the restrictions on mixins are
|
||||
@@ -19312,40 +19349,25 @@ intptr_t Bytecode::GetTryIndexAtPc(uword return_address) const {
|
||||
#endif
|
||||
}
|
||||
|
||||
uword Bytecode::GetFirstDebugCheckOpcodePc() const {
|
||||
uword Bytecode::GetInstructionBefore(uword return_address) const {
|
||||
#if defined(DART_DYNAMIC_MODULES)
|
||||
uword pc = PayloadStart();
|
||||
const uword end_pc = pc + Size();
|
||||
while (pc < end_pc) {
|
||||
if (KernelBytecode::IsDebugCheckOpcode(
|
||||
reinterpret_cast<const KBCInstr*>(pc))) {
|
||||
return pc;
|
||||
}
|
||||
pc = KernelBytecode::Next(pc);
|
||||
const uword start = PayloadStart();
|
||||
// return_address could be the end of the bytecode instructions
|
||||
// if the last instruction is Throw.
|
||||
if (return_address <= start || return_address > start + Size()) {
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
uword prev = start;
|
||||
uword current = KernelBytecode::Next(start);
|
||||
while (current < return_address) {
|
||||
prev = current;
|
||||
current = KernelBytecode::Next(prev);
|
||||
}
|
||||
// Any valid return address should be on an instruction boundary.
|
||||
return current == return_address ? prev : 0;
|
||||
#else
|
||||
UNREACHABLE();
|
||||
#endif
|
||||
}
|
||||
|
||||
uword Bytecode::GetDebugCheckedOpcodeReturnAddress(uword from_offset,
|
||||
uword to_offset) const {
|
||||
#if defined(DART_DYNAMIC_MODULES)
|
||||
uword pc = PayloadStart() + from_offset;
|
||||
const uword end_pc = pc + (to_offset - from_offset);
|
||||
while (pc < end_pc) {
|
||||
uword next_pc = KernelBytecode::Next(pc);
|
||||
if (KernelBytecode::IsDebugCheckedOpcode(
|
||||
reinterpret_cast<const KBCInstr*>(pc))) {
|
||||
// Return the pc after the opcode, i.e. its 'return address'.
|
||||
return next_pc;
|
||||
}
|
||||
pc = next_pc;
|
||||
}
|
||||
return 0;
|
||||
#else
|
||||
UNREACHABLE();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
+7
-9
@@ -3049,6 +3049,10 @@ class Function : public Object {
|
||||
|
||||
virtual StringPtr DictionaryName() const { return name(); }
|
||||
|
||||
// Returns whether either the fully qualified name or qualified scrubbed name
|
||||
// matches the given name filter, or true if the filter is nullptr.
|
||||
bool NamePassesFilter(const char* name_filter) const;
|
||||
|
||||
StringPtr GetSource() const;
|
||||
|
||||
// Set the "C signature" for an FFI trampoline.
|
||||
@@ -7564,14 +7568,9 @@ class Bytecode : public Object {
|
||||
TokenPosition GetTokenIndexOfPC(uword return_address) const;
|
||||
intptr_t GetTryIndexAtPc(uword return_address) const;
|
||||
|
||||
// Return the pc of the first 'DebugCheck' opcode of the bytecode.
|
||||
// Return 0 if none is found.
|
||||
uword GetFirstDebugCheckOpcodePc() const;
|
||||
|
||||
// Return the pc after the first 'debug checked' opcode in the range.
|
||||
// Return 0 if none is found.
|
||||
uword GetDebugCheckedOpcodeReturnAddress(uword from_offset,
|
||||
uword to_offset) const;
|
||||
// Returns the address of the previous instruction when given
|
||||
// a valid return address for the given bytecode or 0 otherwise.
|
||||
uword GetInstructionBefore(uword return_address) const;
|
||||
|
||||
intptr_t instructions_binary_offset() const {
|
||||
return untag()->instructions_binary_offset_;
|
||||
@@ -7617,7 +7616,6 @@ class Bytecode : public Object {
|
||||
// Will compute local var descriptors if necessary.
|
||||
LocalVarDescriptorsPtr GetLocalVarDescriptors() const;
|
||||
#endif // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
|
||||
|
||||
const char* Name() const;
|
||||
const char* QualifiedName() const;
|
||||
const char* FullyQualifiedName() const;
|
||||
|
||||
@@ -2178,6 +2178,14 @@ DEFINE_RUNTIME_ENTRY(SingleStepHandler, 0) {
|
||||
#endif
|
||||
}
|
||||
|
||||
DEFINE_RUNTIME_ENTRY(ResumptionBreakpointHandler, 0) {
|
||||
#if defined(DART_DYNAMIC_MODULES) && !defined(PRODUCT)
|
||||
isolate->debugger()->ResumptionBreakpoint();
|
||||
#else
|
||||
UNREACHABLE();
|
||||
#endif
|
||||
}
|
||||
|
||||
// An instance call of the form o.f(...) could not be resolved. Check if
|
||||
// there is a getter with the same name. If so, invoke it. If the value is
|
||||
// a closure, invoke it with the given arguments. If the value is a
|
||||
|
||||
@@ -26,6 +26,7 @@ namespace dart {
|
||||
V(BoxFloat64x2) \
|
||||
V(BreakpointRuntimeHandler) \
|
||||
V(SingleStepHandler) \
|
||||
V(ResumptionBreakpointHandler) \
|
||||
V(CloneContext) \
|
||||
V(CloneSuspendState) \
|
||||
V(DoubleToInteger) \
|
||||
|
||||
Reference in New Issue
Block a user