Messaging over debug channel.

This commit is contained in:
Ben Vanik 2013-12-22 00:21:35 -08:00
parent a9378eb7eb
commit 80d8dc02aa
13 changed files with 319 additions and 20 deletions

3
.gitmodules vendored
View File

@ -19,3 +19,6 @@
[submodule "third_party/wslay"] [submodule "third_party/wslay"]
path = third_party/wslay path = third_party/wslay
url = https://github.com/benvanik/wslay.git url = https://github.com/benvanik/wslay.git
[submodule "third_party/jansson"]
path = third_party/jansson
url = https://github.com/akheron/jansson.git

View File

@ -1,4 +1,4 @@
<div class="debugger-main"> <div class="debugger-main" ng-controller="CodeTabController">
<div class="debugger-header"> <div class="debugger-header">
debug header/toolbar/etc debug header/toolbar/etc
<div ui-view></div> <div ui-view></div>

View File

@ -0,0 +1,32 @@
/**
******************************************************************************
* 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', [
'xe.log',
'xe.session'
]);
module.controller('CodeTabController', function(
$rootScope, $scope, app, log) {
$rootScope.$on('refresh', function() {
var dataSource = app.session.dataSource;
dataSource.getModuleList().then(function(list) {
console.log(list);
}, function(e) {
log('Unable to fetch module list');
});
console.log('refresh');
});
});

View File

@ -13,12 +13,15 @@
<ul class="nav navbar-nav navbar-right"> <ul class="nav navbar-nav navbar-right">
<li class="dropdown"> <li class="dropdown">
<div class="btn-group navbar-btn"> <div class="btn-group navbar-btn">
<button type="button" class="btn btn-default" ng-click="connectClicked()" ng-disabled="app.loading"> <button type="button" class="btn btn-default" ng-click="connect()" ng-disabled="app.loading">
<span class="glyphicon glyphicon-link"></span> Connect <span class="glyphicon glyphicon-link"></span> Connect
</button> </button>
<button type="button" class="btn btn-default" ng-click="openClicked()" ng-disabled="app.loading"> <button type="button" class="btn btn-default" ng-click="open()" ng-disabled="app.loading">
<span class="glyphicon glyphicon-file"></span> Open <span class="glyphicon glyphicon-file"></span> Open
</button> </button>
<button type="button" class="btn btn-default" ng-click="refresh()" ng-disabled="app.loading">
<span class="glyphicon glyphicon-refresh"></span> Refresh
</button>
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" ng-disabled="app.loading"> <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" ng-disabled="app.loading">
<span class="glyphicon glyphicon-cog"></span> <span class="caret"></span> <span class="glyphicon glyphicon-cog"></span> <span class="caret"></span>
</button> </button>

View File

@ -13,9 +13,13 @@ var module = angular.module('xe.ui.navbar', []);
module.controller('NavbarController', function( module.controller('NavbarController', function(
$scope, $state, app, log) { $rootScope, $scope, $state, app, log) {
$scope.connectClicked = function() { $scope.refresh = function() {
$rootScope.$emit('refresh');
};
$scope.connect = function() {
// TODO(benvanik): show a fancy dialog or something. // TODO(benvanik): show a fancy dialog or something.
var oldSession = app.session; var oldSession = app.session;
app.connect().then(function(session) { app.connect().then(function(session) {
@ -34,7 +38,7 @@ module.controller('NavbarController', function(
}); });
}; };
$scope.openClicked = function() { $scope.open = function() {
var inputEl = document.createElement('input'); var inputEl = document.createElement('input');
inputEl.type = 'file'; inputEl.type = 'file';
inputEl.accept = '.xe-trace,application/x-extension-xe-trace'; inputEl.accept = '.xe-trace,application/x-extension-xe-trace';

View File

@ -31,6 +31,7 @@
<script src="src/session.js"></script> <script src="src/session.js"></script>
<script src="assets/ui/navbar.js"></script> <script src="assets/ui/navbar.js"></script>
<script src="assets/ui/console/console.js"></script> <script src="assets/ui/console/console.js"></script>
<script src="assets/ui/code/code-tab.js"></script>
<script src="debugger.js"></script> <script src="debugger.js"></script>
</body> </body>

View File

@ -17,6 +17,7 @@ var module = angular.module('app', [
'xe.log', 'xe.log',
'xe.router', 'xe.router',
'xe.session', 'xe.session',
'xe.ui.code',
'xe.ui.console', 'xe.ui.console',
'xe.ui.navbar' 'xe.ui.navbar'
]); ]);

View File

@ -12,20 +12,88 @@
var module = angular.module('xe.datasources', []); var module = angular.module('xe.datasources', []);
var DataSource = function(source) { module.service('DataSource', function($q) {
var DataSource = function(source) {
this.source = source; this.source = source;
this.online = false; this.online = false;
this.status = 'disconnected'; this.status = 'disconnected';
}; };
DataSource.prototype.open = function() {}; DataSource.prototype.open = function() {};
DataSource.prototype.dispose = function() {}; DataSource.prototype.dispose = function() {};
DataSource.prototype.issue = function(command) {};
DataSource.prototype.addBreakpoint = function(breakpoint) {
return this.addBreakpoints([breakpoint]);
};
module.service('RemoteDataSource', function($q) { 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.getModuleList = function() {
return this.issue({
command: 'cpu.get_module_list'
});
};
DataSource.prototype.getModule = function(moduleId) {
return this.issue({
command: 'cpu.get_module',
moduleId: moduleId
});
};
DataSource.prototype.getFunctionList = function(moduleId) {
return this.issue({
command: 'cpu.get_function_list',
moduleId: moduleId
});
};
DataSource.prototype.getFunction = function(address) {
return this.issue({
command: 'cpu.get_function',
address: address
});
};
return DataSource;
});
module.service('RemoteDataSource', function($q, DataSource) {
var RemoteDataSource = function(url) { var RemoteDataSource = function(url) {
DataSource.call(this, url); DataSource.call(this, url);
this.url = url; this.url = url;
this.socket = null; this.socket = null;
this.nextRequestId_ = 1;
this.pendingRequests_ = {};
}; };
inherits(RemoteDataSource, DataSource); inherits(RemoteDataSource, DataSource);
@ -62,12 +130,28 @@ module.service('RemoteDataSource', function($q) {
this.socket.onmessage = (function(e) { this.socket.onmessage = (function(e) {
console.log('message', e.data); 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.
}
}).bind(this); }).bind(this);
return d.promise; return d.promise;
}; };
RemoteDataSource.prototype.dispose = function() { RemoteDataSource.prototype.dispose = function() {
this.pendingRequests_ = {};
this.online = false; this.online = false;
this.status = 'disconnected'; this.status = 'disconnected';
if (this.socket) { if (this.socket) {
@ -77,11 +161,20 @@ module.service('RemoteDataSource', function($q) {
DataSource.prototype.dispose.call(this); 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; return RemoteDataSource;
}); });
module.service('FileDataSource', function($q) { module.service('FileDataSource', function($q, DataSource) {
var FileDataSource = function(file) { var FileDataSource = function(file) {
DataSource.call(this, file.name); DataSource.call(this, file.name);
this.file = file; this.file = file;

View File

@ -11,6 +11,7 @@
#include <iomanip> #include <iomanip>
#include <sstream> #include <sstream>
#include <third_party/jansson/src/jansson.h>
#include <xenia/emulator.h> #include <xenia/emulator.h>
#include <xenia/debug/debug_server.h> #include <xenia/debug/debug_server.h>
@ -133,10 +134,10 @@ void WSClientOnMsgCallback(wslay_event_context_ptr ctx,
WSClient* client = reinterpret_cast<WSClient*>(user_data); WSClient* client = reinterpret_cast<WSClient*>(user_data);
switch (arg->opcode) { switch (arg->opcode) {
case WSLAY_TEXT_FRAME: case WSLAY_TEXT_FRAME:
XELOGW("Text frame ignored; use binary messages"); client->OnMessage(arg->msg, arg->msg_length);
break; break;
case WSLAY_BINARY_FRAME: case WSLAY_BINARY_FRAME:
//client->OnMessage(arg->msg, arg->msg_length); // Ignored.
break; break;
default: default:
// Unknown opcode - some frame stuff? // Unknown opcode - some frame stuff?
@ -371,7 +372,18 @@ void WSClient::EventThread() {
delete this; delete this;
} }
void WSClient::Write(uint8_t** buffers, size_t* lengths, size_t count) { void WSClient::Write(const char* value) {
const uint8_t* buffers[] = {
(uint8_t*)value,
};
size_t lengths[] = {
sizeof(char) * xestrlena(value),
};
Write(buffers, lengths, XECOUNT(buffers), false);
}
void WSClient::Write(const uint8_t** buffers, size_t* lengths, size_t count,
bool binary) {
if (!count) { if (!count) {
return; return;
} }
@ -400,7 +412,7 @@ void WSClient::Write(uint8_t** buffers, size_t* lengths, size_t count) {
} }
struct wslay_event_msg msg = { struct wslay_event_msg msg = {
WSLAY_BINARY_FRAME, binary ? WSLAY_BINARY_FRAME : WSLAY_TEXT_FRAME,
combined_buffer, combined_buffer,
combined_length, combined_length,
}; };
@ -415,3 +427,84 @@ void WSClient::Write(uint8_t** buffers, size_t* lengths, size_t count) {
xe_socket_loop_set_queued_write(loop_); xe_socket_loop_set_queued_write(loop_);
} }
} }
void WSClient::OnMessage(const uint8_t* data, size_t length) {
//
const char* s = (const char*)data;
printf(s);
// command
// requestId
json_error_t error;
json_t* request = json_loadb(
(const char*)data, length, JSON_DECODE_INT_AS_REAL, &error);
if (!request) {
// Failed to parse.
XELOGE("Failed to parse JSON request: %d:%d %s %s",
error.line, error.column,
error.source, error.text);
return;
}
if (!json_is_object(request)) {
XELOGE("Expected JSON object");
json_decref(request);
return;
}
json_t* request_id_json = json_object_get(request, "requestId");
if (!request_id_json || !json_is_number(request_id_json)) {
XELOGE("Need requestId field");
json_decref(request);
return;
}
json_t* command_json = json_object_get(request, "command");
if (!command_json || !json_is_string(command_json)) {
XELOGE("Need command field");
json_decref(request);
return;
}
// Handle command.
bool succeeded = false;
const char* command = json_string_value(command_json);
json_t* result_json = HandleMessage(command, request, succeeded);
if (!result_json) {
result_json = json_null();
}
// Prepare response.
json_t* response = json_object();
json_object_set(response, "requestId", request_id_json);
json_t* status_json = succeeded ? json_true() : json_false();
json_object_set_new(response, "status", status_json);
json_object_set_new(response, "result", result_json);
// Encode response to string and send back.
const char* response_string = json_dumps(response, JSON_INDENT(2));
Write(response_string);
json_decref(request);
json_decref(response);
}
json_t* WSClient::HandleMessage(const char* command, json_t* request,
bool& succeeded) {
succeeded = false;
// Get target.
char target_name[16] = { 0 };
const char* dot = xestrchra(command, '.');
if (dot) {
if (dot - command > XECOUNT(target_name)) {
return NULL;
}
xestrncpya(target_name, XECOUNT(target_name),
command, dot - command);
}
const char* sub_command = command + (dot - command + 1);
// Lookup target and dispatch.
succeeded = true;
return json_null();
}

View File

@ -19,6 +19,7 @@
struct wslay_event_msg; struct wslay_event_msg;
struct json_t;
namespace xe { namespace xe {
@ -37,7 +38,12 @@ public:
virtual int Setup(); virtual int Setup();
virtual void Close(); virtual void Close();
void Write(uint8_t** buffers, size_t* lengths, size_t count); void Write(const char* value);
void Write(const uint8_t** buffers, size_t* lengths, size_t count,
bool binary = true);
void OnMessage(const uint8_t* data, size_t length);
json_t* HandleMessage(const char* command, json_t* request, bool& succeeded);
private: private:
static void StartCallback(void* param); static void StartCallback(void* param);

1
third_party/jansson vendored Submodule

@ -0,0 +1 @@
Subproject commit 4e8c4bfbd209e50cc7b13993bbd56adb9282c465

59
third_party/jansson.gypi vendored Normal file
View File

@ -0,0 +1,59 @@
# Copyright 2013 Ben Vanik. All Rights Reserved.
{
'targets': [
{
'target_name': 'jansson',
'type': '<(library)',
'direct_dependent_settings': {
'include_dirs': [
'jansson/win32/',
'jansson/src/',
],
'defines': [
],
},
'msvs_disabled_warnings': [4267],
'defines': [
],
'conditions': [
['OS != "win"', {
'defines': [
],
}],
['OS == "win"', {
'defines': [
],
}],
],
'include_dirs': [
'jansson/win32/',
'jansson/src/',
],
'sources': [
'jansson/win32/jansson_config.h',
'jansson/src/dump.c',
'jansson/src/error.c',
'jansson/src/hashtable.c',
'jansson/src/hashtable.h',
'jansson/src/jansson.h',
'jansson/src/jansson_private.h',
'jansson/src/load.c',
'jansson/src/memory.c',
'jansson/src/pack_unpack.c',
'jansson/src/strbuffer.c',
'jansson/src/strbuffer.h',
'jansson/src/strconv.c',
'jansson/src/utf.c',
'jansson/src/utf.h',
'jansson/src/value.c',
],
}
]
}

View File

@ -5,6 +5,7 @@
'third_party/asmjit.gypi', 'third_party/asmjit.gypi',
'third_party/beaengine.gypi', 'third_party/beaengine.gypi',
'third_party/gflags.gypi', 'third_party/gflags.gypi',
'third_party/jansson.gypi',
'third_party/sparsehash.gypi', 'third_party/sparsehash.gypi',
'third_party/wslay.gypi', 'third_party/wslay.gypi',
], ],
@ -249,6 +250,7 @@
'asmjit', 'asmjit',
'beaengine', 'beaengine',
'gflags', 'gflags',
'jansson',
'wslay', 'wslay',
'alloy', 'alloy',
], ],
@ -256,6 +258,7 @@
'asmjit', 'asmjit',
'beaengine', 'beaengine',
'gflags', 'gflags',
'jansson',
'wslay', 'wslay',
'alloy', 'alloy',
], ],