Merge remote-tracking branch 'gzdoom/master' into big_beautiful_merge
This commit is contained in:
commit
3fdd22ef91
1433 changed files with 484787 additions and 9566 deletions
|
|
@ -3326,11 +3326,11 @@ FxExpression *FxAddSub::Resolve(FCompileContext& ctx)
|
|||
|
||||
if (compileEnvironment.CheckForCustomAddition)
|
||||
{
|
||||
auto result = compileEnvironment.CheckForCustomAddition(this, ctx);
|
||||
if (result)
|
||||
auto expr = compileEnvironment.CheckForCustomAddition(this, ctx);
|
||||
if (expr)
|
||||
{
|
||||
ABORT(right);
|
||||
goto goon;
|
||||
delete this;
|
||||
return expr->Resolve(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -6701,7 +6701,7 @@ FxExpression *FxIdentifier::Resolve(FCompileContext& ctx)
|
|||
}
|
||||
FxExpression *self = new FxSelf(ScriptPosition);
|
||||
self = self->Resolve(ctx);
|
||||
newex = ResolveMember(ctx, ctx.Function->Variants[0].SelfClass, self, ctx.Function->Variants[0].SelfClass);
|
||||
newex = ResolveMember(ctx, ctx.Function->Variants[0].SelfClass, self, ctx.Function->Variants[0].SelfClass, ctx.Function->Variants[0].Flags & VARF_SafeConst);
|
||||
ABORT(newex);
|
||||
goto foundit;
|
||||
}
|
||||
|
|
@ -6863,7 +6863,7 @@ foundit:
|
|||
//
|
||||
//==========================================================================
|
||||
|
||||
FxExpression *FxIdentifier::ResolveMember(FCompileContext &ctx, PContainerType *classctx, FxExpression *&object, PContainerType *objtype)
|
||||
FxExpression *FxIdentifier::ResolveMember(FCompileContext &ctx, PContainerType *classctx, FxExpression *&object, PContainerType *objtype, bool isConst)
|
||||
{
|
||||
PSymbol *sym;
|
||||
PSymbolTable *symtbl;
|
||||
|
|
@ -6956,7 +6956,7 @@ FxExpression *FxIdentifier::ResolveMember(FCompileContext &ctx, PContainerType *
|
|||
}
|
||||
}
|
||||
|
||||
auto x = isclass ? new FxClassMember(object, vsym, ScriptPosition) : new FxStructMember(object, vsym, ScriptPosition);
|
||||
auto x = isclass ? new FxClassMember(object, vsym, ScriptPosition, isConst) : new FxStructMember(object, vsym, ScriptPosition, isConst);
|
||||
object = nullptr;
|
||||
return x->Resolve(ctx);
|
||||
}
|
||||
|
|
@ -7611,8 +7611,8 @@ FxMemberBase::FxMemberBase(EFxType type, PField *f, const FScriptPosition &p)
|
|||
}
|
||||
|
||||
|
||||
FxStructMember::FxStructMember(FxExpression *x, PField* mem, const FScriptPosition &pos)
|
||||
: FxMemberBase(EFX_StructMember, mem, pos)
|
||||
FxStructMember::FxStructMember(FxExpression *x, PField* mem, const FScriptPosition &pos, bool isConst)
|
||||
: FxMemberBase(EFX_StructMember, mem, pos), IsConst(isConst)
|
||||
{
|
||||
classx = x;
|
||||
}
|
||||
|
|
@ -7662,7 +7662,7 @@ bool FxStructMember::RequestAddress(FCompileContext &ctx, bool *writable)
|
|||
bWritable = false;
|
||||
}
|
||||
|
||||
*writable = bWritable;
|
||||
*writable = bWritable && !IsConst;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
@ -7873,8 +7873,8 @@ ExpEmit FxStructMember::Emit(VMFunctionBuilder *build)
|
|||
//
|
||||
//==========================================================================
|
||||
|
||||
FxClassMember::FxClassMember(FxExpression *x, PField* mem, const FScriptPosition &pos)
|
||||
: FxStructMember(x, mem, pos)
|
||||
FxClassMember::FxClassMember(FxExpression *x, PField* mem, const FScriptPosition &pos, bool isConst)
|
||||
: FxStructMember(x, mem, pos, isConst)
|
||||
{
|
||||
ExprType = EFX_ClassMember;
|
||||
}
|
||||
|
|
@ -8072,7 +8072,6 @@ ExpEmit FxArrayElement::Emit(VMFunctionBuilder *build)
|
|||
{
|
||||
arraytype = static_cast<PArray*>(Array->ValueType);
|
||||
}
|
||||
ExpEmit arrayvar = Array->Emit(build);
|
||||
ExpEmit start;
|
||||
ExpEmit bound;
|
||||
bool nestedarray = false;
|
||||
|
|
@ -8080,31 +8079,99 @@ ExpEmit FxArrayElement::Emit(VMFunctionBuilder *build)
|
|||
if (SizeAddr != ~0u)
|
||||
{
|
||||
bool ismeta = Array->ExprType == EFX_ClassMember && static_cast<FxClassMember*>(Array)->membervar->Flags & VARF_Meta;
|
||||
|
||||
start = ExpEmit(build, REGT_POINTER);
|
||||
build->Emit(OP_LP, start.RegNum, arrayvar.RegNum, build->GetConstantInt(0));
|
||||
|
||||
|
||||
auto f = Create<PField>(NAME_None, TypeUInt32, ismeta? VARF_Meta : 0, SizeAddr);
|
||||
auto arraymemberbase = static_cast<FxMemberBase *>(Array);
|
||||
|
||||
auto origmembervar = arraymemberbase->membervar;
|
||||
auto origaddrreq = arraymemberbase->AddressRequested;
|
||||
auto origvaluetype = Array->ValueType;
|
||||
if (Array->ExprType == EFX_StructMember || Array->ExprType == EFX_ClassMember)
|
||||
{
|
||||
struct DummyVar : public FxExpression
|
||||
{
|
||||
ExpEmit dummy;
|
||||
DummyVar(ExpEmit e) : FxExpression(EFX_Expression,{}), dummy(e){}
|
||||
ExpEmit Emit(VMFunctionBuilder *build)
|
||||
{
|
||||
return dummy;
|
||||
};
|
||||
};
|
||||
|
||||
arraymemberbase->membervar = f;
|
||||
arraymemberbase->AddressRequested = false;
|
||||
Array->ValueType = TypeUInt32;
|
||||
//fix expression bug
|
||||
FxStructMember * orig = static_cast<FxStructMember *>(Array);
|
||||
FxExpression * prev = orig->classx;
|
||||
ExpEmit objvar = prev->Emit(build);
|
||||
bool wasFixed = objvar.Fixed;
|
||||
|
||||
bound = Array->Emit(build);
|
||||
objvar.Fixed = true;
|
||||
|
||||
arraymemberbase->membervar = origmembervar;
|
||||
arraymemberbase->AddressRequested = origaddrreq;
|
||||
Array->ValueType = origvaluetype;
|
||||
orig->classx = new DummyVar(objvar);
|
||||
orig->classx->ValueType = prev->ValueType;
|
||||
ExpEmit arrayvar = Array->Emit(build);
|
||||
start = ExpEmit(build, REGT_POINTER);
|
||||
delete orig->classx;
|
||||
orig->classx = prev;
|
||||
|
||||
arrayvar.Free(build);
|
||||
build->Emit(OP_LP, start.RegNum, arrayvar.RegNum, build->GetConstantInt(0));
|
||||
|
||||
if(ismeta)
|
||||
{
|
||||
// [Jay0]
|
||||
// ugh do it the old way for meta since i don't want to duplicate that code here, but this time it only double-emits the member access, not the function call, so no bad side-effects, still not "right" though
|
||||
// TODO handle meta better
|
||||
|
||||
auto origmembervar = arraymemberbase->membervar;
|
||||
auto origaddrreq = arraymemberbase->AddressRequested;
|
||||
auto origvaluetype = Array->ValueType;
|
||||
|
||||
arraymemberbase->membervar = f;
|
||||
arraymemberbase->AddressRequested = false;
|
||||
Array->ValueType = TypeUInt32;
|
||||
|
||||
bound = Array->Emit(build);
|
||||
|
||||
arraymemberbase->membervar = origmembervar;
|
||||
arraymemberbase->AddressRequested = origaddrreq;
|
||||
Array->ValueType = origvaluetype;
|
||||
}
|
||||
else
|
||||
{
|
||||
bound = ExpEmit(build, REGT_INT);
|
||||
build->Emit(OP_LW, bound.RegNum, objvar.RegNum, build->GetConstantInt((int)SizeAddr));
|
||||
}
|
||||
|
||||
objvar.Fixed = wasFixed;
|
||||
objvar.Free(build);
|
||||
arrayvar.Free(build);
|
||||
}
|
||||
else
|
||||
{
|
||||
// [Jay0]
|
||||
// now only runs for global variables and stack variables, so the double-emit is """fine"""
|
||||
// TODO replace this entirely with something better still
|
||||
|
||||
ExpEmit arrayvar = Array->Emit(build);
|
||||
|
||||
start = ExpEmit(build, REGT_POINTER);
|
||||
build->Emit(OP_LP, start.RegNum, arrayvar.RegNum, build->GetConstantInt(0));
|
||||
|
||||
auto origmembervar = arraymemberbase->membervar;
|
||||
auto origaddrreq = arraymemberbase->AddressRequested;
|
||||
auto origvaluetype = Array->ValueType;
|
||||
|
||||
arraymemberbase->membervar = f;
|
||||
arraymemberbase->AddressRequested = false;
|
||||
Array->ValueType = TypeUInt32;
|
||||
|
||||
bound = Array->Emit(build);
|
||||
|
||||
arraymemberbase->membervar = origmembervar;
|
||||
arraymemberbase->AddressRequested = origaddrreq;
|
||||
Array->ValueType = origvaluetype;
|
||||
arrayvar.Free(build);
|
||||
}
|
||||
}
|
||||
else if ((Array->ExprType == EFX_ArrayElement || Array->ExprType == EFX_OutVarDereference) && Array->isStaticArray())
|
||||
{
|
||||
ExpEmit arrayvar = Array->Emit(build);
|
||||
bound = ExpEmit(build, REGT_INT);
|
||||
build->Emit(OP_LW, bound.RegNum, arrayvar.RegNum, build->GetConstantInt(myoffsetof(FArray, Count)));
|
||||
|
||||
|
|
@ -8114,7 +8181,10 @@ ExpEmit FxArrayElement::Emit(VMFunctionBuilder *build)
|
|||
|
||||
nestedarray = true;
|
||||
}
|
||||
else start = arrayvar;
|
||||
else
|
||||
{
|
||||
start = Array->Emit(build);
|
||||
}
|
||||
|
||||
if (index->isConstant())
|
||||
{
|
||||
|
|
@ -9063,7 +9133,7 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx)
|
|||
else if (Self->IsQuaternion())
|
||||
{
|
||||
// Reuse vector built-ins for quaternion
|
||||
if (MethodName == NAME_Length || MethodName == NAME_LengthSquared || MethodName == NAME_Unit)
|
||||
if (MethodName == NAME_Length || MethodName == NAME_LengthSquared || MethodName == NAME_Unit || MethodName == NAME_Conjugate || MethodName == NAME_Inverse)
|
||||
{
|
||||
if (ArgList.Size() > 0)
|
||||
{
|
||||
|
|
@ -10361,6 +10431,9 @@ FxExpression *FxVectorBuiltin::Resolve(FCompileContext &ctx)
|
|||
ValueType = TypeFloat64;
|
||||
break;
|
||||
|
||||
case NAME_Conjugate:
|
||||
case NAME_Inverse:
|
||||
assert(Self->IsQuaternion());
|
||||
case NAME_Unit:
|
||||
ValueType = Self->ValueType;
|
||||
break;
|
||||
|
|
@ -10413,6 +10486,18 @@ ExpEmit FxVectorBuiltin::Emit(VMFunctionBuilder *build)
|
|||
build->Emit(vecSize == 2 ? OP_DIVVF2_RR : vecSize == 3 ? OP_DIVVF3_RR : OP_DIVVF4_RR, to.RegNum, op.RegNum, len.RegNum);
|
||||
len.Free(build);
|
||||
}
|
||||
else if (Function == NAME_Conjugate)
|
||||
{
|
||||
build->Emit(OP_CONJQ, to.RegNum, op.RegNum);
|
||||
}
|
||||
else if (Function == NAME_Inverse)
|
||||
{
|
||||
ExpEmit len(build, REGT_FLOAT);
|
||||
build->Emit(OP_DOTV4_RR, len.RegNum, op.RegNum, op.RegNum);
|
||||
build->Emit(OP_CONJQ, to.RegNum, op.RegNum);
|
||||
build->Emit(OP_DIVVF4_RR, to.RegNum, to.RegNum, len.RegNum);
|
||||
len.Free(build);
|
||||
}
|
||||
else if (Function == NAME_Angle)
|
||||
{
|
||||
build->Emit(OP_ATAN2, to.RegNum, op.RegNum + 1, op.RegNum);
|
||||
|
|
@ -10878,12 +10963,17 @@ FxExpression *FxCompoundStatement::Resolve(FCompileContext &ctx)
|
|||
|
||||
ExpEmit FxCompoundStatement::Emit(VMFunctionBuilder *build)
|
||||
{
|
||||
auto start = build->GetAddress();
|
||||
auto e = FxSequence::Emit(build);
|
||||
TArray<VMLocalVariable> locals;
|
||||
// Release all local variables in this block.
|
||||
for (auto l : LocalVars)
|
||||
{
|
||||
locals.Push({l->Name, l->ValueType, l->VarFlags, l->RegCount, l->RegNum, l->ScriptPosition.ScriptLine, l->StackOffset});
|
||||
l->Release(build);
|
||||
}
|
||||
auto end = build->GetAddress();
|
||||
build->AddBlock(locals, start, end);
|
||||
return e;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -316,6 +316,8 @@ enum EFxType
|
|||
EFX_LocalArrayDeclaration,
|
||||
EFX_OutVarDereference,
|
||||
EFX_ToVector,
|
||||
EFX_FStateOffset,
|
||||
|
||||
EFX_COUNT
|
||||
};
|
||||
|
||||
|
|
@ -396,7 +398,7 @@ public:
|
|||
|
||||
FxIdentifier(FName i, const FScriptPosition &p);
|
||||
FxExpression *Resolve(FCompileContext&);
|
||||
FxExpression *ResolveMember(FCompileContext&, PContainerType*, FxExpression*&, PContainerType*);
|
||||
FxExpression *ResolveMember(FCompileContext&, PContainerType*, FxExpression*&, PContainerType*, bool isConst = false);
|
||||
};
|
||||
|
||||
|
||||
|
|
@ -1475,8 +1477,9 @@ class FxStructMember : public FxMemberBase
|
|||
{
|
||||
public:
|
||||
FxExpression *classx;
|
||||
bool IsConst;
|
||||
|
||||
FxStructMember(FxExpression*, PField*, const FScriptPosition&);
|
||||
FxStructMember(FxExpression*, PField*, const FScriptPosition&, bool isConst = false);
|
||||
~FxStructMember();
|
||||
FxExpression *Resolve(FCompileContext&);
|
||||
bool RequestAddress(FCompileContext &ctx, bool *writable);
|
||||
|
|
@ -1494,7 +1497,7 @@ class FxClassMember : public FxStructMember
|
|||
{
|
||||
public:
|
||||
|
||||
FxClassMember(FxExpression*, PField*, const FScriptPosition&);
|
||||
FxClassMember(FxExpression*, PField*, const FScriptPosition&, bool isConst = false);
|
||||
};
|
||||
|
||||
//==========================================================================
|
||||
|
|
@ -2387,7 +2390,7 @@ public:
|
|||
struct CompileEnvironment
|
||||
{
|
||||
FxExpression* (*SpecialTypeCast)(FxTypeCast* func, FCompileContext& ctx);
|
||||
bool (*CheckForCustomAddition)(FxAddSub* func, FCompileContext& ctx);
|
||||
FxExpression* (*CheckForCustomAddition)(FxAddSub* func, FCompileContext& ctx);
|
||||
FxExpression* (*CheckSpecialIdentifier)(FxIdentifier* func, FCompileContext& ctx);
|
||||
FxExpression* (*CheckSpecialGlobalIdentifier)(FxIdentifier* func, FCompileContext& ctx);
|
||||
FxExpression* (*ResolveSpecialIdentifier)(FxIdentifier* func, FxExpression*& object, PContainerType* objtype, FCompileContext& ctx);
|
||||
|
|
|
|||
|
|
@ -148,6 +148,14 @@ void VMFunctionBuilder::MakeFunction(VMScriptFunction *func)
|
|||
func->MaxParam = MaxParam;
|
||||
func->StackSize = VMFrame::FrameSize(func->NumRegD, func->NumRegF, func->NumRegS, func->NumRegA, func->MaxParam, func->ExtraSpace);
|
||||
|
||||
int i = 0;
|
||||
for (auto &block : Blocks)
|
||||
{
|
||||
|
||||
std::pair new_key = { func->Code + block.first.first, func->Code + block.first.second };
|
||||
func->LocalVariableBlocks.Push({{new_key.first, new_key.second}, block.second});
|
||||
i++;
|
||||
}
|
||||
// Technically, there's no reason why we can't end the function with
|
||||
// entries on the parameter stack, but it means the caller probably
|
||||
// did something wrong.
|
||||
|
|
@ -201,6 +209,11 @@ void VMFunctionBuilder::FillStringConstants(FString *konst)
|
|||
}
|
||||
}
|
||||
|
||||
void VMFunctionBuilder::AddBlock(const TArray<VMLocalVariable> &block, size_t start, size_t end)
|
||||
{
|
||||
Blocks.Push( {{ start, end }, block});
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// VMFunctionBuilder :: GetConstantInt
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@
|
|||
#include <vector>
|
||||
#include <functional>
|
||||
|
||||
|
||||
class FxCompoundStatement;
|
||||
class VMFunctionBuilder;
|
||||
class FxExpression;
|
||||
class FxLocalVariableDeclaration;
|
||||
|
|
@ -52,6 +54,8 @@ public:
|
|||
friend class VMFunctionBuilder;
|
||||
};
|
||||
|
||||
using BlockMap = TArray<std::pair<std::pair<size_t, size_t>, TArray<VMLocalVariable>>>;
|
||||
|
||||
VMFunctionBuilder(int numimplicits);
|
||||
~VMFunctionBuilder();
|
||||
|
||||
|
|
@ -97,6 +101,8 @@ public:
|
|||
void FillAddressConstants(FVoidObj *konst);
|
||||
void FillStringConstants(FString *strings);
|
||||
|
||||
void AddBlock(const TArray<VMLocalVariable> &vars, size_t start, size_t end);
|
||||
|
||||
// PARAM increases ActiveParam; CALL decreases it.
|
||||
void ParamChange(int delta);
|
||||
|
||||
|
|
@ -128,7 +134,7 @@ private:
|
|||
int ActiveParam;
|
||||
|
||||
TArray<VMOP> Code;
|
||||
|
||||
BlockMap Blocks;
|
||||
};
|
||||
|
||||
void DumpFunction(FILE *dump, VMScriptFunction *sfunc, const char *label, int labellen);
|
||||
|
|
|
|||
|
|
@ -170,7 +170,7 @@ VersionInfo PField::GetVersion()
|
|||
{
|
||||
VersionInfo Highest = { 0,0,0 };
|
||||
if (!(Flags & VARF_Deprecated)) Highest = mVersion;
|
||||
if (Type->mVersion > Highest) Highest = Type->mVersion;
|
||||
if (Type->mVersion > Highest && !Type->TypeDeprecated) Highest = Type->mVersion;
|
||||
return Highest;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1493,6 +1493,7 @@ PPointer::PPointer(PType *pointsat, bool isconst)
|
|||
{
|
||||
mDescriptiveName.Format("Pointer<%s%s>", pointsat->DescriptiveName(), isconst ? "readonly " : "");
|
||||
mVersion = pointsat->mVersion;
|
||||
TypeDeprecated = pointsat->TypeDeprecated;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -1674,7 +1675,11 @@ PClassPointer::PClassPointer(PClass *restrict)
|
|||
loadOp = OP_LP;
|
||||
storeOp = OP_SP;
|
||||
Flags |= TYPE_ClassPointer;
|
||||
if (restrict) mVersion = restrict->VMType->mVersion;
|
||||
if (restrict)
|
||||
{
|
||||
mVersion = restrict->VMType->mVersion;
|
||||
TypeDeprecated = restrict->VMType->TypeDeprecated;
|
||||
}
|
||||
else mVersion = 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ enum
|
|||
VARF_VirtualScope = (1<<22), // [ZZ] virtualscope: object should use the scope of the particular class it's being used with (methods only)
|
||||
VARF_ClearScope = (1<<23), // [ZZ] clearscope: this method ignores the member access chain that leads to it and is always plain data.
|
||||
VARF_Abstract = (1<<24), // [Player701] Function does not have a body and must be overridden in subclasses
|
||||
VARF_SafeConst = (1<<25), // [Jay] properly-working const function/unsafe clearscope field
|
||||
};
|
||||
|
||||
// Basic information shared by all types ------------------------------------
|
||||
|
|
@ -110,6 +111,8 @@ public:
|
|||
bool SizeKnown = true;
|
||||
|
||||
bool VMInternalStruct = false;
|
||||
bool TypeDeprecated = false; // mVersion is deprecation version, not minimum version
|
||||
FString mDeprecationMessage;
|
||||
|
||||
PType * LocalType = nullptr;
|
||||
|
||||
|
|
|
|||
|
|
@ -266,7 +266,7 @@ void VMDumpConstants(FILE *out, const VMScriptFunction *func)
|
|||
}
|
||||
}
|
||||
|
||||
void VMDisasm(FILE *out, const VMOP *code, int codesize, const VMScriptFunction *func)
|
||||
void VMDisasm(FILE *out, const VMOP *code, int codesize, const VMScriptFunction *func, uint64_t starting_offset)
|
||||
{
|
||||
VMFunction *callfunc = nullptr;
|
||||
const char *name;
|
||||
|
|
@ -322,13 +322,13 @@ void VMDisasm(FILE *out, const VMOP *code, int codesize, const VMScriptFunction
|
|||
name = cmpname;
|
||||
}
|
||||
}
|
||||
printf_wrapper(out, "%08x: %02x%02x%02x%02x %-8s", i << 2, code[i].op, code[i].a, code[i].b, code[i].c, name);
|
||||
printf_wrapper(out, "%08llx: %02x%02x%02x%02x %-8s", starting_offset + (i << 2), code[i].op, code[i].a, code[i].b, code[i].c, name);
|
||||
col = 0;
|
||||
switch (code[i].op)
|
||||
{
|
||||
case OP_JMP:
|
||||
//case OP_TRY:
|
||||
col = printf_wrapper(out, "%08x", (i + 1 + code[i].i24) << 2);
|
||||
col = printf_wrapper(out, "%08llx", starting_offset + ((i + 1 + code[i].i24) << 2));
|
||||
break;
|
||||
|
||||
case OP_PARAMI:
|
||||
|
|
@ -510,7 +510,7 @@ void VMDisasm(FILE *out, const VMOP *code, int codesize, const VMScriptFunction
|
|||
}
|
||||
else
|
||||
{
|
||||
col += printf_wrapper(out, " => %08x", (i + 2 + code[i+1].i24) << 2);
|
||||
col += printf_wrapper(out, " => %08llx", starting_offset + ((i + 2 + code[i+1].i24) << 2));
|
||||
}
|
||||
}
|
||||
if (col > 30)
|
||||
|
|
|
|||
492
src/common/scripting/dap/BreakpointManager.cpp
Normal file
492
src/common/scripting/dap/BreakpointManager.cpp
Normal file
|
|
@ -0,0 +1,492 @@
|
|||
#include "BreakpointManager.h"
|
||||
#include <cstdint>
|
||||
#include <regex>
|
||||
#include "Utilities.h"
|
||||
#include "RuntimeEvents.h"
|
||||
#include "GameInterfaces.h"
|
||||
#include "DebugExecutionManager.h"
|
||||
|
||||
namespace DebugServer
|
||||
{
|
||||
|
||||
int64_t BreakpointManager::GetBreakpointID()
|
||||
{
|
||||
++m_CurrentID;
|
||||
int64_t id = m_CurrentID;
|
||||
if (id < 0)
|
||||
{
|
||||
m_CurrentID = 1;
|
||||
id = m_CurrentID;
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
|
||||
int BreakpointManager::AddInvalidBreakpoint(
|
||||
std::vector<dap::Breakpoint> &breakpoints, int line, void *address, const std::string &reason, const dap::optional<dap::Source> &source = {})
|
||||
{
|
||||
auto breakpointId = GetBreakpointID();
|
||||
dap::Breakpoint &breakpoint = breakpoints.emplace_back();
|
||||
breakpoint.id = breakpointId;
|
||||
breakpoint.message = reason;
|
||||
breakpoint.source = source;
|
||||
breakpoint.verified = false;
|
||||
breakpoint.reason = "failed";
|
||||
if (line)
|
||||
{
|
||||
breakpoint.line = line;
|
||||
}
|
||||
if (address)
|
||||
{
|
||||
breakpoint.instructionReference = AddrToString(nullptr, address);
|
||||
}
|
||||
return breakpointId;
|
||||
}
|
||||
|
||||
bool BreakpointManager::AddBreakpointInfo(
|
||||
const std::shared_ptr<Binary> &binary,
|
||||
VMScriptFunction *function,
|
||||
int line,
|
||||
void *p_instrRef,
|
||||
int offset,
|
||||
BreakpointInfo::Type type,
|
||||
std::vector<dap::Breakpoint> &r_bpoint,
|
||||
const std::string &funcText)
|
||||
{
|
||||
// Only call this with positional breakpoints (line, script function, instruction)
|
||||
assert(p_instrRef != nullptr);
|
||||
int64_t breakpointId = GetBreakpointID();
|
||||
int sourceRef = -1;
|
||||
if (binary)
|
||||
{
|
||||
sourceRef = binary->GetScriptRef();
|
||||
}
|
||||
auto instrRef = (void *)(static_cast<char *>(p_instrRef) + offset);
|
||||
bool found = false;
|
||||
if (m_breakpoints.find(instrRef) != m_breakpoints.end())
|
||||
{
|
||||
for (auto &binfo : m_breakpoints[instrRef])
|
||||
{
|
||||
if (binfo.type == type)
|
||||
{
|
||||
if (sourceRef == -1 || binfo.ref == sourceRef)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_breakpoints[instrRef] = {};
|
||||
}
|
||||
auto &binfo = m_breakpoints[instrRef].emplace_back();
|
||||
binfo.type = type;
|
||||
binfo.ref = sourceRef;
|
||||
binfo.funcBreakpointText = funcText;
|
||||
binfo.bpoint.id = breakpointId;
|
||||
binfo.bpoint.line = line;
|
||||
binfo.bpoint.instructionReference = AddrToString(function, p_instrRef);
|
||||
if (offset)
|
||||
{
|
||||
binfo.bpoint.offset = offset;
|
||||
}
|
||||
if (binary) binfo.bpoint.source = binary->GetDapSource();
|
||||
binfo.bpoint.verified = false;
|
||||
// Only send back one breakpoint per line for line breakpoints, or the DAP client will get confused
|
||||
if (type == BreakpointInfo::Type::Line)
|
||||
{
|
||||
for (auto &kv : m_breakpoints)
|
||||
{
|
||||
for (auto &existing : kv.second)
|
||||
{
|
||||
// not the one we just added and the same type
|
||||
if (binfo.bpoint.id != existing.bpoint.id && existing.type == type)
|
||||
{
|
||||
if ((sourceRef == -1 || existing.ref == sourceRef) && (existing.bpoint.line.value(0) == line))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
binfo.bpoint.verified = true;
|
||||
r_bpoint.push_back(binfo.bpoint);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void BreakpointManager::GetBpointsForResponse(BreakpointInfo::Type type, std::vector<dap::Breakpoint> &responseBpoints)
|
||||
{
|
||||
for (const auto &bPoints : m_breakpoints)
|
||||
{
|
||||
if (bPoints.second.empty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (bPoints.second[0].type != type)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
for (const auto &bp : bPoints.second)
|
||||
{
|
||||
responseBpoints.push_back(bp.bpoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dap::ResponseOrError<dap::SetBreakpointsResponse> BreakpointManager::SetBreakpoints(const dap::Source &source, const dap::SetBreakpointsRequest &request)
|
||||
{
|
||||
RETURN_COND_DAP_ERROR(!request.breakpoints.has_value(), "SetBreakpoints: No breakpoints provided!");
|
||||
auto &srcBreakpoints = request.breakpoints.value();
|
||||
dap::SetBreakpointsResponse response;
|
||||
std::set<int> breakpointLines;
|
||||
auto scriptPath = source.name.value("");
|
||||
auto sourceRef = GetSourceReference(source);
|
||||
|
||||
std::map<int, BreakpointInfo> foundBreakpoints;
|
||||
auto addInvalidBreakpoint = [&](int line, const std::string &reason, bool shouldLog = true)
|
||||
{
|
||||
if (shouldLog)
|
||||
{
|
||||
LogError("SetBreakpoints: %s", reason.c_str());
|
||||
}
|
||||
AddInvalidBreakpoint(response.breakpoints, line, nullptr, reason, source);
|
||||
};
|
||||
ClearBreakpointsForScript(sourceRef, BreakpointInfo::Type::Line);
|
||||
|
||||
auto binary = m_pexCache->GetScript(source);
|
||||
if (!binary)
|
||||
{
|
||||
// check if the archive name is loaded
|
||||
auto archive_name = source.origin.value("");
|
||||
int containerNum = fileSystem.CheckIfResourceFileLoaded(archive_name.c_str());
|
||||
std::string error_message = containerNum == -1 ? StringFormat("%s: Archive %s not loaded", scriptPath.c_str(), archive_name.c_str())
|
||||
: StringFormat("%s: Could not find script in loaded sources!", scriptPath.c_str());
|
||||
for (const auto &srcBreakpoint : srcBreakpoints)
|
||||
{
|
||||
addInvalidBreakpoint(srcBreakpoint.line, error_message);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
if (!binary->HasFunctions())
|
||||
{
|
||||
for (const auto &srcBreakpoint : srcBreakpoints)
|
||||
{
|
||||
addInvalidBreakpoint(srcBreakpoint.line, StringFormat("Script %s is present but not loaded", scriptPath.c_str()));
|
||||
}
|
||||
return response;
|
||||
}
|
||||
else if (!binary->HasFunctionLines())
|
||||
{
|
||||
for (const auto &srcBreakpoint : srcBreakpoints)
|
||||
{
|
||||
addInvalidBreakpoint(srcBreakpoint.line, StringFormat("No debug info found for script %s", scriptPath.c_str()));
|
||||
}
|
||||
}
|
||||
int srcRef = binary->GetScriptRef();
|
||||
for (const auto &srcBreakpoint : srcBreakpoints)
|
||||
{
|
||||
int breakpointsSet = 0;
|
||||
int line = static_cast<int>(srcBreakpoint.line);
|
||||
int instructionNum = -1;
|
||||
int64_t breakpointId = -1;
|
||||
auto found = binary->FindFunctionRangesByLine(line);
|
||||
if (found.size() == 0)
|
||||
{
|
||||
addInvalidBreakpoint(line, "Invalid instruction", false);
|
||||
continue;
|
||||
}
|
||||
|
||||
while (!found.empty())
|
||||
{
|
||||
auto func = found.top()->mapped();
|
||||
if (func == nullptr || IsFunctionAbstract(func) || func->LineInfoCount == 0)
|
||||
{
|
||||
found.pop();
|
||||
continue;
|
||||
}
|
||||
for (unsigned int i = 0; i < func->LineInfoCount; i++)
|
||||
{
|
||||
if (func->LineInfo[i].LineNumber == line)
|
||||
{
|
||||
instructionNum = func->LineInfo[i].InstructionIndex;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (instructionNum == -1)
|
||||
{
|
||||
found.pop();
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
void *instrRef = func->Code + instructionNum;
|
||||
auto actualBin = binary;
|
||||
|
||||
// Mixin; find the actual script
|
||||
if (srcRef != GetScriptReference(func->SourceFileName.GetChars()))
|
||||
{
|
||||
actualBin = m_pexCache->GetScript(func->SourceFileName.GetChars());
|
||||
if (!actualBin)
|
||||
{
|
||||
addInvalidBreakpoint(line, StringFormat("Could not find script %s in loaded sources!", func->SourceFileName.GetChars()));
|
||||
found.pop();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (AddBreakpointInfo(actualBin, func, line, instrRef, 0, BreakpointInfo::Type::Line, response.breakpoints))
|
||||
{
|
||||
breakpointId = response.breakpoints.back().id.value(-1);
|
||||
}
|
||||
found.pop();
|
||||
breakpointsSet++;
|
||||
}
|
||||
if (breakpointId == -1)
|
||||
{
|
||||
addInvalidBreakpoint(line, StringFormat("No function found for line %d in script %s", line, scriptPath.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
|
||||
dap::ResponseOrError<dap::SetFunctionBreakpointsResponse> BreakpointManager::SetFunctionBreakpoints(const dap::SetFunctionBreakpointsRequest &request)
|
||||
{
|
||||
auto &breakpoints = request.breakpoints;
|
||||
dap::SetFunctionBreakpointsResponse response;
|
||||
// each request clears the previous function breakpoints
|
||||
ClearBreakpointsType(BreakpointInfo::Type::Function);
|
||||
m_nativeFunctionBreakpoints.clear();
|
||||
int bpointCount = 0;
|
||||
|
||||
for (const auto &breakpoint : breakpoints)
|
||||
{
|
||||
auto fullFuncName = breakpoint.name;
|
||||
// function names are `class.function`
|
||||
auto func_name_parts = Split(fullFuncName, ".");
|
||||
|
||||
if (func_name_parts.size() != 2)
|
||||
{
|
||||
AddInvalidBreakpoint(response.breakpoints, 1, nullptr, StringFormat("Invalid function name %s", fullFuncName.c_str()));
|
||||
continue;
|
||||
}
|
||||
auto className = FName(func_name_parts[0]);
|
||||
auto functionName = FName(func_name_parts[1]);
|
||||
auto cls = PClass::FindClass(className);
|
||||
auto func = PClass::FindFunction(className, functionName);
|
||||
if (!func)
|
||||
{
|
||||
AddInvalidBreakpoint(response.breakpoints, 1, nullptr, StringFormat("Could not find function %s in loaded sources!", fullFuncName.c_str()));
|
||||
continue;
|
||||
}
|
||||
if (IsFunctionNative(func))
|
||||
{
|
||||
BreakpointInfo bpoint_info;
|
||||
bpoint_info.type = BreakpointInfo::Type::Function;
|
||||
bpoint_info.ref = -1;
|
||||
bpoint_info.funcBreakpointText = fullFuncName;
|
||||
bpoint_info.bpoint.id = GetBreakpointID();
|
||||
bpoint_info.bpoint.line = 1;
|
||||
bpoint_info.bpoint.verified = true;
|
||||
m_nativeFunctionBreakpoints[func->QualifiedName] = bpoint_info;
|
||||
response.breakpoints.push_back(bpoint_info.bpoint);
|
||||
continue;
|
||||
}
|
||||
// script function
|
||||
auto scriptFunction = dynamic_cast<VMScriptFunction *>(func);
|
||||
auto scriptName = scriptFunction->SourceFileName.GetChars();
|
||||
dap::Source source;
|
||||
auto binary = m_pexCache->GetScript(scriptName);
|
||||
if (!binary)
|
||||
{
|
||||
AddInvalidBreakpoint(response.breakpoints, 1, nullptr, StringFormat("Could not find script %s in loaded sources!", scriptName));
|
||||
continue;
|
||||
}
|
||||
if (scriptFunction->LineInfoCount == 0)
|
||||
{
|
||||
AddInvalidBreakpoint(response.breakpoints, 1, nullptr, StringFormat("Could not find line info for function %s!", fullFuncName.c_str()), source);
|
||||
continue;
|
||||
}
|
||||
auto lineNum = scriptFunction->LineInfo[0].LineNumber;
|
||||
auto instructionNum = scriptFunction->LineInfo[0].InstructionIndex;
|
||||
void *instrRef = scriptFunction->Code + instructionNum;
|
||||
AddBreakpointInfo(binary, scriptFunction, lineNum, instrRef, 0, BreakpointInfo::Type::Function, response.breakpoints, fullFuncName);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
void BreakpointManager::ClearBreakpoints(bool emitChanged)
|
||||
{
|
||||
if (emitChanged)
|
||||
{
|
||||
std::vector<int> refs;
|
||||
for (auto &kv : m_breakpoints)
|
||||
{
|
||||
for (auto bpointInfo : kv.second)
|
||||
{
|
||||
if (emitChanged && bpointInfo.bpoint.verified)
|
||||
{
|
||||
bpointInfo.bpoint.verified = false;
|
||||
RuntimeEvents::EmitBreakpointChangedEvent(bpointInfo.bpoint, "changed");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
m_breakpoints.clear();
|
||||
}
|
||||
|
||||
void BreakpointManager::ClearBreakpointsType(BreakpointInfo::Type type)
|
||||
{
|
||||
std::vector<void *> toRemove;
|
||||
for (auto &KV : m_breakpoints)
|
||||
{
|
||||
auto bpinfos = KV.second;
|
||||
for (int64_t i = bpinfos.size() - 1; i >= 0; i--)
|
||||
{
|
||||
if (bpinfos[i].type == type)
|
||||
{
|
||||
bpinfos.erase(bpinfos.begin() + i);
|
||||
}
|
||||
}
|
||||
if (bpinfos.empty())
|
||||
{
|
||||
toRemove.push_back(KV.first);
|
||||
}
|
||||
}
|
||||
for (auto &key : toRemove)
|
||||
{
|
||||
m_breakpoints.erase(key);
|
||||
}
|
||||
}
|
||||
|
||||
void BreakpointManager::ClearBreakpointsForScript(int ref, BreakpointInfo::Type type, bool emitChanged)
|
||||
{
|
||||
std::vector<void *> toRemove;
|
||||
for (auto &KV : m_breakpoints)
|
||||
{
|
||||
auto bpinfos = KV.second;
|
||||
for (int64_t i = bpinfos.size() - 1; i >= 0; i--)
|
||||
{
|
||||
auto &bpinfo = bpinfos[i];
|
||||
if (bpinfo.ref == ref && (type == BreakpointInfo::Type::NONE || type == bpinfo.type))
|
||||
{
|
||||
if (emitChanged && bpinfo.bpoint.verified)
|
||||
{
|
||||
bpinfo.bpoint.verified = false;
|
||||
RuntimeEvents::EmitBreakpointChangedEvent(bpinfo.bpoint, "changed");
|
||||
}
|
||||
bpinfos.erase(bpinfos.begin() + i);
|
||||
}
|
||||
if (bpinfos.empty())
|
||||
{
|
||||
toRemove.push_back(KV.first);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (auto &key : toRemove)
|
||||
{
|
||||
m_breakpoints.erase(key);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool BreakpointManager::GetExecutionIsAtValidBreakpoint(VMFrameStack *stack, VMReturn *ret, int numret, const VMOP *pc)
|
||||
{
|
||||
return m_breakpoints.find((void *)pc) != m_breakpoints.end() || (!m_nativeFunctionBreakpoints.empty() && IsAtNativeBreakpoint(stack));
|
||||
}
|
||||
|
||||
inline bool BreakpointManager::IsAtNativeBreakpoint(VMFrameStack *stack)
|
||||
{
|
||||
return PCIsAtNativeCall(stack->TopFrame())
|
||||
&& m_nativeFunctionBreakpoints.find(GetCalledFunction(stack->TopFrame())->QualifiedName) != m_nativeFunctionBreakpoints.end();
|
||||
}
|
||||
|
||||
void BreakpointManager::SetBPStoppedEventInfo(VMFrameStack *stack, dap::StoppedEvent &event)
|
||||
{
|
||||
std::vector<dap::integer> breakpoints;
|
||||
if (!stack->HasFrames())
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto frame = stack->TopFrame();
|
||||
std::string description = "Paused on breakpoint";
|
||||
if (m_breakpoints.find((void *)frame->PC) != m_breakpoints.end())
|
||||
{
|
||||
for (auto &bpoint : m_breakpoints[(void *)frame->PC])
|
||||
{
|
||||
breakpoints.push_back(bpoint.bpoint.id.value(-1));
|
||||
}
|
||||
}
|
||||
if (IsAtNativeBreakpoint(stack))
|
||||
{
|
||||
auto func = GetCalledFunction(frame);
|
||||
auto &bpoint_info = m_nativeFunctionBreakpoints[func->QualifiedName];
|
||||
description = std::string("Paused on breakpoint at '") + bpoint_info.funcBreakpointText + "'";
|
||||
if (!CaseInsensitiveEquals(bpoint_info.funcBreakpointText, func->QualifiedName))
|
||||
{
|
||||
event.text = description + " (" + func->QualifiedName + ")";
|
||||
}
|
||||
else
|
||||
{
|
||||
event.text = description;
|
||||
}
|
||||
breakpoints.push_back(m_nativeFunctionBreakpoints[func->QualifiedName].bpoint.id.value(-1));
|
||||
}
|
||||
if (breakpoints.empty())
|
||||
{
|
||||
LogInternalError("No breakpoints found for stopped event");
|
||||
}
|
||||
if (!description.empty())
|
||||
{
|
||||
event.description = description;
|
||||
}
|
||||
event.reason = "breakpoint";
|
||||
event.hitBreakpointIds = breakpoints;
|
||||
}
|
||||
|
||||
dap::ResponseOrError<dap::SetInstructionBreakpointsResponse> BreakpointManager::SetInstructionBreakpoints(const dap::SetInstructionBreakpointsRequest &request)
|
||||
{
|
||||
auto breakpoints = request.breakpoints;
|
||||
|
||||
dap::SetInstructionBreakpointsResponse response;
|
||||
ClearBreakpointsType(BreakpointInfo::Type::Instruction);
|
||||
for (unsigned int i = 0; i < breakpoints.size(); i++)
|
||||
{
|
||||
auto &bp = breakpoints[i];
|
||||
void *srcAddress = (void *)(std::stoull(bp.instructionReference.substr(2), nullptr, 16));
|
||||
int64_t offset = bp.offset.value(0);
|
||||
void *address = offset + static_cast<char *>(srcAddress);
|
||||
|
||||
auto found = m_pexCache->GetFunctionsAtAddress(address);
|
||||
if (found.empty())
|
||||
{
|
||||
AddInvalidBreakpoint(response.breakpoints, 1, address, StringFormat("No function found for address %p", address));
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto func = found.front();
|
||||
auto bpoint_info = BreakpointInfo {};
|
||||
|
||||
bpoint_info.type = BreakpointInfo::Type::Instruction;
|
||||
int ref;
|
||||
auto scriptFunc = dynamic_cast<VMScriptFunction *>(func);
|
||||
|
||||
if (IsFunctionNative(func) || !scriptFunc)
|
||||
{
|
||||
AddInvalidBreakpoint(response.breakpoints, 1, address, StringFormat("Instruction breakpoints are not supported for native functions"));
|
||||
continue;
|
||||
}
|
||||
auto line = scriptFunc->LineInfo[0].LineNumber;
|
||||
auto binary = m_pexCache->GetScript(scriptFunc->SourceFileName.GetChars());
|
||||
dap::Breakpoint bpoint;
|
||||
AddBreakpointInfo(binary, scriptFunc, line, srcAddress, (int)offset, BreakpointInfo::Type::Instruction, response.breakpoints);
|
||||
}
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
}
|
||||
76
src/common/scripting/dap/BreakpointManager.h
Normal file
76
src/common/scripting/dap/BreakpointManager.h
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
#pragma once
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <dap/protocol.h>
|
||||
#include <dap/session.h>
|
||||
|
||||
#include "GameInterfaces.h"
|
||||
#include "IdProvider.h"
|
||||
|
||||
#include "PexCache.h"
|
||||
|
||||
namespace DebugServer
|
||||
{
|
||||
class BreakpointManager
|
||||
{
|
||||
public:
|
||||
struct BreakpointInfo
|
||||
{
|
||||
enum class Type
|
||||
{
|
||||
NONE = -1,
|
||||
Line,
|
||||
Function,
|
||||
Instruction
|
||||
};
|
||||
Type type;
|
||||
int ref;
|
||||
std::string funcBreakpointText;
|
||||
const char *nativeFuncName;
|
||||
dap::Breakpoint bpoint;
|
||||
};
|
||||
|
||||
struct ScriptBreakpoints
|
||||
{
|
||||
int ref {-1};
|
||||
dap::Source source;
|
||||
std::time_t modificationTime {0};
|
||||
std::map<void *, BreakpointInfo> breakpoints;
|
||||
};
|
||||
|
||||
|
||||
explicit BreakpointManager(PexCache *pexCache) : m_pexCache(pexCache) { }
|
||||
int64_t GetBreakpointID();
|
||||
int AddInvalidBreakpoint(
|
||||
std::vector<dap::Breakpoint> &breakpoints, int line, void *address, const std::string &reason, const dap::optional<dap::Source> &source);
|
||||
bool AddBreakpointInfo(
|
||||
const std::shared_ptr<Binary> &binary,
|
||||
VMScriptFunction *function,
|
||||
int line,
|
||||
void *p_instrRef,
|
||||
int offset,
|
||||
BreakpointInfo::Type type,
|
||||
std::vector<dap::Breakpoint> &r_bpoint,
|
||||
const std::string &funcText = {});
|
||||
void GetBpointsForResponse(BreakpointInfo::Type type, std::vector<dap::Breakpoint> &responseBpoints);
|
||||
dap::ResponseOrError<dap::SetBreakpointsResponse> SetBreakpoints(const dap::Source &src, const dap::SetBreakpointsRequest &request);
|
||||
dap::ResponseOrError<dap::SetFunctionBreakpointsResponse> SetFunctionBreakpoints(const dap::SetFunctionBreakpointsRequest &request);
|
||||
dap::ResponseOrError<dap::SetInstructionBreakpointsResponse> SetInstructionBreakpoints(const dap::SetInstructionBreakpointsRequest &request);
|
||||
void ClearBreakpoints(bool emitChanged = false);
|
||||
void ClearBreakpointsForScript(int ref, BreakpointInfo::Type type, bool emitChanged = false);
|
||||
bool GetExecutionIsAtValidBreakpoint(VMFrameStack *stack, VMReturn *ret, int numret, const VMOP *pc);
|
||||
inline bool IsAtNativeBreakpoint(VMFrameStack *stack);
|
||||
void SetBPStoppedEventInfo(VMFrameStack *stack, dap::StoppedEvent &event);
|
||||
private:
|
||||
|
||||
PexCache *m_pexCache;
|
||||
std::map<void *, std::vector<BreakpointInfo>> m_breakpoints;
|
||||
// set of case-insensitive strings
|
||||
std::map<std::string_view, BreakpointInfo, ci_less> m_nativeFunctionBreakpoints;
|
||||
IdProvider m_idProvider;
|
||||
int64_t m_CurrentID = 1;
|
||||
size_t times_seen = 0;
|
||||
|
||||
void ClearBreakpointsType(BreakpointInfo::Type type);
|
||||
};
|
||||
}
|
||||
388
src/common/scripting/dap/DebugExecutionManager.cpp
Normal file
388
src/common/scripting/dap/DebugExecutionManager.cpp
Normal file
|
|
@ -0,0 +1,388 @@
|
|||
#include "DebugExecutionManager.h"
|
||||
#include <thread>
|
||||
// #include "Window.h"
|
||||
#include "GameInterfaces.h"
|
||||
#ifdef _WIN32
|
||||
extern void I_GetWindowEvent();
|
||||
extern bool win32EnableInput;
|
||||
#endif
|
||||
|
||||
namespace DebugServer
|
||||
{
|
||||
// using namespace RE::BSScript::Internal;
|
||||
static constexpr const char *pauseReasonStrings[] = {"none", "step", "breakpoint", "paused", "exception"};
|
||||
static constexpr size_t pauseReasonStringsSize = sizeof(pauseReasonStrings) / sizeof(pauseReasonStrings[0]);
|
||||
static_assert(pauseReasonStringsSize == static_cast<int>(DebugExecutionManager::pauseReason::exception) + 1, "pauseReasonStrings size mismatch");
|
||||
static constexpr const char *exceptionStrings[]
|
||||
= {"Other", "ReadNil", "WriteNil", "TooManyTries", "ArrayOutOfBounds", "DivisionByZero", "BadSelf", "StringFormat"};
|
||||
static constexpr size_t exceptionStringsSize = sizeof(exceptionStrings) / sizeof(exceptionStrings[0]);
|
||||
static_assert(exceptionStringsSize == X_FORMAT_ERROR + 1, "exceptionStrings size mismatch");
|
||||
|
||||
static constexpr const char *exceptionFilters[] = {"VM"};
|
||||
static constexpr const char *exceptionFilterDescriptions[] = {"VM exceptions"};
|
||||
static_assert(sizeof(exceptionFilters) / sizeof(exceptionFilters[0]) == (size_t)DebugExecutionManager::ExceptionFilter::kMAX, "exceptionFilters size mismatch");
|
||||
static_assert(
|
||||
sizeof(exceptionFilterDescriptions) / sizeof(exceptionFilterDescriptions[0]) == (size_t)DebugExecutionManager::ExceptionFilter::kMAX,
|
||||
"exceptionFilterDescriptions size mismatch");
|
||||
|
||||
|
||||
DebugExecutionManager::pauseReason DebugExecutionManager::CheckState(VMFrameStack *stack, VMReturn *ret, int numret, const VMOP *pc)
|
||||
{
|
||||
switch (m_state)
|
||||
{
|
||||
case DebuggerState::kPaused:
|
||||
{
|
||||
return pauseReason::paused;
|
||||
}
|
||||
case DebuggerState::kRunning:
|
||||
{
|
||||
if (m_breakpointManager->GetExecutionIsAtValidBreakpoint(stack, ret, numret, pc))
|
||||
{
|
||||
return pauseReason::breakpoint;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case DebuggerState::kStepping:
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_instructionMutex);
|
||||
if (m_breakpointManager->GetExecutionIsAtValidBreakpoint(stack, ret, numret, pc))
|
||||
{
|
||||
return pauseReason::breakpoint;
|
||||
}
|
||||
else if (!RuntimeState::GetStack(m_currentStepStackId))
|
||||
{
|
||||
return pauseReason::CONTINUING;
|
||||
}
|
||||
else if (m_currentStepStackFrame)
|
||||
{
|
||||
VMScriptFunction *func = nullptr;
|
||||
auto lastInst = m_lastInstruction;
|
||||
m_lastInstruction = pc;
|
||||
std::vector<VMFrame *> currentFrames;
|
||||
RuntimeState::GetStackFrames(stack, currentFrames);
|
||||
if (!currentFrames.empty())
|
||||
{
|
||||
ptrdiff_t stepFrameIndex = -1;
|
||||
const auto stepFrameIter = std::find(currentFrames.begin(), currentFrames.end(), m_currentStepStackFrame);
|
||||
if (stepFrameIter != currentFrames.end() && m_currentVMFunction && m_currentStepStackFrame->Func == m_currentVMFunction)
|
||||
{
|
||||
stepFrameIndex = std::distance(currentFrames.begin(), stepFrameIter);
|
||||
}
|
||||
// Only get the function if we're not stepping by instruction and the frame exists
|
||||
if (m_granularity != kInstruction && stepFrameIndex != -1)
|
||||
{
|
||||
func = !IsFunctionNative(m_currentStepStackFrame->Func) ? dynamic_cast<VMScriptFunction *>(m_currentStepStackFrame->Func) : nullptr;
|
||||
// if we're in the same frame, the last instruction was at the previous address, and the line is the same, we should continue
|
||||
if (func && stepFrameIndex == 0 && lastInst == pc - 1 && m_lastLine == func->PCToLine(pc))
|
||||
{
|
||||
// NONE will cause the function to continue execution without resetting the step state
|
||||
return pauseReason::NONE;
|
||||
}
|
||||
}
|
||||
|
||||
switch (m_currentStepType)
|
||||
{
|
||||
case StepType::STEP_IN:
|
||||
return pauseReason::step;
|
||||
break;
|
||||
case StepType::STEP_OUT:
|
||||
// If the stack exists, but the original frame is gone, we know we're in a previous frame now.
|
||||
if (stepFrameIndex == -1)
|
||||
{
|
||||
return pauseReason::step;
|
||||
}
|
||||
break;
|
||||
case StepType::STEP_OVER:
|
||||
if (stepFrameIndex <= 0)
|
||||
{
|
||||
return pauseReason::step;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (m_granularity != kInstruction && func)
|
||||
{
|
||||
m_lastLine = func->PCToLine(pc);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_lastLine = -1;
|
||||
}
|
||||
// we deliberately don't set shouldContinue here in an else here, as we want to continue until we hit the next step point
|
||||
}
|
||||
else
|
||||
{
|
||||
// no more frames on stack, should continue
|
||||
if (!stack->HasFrames())
|
||||
{
|
||||
return pauseReason::CONTINUING;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
return pauseReason::NONE;
|
||||
}
|
||||
|
||||
void DebugExecutionManager::ResetStepState(DebuggerState state, VMFrameStack *stack)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_instructionMutex);
|
||||
// `stack` is thread_local, we're currently on that thread,
|
||||
// and the debugger will be running in a separate thread, so we need to set it here.
|
||||
m_state = state;
|
||||
|
||||
m_currentStepStackId = 0;
|
||||
m_currentStepStackFrame = nullptr;
|
||||
m_currentVMFunction = nullptr;
|
||||
if (state == DebuggerState::kPaused && stack)
|
||||
{
|
||||
RuntimeState::m_GlobalVMStack = stack;
|
||||
}
|
||||
else if (state == DebuggerState::kRunning)
|
||||
{
|
||||
m_lastLine = -1;
|
||||
m_lastInstruction = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void DebugExecutionManager::WaitWhilePaused(pauseReason pauseReason, VMFrameStack *stack)
|
||||
{
|
||||
if (pauseReason != pauseReason::NONE)
|
||||
{
|
||||
if (pauseReason > pauseReason::NONE)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
win32EnableInput = false;
|
||||
#endif
|
||||
|
||||
while (m_state == DebuggerState::kPaused)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
I_GetWindowEvent();
|
||||
#endif
|
||||
using namespace std::chrono_literals;
|
||||
std::this_thread::sleep_for(100ms);
|
||||
}
|
||||
#ifdef _WIN32
|
||||
win32EnableInput = true;
|
||||
#endif
|
||||
}
|
||||
std::lock_guard<std::mutex> lock(m_instructionMutex);
|
||||
// reset the state
|
||||
m_runtimeState->Reset();
|
||||
if (m_state != DebuggerState::kRunning && stack)
|
||||
{
|
||||
RuntimeState::m_GlobalVMStack = stack;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DebugExecutionManager::HandleInstruction(VMFrameStack *stack, VMReturn *ret, int numret, const VMOP *pc)
|
||||
{
|
||||
if (m_closed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
pauseReason pauseReason = CheckState(stack, ret, numret, pc);
|
||||
switch (pauseReason)
|
||||
{
|
||||
case pauseReason::NONE:
|
||||
break;
|
||||
case pauseReason::CONTINUING:
|
||||
{
|
||||
ResetStepState(DebuggerState::kRunning, stack);
|
||||
if (m_session)
|
||||
{
|
||||
dap::ContinuedEvent event;
|
||||
event.allThreadsContinued = true;
|
||||
event.threadId = 1;
|
||||
m_session->send(event);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default: // not NONE
|
||||
{
|
||||
// don't reset the last line or last instruction here
|
||||
ResetStepState(DebuggerState::kPaused, stack);
|
||||
if (m_session)
|
||||
{
|
||||
dap::StoppedEvent event;
|
||||
event.allThreadsStopped = true;
|
||||
event.reason = pauseReasonStrings[(int)pauseReason];
|
||||
if (pauseReason == pauseReason::breakpoint)
|
||||
{
|
||||
m_breakpointManager->SetBPStoppedEventInfo(stack, event);
|
||||
}
|
||||
event.threadId = 1;
|
||||
m_session->send(event);
|
||||
}
|
||||
// TODO: How to do this in GZDoom?
|
||||
// Window::ReleaseFocus();
|
||||
}
|
||||
break;
|
||||
}
|
||||
WaitWhilePaused(pauseReason, stack);
|
||||
}
|
||||
|
||||
void DebugExecutionManager::HandleException(EVMAbortException reason, const std::string &message, const std::string &stackTrace)
|
||||
{
|
||||
if (m_exceptionFilters.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
// If we're in a VM exception being thrown, we're on the main thread
|
||||
ResetStepState(DebuggerState::kPaused, &GlobalVMStack);
|
||||
if (m_session)
|
||||
{
|
||||
auto event = dap::StoppedEvent();
|
||||
event.allThreadsStopped = true;
|
||||
if (!stackTrace.empty())
|
||||
{
|
||||
event.text = message + "\n" + stackTrace;
|
||||
}
|
||||
else
|
||||
{
|
||||
event.text = message;
|
||||
}
|
||||
event.reason = "exception";
|
||||
event.description = "Paused on exception: " + (reason < exceptionStringsSize ? std::string(exceptionStrings[(int)reason]) : "Unknown");
|
||||
event.threadId = 1;
|
||||
m_session->send(event);
|
||||
};
|
||||
WaitWhilePaused(pauseReason::exception, nullptr);
|
||||
}
|
||||
|
||||
|
||||
void DebugExecutionManager::Open(std::shared_ptr<dap::Session> ses)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_instructionMutex);
|
||||
m_closed = false;
|
||||
m_session = ses;
|
||||
}
|
||||
|
||||
void DebugExecutionManager::Close()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_instructionMutex);
|
||||
m_state = DebuggerState::kRunning;
|
||||
m_closed = true;
|
||||
m_session = nullptr;
|
||||
}
|
||||
|
||||
bool DebugExecutionManager::Continue()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_instructionMutex);
|
||||
m_state = DebuggerState::kRunning;
|
||||
if (m_session){
|
||||
m_session->send(dap::ContinuedEvent());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DebugExecutionManager::Pause()
|
||||
{
|
||||
if (m_state == DebuggerState::kPaused)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
std::lock_guard<std::mutex> lock(m_instructionMutex);
|
||||
m_state = DebuggerState::kPaused;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DebugExecutionManager::Step(uint32_t stackId, const StepType stepType, StepGranularity stepGranularity)
|
||||
{
|
||||
if (m_state != DebuggerState::kPaused)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
std::lock_guard<std::mutex> lock(m_instructionMutex);
|
||||
const auto stack = RuntimeState::GetStack(stackId);
|
||||
if (stack)
|
||||
{
|
||||
if (stack->HasFrames())
|
||||
{
|
||||
m_currentStepStackFrame = stack->TopFrame();
|
||||
if (m_currentStepStackFrame)
|
||||
{
|
||||
m_currentVMFunction = m_currentStepStackFrame->Func;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
m_currentStepStackId = stackId;
|
||||
m_currentStepType = stepType;
|
||||
m_state = DebuggerState::kStepping;
|
||||
m_granularity = stepGranularity;
|
||||
m_lastInstruction = nullptr;
|
||||
if (m_granularity != kInstruction)
|
||||
{
|
||||
VMScriptFunction *func = !IsFunctionNative(m_currentStepStackFrame->Func) ? dynamic_cast<VMScriptFunction *>(m_currentStepStackFrame->Func) : nullptr;
|
||||
if (func)
|
||||
{
|
||||
m_lastInstruction = m_currentStepStackFrame->PC;
|
||||
m_lastLine = func->PCToLine(m_currentStepStackFrame->PC);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
dap::array<dap::Breakpoint> DebugExecutionManager::SetExceptionBreakpointFilters(const std::vector<std::string> &filterIds)
|
||||
{
|
||||
m_exceptionFilters.clear();
|
||||
dap::array<dap::Breakpoint> breakpoints;
|
||||
for (unsigned int i = 0; i < filterIds.size(); i++)
|
||||
{
|
||||
auto breakpoint = dap::Breakpoint();
|
||||
int64_t id = (int64_t)DebugExecutionManager::GetFilterID(filterIds[i]);
|
||||
breakpoint.id = id;
|
||||
breakpoint.verified = false;
|
||||
if (id != -1)
|
||||
{
|
||||
m_exceptionFilters.insert((ExceptionFilter)id);
|
||||
breakpoint.verified = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
breakpoint.reason = "failed";
|
||||
breakpoint.message = "No such exception filter";
|
||||
}
|
||||
breakpoints.push_back(breakpoint);
|
||||
}
|
||||
return breakpoints;
|
||||
}
|
||||
|
||||
DebugExecutionManager::ExceptionFilter DebugExecutionManager::GetFilterID(const std::string &filter_string)
|
||||
{
|
||||
for (int i = 0; i < (int)ExceptionFilter::kMAX; i++)
|
||||
{
|
||||
if (filter_string == exceptionFilters[i])
|
||||
{
|
||||
return (ExceptionFilter)i;
|
||||
}
|
||||
}
|
||||
return (ExceptionFilter)-1;
|
||||
}
|
||||
|
||||
dap::array<dap::ExceptionBreakpointsFilter> DebugExecutionManager::GetAllExceptionFilters()
|
||||
{
|
||||
dap::array<dap::ExceptionBreakpointsFilter> filters;
|
||||
for (int i = 0; i < (int)ExceptionFilter::kMAX; i++)
|
||||
{
|
||||
dap::ExceptionBreakpointsFilter filter;
|
||||
filter.filter = exceptionFilters[i];
|
||||
filter.label = exceptionFilterDescriptions[i];
|
||||
filter.description = exceptionFilterDescriptions[i];
|
||||
filter.conditionDescription = exceptionFilterDescriptions[i];
|
||||
filter.def = true;
|
||||
filters.push_back(filter);
|
||||
}
|
||||
return filters;
|
||||
}
|
||||
}
|
||||
90
src/common/scripting/dap/DebugExecutionManager.h
Normal file
90
src/common/scripting/dap/DebugExecutionManager.h
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
#pragma once
|
||||
|
||||
#include "GameInterfaces.h"
|
||||
|
||||
#include "BreakpointManager.h"
|
||||
#include "RuntimeState.h"
|
||||
#include <dap/session.h>
|
||||
#include <mutex>
|
||||
#include <atomic>
|
||||
|
||||
namespace DebugServer
|
||||
{
|
||||
enum StepType
|
||||
{
|
||||
STEP_IN = 0,
|
||||
STEP_OVER,
|
||||
STEP_OUT
|
||||
};
|
||||
|
||||
enum StepGranularity
|
||||
{
|
||||
kInstruction = 0,
|
||||
kLine,
|
||||
kStatement
|
||||
};
|
||||
|
||||
class DebugExecutionManager
|
||||
{
|
||||
|
||||
public:
|
||||
enum class DebuggerState
|
||||
{
|
||||
kRunning = 0,
|
||||
kPaused,
|
||||
kStepping
|
||||
};
|
||||
enum class pauseReason
|
||||
{
|
||||
CONTINUING = -1,
|
||||
NONE = 0,
|
||||
step,
|
||||
breakpoint,
|
||||
paused,
|
||||
exception
|
||||
};
|
||||
enum class ExceptionFilter
|
||||
{
|
||||
kScript,
|
||||
kMAX
|
||||
};
|
||||
private:
|
||||
std::mutex m_instructionMutex;
|
||||
bool m_closed;
|
||||
|
||||
std::shared_ptr<dap::Session> m_session;
|
||||
RuntimeState *m_runtimeState;
|
||||
BreakpointManager *m_breakpointManager;
|
||||
|
||||
std::atomic<DebuggerState> m_state = DebuggerState::kRunning;
|
||||
std::atomic<uint32_t> m_currentStepStackId = 0;
|
||||
StepType m_currentStepType = StepType::STEP_IN;
|
||||
StepGranularity m_granularity;
|
||||
int m_lastLine = -1;
|
||||
const VMOP *m_lastInstruction = nullptr;
|
||||
VMFrame *m_currentStepStackFrame;
|
||||
VMFunction *m_currentVMFunction;
|
||||
std::set<ExceptionFilter> m_exceptionFilters = {ExceptionFilter::kScript};
|
||||
public:
|
||||
explicit DebugExecutionManager(RuntimeState *runtimeState, BreakpointManager *breakpointManager) : m_closed(true), m_runtimeState(runtimeState), m_breakpointManager(breakpointManager), m_currentStepStackFrame(nullptr)
|
||||
{
|
||||
}
|
||||
static dap::array<dap::ExceptionBreakpointsFilter> GetAllExceptionFilters();
|
||||
|
||||
|
||||
void Close();
|
||||
void HandleInstruction(VMFrameStack *stack, VMReturn *ret, int numret, const VMOP *pc);
|
||||
void HandleException(EVMAbortException reason, const std::string &message, const std::string &stackTrace);
|
||||
void Open(std::shared_ptr<dap::Session> ses);
|
||||
bool Continue();
|
||||
bool Pause();
|
||||
bool Step(uint32_t stackId, StepType stepType, StepGranularity stepGranularity);
|
||||
dap::array<dap::Breakpoint> SetExceptionBreakpointFilters(const std::vector<std::string> &filterIds);
|
||||
static ExceptionFilter GetFilterID(const std::string &filter_string);
|
||||
bool IsPaused() const { return m_state == DebuggerState::kPaused; }
|
||||
private:
|
||||
inline pauseReason CheckState(VMFrameStack *stack, VMReturn *ret, int numret, const VMOP *pc);
|
||||
void ResetStepState(DebuggerState state, VMFrameStack *stack);
|
||||
void WaitWhilePaused(pauseReason pauseReason, VMFrameStack *stack);
|
||||
};
|
||||
}
|
||||
161
src/common/scripting/dap/DebugServer.cpp
Normal file
161
src/common/scripting/dap/DebugServer.cpp
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
#include "DebugServer.h"
|
||||
#include <thread>
|
||||
#include <functional>
|
||||
#include <dap/network.h>
|
||||
#include "ZScriptDebugger.h"
|
||||
|
||||
// Main entry point for the debug server
|
||||
|
||||
namespace DebugServer
|
||||
{
|
||||
DebugServer::DebugServer()
|
||||
{
|
||||
terminate = false;
|
||||
stopped = false;
|
||||
quitting = false;
|
||||
debugger = std::unique_ptr<ZScriptDebugger>(new ZScriptDebugger());
|
||||
}
|
||||
|
||||
void DebugServer::RunRestartThread()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
ResetThreadLock lock(mutex);
|
||||
cv.wait(lock, [&] { return terminate; });
|
||||
quitting = debugger->EndSession(closed);
|
||||
terminate = false;
|
||||
if (stopped || quitting)
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (restart_server){
|
||||
m_server->stop();
|
||||
m_server = nullptr;
|
||||
m_server = dap::net::Server::create();
|
||||
if (!StartServer()) {
|
||||
RuntimeEvents::UnsubscribeFromDebuggerEnabled([](){return true;});
|
||||
stopped = true;
|
||||
break;
|
||||
}
|
||||
restart_server = false;
|
||||
}
|
||||
}
|
||||
if (quitting)
|
||||
{
|
||||
throw CExitEvent(0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool DebugServer::Listen(int p_port)
|
||||
{
|
||||
terminate = false;
|
||||
stopped = false;
|
||||
quitting = false;
|
||||
closed = false;
|
||||
if (!p_port)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
port = p_port;
|
||||
if (!m_server)
|
||||
{
|
||||
m_server = dap::net::Server::create();
|
||||
}
|
||||
else
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
restart_thread = std::thread(std::bind(&DebugServer::RunRestartThread, this));
|
||||
return StartServer();
|
||||
}
|
||||
|
||||
void DebugServer::onClientConnected(const std::shared_ptr<dap::ReaderWriter> &connection)
|
||||
{
|
||||
if (!connection || !connection->isOpen())
|
||||
{
|
||||
LogInternalError("Client connected but connection is not open");
|
||||
return;
|
||||
}
|
||||
closed = false;
|
||||
std::shared_ptr<dap::Session> sess;
|
||||
sess = dap::Session::create();
|
||||
sess->bind(
|
||||
connection,
|
||||
[this]()
|
||||
{
|
||||
LogInternal("DAP connection closed.");
|
||||
// try_lock here because this can be called when the reset thread is ending the session
|
||||
ResetThreadLock lock(mutex, std::defer_lock);
|
||||
if (lock.try_lock() && !terminate && !debugger->IsEndingSession())
|
||||
{
|
||||
LogInternalError("DAP connection closed without terminating session.");
|
||||
closed = true;
|
||||
terminate = true;
|
||||
cv.notify_all();
|
||||
}
|
||||
});
|
||||
// Registering a handle for when we send the DisconnectResponse;
|
||||
// After we send the disconnect response, the restart thread will stop the session and restart the server.
|
||||
sess->registerSentHandler(
|
||||
[this](const dap::ResponseOrError<dap::DisconnectResponse> &)
|
||||
{
|
||||
LogInternal("Debugger disconnecting...");
|
||||
ResetThreadLock lock(mutex);
|
||||
terminate = true;
|
||||
cv.notify_all();
|
||||
});
|
||||
|
||||
LogInternal("DAP client connected.");
|
||||
debugger->StartSession(sess);
|
||||
};
|
||||
|
||||
void DebugServer::onError(const char *msg)
|
||||
{
|
||||
LogInternalError("Server error: %s\n", msg);
|
||||
if (restart_server){
|
||||
LogInternalError("Restart failed! Stopping server...");
|
||||
} else {
|
||||
LogInternalError("Restarting server...");
|
||||
{
|
||||
ResetThreadLock lock(mutex);
|
||||
terminate = true;
|
||||
closed = true;
|
||||
restart_server = true;
|
||||
cv.notify_all();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
bool DebugServer::StartServer()
|
||||
{
|
||||
if (!m_server->start(port, [this](auto conn){this->onClientConnected(conn);}, [this](auto msg){this->onError(msg);}))
|
||||
{
|
||||
LogInternalError("DAP debugging server failed to start on port %d, debugging will be unavailable!", port);
|
||||
return false;
|
||||
}
|
||||
RuntimeEvents::SubscribeToDebuggerEnabled([](){return true;});
|
||||
LogInternal("DAP debugging server started on port %d", port);
|
||||
return true;
|
||||
}
|
||||
|
||||
void DebugServer::Stop()
|
||||
{
|
||||
RuntimeEvents::UnsubscribeFromDebuggerEnabled([](){return true;});
|
||||
{
|
||||
ResetThreadLock lock(mutex);
|
||||
terminate = true;
|
||||
stopped = true;
|
||||
cv.notify_all();
|
||||
}
|
||||
if (m_server) {
|
||||
m_server->stop();
|
||||
}
|
||||
if (restart_thread.joinable())
|
||||
{
|
||||
restart_thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
DebugServer::~DebugServer() { Stop(); }
|
||||
}
|
||||
46
src/common/scripting/dap/DebugServer.h
Normal file
46
src/common/scripting/dap/DebugServer.h
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
#pragma once
|
||||
|
||||
#include <condition_variable>
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
|
||||
namespace dap
|
||||
{
|
||||
namespace net
|
||||
{
|
||||
class Server;
|
||||
}
|
||||
class ReaderWriter;
|
||||
}
|
||||
namespace DebugServer
|
||||
{
|
||||
class ZScriptDebugger;
|
||||
class DebugServer
|
||||
{
|
||||
public:
|
||||
DebugServer();
|
||||
~DebugServer();
|
||||
|
||||
void RunRestartThread();
|
||||
bool Listen(int port);
|
||||
void Stop();
|
||||
|
||||
private:
|
||||
void onClientConnected(const std::shared_ptr<dap::ReaderWriter> &connection);
|
||||
void onError(const char * msg);
|
||||
bool StartServer();
|
||||
|
||||
using ResetThreadLock = std::unique_lock<std::mutex>;
|
||||
std::unique_ptr<ZScriptDebugger> debugger;
|
||||
std::unique_ptr<dap::net::Server> m_server;
|
||||
std::condition_variable cv;
|
||||
std::mutex mutex; // guards 'terminate'
|
||||
int port;
|
||||
bool stopped = false;
|
||||
bool terminate = false;
|
||||
bool restart_server = false;
|
||||
bool closed = false;
|
||||
bool quitting = false; // On receiving a disconnect request with a terminateDebuggee flag
|
||||
std::thread restart_thread;
|
||||
};
|
||||
}
|
||||
15
src/common/scripting/dap/GameEventEmit.h
Normal file
15
src/common/scripting/dap/GameEventEmit.h
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
#pragma once
|
||||
#include "vm.h"
|
||||
|
||||
class VMFrameStack;
|
||||
|
||||
namespace DebugServer
|
||||
{
|
||||
namespace RuntimeEvents
|
||||
{
|
||||
void EmitInstructionExecutionEvent(VMFrameStack *stack, VMReturn *ret, int numret, const VMOP *pc);
|
||||
void EmitLogEvent(int level, const char *message);
|
||||
void EmitExceptionEvent(EVMAbortException reason, const std::string &message, const std::string &stackTrace);
|
||||
bool IsDebugServerRunning();
|
||||
}
|
||||
}
|
||||
1159
src/common/scripting/dap/GameInterfaces.h
Normal file
1159
src/common/scripting/dap/GameInterfaces.h
Normal file
File diff suppressed because it is too large
Load diff
1
src/common/scripting/dap/IdHandleBase.cpp
Normal file
1
src/common/scripting/dap/IdHandleBase.cpp
Normal file
|
|
@ -0,0 +1 @@
|
|||
#include "IdHandleBase.h"
|
||||
20
src/common/scripting/dap/IdHandleBase.h
Normal file
20
src/common/scripting/dap/IdHandleBase.h
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
#pragma once
|
||||
#include "IdMap.h"
|
||||
|
||||
namespace DebugServer
|
||||
{
|
||||
template <class T> class IdHandleBase
|
||||
{
|
||||
IdMap<T> *m_idMap;
|
||||
public:
|
||||
explicit IdHandleBase(IdMap<T> *idMap) : m_idMap(idMap)
|
||||
{
|
||||
static_assert(std::is_base_of<IdHandleBase<T>, T>());
|
||||
m_idMap->Add(static_cast<T *>(this));
|
||||
}
|
||||
|
||||
virtual ~IdHandleBase() { m_idMap->Remove(static_cast<T *>(this)); }
|
||||
|
||||
uint32_t GetId() const { return m_idMap->GetId(static_cast<T *>(this)); }
|
||||
};
|
||||
}
|
||||
1
src/common/scripting/dap/IdMap.cpp
Normal file
1
src/common/scripting/dap/IdMap.cpp
Normal file
|
|
@ -0,0 +1 @@
|
|||
#include "IdMap.h"
|
||||
101
src/common/scripting/dap/IdMap.h
Normal file
101
src/common/scripting/dap/IdMap.h
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
#pragma once
|
||||
#include "IdProvider.h"
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
namespace DebugServer
|
||||
{
|
||||
template <typename T> class IdMap
|
||||
{
|
||||
std::shared_ptr<IdProvider> m_idProvider;
|
||||
std::unordered_map<uint32_t, T> m_idsToElements;
|
||||
std::unordered_map<T, uint32_t> m_elementsToIds;
|
||||
|
||||
std::recursive_mutex m_elementsMutex;
|
||||
public:
|
||||
explicit IdMap(const std::shared_ptr<IdProvider> idProvider) : m_idProvider(idProvider) { }
|
||||
|
||||
~IdMap() { Clear(); }
|
||||
|
||||
bool Get(uint32_t id, T &value)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(m_elementsMutex);
|
||||
|
||||
auto pair = m_idsToElements.find(id);
|
||||
if (pair != m_idsToElements.end())
|
||||
{
|
||||
value = pair->second;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GetId(T element, uint32_t &id)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(m_elementsMutex);
|
||||
|
||||
auto pair = m_elementsToIds.find(element);
|
||||
if (pair != m_elementsToIds.end())
|
||||
{
|
||||
id = pair->second;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AddOrGetExisting(T element, uint32_t &id)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(m_elementsMutex);
|
||||
|
||||
if (GetId(element, id))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
id = m_idProvider->GetNext();
|
||||
m_idsToElements.emplace(id, element);
|
||||
m_elementsToIds.emplace(element, id);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Remove(uint32_t id)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(m_elementsMutex);
|
||||
|
||||
auto pair = m_idsToElements.find(id);
|
||||
if (pair != m_idsToElements.end())
|
||||
{
|
||||
m_idsToElements.erase(id);
|
||||
m_elementsToIds.erase(pair->second);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Remove(T element)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(m_elementsMutex);
|
||||
|
||||
uint32_t id;
|
||||
if (GetId(element), id)
|
||||
{
|
||||
return Remove(id);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void Clear()
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(m_elementsMutex);
|
||||
|
||||
m_idsToElements.clear();
|
||||
m_elementsToIds.clear();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
10
src/common/scripting/dap/IdProvider.cpp
Normal file
10
src/common/scripting/dap/IdProvider.cpp
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
#include "IdProvider.h"
|
||||
|
||||
namespace DebugServer
|
||||
{
|
||||
uint32_t IdProvider::GetNext()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_idMutex);
|
||||
return m_currentId++;
|
||||
}
|
||||
}
|
||||
13
src/common/scripting/dap/IdProvider.h
Normal file
13
src/common/scripting/dap/IdProvider.h
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
#pragma once
|
||||
#include <mutex>
|
||||
|
||||
namespace DebugServer
|
||||
{
|
||||
class IdProvider
|
||||
{
|
||||
uint32_t m_currentId = 1000;
|
||||
std::mutex m_idMutex;
|
||||
public:
|
||||
uint32_t GetNext();
|
||||
};
|
||||
}
|
||||
236
src/common/scripting/dap/Nodes/ArrayStateNode.cpp
Normal file
236
src/common/scripting/dap/Nodes/ArrayStateNode.cpp
Normal file
|
|
@ -0,0 +1,236 @@
|
|||
#include "ArrayStateNode.h"
|
||||
#include "common/scripting/dap/GameInterfaces.h"
|
||||
#include "dobject.h"
|
||||
#include "vm.h"
|
||||
#include <common/scripting/dap/Utilities.h>
|
||||
#include <common/scripting/dap/RuntimeState.h>
|
||||
|
||||
namespace DebugServer
|
||||
{
|
||||
static PType * GetElementType(const PType * p_type)
|
||||
{
|
||||
auto type = p_type;
|
||||
if (p_type->isPointer())
|
||||
{
|
||||
type = dynamic_cast<const PPointer *>(type)->PointedType;
|
||||
}
|
||||
if (type->isArray())
|
||||
{
|
||||
auto *arraytype = dynamic_cast<const PArray *>(type);
|
||||
return arraytype->ElementType;
|
||||
}
|
||||
else if (type->isDynArray())
|
||||
{
|
||||
auto arrayType = dynamic_cast<const PDynArray *>(type);
|
||||
return arrayType->ElementType;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static int64_t GetElementCount(const VMValue &value, PType *p_type)
|
||||
{
|
||||
auto type = p_type;
|
||||
auto array_head = value.a;
|
||||
if (type->toPointer())
|
||||
{
|
||||
type = type->toPointer()->PointedType;
|
||||
}
|
||||
|
||||
if (type->isDynArray() || type->isStaticArray())
|
||||
{
|
||||
if (!IsVMValueValid(&value))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// FArray has the same layout as TArray, just return count
|
||||
auto *arr = static_cast<FArray *>(array_head);
|
||||
if (arr->Count == UINT_MAX)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
return arr->Count;
|
||||
}
|
||||
if (type->isArray())
|
||||
{
|
||||
if (static_cast<PArray *>(type)->ElementCount == UINT_MAX)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
return static_cast<PArray *>(type)->ElementCount;
|
||||
}
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
ArrayStateNode::ArrayStateNode(std::string name, VMValue value, PType *p_type) : StateNodeNamedVariable(name), m_value(value), m_type(p_type)
|
||||
{
|
||||
auto type = m_type;
|
||||
if (type->toPointer())
|
||||
{
|
||||
type = type->toPointer()->PointedType;
|
||||
}
|
||||
if (type->isArray())
|
||||
{
|
||||
auto *arraytype = dynamic_cast<PArray *>(type);
|
||||
m_elementType = arraytype->ElementType;
|
||||
}
|
||||
else if (type->isDynArray())
|
||||
{
|
||||
auto arrayType = dynamic_cast<PDynArray *>(type);
|
||||
m_elementType = arrayType->ElementType;
|
||||
}
|
||||
}
|
||||
|
||||
bool ArrayStateNode::SerializeToProtocol(dap::Variable &variable)
|
||||
{
|
||||
variable.variablesReference = GetId();
|
||||
auto count = GetElementCount(m_value, m_type);
|
||||
variable.indexedVariables = count < 0 ? 0 : count;
|
||||
SetVariableName(variable);
|
||||
std::string elementTypeName = m_elementType->DescriptiveName();
|
||||
variable.type = m_type->mDescriptiveName.GetChars();
|
||||
if (!IsVMValueValid(&m_value) || count < 0)
|
||||
{
|
||||
variable.value = StringFormat("%s[<NONE>]", elementTypeName.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
variable.value = StringFormat("%s[%d]", elementTypeName.c_str(), variable.indexedVariables.value(0));
|
||||
if (m_type->toPointer())
|
||||
{
|
||||
variable.value += StringFormat(" (%p)", m_value.a);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ArrayStateNode::GetChildNames(std::vector<std::string> &names)
|
||||
{
|
||||
if (!IsVMValueValid(&m_value))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
auto count = GetElementCount(m_value, m_type);
|
||||
for (uint32_t i = 0; i < count; i++)
|
||||
{
|
||||
names.push_back(std::to_string(i));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ArrayStateNode::GetChildNode(std::string name, std::shared_ptr<StateNodeBase> &node)
|
||||
{
|
||||
if (!IsVMValueValid(&m_value))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
auto count = GetElementCount(m_value, m_type);
|
||||
if (count <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int elementIndex;
|
||||
if (!ParseInt(name, &elementIndex))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (elementIndex < 0 || ((uint32_t)elementIndex) > count - 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
auto elidx_str = std::to_string(elementIndex);
|
||||
|
||||
if (m_children.find(elidx_str) != m_children.end())
|
||||
{
|
||||
node = m_children[elidx_str];
|
||||
return true;
|
||||
}
|
||||
if (m_value.a == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
auto array_head = m_value.a;
|
||||
auto type = m_type;
|
||||
if (m_type->isPointer())
|
||||
{
|
||||
type = static_cast<PPointer *>(m_type)->PointedType;
|
||||
// array_head = *static_cast<void **>(m_value.a);
|
||||
}
|
||||
|
||||
if (type->isDynArray() || type->isStaticArray())
|
||||
{
|
||||
auto elementType = GetElementType(m_type);
|
||||
auto returnType = elementType;
|
||||
// too large, return a pointer to the array element
|
||||
if (elementType->Size > 8)
|
||||
{
|
||||
returnType = NewPointer(elementType);
|
||||
}
|
||||
VMValue element_val;
|
||||
if (elementType->isFloat())
|
||||
{
|
||||
switch (elementType->Size)
|
||||
{
|
||||
case 8:
|
||||
element_val = VMValue(static_cast<TArray<double> *>(array_head)->operator[](elementIndex));
|
||||
break;
|
||||
case 4:
|
||||
element_val = VMValue(static_cast<TArray<float> *>(array_head)->operator[](elementIndex));
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (elementType->isObjectPointer())
|
||||
{
|
||||
element_val = VMValue(static_cast<TArray<DObject *> *>(array_head)->operator[](elementIndex));
|
||||
}
|
||||
else if (elementType == TypeString)
|
||||
{
|
||||
element_val = VMValue(&static_cast<TArray<FString> *>(array_head)->operator[](elementIndex));
|
||||
}
|
||||
else if (elementType->isPointer())
|
||||
{
|
||||
element_val = VMValue(static_cast<TArray<void *> *>(array_head)->operator[](elementIndex));
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (elementType->Size)
|
||||
{
|
||||
case 8:
|
||||
element_val = VMValue((void *)static_cast<TArray<uint64_t> *>(array_head)->operator[](elementIndex));
|
||||
break;
|
||||
case 4:
|
||||
element_val = VMValue(static_cast<TArray<uint32_t> *>(array_head)->operator[](elementIndex));
|
||||
break;
|
||||
case 2:
|
||||
element_val = VMValue(static_cast<TArray<uint16_t> *>(array_head)->operator[](elementIndex));
|
||||
break;
|
||||
case 1:
|
||||
element_val = VMValue(static_cast<TArray<uint8_t> *>(array_head)->operator[](elementIndex));
|
||||
break;
|
||||
default:
|
||||
// too large, return a ptr to the array element
|
||||
assert(unsigned(elementIndex) <= static_cast<FArray *>(array_head)->Count);
|
||||
element_val = VMValue((void *)(((char *)static_cast<FArray *>(array_head)->Array) + (elementIndex * elementType->Size)));
|
||||
}
|
||||
}
|
||||
m_children[elidx_str] = RuntimeState::CreateNodeForVariable(elidx_str, element_val, returnType);
|
||||
}
|
||||
else if (type->isArray())
|
||||
{
|
||||
auto element_size = m_elementType->Size;
|
||||
void *var = (void *)((char *)array_head + (element_size * elementIndex));
|
||||
VMValue value = GetVMValue(var, m_elementType);
|
||||
// m_children[elidx_str] = RuntimeState::CreateNodeForVariable(std::to_string(elementIndex), DerefValue(&element_ptr, GetBasicType(m_elementType)), m_elementType);
|
||||
|
||||
m_children[elidx_str] = RuntimeState::CreateNodeForVariable(std::to_string(elementIndex), value, m_elementType);
|
||||
}
|
||||
node = m_children[elidx_str];
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
28
src/common/scripting/dap/Nodes/ArrayStateNode.h
Normal file
28
src/common/scripting/dap/Nodes/ArrayStateNode.h
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
#pragma once
|
||||
#include <common/scripting/dap/GameInterfaces.h>
|
||||
|
||||
#include <dap/protocol.h>
|
||||
#include <map>
|
||||
#include "StateNodeBase.h"
|
||||
|
||||
namespace DebugServer
|
||||
{
|
||||
class ArrayStateNode : public StateNodeNamedVariable, public IStructuredState
|
||||
{
|
||||
|
||||
const VMValue m_value;
|
||||
PType *m_type;
|
||||
PType *m_elementType;
|
||||
std::map<std::string, std::shared_ptr<StateNodeBase>> m_children;
|
||||
public:
|
||||
ArrayStateNode(std::string name, VMValue value, PType *type);
|
||||
|
||||
virtual ~ArrayStateNode() override = default;
|
||||
|
||||
bool SerializeToProtocol(dap::Variable &variable) override;
|
||||
|
||||
bool GetChildNames(std::vector<std::string> &names) override;
|
||||
bool CacheChildren();
|
||||
bool GetChildNode(std::string name, std::shared_ptr<StateNodeBase> &node) override;
|
||||
};
|
||||
}
|
||||
195
src/common/scripting/dap/Nodes/CVarScopeStateNode.cpp
Normal file
195
src/common/scripting/dap/Nodes/CVarScopeStateNode.cpp
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
#include "CVarScopeStateNode.h"
|
||||
#include <common/scripting/dap/Utilities.h>
|
||||
#include <common/scripting/dap/RuntimeState.h>
|
||||
#include "common/console/c_cvars.h"
|
||||
#include "gstrings.h"
|
||||
|
||||
namespace DebugServer
|
||||
{
|
||||
CVarStateNode::CVarStateNode(FBaseCVar *cvar) : m_cvar(cvar) { }
|
||||
|
||||
dap::Variable CVarStateNode::ToVariable(FBaseCVar *m_cvar)
|
||||
{
|
||||
dap::Variable variable;
|
||||
if (!m_cvar)
|
||||
{
|
||||
variable.name = "<INVALID>";
|
||||
variable.value = "<INVALID>";
|
||||
return variable;
|
||||
}
|
||||
|
||||
|
||||
const char *vmvalstr = nullptr;
|
||||
const char *realTypeString = nullptr;
|
||||
ECVarType favoriteType;
|
||||
UCVarValue val = m_cvar->GetFavoriteRep(&favoriteType);
|
||||
// VMValue vmval;
|
||||
// PType *vmtype;
|
||||
// switch (type)
|
||||
// {
|
||||
// case CVAR_Flag:
|
||||
// case CVAR_Bool:
|
||||
// vmval = VMValue(val.Bool);
|
||||
// vmtype = TypeBool;
|
||||
// break;
|
||||
// case CVAR_Int:
|
||||
// case CVAR_Color:
|
||||
// case CVAR_Mask:
|
||||
// vmval = VMValue(val.Int);
|
||||
// vmtype = TypeSInt32;
|
||||
// break;
|
||||
// case CVAR_Float:
|
||||
// vmval = VMValue(val.Float);
|
||||
// vmtype = TypeFloat64;
|
||||
// break;
|
||||
// case CVAR_String:
|
||||
// {
|
||||
// vmvalstr = val.String;
|
||||
// vmval = VMValue(&vmvalstr);
|
||||
// vmtype = TypeString;
|
||||
// auto test = vmval.sp;
|
||||
// int i = 0;
|
||||
// }
|
||||
// break;
|
||||
// default:
|
||||
// break;
|
||||
// }
|
||||
variable.value = m_cvar->GetHumanString();
|
||||
|
||||
switch (m_cvar->GetRealType())
|
||||
{
|
||||
// actually do these in order of the enum
|
||||
case CVAR_Bool:
|
||||
realTypeString = "CVar<Bool>";
|
||||
break;
|
||||
case CVAR_Int:
|
||||
realTypeString = "CVar<Int>";
|
||||
break;
|
||||
case CVAR_Float:
|
||||
realTypeString = "CVar<Float>";
|
||||
if (variable.value.find('.') == std::string::npos && variable.value.find('e') == std::string::npos)
|
||||
{
|
||||
variable.value += ".0";
|
||||
}
|
||||
break;
|
||||
case CVAR_String:
|
||||
{
|
||||
realTypeString = "CVar<String>";
|
||||
// If none of the values are non-numbers or non-'.', then we need to wrap it in quotes
|
||||
if (favoriteType == CVAR_String)
|
||||
{
|
||||
variable.value = StringFormat("\"%s\"", variable.value.c_str());
|
||||
}
|
||||
}
|
||||
break;
|
||||
case CVAR_Color:
|
||||
realTypeString = "CVar<Color>";
|
||||
break;
|
||||
case CVAR_Flag:
|
||||
realTypeString = "CVar<Flag>";
|
||||
break;
|
||||
case CVAR_Mask:
|
||||
realTypeString = "CVar<Mask>";
|
||||
break;
|
||||
case CVAR_Dummy:
|
||||
realTypeString = "CVar<Dummy>";
|
||||
break;
|
||||
default:
|
||||
realTypeString = "<UNKNOWN>";
|
||||
break;
|
||||
}
|
||||
const FString &description = m_cvar->GetDescription();
|
||||
|
||||
|
||||
if (!description.IsEmpty())
|
||||
{
|
||||
std::string_view localized = GStrings.localize(description.GetChars());
|
||||
if (!localized.empty() && localized.substr(1) != description.GetChars())
|
||||
{
|
||||
variable.type = StringFormat("%s (%s)", realTypeString, localized.data());
|
||||
}
|
||||
else
|
||||
{
|
||||
variable.type = realTypeString;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
variable.type = realTypeString;
|
||||
}
|
||||
variable.name = m_cvar->GetName();
|
||||
|
||||
return variable;
|
||||
}
|
||||
|
||||
bool CVarStateNode::SerializeToProtocol(dap::Variable &variable)
|
||||
{
|
||||
variable = ToVariable(m_cvar);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CVarScopeStateNode::SerializeToProtocol(dap::Scope &scope)
|
||||
{
|
||||
scope.name = "CVars";
|
||||
scope.expensive = true;
|
||||
scope.presentationHint = "cvars";
|
||||
scope.variablesReference = GetId();
|
||||
|
||||
if (m_CachedNames.empty())
|
||||
{
|
||||
std::vector<std::string> childNames;
|
||||
GetChildNames(childNames);
|
||||
}
|
||||
scope.namedVariables = m_CachedNames.size();
|
||||
scope.indexedVariables = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CVarScopeStateNode::GetChildNames(std::vector<std::string> &names)
|
||||
{
|
||||
if (!m_CachedNames.empty())
|
||||
{
|
||||
names = m_CachedNames;
|
||||
return true;
|
||||
}
|
||||
decltype(cvarMap)::Iterator it(cvarMap);
|
||||
decltype(cvarMap)::Pair *pair;
|
||||
m_CachedNames.reserve(cvarMap.CountUsed());
|
||||
while (it.NextPair(pair))
|
||||
{
|
||||
auto var = pair->Value;
|
||||
m_CachedNames.emplace_back(var->GetName());
|
||||
m_children[var->GetName()] = std::make_shared<CVarStateNode>(var);
|
||||
}
|
||||
std::sort(m_CachedNames.begin(), m_CachedNames.end(), CVarNameComparer());
|
||||
names = m_CachedNames;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CVarScopeStateNode::GetChildNode(std::string name, std::shared_ptr<StateNodeBase> &node)
|
||||
{
|
||||
if (m_CachedNames.empty())
|
||||
{
|
||||
std::vector<std::string> childNames;
|
||||
GetChildNames(childNames);
|
||||
}
|
||||
if (m_children.find(name) == m_children.end())
|
||||
{
|
||||
auto var = FindCVar(name.c_str(), nullptr);
|
||||
if (!var)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
m_children[var->GetName()] = std::make_shared<CVarStateNode>(var);
|
||||
m_CachedNames.push_back(var->GetName());
|
||||
std::sort(m_CachedNames.begin(), m_CachedNames.end(), CVarNameComparer());
|
||||
}
|
||||
if (m_children.find(name) != m_children.end())
|
||||
{
|
||||
node = m_children[name];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
48
src/common/scripting/dap/Nodes/CVarScopeStateNode.h
Normal file
48
src/common/scripting/dap/Nodes/CVarScopeStateNode.h
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
#pragma once
|
||||
|
||||
#include <common/scripting/dap/GameInterfaces.h>
|
||||
#include <dap/protocol.h>
|
||||
|
||||
#include "StateNodeBase.h"
|
||||
|
||||
class FBaseCVar;
|
||||
namespace DebugServer
|
||||
{
|
||||
|
||||
class CVarStateNode : public StateNodeBase, public IProtocolVariableSerializable{
|
||||
FBaseCVar *m_cvar = nullptr;
|
||||
public:
|
||||
CVarStateNode(FBaseCVar *cvar);
|
||||
bool SerializeToProtocol(dap::Variable &variable) override;
|
||||
static dap::Variable ToVariable(FBaseCVar *cvar);
|
||||
};
|
||||
|
||||
class CVarNameComparer {
|
||||
public:
|
||||
bool operator()(const std::string &a, const std::string &b) const {
|
||||
if (!a.empty() && !b.empty()) {
|
||||
// check if the first character is uppercase and the other is lowercase
|
||||
// uppercase goes last
|
||||
if (isupper(a[0]) && islower(b[0])) {
|
||||
return false;
|
||||
}
|
||||
if (islower(a[0]) && isupper(b[0])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return a < b;
|
||||
}
|
||||
};
|
||||
|
||||
class CVarScopeStateNode : public StateNodeBase, public IProtocolScopeSerializable, public IStructuredState
|
||||
{
|
||||
caseless_path_map<std::shared_ptr<StateNodeBase>> m_children;
|
||||
std::vector<std::string> m_CachedNames; // to preserve order
|
||||
public:
|
||||
CVarScopeStateNode() = default;
|
||||
|
||||
bool SerializeToProtocol(dap::Scope &scope) override;
|
||||
bool GetChildNames(std::vector<std::string> &names) override;
|
||||
bool GetChildNode(std::string name, std::shared_ptr<StateNodeBase> &node) override;
|
||||
};
|
||||
}
|
||||
49
src/common/scripting/dap/Nodes/DummyNode.cpp
Normal file
49
src/common/scripting/dap/Nodes/DummyNode.cpp
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
#include "DummyNode.h"
|
||||
|
||||
#include <utility>
|
||||
namespace DebugServer
|
||||
{
|
||||
DummyNode::DummyNode(std::string name, std::string value, std::string type) : StateNodeNamedVariable(std::move(name)), m_value(std::move(value)), m_type(std::move(type)) { }
|
||||
|
||||
bool DummyNode::SerializeToProtocol(dap::Variable &variable)
|
||||
{
|
||||
SetVariableName(variable);
|
||||
variable.value = m_value;
|
||||
variable.type = m_type;
|
||||
return true;
|
||||
}
|
||||
|
||||
DummyWithChildrenNode::DummyWithChildrenNode(std::string name, std::string value, std::string type, caseless_path_map<std::shared_ptr<StateNodeBase>> children)
|
||||
: StateNodeNamedVariable(std::move(name)), m_value(std::move(value)), m_type(std::move(type)), m_children(std::move(children))
|
||||
{
|
||||
}
|
||||
|
||||
bool DummyWithChildrenNode::GetChildNames(std::vector<std::string> &names)
|
||||
{
|
||||
for (const auto &child : m_children)
|
||||
{
|
||||
names.push_back(child.first);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DummyWithChildrenNode::GetChildNode(std::string name, std::shared_ptr<StateNodeBase> &node)
|
||||
{
|
||||
if (m_children.find(name) != m_children.end())
|
||||
{
|
||||
node = m_children[name];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DummyWithChildrenNode::SerializeToProtocol(dap::Variable &variable)
|
||||
{
|
||||
SetVariableName(variable);
|
||||
variable.value = m_value;
|
||||
variable.type = m_type;
|
||||
variable.variablesReference = GetId();
|
||||
variable.namedVariables = m_children.size();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
30
src/common/scripting/dap/Nodes/DummyNode.h
Normal file
30
src/common/scripting/dap/Nodes/DummyNode.h
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
#pragma once
|
||||
|
||||
#include <common/scripting/dap/GameInterfaces.h>
|
||||
|
||||
#include <dap/protocol.h>
|
||||
#include "StateNodeBase.h"
|
||||
|
||||
namespace DebugServer
|
||||
{
|
||||
class DummyNode : public StateNodeNamedVariable
|
||||
{
|
||||
const std::string m_value;
|
||||
const std::string m_type;
|
||||
public:
|
||||
DummyNode(std::string name, std::string value, std::string type);
|
||||
bool SerializeToProtocol(dap::Variable &variable) override;
|
||||
};
|
||||
|
||||
class DummyWithChildrenNode : public StateNodeNamedVariable, public IStructuredState
|
||||
{
|
||||
const std::string m_value;
|
||||
const std::string m_type;
|
||||
caseless_path_map<std::shared_ptr<StateNodeBase>> m_children;
|
||||
public:
|
||||
DummyWithChildrenNode(std::string name, std::string value, std::string type, caseless_path_map<std::shared_ptr<StateNodeBase>> children);
|
||||
bool SerializeToProtocol(dap::Variable &variable) override;
|
||||
bool GetChildNames(std::vector<std::string> &names) override;
|
||||
bool GetChildNode(std::string name, std::shared_ptr<StateNodeBase> &node) override;
|
||||
};
|
||||
}
|
||||
141
src/common/scripting/dap/Nodes/GlobalScopeStateNode.cpp
Normal file
141
src/common/scripting/dap/Nodes/GlobalScopeStateNode.cpp
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
#include "GlobalScopeStateNode.h"
|
||||
#include <common/scripting/dap/Utilities.h>
|
||||
#include <common/scripting/dap/RuntimeState.h>
|
||||
|
||||
namespace DebugServer
|
||||
{
|
||||
// TODO: Do this dynamically?
|
||||
static const char *const GlobalNames[] = {
|
||||
"NotifyFontScale",
|
||||
"ConsoleState",
|
||||
"menuactive",
|
||||
"BackbuttonTime",
|
||||
"BackbuttonAlpha",
|
||||
"GameTicRate",
|
||||
"menuDelegate",
|
||||
"WP_NOCHANGE",
|
||||
"SmallFont",
|
||||
"SmallFont2",
|
||||
"BigFont",
|
||||
"ConFont",
|
||||
"NewConsoleFont",
|
||||
"NewSmallFont",
|
||||
"AlternativeSmallFont",
|
||||
"AlternativeBigFont",
|
||||
"OriginalSmallFont",
|
||||
"OriginalBigFont",
|
||||
"IntermissionFont",
|
||||
"CleanXfac",
|
||||
"CleanYfac",
|
||||
"CleanWidth",
|
||||
"CleanHeight",
|
||||
"CleanXfac_1",
|
||||
"CleanYfac_1",
|
||||
"CleanWidth_1",
|
||||
"CleanHeight_1",
|
||||
"AllServices",
|
||||
"Bindings",
|
||||
"AutomapBindings",
|
||||
"generic_ui",
|
||||
"deh",
|
||||
"gameinfo",
|
||||
"Teams",
|
||||
"LocalViewPitch",
|
||||
"StatusBar",
|
||||
"players",
|
||||
"playeringame",
|
||||
"PlayerClasses",
|
||||
"consoleplayer",
|
||||
"validcount",
|
||||
"multiplayer",
|
||||
"gameaction",
|
||||
"gamestate",
|
||||
"skyflatnum",
|
||||
"globalfreeze",
|
||||
"gametic",
|
||||
"demoplayback",
|
||||
"automapactive",
|
||||
"viewactive",
|
||||
"Net_Arbitrator",
|
||||
"netgame",
|
||||
"paused",
|
||||
"Terrains",
|
||||
"OptionMenuSettings",
|
||||
"musplaying",
|
||||
"AllClasses",
|
||||
"Level",
|
||||
// "level", technically its own global, but only the VM one is accessible by the VM
|
||||
"AllActorClasses",
|
||||
|
||||
nullptr};
|
||||
|
||||
GlobalScopeStateNode::GlobalScopeStateNode() { }
|
||||
|
||||
bool GlobalScopeStateNode::SerializeToProtocol(dap::Scope &scope)
|
||||
{
|
||||
scope.name = "Global";
|
||||
scope.expensive = false;
|
||||
scope.presentationHint = "globals";
|
||||
scope.variablesReference = GetId();
|
||||
|
||||
std::vector<std::string> childNames;
|
||||
GetChildNames(childNames);
|
||||
|
||||
scope.namedVariables = childNames.size();
|
||||
scope.indexedVariables = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GlobalScopeStateNode::GetChildNames(std::vector<std::string> &names)
|
||||
{
|
||||
for (int i = 0; GlobalNames[i] != nullptr; i++)
|
||||
{
|
||||
names.push_back(GlobalNames[i]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GlobalScopeStateNode::GetChildNode(std::string name, std::shared_ptr<StateNodeBase> &node)
|
||||
{
|
||||
if (m_children.empty())
|
||||
{
|
||||
std::vector<std::string> childNames;
|
||||
GetChildNames(childNames);
|
||||
caseless_path_set childSet {childNames.begin(), childNames.end()};
|
||||
for (auto ns : Namespaces.AllNamespaces)
|
||||
{
|
||||
auto symbolIter = ns->Symbols.GetIterator();
|
||||
PSymbolTable::MapType::Pair *pair;
|
||||
while (symbolIter.NextPair(pair))
|
||||
{
|
||||
if (childSet.find(pair->Key.GetChars()) != childSet.end())
|
||||
{
|
||||
std::string symname = pair->Key.GetChars();
|
||||
PSymbol *val = pair->Value;
|
||||
if (val->SymbolName == NAME_None)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
PField *field = dyn_cast<PField>(val);
|
||||
if (field)
|
||||
{
|
||||
field->Type;
|
||||
// the offset is the address of the field
|
||||
void *addr = (void *)(field->Offset);
|
||||
VMValue val = GetVMValue(addr, field->Type, field->BitValue);
|
||||
m_children[symname] = RuntimeState::CreateNodeForVariable(symname, val, field->Type);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (m_children.find(name) != m_children.end())
|
||||
{
|
||||
node = m_children[name];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
20
src/common/scripting/dap/Nodes/GlobalScopeStateNode.h
Normal file
20
src/common/scripting/dap/Nodes/GlobalScopeStateNode.h
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
#pragma once
|
||||
|
||||
#include <common/scripting/dap/GameInterfaces.h>
|
||||
#include <dap/protocol.h>
|
||||
|
||||
#include "StateNodeBase.h"
|
||||
|
||||
namespace DebugServer
|
||||
{
|
||||
class GlobalScopeStateNode : public StateNodeBase, public IProtocolScopeSerializable, public IStructuredState
|
||||
{
|
||||
caseless_path_map<std::shared_ptr<StateNodeBase>> m_children;
|
||||
public:
|
||||
GlobalScopeStateNode();
|
||||
|
||||
bool SerializeToProtocol(dap::Scope &scope) override;
|
||||
bool GetChildNames(std::vector<std::string> &names) override;
|
||||
bool GetChildNode(std::string name, std::shared_ptr<StateNodeBase> &node) override;
|
||||
};
|
||||
}
|
||||
146
src/common/scripting/dap/Nodes/LocalScopeStateNode.cpp
Normal file
146
src/common/scripting/dap/Nodes/LocalScopeStateNode.cpp
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
#include "LocalScopeStateNode.h"
|
||||
|
||||
#include "common/scripting/dap/GameInterfaces.h"
|
||||
#include "common/scripting/dap/Nodes/StateNodeBase.h"
|
||||
#include <common/scripting/dap/Utilities.h>
|
||||
#include <common/scripting/dap/RuntimeState.h>
|
||||
|
||||
namespace DebugServer
|
||||
{
|
||||
static constexpr const char *const LOCAL = "Local";
|
||||
static constexpr const char *const SELF = "self";
|
||||
static constexpr const char *const INVOKER = "invoker";
|
||||
|
||||
LocalScopeStateNode::LocalScopeStateNode(VMFrame *stackFrame) : m_stackFrame(stackFrame) { }
|
||||
|
||||
|
||||
std::string LocalScopeStateNode::GetLineQualifiedName(const std::string &name, int line)
|
||||
{
|
||||
return name + " @ line " + std::to_string(line);
|
||||
}
|
||||
|
||||
int LocalScopeStateNode::GetLineFromLineQualifiedName(const std::string &name)
|
||||
{
|
||||
auto pos = name.find(" @ line ");
|
||||
if (pos == std::string::npos) {
|
||||
return -1;
|
||||
}
|
||||
auto lineNumberStr = name.substr(pos + 8);
|
||||
return std::stoi(lineNumberStr);
|
||||
}
|
||||
|
||||
bool LocalScopeStateNode::SerializeToProtocol(dap::Scope &scope)
|
||||
{
|
||||
scope.name = LOCAL;
|
||||
scope.expensive = false;
|
||||
|
||||
scope.variablesReference = GetId();
|
||||
scope.presentationHint = "locals";
|
||||
std::vector<std::string> childNames;
|
||||
GetChildNames(childNames);
|
||||
|
||||
scope.namedVariables = childNames.size();
|
||||
scope.indexedVariables = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool LocalScopeStateNode::GetChildNames(std::vector<std::string> &names)
|
||||
{
|
||||
if (m_state.m_locals.empty()) m_state = GetLocalsState(m_stackFrame);
|
||||
|
||||
auto scriptFunc = dynamic_cast<VMScriptFunction *>(m_stackFrame->Func);
|
||||
std::vector<size_t> localIdx;
|
||||
|
||||
for (size_t i = 0; i < m_state.m_locals.size(); i++)
|
||||
{
|
||||
auto &local = m_state.m_locals[i];
|
||||
if (scriptFunc && scriptFunc->PCToLine(m_stackFrame->PC) <= local.Line)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
auto oldPos = std::find(names.begin(), names.end(), local.Name);
|
||||
auto name = local.Name;
|
||||
if (oldPos != names.end()){
|
||||
auto oldIdx = oldPos - names.begin();
|
||||
auto &oldName = names.at(oldIdx);
|
||||
auto oldLocalIdx = localIdx.at(oldIdx);
|
||||
auto oldLocalLine = m_state.m_locals.at(oldLocalIdx).Line;
|
||||
oldName = GetLineQualifiedName(local.Name, oldLocalLine);
|
||||
name = GetLineQualifiedName(local.Name, local.Line);
|
||||
}
|
||||
localIdx.push_back(i);
|
||||
names.push_back(name);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void LocalScopeStateNode::CacheChildren()
|
||||
{
|
||||
if (m_state.m_locals.empty()) m_state = GetLocalsState(m_stackFrame);
|
||||
m_children.clear();
|
||||
PClass *invoker = nullptr;
|
||||
caseless_path_map<int> name_to_line;
|
||||
std::vector<std::pair<std::string, std::shared_ptr<StateNodeBase>>> nodes;
|
||||
if (m_stackFrame->Func->ImplicitArgs >= 2 && m_state.m_locals.size() >= 2 && m_state.m_locals[1].Name == INVOKER)
|
||||
{
|
||||
invoker = GetClassDescriptor(m_state.m_locals[1].Type);
|
||||
}
|
||||
else if (
|
||||
IsFunctionAction(m_stackFrame->Func) && m_stackFrame->Func->ImplicitArgs >= 1 && m_state.m_locals.size() >= 1
|
||||
&& m_state.m_locals[0].Name == SELF) // Try 'self' ?
|
||||
{
|
||||
invoker = GetClassDescriptor(m_state.m_locals[0].Type);
|
||||
}
|
||||
for (auto &local : m_state.m_locals)
|
||||
{
|
||||
const VMFrame * current_frame = m_stackFrame;
|
||||
auto scriptFunc = dynamic_cast<VMScriptFunction *>(m_stackFrame->Func);
|
||||
if (scriptFunc && scriptFunc->PCToLine(m_stackFrame->PC) <= local.Line)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
std::string name = local.Name;
|
||||
auto node = RuntimeState::CreateNodeForVariable(name, local.Value, local.Type, current_frame, invoker);
|
||||
if (m_children.find(name) != m_children.end()){
|
||||
std::shared_ptr<IProtocolVariableSerializableWithName> oldNamedNode = std::dynamic_pointer_cast<IProtocolVariableSerializableWithName>(m_children[name]);
|
||||
std::shared_ptr<IProtocolVariableSerializableWithName> newNamedNode = std::dynamic_pointer_cast<IProtocolVariableSerializableWithName>(node);
|
||||
if (oldNamedNode && newNamedNode) {
|
||||
auto oldLine = name_to_line[name];
|
||||
auto newLine = local.Line;
|
||||
std::string oldName = GetLineQualifiedName(name, oldLine);
|
||||
std::string newName = GetLineQualifiedName(name, newLine);
|
||||
oldNamedNode->SetName(oldName);
|
||||
newNamedNode->SetName(newName);
|
||||
m_children[oldName] = m_children[name];
|
||||
m_children.erase(name);
|
||||
name_to_line.erase(name);
|
||||
name_to_line[oldName] = oldLine;
|
||||
name = newName;
|
||||
}
|
||||
}
|
||||
name_to_line[name] = local.Line;
|
||||
m_children[name] = node;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool LocalScopeStateNode::GetChildNode(std::string name, std::shared_ptr<StateNodeBase> &node)
|
||||
{
|
||||
if (m_state.m_locals.empty())
|
||||
m_state = GetLocalsState(m_stackFrame);
|
||||
|
||||
if (m_children.empty())
|
||||
{
|
||||
CacheChildren();
|
||||
}
|
||||
if (m_children.find(name) != m_children.end())
|
||||
{
|
||||
node = m_children[name];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
27
src/common/scripting/dap/Nodes/LocalScopeStateNode.h
Normal file
27
src/common/scripting/dap/Nodes/LocalScopeStateNode.h
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
#pragma once
|
||||
|
||||
#include <common/scripting/dap/GameInterfaces.h>
|
||||
#include <dap/protocol.h>
|
||||
|
||||
#include "StateNodeBase.h"
|
||||
|
||||
namespace DebugServer
|
||||
{
|
||||
class LocalScopeStateNode : public StateNodeBase, public IProtocolScopeSerializable, public IStructuredState
|
||||
{
|
||||
VMFrame *m_stackFrame;
|
||||
FrameLocalsState m_state;
|
||||
caseless_path_map<std::shared_ptr<StateNodeBase>> m_children;
|
||||
|
||||
void CacheChildren();
|
||||
public:
|
||||
LocalScopeStateNode(VMFrame *stackFrame);
|
||||
|
||||
static std::string GetLineQualifiedName(const std::string &name, int line);
|
||||
static int GetLineFromLineQualifiedName(const std::string &name);
|
||||
|
||||
bool SerializeToProtocol(dap::Scope &scope) override;
|
||||
bool GetChildNames(std::vector<std::string> &names) override;
|
||||
bool GetChildNode(std::string name, std::shared_ptr<StateNodeBase> &node) override;
|
||||
};
|
||||
}
|
||||
175
src/common/scripting/dap/Nodes/ObjectStateNode.cpp
Normal file
175
src/common/scripting/dap/Nodes/ObjectStateNode.cpp
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
|
||||
#include "ObjectStateNode.h"
|
||||
#include "common/scripting/dap/GameInterfaces.h"
|
||||
#include <common/scripting/dap/Utilities.h>
|
||||
#include <common/scripting/dap/RuntimeState.h>
|
||||
#include <common/objects/dobject.h>
|
||||
#include <memory>
|
||||
|
||||
namespace DebugServer
|
||||
{
|
||||
|
||||
ObjectStateNode::ObjectStateNode(const std::string &name, VMValue value, PType *asClass, const bool subView)
|
||||
: StateNodeNamedVariable(name), m_subView(subView), m_value(value), m_ClassType(asClass)
|
||||
{
|
||||
}
|
||||
|
||||
bool ObjectStateNode::SerializeToProtocol(dap::Variable &variable)
|
||||
{
|
||||
variable.variablesReference = IsVMValValidDObject(&m_value) ? GetId() : 0;
|
||||
auto pointedType = m_ClassType->isObjectPointer() ? m_ClassType->toPointer()->PointedType : m_ClassType;
|
||||
SetVariableName(variable);
|
||||
const char *typeName = pointedType->mDescriptiveName.GetChars();
|
||||
variable.type = typeName;
|
||||
std::vector<std::string> childNames;
|
||||
GetChildNames(childNames);
|
||||
variable.namedVariables = childNames.size();
|
||||
if (m_ClassType->isObjectPointer())
|
||||
{
|
||||
if (!m_value.a)
|
||||
{
|
||||
variable.value = StringFormat("%s <NULL>", typeName);
|
||||
}
|
||||
else if (m_VMType != nullptr && !m_subView && pointedType != m_VMType)
|
||||
{
|
||||
// If this is something that isn't actually descended from the class...
|
||||
if (!PType::toClass(m_VMType)->Descriptor->IsDescendantOf(PType::toClass(pointedType)->Descriptor))
|
||||
{
|
||||
variable.value = StringFormat("%s (%p) as %s", m_VMType->mDescriptiveName.GetChars(), m_value.a, typeName);
|
||||
}
|
||||
else
|
||||
{
|
||||
variable.value = StringFormat("%s (%p)", m_VMType->mDescriptiveName.GetChars(), m_value.a);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
variable.value = StringFormat("%s (%p)", typeName, m_value.a);
|
||||
}
|
||||
}
|
||||
else if (!m_subView)
|
||||
{
|
||||
variable.value = typeName;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ObjectStateNode::GetChildNames(std::vector<std::string> &names)
|
||||
{
|
||||
if (!m_cachedNames.empty())
|
||||
{
|
||||
names = m_cachedNames;
|
||||
return true;
|
||||
}
|
||||
auto p_type = m_ClassType->isObjectPointer() ? m_ClassType->toPointer()->PointedType : m_ClassType;
|
||||
if (p_type->isClass())
|
||||
{
|
||||
// do the parent class first
|
||||
DObject *dobject = IsVMValValidDObject(&m_value) ? static_cast<DObject *>(m_value.a) : nullptr;
|
||||
if (!dobject)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
m_VMType = dobject->GetClass()->VMType;
|
||||
auto classType = PType::toClass(p_type);
|
||||
auto descriptor = classType->Descriptor;
|
||||
// If the VMType is something else (and this isn't a view into the parent class properties)...
|
||||
if (!m_subView && m_VMType && m_VMType != classType && m_VMType->isClass())
|
||||
{
|
||||
classType = PType::toClass(m_VMType);
|
||||
descriptor = dobject->GetClass();
|
||||
}
|
||||
std::string error_msg;
|
||||
m_cachedNames.reserve(descriptor->Fields.Size() + 1);
|
||||
if (classType->ParentType && descriptor && descriptor->ParentClass)
|
||||
{
|
||||
auto parent = classType->ParentType;
|
||||
auto parentName = parent->mDescriptiveName.GetChars();
|
||||
m_children[parentName] = std::make_shared<ObjectStateNode>(parentName, m_value, parent, true);
|
||||
m_virtualChildren[parentName] = m_children[parentName];
|
||||
m_cachedNames.push_back(parentName);
|
||||
}
|
||||
try
|
||||
{
|
||||
for (auto field : descriptor->Fields)
|
||||
{
|
||||
auto name = field->SymbolName.GetChars();
|
||||
if (!dobject)
|
||||
{
|
||||
m_children[name] = RuntimeState::CreateNodeForVariable(name, VMValue(), field->Type, nullptr, descriptor);
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
auto child_val_ptr = GetVMValueVar(dobject, field->SymbolName, field->Type, field->BitValue);
|
||||
m_children[name] = RuntimeState::CreateNodeForVariable(name, child_val_ptr, field->Type, nullptr, descriptor);
|
||||
}
|
||||
catch (CRecoverableError &e)
|
||||
{
|
||||
error_msg = e.what();
|
||||
// class is not actually its descriptor (this is the case where things are intentionally set to destroyed objects, like `PendingWeapon = WP_NOCHANGE`)
|
||||
// try again with the actual class
|
||||
m_children.clear();
|
||||
m_cachedNames.clear();
|
||||
descriptor = dobject->GetClass();
|
||||
for (auto field : descriptor->Fields)
|
||||
{
|
||||
name = field->SymbolName.GetChars();
|
||||
auto child_val_ptr = GetVMValueVar(dobject, field->SymbolName, field->Type, field->BitValue);
|
||||
m_children[name] = RuntimeState::CreateNodeForVariable(name, child_val_ptr, field->Type, nullptr, descriptor);
|
||||
m_cachedNames.emplace_back(name);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
m_cachedNames.emplace_back(name);
|
||||
}
|
||||
}
|
||||
catch (CRecoverableError &e)
|
||||
{
|
||||
|
||||
LogError("Failed to get child names for object '%s' of type %s", m_name.c_str(), p_type->mDescriptiveName.GetChars());
|
||||
if (!error_msg.empty())
|
||||
{
|
||||
LogError("Error: %s", error_msg.c_str());
|
||||
}
|
||||
LogError("Error: %s", e.what());
|
||||
|
||||
return false;
|
||||
}
|
||||
names = m_cachedNames;
|
||||
return true;
|
||||
}
|
||||
LogError("Failed to get child names for object '%s' of type %s", m_name.c_str(), p_type->mDescriptiveName.GetChars());
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ObjectStateNode::GetChildNode(std::string name, std::shared_ptr<StateNodeBase> &node)
|
||||
{
|
||||
if (m_children.empty())
|
||||
{
|
||||
std::vector<std::string> names;
|
||||
GetChildNames(names);
|
||||
}
|
||||
if (m_children.find(name) != m_children.end())
|
||||
{
|
||||
node = m_children[name];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ObjectStateNode::Reset() { m_children.clear(); }
|
||||
|
||||
caseless_path_map<std::shared_ptr<StateNodeBase>> ObjectStateNode::GetVirtualContainerChildren()
|
||||
{
|
||||
caseless_path_map<std::shared_ptr<StateNodeBase>> children;
|
||||
for (auto &child : m_virtualChildren)
|
||||
{
|
||||
children[child.first] = child.second;
|
||||
}
|
||||
return children;
|
||||
}
|
||||
}
|
||||
31
src/common/scripting/dap/Nodes/ObjectStateNode.h
Normal file
31
src/common/scripting/dap/Nodes/ObjectStateNode.h
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
#pragma once
|
||||
#include <common/scripting/dap/GameInterfaces.h>
|
||||
#include <dap/protocol.h>
|
||||
|
||||
#include "StateNodeBase.h"
|
||||
#include <map>
|
||||
|
||||
namespace DebugServer
|
||||
{
|
||||
class ObjectStateNode : public StateNodeNamedVariable, public IStructuredState
|
||||
{
|
||||
bool m_subView;
|
||||
|
||||
const VMValue m_value;
|
||||
PType *m_ClassType;
|
||||
std::vector<std::string> m_cachedNames; // to ensure proper order of children
|
||||
caseless_path_map<std::shared_ptr<StateNodeBase>> m_children;
|
||||
caseless_path_map<std::shared_ptr<StateNodeBase>> m_virtualChildren;
|
||||
PType *m_VMType = nullptr;
|
||||
public:
|
||||
ObjectStateNode(const std::string &name, VMValue value, PType *asClass, bool subView = false);
|
||||
|
||||
bool SerializeToProtocol(dap::Variable &variable) override;
|
||||
|
||||
bool GetChildNames(std::vector<std::string> &names) override;
|
||||
bool GetChildNode(std::string name, std::shared_ptr<StateNodeBase> &node) override;
|
||||
void Reset();
|
||||
bool IsVirtualStructure() override { return m_subView; }
|
||||
caseless_path_map<std::shared_ptr<StateNodeBase>> GetVirtualContainerChildren() override;
|
||||
};
|
||||
}
|
||||
261
src/common/scripting/dap/Nodes/RegistersScopeStateNode.cpp
Normal file
261
src/common/scripting/dap/Nodes/RegistersScopeStateNode.cpp
Normal file
|
|
@ -0,0 +1,261 @@
|
|||
#include "RegistersScopeStateNode.h"
|
||||
#include <common/scripting/dap/Utilities.h>
|
||||
#include <common/scripting/dap/RuntimeState.h>
|
||||
|
||||
static const char *const PARAMS = "Params";
|
||||
static const char *const INTS = "Ints";
|
||||
static const char *const FLOATS = "Floats";
|
||||
static const char *const STRINGS = "Strings";
|
||||
static const char *const POINTERS = "Pointers";
|
||||
static const char *const SPECIAL_SETUP = "SpecialInits";
|
||||
namespace DebugServer
|
||||
{
|
||||
RegistersScopeStateNode::RegistersScopeStateNode(VMFrame *stackFrame) : m_stackFrame(stackFrame) { }
|
||||
|
||||
bool RegistersScopeStateNode::SerializeToProtocol(dap::Scope &scope)
|
||||
{
|
||||
scope.name = "Registers";
|
||||
scope.expensive = false;
|
||||
scope.presentationHint = "registers";
|
||||
scope.variablesReference = GetId();
|
||||
|
||||
std::vector<std::string> childNames;
|
||||
GetChildNames(childNames);
|
||||
|
||||
scope.namedVariables = childNames.size();
|
||||
scope.indexedVariables = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RegistersScopeStateNode::GetChildNames(std::vector<std::string> &names)
|
||||
{
|
||||
names.emplace_back(PARAMS);
|
||||
names.emplace_back(INTS);
|
||||
names.emplace_back(FLOATS);
|
||||
names.emplace_back(STRINGS);
|
||||
names.emplace_back(POINTERS);
|
||||
if (GetVMScriptFunction(m_stackFrame->Func))
|
||||
{
|
||||
names.emplace_back(SPECIAL_SETUP);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RegistersScopeStateNode::GetChildNode(std::string name, std::shared_ptr<StateNodeBase> &node)
|
||||
{
|
||||
if (CaseInsensitiveEquals(name, PARAMS))
|
||||
{
|
||||
node = std::make_shared<ParamsRegistersNode>(name, m_stackFrame);
|
||||
return true;
|
||||
}
|
||||
else if (CaseInsensitiveEquals(name, INTS))
|
||||
{
|
||||
node = std::make_shared<IntRegistersNode>(name, m_stackFrame);
|
||||
return true;
|
||||
}
|
||||
else if (CaseInsensitiveEquals(name, FLOATS))
|
||||
{
|
||||
node = std::make_shared<FloatRegistersNode>(name, m_stackFrame);
|
||||
return true;
|
||||
}
|
||||
else if (CaseInsensitiveEquals(name, STRINGS))
|
||||
{
|
||||
node = std::make_shared<StringRegistersNode>(name, m_stackFrame);
|
||||
return true;
|
||||
}
|
||||
else if (CaseInsensitiveEquals(name, POINTERS))
|
||||
{
|
||||
node = std::make_shared<PointerRegistersNode>(name, m_stackFrame);
|
||||
return true;
|
||||
}
|
||||
else if (CaseInsensitiveEquals(name, SPECIAL_SETUP))
|
||||
{
|
||||
node = std::make_shared<SpecialSetupRegistersNode>(name, m_stackFrame);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool RegistersNode::SerializeToProtocol(dap::Variable &variable)
|
||||
{
|
||||
|
||||
variable.name = m_name;
|
||||
variable.type = m_name + " Registers";
|
||||
// value will be the max number of registers
|
||||
auto max_num_reg = GetNumberOfRegisters();
|
||||
variable.value = m_name + "[" + std::to_string(max_num_reg) + "]";
|
||||
variable.namedVariables = max_num_reg;
|
||||
variable.variablesReference = GetId();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RegistersNode::GetChildNames(std::vector<std::string> &names)
|
||||
{
|
||||
for (int i = 0; i < GetNumberOfRegisters(); i++)
|
||||
{
|
||||
names.push_back(GetPrefix() + std::to_string(i));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RegistersNode::GetChildNode(std::string name, std::shared_ptr<StateNodeBase> &node)
|
||||
{
|
||||
|
||||
// name is "a2" or "s3" etc
|
||||
std::string prefix = GetPrefix();
|
||||
int index = std::stoi(name.substr(prefix.size()));
|
||||
if (index < 0 || index >= GetNumberOfRegisters())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
VMValue val = GetRegisterValue(index);
|
||||
node = RuntimeState::CreateNodeForVariable(name, val, GetRegisterType(index));
|
||||
return true;
|
||||
}
|
||||
|
||||
int PointerRegistersNode::GetNumberOfRegisters() const
|
||||
{
|
||||
return m_stackFrame->NumRegA;
|
||||
}
|
||||
|
||||
VMValue PointerRegistersNode::GetRegisterValue(int index) const
|
||||
{
|
||||
return m_stackFrame->GetRegA()[index];
|
||||
}
|
||||
|
||||
PType *PointerRegistersNode::GetRegisterType(int index) const
|
||||
{
|
||||
return TypeVoidPtr;
|
||||
}
|
||||
|
||||
int StringRegistersNode::GetNumberOfRegisters() const
|
||||
{
|
||||
return m_stackFrame->NumRegS;
|
||||
}
|
||||
|
||||
VMValue StringRegistersNode::GetRegisterValue(int index) const
|
||||
{
|
||||
return {&m_stackFrame->GetRegS()[index]};
|
||||
}
|
||||
|
||||
PType *StringRegistersNode::GetRegisterType(int index) const
|
||||
{
|
||||
return TypeString;
|
||||
}
|
||||
|
||||
int FloatRegistersNode::GetNumberOfRegisters() const
|
||||
{
|
||||
return m_stackFrame->NumRegF;
|
||||
}
|
||||
|
||||
VMValue FloatRegistersNode::GetRegisterValue(int index) const
|
||||
{
|
||||
return m_stackFrame->GetRegF()[index];
|
||||
}
|
||||
|
||||
PType *FloatRegistersNode::GetRegisterType(int index) const
|
||||
{
|
||||
return TypeFloat64;
|
||||
}
|
||||
|
||||
int IntRegistersNode::GetNumberOfRegisters() const
|
||||
{
|
||||
return m_stackFrame->NumRegD;
|
||||
}
|
||||
|
||||
VMValue IntRegistersNode::GetRegisterValue(int index) const
|
||||
{
|
||||
return m_stackFrame->GetRegD()[index];
|
||||
}
|
||||
|
||||
PType *IntRegistersNode::GetRegisterType(int index) const
|
||||
{
|
||||
return TypeSInt32;
|
||||
}
|
||||
|
||||
int ParamsRegistersNode::GetNumberOfRegisters() const
|
||||
{
|
||||
return m_stackFrame->MaxParam;
|
||||
}
|
||||
|
||||
VMValue ParamsRegistersNode::GetRegisterValue(int index) const
|
||||
{
|
||||
return m_stackFrame->GetParam()[index];
|
||||
}
|
||||
|
||||
PType *ParamsRegistersNode::GetRegisterType(int index) const
|
||||
{
|
||||
// TODO: Is it possible to get the type of parameters?
|
||||
return TypeVoidPtr;
|
||||
}
|
||||
|
||||
bool PointerRegistersNode::GetChildNode(std::string name, std::shared_ptr<StateNodeBase> &node)
|
||||
{
|
||||
std::string prefix = GetPrefix();
|
||||
int index = std::stoi(name.substr(prefix.size()));
|
||||
if (index < 0 || index >= GetNumberOfRegisters())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
VMValue val = GetRegisterValue(index);
|
||||
node = RuntimeState::CreateNodeForVariable(name, val, GetRegisterType(index));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParamsRegistersNode::SerializeToProtocol(dap::Variable &variable)
|
||||
{
|
||||
|
||||
variable.name = PARAMS;
|
||||
variable.type = "Parameter Registers";
|
||||
// value will be the max number of registers
|
||||
auto max_num_reg = GetNumberOfRegisters();
|
||||
variable.value = "Params - Max: " + std::to_string(max_num_reg) + ", In Use: " + std::to_string(m_stackFrame->NumParam);
|
||||
variable.indexedVariables = max_num_reg;
|
||||
variable.variablesReference = GetId();
|
||||
return true;
|
||||
}
|
||||
|
||||
RegistersNode::RegistersNode(std::string name, VMFrame *stackFrame) : m_stackFrame(stackFrame), m_name(name) { }
|
||||
|
||||
//SpecialSetupRegistersNode
|
||||
|
||||
bool SpecialSetupRegistersNode::SerializeToProtocol(dap::Variable &variable)
|
||||
{
|
||||
|
||||
variable.name = SPECIAL_SETUP;
|
||||
variable.type = "Special Setup Registers";
|
||||
// value will be the max number of registers
|
||||
auto max_num_reg = GetNumberOfRegisters();
|
||||
variable.value = "Special Setup - Max: " + std::to_string(max_num_reg);
|
||||
variable.indexedVariables = max_num_reg;
|
||||
variable.variablesReference = GetId();
|
||||
return true;
|
||||
}
|
||||
|
||||
int SpecialSetupRegistersNode::GetNumberOfRegisters() const
|
||||
{
|
||||
return GetVMScriptFunction(m_stackFrame->Func)->SpecialInits.size();
|
||||
}
|
||||
|
||||
VMValue SpecialSetupRegistersNode::GetRegisterValue(int index) const
|
||||
{
|
||||
auto *fun = GetVMScriptFunction(m_stackFrame->Func);
|
||||
auto *addr = m_stackFrame->GetExtra();
|
||||
auto *caddr = static_cast<char *>(addr);
|
||||
auto &tao = fun->SpecialInits[index];
|
||||
auto *type = tao.first;
|
||||
void *var = caddr + tao.second;
|
||||
return GetVMValue(var, type);
|
||||
}
|
||||
|
||||
PType *SpecialSetupRegistersNode::GetRegisterType(int index) const
|
||||
{
|
||||
auto *fun = GetVMScriptFunction(m_stackFrame->Func);
|
||||
auto &tao = fun->SpecialInits[index];
|
||||
return const_cast<PType *>(tao.first);
|
||||
}
|
||||
|
||||
|
||||
} // namespace DebugServer
|
||||
135
src/common/scripting/dap/Nodes/RegistersScopeStateNode.h
Normal file
135
src/common/scripting/dap/Nodes/RegistersScopeStateNode.h
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
#pragma once
|
||||
|
||||
#include <common/scripting/dap/GameInterfaces.h>
|
||||
#include <dap/protocol.h>
|
||||
|
||||
#include "StateNodeBase.h"
|
||||
|
||||
namespace DebugServer
|
||||
{
|
||||
|
||||
class RegistersNode : public StateNodeBase, public IProtocolVariableSerializable, public IStructuredState
|
||||
{
|
||||
protected:
|
||||
VMFrame *m_stackFrame;
|
||||
caseless_path_map<std::shared_ptr<StateNodeBase>> m_children;
|
||||
std::string m_name;
|
||||
public:
|
||||
RegistersNode(std::string name, VMFrame *stackFrame);
|
||||
|
||||
virtual std::string GetPrefix() const = 0;
|
||||
virtual int GetNumberOfRegisters() const = 0;
|
||||
|
||||
virtual VMValue GetRegisterValue(int index) const = 0;
|
||||
|
||||
virtual PType *GetRegisterType([[maybe_unused]] int index) const = 0;
|
||||
|
||||
bool SerializeToProtocol(dap::Variable &variable) override;
|
||||
|
||||
bool GetChildNames(std::vector<std::string> &names) override;
|
||||
|
||||
bool GetChildNode(std::string name, std::shared_ptr<StateNodeBase> &node) override;
|
||||
};
|
||||
|
||||
class PointerRegistersNode : public RegistersNode
|
||||
{
|
||||
public:
|
||||
int GetNumberOfRegisters() const override;
|
||||
|
||||
std::string GetPrefix() const override { return "a"; }
|
||||
VMValue GetRegisterValue(int index) const override;
|
||||
|
||||
PType *GetRegisterType([[maybe_unused]] int index) const override;
|
||||
|
||||
PointerRegistersNode(std::string name, VMFrame *stackFrame) : RegistersNode(name, stackFrame) { };
|
||||
|
||||
bool GetChildNode(std::string name, std::shared_ptr<StateNodeBase> &node);
|
||||
};
|
||||
|
||||
class StringRegistersNode : public RegistersNode
|
||||
{
|
||||
public:
|
||||
std::string GetPrefix() const override { return "s"; }
|
||||
int GetNumberOfRegisters() const override;
|
||||
|
||||
VMValue GetRegisterValue(int index) const override;
|
||||
|
||||
PType *GetRegisterType([[maybe_unused]] int index) const override;
|
||||
|
||||
StringRegistersNode(std::string name, VMFrame *stackFrame) : RegistersNode(name, stackFrame) { };
|
||||
};
|
||||
|
||||
class FloatRegistersNode : public RegistersNode
|
||||
{
|
||||
public:
|
||||
std::string GetPrefix() const override { return "f"; }
|
||||
|
||||
int GetNumberOfRegisters() const override;
|
||||
|
||||
VMValue GetRegisterValue(int index) const override;
|
||||
|
||||
PType *GetRegisterType([[maybe_unused]] int index) const override;
|
||||
|
||||
FloatRegistersNode(std::string name, VMFrame *stackFrame) : RegistersNode(name, stackFrame) { };
|
||||
};
|
||||
|
||||
class IntRegistersNode : public RegistersNode
|
||||
{
|
||||
public:
|
||||
std::string GetPrefix() const override { return "d"; }
|
||||
|
||||
int GetNumberOfRegisters() const override;
|
||||
|
||||
VMValue GetRegisterValue(int index) const override;
|
||||
|
||||
PType *GetRegisterType([[maybe_unused]] int index) const override;
|
||||
|
||||
IntRegistersNode(std::string name, VMFrame *stackFrame) : RegistersNode(name, stackFrame) { };
|
||||
};
|
||||
|
||||
class ParamsRegistersNode : public RegistersNode
|
||||
{
|
||||
public:
|
||||
std::string GetPrefix() const override { return ""; }
|
||||
|
||||
int GetNumberOfRegisters() const override;
|
||||
|
||||
VMValue GetRegisterValue(int index) const override;
|
||||
|
||||
PType *GetRegisterType([[maybe_unused]] int index) const override;
|
||||
|
||||
ParamsRegistersNode(std::string name, VMFrame *stackFrame) : RegistersNode(name, stackFrame) { };
|
||||
|
||||
bool SerializeToProtocol(dap::Variable &variable) override;
|
||||
};
|
||||
|
||||
class SpecialSetupRegistersNode : public RegistersNode
|
||||
{
|
||||
public:
|
||||
std::string GetPrefix() const override { return ""; }
|
||||
|
||||
int GetNumberOfRegisters() const override;
|
||||
|
||||
VMValue GetRegisterValue(int index) const override;
|
||||
|
||||
PType *GetRegisterType([[maybe_unused]] int index) const override;
|
||||
|
||||
SpecialSetupRegistersNode(std::string name, VMFrame *stackFrame) : RegistersNode(name, stackFrame) { };
|
||||
|
||||
bool SerializeToProtocol(dap::Variable &variable) override;
|
||||
};
|
||||
|
||||
class RegistersScopeStateNode : public StateNodeBase, public IProtocolScopeSerializable, public IStructuredState
|
||||
{
|
||||
VMFrame *m_stackFrame;
|
||||
caseless_path_map<std::shared_ptr<StateNodeBase>> m_children;
|
||||
public:
|
||||
RegistersScopeStateNode(VMFrame *stackFrame);
|
||||
|
||||
bool SerializeToProtocol(dap::Scope &scope) override;
|
||||
|
||||
bool GetChildNames(std::vector<std::string> &names) override;
|
||||
|
||||
bool GetChildNode(std::string name, std::shared_ptr<StateNodeBase> &node) override;
|
||||
};
|
||||
}
|
||||
139
src/common/scripting/dap/Nodes/StackFrameStateNode.cpp
Normal file
139
src/common/scripting/dap/Nodes/StackFrameStateNode.cpp
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
#include "StackFrameStateNode.h"
|
||||
|
||||
|
||||
#include <common/scripting/dap/Utilities.h>
|
||||
#include <string>
|
||||
|
||||
#include "LocalScopeStateNode.h"
|
||||
#include "RegistersScopeStateNode.h"
|
||||
#include "GlobalScopeStateNode.h"
|
||||
#include "CVarScopeStateNode.h"
|
||||
|
||||
namespace DebugServer
|
||||
{
|
||||
StackFrameStateNode::StackFrameStateNode(VMFunction *nativeFunction, VMFrame *parentStackFrame)
|
||||
{
|
||||
m_fakeStackFrame.Func = nativeFunction;
|
||||
m_fakeStackFrame.ParentFrame = parentStackFrame;
|
||||
m_fakeStackFrame.PC = nullptr;
|
||||
m_fakeStackFrame.NumRegD = 0;
|
||||
m_fakeStackFrame.NumRegF = 0;
|
||||
m_fakeStackFrame.NumRegS = 0;
|
||||
m_fakeStackFrame.NumRegA = 0;
|
||||
m_fakeStackFrame.MaxParam = 0;
|
||||
m_fakeStackFrame.NumParam = 0;
|
||||
m_stackFrame = &m_fakeStackFrame;
|
||||
m_globalsScope = std::make_shared<GlobalScopeStateNode>();
|
||||
m_cvarScope = std::make_shared<CVarScopeStateNode>();
|
||||
}
|
||||
|
||||
StackFrameStateNode::StackFrameStateNode(VMFrame *stackFrame) : m_stackFrame(stackFrame), m_fakeStackFrame()
|
||||
{
|
||||
if (!IsFunctionNative(m_stackFrame->Func))
|
||||
{
|
||||
auto scriptFunction = dynamic_cast<VMScriptFunction *>(m_stackFrame->Func);
|
||||
if (scriptFunction)
|
||||
{
|
||||
m_localScope = std::make_shared<LocalScopeStateNode>(m_stackFrame);
|
||||
}
|
||||
m_registersScope = std::make_shared<RegistersScopeStateNode>(m_stackFrame);
|
||||
}
|
||||
m_globalsScope = std::make_shared<GlobalScopeStateNode>();
|
||||
m_cvarScope = std::make_shared<CVarScopeStateNode>();
|
||||
}
|
||||
|
||||
bool StackFrameStateNode::SerializeToProtocol(dap::StackFrame &stackFrame, PexCache *pexCache) const
|
||||
{
|
||||
stackFrame.id = GetId();
|
||||
dap::Source source;
|
||||
if (IsFunctionNative(m_stackFrame->Func))
|
||||
{
|
||||
stackFrame.name = m_stackFrame->Func->PrintableName;
|
||||
return true;
|
||||
}
|
||||
auto scriptFunction = dynamic_cast<VMScriptFunction *>(m_stackFrame->Func);
|
||||
if (scriptFunction && scriptFunction->SourceFileName.GetChars() && pexCache->GetSourceData(scriptFunction->SourceFileName.GetChars(), source))
|
||||
{
|
||||
stackFrame.source = source;
|
||||
if (m_stackFrame->PC)
|
||||
{
|
||||
int lineNumber = scriptFunction->PCToLine(m_stackFrame->PC);
|
||||
if (lineNumber > 0)
|
||||
{
|
||||
stackFrame.line = lineNumber;
|
||||
stackFrame.column = 1;
|
||||
}
|
||||
else if (lineNumber == -1 && scriptFunction->LineInfoCount > 0)
|
||||
{
|
||||
// end of the function, get the max line number
|
||||
int max_line = 0;
|
||||
for (unsigned int i = 0; i < scriptFunction->LineInfoCount; i++)
|
||||
{
|
||||
if (scriptFunction->LineInfo[i].LineNumber > max_line)
|
||||
{
|
||||
max_line = scriptFunction->LineInfo[i].LineNumber;
|
||||
}
|
||||
}
|
||||
stackFrame.line = max_line + 1;
|
||||
stackFrame.column = 1;
|
||||
}
|
||||
stackFrame.instructionPointerReference = StringFormat("%p", m_stackFrame->PC);
|
||||
}
|
||||
}
|
||||
|
||||
stackFrame.name = m_stackFrame->Func->PrintableName;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool StackFrameStateNode::GetChildNames(std::vector<std::string> &names)
|
||||
{
|
||||
auto scriptFunction = GetVMScriptFunction(m_stackFrame->Func);
|
||||
if (scriptFunction)
|
||||
{
|
||||
names.push_back(LOCAL_SCOPE_NAME);
|
||||
}
|
||||
names.push_back(GLOBALS_SCOPE_NAME);
|
||||
names.push_back(CVAR_SCOPE_NAME);
|
||||
if (scriptFunction)
|
||||
{
|
||||
names.push_back(REGISTERS_SCOPE_NAME);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool StackFrameStateNode::GetChildNode(std::string name, std::shared_ptr<StateNodeBase> &node)
|
||||
{
|
||||
if (CaseInsensitiveEquals(name, GLOBALS_SCOPE_NAME))
|
||||
{
|
||||
node = m_globalsScope;
|
||||
return true;
|
||||
}
|
||||
if (CaseInsensitiveEquals(name, CVAR_SCOPE_NAME))
|
||||
{
|
||||
node = m_cvarScope;
|
||||
return true;
|
||||
}
|
||||
// Native functions don't have the Local or Registers scopes
|
||||
if (IsFunctionNative(m_stackFrame->Func))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (CaseInsensitiveEquals(name, REGISTERS_SCOPE_NAME))
|
||||
{
|
||||
node = m_registersScope;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (CaseInsensitiveEquals(name, LOCAL_SCOPE_NAME) && !IsFunctionNative(m_stackFrame->Func))
|
||||
{
|
||||
auto scriptFunction = dynamic_cast<VMScriptFunction *>(m_stackFrame->Func);
|
||||
if (scriptFunction)
|
||||
{
|
||||
node = m_localScope;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
35
src/common/scripting/dap/Nodes/StackFrameStateNode.h
Normal file
35
src/common/scripting/dap/Nodes/StackFrameStateNode.h
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
#pragma once
|
||||
|
||||
#include "common/scripting/vm/vmintern.h"
|
||||
|
||||
#include <dap/protocol.h>
|
||||
#include "StateNodeBase.h"
|
||||
#include <common/scripting/dap/PexCache.h>
|
||||
#include <memory>
|
||||
|
||||
namespace DebugServer
|
||||
{
|
||||
class StackFrameStateNode : public StateNodeBase, public IStructuredState
|
||||
{
|
||||
VMFrame *m_stackFrame;
|
||||
VMFrame m_fakeStackFrame;
|
||||
std::shared_ptr<StateNodeBase> m_localScope = nullptr;
|
||||
std::shared_ptr<StateNodeBase> m_registersScope = nullptr;
|
||||
std::shared_ptr<StateNodeBase> m_globalsScope = nullptr;
|
||||
std::shared_ptr<StateNodeBase> m_cvarScope = nullptr;
|
||||
public:
|
||||
constexpr static const char *LOCAL_SCOPE_NAME = "Local";
|
||||
constexpr static const char *REGISTERS_SCOPE_NAME = "Registers";
|
||||
constexpr static const char *GLOBALS_SCOPE_NAME = "Global";
|
||||
constexpr static const char *CVAR_SCOPE_NAME = "CVars";
|
||||
|
||||
StackFrameStateNode(VMFunction *nativeFunction, VMFrame *parentStackFrame);
|
||||
explicit StackFrameStateNode(VMFrame *stackFrame);
|
||||
VMFrame *GetStackFrame() const { return m_stackFrame; }
|
||||
|
||||
bool SerializeToProtocol(dap::StackFrame &stackFrame, PexCache *pexCache) const;
|
||||
|
||||
bool GetChildNames(std::vector<std::string> &names) override;
|
||||
bool GetChildNode(std::string name, std::shared_ptr<StateNodeBase> &node) override;
|
||||
};
|
||||
}
|
||||
83
src/common/scripting/dap/Nodes/StackStateNode.cpp
Normal file
83
src/common/scripting/dap/Nodes/StackStateNode.cpp
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
#include "StackStateNode.h"
|
||||
|
||||
#include <common/scripting/dap/RuntimeState.h>
|
||||
#include <common/scripting/dap/Utilities.h>
|
||||
|
||||
#include <string>
|
||||
#include "StackFrameStateNode.h"
|
||||
|
||||
namespace DebugServer
|
||||
{
|
||||
StackStateNode::StackStateNode(const uint32_t stackId) : m_stackId(stackId) { }
|
||||
|
||||
bool StackStateNode::SerializeToProtocol(dap::Thread &thread) const
|
||||
{
|
||||
thread.id = m_stackId;
|
||||
|
||||
std::vector<VMFrame *> frames;
|
||||
RuntimeState::GetStackFrames(m_stackId, frames);
|
||||
|
||||
if (frames.empty())
|
||||
{
|
||||
thread.name = StringFormat("(%d)", thread.id);
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto frame = frames.back();
|
||||
const auto name = frame->Func ? frame->Func->PrintableName : "<unknown>";
|
||||
thread.name = StringFormat("%s (%d)", name, thread.id);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool StackStateNode::GetChildNames(std::vector<std::string> &names)
|
||||
{
|
||||
if (!m_children.empty())
|
||||
{
|
||||
for (const auto &child : m_children)
|
||||
{
|
||||
names.push_back(std::to_string(child.first));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
std::vector<VMFrame *> frames;
|
||||
RuntimeState::GetStackFrames(m_stackId, frames);
|
||||
|
||||
size_t frameNum = 0;
|
||||
for (size_t i = 0; i < frames.size(); i++)
|
||||
{
|
||||
if (PCIsAtNativeCall(frames.at(i)))
|
||||
{
|
||||
names.push_back(std::to_string(frameNum));
|
||||
m_children[frameNum] = std::make_shared<StackFrameStateNode>(GetCalledFunction(frames.at(i)), frames.at(i));
|
||||
frameNum++;
|
||||
}
|
||||
names.push_back(std::to_string(frameNum));
|
||||
m_children[frameNum] = std::make_shared<StackFrameStateNode>(frames.at(i));
|
||||
frameNum++;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool StackStateNode::GetChildNode(const std::string name, std::shared_ptr<StateNodeBase> &node)
|
||||
{
|
||||
int level;
|
||||
if (!ParseInt(name, &level))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (m_children.empty())
|
||||
{
|
||||
std::vector<std::string> names;
|
||||
GetChildNames(names);
|
||||
}
|
||||
if (m_children.find(level) != m_children.end())
|
||||
{
|
||||
node = m_children[level];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
22
src/common/scripting/dap/Nodes/StackStateNode.h
Normal file
22
src/common/scripting/dap/Nodes/StackStateNode.h
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
#pragma once
|
||||
|
||||
#include <common/scripting/dap/GameInterfaces.h>
|
||||
|
||||
#include <dap/protocol.h>
|
||||
#include "StateNodeBase.h"
|
||||
|
||||
namespace DebugServer
|
||||
{
|
||||
class StackStateNode : public StateNodeBase, public IStructuredState
|
||||
{
|
||||
uint32_t m_stackId;
|
||||
std::map<size_t, std::shared_ptr<StateNodeBase>> m_children;
|
||||
public:
|
||||
StackStateNode(uint32_t stackId);
|
||||
|
||||
bool SerializeToProtocol(dap::Thread &thread) const;
|
||||
|
||||
bool GetChildNames(std::vector<std::string> &names) override;
|
||||
bool GetChildNode(std::string name, std::shared_ptr<StateNodeBase> &node) override;
|
||||
};
|
||||
}
|
||||
43
src/common/scripting/dap/Nodes/StateNodeBase.cpp
Normal file
43
src/common/scripting/dap/Nodes/StateNodeBase.cpp
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
#include "StateNodeBase.h"
|
||||
namespace DebugServer
|
||||
{
|
||||
int StateNodeBase::GetId() const
|
||||
{
|
||||
return m_id;
|
||||
}
|
||||
|
||||
void StateNodeBase::SetId(const uint32_t id)
|
||||
{
|
||||
m_id = id;
|
||||
}
|
||||
|
||||
StateNodeNamedVariable::StateNodeNamedVariable(const std::string &name, const std::string &evalName)
|
||||
: m_name(name), m_evalName(evalName.empty() ? name : evalName)
|
||||
{
|
||||
}
|
||||
std::string StateNodeNamedVariable::GetName() const
|
||||
{
|
||||
return m_name;
|
||||
}
|
||||
std::string StateNodeNamedVariable::GetEvalName() const
|
||||
{
|
||||
return m_evalName;
|
||||
}
|
||||
void StateNodeNamedVariable::SetName(const std::string &name)
|
||||
{
|
||||
m_name = name;
|
||||
}
|
||||
void StateNodeNamedVariable::SetEvalName(const std::string &evalName)
|
||||
{
|
||||
m_evalName = evalName;
|
||||
}
|
||||
void StateNodeNamedVariable::SetVariableName(dap::Variable &variable)
|
||||
{
|
||||
variable.name = m_name;
|
||||
if (m_evalName != m_name)
|
||||
{
|
||||
variable.evaluateName = m_evalName;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace DebugServer
|
||||
67
src/common/scripting/dap/Nodes/StateNodeBase.h
Normal file
67
src/common/scripting/dap/Nodes/StateNodeBase.h
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
#pragma once
|
||||
|
||||
#include <common/scripting/dap/GameInterfaces.h>
|
||||
|
||||
#include <cstring>
|
||||
#include <dap/protocol.h>
|
||||
|
||||
namespace DebugServer
|
||||
{
|
||||
class StateNodeBase
|
||||
{
|
||||
uint32_t m_id = 0;
|
||||
public:
|
||||
virtual ~StateNodeBase() = default;
|
||||
|
||||
int GetId() const;
|
||||
|
||||
void SetId(uint32_t id);
|
||||
};
|
||||
|
||||
class RuntimeState;
|
||||
|
||||
class IProtocolVariableSerializable
|
||||
{
|
||||
public:
|
||||
virtual bool SerializeToProtocol(dap::Variable &variable) = 0;
|
||||
};
|
||||
|
||||
class IProtocolVariableSerializableWithName : public IProtocolVariableSerializable
|
||||
{
|
||||
public:
|
||||
virtual std::string GetName() const = 0;
|
||||
virtual std::string GetEvalName() const = 0;
|
||||
virtual void SetName(const std::string &name) = 0;
|
||||
virtual void SetEvalName(const std::string &evalName) = 0;
|
||||
};
|
||||
|
||||
class StateNodeNamedVariable : public StateNodeBase, public IProtocolVariableSerializableWithName
|
||||
{
|
||||
protected:
|
||||
std::string m_name;
|
||||
std::string m_evalName;
|
||||
public:
|
||||
StateNodeNamedVariable(const std::string &name, const std::string &evalName = {});
|
||||
std::string GetName() const override;
|
||||
std::string GetEvalName() const override;
|
||||
void SetName(const std::string &name) override;
|
||||
void SetEvalName(const std::string &evalName) override;
|
||||
void SetVariableName(dap::Variable &variable);
|
||||
|
||||
};
|
||||
|
||||
class IProtocolScopeSerializable
|
||||
{
|
||||
public:
|
||||
virtual bool SerializeToProtocol(dap::Scope &scope) = 0;
|
||||
};
|
||||
|
||||
class IStructuredState
|
||||
{
|
||||
public:
|
||||
virtual bool GetChildNames(std::vector<std::string> &names) = 0;
|
||||
virtual bool GetChildNode(std::string name, std::shared_ptr<StateNodeBase> &node) = 0;
|
||||
virtual bool IsVirtualStructure() { return false; }
|
||||
virtual caseless_path_map<std::shared_ptr<StateNodeBase>> GetVirtualContainerChildren() { return {}; }
|
||||
};
|
||||
} // namespace DebugServer
|
||||
260
src/common/scripting/dap/Nodes/StatePointerNode.cpp
Normal file
260
src/common/scripting/dap/Nodes/StatePointerNode.cpp
Normal file
|
|
@ -0,0 +1,260 @@
|
|||
#include "StatePointerNode.h"
|
||||
|
||||
#include "types.h"
|
||||
#include "ValueStateNode.h"
|
||||
#include <common/scripting/dap/Utilities.h>
|
||||
#include <common/scripting/dap/RuntimeState.h>
|
||||
#include <common/objects/dobject.h>
|
||||
#include <common/scripting/core/symbols.h>
|
||||
#include <info.h>
|
||||
#include "DummyNode.h"
|
||||
|
||||
namespace DebugServer
|
||||
{
|
||||
StatePointerNode::StatePointerNode(std::string name, VMValue value, PClass *owningType) : StateNodeNamedVariable(name), m_value(value), m_OwningType(owningType) { }
|
||||
void DumpStateHelper(FStateLabels *StateList, const FString &prefix)
|
||||
{
|
||||
for (int i = 0; i < StateList->NumLabels; i++)
|
||||
{
|
||||
auto state = StateList->Labels[i].State;
|
||||
if (state != NULL)
|
||||
{
|
||||
const PClassActor *owner = FState::StaticFindStateOwner(state);
|
||||
if (owner == NULL)
|
||||
{
|
||||
if (state->DehIndex >= 0) Printf(PRINT_NONOTIFY, "%s%s: DehExtra %d\n", prefix.GetChars(), StateList->Labels[i].Label.GetChars(), state->DehIndex);
|
||||
else
|
||||
Printf(PRINT_NONOTIFY, "%s%s: invalid\n", prefix.GetChars(), StateList->Labels[i].Label.GetChars());
|
||||
}
|
||||
else
|
||||
{
|
||||
Printf(PRINT_NONOTIFY, "%s%s: %s\n", prefix.GetChars(), StateList->Labels[i].Label.GetChars(), FState::StaticGetStateName(state).GetChars());
|
||||
}
|
||||
}
|
||||
if (StateList->Labels[i].Children != NULL)
|
||||
{
|
||||
DumpStateHelper(StateList->Labels[i].Children, prefix + '.' + StateList->Labels[i].Label.GetChars());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool StatePointerNode::SerializeToProtocol(dap::Variable &variable)
|
||||
{
|
||||
variable.variablesReference = IsVMValueValid(&m_value) ? GetId() : 0;
|
||||
variable.namedVariables = 0;
|
||||
std::vector<std::string> names;
|
||||
GetChildNames(names);
|
||||
variable.namedVariables = names.size();
|
||||
SetVariableName(variable);
|
||||
variable.type = "StatePointer";
|
||||
if (!IsVMValueValid(&m_value))
|
||||
{
|
||||
variable.value = "<NULL>";
|
||||
}
|
||||
else
|
||||
{
|
||||
auto *state = static_cast<FState *>(m_value.a);
|
||||
auto *owner = FState::StaticFindStateOwner(state);
|
||||
FName label = NAME_None;
|
||||
if (owner)
|
||||
{
|
||||
for (int i = 0; i < owner->GetStateLabels()->NumLabels; i++)
|
||||
{
|
||||
if (owner->GetStateLabels()->Labels[i].State == state)
|
||||
{
|
||||
label = owner->GetStateLabels()->Labels[i].Label;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!owner || label == NAME_None)
|
||||
{
|
||||
variable.value = FState::StaticGetStateName(state).GetChars();
|
||||
}
|
||||
else
|
||||
{
|
||||
variable.value = StringFormat("%s.%s", owner->TypeName.GetChars(), label.GetChars());
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool StatePointerNode::GetChildNames(std::vector<std::string> &names)
|
||||
{
|
||||
|
||||
names.push_back("NextState");
|
||||
names.push_back("sprite");
|
||||
names.push_back("Tics");
|
||||
names.push_back("TicRange");
|
||||
names.push_back("Light");
|
||||
names.push_back("StateFlags");
|
||||
names.push_back("Frame");
|
||||
names.push_back("UseFlags");
|
||||
names.push_back("DefineFlags");
|
||||
names.push_back("Misc1");
|
||||
names.push_back("Misc2");
|
||||
names.push_back("DehIndex");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool StatePointerNode::GetChildNode(std::string name, std::shared_ptr<StateNodeBase> &node)
|
||||
{
|
||||
if (!IsVMValueValid(&m_value))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
FState *state = static_cast<FState *>(m_value.a);
|
||||
if (!state)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (CaseInsensitiveEquals(name, "NextState"))
|
||||
{
|
||||
node = std::make_shared<StatePointerNode>("NextState", state->NextState, m_OwningType);
|
||||
return true;
|
||||
}
|
||||
if (CaseInsensitiveEquals(name, "sprite"))
|
||||
{
|
||||
node = std::make_shared<ValueStateNode>("sprite", state->sprite, TypeSpriteID);
|
||||
return true;
|
||||
}
|
||||
else if (CaseInsensitiveEquals(name, "Tics"))
|
||||
{
|
||||
node = std::make_shared<ValueStateNode>("Tics", state->Tics, TypeSInt16);
|
||||
return true;
|
||||
}
|
||||
else if (CaseInsensitiveEquals(name, "TicRange"))
|
||||
{
|
||||
node = std::make_shared<ValueStateNode>("TicRange", state->TicRange, TypeUInt16);
|
||||
return true;
|
||||
}
|
||||
else if (CaseInsensitiveEquals(name, "Light"))
|
||||
{
|
||||
node = std::make_shared<ValueStateNode>("Light", state->Light, TypeSInt16);
|
||||
return true;
|
||||
}
|
||||
else if (CaseInsensitiveEquals(name, "StateFlags"))
|
||||
{
|
||||
std::vector<std::string> strings;
|
||||
if (state->StateFlags & STF_SLOW)
|
||||
{
|
||||
strings.push_back("STF_SLOW");
|
||||
}
|
||||
if (state->StateFlags & STF_FAST)
|
||||
{
|
||||
strings.push_back("STF_FAST");
|
||||
}
|
||||
if (state->StateFlags & STF_FULLBRIGHT)
|
||||
{
|
||||
strings.push_back("STF_FULLBRIGHT");
|
||||
}
|
||||
if (state->StateFlags & STF_NODELAY)
|
||||
{
|
||||
strings.push_back("STF_NODELAY");
|
||||
}
|
||||
if (state->StateFlags & STF_SAMEFRAME)
|
||||
{
|
||||
strings.push_back("STF_SAMEFRAME");
|
||||
}
|
||||
if (state->StateFlags & STF_CANRAISE)
|
||||
{
|
||||
strings.push_back("STF_CANRAISE");
|
||||
}
|
||||
if (state->StateFlags & STF_DEHACKED)
|
||||
{
|
||||
strings.push_back("STF_DEHACKED");
|
||||
}
|
||||
if (state->StateFlags & STF_CONSUMEAMMO)
|
||||
{
|
||||
strings.push_back("STF_CONSUMEAMMO");
|
||||
}
|
||||
std::string value = StringFormat("%d (%s)", state->StateFlags, StringJoin(strings, " | ").c_str());
|
||||
node = std::make_shared<DummyNode>("StateFlags", value, "StateFlags");
|
||||
return true;
|
||||
}
|
||||
else if (CaseInsensitiveEquals(name, "Frame"))
|
||||
{
|
||||
node = std::make_shared<ValueStateNode>("Frame", state->Frame, TypeUInt8);
|
||||
return true;
|
||||
}
|
||||
else if (CaseInsensitiveEquals(name, "UseFlags"))
|
||||
{
|
||||
std::vector<std::string> strings;
|
||||
if (state->UseFlags & SUF_ACTOR)
|
||||
{
|
||||
strings.push_back("SUF_ACTOR");
|
||||
}
|
||||
if (state->UseFlags & SUF_OVERLAY)
|
||||
{
|
||||
strings.push_back("SUF_OVERLAY");
|
||||
}
|
||||
if (state->UseFlags & SUF_WEAPON)
|
||||
{
|
||||
strings.push_back("SUF_WEAPON");
|
||||
}
|
||||
if (state->UseFlags & SUF_ITEM)
|
||||
{
|
||||
strings.push_back("SUF_ITEM");
|
||||
}
|
||||
// join them together with a ' | ' separator
|
||||
const char *const delim = " | ";
|
||||
std::ostringstream imploded;
|
||||
std::copy(strings.begin(), strings.end(), std::ostream_iterator<std::string>(imploded, delim));
|
||||
|
||||
node = std::make_shared<ValueStateNode>("UseFlags", state->DefineFlags, TypeUInt8);
|
||||
return true;
|
||||
}
|
||||
else if (CaseInsensitiveEquals(name, "DefineFlags"))
|
||||
{
|
||||
std::string value = std::to_string(state->DefineFlags) + " ";
|
||||
switch (state->DefineFlags)
|
||||
{
|
||||
case SDF_NEXT:
|
||||
value += "SDF_NEXT";
|
||||
break;
|
||||
case SDF_STATE:
|
||||
value += "SDF_STATE";
|
||||
break;
|
||||
case SDF_STOP:
|
||||
value += "SDF_STOP";
|
||||
break;
|
||||
case SDF_WAIT:
|
||||
value += "SDF_WAIT";
|
||||
break;
|
||||
case SDF_LABEL:
|
||||
value += "SDF_LABEL";
|
||||
break;
|
||||
case SDF_INDEX:
|
||||
value += "SDF_INDEX";
|
||||
break;
|
||||
case SDF_MASK:
|
||||
value += "SDF_MASK";
|
||||
break;
|
||||
default:
|
||||
value += "<INVALID>";
|
||||
break;
|
||||
}
|
||||
node = std::make_shared<DummyNode>("DefineFlags", value, "DefineFlags");
|
||||
return true;
|
||||
}
|
||||
else if (CaseInsensitiveEquals(name, "Misc1"))
|
||||
{
|
||||
node = std::make_shared<ValueStateNode>("Misc1", state->Misc1, TypeSInt32);
|
||||
return true;
|
||||
}
|
||||
else if (CaseInsensitiveEquals(name, "Misc2"))
|
||||
{
|
||||
node = std::make_shared<ValueStateNode>("Misc2", state->Misc2, TypeSInt32);
|
||||
return true;
|
||||
}
|
||||
else if (CaseInsensitiveEquals(name, "DehIndex"))
|
||||
{
|
||||
node = std::make_shared<ValueStateNode>("DehIndex", state->DehIndex, TypeSInt32);
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
21
src/common/scripting/dap/Nodes/StatePointerNode.h
Normal file
21
src/common/scripting/dap/Nodes/StatePointerNode.h
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
#pragma once
|
||||
|
||||
#include <common/scripting/dap/GameInterfaces.h>
|
||||
|
||||
#include <dap/protocol.h>
|
||||
#include "StateNodeBase.h"
|
||||
|
||||
namespace DebugServer
|
||||
{
|
||||
class StatePointerNode : public StateNodeNamedVariable, public IStructuredState
|
||||
{
|
||||
const VMValue m_value;
|
||||
PClass *m_OwningType;
|
||||
caseless_path_map<std::shared_ptr<StateNodeBase>> m_children;
|
||||
public:
|
||||
StatePointerNode(std::string name, VMValue variable, PClass *owningType);
|
||||
bool SerializeToProtocol(dap::Variable &variable) override;
|
||||
bool GetChildNames(std::vector<std::string> &names) override;
|
||||
bool GetChildNode(std::string name, std::shared_ptr<StateNodeBase> &node) override;
|
||||
};
|
||||
}
|
||||
83
src/common/scripting/dap/Nodes/StructStateNode.cpp
Normal file
83
src/common/scripting/dap/Nodes/StructStateNode.cpp
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
|
||||
#include "StructStateNode.h"
|
||||
#include "types.h"
|
||||
#include <common/scripting/dap/Utilities.h>
|
||||
#include <common/scripting/dap/RuntimeState.h>
|
||||
#include <common/objects/dobject.h>
|
||||
#include <common/scripting/core/symbols.h>
|
||||
|
||||
namespace DebugServer
|
||||
{ // std::string name, VMValue* value, PType* knownType
|
||||
StructStateNode::StructStateNode(std::string name, VMValue value, PType *knownType, const VMFrame * currentFrame) : StateNodeNamedVariable(name), m_value(value), m_type(knownType), m_currentFrame(currentFrame) {}
|
||||
|
||||
bool StructStateNode::SerializeToProtocol(dap::Variable &variable)
|
||||
{
|
||||
if (m_children.empty())
|
||||
{
|
||||
CacheState();
|
||||
}
|
||||
// If this is a valid heap/stack allocated struct or one that is optimized to be stored in the stackframe registers
|
||||
bool inRegisters = !m_value.a && m_currentFrame && m_structInfo.StructFields.size();
|
||||
bool valid = m_structInfo.IsValid() && (inRegisters || IsVMValueValid(&m_value));
|
||||
variable.variablesReference = valid ? GetId() : 0;
|
||||
variable.namedVariables = m_structInfo.StructFields.size();
|
||||
SetVariableName(variable);
|
||||
variable.type = m_type->DescriptiveName();
|
||||
auto typeval = variable.type.value("");
|
||||
// check if name begins with 'Pointer<'; if so, remove it, and the trailing '>'
|
||||
if (typeval.size() > 9 && typeval.find("Pointer<") == 0 && typeval[typeval.size() - 1] == '>')
|
||||
{
|
||||
typeval = typeval.substr(8, typeval.size() - 9);
|
||||
}
|
||||
if (inRegisters)
|
||||
{
|
||||
variable.value = StringFormat("%s <REGISTERS>", typeval.c_str());
|
||||
}
|
||||
else if (!valid)
|
||||
{
|
||||
variable.value = StringFormat("%s <NULL>", typeval.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
variable.value = StringFormat("%s (%p)", typeval.c_str(), m_value.a);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool StructStateNode::GetChildNames(std::vector<std::string> &names)
|
||||
{
|
||||
if (m_children.empty())
|
||||
{
|
||||
CacheState();
|
||||
}
|
||||
for (auto &field : m_structInfo.StructFields)
|
||||
{
|
||||
names.push_back(field.Name);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void StructStateNode::CacheState()
|
||||
{
|
||||
m_structInfo = GetStructState(m_name, m_value, m_type, m_currentFrame);
|
||||
for (auto &field : m_structInfo.StructFields)
|
||||
{
|
||||
m_children[field.Name] = RuntimeState::CreateNodeForVariable(field.Name, field.Value, field.Type, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
bool StructStateNode::GetChildNode(std::string name, std::shared_ptr<StateNodeBase> &node)
|
||||
{
|
||||
if (m_children.empty())
|
||||
{
|
||||
CacheState();
|
||||
}
|
||||
if (m_children.find(name) != m_children.end())
|
||||
{
|
||||
node = m_children[name];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
25
src/common/scripting/dap/Nodes/StructStateNode.h
Normal file
25
src/common/scripting/dap/Nodes/StructStateNode.h
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
#pragma once
|
||||
#include <common/scripting/dap/GameInterfaces.h>
|
||||
|
||||
#include <dap/protocol.h>
|
||||
#include "StateNodeBase.h"
|
||||
|
||||
namespace DebugServer
|
||||
{
|
||||
class StructStateNode : public StateNodeNamedVariable, public IStructuredState
|
||||
{
|
||||
StructInfo m_structInfo;
|
||||
const VMValue m_value;
|
||||
PType *m_type;
|
||||
const VMFrame * m_currentFrame = nullptr;
|
||||
caseless_path_map<std::shared_ptr<StateNodeBase>> m_children;
|
||||
void CacheState();
|
||||
public:
|
||||
StructStateNode(std::string name, VMValue value, PType *knownType, const VMFrame *currentFrame = nullptr);
|
||||
|
||||
bool SerializeToProtocol(dap::Variable &variable) override;
|
||||
|
||||
bool GetChildNames(std::vector<std::string> &names) override;
|
||||
bool GetChildNode(std::string name, std::shared_ptr<StateNodeBase> &node) override;
|
||||
};
|
||||
}
|
||||
243
src/common/scripting/dap/Nodes/ValueStateNode.cpp
Normal file
243
src/common/scripting/dap/Nodes/ValueStateNode.cpp
Normal file
|
|
@ -0,0 +1,243 @@
|
|||
#include "ValueStateNode.h"
|
||||
|
||||
#include "actor.h"
|
||||
|
||||
#include <common/scripting/dap/Utilities.h>
|
||||
#include "common/scripting/dap/GameInterfaces.h"
|
||||
#include "types.h"
|
||||
#include "vm.h"
|
||||
#include <common/audio/sound/s_soundinternal.h>
|
||||
#include <palettecontainer.h>
|
||||
#include <texturemanager.h>
|
||||
#include <info.h>
|
||||
|
||||
namespace DebugServer
|
||||
{
|
||||
static const char *basicTypeNames[] = {"NONE", "uint32", "int32", "uint16", "int16", "uint8", "int8", "float", "double", "bool", "string",
|
||||
"name", "SpriteID", "TextureID", "TranslationID", "Sound", "Color", "Enum", "StateLabel", "pointer", "VoidPointer", nullptr};
|
||||
|
||||
ValueStateNode::ValueStateNode(std::string name, VMValue variable, PType *type, PClass *stateOwningClass)
|
||||
: StateNodeNamedVariable(name), m_variable(variable), m_type(type), m_StateOwningClass(stateOwningClass)
|
||||
{
|
||||
}
|
||||
|
||||
dap::Variable ValueStateNode::ToVariable(const VMValue &m_variable, PType *m_type, PClass *stateOwningClass)
|
||||
{
|
||||
dap::Variable variable;
|
||||
|
||||
auto basic_type = GetBasicType(m_type);
|
||||
variable.type = basicTypeNames[basic_type];
|
||||
|
||||
if (m_type == TypeString)
|
||||
{
|
||||
variable.type = "string";
|
||||
if (IsVMValueValid(&m_variable))
|
||||
{
|
||||
const FString &str = m_variable.s();
|
||||
auto chars = isFStringValid(str) ? str.GetChars() : nullptr;
|
||||
variable.value = !chars? "\"\"" : StringFormat("\"%s\"", chars);
|
||||
}
|
||||
else
|
||||
{
|
||||
variable.value = "<EMPTY>";
|
||||
}
|
||||
}
|
||||
else if (m_type->isPointer())
|
||||
{
|
||||
if (m_type->isClassPointer())
|
||||
{
|
||||
variable.type = "ClassPointer";
|
||||
auto type = PType::toClassPointer(m_type)->PointedType;
|
||||
variable.value = type->DescriptiveName();
|
||||
}
|
||||
else if (m_type->isFunctionPointer())
|
||||
{
|
||||
variable.type = PType::toFunctionPointer(m_type)->mDescriptiveName.GetChars();
|
||||
if (IsVMValueValid(&m_variable))
|
||||
{
|
||||
|
||||
PFunction *func = (PFunction *)m_variable.a;
|
||||
if (func->OwningClass)
|
||||
{
|
||||
variable.value += StringFormat("%s.%s (%p)", func->OwningClass->TypeName.GetChars(), func->SymbolName.GetChars(), m_variable.a);
|
||||
}
|
||||
else if (func->Variants.size() > 0 && func->Variants[0].Implementation)
|
||||
{
|
||||
variable.value += StringFormat("%s (%p)", func->Variants[0].Implementation->PrintableName, m_variable.a);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
variable.value = "<NULL>";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
auto pointedType = m_type->toPointer()->PointedType;
|
||||
variable.type = std::string("Pointer<") + pointedType->DescriptiveName() + ">";
|
||||
if (!IsVMValueValid(&m_variable))
|
||||
{
|
||||
variable.value = "<NULL>";
|
||||
}
|
||||
else if (pointedType->isScalar() && !pointedType->isPointer())
|
||||
{
|
||||
// TODO: TypeState?
|
||||
auto val = DerefValue(&m_variable, GetBasicType(pointedType));
|
||||
auto deref_var = ToVariable(val, pointedType, stateOwningClass);
|
||||
variable.value = StringFormat("%p {%s}", (m_variable.a), deref_var.value.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
// just display the address
|
||||
variable.value = StringFormat("%p", (m_variable.a));
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (m_type->isInt())
|
||||
{ // explicitly not TYPE_IntNotInt
|
||||
int64_t val = TruncateVMValue(&m_variable, basic_type).i;
|
||||
if (basic_type == BASIC_uint32 || basic_type == BASIC_uint16 || basic_type == BASIC_uint8){
|
||||
variable.value = StringFormat("%u", val);
|
||||
} else {
|
||||
variable.value = StringFormat("%d", val);
|
||||
}
|
||||
}
|
||||
else if (m_type->isFloat())
|
||||
{
|
||||
variable.value = StringFormat("%f", TruncateVMValue(&m_variable, basic_type).f);
|
||||
}
|
||||
else if (m_type->Flags & TT::TypeFlags::TYPE_IntNotInt)
|
||||
{
|
||||
// PBool
|
||||
// PName
|
||||
// PSpriteID
|
||||
// PTextureID
|
||||
// PTranslationID
|
||||
// PSound
|
||||
// PColor
|
||||
// PStateLabel
|
||||
// PEnum
|
||||
if (m_type == TypeBool)
|
||||
{
|
||||
variable.type = "bool";
|
||||
variable.value = m_variable.i ? "true" : "false";
|
||||
}
|
||||
else if (m_type == TypeName)
|
||||
{
|
||||
variable.type = "Name";
|
||||
auto name = FName((ENamedName)(m_variable.i));
|
||||
variable.value = StringFormat("Name# %d: \'%s\'", m_variable.i, name.GetChars());
|
||||
}
|
||||
else if (m_type == TypeSpriteID)
|
||||
{
|
||||
variable.type = "SpriteID";
|
||||
// TODO: Get the sprite name? how do they get the sprite name into the serializer?
|
||||
variable.value = StringFormat("SpriteID# %d", m_variable.i);
|
||||
}
|
||||
else if (m_type == TypeTextureID)
|
||||
{
|
||||
variable.type = "TextureID";
|
||||
int val = m_variable.i;
|
||||
int *val_ptr = &val;
|
||||
FTextureID textureID = *reinterpret_cast<FTextureID *>(val_ptr);
|
||||
FGameTexture *gameTexture = TexMan.GetGameTexture(textureID);
|
||||
const char *tex_name = gameTexture ? gameTexture->GetName().GetChars() : "<INVALID>";
|
||||
variable.value = StringFormat("TextureID# %d (%s)", m_variable.i, tex_name);
|
||||
}
|
||||
else if (m_type == TypeTranslationID)
|
||||
{
|
||||
variable.type = "TranslationID";
|
||||
FTranslationID translationID = FTranslationID::fromInt(m_variable.i);
|
||||
variable.value = StringFormat("TranslationID# %d {type: %d, index: %d}", m_variable.i, GetTranslationType(translationID), GetTranslationIndex(translationID));
|
||||
}
|
||||
else if (m_type == TypeSound)
|
||||
{
|
||||
variable.type = "Sound";
|
||||
const char *soundName = (m_variable.i > 0 && (uint32_t)m_variable.i < soundEngine->GetNumSounds()) ? soundEngine->GetSoundName(FSoundID::fromInt(m_variable.i)) : "<INVALID>";
|
||||
variable.value = StringFormat("Sound# %d (%s)", m_variable.i, soundName);
|
||||
}
|
||||
else if (m_type == TypeColor)
|
||||
{
|
||||
variable.type = "Color";
|
||||
int soundval = m_variable.i;
|
||||
|
||||
uint8_t a = (soundval >> 24) & 0xFF;
|
||||
uint8_t r = (soundval >> 16) & 0xFF;
|
||||
uint8_t g = (soundval >> 8) & 0xFF;
|
||||
uint8_t b = soundval & 0xFF;
|
||||
// hex format
|
||||
variable.value = StringFormat("Color #%08X (a: %d, r: %d, g: %d, b: %d)", soundval, a, r, g, b);
|
||||
}
|
||||
else if (m_type == TypeStateLabel)
|
||||
{
|
||||
variable.type = "StateLabel";
|
||||
std::string label_name = "<unknown>";
|
||||
const char *label = nullptr;
|
||||
auto NameVal = m_variable.i;
|
||||
FName StateName = NAME_None;
|
||||
PClassActor *actualOwner = nullptr;
|
||||
if (NameVal > 0)
|
||||
{
|
||||
if (NameVal > 0x10000000)
|
||||
{
|
||||
NameVal -= 0x10000000;
|
||||
}
|
||||
auto name = ENamedName(NameVal);
|
||||
StateName = FName(name);
|
||||
}
|
||||
auto state = GetStateFromIdx(m_variable.i, stateOwningClass, actualOwner);
|
||||
if (state)
|
||||
{
|
||||
if (actualOwner && NameVal < 0 && actualOwner->OwnsState(state))
|
||||
{
|
||||
int stateidx = state - actualOwner->GetStates();
|
||||
if (stateidx < actualOwner->GetStateLabels()->NumLabels)
|
||||
{
|
||||
StateName = actualOwner->GetStateLabels()->Labels[stateidx].Label;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (StateName != NAME_None)
|
||||
{
|
||||
const char *ownerName = actualOwner ? actualOwner->TypeName.GetChars() : "<unknownOwner>";
|
||||
label_name = StringFormat("%s.%s", ownerName, StateName.GetChars());
|
||||
}
|
||||
else
|
||||
{
|
||||
label_name = "<unknown_state>";
|
||||
}
|
||||
variable.value = StringFormat("StateLabel# %d: \'%s\'", m_variable.i, label_name.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
variable.type = m_type->DescriptiveName();
|
||||
// check if it begins with "Enum"
|
||||
if (ToLowerCopy(variable.type.value("")).find("enum") == 0)
|
||||
{
|
||||
auto enum_type = static_cast<PEnum *>(m_type);
|
||||
// TODO: How to get the enum value names?
|
||||
variable.value = StringFormat("%d", m_variable.i);
|
||||
}
|
||||
else
|
||||
{
|
||||
variable.value = StringFormat("%d", m_variable.i);
|
||||
}
|
||||
variable.type = "Enum";
|
||||
variable.value = StringFormat("%d", m_variable.i);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
variable.type = m_type->DescriptiveName();
|
||||
variable.value = "<ERROR?>";
|
||||
}
|
||||
return variable;
|
||||
}
|
||||
|
||||
bool ValueStateNode::SerializeToProtocol(dap::Variable &variable)
|
||||
{
|
||||
variable = ToVariable(m_variable, m_type, m_StateOwningClass);
|
||||
SetVariableName(variable);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
20
src/common/scripting/dap/Nodes/ValueStateNode.h
Normal file
20
src/common/scripting/dap/Nodes/ValueStateNode.h
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
#pragma once
|
||||
|
||||
#include <common/scripting/dap/GameInterfaces.h>
|
||||
|
||||
#include <dap/protocol.h>
|
||||
#include "StateNodeBase.h"
|
||||
|
||||
namespace DebugServer
|
||||
{
|
||||
class ValueStateNode : public StateNodeNamedVariable
|
||||
{
|
||||
const VMValue m_variable;
|
||||
PType *m_type;
|
||||
PClass *m_StateOwningClass = nullptr;
|
||||
public:
|
||||
ValueStateNode(std::string name, VMValue variable, PType *type, PClass *stateOwningClass = nullptr);
|
||||
bool SerializeToProtocol(dap::Variable &variable) override;
|
||||
static dap::Variable ToVariable(const VMValue &m_variable, PType *m_type, PClass *stateOwningClass = nullptr);
|
||||
};
|
||||
}
|
||||
949
src/common/scripting/dap/PexCache.cpp
Normal file
949
src/common/scripting/dap/PexCache.cpp
Normal file
|
|
@ -0,0 +1,949 @@
|
|||
#include "PexCache.h"
|
||||
#include "Utilities.h"
|
||||
#include "GameInterfaces.h"
|
||||
|
||||
#include <functional>
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <common/engine/filesystem.h>
|
||||
#include <zcc_parser.h>
|
||||
#include "resourcefile.h"
|
||||
#include "RuntimeState.h"
|
||||
|
||||
namespace DebugServer
|
||||
{
|
||||
|
||||
static void NormalizeArchivePath(std::string &path)
|
||||
{
|
||||
if (path.find(":") != std::string::npos)
|
||||
{
|
||||
path.erase(std::remove(path.begin(), path.end(), ':'), path.end());
|
||||
}
|
||||
}
|
||||
|
||||
bool PexCache::HasScript(const int scriptReference)
|
||||
{
|
||||
scripts_lock scriptLock(m_scriptsMutex);
|
||||
return m_scripts.find(scriptReference) != m_scripts.end();
|
||||
}
|
||||
bool PexCache::HasScript(const std::string &scriptName) { return HasScript(GetScriptReference(scriptName)); }
|
||||
|
||||
PexCache::BinaryPtr PexCache::GetCachedScript(const int ref)
|
||||
{
|
||||
scripts_lock scriptLock(m_scriptsMutex);
|
||||
const auto entry = m_scripts.find(ref);
|
||||
return entry != m_scripts.end() ? entry->second : nullptr;
|
||||
}
|
||||
|
||||
void PexCache::PrintOutAllLoadedScripts()
|
||||
{
|
||||
scripts_lock scriptLock(m_scriptsMutex);
|
||||
for (auto &script : m_scripts)
|
||||
{
|
||||
Printf("Loaded %d functions from script: %s", script.second->GetFunctionCount(), script.second->GetQualifiedPath().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
PexCache::BinaryPtr PexCache::GetScript(const dap::Source &source)
|
||||
{
|
||||
auto binary = GetCachedScript(GetSourceReference(source));
|
||||
if (binary)
|
||||
{
|
||||
return binary;
|
||||
}
|
||||
return GetScript(GetScriptWithQual(source.path.value(""), source.origin.value("")));
|
||||
}
|
||||
|
||||
|
||||
PexCache::BinaryPtr PexCache::makeEmptyBinary(const std::string &scriptPath, int lump)
|
||||
{
|
||||
auto binary = std::make_shared<Binary>();
|
||||
auto truncScriptPath = GetScriptPathNoQual(scriptPath);
|
||||
binary->lump = lump;
|
||||
int wadnum = fileSystem.GetFileContainer(binary->lump);
|
||||
binary->scriptName = FileSys::ExtractBaseName(truncScriptPath.c_str(), true);
|
||||
binary->unqualifiedScriptPath = truncScriptPath;
|
||||
// check for the archive name in the script path
|
||||
binary->archivePath = wadnum >= 0 ? fileSystem.GetResourceFileFullName(wadnum) : GetArchiveNameFromPath(scriptPath);
|
||||
binary->archiveName = wadnum >= 0 ? fileSystem.GetResourceFileName(wadnum) : binary->archivePath;
|
||||
NormalizeArchivePath(binary->archivePath);
|
||||
binary->scriptReference = GetScriptReference(binary->GetQualifiedPath());
|
||||
return binary;
|
||||
}
|
||||
|
||||
void PexCache::PopulateCodeMap(PexCache::BinaryPtr binary, Binary::FunctionCodeMap &functionCodeMap)
|
||||
{
|
||||
if (!binary)
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto qualPath = binary->GetQualifiedPath();
|
||||
int i = 0;
|
||||
for (auto &func : binary->functions)
|
||||
{
|
||||
i++;
|
||||
for (auto &variant : func.second->Variants)
|
||||
{
|
||||
auto scriptFunc = GetVMScriptFunction(variant.Implementation);
|
||||
if (!scriptFunc || IsFunctionAbstract(scriptFunc)) continue;
|
||||
Binary::FunctionCodeMap::range_type codeRange(scriptFunc->Code, scriptFunc->Code + scriptFunc->CodeSize, scriptFunc);
|
||||
functionCodeMap.insert(true, codeRange);
|
||||
}
|
||||
}
|
||||
for (auto &pair : binary->stateFunctions)
|
||||
{
|
||||
auto scriptFunc = GetVMScriptFunction(pair.second);
|
||||
if (!scriptFunc || IsFunctionAbstract(scriptFunc)) continue;
|
||||
Binary::FunctionCodeMap::range_type codeRange(scriptFunc->Code, scriptFunc->Code + scriptFunc->CodeSize, scriptFunc);
|
||||
functionCodeMap.insert(true, codeRange);
|
||||
}
|
||||
}
|
||||
|
||||
void PexCache::ScanAllScripts()
|
||||
{
|
||||
scripts_lock scriptLock(m_scriptsMutex);
|
||||
m_scripts.clear();
|
||||
m_globalCodeMap.clear();
|
||||
|
||||
ScanScriptsInContainer(-1, m_scripts);
|
||||
for (auto &bin : m_scripts)
|
||||
{
|
||||
PopulateCodeMap(bin.second, m_globalCodeMap);
|
||||
}
|
||||
#ifndef NDEBUG
|
||||
PrintOutAllLoadedScripts();
|
||||
#endif
|
||||
}
|
||||
|
||||
void PexCache::PopulateFromPaths(const std::map<std::string, int> &scripts, BinaryMap &p_scripts, bool clobber)
|
||||
{
|
||||
for (auto &script : scripts)
|
||||
{
|
||||
auto ref = GetScriptReference(script.first);
|
||||
if (clobber || p_scripts.find(ref) == p_scripts.end())
|
||||
{
|
||||
p_scripts[ref] = makeEmptyBinary(script.first, script.second);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void PexCache::ScanScriptsInContainer(int baselump, BinaryMap &p_scripts, const std::string &filter)
|
||||
{
|
||||
// TODO: Get md5 hash of script
|
||||
TArray<PNamespace *> namespaces;
|
||||
std::string filterPath = filter;
|
||||
std::vector<int> filterRefs;
|
||||
if (!filter.empty())
|
||||
{
|
||||
// get the archive name
|
||||
std::string namespaceName = GetArchiveNameFromPath(filter);
|
||||
if (!namespaceName.empty())
|
||||
{
|
||||
int containerLump = fileSystem.CheckIfResourceFileLoaded(namespaceName.c_str());
|
||||
if (containerLump == -1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
auto found = FindScripts(filter, baselump);
|
||||
if (found.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
for (auto &script : found)
|
||||
{
|
||||
filterRefs.push_back(GetScriptReference(script.first));
|
||||
}
|
||||
PopulateFromPaths(found, p_scripts, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto found = FindAllScripts(baselump);
|
||||
if (found.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
PopulateFromPaths(found, p_scripts, true);
|
||||
}
|
||||
auto addEmptyBinIfNotExists = [&](int ref, const char *scriptPath)
|
||||
{
|
||||
if (p_scripts.find(ref) == p_scripts.end())
|
||||
{
|
||||
p_scripts[ref] = makeEmptyBinary(scriptPath, GetScriptFileID(scriptPath));
|
||||
}
|
||||
};
|
||||
for (auto &func : VMFunction::AllFunctions){
|
||||
auto vmscriptfunc = GetVMScriptFunction(func);
|
||||
if (!vmscriptfunc){
|
||||
continue;
|
||||
}
|
||||
std::string source_name = vmscriptfunc->SourceFileName.GetChars();
|
||||
if (source_name.empty() && !IsFunctionAbstract(vmscriptfunc)){
|
||||
auto class_name = GetFunctionClassName(func);
|
||||
if (class_name.empty()){
|
||||
continue;
|
||||
}
|
||||
auto *cls = PClass::FindClass(class_name.c_str());
|
||||
if (!cls){
|
||||
continue;
|
||||
}
|
||||
source_name = cls->SourceLumpName.GetChars();
|
||||
}
|
||||
if (source_name.empty()){
|
||||
continue;
|
||||
}
|
||||
auto ref = GetScriptReference(source_name);
|
||||
if (!filterRefs.empty() && std::find(filterRefs.begin(), filterRefs.end(), ref) == filterRefs.end())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
PFunction * pfunc = GetFunctionSymbol(func);
|
||||
addEmptyBinIfNotExists(ref, vmscriptfunc->SourceFileName.GetChars());
|
||||
if (!pfunc && IsNonAbstractScriptFunction(func)){
|
||||
p_scripts[ref]->stateFunctions[vmscriptfunc->QualifiedName] = vmscriptfunc;
|
||||
} else if (pfunc) {
|
||||
std::string symbolName = pfunc->SymbolName.GetChars();
|
||||
for (auto &variant : pfunc->Variants)
|
||||
{
|
||||
if (variant.Implementation)
|
||||
{
|
||||
auto vmfunc = variant.Implementation;
|
||||
if (!IsNonAbstractScriptFunction(vmfunc))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
auto scriptFunc = static_cast<VMScriptFunction *>(vmfunc);
|
||||
// add to the function map
|
||||
p_scripts[ref]->functions[vmscriptfunc->QualifiedName] = pfunc;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto &bin : p_scripts)
|
||||
{
|
||||
bin.second->PopulateFunctionMaps();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
std::shared_ptr<Binary> PexCache::AddScript(const std::string &scriptPath)
|
||||
{
|
||||
scripts_lock scriptLock(m_scriptsMutex);
|
||||
std::shared_ptr<Binary> bin;
|
||||
|
||||
ScanScriptsInContainer(-1, m_scripts, scriptPath);
|
||||
bin = GetCachedScript(GetScriptReference(scriptPath));
|
||||
PopulateCodeMap(bin, m_globalCodeMap);
|
||||
|
||||
return bin;
|
||||
}
|
||||
|
||||
std::vector<VMFunction *> PexCache::GetFunctionsAtAddress(void *address)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(m_scriptsMutex);
|
||||
if (!address)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
std::stack<Binary::FunctionCodeMap::iterator> found;
|
||||
found = m_globalCodeMap.find_ranges(address);
|
||||
std::vector<VMFunction *> funcs;
|
||||
while (!found.empty())
|
||||
{
|
||||
auto func = found.top()->mapped();
|
||||
if (func)
|
||||
{
|
||||
funcs.push_back(func);
|
||||
}
|
||||
found.pop();
|
||||
}
|
||||
|
||||
return funcs;
|
||||
}
|
||||
std::shared_ptr<Binary> PexCache::GetScript(std::string fqsn)
|
||||
{
|
||||
uint32_t reference = GetScriptReference(fqsn);
|
||||
auto binary = GetCachedScript(reference);
|
||||
if (binary)
|
||||
{
|
||||
return binary;
|
||||
}
|
||||
return AddScript(fqsn);
|
||||
}
|
||||
|
||||
bool PexCache::GetSourceContent(const std::string &scriptPath, std::string &decompiledSource)
|
||||
{
|
||||
auto lump = GetScriptFileID(scriptPath);
|
||||
if (lump == -1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
auto size = fileSystem.FileLength(lump);
|
||||
if (size == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> buffer(size);
|
||||
// Godspeed, you magnificent bastard
|
||||
fileSystem.ReadFile(lump, buffer.data());
|
||||
// Check if the file is binary; just check the first 8000 bytes for 0s
|
||||
for (size_t i = 0; i < std::min((size_t)8000, (size_t)size); i++)
|
||||
{
|
||||
if (buffer[i] == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
decompiledSource = std::string(buffer.begin(), buffer.end());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PexCache::GetOrCacheSource(BinaryPtr binary, std::string &decompiledSource)
|
||||
{
|
||||
if (!binary)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!binary->cachedSourceCode.empty())
|
||||
{
|
||||
decompiledSource = binary->cachedSourceCode;
|
||||
return true;
|
||||
}
|
||||
{
|
||||
scripts_lock scriptLock(m_scriptsMutex);
|
||||
|
||||
if (binary->cachedSourceCode.empty() && !GetSourceContent(binary->GetQualifiedPath(), binary->cachedSourceCode))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
decompiledSource = binary->cachedSourceCode;
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
bool PexCache::GetDecompiledSource(const dap::Source &source, std::string &decompiledSource)
|
||||
{
|
||||
return GetOrCacheSource(this->GetScript(source), decompiledSource);
|
||||
}
|
||||
|
||||
bool PexCache::GetDecompiledSource(const std::string &fqpn, std::string &decompiledSource)
|
||||
{
|
||||
return GetOrCacheSource(this->GetScript(fqpn), decompiledSource);
|
||||
}
|
||||
|
||||
bool PexCache::GetSourceData(const std::string &scriptName, dap::Source &data)
|
||||
{
|
||||
auto binary = GetScript(scriptName);
|
||||
if (!binary)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
data = binary->GetDapSource();
|
||||
return true;
|
||||
}
|
||||
|
||||
void PexCache::Clear()
|
||||
{
|
||||
scripts_lock scriptLock(m_scriptsMutex);
|
||||
m_scripts.clear();
|
||||
m_globalCodeMap.clear();
|
||||
m_disassemblyMap.clear();
|
||||
}
|
||||
|
||||
dap::ResponseOrError<dap::LoadedSourcesResponse> PexCache::GetLoadedSources(const dap::LoadedSourcesRequest &request)
|
||||
{
|
||||
dap::LoadedSourcesResponse response;
|
||||
ScanAllScripts();
|
||||
scripts_lock scriptLock(m_scriptsMutex);
|
||||
for (auto &bin : m_scripts)
|
||||
{
|
||||
response.sources.push_back(bin.second->GetDapSource());
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
inline bool LineIsFunctionDeclaration(const std::string &line, const std::string &function_name)
|
||||
{
|
||||
std::string new_line = line;
|
||||
new_line.erase(0, new_line.find_first_not_of(" \t\n"));
|
||||
new_line.erase(new_line.find_last_not_of(" \t\n") + 1);
|
||||
|
||||
auto func_name_pos = new_line.find(function_name);
|
||||
if (func_name_pos == std::string::npos)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// hack for deprecated annotated functions
|
||||
if (new_line.find("deprecated(") != std::string::npos)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
auto func_name_end = func_name_pos + function_name.size();
|
||||
if (func_name_end >= line.size())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// trim the whitespace of the line
|
||||
// if there's no whitespace in the line now, it's not a function declaration
|
||||
if (new_line.find_first_of(" \t\n") == std::string::npos)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
auto open_paren_pos = new_line.find_first_not_of(" \t\n", func_name_end);
|
||||
if (open_paren_pos == std::string::npos || new_line[open_paren_pos] != '(')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (new_line.find("super.") != std::string::npos)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO: rely on compiler information somehow instead of this
|
||||
// find the LINE that the function declaration starts on, lines starting at 1
|
||||
int PexCache::FindFunctionDeclaration(const std::shared_ptr<Binary> &source, const VMScriptFunction *func, int start_line_from_1)
|
||||
{
|
||||
|
||||
std::string source_code;
|
||||
if (!GetOrCacheSource(source, source_code))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
// convert source_code to lowercase
|
||||
std::transform(source_code.begin(), source_code.end(), source_code.begin(), ::tolower);
|
||||
std::string function_name = func->Name.GetChars();
|
||||
std::transform(function_name.begin(), function_name.end(), function_name.begin(), ::tolower);
|
||||
|
||||
auto lines = Split(source_code, "\n");
|
||||
auto func_decl_line = source->GetFunctionLineRange(func).first;
|
||||
// void funcs have a minimum line that is equal to the func decl line because the hidden return is mapped to it
|
||||
if (func_decl_line - 1 > 0 && lines[func_decl_line - 1].find(function_name) != std::string::npos && lines[func_decl_line - 1].find("void") != std::string::npos)
|
||||
{
|
||||
return func_decl_line;
|
||||
}
|
||||
|
||||
if (start_line_from_1 == 0)
|
||||
{
|
||||
start_line_from_1 = lines.size() - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
start_line_from_1 = start_line_from_1 - 1; //std::min(start_line_from_1, std::min(line_range_from_1.second, lines.size())) - 1;
|
||||
}
|
||||
for (int i = start_line_from_1; i >= 0; i--)
|
||||
{
|
||||
auto &line = lines[i];
|
||||
// find the line that contains the function name
|
||||
auto func_name_pos = line.find(function_name);
|
||||
if (func_name_pos != std::string::npos && LineIsFunctionDeclaration(lines[i], function_name))
|
||||
{
|
||||
|
||||
return i + 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::shared_ptr<DisassemblyLine>
|
||||
PexCache::MakeInstruction(VMScriptFunction *func, int ref, const std::string &instruction_text, const std::string &opcode, const std::string &comment, unsigned long long ipnum, const std::string &pointed_symbol)
|
||||
{
|
||||
std::shared_ptr<DisassemblyLine> instruction = std::make_shared<DisassemblyLine>();
|
||||
instruction->funcPtr = func;
|
||||
instruction->function = func->QualifiedName;
|
||||
instruction->instruction = instruction_text;
|
||||
instruction->address = (void *)ipnum;
|
||||
instruction->bytes = opcode;
|
||||
instruction->comment = comment;
|
||||
instruction->ref = ref;
|
||||
instruction->line = func->PCToLine((const VMOP *)instruction->address);
|
||||
instruction->is_valid_bp = true;
|
||||
if (instruction->line < 0)
|
||||
{
|
||||
// find the max line number
|
||||
int max_line = 0;
|
||||
for (size_t li = 0; li < func->LineInfoCount; ++li)
|
||||
{
|
||||
if (func->LineInfo[li].LineNumber > max_line)
|
||||
{
|
||||
max_line = func->LineInfo[li].LineNumber;
|
||||
}
|
||||
}
|
||||
instruction->line = max_line + 1;
|
||||
}
|
||||
instruction->endLine = instruction->line;
|
||||
instruction->pointed_symbol = pointed_symbol;
|
||||
return instruction;
|
||||
}
|
||||
|
||||
std::vector<dap::Module> PexCache::GetModules()
|
||||
{
|
||||
std::vector<dap::Module> modules;
|
||||
int count = fileSystem.GetNumWads();
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
dap::Module module;
|
||||
module.id = dap::integer(i);
|
||||
std::string name = fileSystem.GetResourceFileName(i);
|
||||
std::string path = fileSystem.GetResourceFileFullName(i);
|
||||
NormalizeArchivePath(name);
|
||||
module.name = name;
|
||||
module.path = path;
|
||||
modules.push_back(module);
|
||||
}
|
||||
return modules;
|
||||
}
|
||||
|
||||
uint64_t PexCache::AddDisassemblyLines(VMScriptFunction *func, DisassemblyMap &instructions)
|
||||
{
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
// TODO: add a windows-compatible fmemopen
|
||||
return 0;
|
||||
#else
|
||||
if (!func || IsFunctionAbstract(func) || IsFunctionNative(func))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
// we need to create a temporary FILE* to pass to Disassemble
|
||||
// we can't use a string because Disassemble expects a FILE*
|
||||
// assume 256 bytes per instruction
|
||||
size_t buf_size = func->CodeSize * 256;
|
||||
std::vector<uint8_t> buffer(buf_size);
|
||||
FILE *f = fmemopen(buffer.data(), buf_size, "w");
|
||||
if (!f)
|
||||
{
|
||||
LogError("Failed to create a temporary file for disassembly");
|
||||
return 0;
|
||||
}
|
||||
auto ref = GetScriptReference(func->SourceFileName.GetChars());
|
||||
auto startPointer = func->Code;
|
||||
auto endPointer = func->Code + func->CodeSize;
|
||||
auto currCodePointer = func->Code;
|
||||
|
||||
VMDisasm(f, func->Code, func->CodeSize, func, (uint64_t)func->Code);
|
||||
// close the file
|
||||
fclose(f);
|
||||
|
||||
// now we can read the disassembled code from CodeBytes
|
||||
std::string disassembly = std::string(buffer.begin(), std::find(buffer.begin(), buffer.end(), 0));
|
||||
// split it into lines
|
||||
auto lines = Split(disassembly, "\n");
|
||||
|
||||
|
||||
auto ret = m_disassemblyMap.insert(true, {startPointer, endPointer, {}});
|
||||
if (!ret.second)
|
||||
{
|
||||
if (!(ret.first->start_pt() == startPointer && ret.first->end_pt() == endPointer))
|
||||
{
|
||||
LogError("Failed to insert the disassembly lines into the map");
|
||||
return 0;
|
||||
}
|
||||
// else, just use the already existing one but clear it
|
||||
ret.first->mapped().clear();
|
||||
}
|
||||
auto &lines_map = ret.first->mapped();
|
||||
// check if the last line in lines is empty; if so, remove it
|
||||
std::vector<size_t> lines_to_remove;
|
||||
auto script_name = func->SourceFileName.GetChars();
|
||||
auto source = GetScript(script_name);
|
||||
int min_line = INT_MAX;
|
||||
|
||||
for (size_t i = 0; i < lines.size(); i++)
|
||||
{
|
||||
auto &line = lines[i];
|
||||
if (line.empty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
auto comment_pos = line.find(';');
|
||||
if (comment_pos == std::string::npos)
|
||||
{
|
||||
// there was a string literal with a newline in it, so we need to check the next line(s) for a comment
|
||||
for (size_t j = i + 1; j < lines.size(); j++)
|
||||
{
|
||||
auto &next_line = lines[j];
|
||||
line += "\n" + next_line;
|
||||
auto next_comment_pos = next_line.find(';');
|
||||
next_line = "";
|
||||
if (next_comment_pos != std::string::npos)
|
||||
{
|
||||
comment_pos = line.find(';');
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (comment_pos == std::string::npos)
|
||||
{
|
||||
LogError("!!!!!!Disassembly line %d has no comment!!!!!", i);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (line.size() < 19)
|
||||
{
|
||||
LogError("!!!!!!Disassembly line %d too short!!!!!", i);
|
||||
continue;
|
||||
}
|
||||
// lines go like this:
|
||||
// ip opcode op arg1, arg2, arg3 ;arg1,arg2,arg3 {[resolved symbol]}(optional)
|
||||
// 00000464: 611e0201 call [0x1319fc620],2,1 ;30,2,1 [ZTBotController.PickTeam]
|
||||
//
|
||||
// we want to extract the ip, the opcode, and the op
|
||||
// we also want to remove the ip and the opcode from the line
|
||||
auto col_pos = line.find(':');
|
||||
auto ipStr = line.substr(0, col_pos);
|
||||
auto opcode = line.substr(col_pos + 2, 8);
|
||||
auto op = line.substr(col_pos + 11, 8);
|
||||
op.erase(std::remove(op.begin(), op.end(), ' '), op.end());
|
||||
|
||||
auto inst_str = line.substr(col_pos + 11, comment_pos - col_pos - 11);
|
||||
// trim the whitespace from inst_str
|
||||
inst_str.erase(std::remove(inst_str.begin(), inst_str.end(), ' '), inst_str.end());
|
||||
std::string comment;
|
||||
comment = line.substr(comment_pos + 1);
|
||||
// get the resolved_symbol if it exists
|
||||
std::string resolved_symbol;
|
||||
if (!comment.empty())
|
||||
{
|
||||
// find the first open bracket in the comment
|
||||
auto open_bracket = comment.find('[');
|
||||
if (open_bracket != std::string::npos)
|
||||
{
|
||||
// find the last close bracket
|
||||
auto close_bracket = comment.find_last_of(']');
|
||||
if (close_bracket != std::string::npos)
|
||||
{
|
||||
resolved_symbol = comment.substr(open_bracket + 1, close_bracket - open_bracket - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
auto ipnum = std::stoull(ipStr, nullptr, 16);
|
||||
|
||||
auto instruction = std::make_shared<DisassemblyLine>();
|
||||
instruction = MakeInstruction(func, ref, line.substr(col_pos + 11), opcode, comment, ipnum, resolved_symbol);
|
||||
if (instruction->line > -1)
|
||||
{
|
||||
min_line = std::min(min_line, instruction->line);
|
||||
}
|
||||
else
|
||||
{
|
||||
int j = 0;
|
||||
}
|
||||
// The reason for this is that the disassembler decodes a cmp and then {jne,je,etc} as a single {bne,be,etc} instruction
|
||||
// We want the instructions to always be 4 bytes long, so we add a dummy instruction if the instruction starts with "b"
|
||||
if (instruction->instruction.front() == 'b')
|
||||
{
|
||||
if (instruction->instruction[1] == 'n' || instruction->instruction[1] == 'e' || instruction->instruction[1] == 'l' || instruction->instruction[1] == 'g')
|
||||
{
|
||||
// instruction->bytesize = 8;
|
||||
// currCodePointer++;
|
||||
// instruction->bytes += StringFormat("%02X%02X%02X%02X", currCodePointer->op, currCodePointer->a, currCodePointer->b, currCodePointer->c);
|
||||
|
||||
lines_map.insert({(void *)currCodePointer, instruction});
|
||||
currCodePointer++;
|
||||
|
||||
instruction = MakeInstruction(
|
||||
func, ref, "--", StringFormat("%02X%02X%02X%02X", currCodePointer->op, currCodePointer->a, currCodePointer->b, currCodePointer->c), StringFormat("; jmp %08X", currCodePointer->i24), ipnum + 4, resolved_symbol);
|
||||
min_line = std::min(min_line, instruction->line);
|
||||
if (instruction->line > -1)
|
||||
{
|
||||
min_line = std::min(min_line, instruction->line);
|
||||
}
|
||||
else
|
||||
{
|
||||
int j = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
lines_map.insert({(void *)currCodePointer, instruction});
|
||||
currCodePointer++;
|
||||
}
|
||||
bool hit_min_line = false;
|
||||
bool after_min_line = false;
|
||||
auto func_decl_line = FindFunctionDeclaration(source, func, min_line);
|
||||
if (func_decl_line > 0)
|
||||
{
|
||||
for (auto &pr : lines_map)
|
||||
{
|
||||
auto &instruction = pr.second;
|
||||
// the objective is to show the function declaration line in the assembly; but we only want to show it for the first set of instructions that map to the min line
|
||||
if (!after_min_line && instruction->line == min_line)
|
||||
{
|
||||
hit_min_line = true;
|
||||
instruction->line = func_decl_line;
|
||||
}
|
||||
else if (hit_min_line == true)
|
||||
{
|
||||
after_min_line = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return instructions.size();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
bool PexCache::GetDisassemblyLines(const VMOP *address, int64_t p_instructionOffset, int64_t p_count, std::vector<std::shared_ptr<DisassemblyLine>> &lines_vec)
|
||||
{
|
||||
scripts_lock scriptLock(m_scriptsMutex);
|
||||
if (!address)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
auto ret = m_globalCodeMap.find_ranges((void *)address);
|
||||
int64_t extra = 0;
|
||||
if (ret.empty())
|
||||
{
|
||||
// back up until we find a valid address
|
||||
if (p_instructionOffset < 0)
|
||||
{
|
||||
int64_t i = 1;
|
||||
ret = m_globalCodeMap.find_ranges((void *)(address - i));
|
||||
while (ret.empty())
|
||||
{
|
||||
i++;
|
||||
ret = m_globalCodeMap.find_ranges((void *)(address - i));
|
||||
if (i > std::abs(p_instructionOffset) + p_count)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
extra = i;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto it = m_globalCodeMap.lower_bound((void *)address);
|
||||
if (it == m_globalCodeMap.end())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
ret.push(it);
|
||||
// get the difference between the start point and the address
|
||||
extra = address - (VMOP *)it->start_pt();
|
||||
}
|
||||
}
|
||||
auto &it = ret.top();
|
||||
Binary::FunctionCodeMap::iterator reverse_it = ret.top();
|
||||
std::map<void *, std::shared_ptr<DisassemblyLine>> instruction_map;
|
||||
auto firstFunc = it->mapped();
|
||||
auto firstFuncInstcount = firstFunc->CodeSize;
|
||||
|
||||
auto addToInstMap = [&](const std::map<void *, std::shared_ptr<DisassemblyLine>> &lines)
|
||||
{
|
||||
size_t added = 0;
|
||||
for (auto &pr : lines)
|
||||
{
|
||||
auto &line = pr.second;
|
||||
auto result = instruction_map.insert({line->address, line});
|
||||
if (result.second)
|
||||
{
|
||||
added++;
|
||||
}
|
||||
}
|
||||
return added;
|
||||
};
|
||||
{
|
||||
int64_t instructionOffset = p_instructionOffset;
|
||||
int64_t count = p_count + std::abs(p_instructionOffset) + firstFuncInstcount + extra;
|
||||
// if the offset is negative, we get the previous instructions
|
||||
if (instructionOffset < 0)
|
||||
{
|
||||
// get the difference between instructionOffset and 0 (+ the first func count to the requested address)
|
||||
int64_t instructionsToGet = count;
|
||||
// keep going backwards until we find enough instructions to fill the request
|
||||
auto &prev_it = reverse_it;
|
||||
while (reverse_it != m_globalCodeMap.end())
|
||||
{
|
||||
auto found = m_disassemblyMap.find_ranges(reverse_it->start_pt());
|
||||
if (found.empty())
|
||||
{
|
||||
AddDisassemblyLines(reverse_it->mapped(), m_disassemblyMap);
|
||||
found = m_disassemblyMap.find_ranges(reverse_it->start_pt());
|
||||
}
|
||||
auto &found_lines = found.top()->mapped();
|
||||
instructionsToGet -= addToInstMap(found_lines);
|
||||
|
||||
if (instructionsToGet <= 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
--reverse_it;
|
||||
}
|
||||
if (std::abs(std::abs(instructionOffset) - count) > 0)
|
||||
{
|
||||
instructionOffset = 0;
|
||||
}
|
||||
}
|
||||
// forward...
|
||||
if (instructionOffset >= 0)
|
||||
{
|
||||
int64_t instructionsToGet = count;
|
||||
|
||||
while (it != m_globalCodeMap.end())
|
||||
{
|
||||
auto found = m_disassemblyMap.find_ranges(it->start_pt());
|
||||
if (found.empty())
|
||||
{
|
||||
AddDisassemblyLines(it->mapped(), m_disassemblyMap);
|
||||
found = m_disassemblyMap.find_ranges(it->start_pt());
|
||||
}
|
||||
auto &found_lines = found.top()->mapped();
|
||||
auto mapped = it->mapped();
|
||||
instructionsToGet -= addToInstMap(found_lines);
|
||||
|
||||
if (instructionsToGet <= 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
auto actualaddress = (void *)((VMOP *)address + p_instructionOffset);
|
||||
for (int64_t i = 0; i < p_count; i++)
|
||||
{
|
||||
auto curaddr = (VMOP *)actualaddress + i;
|
||||
auto found = instruction_map.find(curaddr);
|
||||
if (found != instruction_map.end())
|
||||
{
|
||||
lines_vec.push_back(found->second);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto invalid_inst = lines_vec.emplace_back(std::make_shared<DisassemblyLine>());
|
||||
invalid_inst->is_valid_bp = false;
|
||||
invalid_inst->address = curaddr;
|
||||
invalid_inst->instruction = "<INVALID>";
|
||||
invalid_inst->bytes = "<INVALID>";
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
std::string DebugServer::Binary::GetQualifiedPath() const { return archiveName + ":" + unqualifiedScriptPath; }
|
||||
std::string DebugServer::Binary::GetArchiveName() const { return archiveName; }
|
||||
std::string DebugServer::Binary::GetArchivePath() const
|
||||
{
|
||||
return archivePath;
|
||||
}
|
||||
|
||||
size_t DebugServer::Binary::GetFunctionCount() const
|
||||
{
|
||||
return functions.size() + stateFunctions.size();
|
||||
}
|
||||
|
||||
std::stack<DebugServer::Binary::FunctionLineMap::const_iterator> DebugServer::Binary::FindFunctionRangesByLine(int line) const { return functionLineMap.find_ranges(line); }
|
||||
|
||||
std::stack<beneficii::range_map<void *, VMScriptFunction *>::const_iterator> DebugServer::Binary::FindFunctionRangesByCode(void *address) const
|
||||
{
|
||||
return functionCodeMap.find_ranges(address);
|
||||
}
|
||||
|
||||
bool DebugServer::Binary::HasFunctions() const
|
||||
{
|
||||
return !functions.empty() || !functionCodeMap.empty();
|
||||
}
|
||||
|
||||
bool DebugServer::Binary::HasFunctionLines() const
|
||||
{
|
||||
return !functionLineMap.empty();
|
||||
}
|
||||
|
||||
void DebugServer::Binary::ProcessScriptFunction(const std::string &qualPath, VMFunction *vmfunc)
|
||||
{
|
||||
if (IsFunctionNative(vmfunc) || IsFunctionAbstract(vmfunc))
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto scriptFunc = static_cast<VMScriptFunction *>(vmfunc);
|
||||
if (!CaseInsensitiveEquals(scriptFunc->SourceFileName.GetChars(), qualPath))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t firstLine = INT_MAX;
|
||||
uint32_t lastLine = 0;
|
||||
|
||||
for (uint32_t i = 0; i < scriptFunc->LineInfoCount; ++i)
|
||||
{
|
||||
if (scriptFunc->LineInfo[i].LineNumber < firstLine)
|
||||
{
|
||||
firstLine = scriptFunc->LineInfo[i].LineNumber;
|
||||
}
|
||||
if (scriptFunc->LineInfo[i].LineNumber > lastLine)
|
||||
{
|
||||
lastLine = scriptFunc->LineInfo[i].LineNumber;
|
||||
}
|
||||
}
|
||||
|
||||
FunctionLineMap::range_type range(firstLine, lastLine + 1, scriptFunc);
|
||||
auto ret = functionLineMap.insert(true, range);
|
||||
if (ret.second == false)
|
||||
{
|
||||
// Probably a mixin, just continue
|
||||
return;
|
||||
}
|
||||
void *code = scriptFunc->Code;
|
||||
void *end = scriptFunc->Code + scriptFunc->CodeSize;
|
||||
FunctionCodeMap::range_type codeRange(code, end, scriptFunc);
|
||||
functionCodeMap.insert(true, codeRange);
|
||||
return;
|
||||
}
|
||||
|
||||
void DebugServer::Binary::PopulateFunctionMaps()
|
||||
{
|
||||
functionLineMap.clear();
|
||||
functionCodeMap.clear();
|
||||
auto qualPath = GetQualifiedPath();
|
||||
int i = 0;
|
||||
for (auto &func : functions)
|
||||
{
|
||||
i++;
|
||||
for (auto &variant : func.second->Variants)
|
||||
{
|
||||
|
||||
auto vmfunc = variant.Implementation;
|
||||
ProcessScriptFunction(qualPath, vmfunc);
|
||||
}
|
||||
}
|
||||
for (auto &pair : stateFunctions)
|
||||
{
|
||||
auto vmfunc = pair.second;
|
||||
ProcessScriptFunction(qualPath, vmfunc);
|
||||
}
|
||||
}
|
||||
|
||||
dap::Source DebugServer::Binary::GetDapSource() const
|
||||
{
|
||||
dap::Source source;
|
||||
source.name = scriptName;
|
||||
source.origin = archiveName;
|
||||
source.path = unqualifiedScriptPath;
|
||||
source.sourceReference = scriptReference;
|
||||
source.adapterData = archivePath;
|
||||
return source;
|
||||
}
|
||||
|
||||
std::pair<int, int> DebugServer::Binary::GetFunctionLineRange(const VMScriptFunction *func) const
|
||||
{
|
||||
if (!func)
|
||||
{
|
||||
return {0, 0};
|
||||
}
|
||||
for (auto &pair : functionLineMap)
|
||||
{
|
||||
if (pair.mapped() == func)
|
||||
{
|
||||
return {pair.range().get_left(), pair.range().get_right()};
|
||||
}
|
||||
}
|
||||
return {0, 0};
|
||||
}
|
||||
135
src/common/scripting/dap/PexCache.h
Normal file
135
src/common/scripting/dap/PexCache.h
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
#pragma once
|
||||
|
||||
#include <map>
|
||||
|
||||
#include <dap/protocol.h>
|
||||
#include <dap/session.h>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include "Utilities.h"
|
||||
#include <range_map/range_map.h>
|
||||
#include <name.h>
|
||||
#include <shared_mutex>
|
||||
#include <vmintern.h>
|
||||
|
||||
class PFunction;
|
||||
class PClassType;
|
||||
class PStruct;
|
||||
class VMFunction;
|
||||
class VMScriptFunction;
|
||||
|
||||
// TODO: we may not want to do it like this, but this is the easiest way to get the opinfo.
|
||||
extern const VMOpInfo OpInfo[NUM_OPS];
|
||||
|
||||
namespace DebugServer
|
||||
{
|
||||
class PexCache;
|
||||
struct Binary
|
||||
{
|
||||
using NameStateFunctionMap = std::map<std::string, VMFunction *>;
|
||||
using NameFunctionMap = std::map<FName, PFunction *>;
|
||||
using NameClassMap = std::map<FName, PClassType *>;
|
||||
using NameStructMap = std::map<FName, PStruct *>;
|
||||
using FunctionLineMap = beneficii::range_map<uint32_t, VMScriptFunction *, std::less<uint32_t>, std::allocator<beneficii::range_map_item<uint32_t, VMScriptFunction *>>, true>;
|
||||
using FunctionCodeMap = beneficii::range_map<void *, VMScriptFunction *>;
|
||||
|
||||
private:
|
||||
friend class PexCache;
|
||||
std::string archiveName;
|
||||
std::string archivePath;
|
||||
std::string scriptName;
|
||||
std::string unqualifiedScriptPath;
|
||||
std::string compiledPath;
|
||||
std::string cachedSourceCode;
|
||||
int lump;
|
||||
int scriptReference;
|
||||
NameFunctionMap functions;
|
||||
FunctionLineMap functionLineMap;
|
||||
FunctionCodeMap functionCodeMap;
|
||||
NameStateFunctionMap stateFunctions;
|
||||
void PopulateFunctionMaps();
|
||||
|
||||
public:
|
||||
std::pair<int, int> GetFunctionLineRange(const VMScriptFunction *functionName) const;
|
||||
std::string GetQualifiedPath() const;
|
||||
dap::Source GetDapSource() const;
|
||||
std::string GetArchiveName() const;
|
||||
std::string GetArchivePath() const;
|
||||
size_t GetFunctionCount() const;
|
||||
std::stack<FunctionLineMap::const_iterator> FindFunctionRangesByLine(int line) const;
|
||||
std::stack<FunctionCodeMap::const_iterator> FindFunctionRangesByCode(void *address) const;
|
||||
int GetScriptRef() const { return scriptReference; }
|
||||
bool HasFunctions() const;
|
||||
bool HasFunctionLines() const;
|
||||
void ProcessScriptFunction(const std::string &qualPath, VMFunction *vmfunc);
|
||||
};
|
||||
struct DisassemblyLine
|
||||
{
|
||||
void *address;
|
||||
int line = -1;
|
||||
int endLine = -1;
|
||||
int ref = -1;
|
||||
uint8_t bytesize = 4;
|
||||
bool is_valid_bp = false;
|
||||
std::string bytes;
|
||||
std::string instruction;
|
||||
std::string comment;
|
||||
std::string pointed_symbol;
|
||||
std::string function;
|
||||
VMFunction *funcPtr;
|
||||
};
|
||||
|
||||
|
||||
class PexCache
|
||||
{
|
||||
public:
|
||||
using BinaryPtr = std::shared_ptr<Binary>;
|
||||
using BinaryMap = std::map<int, BinaryPtr>;
|
||||
using DisassemblyLinePtr = std::shared_ptr<DisassemblyLine>;
|
||||
using DisassemblyMap = beneficii::range_map<void *, std::map<void *, DisassemblyLinePtr>>;
|
||||
PexCache() = default;
|
||||
bool HasScript(int scriptReference);
|
||||
bool HasScript(const std::string &scriptName);
|
||||
|
||||
std::shared_ptr<Binary> GetCachedScript(const int ref);
|
||||
void PrintOutAllLoadedScripts();
|
||||
std::shared_ptr<Binary> GetScript(const dap::Source &source);
|
||||
|
||||
std::shared_ptr<Binary> GetScript(std::string fqsn);
|
||||
bool GetDecompiledSource(const dap::Source &source, std::string &decompiledSource);
|
||||
|
||||
bool GetDecompiledSource(const std::string &fqpn, std::string &decompiledSource);
|
||||
bool GetSourceData(const std::string &scriptName, dap::Source &data);
|
||||
void Clear();
|
||||
void ScanAllScripts();
|
||||
dap::ResponseOrError<dap::LoadedSourcesResponse> GetLoadedSources(const dap::LoadedSourcesRequest &request);
|
||||
|
||||
static std::shared_ptr<DisassemblyLine>
|
||||
MakeInstruction(VMScriptFunction *func, int ref, const std::string &instruction_text, const std::string &opcode, const std::string &comment, unsigned long long ipnum, const std::string &pointed_symbol);
|
||||
|
||||
bool GetDisassemblyLines(const VMOP *address, int64_t p_instructionOffset, int64_t p_count, std::vector<std::shared_ptr<DisassemblyLine>> &lines_vec);
|
||||
|
||||
std::shared_ptr<Binary> AddScript(const std::string &scriptPath);
|
||||
std::vector<VMFunction *> GetFunctionsAtAddress(void *address);
|
||||
|
||||
std::vector<dap::Module> GetModules();
|
||||
private:
|
||||
using scripts_lock = std::scoped_lock<std::recursive_mutex>;
|
||||
|
||||
int FindFunctionDeclaration(const std::shared_ptr<Binary> &source, const VMScriptFunction *func, int start_line_from_1);
|
||||
bool GetOrCacheSource(BinaryPtr binary, std::string &decompiledSource);
|
||||
uint64_t AddDisassemblyLines(VMScriptFunction *func, DisassemblyMap &instructions);
|
||||
static bool GetSourceContent(const std::string &scriptPath, std::string &decompiledSource);
|
||||
|
||||
static void PopulateCodeMap(BinaryPtr binary, Binary::FunctionCodeMap &functionCodeMap);
|
||||
static void PopulateFromPaths(const std::map<std::string, int> &scripts, BinaryMap &p_scripts, bool clobber = false);
|
||||
static void ScanScriptsInContainer(int baselump, BinaryMap &m_scripts, const std::string &filter = "");
|
||||
static BinaryPtr makeEmptyBinary(const std::string &scriptPath, int lump);
|
||||
|
||||
DisassemblyMap m_disassemblyMap;
|
||||
Binary::FunctionCodeMap m_globalCodeMap;
|
||||
std::recursive_mutex m_scriptsMutex;
|
||||
BinaryMap m_scripts;
|
||||
|
||||
};
|
||||
}
|
||||
77
src/common/scripting/dap/Protocol/debugger.h
Normal file
77
src/common/scripting/dap/Protocol/debugger.h
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <dap/protocol.h>
|
||||
#include <dap/session.h>
|
||||
|
||||
namespace dap
|
||||
{
|
||||
class Debugger
|
||||
{
|
||||
public:
|
||||
enum StepType
|
||||
{
|
||||
STEP_IN = 0,
|
||||
STEP_OVER,
|
||||
STEP_OUT
|
||||
};
|
||||
enum VariablesFilter
|
||||
{
|
||||
VariablesNamed,
|
||||
VariablesIndexed,
|
||||
VariablesBoth
|
||||
};
|
||||
enum DisconnectAction
|
||||
{
|
||||
DisconnectDefault, // Attach -> Detach, Launch -> Terminate
|
||||
DisconnectTerminate,
|
||||
DisconnectDetach
|
||||
};
|
||||
|
||||
virtual ~Debugger() { }
|
||||
|
||||
virtual ResponseOrError<AttachResponse> Attach(const AttachRequest &request) = 0;
|
||||
virtual ResponseOrError<BreakpointLocationsResponse> BreakpointLocations(const BreakpointLocationsRequest &request) = 0;
|
||||
virtual ResponseOrError<CompletionsResponse> Completions(const CompletionsRequest &request) = 0;
|
||||
virtual ResponseOrError<ConfigurationDoneResponse> ConfigurationDone(const ConfigurationDoneRequest &request) = 0;
|
||||
virtual ResponseOrError<ContinueResponse> Continue(const ContinueRequest &request) = 0;
|
||||
virtual ResponseOrError<DataBreakpointInfoResponse> DataBreakpointInfo(const DataBreakpointInfoRequest &request) = 0;
|
||||
virtual ResponseOrError<DisassembleResponse> Disassemble(const DisassembleRequest &request) = 0;
|
||||
virtual ResponseOrError<DisconnectResponse> Disconnect(const DisconnectRequest &request) = 0;
|
||||
virtual ResponseOrError<EvaluateResponse> Evaluate(const EvaluateRequest &request) = 0;
|
||||
virtual ResponseOrError<ExceptionInfoResponse> ExceptionInfo(const ExceptionInfoRequest &request) = 0;
|
||||
virtual ResponseOrError<GotoResponse> Goto(const GotoRequest &request) = 0;
|
||||
virtual ResponseOrError<GotoTargetsResponse> GotoTargets(const GotoTargetsRequest &request) = 0;
|
||||
virtual ResponseOrError<InitializeResponse> Initialize(const InitializeRequest &request) = 0;
|
||||
virtual ResponseOrError<LaunchResponse> Launch(const LaunchRequest &request) = 0;
|
||||
virtual ResponseOrError<LoadedSourcesResponse> LoadedSources(const LoadedSourcesRequest &request) = 0;
|
||||
virtual ResponseOrError<ModulesResponse> Modules(const ModulesRequest &request) = 0;
|
||||
virtual ResponseOrError<NextResponse> Next(const NextRequest &request) = 0;
|
||||
virtual ResponseOrError<PauseResponse> Pause(const PauseRequest &request) = 0;
|
||||
virtual ResponseOrError<ReadMemoryResponse> ReadMemory(const ReadMemoryRequest &request) = 0;
|
||||
virtual ResponseOrError<RestartResponse> Restart(const RestartRequest &request) = 0;
|
||||
virtual ResponseOrError<RestartFrameResponse> RestartFrame(const RestartFrameRequest &request) = 0;
|
||||
virtual ResponseOrError<ReverseContinueResponse> ReverseContinue(const ReverseContinueRequest &request) = 0;
|
||||
virtual ResponseOrError<ScopesResponse> Scopes(const ScopesRequest &request) = 0;
|
||||
virtual ResponseOrError<SetBreakpointsResponse> SetBreakpoints(const SetBreakpointsRequest &request) = 0;
|
||||
virtual ResponseOrError<SetDataBreakpointsResponse> SetDataBreakpoints(const SetDataBreakpointsRequest &request) = 0;
|
||||
virtual ResponseOrError<SetExceptionBreakpointsResponse> SetExceptionBreakpoints(const SetExceptionBreakpointsRequest &request) = 0;
|
||||
virtual ResponseOrError<SetExpressionResponse> SetExpression(const SetExpressionRequest &request) = 0;
|
||||
virtual ResponseOrError<SetFunctionBreakpointsResponse> SetFunctionBreakpoints(const SetFunctionBreakpointsRequest &request) = 0;
|
||||
virtual ResponseOrError<SetInstructionBreakpointsResponse> SetInstructionBreakpoints(const SetInstructionBreakpointsRequest &request) = 0;
|
||||
virtual ResponseOrError<SetVariableResponse> SetVariable(const SetVariableRequest &request) = 0;
|
||||
virtual ResponseOrError<SourceResponse> Source(const SourceRequest &request) = 0;
|
||||
virtual ResponseOrError<StackTraceResponse> StackTrace(const StackTraceRequest &request) = 0;
|
||||
virtual ResponseOrError<StepBackResponse> StepBack(const StepBackRequest &request) = 0;
|
||||
virtual ResponseOrError<StepInResponse> StepIn(const StepInRequest &request) = 0;
|
||||
virtual ResponseOrError<StepInTargetsResponse> StepInTargets(const StepInTargetsRequest &request) = 0;
|
||||
virtual ResponseOrError<StepOutResponse> StepOut(const StepOutRequest &request) = 0;
|
||||
virtual ResponseOrError<TerminateResponse> Terminate(const TerminateRequest &request) = 0;
|
||||
virtual ResponseOrError<TerminateThreadsResponse> TerminateThreads(const TerminateThreadsRequest &request) = 0;
|
||||
virtual ResponseOrError<ThreadsResponse> Threads(const ThreadsRequest &request) = 0;
|
||||
virtual ResponseOrError<VariablesResponse> Variables(const VariablesRequest &request) = 0;
|
||||
protected:
|
||||
std::shared_ptr<dap::Session> m_Session;
|
||||
};
|
||||
}
|
||||
22
src/common/scripting/dap/Protocol/struct_extensions.cpp
Normal file
22
src/common/scripting/dap/Protocol/struct_extensions.cpp
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
#include "struct_extensions.h"
|
||||
|
||||
namespace dap
|
||||
{
|
||||
DAP_IMPLEMENT_STRUCT_TYPEINFO_EXT(
|
||||
PDSAttachRequest,
|
||||
AttachRequest,
|
||||
"attach",
|
||||
DAP_FIELD(name, "name"),
|
||||
DAP_FIELD(type, "type"),
|
||||
DAP_FIELD(request, "request"),
|
||||
DAP_FIELD(projectSources, "projectSources"));
|
||||
|
||||
DAP_IMPLEMENT_STRUCT_TYPEINFO_EXT(
|
||||
PDSLaunchRequest,
|
||||
LaunchRequest,
|
||||
"launch",
|
||||
DAP_FIELD(name, "name"),
|
||||
DAP_FIELD(type, "type"),
|
||||
DAP_FIELD(request, "request"),
|
||||
DAP_FIELD(projectSources, "projectSources"));
|
||||
}
|
||||
33
src/common/scripting/dap/Protocol/struct_extensions.h
Normal file
33
src/common/scripting/dap/Protocol/struct_extensions.h
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
#pragma once
|
||||
|
||||
#include <dap/typeof.h>
|
||||
#include <dap/types.h>
|
||||
#include <dap/protocol.h>
|
||||
|
||||
namespace dap
|
||||
{
|
||||
|
||||
// Extended AttachRequest struct for implementation specific parameters
|
||||
|
||||
struct PDSAttachRequest : public AttachRequest
|
||||
{
|
||||
using Response = AttachResponse;
|
||||
string name;
|
||||
string type;
|
||||
string request;
|
||||
optional<array<Source>> projectSources;
|
||||
};
|
||||
|
||||
struct PDSLaunchRequest : public LaunchRequest
|
||||
{
|
||||
using Response = LaunchResponse;
|
||||
string name;
|
||||
string type;
|
||||
string request;
|
||||
optional<array<Source>> projectSources;
|
||||
};
|
||||
|
||||
DAP_DECLARE_STRUCT_TYPEINFO(PDSAttachRequest);
|
||||
DAP_DECLARE_STRUCT_TYPEINFO(PDSLaunchRequest);
|
||||
|
||||
}
|
||||
86
src/common/scripting/dap/RuntimeEvents.cpp
Normal file
86
src/common/scripting/dap/RuntimeEvents.cpp
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
#define XBYAK_NO_OP_NAMES
|
||||
|
||||
#include "RuntimeEvents.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <dap/protocol.h>
|
||||
#include "GameInterfaces.h"
|
||||
#include "common/scripting/vm/vmintern.h"
|
||||
|
||||
namespace DebugServer
|
||||
{
|
||||
namespace RuntimeEvents
|
||||
{
|
||||
#define EVENT_WRAPPER_IMPL(NAME, HANDLER_SIGNATURE) \
|
||||
bool g_## NAME## EventActive = false; \
|
||||
std::function<HANDLER_SIGNATURE> g_## NAME## Event; \
|
||||
\
|
||||
NAME##EventHandle SubscribeTo##NAME(std::function<HANDLER_SIGNATURE> handler) \
|
||||
{ \
|
||||
g_## NAME## Event = handler;\
|
||||
g_## NAME## EventActive = true; \
|
||||
return handler;\
|
||||
}\
|
||||
\
|
||||
bool UnsubscribeFrom## NAME(NAME## EventHandle handle)\
|
||||
{\
|
||||
if (!handle) \
|
||||
return false; \
|
||||
g_## NAME## EventActive = false; \
|
||||
g_## NAME## Event = nullptr;\
|
||||
return true;\
|
||||
}
|
||||
|
||||
EVENT_WRAPPER_IMPL(InstructionExecution, void(VMFrameStack *stack, VMReturn *ret, int numret, const VMOP *pc))
|
||||
EVENT_WRAPPER_IMPL(CreateStack, void(VMFrameStack *))
|
||||
EVENT_WRAPPER_IMPL(CleanupStack, void(uint32_t))
|
||||
EVENT_WRAPPER_IMPL(Log, void(int level, const char *msg))
|
||||
EVENT_WRAPPER_IMPL(BreakpointChanged, void(const dap::Breakpoint &bpoint, const std::string &))
|
||||
EVENT_WRAPPER_IMPL(ExceptionThrown, void(EVMAbortException reason, const std::string &message, const std::string &stackTrace))
|
||||
EVENT_WRAPPER_IMPL(DebuggerEnabled, bool(void))
|
||||
|
||||
#undef EVENT_WRAPPER_IMPL
|
||||
|
||||
|
||||
void EmitBreakpointChangedEvent(const dap::Breakpoint &bpoint, const std::string &what)
|
||||
{
|
||||
if (g_BreakpointChangedEventActive)
|
||||
{
|
||||
g_BreakpointChangedEvent(bpoint, what);
|
||||
}
|
||||
}
|
||||
void EmitInstructionExecutionEvent(VMFrameStack *stack, VMReturn *ret, int numret, const VMOP *pc)
|
||||
{
|
||||
if (g_InstructionExecutionEventActive)
|
||||
{
|
||||
g_InstructionExecutionEvent(stack, ret, numret, pc);
|
||||
}
|
||||
}
|
||||
void EmitLogEvent(int level, const char *msg)
|
||||
{
|
||||
if (g_LogEventActive)
|
||||
{
|
||||
g_LogEvent(level, msg);
|
||||
}
|
||||
}
|
||||
void EmitExceptionEvent(EVMAbortException reason, const std::string &message, const std::string &stackTrace)
|
||||
{
|
||||
if (g_ExceptionThrownEventActive)
|
||||
{
|
||||
g_ExceptionThrownEvent(reason, message, stackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
bool IsDebugServerRunning()
|
||||
{
|
||||
if (g_DebuggerEnabledEventActive){
|
||||
return g_DebuggerEnabledEvent();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: Are CreateStack and CleanupStack events needed? VM execution is single-threaded and there's only one stack.
|
||||
// Maybe an event when the last frame gets popped off, but I'm not sure what would even need that.
|
||||
|
||||
}
|
||||
}
|
||||
38
src/common/scripting/dap/RuntimeEvents.h
Normal file
38
src/common/scripting/dap/RuntimeEvents.h
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
#pragma once
|
||||
#include <functional>
|
||||
#include "vm.h"
|
||||
#include "GameEventEmit.h"
|
||||
|
||||
namespace dap
|
||||
{
|
||||
struct Breakpoint;
|
||||
}
|
||||
|
||||
#define EVENT_DECLARATION(NAME, HANDLER_SIGNATURE) \
|
||||
typedef std::function<HANDLER_SIGNATURE> NAME## EventHandle; \
|
||||
NAME##EventHandle SubscribeTo##NAME(std::function<HANDLER_SIGNATURE> handler); \
|
||||
bool UnsubscribeFrom##NAME(NAME##EventHandle handle);
|
||||
|
||||
|
||||
namespace dap
|
||||
{
|
||||
struct Breakpoint;
|
||||
}
|
||||
|
||||
namespace DebugServer
|
||||
{
|
||||
namespace RuntimeEvents
|
||||
{
|
||||
EVENT_DECLARATION(InstructionExecution, void(VMFrameStack *stack, VMReturn *ret, int numret, const VMOP *pc))
|
||||
EVENT_DECLARATION(CreateStack, void(VMFrameStack *))
|
||||
EVENT_DECLARATION(CleanupStack, void(uint32_t))
|
||||
EVENT_DECLARATION(Log, void(int level, const char *message))
|
||||
EVENT_DECLARATION(BreakpointChanged, void(const dap::Breakpoint &bpoint, const std::string &))
|
||||
EVENT_DECLARATION(ExceptionThrown, void(EVMAbortException reason, const std::string &message, const std::string &stackTrace))
|
||||
EVENT_DECLARATION(DebuggerEnabled, bool(void))
|
||||
|
||||
void EmitBreakpointChangedEvent(const dap::Breakpoint &bpoint, const std::string &what);
|
||||
}
|
||||
}
|
||||
|
||||
#undef EVENT_DECLARATION
|
||||
302
src/common/scripting/dap/RuntimeState.cpp
Normal file
302
src/common/scripting/dap/RuntimeState.cpp
Normal file
|
|
@ -0,0 +1,302 @@
|
|||
#include "RuntimeState.h"
|
||||
#include "Utilities.h"
|
||||
#include "Nodes/StateNodeBase.h"
|
||||
|
||||
#include "Nodes/StackStateNode.h"
|
||||
#include "Nodes/ObjectStateNode.h"
|
||||
#include "Nodes/StructStateNode.h"
|
||||
#include "Nodes/ArrayStateNode.h"
|
||||
#include "Nodes/ValueStateNode.h"
|
||||
#include "common/scripting/dap/GameInterfaces.h"
|
||||
#include "vm.h"
|
||||
#include <common/scripting/dap/Nodes/StatePointerNode.h>
|
||||
|
||||
namespace DebugServer
|
||||
{
|
||||
VMFrameStack *RuntimeState::m_GlobalVMStack = nullptr;
|
||||
|
||||
RuntimeState::RuntimeState(const std::shared_ptr<IdProvider> &idProvider)
|
||||
{
|
||||
m_paths = std::make_unique<IdMap<std::string>>(idProvider);
|
||||
Reset();
|
||||
}
|
||||
|
||||
bool RuntimeState::ResolveStateByPath(const std::string requestedPath, std::shared_ptr<StateNodeBase> &node, bool isEvaluate)
|
||||
{
|
||||
const auto path = ToLowerCopy(requestedPath);
|
||||
|
||||
auto elements = Split(path, ".");
|
||||
|
||||
if (elements.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto stackIdElement = elements.at(0);
|
||||
int stackId;
|
||||
if (!ParseInt(stackIdElement, &stackId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<std::string> currentPathElements;
|
||||
currentPathElements.push_back(stackIdElement);
|
||||
|
||||
elements.erase(elements.begin());
|
||||
|
||||
std::shared_ptr<StateNodeBase> currentNode;
|
||||
if (stackId == 1)
|
||||
{
|
||||
currentNode = m_root;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentNode = std::make_shared<StackStateNode>(stackId);
|
||||
}
|
||||
|
||||
|
||||
std::function<std::shared_ptr<StateNodeBase>(const std::string &, std::shared_ptr<StateNodeBase>&)> traversePathPart = [&](
|
||||
const std::string ¤tName,
|
||||
std::shared_ptr<StateNodeBase> &part)
|
||||
{
|
||||
auto structured = dynamic_cast<IStructuredState *>(part.get());
|
||||
auto currentPath = Join(currentPathElements, ".");
|
||||
ToLower(currentPath);
|
||||
if (structured)
|
||||
{
|
||||
std::vector<std::string> childNames;
|
||||
structured->GetChildNames(childNames);
|
||||
|
||||
for (auto childName : childNames)
|
||||
{
|
||||
ToLower(childName);
|
||||
uint32_t addedId;
|
||||
m_paths->AddOrGetExisting(currentPath + "." + childName, addedId);
|
||||
}
|
||||
}
|
||||
|
||||
bool foundChild = false;
|
||||
std::shared_ptr<StateNodeBase> foundNode = nullptr;
|
||||
if (structured)
|
||||
{
|
||||
// First try normal child nodes
|
||||
if (structured->GetChildNode(currentName, foundNode))
|
||||
{
|
||||
currentPathElements.push_back(currentName);
|
||||
return foundNode;
|
||||
} else if (isEvaluate) {
|
||||
auto virtualContainers = structured->GetVirtualContainerChildren();
|
||||
// If not found and we have virtual containers, check them
|
||||
if (!virtualContainers.empty())
|
||||
{
|
||||
for (auto &virtualContainer : virtualContainers)
|
||||
{
|
||||
if (virtualContainer.second)
|
||||
{
|
||||
currentPathElements.push_back(virtualContainer.first);
|
||||
foundNode = traversePathPart(currentName, virtualContainer.second);
|
||||
if (foundNode) {
|
||||
return foundNode;
|
||||
}
|
||||
// pop the last element
|
||||
currentPathElements.pop_back();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
foundNode = nullptr;
|
||||
return foundNode;
|
||||
};
|
||||
|
||||
while (!elements.empty() && currentNode)
|
||||
{
|
||||
currentNode = traversePathPart(elements.at(0), currentNode);
|
||||
if (!currentNode)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
elements.erase(elements.begin());
|
||||
}
|
||||
|
||||
if (!currentNode)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
node = currentNode;
|
||||
|
||||
if (currentPathElements.size() > 1)
|
||||
{
|
||||
uint32_t id;
|
||||
auto actualPath = Join(currentPathElements, ".");
|
||||
ToLower(actualPath);
|
||||
m_paths->AddOrGetExisting(actualPath, id);
|
||||
node->SetId(id);
|
||||
}
|
||||
else
|
||||
{
|
||||
node->SetId(stackId);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string RuntimeState::GetPathById(const uint32_t id) const
|
||||
{
|
||||
if (id == 1){
|
||||
return "1";
|
||||
}
|
||||
std::string path;
|
||||
if (m_paths->Get(id, path))
|
||||
{
|
||||
return path;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
bool RuntimeState::ResolveStateById(const uint32_t id, std::shared_ptr<StateNodeBase> &node, bool isEvaluate)
|
||||
{
|
||||
std::string path;
|
||||
|
||||
if (m_paths->Get(id, path))
|
||||
{
|
||||
return ResolveStateByPath(path, node, isEvaluate);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool RuntimeState::ResolveChildrenByParentPath(const std::string requestedPath, std::vector<std::shared_ptr<StateNodeBase>> &nodes, size_t start, size_t count)
|
||||
{
|
||||
std::shared_ptr<StateNodeBase> resolvedParent;
|
||||
if (!ResolveStateByPath(requestedPath, resolvedParent))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
auto structured = dynamic_cast<IStructuredState *>(resolvedParent.get());
|
||||
if (!structured)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<std::string> childNames;
|
||||
structured->GetChildNames(childNames);
|
||||
count = count == 0 ? childNames.size() : count;
|
||||
size_t maxCount = std::min(count + start, childNames.size());
|
||||
|
||||
for (size_t i = start; i < maxCount; i++)
|
||||
{
|
||||
std::shared_ptr<StateNodeBase> childNode;
|
||||
if (ResolveStateByPath(StringFormat("%s.%s", requestedPath.c_str(), childNames[i].c_str()), childNode))
|
||||
{
|
||||
nodes.push_back(childNode);
|
||||
}
|
||||
else
|
||||
{
|
||||
maxCount = std::min(childNames.size(), maxCount + 1);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RuntimeState::ResolveChildrenByParentId(const uint32_t id, std::vector<std::shared_ptr<StateNodeBase>> &nodes, size_t start, size_t count)
|
||||
{
|
||||
std::string path;
|
||||
|
||||
if (m_paths->Get(id, path))
|
||||
{
|
||||
return ResolveChildrenByParentPath(path, nodes, start, count);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::shared_ptr<StateNodeBase>
|
||||
RuntimeState::CreateNodeForVariable(std::string name, VMValue variable, PType *p_type, const VMFrame *current_frame, PClass *stateOwningClass)
|
||||
{
|
||||
(void)current_frame;
|
||||
if (p_type->isPointer() && !p_type->isObjectPointer() && !static_cast<PPointer *>(p_type)->PointedType->isStruct())
|
||||
{
|
||||
auto pointed = static_cast<PPointer *>(p_type)->PointedType;
|
||||
}
|
||||
if (TypeIsArrayOrArrayPtr(p_type))
|
||||
{
|
||||
return std::make_shared<ArrayStateNode>(name, variable, p_type);
|
||||
}
|
||||
if (IsBasicNonPointerType(p_type) || (p_type->isPointer() && !p_type->isObjectPointer() && !static_cast<PPointer *>(p_type)->PointedType->isStruct()))
|
||||
{
|
||||
return std::make_shared<ValueStateNode>(name, variable, p_type, stateOwningClass);
|
||||
}
|
||||
if (p_type == TypeState)
|
||||
{
|
||||
return std::make_shared<StatePointerNode>(name, variable, stateOwningClass);
|
||||
}
|
||||
if (p_type->isClass() || p_type->isObjectPointer())
|
||||
{
|
||||
return std::make_shared<ObjectStateNode>(name, variable, p_type);
|
||||
}
|
||||
if (TypeIsStructOrStructPtr(p_type) || TypeIsNonStructContainer(p_type))
|
||||
{
|
||||
return std::make_shared<StructStateNode>(name, variable, p_type, current_frame);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
VMFrameStack *RuntimeState::GetStack(uint32_t stackId)
|
||||
{
|
||||
// just one stack!
|
||||
return m_GlobalVMStack;
|
||||
}
|
||||
|
||||
VMFrame *RuntimeState::GetFrame(const uint32_t stackId, const uint32_t level)
|
||||
{
|
||||
std::vector<VMFrame *> frames;
|
||||
GetStackFrames(stackId, frames);
|
||||
|
||||
if (frames.empty() || level > frames.size() - 1)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return frames.at(level);
|
||||
}
|
||||
|
||||
void RuntimeState::GetStackFrames(VMFrameStack *stack, std::vector<VMFrame *> &frames)
|
||||
{
|
||||
if (!stack->HasFrames())
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto frame = stack->TopFrame();
|
||||
|
||||
while (frame)
|
||||
{
|
||||
frames.push_back(frame);
|
||||
frame = frame->ParentFrame;
|
||||
}
|
||||
}
|
||||
|
||||
bool RuntimeState::GetStackFrames(const uint32_t stackId, std::vector<VMFrame *> &frames)
|
||||
{
|
||||
const auto stack = GetStack(stackId);
|
||||
if (!stack)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
GetStackFrames(stack, frames);
|
||||
|
||||
return true;
|
||||
}
|
||||
void RuntimeState::Reset()
|
||||
{
|
||||
m_paths->Clear();
|
||||
// RuntimeState::m_GlobalVMStack = nullptr;
|
||||
m_root = std::make_shared<StackStateNode>(1);
|
||||
}
|
||||
}
|
||||
35
src/common/scripting/dap/RuntimeState.h
Normal file
35
src/common/scripting/dap/RuntimeState.h
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
#pragma once
|
||||
|
||||
#include "IdMap.h"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include "Nodes/StateNodeBase.h"
|
||||
#include "vm.h"
|
||||
|
||||
namespace DebugServer
|
||||
{
|
||||
class RuntimeState
|
||||
{
|
||||
std::unique_ptr<IdMap<std::string>> m_paths;
|
||||
std::shared_ptr<StateNodeBase> m_root;
|
||||
public:
|
||||
static VMFrameStack *m_GlobalVMStack;
|
||||
explicit RuntimeState(const std::shared_ptr<IdProvider> &idProvider);
|
||||
void Reset();
|
||||
bool ResolveStateByPath(std::string requestedPath, std::shared_ptr<StateNodeBase> &node, bool isEvaluate = false);
|
||||
bool ResolveStateById(uint32_t id, std::shared_ptr<StateNodeBase> &node, bool isEvaluate = false);
|
||||
bool ResolveChildrenByParentPath(std::string requestedPath, std::vector<std::shared_ptr<StateNodeBase>> &nodes, size_t start = 0, size_t count = INT_MAX);
|
||||
bool ResolveChildrenByParentId(uint32_t id, std::vector<std::shared_ptr<StateNodeBase>> &nodes, size_t start = 0, size_t count = INT_MAX);
|
||||
std::string GetPathById(const uint32_t id) const;
|
||||
|
||||
static std::shared_ptr<StateNodeBase>
|
||||
CreateNodeForVariable(std::string name, VMValue variable, PType *p_type, const VMFrame *current_frame = nullptr, PClass *stateOwningClass = nullptr);
|
||||
|
||||
static VMFrameStack *GetStack(uint32_t stackId);
|
||||
static VMFrame *GetFrame(uint32_t stackId, uint32_t level);
|
||||
|
||||
static void GetStackFrames(VMFrameStack *stack, std::vector<VMFrame *> &frames);
|
||||
static bool GetStackFrames(uint32_t stackId, std::vector<VMFrame *> &frames);
|
||||
};
|
||||
}
|
||||
275
src/common/scripting/dap/Utilities.h
Normal file
275
src/common/scripting/dap/Utilities.h
Normal file
|
|
@ -0,0 +1,275 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <regex>
|
||||
#include <dap/protocol.h>
|
||||
#include <common/engine/printf.h>
|
||||
#include <map>
|
||||
#include <set>
|
||||
namespace DebugServer
|
||||
{
|
||||
|
||||
// Caseless comparison, required for script identifiers (ZScript/ACS/DECORATE are all case-insensitive)
|
||||
struct ci_less
|
||||
{
|
||||
// case-independent (ci) compare_less binary function
|
||||
struct nocase_compare
|
||||
{
|
||||
bool operator()(const unsigned char &c1, const unsigned char &c2) const { return tolower(c1) < tolower(c2); }
|
||||
};
|
||||
template <
|
||||
typename T,
|
||||
typename U,
|
||||
typename = std::enable_if_t<
|
||||
(std::is_same_v<T, std::string> || std::is_same_v<T, std::string_view>) && (std::is_same_v<U, std::string> || std::is_same_v<U, std::string_view>)>>
|
||||
bool operator()(T const &s1, U const &s2) const
|
||||
{
|
||||
return std::lexicographical_compare(
|
||||
s1.begin(),
|
||||
s1.end(), // source range
|
||||
s2.begin(),
|
||||
s2.end(), // dest range
|
||||
nocase_compare()); // comparison
|
||||
}
|
||||
};
|
||||
|
||||
template <typename V> using caseless_path_map = typename std::map<std::string, V, ci_less>;
|
||||
|
||||
using caseless_path_set = typename std::set<std::string, ci_less>;
|
||||
|
||||
inline bool CaseInsensitiveEquals(const std::string &s1, const std::string &s2)
|
||||
{
|
||||
return s1.size() == s2.size()
|
||||
&& std::equal(
|
||||
s1.begin(), s1.end(), // source range
|
||||
s2.begin(), s2.end(), // dest range
|
||||
[](const unsigned char &c1, const unsigned char &c2) { return tolower(c1) == tolower(c2); }); // comparison
|
||||
}
|
||||
|
||||
inline size_t CaseInsensitiveFind(const std::string &s1, const std::string &s2)
|
||||
{
|
||||
auto pos = std::search(s1.begin(), s1.end(), s2.begin(), s2.end(), [](char c1, char c2) { return tolower(c1) == tolower(c2); });
|
||||
if (pos == s1.end()){
|
||||
return std::string::npos;
|
||||
}
|
||||
return std::distance(s1.begin(), pos);
|
||||
}
|
||||
|
||||
template <typename... Args> std::string StringFormat(const char *fmt, Args... args)
|
||||
{
|
||||
const size_t size = snprintf(nullptr, 0, fmt, args...);
|
||||
std::string buf;
|
||||
buf.reserve(size + 1);
|
||||
buf.resize(size);
|
||||
snprintf(&buf[0], size + 1, fmt, args...);
|
||||
return buf;
|
||||
}
|
||||
|
||||
static inline std::string StripColorCodes(const std::string &str)
|
||||
{
|
||||
TArray<char> copy(str.size() + 1);
|
||||
const char *srcp = str.c_str();
|
||||
char *dstp = copy.Data();
|
||||
|
||||
while (*srcp != 0)
|
||||
{
|
||||
|
||||
if (*srcp != TEXTCOLOR_ESCAPE)
|
||||
{
|
||||
*dstp++ = *srcp++;
|
||||
}
|
||||
else if (srcp[1] == '[')
|
||||
{
|
||||
srcp += 2;
|
||||
while (*srcp != ']' && *srcp != 0)
|
||||
srcp++;
|
||||
if (*srcp == ']') srcp++;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (srcp[1] != 0) srcp += 2;
|
||||
else
|
||||
break;
|
||||
}
|
||||
}
|
||||
*dstp = 0;
|
||||
|
||||
return copy.Data();
|
||||
}
|
||||
|
||||
template <typename... Args> void LogInternal(const char *fmt, Args... args)
|
||||
{
|
||||
Printf(PRINT_HIGH | PRINT_NODAPEVENT | PRINT_NONOTIFY, "%s\n", StringFormat(fmt, args...).c_str());
|
||||
}
|
||||
template <typename... Args> void LogInternalError(const char *fmt, Args... args)
|
||||
{
|
||||
Printf(PRINT_HIGH | PRINT_NODAPEVENT | PRINT_NONOTIFY, TEXTCOLOR_RED "%s\n", StringFormat(fmt, args...).c_str());
|
||||
}
|
||||
template <typename... Args> void Log(const char *fmt, Args... args)
|
||||
{
|
||||
Printf(PRINT_HIGH | PRINT_NONOTIFY, "%s\n", StringFormat(fmt, args...).c_str());
|
||||
}
|
||||
template <typename... Args> void LogError(const char *fmt, Args... args)
|
||||
{
|
||||
Printf(PRINT_HIGH | PRINT_NONOTIFY, TEXTCOLOR_RED "%s\n", StringFormat(fmt, args...).c_str());
|
||||
}
|
||||
|
||||
#define RETURN_DAP_ERROR(message) \
|
||||
LogError("%s", message); \
|
||||
return dap::Error(message);
|
||||
|
||||
#define RETURN_DAP_ERROR_NO_NOTIFY(message) \
|
||||
return dap::Error(message);
|
||||
|
||||
#define RETURN_COND_DAP_ERROR(cond, message) \
|
||||
if (cond) { \
|
||||
RETURN_DAP_ERROR(message); \
|
||||
}
|
||||
|
||||
template <typename T> T ByteSwap(T val)
|
||||
{
|
||||
T retVal;
|
||||
const auto pVal = reinterpret_cast<char *>(&val);
|
||||
const auto pRetVal = reinterpret_cast<char *>(&retVal);
|
||||
const int size = sizeof(T);
|
||||
for (auto i = 0; i < size; i++)
|
||||
{
|
||||
pRetVal[size - 1 - i] = pVal[i];
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
inline std::vector<std::string> Split(const std::string &s, const std::string &delimiter)
|
||||
{
|
||||
size_t posStart = 0, posEnd;
|
||||
const auto delimLen = delimiter.length();
|
||||
std::vector<std::string> res;
|
||||
|
||||
while ((posEnd = s.find(delimiter, posStart)) != std::string::npos)
|
||||
{
|
||||
auto token = s.substr(posStart, posEnd - posStart);
|
||||
posStart = posEnd + delimLen;
|
||||
res.push_back(token);
|
||||
}
|
||||
|
||||
res.push_back(s.substr(posStart));
|
||||
return res;
|
||||
}
|
||||
|
||||
inline std::string Join(const std::vector<std::string> &elements, const char *const separator)
|
||||
{
|
||||
switch (elements.size())
|
||||
{
|
||||
case 0:
|
||||
return "";
|
||||
case 1:
|
||||
return elements[0];
|
||||
default:
|
||||
std::ostringstream os;
|
||||
std::copy(elements.begin(), elements.end() - 1, std::ostream_iterator<std::string>(os, separator));
|
||||
os << *elements.rbegin();
|
||||
return os.str();
|
||||
}
|
||||
}
|
||||
|
||||
inline bool ParseInt(const std::string &str, int *value, std::size_t *pos = nullptr, const int base = 10)
|
||||
{
|
||||
try
|
||||
{
|
||||
*value = std::stoi(str, pos, base);
|
||||
}
|
||||
catch (void *)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
inline void ToLower(std::string &p_str)
|
||||
{
|
||||
for (size_t i = 0; i < p_str.size(); i++)
|
||||
{
|
||||
p_str[i] = tolower(p_str[i]);
|
||||
}
|
||||
}
|
||||
|
||||
inline std::string ToLowerCopy(const std::string &p_str)
|
||||
{
|
||||
std::string r_str = p_str;
|
||||
ToLower(r_str);
|
||||
return r_str;
|
||||
}
|
||||
|
||||
inline std::string DemangleName(std::string name)
|
||||
{
|
||||
if (name.front() == ':')
|
||||
{
|
||||
return name.substr(2, name.length() - 6);
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
inline int GetScriptReference(const std::string &scriptName)
|
||||
{
|
||||
constexpr std::hash<std::string> hasher {};
|
||||
std::string name = scriptName;
|
||||
std::transform(name.begin(), name.end(), name.begin(), ::tolower);
|
||||
|
||||
return std::abs(static_cast<int>(hasher(name))) + 1;
|
||||
}
|
||||
|
||||
inline int GetSourceReference(const dap::Source &src)
|
||||
{
|
||||
// If the source reference <= 0, it's invalid
|
||||
if (src.sourceReference.value(0) > 0)
|
||||
{
|
||||
return static_cast<int>(src.sourceReference.value());
|
||||
}
|
||||
if (!src.path.has_value())
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
std::string path = src.path.value();
|
||||
if (src.origin.has_value())
|
||||
{
|
||||
path = src.origin.value() + ":" + path;
|
||||
}
|
||||
return GetScriptReference(path);
|
||||
}
|
||||
|
||||
inline std::string GetSourceModfiedTime(const dap::Source &src)
|
||||
{
|
||||
if (!src.checksums.has_value())
|
||||
{
|
||||
return "";
|
||||
}
|
||||
for (auto &checksum : src.checksums.value())
|
||||
{
|
||||
if (checksum.algorithm == "timestamp")
|
||||
{
|
||||
return checksum.checksum;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
inline bool CompareSourceModifiedTime(const dap::Source &src1, const dap::Source &src2)
|
||||
{
|
||||
if (GetSourceModfiedTime(src1) != GetSourceModfiedTime(src2))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
inline std::string StringJoin(const std::vector<std::string> &strings, const char *delim)
|
||||
{
|
||||
std::ostringstream imploded;
|
||||
std::copy(strings.begin(), strings.end(), std::ostream_iterator<std::string>(imploded, delim));
|
||||
return imploded.str().substr(0, imploded.str().size() - strlen(delim));
|
||||
}
|
||||
}
|
||||
807
src/common/scripting/dap/ZScriptDebugger.cpp
Normal file
807
src/common/scripting/dap/ZScriptDebugger.cpp
Normal file
|
|
@ -0,0 +1,807 @@
|
|||
#include "ZScriptDebugger.h"
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <dap/protocol.h>
|
||||
#include <dap/session.h>
|
||||
|
||||
#include "Utilities.h"
|
||||
#include "GameInterfaces.h"
|
||||
#include "Nodes/StackFrameStateNode.h"
|
||||
#include "Nodes/StateNodeBase.h"
|
||||
#include "Nodes/CVarScopeStateNode.h"
|
||||
#include "common/scripting/dap/Nodes/LocalScopeStateNode.h"
|
||||
|
||||
|
||||
// This is the main class that handles the debug session and the debug requests/responses and events
|
||||
|
||||
namespace DebugServer
|
||||
{
|
||||
ZScriptDebugger::ZScriptDebugger()
|
||||
{
|
||||
m_pexCache = std::make_shared<PexCache>();
|
||||
|
||||
m_breakpointManager = std::make_shared<BreakpointManager>(m_pexCache.get());
|
||||
|
||||
m_idProvider = std::make_shared<IdProvider>();
|
||||
m_runtimeState = std::make_shared<RuntimeState>(m_idProvider);
|
||||
|
||||
m_executionManager = std::make_shared<DebugExecutionManager>(m_runtimeState.get(), m_breakpointManager.get());
|
||||
}
|
||||
|
||||
void ZScriptDebugger::StartSession(std::shared_ptr<dap::Session> session)
|
||||
{
|
||||
if (m_session)
|
||||
{
|
||||
if (m_initialized)
|
||||
{
|
||||
LogInternalError("Session is already active, ending it first!");
|
||||
}
|
||||
EndSession();
|
||||
}
|
||||
m_initialized = false;
|
||||
m_quitting = false;
|
||||
m_session = session;
|
||||
m_executionManager->Open(session);
|
||||
m_createStackEventHandle = RuntimeEvents::SubscribeToCreateStack(std::bind(&ZScriptDebugger::StackCreated, this, std::placeholders::_1));
|
||||
|
||||
m_cleanupStackEventHandle = RuntimeEvents::SubscribeToCleanupStack(std::bind(&ZScriptDebugger::StackCleanedUp, this, std::placeholders::_1));
|
||||
|
||||
m_instructionExecutionEventHandle = RuntimeEvents::SubscribeToInstructionExecution(
|
||||
std::bind(&ZScriptDebugger::InstructionExecution, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4));
|
||||
|
||||
// m_initScriptEventHandle = RuntimeEvents::SubscribeToInitScript(std::bind(&ZScriptDebugger::InitScriptEvent, this, std::placeholders::_1));
|
||||
m_logEventHandle = RuntimeEvents::SubscribeToLog(std::bind(&ZScriptDebugger::EventLogged, this, std::placeholders::_1, std::placeholders::_2));
|
||||
|
||||
m_breakpointChangedEventHandle
|
||||
= RuntimeEvents::SubscribeToBreakpointChanged(std::bind(&ZScriptDebugger::BreakpointChanged, this, std::placeholders::_1, std::placeholders::_2));
|
||||
|
||||
m_exceptionThrownEventHandle = RuntimeEvents::SubscribeToExceptionThrown(
|
||||
std::bind(&ZScriptDebugger::ExceptionThrown, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
|
||||
RegisterSessionHandlers();
|
||||
}
|
||||
|
||||
bool ZScriptDebugger::IsEndingSession()
|
||||
{
|
||||
return m_endingSession;
|
||||
}
|
||||
|
||||
bool ZScriptDebugger::EndSession(bool closed)
|
||||
{
|
||||
// This is just to prevent the closedHandler from ending the session again
|
||||
if (m_endingSession)
|
||||
{
|
||||
return m_quitting;
|
||||
}
|
||||
m_endingSession = true;
|
||||
m_executionManager->Close();
|
||||
if (m_session)
|
||||
{
|
||||
if (!closed && m_initialized)
|
||||
{
|
||||
LogInternal("Ending DAP debugging session.");
|
||||
SendEvent(dap::TerminatedEvent());
|
||||
}
|
||||
}
|
||||
m_initialized = false;
|
||||
m_session = nullptr;
|
||||
|
||||
RuntimeEvents::UnsubscribeFromLog(m_logEventHandle);
|
||||
// RuntimeEvents::UnsubscribeFromInitScript(m_initScriptEventHandle);
|
||||
RuntimeEvents::UnsubscribeFromInstructionExecution(m_instructionExecutionEventHandle);
|
||||
RuntimeEvents::UnsubscribeFromCreateStack(m_createStackEventHandle);
|
||||
RuntimeEvents::UnsubscribeFromCleanupStack(m_cleanupStackEventHandle);
|
||||
RuntimeEvents::UnsubscribeFromBreakpointChanged(m_breakpointChangedEventHandle);
|
||||
RuntimeEvents::UnsubscribeFromExceptionThrown(m_exceptionThrownEventHandle);
|
||||
m_logEventHandle = nullptr;
|
||||
m_instructionExecutionEventHandle = nullptr;
|
||||
m_createStackEventHandle = nullptr;
|
||||
m_cleanupStackEventHandle = nullptr;
|
||||
m_breakpointChangedEventHandle = nullptr;
|
||||
m_exceptionThrownEventHandle = nullptr;
|
||||
// clear session data
|
||||
m_projectArchive.clear();
|
||||
m_projectPath.clear();
|
||||
m_projectSources.clear();
|
||||
m_breakpointManager->ClearBreakpoints();
|
||||
m_endingSession = false;
|
||||
return m_quitting;
|
||||
}
|
||||
|
||||
dap::ResponseOrError<dap::SetInstructionBreakpointsResponse> ZScriptDebugger::SetInstructionBreakpoints(const dap::SetInstructionBreakpointsRequest &request)
|
||||
{
|
||||
return m_breakpointManager->SetInstructionBreakpoints(request);
|
||||
}
|
||||
|
||||
void ZScriptDebugger::RegisterSessionHandlers()
|
||||
{
|
||||
// The Initialize request is the first message sent from the client and the response reports debugger capabilities.
|
||||
// https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Initialize
|
||||
m_session->registerHandler([this](const dap::InitializeRequest &request) { return Initialize(request); });
|
||||
m_session->onError([this](const char *msg) { LogInternalError("%s", msg); });
|
||||
m_session->registerSentHandler(
|
||||
// After an intialize response is sent, we send an initialized event to indicate that the client can now send requests.
|
||||
[this](const dap::ResponseOrError<dap::InitializeResponse> &)
|
||||
{
|
||||
LogInternal("DAP debugging session started.");
|
||||
// enable event sending
|
||||
m_initialized = true;
|
||||
SendEvent(dap::InitializedEvent());
|
||||
});
|
||||
|
||||
// Client is done configuring.
|
||||
m_session->registerHandler([this](const dap::ConfigurationDoneRequest &) { return dap::ConfigurationDoneResponse {}; });
|
||||
|
||||
// The Disconnect request is sent by the client before it disconnects from the server.
|
||||
// https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Disconnect
|
||||
m_session->registerHandler(
|
||||
[this](const dap::DisconnectRequest &request)
|
||||
{
|
||||
// Client wants to disconnect.
|
||||
if (request.terminateDebuggee.value(false))
|
||||
{
|
||||
m_quitting = true;
|
||||
}
|
||||
return dap::DisconnectResponse {};
|
||||
});
|
||||
m_session->registerHandler([this](const dap::PDSLaunchRequest &request) { return Launch(request); });
|
||||
m_session->registerHandler([this](const dap::PDSAttachRequest &request) { return Attach(request); });
|
||||
m_session->registerHandler([this](const dap::PauseRequest &request) { return Pause(request); });
|
||||
m_session->registerHandler([this](const dap::ContinueRequest &request) { return Continue(request); });
|
||||
m_session->registerHandler([this](const dap::ThreadsRequest &request) { return GetThreads(request); });
|
||||
m_session->registerHandler([this](const dap::SetBreakpointsRequest &request) { return SetBreakpoints(request); });
|
||||
m_session->registerHandler([this](const dap::SetExceptionBreakpointsRequest &request) { return SetExceptionBreakpoints(request); });
|
||||
m_session->registerHandler([this](const dap::SetFunctionBreakpointsRequest &request) { return SetFunctionBreakpoints(request); });
|
||||
m_session->registerHandler([this](const dap::SetInstructionBreakpointsRequest &request) { return SetInstructionBreakpoints(request); });
|
||||
m_session->registerHandler([this](const dap::StackTraceRequest &request) { return GetStackTrace(request); });
|
||||
m_session->registerHandler([this](const dap::StepInRequest &request) { return StepIn(request); });
|
||||
m_session->registerHandler([this](const dap::StepOutRequest &request) { return StepOut(request); });
|
||||
m_session->registerHandler([this](const dap::NextRequest &request) { return Next(request); });
|
||||
m_session->registerHandler([this](const dap::ScopesRequest &request) { return GetScopes(request); });
|
||||
m_session->registerHandler([this](const dap::VariablesRequest &request) { return GetVariables(request); });
|
||||
m_session->registerHandler([this](const dap::SourceRequest &request) { return GetSource(request); });
|
||||
m_session->registerHandler([this](const dap::LoadedSourcesRequest &request) { return GetLoadedSources(request); });
|
||||
m_session->registerHandler([this](const dap::DisassembleRequest &request) { return Disassemble(request); });
|
||||
m_session->registerHandler([this](const dap::EvaluateRequest &request) { return Evaluate(request); });
|
||||
m_session->registerHandler([this](const dap::ModulesRequest &request) { return Modules(request); });
|
||||
}
|
||||
|
||||
dap::Error ZScriptDebugger::Error(const std::string &msg)
|
||||
{
|
||||
Printf("%s", msg.c_str());
|
||||
return dap::Error(msg);
|
||||
}
|
||||
|
||||
template <typename T, typename> void ZScriptDebugger::SendEvent(const T &event)
|
||||
{
|
||||
if (m_session && m_initialized)
|
||||
{
|
||||
try
|
||||
{
|
||||
m_session->send(event);
|
||||
// catch signal 13
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LogInternalError("Error sending event");
|
||||
EndSession(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ZScriptDebugger::EventLogged(int severity, const char *msg)
|
||||
{
|
||||
if (severity & PRINT_NODAPEVENT)
|
||||
{
|
||||
return;
|
||||
}
|
||||
dap::OutputEvent output;
|
||||
output.category = "console";
|
||||
output.output = std::string(msg) + "\r\n";
|
||||
// LogGameOutput(logEvent->severity, output.output);
|
||||
SendEvent(output);
|
||||
}
|
||||
|
||||
void ZScriptDebugger::StackCreated(VMFrameStack *stack)
|
||||
{
|
||||
#if 0
|
||||
const auto stackId = 0; // only one stack
|
||||
SendEvent(dap::ThreadEvent{
|
||||
.reason = "started",
|
||||
.threadId = stackId
|
||||
});
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
void ZScriptDebugger::StackCleanedUp(uint32_t stackId)
|
||||
{
|
||||
#if 0
|
||||
SendEvent(dap::ThreadEvent{
|
||||
.reason = "exited",
|
||||
.threadId = stackId
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
void ZScriptDebugger::InstructionExecution(VMFrameStack *stack, VMReturn *ret, int numret, const VMOP *pc)
|
||||
{
|
||||
m_executionManager->HandleInstruction(stack, ret, numret, pc);
|
||||
}
|
||||
|
||||
// For source loaded events
|
||||
void ZScriptDebugger::CheckSourceLoaded(const std::string &scriptName)
|
||||
{
|
||||
auto binary = m_pexCache->GetScript(scriptName);
|
||||
if (binary && m_session)
|
||||
{
|
||||
dap::LoadedSourceEvent event;
|
||||
event.reason = "new";
|
||||
event.source = binary->GetDapSource();
|
||||
SendEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
void ZScriptDebugger::BreakpointChanged(const dap::Breakpoint &bpoint, const std::string &reason)
|
||||
{
|
||||
dap::BreakpointEvent event;
|
||||
event.breakpoint = bpoint;
|
||||
event.reason = reason;
|
||||
SendEvent(event);
|
||||
}
|
||||
|
||||
void ZScriptDebugger::ExceptionThrown(EVMAbortException reason, const std::string &message, const std::string &stackTrace)
|
||||
{
|
||||
m_executionManager->HandleException(reason, message, stackTrace);
|
||||
}
|
||||
|
||||
ZScriptDebugger::~ZScriptDebugger()
|
||||
{
|
||||
m_initialized = false;
|
||||
EndSession();
|
||||
m_runtimeState->Reset();
|
||||
m_pexCache->Clear();
|
||||
}
|
||||
|
||||
dap::ResponseOrError<dap::InitializeResponse> ZScriptDebugger::Initialize(const dap::InitializeRequest &request)
|
||||
{
|
||||
m_clientCaps = request;
|
||||
dap::InitializeResponse response;
|
||||
LogInternal("Initializing DAP session...");
|
||||
response.supportsConfigurationDoneRequest = true;
|
||||
response.supportsLoadedSourcesRequest = true;
|
||||
response.supportedChecksumAlgorithms = {"CRC32"};
|
||||
response.supportsFunctionBreakpoints = true;
|
||||
#if !defined(_WIN32) && !defined(_WIN64)
|
||||
// TODO: remove this when disassemble is supported on windows
|
||||
response.supportsDisassembleRequest = true;
|
||||
response.supportsSteppingGranularity = true;
|
||||
#endif
|
||||
response.supportTerminateDebuggee = true;
|
||||
response.supportsInstructionBreakpoints = true;
|
||||
response.supportsEvaluateForHovers = true;
|
||||
response.supportsModulesRequest = true;
|
||||
response.exceptionBreakpointFilters = DebugExecutionManager::GetAllExceptionFilters();
|
||||
return response;
|
||||
}
|
||||
|
||||
dap::ResponseOrError<dap::LaunchResponse> ZScriptDebugger::Launch(const dap::PDSLaunchRequest &request)
|
||||
{
|
||||
dap::PDSAttachRequest attach_request;
|
||||
attach_request.name = request.name;
|
||||
attach_request.type = request.type;
|
||||
attach_request.request = request.request;
|
||||
attach_request.projectSources = request.projectSources;
|
||||
|
||||
auto resp = Attach(attach_request);
|
||||
if (resp.error)
|
||||
{
|
||||
RETURN_DAP_ERROR(resp.error.message.c_str());
|
||||
}
|
||||
return dap::ResponseOrError<dap::LaunchResponse>();
|
||||
}
|
||||
|
||||
dap::ResponseOrError<dap::AttachResponse> ZScriptDebugger::Attach(const dap::PDSAttachRequest &request)
|
||||
{
|
||||
m_projectSources.clear();
|
||||
if (!request.restart.has_value())
|
||||
{
|
||||
m_pexCache->Clear();
|
||||
}
|
||||
m_pexCache->ScanAllScripts();
|
||||
for (auto src : request.projectSources.value(std::vector<dap::Source>()))
|
||||
{
|
||||
auto binary = m_pexCache->GetScript(src);
|
||||
if (!binary)
|
||||
{ // no source ref or name, we'll ignore it
|
||||
continue;
|
||||
}
|
||||
auto source = binary->GetDapSource();
|
||||
m_projectSources[source.sourceReference.value()] = source;
|
||||
}
|
||||
|
||||
return dap::AttachResponse();
|
||||
}
|
||||
|
||||
dap::ResponseOrError<dap::ContinueResponse> ZScriptDebugger::Continue(const dap::ContinueRequest &request)
|
||||
{
|
||||
if (m_executionManager->Continue()) return dap::ContinueResponse();
|
||||
RETURN_DAP_ERROR("Could not Continue");
|
||||
}
|
||||
|
||||
dap::ResponseOrError<dap::PauseResponse> ZScriptDebugger::Pause(const dap::PauseRequest &request)
|
||||
{
|
||||
if (m_executionManager->Pause()) return dap::PauseResponse();
|
||||
RETURN_DAP_ERROR("Already paused!");
|
||||
}
|
||||
|
||||
dap::ResponseOrError<dap::ThreadsResponse> ZScriptDebugger::GetThreads(const dap::ThreadsRequest &request)
|
||||
{
|
||||
dap::ThreadsResponse response;
|
||||
|
||||
// just one thread!
|
||||
dap::Thread thread;
|
||||
thread.id = 1;
|
||||
thread.name = "Main Thread";
|
||||
response.threads.push_back(thread);
|
||||
return response;
|
||||
}
|
||||
|
||||
dap::ResponseOrError<dap::SetBreakpointsResponse> ZScriptDebugger::SetBreakpoints(const dap::SetBreakpointsRequest &request)
|
||||
{
|
||||
dap::Source source = request.source;
|
||||
auto ref = GetSourceReference(source);
|
||||
if (m_projectSources.find(ref) != m_projectSources.end())
|
||||
{
|
||||
source = m_projectSources[ref];
|
||||
}
|
||||
else if (ref > 0)
|
||||
{
|
||||
// It's not part of the project's imported sources, they have to get the decompiled source from us,
|
||||
// So we set sourceReference to make the debugger request the source from us
|
||||
source.sourceReference = ref;
|
||||
}
|
||||
return m_breakpointManager->SetBreakpoints(source, request);
|
||||
;
|
||||
}
|
||||
|
||||
dap::ResponseOrError<dap::SetFunctionBreakpointsResponse> ZScriptDebugger::SetFunctionBreakpoints(const dap::SetFunctionBreakpointsRequest &request) { return m_breakpointManager->SetFunctionBreakpoints(request); }
|
||||
dap::ResponseOrError<dap::StackTraceResponse> ZScriptDebugger::GetStackTrace(const dap::StackTraceRequest &request)
|
||||
{
|
||||
dap::StackTraceResponse response;
|
||||
|
||||
if (request.threadId <= -1)
|
||||
{
|
||||
response.totalFrames = 0;
|
||||
RETURN_DAP_ERROR("No threadId specified");
|
||||
}
|
||||
auto frameVal = request.startFrame.value(0);
|
||||
auto levelVal = request.levels.value(0);
|
||||
std::vector<std::shared_ptr<StateNodeBase>> frameNodes;
|
||||
if (!m_runtimeState->ResolveChildrenByParentPath(std::to_string(request.threadId), frameNodes))
|
||||
{
|
||||
RETURN_DAP_ERROR("Could not find ThreadId");
|
||||
}
|
||||
uint32_t startFrame = static_cast<uint32_t>(frameVal > 0 ? frameVal : dap::integer(0));
|
||||
uint32_t levels = static_cast<uint32_t>(levelVal > 0 ? levelVal : dap::integer(0));
|
||||
|
||||
for (uint32_t frameIndex = startFrame; frameIndex < frameNodes.size() && frameIndex < startFrame + levels; frameIndex++)
|
||||
{
|
||||
const auto node = dynamic_cast<StackFrameStateNode *>(frameNodes.at(frameIndex).get());
|
||||
|
||||
dap::StackFrame frame;
|
||||
if (!node->SerializeToProtocol(frame, m_pexCache.get()))
|
||||
{
|
||||
RETURN_DAP_ERROR("Serialization error");
|
||||
}
|
||||
|
||||
response.stackFrames.push_back(frame);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
inline StepGranularity granularityStringToEnum(const std::string &granularity)
|
||||
{
|
||||
if (granularity == "instruction")
|
||||
{
|
||||
return StepGranularity::kInstruction;
|
||||
}
|
||||
else if (granularity == "statement")
|
||||
{
|
||||
return StepGranularity::kStatement;
|
||||
}
|
||||
return StepGranularity::kLine;
|
||||
}
|
||||
|
||||
dap::ResponseOrError<dap::StepInResponse> ZScriptDebugger::StepIn(const dap::StepInRequest &request)
|
||||
{
|
||||
if (m_executionManager->Step(static_cast<uint32_t>(request.threadId), STEP_IN, granularityStringToEnum(request.granularity.value("line"))))
|
||||
{
|
||||
return dap::StepInResponse();
|
||||
}
|
||||
RETURN_DAP_ERROR("Could not StepIn");
|
||||
}
|
||||
dap::ResponseOrError<dap::StepOutResponse> ZScriptDebugger::StepOut(const dap::StepOutRequest &request)
|
||||
{
|
||||
if (m_executionManager->Step(static_cast<uint32_t>(request.threadId), STEP_OUT, granularityStringToEnum(request.granularity.value("line"))))
|
||||
{
|
||||
return dap::StepOutResponse();
|
||||
}
|
||||
RETURN_DAP_ERROR("Could not StepOut");
|
||||
}
|
||||
dap::ResponseOrError<dap::NextResponse> ZScriptDebugger::Next(const dap::NextRequest &request)
|
||||
{
|
||||
if (m_executionManager->Step(static_cast<uint32_t>(request.threadId), STEP_OVER, granularityStringToEnum(request.granularity.value("line"))))
|
||||
{
|
||||
return dap::NextResponse();
|
||||
}
|
||||
RETURN_DAP_ERROR("Could not Next");
|
||||
}
|
||||
dap::ResponseOrError<dap::ScopesResponse> ZScriptDebugger::GetScopes(const dap::ScopesRequest &request)
|
||||
{
|
||||
dap::ScopesResponse response;
|
||||
|
||||
std::vector<std::shared_ptr<StateNodeBase>> frameScopes;
|
||||
if (request.frameId < 0)
|
||||
{
|
||||
RETURN_DAP_ERROR(StringFormat("Invalid frameId %d", request.frameId).c_str());
|
||||
}
|
||||
auto frameId = static_cast<uint32_t>(request.frameId);
|
||||
if (!m_runtimeState->ResolveChildrenByParentId(frameId, frameScopes))
|
||||
{
|
||||
// Don't log, this happens as a result of a scopes request being sent after a step request that invalidates the state
|
||||
return dap::Error(StringFormat("No such frameId %d", frameId).c_str());
|
||||
}
|
||||
|
||||
for (const auto &frameScope : frameScopes)
|
||||
{
|
||||
auto asScopeSerializable = dynamic_cast<IProtocolScopeSerializable *>(frameScope.get());
|
||||
if (!asScopeSerializable)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
dap::Scope scope;
|
||||
if (!asScopeSerializable->SerializeToProtocol(scope))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
response.scopes.push_back(scope);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
dap::ResponseOrError<dap::VariablesResponse> ZScriptDebugger::GetVariables(const dap::VariablesRequest &request)
|
||||
{
|
||||
dap::VariablesResponse response;
|
||||
|
||||
std::vector<std::shared_ptr<StateNodeBase>> variableNodes;
|
||||
int64_t maxCount = request.count.value(INT_MAX);
|
||||
int64_t start = request.start.value(0);
|
||||
|
||||
if (!m_runtimeState->ResolveChildrenByParentId(static_cast<uint32_t>(request.variablesReference), variableNodes, start, maxCount))
|
||||
{
|
||||
// Don't log, this happens as a result of a variables request being sent after a step request that invalidates the state
|
||||
return dap::Error(StringFormat("No such variablesReference %d", request.variablesReference).c_str());
|
||||
}
|
||||
bool only_indexed = request.filter.value("") == "indexed";
|
||||
bool only_named = request.filter.value("") == "named";
|
||||
|
||||
for (int64_t i = 0; i < (int64_t)variableNodes.size(); i++)
|
||||
{
|
||||
auto asVariableSerializable = dynamic_cast<IProtocolVariableSerializable *>(variableNodes.at(i).get());
|
||||
if (!asVariableSerializable)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
dap::Variable variable;
|
||||
if (!asVariableSerializable->SerializeToProtocol(variable))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// if it's a number (i.e. the index of an array), we only want to show indexed variables
|
||||
if (only_indexed || only_named)
|
||||
{
|
||||
bool is_indexed = false;
|
||||
if (variable.name.size() > 0)
|
||||
{
|
||||
size_t offset = variable.name[0] == '[' && variable.name[variable.name.size() - 1] == ']' ? 1 : 0;
|
||||
is_indexed = std::all_of(variable.name.begin() + offset, variable.name.begin() + variable.name.size() - 1 - offset, ::isdigit);
|
||||
}
|
||||
if ((is_indexed && only_named) || (!is_indexed && only_indexed))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
response.variables.push_back(variable);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
dap::ResponseOrError<dap::SourceResponse> ZScriptDebugger::GetSource(const dap::SourceRequest &request)
|
||||
{
|
||||
if (!request.source.has_value() || !request.source.value().path.has_value() || !request.source.value().sourceReference.has_value())
|
||||
{
|
||||
RETURN_DAP_ERROR("No source path or reference");
|
||||
}
|
||||
auto source = request.source.value();
|
||||
dap::SourceResponse response;
|
||||
std::string sourceContent;
|
||||
if (m_pexCache->GetDecompiledSource(source, sourceContent))
|
||||
{
|
||||
response.content = sourceContent;
|
||||
return response;
|
||||
}
|
||||
RETURN_DAP_ERROR(StringFormat("No source found for %s", source.path.value("").c_str()).c_str());
|
||||
}
|
||||
dap::ResponseOrError<dap::LoadedSourcesResponse> ZScriptDebugger::GetLoadedSources(const dap::LoadedSourcesRequest &request) { return m_pexCache->GetLoadedSources(request); }
|
||||
|
||||
dap::ResponseOrError<dap::DisassembleResponse> ZScriptDebugger::Disassemble(const dap::DisassembleRequest &request)
|
||||
{
|
||||
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
RETURN_DAP_ERROR("Disassemble not supported on Windows");
|
||||
#else
|
||||
auto ref = request.memoryReference;
|
||||
// ref is in the format "0x12345678", we need to convert it to a number
|
||||
if (ref.size() < 3 || ref[0] != '0' || ref[1] != 'x')
|
||||
{
|
||||
RETURN_DAP_ERROR("Invalid memoryReference");
|
||||
}
|
||||
const uint64_t req_address = std::stoull(ref.substr(2), nullptr, 16);
|
||||
const int64_t offset = request.instructionOffset.value(0);
|
||||
const VMOP *currCodePointer = (VMOP *)req_address;
|
||||
auto response = dap::DisassembleResponse();
|
||||
std::vector<std::shared_ptr<DisassemblyLine>> lines;
|
||||
m_pexCache->GetDisassemblyLines(currCodePointer, offset, request.instructionCount, lines);
|
||||
std::shared_ptr<Binary> bin;
|
||||
std::vector<std::string> instruction_addrs;
|
||||
for (auto &line : lines)
|
||||
{
|
||||
auto instruction = dap::DisassembledInstruction();
|
||||
instruction.instruction = line->instruction;
|
||||
instruction.address = StringFormat("%p", line->address);
|
||||
instruction_addrs.push_back(instruction.address);
|
||||
instruction.line = line->line;
|
||||
if (line->line != line->endLine && line->endLine > 0)
|
||||
{
|
||||
instruction.endLine = line->endLine;
|
||||
}
|
||||
|
||||
// TODO: turn this back on, vscode doesn't like it currently
|
||||
// only map the source for the first instruction, or if the source location has changed
|
||||
// if (!bin || bin->sourceData.sourceReference.value(-1) != line->ref){
|
||||
bin = m_pexCache->GetCachedScript(line->ref);
|
||||
if (bin)
|
||||
{
|
||||
instruction.location = bin->GetDapSource();
|
||||
}
|
||||
// }
|
||||
instruction.instructionBytes = line->bytes;
|
||||
response.instructions.push_back(instruction);
|
||||
}
|
||||
return response;
|
||||
#endif
|
||||
}
|
||||
|
||||
dap::ResponseOrError<dap::SetExceptionBreakpointsResponse> ZScriptDebugger::SetExceptionBreakpoints(const dap::SetExceptionBreakpointsRequest &request)
|
||||
{
|
||||
auto response = dap::SetExceptionBreakpointsResponse();
|
||||
response.breakpoints = m_executionManager->SetExceptionBreakpointFilters(request.filters);
|
||||
return response;
|
||||
}
|
||||
|
||||
dap::ResponseOrError<dap::ModulesResponse> ZScriptDebugger::Modules(const dap::ModulesRequest &request)
|
||||
{
|
||||
auto response = dap::ModulesResponse();
|
||||
response.modules = m_pexCache->GetModules();
|
||||
if (request.startModule.has_value()){
|
||||
response.modules = {response.modules.begin() + request.startModule.value(), response.modules.end()};
|
||||
}
|
||||
if (request.moduleCount.has_value()){
|
||||
response.modules = {response.modules.begin(), response.modules.begin() + request.moduleCount.value()};
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
dap::ResponseOrError<dap::EvaluateResponse> ZScriptDebugger::Evaluate(const dap::EvaluateRequest &request)
|
||||
{
|
||||
// get the type of evaluate
|
||||
auto context = request.context.value("repl");
|
||||
auto response = dap::EvaluateResponse();
|
||||
dap::Variable variable;
|
||||
bool found = false;
|
||||
auto isNonVariableContext = context != "variables";
|
||||
|
||||
auto TryPath = [&](const std::string &path){
|
||||
std::shared_ptr<StateNodeBase> node;
|
||||
if(m_runtimeState->ResolveStateByPath(path, node, isNonVariableContext)){
|
||||
auto asVariableSerializable = std::dynamic_pointer_cast<IProtocolVariableSerializable>(node);
|
||||
if(asVariableSerializable && asVariableSerializable->SerializeToProtocol(variable)){
|
||||
found = true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
if (context == "variables"){
|
||||
isNonVariableContext = false;
|
||||
} else {
|
||||
isNonVariableContext = true;
|
||||
}
|
||||
if (context == "variables" || context == "hover" || context == "watch" || (context == "repl" && m_executionManager->IsPaused()))
|
||||
{
|
||||
int64_t frameId = request.frameId.value(0);
|
||||
int64_t evalLineNumber = request.line.value(-1);
|
||||
std::shared_ptr<StateNodeBase> _frameNode;
|
||||
if( m_runtimeState->ResolveStateById(frameId, _frameNode)){
|
||||
auto frameNode = std::dynamic_pointer_cast<StackFrameStateNode>(_frameNode);
|
||||
if (!frameNode){
|
||||
RETURN_DAP_ERROR(StringFormat("Could not find frameId %d", frameId).c_str());
|
||||
}
|
||||
auto frameNodePath = m_runtimeState->GetPathById(frameNode->GetId());
|
||||
if(frameNodePath.empty()){
|
||||
RETURN_DAP_ERROR(StringFormat("Could not find frameId %d", frameId).c_str());
|
||||
}
|
||||
// try locals first
|
||||
std::string localsPath = StringFormat("%s.%s", frameNodePath.c_str(), StackFrameStateNode::LOCAL_SCOPE_NAME);
|
||||
std::string path;
|
||||
int64_t funcStartingLineNumber = -1;
|
||||
|
||||
auto stackFrame = frameNode->GetStackFrame();
|
||||
if (stackFrame && !IsFunctionNative(stackFrame->Func)){
|
||||
auto vmscriptfunc = GetVMScriptFunction(stackFrame->Func);
|
||||
if (vmscriptfunc){
|
||||
funcStartingLineNumber = vmscriptfunc->PCToLine(vmscriptfunc->Code);
|
||||
if (evalLineNumber == -1){
|
||||
evalLineNumber = vmscriptfunc->PCToLine(stackFrame->PC);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<LocalScopeStateNode> localScope;
|
||||
{
|
||||
std::shared_ptr<StateNodeBase> _localScopeNodeBase;
|
||||
if (m_runtimeState->ResolveStateByPath(localsPath, _localScopeNodeBase, true)){
|
||||
localScope = std::dynamic_pointer_cast<LocalScopeStateNode>(_localScopeNodeBase);
|
||||
}
|
||||
}
|
||||
std::vector<std::string> localChildrenNames;
|
||||
if (localScope){
|
||||
localScope->GetChildNames(localChildrenNames);
|
||||
caseless_path_set localChildrenNamesSet(localChildrenNames.begin(), localChildrenNames.end());
|
||||
|
||||
path = StringFormat("%s.%s", localsPath.c_str(), request.expression.c_str());
|
||||
if(!TryPath(path)){
|
||||
RETURN_DAP_ERROR(StringFormat("Could not serialize variable %s", request.expression.c_str()).c_str());
|
||||
}
|
||||
|
||||
if (!found && evalLineNumber > -1 && funcStartingLineNumber <= evalLineNumber && request.expression.find(" @") == std::string::npos){
|
||||
// try @ line number`
|
||||
std::string qualName;
|
||||
for (auto &childName : localChildrenNames) {
|
||||
// check if the child name contains an @ and starts with the expression
|
||||
if (childName.find(" @") != std::string::npos && CaseInsensitiveFind(childName, request.expression) == 0){
|
||||
// get the line number from the child name
|
||||
auto lineNum = LocalScopeStateNode::GetLineFromLineQualifiedName(childName);
|
||||
if (lineNum <= evalLineNumber){
|
||||
qualName = childName;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!qualName.empty()){
|
||||
path = StringFormat("%s.%s", localsPath.c_str(), qualName.c_str());
|
||||
if(!TryPath(path)){
|
||||
RETURN_DAP_ERROR(StringFormat("Could not serialize variable %s", request.expression.c_str()).c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!found && request.expression.find("self.") != 0){
|
||||
// if the first part isn't self, and the current function on the stack is an action or method, try "locals.self"
|
||||
if (stackFrame && (IsFunctionHasSelf(stackFrame->Func))){
|
||||
path = StringFormat("%s.self.%s", localsPath.c_str(), request.expression.c_str());
|
||||
if(!TryPath(path)){
|
||||
RETURN_DAP_ERROR(StringFormat("Could not serialize variable %s", request.expression.c_str()).c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// try globals next
|
||||
if (!found && !TryPath(StringFormat("%s.%s.%s", frameNodePath.c_str(), StackFrameStateNode::GLOBALS_SCOPE_NAME, request.expression.c_str()))){
|
||||
RETURN_DAP_ERROR(StringFormat("Could not serialize variable %s", request.expression.c_str()).c_str());
|
||||
}
|
||||
// cvars?
|
||||
if (!found && context != "variables" && !TryPath(StringFormat("%s.%s.%s", frameNodePath.c_str(), StackFrameStateNode::CVAR_SCOPE_NAME, request.expression.c_str()))){
|
||||
RETURN_DAP_ERROR(StringFormat("Could not serialize variable %s", request.expression.c_str()).c_str());
|
||||
}
|
||||
}
|
||||
if (found){
|
||||
response.result = variable.value;
|
||||
response.variablesReference = variable.variablesReference;
|
||||
response.type = variable.type;
|
||||
response.namedVariables = variable.namedVariables;
|
||||
response.indexedVariables = variable.indexedVariables;
|
||||
response.memoryReference = variable.memoryReference;
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
if (context == "repl" && !m_executionManager->IsPaused())
|
||||
{
|
||||
// TODO: This isn't safe to do from the debugger thread, figure out a way to safely dispatch to the main thread later
|
||||
#if 0
|
||||
// we don't support repl commands when not paused
|
||||
auto args = Split(request.expression, " ");
|
||||
if (args.size() == 0){
|
||||
return dap::Error(StringFormat("No command provided").c_str());
|
||||
}
|
||||
auto cmdstr = args.front();
|
||||
FConsoleCommand *cmd = FConsoleCommand::FindByName(args.front().c_str());
|
||||
if (cmd)
|
||||
{
|
||||
bool unsafe = false;
|
||||
if (cmd->IsAlias())
|
||||
{
|
||||
FUnsafeConsoleAlias *alias = dynamic_cast<FUnsafeConsoleAlias *>(cmd);
|
||||
unsafe = alias != nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
FUnsafeConsoleCommand *unsafeCmd = dynamic_cast<FUnsafeConsoleCommand *>(cmd);
|
||||
unsafe = unsafeCmd != nullptr;
|
||||
}
|
||||
if (unsafe)
|
||||
{
|
||||
return dap::Error(StringFormat("Cannot run command %s from debugger (unsafe context)!", cmdstr.c_str()).c_str());
|
||||
}
|
||||
|
||||
FCommandLine cmdline(request.expression.c_str());
|
||||
cmd->Run(cmdline, 0);
|
||||
// there's no response for this; the output will be in the debug console buffer
|
||||
return response;
|
||||
}
|
||||
|
||||
// try a c_var?
|
||||
auto cvar = FindConsoleVariable(cmdstr);
|
||||
if (cvar){
|
||||
|
||||
if (args.size() > 1){
|
||||
if (cmdstr == "vm_debug" || cmdstr == "vm_debug_port"){
|
||||
return dap::Error(StringFormat("Refusing change %s while debugging!", cmdstr.c_str()).c_str());
|
||||
}
|
||||
if (cvar->GetFlags() & CVAR_UNSAFECONTEXT) {
|
||||
return dap::Error(StringFormat("Cannot change cvar %s from debugger!", cmdstr.c_str()).c_str());
|
||||
}
|
||||
auto val = request.expression.substr(request.expression.find_first_of(" ") + 1);
|
||||
// trim whitespace
|
||||
while (val[0] == ' '){
|
||||
val = val.substr(1);
|
||||
}
|
||||
while (val[val.size() - 1] == ' '){
|
||||
val = val.substr(0, val.size() - 1);
|
||||
}
|
||||
cvar->SetGenericRep(val.c_str(), CVAR_String);
|
||||
} else {
|
||||
auto var = CVarStateNode::ToVariable(cvar);
|
||||
response.result = var.value;
|
||||
response.type = var.type;
|
||||
response.namedVariables = var.namedVariables;
|
||||
response.indexedVariables = var.indexedVariables;
|
||||
response.presentationHint = var.presentationHint;
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
return dap::Error(StringFormat("Command %s not found!", request.expression.c_str()).c_str());
|
||||
#endif
|
||||
return dap::Error("Cannot run evaluate while running!");
|
||||
}
|
||||
return dap::Error(StringFormat("Could not evaluate expression %s", request.expression.c_str()).c_str());
|
||||
|
||||
}
|
||||
|
||||
} // namespace DebugServer
|
||||
104
src/common/scripting/dap/ZScriptDebugger.h
Normal file
104
src/common/scripting/dap/ZScriptDebugger.h
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
#pragma once
|
||||
|
||||
#include <dap/protocol.h>
|
||||
#include <dap/session.h>
|
||||
#include <dap/traits.h>
|
||||
#include "RuntimeEvents.h"
|
||||
#include "PexCache.h"
|
||||
#include "BreakpointManager.h"
|
||||
#include "DebugExecutionManager.h"
|
||||
#include "IdMap.h"
|
||||
#include "Protocol/struct_extensions.h"
|
||||
|
||||
#include <thread>
|
||||
|
||||
namespace DebugServer
|
||||
{
|
||||
enum DisconnectAction
|
||||
{
|
||||
DisconnectDefault, // Attach -> Detach, Launch -> Terminate
|
||||
DisconnectTerminate,
|
||||
DisconnectDetach
|
||||
};
|
||||
|
||||
enum VariablesFilter
|
||||
{
|
||||
VariablesNamed,
|
||||
VariablesIndexed,
|
||||
VariablesBoth
|
||||
};
|
||||
class ZScriptDebugger
|
||||
{
|
||||
template <typename T> using IsEvent = dap::traits::EnableIfIsType<dap::Event, T>;
|
||||
public:
|
||||
ZScriptDebugger();
|
||||
~ZScriptDebugger();
|
||||
|
||||
void StartSession(std::shared_ptr<dap::Session> session);
|
||||
bool EndSession(bool closed = false);
|
||||
bool IsJustMyCode() const { return false; }
|
||||
void SetJustMyCode(bool enable) { }
|
||||
dap::ResponseOrError<dap::EvaluateResponse> Evaluate(const dap::EvaluateRequest &request);
|
||||
;
|
||||
template <typename T, typename = IsEvent<T>> void SendEvent(const T &event);
|
||||
bool IsEndingSession();
|
||||
int GetLastStoppedThreadId() { return 0; }
|
||||
|
||||
dap::ResponseOrError<dap::InitializeResponse> Initialize(const dap::InitializeRequest &request);
|
||||
dap::ResponseOrError<dap::LaunchResponse> Launch(const dap::PDSLaunchRequest &request);
|
||||
dap::ResponseOrError<dap::AttachResponse> Attach(const dap::PDSAttachRequest &request);
|
||||
dap::ResponseOrError<dap::ContinueResponse> Continue(const dap::ContinueRequest &request);
|
||||
dap::ResponseOrError<dap::PauseResponse> Pause(const dap::PauseRequest &request);
|
||||
dap::ResponseOrError<dap::ThreadsResponse> GetThreads(const dap::ThreadsRequest &request);
|
||||
dap::ResponseOrError<dap::SetBreakpointsResponse> SetBreakpoints(const dap::SetBreakpointsRequest &request);
|
||||
dap::ResponseOrError<dap::SetFunctionBreakpointsResponse> SetFunctionBreakpoints(const dap::SetFunctionBreakpointsRequest &request);
|
||||
dap::ResponseOrError<dap::StackTraceResponse> GetStackTrace(const dap::StackTraceRequest &request);
|
||||
dap::ResponseOrError<dap::StepInResponse> StepIn(const dap::StepInRequest &request);
|
||||
dap::ResponseOrError<dap::StepOutResponse> StepOut(const dap::StepOutRequest &request);
|
||||
dap::ResponseOrError<dap::NextResponse> Next(const dap::NextRequest &request);
|
||||
dap::ResponseOrError<dap::ScopesResponse> GetScopes(const dap::ScopesRequest &request);
|
||||
dap::ResponseOrError<dap::VariablesResponse> GetVariables(const dap::VariablesRequest &request);
|
||||
dap::ResponseOrError<dap::SourceResponse> GetSource(const dap::SourceRequest &request);
|
||||
dap::ResponseOrError<dap::LoadedSourcesResponse> GetLoadedSources(const dap::LoadedSourcesRequest &request);
|
||||
dap::ResponseOrError<dap::DisassembleResponse> Disassemble(const dap::DisassembleRequest &request);
|
||||
dap::ResponseOrError<dap::SetExceptionBreakpointsResponse> SetExceptionBreakpoints(const dap::SetExceptionBreakpointsRequest &request);
|
||||
dap::ResponseOrError<dap::SetInstructionBreakpointsResponse> SetInstructionBreakpoints(const dap::SetInstructionBreakpointsRequest &request);
|
||||
dap::ResponseOrError<dap::ModulesResponse> Modules(const dap::ModulesRequest &request);
|
||||
private:
|
||||
std::shared_ptr<IdProvider> m_idProvider;
|
||||
|
||||
std::shared_ptr<dap::Session> m_session = nullptr;
|
||||
std::shared_ptr<PexCache> m_pexCache;
|
||||
std::shared_ptr<BreakpointManager> m_breakpointManager;
|
||||
std::shared_ptr<RuntimeState> m_runtimeState;
|
||||
std::shared_ptr<DebugExecutionManager> m_executionManager;
|
||||
std::map<int, dap::Source> m_projectSources;
|
||||
std::string m_projectPath;
|
||||
std::string m_projectArchive;
|
||||
dap::InitializeRequest m_clientCaps;
|
||||
bool m_printLog = false;
|
||||
|
||||
RuntimeEvents::CreateStackEventHandle m_createStackEventHandle;
|
||||
RuntimeEvents::CleanupStackEventHandle m_cleanupStackEventHandle;
|
||||
RuntimeEvents::InstructionExecutionEventHandle m_instructionExecutionEventHandle;
|
||||
RuntimeEvents::LogEventHandle m_logEventHandle;
|
||||
RuntimeEvents::BreakpointChangedEventHandle m_breakpointChangedEventHandle;
|
||||
|
||||
|
||||
std::atomic<bool> m_endingSession = false;
|
||||
bool m_quitting = false; // Received a disconnect request with a terminateDebuggee flag; if this is true, we exit the program
|
||||
bool m_initialized
|
||||
= false; // Received initialize request; If this isn't true, we don't send events, prevents sending events before the client is ready (or if socket has been closed before initialization)
|
||||
RuntimeEvents::ExceptionThrownEventHandle m_exceptionThrownEventHandle;
|
||||
|
||||
void RegisterSessionHandlers();
|
||||
dap::Error Error(const std::string &msg);
|
||||
void EventLogged(int severity, const char *msg);
|
||||
void StackCreated(VMFrameStack *stack);
|
||||
void StackCleanedUp(uint32_t stackId);
|
||||
void InstructionExecution(VMFrameStack *stack, VMReturn *ret, int numret, const VMOP *pc);
|
||||
void CheckSourceLoaded(const std::string &scriptName);
|
||||
void BreakpointChanged(const dap::Breakpoint &bpoint, const std::string &reason);
|
||||
void ExceptionThrown(EVMAbortException reason, const std::string &message, const std::string &stackTrace);
|
||||
};
|
||||
}
|
||||
|
|
@ -80,6 +80,7 @@ static void SetNodeLine(ZCC_TreeNode *name, int line)
|
|||
ZCC_Identifier *Replaces;
|
||||
ZCC_Identifier *Sealed;
|
||||
VersionInfo Version;
|
||||
FString *DeprecationMessage;
|
||||
};
|
||||
|
||||
struct StateOpts {
|
||||
|
|
@ -232,6 +233,7 @@ class_head(X) ::= EXTEND CLASS(T) IDENTIFIER(A).
|
|||
head->Version = {0, 0};
|
||||
head->Type = nullptr;
|
||||
head->Symbol = nullptr;
|
||||
head->DeprecationMessage = nullptr;
|
||||
X = head;
|
||||
}
|
||||
|
||||
|
|
@ -247,6 +249,7 @@ class_head(X) ::= CLASS(T) IDENTIFIER(A) class_ancestry(B) class_flags(C).
|
|||
head->Version = C.Version;
|
||||
head->Type = nullptr;
|
||||
head->Symbol = nullptr;
|
||||
head->DeprecationMessage = C.DeprecationMessage;
|
||||
X = head;
|
||||
}
|
||||
|
||||
|
|
@ -255,15 +258,24 @@ class_ancestry(X) ::= . { X = NULL; }
|
|||
class_ancestry(X) ::= COLON dottable_id(A). { X = A; /*X-overwrites-A*/ }
|
||||
|
||||
%type class_flags{ClassFlagsBlock}
|
||||
class_flags(X) ::= . { X.Flags = 0; X.Replaces = NULL; X.Version = {0,0}; X.Sealed = NULL; }
|
||||
class_flags(X) ::= class_flags(A) ABSTRACT. { X.Flags = A.Flags | ZCC_Abstract; X.Replaces = A.Replaces; X.Version = A.Version; X.Sealed = A.Sealed; }
|
||||
class_flags(X) ::= class_flags(A) FINAL. { X.Flags = A.Flags | ZCC_Final; X.Replaces = A.Replaces; X.Version = A.Version; X.Sealed = A.Sealed;}
|
||||
class_flags(X) ::= class_flags(A) NATIVE. { X.Flags = A.Flags | ZCC_Native; X.Replaces = A.Replaces; X.Version = A.Version; X.Sealed = A.Sealed; }
|
||||
class_flags(X) ::= class_flags(A) UI. { X.Flags = A.Flags | ZCC_UIFlag; X.Replaces = A.Replaces; X.Version = A.Version; X.Sealed = A.Sealed; }
|
||||
class_flags(X) ::= class_flags(A) PLAY. { X.Flags = A.Flags | ZCC_Play; X.Replaces = A.Replaces; X.Version = A.Version; X.Sealed = A.Sealed; }
|
||||
class_flags(X) ::= class_flags(A) REPLACES dottable_id(B). { X.Flags = A.Flags; X.Replaces = B; X.Version = A.Version; X.Sealed = A.Sealed; }
|
||||
class_flags(X) ::= class_flags(A) VERSION LPAREN STRCONST(C) RPAREN. { X.Flags = A.Flags | ZCC_Version; X.Replaces = A.Replaces; X.Version = C.String->GetChars(); X.Sealed = A.Sealed; }
|
||||
class_flags(X) ::= class_flags(A) SEALED LPAREN states_opt(B) RPAREN. { X.Flags = A.Flags | ZCC_Sealed; X.Replaces = A.Replaces; X.Version = A.Version; X.Sealed = B; }
|
||||
class_flags(X) ::= . { X.Flags = 0; X.Replaces = NULL; X.Version = {0,0}; X.Sealed = NULL; X.DeprecationMessage = NULL; }
|
||||
class_flags(X) ::= class_flags(A) ABSTRACT. { X.Flags = A.Flags | ZCC_Abstract; X.Replaces = A.Replaces; X.Version = A.Version; X.Sealed = A.Sealed; X.DeprecationMessage = A.DeprecationMessage; }
|
||||
class_flags(X) ::= class_flags(A) FINAL. { X.Flags = A.Flags | ZCC_Final; X.Replaces = A.Replaces; X.Version = A.Version; X.Sealed = A.Sealed; X.DeprecationMessage = A.DeprecationMessage; }
|
||||
class_flags(X) ::= class_flags(A) NATIVE. { X.Flags = A.Flags | ZCC_Native; X.Replaces = A.Replaces; X.Version = A.Version; X.Sealed = A.Sealed; X.DeprecationMessage = A.DeprecationMessage; }
|
||||
class_flags(X) ::= class_flags(A) UI. { X.Flags = A.Flags | ZCC_UIFlag; X.Replaces = A.Replaces; X.Version = A.Version; X.Sealed = A.Sealed; X.DeprecationMessage = A.DeprecationMessage; }
|
||||
class_flags(X) ::= class_flags(A) PLAY. { X.Flags = A.Flags | ZCC_Play; X.Replaces = A.Replaces; X.Version = A.Version; X.Sealed = A.Sealed; X.DeprecationMessage = A.DeprecationMessage; }
|
||||
class_flags(X) ::= class_flags(A) REPLACES dottable_id(B). { X.Flags = A.Flags; X.Replaces = B; X.Version = A.Version; X.Sealed = A.Sealed; X.DeprecationMessage = A.DeprecationMessage; }
|
||||
class_flags(X) ::= class_flags(A) VERSION LPAREN STRCONST(C) RPAREN. { X.Flags = A.Flags | ZCC_Version; X.Replaces = A.Replaces; X.Version = C.String->GetChars(); X.Sealed = A.Sealed; X.DeprecationMessage = A.DeprecationMessage; }
|
||||
class_flags(X) ::= class_flags(A) SEALED LPAREN states_opt(B) RPAREN. { X.Flags = A.Flags | ZCC_Sealed; X.Replaces = A.Replaces; X.Version = A.Version; X.Sealed = B; X.DeprecationMessage = A.DeprecationMessage; }
|
||||
|
||||
class_flags(X) ::= class_flags(A) DEPRECATED LPAREN STRCONST(C) opt_deprecation_message(D) RPAREN.
|
||||
{
|
||||
X.Flags = A.Flags | ZCC_Deprecated;
|
||||
X.Replaces = A.Replaces;
|
||||
X.Version = C.String->GetChars();
|
||||
X.Sealed = A.Sealed;
|
||||
X.DeprecationMessage = D.String;
|
||||
}
|
||||
|
||||
/*----- Dottable Identifier -----*/
|
||||
// This can be either a single identifier or two identifiers connected by a .
|
||||
|
|
@ -412,6 +424,7 @@ struct_def(X) ::= STRUCT(T) IDENTIFIER(A) struct_flags(S) LBRACE opt_struct_body
|
|||
def->Symbol = nullptr;
|
||||
def->Version = S.Version;
|
||||
def->Flags = S.Flags;
|
||||
def->DeprecationMessage = S.DeprecationMessage;
|
||||
X = def;
|
||||
}
|
||||
|
||||
|
|
@ -422,18 +435,27 @@ struct_def(X) ::= EXTEND STRUCT(T) IDENTIFIER(A) LBRACE opt_struct_body(B) RBRAC
|
|||
def->Body = B;
|
||||
def->Type = nullptr;
|
||||
def->Symbol = nullptr;
|
||||
def->Version = {0, 0};
|
||||
def->Flags = ZCC_Extension;
|
||||
def->DeprecationMessage = nullptr;
|
||||
X = def;
|
||||
}
|
||||
|
||||
%type struct_flags{ClassFlagsBlock}
|
||||
struct_flags(X) ::= . { X.Flags = 0; X.Version = {0, 0}; }
|
||||
struct_flags(X) ::= struct_flags(A) UI. { X.Flags = A.Flags | ZCC_UIFlag; X.Version = A.Version; }
|
||||
struct_flags(X) ::= struct_flags(A) PLAY. { X.Flags = A.Flags | ZCC_Play; X.Version = A.Version; }
|
||||
struct_flags(X) ::= struct_flags(A) CLEARSCOPE. { X.Flags = A.Flags | ZCC_ClearScope; X.Version = A.Version; }
|
||||
struct_flags(X) ::= struct_flags(A) NATIVE. { X.Flags = A.Flags | ZCC_Native; X.Version = A.Version; }
|
||||
struct_flags(X) ::= struct_flags(A) UNSAFE LPAREN INTERNAL RPAREN. { X.Flags = A.Flags | ZCC_VMInternalStruct; X.Version = A.Version; }
|
||||
struct_flags(X) ::= struct_flags(A) VERSION LPAREN STRCONST(C) RPAREN. { X.Flags = A.Flags | ZCC_Version; X.Version = C.String->GetChars(); }
|
||||
struct_flags(X) ::= . { X.Flags = 0; X.Version = {0, 0}; X.DeprecationMessage = NULL; }
|
||||
struct_flags(X) ::= struct_flags(A) UI. { X.Flags = A.Flags | ZCC_UIFlag; X.Version = A.Version; X.DeprecationMessage = A.DeprecationMessage; }
|
||||
struct_flags(X) ::= struct_flags(A) PLAY. { X.Flags = A.Flags | ZCC_Play; X.Version = A.Version; X.DeprecationMessage = A.DeprecationMessage; }
|
||||
struct_flags(X) ::= struct_flags(A) CLEARSCOPE. { X.Flags = A.Flags | ZCC_ClearScope; X.Version = A.Version; X.DeprecationMessage = A.DeprecationMessage; }
|
||||
struct_flags(X) ::= struct_flags(A) NATIVE. { X.Flags = A.Flags | ZCC_Native; X.Version = A.Version; X.DeprecationMessage = A.DeprecationMessage; }
|
||||
struct_flags(X) ::= struct_flags(A) UNSAFE LPAREN INTERNAL RPAREN. { X.Flags = A.Flags | ZCC_VMInternalStruct; X.Version = A.Version; X.DeprecationMessage = A.DeprecationMessage; }
|
||||
struct_flags(X) ::= struct_flags(A) VERSION LPAREN STRCONST(C) RPAREN. { X.Flags = A.Flags | ZCC_Version; X.Version = C.String->GetChars(); X.DeprecationMessage = A.DeprecationMessage; }
|
||||
|
||||
struct_flags(X) ::= struct_flags(A) DEPRECATED LPAREN STRCONST(C) opt_deprecation_message(D) RPAREN.
|
||||
{
|
||||
X.Flags = A.Flags | ZCC_Deprecated;
|
||||
X.Version = C.String->GetChars();
|
||||
X.DeprecationMessage = D.String;
|
||||
}
|
||||
|
||||
opt_struct_body(X) ::= . { X = NULL; }
|
||||
opt_struct_body(X) ::= struct_body(X).
|
||||
|
|
@ -1369,10 +1391,12 @@ decl_flag(X) ::= VARARG(T). { X.Int = ZCC_VarArg; X.SourceLoc = T.SourceLoc;
|
|||
decl_flag(X) ::= UI(T). { X.Int = ZCC_UIFlag; X.SourceLoc = T.SourceLoc; }
|
||||
decl_flag(X) ::= PLAY(T). { X.Int = ZCC_Play; X.SourceLoc = T.SourceLoc; }
|
||||
decl_flag(X) ::= CLEARSCOPE(T). { X.Int = ZCC_ClearScope; X.SourceLoc = T.SourceLoc; }
|
||||
decl_flag(X) ::= UNSAFE(T) LPAREN CLEARSCOPE RPAREN. { X.Int = ZCC_UnsafeClearScope; X.SourceLoc = T.SourceLoc; }
|
||||
decl_flag(X) ::= VIRTUALSCOPE(T). { X.Int = ZCC_VirtualScope; X.SourceLoc = T.SourceLoc; }
|
||||
|
||||
func_const(X) ::= . { X.Int = 0; X.SourceLoc = stat->sc->GetMessageLine(); }
|
||||
func_const(X) ::= CONST(T). { X.Int = ZCC_FuncConst; X.SourceLoc = T.SourceLoc; }
|
||||
func_const(X) ::= . { X.Int = 0; X.SourceLoc = stat->sc->GetMessageLine(); }
|
||||
func_const(X) ::= CONST(T). { X.Int = ZCC_FuncConst; X.SourceLoc = T.SourceLoc; }
|
||||
func_const(X) ::= UNSAFE(T) LPAREN CONST RPAREN. { X.Int = ZCC_FuncConstUnsafe; X.SourceLoc = T.SourceLoc; }
|
||||
|
||||
opt_func_body(X) ::= SEMICOLON. { X = NULL; }
|
||||
opt_func_body(X) ::= function_body(X).
|
||||
|
|
|
|||
|
|
@ -756,6 +756,13 @@ void ZCCCompiler::CreateStructTypes()
|
|||
s->strct->Type->mVersion = s->strct->Version;
|
||||
}
|
||||
|
||||
if (s->strct->Flags & ZCC_Deprecated)
|
||||
{
|
||||
s->strct->Type->mVersion = s->strct->Version;
|
||||
s->strct->Type->TypeDeprecated = true;
|
||||
s->strct->Type->mDeprecationMessage = s->strct->DeprecationMessage ? *s->strct->DeprecationMessage : "";
|
||||
}
|
||||
|
||||
if (s->strct->Flags & ZCC_VMInternalStruct)
|
||||
{
|
||||
if(fileSystem.GetFileContainer(Lump) == 0)
|
||||
|
|
@ -909,6 +916,7 @@ void ZCCCompiler::CreateClassTypes()
|
|||
else
|
||||
{
|
||||
c->cls->Type = NewClassType(newclass, AST.FileNo);
|
||||
newclass->SourceLumpName = *c->cls->SourceName;
|
||||
DPrintf(DMSG_SPAMMY, "Created class %s with parent %s\n", c->Type()->TypeName.GetChars(), c->ClassType()->ParentClass->TypeName.GetChars());
|
||||
}
|
||||
}
|
||||
|
|
@ -931,6 +939,13 @@ void ZCCCompiler::CreateClassTypes()
|
|||
{
|
||||
c->Type()->mVersion = c->cls->Version;
|
||||
}
|
||||
|
||||
if (c->cls->Flags & ZCC_Deprecated)
|
||||
{
|
||||
c->Type()->mVersion = c->cls->Version;
|
||||
c->Type()->TypeDeprecated = true;
|
||||
c->Type()->mDeprecationMessage = c->cls->DeprecationMessage ? *c->cls->DeprecationMessage : "";
|
||||
}
|
||||
|
||||
|
||||
if (c->cls->Flags & ZCC_Final)
|
||||
|
|
@ -1500,8 +1515,8 @@ bool ZCCCompiler::CompileFields(PContainerType *type, TArray<ZCC_VarDeclarator *
|
|||
|
||||
// For structs only allow 'deprecated', for classes exclude function qualifiers.
|
||||
int notallowed = forstruct?
|
||||
ZCC_Latent | ZCC_Final | ZCC_Action | ZCC_Static | ZCC_FuncConst | ZCC_Abstract | ZCC_Virtual | ZCC_Override | ZCC_Meta | ZCC_Extension | ZCC_VirtualScope | ZCC_ClearScope :
|
||||
ZCC_Latent | ZCC_Final | ZCC_Action | ZCC_Static | ZCC_FuncConst | ZCC_Abstract | ZCC_Virtual | ZCC_Override | ZCC_Extension | ZCC_VirtualScope | ZCC_ClearScope;
|
||||
ZCC_Latent | ZCC_Final | ZCC_Action | ZCC_Static | ZCC_FuncConst | ZCC_FuncConstUnsafe | ZCC_Abstract | ZCC_Virtual | ZCC_Override | ZCC_Meta | ZCC_Extension | ZCC_VirtualScope | ZCC_ClearScope :
|
||||
ZCC_Latent | ZCC_Final | ZCC_Action | ZCC_Static | ZCC_FuncConst | ZCC_FuncConstUnsafe | ZCC_Abstract | ZCC_Virtual | ZCC_Override | ZCC_Extension | ZCC_VirtualScope | ZCC_ClearScope;
|
||||
|
||||
// Some internal fields need to be set to clearscope.
|
||||
if (fileSystem.GetFileContainer(Lump) == 0) notallowed &= ~ZCC_ClearScope;
|
||||
|
|
@ -1533,7 +1548,7 @@ bool ZCCCompiler::CompileFields(PContainerType *type, TArray<ZCC_VarDeclarator *
|
|||
varflags = FScopeBarrier::ChangeSideInFlags(varflags, FScopeBarrier::Side_UI);
|
||||
if (field->Flags & ZCC_Play)
|
||||
varflags = FScopeBarrier::ChangeSideInFlags(varflags, FScopeBarrier::Side_Play);
|
||||
if (field->Flags & ZCC_ClearScope)
|
||||
if (field->Flags & (ZCC_ClearScope | ZCC_UnsafeClearScope))
|
||||
varflags = FScopeBarrier::ChangeSideInFlags(varflags, FScopeBarrier::Side_PlainData);
|
||||
}
|
||||
else
|
||||
|
|
@ -2189,7 +2204,16 @@ PType *ZCCCompiler::ResolveUserType(PType *outertype, ZCC_BasicType *type, ZCC_I
|
|||
if (sym != nullptr && sym->IsKindOf(RUNTIME_CLASS(PSymbolType)))
|
||||
{
|
||||
auto ptype = static_cast<PSymbolType *>(sym)->Type;
|
||||
if (ptype->mVersion > mVersion)
|
||||
|
||||
if (ptype->TypeDeprecated)
|
||||
{
|
||||
if(ptype->mVersion <= mVersion && !outertype->TypeDeprecated && fileSystem.GetFileContainer(Lump) > 0)
|
||||
{
|
||||
Warn(type, "Type %s is deprecated since ZScript version %d.%d.%d%s%s",
|
||||
FName(type->UserType->Id).GetChars(), mVersion.major, mVersion.minor, mVersion.revision, ptype->mDeprecationMessage.IsEmpty() ? "" : ": ", ptype->mDeprecationMessage.GetChars());
|
||||
}
|
||||
}
|
||||
else if (ptype->mVersion > mVersion)
|
||||
{
|
||||
Error(type, "Type %s not accessible to ZScript version %d.%d.%d", FName(type->UserType->Id).GetChars(), mVersion.major, mVersion.minor, mVersion.revision);
|
||||
return TypeError;
|
||||
|
|
@ -2348,7 +2372,7 @@ void ZCCCompiler::SetImplicitArgs(TArray<PType*>* args, TArray<uint32_t>* argfla
|
|||
if (funcflags & VARF_Method)
|
||||
{
|
||||
// implied self pointer
|
||||
if (args != nullptr) args->Push(NewPointer(cls, !!(funcflags & VARF_ReadOnly)));
|
||||
if (args != nullptr) args->Push(NewPointer(cls, (funcflags & VARF_SafeConst)));
|
||||
if (argflags != nullptr) argflags->Push(VARF_Implicit | VARF_ReadOnly);
|
||||
if (argnames != nullptr) argnames->Push(NAME_self);
|
||||
}
|
||||
|
|
@ -2479,7 +2503,9 @@ void ZCCCompiler::CompileFunction(ZCC_StructWork *c, ZCC_FuncDeclarator *f, bool
|
|||
if (f->Flags & ZCC_Override) varflags |= VARF_Override;
|
||||
if (f->Flags & ZCC_Abstract) varflags |= VARF_Abstract;
|
||||
if (f->Flags & ZCC_VarArg) varflags |= VARF_VarArg;
|
||||
if (f->Flags & ZCC_FuncConst) varflags |= VARF_ReadOnly; // FuncConst method is internally marked as VARF_ReadOnly
|
||||
if (f->Flags & ZCC_FuncConst) varflags |= (mVersion >= MakeVersion(4, 15, 1) ? VARF_ReadOnly | VARF_SafeConst : VARF_ReadOnly); // FuncConst method is internally marked as VARF_ReadOnly
|
||||
if (f->Flags & ZCC_FuncConstUnsafe) varflags |= VARF_ReadOnly;
|
||||
|
||||
if (mVersion >= MakeVersion(2, 4, 0))
|
||||
{
|
||||
if (c->Type()->ScopeFlags & Scope_UI)
|
||||
|
|
|
|||
|
|
@ -65,7 +65,9 @@ enum
|
|||
ZCC_Version = 1 << 21,
|
||||
ZCC_Internal = 1 << 22,
|
||||
ZCC_Sealed = 1 << 23,
|
||||
ZCC_VMInternalStruct = 1 << 24,
|
||||
ZCC_FuncConstUnsafe = 1 << 24,
|
||||
ZCC_UnsafeClearScope = 1 << 25,
|
||||
ZCC_VMInternalStruct = 1 << 26,
|
||||
};
|
||||
|
||||
// Function parameter modifiers
|
||||
|
|
@ -242,6 +244,7 @@ struct ZCC_Struct : ZCC_NamedNode
|
|||
ZCC_TreeNode *Body;
|
||||
PContainerType *Type;
|
||||
VersionInfo Version;
|
||||
FString *DeprecationMessage;
|
||||
};
|
||||
|
||||
struct ZCC_Property : ZCC_NamedNode
|
||||
|
|
|
|||
|
|
@ -564,6 +564,52 @@ DEFINE_ACTION_FUNCTION_NATIVE(FStringStruct, Substitute, StringSubst)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int StringCompare(FString *self, const FString &other)
|
||||
{
|
||||
return self->Compare(other);
|
||||
}
|
||||
|
||||
DEFINE_ACTION_FUNCTION_NATIVE(FStringStruct, Compare, StringCompare)
|
||||
{
|
||||
PARAM_SELF_STRUCT_PROLOGUE(FString);
|
||||
PARAM_STRING(other);
|
||||
ACTION_RETURN_INT(StringCompare(self, other));
|
||||
}
|
||||
|
||||
static int StringCompareNoCase(FString *self, const FString &other)
|
||||
{
|
||||
return self->CompareNoCase(other);
|
||||
}
|
||||
|
||||
DEFINE_ACTION_FUNCTION_NATIVE(FStringStruct, CompareNoCase, StringCompareNoCase)
|
||||
{
|
||||
PARAM_SELF_STRUCT_PROLOGUE(FString);
|
||||
PARAM_STRING(other);
|
||||
ACTION_RETURN_INT(StringCompareNoCase(self, other));
|
||||
}
|
||||
|
||||
static int StringIsEmpty(FString *self)
|
||||
{
|
||||
return self->IsEmpty();
|
||||
}
|
||||
|
||||
DEFINE_ACTION_FUNCTION_NATIVE(FStringStruct, IsEmpty, StringIsEmpty)
|
||||
{
|
||||
PARAM_SELF_STRUCT_PROLOGUE(FString);
|
||||
ACTION_RETURN_INT(StringIsEmpty(self));
|
||||
}
|
||||
|
||||
static int StringIsNotEmpty(FString *self)
|
||||
{
|
||||
return self->IsNotEmpty();
|
||||
}
|
||||
|
||||
DEFINE_ACTION_FUNCTION_NATIVE(FStringStruct, IsNotEmpty, StringIsNotEmpty)
|
||||
{
|
||||
PARAM_SELF_STRUCT_PROLOGUE(FString);
|
||||
ACTION_RETURN_INT(StringIsNotEmpty(self));
|
||||
}
|
||||
|
||||
static void StringStripRight(FString* self, const FString& junk)
|
||||
{
|
||||
if (junk.IsNotEmpty()) self->StripRight(junk);
|
||||
|
|
|
|||
|
|
@ -1283,6 +1283,7 @@ DEFINE_GLOBAL_NAMED(PClass::AllClasses, AllClasses)
|
|||
DEFINE_GLOBAL(AllServices)
|
||||
|
||||
DEFINE_GLOBAL(Bindings)
|
||||
DEFINE_GLOBAL(DoubleBindings)
|
||||
DEFINE_GLOBAL(AutomapBindings)
|
||||
DEFINE_GLOBAL(generic_ui)
|
||||
|
||||
|
|
@ -1396,40 +1397,6 @@ DEFINE_ACTION_FUNCTION_NATIVE(_QuatStruct, SLerp, QuatSLerp)
|
|||
ACTION_RETURN_QUAT(quat);
|
||||
}
|
||||
|
||||
void QuatConjugate(
|
||||
double x, double y, double z, double w,
|
||||
DQuaternion* pquat
|
||||
)
|
||||
{
|
||||
*pquat = DQuaternion(x, y, z, w).Conjugate();
|
||||
}
|
||||
|
||||
DEFINE_ACTION_FUNCTION_NATIVE(_QuatStruct, Conjugate, QuatConjugate)
|
||||
{
|
||||
PARAM_SELF_STRUCT_PROLOGUE(DQuaternion);
|
||||
|
||||
DQuaternion quat;
|
||||
QuatConjugate(self->X, self->Y, self->Z, self->W, &quat);
|
||||
ACTION_RETURN_QUAT(quat);
|
||||
}
|
||||
|
||||
void QuatInverse(
|
||||
double x, double y, double z, double w,
|
||||
DQuaternion* pquat
|
||||
)
|
||||
{
|
||||
*pquat = DQuaternion(x, y, z, w).Inverse();
|
||||
}
|
||||
|
||||
DEFINE_ACTION_FUNCTION_NATIVE(_QuatStruct, Inverse, QuatInverse)
|
||||
{
|
||||
PARAM_SELF_STRUCT_PROLOGUE(DQuaternion);
|
||||
|
||||
DQuaternion quat;
|
||||
QuatInverse(self->X, self->Y, self->Z, self->W, &quat);
|
||||
ACTION_RETURN_QUAT(quat);
|
||||
}
|
||||
|
||||
PFunction * FindFunctionPointer(PClass * cls, int fn_name)
|
||||
{
|
||||
auto fn = dyn_cast<PFunction>(cls->FindSymbol(ENamedName(fn_name), true));
|
||||
|
|
|
|||
|
|
@ -1617,6 +1617,11 @@ void FuncMULQV3(void *result, double ax, double ay, double az, double aw, double
|
|||
*reinterpret_cast<DVector3*>(result) = DQuaternion(ax, ay, az, aw) * DVector3(bx, by, bz);
|
||||
}
|
||||
|
||||
void FuncCONJQ(void* result, double x, double y, double z, double w)
|
||||
{
|
||||
*reinterpret_cast<DQuaternion*>(result) = DQuaternion(-x, -y, -z, w);
|
||||
}
|
||||
|
||||
void JitCompiler::EmitMULQQ_RR()
|
||||
{
|
||||
auto stack = GetTemporaryVectorStackStorage();
|
||||
|
|
@ -1661,6 +1666,25 @@ void JitCompiler::EmitMULQV3_RR()
|
|||
cc.movsd(regF[A + 2], asmjit::x86::qword_ptr(tmp, 16));
|
||||
}
|
||||
|
||||
void JitCompiler::EmitCONJQ()
|
||||
{
|
||||
auto stack = GetTemporaryVectorStackStorage();
|
||||
auto tmp = newTempIntPtr();
|
||||
cc.lea(tmp, stack);
|
||||
|
||||
auto call = CreateCall<void, void*, double, double, double, double>(FuncCONJQ);
|
||||
call->setArg(0, tmp);
|
||||
call->setArg(1, regF[B + 0]);
|
||||
call->setArg(2, regF[B + 1]);
|
||||
call->setArg(3, regF[B + 2]);
|
||||
call->setArg(4, regF[B + 3]);
|
||||
|
||||
cc.movsd(regF[A + 0], asmjit::x86::qword_ptr(tmp, 0));
|
||||
cc.movsd(regF[A + 1], asmjit::x86::qword_ptr(tmp, 8));
|
||||
cc.movsd(regF[A + 2], asmjit::x86::qword_ptr(tmp, 16));
|
||||
cc.movsd(regF[A + 3], asmjit::x86::qword_ptr(tmp, 24));
|
||||
}
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// Pointer math.
|
||||
|
|
|
|||
|
|
@ -534,235 +534,301 @@ inline int VMCallAction(VMFunction *func, VMValue *params, int numparams, VMRetu
|
|||
return VMCall(func, params, numparams, results, numresults);
|
||||
}
|
||||
|
||||
template<typename T> struct VMArgTypeTrait { typedef T type; static const int ArgCount = 1; };
|
||||
template<> struct VMArgTypeTrait<DVector2> { typedef double type; static const int ArgCount = 2; };
|
||||
template<> struct VMArgTypeTrait<DVector3> { typedef double type; static const int ArgCount = 3; };
|
||||
template<> struct VMArgTypeTrait<DVector4> { typedef double type; static const int ArgCount = 4; };
|
||||
template<> struct VMArgTypeTrait<DQuaternion> { typedef double type; static const int ArgCount = 4; };
|
||||
|
||||
template<typename T> struct VMReturnTypeTrait { typedef T type; static const int ReturnCount = 1; };
|
||||
template<> struct VMReturnTypeTrait<void> { typedef void type; static const int ReturnCount = 0; };
|
||||
|
||||
template<typename T, typename... Vals>
|
||||
struct FirstTemplateValue
|
||||
{
|
||||
using type = T;
|
||||
};
|
||||
|
||||
|
||||
|
||||
void VMCheckParamCount(VMFunction* func, int retcount, int argcount);
|
||||
|
||||
template<typename RetVal>
|
||||
void VMCheckParamCount(VMFunction* func, int argcount) { return VMCheckParamCount(func, VMReturnTypeTrait<RetVal>::ReturnCount, argcount); }
|
||||
template<typename... Rets>
|
||||
void VMCheckParamCount(VMFunction* func, int argcount)
|
||||
{
|
||||
if constexpr (sizeof...(Rets) == 1)
|
||||
{
|
||||
return VMCheckParamCount(func, VMReturnTypeTrait<typename FirstTemplateValue<Rets...>::type>::ReturnCount, argcount);
|
||||
}
|
||||
else
|
||||
{
|
||||
return VMCheckParamCount(func, sizeof...(Rets), argcount);
|
||||
}
|
||||
}
|
||||
|
||||
// The type can't be mapped to ZScript automatically:
|
||||
|
||||
template<typename NativeType> void VMCheckParam(VMFunction* func, int index) = delete;
|
||||
template<typename NativeType> void VMCheckReturn(VMFunction* func) = delete;
|
||||
template<typename NativeType> void VMCheckReturn(VMFunction* func, int index) = delete;
|
||||
|
||||
// Native types we support converting to/from:
|
||||
|
||||
template<> void VMCheckParam<int>(VMFunction* func, int index);
|
||||
template<> void VMCheckParam<double>(VMFunction* func, int index);
|
||||
template<> void VMCheckParam<DVector2>(VMFunction* func, int index);
|
||||
template<> void VMCheckParam<DVector3>(VMFunction* func, int index);
|
||||
template<> void VMCheckParam<DVector4>(VMFunction* func, int index);
|
||||
template<> void VMCheckParam<DQuaternion>(VMFunction* func, int index);
|
||||
template<> void VMCheckParam<FString>(VMFunction* func, int index);
|
||||
template<> void VMCheckParam<DObject*>(VMFunction* func, int index);
|
||||
|
||||
template<> void VMCheckReturn<void>(VMFunction* func);
|
||||
template<> void VMCheckReturn<int>(VMFunction* func);
|
||||
template<> void VMCheckReturn<double>(VMFunction* func);
|
||||
template<> void VMCheckReturn<FString>(VMFunction* func);
|
||||
template<> void VMCheckReturn<DObject*>(VMFunction* func);
|
||||
template<> void VMCheckReturn<void>(VMFunction* func, int index);
|
||||
template<> void VMCheckReturn<int>(VMFunction* func, int index);
|
||||
template<> void VMCheckReturn<double>(VMFunction* func, int index);
|
||||
template<> void VMCheckReturn<DVector2>(VMFunction* func, int index);
|
||||
template<> void VMCheckReturn<DVector3>(VMFunction* func, int index);
|
||||
template<> void VMCheckReturn<DVector4>(VMFunction* func, int index);
|
||||
template<> void VMCheckReturn<DQuaternion>(VMFunction* func, int index);
|
||||
template<> void VMCheckReturn<FString>(VMFunction* func, int index);
|
||||
template<> void VMCheckReturn<DObject*>(VMFunction* func, int index);
|
||||
template<> void VMCheckReturn<void*>(VMFunction* func, int index);
|
||||
|
||||
template<typename RetVal> void VMValidateSignature(VMFunction* func)
|
||||
template<typename T>
|
||||
struct vm_decay_pointer_object
|
||||
{ // convert any pointer to a type derived from DObject into a pointer to DObject, and any other to a pointer to void
|
||||
using decayed = typename std::conditional<std::is_base_of_v<DObject, typename std::pointer_traits<T>::element_type>,
|
||||
typename std::pointer_traits<T>::template rebind<DObject>,
|
||||
typename std::pointer_traits<T>::template rebind<void>>::type;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct vm_decay_pointer_void
|
||||
{ // convert any pointer to a pointer to void
|
||||
using decayed = typename std::pointer_traits<T>::template rebind<void>;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct vm_decay_none
|
||||
{
|
||||
VMCheckParamCount<RetVal>(func, 0);
|
||||
VMCheckReturn<RetVal>(func);
|
||||
using decayed = T;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
using vm_pointer_decay = typename std::conditional<std::is_pointer_v<T>, vm_decay_pointer_object<T>, vm_decay_none<T>>::type::decayed;
|
||||
|
||||
template<typename T>
|
||||
using vm_pointer_decay_void = typename std::conditional<std::is_pointer_v<T>, vm_decay_pointer_void<T>, vm_decay_none<T>>::type::decayed;
|
||||
|
||||
template<typename RetVal, typename... Args, size_t... I>
|
||||
void VMValidateSignatureSingle(VMFunction* func, std::index_sequence<I...>)
|
||||
{
|
||||
VMCheckParamCount<RetVal>(func, sizeof...(Args));
|
||||
VMCheckReturn<vm_pointer_decay<RetVal>>(func, 0);
|
||||
(VMCheckParam<vm_pointer_decay<Args>>(func, I), ...);
|
||||
}
|
||||
|
||||
template<typename RetVal, typename P1> void VMValidateSignature(VMFunction* func)
|
||||
template<typename... Rets, typename... Args, size_t... IRets, size_t... IArgs>
|
||||
void VMValidateSignatureMulti(VMFunction* func, std::index_sequence<IRets...>, std::index_sequence<IArgs...>, Args... args)
|
||||
{
|
||||
VMCheckParamCount<RetVal>(func, 1);
|
||||
VMCheckReturn<RetVal>(func);
|
||||
VMCheckParam<P1>(func, 0);
|
||||
}
|
||||
|
||||
template<typename RetVal, typename P1, typename P2> void VMValidateSignature(VMFunction* func)
|
||||
{
|
||||
VMCheckParamCount<RetVal>(func, 2);
|
||||
VMCheckReturn<RetVal>(func);
|
||||
VMCheckParam<P1>(func, 0);
|
||||
VMCheckParam<P2>(func, 1);
|
||||
}
|
||||
|
||||
template<typename RetVal, typename P1, typename P2, typename P3> void VMValidateSignature(VMFunction* func)
|
||||
{
|
||||
VMCheckParamCount<RetVal>(func, 3);
|
||||
VMCheckReturn<RetVal>(func);
|
||||
VMCheckParam<P1>(func, 0);
|
||||
VMCheckParam<P2>(func, 1);
|
||||
VMCheckParam<P3>(func, 2);
|
||||
}
|
||||
|
||||
template<typename RetVal, typename P1, typename P2, typename P3, typename P4> void VMValidateSignature(VMFunction* func)
|
||||
{
|
||||
VMCheckParamCount<RetVal>(func, 4);
|
||||
VMCheckReturn<RetVal>(func);
|
||||
VMCheckParam<P1>(func, 0);
|
||||
VMCheckParam<P2>(func, 1);
|
||||
VMCheckParam<P3>(func, 2);
|
||||
VMCheckParam<P4>(func, 3);
|
||||
}
|
||||
|
||||
template<typename RetVal, typename P1, typename P2, typename P3, typename P4, typename P5> void VMValidateSignature(VMFunction* func)
|
||||
{
|
||||
VMCheckParamCount<RetVal>(func, 5);
|
||||
VMCheckReturn<RetVal>(func);
|
||||
VMCheckParam<P1>(func, 0);
|
||||
VMCheckParam<P2>(func, 1);
|
||||
VMCheckParam<P3>(func, 2);
|
||||
VMCheckParam<P4>(func, 3);
|
||||
VMCheckParam<P5>(func, 4);
|
||||
}
|
||||
|
||||
template<typename RetVal, typename P1, typename P2, typename P3, typename P4, typename P5, typename P6> void VMValidateSignature(VMFunction* func)
|
||||
{
|
||||
VMCheckParamCount<RetVal>(func, 6);
|
||||
VMCheckReturn<RetVal>(func);
|
||||
VMCheckParam<P1>(func, 0);
|
||||
VMCheckParam<P2>(func, 1);
|
||||
VMCheckParam<P3>(func, 2);
|
||||
VMCheckParam<P4>(func, 3);
|
||||
VMCheckParam<P5>(func, 4);
|
||||
VMCheckParam<P6>(func, 5);
|
||||
}
|
||||
|
||||
template<typename RetVal, typename P1, typename P2, typename P3, typename P4, typename P5, typename P6, typename P7> void VMValidateSignature(VMFunction* func)
|
||||
{
|
||||
VMCheckParamCount<RetVal>(func, 7);
|
||||
VMCheckReturn<RetVal>(func);
|
||||
VMCheckParam<P1>(func, 0);
|
||||
VMCheckParam<P2>(func, 1);
|
||||
VMCheckParam<P3>(func, 2);
|
||||
VMCheckParam<P4>(func, 3);
|
||||
VMCheckParam<P5>(func, 4);
|
||||
VMCheckParam<P6>(func, 5);
|
||||
VMCheckParam<P7>(func, 6);
|
||||
VMCheckParamCount<Rets...>(func, sizeof...(Args));
|
||||
(VMCheckReturn<vm_pointer_decay<Rets>>(func, IRets), ...);
|
||||
(VMCheckParam<vm_pointer_decay<Args>>(func, IArgs), ...);
|
||||
}
|
||||
|
||||
void VMCallCheckResult(VMFunction* func, VMValue* params, int numparams, VMReturn* results, int numresults);
|
||||
|
||||
template<typename RetVal, typename P1>
|
||||
typename VMReturnTypeTrait<RetVal>::type VMCallScript(VMFunction* func, P1 p1)
|
||||
|
||||
struct VMValueMulti
|
||||
{
|
||||
VMValidateSignature<RetVal, P1>(func);
|
||||
VMValue params[] = { p1 };
|
||||
RetVal resultval; VMReturn results(&resultval);
|
||||
VMCallCheckResult(func, params, 1, &results, 1);
|
||||
return resultval;
|
||||
VMValue vals[4];
|
||||
int count;
|
||||
|
||||
template<typename T>
|
||||
VMValueMulti(T val)
|
||||
{
|
||||
vals[0] = val;
|
||||
count = 1;
|
||||
}
|
||||
|
||||
VMValueMulti(DVector2 val)
|
||||
{
|
||||
vals[0] = val.X;
|
||||
vals[1] = val.Y;
|
||||
count = 2;
|
||||
}
|
||||
|
||||
VMValueMulti(DVector3 val)
|
||||
{
|
||||
vals[0] = val.X;
|
||||
vals[1] = val.Y;
|
||||
vals[2] = val.Z;
|
||||
count = 3;
|
||||
}
|
||||
|
||||
VMValueMulti(DVector4 val)
|
||||
{
|
||||
vals[0] = val.X;
|
||||
vals[1] = val.Y;
|
||||
vals[2] = val.Z;
|
||||
vals[3] = val.W;
|
||||
count = 4;
|
||||
}
|
||||
|
||||
VMValueMulti(DQuaternion val)
|
||||
{
|
||||
vals[0] = val.X;
|
||||
vals[1] = val.Y;
|
||||
vals[2] = val.Z;
|
||||
vals[3] = val.W;
|
||||
count = 4;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Args>
|
||||
constexpr int numArgs()
|
||||
{
|
||||
return (VMArgTypeTrait<Args>::ArgCount + ...);
|
||||
}
|
||||
|
||||
template<typename RetVal, typename P1, typename P2>
|
||||
typename VMReturnTypeTrait<RetVal>::type VMCallScript(VMFunction* func, P1 p1, P2 p2)
|
||||
template<typename... Args>
|
||||
constexpr bool hasVector()
|
||||
{
|
||||
VMValidateSignature<RetVal, P1, P2>(func);
|
||||
VMValue params[] = { p1, p2 };
|
||||
RetVal resultval; VMReturn results(&resultval);
|
||||
VMCallCheckResult(func, params, 2, &results, 1);
|
||||
return resultval;
|
||||
return (VMArgTypeTrait<Args>::ArgCount + ...) != sizeof...(Args);
|
||||
}
|
||||
|
||||
template<typename RetVal, typename P1, typename P2, typename P3>
|
||||
typename VMReturnTypeTrait<RetVal>::type VMCallScript(VMFunction* func, P1 p1, P2 p2, P3 p3)
|
||||
|
||||
template<typename... Args>
|
||||
typename VMReturnTypeTrait<void>::type VMCallVoid(VMFunction* func, Args... args)
|
||||
{
|
||||
VMValidateSignature<RetVal, P1, P2, P3>(func);
|
||||
VMValue params[] = { p1, p2, p3 };
|
||||
RetVal resultval; VMReturn results(&resultval);
|
||||
VMCallCheckResult(func, params, 3, &results, 1);
|
||||
return resultval;
|
||||
VMValidateSignatureSingle<void, Args...>(func, std::make_index_sequence<sizeof...(Args)>{});
|
||||
if constexpr(hasVector<Args...>())
|
||||
{
|
||||
VMValueMulti arglist[] = { args... };
|
||||
|
||||
constexpr int argCount = numArgs<Args...>();
|
||||
|
||||
VMValue params[argCount];
|
||||
|
||||
for(int i = 0, j = 0; i < sizeof...(Args); i++)
|
||||
{
|
||||
for(int k = 0; k < arglist[i].count; k++, j++)
|
||||
{
|
||||
params[j] = arglist[i].vals[k];
|
||||
}
|
||||
}
|
||||
|
||||
VMCallCheckResult(func, params, argCount, nullptr, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
VMValue params[] = { args... };
|
||||
VMCallCheckResult(func, params, sizeof...(Args), nullptr, 0);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename RetVal, typename P1, typename P2, typename P3, typename P4>
|
||||
typename VMReturnTypeTrait<RetVal>::type VMCallScript(VMFunction* func, P1 p1, P2 p2, P3 p3, P4 p4)
|
||||
template<typename RetVal, typename... Args>
|
||||
typename VMReturnTypeTrait<RetVal>::type VMCallSingle(VMFunction* func, Args... args)
|
||||
{
|
||||
VMValidateSignature<RetVal, P1, P2, P3, P4>(func);
|
||||
VMValue params[] = { p1, p2, p3, p4 };
|
||||
RetVal resultval; VMReturn results(&resultval);
|
||||
VMCallCheckResult(func, params, 4, &results, 1);
|
||||
return resultval;
|
||||
VMValidateSignatureSingle<RetVal, Args...>(func, std::make_index_sequence<sizeof...(Args)>{});
|
||||
if constexpr(hasVector<Args...>())
|
||||
{
|
||||
VMValueMulti arglist[] = { args... };
|
||||
|
||||
constexpr int argCount = numArgs<Args...>();
|
||||
|
||||
VMValue params[argCount];
|
||||
|
||||
for(int i = 0, j = 0; i < sizeof...(Args); i++)
|
||||
{
|
||||
for(int k = 0; k < arglist[i].count; k++, j++)
|
||||
{
|
||||
params[j] = arglist[i].vals[k];
|
||||
}
|
||||
}
|
||||
|
||||
vm_pointer_decay_void<RetVal> resultval; // convert any pointer to void
|
||||
VMReturn results(&resultval);
|
||||
VMCallCheckResult(func, params, argCount, &results, 1);
|
||||
return (RetVal)resultval;
|
||||
}
|
||||
else
|
||||
{
|
||||
VMValue params[] = { args... };
|
||||
|
||||
vm_pointer_decay_void<RetVal> resultval; // convert any pointer to void
|
||||
VMReturn results(&resultval);
|
||||
VMCallCheckResult(func, params, sizeof...(Args), &results, 1);
|
||||
return (RetVal)resultval;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename RetVal, typename P1, typename P2, typename P3, typename P4, typename P5>
|
||||
typename VMReturnTypeTrait<RetVal>::type VMCallScript(VMFunction* func, P1 p1, P2 p2, P3 p3, P4 p4, P5 p5)
|
||||
template<typename... Rets, typename... Args, size_t... IRets>
|
||||
std::tuple<typename VMReturnTypeTrait<Rets>::type...> VMCallMultiImpl(VMFunction* func, std::index_sequence<IRets...> retsSeq, Args... args)
|
||||
{
|
||||
VMValidateSignature<RetVal, P1, P2, P3, P4, P5>(func);
|
||||
VMValue params[] = { p1, p2, p3, p4, p5 };
|
||||
RetVal resultval; VMReturn results(&resultval);
|
||||
VMCallCheckResult(func, params, 5, &results, 1);
|
||||
return resultval;
|
||||
VMValidateSignatureMulti<Rets...>(func, retsSeq, std::make_index_sequence<sizeof...(Args)>{}, std::forward<Args>(args)...);
|
||||
if constexpr(hasVector<Args...>())
|
||||
{
|
||||
VMValueMulti arglist[] = { args... };
|
||||
|
||||
constexpr int argCount = numArgs<Args...>();
|
||||
|
||||
VMValue params[argCount];
|
||||
|
||||
for(int i = 0, j = 0; i < sizeof...(Args); i++)
|
||||
{
|
||||
for(int k = 0; k < arglist[i].count; k++, j++)
|
||||
{
|
||||
params[j] = arglist[i].vals[k];
|
||||
}
|
||||
}
|
||||
|
||||
std::tuple<typename VMReturnTypeTrait<vm_pointer_decay_void<Rets>>::type...> resultval; // convert any pointer to void
|
||||
VMReturn results[sizeof...(Rets)] { &std::get<IRets>(resultval)... };
|
||||
VMCallCheckResult(func, params, argCount, results, sizeof...(Rets));
|
||||
return (std::tuple<typename VMReturnTypeTrait<Rets>::type...>)resultval;
|
||||
}
|
||||
else
|
||||
{
|
||||
VMValue params[] = { args... };
|
||||
|
||||
std::tuple<typename VMReturnTypeTrait<vm_pointer_decay_void<Rets>>::type...> resultval; // convert any pointer to void
|
||||
VMReturn results[sizeof...(Rets)] { &std::get<IRets>(resultval)... };
|
||||
VMCallCheckResult(func, params, sizeof...(Args), results, sizeof...(Rets));
|
||||
return (std::tuple<typename VMReturnTypeTrait<Rets>::type...>)resultval;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename RetVal, typename P1, typename P2, typename P3, typename P4, typename P5, typename P6>
|
||||
typename VMReturnTypeTrait<RetVal>::type VMCallScript(VMFunction* func, P1 p1, P2 p2, P3 p3, P4 p4, P5 p5, P6 p6)
|
||||
template<typename... Rets, typename... Args>
|
||||
std::tuple<typename VMReturnTypeTrait<Rets>::type...> VMCallMulti(VMFunction* func, Args... args)
|
||||
{
|
||||
VMValidateSignature<RetVal, P1, P2, P3, P4, P5, P6>(func);
|
||||
VMValue params[] = { p1, p2, p3, p4, p5, p6 };
|
||||
RetVal resultval; VMReturn results(&resultval);
|
||||
VMCallCheckResult(func, params, 6, &results, 1);
|
||||
return resultval;
|
||||
return VMCallMultiImpl<Rets...>(func, std::make_index_sequence<sizeof...(Rets)>{}, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template<typename RetVal, typename P1, typename P2, typename P3, typename P4, typename P5, typename P6, typename P7>
|
||||
typename VMReturnTypeTrait<RetVal>::type VMCallScript(VMFunction* func, P1 p1, P2 p2, P3 p3, P4 p4, P5 p5, P6 p6, P7 p7)
|
||||
template<typename... Rets>
|
||||
using MultiVMReturn = std::conditional<
|
||||
(sizeof...(Rets) > 1),
|
||||
std::tuple<typename VMReturnTypeTrait<Rets>::type...>,
|
||||
typename VMReturnTypeTrait<typename FirstTemplateValue<Rets...>::type>::type
|
||||
>;
|
||||
|
||||
template<typename... Rets, typename... Args>
|
||||
typename MultiVMReturn<Rets...>::type CallVM(VMFunction* func, Args... args)
|
||||
{
|
||||
VMValidateSignature<RetVal, P1, P2, P3, P4, P5, P6, P7>(func);
|
||||
VMValue params[] = { p1, p2, p3, p4, p5, p6, p7 };
|
||||
RetVal resultval; VMReturn results(&resultval);
|
||||
VMCallCheckResult(func, params, 7, &results, 1);
|
||||
return resultval;
|
||||
static_assert(sizeof...(Rets) > 0, "missing return type in VMCall");
|
||||
if constexpr(sizeof...(Rets) == 1 && std::is_same_v<typename FirstTemplateValue<Rets...>::type, void>)
|
||||
{
|
||||
return VMCallVoid(func, std::forward<Args>(args)...);
|
||||
}
|
||||
else if constexpr(sizeof...(Rets) == 1)
|
||||
{
|
||||
return VMCallSingle<Rets...>(func, std::forward<Args>(args)...);
|
||||
}
|
||||
else
|
||||
{
|
||||
return VMCallMulti<Rets...>(func, std::forward<Args>(args)...);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename P1>
|
||||
typename VMReturnTypeTrait<void>::type VMCallScript(VMFunction* func, P1 p1)
|
||||
{
|
||||
VMValidateSignature<void, P1>(func);
|
||||
VMValue params[1] = { p1 };
|
||||
VMCallCheckResult(func, params, 1, nullptr, 0);
|
||||
}
|
||||
|
||||
template<typename P1, typename P2>
|
||||
typename VMReturnTypeTrait<void>::type VMCallScript(VMFunction* func, P1 p1, P2 p2)
|
||||
{
|
||||
VMValidateSignature<void, P1, P2>(func);
|
||||
VMValue params[] = { p1, p2 };
|
||||
VMCallCheckResult(func, params, 2, nullptr, 0);
|
||||
}
|
||||
|
||||
template<typename P1, typename P2, typename P3>
|
||||
typename VMReturnTypeTrait<void>::type VMCallScript(VMFunction* func, P1 p1, P2 p2, P3 p3)
|
||||
{
|
||||
VMValidateSignature<void, P1, P2, P3>(func);
|
||||
VMValue params[] = { p1, p2, p3 };
|
||||
VMCallCheckResult(func, params, 3, nullptr, 0);
|
||||
}
|
||||
|
||||
template<typename P1, typename P2, typename P3, typename P4>
|
||||
typename VMReturnTypeTrait<void>::type VMCallScript(VMFunction* func, P1 p1, P2 p2, P3 p3, P4 p4)
|
||||
{
|
||||
VMValidateSignature<void, P1, P2, P3, P4>(func);
|
||||
VMValue params[] = { p1, p2, p3, p4 };
|
||||
VMCallCheckResult(func, params, 4, nullptr, 0);
|
||||
}
|
||||
|
||||
template<typename P1, typename P2, typename P3, typename P4, typename P5>
|
||||
typename VMReturnTypeTrait<void>::type VMCallScript(VMFunction* func, P1 p1, P2 p2, P3 p3, P4 p4, P5 p5)
|
||||
{
|
||||
VMValidateSignature<void, P1, P2, P3, P4, P5>(func);
|
||||
VMValue params[] = { p1, p2, p3, p4, p5 };
|
||||
VMCallCheckResult(func, params, 5, nullptr, 0);
|
||||
}
|
||||
|
||||
template<typename P1, typename P2, typename P3, typename P4, typename P5, typename P6>
|
||||
typename VMReturnTypeTrait<void>::type VMCallScript(VMFunction* func, P1 p1, P2 p2, P3 p3, P4 p4, P5 p5, P6 p6)
|
||||
{
|
||||
VMValidateSignature<void, P1, P2, P3, P4, P5, P6>(func);
|
||||
VMValue params[] = { p1, p2, p3, p4, p5, p6 };
|
||||
VMCallCheckResult(func, params, 6, nullptr, 0);
|
||||
}
|
||||
|
||||
template<typename P1, typename P2, typename P3, typename P4, typename P5, typename P6, typename P7>
|
||||
typename VMReturnTypeTrait<void>::type VMCallScript(VMFunction* func, P1 p1, P2 p2, P3 p3, P4 p4, P5 p5, P6 p6, P7 p7)
|
||||
{
|
||||
VMValidateSignature<void, P1, P2, P3, P4, P5, P6, P7>(func);
|
||||
VMValue params[] = { p1, p2, p3, p4, p5, p6, p7 };
|
||||
VMCallCheckResult(func, params, 7, nullptr, 0);
|
||||
}
|
||||
|
||||
// Use these to collect the parameters in a native function.
|
||||
// variable name <x> at position <p>
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@
|
|||
#include "basics.h"
|
||||
#include "texturemanager.h"
|
||||
#include "palutil.h"
|
||||
#include "common/scripting/dap/GameEventEmit.h"
|
||||
|
||||
extern cycle_t VMCycles[10];
|
||||
extern int VMCalls[10];
|
||||
|
|
@ -63,7 +64,7 @@ void ThrowVMException(VMException *x);
|
|||
|
||||
#if COMPGOTO
|
||||
#define OP(x) x
|
||||
#define NEXTOP do { pc++; unsigned op = pc->op; a = pc->a; goto *ops[op]; } while(0)
|
||||
#define NEXTOP do { pc++; DebugServer::RuntimeEvents::EmitInstructionExecutionEvent(stack, ret, numret, pc); unsigned op = pc->op; a = pc->a; goto *ops[op]; } while(0)
|
||||
#else
|
||||
#define OP(x) case OP_##x
|
||||
#define NEXTOP pc++; break
|
||||
|
|
|
|||
|
|
@ -54,7 +54,8 @@ static int ExecScriptFunc(VMFrameStack *stack, VMReturn *ret, int numret)
|
|||
const double *konstf = sfunc->KonstF;
|
||||
const FString *konsts = sfunc->KonstS;
|
||||
const FVoidObj *konsta = sfunc->KonstA;
|
||||
const VMOP *pc = sfunc->Code;
|
||||
f->PC = sfunc->Code;
|
||||
const VMOP *&pc = f->PC;
|
||||
|
||||
assert(!(f->Func->VarFlags & VARF_Native) && "Only script functions should ever reach VMExec");
|
||||
|
||||
|
|
@ -70,7 +71,9 @@ static int ExecScriptFunc(VMFrameStack *stack, VMReturn *ret, int numret)
|
|||
{
|
||||
#if !COMPGOTO
|
||||
VM_UBYTE op;
|
||||
for(;;) switch(op = pc->op, a = pc->a, op)
|
||||
for(;;) {
|
||||
DebugServer::RuntimeEvents::EmitInstructionExecutionEvent(stack, ret, numret, pc);
|
||||
switch(op = pc->op, a = pc->a, op)
|
||||
#else
|
||||
pc--;
|
||||
NEXTOP;
|
||||
|
|
@ -1908,6 +1911,13 @@ static int ExecScriptFunc(VMFrameStack *stack, VMReturn *ret, int numret)
|
|||
reinterpret_cast<DQuaternion&>(reg.f[A]) = q1 * q2;
|
||||
}
|
||||
NEXTOP;
|
||||
OP(CONJQ):
|
||||
ASSERTF(a + 3); ASSERTF(B + 3);
|
||||
{
|
||||
const DQuaternion& q = reinterpret_cast<DQuaternion&>(reg.f[B]);
|
||||
reinterpret_cast<DQuaternion&>(reg.f[A]) = q.Conjugate();
|
||||
}
|
||||
NEXTOP;
|
||||
OP(ADDA_RR):
|
||||
ASSERTA(a); ASSERTA(B); ASSERTD(C);
|
||||
c = reg.d[C];
|
||||
|
|
@ -1950,6 +1960,9 @@ static int ExecScriptFunc(VMFrameStack *stack, VMReturn *ret, int numret)
|
|||
NEXTOP;
|
||||
}
|
||||
}
|
||||
#if !COMPGOTO
|
||||
}
|
||||
#endif
|
||||
#if 0
|
||||
catch(VMException *exception)
|
||||
{
|
||||
|
|
@ -1958,7 +1971,8 @@ static int ExecScriptFunc(VMFrameStack *stack, VMReturn *ret, int numret)
|
|||
|
||||
while(--try_depth >= 0)
|
||||
{
|
||||
pc = exception_frames[try_depth];
|
||||
f->PC = exception_frames[try_depth];
|
||||
pc = f->PC;
|
||||
assert(pc->op == OP_CATCH);
|
||||
while (pc->a > 1)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@
|
|||
#include "jit.h"
|
||||
#include "c_cvars.h"
|
||||
#include "version.h"
|
||||
#include "common/scripting/dap/GameEventEmit.h"
|
||||
|
||||
#ifdef HAVE_VM_JIT
|
||||
#ifdef __DragonFly__
|
||||
|
|
@ -274,6 +275,25 @@ int VMScriptFunction::PCToLine(const VMOP *pc)
|
|||
return -1;
|
||||
}
|
||||
|
||||
TArray<VMLocalVariable> VMScriptFunction::GetLocalVariableBlocksAt(const VMOP *pc)
|
||||
{
|
||||
TArray<VMLocalVariable> ret;
|
||||
// LocalVariableBlocks should already be sorted by start address.
|
||||
std::vector<std::pair<const VMOP*, const VMOP*>> ranges;
|
||||
for (auto &block : LocalVariableBlocks)
|
||||
{
|
||||
if (pc >= block.first.first && pc < block.first.second)
|
||||
{
|
||||
ranges.push_back(block.first);
|
||||
for (auto &var : block.second)
|
||||
{
|
||||
ret.Push(var);
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool CanJit(VMScriptFunction *func)
|
||||
{
|
||||
// Asmjit has a 256 register limit. Stay safely away from it as the jit compiler uses a few for temporaries as well.
|
||||
|
|
@ -557,62 +577,110 @@ VMFrame *VMFrameStack::PopFrame()
|
|||
|
||||
void VMCheckParamCount(VMFunction* func, int retcount, int argcount)
|
||||
{
|
||||
if (func->Proto->ReturnTypes.Size() != retcount)
|
||||
if (func->Proto->ReturnTypes.SSize() != retcount)
|
||||
I_FatalError("Incorrect return value passed to %s", func->PrintableName);
|
||||
if (func->Proto->ArgumentTypes.Size() != argcount)
|
||||
if (func->Proto->ArgumentTypes.SSize() != argcount)
|
||||
I_FatalError("Incorrect parameter count passed to %s", func->PrintableName);
|
||||
}
|
||||
|
||||
template<> void VMCheckParam<int>(VMFunction* func, int index)
|
||||
{
|
||||
if (!func->Proto->ArgumentTypes[index]->isIntCompatible())
|
||||
I_FatalError("%s argument %d is not an integer", func->PrintableName);
|
||||
I_FatalError("%s argument %d is not an integer", func->PrintableName, index);
|
||||
}
|
||||
|
||||
template<> void VMCheckParam<double>(VMFunction* func, int index)
|
||||
{
|
||||
if (func->Proto->ArgumentTypes[index] != TypeFloat64)
|
||||
I_FatalError("%s argument %d is not a double", func->PrintableName);
|
||||
I_FatalError("%s argument %d is not a double", func->PrintableName, index);
|
||||
}
|
||||
|
||||
template<> void VMCheckParam<DVector2>(VMFunction* func, int index)
|
||||
{
|
||||
if (func->Proto->ArgumentTypes[index] != TypeVector2)
|
||||
I_FatalError("%s argument %d is not a vector2", func->PrintableName, index);
|
||||
}
|
||||
|
||||
template<> void VMCheckParam<DVector3>(VMFunction* func, int index)
|
||||
{
|
||||
if (func->Proto->ArgumentTypes[index] != TypeVector3)
|
||||
I_FatalError("%s argument %d is not a vector3", func->PrintableName, index);
|
||||
}
|
||||
|
||||
template<> void VMCheckParam<DVector4>(VMFunction* func, int index)
|
||||
{
|
||||
if (func->Proto->ArgumentTypes[index] != TypeVector4)
|
||||
I_FatalError("%s argument %d is not a vector4", func->PrintableName, index);
|
||||
}
|
||||
|
||||
template<> void VMCheckParam<DQuaternion>(VMFunction* func, int index)
|
||||
{
|
||||
if (func->Proto->ArgumentTypes[index] != TypeQuaternion)
|
||||
I_FatalError("%s argument %d is not a quat", func->PrintableName, index);
|
||||
}
|
||||
|
||||
template<> void VMCheckParam<FString>(VMFunction* func, int index)
|
||||
{
|
||||
if (func->Proto->ArgumentTypes[index] != TypeString)
|
||||
I_FatalError("%s argument %d is not a string", func->PrintableName);
|
||||
I_FatalError("%s argument %d is not a string", func->PrintableName, index);
|
||||
}
|
||||
|
||||
template<> void VMCheckParam<DObject*>(VMFunction* func, int index)
|
||||
{
|
||||
if (func->Proto->ArgumentTypes[index]->isObjectPointer())
|
||||
I_FatalError("%s argument %d is not an object", func->PrintableName);
|
||||
if (!func->Proto->ArgumentTypes[index]->isObjectPointer())
|
||||
I_FatalError("%s argument %d is not an object", func->PrintableName, index);
|
||||
}
|
||||
|
||||
template<> void VMCheckReturn<void>(VMFunction* func)
|
||||
template<> void VMCheckReturn<void>(VMFunction* func, int index)
|
||||
{
|
||||
}
|
||||
|
||||
template<> void VMCheckReturn<int>(VMFunction* func)
|
||||
template<> void VMCheckReturn<int>(VMFunction* func, int index)
|
||||
{
|
||||
if (!func->Proto->ReturnTypes[0]->isIntCompatible())
|
||||
I_FatalError("%s return value %d is not an integer", func->PrintableName);
|
||||
if (!func->Proto->ReturnTypes[index]->isIntCompatible())
|
||||
I_FatalError("%s return value %d is not an integer", func->PrintableName, index);
|
||||
}
|
||||
|
||||
template<> void VMCheckReturn<double>(VMFunction* func)
|
||||
template<> void VMCheckReturn<double>(VMFunction* func, int index)
|
||||
{
|
||||
if (func->Proto->ReturnTypes[0] != TypeFloat64)
|
||||
I_FatalError("%s return value %d is not a double", func->PrintableName);
|
||||
if (func->Proto->ReturnTypes[index] != TypeFloat64)
|
||||
I_FatalError("%s return value %d is not a double", func->PrintableName, index);
|
||||
}
|
||||
|
||||
template<> void VMCheckReturn<FString>(VMFunction* func)
|
||||
template<> void VMCheckReturn<DVector2>(VMFunction* func, int index)
|
||||
{
|
||||
if (func->Proto->ReturnTypes[0] != TypeString)
|
||||
I_FatalError("%s return value %d is not a string", func->PrintableName);
|
||||
if (func->Proto->ReturnTypes[index] != TypeVector2)
|
||||
I_FatalError("%s return value %d is not a vector2", func->PrintableName, index);
|
||||
}
|
||||
|
||||
template<> void VMCheckReturn<DObject*>(VMFunction* func)
|
||||
template<> void VMCheckReturn<DVector3>(VMFunction* func, int index)
|
||||
{
|
||||
if (func->Proto->ReturnTypes[0]->isObjectPointer())
|
||||
I_FatalError("%s return value %d is not an object", func->PrintableName);
|
||||
if (func->Proto->ReturnTypes[index] != TypeVector3)
|
||||
I_FatalError("%s return value %d is not a vector3", func->PrintableName, index);
|
||||
}
|
||||
|
||||
template<> void VMCheckReturn<DVector4>(VMFunction* func, int index)
|
||||
{
|
||||
if (func->Proto->ReturnTypes[index] != TypeVector4)
|
||||
I_FatalError("%s return value %d is not a vector4", func->PrintableName, index);
|
||||
}
|
||||
|
||||
template<> void VMCheckReturn<DQuaternion>(VMFunction* func, int index)
|
||||
{
|
||||
if (func->Proto->ReturnTypes[index] != TypeQuaternion)
|
||||
I_FatalError("%s return value %d is not a quat", func->PrintableName, index);
|
||||
}
|
||||
|
||||
template<> void VMCheckReturn<FString>(VMFunction* func, int index)
|
||||
{
|
||||
if (func->Proto->ReturnTypes[index] != TypeString)
|
||||
I_FatalError("%s return value %d is not a string", func->PrintableName, index);
|
||||
}
|
||||
|
||||
template<> void VMCheckReturn<DObject*>(VMFunction* func, int index)
|
||||
{
|
||||
if (!func->Proto->ReturnTypes[index]->isObjectPointer())
|
||||
I_FatalError("%s return value %d is not an object", func->PrintableName, index);
|
||||
}
|
||||
|
||||
void VMCallCheckResult(VMFunction* func, VMValue* params, int numparams, VMReturn* results, int numresults)
|
||||
|
|
@ -779,7 +847,9 @@ void CVMAbortException::MaybePrintMessage()
|
|||
{
|
||||
va_list ap;
|
||||
va_start(ap, moreinfo);
|
||||
throw CVMAbortException(reason, moreinfo, ap);
|
||||
CVMAbortException err(reason, moreinfo, ap);
|
||||
DebugServer::RuntimeEvents::EmitExceptionEvent(reason, err.GetMessage(), err.stacktrace.GetChars());
|
||||
throw err;
|
||||
}
|
||||
|
||||
[[noreturn]] void ThrowAbortException(VMScriptFunction *sfunc, VMOP *line, EVMAbortException reason, const char *moreinfo, ...)
|
||||
|
|
@ -790,6 +860,7 @@ void CVMAbortException::MaybePrintMessage()
|
|||
CVMAbortException err(reason, moreinfo, ap);
|
||||
|
||||
err.stacktrace.AppendFormat("Called from %s at %s, line %d\n", sfunc->PrintableName, sfunc->SourceFileName.GetChars(), sfunc->PCToLine(line));
|
||||
DebugServer::RuntimeEvents::EmitExceptionEvent(reason, err.GetMessage(), err.stacktrace.GetChars());
|
||||
throw err;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include "vm.h"
|
||||
#include <csetjmp>
|
||||
#include <range_map/range_map.h>
|
||||
|
||||
class VMScriptFunction;
|
||||
|
||||
|
|
@ -239,6 +240,7 @@ extern const VMOpInfo OpInfo[NUM_OPS];
|
|||
// VM frame layout:
|
||||
// VMFrame header
|
||||
// parameter stack - 16 byte boundary, 16 bytes each
|
||||
// program counter
|
||||
// double registers - 8 bytes each
|
||||
// string registers - 4 or 8 bytes each
|
||||
// address registers - 4 or 8 bytes each
|
||||
|
|
@ -249,6 +251,7 @@ struct VMFrame
|
|||
{
|
||||
VMFrame *ParentFrame;
|
||||
VMFunction *Func;
|
||||
const VMOP *PC = nullptr;
|
||||
VM_UBYTE NumRegD;
|
||||
VM_UBYTE NumRegF;
|
||||
VM_UBYTE NumRegS;
|
||||
|
|
@ -360,6 +363,9 @@ public:
|
|||
assert(Blocks != NULL && Blocks->LastFrame != NULL);
|
||||
return Blocks->LastFrame;
|
||||
}
|
||||
bool HasFrames() {
|
||||
return Blocks != NULL && Blocks->LastFrame != NULL;
|
||||
}
|
||||
static int OffsetLastFrame() { return (int)(ptrdiff_t)offsetof(BlockHeader, LastFrame); }
|
||||
private:
|
||||
enum { BLOCK_SIZE = 4096 }; // Default block size
|
||||
|
|
@ -436,7 +442,7 @@ extern int (*VMExec)(VMFunction *func, VMValue *params, int numparams, VMReturn
|
|||
void VMFillParams(VMValue *params, VMFrame *callee, int numparam);
|
||||
|
||||
void VMDumpConstants(FILE *out, const VMScriptFunction *func);
|
||||
void VMDisasm(FILE *out, const VMOP *code, int codesize, const VMScriptFunction *func);
|
||||
void VMDisasm(FILE *out, const VMOP *code, int codesize, const VMScriptFunction *func, uint64_t starting_offset = 0);
|
||||
|
||||
extern thread_local VMFrameStack GlobalVMStack;
|
||||
|
||||
|
|
@ -444,6 +450,17 @@ typedef std::pair<const class PType *, unsigned> FTypeAndOffset;
|
|||
|
||||
typedef int(*JitFuncPtr)(VMFunction *func, VMValue *params, int numparams, VMReturn *ret, int numret);
|
||||
|
||||
struct VMLocalVariable
|
||||
{
|
||||
FName Name;
|
||||
PType * type;
|
||||
int VarFlags;
|
||||
int RegCount;
|
||||
int RegNum;
|
||||
int LineNumber;
|
||||
int StackOffset;
|
||||
};
|
||||
|
||||
class VMScriptFunction : public VMFunction
|
||||
{
|
||||
public:
|
||||
|
|
@ -473,13 +490,14 @@ public:
|
|||
VM_UHALF MaxParam; // Maximum number of parameters this function has on the stack at once
|
||||
VM_UBYTE NumArgs; // Number of arguments this function takes
|
||||
TArray<FTypeAndOffset> SpecialInits; // list of all contents on the extra stack which require construction and destruction
|
||||
|
||||
TArray<std::pair<std::pair<const VMOP *, const VMOP *>, TArray<VMLocalVariable>>> LocalVariableBlocks; // map of local variable blocks to their [start, end) instruction
|
||||
bool blockJit = false; // function triggers Jit bugs, block compilation until bugs are fixed
|
||||
|
||||
void InitExtra(void *addr);
|
||||
void DestroyExtra(void *addr);
|
||||
int AllocExtraStack(PType *type);
|
||||
int PCToLine(const VMOP *pc);
|
||||
TArray<VMLocalVariable> GetLocalVariableBlocksAt(const VMOP *pc);
|
||||
|
||||
private:
|
||||
static int FirstScriptCall(VMFunction *func, VMValue *params, int numparams, VMReturn *ret, int numret);
|
||||
|
|
|
|||
|
|
@ -281,6 +281,7 @@ xx(EQV4_K, beqv4, CVRK, NOP, 0, 0) // this will never be used.
|
|||
// Quaternion math
|
||||
xx(MULQQ_RR, mulqq, RVRVRV, NOP, 0, 0) // qA = qB * qC
|
||||
xx(MULQV3_RR, mulqv3, RVRVRV, NOP, 0, 0) // qA = qB * vC
|
||||
xx(CONJQ, conjq, RVRVRV, NOP, 0, 0) // qA = qB.Conjugate
|
||||
|
||||
// Pointer math.
|
||||
xx(ADDA_RR, add, RPRPRI, NOP, 0, 0) // pA = pB + dkC
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue