From 2c92639ff4267b05285f77e956c76e1bcaf47d35 Mon Sep 17 00:00:00 2001 From: yp9522 Date: Fri, 18 Apr 2025 14:16:22 +0800 Subject: [PATCH] Retrieve ArkTS variable information Issue:https://gitee.com/openharmony/arkcompiler_toolchain/issues/IC7XF2 Signed-off-by: yp9522 Change-Id: If545c451e57dd3e846bc7ece3f023937392856b5 --- inspector/inspector.cpp | 24 +++++++++ inspector/inspector.h | 2 + tooling/agent/debugger_impl.cpp | 41 ++++++++++++++ tooling/agent/debugger_impl.h | 2 + tooling/agent/runtime_impl.cpp | 28 ++++++++++ tooling/agent/runtime_impl.h | 1 + tooling/debugger_service.cpp | 19 +++++++ tooling/debugger_service.h | 2 + tooling/dispatcher.cpp | 74 ++++++++++++++++++++++++++ tooling/dispatcher.h | 6 +++ tooling/test/debugger_impl_test.cpp | 46 +++++++++++++++- tooling/test/debugger_service_test.cpp | 15 ++++++ tooling/test/dispatcher_test.cpp | 41 ++++++++++++++ tooling/test/runtime_impl_test.cpp | 19 ++++++- 14 files changed, 318 insertions(+), 2 deletions(-) diff --git a/inspector/inspector.cpp b/inspector/inspector.cpp index 0988b350..98c672b2 100644 --- a/inspector/inspector.cpp +++ b/inspector/inspector.cpp @@ -50,6 +50,7 @@ using OnMessage = void(*)(void*, std::string&&); using ProcessMessage = void(*)(void*); using GetDispatchStatus = int32_t(*)(void*); using GetCallFrames = char*(*)(void*); +using GetVariable = char*(*)(void*, const char*); OnMessage g_onMessage = nullptr; InitializeDebugger g_initializeDebugger = nullptr; @@ -58,6 +59,7 @@ WaitForDebugger g_waitForDebugger = nullptr; ProcessMessage g_processMessage = nullptr; GetDispatchStatus g_getDispatchStatus = nullptr; GetCallFrames g_getCallFrames = nullptr; +GetVariable g_getVariable = nullptr; std::atomic g_hasArkFuncsInited = false; std::unordered_map g_inspectors; @@ -222,6 +224,12 @@ bool InitializeArkFunctionsOthers() ResetServiceLocked(g_vm, true); return false; } + g_getVariable = reinterpret_cast( + GetArkDynFunction("GetVariable")); + if (g_getVariable == nullptr) { + ResetServiceLocked(g_vm, true); + return false; + } return true; } #else @@ -235,6 +243,7 @@ bool InitializeArkFunctionsIOS() g_getDispatchStatus = reinterpret_cast(&tooling::GetDispatchStatus); g_processMessage = reinterpret_cast(&tooling::ProcessMessage); g_getCallFrames = reinterpret_cast(&tooling::GetCallFrames); + g_getVariable = reinterpret_cast(&tooling::GetVariable); return true; } #endif @@ -479,4 +488,19 @@ const char* GetJsBacktrace() return ""; #endif } + +const char* GetJsVariable(const char* message) +{ + (void)message; +#if defined(OHOS_PLATFORM) + void* vm = GetEcmaVM(Inspector::GetThreadOrTaskId()); + if (g_getVariable == nullptr) { + LOGE("GetVariable symbol resolve failed"); + return ""; + } + return g_getVariable(vm, message); +#else + return ""; +#endif +} } // namespace OHOS::ArkCompiler::Toolchain diff --git a/inspector/inspector.h b/inspector/inspector.h index 9b4db968..2da47ce6 100644 --- a/inspector/inspector.h +++ b/inspector/inspector.h @@ -50,6 +50,8 @@ void StoreDebuggerInfo(int tid, void* vm, const DebuggerPostTask& debuggerPostTa // The returned pointer must be released using free() after it is no longer needed. // Failure to release the memory will result in memory leaks. const char* GetJsBacktrace(); + +const char* GetJsVariable(const char* message); #if __cplusplus } #endif diff --git a/tooling/agent/debugger_impl.cpp b/tooling/agent/debugger_impl.cpp index a8a80762..e064cf17 100755 --- a/tooling/agent/debugger_impl.cpp +++ b/tooling/agent/debugger_impl.cpp @@ -1519,6 +1519,47 @@ DispatchResponse DebuggerImpl::RemoveSymbolicBreakpoints(const RemoveSymbolicBre return DispatchResponse::Ok(); } +std::string DebuggerImpl::DispatcherImpl::EvaluateOnCallFrame( + const int32_t callId, std::unique_ptr params) +{ + if (params == nullptr) { + LOG_DEBUGGER(WARN) << "DispatcherImpl::EvaluateOnCallFrame: params is nullptr"; + return ReturnsValueToString(callId, DispatchResponsetToJson(DispatchResponse::Fail("wrong params"))); + } + std::unique_ptr remoteObject; + DispatchResponse response = debugger_->EvaluateOnCallFrame(*params, &remoteObject); + if (remoteObject == nullptr || !response.IsOk()) { + LOG_DEBUGGER(WARN) << "remoteObject is nullptr or response code is not ok"; + return ReturnsValueToString(callId, DispatchResponsetToJson(response)); + } + + EvaluateOnCallFrameReturns result(std::move(remoteObject)); + return ReturnsValueToString(callId, result.ToJson()); +} + +std::string DebuggerImpl::DispatcherImpl::CallFunctionOn( + const int32_t callId, std::unique_ptr params) +{ + if (params == nullptr) { + LOG_DEBUGGER(WARN) << "DispatcherImpl::CallFunctionOn: params is nullptr"; + return ReturnsValueToString(callId, DispatchResponsetToJson(DispatchResponse::Fail("wrong params"))); + } + std::unique_ptr outRemoteObject; + std::optional> outExceptionDetails; + DispatchResponse response = debugger_->CallFunctionOn(*params, &outRemoteObject, &outExceptionDetails); + if (outExceptionDetails) { + ASSERT(outExceptionDetails.value() != nullptr); + LOG_DEBUGGER(WARN) << "CallFunctionOn thrown an exception"; + } + if (outRemoteObject == nullptr || !response.IsOk()) { + LOG_DEBUGGER(WARN) << "outRemoteObject is nullptr or response code is not ok"; + return ReturnsValueToString(callId, DispatchResponsetToJson(response)); + } + + CallFunctionOnReturns result(std::move(outRemoteObject), std::move(outExceptionDetails)); + return ReturnsValueToString(callId, result.ToJson()); +} + void DebuggerImpl::SavePendingBreakpoints(const SaveAllPossibleBreakpointsParams ¶ms) { for (const auto &entry : *(params.GetBreakpointsMap())) { diff --git a/tooling/agent/debugger_impl.h b/tooling/agent/debugger_impl.h index 9ee191c5..8a87bb59 100644 --- a/tooling/agent/debugger_impl.h +++ b/tooling/agent/debugger_impl.h @@ -156,6 +156,8 @@ public: void ContinueToLocation(const DispatchRequest &request); std::string GetJsFrames(); + std::string EvaluateOnCallFrame(const int32_t callId, std::unique_ptr params); + std::string CallFunctionOn(const int32_t callId, std::unique_ptr params); void Dispatch(const DispatchRequest &request) override; void Enable(const DispatchRequest &request); void Disable(const DispatchRequest &request); diff --git a/tooling/agent/runtime_impl.cpp b/tooling/agent/runtime_impl.cpp index 2437a9d8..ed68adee 100644 --- a/tooling/agent/runtime_impl.cpp +++ b/tooling/agent/runtime_impl.cpp @@ -114,6 +114,34 @@ void RuntimeImpl::DispatcherImpl::GetProperties(const DispatchRequest &request) SendResponse(request, response, result); } +std::string RuntimeImpl::DispatcherImpl::GetProperties( + const int32_t callId, std::unique_ptr params) +{ + if (params == nullptr) { + LOG_DEBUGGER(WARN) << "DispatcherImpl::GetProperties: params is nullptr"; + return ReturnsValueToString(callId, DispatchResponsetToJson(DispatchResponse::Fail("wrong params"))); + } + std::vector> outPropertyDesc; + std::optional>> outInternalDescs; + std::optional>> outPrivateProperties; + std::optional> outExceptionDetails; + DispatchResponse response = runtime_->GetProperties(*params, &outPropertyDesc, &outInternalDescs, + &outPrivateProperties, &outExceptionDetails); + if (outExceptionDetails) { + ASSERT(outExceptionDetails.value() != nullptr); + LOG_DEBUGGER(WARN) << "GetProperties thrown an exception"; + } + GetPropertiesReturns result(std::move(outPropertyDesc), + std::move(outInternalDescs), + std::move(outPrivateProperties), + std::move(outExceptionDetails)); + if (!response.IsOk()) { + LOG_DEBUGGER(WARN) << "response code is not OK"; + return ReturnsValueToString(callId, DispatchResponsetToJson(response)); + } + return ReturnsValueToString(callId, result.ToJson()); +} + void RuntimeImpl::DispatcherImpl::GetHeapUsage(const DispatchRequest &request) { double usedSize = 0; diff --git a/tooling/agent/runtime_impl.h b/tooling/agent/runtime_impl.h index 0c40beb6..52124416 100644 --- a/tooling/agent/runtime_impl.h +++ b/tooling/agent/runtime_impl.h @@ -51,6 +51,7 @@ public: void Enable(const DispatchRequest &request); void RunIfWaitingForDebugger(const DispatchRequest &request); void GetProperties(const DispatchRequest &request); + std::string GetProperties(const int32_t callId, std::unique_ptr params); void GetHeapUsage(const DispatchRequest &request); enum class Method { diff --git a/tooling/debugger_service.cpp b/tooling/debugger_service.cpp index 86afa0a4..cdd54485 100644 --- a/tooling/debugger_service.cpp +++ b/tooling/debugger_service.cpp @@ -115,4 +115,23 @@ const char* GetCallFrames(const ::panda::ecmascript::EcmaVM *vm) } return ""; } + +const char* GetVariable(const ::panda::ecmascript::EcmaVM *vm, const char* message) +{ + if (vm == nullptr || vm->GetJsDebuggerManager() == nullptr) { + LOG_DEBUGGER(ERROR) << "VM has already been destroyed"; + return ""; + } + ProtocolHandler *handler = vm->GetJsDebuggerManager()->GetDebuggerHandler(); + if (LIKELY(handler != nullptr)) { + auto dispatcher = handler->GetDispatcher(); + if (LIKELY(dispatcher != nullptr)) { + auto info = dispatcher->GetVariable(message); + const char* buffer = strdup(info.c_str()); + return buffer; + } + return ""; + } + return ""; +} } // namespace panda::ecmascript::tooling \ No newline at end of file diff --git a/tooling/debugger_service.h b/tooling/debugger_service.h index 95bd86d9..c69eba1a 100644 --- a/tooling/debugger_service.h +++ b/tooling/debugger_service.h @@ -47,6 +47,8 @@ TOOLCHAIN_EXPORT int32_t GetDispatchStatus(const ::panda::ecmascript::EcmaVM *vm TOOLCHAIN_EXPORT const char* GetCallFrames(const ::panda::ecmascript::EcmaVM *vm); +TOOLCHAIN_EXPORT const char* GetVariable(const ::panda::ecmascript::EcmaVM *vm, const char* message); + #ifdef __cplusplus #if __cplusplus } diff --git a/tooling/dispatcher.cpp b/tooling/dispatcher.cpp index afbdbc07..e66a56fa 100644 --- a/tooling/dispatcher.cpp +++ b/tooling/dispatcher.cpp @@ -130,6 +130,32 @@ void DispatcherBase::SendResponse(const DispatchRequest &request, const Dispatch } } +std::string DispatcherBase::ReturnsValueToString(const int32_t callId, const std::unique_ptr resultObj) +{ + std::unique_ptr ptJson = PtJson::CreateObject(); + ptJson->Add("id", callId); + ptJson->Add("result", resultObj); + std::string str = ptJson->Stringify(); + if (str.empty()) { + LOG_DEBUGGER(ERROR) << "Dispatcher::ReturnsValueToString: json stringify error"; + return ""; + } + ptJson->ReleaseRoot(); + return str; +} + +std::unique_ptr DispatcherBase::DispatchResponsetToJson(const DispatchResponse &response) const +{ + std::unique_ptr result = PtJson::CreateObject(); + + if (!response.IsOk()) { + result->Add("code", static_cast(response.GetError())); + result->Add("message", response.GetMessage().c_str()); + } + + return result; +} + Dispatcher::Dispatcher(const EcmaVM *vm, ProtocolChannel *channel) { // profiler @@ -214,4 +240,52 @@ std::string Dispatcher::GetJsFrames() const } return ""; } + +std::string Dispatcher::GetVariable(const char* message) const +{ + DispatchRequest request(message); + if (request.GetMethod() == "getProperties") { + auto dispatcher = dispatchers_.find("Runtime"); + if (dispatcher != dispatchers_.end()) { + return GetProperties(request, dispatcher->second.get()); + } + return ""; + } + if (request.GetMethod() == "callFunctionOn") { + auto dispatcher = dispatchers_.find("Debugger"); + if (dispatcher != dispatchers_.end()) { + return CallFunctionOn(request, dispatcher->second.get()); + } + return ""; + } + if (request.GetMethod() == "evaluateOnCallFrame") { + auto dispatcher = dispatchers_.find("Debugger"); + if (dispatcher != dispatchers_.end()) { + return EvaluateOnCallFrame(request, dispatcher->second.get()); + } + return ""; + } + return ""; +} + +std::string Dispatcher::GetProperties(const DispatchRequest &request, DispatcherBase *dispatcher) const +{ + auto runtimeImpl = reinterpret_cast(dispatcher); + std::unique_ptr params = GetPropertiesParams::Create(request.GetParams()); + return runtimeImpl->GetProperties(request.GetCallId(), std::move(params)); +} + +std::string Dispatcher::CallFunctionOn(const DispatchRequest &request, DispatcherBase *dispatcher) const +{ + auto debuggerImpl = reinterpret_cast(dispatcher); + std::unique_ptr params = CallFunctionOnParams::Create(request.GetParams()); + return debuggerImpl->CallFunctionOn(request.GetCallId(), std::move(params)); +} + +std::string Dispatcher::EvaluateOnCallFrame(const DispatchRequest &request, DispatcherBase *dispatcher) const +{ + auto debuggerImpl = reinterpret_cast(dispatcher); + std::unique_ptr params = EvaluateOnCallFrameParams::Create(request.GetParams()); + return debuggerImpl->EvaluateOnCallFrame(request.GetCallId(), std::move(params)); +} } // namespace panda::ecmascript::tooling diff --git a/tooling/dispatcher.h b/tooling/dispatcher.h index 7066f86e..d8a77b90 100644 --- a/tooling/dispatcher.h +++ b/tooling/dispatcher.h @@ -136,6 +136,8 @@ public: protected: void SendResponse(const DispatchRequest &request, const DispatchResponse &response, const PtBaseReturns &result = PtBaseReturns()); + std::string ReturnsValueToString(const int32_t callId, const std::unique_ptr resultObj); + std::unique_ptr DispatchResponsetToJson(const DispatchResponse &response) const; private: ProtocolChannel *channel_ {nullptr}; @@ -150,8 +152,12 @@ public: ~Dispatcher() = default; void Dispatch(const DispatchRequest &request); std::string GetJsFrames() const; + std::string GetVariable(const char* message) const; private: + std::string GetProperties(const DispatchRequest &request, DispatcherBase *dispatcher) const; + std::string CallFunctionOn(const DispatchRequest &request, DispatcherBase *dispatcher) const; + std::string EvaluateOnCallFrame(const DispatchRequest &request, DispatcherBase *dispatcher) const; std::unordered_map> dispatchers_ {}; NO_COPY_SEMANTIC(Dispatcher); diff --git a/tooling/test/debugger_impl_test.cpp b/tooling/test/debugger_impl_test.cpp index 7e598763..8b4da6cb 100644 --- a/tooling/test/debugger_impl_test.cpp +++ b/tooling/test/debugger_impl_test.cpp @@ -349,6 +349,32 @@ HWTEST_F_L0(DebuggerImplTest, Dispatcher_Dispatch_EvaluateOnCallFrame__003) } } +HWTEST_F_L0(DebuggerImplTest, Dispatcher_Dispatch_EvaluateOnCallFrame__004) +{ + ProtocolChannel *protocolChannel = new ProtocolHandler(nullptr, ecmaVm); + auto runtimeImpl = std::make_unique(ecmaVm, protocolChannel); + auto debuggerImpl = std::make_unique(ecmaVm, protocolChannel, runtimeImpl.get()); + auto dispatcherImpl = std::make_unique(protocolChannel, std::move(debuggerImpl)); + int32_t callId = 0; + std::string msg = std::string() + + R"({ + "id":0, + "method":"Debugger.evaluateOnCallFrame", + "params":{ + "callFrameId":"-1", + "expression":"the expression" + } + })"; + std::unique_ptr params = + EvaluateOnCallFrameParams::Create(DispatchRequest(msg).GetParams()); + std::string result = dispatcherImpl->EvaluateOnCallFrame(callId, std::move(params)); + EXPECT_STREQ(result.c_str(), R"({"id":0,"result":{"code":1,"message":"Invalid callFrameId."}})"); + if (protocolChannel) { + delete protocolChannel; + protocolChannel = nullptr; + } +} + HWTEST_F_L0(DebuggerImplTest, Dispatcher_Dispatch_GetPossibleBreakpoints__001) { std::string outStrForCallbackCheck = ""; @@ -1364,7 +1390,7 @@ HWTEST_F_L0(DebuggerImplTest, Dispatcher_Dispatch_DropFrame__001) } } -HWTEST_F_L0(DebuggerImplTest, DispatcherImplCallFunctionOn) +HWTEST_F_L0(DebuggerImplTest, DispatcherImplCallFunctionOn__001) { std::string outStrForCallbackCheck = ""; std::function callback = @@ -1392,6 +1418,24 @@ HWTEST_F_L0(DebuggerImplTest, DispatcherImplCallFunctionOn) } } +HWTEST_F_L0(DebuggerImplTest, DispatcherImplCallFunctionOn__002) +{ + ProtocolChannel *protocolChannel = new ProtocolHandler(nullptr, ecmaVm); + auto runtimeImpl = std::make_unique(ecmaVm, protocolChannel); + auto debuggerImpl = std::make_unique(ecmaVm, protocolChannel, runtimeImpl.get()); + auto dispatcherImpl = std::make_unique(protocolChannel, std::move(debuggerImpl)); + std::string msg = std::string() + R"({"id":0,"method":"Debugger.callFunctionOn","params":{ + "callFrameId":"0", "functionDeclaration":"test"}})"; + std::unique_ptr params = CallFunctionOnParams::Create(DispatchRequest(msg).GetParams()); + int32_t callId = 0; + std::string result = dispatcherImpl->CallFunctionOn(callId, std::move(params)); + EXPECT_STREQ(result.c_str(), R"({"id":0,"result":{"code":1,"message":"Invalid callFrameId."}})"); + if (protocolChannel != nullptr) { + delete protocolChannel; + protocolChannel = nullptr; + } +} + HWTEST_F_L0(DebuggerImplTest, Dispatcher_Dispatch_SmartStepInto__001) { std::string outStrForCallbackCheck = ""; diff --git a/tooling/test/debugger_service_test.cpp b/tooling/test/debugger_service_test.cpp index 68b8796f..0bf0902d 100644 --- a/tooling/test/debugger_service_test.cpp +++ b/tooling/test/debugger_service_test.cpp @@ -135,4 +135,19 @@ HWTEST_F_L0(DebuggerServiceTest, GetDispatchStatusTest) int32_t GetDispatchStatusTest = GetDispatchStatus(vm); ASSERT_TRUE(GetDispatchStatusTest == 0); } + +HWTEST_F_L0(DebuggerServiceTest, GetVariableTest) +{ + const char *message = "{\"id\":0,\"method\":\"Runtime.getProperties\",\"params\":{\"objectId\":\"1\"}}"; + const char *str0 = GetVariable(nullptr, message); + ASSERT_TRUE(str0 != nullptr && *str0 == '\0'); + + const char *str1 = GetVariable(ecmaVm, message); + ASSERT_TRUE(str1 != nullptr && *str1 == '\0'); + + InitializeDebugger(ecmaVm, nullptr); + const char *str2 = GetVariable(ecmaVm, message); + ASSERT_TRUE(str2 != nullptr && *str2 != '\0'); + UninitializeDebugger(ecmaVm); +} } // namespace panda::test \ No newline at end of file diff --git a/tooling/test/dispatcher_test.cpp b/tooling/test/dispatcher_test.cpp index 9624b450..a8a3f1d9 100644 --- a/tooling/test/dispatcher_test.cpp +++ b/tooling/test/dispatcher_test.cpp @@ -148,4 +148,45 @@ HWTEST_F_L0(DispatcherTest, DispatcherDispatchTest) dispatcher->Dispatch(dispatchRequest2); ASSERT_TRUE(result == ""); } + +HWTEST_F_L0(DispatcherTest, GetVariableTest) +{ + ProtocolChannel *channel = new ProtocolHandler(nullptr, ecmaVm); + auto dispatcher = std::make_unique(ecmaVm, channel); + std::string result = ""; + + std::string msg = R"({"id":0,"method":"Runtime.getProperties"})"; + result = dispatcher->GetVariable(msg.c_str()); + ASSERT_TRUE(result != ""); + msg = R"({"id":0,"method":"Runtime.getProperties","params":{"objectId":"1"}})"; + result = dispatcher->GetVariable(msg.c_str()); + ASSERT_TRUE(result != ""); + + std::string msg2 = R"({"id":0,"method":"Debugger.callFunctionOn"})"; + result = dispatcher->GetVariable(msg2.c_str()); + ASSERT_TRUE(result != ""); + msg2 = std::string() + R"({"id":0,"method":"Debugger.callFunctionOn","params":{ + "callFrameId":"0", "functionDeclaration":"test"}})"; + result = dispatcher->GetVariable(msg2.c_str()); + EXPECT_STREQ(result.c_str(), R"({"id":0,"result":{"code":1,"message":"Invalid callFrameId."}})"); + + std::string msg3 = R"({"id":0,"method":"Debugger.evaluateOnCallFrame"})"; + result = dispatcher->GetVariable(msg3.c_str()); + ASSERT_TRUE(result != ""); + msg3 = std::string() + + R"({ + "id":0, + "method":"Debugger.evaluateOnCallFrame", + "params":{ + "callFrameId":"-1", + "expression":"the expression" + } + })"; + result = dispatcher->GetVariable(msg3.c_str()); + EXPECT_STREQ(result.c_str(), R"({"id":0,"result":{"code":1,"message":"Invalid callFrameId."}})"); + if (channel) { + delete channel; + channel = nullptr; + } +} } // namespace panda::test \ No newline at end of file diff --git a/tooling/test/runtime_impl_test.cpp b/tooling/test/runtime_impl_test.cpp index 510e8ab4..4fffbaad 100644 --- a/tooling/test/runtime_impl_test.cpp +++ b/tooling/test/runtime_impl_test.cpp @@ -130,7 +130,7 @@ HWTEST_F_L0(RuntimeImplTest, DispatcherImplRunIfWaitingForDebugger) } } -HWTEST_F_L0(RuntimeImplTest, DispatcherImplGetProperties) +HWTEST_F_L0(RuntimeImplTest, DispatcherImplGetProperties__001) { std::string result = ""; std::function callback = @@ -154,6 +154,23 @@ HWTEST_F_L0(RuntimeImplTest, DispatcherImplGetProperties) } } +HWTEST_F_L0(RuntimeImplTest, DispatcherImplGetProperties__002) +{ + ProtocolChannel *channel = new ProtocolHandler(nullptr, ecmaVm); + auto runtimeImpl = std::make_unique(ecmaVm, channel); + auto dispatcherImpl = std::make_unique(channel, std::move(runtimeImpl)); + std::string msg = std::string() + R"({"id":0,"method":"Rumtime.getProperties","params":{"objectId":"0"}})"; + DispatchRequest request(msg); + std::unique_ptr params = GetPropertiesParams::Create(request.GetParams()); + int32_t callId = 0; + std::string result = dispatcherImpl->GetProperties(callId, std::move(params)); + EXPECT_STREQ(result.c_str(), R"({"id":0,"result":{"code":1,"message":"Unknown object id"}})"); + if (channel != nullptr) { + delete channel; + channel = nullptr; + } +} + HWTEST_F_L0(RuntimeImplTest, DispatcherImplGetHeapUsage) { std::string result = ""; -- Gitee