https://github.com/JDevlieghere updated https://github.com/llvm/llvm-project/pull/138219
>From e7333489e50b1efe16203e8fd7b6dc7f2dcd4439 Mon Sep 17 00:00:00 2001 From: Jonas Devlieghere <jo...@devlieghere.com> Date: Fri, 2 May 2025 09:59:01 -0700 Subject: [PATCH 1/3] [lldb-dap] Change the launch sequence This PR changes how we treat the launch sequence in lldb-dap. - Send the initialized event after we finish handling the initialize request, rather than after we finish attaching or launching. - Delay handling the launch and attach request until we have handled the configurationDone request. The latter is now largely a NO-OP and only exists to signal lldb-dap that it can handle the launch and attach requests. - Delay handling the initial threads requests until we have handled the launch or attach request. - Make all attaching and launching asynchronous, including when we have attach or launch commands. That removes the need to synchronize between the request and event thread. Background: https://discourse.llvm.org/t/reliability-of-the-lldb-dap-tests/86125 --- lldb/tools/lldb-dap/DAP.cpp | 53 +++++++--- lldb/tools/lldb-dap/DAP.h | 6 +- lldb/tools/lldb-dap/EventHelper.cpp | 2 +- .../lldb-dap/Handler/AttachRequestHandler.cpp | 98 ++++++++++--------- .../ConfigurationDoneRequestHandler.cpp | 14 +-- .../Handler/InitializeRequestHandler.cpp | 44 ++++----- .../lldb-dap/Handler/LaunchRequestHandler.cpp | 2 - .../tools/lldb-dap/Handler/RequestHandler.cpp | 69 ++++++++----- lldb/tools/lldb-dap/Handler/RequestHandler.h | 1 + 9 files changed, 163 insertions(+), 126 deletions(-) diff --git a/lldb/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp index 4cb0d8e49004c..470cde5ae69b0 100644 --- a/lldb/tools/lldb-dap/DAP.cpp +++ b/lldb/tools/lldb-dap/DAP.cpp @@ -84,8 +84,8 @@ DAP::DAP(Log *log, const ReplMode default_repl_mode, : log(log), transport(transport), broadcaster("lldb-dap"), exception_breakpoints(), focus_tid(LLDB_INVALID_THREAD_ID), stop_at_entry(false), is_attach(false), - restarting_process_id(LLDB_INVALID_PROCESS_ID), - configuration_done_sent(false), waiting_for_run_in_terminal(false), + restarting_process_id(LLDB_INVALID_PROCESS_ID), configuration_done(false), + waiting_for_run_in_terminal(false), progress_event_reporter( [&](const ProgressEvent &event) { SendJSON(event.ToJSON()); }), reverse_request_seq(0), repl_mode(default_repl_mode) { @@ -893,10 +893,23 @@ llvm::Error DAP::Loop() { return errWrapper; } + // The launch sequence is special and we need to carefully handle + // packets in the right order. The launch and attach requests cannot + // be answered until we've gotten the confgigurationDone request. We + // can't answer the threads request until we've successfully launched + // or attached. + bool is_part_of_launch_sequence = false; + if (const protocol::Request *req = - std::get_if<protocol::Request>(&*next); - req && req->command == "disconnect") { - disconnecting = true; + std::get_if<protocol::Request>(&*next)) { + if (req->command == "disconnect") + disconnecting = true; + if (!configuration_done) { + if (req->command == "launch" || req->command == "attach") + is_part_of_launch_sequence = true; + if (req->command == "threads" && !m_launch_sequence_queue.empty()) + is_part_of_launch_sequence = true; + } } const std::optional<CancelArguments> cancel_args = @@ -924,7 +937,10 @@ llvm::Error DAP::Loop() { { std::lock_guard<std::mutex> guard(m_queue_mutex); - m_queue.push_back(std::move(*next)); + if (is_part_of_launch_sequence) + m_launch_sequence_queue.push_back(std::move(*next)); + else + m_queue.push_back(std::move(*next)); } m_queue_cv.notify_one(); } @@ -938,15 +954,30 @@ llvm::Error DAP::Loop() { StopEventHandlers(); }); - while (!disconnecting || !m_queue.empty()) { + while (true) { std::unique_lock<std::mutex> lock(m_queue_mutex); - m_queue_cv.wait(lock, [&] { return disconnecting || !m_queue.empty(); }); + m_queue_cv.wait(lock, [&] { + return disconnecting || !m_queue.empty() || + (!m_launch_sequence_queue.empty() && configuration_done); + }); - if (m_queue.empty()) + if (disconnecting) break; - Message next = m_queue.front(); - m_queue.pop_front(); + bool is_launch_seqeuence = + !m_launch_sequence_queue.empty() && configuration_done; + + auto &active_queue = + is_launch_seqeuence ? m_launch_sequence_queue : m_queue; + + assert(!active_queue.empty() && + "shouldn't have gotten past the wait with an empty queue"); + + Message next = active_queue.front(); + active_queue.pop_front(); + + // Unlock while we're processing the event. + lock.unlock(); if (!HandleObject(next)) return llvm::createStringError(llvm::inconvertibleErrorCode(), diff --git a/lldb/tools/lldb-dap/DAP.h b/lldb/tools/lldb-dap/DAP.h index 88eedb0860cf1..4f038b66757c8 100644 --- a/lldb/tools/lldb-dap/DAP.h +++ b/lldb/tools/lldb-dap/DAP.h @@ -188,7 +188,7 @@ struct DAP { // shutting down the entire adapter. When we're restarting, we keep the id of // the old process here so we can detect this case and keep running. lldb::pid_t restarting_process_id; - bool configuration_done_sent; + bool configuration_done; llvm::StringMap<std::unique_ptr<BaseRequestHandler>> request_handlers; bool waiting_for_run_in_terminal; ProgressEventReporter progress_event_reporter; @@ -417,8 +417,10 @@ struct DAP { lldb::SBMutex GetAPIMutex() const { return target.GetAPIMutex(); } private: - std::mutex m_queue_mutex; + /// Queue for all incoming messages. std::deque<protocol::Message> m_queue; + std::deque<protocol::Message> m_launch_sequence_queue; + std::mutex m_queue_mutex; std::condition_variable m_queue_cv; std::mutex m_cancelled_requests_mutex; diff --git a/lldb/tools/lldb-dap/EventHelper.cpp b/lldb/tools/lldb-dap/EventHelper.cpp index 2c659f39f4b66..ed2d8700c26b0 100644 --- a/lldb/tools/lldb-dap/EventHelper.cpp +++ b/lldb/tools/lldb-dap/EventHelper.cpp @@ -222,7 +222,7 @@ void SendContinuedEvent(DAP &dap) { // If the focus thread is not set then we haven't reported any thread status // to the client, so nothing to report. - if (!dap.configuration_done_sent || dap.focus_tid == LLDB_INVALID_THREAD_ID) { + if (!dap.configuration_done || dap.focus_tid == LLDB_INVALID_THREAD_ID) { return; } diff --git a/lldb/tools/lldb-dap/Handler/AttachRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/AttachRequestHandler.cpp index 7084d30f2625b..26bd46e587c6d 100644 --- a/lldb/tools/lldb-dap/Handler/AttachRequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/AttachRequestHandler.cpp @@ -137,55 +137,67 @@ void AttachRequestHandler::operator()(const llvm::json::Object &request) const { dap.SendOutput(OutputType::Console, llvm::StringRef(attach_msg, attach_msg_len)); } - if (attachCommands.empty()) { - // No "attachCommands", just attach normally. - // Disable async events so the attach will be successful when we return from - // the launch call and the launch will happen synchronously + { + // Perform the launch in synchronous mode so that we don't have to worry + // about process state changes during the launch. ScopeSyncMode scope_sync_mode(dap.debugger); - - if (core_file.empty()) { - if ((pid != LLDB_INVALID_PROCESS_ID) && - (gdb_remote_port != invalid_port)) { - // If both pid and port numbers are specified. - error.SetErrorString("The user can't specify both pid and port"); - } else if (gdb_remote_port != invalid_port) { - // If port is specified and pid is not. - lldb::SBListener listener = dap.debugger.GetListener(); - - // If the user hasn't provided the hostname property, default localhost - // being used. - std::string connect_url = - llvm::formatv("connect://{0}:", gdb_remote_hostname); - connect_url += std::to_string(gdb_remote_port); - dap.target.ConnectRemote(listener, connect_url.c_str(), "gdb-remote", - error); + if (attachCommands.empty()) { + // No "attachCommands", just attach normally. + if (core_file.empty()) { + if ((pid != LLDB_INVALID_PROCESS_ID) && + (gdb_remote_port != invalid_port)) { + // If both pid and port numbers are specified. + error.SetErrorString("The user can't specify both pid and port"); + } else if (gdb_remote_port != invalid_port) { + // If port is specified and pid is not. + lldb::SBListener listener = dap.debugger.GetListener(); + + // If the user hasn't provided the hostname property, default + // localhost being used. + std::string connect_url = + llvm::formatv("connect://{0}:", gdb_remote_hostname); + connect_url += std::to_string(gdb_remote_port); + dap.target.ConnectRemote(listener, connect_url.c_str(), "gdb-remote", + error); + } else { + // Attach by process name or id. + dap.target.Attach(attach_info, error); + } } else { - // Attach by process name or id. - dap.target.Attach(attach_info, error); + dap.target.LoadCore(core_file.data(), error); } } else { - dap.target.LoadCore(core_file.data(), error); - } - } else { - // We have "attachCommands" that are a set of commands that are expected - // to execute the commands after which a process should be created. If there - // is no valid process after running these commands, we have failed. - if (llvm::Error err = dap.RunAttachCommands(attachCommands)) { - response["success"] = false; - EmplaceSafeString(response, "message", llvm::toString(std::move(err))); - dap.SendJSON(llvm::json::Value(std::move(response))); - return; + // We have "attachCommands" that are a set of commands that are expected + // to execute the commands after which a process should be created. If + // there is no valid process after running these commands, we have failed. + if (llvm::Error err = dap.RunAttachCommands(attachCommands)) { + response["success"] = false; + EmplaceSafeString(response, "message", llvm::toString(std::move(err))); + dap.SendJSON(llvm::json::Value(std::move(response))); + return; + } + // The custom commands might have created a new target so we should use + // the selected target after these commands are run. + dap.target = dap.debugger.GetSelectedTarget(); } - // The custom commands might have created a new target so we should use the - // selected target after these commands are run. - dap.target = dap.debugger.GetSelectedTarget(); - - // Make sure the process is attached and stopped before proceeding as the - // the launch commands are not run using the synchronous mode. - error = dap.WaitForProcessToStop(std::chrono::seconds(timeout_seconds)); } + // Make sure the process is attached and stopped. + error = dap.WaitForProcessToStop(std::chrono::seconds(timeout_seconds)); + + // Clients can request a baseline of currently existing threads after + // we acknowledge the configurationDone request. + // Client requests the baseline of currently existing threads after + // a successful or attach by sending a 'threads' request + // right after receiving the configurationDone response. + // Obtain the list of threads before we resume the process + dap.initial_thread_list = + GetThreads(dap.target.GetProcess(), dap.thread_format); + + if (!dap.stop_at_entry) + dap.target.GetProcess().Continue(); + if (error.Success() && core_file.empty()) { auto attached_pid = dap.target.GetProcess().GetProcessID(); if (attached_pid == LLDB_INVALID_PROCESS_ID) { @@ -204,10 +216,8 @@ void AttachRequestHandler::operator()(const llvm::json::Object &request) const { } dap.SendJSON(llvm::json::Value(std::move(response))); - if (error.Success()) { + if (error.Success()) SendProcessEvent(dap, Attach); - dap.SendJSON(CreateEventObject("initialized")); - } } } // namespace lldb_dap diff --git a/lldb/tools/lldb-dap/Handler/ConfigurationDoneRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/ConfigurationDoneRequestHandler.cpp index f39bbdefdbb95..a10cd0c832782 100644 --- a/lldb/tools/lldb-dap/Handler/ConfigurationDoneRequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/ConfigurationDoneRequestHandler.cpp @@ -47,21 +47,11 @@ namespace lldb_dap { void ConfigurationDoneRequestHandler::operator()( const llvm::json::Object &request) const { + dap.configuration_done = true; + llvm::json::Object response; FillResponse(request, response); dap.SendJSON(llvm::json::Value(std::move(response))); - dap.configuration_done_sent = true; - if (dap.stop_at_entry) - SendThreadStoppedEvent(dap); - else { - // Client requests the baseline of currently existing threads after - // a successful launch or attach by sending a 'threads' request - // right after receiving the configurationDone response. - // Obtain the list of threads before we resume the process - dap.initial_thread_list = - GetThreads(dap.target.GetProcess(), dap.thread_format); - dap.target.GetProcess().Continue(); - } } } // namespace lldb_dap diff --git a/lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp index ce34c52bcc334..aa947d3cb5ab9 100644 --- a/lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp @@ -140,43 +140,28 @@ static void EventThreadFunction(DAP &dap) { lldb::SBProcess process = lldb::SBProcess::GetProcessFromEvent(event); if (event_mask & lldb::SBProcess::eBroadcastBitStateChanged) { auto state = lldb::SBProcess::GetStateFromEvent(event); + + DAP_LOG(dap.log, "State = {0}", state); switch (state) { + case lldb::eStateConnected: + case lldb::eStateDetached: case lldb::eStateInvalid: - // Not a state event - break; case lldb::eStateUnloaded: break; - case lldb::eStateConnected: - break; case lldb::eStateAttaching: - break; - case lldb::eStateLaunching: - break; - case lldb::eStateStepping: - break; case lldb::eStateCrashed: - break; - case lldb::eStateDetached: - break; - case lldb::eStateSuspended: - break; + case lldb::eStateLaunching: case lldb::eStateStopped: - // We launch and attach in synchronous mode then the first stop - // event will not be delivered. If we use "launchCommands" during a - // launch or "attachCommands" during an attach we might some process - // stop events which we do not want to send an event for. We will - // manually send a stopped event in request_configurationDone(...) - // so don't send any before then. - if (dap.configuration_done_sent) { - // Only report a stopped event if the process was not - // automatically restarted. - if (!lldb::SBProcess::GetRestartedFromEvent(event)) { - SendStdOutStdErr(dap, process); - SendThreadStoppedEvent(dap); - } + case lldb::eStateSuspended: + // Only report a stopped event if the process was not + // automatically restarted. + if (!lldb::SBProcess::GetRestartedFromEvent(event)) { + SendStdOutStdErr(dap, process); + SendThreadStoppedEvent(dap); } break; case lldb::eStateRunning: + case lldb::eStateStepping: dap.WillContinue(); SendContinuedEvent(dap); break; @@ -284,6 +269,7 @@ llvm::Expected<InitializeResponseBody> InitializeRequestHandler::Run( // Do not source init files until in/out/err are configured. dap.debugger = lldb::SBDebugger::Create(false); dap.debugger.SetInputFile(dap.in); + dap.target = dap.debugger.GetDummyTarget(); llvm::Expected<int> out_fd = dap.out.GetWriteFileDescriptor(); if (!out_fd) @@ -338,4 +324,8 @@ llvm::Expected<InitializeResponseBody> InitializeRequestHandler::Run( return dap.GetCapabilities(); } +void InitializeRequestHandler::PostRun() const { + dap.SendJSON(CreateEventObject("initialized")); +} + } // namespace lldb_dap diff --git a/lldb/tools/lldb-dap/Handler/LaunchRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/LaunchRequestHandler.cpp index 3e4532e754ec6..ffd61b040e55f 100644 --- a/lldb/tools/lldb-dap/Handler/LaunchRequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/LaunchRequestHandler.cpp @@ -72,8 +72,6 @@ void LaunchRequestHandler::PostRun() const { // Attach happens when launching with runInTerminal. SendProcessEvent(dap, dap.is_attach ? Attach : Launch); } - - dap.SendJSON(CreateEventObject("initialized")); } } // namespace lldb_dap diff --git a/lldb/tools/lldb-dap/Handler/RequestHandler.cpp b/lldb/tools/lldb-dap/Handler/RequestHandler.cpp index 7a75cd93abc19..82c6e1b0071a9 100644 --- a/lldb/tools/lldb-dap/Handler/RequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/RequestHandler.cpp @@ -162,7 +162,7 @@ RunInTerminal(DAP &dap, const protocol::LaunchRequestArguments &arguments) { dap.target.GetProcess().Continue(); // Now that the actual target is just starting (i.e. exec was just invoked), - // we return the debugger to its async state. + // we return the debugger to its sync state. scope_sync_mode.reset(); // If sending the notification failed, the launcher should be dead by now and @@ -238,35 +238,50 @@ llvm::Error BaseRequestHandler::LaunchProcess( launch_info.SetLaunchFlags(flags | lldb::eLaunchFlagDebug | lldb::eLaunchFlagStopAtEntry); - if (arguments.runInTerminal) { - if (llvm::Error err = RunInTerminal(dap, arguments)) - return err; - } else if (launchCommands.empty()) { - lldb::SBError error; - // Disable async events so the launch will be successful when we return from - // the launch call and the launch will happen synchronously + { + // Perform the launch in synchronous mode so that we don't have to worry + // about process state changes during the launch. ScopeSyncMode scope_sync_mode(dap.debugger); - dap.target.Launch(launch_info, error); - if (error.Fail()) - return llvm::make_error<DAPError>(error.GetCString()); - } else { - // Set the launch info so that run commands can access the configured - // launch details. - dap.target.SetLaunchInfo(launch_info); - if (llvm::Error err = dap.RunLaunchCommands(launchCommands)) - return err; - - // The custom commands might have created a new target so we should use the - // selected target after these commands are run. - dap.target = dap.debugger.GetSelectedTarget(); - // Make sure the process is launched and stopped at the entry point before - // proceeding as the launch commands are not run using the synchronous - // mode. - lldb::SBError error = dap.WaitForProcessToStop(arguments.timeout); - if (error.Fail()) - return llvm::make_error<DAPError>(error.GetCString()); + + if (arguments.runInTerminal) { + if (llvm::Error err = RunInTerminal(dap, arguments)) + return err; + } else if (launchCommands.empty()) { + lldb::SBError error; + dap.target.Launch(launch_info, error); + if (error.Fail()) + return llvm::make_error<DAPError>(error.GetCString()); + } else { + // Set the launch info so that run commands can access the configured + // launch details. + dap.target.SetLaunchInfo(launch_info); + if (llvm::Error err = dap.RunLaunchCommands(launchCommands)) + return err; + + // The custom commands might have created a new target so we should use + // the selected target after these commands are run. + dap.target = dap.debugger.GetSelectedTarget(); + } } + // Make sure the process is launched and stopped at the entry point before + // proceeding. + lldb::SBError error = dap.WaitForProcessToStop(arguments.timeout); + if (error.Fail()) + return llvm::make_error<DAPError>(error.GetCString()); + + // Clients can request a baseline of currently existing threads after + // we acknowledge the configurationDone request. + // Client requests the baseline of currently existing threads after + // a successful or attach by sending a 'threads' request + // right after receiving the configurationDone response. + // Obtain the list of threads before we resume the process + dap.initial_thread_list = + GetThreads(dap.target.GetProcess(), dap.thread_format); + + if (!dap.stop_at_entry) + dap.target.GetProcess().Continue(); + return llvm::Error::success(); } diff --git a/lldb/tools/lldb-dap/Handler/RequestHandler.h b/lldb/tools/lldb-dap/Handler/RequestHandler.h index fa3d76ed4a125..dec35c5dce1e5 100644 --- a/lldb/tools/lldb-dap/Handler/RequestHandler.h +++ b/lldb/tools/lldb-dap/Handler/RequestHandler.h @@ -282,6 +282,7 @@ class InitializeRequestHandler static llvm::StringLiteral GetCommand() { return "initialize"; } llvm::Expected<protocol::InitializeResponseBody> Run(const protocol::InitializeRequestArguments &args) const override; + void PostRun() const override; }; class LaunchRequestHandler >From 7441ea0b2f9dbb95fc556fa5c562a6c14a20c54c Mon Sep 17 00:00:00 2001 From: Jonas Devlieghere <jo...@devlieghere.com> Date: Fri, 2 May 2025 13:14:34 -0700 Subject: [PATCH 2/3] Address John's feedback --- lldb/tools/lldb-dap/DAP.cpp | 56 ++++++++----------- lldb/tools/lldb-dap/DAP.h | 4 +- .../lldb-dap/Handler/AttachRequestHandler.cpp | 4 +- .../ConfigurationDoneRequestHandler.cpp | 2 +- .../tools/lldb-dap/Handler/RequestHandler.cpp | 5 +- 5 files changed, 35 insertions(+), 36 deletions(-) diff --git a/lldb/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp index 470cde5ae69b0..0ab16f16d30bf 100644 --- a/lldb/tools/lldb-dap/DAP.cpp +++ b/lldb/tools/lldb-dap/DAP.cpp @@ -894,22 +894,18 @@ llvm::Error DAP::Loop() { } // The launch sequence is special and we need to carefully handle - // packets in the right order. The launch and attach requests cannot - // be answered until we've gotten the confgigurationDone request. We - // can't answer the threads request until we've successfully launched - // or attached. - bool is_part_of_launch_sequence = false; + // packets in the right order. Until we've handled configurationDone, + bool add_to_pending_queue = false; if (const protocol::Request *req = std::get_if<protocol::Request>(&*next)) { - if (req->command == "disconnect") + llvm::StringRef command = req->command; + if (command == "disconnect") disconnecting = true; - if (!configuration_done) { - if (req->command == "launch" || req->command == "attach") - is_part_of_launch_sequence = true; - if (req->command == "threads" && !m_launch_sequence_queue.empty()) - is_part_of_launch_sequence = true; - } + if (!configuration_done) + add_to_pending_queue = + command != "initialize" && command != "configurationDone" && + command != "disconnect" && !command.ends_with("Breakpoints"); } const std::optional<CancelArguments> cancel_args = @@ -937,10 +933,8 @@ llvm::Error DAP::Loop() { { std::lock_guard<std::mutex> guard(m_queue_mutex); - if (is_part_of_launch_sequence) - m_launch_sequence_queue.push_back(std::move(*next)); - else - m_queue.push_back(std::move(*next)); + auto &queue = add_to_pending_queue ? m_pending_queue : m_queue; + queue.push_back(std::move(*next)); } m_queue_cv.notify_one(); } @@ -956,25 +950,13 @@ llvm::Error DAP::Loop() { while (true) { std::unique_lock<std::mutex> lock(m_queue_mutex); - m_queue_cv.wait(lock, [&] { - return disconnecting || !m_queue.empty() || - (!m_launch_sequence_queue.empty() && configuration_done); - }); + m_queue_cv.wait(lock, [&] { return disconnecting || !m_queue.empty(); }); - if (disconnecting) + if (disconnecting && m_queue.empty()) break; - bool is_launch_seqeuence = - !m_launch_sequence_queue.empty() && configuration_done; - - auto &active_queue = - is_launch_seqeuence ? m_launch_sequence_queue : m_queue; - - assert(!active_queue.empty() && - "shouldn't have gotten past the wait with an empty queue"); - - Message next = active_queue.front(); - active_queue.pop_front(); + Message next = m_queue.front(); + m_queue.pop_front(); // Unlock while we're processing the event. lock.unlock(); @@ -1252,6 +1234,16 @@ void DAP::SetConfiguration(const protocol::Configuration &config, SetThreadFormat(*configuration.customThreadFormat); } +void DAP::SetConfigurationDone() { + { + std::lock_guard<std::mutex> guard(m_queue_mutex); + std::copy(m_pending_queue.begin(), m_pending_queue.end(), + std::front_inserter(m_queue)); + configuration_done = true; + } + m_queue_cv.notify_all(); +} + void DAP::SetFrameFormat(llvm::StringRef format) { if (format.empty()) return; diff --git a/lldb/tools/lldb-dap/DAP.h b/lldb/tools/lldb-dap/DAP.h index 4f038b66757c8..b581ae759b1bc 100644 --- a/lldb/tools/lldb-dap/DAP.h +++ b/lldb/tools/lldb-dap/DAP.h @@ -251,6 +251,8 @@ struct DAP { /// Configures the debug adapter for launching/attaching. void SetConfiguration(const protocol::Configuration &confing, bool is_attach); + void SetConfigurationDone(); + /// Configure source maps based on the current `DAPConfiguration`. void ConfigureSourceMaps(); @@ -419,7 +421,7 @@ struct DAP { private: /// Queue for all incoming messages. std::deque<protocol::Message> m_queue; - std::deque<protocol::Message> m_launch_sequence_queue; + std::deque<protocol::Message> m_pending_queue; std::mutex m_queue_mutex; std::condition_variable m_queue_cv; diff --git a/lldb/tools/lldb-dap/Handler/AttachRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/AttachRequestHandler.cpp index 26bd46e587c6d..da29193b2120b 100644 --- a/lldb/tools/lldb-dap/Handler/AttachRequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/AttachRequestHandler.cpp @@ -195,7 +195,9 @@ void AttachRequestHandler::operator()(const llvm::json::Object &request) const { dap.initial_thread_list = GetThreads(dap.target.GetProcess(), dap.thread_format); - if (!dap.stop_at_entry) + if (dap.stop_at_entry) + SendThreadStoppedEvent(dap); + else dap.target.GetProcess().Continue(); if (error.Success() && core_file.empty()) { diff --git a/lldb/tools/lldb-dap/Handler/ConfigurationDoneRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/ConfigurationDoneRequestHandler.cpp index a10cd0c832782..802c28d7b8904 100644 --- a/lldb/tools/lldb-dap/Handler/ConfigurationDoneRequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/ConfigurationDoneRequestHandler.cpp @@ -47,7 +47,7 @@ namespace lldb_dap { void ConfigurationDoneRequestHandler::operator()( const llvm::json::Object &request) const { - dap.configuration_done = true; + dap.SetConfigurationDone(); llvm::json::Object response; FillResponse(request, response); diff --git a/lldb/tools/lldb-dap/Handler/RequestHandler.cpp b/lldb/tools/lldb-dap/Handler/RequestHandler.cpp index 82c6e1b0071a9..bac711c8df83a 100644 --- a/lldb/tools/lldb-dap/Handler/RequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/RequestHandler.cpp @@ -8,6 +8,7 @@ #include "Handler/RequestHandler.h" #include "DAP.h" +#include "EventHelper.h" #include "Handler/ResponseHandler.h" #include "JSONUtils.h" #include "LLDBUtils.h" @@ -279,7 +280,9 @@ llvm::Error BaseRequestHandler::LaunchProcess( dap.initial_thread_list = GetThreads(dap.target.GetProcess(), dap.thread_format); - if (!dap.stop_at_entry) + if (dap.stop_at_entry) + SendThreadStoppedEvent(dap); + else dap.target.GetProcess().Continue(); return llvm::Error::success(); >From 17e812f2396d81375c2c2a1f941fea0f7ff60ec9 Mon Sep 17 00:00:00 2001 From: Jonas Devlieghere <jo...@devlieghere.com> Date: Fri, 2 May 2025 14:27:16 -0700 Subject: [PATCH 3/3] Re-enable and update tests --- .../test/tools/lldb-dap/dap_server.py | 44 ++++++++++--------- .../test/tools/lldb-dap/lldbdap_testcase.py | 4 ++ .../tools/lldb-dap/attach/TestDAP_attach.py | 3 +- .../attach/TestDAP_attachByPortNum.py | 8 ++-- .../breakpoint/TestDAP_breakpointLocations.py | 3 +- .../breakpoint/TestDAP_setBreakpoints.py | 3 +- .../lldb-dap/commands/TestDAP_commands.py | 3 +- .../completions/TestDAP_completions.py | 6 +-- .../tools/lldb-dap/console/TestDAP_console.py | 2 +- .../disassemble/TestDAP_disassemble.py | 3 +- .../lldb-dap/disconnect/TestDAP_disconnect.py | 6 ++- .../lldb-dap/evaluate/TestDAP_evaluate.py | 3 +- .../tools/lldb-dap/launch/TestDAP_launch.py | 4 +- .../tools/lldb-dap/memory/TestDAP_memory.py | 3 +- .../lldb-dap/progress/TestDAP_Progress.py | 2 +- .../repl-mode/TestDAP_repl_mode_detection.py | 2 +- .../tools/lldb-dap/restart/TestDAP_restart.py | 1 - .../restart/TestDAP_restart_runInTerminal.py | 1 - .../lldb-dap/stop-hooks/TestDAP_stop_hooks.py | 2 +- .../lldb-dap/variables/TestDAP_variables.py | 3 +- 20 files changed, 54 insertions(+), 52 deletions(-) diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py index 6d9ab770684f1..7d4f5a2b15680 100644 --- a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py @@ -591,6 +591,7 @@ def request_attach( attachCommands=None, terminateCommands=None, coreFile=None, + stopOnAttach=True, postRunCommands=None, sourceMap=None, gdbRemotePort=None, @@ -620,6 +621,8 @@ def request_attach( args_dict["attachCommands"] = attachCommands if coreFile: args_dict["coreFile"] = coreFile + if stopOnAttach: + args_dict["stopOnEntry"] = stopOnAttach if postRunCommands: args_dict["postRunCommands"] = postRunCommands if sourceMap: @@ -666,10 +669,6 @@ def request_configurationDone(self): response = self.send_recv(command_dict) if response: self.configuration_done_sent = True - # Client requests the baseline of currently existing threads after - # a successful launch or attach. - # Kick off the threads request that follows - self.request_threads() return response def _process_stopped(self): @@ -1325,6 +1324,26 @@ def attach_options_specified(options): def run_vscode(dbg, args, options): dbg.request_initialize(options.sourceInitFile) + + if options.sourceBreakpoints: + source_to_lines = {} + for file_line in options.sourceBreakpoints: + (path, line) = file_line.split(":") + if len(path) == 0 or len(line) == 0: + print('error: invalid source with line "%s"' % (file_line)) + + else: + if path in source_to_lines: + source_to_lines[path].append(int(line)) + else: + source_to_lines[path] = [int(line)] + for source in source_to_lines: + dbg.request_setBreakpoints(source, source_to_lines[source]) + if options.funcBreakpoints: + dbg.request_setFunctionBreakpoints(options.funcBreakpoints) + + dbg.request_configurationDone() + if attach_options_specified(options): response = dbg.request_attach( program=options.program, @@ -1353,23 +1372,6 @@ def run_vscode(dbg, args, options): ) if response["success"]: - if options.sourceBreakpoints: - source_to_lines = {} - for file_line in options.sourceBreakpoints: - (path, line) = file_line.split(":") - if len(path) == 0 or len(line) == 0: - print('error: invalid source with line "%s"' % (file_line)) - - else: - if path in source_to_lines: - source_to_lines[path].append(int(line)) - else: - source_to_lines[path] = [int(line)] - for source in source_to_lines: - dbg.request_setBreakpoints(source, source_to_lines[source]) - if options.funcBreakpoints: - dbg.request_setFunctionBreakpoints(options.funcBreakpoints) - dbg.request_configurationDone() dbg.wait_for_stopped() else: if "message" in response: diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py index ee5272850b9a8..5e48f8f1e9bde 100644 --- a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py @@ -333,6 +333,7 @@ def attach( exitCommands=None, attachCommands=None, coreFile=None, + stopOnAttach=True, disconnectAutomatically=True, terminateCommands=None, postRunCommands=None, @@ -357,6 +358,7 @@ def cleanup(): self.addTearDownHook(cleanup) # Initialize and launch the program self.dap_server.request_initialize(sourceInitFile) + self.dap_server.request_configurationDone() response = self.dap_server.request_attach( program=program, pid=pid, @@ -369,6 +371,7 @@ def cleanup(): attachCommands=attachCommands, terminateCommands=terminateCommands, coreFile=coreFile, + stopOnAttach=stopOnAttach, postRunCommands=postRunCommands, sourceMap=sourceMap, gdbRemotePort=gdbRemotePort, @@ -427,6 +430,7 @@ def cleanup(): # Initialize and launch the program self.dap_server.request_initialize(sourceInitFile) + self.dap_server.request_configurationDone() response = self.dap_server.request_launch( program, args=args, diff --git a/lldb/test/API/tools/lldb-dap/attach/TestDAP_attach.py b/lldb/test/API/tools/lldb-dap/attach/TestDAP_attach.py index 6f70316821c8c..01fba0e5694d4 100644 --- a/lldb/test/API/tools/lldb-dap/attach/TestDAP_attach.py +++ b/lldb/test/API/tools/lldb-dap/attach/TestDAP_attach.py @@ -25,9 +25,10 @@ def spawn_and_wait(program, delay): process.wait() -@skipIf class TestDAP_attach(lldbdap_testcase.DAPTestCaseBase): def set_and_hit_breakpoint(self, continueToExit=True): + self.dap_server.wait_for_stopped() + source = "main.c" breakpoint1_line = line_number(source, "// breakpoint 1") lines = [breakpoint1_line] diff --git a/lldb/test/API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py b/lldb/test/API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py index 51f62b79f3f4f..4f2298a9b73b6 100644 --- a/lldb/test/API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py +++ b/lldb/test/API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py @@ -19,17 +19,17 @@ import socket -@skip class TestDAP_attachByPortNum(lldbdap_testcase.DAPTestCaseBase): default_timeout = 20 def set_and_hit_breakpoint(self, continueToExit=True): + self.dap_server.wait_for_stopped() + source = "main.c" - main_source_path = os.path.join(os.getcwd(), source) - breakpoint1_line = line_number(main_source_path, "// breakpoint 1") + breakpoint1_line = line_number(source, "// breakpoint 1") lines = [breakpoint1_line] # Set breakpoint in the thread function so we can step the threads - breakpoint_ids = self.set_source_breakpoints(main_source_path, lines) + breakpoint_ids = self.set_source_breakpoints(source, lines) self.assertEqual( len(breakpoint_ids), len(lines), "expect correct number of breakpoints" ) diff --git a/lldb/test/API/tools/lldb-dap/breakpoint/TestDAP_breakpointLocations.py b/lldb/test/API/tools/lldb-dap/breakpoint/TestDAP_breakpointLocations.py index 4a99cacc761a3..1058157e2c668 100644 --- a/lldb/test/API/tools/lldb-dap/breakpoint/TestDAP_breakpointLocations.py +++ b/lldb/test/API/tools/lldb-dap/breakpoint/TestDAP_breakpointLocations.py @@ -11,8 +11,7 @@ import lldbdap_testcase import os -# DAP tests are flakey, see https://github.com/llvm/llvm-project/issues/137660. -@skip + class TestDAP_breakpointLocations(lldbdap_testcase.DAPTestCaseBase): def setUp(self): lldbdap_testcase.DAPTestCaseBase.setUp(self) diff --git a/lldb/test/API/tools/lldb-dap/breakpoint/TestDAP_setBreakpoints.py b/lldb/test/API/tools/lldb-dap/breakpoint/TestDAP_setBreakpoints.py index 6c6681804f250..26df2573555df 100644 --- a/lldb/test/API/tools/lldb-dap/breakpoint/TestDAP_setBreakpoints.py +++ b/lldb/test/API/tools/lldb-dap/breakpoint/TestDAP_setBreakpoints.py @@ -11,8 +11,7 @@ import lldbdap_testcase import os -# DAP tests are flakey, see https://github.com/llvm/llvm-project/issues/137660. -@skip + class TestDAP_setBreakpoints(lldbdap_testcase.DAPTestCaseBase): def setUp(self): lldbdap_testcase.DAPTestCaseBase.setUp(self) diff --git a/lldb/test/API/tools/lldb-dap/commands/TestDAP_commands.py b/lldb/test/API/tools/lldb-dap/commands/TestDAP_commands.py index 8398eeab7bba2..25ecbb5cf106b 100644 --- a/lldb/test/API/tools/lldb-dap/commands/TestDAP_commands.py +++ b/lldb/test/API/tools/lldb-dap/commands/TestDAP_commands.py @@ -5,8 +5,7 @@ from lldbsuite.test import lldbtest, lldbutil from lldbsuite.test.decorators import * -# DAP tests are flakey, see https://github.com/llvm/llvm-project/issues/137660. -@skip + class TestDAP_commands(lldbdap_testcase.DAPTestCaseBase): def test_command_directive_quiet_on_success(self): program = self.getBuildArtifact("a.out") diff --git a/lldb/test/API/tools/lldb-dap/completions/TestDAP_completions.py b/lldb/test/API/tools/lldb-dap/completions/TestDAP_completions.py index 210e591bff426..455ac84168baf 100644 --- a/lldb/test/API/tools/lldb-dap/completions/TestDAP_completions.py +++ b/lldb/test/API/tools/lldb-dap/completions/TestDAP_completions.py @@ -44,9 +44,9 @@ def verify_completions(self, actual_list, expected_list, not_expected_list=[]): self.assertNotIn(not_expected_item, actual_list) - def setup_debugee(self): + def setup_debugee(self, stopOnEntry=False): program = self.getBuildArtifact("a.out") - self.build_and_launch(program) + self.build_and_launch(program, stopOnEntry=stopOnEntry) source = "main.cpp" breakpoint1_line = line_number(source, "// breakpoint 1") @@ -235,7 +235,7 @@ def test_auto_completions(self): """ Tests completion requests in "repl-mode=auto" """ - self.setup_debugee() + self.setup_debugee(stopOnEntry=True) res = self.dap_server.request_evaluate( "`lldb-dap repl-mode auto", context="repl" diff --git a/lldb/test/API/tools/lldb-dap/console/TestDAP_console.py b/lldb/test/API/tools/lldb-dap/console/TestDAP_console.py index b07c4f871d73b..65a1bc04c7cd7 100644 --- a/lldb/test/API/tools/lldb-dap/console/TestDAP_console.py +++ b/lldb/test/API/tools/lldb-dap/console/TestDAP_console.py @@ -167,7 +167,7 @@ def test_exit_status_message_ok(self): def test_diagnositcs(self): program = self.getBuildArtifact("a.out") - self.build_and_launch(program) + self.build_and_launch(program, stopOnEntry=True) core = self.getBuildArtifact("minidump.core") self.yaml2obj("minidump.yaml", core) diff --git a/lldb/test/API/tools/lldb-dap/disassemble/TestDAP_disassemble.py b/lldb/test/API/tools/lldb-dap/disassemble/TestDAP_disassemble.py index ebecb349ac177..9e8ef5b289f2e 100644 --- a/lldb/test/API/tools/lldb-dap/disassemble/TestDAP_disassemble.py +++ b/lldb/test/API/tools/lldb-dap/disassemble/TestDAP_disassemble.py @@ -10,8 +10,7 @@ import lldbdap_testcase import os -# DAP tests are flakey, see https://github.com/llvm/llvm-project/issues/137660. -@skip + class TestDAP_disassemble(lldbdap_testcase.DAPTestCaseBase): @skipIfWindows def test_disassemble(self): diff --git a/lldb/test/API/tools/lldb-dap/disconnect/TestDAP_disconnect.py b/lldb/test/API/tools/lldb-dap/disconnect/TestDAP_disconnect.py index 0cb792d662a80..09e3f62f0eead 100644 --- a/lldb/test/API/tools/lldb-dap/disconnect/TestDAP_disconnect.py +++ b/lldb/test/API/tools/lldb-dap/disconnect/TestDAP_disconnect.py @@ -31,7 +31,7 @@ def test_launch(self): created. """ program = self.getBuildArtifact("a.out") - self.build_and_launch(program, disconnectAutomatically=False) + self.build_and_launch(program, stopOnEntry=True, disconnectAutomatically=False) # We set a breakpoint right before the side effect file is created self.set_source_breakpoints( @@ -39,7 +39,11 @@ def test_launch(self): ) self.continue_to_next_stop() + # verify we haven't produced the side effect file yet + self.assertFalse(os.path.exists(program + ".side_effect")) + self.dap_server.request_disconnect() + # verify we didn't produce the side effect file time.sleep(1) self.assertFalse(os.path.exists(program + ".side_effect")) diff --git a/lldb/test/API/tools/lldb-dap/evaluate/TestDAP_evaluate.py b/lldb/test/API/tools/lldb-dap/evaluate/TestDAP_evaluate.py index d97fda730c46a..e2f843bd337a6 100644 --- a/lldb/test/API/tools/lldb-dap/evaluate/TestDAP_evaluate.py +++ b/lldb/test/API/tools/lldb-dap/evaluate/TestDAP_evaluate.py @@ -10,8 +10,7 @@ from lldbsuite.test.decorators import * from lldbsuite.test.lldbtest import * -# DAP tests are flakey, see https://github.com/llvm/llvm-project/issues/137660. -@skip + class TestDAP_evaluate(lldbdap_testcase.DAPTestCaseBase): def assertEvaluate(self, expression, regex): self.assertRegex( diff --git a/lldb/test/API/tools/lldb-dap/launch/TestDAP_launch.py b/lldb/test/API/tools/lldb-dap/launch/TestDAP_launch.py index 931456299e03e..604a41678500c 100644 --- a/lldb/test/API/tools/lldb-dap/launch/TestDAP_launch.py +++ b/lldb/test/API/tools/lldb-dap/launch/TestDAP_launch.py @@ -88,8 +88,8 @@ def test_stopOnEntry(self): """ program = self.getBuildArtifact("a.out") self.build_and_launch(program, stopOnEntry=True) - self.set_function_breakpoints(["main"]) - stopped_events = self.continue_to_next_stop() + + stopped_events = self.dap_server.wait_for_stopped() for stopped_event in stopped_events: if "body" in stopped_event: body = stopped_event["body"] diff --git a/lldb/test/API/tools/lldb-dap/memory/TestDAP_memory.py b/lldb/test/API/tools/lldb-dap/memory/TestDAP_memory.py index c71ba871b8a22..ea43fccf016a7 100644 --- a/lldb/test/API/tools/lldb-dap/memory/TestDAP_memory.py +++ b/lldb/test/API/tools/lldb-dap/memory/TestDAP_memory.py @@ -10,8 +10,7 @@ import lldbdap_testcase import os -# DAP tests are flakey, see https://github.com/llvm/llvm-project/issues/137660. -@skip + class TestDAP_memory(lldbdap_testcase.DAPTestCaseBase): def test_memory_refs_variables(self): """ diff --git a/lldb/test/API/tools/lldb-dap/progress/TestDAP_Progress.py b/lldb/test/API/tools/lldb-dap/progress/TestDAP_Progress.py index fee63655de0da..0f94b50c31fba 100755 --- a/lldb/test/API/tools/lldb-dap/progress/TestDAP_Progress.py +++ b/lldb/test/API/tools/lldb-dap/progress/TestDAP_Progress.py @@ -50,7 +50,7 @@ def verify_progress_events( @skipIfWindows def test(self): program = self.getBuildArtifact("a.out") - self.build_and_launch(program) + self.build_and_launch(program, stopOnEntry=True) progress_emitter = os.path.join(os.getcwd(), "Progress_emitter.py") self.dap_server.request_evaluate( f"`command script import {progress_emitter}", context="repl" diff --git a/lldb/test/API/tools/lldb-dap/repl-mode/TestDAP_repl_mode_detection.py b/lldb/test/API/tools/lldb-dap/repl-mode/TestDAP_repl_mode_detection.py index c6f59949d668e..81edcdf4bd0f9 100644 --- a/lldb/test/API/tools/lldb-dap/repl-mode/TestDAP_repl_mode_detection.py +++ b/lldb/test/API/tools/lldb-dap/repl-mode/TestDAP_repl_mode_detection.py @@ -20,7 +20,7 @@ def assertEvaluate(self, expression, regex): def test_completions(self): program = self.getBuildArtifact("a.out") - self.build_and_launch(program) + self.build_and_launch(program, stopOnEntry=True) source = "main.cpp" breakpoint1_line = line_number(source, "// breakpoint 1") diff --git a/lldb/test/API/tools/lldb-dap/restart/TestDAP_restart.py b/lldb/test/API/tools/lldb-dap/restart/TestDAP_restart.py index 36fa0bd40183f..5f95c7bfb1556 100644 --- a/lldb/test/API/tools/lldb-dap/restart/TestDAP_restart.py +++ b/lldb/test/API/tools/lldb-dap/restart/TestDAP_restart.py @@ -22,7 +22,6 @@ def test_basic_functionality(self): [bp_A, bp_B] = self.set_source_breakpoints("main.c", [line_A, line_B]) # Verify we hit A, then B. - self.dap_server.request_configurationDone() self.verify_breakpoint_hit([bp_A]) self.dap_server.request_continue() self.verify_breakpoint_hit([bp_B]) diff --git a/lldb/test/API/tools/lldb-dap/restart/TestDAP_restart_runInTerminal.py b/lldb/test/API/tools/lldb-dap/restart/TestDAP_restart_runInTerminal.py index a94c9860c1508..eed769a5a0cc6 100644 --- a/lldb/test/API/tools/lldb-dap/restart/TestDAP_restart_runInTerminal.py +++ b/lldb/test/API/tools/lldb-dap/restart/TestDAP_restart_runInTerminal.py @@ -74,7 +74,6 @@ def test_stopOnEntry(self): program = self.getBuildArtifact("a.out") self.build_and_launch(program, runInTerminal=True, stopOnEntry=True) [bp_main] = self.set_function_breakpoints(["main"]) - self.dap_server.request_configurationDone() # When using stopOnEntry, configurationDone doesn't result in a running # process, we should immediately get a stopped event instead. diff --git a/lldb/test/API/tools/lldb-dap/stop-hooks/TestDAP_stop_hooks.py b/lldb/test/API/tools/lldb-dap/stop-hooks/TestDAP_stop_hooks.py index 70c11a63a79f7..7e28a5af4331c 100644 --- a/lldb/test/API/tools/lldb-dap/stop-hooks/TestDAP_stop_hooks.py +++ b/lldb/test/API/tools/lldb-dap/stop-hooks/TestDAP_stop_hooks.py @@ -19,7 +19,7 @@ def test_stop_hooks_before_run(self): self.build_and_launch(program, stopOnEntry=True, preRunCommands=preRunCommands) # The first stop is on entry. - self.continue_to_next_stop() + self.dap_server.wait_for_stopped() breakpoint_ids = self.set_function_breakpoints(["main"]) # This request hangs if the race happens, because, in that case, the diff --git a/lldb/test/API/tools/lldb-dap/variables/TestDAP_variables.py b/lldb/test/API/tools/lldb-dap/variables/TestDAP_variables.py index 901c260d7d413..286bf3390a440 100644 --- a/lldb/test/API/tools/lldb-dap/variables/TestDAP_variables.py +++ b/lldb/test/API/tools/lldb-dap/variables/TestDAP_variables.py @@ -17,8 +17,7 @@ def make_buffer_verify_dict(start_idx, count, offset=0): verify_dict["[%i]" % (i)] = {"type": "int", "value": str(i + offset)} return verify_dict -# DAP tests are flakey, see https://github.com/llvm/llvm-project/issues/137660. -@skip + class TestDAP_variables(lldbdap_testcase.DAPTestCaseBase): def verify_values(self, verify_dict, actual, varref_dict=None, expression=None): if "equals" in verify_dict: _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits