1
0
mirror of https://github.com/rwengine/openrw.git synced 2024-09-18 16:32:32 +02:00

Implement Script Disassembly

This commit is contained in:
Daniel Evans 2015-05-03 18:09:25 +01:00
parent 53d2e318a3
commit a690c8c08c
6 changed files with 207 additions and 117 deletions

View File

@ -0,0 +1,45 @@
#pragma once
#ifndef _SCRIPTDISASSEMBLY_HPP_
#define _SCRIPTDISASSEMBLY_HPP_
#include <script/ScriptTypes.hpp>
class SCMFile;
/**
* Extracts instruction level information from a SCM file
*/
class ScriptDisassembly
{
public:
/**
* Information about a single call to a single opcode and
* it's parameters
*/
struct InstructionInfo
{
SCMOpcode opcode;
SCMParams parameters;
};
ScriptDisassembly(SCMOpcodes* codes, SCMFile* scm);
/**
* Execute the disassembly routine.
*
* If there is an error during disassembly, an exeption will be
* thrown
*/
void disassemble(SCMAddress startAddress);
std::map<SCMAddress, InstructionInfo>& getInstructions() { return instructions; }
private:
SCMOpcodes* codes;
SCMFile* scm;
std::map<SCMAddress, InstructionInfo> instructions;
};
#endif

View File

