From 92130d11737a1449ae4d4c9e1b249f50a3a17be3 Mon Sep 17 00:00:00 2001 From: kanghonglin Date: Mon, 21 Jul 2025 14:33:55 +0800 Subject: [PATCH] Description: support coroutines in debugger Issue: https://gitee.com/openharmony/arkcompiler_runtime_core/issues/ICNKCE Signed-off-by: kanghonglin Change-Id: Ica50af19ed1ac3d834ba87ae298563bb4b5fb7ec --- tooling/static/BUILD.gn | 1 - tooling/static/CMakeLists.txt | 1 - tooling/static/connection/endpoint_base.h | 6 +- tooling/static/connection/event_loop.cpp | 32 --------- tooling/static/connection/event_loop.h | 53 -------------- .../ohos_ws/ohos_ws_server_endpoint.h | 7 +- tooling/static/connection/server.h | 69 ++++++++++++++++--- .../connection/server_endpoint_base.cpp | 2 +- .../static/connection/server_endpoint_base.h | 2 +- tooling/static/debugger/debuggable_thread.cpp | 7 ++ tooling/static/debugger/debuggable_thread.h | 3 + tooling/static/inspector.cpp | 33 +++++++-- tooling/static/inspector.h | 1 + tooling/static/inspector_server.cpp | 53 ++++++++++++-- tooling/static/inspector_server.h | 3 +- tooling/static/tests/inspector_server.cpp | 47 +++++++++---- 16 files changed, 188 insertions(+), 132 deletions(-) delete mode 100644 tooling/static/connection/event_loop.cpp delete mode 100644 tooling/static/connection/event_loop.h diff --git a/tooling/static/BUILD.gn b/tooling/static/BUILD.gn index d5bc50ac..4264ed28 100644 --- a/tooling/static/BUILD.gn +++ b/tooling/static/BUILD.gn @@ -16,7 +16,6 @@ import("../../toolchain.gni") libarkinspector_sources = [ "init.cpp", "connection/endpoint_base.cpp", - "connection/event_loop.cpp", "connection/ohos_ws/ohos_ws_server.cpp", "connection/ohos_ws/ohos_ws_server_endpoint.cpp", "connection/server_endpoint_base.cpp", diff --git a/tooling/static/CMakeLists.txt b/tooling/static/CMakeLists.txt index 0773e218..aeae3862 100644 --- a/tooling/static/CMakeLists.txt +++ b/tooling/static/CMakeLists.txt @@ -17,7 +17,6 @@ project(arkinspector) set(ARKINSPECTOR_ROOTS connection/endpoint_base.cpp - connection/event_loop.cpp connection/server_endpoint_base.cpp debugger/breakpoint.cpp debugger/breakpoint_storage.cpp diff --git a/tooling/static/connection/endpoint_base.h b/tooling/static/connection/endpoint_base.h index 629bd741..d3ca4438 100644 --- a/tooling/static/connection/endpoint_base.h +++ b/tooling/static/connection/endpoint_base.h @@ -43,7 +43,7 @@ enum class InspectorErrorCode { SERVER_ERROR = -32000, }; -// Base class implementation of JSON-RPC endpoint handling the Inspector protocol. +/// @brief Base class implementation of JSON-RPC endpoint handling the Inspector protocol. class EndpointBase { public: using Id = double; @@ -93,10 +93,10 @@ protected: } private: - /// Send JSON message. + /// @brief Sends JSON message. virtual void SendMessage(const std::string &message) = 0; - /// Send JSON message created with the provided build function. + /// @brief Sends JSON message created with the provided build function. template void Send(BuildFunction &&build) { diff --git a/tooling/static/connection/event_loop.cpp b/tooling/static/connection/event_loop.cpp deleted file mode 100644 index c55b4da3..00000000 --- a/tooling/static/connection/event_loop.cpp +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Copyright (c) 2022-2025 Huawei Device Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "connection/event_loop.h" - -#include "macros.h" - -namespace ark::tooling::inspector { -bool EventLoop::Kill() -{ - return running_.exchange(false); -} - -void EventLoop::Run(const std::string& msg) -{ - ASSERT_PRINT(!running_, "Event loop is already running"); - - ParseMessage(msg); -} -} // namespace ark::tooling::inspector diff --git a/tooling/static/connection/event_loop.h b/tooling/static/connection/event_loop.h deleted file mode 100644 index 73e2f518..00000000 --- a/tooling/static/connection/event_loop.h +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Copyright (c) 2022-2025 Huawei Device Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef PANDA_TOOLING_INSPECTOR_CONNECTION_EVENT_LOOP_H -#define PANDA_TOOLING_INSPECTOR_CONNECTION_EVENT_LOOP_H - -#include - -#include "os/mutex.h" - -namespace ark::tooling::inspector { -class EventLoop { -public: - // Notify the running event loop to stop. - bool Kill(); - - // Run the event loop until paused. - void Run(const std::string& msg); - - // Run at most one event loop handler, may block. - virtual bool ParseMessage(const std::string& msg) = 0; - - // Pause the event loop. Wait for the current task to finish. - void Pause() ACQUIRE_SHARED(taskExecution_) - { - taskExecution_.ReadLock(); - } - - // Notify the event loop to continue. - void Continue() RELEASE_GENERIC(taskExecution_) - { - taskExecution_.Unlock(); - } - -private: - std::atomic running_ {false}; - os::memory::RWLock taskExecution_; -}; -} // namespace ark::tooling::inspector - -#endif // PANDA_TOOLING_INSPECTOR_CONNECTION_EVENT_LOOP_H diff --git a/tooling/static/connection/ohos_ws/ohos_ws_server_endpoint.h b/tooling/static/connection/ohos_ws/ohos_ws_server_endpoint.h index e248ec43..f3579ebc 100644 --- a/tooling/static/connection/ohos_ws/ohos_ws_server_endpoint.h +++ b/tooling/static/connection/ohos_ws/ohos_ws_server_endpoint.h @@ -37,12 +37,7 @@ protected: private: void SendMessage(const std::string &message) override { - auto wasSent = false; - if (endpoint_ != nullptr) { - if (endpoint_->IsConnected()) { - wasSent = endpoint_->SendReply(message); - } - } + auto wasSent = endpoint_->SendReply(message); if (!wasSent) { LOG(INFO, DEBUGGER) << "Did not send message: " << message; } diff --git a/tooling/static/connection/server.h b/tooling/static/connection/server.h index 46ef13fd..a31d1771 100644 --- a/tooling/static/connection/server.h +++ b/tooling/static/connection/server.h @@ -16,13 +16,14 @@ #ifndef PANDA_TOOLING_INSPECTOR_CONNECTION_SERVER_H #define PANDA_TOOLING_INSPECTOR_CONNECTION_SERVER_H +#include #include #include "json_serialization/serializable.h" +#include "libpandabase/os/mutex.h" #include "libpandabase/utils/expected.h" #include "libpandabase/utils/json_builder.h" -#include "connection/event_loop.h" #include "json_serialization/jrpc_error.h" namespace ark { @@ -31,18 +32,52 @@ class JsonObjectBuilder; } // namespace ark namespace ark::tooling::inspector { -class Server : public virtual EventLoop { // NOLINT(fuchsia-virtual-inheritance) + +/// @brief Base class for server with a single listener thread +class Server { public: using MethodResponse = Expected, JRPCError>; - using Handler = std::function; + using Handler = std::function; public: - virtual void OnValidate(std::function &&handler) = 0; - virtual void OnOpen(std::function &&handler) = 0; - virtual void OnFail(std::function &&handler) = 0; + Server() = default; + NO_COPY_SEMANTIC(Server); + NO_MOVE_SEMANTIC(Server); + virtual ~Server() = default; - virtual void Call(const std::string &sessionId, const char *method, - std::function &¶ms) = 0; + /// @brief Notifies the running server to stop. + bool Kill() + { + return running_.exchange(false); + } + + /// @brief Runs server loop until paused. This function must be entrypoint of listener thread. + void Run(const std::string& msg) + { + ASSERT_PRINT(!running_, "Event loop is already running"); + + ParseMessage(msg); + } + + /// @brief Pauses the server while waiting for the current task to finish. + void Pause() ACQUIRE_SHARED(taskExecution_) + { + taskExecution_.ReadLock(); + } + + /// @brief Notifies the event loop to continue. + void Continue() RELEASE_GENERIC(taskExecution_) + { + taskExecution_.Unlock(); + } + + void OnCall(const char *method, Handler &&handler) + { + OnCallImpl(method, [this, h = std::move(handler)](const std::string &sessionId, const JsonObject ¶ms) { + os::memory::WriteLockHolder lock(taskExecution_); + return h(sessionId, params); + }); + } void Call(const char *method, std::function &¶ms) { @@ -59,8 +94,24 @@ public: Call({}, method, [](JsonObjectBuilder & /* builder */) {}); } - virtual void OnCall(const char *method, Handler &&handler) = 0; + virtual void Call(const std::string &sessionId, const char *method, + std::function &¶ms) = 0; + + virtual void OnValidate(std::function &&handler) = 0; + virtual void OnOpen(std::function &&handler) = 0; + virtual void OnFail(std::function &&handler) = 0; + + // Run at most one event loop handler, may block. + virtual bool ParseMessage(const std::string& msg) = 0; + +private: + virtual void OnCallImpl(const char *method, Handler &&handler) = 0; + +private: + std::atomic running_ {false}; + os::memory::RWLock taskExecution_; }; + } // namespace ark::tooling::inspector #endif // PANDA_TOOLING_INSPECTOR_CONNECTION_SERVER_H diff --git a/tooling/static/connection/server_endpoint_base.cpp b/tooling/static/connection/server_endpoint_base.cpp index bedbc976..5f43aedd 100644 --- a/tooling/static/connection/server_endpoint_base.cpp +++ b/tooling/static/connection/server_endpoint_base.cpp @@ -38,7 +38,7 @@ void ServerEndpointBase::Call(const std::string &sessionId, const char *method, EndpointBase::Call(sessionId, std::nullopt, method, std::move(params)); } -void ServerEndpointBase::OnCall(const char *method, Handler &&handler) +void ServerEndpointBase::OnCallImpl(const char *method, Handler &&handler) { EndpointBase::OnCall(method, [this, handler = std::move(handler)](auto &sessionId, auto id, auto ¶ms) { if (!id) { diff --git a/tooling/static/connection/server_endpoint_base.h b/tooling/static/connection/server_endpoint_base.h index 96ebe5d6..e743ab3e 100644 --- a/tooling/static/connection/server_endpoint_base.h +++ b/tooling/static/connection/server_endpoint_base.h @@ -50,7 +50,7 @@ public: void Call(const std::string &sessionId, const char *method, std::function &¶ms) override; - void OnCall(const char *method, Handler &&handler) override; + void OnCallImpl(const char *method, Handler &&handler) override; protected: std::function onValidate_ = []() {}; // NOLINT(misc-non-private-member-variables-in-classes) diff --git a/tooling/static/debugger/debuggable_thread.cpp b/tooling/static/debugger/debuggable_thread.cpp index c5057449..2763d269 100644 --- a/tooling/static/debugger/debuggable_thread.cpp +++ b/tooling/static/debugger/debuggable_thread.cpp @@ -286,4 +286,11 @@ void DebuggableThread::Resume() callbacks_.postResume(); } + +bool DebuggableThread::IsPausedByBreakOnStart() +{ + os::memory::LockHolder lock(mutex_); + return state_.IsPaused() && (state_.GetPauseReason() == PauseReason::BREAK_ON_START); +} + } // namespace ark::tooling::inspector diff --git a/tooling/static/debugger/debuggable_thread.h b/tooling/static/debugger/debuggable_thread.h index e3395d20..deb86c7a 100644 --- a/tooling/static/debugger/debuggable_thread.h +++ b/tooling/static/debugger/debuggable_thread.h @@ -125,6 +125,9 @@ public: Expected>, std::string> EvaluateExpression( uint32_t frameNumber, const ExpressionWrapper &bytecode); + // Checks if the thread was paused by BreakOnStart + bool IsPausedByBreakOnStart(); + private: using PtThreadEvaluationEngine::EvaluateExpression; diff --git a/tooling/static/inspector.cpp b/tooling/static/inspector.cpp index cc55c7c0..595bbfa1 100644 --- a/tooling/static/inspector.cpp +++ b/tooling/static/inspector.cpp @@ -200,7 +200,9 @@ void Inspector::ThreadStart(PtThread thread) os::memory::ReadLockHolder lock(debuggerEventsLock_); if (thread != PtThread::NONE) { - inspectorServer_.CallTargetAttachedToTarget(thread); + if (inspectorServer_.CallTargetAttachedToTarget(thread)) { + WaitForDebugger(); + } } // NOLINTBEGIN(modernize-avoid-bind) @@ -235,6 +237,17 @@ void Inspector::ThreadEnd(PtThread thread) ASSERT(erased == 1); } +void Inspector::PauseOtherThreads(PtThread thread) +{ + auto threadId = thread.GetId(); + for (auto &[threadTmp, dbgThread] : threads_) { + if (threadTmp.GetId() == threadId) { + continue; + } + dbgThread.Pause(); + } +} + void Inspector::VmDeath() { os::memory::WriteLockHolder lock(vmDeathLock_); @@ -285,9 +298,10 @@ void Inspector::Pause(PtThread thread) return; } - auto *debuggableThread = GetDebuggableThread(thread); - if (debuggableThread != nullptr) { - debuggableThread->Pause(); + (void)thread; + + for (auto &[_, dbgThread] : threads_) { + dbgThread.Pause(); } } @@ -299,8 +313,13 @@ void Inspector::Continue(PtThread thread) } auto *debuggableThread = GetDebuggableThread(thread); - if (debuggableThread != nullptr) { + if ((debuggableThread != nullptr) && debuggableThread->IsPausedByBreakOnStart()) { debuggableThread->Continue(); + return; + } + + for (auto &[_, dbgThread] : threads_) { + dbgThread.Continue(); } } @@ -597,6 +616,10 @@ void Inspector::DebuggableThreadPostSuspend(PtThread thread, ObjectRepository &o return true; })); }); + + if (!hitBreakpoints.empty()) { + PauseOtherThreads(thread); + } } void Inspector::NotifyExecutionEnded() diff --git a/tooling/static/inspector.h b/tooling/static/inspector.h index e81e29a6..3eb95a73 100644 --- a/tooling/static/inspector.h +++ b/tooling/static/inspector.h @@ -147,6 +147,7 @@ private: void CollectModules(); void DebuggerEnable(); void SourceNameInsert(const panda_file::DebugInfoExtractor *extractor); + void PauseOtherThreads(PtThread thread); private: bool breakOnStart_; diff --git a/tooling/static/inspector_server.cpp b/tooling/static/inspector_server.cpp index 5130066c..c973a656 100644 --- a/tooling/static/inspector_server.cpp +++ b/tooling/static/inspector_server.cpp @@ -43,7 +43,10 @@ #include "types/url_breakpoint_response.h" namespace ark::tooling::inspector { -InspectorServer::InspectorServer(Server &server) : server_(server) {} +InspectorServer::InspectorServer(Server &server) : server_(server) +{ + OnCallTargetAttachToTarget(); +} void InspectorServer::Kill() { @@ -206,12 +209,14 @@ void InspectorServer::CallRuntimeExecutionContextsCleared() server_.Call("Runtime.executionContextsCleared"); } -void InspectorServer::CallTargetAttachedToTarget(PtThread thread) +bool InspectorServer::CallTargetAttachedToTarget(PtThread thread) { auto &sessionId = sessionManager_.AddSession(thread); if (!sessionId.empty()) { SendTargetAttachedToTarget(sessionId); + return true; } + return false; } void InspectorServer::CallTargetDetachedFromTarget(PtThread thread) @@ -228,7 +233,7 @@ void InspectorServer::CallTargetDetachedFromTarget(PtThread thread) if (!sessionId.empty()) { server_.Call("Target.detachedFromTarget", - [&sessionId](auto ¶ms) { params.AddProperty("session_id", sessionId); }); + [&sessionId](auto ¶ms) { params.AddProperty("sessionId", sessionId); }); } } @@ -957,7 +962,7 @@ void InspectorServer::OnCallProfilerSetSamplingInterval(std::function(); }); @@ -969,9 +974,8 @@ void InspectorServer::OnCallProfilerStart(std::function Server::MethodResponse { auto optResult = handler(); if (!optResult) { - std::stringstream ss; - ss << "Profiler failed: " << optResult.Error(); - auto msg = ss.str(); + std::string msg = "Profiler failed: "; + msg = msg.append(optResult.Error()); LOG(DEBUG, DEBUGGER) << msg; return Unexpected(JRPCError(std::move(msg), ErrorCode::INTERNAL_ERROR)); } @@ -994,6 +998,41 @@ void InspectorServer::OnCallProfilerStop(std::function Server::MethodResponse { + const auto *targetId = params.template GetValue("targetId"); + if (targetId == nullptr) { + std::string_view msg = "No 'targetId' property"; + LOG(INFO, DEBUGGER) << msg; + return Unexpected(JRPCError(msg, ErrorCode::INVALID_REQUEST)); + } + auto thread = sessionManager_.GetThreadBySessionId(*targetId); + if (thread == PtThread::NONE) { + std::string msg = "'targetId' "; + msg = msg.append(*targetId).append(" did not start"); + LOG(INFO, DEBUGGER) << msg; + return Unexpected(JRPCError(msg, ErrorCode::INVALID_PARAMS)); + } + // No-op if coroutine already exists + return std::unique_ptr(std::make_unique(*targetId)); + }); + // clang-format on +} + void InspectorServer::SendTargetAttachedToTarget(const std::string &sessionId) { server_.Call("Target.attachedToTarget", [&sessionId](auto ¶ms) { diff --git a/tooling/static/inspector_server.h b/tooling/static/inspector_server.h index f6236e6a..b466cf68 100644 --- a/tooling/static/inspector_server.h +++ b/tooling/static/inspector_server.h @@ -74,7 +74,7 @@ public: const std::vector &arguments); void CallRuntimeExecutionContextCreated(PtThread thread); void CallRuntimeExecutionContextsCleared(); - void CallTargetAttachedToTarget(PtThread thread); + bool CallTargetAttachedToTarget(PtThread thread); void CallTargetDetachedFromTarget(PtThread thread); void OnCallDebuggerContinueToLocation(std::function &&handler); @@ -115,6 +115,7 @@ public: void OnCallRuntimeRunIfWaitingForDebugger(std::function &&handler); void OnCallRuntimeEvaluate( std::function(PtThread, const std::string &)> &&handler); + void OnCallTargetAttachToTarget(); void OnCallProfilerEnable(); void OnCallProfilerDisable(); void OnCallProfilerSetSamplingInterval(std::function &&handler); diff --git a/tooling/static/tests/inspector_server.cpp b/tooling/static/tests/inspector_server.cpp index 9eeefa86..feacc8c6 100644 --- a/tooling/static/tests/inspector_server.cpp +++ b/tooling/static/tests/inspector_server.cpp @@ -48,12 +48,6 @@ public: CallMock(session, tmp, std::move(parameters)); } - void OnCall(const char *method_call, Handler &&handler) override - { - std::string tmp(method_call); - OnCallMock(tmp, std::move(handler)); - } - MOCK_METHOD(void, CallMock, (const std::string &session, const std::string &method_call, std::function &¶meters)); @@ -64,6 +58,13 @@ public: { return true; }; + +private: + void OnCallImpl(const char *method_call, Handler &&handler) override + { + std::string tmp(method_call); + OnCallMock(tmp, std::move(handler)); + } }; static PtThread g_mthread = PtThread(PtThread::NONE); @@ -135,6 +136,12 @@ static void GetResult(Expected, JRPCError> &&r TEST_F(ServerTest, DebuggerEnable) { TestServer server1; + EXPECT_CALL(server1, OnCallMock("Target.attachToTarget", testing::_)).WillOnce([&](testing::Unused, auto handler) { + JsonObjectBuilder params; + params.AddProperty("targetId", g_sessionId); + auto res = handler(g_sessionId, JsonObject(std::move(params).Build())); + ASSERT_FALSE(res.HasValue()); + }); EXPECT_CALL(server1, OnCallMock("Debugger.enable", testing::_)).WillOnce([&](testing::Unused, auto handler) { JsonObject empty; auto res = handler(g_sessionId, empty); @@ -145,8 +152,24 @@ TEST_F(ServerTest, DebuggerEnable) JsonProperties(JsonProperty {"debuggerId", 0}, JsonProperty {"protocols", JsonElementsAreArray(protocols)})); }); - InspectorServer inspector_server1(server1); - inspector_server1.OnCallDebuggerEnable([] {}); + InspectorServer inspectorServer1(server1); + inspectorServer1.OnCallDebuggerEnable([] {}); +} + +TEST_F(ServerTest, OnCallAttachToTarget) +{ + inspectorServer.CallTargetAttachedToTarget(g_mthread); + + EXPECT_CALL(server, OnCallMock("Target.attachToTarget", testing::_)).WillOnce([&](testing::Unused, auto handler) { + JsonObjectBuilder params; + params.AddProperty("targetId", g_sessionId); + auto res = handler(g_sessionId, JsonObject(std::move(params).Build())); + ResultHolder result; + GetResult(std::move(res), result); + ASSERT_TRUE(result); + ASSERT_THAT(*result, JsonProperties(JsonProperty {"sessionId", g_sessionId})); + }); + inspectorServer.OnCallTargetAttachToTarget(); } static auto g_simpleHandler = []([[maybe_unused]] auto unused, auto handler) { @@ -171,7 +194,7 @@ TEST_F(ServerTest, OnCallDebuggerPause) TEST_F(ServerTest, OnCallDebuggerRemoveBreakpoint) { - size_t break_id = 14; + size_t breakId = 14; inspectorServer.CallTargetAttachedToTarget(g_mthread); @@ -183,8 +206,8 @@ TEST_F(ServerTest, OnCallDebuggerRemoveBreakpoint) ASSERT_FALSE(g_handlerCalled); }); - auto breaks = [break_id](PtThread thread, BreakpointId bid) { - ASSERT_EQ(bid, BreakpointId(break_id)); + auto breaks = [breakId](PtThread thread, BreakpointId bid) { + ASSERT_EQ(bid, BreakpointId(breakId)); ASSERT_EQ(thread.GetId(), g_mthread.GetId()); g_handlerCalled = true; }; @@ -193,7 +216,7 @@ TEST_F(ServerTest, OnCallDebuggerRemoveBreakpoint) EXPECT_CALL(server, OnCallMock("Debugger.removeBreakpoint", testing::_)) .WillOnce([&](testing::Unused, auto handler) { JsonObjectBuilder params; - params.AddProperty("breakpointId", std::to_string(break_id)); + params.AddProperty("breakpointId", std::to_string(breakId)); auto res = handler(g_sessionId, JsonObject(std::move(params).Build())); ResultHolder result; GetResult(std::move(res), result); -- Gitee