ScriptRemoteDebugging.cpp 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include "ScriptRemoteDebugging.h"
  9. #include "ScriptDebugAgentBus.h"
  10. #include "ScriptDebugMsgReflection.h"
  11. #include <AzFramework/Network/IRemoteTools.h>
  12. #include <AzFramework/Metrics/MetricsPlainTextNameRegistration.h>
  13. #include <AzFramework/Script/ScriptRemoteDebuggingConstants.h>
  14. #include <AzFramework/AzFramework_Traits_Platform.h> // Need to know the state of AZ_TRAIT_AZFRAMEWORK_SHOW_MOUSE_ON_LUA_BREAKPOINT
  15. #include <AzFramework/Input/Buses/Requests/InputSystemCursorRequestBus.h>
  16. #include <AzFramework/Input/Devices/Mouse/InputDeviceMouse.h>
  17. #include <AzCore/Component/TickBus.h>
  18. #include <AzCore/Component/Component.h>
  19. #include <AzCore/Component/ComponentApplicationBus.h>
  20. #include <AzCore/Interface/Interface.h>
  21. #include <AzCore/Script/ScriptSystemBus.h>
  22. #include <AzCore/Serialization/SerializeContext.h>
  23. #include <AzCore/Serialization/EditContext.h>
  24. #include <AzCore/RTTI/BehaviorContext.h>
  25. #include <AzCore/std/containers/vector.h>
  26. #include <AzCore/std/string/string.h>
  27. #include <AzCore/std/parallel/lock.h>
  28. #include <AzCore/Math/Crc.h>
  29. #include <AzCore/std/parallel/thread.h>
  30. #include <AzCore/std/parallel/atomic.h>
  31. #include <AzNetworking/Framework/INetworking.h>
  32. namespace AzFramework
  33. {
  34. namespace ScriptDebugAgentInternal
  35. {
  36. //-------------------------------------------------------------------------
  37. static bool EnumClass(const char* name, const AZ::Uuid& typeId, void* userData)
  38. {
  39. ScriptUserClassList& output = *reinterpret_cast<ScriptUserClassList*>(userData);
  40. output.emplace_back().m_name = name;
  41. output.back().m_typeId = typeId;
  42. return true;
  43. }
  44. //-------------------------------------------------------------------------
  45. static bool EnumMethod(const AZ::Uuid* classTypeId, const char* name, const char* dbgParamInfo, void* userData)
  46. {
  47. (void)classTypeId;
  48. ScriptUserMethodList& output = *reinterpret_cast<ScriptUserMethodList*>(userData);
  49. output.emplace_back().m_name = name;
  50. output.back().m_dbgParamInfo = dbgParamInfo ? dbgParamInfo : "null";
  51. return true;
  52. }
  53. //-------------------------------------------------------------------------
  54. static bool EnumProperty(const AZ::Uuid* classTypeId, const char* name, bool isRead, bool isWrite, void* userData)
  55. {
  56. (void)classTypeId;
  57. ScriptUserPropertyList& output = *reinterpret_cast<ScriptUserPropertyList*>(userData);
  58. output.emplace_back().m_name = name;
  59. output.back().m_isRead = isRead;
  60. output.back().m_isWrite = isWrite;
  61. return true;
  62. }
  63. //-------------------------------------------------------------------------
  64. static bool EnumEBus(const AZStd::string& name, bool canBroadcast, bool canQueue, bool hasHandler, void* userData)
  65. {
  66. ScriptUserEBusList& output = *reinterpret_cast<ScriptUserEBusList*>(userData);
  67. bool found = false;
  68. for (ScriptUserEBusList::iterator it = output.begin(); it != output.end(); ++it)
  69. {
  70. if (name == it->m_name)
  71. {
  72. found = true;
  73. }
  74. }
  75. AZ_Warning("ScriptRemoteDebugging", !found, "Ebus (%s) already enumerated", name.c_str());
  76. if (!found)
  77. {
  78. auto& ebus = output.emplace_back();
  79. ebus.m_name = name;
  80. ebus.m_canBroadcast = canBroadcast;
  81. ebus.m_canQueue = canQueue;
  82. ebus.m_hasHandler = hasHandler;
  83. }
  84. return true;
  85. }
  86. //-------------------------------------------------------------------------
  87. static bool EnumEBusSender(const AZStd::string& ebusName, const AZStd::string& senderName, const AZStd::string& dbgParamInfo, const AZStd::string& category, void* userData)
  88. {
  89. ScriptUserEBusList& output = *reinterpret_cast<ScriptUserEBusList*>(userData);
  90. for (ScriptUserEBusList::iterator it = output.begin(); it != output.end(); ++it)
  91. {
  92. if (ebusName == it->m_name)
  93. {
  94. auto& event = it->m_events.emplace_back();
  95. event.m_name = senderName;
  96. event.m_dbgParamInfo = dbgParamInfo;
  97. event.m_category = category;
  98. return true;
  99. }
  100. }
  101. AZ_Assert(false, "Received an enumeration of an eBus sender method for an eBus we have not enumerated yet!");
  102. return false;
  103. }
  104. //-------------------------------------------------------------------------
  105. static bool EnumClassMethod(const AZ::Uuid* classTypeId, const char* name, const char* dbgParamInfo, void* userData)
  106. {
  107. ScriptUserClassList& output = *reinterpret_cast<ScriptUserClassList*>(userData);
  108. for (ScriptUserClassList::iterator it = output.begin(); it != output.end(); ++it)
  109. {
  110. if (classTypeId && *classTypeId == it->m_typeId)
  111. {
  112. return EnumMethod(classTypeId, name, dbgParamInfo, &(it->m_methods));
  113. }
  114. }
  115. AZ_Assert(false, "Received enumeration of a class method for a class we have not enumerated yet!");
  116. return true;
  117. }
  118. //-------------------------------------------------------------------------
  119. static bool EnumClassProperty(const AZ::Uuid* classTypeId, const char* name, bool isRead, bool isWrite, void* userData)
  120. {
  121. ScriptUserClassList& output = *reinterpret_cast<ScriptUserClassList*>(userData);
  122. for (ScriptUserClassList::iterator it = output.begin(); it != output.end(); ++it)
  123. {
  124. if (classTypeId && *classTypeId == it->m_typeId)
  125. {
  126. return EnumProperty(classTypeId, name, isRead, isWrite, &(it->m_properties));
  127. }
  128. }
  129. AZ_Assert(false, "Received enumeration of a class property for a class we have not enumerated yet!");
  130. return true;
  131. }
  132. //-------------------------------------------------------------------------
  133. static bool EnumGlobalMethod(const AZ::Uuid* classTypeId, const char* name, const char* dbgParamInfo, void* userData)
  134. {
  135. ScriptDebugRegisteredGlobalsResult* output = reinterpret_cast<ScriptDebugRegisteredGlobalsResult*>(userData);
  136. return EnumMethod(classTypeId, name, dbgParamInfo, &output->m_methods);
  137. }
  138. //-------------------------------------------------------------------------
  139. static bool EnumGlobalProperty(const AZ::Uuid* classTypeId, const char* name, bool isRead, bool isWrite, void* userData)
  140. {
  141. ScriptDebugRegisteredGlobalsResult* output = reinterpret_cast<ScriptDebugRegisteredGlobalsResult*>(userData);
  142. return EnumProperty(classTypeId, name, isRead, isWrite, &output->m_properties);
  143. }
  144. //-------------------------------------------------------------------------
  145. static bool EnumLocals(AZStd::vector<AZStd::string>* output, const char* name, AZ::ScriptDataContext& dataContext)
  146. {
  147. (void)dataContext;
  148. output->push_back(name);
  149. return true;
  150. }
  151. //-------------------------------------------------------------------------
  152. } // namespace ScriptDebugAgentInternal
  153. //-------------------------------------------------------------------------
  154. //-------------------------------------------------------------------------
  155. class ScriptDebugAgent
  156. : public AZ::Component
  157. , public ScriptDebugAgentBus::Handler
  158. , AZ::SystemTickBus::Handler
  159. {
  160. public:
  161. AZ_COMPONENT(ScriptDebugAgent, "{624a7be2-3c7e-4119-aee2-1db2bdb6cc89}");
  162. ScriptDebugAgent() = default;
  163. //////////////////////////////////////////////////////////////////////////
  164. // Component base
  165. void Init() override;
  166. void Activate() override;
  167. void Deactivate() override;
  168. //////////////////////////////////////////////////////////////////////////
  169. //////////////////////////////////////////////////////////////////////////
  170. // AZ::SystemTickBus
  171. void OnSystemTick() override;
  172. //////////////////////////////////////////////////////////////////////////
  173. //////////////////////////////////////////////////////////////////////////
  174. // ScriptDebugAgentBus
  175. void RegisterContext(AZ::ScriptContext* sc, const char* name) override;
  176. void UnregisterContext(AZ::ScriptContext* sc) override;
  177. //////////////////////////////////////////////////////////////////////////
  178. protected:
  179. ScriptDebugAgent(const ScriptDebugAgent&) = delete;
  180. static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
  181. static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible);
  182. static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent);
  183. static void Reflect(AZ::ReflectContext* context);
  184. void Attach(const RemoteToolsEndpointInfo& ti, const char* scriptContextName);
  185. void Detach();
  186. void BreakpointCallback(AZ::ScriptContextDebug* debugContext, const AZ::ScriptContextDebug::Breakpoint* breakpoint);
  187. void DebugCommandCallback(AZ::ScriptContextDebug* debugContext);
  188. void Process();
  189. RemoteToolsEndpointInfo m_debugger;
  190. RemoteToolsMessageQueue m_msgQueue;
  191. AZStd::mutex m_msgMutex;
  192. AZ::ScriptContext* m_curContext;
  193. struct ContextRecord
  194. {
  195. AZ::ScriptContext* m_context;
  196. AZStd::string m_name;
  197. };
  198. typedef AZStd::vector<ContextRecord> ContextMap;
  199. ContextMap m_availableContexts;
  200. enum SDA_STATE
  201. {
  202. SDA_STATE_DETACHED,
  203. SDA_STATE_RUNNING,
  204. SDA_STATE_PAUSED,
  205. SDA_STATE_DETACHING,
  206. };
  207. AZStd::atomic_uint m_executionState;
  208. };
  209. //-------------------------------------------------------------------------
  210. void ScriptDebugAgent::Init()
  211. {
  212. }
  213. //-------------------------------------------------------------------------
  214. void ScriptDebugAgent::Activate()
  215. {
  216. m_executionState = SDA_STATE_DETACHED;
  217. m_curContext = nullptr;
  218. // register default app script context if there is one
  219. AZ::ScriptContext* defaultScriptContext = nullptr;
  220. AZ::ScriptSystemRequestBus::BroadcastResult(
  221. defaultScriptContext, &AZ::ScriptSystemRequestBus::Events::GetContext, AZ::ScriptContextIds::DefaultScriptContextId);
  222. if (defaultScriptContext)
  223. {
  224. RegisterContext(defaultScriptContext, "Default");
  225. }
  226. AZ::ScriptContext* cryScriptContext = nullptr;
  227. AZ::ScriptSystemRequestBus::BroadcastResult(
  228. cryScriptContext, &AZ::ScriptSystemRequestBus::Events::GetContext, AZ::ScriptContextIds::CryScriptContextId);
  229. if (cryScriptContext)
  230. {
  231. RegisterContext(cryScriptContext, "Cry");
  232. }
  233. ScriptDebugAgentBus::Handler::BusConnect();
  234. AZ::SystemTickBus::Handler::BusConnect();
  235. }
  236. //-------------------------------------------------------------------------
  237. void ScriptDebugAgent::Deactivate()
  238. {
  239. AZ::SystemTickBus::Handler::BusDisconnect();
  240. // TODO: Make thread safe if we ever have multithreaded script contexts!
  241. if (m_executionState != SDA_STATE_DETACHED)
  242. {
  243. Detach();
  244. }
  245. AZStd::lock_guard<AZStd::mutex> l(m_msgMutex);
  246. m_msgQueue.clear();
  247. }
  248. //-------------------------------------------------------------------------
  249. void ScriptDebugAgent::OnSystemTick()
  250. {
  251. AzFramework::IRemoteTools* remoteTools = AzFramework::RemoteToolsInterface::Get();
  252. if (remoteTools)
  253. {
  254. const AzFramework::ReceivedRemoteToolsMessages* messages =
  255. remoteTools->GetReceivedMessages(AzFramework::LuaToolsKey);
  256. if (messages)
  257. {
  258. for (const AzFramework::RemoteToolsMessagePointer& msg : *messages)
  259. {
  260. AZStd::lock_guard<AZStd::mutex> l(m_msgMutex);
  261. m_msgQueue.push_back(msg);
  262. }
  263. remoteTools->ClearReceivedMessages(AzFramework::LuaToolsKey);
  264. }
  265. }
  266. // If we are attached, then all processing should happen
  267. // in the attached context.
  268. if (m_executionState == SDA_STATE_DETACHED)
  269. {
  270. Process();
  271. }
  272. }
  273. //-------------------------------------------------------------------------
  274. void ScriptDebugAgent::RegisterContext(AZ::ScriptContext* sc, const char* name)
  275. {
  276. for (ContextMap::const_iterator it = m_availableContexts.begin(); it != m_availableContexts.end(); ++it)
  277. {
  278. if (it->m_context == sc)
  279. {
  280. AZ_Assert(false, "ScriptContext 0x%p is already registered as %s! New registration ignored.", sc, it->m_name.c_str());
  281. return;
  282. }
  283. }
  284. m_availableContexts.emplace_back().m_context = sc;
  285. m_availableContexts.back().m_name = name;
  286. }
  287. //-------------------------------------------------------------------------
  288. void ScriptDebugAgent::UnregisterContext(AZ::ScriptContext* sc)
  289. {
  290. for (ContextMap::const_iterator it = m_availableContexts.begin(); it != m_availableContexts.end(); ++it)
  291. {
  292. if (it->m_context == sc)
  293. {
  294. if (m_curContext == sc)
  295. {
  296. // TODO: This operation needs to be thread-safe if we ever run contexts from multiple threads.
  297. Detach();
  298. }
  299. m_availableContexts.erase(it);
  300. return;
  301. }
  302. }
  303. }
  304. //-------------------------------------------------------------------------
  305. void ScriptDebugAgent::Attach(const RemoteToolsEndpointInfo& ti, const char* scriptContextName)
  306. {
  307. for (ContextMap::iterator it = m_availableContexts.begin(); it != m_availableContexts.end(); ++it)
  308. {
  309. if (azstricmp(scriptContextName, it->m_name.c_str()) == 0)
  310. {
  311. AZ::ScriptContext* sc = it->m_context;
  312. AZ_Assert(sc, "How did we end up with a NULL in the available contexts map?");
  313. m_debugger = ti;
  314. m_curContext = sc;
  315. sc->EnableDebug();
  316. AZ::ScriptContextDebug* dbgContext = sc->GetDebugContext();
  317. if (dbgContext)
  318. {
  319. dbgContext->EnableStackRecord();
  320. AZ::ScriptContextDebug::BreakpointCallback breakpointCallback = AZStd::bind(&ScriptDebugAgent::BreakpointCallback, this, AZStd::placeholders::_1, AZStd::placeholders::_2);
  321. dbgContext->EnableBreakpoints(breakpointCallback);
  322. AZ::ScriptContextDebug::ProcessDebugCmdCallback debugCommandCallback = AZStd::bind(&ScriptDebugAgent::DebugCommandCallback, this, AZStd::placeholders::_1);
  323. dbgContext->EnableDebugCmdProcess(debugCommandCallback);
  324. }
  325. // Notify debugger that we successfully connected
  326. RemoteToolsInterface::Get()->SendRemoteToolsMessage(
  327. ti, ScriptDebugAck(AZ_CRC_CE("AttachDebugger"), AZ_CRC_CE("Ack")));
  328. AZ_TracePrintf("LUA", "Remote debugger %s has attached to context %s.\n", m_debugger.GetDisplayName(), it->m_name.c_str());
  329. m_executionState = SDA_STATE_RUNNING;
  330. return;
  331. }
  332. }
  333. // Failed to find context, notify debugger that the connection was rejected.
  334. RemoteToolsInterface::Get()->SendRemoteToolsMessage(
  335. ti, ScriptDebugAck(AZ_CRC_CE("AttachDebugger"), AZ_CRC_CE("IllegalOperation")));
  336. }
  337. //-------------------------------------------------------------------------
  338. void ScriptDebugAgent::Detach()
  339. {
  340. RemoteToolsInterface::Get()->SendRemoteToolsMessage(
  341. m_debugger, ScriptDebugAck(AZ_CRC_CE("DetachDebugger"), AZ_CRC_CE("Ack")));
  342. // TODO: We need to make sure we are thread safe if the contexts are running on
  343. // different threads.
  344. //if (m_curContext->GetErrorHookUserData() == this) {
  345. // m_curContext->SetErrorHook(NULL);
  346. //}
  347. AZ::ScriptContextDebug* debugContext = m_curContext->GetDebugContext();
  348. debugContext->DisableBreakpoints();
  349. debugContext->DisableStackRecord();
  350. debugContext->DisableDebugCmdProcess();
  351. m_curContext->DisableDebug();
  352. AZ_TracePrintf("LUA", "Remote debugger %s has detached from context 0x%p.\n", m_debugger.GetDisplayName(), m_curContext);
  353. m_debugger = RemoteToolsEndpointInfo();
  354. m_curContext = nullptr;
  355. m_executionState = SDA_STATE_DETACHED;
  356. }
  357. //-------------------------------------------------------------------------
  358. void ScriptDebugAgent::DebugCommandCallback(AZ::ScriptContextDebug* debugContext)
  359. {
  360. (void)debugContext;
  361. AZ_Assert(m_curContext, "We are debugging without a script context!");
  362. AZ_Assert(m_curContext->GetDebugContext() == debugContext, "Context mismatch. Are we attached to the correct script context?");
  363. if (m_executionState != SDA_STATE_DETACHED)
  364. {
  365. // This is the only safe place to tear down the debug context because
  366. // it is the only function that runs in the script context thread and is never
  367. // called from within any debugContext callbacks.
  368. if (m_executionState == SDA_STATE_DETACHING)
  369. {
  370. AZ_TracePrintf("LUA", "Disabling debugging for script context(0x%p).\n", m_curContext);
  371. Detach();
  372. }
  373. else
  374. {
  375. Process();
  376. }
  377. }
  378. }
  379. //-------------------------------------------------------------------------
  380. void ScriptDebugAgent::BreakpointCallback(AZ::ScriptContextDebug* debugContext, const AZ::ScriptContextDebug::Breakpoint* breakpoint)
  381. {
  382. (void)debugContext;
  383. AZ_Assert(m_curContext, "We are debugging without a script context!");
  384. AZ_Assert(m_curContext->GetDebugContext() == debugContext, "Context mismatch. Are we attached to the correct script context?");
  385. if (m_executionState == SDA_STATE_RUNNING)
  386. {
  387. m_executionState = SDA_STATE_PAUSED;
  388. #if AZ_TRAIT_AZFRAMEWORK_SHOW_MOUSE_ON_LUA_BREAKPOINT
  389. // We are about to block the main thread, only allowing to process
  390. // network events. This works fine on Windows, but on Linux the mouse
  391. // pointer doesn't show up when the user ALT+TAB out of the Editor window.
  392. // We need to make mouse cursor visible again and it fixes all the Linux
  393. // problems, and it doesn't hurt Windows either.
  394. SystemCursorState systemCursorState{}; // Remember the state of the cursor.
  395. AzFramework::InputSystemCursorRequestBus::Event(
  396. AzFramework::InputDeviceMouse::Id,
  397. [&systemCursorState](AzFramework::InputSystemCursorRequests* requests)
  398. {
  399. systemCursorState = requests->GetSystemCursorState();
  400. requests->SetSystemCursorState(AzFramework::SystemCursorState::UnconstrainedAndVisible);
  401. }
  402. );
  403. #endif
  404. ScriptDebugAckBreakpoint response;
  405. response.m_id = AZ_CRC_CE("BreakpointHit");
  406. response.m_moduleName = breakpoint->m_sourceName;
  407. response.m_line = static_cast<AZ::u32>(breakpoint->m_lineNumber);
  408. RemoteToolsInterface::Get()->SendRemoteToolsMessage(m_debugger, response);
  409. while (m_executionState == SDA_STATE_PAUSED)
  410. {
  411. AzFramework::IRemoteTools* remoteTools = AzFramework::RemoteToolsInterface::Get();
  412. if (remoteTools)
  413. {
  414. const AzFramework::ReceivedRemoteToolsMessages* messages =
  415. remoteTools->GetReceivedMessages(AzFramework::LuaToolsKey);
  416. if (messages)
  417. {
  418. for (const AzFramework::RemoteToolsMessagePointer& msg : *messages)
  419. {
  420. AZStd::lock_guard<AZStd::mutex> l(m_msgMutex);
  421. m_msgQueue.push_back(msg);
  422. }
  423. remoteTools->ClearReceivedMessages(AzFramework::LuaToolsKey);
  424. }
  425. }
  426. Process();
  427. AZ::Interface<AzNetworking::INetworking>::Get()->ForceUpdate();
  428. AZStd::this_thread::yield();
  429. }
  430. #if AZ_TRAIT_AZFRAMEWORK_SHOW_MOUSE_ON_LUA_BREAKPOINT
  431. // Restore the state of the mouse cursor, and the game should continue running as usual.
  432. AzFramework::InputSystemCursorRequestBus::Event(
  433. AzFramework::InputDeviceMouse::Id,
  434. &AzFramework::InputSystemCursorRequests::SetSystemCursorState,
  435. systemCursorState);
  436. #endif
  437. }
  438. }
  439. //-------------------------------------------------------------------------
  440. void ScriptDebugAgent::Process()
  441. {
  442. // Process messages
  443. AZ::ScriptContextDebug* dbgContext = m_curContext ? m_curContext->GetDebugContext() : nullptr;
  444. while (!m_msgQueue.empty())
  445. {
  446. m_msgMutex.lock();
  447. RemoteToolsMessagePointer msg = *m_msgQueue.begin();
  448. m_msgQueue.pop_front();
  449. m_msgMutex.unlock();
  450. AZ_Assert(msg, "We received a NULL message in the script debug agent's message queue!");
  451. RemoteToolsEndpointInfo sender = RemoteToolsInterface::Get()->GetEndpointInfo(AzFramework::LuaToolsKey, msg->GetSenderTargetId());
  452. // The only message we accept without a target match is AttachDebugger
  453. if (!m_debugger.IsIdentityEqualTo(sender))
  454. {
  455. ScriptDebugRequest* request = azdynamic_cast<ScriptDebugRequest*>(msg.get());
  456. if (!request ||
  457. (request->m_request != AZ_CRC_CE("AttachDebugger") &&
  458. request->m_request != AZ_CRC_CE("EnumContexts")))
  459. {
  460. AZ_TracePrintf(
  461. "LUA", "Rejecting msg 0x%x (%s is not the attached debugger)\n", request->m_request, sender.GetDisplayName());
  462. RemoteToolsInterface::Get()->SendRemoteToolsMessage(
  463. sender, ScriptDebugAck(request->m_request, AZ_CRC_CE("AccessDenied")));
  464. continue;
  465. }
  466. }
  467. if (azrtti_istypeof<ScriptDebugBreakpointRequest*>(msg.get()))
  468. {
  469. ScriptDebugBreakpointRequest* request = azdynamic_cast<ScriptDebugBreakpointRequest*>(msg.get());
  470. AZ::ScriptContextDebug::Breakpoint bp;
  471. bp.m_sourceName = request->m_context.c_str();
  472. bp.m_lineNumber = request->m_line;
  473. if (request->m_request == AZ_CRC_CE("AddBreakpoint"))
  474. {
  475. AZ_TracePrintf("LUA", "Adding breakpoint %s:%d\n", bp.m_sourceName.c_str(), bp.m_lineNumber);
  476. dbgContext->AddBreakpoint(bp);
  477. }
  478. else if (request->m_request == AZ_CRC_CE("RemoveBreakpoint"))
  479. {
  480. AZ_TracePrintf("LUA", "Removing breakpoint %s:%d\n", bp.m_sourceName.c_str(), bp.m_lineNumber);
  481. dbgContext->RemoveBreakpoint(bp);
  482. }
  483. ScriptDebugAckBreakpoint response;
  484. response.m_id = request->m_request;
  485. response.m_moduleName = request->m_context;
  486. response.m_line = request->m_line;
  487. RemoteToolsInterface::Get()->SendRemoteToolsMessage(sender, response);
  488. }
  489. else if (azrtti_istypeof<ScriptDebugSetValue*>(msg.get())) // sets the value of a variable
  490. {
  491. if (m_executionState == SDA_STATE_PAUSED)
  492. {
  493. ScriptDebugSetValue* request = azdynamic_cast<ScriptDebugSetValue*>(msg.get());
  494. ScriptDebugSetValueResult response;
  495. response.m_name = request->m_value.m_name;
  496. response.m_result = dbgContext->SetValue(request->m_value);
  497. RemoteToolsInterface::Get()->SendRemoteToolsMessage(sender, response);
  498. }
  499. else
  500. {
  501. AZ_TracePrintf("LUA", "Command rejected. 'SetValue' can only be issued while on a breakpoint.\n");
  502. RemoteToolsInterface::Get()->SendRemoteToolsMessage(sender, ScriptDebugAck(AZ_CRC_CE("SetValue"), AZ_CRC_CE("IllegalOperation")));
  503. }
  504. }
  505. else if (azrtti_istypeof<ScriptDebugRequest*>(msg.get()))
  506. {
  507. ScriptDebugRequest* request = azdynamic_cast<ScriptDebugRequest*>(msg.get());
  508. // Check request type
  509. // EnumLocals
  510. if (request->m_request == AZ_CRC_CE("EnumLocals")) // enumerates local variables
  511. {
  512. if (m_executionState == SDA_STATE_PAUSED)
  513. {
  514. ScriptDebugEnumLocalsResult response;
  515. AZ::ScriptContextDebug::EnumLocalCallback enumCB = AZStd::bind(&ScriptDebugAgentInternal::EnumLocals, &response.m_names, AZStd::placeholders::_1, AZStd::placeholders::_2);
  516. dbgContext->EnumLocals(enumCB);
  517. RemoteToolsInterface::Get()->SendRemoteToolsMessage(sender, response);
  518. }
  519. else
  520. {
  521. AZ_TracePrintf("LUA", "Command rejected. 'EnumLocals' can only be issued while on a breakpoint.\n");
  522. RemoteToolsInterface::Get()->SendRemoteToolsMessage(sender, ScriptDebugAck(request->m_request, AZ_CRC_CE("IllegalOperation")));
  523. }
  524. // GetValue
  525. }
  526. else if (request->m_request == AZ_CRC_CE("GetValue"))
  527. {
  528. ScriptDebugGetValueResult response;
  529. response.m_value.m_name = request->m_context;
  530. dbgContext->GetValue(response.m_value);
  531. RemoteToolsInterface::Get()->SendRemoteToolsMessage(sender, response);
  532. // StepOver
  533. }
  534. else if (request->m_request == AZ_CRC_CE("StepOver"))
  535. {
  536. if (m_executionState == SDA_STATE_PAUSED)
  537. {
  538. dbgContext->StepOver();
  539. m_executionState = SDA_STATE_RUNNING;
  540. RemoteToolsInterface::Get()->SendRemoteToolsMessage(sender, ScriptDebugAck(request->m_request, AZ_CRC_CE("Ack")));
  541. }
  542. else
  543. {
  544. AZ_TracePrintf("LUA", "Command rejected. 'StepOver' can only be issued while on a breakpoint.\n");
  545. RemoteToolsInterface::Get()->SendRemoteToolsMessage(sender, ScriptDebugAck(request->m_request, AZ_CRC_CE("IllegalOperation")));
  546. }
  547. // StepIn
  548. }
  549. else if (request->m_request == AZ_CRC_CE("StepIn"))
  550. {
  551. if (m_executionState == SDA_STATE_PAUSED)
  552. {
  553. dbgContext->StepInto();
  554. m_executionState = SDA_STATE_RUNNING;
  555. RemoteToolsInterface::Get()->SendRemoteToolsMessage(sender, ScriptDebugAck(request->m_request, AZ_CRC_CE("Ack")));
  556. }
  557. else
  558. {
  559. AZ_TracePrintf("LUA", "Command rejected. 'StepIn' can only be issued while on a breakpoint.\n");
  560. RemoteToolsInterface::Get()->SendRemoteToolsMessage(sender, ScriptDebugAck(request->m_request, AZ_CRC_CE("IllegalOperation")));
  561. }
  562. // StepOut
  563. }
  564. else if (request->m_request == AZ_CRC_CE("StepOut"))
  565. {
  566. if (m_executionState == SDA_STATE_PAUSED)
  567. {
  568. dbgContext->StepOut();
  569. m_executionState = SDA_STATE_RUNNING;
  570. RemoteToolsInterface::Get()->SendRemoteToolsMessage(sender, ScriptDebugAck(request->m_request, AZ_CRC_CE("Ack")));
  571. }
  572. else
  573. {
  574. AZ_TracePrintf("LUA", "Command rejected. 'StepOut' can only be issued while on a breakpoint.\n");
  575. RemoteToolsInterface::Get()->SendRemoteToolsMessage(sender, ScriptDebugAck(request->m_request, AZ_CRC_CE("IllegalOperation")));
  576. }
  577. // Continue
  578. }
  579. else if (request->m_request == AZ_CRC_CE("Continue"))
  580. {
  581. if (m_executionState == SDA_STATE_PAUSED)
  582. {
  583. m_executionState = SDA_STATE_RUNNING;
  584. RemoteToolsInterface::Get()->SendRemoteToolsMessage(sender, ScriptDebugAck(request->m_request, AZ_CRC_CE("Ack")));
  585. }
  586. else
  587. {
  588. AZ_TracePrintf("LUA", "Command rejected. 'Continue' can only be issued while on a breakpoint.\n");
  589. RemoteToolsInterface::Get()->SendRemoteToolsMessage(sender, ScriptDebugAck(request->m_request, AZ_CRC_CE("IllegalOperation")));
  590. }
  591. // GetCallstack
  592. }
  593. else if (request->m_request == AZ_CRC_CE("GetCallstack"))
  594. {
  595. if (m_executionState == SDA_STATE_PAUSED)
  596. {
  597. char bufStackTrace[4096];
  598. dbgContext->StackTrace(bufStackTrace, 4096);
  599. ScriptDebugCallStackResult response;
  600. response.m_callstack = bufStackTrace;
  601. RemoteToolsInterface::Get()->SendRemoteToolsMessage(sender, response);
  602. }
  603. else
  604. {
  605. AZ_TracePrintf("LUA", "Command rejected. 'GetCallstack' can only be issued while on a breakpoint.\n");
  606. RemoteToolsInterface::Get()->SendRemoteToolsMessage(sender, ScriptDebugAck(request->m_request, AZ_CRC_CE("IllegalOperation")));
  607. }
  608. // enumerates global C++ functions that have been exposed to script
  609. }
  610. else if (request->m_request == AZ_CRC_CE("EnumRegisteredGlobals"))
  611. {
  612. ScriptDebugRegisteredGlobalsResult response;
  613. dbgContext->EnumRegisteredGlobals(&ScriptDebugAgentInternal::EnumGlobalMethod, &ScriptDebugAgentInternal::EnumGlobalProperty, &response);
  614. RemoteToolsInterface::Get()->SendRemoteToolsMessage(sender, response);
  615. // enumerates C++ classes that have been exposed to script
  616. }
  617. else if (request->m_request == AZ_CRC_CE("EnumRegisteredClasses"))
  618. {
  619. ScriptDebugRegisteredClassesResult response;
  620. dbgContext->EnumRegisteredClasses(&ScriptDebugAgentInternal::EnumClass, &ScriptDebugAgentInternal::EnumClassMethod, &ScriptDebugAgentInternal::EnumClassProperty, &response.m_classes);
  621. RemoteToolsInterface::Get()->SendRemoteToolsMessage(sender, response);
  622. // enumerates C++ busses that have been exposed to script
  623. }
  624. else if (request->m_request == AZ_CRC_CE("EnumRegisteredEBuses"))
  625. {
  626. ScriptDebugRegisteredEBusesResult response;
  627. dbgContext->EnumRegisteredEBuses(&ScriptDebugAgentInternal::EnumEBus, &ScriptDebugAgentInternal::EnumEBusSender, &response.m_ebusList);
  628. RemoteToolsInterface::Get()->SendRemoteToolsMessage(sender, response);
  629. // ExecuteScript
  630. }
  631. else if (request->m_request == AZ_CRC_CE("ExecuteScript"))
  632. {
  633. if (sender.IsSelf() && m_debugger.IsSelf() && m_executionState == SDA_STATE_RUNNING)
  634. {
  635. AZ_Assert(!request->GetCustomBlob().empty(), "ScriptDebugAgent was asked to execute a script but script is missing!");
  636. ScriptDebugAckExecute response;
  637. response.m_moduleName = request->m_context;
  638. response.m_result =
  639. m_curContext->Execute(reinterpret_cast<const char*>(request->GetCustomBlob().data()), request->m_context.c_str());
  640. RemoteToolsInterface::Get()->SendRemoteToolsMessage(sender, response);
  641. }
  642. else
  643. {
  644. AZ_TracePrintf("LUA", "Command rejected. 'ExecuteScript' cannot be issued while on a breakpoint or remotely.\n");
  645. RemoteToolsInterface::Get()->SendRemoteToolsMessage(
  646. sender, ScriptDebugAck(request->m_request, AZ_CRC_CE("IllegalOperation")));
  647. }
  648. // AttachDebugger
  649. }
  650. else if (request->m_request == AZ_CRC_CE("AttachDebugger"))
  651. {
  652. if (m_executionState == SDA_STATE_DETACHED)
  653. {
  654. Attach(sender, request->m_context.c_str());
  655. }
  656. else if (m_debugger.GetNetworkId() != sender.GetNetworkId())
  657. {
  658. // we need to detach from the current context first
  659. AZ_TracePrintf("LUA", "Received connection from %s while still connected to %s! Detaching from %s.\n", sender.GetDisplayName(), m_debugger.GetDisplayName(), m_debugger.GetDisplayName());
  660. AZ_TracePrintf("LUA", "Force disconnecting debugger %s from context 0x%p.\n", m_debugger.GetDisplayName(), m_curContext);
  661. AZStd::lock_guard<AZStd::mutex> l(m_msgMutex);
  662. m_msgQueue.push_front(msg);
  663. m_executionState = SDA_STATE_DETACHING;
  664. }
  665. // We need to switch contexts before any more processing, keep remaining messages
  666. // in the queue and return.
  667. return;
  668. // DetachDebugger
  669. }
  670. else if (request->m_request == AZ_CRC_CE("DetachDebugger"))
  671. {
  672. // We need to switch contexts before any more processing, keep remaining messages
  673. // in the queue and return.
  674. if (m_executionState != SDA_STATE_DETACHED)
  675. {
  676. m_executionState = SDA_STATE_DETACHING;
  677. }
  678. return;
  679. // EnumContexts
  680. }
  681. else if (request->m_request == AZ_CRC_CE("EnumContexts"))
  682. {
  683. AZ_TracePrintf("LUA", "Received EnumContexts request\n");
  684. ScriptDebugEnumContextsResult response;
  685. for (ContextMap::const_iterator it = m_availableContexts.begin(); it != m_availableContexts.end(); ++it)
  686. {
  687. response.m_names.push_back(it->m_name);
  688. }
  689. RemoteToolsInterface::Get()->SendRemoteToolsMessage(sender, response);
  690. // Invalid command
  691. }
  692. else
  693. {
  694. AZ_TracePrintf("LUA", "Received invalid command 0x%x.\n", request->m_request);
  695. RemoteToolsInterface::Get()->SendRemoteToolsMessage(sender, ScriptDebugAck(request->m_request, AZ_CRC_CE("InvalidCmd")));
  696. }
  697. }
  698. else
  699. {
  700. AZ_Assert(false, "ScriptDebugAgent received a message that is not of any recognized types!");
  701. }
  702. }
  703. // Check if our debugger is still around
  704. if (m_executionState != SDA_STATE_DETACHED)
  705. {
  706. if (!RemoteToolsInterface::Get()->IsEndpointOnline(AzFramework::LuaToolsKey, m_debugger.GetPersistentId()))
  707. {
  708. m_executionState = SDA_STATE_DETACHING;
  709. }
  710. }
  711. }
  712. //-------------------------------------------------------------------------
  713. void ScriptDebugAgent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
  714. {
  715. provided.push_back(AZ_CRC_CE("ScriptDebugService"));
  716. }
  717. //-------------------------------------------------------------------------
  718. void ScriptDebugAgent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
  719. {
  720. incompatible.push_back(AZ_CRC_CE("ScriptDebugService"));
  721. }
  722. //-------------------------------------------------------------------------
  723. void ScriptDebugAgent::GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent)
  724. {
  725. dependent.push_back(AZ_CRC_CE("ScriptService"));
  726. }
  727. //-------------------------------------------------------------------------
  728. void ScriptDebugAgent::Reflect(AZ::ReflectContext* context)
  729. {
  730. if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
  731. {
  732. serializeContext->Class<ScriptDebugAgent, AZ::Component>()
  733. ->Version(1)
  734. ;
  735. if (AZ::EditContext* editContext = serializeContext->GetEditContext())
  736. {
  737. editContext->Class<ScriptDebugAgent>(
  738. "Script Debug Agent", "Provides remote debugging services for script contexts")
  739. ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
  740. ->Attribute(AZ::Edit::Attributes::Category, "Profiling")
  741. ;
  742. }
  743. }
  744. ReflectScriptDebugClasses(context);
  745. static bool registeredComponentUuidWithMetricsAlready = false;
  746. if (!registeredComponentUuidWithMetricsAlready)
  747. {
  748. // have to let the metrics system know that it's ok to send back the name of the ScriptDebugAgent component to Amazon as plain
  749. // text, without hashing
  750. AzFramework::MetricsPlainTextNameRegistrationBus::Broadcast(
  751. &AzFramework::MetricsPlainTextNameRegistrationBus::Events::RegisterForNameSending,
  752. AZStd::vector<AZ::Uuid>{ azrtti_typeid<ScriptDebugAgent>() });
  753. // only ever do this once
  754. registeredComponentUuidWithMetricsAlready = true;
  755. }
  756. }
  757. ////-------------------------------------------------------------------------
  758. //class ScriptDebugAgentFactory : public AZ::ComponentFactory<ScriptDebugAgent>
  759. //{
  760. //public:
  761. // AZ_CLASS_ALLOCATOR(ScriptDebugAgentFactory, AZ::SystemAllocator,0);
  762. // ScriptDebugAgentFactory() : AZ::ComponentFactory<ScriptDebugAgent>(AZ_CRC_CE("ScriptDebugAgent"))
  763. // {
  764. // }
  765. // virtual const char* GetName() { return "ScriptDebugAgent"; }
  766. // virtual void Reflect(const AZ::ClassDataReflection& reflection)
  767. // {
  768. // if( reflection.m_serialize )
  769. // {
  770. // reflection.m_serialize->Class<ScriptDebugAgent>("ScriptDebugAgent", "{6CEA890A-CEC0-4725-8E9A-97ACCE5941A9}")
  771. // ->Version(1)
  772. // AZ::EditContext *ec = reflection.m_serialize->GetEditContext();
  773. // if (ec) {
  774. // ec->Class<ScriptDebugAgent>("Script Debug Agent", "Provides remote debugging services for script contexts.");
  775. // }
  776. // }
  777. // ReflectScriptDebugClasses(reflection);
  778. // }
  779. //};
  780. //-------------------------------------------------------------------------
  781. AZ::ComponentDescriptor* CreateScriptDebugAgentFactory()
  782. {
  783. return ScriptDebugAgent::CreateDescriptor();
  784. }
  785. //-------------------------------------------------------------------------
  786. } // namespace AzFramework