You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
786 lines
38 KiB
C++
786 lines
38 KiB
C++
/*
|
|
* Copyright (c) Contributors to the Open 3D Engine Project.
|
|
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
*
|
|
*/
|
|
|
|
#include "ScriptRemoteDebugging.h"
|
|
#include "ScriptDebugAgentBus.h"
|
|
#include "ScriptDebugMsgReflection.h"
|
|
#include <AzFramework/TargetManagement/TargetManagementAPI.h>
|
|
#include <AzFramework/Metrics/MetricsPlainTextNameRegistration.h>
|
|
#include <AzCore/Component/TickBus.h>
|
|
#include <AzCore/Component/Component.h>
|
|
#include <AzCore/Component/ComponentApplicationBus.h>
|
|
#include <AzCore/Script/ScriptSystemBus.h>
|
|
#include <AzCore/Serialization/SerializeContext.h>
|
|
#include <AzCore/Serialization/EditContext.h>
|
|
#include <AzCore/RTTI/BehaviorContext.h>
|
|
#include <AzCore/std/containers/vector.h>
|
|
#include <AzCore/std/string/string.h>
|
|
#include <AzCore/std/parallel/lock.h>
|
|
#include <AzCore/Math/Crc.h>
|
|
#include <AzCore/std/parallel/thread.h>
|
|
#include <AzCore/std/parallel/atomic.h>
|
|
#include <GridMate/Serialize/Buffer.h>
|
|
#include <GridMate/Serialize/DataMarshal.h>
|
|
|
|
namespace AzFramework
|
|
{
|
|
namespace ScriptDebugAgentInternal
|
|
{
|
|
//-------------------------------------------------------------------------
|
|
static bool EnumClass(const char* name, const AZ::Uuid& typeId, void* userData)
|
|
{
|
|
ScriptUserClassList& output = *reinterpret_cast<ScriptUserClassList*>(userData);
|
|
output.push_back();
|
|
output.back().m_name = name;
|
|
output.back().m_typeId = typeId;
|
|
return true;
|
|
}
|
|
//-------------------------------------------------------------------------
|
|
static bool EnumMethod(const AZ::Uuid* classTypeId, const char* name, const char* dbgParamInfo, void* userData)
|
|
{
|
|
(void)classTypeId;
|
|
ScriptUserMethodList& output = *reinterpret_cast<ScriptUserMethodList*>(userData);
|
|
output.push_back();
|
|
output.back().m_name = name;
|
|
output.back().m_dbgParamInfo = dbgParamInfo ? dbgParamInfo : "null";
|
|
return true;
|
|
}
|
|
//-------------------------------------------------------------------------
|
|
static bool EnumProperty(const AZ::Uuid* classTypeId, const char* name, bool isRead, bool isWrite, void* userData)
|
|
{
|
|
(void)classTypeId;
|
|
ScriptUserPropertyList& output = *reinterpret_cast<ScriptUserPropertyList*>(userData);
|
|
output.push_back();
|
|
output.back().m_name = name;
|
|
output.back().m_isRead = isRead;
|
|
output.back().m_isWrite = isWrite;
|
|
return true;
|
|
}
|
|
//-------------------------------------------------------------------------
|
|
static bool EnumEBus(const AZStd::string& name, bool canBroadcast, bool canQueue, bool hasHandler, void* userData)
|
|
{
|
|
ScriptUserEBusList& output = *reinterpret_cast<ScriptUserEBusList*>(userData);
|
|
|
|
bool found = false;
|
|
|
|
for (ScriptUserEBusList::iterator it = output.begin(); it != output.end(); ++it)
|
|
{
|
|
if (name == it->m_name)
|
|
{
|
|
found = true;
|
|
}
|
|
}
|
|
|
|
AZ_Warning("ScriptRemoteDebugging", !found, "Ebus (%s) already enumerated", name.c_str());
|
|
|
|
if (!found)
|
|
{
|
|
output.push_back();
|
|
auto& ebus = output.back();
|
|
ebus.m_name = name;
|
|
ebus.m_canBroadcast = canBroadcast;
|
|
ebus.m_canQueue = canQueue;
|
|
ebus.m_hasHandler = hasHandler;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
//-------------------------------------------------------------------------
|
|
static bool EnumEBusSender(const AZStd::string& ebusName, const AZStd::string& senderName, const AZStd::string& dbgParamInfo, const AZStd::string& category, void* userData)
|
|
{
|
|
ScriptUserEBusList& output = *reinterpret_cast<ScriptUserEBusList*>(userData);
|
|
|
|
for (ScriptUserEBusList::iterator it = output.begin(); it != output.end(); ++it)
|
|
{
|
|
if (ebusName == it->m_name)
|
|
{
|
|
it->m_events.push_back();
|
|
auto& event = it->m_events.back();
|
|
event.m_name = senderName;
|
|
event.m_dbgParamInfo = dbgParamInfo;
|
|
event.m_category = category;
|
|
return true;
|
|
}
|
|
}
|
|
AZ_Assert(false, "Received an enumeration of an eBus sender method for an eBus we have not enumerated yet!");
|
|
return false;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
static bool EnumClassMethod(const AZ::Uuid* classTypeId, const char* name, const char* dbgParamInfo, void* userData)
|
|
{
|
|
ScriptUserClassList& output = *reinterpret_cast<ScriptUserClassList*>(userData);
|
|
for (ScriptUserClassList::iterator it = output.begin(); it != output.end(); ++it)
|
|
{
|
|
if (classTypeId && *classTypeId == it->m_typeId)
|
|
{
|
|
return EnumMethod(classTypeId, name, dbgParamInfo, &(it->m_methods));
|
|
}
|
|
}
|
|
AZ_Assert(false, "Received enumeration of a class method for a class we have not enumerated yet!");
|
|
return true;
|
|
}
|
|
//-------------------------------------------------------------------------
|
|
static bool EnumClassProperty(const AZ::Uuid* classTypeId, const char* name, bool isRead, bool isWrite, void* userData)
|
|
{
|
|
ScriptUserClassList& output = *reinterpret_cast<ScriptUserClassList*>(userData);
|
|
for (ScriptUserClassList::iterator it = output.begin(); it != output.end(); ++it)
|
|
{
|
|
if (classTypeId && *classTypeId == it->m_typeId)
|
|
{
|
|
return EnumProperty(classTypeId, name, isRead, isWrite, &(it->m_properties));
|
|
}
|
|
}
|
|
AZ_Assert(false, "Received enumeration of a class property for a class we have not enumerated yet!");
|
|
return true;
|
|
}
|
|
//-------------------------------------------------------------------------
|
|
static bool EnumGlobalMethod(const AZ::Uuid* classTypeId, const char* name, const char* dbgParamInfo, void* userData)
|
|
{
|
|
ScriptDebugRegisteredGlobalsResult* output = reinterpret_cast<ScriptDebugRegisteredGlobalsResult*>(userData);
|
|
return EnumMethod(classTypeId, name, dbgParamInfo, &output->m_methods);
|
|
}
|
|
//-------------------------------------------------------------------------
|
|
static bool EnumGlobalProperty(const AZ::Uuid* classTypeId, const char* name, bool isRead, bool isWrite, void* userData)
|
|
{
|
|
ScriptDebugRegisteredGlobalsResult* output = reinterpret_cast<ScriptDebugRegisteredGlobalsResult*>(userData);
|
|
return EnumProperty(classTypeId, name, isRead, isWrite, &output->m_properties);
|
|
}
|
|
//-------------------------------------------------------------------------
|
|
static bool EnumLocals(AZStd::vector<AZStd::string>* output, const char* name, AZ::ScriptDataContext& dataContext)
|
|
{
|
|
(void)dataContext;
|
|
output->push_back(name);
|
|
return true;
|
|
}
|
|
//-------------------------------------------------------------------------
|
|
} // namespace ScriptDebugAgentInternal
|
|
|
|
//-------------------------------------------------------------------------
|
|
//-------------------------------------------------------------------------
|
|
class ScriptDebugAgent
|
|
: public AZ::Component
|
|
, public ScriptDebugAgentBus::Handler
|
|
, public TmMsgBus::Handler
|
|
, AZ::SystemTickBus::Handler
|
|
{
|
|
public:
|
|
AZ_COMPONENT(ScriptDebugAgent, "{624a7be2-3c7e-4119-aee2-1db2bdb6cc89}");
|
|
ScriptDebugAgent() = default;
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Component base
|
|
void Init() override;
|
|
void Activate() override;
|
|
void Deactivate() override;
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// AZ::SystemTickBus
|
|
void OnSystemTick() override;
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// ScriptDebugAgentBus
|
|
void RegisterContext(AZ::ScriptContext* sc, const char* name) override;
|
|
void UnregisterContext(AZ::ScriptContext* sc) override;
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// TmMsgBus
|
|
void OnReceivedMsg(TmMsgPtr msg) override;
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
protected:
|
|
ScriptDebugAgent(const ScriptDebugAgent&) = delete;
|
|
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
|
|
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible);
|
|
static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent);
|
|
static void Reflect(AZ::ReflectContext* context);
|
|
|
|
void Attach(const TargetInfo& ti, const char* scriptContextName);
|
|
void Detach();
|
|
|
|
void BreakpointCallback(AZ::ScriptContextDebug* debugContext, const AZ::ScriptContextDebug::Breakpoint* breakpoint);
|
|
void DebugCommandCallback(AZ::ScriptContextDebug* debugContext);
|
|
|
|
void Process();
|
|
|
|
TargetInfo m_debugger;
|
|
TmMsgQueue m_msgQueue;
|
|
AZStd::mutex m_msgMutex;
|
|
AZ::ScriptContext* m_curContext;
|
|
|
|
struct ContextRecord
|
|
{
|
|
AZ::ScriptContext* m_context;
|
|
AZStd::string m_name;
|
|
};
|
|
typedef AZStd::vector<ContextRecord> ContextMap;
|
|
|
|
ContextMap m_availableContexts;
|
|
|
|
enum SDA_STATE
|
|
{
|
|
SDA_STATE_DETACHED,
|
|
SDA_STATE_RUNNING,
|
|
SDA_STATE_PAUSED,
|
|
SDA_STATE_DETACHING,
|
|
};
|
|
AZStd::atomic_uint m_executionState;
|
|
};
|
|
//-------------------------------------------------------------------------
|
|
void ScriptDebugAgent::Init()
|
|
{
|
|
}
|
|
//-------------------------------------------------------------------------
|
|
void ScriptDebugAgent::Activate()
|
|
{
|
|
m_executionState = SDA_STATE_DETACHED;
|
|
m_curContext = nullptr;
|
|
|
|
// register default app script context if there is one
|
|
AZ::ScriptContext* defaultScriptContext = nullptr;
|
|
EBUS_EVENT_RESULT(defaultScriptContext, AZ::ScriptSystemRequestBus, GetContext, AZ::ScriptContextIds::DefaultScriptContextId);
|
|
if (defaultScriptContext)
|
|
{
|
|
RegisterContext(defaultScriptContext, "Default");
|
|
}
|
|
|
|
AZ::ScriptContext* cryScriptContext = nullptr;
|
|
EBUS_EVENT_RESULT(cryScriptContext, AZ::ScriptSystemRequestBus, GetContext, AZ::ScriptContextIds::CryScriptContextId);
|
|
if (cryScriptContext)
|
|
{
|
|
RegisterContext(cryScriptContext, "Cry");
|
|
}
|
|
|
|
ScriptDebugAgentBus::Handler::BusConnect();
|
|
AZ::SystemTickBus::Handler::BusConnect();
|
|
TmMsgBus::Handler::BusConnect(AZ_CRC("ScriptDebugAgent", 0xb6be0836));
|
|
}
|
|
//-------------------------------------------------------------------------
|
|
void ScriptDebugAgent::Deactivate()
|
|
{
|
|
TmMsgBus::Handler::BusDisconnect(AZ_CRC("ScriptDebugAgent", 0xb6be0836));
|
|
AZ::SystemTickBus::Handler::BusDisconnect();
|
|
|
|
// TODO: Make thread safe if we ever have multithreaded script contexts!
|
|
if (m_executionState != SDA_STATE_DETACHED)
|
|
{
|
|
Detach();
|
|
}
|
|
|
|
AZStd::lock_guard<AZStd::mutex> l(m_msgMutex);
|
|
m_msgQueue.clear();
|
|
}
|
|
//-------------------------------------------------------------------------
|
|
void ScriptDebugAgent::OnSystemTick()
|
|
{
|
|
// If we are attached, then all processing should happen
|
|
// in the attached context.
|
|
if (m_executionState == SDA_STATE_DETACHED)
|
|
{
|
|
Process();
|
|
}
|
|
}
|
|
//-------------------------------------------------------------------------
|
|
void ScriptDebugAgent::RegisterContext(AZ::ScriptContext* sc, const char* name)
|
|
{
|
|
for (ContextMap::const_iterator it = m_availableContexts.begin(); it != m_availableContexts.end(); ++it)
|
|
{
|
|
if (it->m_context == sc)
|
|
{
|
|
AZ_Assert(false, "ScriptContext 0x%p is already registered as %s! New registration ignored.", sc, it->m_name.c_str());
|
|
return;
|
|
}
|
|
}
|
|
m_availableContexts.push_back();
|
|
m_availableContexts.back().m_context = sc;
|
|
m_availableContexts.back().m_name = name;
|
|
}
|
|
//-------------------------------------------------------------------------
|
|
void ScriptDebugAgent::UnregisterContext(AZ::ScriptContext* sc)
|
|
{
|
|
for (ContextMap::const_iterator it = m_availableContexts.begin(); it != m_availableContexts.end(); ++it)
|
|
{
|
|
if (it->m_context == sc)
|
|
{
|
|
if (m_curContext == sc)
|
|
{
|
|
// TODO: This operation needs to be thread-safe if we ever run contexts from multiple threads.
|
|
Detach();
|
|
}
|
|
m_availableContexts.erase(it);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
//-------------------------------------------------------------------------
|
|
void ScriptDebugAgent::OnReceivedMsg(TmMsgPtr msg)
|
|
{
|
|
AZStd::lock_guard<AZStd::mutex> l(m_msgMutex);
|
|
m_msgQueue.push_back(msg);
|
|
}
|
|
//-------------------------------------------------------------------------
|
|
void ScriptDebugAgent::Attach(const TargetInfo& ti, const char* scriptContextName)
|
|
{
|
|
for (ContextMap::iterator it = m_availableContexts.begin(); it != m_availableContexts.end(); ++it)
|
|
{
|
|
if (azstricmp(scriptContextName, it->m_name.c_str()) == 0)
|
|
{
|
|
AZ::ScriptContext* sc = it->m_context;
|
|
AZ_Assert(sc, "How did we end up with a NULL in the available contexts map?");
|
|
|
|
m_debugger = ti;
|
|
m_curContext = sc;
|
|
sc->EnableDebug();
|
|
AZ::ScriptContextDebug* dbgContext = sc->GetDebugContext();
|
|
if (dbgContext)
|
|
{
|
|
dbgContext->EnableStackRecord();
|
|
|
|
AZ::ScriptContextDebug::BreakpointCallback breakpointCallback = AZStd::bind(&ScriptDebugAgent::BreakpointCallback, this, AZStd::placeholders::_1, AZStd::placeholders::_2);
|
|
dbgContext->EnableBreakpoints(breakpointCallback);
|
|
|
|
AZ::ScriptContextDebug::ProcessDebugCmdCallback debugCommandCallback = AZStd::bind(&ScriptDebugAgent::DebugCommandCallback, this, AZStd::placeholders::_1);
|
|
dbgContext->EnableDebugCmdProcess(debugCommandCallback);
|
|
}
|
|
|
|
// Notify debugger that he successfully connected
|
|
EBUS_EVENT(TargetManager::Bus, SendTmMessage, ti, ScriptDebugAck(AZ_CRC("AttachDebugger", 0x6590ff36), AZ_CRC("Ack", 0x22e4f8b1)));
|
|
AZ_TracePrintf("LUA", "Remote debugger %s has attached to context %s.\n", m_debugger.GetDisplayName(), it->m_name.c_str());
|
|
m_executionState = SDA_STATE_RUNNING;
|
|
|
|
return;
|
|
}
|
|
}
|
|
// Failed to find context, notify debugger that the connection was rejected.
|
|
EBUS_EVENT(TargetManager::Bus, SendTmMessage, ti, ScriptDebugAck(AZ_CRC("AttachDebugger", 0x6590ff36), AZ_CRC("IllegalOperation", 0x437dc900)));
|
|
}
|
|
//-------------------------------------------------------------------------
|
|
void ScriptDebugAgent::Detach()
|
|
{
|
|
EBUS_EVENT(TargetManager::Bus, SendTmMessage, m_debugger, ScriptDebugAck(AZ_CRC("DetachDebugger", 0x88a2ee04), AZ_CRC("Ack", 0x22e4f8b1)));
|
|
|
|
// TODO: We need to make sure we are thread safe if the contexts are running on
|
|
// different threads.
|
|
//if (m_curContext->GetErrorHookUserData() == this) {
|
|
// m_curContext->SetErrorHook(NULL);
|
|
//}
|
|
AZ::ScriptContextDebug* debugContext = m_curContext->GetDebugContext();
|
|
debugContext->DisableBreakpoints();
|
|
debugContext->DisableStackRecord();
|
|
debugContext->DisableDebugCmdProcess();
|
|
m_curContext->DisableDebug();
|
|
|
|
AZ_TracePrintf("LUA", "Remote debugger %s has detached from context 0x%p.\n", m_debugger.GetDisplayName(), m_curContext);
|
|
m_debugger = TargetInfo();
|
|
m_curContext = nullptr;
|
|
m_executionState = SDA_STATE_DETACHED;
|
|
}
|
|
//-------------------------------------------------------------------------
|
|
void ScriptDebugAgent::DebugCommandCallback(AZ::ScriptContextDebug* debugContext)
|
|
{
|
|
(void)debugContext;
|
|
AZ_Assert(m_curContext, "We are debugging without a script context!");
|
|
AZ_Assert(m_curContext->GetDebugContext() == debugContext, "Context mismatch. Are we attached to the correct script context?");
|
|
|
|
if (m_executionState != SDA_STATE_DETACHED)
|
|
{
|
|
// This is the only safe place to tear down the debug context because
|
|
// it is the only function that runs in the script context thread and is never
|
|
// called from within any debugContext callbacks.
|
|
if (m_executionState == SDA_STATE_DETACHING)
|
|
{
|
|
AZ_TracePrintf("LUA", "Disabling debugging for script context(0x%p).\n", m_curContext);
|
|
Detach();
|
|
}
|
|
else
|
|
{
|
|
Process();
|
|
}
|
|
}
|
|
}
|
|
//-------------------------------------------------------------------------
|
|
void ScriptDebugAgent::BreakpointCallback(AZ::ScriptContextDebug* debugContext, const AZ::ScriptContextDebug::Breakpoint* breakpoint)
|
|
{
|
|
(void)debugContext;
|
|
AZ_Assert(m_curContext, "We are debugging without a script context!");
|
|
AZ_Assert(m_curContext->GetDebugContext() == debugContext, "Context mismatch. Are we attached to the correct script context?");
|
|
|
|
if (m_executionState == SDA_STATE_RUNNING)
|
|
{
|
|
m_executionState = SDA_STATE_PAUSED;
|
|
if (m_debugger.IsValid())
|
|
{
|
|
ScriptDebugAckBreakpoint response;
|
|
response.m_id = AZ_CRC("BreakpointHit", 0xf1a38e0b);
|
|
response.m_moduleName = breakpoint->m_sourceName;
|
|
response.m_line = static_cast<AZ::u32>(breakpoint->m_lineNumber);
|
|
EBUS_EVENT(TargetManager::Bus, SendTmMessage, m_debugger, response);
|
|
}
|
|
while (m_executionState == SDA_STATE_PAUSED)
|
|
{
|
|
EBUS_EVENT(TargetManager::Bus, DispatchMessages, AZ_CRC("ScriptDebugAgent", 0xb6be0836));
|
|
Process();
|
|
AZStd::this_thread::yield();
|
|
}
|
|
}
|
|
}
|
|
//-------------------------------------------------------------------------
|
|
void ScriptDebugAgent::Process()
|
|
{
|
|
// Process messages
|
|
AZ::ScriptContextDebug* dbgContext = m_curContext ? m_curContext->GetDebugContext() : nullptr;
|
|
while (!m_msgQueue.empty())
|
|
{
|
|
m_msgMutex.lock();
|
|
TmMsgPtr msg = *m_msgQueue.begin();
|
|
m_msgQueue.pop_front();
|
|
m_msgMutex.unlock();
|
|
AZ_Assert(msg, "We received a NULL message in the script debug agent's message queue!");
|
|
TargetInfo sender;
|
|
EBUS_EVENT_RESULT(sender, TargetManager::Bus, GetTargetInfo, msg->GetSenderTargetId());
|
|
|
|
// The only message we accept without a target match is AttachDebugger
|
|
if (m_debugger.GetNetworkId() != sender.GetNetworkId())
|
|
{
|
|
ScriptDebugRequest* request = azdynamic_cast<ScriptDebugRequest*>(msg.get());
|
|
if (!request || (request->m_request != AZ_CRC("AttachDebugger", 0x6590ff36) && request->m_request != AZ_CRC("EnumContexts", 0xbdb959ba)))
|
|
{
|
|
AZ_TracePrintf("LUA", "Rejecting msg 0x%x (%s is not the attached debugger)\n", request->m_request, sender.GetDisplayName());
|
|
EBUS_EVENT(TargetManager::Bus, SendTmMessage, sender, ScriptDebugAck(request->m_request, AZ_CRC("AccessDenied", 0xde72ce21)));
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (azrtti_istypeof<ScriptDebugBreakpointRequest*>(msg.get()))
|
|
{
|
|
ScriptDebugBreakpointRequest* request = azdynamic_cast<ScriptDebugBreakpointRequest*>(msg.get());
|
|
|
|
AZ::ScriptContextDebug::Breakpoint bp;
|
|
bp.m_sourceName = request->m_context.c_str();
|
|
bp.m_lineNumber = request->m_line;
|
|
|
|
if (request->m_request == AZ_CRC("AddBreakpoint", 0xba71daa4))
|
|
{
|
|
AZ_TracePrintf("LUA", "Adding breakpoint %s:%d\n", bp.m_sourceName.c_str(), bp.m_lineNumber);
|
|
dbgContext->AddBreakpoint(bp);
|
|
}
|
|
else if (request->m_request == AZ_CRC("RemoveBreakpoint", 0x90ade500))
|
|
{
|
|
AZ_TracePrintf("LUA", "Removing breakpoint %s:%d\n", bp.m_sourceName.c_str(), bp.m_lineNumber);
|
|
dbgContext->RemoveBreakpoint(bp);
|
|
}
|
|
|
|
ScriptDebugAckBreakpoint response;
|
|
response.m_id = request->m_request;
|
|
response.m_moduleName = request->m_context;
|
|
response.m_line = request->m_line;
|
|
EBUS_EVENT(TargetManager::Bus, SendTmMessage, sender, response);
|
|
}
|
|
else if (azrtti_istypeof<ScriptDebugSetValue*>(msg.get())) // sets the value of a variable
|
|
{
|
|
if (m_executionState == SDA_STATE_PAUSED)
|
|
{
|
|
ScriptDebugSetValue* request = azdynamic_cast<ScriptDebugSetValue*>(msg.get());
|
|
ScriptDebugSetValueResult response;
|
|
response.m_name = request->m_value.m_name;
|
|
response.m_result = dbgContext->SetValue(request->m_value);
|
|
EBUS_EVENT(TargetManager::Bus, SendTmMessage, sender, response);
|
|
}
|
|
else
|
|
{
|
|
AZ_TracePrintf("LUA", "Command rejected. 'SetValue' can only be issued while on a breakpoint.\n");
|
|
EBUS_EVENT(TargetManager::Bus, SendTmMessage, sender, ScriptDebugAck(AZ_CRC("SetValue", 0xd595caa6), AZ_CRC("IllegalOperation", 0x437dc900)));
|
|
}
|
|
}
|
|
else if (azrtti_istypeof<ScriptDebugRequest*>(msg.get()))
|
|
{
|
|
ScriptDebugRequest* request = azdynamic_cast<ScriptDebugRequest*>(msg.get());
|
|
// Check request type
|
|
// EnumLocals
|
|
if (request->m_request == AZ_CRC("EnumLocals", 0x4aa29dcf)) // enumerates local variables
|
|
{
|
|
if (m_executionState == SDA_STATE_PAUSED)
|
|
{
|
|
ScriptDebugEnumLocalsResult response;
|
|
AZ::ScriptContextDebug::EnumLocalCallback enumCB = AZStd::bind(&ScriptDebugAgentInternal::EnumLocals, &response.m_names, AZStd::placeholders::_1, AZStd::placeholders::_2);
|
|
dbgContext->EnumLocals(enumCB);
|
|
EBUS_EVENT(TargetManager::Bus, SendTmMessage, sender, response);
|
|
}
|
|
else
|
|
{
|
|
AZ_TracePrintf("LUA", "Command rejected. 'EnumLocals' can only be issued while on a breakpoint.\n");
|
|
EBUS_EVENT(TargetManager::Bus, SendTmMessage, sender, ScriptDebugAck(request->m_request, AZ_CRC("IllegalOperation", 0x437dc900)));
|
|
}
|
|
// GetValue
|
|
}
|
|
else if (request->m_request == AZ_CRC("GetValue", 0x2d64f577))
|
|
{
|
|
ScriptDebugGetValueResult response;
|
|
response.m_value.m_name = request->m_context;
|
|
dbgContext->GetValue(response.m_value);
|
|
EBUS_EVENT(TargetManager::Bus, SendTmMessage, sender, response);
|
|
// StepOver
|
|
}
|
|
else if (request->m_request == AZ_CRC("StepOver", 0x6b89bf41))
|
|
{
|
|
if (m_executionState == SDA_STATE_PAUSED)
|
|
{
|
|
dbgContext->StepOver();
|
|
m_executionState = SDA_STATE_RUNNING;
|
|
EBUS_EVENT(TargetManager::Bus, SendTmMessage, sender, ScriptDebugAck(request->m_request, AZ_CRC("Ack", 0x22e4f8b1)));
|
|
}
|
|
else
|
|
{
|
|
AZ_TracePrintf("LUA", "Command rejected. 'StepOver' can only be issued while on a breakpoint.\n");
|
|
EBUS_EVENT(TargetManager::Bus, SendTmMessage, sender, ScriptDebugAck(request->m_request, AZ_CRC("IllegalOperation", 0x437dc900)));
|
|
}
|
|
// StepIn
|
|
}
|
|
else if (request->m_request == AZ_CRC("StepIn", 0x761a6b13))
|
|
{
|
|
if (m_executionState == SDA_STATE_PAUSED)
|
|
{
|
|
dbgContext->StepInto();
|
|
m_executionState = SDA_STATE_RUNNING;
|
|
EBUS_EVENT(TargetManager::Bus, SendTmMessage, sender, ScriptDebugAck(request->m_request, AZ_CRC("Ack", 0x22e4f8b1)));
|
|
}
|
|
else
|
|
{
|
|
AZ_TracePrintf("LUA", "Command rejected. 'StepIn' can only be issued while on a breakpoint.\n");
|
|
EBUS_EVENT(TargetManager::Bus, SendTmMessage, sender, ScriptDebugAck(request->m_request, AZ_CRC("IllegalOperation", 0x437dc900)));
|
|
}
|
|
// StepOut
|
|
}
|
|
else if (request->m_request == AZ_CRC("StepOut", 0xac19b635))
|
|
{
|
|
if (m_executionState == SDA_STATE_PAUSED)
|
|
{
|
|
dbgContext->StepOut();
|
|
m_executionState = SDA_STATE_RUNNING;
|
|
EBUS_EVENT(TargetManager::Bus, SendTmMessage, sender, ScriptDebugAck(request->m_request, AZ_CRC("Ack", 0x22e4f8b1)));
|
|
}
|
|
else
|
|
{
|
|
AZ_TracePrintf("LUA", "Command rejected. 'StepOut' can only be issued while on a breakpoint.\n");
|
|
EBUS_EVENT(TargetManager::Bus, SendTmMessage, sender, ScriptDebugAck(request->m_request, AZ_CRC("IllegalOperation", 0x437dc900)));
|
|
}
|
|
// Continue
|
|
}
|
|
else if (request->m_request == AZ_CRC("Continue", 0x13e32adf))
|
|
{
|
|
if (m_executionState == SDA_STATE_PAUSED)
|
|
{
|
|
m_executionState = SDA_STATE_RUNNING;
|
|
EBUS_EVENT(TargetManager::Bus, SendTmMessage, sender, ScriptDebugAck(request->m_request, AZ_CRC("Ack", 0x22e4f8b1)));
|
|
}
|
|
else
|
|
{
|
|
AZ_TracePrintf("LUA", "Command rejected. 'Continue' can only be issued while on a breakpoint.\n");
|
|
EBUS_EVENT(TargetManager::Bus, SendTmMessage, sender, ScriptDebugAck(request->m_request, AZ_CRC("IllegalOperation", 0x437dc900)));
|
|
}
|
|
// GetCallstack
|
|
}
|
|
else if (request->m_request == AZ_CRC("GetCallstack", 0x343b24f3))
|
|
{
|
|
if (m_executionState == SDA_STATE_PAUSED)
|
|
{
|
|
char bufStackTrace[4096];
|
|
dbgContext->StackTrace(bufStackTrace, 4096);
|
|
ScriptDebugCallStackResult response;
|
|
response.m_callstack = bufStackTrace;
|
|
EBUS_EVENT(TargetManager::Bus, SendTmMessage, sender, response);
|
|
}
|
|
else
|
|
{
|
|
AZ_TracePrintf("LUA", "Command rejected. 'GetCallstack' can only be issued while on a breakpoint.\n");
|
|
EBUS_EVENT(TargetManager::Bus, SendTmMessage, sender, ScriptDebugAck(request->m_request, AZ_CRC("IllegalOperation", 0x437dc900)));
|
|
}
|
|
// enumerates global C++ functions that have been exposed to script
|
|
}
|
|
else if (request->m_request == AZ_CRC("EnumRegisteredGlobals", 0x80d1e6af))
|
|
{
|
|
ScriptDebugRegisteredGlobalsResult response;
|
|
dbgContext->EnumRegisteredGlobals(&ScriptDebugAgentInternal::EnumGlobalMethod, &ScriptDebugAgentInternal::EnumGlobalProperty, &response);
|
|
EBUS_EVENT(TargetManager::Bus, SendTmMessage, sender, response);
|
|
// enumerates C++ classes that have been exposed to script
|
|
}
|
|
else if (request->m_request == AZ_CRC("EnumRegisteredClasses", 0xed6b8070))
|
|
{
|
|
ScriptDebugRegisteredClassesResult response;
|
|
dbgContext->EnumRegisteredClasses(&ScriptDebugAgentInternal::EnumClass, &ScriptDebugAgentInternal::EnumClassMethod, &ScriptDebugAgentInternal::EnumClassProperty, &response.m_classes);
|
|
EBUS_EVENT(TargetManager::Bus, SendTmMessage, sender, response);
|
|
// ExecuteScript
|
|
}
|
|
else if (request->m_request == AZ_CRC("EnumRegisteredEBuses", 0x8237bde7))
|
|
{
|
|
ScriptDebugRegisteredEBusesResult response;
|
|
dbgContext->EnumRegisteredEBuses(&ScriptDebugAgentInternal::EnumEBus, &ScriptDebugAgentInternal::EnumEBusSender, &response.m_ebusList);
|
|
EBUS_EVENT(TargetManager::Bus, SendTmMessage, sender, response);
|
|
}
|
|
else if (request->m_request == AZ_CRC("ExecuteScript", 0xc35e01e7))
|
|
{
|
|
if (m_executionState == SDA_STATE_RUNNING)
|
|
{
|
|
AZ_Assert(request->GetCustomBlob(), "ScriptDebugAgent was asked to execute a script but script is missing!");
|
|
ScriptDebugAckExecute response;
|
|
response.m_moduleName = request->m_context;
|
|
response.m_result = m_curContext->Execute(reinterpret_cast<const char*>(request->GetCustomBlob()), request->m_context.c_str());
|
|
EBUS_EVENT(TargetManager::Bus, SendTmMessage, sender, response);
|
|
}
|
|
else
|
|
{
|
|
AZ_TracePrintf("LUA", "Command rejected. 'ExecuteScript' cannot be issued while on a breakpoint.\n");
|
|
EBUS_EVENT(TargetManager::Bus, SendTmMessage, sender, ScriptDebugAck(request->m_request, AZ_CRC("IllegalOperation", 0x437dc900)));
|
|
}
|
|
// AttachDebugger
|
|
}
|
|
else if (request->m_request == AZ_CRC("AttachDebugger", 0x6590ff36))
|
|
{
|
|
if (m_executionState == SDA_STATE_DETACHED)
|
|
{
|
|
Attach(sender, request->m_context.c_str());
|
|
}
|
|
else
|
|
{
|
|
// we need to detach from the current context first
|
|
AZ_TracePrintf("LUA", "Received connection from %s while still connected to %s! Detaching from %s.\n", sender.GetDisplayName(), m_debugger.GetDisplayName(), m_debugger.GetDisplayName());
|
|
AZ_TracePrintf("LUA", "Force disconnecting debugger %s from context 0x%p.\n", m_debugger.GetDisplayName(), m_curContext);
|
|
AZStd::lock_guard<AZStd::mutex> l(m_msgMutex);
|
|
m_msgQueue.push_front(msg);
|
|
m_executionState = SDA_STATE_DETACHING;
|
|
}
|
|
// We need to switch contexts before any more processing, keep remaining messages
|
|
// in the queue and return.
|
|
return;
|
|
// DetachDebugger
|
|
}
|
|
else if (request->m_request == AZ_CRC("DetachDebugger", 0x88a2ee04))
|
|
{
|
|
// We need to switch contexts before any more processing, keep remaining messages
|
|
// in the queue and return.
|
|
m_executionState = SDA_STATE_DETACHING;
|
|
return;
|
|
// EnumContexts
|
|
}
|
|
else if (request->m_request == AZ_CRC("EnumContexts", 0xbdb959ba))
|
|
{
|
|
AZ_TracePrintf("LUA", "Received EnumContexts request\n");
|
|
ScriptDebugEnumContextsResult response;
|
|
for (ContextMap::const_iterator it = m_availableContexts.begin(); it != m_availableContexts.end(); ++it)
|
|
{
|
|
response.m_names.push_back(it->m_name);
|
|
}
|
|
EBUS_EVENT(TargetManager::Bus, SendTmMessage, sender, response);
|
|
// Invalid command
|
|
}
|
|
else
|
|
{
|
|
AZ_TracePrintf("LUA", "Received invalid command 0x%x.\n", request->m_request);
|
|
EBUS_EVENT(TargetManager::Bus, SendTmMessage, sender, ScriptDebugAck(request->m_request, AZ_CRC("InvalidCmd", 0x926abd27)));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
AZ_Assert(false, "ScriptDebugAgent received a message that is not of any recognized types!");
|
|
}
|
|
}
|
|
|
|
// Check if our debugger is still around
|
|
if (m_executionState != SDA_STATE_DETACHED)
|
|
{
|
|
bool debuggerOnline = false;
|
|
EBUS_EVENT_RESULT(debuggerOnline, TargetManager::Bus, IsTargetOnline, m_debugger.GetNetworkId());
|
|
if (!debuggerOnline)
|
|
{
|
|
m_executionState = SDA_STATE_DETACHING;
|
|
}
|
|
}
|
|
}
|
|
//-------------------------------------------------------------------------
|
|
void ScriptDebugAgent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
|
|
{
|
|
provided.push_back(AZ_CRC("ScriptDebugService", 0x5b0c3898));
|
|
}
|
|
//-------------------------------------------------------------------------
|
|
void ScriptDebugAgent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
|
|
{
|
|
incompatible.push_back(AZ_CRC("ScriptDebugService", 0x5b0c3898));
|
|
}
|
|
//-------------------------------------------------------------------------
|
|
void ScriptDebugAgent::GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent)
|
|
{
|
|
dependent.push_back(AZ_CRC("ScriptService", 0x787235ab));
|
|
}
|
|
//-------------------------------------------------------------------------
|
|
void ScriptDebugAgent::Reflect(AZ::ReflectContext* context)
|
|
{
|
|
if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
|
|
{
|
|
serializeContext->Class<ScriptDebugAgent, AZ::Component>()
|
|
->Version(1)
|
|
;
|
|
|
|
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
|
|
{
|
|
editContext->Class<ScriptDebugAgent>(
|
|
"Script Debug Agent", "Provides remote debugging services for script contexts")
|
|
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
|
|
->Attribute(AZ::Edit::Attributes::Category, "Profiling")
|
|
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("System", 0xc94d118b))
|
|
;
|
|
}
|
|
}
|
|
ReflectScriptDebugClasses(context);
|
|
|
|
static bool registeredComponentUuidWithMetricsAlready = false;
|
|
if (!registeredComponentUuidWithMetricsAlready)
|
|
{
|
|
// have to let the metrics system know that it's ok to send back the name of the ScriptDebugAgent component to Amazon as plain text, without hashing
|
|
EBUS_EVENT(AzFramework::MetricsPlainTextNameRegistrationBus, RegisterForNameSending, AZStd::vector<AZ::Uuid>{ azrtti_typeid<ScriptDebugAgent>() });
|
|
|
|
// only ever do this once
|
|
registeredComponentUuidWithMetricsAlready = true;
|
|
}
|
|
}
|
|
|
|
////-------------------------------------------------------------------------
|
|
//class ScriptDebugAgentFactory : public AZ::ComponentFactory<ScriptDebugAgent>
|
|
//{
|
|
//public:
|
|
// AZ_CLASS_ALLOCATOR(ScriptDebugAgentFactory, AZ::SystemAllocator,0);
|
|
|
|
// ScriptDebugAgentFactory() : AZ::ComponentFactory<ScriptDebugAgent>(AZ_CRC("ScriptDebugAgent", 0xb6be0836))
|
|
// {
|
|
// }
|
|
|
|
// virtual const char* GetName() { return "ScriptDebugAgent"; }
|
|
// virtual void Reflect(const AZ::ClassDataReflection& reflection)
|
|
// {
|
|
// if( reflection.m_serialize )
|
|
// {
|
|
// reflection.m_serialize->Class<ScriptDebugAgent>("ScriptDebugAgent", "{6CEA890A-CEC0-4725-8E9A-97ACCE5941A9}")
|
|
// ->Version(1)
|
|
|
|
// AZ::EditContext *ec = reflection.m_serialize->GetEditContext();
|
|
// if (ec) {
|
|
// ec->Class<ScriptDebugAgent>("Script Debug Agent", "Provides remote debugging services for script contexts.");
|
|
// }
|
|
// }
|
|
// ReflectScriptDebugClasses(reflection);
|
|
// }
|
|
//};
|
|
//-------------------------------------------------------------------------
|
|
AZ::ComponentDescriptor* CreateScriptDebugAgentFactory()
|
|
{
|
|
return ScriptDebugAgent::CreateDescriptor();
|
|
}
|
|
//-------------------------------------------------------------------------
|
|
} // namespace AzFramework
|