272 lines · cpp
1//===-- RequestHandler.cpp ------------------------------------------------===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8 9#include "Handler/RequestHandler.h"10#include "DAP.h"11#include "EventHelper.h"12#include "Handler/ResponseHandler.h"13#include "JSONUtils.h"14#include "LLDBUtils.h"15#include "Protocol/ProtocolBase.h"16#include "Protocol/ProtocolRequests.h"17#include "RunInTerminal.h"18#include "lldb/API/SBDefines.h"19#include "lldb/API/SBEnvironment.h"20#include "llvm/Support/Error.h"21#include <mutex>22 23#if !defined(_WIN32)24#include <unistd.h>25#endif26 27using namespace lldb_dap::protocol;28 29namespace lldb_dap {30 31static std::vector<const char *>32MakeArgv(const llvm::ArrayRef<std::string> &strs) {33 // Create and return an array of "const char *", one for each C string in34 // "strs" and terminate the list with a NULL. This can be used for argument35 // vectors (argv) or environment vectors (envp) like those passed to the36 // "main" function in C programs.37 std::vector<const char *> argv;38 for (const auto &s : strs)39 argv.push_back(s.c_str());40 argv.push_back(nullptr);41 return argv;42}43 44static uint32_t SetLaunchFlag(uint32_t flags, bool flag,45 lldb::LaunchFlags mask) {46 if (flag)47 flags |= mask;48 else49 flags &= ~mask;50 51 return flags;52}53 54static void55SetupIORedirection(const std::vector<std::optional<std::string>> &stdio,56 lldb::SBLaunchInfo &launch_info) {57 size_t n = std::max(stdio.size(), static_cast<size_t>(3));58 for (size_t i = 0; i < n; i++) {59 std::optional<std::string> path;60 if (stdio.size() <= i)61 path = stdio.back();62 else63 path = stdio[i];64 if (!path)65 continue;66 switch (i) {67 case 0:68 launch_info.AddOpenFileAction(i, path->c_str(), true, false);69 break;70 case 1:71 case 2:72 launch_info.AddOpenFileAction(i, path->c_str(), false, true);73 break;74 default:75 launch_info.AddOpenFileAction(i, path->c_str(), true, true);76 break;77 }78 }79}80 81static llvm::Error82RunInTerminal(DAP &dap, const protocol::LaunchRequestArguments &arguments) {83 if (!dap.clientFeatures.contains(84 protocol::eClientFeatureRunInTerminalRequest))85 return llvm::make_error<DAPError>("Cannot use runInTerminal, feature is "86 "not supported by the connected client");87 88 if (arguments.configuration.program.empty())89 return llvm::make_error<DAPError>(90 "program must be set to when using runInTerminal");91 92 dap.is_attach = true;93 lldb::SBAttachInfo attach_info;94 95 llvm::Expected<std::shared_ptr<FifoFile>> comm_file_or_err =96 CreateRunInTerminalCommFile();97 if (!comm_file_or_err)98 return comm_file_or_err.takeError();99 FifoFile &comm_file = *comm_file_or_err.get();100 101 RunInTerminalDebugAdapterCommChannel comm_channel(comm_file.m_path);102 103 lldb::pid_t debugger_pid = LLDB_INVALID_PROCESS_ID;104#if !defined(_WIN32)105 debugger_pid = getpid();106#endif107 108 llvm::json::Object reverse_request = CreateRunInTerminalReverseRequest(109 arguments.configuration.program, arguments.args, arguments.env,110 arguments.cwd, comm_file.m_path, debugger_pid, arguments.stdio,111 arguments.console == protocol::eConsoleExternalTerminal);112 dap.SendReverseRequest<LogFailureResponseHandler>("runInTerminal",113 std::move(reverse_request));114 115 if (llvm::Expected<lldb::pid_t> pid = comm_channel.GetLauncherPid())116 attach_info.SetProcessID(*pid);117 else118 return pid.takeError();119 120 std::optional<ScopeSyncMode> scope_sync_mode(dap.debugger);121 lldb::SBError error;122 dap.target.Attach(attach_info, error);123 124 if (error.Fail())125 return llvm::createStringError(llvm::inconvertibleErrorCode(),126 "Failed to attach to the target process. %s",127 comm_channel.GetLauncherError().c_str());128 // This will notify the runInTerminal launcher that we attached.129 // We have to make this async, as the function won't return until the launcher130 // resumes and reads the data.131 std::future<lldb::SBError> did_attach_message_success =132 comm_channel.NotifyDidAttach();133 134 // We just attached to the runInTerminal launcher, which was waiting to be135 // attached. We now resume it, so it can receive the didAttach notification136 // and then perform the exec. Upon continuing, the debugger will stop the137 // process right in the middle of the exec. To the user, what we are doing is138 // transparent, as they will only be able to see the process since the exec,139 // completely unaware of the preparatory work.140 dap.target.GetProcess().Continue();141 142 // Now that the actual target is just starting (i.e. exec was just invoked),143 // we return the debugger to its sync state.144 scope_sync_mode.reset();145 146 // If sending the notification failed, the launcher should be dead by now and147 // the async didAttach notification should have an error message, so we148 // return it. Otherwise, everything was a success.149 did_attach_message_success.wait();150 error = did_attach_message_success.get();151 if (error.Success())152 return llvm::Error::success();153 return llvm::createStringError(llvm::inconvertibleErrorCode(),154 error.GetCString());155}156 157void BaseRequestHandler::Run(const Request &request) {158 // If this request was cancelled, send a cancelled response.159 if (dap.IsCancelled(request)) {160 Response cancelled{161 /*request_seq=*/request.seq,162 /*command=*/request.command,163 /*success=*/false,164 /*message=*/eResponseMessageCancelled,165 };166 dap.Send(cancelled);167 return;168 }169 170 lldb::SBMutex lock = dap.GetAPIMutex();171 std::lock_guard<lldb::SBMutex> guard(lock);172 173 // FIXME: After all the requests have migrated from LegacyRequestHandler >174 // RequestHandler<> we should be able to move this into175 // RequestHandler<>::operator().176 operator()(request);177 178 // FIXME: After all the requests have migrated from LegacyRequestHandler >179 // RequestHandler<> we should be able to check `debugger.InterruptRequest` and180 // mark the response as cancelled.181}182 183llvm::Error BaseRequestHandler::LaunchProcess(184 const protocol::LaunchRequestArguments &arguments) const {185 const std::vector<std::string> &launchCommands = arguments.launchCommands;186 187 // Instantiate a launch info instance for the target.188 auto launch_info = dap.target.GetLaunchInfo();189 190 // Grab the current working directory if there is one and set it in the191 // launch info.192 if (!arguments.cwd.empty())193 launch_info.SetWorkingDirectory(arguments.cwd.data());194 195 // Extract any extra arguments and append them to our program arguments for196 // when we launch197 if (!arguments.args.empty())198 launch_info.SetArguments(MakeArgv(arguments.args).data(), true);199 200 // Pass any environment variables along that the user specified.201 if (!arguments.env.empty()) {202 lldb::SBEnvironment env;203 for (const auto &kv : arguments.env)204 env.Set(kv.first().data(), kv.second.c_str(), true);205 launch_info.SetEnvironment(env, true);206 }207 208 if (!arguments.stdio.empty() && !arguments.disableSTDIO)209 SetupIORedirection(arguments.stdio, launch_info);210 211 launch_info.SetDetachOnError(arguments.detachOnError);212 launch_info.SetShellExpandArguments(arguments.shellExpandArguments);213 214 auto flags = launch_info.GetLaunchFlags();215 flags =216 SetLaunchFlag(flags, arguments.disableASLR, lldb::eLaunchFlagDisableASLR);217 flags = SetLaunchFlag(flags, arguments.disableSTDIO,218 lldb::eLaunchFlagDisableSTDIO);219 launch_info.SetLaunchFlags(flags | lldb::eLaunchFlagDebug |220 lldb::eLaunchFlagStopAtEntry);221 222 {223 // Perform the launch in synchronous mode so that we don't have to worry224 // about process state changes during the launch.225 ScopeSyncMode scope_sync_mode(dap.debugger);226 227 if (arguments.console != protocol::eConsoleInternal) {228 if (llvm::Error err = RunInTerminal(dap, arguments))229 return err;230 } else if (launchCommands.empty()) {231 lldb::SBError error;232 dap.target.Launch(launch_info, error);233 if (error.Fail())234 return ToError(error);235 } else {236 // Set the launch info so that run commands can access the configured237 // launch details.238 dap.target.SetLaunchInfo(launch_info);239 if (llvm::Error err = dap.RunLaunchCommands(launchCommands))240 return err;241 242 // The custom commands might have created a new target so we should use243 // the selected target after these commands are run.244 dap.target = dap.debugger.GetSelectedTarget();245 }246 }247 248 // Make sure the process is launched and stopped at the entry point before249 // proceeding.250 lldb::SBError error =251 dap.WaitForProcessToStop(arguments.configuration.timeout);252 if (error.Fail())253 return ToError(error);254 255 return llvm::Error::success();256}257 258void BaseRequestHandler::PrintWelcomeMessage() const {259#ifdef LLDB_DAP_WELCOME_MESSAGE260 dap.SendOutput(OutputType::Console, LLDB_DAP_WELCOME_MESSAGE);261#endif262}263 264bool BaseRequestHandler::HasInstructionGranularity(265 const llvm::json::Object &arguments) const {266 if (std::optional<llvm::StringRef> value = arguments.getString("granularity"))267 return value == "instruction";268 return false;269}270 271} // namespace lldb_dap272