mirror of
https://github.com/rwengine/openrw.git
synced 2024-11-07 03:12:36 +01:00
Improve debug server functionality and appearence
This commit is contained in:
parent
868883fd36
commit
4ce275c381
@ -174,6 +174,8 @@ public:
|
|||||||
|
|
||||||
SCMFile* getFile() const { return _file; }
|
SCMFile* getFile() const { return _file; }
|
||||||
|
|
||||||
|
SCMOpcodes* getOpcodes() const { return _ops; }
|
||||||
|
|
||||||
void startThread(SCMThread::pc_t start, bool mission = false);
|
void startThread(SCMThread::pc_t start, bool mission = false);
|
||||||
|
|
||||||
std::vector<SCMThread>& getThreads() { return _activeThreads; }
|
std::vector<SCMThread>& getThreads() { return _activeThreads; }
|
||||||
|
@ -415,6 +415,7 @@ bool SaveGame::loadGame(GameState& state, const std::string& file)
|
|||||||
state.script->startThread(scripts[s].programCounter);
|
state.script->startThread(scripts[s].programCounter);
|
||||||
SCMThread& thread = threads.back();
|
SCMThread& thread = threads.back();
|
||||||
// thread.baseAddress // ??
|
// thread.baseAddress // ??
|
||||||
|
strncpy(thread.name, scripts[s].name, sizeof(SCMThread::name)-1);
|
||||||
thread.conditionResult = scripts[s].ifFlag;
|
thread.conditionResult = scripts[s].ifFlag;
|
||||||
thread.conditionCount = scripts[s].ifNumber;
|
thread.conditionCount = scripts[s].ifNumber;
|
||||||
thread.stackDepth = scripts[s].stackCounter;
|
thread.stackDepth = scripts[s].stackCounter;
|
||||||
|
@ -225,6 +225,11 @@ void RWGame::startScript(const std::string& name)
|
|||||||
opcodes->modules.push_back(new ObjectModule);
|
opcodes->modules.push_back(new ObjectModule);
|
||||||
|
|
||||||
script = new ScriptMachine(state, f, opcodes);
|
script = new ScriptMachine(state, f, opcodes);
|
||||||
|
|
||||||
|
/* If Debug server is available, break on the first opcode executed */
|
||||||
|
if( httpserver ) {
|
||||||
|
script->interuptNext();
|
||||||
|
}
|
||||||
|
|
||||||
// Set up breakpoint handler
|
// Set up breakpoint handler
|
||||||
script->setBreakpointHandler(
|
script->setBreakpointHandler(
|
||||||
@ -328,6 +333,11 @@ int RWGame::run()
|
|||||||
window.display();
|
window.display();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if( httpserver_thread )
|
||||||
|
{
|
||||||
|
httpserver_thread->join();
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,6 +24,12 @@ app.controller('DebugCtrl', function($scope,$http) {
|
|||||||
$scope.refresh();
|
$scope.refresh();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
$scope.step = function() {
|
||||||
|
var promise = $http.get('/step')
|
||||||
|
.success(function(data, status, headers, config) {
|
||||||
|
$scope.refresh();
|
||||||
|
});
|
||||||
|
}
|
||||||
$scope.continue = function() {
|
$scope.continue = function() {
|
||||||
var promise = $http.get('/continue')
|
var promise = $http.get('/continue')
|
||||||
.success(function(data, status, headers, config) {
|
.success(function(data, status, headers, config) {
|
||||||
@ -39,13 +45,32 @@ const char* src_page = R"(
|
|||||||
<html ng-app="debugApp">
|
<html ng-app="debugApp">
|
||||||
<head>
|
<head>
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
html,body { font-family: sans-serif; }
|
html,body { font-family: sans-serif; }
|
||||||
|
.disassembly {
|
||||||
|
background: white;
|
||||||
|
border: 1px solid #CCC
|
||||||
|
}
|
||||||
|
.disassembly .breakpoint {
|
||||||
|
background: #CFC
|
||||||
|
}
|
||||||
|
.disassembly .line-number {
|
||||||
|
width: 6em;
|
||||||
|
text-align: right;
|
||||||
|
margin-right: 0.75em;
|
||||||
|
padding-left: 0.25em;
|
||||||
|
background: #F6F6F6;
|
||||||
|
border-right: solid 1px #EEE;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</style>
|
</style>
|
||||||
<body ng-controller="DebugCtrl">
|
<body ng-controller="DebugCtrl">
|
||||||
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.1/angular.min.js"></script>
|
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.1/angular.min.js"></script>
|
||||||
<h1>OpenRW Debugger</h1>
|
<h1>OpenRW Debugger</h1>
|
||||||
<button ng-click="interrupt();">Interrupt</button> <button ng-click="continue();">Continue</button>
|
<div class="debugger-controlls">
|
||||||
|
<button ng-click="interrupt();">Interrupt</button>
|
||||||
|
<button ng-click="step();">Step</button>
|
||||||
|
<button ng-click="continue();">Continue</button>
|
||||||
|
</div>
|
||||||
<span id="running" style="background: green; color: white; padding: 2px; border-radius: 2px" ng-show="running">Game is running</span>
|
<span id="running" style="background: green; color: white; padding: 2px; border-radius: 2px" ng-show="running">Game is running</span>
|
||||||
<div>
|
<div>
|
||||||
<h2>Threads</h2>
|
<h2>Threads</h2>
|
||||||
@ -54,6 +79,35 @@ const char* src_page = R"(
|
|||||||
<h3>{{thread.name}}</h3>
|
<h3>{{thread.name}}</h3>
|
||||||
<i>program counter: </i> {{thread.program_counter}}<br>
|
<i>program counter: </i> {{thread.program_counter}}<br>
|
||||||
<i>wake counter: </i> {{thread.wake_counter}} ms<br>
|
<i>wake counter: </i> {{thread.wake_counter}} ms<br>
|
||||||
|
<h4>Return Stack</h4>
|
||||||
|
<ol>
|
||||||
|
<li ng-repeat="return_address in thread.call_stack">
|
||||||
|
Address {{return_address}}
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
<h4>Disassembly</h4>
|
||||||
|
<div class="disassembly">
|
||||||
|
<div ng-repeat="call in thread.disassembly" class="instruction">
|
||||||
|
<span class="line-number">
|
||||||
|
{{ call.address }}
|
||||||
|
</span>
|
||||||
|
<span class="function">
|
||||||
|
{{call.function }}
|
||||||
|
</span>
|
||||||
|
<span class="parameters">
|
||||||
|
(
|
||||||
|
<span class="param" ng-repeat="param in call.arguments">
|
||||||
|
{{param.value}}<span ng-show="!$last">,</span>
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ol>
|
||||||
|
<li ng-repeat="return_address in thread.call_stack">
|
||||||
|
Address {{return_address}}
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@ -68,12 +122,12 @@ HttpServer::HttpServer(RWGame* game, GameWorld* world)
|
|||||||
|
|
||||||
void HttpServer::run()
|
void HttpServer::run()
|
||||||
{
|
{
|
||||||
listener.listen(8091);
|
listener.create();
|
||||||
listener.reuse();
|
listener.listen(8091);
|
||||||
|
|
||||||
std::cout << "STARTING HTTP SERVER" << std::endl;
|
std::cout << "STARTING HTTP SERVER" << std::endl;
|
||||||
|
|
||||||
while (true) {
|
while ( game->getWindow().isOpen() ) {
|
||||||
sf::TcpSocket client;
|
sf::TcpSocket client;
|
||||||
if (listener.accept(client) == sf::Socket::Done) {
|
if (listener.accept(client) == sf::Socket::Done) {
|
||||||
std::cout << "New connection from "
|
std::cout << "New connection from "
|
||||||
@ -113,6 +167,114 @@ void HttpServer::handleBreakpoint(const SCMBreakpoint &bp)
|
|||||||
// Do nothing
|
// Do nothing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
std::string thread_stack(SCMThread& th)
|
||||||
|
{
|
||||||
|
std::stringstream ss;
|
||||||
|
for(unsigned int i = 0; i < th.stackDepth; ++i)
|
||||||
|
{
|
||||||
|
bool last = (th.stackDepth == i+1);
|
||||||
|
ss << th.calls[i]
|
||||||
|
<< (last ? "" : ",");
|
||||||
|
}
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
#include <script/ScriptDisassembly.hpp>
|
||||||
|
|
||||||
|
std::string describe_parameter(SCMOpcodeParameter& p)
|
||||||
|
{
|
||||||
|
std::stringstream ss;
|
||||||
|
|
||||||
|
switch(p.type) {
|
||||||
|
case TGlobal:
|
||||||
|
ss << "G: " << p.globalPtr;
|
||||||
|
break;
|
||||||
|
case TLocal:
|
||||||
|
ss << "L: " << p.globalPtr;
|
||||||
|
break;
|
||||||
|
case TInt8:
|
||||||
|
ss << "i8: " << std::dec << (uint16_t)((uint8_t)p.integer);
|
||||||
|
break;
|
||||||
|
case TInt16:
|
||||||
|
ss << "i16: " << std::dec << (uint16_t)p.integer;
|
||||||
|
break;
|
||||||
|
case TInt32:
|
||||||
|
ss << "i32: " << std::dec << (uint32_t)p.integer;
|
||||||
|
break;
|
||||||
|
case TString:
|
||||||
|
ss << "str: \\\"" << p.string << "\\\"";
|
||||||
|
break;
|
||||||
|
case TFloat16:
|
||||||
|
ss << "f16: " << p.real;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ss << "OTHER";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string thread_disassembly(ScriptMachine* script, SCMThread& th)
|
||||||
|
{
|
||||||
|
std::stringstream ss;
|
||||||
|
|
||||||
|
ScriptDisassembly ds(script->getOpcodes(), script->getFile());
|
||||||
|
try {
|
||||||
|
ds.disassemble(th.programCounter);
|
||||||
|
}
|
||||||
|
catch(SCMException& ex)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
auto it = ds.getInstructions().find(th.programCounter);
|
||||||
|
for( int i = 0; i < 5 && it != ds.getInstructions().end(); ++i ) {
|
||||||
|
bool last = i+1 == 5;
|
||||||
|
last = last || it == --ds.getInstructions().end();
|
||||||
|
auto opcode = it->second.opcode;
|
||||||
|
ScriptFunctionMeta* meta = nullptr;
|
||||||
|
ss << "{";
|
||||||
|
if( script->getOpcodes()->findOpcode((uint16_t)opcode, &meta) )
|
||||||
|
{
|
||||||
|
ss << "\"address\": " << it->first << ","
|
||||||
|
<< "\"function\": \"" << meta->signature << "\","
|
||||||
|
<< "\"arguments\": [";
|
||||||
|
SCMParams& parameters = it->second.parameters;
|
||||||
|
for(size_t p = 0; p < parameters.size(); ++p)
|
||||||
|
{
|
||||||
|
if(p != 0)
|
||||||
|
ss << ",";
|
||||||
|
ss << "{"
|
||||||
|
<< "\"type\": " << parameters[p].type << ","
|
||||||
|
<< "\"value\": \"" << describe_parameter(parameters[p]) << "\""
|
||||||
|
<< "}";
|
||||||
|
}
|
||||||
|
ss << "]";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ss << "\"err\":\"err\"";
|
||||||
|
}
|
||||||
|
ss << (last ? "}" : "},");
|
||||||
|
it++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string thread(ScriptMachine* script, SCMThread& th)
|
||||||
|
{
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << "{"
|
||||||
|
<< "\"program_counter\": " << th.programCounter << ","
|
||||||
|
<< "\"name\": \"" << th.name << "\","
|
||||||
|
<< "\"wake_counter\": " << th.wakeCounter << ","
|
||||||
|
<< "\"call_stack\": [" << thread_stack(th) << "],"
|
||||||
|
<< "\"disassembly\": [" << thread_disassembly(script, th) << "]"
|
||||||
|
<< "}";
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
std::string HttpServer::getState() const
|
std::string HttpServer::getState() const
|
||||||
{
|
{
|
||||||
@ -128,11 +290,7 @@ std::string HttpServer::getState() const
|
|||||||
{
|
{
|
||||||
SCMThread& th = game->getScript()->getThreads()[i];
|
SCMThread& th = game->getScript()->getThreads()[i];
|
||||||
bool last = (game->getScript()->getThreads().size() == i+1);
|
bool last = (game->getScript()->getThreads().size() == i+1);
|
||||||
ss << "{"
|
ss << thread(game->getScript(), th) << (last ? "" : ",");
|
||||||
<< "\"program_counter\": " << th.programCounter << ","
|
|
||||||
<< "\"name\": \"" << th.name << "\","
|
|
||||||
<< "\"wake_counter\": " << th.wakeCounter << ""
|
|
||||||
<< (last ? "}" : "},");
|
|
||||||
}
|
}
|
||||||
ss << R"(])";
|
ss << R"(])";
|
||||||
ss << "}";
|
ss << "}";
|
||||||
@ -159,6 +317,14 @@ std::string HttpServer::dispatch(std::string method, std::string path)
|
|||||||
ss << getState();
|
ss << getState();
|
||||||
mime = "application/json";
|
mime = "application/json";
|
||||||
}
|
}
|
||||||
|
else if(path == "/step") {
|
||||||
|
if( paused ) {
|
||||||
|
game->getScript()->interuptNext();
|
||||||
|
paused = false;
|
||||||
|
}
|
||||||
|
ss << getState();
|
||||||
|
mime = "application/json";
|
||||||
|
}
|
||||||
else if(path == "/continue") {
|
else if(path == "/continue") {
|
||||||
paused = false;
|
paused = false;
|
||||||
ss << getState();
|
ss << getState();
|
||||||
|
@ -13,9 +13,14 @@
|
|||||||
class ReuseableListener : public sf::TcpListener
|
class ReuseableListener : public sf::TcpListener
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
void reuse() {
|
void create() {
|
||||||
char reuse = 1;
|
char reuse = 1;
|
||||||
setsockopt(getHandle(), SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
|
sf::SocketHandle handle = socket(PF_INET, SOCK_STREAM, 0);
|
||||||
|
if( setsockopt(handle, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0 )
|
||||||
|
{
|
||||||
|
std::cerr << "Failed to set socket SO_REUSEADDR: errno = " << errno << std::endl;
|
||||||
|
}
|
||||||
|
Socket::create(handle);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user