Merge remote-tracking branch 'gzdoom/master' into big_beautiful_merge

This commit is contained in:
Magnus Norddahl 2025-08-06 21:06:53 +02:00
commit 3fdd22ef91
1433 changed files with 484787 additions and 9566 deletions

View file

@ -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;
}

View file

@ -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);

View file

@ -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

View file

@ -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);

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;

View file

@ -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)

View 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;
}
}

View 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);
};
}

View 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;
}
}

View 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);
};
}

View 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(); }
}

View 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;
};
}

View 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();
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1 @@
#include "IdHandleBase.h"

View 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)); }
};
}

View file

@ -0,0 +1 @@
#include "IdMap.h"

View 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();
}
};
}

View 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++;
}
}

View 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();
};
}

View 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;
}
}

View 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;
};
}

View 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;
}
}

View 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;
};
}

View 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;
}
}

View 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;
};
}

View 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;
}
}

View 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;
};
}

View 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;
}
}

View 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;
};
}

View 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;
}
}

View 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;
};
}

View 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

View 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;
};
}

View 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;
}
}

View 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;
};
}

View 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;
}
}

View 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;
};
}

View 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

View 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

View 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;
}
}

View 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;
};
}

View 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;
}
}

View 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;
};
}

View 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;
}
}

View 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);
};
}

View 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};
}

View 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;
};
}

View 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;
};
}

View 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"));
}

View 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);
}

View 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.
}
}

View 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

View 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 &currentName,
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);
}
}

View 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);
};
}

View 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));
}
}

View 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

View 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);
};
}

View file

@ -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).

View file

@ -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)

View file

@ -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

View file

@ -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);

View file

@ -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));

View file

@ -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.

View file

@ -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>

View file

@ -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

View file

@ -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)
{

View file

@ -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;
}

View file

@ -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);

View file

@ -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