mirror of
https://github.com/rwengine/openrw.git
synced 2024-11-22 02:12:45 +01:00
Improve debugger functionality and display
* Display Breakpoint information * Current instruction is hilighted
This commit is contained in:
parent
4ce275c381
commit
5461f1a5bb
@ -46,9 +46,9 @@ void ScriptMachine::executeThread(SCMThread &t, int msPassed)
|
||||
|
||||
bool hasDebugging = !! bpHandler;
|
||||
|
||||
while( t.wakeCounter == 0 ) {
|
||||
auto opcode = _file->read<SCMOpcode>(t.programCounter);
|
||||
auto opcorg = opcode;
|
||||
while( t.wakeCounter == 0 ) {
|
||||
auto pc = t.programCounter;
|
||||
auto opcode = _file->read<SCMOpcode>(pc);
|
||||
|
||||
bool isNegatedConditional = ((opcode & SCM_NEGATE_CONDITIONAL_MASK) == SCM_NEGATE_CONDITIONAL_MASK);
|
||||
opcode = opcode & ~SCM_NEGATE_CONDITIONAL_MASK;
|
||||
@ -56,13 +56,11 @@ void ScriptMachine::executeThread(SCMThread &t, int msPassed)
|
||||
ScriptFunctionMeta* foundcode;
|
||||
if( ! _ops->findOpcode(opcode, &foundcode) )
|
||||
{
|
||||
throw IllegalInstruction(opcode, t.programCounter, t.name);
|
||||
throw IllegalInstruction(opcode, pc, t.name);
|
||||
}
|
||||
ScriptFunctionMeta& code = *foundcode;
|
||||
|
||||
// Keep the pc for the debugger
|
||||
auto pc = t.programCounter;
|
||||
t.programCounter += sizeof(SCMOpcode);
|
||||
pc += sizeof(SCMOpcode);
|
||||
|
||||
SCMParams parameters;
|
||||
|
||||
@ -70,7 +68,7 @@ void ScriptMachine::executeThread(SCMThread &t, int msPassed)
|
||||
auto requiredParams = std::abs(code.arguments);
|
||||
|
||||
for( int p = 0; p < requiredParams || hasExtraParameters; ++p ) {
|
||||
auto type_r = _file->read<SCMByte>(t.programCounter);
|
||||
auto type_r = _file->read<SCMByte>(pc);
|
||||
auto type = static_cast<SCMType>(type_r);
|
||||
|
||||
if( type_r > 42 ) {
|
||||
@ -78,7 +76,7 @@ void ScriptMachine::executeThread(SCMThread &t, int msPassed)
|
||||
type = TString;
|
||||
}
|
||||
else {
|
||||
t.programCounter += sizeof(SCMByte);
|
||||
pc += sizeof(SCMByte);
|
||||
}
|
||||
|
||||
parameters.push_back(SCMOpcodeParameter { type, { 0 } });
|
||||
@ -87,71 +85,74 @@ void ScriptMachine::executeThread(SCMThread &t, int msPassed)
|
||||
hasExtraParameters = false;
|
||||
break;
|
||||
case TInt8:
|
||||
parameters.back().integer = _file->read<std::uint8_t>(t.programCounter);
|
||||
t.programCounter += sizeof(SCMByte);
|
||||
parameters.back().integer = _file->read<std::uint8_t>(pc);
|
||||
pc += sizeof(SCMByte);
|
||||
break;
|
||||
case TInt16:
|
||||
parameters.back().integer = _file->read<std::int16_t>(t.programCounter);
|
||||
t.programCounter += sizeof(SCMByte) * 2;
|
||||
parameters.back().integer = _file->read<std::int16_t>(pc);
|
||||
pc += sizeof(SCMByte) * 2;
|
||||
break;
|
||||
case TGlobal: {
|
||||
auto v = _file->read<std::uint16_t>(t.programCounter);
|
||||
auto v = _file->read<std::uint16_t>(pc);
|
||||
parameters.back().globalPtr = globalData.data() + v; //* SCM_VARIABLE_SIZE;
|
||||
if( v >= _file->getGlobalsSize() )
|
||||
{
|
||||
state->world->logger->error("SCM", "Global Out of bounds! "+ std::to_string(v) + " " + std::to_string(_file->getGlobalsSize()));
|
||||
}
|
||||
t.programCounter += sizeof(SCMByte) * 2;
|
||||
pc += sizeof(SCMByte) * 2;
|
||||
}
|
||||
break;
|
||||
case TLocal: {
|
||||
auto v = _file->read<std::uint16_t>(t.programCounter);
|
||||
auto v = _file->read<std::uint16_t>(pc);
|
||||
parameters.back().globalPtr = t.locals.data() + v * SCM_VARIABLE_SIZE;
|
||||
if( v >= SCM_THREAD_LOCAL_SIZE )
|
||||
{
|
||||
state->world->logger->error("SCM", "Local Out of bounds!");
|
||||
}
|
||||
t.programCounter += sizeof(SCMByte) * 2;
|
||||
pc += sizeof(SCMByte) * 2;
|
||||
}
|
||||
break;
|
||||
case TInt32:
|
||||
parameters.back().integer = _file->read<std::uint32_t>(t.programCounter);
|
||||
t.programCounter += sizeof(SCMByte) * 4;
|
||||
parameters.back().integer = _file->read<std::uint32_t>(pc);
|
||||
pc += sizeof(SCMByte) * 4;
|
||||
break;
|
||||
case TString:
|
||||
std::copy(_file->data()+t.programCounter, _file->data()+t.programCounter+8,
|
||||
std::copy(_file->data()+pc, _file->data()+pc+8,
|
||||
parameters.back().string);
|
||||
t.programCounter += sizeof(SCMByte) * 8;
|
||||
pc += sizeof(SCMByte) * 8;
|
||||
break;
|
||||
case TFloat16:
|
||||
parameters.back().real = _file->read<std::int16_t>(t.programCounter) / 16.f;
|
||||
t.programCounter += sizeof(SCMByte) * 2;
|
||||
parameters.back().real = _file->read<std::int16_t>(pc) / 16.f;
|
||||
pc += sizeof(SCMByte) * 2;
|
||||
break;
|
||||
default:
|
||||
throw UnknownType(type, t.programCounter, t.name);
|
||||
throw UnknownType(type, pc, t.name);
|
||||
break;
|
||||
};
|
||||
}
|
||||
|
||||
ScriptArguments sca(¶meters, &t, this);
|
||||
|
||||
if( hasDebugging )
|
||||
{
|
||||
if( breakpoints.find(pc) != breakpoints.end() || interupt )
|
||||
{
|
||||
interupt = false;
|
||||
SCMBreakpoint bp;
|
||||
bp.pc = t.programCounter;
|
||||
bp.thread = &t;
|
||||
bp.vm = this;
|
||||
bp.function = &code;
|
||||
bp.args = &sca;
|
||||
bpHandler(bp);
|
||||
}
|
||||
}
|
||||
|
||||
// After debugging has been completed, update the program counter
|
||||
t.programCounter = pc;
|
||||
|
||||
if(code.function)
|
||||
{
|
||||
ScriptArguments sca(¶meters, &t, this);
|
||||
|
||||
if( hasDebugging )
|
||||
{
|
||||
if( breakpoints.find(pc) != breakpoints.end() || interupt )
|
||||
{
|
||||
interupt = false;
|
||||
SCMBreakpoint bp;
|
||||
bp.pc = pc;
|
||||
bp.thread = &t;
|
||||
bp.vm = this;
|
||||
bp.function = &code;
|
||||
bp.args = &sca;
|
||||
bpHandler(bp);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
code.function(sca);
|
||||
}
|
||||
|
||||
@ -181,7 +182,7 @@ void ScriptMachine::executeThread(SCMThread &t, int msPassed)
|
||||
|
||||
t.conditionResult = (t.conditionMask != 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SCMOpcodeParameter p;
|
||||
p.globalPtr = (t.locals.data() + 16 * sizeof ( SCMByte ) * 4);
|
||||
@ -239,15 +240,12 @@ void ScriptMachine::execute(float dt)
|
||||
{
|
||||
int ms = dt * 1000.f;
|
||||
for(size_t ti = 0; ti < _activeThreads.size(); ++ti) {
|
||||
auto thread = _activeThreads[ti];
|
||||
auto& thread = _activeThreads[ti];
|
||||
executeThread( thread, ms );
|
||||
|
||||
if( thread.finished ) {
|
||||
_activeThreads.erase( _activeThreads.begin() + ti );
|
||||
}
|
||||
else {
|
||||
_activeThreads[ti] = thread;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -903,6 +903,7 @@ GameModule::GameModule()
|
||||
bindFunction(0x015A, game_reset_camera, 0, "Reset Camera" );
|
||||
|
||||
bindFunction(0x015C, game_set_zone_ped_info, 11, "Set zone ped info" );
|
||||
bindUnimplemented( 0x015D, game_set_timescale;, 1, "Set Game Timescale" );
|
||||
|
||||
bindFunction(0x015F, game_camera_fixed_position, 6, "Set Fixed Camera Position" );
|
||||
bindFunction(0x0160, game_camera_lookat_position, 4, "Point Camera at Point" );
|
||||
|
@ -293,6 +293,23 @@ bool game_player_near_point_on_foot_3D(const ScriptArguments& args)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool game_player_near_point_in_vehicle_3D(const ScriptArguments& args)
|
||||
{
|
||||
auto character = static_cast<CharacterObject*>(args.getPlayerCharacter(0));
|
||||
glm::vec3 center(args[1].real, args[2].real, args[3].real);
|
||||
glm::vec3 size(args[4].real, args[5].real, args[6].real);
|
||||
bool unkown = !!args[7].integer;
|
||||
|
||||
auto vehicle = character->getCurrentVehicle();
|
||||
if( vehicle ) {
|
||||
auto distance = center - character->getPosition();
|
||||
distance /= size;
|
||||
if( glm::length( distance ) < 1.f ) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool game_character_near_point_in_vehicle(const ScriptArguments& args)
|
||||
{
|
||||
auto character = static_cast<CharacterObject*>(args.getObject<CharacterObject>(0));
|
||||
@ -432,6 +449,21 @@ void game_create_character_in_vehicle(const ScriptArguments& args)
|
||||
*args[3].globalInteger = character->getGameObjectID();
|
||||
}
|
||||
|
||||
bool game_is_vehicle_model(const ScriptArguments& args)
|
||||
{
|
||||
auto vdata = args.getWorld()->data->findObjectType<VehicleData>(args[1].integer);
|
||||
if( vdata )
|
||||
{
|
||||
auto vehicle = args.getObject<VehicleObject>(0);
|
||||
if ( vehicle ) {
|
||||
|
||||
return vehicle->model && vdata->modelName == vehicle->model->name;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
void game_set_player_heading(const ScriptArguments& args)
|
||||
{
|
||||
auto object = args.getPlayerCharacter(0);
|
||||
@ -1016,6 +1048,7 @@ ObjectModule::ObjectModule()
|
||||
|
||||
bindUnimplemented( 0x00F5, game_locate_character_in_sphere, 8, "Locate Player In Sphere" );
|
||||
bindFunction(0x00F6, game_player_near_point_on_foot_3D, 8, "Is Player near point on foot" );
|
||||
bindFunction(0x00F7, game_player_near_point_in_vehicle_3D, 8, "Is Player near point in car" );
|
||||
|
||||
bindFunction(0x0100, game_character_near_point_in_vehicle, 8, "Is Character near point in car" );
|
||||
|
||||
@ -1031,6 +1064,8 @@ ObjectModule::ObjectModule()
|
||||
bindFunction(0x0129, game_create_character_in_vehicle, 4, "Create Character In Car" );
|
||||
|
||||
bindUnimplemented(0x0135, game_set_vehicle_locked, 2, "Set Vehicle locked state");
|
||||
|
||||
bindFunction(0x0137, game_is_vehicle_model, 2, "Is Vehicle Model" );
|
||||
|
||||
bindFunction(0x0171, game_set_player_heading, 2, "Set Player Heading" );
|
||||
|
||||
|
@ -11,11 +11,13 @@ var app = angular.module('debugApp', []);
|
||||
app.controller('DebugCtrl', function($scope,$http) {
|
||||
$scope.threads = [];
|
||||
$scope.running = true;
|
||||
$scope.breakpoint = {};
|
||||
$scope.refresh = function() {
|
||||
var promise = $http.get('/state')
|
||||
.success(function(data, status, headers, config) {
|
||||
$scope.running = data.status == 'running';
|
||||
$scope.threads = data.threads;
|
||||
$scope.breakpoint = data.breakpoint;
|
||||
});
|
||||
};
|
||||
$scope.interrupt = function() {
|
||||
@ -73,10 +75,11 @@ html,body { font-family: sans-serif; }
|
||||
</div>
|
||||
<span id="running" style="background: green; color: white; padding: 2px; border-radius: 2px" ng-show="running">Game is running</span>
|
||||
<div>
|
||||
{{ breakpoint.program_counter }} {{ breakpoint.thread }}
|
||||
<h2>Threads</h2>
|
||||
<ul>
|
||||
<li ng-repeat="thread in threads">
|
||||
<h3>{{thread.name}}</h3>
|
||||
<h3>{{thread.name}} ({{thread.address}})</h3>
|
||||
<i>program counter: </i> {{thread.program_counter}}<br>
|
||||
<i>wake counter: </i> {{thread.wake_counter}} ms<br>
|
||||
<h4>Return Stack</h4>
|
||||
@ -87,7 +90,8 @@ html,body { font-family: sans-serif; }
|
||||
</ol>
|
||||
<h4>Disassembly</h4>
|
||||
<div class="disassembly">
|
||||
<div ng-repeat="call in thread.disassembly" class="instruction">
|
||||
<div ng-repeat="call in thread.disassembly" class="instruction"
|
||||
ng-class="(breakpoint.program_counter == call.address && thread.address == breakpoint.thread)? 'breakpoint' : ''">
|
||||
<span class="line-number">
|
||||
{{ call.address }}
|
||||
</span>
|
||||
@ -117,12 +121,11 @@ html,body { font-family: sans-serif; }
|
||||
</html>)";
|
||||
|
||||
HttpServer::HttpServer(RWGame* game, GameWorld* world)
|
||||
: game(game), world(world), paused(false)
|
||||
: game(game), world(world), paused(false), lastBreakpoint(nullptr)
|
||||
{}
|
||||
|
||||
void HttpServer::run()
|
||||
{
|
||||
listener.create();
|
||||
listener.listen(8091);
|
||||
|
||||
std::cout << "STARTING HTTP SERVER" << std::endl;
|
||||
@ -161,6 +164,7 @@ void HttpServer::run()
|
||||
|
||||
void HttpServer::handleBreakpoint(const SCMBreakpoint &bp)
|
||||
{
|
||||
lastBreakpoint = &bp;
|
||||
paused = true;
|
||||
|
||||
while( paused ) {
|
||||
@ -237,7 +241,7 @@ std::string thread_disassembly(ScriptMachine* script, SCMThread& th)
|
||||
ss << "{";
|
||||
if( script->getOpcodes()->findOpcode((uint16_t)opcode, &meta) )
|
||||
{
|
||||
ss << "\"address\": " << it->first << ","
|
||||
ss << "\"address\": \"" << it->first << "\","
|
||||
<< "\"function\": \"" << meta->signature << "\","
|
||||
<< "\"arguments\": [";
|
||||
SCMParams& parameters = it->second.parameters;
|
||||
@ -267,6 +271,7 @@ std::string thread(ScriptMachine* script, SCMThread& th)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "{"
|
||||
<< "\"address\": \"" << &th << "\","
|
||||
<< "\"program_counter\": " << th.programCounter << ","
|
||||
<< "\"name\": \"" << th.name << "\","
|
||||
<< "\"wake_counter\": " << th.wakeCounter << ","
|
||||
@ -276,6 +281,20 @@ std::string thread(ScriptMachine* script, SCMThread& th)
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string breakpoint(const SCMBreakpoint* breakpoint)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "{";
|
||||
if( breakpoint != nullptr )
|
||||
{
|
||||
ss << "\"program_counter\": " << breakpoint->pc << ","
|
||||
<< "\"thread\": \"" << breakpoint->thread << "\"";
|
||||
}
|
||||
ss << "}";
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string HttpServer::getState() const
|
||||
{
|
||||
if( !paused ) {
|
||||
@ -285,12 +304,14 @@ std::string HttpServer::getState() const
|
||||
std::stringstream ss;
|
||||
ss << "{";
|
||||
ss << R"("status":"interrupted",)";
|
||||
ss << R"("breakpoint": )" << breakpoint(lastBreakpoint) << ",";
|
||||
ss << R"("threads": [)";
|
||||
for(unsigned int i = 0; i < game->getScript()->getThreads().size(); ++i)
|
||||
{
|
||||
SCMThread& th = game->getScript()->getThreads()[i];
|
||||
bool last = (game->getScript()->getThreads().size() == i+1);
|
||||
ss << thread(game->getScript(), th) << (last ? "" : ",");
|
||||
if( i != 0 )
|
||||
ss << ",";
|
||||
SCMThread& th = game->getScript()->getThreads().at(i);
|
||||
ss << thread(game->getScript(), th);
|
||||
}
|
||||
ss << R"(])";
|
||||
ss << "}";
|
||||
@ -320,12 +341,14 @@ std::string HttpServer::dispatch(std::string method, std::string path)
|
||||
else if(path == "/step") {
|
||||
if( paused ) {
|
||||
game->getScript()->interuptNext();
|
||||
lastBreakpoint = nullptr;
|
||||
paused = false;
|
||||
}
|
||||
ss << getState();
|
||||
mime = "application/json";
|
||||
}
|
||||
else if(path == "/continue") {
|
||||
lastBreakpoint = nullptr;
|
||||
paused = false;
|
||||
ss << getState();
|
||||
mime = "application/json";
|
||||
|
@ -10,20 +10,6 @@
|
||||
#include <sys/socket.h>
|
||||
#endif
|
||||
|
||||
class ReuseableListener : public sf::TcpListener
|
||||
{
|
||||
public:
|
||||
void create() {
|
||||
char reuse = 1;
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
class HttpServer
|
||||
{
|
||||
public:
|
||||
@ -32,10 +18,11 @@ public:
|
||||
|
||||
void handleBreakpoint(const SCMBreakpoint& bp);
|
||||
private:
|
||||
ReuseableListener listener;
|
||||
sf::TcpListener listener;
|
||||
RWGame* game;
|
||||
GameWorld* world;
|
||||
bool paused;
|
||||
const SCMBreakpoint* lastBreakpoint;
|
||||
|
||||
std::string dispatch(std::string method, std::string path);
|
||||
std::string getState() const;
|
||||
|
Loading…
Reference in New Issue
Block a user