blob: 282c5f4ab15a5ed647ffff609ec03c1e3ac00e04 [file] [log] [blame]
//===-- RequestHandler.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 "Handler/RequestHandler.h"
#include "DAP.h"
#include "EventHelper.h"
#include "Handler/ResponseHandler.h"
#include "JSONUtils.h"
#include "LLDBUtils.h"
#include "Protocol/ProtocolBase.h"
#include "Protocol/ProtocolRequests.h"
#include "RunInTerminal.h"
#include "lldb/API/SBDefines.h"
#include "lldb/API/SBEnvironment.h"
#include "llvm/Support/Error.h"
#include <mutex>
#if !defined(_WIN32)
#include <unistd.h>
#endif
using namespace lldb_dap::protocol;
namespace lldb_dap {
static std::vector<const char *>
MakeArgv(const llvm::ArrayRef<std::string> &strs) {
// Create and return an array of "const char *", one for each C string in
// "strs" and terminate the list with a NULL. This can be used for argument
// vectors (argv) or environment vectors (envp) like those passed to the
// "main" function in C programs.
std::vector<const char *> argv;
for (const auto &s : strs)
argv.push_back(s.c_str());
argv.push_back(nullptr);
return argv;
}
static uint32_t SetLaunchFlag(uint32_t flags, bool flag,
lldb::LaunchFlags mask) {
if (flag)
flags |= mask;
else
flags &= ~mask;
return flags;
}
// Both attach and launch take either a sourcePath or a sourceMap
// argument (or neither), from which we need to set the target.source-map.
void BaseRequestHandler::SetSourceMapFromArguments(
const llvm::json::Object &arguments) const {
const char *sourceMapHelp =
"source must be be an array of two-element arrays, "
"each containing a source and replacement path string.\n";
std::string sourceMapCommand;
llvm::raw_string_ostream strm(sourceMapCommand);
strm << "settings set target.source-map ";
const auto sourcePath = GetString(arguments, "sourcePath").value_or("");
// sourceMap is the new, more general form of sourcePath and overrides it.
constexpr llvm::StringRef sourceMapKey = "sourceMap";
if (const auto *sourceMapArray = arguments.getArray(sourceMapKey)) {
for (const auto &value : *sourceMapArray) {
const auto *mapping = value.getAsArray();
if (mapping == nullptr || mapping->size() != 2 ||
(*mapping)[0].kind() != llvm::json::Value::String ||
(*mapping)[1].kind() != llvm::json::Value::String) {
dap.SendOutput(OutputType::Console, llvm::StringRef(sourceMapHelp));
return;
}
const auto mapFrom = GetAsString((*mapping)[0]);
const auto mapTo = GetAsString((*mapping)[1]);
strm << "\"" << mapFrom << "\" \"" << mapTo << "\" ";
}
} else if (const auto *sourceMapObj = arguments.getObject(sourceMapKey)) {
for (const auto &[key, value] : *sourceMapObj) {
if (value.kind() == llvm::json::Value::String) {
strm << "\"" << key.str() << "\" \"" << GetAsString(value) << "\" ";
}
}
} else {
if (ObjectContainsKey(arguments, sourceMapKey)) {
dap.SendOutput(OutputType::Console, llvm::StringRef(sourceMapHelp));
return;
}
if (sourcePath.empty())
return;
// Do any source remapping needed before we create our targets
strm << "\".\" \"" << sourcePath << "\"";
}
if (!sourceMapCommand.empty()) {
dap.RunLLDBCommands("Setting source map:", {sourceMapCommand});
}
}
static llvm::Error
RunInTerminal(DAP &dap, const protocol::LaunchRequestArguments &arguments) {
if (!dap.clientFeatures.contains(
protocol::eClientFeatureRunInTerminalRequest))
return llvm::make_error<DAPError>("Cannot use runInTerminal, feature is "
"not supported by the connected client");
if (!arguments.configuration.program ||
arguments.configuration.program->empty())
return llvm::make_error<DAPError>(
"program must be set to when using runInTerminal");
dap.is_attach = true;
lldb::SBAttachInfo attach_info;
llvm::Expected<std::shared_ptr<FifoFile>> comm_file_or_err =
CreateRunInTerminalCommFile();
if (!comm_file_or_err)
return comm_file_or_err.takeError();
FifoFile &comm_file = *comm_file_or_err.get();
RunInTerminalDebugAdapterCommChannel comm_channel(comm_file.m_path);
lldb::pid_t debugger_pid = LLDB_INVALID_PROCESS_ID;
#if !defined(_WIN32)
debugger_pid = getpid();
#endif
llvm::json::Object reverse_request = CreateRunInTerminalReverseRequest(
*arguments.configuration.program, arguments.args, arguments.env,
arguments.cwd.value_or(""), comm_file.m_path, debugger_pid);
dap.SendReverseRequest<LogFailureResponseHandler>("runInTerminal",
std::move(reverse_request));
if (llvm::Expected<lldb::pid_t> pid = comm_channel.GetLauncherPid())
attach_info.SetProcessID(*pid);
else
return pid.takeError();
std::optional<ScopeSyncMode> scope_sync_mode(dap.debugger);
lldb::SBError error;
dap.target.Attach(attach_info, error);
if (error.Fail())
return llvm::createStringError(llvm::inconvertibleErrorCode(),
"Failed to attach to the target process. %s",
comm_channel.GetLauncherError().c_str());
// This will notify the runInTerminal launcher that we attached.
// We have to make this async, as the function won't return until the launcher
// resumes and reads the data.
std::future<lldb::SBError> did_attach_message_success =
comm_channel.NotifyDidAttach();
// We just attached to the runInTerminal launcher, which was waiting to be
// attached. We now resume it, so it can receive the didAttach notification
// and then perform the exec. Upon continuing, the debugger will stop the
// process right in the middle of the exec. To the user, what we are doing is
// transparent, as they will only be able to see the process since the exec,
// completely unaware of the preparatory work.
dap.target.GetProcess().Continue();
// Now that the actual target is just starting (i.e. exec was just invoked),
// 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
// the async didAttach notification should have an error message, so we
// return it. Otherwise, everything was a success.
did_attach_message_success.wait();
error = did_attach_message_success.get();
if (error.Success())
return llvm::Error::success();
return llvm::createStringError(llvm::inconvertibleErrorCode(),
error.GetCString());
}
void BaseRequestHandler::Run(const Request &request) {
// If this request was cancelled, send a cancelled response.
if (dap.IsCancelled(request)) {
Response cancelled{/*request_seq=*/request.seq,
/*command=*/request.command,
/*success=*/false,
/*message=*/eResponseMessageCancelled,
/*body=*/std::nullopt};
dap.Send(cancelled);
return;
}
lldb::SBMutex lock = dap.GetAPIMutex();
std::lock_guard<lldb::SBMutex> guard(lock);
// FIXME: After all the requests have migrated from LegacyRequestHandler >
// RequestHandler<> we should be able to move this into
// RequestHandler<>::operator().
operator()(request);
// FIXME: After all the requests have migrated from LegacyRequestHandler >
// RequestHandler<> we should be able to check `debugger.InterruptRequest` and
// mark the response as cancelled.
}
llvm::Error BaseRequestHandler::LaunchProcess(
const protocol::LaunchRequestArguments &arguments) const {
auto launchCommands = arguments.launchCommands;
// Instantiate a launch info instance for the target.
auto launch_info = dap.target.GetLaunchInfo();
// Grab the current working directory if there is one and set it in the
// launch info.
const auto cwd = arguments.cwd.value_or("");
if (!cwd.empty())
launch_info.SetWorkingDirectory(cwd.data());
// Extract any extra arguments and append them to our program arguments for
// when we launch
if (!arguments.args.empty())
launch_info.SetArguments(MakeArgv(arguments.args).data(), true);
// Pass any environment variables along that the user specified.
if (!arguments.env.empty()) {
lldb::SBEnvironment env;
for (const auto &kv : arguments.env)
env.Set(kv.first().data(), kv.second.c_str(), true);
launch_info.SetEnvironment(env, true);
}
launch_info.SetDetachOnError(arguments.detachOnError);
launch_info.SetShellExpandArguments(arguments.shellExpandArguments);
auto flags = launch_info.GetLaunchFlags();
flags =
SetLaunchFlag(flags, arguments.disableASLR, lldb::eLaunchFlagDisableASLR);
flags = SetLaunchFlag(flags, arguments.disableSTDIO,
lldb::eLaunchFlagDisableSTDIO);
launch_info.SetLaunchFlags(flags | lldb::eLaunchFlagDebug |
lldb::eLaunchFlagStopAtEntry);
{
// 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 (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);
return llvm::Error::success();
}
void BaseRequestHandler::PrintWelcomeMessage() const {
#ifdef LLDB_DAP_WELCOME_MESSAGE
dap.SendOutput(OutputType::Console, LLDB_DAP_WELCOME_MESSAGE);
#endif
}
bool BaseRequestHandler::HasInstructionGranularity(
const llvm::json::Object &arguments) const {
if (std::optional<llvm::StringRef> value = arguments.getString("granularity"))
return value == "instruction";
return false;
}
} // namespace lldb_dap