diff --git a/debugger/assets/styles/app.css b/debugger/assets/styles/app.css index 14d7fca14..0cd1bc64e 100644 --- a/debugger/assets/styles/app.css +++ b/debugger/assets/styles/app.css @@ -265,6 +265,9 @@ body { padding-left: 6px; color: #aaa; } +.debugger-fnview-line-highlight-bg { + background-color: red; +} .debugger-fnview-graphview { order: 2; flex: 0 0 auto; diff --git a/debugger/assets/ui/code/code-tab.html b/debugger/assets/ui/code/code-tab.html index ea43d5694..500d476ba 100644 --- a/debugger/assets/ui/code/code-tab.html +++ b/debugger/assets/ui/code/code-tab.html @@ -1,6 +1,21 @@
- debug header/toolbar/etc +
+ + +
+
+ + +
@@ -18,7 +33,7 @@ diff --git a/debugger/assets/ui/code/code-tab.js b/debugger/assets/ui/code/code-tab.js index 5e746e5be..08bc3b374 100644 --- a/debugger/assets/ui/code/code-tab.js +++ b/debugger/assets/ui/code/code-tab.js @@ -61,6 +61,10 @@ module.controller('CodeTabController', function( }); }; + $scope.showLocation = function() { + // + }; + if (app.session.dataSource) { refresh(); } diff --git a/debugger/assets/ui/code/function-view.js b/debugger/assets/ui/code/function-view.js index 284066bc2..1b848b388 100644 --- a/debugger/assets/ui/code/function-view.js +++ b/debugger/assets/ui/code/function-view.js @@ -16,8 +16,9 @@ var module = angular.module('xe.ui.code.functionView', [ module.controller('FunctionViewController', function( - $rootScope, $scope, app, log, Breakpoint) { + $rootScope, $scope, $location, app, log, Breakpoint) { $scope.codeType = 'source'; + $scope.highlightInfo = null; function refresh() { if (!app.session || !app.session.dataSource) { @@ -36,6 +37,46 @@ module.controller('FunctionViewController', function( $rootScope.$on('refresh', refresh); $scope.$watch('functionAddress', refresh); + function updateHighlight(address) { + if (!$scope.sourceLines || $scope.codeType != 'source') { + return; + } + if ($scope.highlightInfo) { + if ($scope.highlightInfo.address == address) { + return; + } + var oldLine = $scope.highlightInfo.line; + if ($scope.highlightInfo.widget) { + $scope.highlightInfo.widget.clear(); + } + $scope.highlightInfo = null; + updateLine(oldLine); + } + // TODO(benvanik): a better mapping. + var line = -1; + for (var n = 0; n < $scope.sourceLines.length; n++) { + var sourceLine = $scope.sourceLines[n]; + if (sourceLine[0] == 'i' && + sourceLine[1] == address) { + line = n; + break; + } + } + if (line != -1) { + $scope.highlightInfo = { + address: address, + line: line, + widget: null + }; + updateLine(line); + } + }; + $scope.$watch(function() { + return $location.search(); + }, function(search) { + updateHighlight(parseInt(search.a, 16)); + }); + var textArea = document.querySelector('.debugger-fnview-textarea'); $scope.codeMirror = CodeMirror.fromTextArea(textArea, { mode: 'javascript', @@ -94,7 +135,7 @@ module.controller('FunctionViewController', function( el.innerText = hex32(line[2]); cm.setGutterMarker(n, 'debugger-fnview-gutter-code', el); - updateLineIcon(n, line); + updateLine(n); } } }; @@ -140,7 +181,8 @@ module.controller('FunctionViewController', function( }; $scope.$watch('codeType', updateCode); - function updateLineIcon(line, sourceLine) { + function updateLine(line) { + var sourceLine = $scope.sourceLines[line]; var cm = $scope.codeMirror; if (sourceLine[0] != 'i') { return; @@ -160,6 +202,26 @@ module.controller('FunctionViewController', function( el = null; } cm.setGutterMarker(line, 'debugger-fnview-gutter-icon', el); + + var highlightInfo = $scope.highlightInfo; + if (highlightInfo && highlightInfo.line == line) { + /* + if (!highlightInfo.widget) { + el = document.createElement('div'); + el.style.width = '100%'; + el.style.height = '20px'; + el.style.backgroundColor = 'red'; + el.innerHTML = 'hi!'; + highlightInfo.widget = cm.addLineWidget(line, el, { + coverGutter: false + }); + cm.scrollIntoView(line, 50); + } + */ + cm.addLineClass(line, 'background', 'debugger-fnview-line-highlight-bg'); + } else { + cm.removeLineClass(line, 'background'); + } }; function toggleBreakpoint(line, sourceLine, shiftKey) { @@ -174,10 +236,11 @@ module.controller('FunctionViewController', function( } } else { // New breakpoint needed. - breakpoint = app.session.addCodeBreakpoint(address); + breakpoint = app.session.addCodeBreakpoint( + $scope.functionAddress, address); } - updateLineIcon(line, sourceLine); + updateLine(line); }; $scope.codeMirror.on('gutterClick', function( diff --git a/debugger/src/datasources.js b/debugger/src/datasources.js index 3119f0514..79296ee90 100644 --- a/debugger/src/datasources.js +++ b/debugger/src/datasources.js @@ -25,6 +25,7 @@ module.service('Breakpoint', function() { var Breakpoint = function(opt_id) { this.id = opt_id || uuid4(); this.type = Breakpoint.Type.TEMP; + this.fnAddress = 0; this.address = 0; this.enabled = true; }; @@ -35,6 +36,7 @@ module.service('Breakpoint', function() { Breakpoint.fromJSON = function(json) { var breakpoint = new Breakpoint(json.id); breakpoint.type = json.type; + breakpoint.fnAddress = json.fnAddress; breakpoint.address = json.address; breakpoint.enabled = json.enabled; return breakpoint; @@ -43,6 +45,7 @@ module.service('Breakpoint', function() { return { 'id': this.id, 'type': this.type, + 'fnAddress': this.fnAddress, 'address': this.address, 'enabled': this.enabled }; @@ -52,8 +55,9 @@ module.service('Breakpoint', function() { module.service('DataSource', function($q) { - var DataSource = function(source) { + var DataSource = function(source, delegate) { this.source = source; + this.delegate = delegate; this.online = false; this.status = 'disconnected'; }; @@ -129,12 +133,31 @@ module.service('DataSource', function($q) { }); }; + DataSource.prototype.continueExecution = function() { + return this.issue({ + command: 'cpu.continue' + }); + }; + + DataSource.prototype.breakExecution = function() { + return this.issue({ + command: 'cpu.break' + }); + }; + + DataSource.prototype.stepNext = function() { + return this.issue({ + command: 'cpu.step' + }); + }; + return DataSource; }); -module.service('RemoteDataSource', function($q, log, DataSource) { - var RemoteDataSource = function(url) { - DataSource.call(this, url); +module.service('RemoteDataSource', function( + $rootScope, $q, log, DataSource) { + var RemoteDataSource = function(url, delegate) { + DataSource.call(this, url, delegate); this.url = url; this.socket = null; this.nextRequestId_ = 1; @@ -152,22 +175,26 @@ module.service('RemoteDataSource', function($q, log, DataSource) { this.socket = new WebSocket(url, []); this.socket.onopen = (function() { - // TODO(benvanik): handshake + $rootScope.$apply((function() { + // TODO(benvanik): handshake - this.online = true; - this.status = 'connected'; - d.resolve(); + this.online = true; + this.status = 'connected'; + d.resolve(); + }).bind(this)); }).bind(this); this.socket.onclose = (function(e) { - this.online = false; - if (this.status == 'connecting') { - this.status = 'disconnected'; - d.reject(e.code + ' ' + e.reason); - } else { - this.status = 'disconnected'; - log.info('Disconnected'); - } + $rootScope.$apply((function() { + this.online = false; + if (this.status == 'connecting') { + this.status = 'disconnected'; + d.reject(e.code + ' ' + e.reason); + } else { + this.status = 'disconnected'; + log.info('Disconnected'); + } + }).bind(this)); }).bind(this); this.socket.onerror = (function(e) { @@ -175,27 +202,42 @@ module.service('RemoteDataSource', function($q, log, DataSource) { }).bind(this); this.socket.onmessage = (function(e) { - console.log('message', e.data); - var json = JSON.parse(e.data); - if (json.requestId) { - // Response to a previous request. - var request = this.pendingRequests_[json.requestId]; - if (request) { - delete this.pendingRequests_[json.requestId]; - if (json.status) { - request.deferred.resolve(json.result); - } else { - request.deferred.reject(json.result); - } - } - } else { - // Notification. - } + $rootScope.$apply((function() { + this.socketMessage(e); + }).bind(this)); }).bind(this); return d.promise; }; + RemoteDataSource.prototype.socketMessage = function(e) { + console.log('message', e.data); + var json = JSON.parse(e.data); + if (json.requestId) { + // Response to a previous request. + var request = this.pendingRequests_[json.requestId]; + if (request) { + delete this.pendingRequests_[json.requestId]; + if (json.status) { + request.deferred.resolve(json.result); + } else { + request.deferred.reject(json.result); + } + } + } else { + // Notification. + switch (json.type) { + case 'breakpoint': + this.delegate.onBreakpointHit( + json.breakpointId, json.threadId); + break; + default: + log.error('Unknown notification type: ' + json.type); + break; + } + } + }; + RemoteDataSource.prototype.dispose = function() { this.pendingRequests_ = {}; this.online = false; @@ -221,8 +263,8 @@ module.service('RemoteDataSource', function($q, log, DataSource) { module.service('FileDataSource', function($q, DataSource) { - var FileDataSource = function(file) { - DataSource.call(this, file.name); + var FileDataSource = function(file, delegate) { + DataSource.call(this, file.name, delegate); this.file = file; }; inherits(FileDataSource, DataSource); diff --git a/debugger/src/router.js b/debugger/src/router.js index c47405504..e5427ab3b 100644 --- a/debugger/src/router.js +++ b/debugger/src/router.js @@ -98,13 +98,11 @@ module.config(function($stateProvider, $urlRouterProvider) { onExit: function() {} }); $stateProvider.state('session.code.function', { - url: '/:module/:function', + url: '/:function?a', templateUrl: 'assets/ui/code/function-view.html', controller: function($scope, $stateParams) { - $scope.moduleName = $stateParams.module; $scope.functionAddress = parseInt($stateParams.function, 16); - $scope.$emit('xxx'); - $scope.$broadcast('yyy'); + $scope.highlightAddress = parseInt($stateParams.a, 16); }, onEnter: function() {}, onExit: function() {} diff --git a/debugger/src/session.js b/debugger/src/session.js index ed92fdf00..3a73c56b2 100644 --- a/debugger/src/session.js +++ b/debugger/src/session.js @@ -13,15 +13,18 @@ var module = angular.module('xe.session', []); module.service('Session', function( - $rootScope, $q, $http, log, + $rootScope, $q, $http, $state, log, Breakpoint, FileDataSource, RemoteDataSource) { var Session = function(id, opt_dataSource) { this.id = id; this.breakpoints = {}; + this.breakpointsById = {}; this.dataSource = opt_dataSource || null; + this.paused = false; + this.loadState(); }; @@ -31,7 +34,11 @@ module.service('Session', function( }; Session.prototype.loadState = function() { - var json = JSON.parse(window.localStorage[this.id]); + var raw = window.localStorage[this.id]; + if (!raw) { + return; + } + var json = JSON.parse(raw); if (!json) { return; } @@ -40,8 +47,9 @@ module.service('Session', function( this.breakpoints = {}; for (var n = 0; n < breakpointList.length; n++) { var breakpointJson = breakpointList[n]; - this.breakpoints[breakpointJson.address] = - Breakpoint.fromJSON(breakpointJson); + var breakpoint = Breakpoint.fromJSON(breakpointJson); + this.breakpoints[breakpointJson.address] = breakpoint; + this.breakpointsById[breakpoint.id] = breakpoint; } }; @@ -50,8 +58,8 @@ module.service('Session', function( id: this.id, breakpoints: [] }; - for (var key in this.breakpoints) { - var breakpoint = this.breakpoints[key]; + for (var key in this.breakpointsById) { + var breakpoint = this.breakpointsById[key]; if (breakpoint.type != Breakpoint.TEMP) { json.breakpoints.push(breakpoint.toJSON()); } @@ -97,7 +105,7 @@ module.service('Session', function( var d = $q.defer(); - var dataSource = new RemoteDataSource(url); + var dataSource = new RemoteDataSource(url, this); var p = dataSource.open(); p.then((function() { log.info('Connected!'); @@ -167,6 +175,7 @@ module.service('Session', function( Session.prototype.addBreakpoint = function(breakpoint) { this.breakpoints[breakpoint.address] = breakpoint; + this.breakpointsById[breakpoint.id] = breakpoint; if (this.dataSource) { this.dataSource.addBreakpoint(breakpoint); } @@ -174,17 +183,19 @@ module.service('Session', function( return breakpoint; }; - Session.prototype.addTempBreakpoint = function(address) { + Session.prototype.addTempBreakpoint = function(fnAddress, address) { var breakpoint = new Breakpoint(); breakpoint.type = Breakpoint.Type.TEMP; + breakpoint.fnAddress = fnAddress; breakpoint.address = address; breakpoint.enabled = true; return this.addBreakpoint(breakpoint); }; - Session.prototype.addCodeBreakpoint = function(address) { + Session.prototype.addCodeBreakpoint = function(fnAddress, address) { var breakpoint = new Breakpoint(); breakpoint.type = Breakpoint.Type.CODE; + breakpoint.fnAddress = fnAddress; breakpoint.address = address; breakpoint.enabled = true; return this.addBreakpoint(breakpoint); @@ -192,6 +203,7 @@ module.service('Session', function( Session.prototype.removeBreakpoint = function(breakpoint) { delete this.breakpoints[breakpoint.address]; + delete this.breakpointsById[breakpoint.id]; if (this.dataSource) { this.dataSource.removeBreakpoint(breakpoint.id); } @@ -211,5 +223,60 @@ module.service('Session', function( this.saveState(); }; + Session.prototype.onBreakpointHit = function(breakpointId, threadId) { + var breakpoint = this.breakpointsById[breakpointId]; + var thread = null; // TODO + if (!breakpoint) { + log.error('Breakpoint hit but not found'); + return; + } + + // Now paused! + this.paused = true; + + $state.go('session.code.function', { + 'function': breakpoint.fnAddress.toString(16).toUpperCase(), + 'a': breakpoint.address.toString(16).toUpperCase() + }, { + notify: false, + reloadOnSearch: false + }); + + // + log.info('breakpoint!!'); + }; + + Session.prototype.continueExecution = function() { + if (!this.dataSource) { + return; + } + this.paused = false; + this.dataSource.continueExecution().then(function() { + }, function(e) { + log.error('Unable to continue: ' + e); + }); + }; + + Session.prototype.breakExecution = function() { + if (!this.dataSource) { + return; + } + this.dataSource.breakExecution().then(function() { + }, function(e) { + log.error('Unable to break: ' + e); + }); + }; + + Session.prototype.stepNext = function() { + if (!this.dataSource) { + return; + } + this.paused = false; + this.dataSource.breakExecution().then(function() { + }, function(e) { + log.error('Unable to step: ' + e); + }); + }; + return Session; });