xenia/debugger/src/datasources.js
2013-12-25 17:31:53 -08:00

318 lines
7.9 KiB
JavaScript

/**
******************************************************************************
* 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.datasources', []);
module.service('Breakpoint', function() {
// http://stackoverflow.com/a/2117523/377392
var uuidFormat = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx';
function uuid4() {
return uuidFormat.replace(/[xy]/g, function(c) {
var r = Math.random()*16|0, v = c === 'x' ? r : (r&0x3|0x8);
return v.toString(16);
});
};
var Breakpoint = function(opt_id) {
this.id = opt_id || uuid4();
this.type = Breakpoint.Type.TEMP;
this.fnAddress = 0;
this.address = 0;
this.enabled = true;
};
Breakpoint.Type = {
TEMP: 'temp',
CODE: 'code'
};
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;
};
Breakpoint.prototype.toJSON = function() {
return {
'id': this.id,
'type': this.type,
'fnAddress': this.fnAddress,
'address': this.address,
'enabled': this.enabled
};
};
return Breakpoint;
});
module.service('DataSource', function($q) {
var DataSource = function(source, delegate) {
EventEmitter.call(this);
this.source = source;
this.delegate = delegate;
this.online = false;
this.status = 'disconnected';
};
inherits(DataSource, EventEmitter);
DataSource.prototype.open = function() {};
DataSource.prototype.dispose = function() {};
DataSource.prototype.issue = function(command) {};
DataSource.prototype.makeReady = function() {
return this.issue({
command: 'debug.make_ready'
});
};
DataSource.prototype.getModuleList = function() {
return this.issue({
command: 'cpu.get_module_list'
});
};
DataSource.prototype.getModule = function(moduleName) {
return this.issue({
command: 'cpu.get_module',
module: moduleName
});
};
DataSource.prototype.getFunctionList = function(moduleName, opt_since) {
return this.issue({
command: 'cpu.get_function_list',
module: moduleName,
since: opt_since || 0
});
};
DataSource.prototype.getFunction = function(address) {
return this.issue({
command: 'cpu.get_function',
address: address
});
};
DataSource.prototype.getThreadStates = function() {
return this.issue({
command: 'cpu.get_thread_states'
});
};
// set registers/etc?
DataSource.prototype.addBreakpoint = function(breakpoint) {
return this.addBreakpoints([breakpoint]);
};
DataSource.prototype.addBreakpoints = function(breakpoints) {
if (!breakpoints.length) {
var d = $q.defer();
d.resolve();
return d.promise;
}
return this.issue({
command: 'cpu.add_breakpoints',
breakpoints: breakpoints.map(function(breakpoint) {
return breakpoint.toJSON();
})
});
};
DataSource.prototype.removeBreakpoint = function(breakpointId) {
return this.removeBreakpoints([breakpointId]);
};
DataSource.prototype.removeBreakpoints = function(breakpointIds) {
return this.issue({
command: 'cpu.remove_breakpoints',
breakpointIds: breakpointIds
});
};
DataSource.prototype.removeAllBreakpoints = function() {
return this.issue({
command: 'cpu.remove_all_breakpoints'
});
};
DataSource.prototype.continueExecution = function() {
return this.issue({
command: 'cpu.continue'
});
};
DataSource.prototype.breakExecution = function() {
return this.issue({
command: 'cpu.break'
});
};
DataSource.prototype.stepNext = function(threadId) {
return this.issue({
command: 'cpu.step',
threadId: threadId
});
};
return DataSource;
});
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;
this.pendingRequests_ = {};
};
inherits(RemoteDataSource, DataSource);
RemoteDataSource.prototype.open = function() {
var url = this.url;
this.online = false;
this.status = 'connecting';
var d = $q.defer();
this.socket = new WebSocket(url, []);
this.socket.onopen = (function() {
$rootScope.$apply((function() {
// TODO(benvanik): handshake
this.online = true;
this.status = 'connected';
this.emit('online');
d.resolve();
}).bind(this));
}).bind(this);
this.socket.onclose = (function(e) {
$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');
this.emit('offline');
}
}).bind(this));
}).bind(this);
this.socket.onerror = (function(e) {
// ?
}).bind(this);
this.socket.onmessage = (function(e) {
$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;
this.status = 'disconnected';
if (this.socket) {
this.socket.close();
this.socket = null;
}
DataSource.prototype.dispose.call(this);
};
RemoteDataSource.prototype.issue = function(command) {
var d = $q.defer();
command.requestId = this.nextRequestId_++;
this.socket.send(JSON.stringify(command));
command.deferred = d;
this.pendingRequests_[command.requestId] = command;
return d.promise;
};
return RemoteDataSource;
});
module.service('FileDataSource', function($q, DataSource) {
var FileDataSource = function(file, delegate) {
DataSource.call(this, file.name, delegate);
this.file = file;
};
inherits(FileDataSource, DataSource);
FileDataSource.prototype.open = function() {
this.status = 'connecting';
var d = $q.defer();
var self = this;
window.setTimeout(function() {
$scope.$apply((function() {
// TODO(benvanik): scan/load trace
this.online = true;
this.status = 'connected';
d.resolve();
}).bind(self));
});
return d.promise;
};
FileDataSource.prototype.dispose = function() {
this.online = false;
if (this.file) {
if (this.file.close) {
this.file.close();
}
this.file = null;
}
DataSource.prototype.dispose.call(this);
};
return FileDataSource;
});