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.
o3de/Code/Tools/LuaIDE/Source/LUA/LUADebuggerComponent.cpp

422 lines
18 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 "LUADebuggerComponent.h"
#include "LUAEditorContextMessages.h"
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/PlatformIncl.h>
#include <AzCore/Math/Crc.h>
#include <AzCore/std/parallel/mutex.h>
#include <AzCore/std/parallel/lock.h>
#include <AzCore/Component/ComponentApplicationBus.h>
#include <AzCore/Script/ScriptContext.h>
#include <GridMate/Serialize/Buffer.h>
#include <GridMate/Serialize/DataMarshal.h>
#include <AzFramework/TargetManagement/TargetManagementAPI.h>
#include <AzFramework/Script/ScriptDebugMsgReflection.h>
#include <AzToolsFramework/API/EditorAssetSystemAPI.h>
namespace LUADebugger
{
// Utility functions
// Returns true if a valid target was found, in which case the info is returned in targetInfo.
bool GetDesiredTarget(AzFramework::TargetInfo& targetInfo)
{
// discover what target the user is currently connected to, if any?
AzFramework::TargetInfo info;
EBUS_EVENT_RESULT(info, AzFramework::TargetManager::Bus, GetDesiredTarget);
if (!info.GetPersistentId())
{
AZ_TracePrintf("Debug", "The user has not chosen a target to connect to.\n");
return false;
}
targetInfo = info;
if (
(!targetInfo.IsValid()) ||
((targetInfo.GetStatusFlags() & AzFramework::TF_ONLINE) == 0) ||
((targetInfo.GetStatusFlags() & AzFramework::TF_DEBUGGABLE) == 0)
)
{
AZ_TracePrintf("Debug", "The target is currently not in a state that would allow debugging code (offline or not debuggable)\n");
return false;
}
return true;
}
Component::Component()
{
}
Component::~Component()
{
}
//////////////////////////////////////////////////////////////////////////
// AZ::Component
void Component::Init()
{
}
void Component::Activate()
{
LUAEditorDebuggerMessages::Bus::Handler::BusConnect();
AzFramework::TargetManagerClient::Bus::Handler::BusConnect();
AzFramework::TmMsgBus::Handler::BusConnect(AZ_CRC("ScriptDebugger", 0xf8ab685e));
}
void Component::Deactivate()
{
LUAEditorDebuggerMessages::Bus::Handler::BusDisconnect();
AzFramework::TargetManagerClient::Bus::Handler::BusDisconnect();
AzFramework::TmMsgBus::Handler::BusDisconnect(AZ_CRC("ScriptDebugger", 0xf8ab685e));
}
void Component::EnumerateContexts()
{
AZ_TracePrintf("LUA Debug", "Component::EnumerateContexts()\n");
AzFramework::TargetInfo targetInfo;
if (GetDesiredTarget(targetInfo))
{
EBUS_EVENT(AzFramework::TargetManager::Bus, SendTmMessage, targetInfo, AzFramework::ScriptDebugRequest(AZ_CRC("EnumContexts", 0xbdb959ba)));
}
}
void Component::AttachDebugger(const char* scriptContextName)
{
AZ_TracePrintf("LUA Debug", "Component::AttachDebugger( %s )\n", scriptContextName);
AZ_Assert(scriptContextName, "You need to supply a valid script context name to attach to!");
AzFramework::TargetInfo targetInfo;
if (GetDesiredTarget(targetInfo))
{
EBUS_EVENT(AzFramework::TargetManager::Bus, SendTmMessage, targetInfo, AzFramework::ScriptDebugRequest(AZ_CRC("AttachDebugger", 0x6590ff36), scriptContextName));
}
}
void Component::DetachDebugger()
{
AZ_TracePrintf("LUA Debug", "Component::DetachDebugger()\n");
AzFramework::TargetInfo targetInfo;
if (GetDesiredTarget(targetInfo))
{
EBUS_EVENT(AzFramework::TargetManager::Bus, SendTmMessage, targetInfo, AzFramework::ScriptDebugRequest(AZ_CRC("DetachDebugger", 0x88a2ee04)));
}
}
void Component::EnumRegisteredClasses(const char* scriptContextName)
{
AzFramework::TargetInfo targetInfo;
if (GetDesiredTarget(targetInfo))
{
EBUS_EVENT(AzFramework::TargetManager::Bus, SendTmMessage, targetInfo, AzFramework::ScriptDebugRequest(AZ_CRC("EnumRegisteredClasses", 0xed6b8070), scriptContextName));
}
}
void Component::EnumRegisteredEBuses(const char* scriptContextName)
{
AzFramework::TargetInfo targetInfo;
if (GetDesiredTarget(targetInfo))
{
EBUS_EVENT(AzFramework::TargetManager::Bus, SendTmMessage, targetInfo, AzFramework::ScriptDebugRequest(AZ_CRC("EnumRegisteredEBuses"), scriptContextName));
}
}
void Component::EnumRegisteredGlobals(const char* scriptContextName)
{
AzFramework::TargetInfo targetInfo;
if (GetDesiredTarget(targetInfo))
{
EBUS_EVENT(AzFramework::TargetManager::Bus, SendTmMessage, targetInfo, AzFramework::ScriptDebugRequest(AZ_CRC("EnumRegisteredGlobals", 0x80d1e6af), scriptContextName));
}
}
void Component::ExecuteScript(const AZStd::string& debugName, const char* scriptData, AZStd::size_t bufferLength)
{
(void)bufferLength;
// note that the data is only valid for this call, and when this call returns it may be destroyed.
// we are expected to forward the script to the current target, and execute it in that target.
AzFramework::TargetInfo targetInfo;
if (GetDesiredTarget(targetInfo))
{
// its all good!
// the target ID (for gridmate) is targetInfo.m_networkID, as a AZ::u32;
// send the script fragment and execute it!
AzFramework::ScriptDebugRequest request(AZ_CRC("ExecuteScript", 0xc35e01e7), debugName.c_str());
request.AddCustomBlob(scriptData, strlen(scriptData) + 1);
EBUS_EVENT(AzFramework::TargetManager::Bus, SendTmMessage, targetInfo, request);
}
}
void Component::CreateBreakpoint(const AZStd::string& debugName, int lineNumber)
{
// register a breakpoint.
// Debug name will be the full, absolute path, so convert it to the relative path
AZStd::string relativePath = debugName;
EBUS_EVENT(AzToolsFramework::AssetSystemRequestBus, GetRelativeProductPathFromFullSourceOrProductPath, debugName, relativePath);
relativePath = "@" + relativePath;
AzFramework::TargetInfo targetInfo;
if (GetDesiredTarget(targetInfo))
{
// local editors are never debuggable (they'd never have the debuggable flag) so if you get here you know its over the network
// and its network id is targetID.
EBUS_EVENT(AzFramework::TargetManager::Bus, SendTmMessage, targetInfo, AzFramework::ScriptDebugBreakpointRequest(AZ_CRC("AddBreakpoint", 0xba71daa4), relativePath.c_str(), static_cast<AZ::u32>(lineNumber)));
}
}
void Component::RemoveBreakpoint(const AZStd::string& debugName, int lineNumber)
{
// remove a breakpoint.
// Debug name will be the full, absolute path, so convert it to the relative path
AZStd::string relativePath = debugName;
EBUS_EVENT(AzToolsFramework::AssetSystemRequestBus, GetRelativeProductPathFromFullSourceOrProductPath, debugName, relativePath);
relativePath = "@" + relativePath;
AzFramework::TargetInfo targetInfo;
if (GetDesiredTarget(targetInfo))
{
// local editors are never debuggable (they'd never have the debuggable flag) so if you get here you know its over the network
// and its network id is targetID.
EBUS_EVENT(AzFramework::TargetManager::Bus, SendTmMessage, targetInfo, AzFramework::ScriptDebugBreakpointRequest(AZ_CRC("RemoveBreakpoint", 0x90ade500), relativePath.c_str(), static_cast<AZ::u32>(lineNumber)));
}
}
void Component::DebugRunStepOver()
{
AzFramework::TargetInfo targetInfo;
if (GetDesiredTarget(targetInfo))
{
EBUS_EVENT(AzFramework::TargetManager::Bus, SendTmMessage, targetInfo, AzFramework::ScriptDebugRequest(AZ_CRC("StepOver", 0x6b89bf41)));
}
}
void Component::DebugRunStepIn()
{
AzFramework::TargetInfo targetInfo;
if (GetDesiredTarget(targetInfo))
{
EBUS_EVENT(AzFramework::TargetManager::Bus, SendTmMessage, targetInfo, AzFramework::ScriptDebugRequest(AZ_CRC("StepIn", 0x761a6b13)));
}
}
void Component::DebugRunStepOut()
{
AzFramework::TargetInfo targetInfo;
if (GetDesiredTarget(targetInfo))
{
EBUS_EVENT(AzFramework::TargetManager::Bus, SendTmMessage, targetInfo, AzFramework::ScriptDebugRequest(AZ_CRC("StepOut", 0xac19b635)));
}
}
void Component::DebugRunStop()
{
// Script context can't be stopped. What should we do here?
}
void Component::DebugRunContinue()
{
AzFramework::TargetInfo targetInfo;
if (GetDesiredTarget(targetInfo))
{
EBUS_EVENT(AzFramework::TargetManager::Bus, SendTmMessage, targetInfo, AzFramework::ScriptDebugRequest(AZ_CRC("Continue", 0x13e32adf)));
}
}
void Component::EnumLocals()
{
AzFramework::TargetInfo targetInfo;
if (GetDesiredTarget(targetInfo))
{
EBUS_EVENT(AzFramework::TargetManager::Bus, SendTmMessage, targetInfo, AzFramework::ScriptDebugRequest(AZ_CRC("EnumLocals", 0x4aa29dcf)));
}
}
void Component::GetValue(const AZStd::string& varName)
{
AzFramework::TargetInfo targetInfo;
if (GetDesiredTarget(targetInfo))
{
EBUS_EVENT(AzFramework::TargetManager::Bus, SendTmMessage, targetInfo, AzFramework::ScriptDebugRequest(AZ_CRC("GetValue", 0x2d64f577), varName.c_str()));
}
}
void Component::SetValue(const AZ::ScriptContextDebug::DebugValue& value)
{
(void)value;
AzFramework::TargetInfo targetInfo;
if (GetDesiredTarget(targetInfo))
{
AzFramework::ScriptDebugSetValue request;
request.m_value = value;
EBUS_EVENT(AzFramework::TargetManager::Bus, SendTmMessage, targetInfo, request);
}
}
void Component::GetCallstack()
{
AzFramework::TargetInfo targetInfo;
if (GetDesiredTarget(targetInfo))
{
EBUS_EVENT(AzFramework::TargetManager::Bus, SendTmMessage, targetInfo, AzFramework::ScriptDebugRequest(AZ_CRC("GetCallstack", 0x343b24f3)));
}
}
void Component::OnReceivedMsg(AzFramework::TmMsgPtr msg)
{
if (AzFramework::ScriptDebugAck* ack = azdynamic_cast<AzFramework::ScriptDebugAck*>(msg.get()))
{
if (ack->m_ackCode == AZ_CRC("Ack", 0x22e4f8b1))
{
if (ack->m_request == AZ_CRC("Continue", 0x13e32adf) || ack->m_request == AZ_CRC("StepIn", 0x761a6b13) || ack->m_request == AZ_CRC("StepOut", 0xac19b635) || ack->m_request == AZ_CRC("StepOver", 0x6b89bf41))
{
EBUS_EVENT(LUAEditor::Context_DebuggerManagement::Bus, OnExecutionResumed);
}
else if (ack->m_request == AZ_CRC("AttachDebugger", 0x6590ff36))
{
EBUS_EVENT(LUAEditor::Context_DebuggerManagement::Bus, OnDebuggerAttached);
}
else if (ack->m_request == AZ_CRC("DetachDebugger", 0x88a2ee04))
{
EBUS_EVENT(LUAEditor::Context_DebuggerManagement::Bus, OnDebuggerDetached);
}
}
else if (ack->m_ackCode == AZ_CRC("IllegalOperation", 0x437dc900))
{
if (ack->m_request == AZ_CRC("ExecuteScript", 0xc35e01e7))
{
EBUS_EVENT(LUAEditor::Context_DebuggerManagement::Bus, OnExecuteScriptResult, false);
}
else if (ack->m_request == AZ_CRC("AttachDebugger", 0x6590ff36))
{
EBUS_EVENT(LUAEditor::Context_DebuggerManagement::Bus, OnDebuggerRefused);
}
else
{
AZ_TracePrintf("LUA Debug", "Debug Agent: Illegal operation 0x%x. Script context is in the wrong state.\n", ack->m_request);
}
}
else if (ack->m_ackCode == AZ_CRC("AccessDenied", 0xde72ce21))
{
AZ_TracePrintf("LUA Debug", "Debug Agent: Access denied 0x%x. Attach debugger first!\n", ack->m_request);
EBUS_EVENT(LUAEditor::Context_DebuggerManagement::Bus, OnDebuggerDetached);
}
else if (ack->m_ackCode == AZ_CRC("InvalidCmd", 0x926abd27))
{
AZ_TracePrintf("LUA Debug", "The remote script debug agent claims that we sent it an invalid request(0x%x)!\n", ack->m_request);
}
}
else if (azrtti_istypeof<AzFramework::ScriptDebugAckBreakpoint*>(msg.get()))
{
AzFramework::ScriptDebugAckBreakpoint* ackBreakpoint = azdynamic_cast<AzFramework::ScriptDebugAckBreakpoint*>(msg.get());
if (ackBreakpoint->m_id == AZ_CRC("BreakpointHit", 0xf1a38e0b))
{
EBUS_EVENT(LUAEditor::Context_DebuggerManagement::Bus, OnBreakpointHit, ackBreakpoint->m_moduleName, ackBreakpoint->m_line);
}
else if (ackBreakpoint->m_id == AZ_CRC("AddBreakpoint", 0xba71daa4))
{
EBUS_EVENT(LUAEditor::Context_DebuggerManagement::Bus, OnBreakpointAdded, ackBreakpoint->m_moduleName, ackBreakpoint->m_line);
}
else if (ackBreakpoint->m_id == AZ_CRC("RemoveBreakpoint", 0x90ade500))
{
EBUS_EVENT(LUAEditor::Context_DebuggerManagement::Bus, OnBreakpointRemoved, ackBreakpoint->m_moduleName, ackBreakpoint->m_line);
}
}
else if (AzFramework::ScriptDebugAckExecute* ackExecute = azdynamic_cast<AzFramework::ScriptDebugAckExecute*>(msg.get()))
{
EBUS_EVENT(LUAEditor::Context_DebuggerManagement::Bus, OnExecuteScriptResult, ackExecute->m_result);
}
else if (azrtti_istypeof<AzFramework::ScriptDebugEnumLocalsResult*>(msg.get()))
{
AzFramework::ScriptDebugEnumLocalsResult* enumLocals = azdynamic_cast<AzFramework::ScriptDebugEnumLocalsResult*>(msg.get());
EBUS_EVENT(LUAEditor::Context_DebuggerManagement::Bus, OnReceivedLocalVariables, enumLocals->m_names);
}
else if (azrtti_istypeof<AzFramework::ScriptDebugEnumContextsResult*>(msg.get()))
{
AzFramework::ScriptDebugEnumContextsResult* enumContexts = azdynamic_cast<AzFramework::ScriptDebugEnumContextsResult*>(msg.get());
EBUS_EVENT(LUAEditor::Context_DebuggerManagement::Bus, OnReceivedAvailableContexts, enumContexts->m_names);
}
else if (azrtti_istypeof<AzFramework::ScriptDebugGetValueResult*>(msg.get()))
{
AzFramework::ScriptDebugGetValueResult* getValues = azdynamic_cast<AzFramework::ScriptDebugGetValueResult*>(msg.get());
EBUS_EVENT(LUAEditor::Context_DebuggerManagement::Bus, OnReceivedValueState, getValues->m_value);
}
else if (azrtti_istypeof<AzFramework::ScriptDebugSetValueResult*>(msg.get()))
{
AzFramework::ScriptDebugSetValueResult* setValue = azdynamic_cast<AzFramework::ScriptDebugSetValueResult*>(msg.get());
EBUS_EVENT(LUAEditor::Context_DebuggerManagement::Bus, OnSetValueResult, setValue->m_name, setValue->m_result);
}
else if (azrtti_istypeof<AzFramework::ScriptDebugCallStackResult*>(msg.get()))
{
AzFramework::ScriptDebugCallStackResult* callStackResult = azdynamic_cast<AzFramework::ScriptDebugCallStackResult*>(msg.get());
AZStd::vector<AZStd::string> callstack;
const char* c1 = callStackResult->m_callstack.c_str();
for (const char* c2 = c1; *c2; ++c2)
{
if (*c2 == '\n')
{
callstack.push_back();
callstack.back().assign(c1, c2);
c1 = c2 + 1;
}
}
callstack.push_back();
callstack.back() = c1;
EBUS_EVENT(LUAEditor::Context_DebuggerManagement::Bus, OnReceivedCallstack, callstack);
}
else if (azrtti_istypeof<AzFramework::ScriptDebugRegisteredGlobalsResult*>(msg.get()))
{
AzFramework::ScriptDebugRegisteredGlobalsResult* registeredGlobals = azdynamic_cast<AzFramework::ScriptDebugRegisteredGlobalsResult*>(msg.get());
EBUS_EVENT(LUAEditor::Context_DebuggerManagement::Bus, OnReceivedRegisteredGlobals, registeredGlobals->m_methods, registeredGlobals->m_properties);
}
else if (azrtti_istypeof<AzFramework::ScriptDebugRegisteredClassesResult*>(msg.get()))
{
AzFramework::ScriptDebugRegisteredClassesResult* registeredClasses = azdynamic_cast<AzFramework::ScriptDebugRegisteredClassesResult*>(msg.get());
EBUS_EVENT(LUAEditor::Context_DebuggerManagement::Bus, OnReceivedRegisteredClasses, registeredClasses->m_classes);
}
else if (azrtti_istypeof<AzFramework::ScriptDebugRegisteredEBusesResult*>(msg.get()))
{
AzFramework::ScriptDebugRegisteredEBusesResult* registeredEBuses = azdynamic_cast<AzFramework::ScriptDebugRegisteredEBusesResult*>(msg.get());
EBUS_EVENT(LUAEditor::Context_DebuggerManagement::Bus, OnReceivedRegisteredEBuses, registeredEBuses->m_ebusList);
}
else
{
AZ_Assert(false, "We received a message of an unrecognized class type!");
}
}
void Component::DesiredTargetChanged(AZ::u32 newTargetID, AZ::u32 oldTargetID)
{
(void)newTargetID;
(void)oldTargetID;
}
void Component::Reflect(AZ::ReflectContext* reflection)
{
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(reflection);
if (serializeContext)
{
serializeContext->Class<Component, AZ::Component>()
->Version(1)
;
}
}
}