Threads displayed.

This commit is contained in:
Ben Vanik 2013-12-25 17:31:53 -08:00
parent d368e0cb74
commit a1da55a006
19 changed files with 468 additions and 131 deletions

View File

@ -10,6 +10,9 @@
.full-width { .full-width {
width: 100%; width: 100%;
} }
.left-align {
text-align: left;
}
body { body {
margin: 0; margin: 0;
@ -194,6 +197,7 @@ body {
position: relative; position: relative;
border-left: 2px solid #ddd; border-left: 2px solid #ddd;
border-right: 2px solid #ddd; border-right: 2px solid #ddd;
min-width: 720px;
} }
.debugger-fnview { .debugger-fnview {
position: absolute; position: absolute;
@ -215,6 +219,7 @@ body {
order: 1; order: 1;
flex: 1 1 auto; flex: 1 1 auto;
padding: 5px; padding: 5px;
line-height: 1;
} }
.debugger-fnview-header-name { .debugger-fnview-header-name {
font-size: 21px; font-size: 21px;
@ -227,6 +232,7 @@ body {
order: 2; order: 2;
flex: 0 0 auto; flex: 0 0 auto;
padding: 5px; padding: 5px;
margin-top: 3px;
} }
.debugger-fnview-body { .debugger-fnview-body {
order: 2; order: 2;
@ -300,20 +306,37 @@ body {
flex: 0 0 auto; flex: 0 0 auto;
display: flex; display: flex;
flex-flow: column nowrap; flex-flow: column nowrap;
min-width: 200px; width: 40%;
} }
.debugger-tools-threads { .debugger-tools-threads {
order: 1; order: 1;
flex: 0 0 auto; flex: 0 0 auto;
padding: 5px; padding: 5px;
} }
.debugger-tools-threads {
order: 1;
flex: 0 0 auto;
padding: 5px;
display: flex;
flex-flow: row nowrap;
}
.debugger-tools-threads-header-left {
order: 1;
flex: 1 1 auto;
padding-right: 5px;
}
.debugger-tools-threads-header-right {
order: 2;
flex: 0 0 auto;
}
.debugger-tools-callstack { .debugger-tools-callstack {
order: 2; order: 2;
flex: 1 1 auto; flex: 0 0 auto;
padding: 5px; padding: 5px;
border-top: 1px solid #ddd; border-top: 1px solid #ddd;
border-bottom: 1px solid #ddd; border-bottom: 1px solid #ddd;
position: relative; position: relative;
height: 100px;
} }
.debugger-tools-registers { .debugger-tools-registers {
order: 3; order: 3;

View File

@ -1,18 +1,18 @@
<div class="debugger-main" ng-controller="CodeTabController"> <div class="debugger-main" ng-controller="CodeTabController">
<div class="debugger-header"> <div class="debugger-header">
<div class="btn-group btn-group-sm"> <div class="btn-group btn-group-sm">
<button type="button" class="btn btn-success" ng-click="app.session.continueExecution()" ng-disabled="!app.session.dataSource || !app.session.paused"> <button type="button" class="btn btn-success" ng-click="app.session.continueExecution()" ng-disabled="!app.session.paused">
<span class="glyphicon glyphicon-play"></span> <span class="glyphicon glyphicon-play"></span>
</button> </button>
<button type="button" class="btn btn-danger" ng-click="app.session.breakExecution()" ng-disabled="!app.session.dataSource || app.session.paused"> <button type="button" class="btn btn-danger" ng-click="app.session.breakExecution()" ng-disabled="app.session.paused">
<span class="glyphicon glyphicon-pause"></span> <span class="glyphicon glyphicon-pause"></span>
</button> </button>
</div> </div>
<div class="btn-group btn-group-sm"> <div class="btn-group btn-group-sm">
<button type="button" class="btn btn-default" ng-click="showLocation()" ng-disabled="!app.session.dataSource || !app.session.paused"> <button type="button" class="btn btn-default" ng-click="showLocation()" ng-disabled="!app.session.paused">
<span class="glyphicon glyphicon glyphicon-arrow-right"></span> <span class="glyphicon glyphicon glyphicon-arrow-right"></span>
</button> </button>
<button type="button" class="btn btn-default" ng-click="app.session.stepNext()" ng-disabled="!app.session.dataSource || !app.session.paused"> <button type="button" class="btn btn-default" ng-click="app.session.stepNext()" ng-disabled="!app.session.paused">
<span class="glyphicon glyphicon-step-forward"></span> <span class="glyphicon glyphicon-step-forward"></span>
</button> </button>
</div> </div>
@ -21,7 +21,7 @@
<div class="debugger-fnlist"> <div class="debugger-fnlist">
<div class="debugger-fnlist-header"> <div class="debugger-fnlist-header">
<div class="debugger-fnlist-header-left btn-group btn-group-xs full-width"> <div class="debugger-fnlist-header-left btn-group btn-group-xs full-width">
<button type="button" class="btn btn-default dropdown-toggle full-width" data-toggle="dropdown"> <button type="button" class="btn btn-default dropdown-toggle left-align full-width" data-toggle="dropdown">
{{selectedModule.name}} <span class="caret"></span> {{selectedModule.name}} <span class="caret"></span>
</button> </button>
<ul class="dropdown-menu" role="menu"> <ul class="dropdown-menu" role="menu">
@ -29,7 +29,7 @@
</ul> </ul>
</div> </div>
<div class="debugger-fnlist-header-right btn-group btn-group-xs"> <div class="debugger-fnlist-header-right btn-group btn-group-xs">
<button type="button" class="btn btn-default" ng-click="showModuleInfo(selectedModule)"> <button type="button" class="btn btn-default" ng-click="showModuleInfo()">
Info Info
</button> </button>
</div> </div>
@ -52,15 +52,21 @@
<div class="debugger-fnview-outer" ui-view></div> <div class="debugger-fnview-outer" ui-view></div>
<div class="debugger-tools"> <div class="debugger-tools">
<div class="debugger-tools-threads"> <div class="debugger-tools-threads">
<div class="btn-group btn-group-xs full-width"> <div class="debugger-tools-threads-header-left btn-group btn-group-xs full-width">
<button type="button" class="btn btn-default dropdown-toggle full-width" data-toggle="dropdown"> <button type="button" class="btn btn-default left-align dropdown-toggle full-width" data-toggle="dropdown">
thread 0 <span class="caret"></span> Thread {{app.session.activeThread.id}}: {{app.session.activeThread.name}} <span class="caret"></span>
</button> </button>
<ul class="dropdown-menu" role="menu"> <ul class="dropdown-menu" role="menu">
<li><a href="#">thread 1</a></li> <li ng-repeat="thread in app.session.state.threadList | orderBy:'id'">
<li><a href="#">thread 2</a></li> <a href="" ng-click="app.session.activeThread = thread;">Thread {{thread.id}}: {{thread.name}}</a>
</li>
</ul> </ul>
</div> </div>
<div class="debugger-tools-threads-header-right btn-group btn-group-xs">
<button type="button" class="btn btn-default" ng-click="showThreadInfo()">
Info
</button>
</div>
</div> </div>
<div class="debugger-tools-callstack"> <div class="debugger-tools-callstack">
callstack callstack

View File

@ -23,24 +23,19 @@ module.controller('CodeTabController', function(
$scope.functionList = []; $scope.functionList = [];
function refresh() { function refresh() {
if (!app.session || !app.session.dataSource) { if (!app.session) {
$scope.moduleList = []; $scope.moduleList = [];
return; return;
} }
var dataSource = app.session.dataSource;
dataSource.getModuleList().then(function(list) { $scope.moduleList = app.session.state.getModuleList();
$scope.moduleList = list; if (!$scope.selectedModule) {
if (!$scope.selectedModule) { if ($scope.moduleList.length) {
if (list.length) { $scope.selectModule($scope.moduleList[0]);
$scope.selectModule(list[0]);
}
} else {
$scope.selectModule($scope.selectedModule);
} }
}, function(e) { } else {
log.error('Unable to fetch module list'); $scope.selectModule($scope.selectedModule);
}); }
console.log('refresh'); console.log('refresh');
}; };
@ -54,15 +49,10 @@ module.controller('CodeTabController', function(
$scope.functionList = []; $scope.functionList = [];
} }
var dataSource = app.session.dataSource; $scope.functionList = app.session.state.getFunctionList(module.name);
dataSource.getFunctionList(module.name).then(function(list) {
$scope.functionList = list;
}, function(e) {
log.error('Unable to fetch function list');
});
}; };
$scope.showModuleInfo = function(module) { $scope.showModuleInfo = function() {
var modalInstance = $modal.open({ var modalInstance = $modal.open({
templateUrl: 'assets/ui/code/module-info.html', templateUrl: 'assets/ui/code/module-info.html',
controller: 'ModuleInfoController', controller: 'ModuleInfoController',
@ -72,13 +62,23 @@ module.controller('CodeTabController', function(
return $scope.selectedModule.name; return $scope.selectedModule.name;
}, },
moduleInfo: function() { moduleInfo: function() {
return app.session.dataSource.getModule( return app.session.state.getModule(
$scope.selectedModule.name); $scope.selectedModule.name);
} }
} }
}); });
modalInstance.result.then(function() { };
}, function () {
$scope.showThreadInfo = function() {
var modalInstance = $modal.open({
templateUrl: 'assets/ui/code/thread-info.html',
controller: 'ThreadInfoController',
windowClass: 'debugger-module-info',
resolve: {
thread: function() {
return app.session.activeThread;
}
}
}); });
}; };

View File

@ -1,7 +1,7 @@
<div class="debugger-fnview" ng-controller="FunctionViewController"> <div class="debugger-fnview" ng-controller="FunctionViewController">
<div class="debugger-fnview-header"> <div class="debugger-fnview-header">
<div class="debugger-fnview-header-left"> <div class="debugger-fnview-header-left">
<span class="debugger-fnview-header-name" ng-bind="fn.name"></span> <span class="debugger-fnview-header-name" ng-bind="fn.name"></span><br/>
<span class="debugger-fnview-header-address">(0x{{fn.startAddress | hex32}}-0x{{fn.endAddress | hex32}})</span> <span class="debugger-fnview-header-address">(0x{{fn.startAddress | hex32}}-0x{{fn.endAddress | hex32}})</span>
</div> </div>
<div class="debugger-fnview-header-right"> <div class="debugger-fnview-header-right">
@ -14,6 +14,7 @@
<button type="button" class="btn btn-default" ng-model="codeType" btn-radio="'lir'">LIR</button> <button type="button" class="btn btn-default" ng-model="codeType" btn-radio="'lir'">LIR</button>
<button type="button" class="btn btn-default" ng-model="codeType" btn-radio="'machineCode'">MC</button> <button type="button" class="btn btn-default" ng-model="codeType" btn-radio="'machineCode'">MC</button>
</div> </div>
<!--
<div class="btn-group btn-group-sm"> <div class="btn-group btn-group-sm">
<button type="button" class="btn btn-default">1</button> <button type="button" class="btn btn-default">1</button>
<button type="button" class="btn btn-default">2</button> <button type="button" class="btn btn-default">2</button>
@ -28,6 +29,7 @@
</ul> </ul>
</div> </div>
</div> </div>
-->
</div> </div>
</div> </div>
</div> </div>

View File

@ -21,17 +21,15 @@ module.controller('FunctionViewController', function(
$scope.highlightInfo = null; $scope.highlightInfo = null;
function refresh() { function refresh() {
if (!app.session || !app.session.dataSource) { if (!app.session) {
$scope.fn = null; $scope.fn = null;
return; return;
} }
var dataSource = app.session.dataSource; app.session.state.fetchFunction($scope.functionAddress).then(function(fn) {
dataSource.getFunction($scope.functionAddress).then(function(fn) {
$scope.fn = fn; $scope.fn = fn;
updateCode(); updateCode();
}, function(e) { }, function(e) {
log.error('Unable to fetch function'); log.error('Unable to fetch function.');
}); });
}; };
$rootScope.$on('refresh', refresh); $rootScope.$on('refresh', refresh);

View File

@ -0,0 +1,35 @@
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true" ng-click="close()">&times;</button>
<h4 class="modal-title">Thread {{ thread.id }}: {{ thread.name }}</h4>
</div>
<div class="modal-body">
<table class="table debugger-module-info-outer-table">
<tbody>
<tr>
<td>Thread</td>
<td>
<table class="table table-hover table-condensed debugger-module-info-inner-table">
<tbody>
<tr>
<td>Stack Address</td>
<td><a xe-memref="{{ thread.stackAddress | hex32 }}" ng-click="$close()">{{ thread.stackAddress | hex32 }}</a></td>
</tr>
<tr>
<td>Stack Size</td>
<td>{{ thread.stackSize }}b</td>
</tr>
<tr>
<td>State Address</td>
<td><a xe-memref="{{ thread.threadStateAddress | hex32 }}" ng-click="$close()">{{ thread.threadStateAddress | hex32 }}</a></td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>

View File

@ -0,0 +1,51 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2013 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
'use strict';
var module = angular.module('xe.ui.code.threadInfo', [
'ui.bootstrap',
'xe.log',
'xe.session'
]);
module.controller('ThreadInfoController', function(
$rootScope, $scope, $modal, log, thread) {
$scope.thread = thread;
$scope.headerSort = {
column: 'key',
reverse: false
};
$scope.sectionSort = {
column: 'startAddress',
reverse: false
};
$scope.staticLibrarySort = {
column: 'name',
reverse: false
};
$scope.importSort = {
column: 'ordinal',
reverse: false
};
$scope.changeSort = function(sort, column) {
if (sort.column == column) {
sort.reverse = !sort.reverse;
} else {
sort.column = column;
sort.reverse = false;
}
};
$scope.close = function() {
$scope.$close(null);
};
});

View File

@ -35,6 +35,7 @@
<script src="assets/ui/code/code-tab.js"></script> <script src="assets/ui/code/code-tab.js"></script>
<script src="assets/ui/code/function-view.js"></script> <script src="assets/ui/code/function-view.js"></script>
<script src="assets/ui/code/module-info.js"></script> <script src="assets/ui/code/module-info.js"></script>
<script src="assets/ui/code/thread-info.js"></script>
<script src="debugger.js"></script> <script src="debugger.js"></script>
</body> </body>

View File

@ -21,6 +21,7 @@ var module = angular.module('app', [
'xe.ui.code', 'xe.ui.code',
'xe.ui.code.functionView', 'xe.ui.code.functionView',
'xe.ui.code.moduleInfo', 'xe.ui.code.moduleInfo',
'xe.ui.code.threadInfo',
'xe.ui.console', 'xe.ui.console',
'xe.ui.navbar' 'xe.ui.navbar'
]); ]);

View File

@ -61,12 +61,6 @@ module.service('DataSource', function($q) {
this.delegate = delegate; this.delegate = delegate;
this.online = false; this.online = false;
this.status = 'disconnected'; this.status = 'disconnected';
this.cache_ = {
modules: {},
moduleFunctionLists: {},
functions: {}
};
}; };
inherits(DataSource, EventEmitter); inherits(DataSource, EventEmitter);
DataSource.prototype.open = function() {}; DataSource.prototype.open = function() {};
@ -86,69 +80,35 @@ module.service('DataSource', function($q) {
}; };
DataSource.prototype.getModule = function(moduleName) { DataSource.prototype.getModule = function(moduleName) {
var d = $q.defer(); return this.issue({
var cached = this.cache_.modules[moduleName];
if (cached) {
d.resolve(cached);
return d.promise;
}
this.issue({
command: 'cpu.get_module', command: 'cpu.get_module',
module: moduleName module: moduleName
}).then((function(result) { });
this.cache_.modules[moduleName] = result;
d.resolve(result);
}).bind(this), (function(e) {
d.reject(e);
}).bind(this));
return d.promise;
}; };
DataSource.prototype.getFunctionList = function(moduleName) { DataSource.prototype.getFunctionList = function(moduleName, opt_since) {
var d = $q.defer(); return this.issue({
var cached = this.cache_.moduleFunctionLists[moduleName];
this.issue({
command: 'cpu.get_function_list', command: 'cpu.get_function_list',
module: moduleName, module: moduleName,
since: cached ? cached.version : 0 since: opt_since || 0
}).then((function(result) { });
if (cached) {
cached.version = result.version;
for (var n = 0; n < result.list.length; n++) {
cached.list.push(result.list[n]);
}
} else {
cached = this.cache_.moduleFunctionLists[moduleName] = {
version: result.version,
list: result.list
};
}
d.resolve(cached.list);
}).bind(this), (function(e) {
d.reject(e);
}).bind(this));
return d.promise;
}; };
DataSource.prototype.getFunction = function(address) { DataSource.prototype.getFunction = function(address) {
var d = $q.defer(); return this.issue({
var cached = this.cache_.functions[address];
if (cached) {
d.resolve(cached);
return d.promise;
}
this.issue({
command: 'cpu.get_function', command: 'cpu.get_function',
address: address address: address
}).then((function(result) { });
this.cache_.functions[address] = result;
d.resolve(result);
}).bind(this), (function(e) {
d.reject(e);
}).bind(this));
return d.promise;
}; };
DataSource.prototype.getThreadStates = function() {
return this.issue({
command: 'cpu.get_thread_states'
});
};
// set registers/etc?
DataSource.prototype.addBreakpoint = function(breakpoint) { DataSource.prototype.addBreakpoint = function(breakpoint) {
return this.addBreakpoints([breakpoint]); return this.addBreakpoints([breakpoint]);
}; };

View File

@ -15,6 +15,154 @@ var module = angular.module('xe.session', []);
module.service('Session', function( module.service('Session', function(
$rootScope, $q, $http, $state, log, $rootScope, $q, $http, $state, log,
Breakpoint, FileDataSource, RemoteDataSource) { Breakpoint, FileDataSource, RemoteDataSource) {
var State = function(session) {
this.session = session;
this.clear();
};
State.prototype.clear = function() {
this.cache_ = {
moduleList: [],
modules: {},
moduleFunctionLists: {},
functions: {},
threadStates: {},
threadList: []
};
};
State.prototype.sync = function() {
var cache = this.cache_;
var dataSource = this.session.dataSource;
if (!dataSource) {
var d = $q.defer();
d.resolve();
return d.promise;
}
var ps = [];
// Update all modules/functions.
var modulesUpdated = $q.defer();
ps.push(modulesUpdated.promise);
dataSource.getModuleList().then((function(list) {
cache.moduleList = list;
// Update module information.
var moduleFetches = [];
list.forEach(function(module) {
if (cache.modules[module.name]) {
return;
}
var moduleFetch = $q.defer();
moduleFetches.push(moduleFetch.promise);
dataSource.getModule(module.name).
then(function(moduleInfo) {
cache.modules[module.name] = moduleInfo;
moduleFetch.resolve();
}, function(e) {
moduleFetch.reject(e);
});
});
// Update function lists for each module.
list.forEach(function(module) {
var cached = cache.moduleFunctionLists[module.name];
var functionListFetch = $q.defer();
moduleFetches.push(functionListFetch);
dataSource.getFunctionList(module.name, cached ? cached.version : 0).
then(function(result) {
if (cached) {
cached.version = result.version;
for (var n = 0; n < result.list.length; n++) {
cached.list.push(result.list[n]);
}
} else {
cached = cache.moduleFunctionLists[module.name] = {
version: result.version,
list: result.list
};
}
functionListFetch.resolve();
}, function(e) {
functionListFetch.reject(e);
});
});
$q.all(moduleFetches).then(function() {
modulesUpdated.resolve();
}, function(e) {
modulesUpdated.reject();
});
}).bind(this), function(e) {
modulesUpdated.reject(e);
});
// Update threads/thread states.
var threadsUpdated = $q.defer();
ps.push(threadsUpdated.promise);
dataSource.getThreadStates().then((function(states) {
cache.threadStates = states;
cache.threadList = [];
for (var threadId in states) {
cache.threadList.push(states[threadId]);
}
threadsUpdated.resolve();
}).bind(this), function(e) {
threadsUpdated.reject(e);
});
var d = $q.defer();
$q.all(ps).then((function() {
d.resolve();
}).bind(this), (function(e) {
d.reject(e);
}).bind(this));
return d.promise;
};
State.prototype.getModuleList = function() {
return this.cache_.moduleList;
};
State.prototype.getModule = function(moduleName) {
return this.cache_.modules[moduleName] || null;
};
State.prototype.getFunctionList = function(moduleName) {
var cached = this.cache_.moduleFunctionLists[moduleName];
return cached ? cached.list : [];
};
State.prototype.getFunction = function(address) {
return this.cache_.functions[address] || null;
};
State.prototype.fetchFunction = function(address) {
var cache = this.cache_;
var d = $q.defer();
var cached = cache.functions[address];
if (cached) {
d.resolve(cached);
return d.promise;
}
var dataSource = this.session.dataSource;
if (!dataSource) {
d.reject(new Error('Not online.'));
return d.promise;
}
dataSource.getFunction(address).then(function(result) {
cache.functions[address] = result;
d.resolve(result);
}, function(e) {
d.reject(e);
});
return d.promise;
}
Object.defineProperty(State.prototype, 'threadList', {
get: function() {
return this.cache_.threadList || [];
}
});
State.prototype.getThreadStates = function() {
return this.cache_.threadStates || {};
};
State.prototype.getThreadState = function(threadId) {
return this.cache_.threadStates[threadId] || null;
};
var Session = function(id, opt_dataSource) { var Session = function(id, opt_dataSource) {
this.id = id; this.id = id;
@ -22,6 +170,9 @@ module.service('Session', function(
this.breakpointsById = {}; this.breakpointsById = {};
this.dataSource = opt_dataSource || null; this.dataSource = opt_dataSource || null;
this.state = new State(this);
this.activeThread = null;
this.paused = false; this.paused = false;
@ -144,6 +295,8 @@ module.service('Session', function(
d.resolve(); d.resolve();
return d.promise; return d.promise;
} }
this.state.clear();
this.activeThread = null;
this.dataSource = dataSource; this.dataSource = dataSource;
this.dataSource.on('online', function() { this.dataSource.on('online', function() {
@ -165,28 +318,31 @@ module.service('Session', function(
} }
ps.push(this.dataSource.addBreakpoints(breakpointList)); ps.push(this.dataSource.addBreakpoints(breakpointList));
// Fetch main module info. // Perform a full sync.
// We need this for entry point info/etc. var syncDeferred = $q.defer();
var moduleInfoDeferred = $q.defer(); ps.push(syncDeferred.promise);
this.dataSource.getModuleList().then(function(moduleList) { this.state.sync().then((function() {
// Put a breakpoint at the entry point.
// TODO(benvanik): make an option?
var moduleList = this.state.getModuleList();
if (!moduleList.length) { if (!moduleList.length) {
// Uh. log.error('No modules found!');
log.error('No modules loaded on startup!'); syncDeferred.reject(new Error('No modules found.'));
moduleInfoDeferred.reject(new Error('No modules found'));
return; return;
} }
moduleList.forEach(function(module) { var moduleInfo = this.state.getModule(moduleList[0].name);
dataSource.getModule(module.name).then(function(moduleInfo) { if (!moduleInfo) {
// Put a breakpoint at the entry point. log.error('Main module not found!');
var entryPoint = moduleInfo.exeEntryPoint; syncDeferred.reject(new Error('Main module not found.'));
self.addTempBreakpoint(entryPoint, entryPoint); return;
moduleInfoDeferred.resolve(); }
}); var entryPoint = moduleInfo.exeEntryPoint;
}); self.addTempBreakpoint(entryPoint, entryPoint);
}, function(e) {
moduleInfoDeferred.reject(e); syncDeferred.resolve();
}); }).bind(this), (function(e) {
ps.push(moduleInfoDeferred.promise); syncDeferred.reject(e);
}).bind(this));
$q.all(ps).then((function() { $q.all(ps).then((function() {
this.dataSource.makeReady().then(function() { this.dataSource.makeReady().then(function() {
@ -258,16 +414,25 @@ module.service('Session', function(
// Now paused! // Now paused!
this.paused = true; this.paused = true;
$rootScope.$emit('refresh'); this.state.sync().then((function() {
// Switch active thread.
var thread = this.state.getThreadState(threadId);
this.activeThread = thread;
if (breakpointId) { if (!breakpointId) {
var breakpoint = this.breakpointsById[breakpointId]; // Just a general pause.
var thread = null; // TODO log.info('Execution paused.');
if (!breakpoint) {
log.error('Breakpoint hit but not found');
return; return;
} }
var breakpoint = this.breakpointsById[breakpointId];
if (!breakpoint) {
log.error('Breakpoint hit but not found.');
return;
}
// TODO(benvanik): stash current breakpoint/thread/etc.
log.info('Breakpoint hit at 0x' + log.info('Breakpoint hit at 0x' +
breakpoint.address.toString(16).toUpperCase() + '.'); breakpoint.address.toString(16).toUpperCase() + '.');
@ -279,10 +444,9 @@ module.service('Session', function(
notify: true, notify: true,
reloadOnSearch: false reloadOnSearch: false
}); });
} else { }).bind(this), (function(e) {
// Just a general pause. log.error('Unable to synchronize state,');
log.info('Execution paused.'); }).bind(this));
}
}; };
Session.prototype.continueExecution = function() { Session.prototype.continueExecution = function() {

View File

@ -76,6 +76,15 @@ int Debugger::ResumeAllThreads(bool force) {
return result; return result;
} }
void Debugger::ForEachThread(std::function<void(ThreadState*)> callback) {
LockMutex(threads_lock_);
for (auto it = threads_.begin(); it != threads_.end(); ++it) {
ThreadState* thread_state = it->second;
callback(thread_state);
}
UnlockMutex(threads_lock_);
}
int Debugger::AddBreakpoint(Breakpoint* breakpoint) { int Debugger::AddBreakpoint(Breakpoint* breakpoint) {
// Add to breakpoints map. // Add to breakpoints map.
LockMutex(breakpoints_lock_); LockMutex(breakpoints_lock_);

View File

@ -86,6 +86,8 @@ public:
int ResumeThread(uint32_t thread_id); int ResumeThread(uint32_t thread_id);
int ResumeAllThreads(bool force = false); int ResumeAllThreads(bool force = false);
void ForEachThread(std::function<void (ThreadState*)> callback);
int AddBreakpoint(Breakpoint* breakpoint); int AddBreakpoint(Breakpoint* breakpoint);
int RemoveBreakpoint(Breakpoint* breakpoint); int RemoveBreakpoint(Breakpoint* breakpoint);
void FindBreakpoints( void FindBreakpoints(

View File

@ -22,7 +22,7 @@ __declspec(thread) ThreadState* thread_state_ = NULL;
ThreadState::ThreadState(Runtime* runtime, uint32_t thread_id) : ThreadState::ThreadState(Runtime* runtime, uint32_t thread_id) :
runtime_(runtime), memory_(runtime->memory()), runtime_(runtime), memory_(runtime->memory()),
thread_id_(thread_id), thread_id_(thread_id), name_(0),
backend_data_(0), raw_context_(0) { backend_data_(0), raw_context_(0) {
if (thread_id_ == UINT_MAX) { if (thread_id_ == UINT_MAX) {
// System thread. Assign the system thread ID with a high bit // System thread. Assign the system thread ID with a high bit
@ -40,6 +40,19 @@ ThreadState::~ThreadState() {
if (thread_state_ == this) { if (thread_state_ == this) {
thread_state_ = NULL; thread_state_ = NULL;
} }
if (name_) {
xe_free(name_);
}
}
void ThreadState::set_name(const char* value) {
if (value == name_) {
return;
}
if (name_) {
xe_free(name_);
}
name_ = xestrdupa(value);
} }
void ThreadState::Bind(ThreadState* thread_state) { void ThreadState::Bind(ThreadState* thread_state) {

View File

@ -29,6 +29,8 @@ public:
Runtime* runtime() const { return runtime_; } Runtime* runtime() const { return runtime_; }
Memory* memory() const { return memory_; } Memory* memory() const { return memory_; }
uint32_t thread_id() const { return thread_id_; } uint32_t thread_id() const { return thread_id_; }
const char* name() const { return name_; }
void set_name(const char* value);
void* backend_data() const { return backend_data_; } void* backend_data() const { return backend_data_; }
void* raw_context() const { return raw_context_; } void* raw_context() const { return raw_context_; }
@ -45,6 +47,7 @@ protected:
Runtime* runtime_; Runtime* runtime_;
Memory* memory_; Memory* memory_;
uint32_t thread_id_; uint32_t thread_id_;
char* name_;
void* backend_data_; void* backend_data_;
void* raw_context_; void* raw_context_;
}; };

View File

@ -31,6 +31,7 @@ public:
const char* GetString() const; const char* GetString() const;
char* ToString(); char* ToString();
char* EncodeBase64();
private: private:
char* buffer_; char* buffer_;

View File

@ -330,6 +330,17 @@ json_t* Processor::OnDebugRequest(
} }
uint64_t address = (uint64_t)json_number_value(address_json); uint64_t address = (uint64_t)json_number_value(address_json);
return DumpFunction(address, succeeded); return DumpFunction(address, succeeded);
} else if (xestrcmpa(command, "get_thread_states") == 0) {
json_t* result = json_object();
runtime_->debugger()->ForEachThread([&](ThreadState* thread_state) {
json_t* state_json = DumpThreadState((XenonThreadState*)thread_state);
char threadIdString[32];
xesnprintfa(
threadIdString, XECOUNT(threadIdString),
"%d", thread_state->thread_id());
json_object_set_new(result, threadIdString, state_json);
});
return result;
} else if (xestrcmpa(command, "add_breakpoints") == 0) { } else if (xestrcmpa(command, "add_breakpoints") == 0) {
// breakpoints: [{}] // breakpoints: [{}]
json_t* breakpoints_json = json_object_get(request, "breakpoints"); json_t* breakpoints_json = json_object_get(request, "breakpoints");
@ -699,6 +710,61 @@ json_t* Processor::DumpFunction(uint64_t address, bool& succeeded) {
return fn_json; return fn_json;
} }
json_t* Processor::DumpThreadState(XenonThreadState* thread_state) {
json_t* result = json_object();
json_object_set_integer_new(result, "id", thread_state->thread_id());
json_object_set_string_new(result, "name", thread_state->name());
json_object_set_integer_new(
result, "stackAddress", thread_state->stack_address());
json_object_set_integer_new(
result, "stackSize", thread_state->stack_size());
json_object_set_integer_new(
result, "threadStateAddress", thread_state->thread_state_address());
json_t* context_json = json_object();
auto context = thread_state->context();
json_object_set_new(
context_json, "lr", json_integer(context->lr));
json_object_set_new(
context_json, "ctr", json_integer(context->ctr));
// xer
// cr*
// fpscr
json_t* r_json = json_array();
for (size_t n = 0; n < 32; n++) {
json_array_append_new(r_json, json_integer(context->r[n]));
}
json_object_set_new(context_json, "r", r_json);
json_t* f_json = json_array();
for (size_t n = 0; n < 32; n++) {
json_array_append_new(f_json, json_real(context->f[n]));
}
json_object_set_new(context_json, "f", f_json);
json_t* v_json = json_array();
for (size_t n = 0; n < 128; n++) {
auto& v = context->v[n];
json_t* vec4_json = json_array();
json_array_append_new(vec4_json, json_integer(v.ix));
json_array_append_new(vec4_json, json_integer(v.iy));
json_array_append_new(vec4_json, json_integer(v.iz));
json_array_append_new(vec4_json, json_integer(v.iw));
json_array_append_new(v_json, vec4_json);
}
json_object_set_new(context_json, "v", v_json);
json_object_set_new(result, "context", context_json);
// TODO(benvanik): callstack
return result;
}
Processor::DebugClientState::DebugClientState(XenonRuntime* runtime) : Processor::DebugClientState::DebugClientState(XenonRuntime* runtime) :
runtime_(runtime) { runtime_(runtime) {
breakpoints_lock_ = xe_mutex_alloc(10000); breakpoints_lock_ = xe_mutex_alloc(10000);

View File

@ -67,6 +67,7 @@ public:
private: private:
json_t* DumpModule(XexModule* module, bool& succeeded); json_t* DumpModule(XexModule* module, bool& succeeded);
json_t* DumpFunction(uint64_t address, bool& succeeded); json_t* DumpFunction(uint64_t address, bool& succeeded);
json_t* DumpThreadState(XenonThreadState* thread_state);
private: private:
Emulator* emulator_; Emulator* emulator_;

View File

@ -29,6 +29,9 @@ public:
size_t stack_size, uint64_t thread_state_address); size_t stack_size, uint64_t thread_state_address);
virtual ~XenonThreadState(); virtual ~XenonThreadState();
uint64_t stack_address() const { return stack_address_; }
size_t stack_size() const { return stack_size_; }
uint64_t thread_state_address() const { return thread_state_address_; }
PPCContext* context() const { return context_; } PPCContext* context() const { return context_; }
virtual volatile int* suspend_flag_address() const; virtual volatile int* suspend_flag_address() const;
@ -37,11 +40,9 @@ public:
virtual void EnterSuspend(); virtual void EnterSuspend();
private: private:
size_t stack_size_;
uint64_t thread_state_address;
uint32_t thread_id_; uint32_t thread_id_;
uint64_t stack_address_; uint64_t stack_address_;
size_t stack_size_;
uint64_t thread_state_address_; uint64_t thread_state_address_;
// NOTE: must be 64b aligned for SSE ops. // NOTE: must be 64b aligned for SSE ops.