@ -111,7 +111,7 @@ static SCMMicrocodeTable knownOps;
struct SCMThread
{
typedef unsigned int pc_t;
typedef SCMAddress pc_t;
char name[17];
pc_t baseAddress;

View File

@ -17,6 +17,7 @@ class GameWorld;
typedef uint16_t SCMOpcode;
typedef char SCMByte;
typedef unsigned int SCMAddress;
enum SCMType {
EndOfArgList = 0x00,

View File

@ -0,0 +1,92 @@
#include <script/ScriptDisassembly.hpp>
#include <script/SCMFile.hpp>
#include <script/ScriptMachine.hpp>
ScriptDisassembly::ScriptDisassembly(SCMOpcodes* _codes, SCMFile* _scm)
: codes(_codes), scm(_scm)
{
}
void ScriptDisassembly::disassemble(SCMAddress startAddress)
{
for( SCMAddress a = startAddress; a < scm->getMainSize(); )
{
auto opcode = scm->read<SCMOpcode>(a);
auto opcorg = opcode;
bool isNegatedConditional = ((opcode & SCM_NEGATE_CONDITIONAL_MASK) == SCM_NEGATE_CONDITIONAL_MASK);
opcode = opcode & ~SCM_NEGATE_CONDITIONAL_MASK;
ScriptFunctionMeta* foundcode;
if( ! codes->findOpcode(opcode, &foundcode) )
{
throw IllegalInstruction(opcode, a, "Disassembler");
}
ScriptFunctionMeta& code = *foundcode;
SCMParams parameters;
auto instructionAddress = a;
a += sizeof(SCMOpcode);
bool hasExtraParameters = code.arguments < 0;
auto requiredParams = std::abs(code.arguments);
for( int p = 0; p < requiredParams || hasExtraParameters; ++p ) {
auto type_r = scm->read<SCMByte>(a);
auto type = static_cast<SCMType>(type_r);
if( type_r > 42 ) {
// for implicit strings, we need the byte we just read.
type = TString;
}
else {
a += sizeof(SCMByte);
}
parameters.push_back(SCMOpcodeParameter { type, { 0 } });
switch(type) {
case EndOfArgList:
hasExtraParameters = false;
break;
case TInt8:
parameters.back().integer = scm->read<std::uint8_t>(a);
a += sizeof(SCMByte);
break;
case TInt16:
parameters.back().integer = scm->read<std::int16_t>(a);
a += sizeof(SCMByte) * 2;
break;
case TGlobal: {
auto v = scm->read<std::uint16_t>(a);
parameters.back().globalPtr = (void*)v; //* SCM_VARIABLE_SIZE;
a += sizeof(SCMByte) * 2;
}
break;
case TLocal: {
auto v = scm->read<std::uint16_t>(a);
parameters.back().globalPtr = (void*)(v * SCM_VARIABLE_SIZE);
a += sizeof(SCMByte) * 2;
}
break;
case TInt32:
parameters.back().integer = scm->read<std::uint32_t>(a);
a += sizeof(SCMByte) * 4;
break;
case TString:
std::copy(scm->data()+a, scm->data()+a+8,
parameters.back().string);
a += sizeof(SCMByte) * 8;
break;
case TFloat16:
parameters.back().real = scm->read<std::int16_t>(a) / 16.f;
a += sizeof(SCMByte) * 2;
break;
default:
throw UnknownType(type, a, "Disassembler");
break;
};
}
instructions[instructionAddress] = InstructionInfo { opcode, parameters };
}
}

View File

@ -3,6 +3,13 @@ add_executable(${SCRIPTTOOL} main.cpp)
include_directories(${CMAKE_SOURCE_DIR}/rwengine/include)
target_link_libraries(${SCRIPTTOOL} rwengine)
target_link_libraries(${SCRIPTTOOL}
rwengine
sfml-graphics
sfml-window
sfml-system
${OPENGL_LIBRARIES}
GLEW
${BULLET_LIBRARIES})
install(TARGETS ${SCRIPTTOOL} RUNTIME DESTINATION bin)

View File

@ -5,6 +5,10 @@
#include <script/SCMFile.hpp>
#include <script/ScriptMachine.hpp>
#include <script/ScriptDisassembly.hpp>
#include <script/modules/VMModule.hpp>
#include <script/modules/GameModule.hpp>
#include <script/modules/ObjectModule.hpp>
#define FIELD_DESC_WIDTH 30
#define FIELD_PARAM_WIDTH 8
@ -37,132 +41,70 @@ void dumpCodeSizes(SCMFile* file)
}
}
void dumpOpcodes(SCMFile* scm, unsigned int offset, unsigned int size)
void dumpOpcodes(SCMFile* scm, SCMOpcodes* codes, unsigned int offset, unsigned int size)
{
std::cout << "Offs Opcd " << std::setw(FIELD_DESC_WIDTH) << std::left
<< "Description" << "Parameters" << std::endl;
for( unsigned int i = offset; i < offset+size; ) {
SCMOpcode op = scm->read<SCMOpcode>(i) & ~SCM_NEGATE_CONDITIONAL_MASK;
ScriptDisassembly disassembly(codes, scm);
auto opit = knownOps.find( op );
try
{
disassembly.disassemble(offset);
}
catch( IllegalInstruction& ex )
{
std::cerr << "Error during disassembly: \n"
<< ex.what() << std::endl;
}
// If we don't know the size of the operator's parameters we can't jump over it.
if( opit == knownOps.end() ) {
throw IllegalInstruction(op, i, "");
for( auto& inst : disassembly.getInstructions() )
{
ScriptFunctionMeta* code;
if(! codes->findOpcode(inst.second.opcode, &code) )
{
std::cerr << "Invalid opcode in disassembly (" << inst.second.opcode << ")" << std::endl;
}
std::cout << std::hex << std::setfill('0') << std::right <<
std::setw(4) << i << ":" <<
std::setw(4) << op <<
std::setw(FIELD_DESC_WIDTH) << std::setfill(' ') <<
std::left << opit->second.name << std::right;
std::setw(4) << inst.first << ":" <<
std::setw(4) << inst.second.opcode << " " <<
std::setw(FIELD_DESC_WIDTH) << std::setfill(' ') <<
std::left << code->signature << std::right << "(";
i += sizeof(SCMOpcode);
bool hasMoreArgs = opit->second.parameters < 0;
for( int p = 0; p < std::abs(opit->second.parameters) || hasMoreArgs; ++p ) {
SCMByte datatype = scm->read<SCMByte>(i);
auto typeit = typeData.find(static_cast<SCMType>(datatype));
if( typeit == typeData.end()) {
if( datatype < 0x06 ) {
throw UnknownType(datatype, i, "");
}
else {
datatype = TString;
}
}
else {
i += sizeof(SCMByte);
}
std::cout << " " << std::setfill('0') << std::setw(2) <<
static_cast<unsigned int>(datatype) << ": ";
std::cout << std::setfill(' ') << std::setw(FIELD_PARAM_WIDTH);
switch( datatype ) {
case TInt32:
std::cout << std::dec << scm->read<int32_t>(i);
break;
case TInt16:
std::cout << std::dec << scm->read<int16_t>(i);
break;
case TGlobal:
case TLocal:
std::cout << std::hex << scm->read<int16_t>(i);
break;
case TInt8:
std::cout << std::dec << static_cast<int>(scm->read<int8_t>(i));
break;
case TFloat16:
std::cout << (float)scm->read<uint16_t>(i) / 16.f;
break;
case EndOfArgList:
hasMoreArgs = false;
break;
case TString: {
char strbuff[8];
for(size_t c = 0; c < 8; ++c) {
strbuff[c] = scm->read<char>(i++);
}
std::cout << strbuff << " ";
}
break;
default:
std::cout << "{unknown}";
break;
}
if( typeit != typeData.end() ) {
i += typeit->second.size;
for( SCMOpcodeParameter& param : inst.second.parameters )
{
switch( param.type )
{
case TInt8:
std::cout << " i8: " << param.integer;
break;
case TInt16:
std::cout << " i16: " << param.integer;
break;
case TInt32:
std::cout << " i32: " << param.integer;
break;
case TFloat16:
std::cout << " f16: " << param.real;
break;
case TString:
std::cout << " str: " << param.string;
break;
case TGlobal:
std::cout << " g: " << param.globalPtr;
break;
case TLocal:
std::cout << " l: " << param.globalPtr;
break;
}
}
std::cout << std::endl;
std::cout << " )\n";
}
}
void loadKnownOps(const std::string& dbfile)
{
std::ifstream dbstream(dbfile.c_str());
if( !dbstream.is_open() ) {
std::cerr << "Failed to open " << dbfile << std::endl;
return;
}
while( ! dbstream.eof() ) {
std::string line;
std::getline(dbstream, line);
auto fnws = line.find_first_not_of(" ");
if( fnws == line.npos || line.at(fnws) == '#' ) continue;
std::stringstream ss(line);
std::string sec;
std::getline(ss, sec, ',');
SCMMicrocode m;
uint16_t opcode = std::stoi(sec, 0, 16);
std::getline(ss, m.name, ',');
std::getline(ss, sec, ',');
m.parameters = std::stoi(sec);
std::getline(ss, sec, ',');
uint16_t flags = std::stoi(sec);
knownOps.insert({opcode, m});
}
std::cout << knownOps.size() << " known opcodes " << std::endl;
}
void readSCM(const std::string& scmname)
void disassemble(const std::string& scmname)
{
std::ifstream scmfile(scmname.c_str());
@ -194,8 +136,13 @@ void readSCM(const std::string& scmname)
dumpModels(&scm);
dumpCodeSizes(&scm);
dumpOpcodes(&scm, scm.getCodeSection(), size);
SCMOpcodes* opcodes = new SCMOpcodes;
opcodes->modules.push_back(new VMModule);
opcodes->modules.push_back(new GameModule);
opcodes->modules.push_back(new ObjectModule);
dumpOpcodes(&scm, opcodes, scm.getCodeSection(), size);
}
catch (SCMException& ex) {
std::cerr << ex.what() << std::endl;
@ -210,9 +157,7 @@ int main(int argc, char** argv)
return 1;
}
loadKnownOps("knownops.txt");
readSCM(std::string(argv[1]));
disassemble(std::string(argv[1]));
return 0;
}