Implement rewind on all platforms except for dbc.

BUG=

Review URL: https://codereview.chromium.org/2534413005 .
This commit is contained in:
Todd Turnidge 2016-12-01 17:19:31 -08:00
parent 1ebeb79942
commit e0d350aa27
35 changed files with 1001 additions and 182 deletions

View file

@ -209,7 +209,8 @@ class DownCommand extends DebuggerCommand {
debugger.downFrame(count);
debugger.console.print('frame = ${debugger.currentFrame}');
} catch (e) {
debugger.console.print('frame must be in range [${e.start},${e.end-1}]');
debugger.console.print(
'frame must be in range [${e.start}..${e.end-1}]');
}
return new Future.value(null);
}
@ -243,7 +244,8 @@ class UpCommand extends DebuggerCommand {
debugger.upFrame(count);
debugger.console.print('frame = ${debugger.currentFrame}');
} on RangeError catch (e) {
debugger.console.print('frame must be in range [${e.start},${e.end-1}]');
debugger.console.print(
'frame must be in range [${e.start}..${e.end-1}]');
}
return new Future.value(null);
}
@ -279,7 +281,8 @@ class FrameCommand extends DebuggerCommand {
debugger.currentFrame = frame;
debugger.console.print('frame = ${debugger.currentFrame}');
} on RangeError catch (e) {
debugger.console.print('frame must be in range [${e.start},${e.end-1}]');
debugger.console.print(
'frame must be in range [${e.start}..${e.end-1}]');
}
return new Future.value(null);
}
@ -403,6 +406,73 @@ class StepCommand extends DebuggerCommand {
'Syntax: step\n';
}
class RewindCommand extends DebuggerCommand {
RewindCommand(Debugger debugger) : super(debugger, 'rewind', []);
Future run(List<String> args) async {
try {
int count = 1;
if (args.length == 1) {
count = int.parse(args[0]);
} else if (args.length > 1) {
debugger.console.print('rewind expects 0 or 1 argument');
return;
} else if (count < 1 || count > debugger.stackDepth) {
debugger.console.print(
'frame must be in range [1..${debugger.stackDepth - 1}]');
return;
}
await debugger.rewind(count);
} on S.ServerRpcException catch(e) {
if (e.code == S.ServerRpcException.kCannotResume) {
debugger.console.printRed(e.data['details']);
} else {
rethrow;
}
}
}
String helpShort = 'Rewind the stack to a previous frame';
String helpLong =
'Rewind the stack to a previous frame.\n'
'\n'
'Syntax: rewind\n'
' rewind <count>\n';
}
class ReloadCommand extends DebuggerCommand {
ReloadCommand(Debugger debugger) : super(debugger, 'reload', []);
Future run(List<String> args) async {
try {
int count = 1;
if (args.length > 0) {
debugger.console.print('reload expects no arguments');
return;
}
await debugger.isolate.reloadSources();
debugger.console.print('reload complete');
await debugger.refreshStack();
} on S.ServerRpcException catch(e) {
if (e.code == S.ServerRpcException.kIsolateReloadBarred ||
e.code == S.ServerRpcException.kIsolateReloadFailed ||
e.code == S.ServerRpcException.kIsolateIsReloading) {
debugger.console.printRed(e.data['details']);
} else {
rethrow;
}
}
}
String helpShort = 'Reload the sources for the current isolate';
String helpLong =
'Reload the sources for the current isolate.\n'
'\n'
'Syntax: reload\n';
}
class ClsCommand extends DebuggerCommand {
ClsCommand(Debugger debugger) : super(debugger, 'cls', []) {}
@ -663,8 +733,8 @@ class BreakCommand extends DebuggerCommand {
var script = loc.script;
await script.load();
if (loc.line < 1 || loc.line > script.lines.length) {
debugger.console
.print('line number must be in range [1,${script.lines.length}]');
debugger.console.print(
'line number must be in range [1..${script.lines.length}]');
return;
}
try {
@ -744,7 +814,7 @@ class ClearCommand extends DebuggerCommand {
var script = loc.script;
if (loc.line < 1 || loc.line > script.lines.length) {
debugger.console
.print('line number must be in range [1,${script.lines.length}]');
.print('line number must be in range [1..${script.lines.length}]');
return;
}
var lineInfo = script.getLine(loc.line);
@ -899,7 +969,6 @@ class IsolateCommand extends DebuggerCommand {
: super(debugger, 'isolate', [
new IsolateListCommand(debugger),
new IsolateNameCommand(debugger),
new IsolateReloadCommand(debugger),
]) {
alias = 'i';
}
@ -1042,27 +1111,6 @@ class IsolateNameCommand extends DebuggerCommand {
'Syntax: isolate name <name>\n';
}
class IsolateReloadCommand extends DebuggerCommand {
IsolateReloadCommand(Debugger debugger) : super(debugger, 'reload', []);
Future run(List<String> args) async {
if (debugger.isolate == null) {
debugger.console.print('There is no current vm');
return;
}
await debugger.isolate.reloadSources();
debugger.console.print('Isolate reloading....');
}
String helpShort = 'Reload the sources for the current isolate.';
String helpLong = 'Reload the sources for the current isolate.\n'
'\n'
'Syntax: reload\n';
}
class InfoCommand extends DebuggerCommand {
InfoCommand(Debugger debugger)
: super(debugger, 'info', [
@ -1371,7 +1419,9 @@ class ObservatoryDebugger extends Debugger {
new LogCommand(this),
new PauseCommand(this),
new PrintCommand(this),
new ReloadCommand(this),
new RefreshCommand(this),
new RewindCommand(this),
new SetCommand(this),
new SmartNextCommand(this),
new StepCommand(this),
@ -1879,6 +1929,20 @@ class ObservatoryDebugger extends Debugger {
return new Future.value(null);
}
}
Future rewind(int count) {
if (isolatePaused()) {
var event = isolate.pauseEvent;
if (event is M.PauseExitEvent) {
console.print("Type 'continue' [F7] to exit the isolate");
return new Future.value(null);
}
return isolate.rewind(count);
} else {
console.print('The program must be paused');
return new Future.value(null);
}
}
}
class DebuggerPageElement extends HtmlElement implements Renderable {
@ -2513,7 +2577,9 @@ class DebuggerFrameElement extends HtmlElement implements Renderable {
}
bool matchFrame(S.Frame newFrame) {
return newFrame.function.id == _frame.function.id;
return (newFrame.function.id == _frame.function.id &&
newFrame.location.script.id ==
frame.location.script.id);
}
void updateFrame(S.Frame newFrame) {

View file

@ -42,11 +42,13 @@ class ServerRpcException extends RpcException implements M.RequestException {
static const kStreamNotSubscribed = 104;
static const kIsolateMustBeRunnable = 105;
static const kIsolateMustBePaused = 106;
static const kCannotResume = 107;
static const kIsolateIsReloading = 1000;
static const kFileSystemAlreadyExists = 1001;
static const kFileSystemDoesNotExist = 1002;
static const kFileDoesNotExist = 1003;
static const kIsolateReloadFailed = 1004;
static const kIsolateReloadBarred = 1005;
int code;
Map data;
@ -1279,7 +1281,6 @@ class Isolate extends ServiceObjectOwner implements M.Isolate {
bool loading = true;
bool runnable = false;
bool ioEnabled = false;
bool reloading = false;
M.IsolateStatus get status {
if (paused) {
return M.IsolateStatus.paused;
@ -1344,13 +1345,12 @@ class Isolate extends ServiceObjectOwner implements M.Isolate {
params['pause'] = pause;
}
return invokeRpc('reloadSources', params).then((result) {
reloading = true;
_cache.clear();
return result;
});
}
void _handleIsolateReloadEvent(ServiceEvent event) {
reloading = false;
if (event.reloadError != null) {
// Failure.
print('Reload failed: ${event.reloadError}');
@ -1777,6 +1777,10 @@ class Isolate extends ServiceObjectOwner implements M.Isolate {
return invokeRpc('resume', {'step': 'Out'});
}
Future rewind(int count) {
return invokeRpc('resume', {'step': 'Rewind', 'frameIndex': count});
}
Future setName(String newName) {
return invokeRpc('setName', {'name': newName});
}

View file

@ -52,5 +52,6 @@ hasStoppedAtBreakpoint,
main(args) => runIsolateTests(args, tests,
testeeConcurrent: testMain,
trace_service: true,
verbose_vm: true);
verbose_vm: true,
extraArgs: [ '--trace-service',
'--trace-service-verbose' ]);

View file

@ -86,5 +86,6 @@ main(args) => runIsolateTests(args, tests,
testeeConcurrent: testMain,
pause_on_start: true,
pause_on_exit: true,
trace_service: true,
verbose_vm: true);
verbose_vm: true,
extraArgs: [ '--trace-service',
'--trace-service-verbose' ]);

View file

@ -89,5 +89,6 @@ main(args) => runIsolateTests(args, tests,
testeeConcurrent: testMain,
pause_on_start: true,
pause_on_exit: true,
trace_service: true,
verbose_vm: true);
verbose_vm: true,
extraArgs: [ '--trace-service',
'--trace-service-verbose' ]);

View file

@ -0,0 +1,86 @@
// Copyright (c) 2016, 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.
// VMOptions=--error_on_bad_type --error_on_bad_override
import 'dart:developer';
import 'package:observatory/service_io.dart';
import 'package:unittest/unittest.dart';
import 'service_test_common.dart';
import 'test_helper.dart';
const alwaysInline = "AlwaysInline";
const noInline = "NeverInline";
int LINE_A = 35;
int LINE_B = 40;
int LINE_C = 43;
int LINE_D = 47;
int global = 0;
@noInline
b3(x) {
int sum = 0;
try {
for (int i = 0; i < x; i++) {
sum += x;
}
} catch (e) {
print("caught $e");
}
if (global >= 100) {
debugger();
}
global = global + 1; // Line A
return sum;
}
@alwaysInline
b2(x) => b3(x); // Line B
@alwaysInline
b1(x) => b2(x); // Line C
test() {
while (true) {
b1(10000); // Line D
}
}
var tests = [
hasStoppedAtBreakpoint,
stoppedAtLine(LINE_A),
(Isolate isolate) async {
// We are at our breakpoint with global=100.
var result = await isolate.rootLibrary.evaluate('global');
print('global is $result');
expect(result.type, equals('Instance'));
expect(result.valueAsString, equals('100'));
// Rewind the top stack frame.
bool caughtException;
try {
result = await isolate.rewind(1);
expect(false, isTrue, reason:'Unreachable');
} on ServerRpcException catch(e) {
caughtException = true;
expect(e.code, equals(ServerRpcException.kCannotResume));
expect(e.message,
'Cannot rewind to frame 1 due to conflicting compiler '
'optimizations. Run the vm with --no-prune-dead-locals to '
'disallow these optimizations. Next valid rewind frame is 4.');
}
expect(caughtException, isTrue);
},
];
main(args) => runIsolateTests(args, tests, testeeConcurrent: test,
extraArgs:
['--trace-rewind',
'--prune-dead-locals',
'--enable-inlining-annotations',
'--no-background-compilation',
'--optimization-counter-threshold=10']);

View file

@ -0,0 +1,173 @@
// Copyright (c) 2016, 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.
// VMOptions=--error_on_bad_type --error_on_bad_override
import 'dart:developer';
import 'package:observatory/service_io.dart';
import 'package:unittest/unittest.dart';
import 'service_test_common.dart';
import 'test_helper.dart';
const alwaysInline = "AlwaysInline";
const noInline = "NeverInline";
int LINE_A = 35;
int LINE_B = 40;
int LINE_C = 43;
int LINE_D = 47;
int global = 0;
@noInline
b3(x) {
int sum = 0;
try {
for (int i = 0; i < x; i++) {
sum += x;
}
} catch (e) {
print("caught $e");
}
if (global >= 100) {
debugger();
}
global = global + 1; // Line A
return sum;
}
@alwaysInline
b2(x) => b3(x); // Line B
@alwaysInline
b1(x) => b2(x); // Line C
test() {
while (true) {
b1(10000); // Line D
}
}
var tests = [
hasStoppedAtBreakpoint,
stoppedAtLine(LINE_A),
(Isolate isolate) async {
// We are not able to rewind frame 0.
bool caughtException;
try {
await isolate.rewind(0);
expect(false, isTrue, reason:'Unreachable');
} on ServerRpcException catch(e) {
caughtException = true;
expect(e.code, equals(ServerRpcException.kCannotResume));
expect(e.message, 'Frame must be in bounds [1..9]: saw 0');
}
expect(caughtException, isTrue);
},
(Isolate isolate) async {
// We are not able to rewind frame 10.
bool caughtException;
try {
await isolate.rewind(10);
expect(false, isTrue, reason:'Unreachable');
} on ServerRpcException catch(e) {
caughtException = true;
expect(e.code, equals(ServerRpcException.kCannotResume));
expect(e.message, 'Frame must be in bounds [1..9]: saw 10');
}
expect(caughtException, isTrue);
},
(Isolate isolate) async {
// We are at our breakpoint with global=100.
var result = await isolate.rootLibrary.evaluate('global');
print('global is $result');
expect(result.type, equals('Instance'));
expect(result.valueAsString, equals('100'));
// Rewind the top stack frame.
result = await isolate.rewind(1);
expect(result['type'], equals('Success'));
},
hasStoppedAtBreakpoint,
stoppedAtLine(LINE_B),
(Isolate isolate) async {
var result = await isolate.resume();
expect(result['type'], equals('Success'));
},
hasStoppedAtBreakpoint,
stoppedAtLine(LINE_A),
(Isolate isolate) async {
// global still is equal to 100. We did not execute "global++".
var result = await isolate.rootLibrary.evaluate('global');
print('global is $result');
expect(result.type, equals('Instance'));
expect(result.valueAsString, equals('100'));
// Resume again, for fun.
result = await isolate.resume();
expect(result['type'], equals('Success'));
},
hasStoppedAtBreakpoint,
stoppedAtLine(LINE_A),
(Isolate isolate) async {
// global is now 101.
var result = await isolate.rootLibrary.evaluate('global');
print('global is $result');
expect(result.type, equals('Instance'));
expect(result.valueAsString, equals('101'));
// Rewind up to 'test'/
result = await isolate.rewind(3);
expect(result['type'], equals('Success'));
},
hasStoppedAtBreakpoint,
stoppedAtLine(LINE_D),
(Isolate isolate) async {
// Reset global to 0 and start again.
var result = await isolate.rootLibrary.evaluate('global=0');
print('set global to $result');
expect(result.type, equals('Instance'));
expect(result.valueAsString, equals('0'));
result = await isolate.resume();
expect(result['type'], equals('Success'));
},
hasStoppedAtBreakpoint,
stoppedAtLine(LINE_A),
(Isolate isolate) async {
// We are at our breakpoint with global=100.
var result = await isolate.rootLibrary.evaluate('global');
print('global is $result');
expect(result.type, equals('Instance'));
expect(result.valueAsString, equals('100'));
// Rewind the top 2 stack frames.
result = await isolate.rewind(2);
expect(result['type'], equals('Success'));
},
hasStoppedAtBreakpoint,
stoppedAtLine(LINE_C),
];
main(args) => runIsolateTests(args, tests, testeeConcurrent: test,
extraArgs:
['--trace-rewind',
'--no-prune-dead-locals',
'--enable-inlining-annotations',
'--no-background-compilation',
'--optimization-counter-threshold=10']);

View file

@ -64,6 +64,8 @@ evaluate_activation_in_method_class_test: CompileTimeError # Issue 24478
get_allocation_samples_test: RuntimeError # Profiling unimplemented.
get_cpu_profile_timeline_rpc_test: RuntimeError # Profiling unimplemented.
implicit_getter_setter_test: RuntimeError # Field guards unimplemented.
rewind_test: RuntimeError # Issue 27878
rewind_optimized_out_test: RuntimeError # Issue 27878
[ $hot_reload || $hot_reload_rollback ]
# Skip all service tests because random reloads interfere.

View file

@ -234,6 +234,10 @@ IsolateTest stoppedAtLine(int line) {
return (Isolate isolate) async {
print("Checking we are at line $line");
// Make sure that the isolate has stopped.
isolate.reload();
expect(isolate.pauseEvent is! M.ResumeEvent, isTrue);
ServiceMap stack = await isolate.getStack();
expect(stack.type, equals('Stack'));

View file

@ -32,4 +32,6 @@ var tests = [
},
];
main(args) async => runVMTests(args, tests, trace_service: true);
main(args) async => runVMTests(args, tests,
extraArgs: [ '--trace-service',
'--trace-service-verbose' ]);

View file

@ -1,7 +1,7 @@
// Copyright (c) 2015, 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.
// VMOptions=--error_on_bad_type --error_on_bad_override --verbose_debug --trace_service
// VMOptions=--error_on_bad_type --error_on_bad_override --verbose_debug
import 'dart:async';
import 'dart:developer';

View file

@ -90,15 +90,12 @@ class _ServiceTesteeLauncher {
Future<Process> _spawnProcess(bool pause_on_start,
bool pause_on_exit,
bool pause_on_unhandled_exceptions,
bool trace_service,
bool trace_compiler,
bool testeeControlsServer,
bool useAuthToken) {
bool useAuthToken,
List<String> extraArgs) {
assert(pause_on_start != null);
assert(pause_on_exit != null);
assert(pause_on_unhandled_exceptions != null);
assert(trace_service != null);
assert(trace_compiler != null);
assert(testeeControlsServer != null);
assert(useAuthToken != null);
@ -106,39 +103,29 @@ class _ServiceTesteeLauncher {
return _spawnSkyProcess(pause_on_start,
pause_on_exit,
pause_on_unhandled_exceptions,
trace_service,
trace_compiler,
testeeControlsServer);
testeeControlsServer,
extraArgs);
} else {
return _spawnDartProcess(pause_on_start,
pause_on_exit,
pause_on_unhandled_exceptions,
trace_service,
trace_compiler,
testeeControlsServer,
useAuthToken);
useAuthToken,
extraArgs);
}
}
Future<Process> _spawnDartProcess(bool pause_on_start,
bool pause_on_exit,
bool pause_on_unhandled_exceptions,
bool trace_service,
bool trace_compiler,
bool testeeControlsServer,
bool useAuthToken) {
bool useAuthToken,
List<String> extraArgs) {
assert(!_shouldLaunchSkyShell());
String dartExecutable = Platform.executable;
var fullArgs = [];
if (trace_service) {
fullArgs.add('--trace-service');
fullArgs.add('--trace-service-verbose');
}
if (trace_compiler) {
fullArgs.add('--trace-compiler');
}
if (pause_on_start) {
fullArgs.add('--pause-isolates-on-start');
}
@ -148,6 +135,9 @@ class _ServiceTesteeLauncher {
if (pause_on_unhandled_exceptions) {
fullArgs.add('--pause-isolates-on-unhandled-exceptions');
}
if (extraArgs != null) {
fullArgs.addAll(extraArgs);
}
fullArgs.addAll(Platform.executableArguments);
if (!testeeControlsServer) {
@ -166,22 +156,14 @@ class _ServiceTesteeLauncher {
Future<Process> _spawnSkyProcess(bool pause_on_start,
bool pause_on_exit,
bool pause_on_unhandled_exceptions,
bool trace_service,
bool trace_compiler,
bool testeeControlsServer) {
bool testeeControlsServer,
List<String> extraArgs) {
assert(_shouldLaunchSkyShell());
String dartExecutable = _skyShellPath();
var dartFlags = [];
var fullArgs = [];
if (trace_service) {
dartFlags.add('--trace_service');
dartFlags.add('--trace_service_verbose');
}
if (trace_compiler) {
dartFlags.add('--trace_compiler');
}
if (pause_on_start) {
dartFlags.add('--pause_isolates_on_start');
fullArgs.add('--start-paused');
@ -194,6 +176,9 @@ class _ServiceTesteeLauncher {
}
// Override mirrors.
dartFlags.add('--enable_mirrors=true');
if (extraArgs != null) {
fullArgs.addAll(extraArgs);
}
fullArgs.addAll(Platform.executableArguments);
if (!testeeControlsServer) {
@ -223,17 +208,15 @@ class _ServiceTesteeLauncher {
Future<Uri> launch(bool pause_on_start,
bool pause_on_exit,
bool pause_on_unhandled_exceptions,
bool trace_service,
bool trace_compiler,
bool testeeControlsServer,
bool useAuthToken) {
bool useAuthToken,
List<String> extraArgs) {
return _spawnProcess(pause_on_start,
pause_on_exit,
pause_on_unhandled_exceptions,
trace_service,
trace_compiler,
testeeControlsServer,
useAuthToken).then((p) {
useAuthToken,
extraArgs).then((p) {
Completer<Uri> completer = new Completer<Uri>();
process = p;
Uri uri;
@ -288,12 +271,11 @@ void setupAddresses(Uri serverAddress) {
class _ServiceTesterRunner {
void run({List<String> mainArgs,
List<String> extraArgs,
List<VMTest> vmTests,
List<IsolateTest> isolateTests,
bool pause_on_start: false,
bool pause_on_exit: false,
bool trace_service: false,
bool trace_compiler: false,
bool verbose_vm: false,
bool pause_on_unhandled_exceptions: false,
bool testeeControlsServer: false,
@ -301,9 +283,8 @@ class _ServiceTesterRunner {
var process = new _ServiceTesteeLauncher();
process.launch(pause_on_start, pause_on_exit,
pause_on_unhandled_exceptions,
trace_service, trace_compiler,
testeeControlsServer,
useAuthToken).then((Uri serverAddress) async {
useAuthToken, extraArgs).then((Uri serverAddress) async {
if (mainArgs.contains("--gdb")) {
var pid = process.process.pid;
var wait = new Duration(seconds: 10);
@ -364,12 +345,11 @@ Future runIsolateTests(List<String> mainArgs,
testeeConcurrent(),
bool pause_on_start: false,
bool pause_on_exit: false,
bool trace_service: false,
bool trace_compiler: false,
bool verbose_vm: false,
bool pause_on_unhandled_exceptions: false,
bool testeeControlsServer: false,
bool useAuthToken: false}) async {
bool useAuthToken: false,
List<String> extraArgs}) async {
assert(!pause_on_start || testeeBefore == null);
if (_isTestee()) {
new _ServiceTesteeRunner().run(testeeBefore: testeeBefore,
@ -379,11 +359,10 @@ Future runIsolateTests(List<String> mainArgs,
} else {
new _ServiceTesterRunner().run(
mainArgs: mainArgs,
extraArgs: extraArgs,
isolateTests: tests,
pause_on_start: pause_on_start,
pause_on_exit: pause_on_exit,
trace_service: trace_service,
trace_compiler: trace_compiler,
verbose_vm: verbose_vm,
pause_on_unhandled_exceptions: pause_on_unhandled_exceptions,
testeeControlsServer: testeeControlsServer,
@ -406,10 +385,9 @@ void runIsolateTestsSynchronous(List<String> mainArgs,
void testeeConcurrent(),
bool pause_on_start: false,
bool pause_on_exit: false,
bool trace_service: false,
bool trace_compiler: false,
bool verbose_vm: false,
bool pause_on_unhandled_exceptions: false}) {
bool pause_on_unhandled_exceptions: false,
List<String> extraArgs}) {
assert(!pause_on_start || testeeBefore == null);
if (_isTestee()) {
new _ServiceTesteeRunner().runSync(testeeBeforeSync: testeeBefore,
@ -419,11 +397,10 @@ void runIsolateTestsSynchronous(List<String> mainArgs,
} else {
new _ServiceTesterRunner().run(
mainArgs: mainArgs,
extraArgs: extraArgs,
isolateTests: tests,
pause_on_start: pause_on_start,
pause_on_exit: pause_on_exit,
trace_service: trace_service,
trace_compiler: trace_compiler,
verbose_vm: verbose_vm,
pause_on_unhandled_exceptions: pause_on_unhandled_exceptions);
}
@ -440,10 +417,9 @@ Future runVMTests(List<String> mainArgs,
testeeConcurrent(),
bool pause_on_start: false,
bool pause_on_exit: false,
bool trace_service: false,
bool trace_compiler: false,
bool verbose_vm: false,
bool pause_on_unhandled_exceptions: false}) async {
bool pause_on_unhandled_exceptions: false,
List<String> extraArgs}) async {
if (_isTestee()) {
new _ServiceTesteeRunner().run(testeeBefore: testeeBefore,
testeeConcurrent: testeeConcurrent,
@ -452,11 +428,10 @@ Future runVMTests(List<String> mainArgs,
} else {
new _ServiceTesterRunner().run(
mainArgs: mainArgs,
extraArgs: extraArgs,
vmTests: tests,
pause_on_start: pause_on_start,
pause_on_exit: pause_on_exit,
trace_service: trace_service,
trace_compiler: trace_compiler,
verbose_vm: verbose_vm,
pause_on_unhandled_exceptions: pause_on_unhandled_exceptions);
}

View file

@ -2215,6 +2215,15 @@ DEFINE_RUNTIME_ENTRY(DeoptimizeMaterialize, 0) {
}
DEFINE_RUNTIME_ENTRY(RewindPostDeopt, 0) {
#if !defined(DART_PRECOMPILED_RUNTIME)
#if !defined(PRODUCT)
isolate->debugger()->RewindPostDeopt();
#endif // !PRODUCT
#endif // !DART_PRECOMPILED_RUNTIME
UNREACHABLE();
}
DEFINE_LEAF_RUNTIME_ENTRY(intptr_t,
BigintCompare,
2,

View file

@ -551,7 +551,7 @@ RawObject* DartLibraryCalls::HandleMessage(const Object& handler,
// If the isolate is being debugged and the debugger was stepping
// through code, enable single stepping so debugger will stop
// at the first location the user is interested in.
isolate->debugger()->SetSingleStep();
isolate->debugger()->SetResumeAction(Debugger::kStepInto);
}
const Object& result =
Object::Handle(zone, DartEntry::InvokeFunction(function, args));

View file

@ -6,6 +6,8 @@
#include "include/dart_api.h"
#include "platform/address_sanitizer.h"
#include "vm/code_generator.h"
#include "vm/code_patcher.h"
#include "vm/compiler.h"
@ -42,6 +44,7 @@ DEFINE_FLAG(bool,
trace_debugger_stacktrace,
false,
"Trace debugger stacktrace collection");
DEFINE_FLAG(bool, trace_rewind, false, "Trace frame rewind");
DEFINE_FLAG(bool, verbose_debug, false, "Verbose debugger messages");
DEFINE_FLAG(bool,
steal_breakpoints,
@ -850,6 +853,24 @@ RawObject* ActivationFrame::GetStackVar(intptr_t slot_index) {
}
bool ActivationFrame::IsRewindable() const {
if (deopt_frame_.IsNull()) {
return true;
}
// TODO(turnidge): This is conservative. It looks at all values in
// the deopt_frame_ even though some of them may correspond to other
// inlined frames.
Object& obj = Object::Handle();
for (int i = 0; i < deopt_frame_.Length(); i++) {
obj = deopt_frame_.At(i);
if (obj.raw() == Symbols::OptimizedOut().raw()) {
return false;
}
}
return true;
}
void ActivationFrame::PrintContextMismatchError(intptr_t ctx_slot,
intptr_t frame_ctx_level,
intptr_t var_ctx_level) {
@ -1108,9 +1129,13 @@ void ActivationFrame::PrintToJSONObject(JSONObject* jsobj, bool full) {
}
}
static bool IsFunctionVisible(const Function& function) {
return FLAG_show_invisible_frames || function.is_visible();
}
void DebuggerStackTrace::AddActivation(ActivationFrame* frame) {
if (FLAG_show_invisible_frames || frame->function().is_visible()) {
if (IsFunctionVisible(frame->function())) {
trace_.Add(frame);
}
}
@ -1237,6 +1262,8 @@ Debugger::Debugger()
breakpoint_locations_(NULL),
code_breakpoints_(NULL),
resume_action_(kContinue),
resume_frame_index_(-1),
post_deopt_frame_index_(-1),
ignore_breakpoints_(false),
pause_event_(NULL),
obj_cache_(NULL),
@ -1300,10 +1327,13 @@ static RawFunction* ResolveLibraryFunction(const Library& library,
}
bool Debugger::SetupStepOverAsyncSuspension() {
bool Debugger::SetupStepOverAsyncSuspension(const char** error) {
ActivationFrame* top_frame = TopDartFrame();
if (!IsAtAsyncJump(top_frame)) {
// Not at an async operation.
if (error) {
*error = "Isolate must be paused at an async suspension point";
}
return false;
}
Object& closure = Object::Handle(top_frame->GetAsyncOperation());
@ -1313,24 +1343,42 @@ bool Debugger::SetupStepOverAsyncSuspension() {
Breakpoint* bpt = SetBreakpointAtActivation(Instance::Cast(closure), true);
if (bpt == NULL) {
// Unable to set the breakpoint.
if (error) {
*error = "Unable to set breakpoint at async suspension point";
}
return false;
}
return true;
}
void Debugger::SetSingleStep() {
resume_action_ = kSingleStep;
}
void Debugger::SetStepOver() {
resume_action_ = kStepOver;
}
void Debugger::SetStepOut() {
resume_action_ = kStepOut;
bool Debugger::SetResumeAction(ResumeAction action,
intptr_t frame_index,
const char** error) {
if (error) {
*error = NULL;
}
resume_frame_index_ = -1;
switch (action) {
case kStepInto:
case kStepOver:
case kStepOut:
case kContinue:
resume_action_ = action;
return true;
case kStepRewind:
if (!CanRewindFrame(frame_index, error)) {
return false;
}
resume_action_ = kStepRewind;
resume_frame_index_ = frame_index;
return true;
case kStepOverAsyncSuspension:
return SetupStepOverAsyncSuspension(error);
default:
UNREACHABLE();
return false;
}
}
@ -1622,6 +1670,7 @@ void Debugger::PauseException(const Instance& exc) {
ASSERT(stack_trace_ == NULL);
stack_trace_ = stack_trace;
Pause(&event);
HandleSteppingRequest(stack_trace_); // we may get a rewind request
stack_trace_ = NULL;
}
@ -2547,7 +2596,7 @@ void Debugger::EnterSingleStepMode() {
void Debugger::HandleSteppingRequest(DebuggerStackTrace* stack_trace,
bool skip_next_step) {
stepping_fp_ = 0;
if (resume_action_ == kSingleStep) {
if (resume_action_ == kStepInto) {
// When single stepping, we need to deoptimize because we might be
// stepping into optimized code. This happens in particular if
// the isolate has been interrupted, but can happen in other cases
@ -2557,7 +2606,7 @@ void Debugger::HandleSteppingRequest(DebuggerStackTrace* stack_trace,
isolate_->set_single_step(true);
skip_next_step_ = skip_next_step;
if (FLAG_verbose_debug) {
OS::Print("HandleSteppingRequest- kSingleStep\n");
OS::Print("HandleSteppingRequest- kStepInto\n");
}
} else if (resume_action_ == kStepOver) {
DeoptimizeWorld();
@ -2582,6 +2631,244 @@ void Debugger::HandleSteppingRequest(DebuggerStackTrace* stack_trace,
if (FLAG_verbose_debug) {
OS::Print("HandleSteppingRequest- kStepOut %" Px "\n", stepping_fp_);
}
} else if (resume_action_ == kStepRewind) {
if (FLAG_trace_rewind) {
OS::PrintErr("Rewinding to frame %" Pd "\n", resume_frame_index_);
OS::PrintErr(
"-------------------------\n"
"All frames...\n\n");
StackFrameIterator iterator(false);
StackFrame* frame = iterator.NextFrame();
intptr_t num = 0;
while ((frame != NULL)) {
OS::PrintErr("#%04" Pd " %s\n", num++, frame->ToCString());
frame = iterator.NextFrame();
}
}
RewindToFrame(resume_frame_index_);
UNREACHABLE();
}
}
static intptr_t FindNextRewindFrameIndex(DebuggerStackTrace* stack,
intptr_t frame_index) {
for (intptr_t i = frame_index + 1; i < stack->Length(); i++) {
ActivationFrame* frame = stack->FrameAt(i);
if (frame->IsRewindable()) {
return i;
}
}
return -1;
}
// Can the top frame be rewound?
bool Debugger::CanRewindFrame(intptr_t frame_index, const char** error) const {
// check rewind pc is found
DebuggerStackTrace* stack = Isolate::Current()->debugger()->StackTrace();
intptr_t num_frames = stack->Length();
if (frame_index < 1 || frame_index >= num_frames) {
if (error) {
*error = Thread::Current()->zone()->PrintToString(
"Frame must be in bounds [1..%" Pd
"]: "
"saw %" Pd "",
num_frames - 1, frame_index);
}
return false;
}
ActivationFrame* frame = stack->FrameAt(frame_index);
if (!frame->IsRewindable()) {
intptr_t next_index = FindNextRewindFrameIndex(stack, frame_index);
if (next_index > 0) {
*error = Thread::Current()->zone()->PrintToString(
"Cannot rewind to frame %" Pd
" due to conflicting compiler "
"optimizations. "
"Run the vm with --no-prune-dead-locals to disallow these "
"optimizations. "
"Next valid rewind frame is %" Pd ".",
frame_index, next_index);
} else {
*error = Thread::Current()->zone()->PrintToString(
"Cannot rewind to frame %" Pd
" due to conflicting compiler "
"optimizations. "
"Run the vm with --no-prune-dead-locals to disallow these "
"optimizations.",
frame_index);
}
return false;
}
return true;
}
// Given a return address pc, find the "rewind" pc, which is the pc
// before the corresponding call.
static uword LookupRewindPc(const Code& code, uword pc) {
ASSERT(!code.is_optimized());
ASSERT(code.ContainsInstructionAt(pc));
uword pc_offset = pc - code.PayloadStart();
const PcDescriptors& descriptors =
PcDescriptors::Handle(code.pc_descriptors());
PcDescriptors::Iterator iter(
descriptors, RawPcDescriptors::kRewind | RawPcDescriptors::kIcCall |
RawPcDescriptors::kUnoptStaticCall);
intptr_t rewind_deopt_id = -1;
uword rewind_pc = 0;
while (iter.MoveNext()) {
if (iter.Kind() == RawPcDescriptors::kRewind) {
// Remember the last rewind so we don't need to iterator twice.
rewind_pc = code.PayloadStart() + iter.PcOffset();
rewind_deopt_id = iter.DeoptId();
}
if ((pc_offset == iter.PcOffset()) && (iter.DeoptId() == rewind_deopt_id)) {
return rewind_pc;
}
}
return 0;
}
void Debugger::RewindToFrame(intptr_t frame_index) {
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
Code& code = Code::Handle(zone);
Function& function = Function::Handle(zone);
// Find the requested frame.
StackFrameIterator iterator(false);
intptr_t current_frame = 0;
for (StackFrame* frame = iterator.NextFrame(); frame != NULL;
frame = iterator.NextFrame()) {
ASSERT(frame->IsValid());
if (frame->IsDartFrame()) {
code = frame->LookupDartCode();
function = code.function();
if (!IsFunctionVisible(function)) {
continue;
}
if (code.is_optimized()) {
intptr_t sub_index = 0;
for (InlinedFunctionsIterator it(code, frame->pc()); !it.Done();
it.Advance()) {
if (current_frame == frame_index) {
RewindToOptimizedFrame(frame, code, sub_index);
UNREACHABLE();
}
current_frame++;
sub_index++;
}
} else {
if (current_frame == frame_index) {
// We are rewinding to an unoptimized frame.
RewindToUnoptimizedFrame(frame, code);
UNREACHABLE();
}
current_frame++;
}
}
}
UNIMPLEMENTED();
}
void Debugger::RewindToUnoptimizedFrame(StackFrame* frame, const Code& code) {
// We will be jumping out of the debugger rather than exiting this
// function, so prepare the debugger state.
stack_trace_ = NULL;
resume_action_ = kContinue;
resume_frame_index_ = -1;
EnterSingleStepMode();
uword rewind_pc = LookupRewindPc(code, frame->pc());
if (FLAG_trace_rewind && rewind_pc == 0) {
OS::PrintErr("Unable to find rewind pc for pc(%" Px ")\n", frame->pc());
}
ASSERT(rewind_pc != 0);
if (FLAG_trace_rewind) {
OS::PrintErr(
"===============================\n"
"Rewinding to unoptimized frame:\n"
" rewind_pc(0x%" Px ") sp(0x%" Px ") fp(0x%" Px
")\n"
"===============================\n",
rewind_pc, frame->sp(), frame->fp());
}
Exceptions::JumpToFrame(Thread::Current(), rewind_pc, frame->sp(),
frame->fp(), true /* clear lazy deopt at target */);
UNREACHABLE();
}
void Debugger::RewindToOptimizedFrame(StackFrame* frame,
const Code& optimized_code,
intptr_t sub_index) {
post_deopt_frame_index_ = sub_index;
// We will be jumping out of the debugger rather than exiting this
// function, so prepare the debugger state.
stack_trace_ = NULL;
resume_action_ = kContinue;
resume_frame_index_ = -1;
EnterSingleStepMode();
if (FLAG_trace_rewind) {
OS::PrintErr(
"===============================\n"
"Deoptimizing frame for rewind:\n"
" deopt_pc(0x%" Px ") sp(0x%" Px ") fp(0x%" Px
")\n"
"===============================\n",
frame->pc(), frame->sp(), frame->fp());
}
Thread* thread = Thread::Current();
thread->set_resume_pc(frame->pc());
uword deopt_stub_pc = StubCode::DeoptForRewind_entry()->EntryPoint();
Exceptions::JumpToFrame(thread, deopt_stub_pc, frame->sp(), frame->fp(),
true /* clear lazy deopt at target */);
UNREACHABLE();
}
void Debugger::RewindPostDeopt() {
intptr_t rewind_frame = post_deopt_frame_index_;
post_deopt_frame_index_ = -1;
if (FLAG_trace_rewind) {
OS::PrintErr("Post deopt, jumping to frame %" Pd "\n", rewind_frame);
OS::PrintErr(
"-------------------------\n"
"All frames...\n\n");
StackFrameIterator iterator(false);
StackFrame* frame = iterator.NextFrame();
intptr_t num = 0;
while ((frame != NULL)) {
OS::PrintErr("#%04" Pd " %s\n", num++, frame->ToCString());
frame = iterator.NextFrame();
}
}
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
Code& code = Code::Handle(zone);
StackFrameIterator iterator(false);
intptr_t current_frame = 0;
for (StackFrame* frame = iterator.NextFrame(); frame != NULL;
frame = iterator.NextFrame()) {
ASSERT(frame->IsValid());
if (frame->IsDartFrame()) {
code = frame->LookupDartCode();
ASSERT(!code.is_optimized());
if (current_frame == rewind_frame) {
RewindToUnoptimizedFrame(frame, code);
UNREACHABLE();
}
current_frame++;
}
}
}
@ -2793,7 +3080,7 @@ RawError* Debugger::PauseBreakpoint() {
// We are at the entry of an async function.
// We issue a step over to resume at the point after the await statement.
SetStepOver();
SetResumeAction(kStepOver);
// When we single step from a user breakpoint, our next stepping
// point will be at the exact same pc. Skip it.
HandleSteppingRequest(stack_trace_, true /* skip next step */);
@ -2846,7 +3133,7 @@ void Debugger::PauseDeveloper(const String& msg) {
// We are in the native call to Developer_debugger. the developer
// gets a better experience by not seeing this call. To accomplish
// this, we continue execution until the call exits (step out).
SetStepOut();
SetResumeAction(kStepOut);
HandleSteppingRequest(stack_trace_);
stack_trace_ = NULL;

View file

@ -276,6 +276,9 @@ class ActivationFrame : public ZoneAllocated {
// to the user and can be debugged.
bool IsDebuggable() const;
// Returns true if it is possible to rewind the debugger to this frame.
bool IsRewindable() const;
// The context level of a frame is the context level at the
// PC/token index of the frame. It determines the depth of the context
// chain that belongs to the function of this activation frame.
@ -373,6 +376,15 @@ class DebuggerStackTrace : public ZoneAllocated {
class Debugger {
public:
enum ResumeAction {
kContinue,
kStepInto,
kStepOver,
kStepOut,
kStepRewind,
kStepOverAsyncSuspension,
};
typedef void EventHandler(ServiceEvent* event);
Debugger();
@ -412,11 +424,10 @@ class Debugger {
void RemoveBreakpoint(intptr_t bp_id);
Breakpoint* GetBreakpointById(intptr_t id);
// Will return false if we are not at an await.
bool SetupStepOverAsyncSuspension();
void SetStepOver();
void SetSingleStep();
void SetStepOut();
bool SetResumeAction(ResumeAction action,
intptr_t frame_index = 1,
const char** error = NULL);
bool IsStepping() const { return resume_action_ != kContinue; }
bool IsPaused() const { return pause_event_ != NULL; }
@ -513,11 +524,15 @@ class Debugger {
intptr_t limitBreakpointId() { return next_id_; }
private:
enum ResumeAction { kContinue, kStepOver, kStepOut, kSingleStep };
// Callback to the debugger to continue frame rewind, post-deoptimization.
void RewindPostDeopt();
private:
RawError* PauseRequest(ServiceEvent::EventKind kind);
// Will return false if we are not at an await.
bool SetupStepOverAsyncSuspension(const char** error);
bool NeedsIsolateEvents();
bool NeedsDebugEvents();
void InvokeEventHandler(ServiceEvent* event);
@ -588,6 +603,15 @@ class Debugger {
void HandleSteppingRequest(DebuggerStackTrace* stack_trace,
bool skip_next_step = false);
// Can we rewind to the indicated frame?
bool CanRewindFrame(intptr_t frame_index, const char** error) const;
void RewindToFrame(intptr_t frame_index);
void RewindToUnoptimizedFrame(StackFrame* frame, const Code& code);
void RewindToOptimizedFrame(StackFrame* frame,
const Code& code,
intptr_t post_deopt_frame_index);
Isolate* isolate_;
Dart_Port isolate_id_; // A unique ID for the isolate in the debugger.
bool initialized_;
@ -601,6 +625,8 @@ class Debugger {
// Tells debugger what to do when resuming execution after a breakpoint.
ResumeAction resume_action_;
intptr_t resume_frame_index_;
intptr_t post_deopt_frame_index_;
// Do not call back to breakpoint handler if this flag is set.
// Effectively this means ignoring breakpoints. Set when Dart code may

View file

@ -485,7 +485,7 @@ DART_EXPORT Dart_Handle Dart_SetStepOver() {
DARTSCOPE(Thread::Current());
Isolate* I = T->isolate();
CHECK_DEBUGGER(I);
I->debugger()->SetStepOver();
I->debugger()->SetResumeAction(Debugger::kStepOver);
return Api::Success();
}
@ -494,7 +494,7 @@ DART_EXPORT Dart_Handle Dart_SetStepInto() {
DARTSCOPE(Thread::Current());
Isolate* I = T->isolate();
CHECK_DEBUGGER(I);
I->debugger()->SetSingleStep();
I->debugger()->SetResumeAction(Debugger::kStepInto);
return Api::Success();
}
@ -503,7 +503,7 @@ DART_EXPORT Dart_Handle Dart_SetStepOut() {
DARTSCOPE(Thread::Current());
Isolate* I = T->isolate();
CHECK_DEBUGGER(I);
I->debugger()->SetStepOut();
I->debugger()->SetResumeAction(Debugger::kStepOut);
return Api::Success();
}

View file

@ -189,7 +189,6 @@ static bool FindExceptionHandler(Thread* thread,
static void FindErrorHandler(uword* handler_pc,
uword* handler_sp,
uword* handler_fp) {
// TODO(turnidge): Is there a faster way to get the next entry frame?
StackFrameIterator frames(StackFrameIterator::kDontValidateFrames);
StackFrame* frame = frames.NextFrame();
ASSERT(frame != NULL);
@ -228,7 +227,17 @@ static uword RemapExceptionPCForDeopt(Thread* thread,
break;
}
}
}
#endif // !DBC
return program_counter;
}
static void ClearLazyDeopts(Thread* thread, uword frame_pointer) {
#if !defined(TARGET_ARCH_DBC)
MallocGrowableArray<PendingLazyDeopt>* pending_deopts =
thread->isolate()->pending_deopts();
if (pending_deopts->length() > 0) {
// We may be jumping over frames scheduled for lazy deopt. Remove these
// frames from the pending deopt table, but only after unmarking them so
// any stack walk that happens before the stack is unwound will still work.
@ -264,7 +273,6 @@ static uword RemapExceptionPCForDeopt(Thread* thread,
#endif
}
#endif // !DBC
return program_counter;
}
@ -281,14 +289,18 @@ static void JumpToExceptionHandler(Thread* thread,
thread->set_resume_pc(remapped_pc);
uword run_exception_pc = StubCode::RunExceptionHandler_entry()->EntryPoint();
Exceptions::JumpToFrame(thread, run_exception_pc, stack_pointer,
frame_pointer);
frame_pointer, false /* do not clear deopt */);
}
void Exceptions::JumpToFrame(Thread* thread,
uword program_counter,
uword stack_pointer,
uword frame_pointer) {
uword frame_pointer,
bool clear_deopt_at_target) {
uword fp_for_clearing =
(clear_deopt_at_target ? frame_pointer + 1 : frame_pointer);
ClearLazyDeopts(thread, fp_for_clearing);
#if defined(USING_SIMULATOR)
// Unwinding of the C++ frames and destroying of their stack resources is done
// by the simulator, because the target stack_pointer is a simulated stack

View file

@ -83,7 +83,8 @@ class Exceptions : AllStatic {
static void JumpToFrame(Thread* thread,
uword program_counter,
uword stack_pointer,
uword frame_pointer);
uword frame_pointer,
bool clear_deopt_at_target);
private:
DISALLOW_COPY_AND_ASSIGN(Exceptions);

View file

@ -1271,6 +1271,7 @@ void FlowGraphCompiler::GenerateStaticCall(intptr_t deopt_id,
kNumArgsChecked)
->raw();
}
AddCurrentDescriptor(RawPcDescriptors::kRewind, deopt_id, token_pos);
EmitUnoptimizedStaticCall(argument_count, deopt_id, token_pos, locs,
call_ic_data);
}

View file

@ -3075,6 +3075,8 @@ void InstanceCallInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
} else {
// Unoptimized code.
ASSERT(!HasICData());
compiler->AddCurrentDescriptor(RawPcDescriptors::kRewind, deopt_id(),
token_pos());
bool is_smi_two_args_op = false;
const StubEntry* stub_entry = TwoArgsSmiOpInlineCacheEntry(token_kind());
if (stub_entry != NULL) {

View file

@ -139,6 +139,8 @@ static const char* GetJSONRpcErrorMessage(intptr_t code) {
return "Isolate must be runnable";
case kIsolateMustBePaused:
return "Isolate must be paused";
case kCannotResume:
return "Cannot resume execution";
case kIsolateIsReloading:
return "Isolate is reloading";
case kFileSystemAlreadyExists:

View file

@ -53,6 +53,7 @@ enum JSONRpcErrorCode {
kStreamNotSubscribed = 104,
kIsolateMustBeRunnable = 105,
kIsolateMustBePaused = 106,
kCannotResume = 107,
// Experimental (used in private rpcs).
kIsolateIsReloading = 1000,

View file

@ -11700,6 +11700,8 @@ const char* PcDescriptors::KindAsStr(RawPcDescriptors::Kind kind) {
return "runtime-call ";
case RawPcDescriptors::kOsrEntry:
return "osr-entry ";
case RawPcDescriptors::kRewind:
return "rewind ";
case RawPcDescriptors::kOther:
return "other ";
case RawPcDescriptors::kAnyKind:

View file

@ -1202,7 +1202,8 @@ class RawPcDescriptors : public RawObject {
kUnoptStaticCall = kIcCall << 1, // Call to a known target via stub.
kRuntimeCall = kUnoptStaticCall << 1, // Runtime call.
kOsrEntry = kRuntimeCall << 1, // OSR entry point in unopt. code.
kOther = kOsrEntry << 1,
kRewind = kOsrEntry << 1, // Call rewind target address.
kOther = kRewind << 1,
kLastKind = kOther,
kAnyKind = -1
};

View file

@ -40,6 +40,7 @@ namespace dart {
V(TraceFunctionEntry) \
V(TraceFunctionExit) \
V(DeoptimizeMaterialize) \
V(RewindPostDeopt) \
V(UpdateFieldCid) \
V(InitStaticField) \
V(GrowRegExpStack) \

View file

@ -2988,18 +2988,57 @@ static bool GetVMTimeline(Thread* thread, JSONStream* js) {
}
static const char* const step_enum_names[] = {
"None", "Into", "Over", "Out", "Rewind", "OverAsyncSuspension", NULL,
};
static const Debugger::ResumeAction step_enum_values[] = {
Debugger::kContinue, Debugger::kStepInto,
Debugger::kStepOver, Debugger::kStepOut,
Debugger::kStepRewind, Debugger::kStepOverAsyncSuspension,
Debugger::kContinue, // Default value
};
static const MethodParameter* resume_params[] = {
RUNNABLE_ISOLATE_PARAMETER, NULL,
RUNNABLE_ISOLATE_PARAMETER,
new EnumParameter("step", false, step_enum_names),
new UIntParameter("frameIndex", false), NULL,
};
static bool Resume(Thread* thread, JSONStream* js) {
const char* step_param = js->LookupParam("step");
Debugger::ResumeAction step = Debugger::kContinue;
if (step_param != NULL) {
step = EnumMapper(step_param, step_enum_names, step_enum_values);
}
#if defined(TARGET_ARCH_DBC)
if (step == Debugger::kStepRewind) {
js->PrintError(kCannotResume,
"Rewind not yet implemented on this architecture");
return true;
}
#endif
intptr_t frame_index = 1;
const char* frame_index_param = js->LookupParam("frameIndex");
if (frame_index_param != NULL) {
if (step != Debugger::kStepRewind) {
// Only rewind supports the frameIndex parameter.
js->PrintError(
kInvalidParams,
"%s: the 'frameIndex' parameter can only be used when rewinding",
js->method());
return true;
}
frame_index = UIntParameter::Parse(js->LookupParam("frameIndex"));
}
Isolate* isolate = thread->isolate();
if (isolate->message_handler()->is_paused_on_start()) {
// If the user is issuing a 'Over' or an 'Out' step, that is the
// same as a regular resume request.
if ((step_param != NULL) && (strcmp(step_param, "Into") == 0)) {
if (step == Debugger::kStepInto) {
isolate->debugger()->EnterSingleStepMode();
}
isolate->message_handler()->set_should_pause_on_start(false);
@ -3028,31 +3067,18 @@ static bool Resume(Thread* thread, JSONStream* js) {
PrintSuccess(js);
return true;
}
if (isolate->debugger()->PauseEvent() != NULL) {
if (step_param != NULL) {
if (strcmp(step_param, "Into") == 0) {
isolate->debugger()->SetSingleStep();
} else if (strcmp(step_param, "Over") == 0) {
isolate->debugger()->SetStepOver();
} else if (strcmp(step_param, "Out") == 0) {
isolate->debugger()->SetStepOut();
} else if (strcmp(step_param, "OverAsyncSuspension") == 0) {
if (!isolate->debugger()->SetupStepOverAsyncSuspension()) {
js->PrintError(kInvalidParams,
"Isolate must be paused at an async suspension point");
return true;
}
} else {
PrintInvalidParamError(js, "step");
return true;
}
}
isolate->SetResumeRequest();
PrintSuccess(js);
if (isolate->debugger()->PauseEvent() == NULL) {
js->PrintError(kIsolateMustBePaused, NULL);
return true;
}
js->PrintError(kIsolateMustBePaused, NULL);
const char* error = NULL;
if (!isolate->debugger()->SetResumeAction(step, frame_index, &error)) {
js->PrintError(kCannotResume, error);
return true;
}
isolate->SetResumeRequest();
PrintSuccess(js);
return true;
}

View file

@ -3715,14 +3715,25 @@ void Simulator::JumpToFrame(uword pc, uword sp, uword fp, Thread* thread) {
fp_ = reinterpret_cast<RawObject**>(fp);
if (pc == StubCode::RunExceptionHandler_entry()->EntryPoint()) {
// Instead of executing the RunException stub, we implement its
// behavior here.
// The RunExceptionHandler stub is a placeholder. We implement
// its behavior here.
RawObject* raw_exception = thread->active_exception();
RawObject* raw_stacktrace = thread->active_stacktrace();
ASSERT(raw_exception != Object::null());
special_[kExceptionSpecialIndex] = raw_exception;
special_[kStacktraceSpecialIndex] = raw_stacktrace;
pc_ = thread->resume_pc();
} else if (pc == StubCode::DeoptForRewind_entry()->EntryPoint()) {
// The DeoptForRewind stub is a placeholder. We will eventually
// implement its behavior here.
//
// TODO(turnidge): Refactor the Deopt bytecode so that we can use
// the implementation here too. The deopt pc is stored in
// Thread::resume_pc(). After invoking deoptimization, we usually
// call into Debugger::RewindPostDeopt(), but I need to figure out
// if that makes any sense (it would JumpToFrame during a
// JumpToFrame, which seems wrong).
UNIMPLEMENTED();
} else {
pc_ = pc;
}

View file

@ -27,6 +27,7 @@ class Deserializer;
V(GetStackPointer) \
V(JumpToFrame) \
V(RunExceptionHandler) \
V(DeoptForRewind) \
V(UpdateStoreBuffer) \
V(PrintStopMessage) \
V(CallToRuntime) \
@ -75,6 +76,7 @@ class Deserializer;
V(LazyCompile) \
V(OptimizeFunction) \
V(RunExceptionHandler) \
V(DeoptForRewind) \
V(FixCallersTarget) \
V(Deoptimize) \
V(DeoptimizeLazyFromReturn) \
@ -195,6 +197,12 @@ class StubCode : public AllStatic {
enum DeoptStubKind { kLazyDeoptFromReturn, kLazyDeoptFromThrow, kEagerDeopt };
// Zap value used to indicate unused CODE_REG in deopt.
static const uword kZapCodeReg = 0xf1f1f1f1;
// Zap value used to indicate unused return address in deopt.
static const uword kZapReturnAddress = 0xe1e1e1e1;
} // namespace dart
#endif // RUNTIME_VM_STUB_CODE_H_

View file

@ -537,19 +537,20 @@ static void GenerateDeoptimizationSequence(Assembler* assembler,
__ LeaveStubFrame();
// Remove materialization arguments.
__ add(SP, SP, Operand(R2, ASR, kSmiTagSize));
__ Ret();
// The caller is responsible for emitting the return instruction.
}
// R0: result, must be preserved
void StubCode::GenerateDeoptimizeLazyFromReturnStub(Assembler* assembler) {
// Push zap value instead of CODE_REG for lazy deopt.
__ LoadImmediate(IP, 0xf1f1f1f1);
__ LoadImmediate(IP, kZapCodeReg);
__ Push(IP);
// Return address for "call" to deopt stub.
__ LoadImmediate(LR, 0xe1e1e1e1);
__ LoadImmediate(LR, kZapReturnAddress);
__ ldr(CODE_REG, Address(THR, Thread::lazy_deopt_from_return_stub_offset()));
GenerateDeoptimizationSequence(assembler, kLazyDeoptFromReturn);
__ Ret();
}
@ -557,17 +558,19 @@ void StubCode::GenerateDeoptimizeLazyFromReturnStub(Assembler* assembler) {
// R1: stacktrace, must be preserved
void StubCode::GenerateDeoptimizeLazyFromThrowStub(Assembler* assembler) {
// Push zap value instead of CODE_REG for lazy deopt.
__ LoadImmediate(IP, 0xf1f1f1f1);
__ LoadImmediate(IP, kZapCodeReg);
__ Push(IP);
// Return address for "call" to deopt stub.
__ LoadImmediate(LR, 0xe1e1e1e1);
__ LoadImmediate(LR, kZapReturnAddress);
__ ldr(CODE_REG, Address(THR, Thread::lazy_deopt_from_throw_stub_offset()));
GenerateDeoptimizationSequence(assembler, kLazyDeoptFromThrow);
__ Ret();
}
void StubCode::GenerateDeoptimizeStub(Assembler* assembler) {
GenerateDeoptimizationSequence(assembler, kEagerDeopt);
__ Ret();
}
@ -1899,6 +1902,26 @@ void StubCode::GenerateRunExceptionHandlerStub(Assembler* assembler) {
}
// Deoptimize a frame on the call stack before rewinding.
// The arguments are stored in the Thread object.
// No result.
void StubCode::GenerateDeoptForRewindStub(Assembler* assembler) {
// Push zap value instead of CODE_REG.
__ LoadImmediate(IP, kZapCodeReg);
__ Push(IP);
// Load the deopt pc into LR.
__ LoadFromOffset(kWord, LR, THR, Thread::resume_pc_offset());
GenerateDeoptimizationSequence(assembler, kEagerDeopt);
// After we have deoptimized, jump to the correct frame.
__ EnterStubFrame();
__ CallRuntime(kRewindPostDeoptRuntimeEntry, 0);
__ LeaveStubFrame();
__ bkpt(0);
}
// Calls to the runtime to optimize the given function.
// R8: function to be reoptimized.
// R4: argument descriptor (preserved).

View file

@ -558,19 +558,20 @@ static void GenerateDeoptimizationSequence(Assembler* assembler,
__ LeaveStubFrame();
// Remove materialization arguments.
__ add(SP, SP, Operand(R2));
__ ret();
// The caller is responsible for emitting the return instruction.
}
// R0: result, must be preserved
void StubCode::GenerateDeoptimizeLazyFromReturnStub(Assembler* assembler) {
// Push zap value instead of CODE_REG for lazy deopt.
__ LoadImmediate(TMP, 0xf1f1f1f1);
__ LoadImmediate(TMP, kZapCodeReg);
__ Push(TMP);
// Return address for "call" to deopt stub.
__ LoadImmediate(LR, 0xe1e1e1e1);
__ LoadImmediate(LR, kZapReturnAddress);
__ ldr(CODE_REG, Address(THR, Thread::lazy_deopt_from_return_stub_offset()));
GenerateDeoptimizationSequence(assembler, kLazyDeoptFromReturn);
__ ret();
}
@ -578,17 +579,19 @@ void StubCode::GenerateDeoptimizeLazyFromReturnStub(Assembler* assembler) {
// R1: stacktrace, must be preserved
void StubCode::GenerateDeoptimizeLazyFromThrowStub(Assembler* assembler) {
// Push zap value instead of CODE_REG for lazy deopt.
__ LoadImmediate(TMP, 0xf1f1f1f1);
__ LoadImmediate(TMP, kZapCodeReg);
__ Push(TMP);
// Return address for "call" to deopt stub.
__ LoadImmediate(LR, 0xe1e1e1e1);
__ LoadImmediate(LR, kZapReturnAddress);
__ ldr(CODE_REG, Address(THR, Thread::lazy_deopt_from_throw_stub_offset()));
GenerateDeoptimizationSequence(assembler, kLazyDeoptFromThrow);
__ ret();
}
void StubCode::GenerateDeoptimizeStub(Assembler* assembler) {
GenerateDeoptimizationSequence(assembler, kEagerDeopt);
__ ret();
}
@ -1949,6 +1952,26 @@ void StubCode::GenerateRunExceptionHandlerStub(Assembler* assembler) {
}
// Deoptimize a frame on the call stack before rewinding.
// The arguments are stored in the Thread object.
// No result.
void StubCode::GenerateDeoptForRewindStub(Assembler* assembler) {
// Push zap value instead of CODE_REG.
__ LoadImmediate(TMP, kZapCodeReg);
__ Push(TMP);
// Load the deopt pc into LR.
__ LoadFromOffset(LR, THR, Thread::resume_pc_offset());
GenerateDeoptimizationSequence(assembler, kEagerDeopt);
// After we have deoptimized, jump to the correct frame.
__ EnterStubFrame();
__ CallRuntime(kRewindPostDeoptRuntimeEntry, 0);
__ LeaveStubFrame();
__ brk(0);
}
// Calls to the runtime to optimize the given function.
// R6: function to be re-optimized.
// R4: argument descriptor (preserved).

View file

@ -47,6 +47,12 @@ void StubCode::GenerateRunExceptionHandlerStub(Assembler* assembler) {
}
// Not executed, but used as a sentinel in Simulator::JumpToFrame.
void StubCode::GenerateDeoptForRewindStub(Assembler* assembler) {
__ Trap();
}
// TODO(vegorov) Don't generate this stub.
void StubCode::GenerateFixCallersTargetStub(Assembler* assembler) {
__ Trap();

View file

@ -351,8 +351,6 @@ static void PushArgumentsArray(Assembler* assembler) {
// +------------------+
// | return-address | (deoptimization point)
// +------------------+
// | Saved CODE_REG |
// +------------------+
// | ... | <- SP of optimized frame
//
// Parts of the code cannot GC, part of the code can GC.
@ -462,15 +460,16 @@ static void GenerateDeoptimizationSequence(Assembler* assembler,
__ popl(ECX); // Pop return address.
__ addl(ESP, EBX); // Remove materialization arguments.
__ pushl(ECX); // Push return address.
__ ret();
// The caller is responsible for emitting the return instruction.
}
// EAX: result, must be preserved
void StubCode::GenerateDeoptimizeLazyFromReturnStub(Assembler* assembler) {
// Return address for "call" to deopt stub.
__ pushl(Immediate(0xe1e1e1e1));
__ pushl(Immediate(kZapReturnAddress));
GenerateDeoptimizationSequence(assembler, kLazyDeoptFromReturn);
__ ret();
}
@ -478,13 +477,15 @@ void StubCode::GenerateDeoptimizeLazyFromReturnStub(Assembler* assembler) {
// EDX: stacktrace, must be preserved
void StubCode::GenerateDeoptimizeLazyFromThrowStub(Assembler* assembler) {
// Return address for "call" to deopt stub.
__ pushl(Immediate(0xe1e1e1e1));
__ pushl(Immediate(kZapReturnAddress));
GenerateDeoptimizationSequence(assembler, kLazyDeoptFromThrow);
__ ret();
}
void StubCode::GenerateDeoptimizeStub(Assembler* assembler) {
GenerateDeoptimizationSequence(assembler, kEagerDeopt);
__ ret();
}
@ -1835,6 +1836,22 @@ void StubCode::GenerateRunExceptionHandlerStub(Assembler* assembler) {
}
// Deoptimize a frame on the call stack before rewinding.
// The arguments are stored in the Thread object.
// No result.
void StubCode::GenerateDeoptForRewindStub(Assembler* assembler) {
// Push the deopt pc.
__ pushl(Address(THR, Thread::resume_pc_offset()));
GenerateDeoptimizationSequence(assembler, kEagerDeopt);
// After we have deoptimized, jump to the correct frame.
__ EnterStubFrame();
__ CallRuntime(kRewindPostDeoptRuntimeEntry, 0);
__ LeaveFrame();
__ int3();
}
// Calls to the runtime to optimize the given function.
// EBX: function to be reoptimized.
// EDX: argument descriptor (preserved).

View file

@ -549,18 +549,19 @@ static void GenerateDeoptimizationSequence(Assembler* assembler,
// Remove materialization arguments.
__ SmiUntag(T1);
__ addu(SP, SP, T1);
__ Ret();
// The caller is responsible for emitting the return instruction.
}
// V0: result, must be preserved
void StubCode::GenerateDeoptimizeLazyFromReturnStub(Assembler* assembler) {
// Push zap value instead of CODE_REG for lazy deopt.
__ LoadImmediate(TMP, 0xf1f1f1f1);
__ LoadImmediate(TMP, kZapCodeReg);
__ Push(TMP);
// Return address for "call" to deopt stub.
__ LoadImmediate(RA, 0xe1e1e1e1);
__ LoadImmediate(RA, kZapReturnAddress);
__ lw(CODE_REG, Address(THR, Thread::lazy_deopt_from_return_stub_offset()));
GenerateDeoptimizationSequence(assembler, kLazyDeoptFromReturn);
__ Ret();
}
@ -568,17 +569,19 @@ void StubCode::GenerateDeoptimizeLazyFromReturnStub(Assembler* assembler) {
// V1: stacktrace, must be preserved
void StubCode::GenerateDeoptimizeLazyFromThrowStub(Assembler* assembler) {
// Push zap value instead of CODE_REG for lazy deopt.
__ LoadImmediate(TMP, 0xf1f1f1f1);
__ LoadImmediate(TMP, kZapCodeReg);
__ Push(TMP);
// Return address for "call" to deopt stub.
__ LoadImmediate(RA, 0xe1e1e1e1);
__ LoadImmediate(RA, kZapReturnAddress);
__ lw(CODE_REG, Address(THR, Thread::lazy_deopt_from_throw_stub_offset()));
GenerateDeoptimizationSequence(assembler, kLazyDeoptFromThrow);
__ Ret();
}
void StubCode::GenerateDeoptimizeStub(Assembler* assembler) {
GenerateDeoptimizationSequence(assembler, kEagerDeopt);
__ Ret();
}
@ -2028,6 +2031,26 @@ void StubCode::GenerateRunExceptionHandlerStub(Assembler* assembler) {
}
// Deoptimize a frame on the call stack before rewinding.
// The arguments are stored in the Thread object.
// No result.
void StubCode::GenerateDeoptForRewindStub(Assembler* assembler) {
// Push zap value instead of CODE_REG.
__ LoadImmediate(TMP, kZapCodeReg);
__ Push(TMP);
// Load the deopt pc into RA.
__ lw(RA, Address(THR, Thread::resume_pc_offset()));
GenerateDeoptimizationSequence(assembler, kEagerDeopt);
// After we have deoptimized, jump to the correct frame.
__ EnterStubFrame();
__ CallRuntime(kRewindPostDeoptRuntimeEntry, 0);
__ LeaveStubFrame();
__ break_(0);
}
// Calls to the runtime to optimize the given function.
// T0: function to be reoptimized.
// S4: argument descriptor (preserved).

View file

@ -498,18 +498,19 @@ static void GenerateDeoptimizationSequence(Assembler* assembler,
__ popq(RCX); // Pop return address.
__ addq(RSP, RBX); // Remove materialization arguments.
__ pushq(RCX); // Push return address.
__ ret();
// The caller is responsible for emitting the return instruction.
}
// RAX: result, must be preserved
void StubCode::GenerateDeoptimizeLazyFromReturnStub(Assembler* assembler) {
// Push zap value instead of CODE_REG for lazy deopt.
__ pushq(Immediate(0xf1f1f1f1));
__ pushq(Immediate(kZapCodeReg));
// Return address for "call" to deopt stub.
__ pushq(Immediate(0xe1e1e1e1));
__ pushq(Immediate(kZapReturnAddress));
__ movq(CODE_REG, Address(THR, Thread::lazy_deopt_from_return_stub_offset()));
GenerateDeoptimizationSequence(assembler, kLazyDeoptFromReturn);
__ ret();
}
@ -517,16 +518,18 @@ void StubCode::GenerateDeoptimizeLazyFromReturnStub(Assembler* assembler) {
// RDX: stacktrace, must be preserved
void StubCode::GenerateDeoptimizeLazyFromThrowStub(Assembler* assembler) {
// Push zap value instead of CODE_REG for lazy deopt.
__ pushq(Immediate(0xf1f1f1f1));
__ pushq(Immediate(kZapCodeReg));
// Return address for "call" to deopt stub.
__ pushq(Immediate(0xe1e1e1e1));
__ pushq(Immediate(kZapReturnAddress));
__ movq(CODE_REG, Address(THR, Thread::lazy_deopt_from_throw_stub_offset()));
GenerateDeoptimizationSequence(assembler, kLazyDeoptFromThrow);
__ ret();
}
void StubCode::GenerateDeoptimizeStub(Assembler* assembler) {
GenerateDeoptimizationSequence(assembler, kEagerDeopt);
__ ret();
}
@ -1891,6 +1894,25 @@ void StubCode::GenerateRunExceptionHandlerStub(Assembler* assembler) {
}
// Deoptimize a frame on the call stack before rewinding.
// The arguments are stored in the Thread object.
// No result.
void StubCode::GenerateDeoptForRewindStub(Assembler* assembler) {
// Push zap value instead of CODE_REG.
__ pushq(Immediate(kZapCodeReg));
// Push the deopt pc.
__ pushq(Address(THR, Thread::resume_pc_offset()));
GenerateDeoptimizationSequence(assembler, kEagerDeopt);
// After we have deoptimized, jump to the correct frame.
__ EnterStubFrame();
__ CallRuntime(kRewindPostDeoptRuntimeEntry, 0);
__ LeaveStubFrame();
__ int3();
}
// Calls to the runtime to optimize the given function.
// RDI: function to be reoptimized.
// R10: argument descriptor (preserved).