//===-- InitializeRequestHandler.cpp --------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "DAP.h" #include "EventHelper.h" #include "JSONUtils.h" #include "Protocol/ProtocolRequests.h" #include "RequestHandler.h" #include "lldb/API/SBEvent.h" #include "lldb/API/SBListener.h" #include "lldb/API/SBStream.h" using namespace lldb; using namespace lldb_dap::protocol; namespace lldb_dap { static std::string GetStringFromStructuredData(lldb::SBStructuredData &data, const char *key) { lldb::SBStructuredData keyValue = data.GetValueForKey(key); if (!keyValue) return std::string(); const size_t length = keyValue.GetStringValue(nullptr, 0); if (length == 0) return std::string(); std::string str(length + 1, 0); keyValue.GetStringValue(&str[0], length + 1); return str; } static uint64_t GetUintFromStructuredData(lldb::SBStructuredData &data, const char *key) { lldb::SBStructuredData keyValue = data.GetValueForKey(key); if (!keyValue.IsValid()) return 0; return keyValue.GetUnsignedIntegerValue(); } void ProgressEventThreadFunction(DAP &dap) { lldb::SBListener listener("lldb-dap.progress.listener"); dap.debugger.GetBroadcaster().AddListener( listener, lldb::SBDebugger::eBroadcastBitProgress | lldb::SBDebugger::eBroadcastBitExternalProgress); dap.broadcaster.AddListener(listener, eBroadcastBitStopProgressThread); lldb::SBEvent event; bool done = false; while (!done) { if (listener.WaitForEvent(1, event)) { const auto event_mask = event.GetType(); if (event.BroadcasterMatchesRef(dap.broadcaster)) { if (event_mask & eBroadcastBitStopProgressThread) { done = true; } } else { lldb::SBStructuredData data = lldb::SBDebugger::GetProgressDataFromEvent(event); const uint64_t progress_id = GetUintFromStructuredData(data, "progress_id"); const uint64_t completed = GetUintFromStructuredData(data, "completed"); const uint64_t total = GetUintFromStructuredData(data, "total"); const std::string details = GetStringFromStructuredData(data, "details"); if (completed == 0) { if (total == UINT64_MAX) { // This progress is non deterministic and won't get updated until it // is completed. Send the "message" which will be the combined title // and detail. The only other progress event for thus // non-deterministic progress will be the completed event So there // will be no need to update the detail. const std::string message = GetStringFromStructuredData(data, "message"); dap.SendProgressEvent(progress_id, message.c_str(), completed, total); } else { // This progress is deterministic and will receive updates, // on the progress creation event VSCode will save the message in // the create packet and use that as the title, so we send just the // title in the progressCreate packet followed immediately by a // detail packet, if there is any detail. const std::string title = GetStringFromStructuredData(data, "title"); dap.SendProgressEvent(progress_id, title.c_str(), completed, total); if (!details.empty()) dap.SendProgressEvent(progress_id, details.c_str(), completed, total); } } else { // This progress event is either the end of the progress dialog, or an // update with possible detail. The "detail" string we send to VS Code // will be appended to the progress dialog's initial text from when it // was created. dap.SendProgressEvent(progress_id, details.c_str(), completed, total); } } } } } // All events from the debugger, target, process, thread and frames are // received in this function that runs in its own thread. We are using a // "FILE *" to output packets back to VS Code and they have mutexes in them // them prevent multiple threads from writing simultaneously so no locking // is required. static void EventThreadFunction(DAP &dap) { llvm::set_thread_name(dap.transport.GetClientName() + ".event_handler"); lldb::SBEvent event; lldb::SBListener listener = dap.debugger.GetListener(); dap.broadcaster.AddListener(listener, eBroadcastBitStopEventThread); bool done = false; while (!done) { if (listener.WaitForEvent(1, event)) { const auto event_mask = event.GetType(); if (lldb::SBProcess::EventIsProcessEvent(event)) { lldb::SBProcess process = lldb::SBProcess::GetProcessFromEvent(event); if (event_mask & lldb::SBProcess::eBroadcastBitStateChanged) { auto state = lldb::SBProcess::GetStateFromEvent(event); switch (state) { 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::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); } } break; case lldb::eStateRunning: dap.WillContinue(); SendContinuedEvent(dap); break; case lldb::eStateExited: lldb::SBStream stream; process.GetStatus(stream); dap.SendOutput(OutputType::Console, stream.GetData()); // When restarting, we can get an "exited" event for the process we // just killed with the old PID, or even with no PID. In that case // we don't have to terminate the session. if (process.GetProcessID() == LLDB_INVALID_PROCESS_ID || process.GetProcessID() == dap.restarting_process_id) { dap.restarting_process_id = LLDB_INVALID_PROCESS_ID; } else { // Run any exit LLDB commands the user specified in the // launch.json dap.RunExitCommands(); SendProcessExitedEvent(dap, process); dap.SendTerminatedEvent(); done = true; } break; } } else if ((event_mask & lldb::SBProcess::eBroadcastBitSTDOUT) || (event_mask & lldb::SBProcess::eBroadcastBitSTDERR)) { SendStdOutStdErr(dap, process); } } else if (lldb::SBBreakpoint::EventIsBreakpointEvent(event)) { if (event_mask & lldb::SBTarget::eBroadcastBitBreakpointChanged) { auto event_type = lldb::SBBreakpoint::GetBreakpointEventTypeFromEvent(event); auto bp = Breakpoint( dap, lldb::SBBreakpoint::GetBreakpointFromEvent(event)); // If the breakpoint was set through DAP, it will have the // BreakpointBase::kDAPBreakpointLabel. Regardless of whether // locations were added, removed, or resolved, the breakpoint isn't // going away and the reason is always "changed". if ((event_type & lldb::eBreakpointEventTypeLocationsAdded || event_type & lldb::eBreakpointEventTypeLocationsRemoved || event_type & lldb::eBreakpointEventTypeLocationsResolved) && bp.MatchesName(BreakpointBase::kDAPBreakpointLabel)) { // As the DAP client already knows the path of this breakpoint, we // don't need to send it back as part of the "changed" event. This // avoids sending paths that should be source mapped. Note that // CreateBreakpoint doesn't apply source mapping and certain // implementation ignore the source part of this event anyway. llvm::json::Value source_bp = CreateBreakpoint(&bp); source_bp.getAsObject()->erase("source"); llvm::json::Object body; body.try_emplace("breakpoint", source_bp); body.try_emplace("reason", "changed"); llvm::json::Object bp_event = CreateEventObject("breakpoint"); bp_event.try_emplace("body", std::move(body)); dap.SendJSON(llvm::json::Value(std::move(bp_event))); } } } else if (event.BroadcasterMatchesRef(dap.broadcaster)) { if (event_mask & eBroadcastBitStopEventThread) { done = true; } } } } } /// Initialize request; value of command field is 'initialize'. llvm::Expected InitializeRequestHandler::Run( const InitializeRequestArguments &arguments) const { dap.clientFeatures = arguments.supportedFeatures; // Do not source init files until in/out/err are configured. dap.debugger = lldb::SBDebugger::Create(false); dap.debugger.SetInputFile(dap.in); llvm::Expected out_fd = dap.out.GetWriteFileDescriptor(); if (!out_fd) return out_fd.takeError(); dap.debugger.SetOutputFile(lldb::SBFile(*out_fd, "w", false)); llvm::Expected err_fd = dap.err.GetWriteFileDescriptor(); if (!err_fd) return err_fd.takeError(); dap.debugger.SetErrorFile(lldb::SBFile(*err_fd, "w", false)); auto interp = dap.debugger.GetCommandInterpreter(); // The sourceInitFile option is not part of the DAP specification. It is an // extension used by the test suite to prevent sourcing `.lldbinit` and // changing its behavior. if (arguments.lldbExtSourceInitFile.value_or(true)) { dap.debugger.SkipLLDBInitFiles(false); dap.debugger.SkipAppInitFiles(false); lldb::SBCommandReturnObject init; interp.SourceInitFileInGlobalDirectory(init); interp.SourceInitFileInHomeDirectory(init); } if (llvm::Error err = dap.RunPreInitCommands()) return err; dap.PopulateExceptionBreakpoints(); auto cmd = dap.debugger.GetCommandInterpreter().AddMultiwordCommand( "lldb-dap", "Commands for managing lldb-dap."); if (arguments.supportedFeatures.contains( eClientFeatureStartDebuggingRequest)) { cmd.AddCommand( "start-debugging", new StartDebuggingRequestHandler(dap), "Sends a startDebugging request from the debug adapter to the client " "to start a child debug session of the same type as the caller."); } cmd.AddCommand( "repl-mode", new ReplModeRequestHandler(dap), "Get or set the repl behavior of lldb-dap evaluation requests."); cmd.AddCommand("send-event", new SendEventRequestHandler(dap), "Sends an DAP event to the client."); if (arguments.supportedFeatures.contains(eClientFeatureProgressReporting)) dap.progress_event_thread = std::thread(ProgressEventThreadFunction, std::ref(dap)); // Start our event thread so we can receive events from the debugger, target, // process and more. dap.event_thread = std::thread(EventThreadFunction, std::ref(dap)); return dap.GetCapabilities(); } } // namespace lldb_dap