blob: b06a43d7421c19208208922e8a156b13cb1a0db1 [file] [log] [blame] [edit]
//===-- 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 "llvm/Support/JSON.h"
#include "llvm/Support/raw_ostream.h"
#include <mutex>
#if !defined(_WIN32)
#include <unistd.h>
#endif
#ifndef LLDB_DAP_README_URL
#define LLDB_DAP_README_URL \
"https://lldb.llvm.org/use/lldbdap.html#debug-console"
#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;
}
static void
SetupIORedirection(const std::vector<std::optional<std::string>> &stdio,
lldb::SBLaunchInfo &launch_info) {
size_t n = std::max(stdio.size(), static_cast<size_t>(3));
for (size_t i = 0; i < n; i++) {
std::optional<std::string> path;
if (stdio.size() <= i)
path = stdio.back();
else
path = stdio[i];
if (!path)
continue;
switch (i) {
case 0:
launch_info.AddOpenFileAction(i, path->c_str(), true, false);
break;
case 1:
case 2:
launch_info.AddOpenFileAction(i, path->c_str(), false, true);
break;
default:
launch_info.AddOpenFileAction(i, path->c_str(), true, true);
break;
}
}
}
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.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, comm_file.m_path, debugger_pid, arguments.stdio,
arguments.console == protocol::eConsoleExternalTerminal);
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,
};
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 {
const std::vector<std::string> &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.
if (!arguments.cwd.empty())
launch_info.SetWorkingDirectory(arguments.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);
}
if (!arguments.stdio.empty() && !arguments.disableSTDIO)
SetupIORedirection(arguments.stdio, launch_info);
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.console != protocol::eConsoleInternal) {
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 ToError(error);
} 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.configuration.timeout);
if (error.Fail())
return ToError(error);
return llvm::Error::success();
}
void BaseRequestHandler::PrintWelcomeMessage() const {
std::string message;
llvm::raw_string_ostream OS(message);
#ifdef LLDB_DAP_WELCOME_MESSAGE
dap.SendOutput(eOutputCategoryConsole, LLDB_DAP_WELCOME_MESSAGE);
#endif
// Trying to provide a brief but helpful welcome message for users to better
// understand how the debug console repl works.
OS << "To get started with the debug console try ";
switch (dap.repl_mode) {
case ReplMode::Auto:
OS << "\"<variable>\", \"<lldb-cmd>\" or \"help [<lldb-cmd>]\"\r\n";
break;
case ReplMode::Command:
OS << "\"<lldb-cmd>\" or \"help [<lldb-cmd>]\".\r\n";
break;
case ReplMode::Variable:
OS << "\"<variable>\" or \"" << dap.configuration.commandEscapePrefix
<< "help [<lldb-cmd>]\".\r\n";
break;
}
OS << "For more information visit " LLDB_DAP_README_URL ".\r\n";
dap.SendOutput(OutputType::Console, message);
}
void BaseRequestHandler::PrintIntroductionMessage() const {
std::string msg;
llvm::raw_string_ostream os(msg);
if (dap.target && dap.target.GetExecutable()) {
std::string path = GetSBFileSpecPath(dap.target.GetExecutable());
os << llvm::formatv("Executable binary set to '{0}' ({1}).\r\n", path,
dap.target.GetTriple());
}
if (dap.target.GetProcess()) {
os << llvm::formatv("Attached to process {0}.\r\n",
dap.target.GetProcess().GetProcessID());
}
dap.SendOutput(OutputType::Console, msg);
}
bool BaseRequestHandler::HasInstructionGranularity(
const llvm::json::Object &arguments) const {
if (std::optional<llvm::StringRef> value = arguments.getString("granularity"))
return value == "instruction";
return false;
}
void BaseRequestHandler::BuildErrorResponse(
llvm::Error err, protocol::Response &response) const {
// Handle the ErrorSuccess case.
if (!err) {
response.success = true;
return;
}
response.success = false;
llvm::handleAllErrors(
std::move(err),
[&](const NotStoppedError &err) {
response.message = lldb_dap::protocol::eResponseMessageNotStopped;
},
[&](const DAPError &err) {
protocol::ErrorMessage error_message;
error_message.sendTelemetry = false;
error_message.format = err.getMessage();
error_message.showUser = err.getShowUser();
error_message.id = err.convertToErrorCode().value();
error_message.url = err.getURL();
error_message.urlLabel = err.getURLLabel();
protocol::ErrorResponseBody body;
body.error = error_message;
response.body = body;
},
[&](const llvm::ErrorInfoBase &err) {
protocol::ErrorMessage error_message;
error_message.showUser = true;
error_message.sendTelemetry = false;
error_message.format = err.message();
error_message.id = err.convertToErrorCode().value();
protocol::ErrorResponseBody body;
body.error = error_message;
response.body = body;
});
}
void BaseRequestHandler::SendError(llvm::Error err,
protocol::Response &response) const {
BuildErrorResponse(std::move(err), response);
Send(response);
}
void BaseRequestHandler::SendSuccess(
protocol::Response &response, std::optional<llvm::json::Value> body) const {
response.success = true;
if (body)
response.body = std::move(*body);
Send(response);
}
void BaseRequestHandler::Send(protocol::Response &response) const {
// Mark the request as 'cancelled' if the debugger was interrupted while
// evaluating this handler.
if (dap.debugger.InterruptRequested()) {
response.success = false;
response.message = protocol::eResponseMessageCancelled;
response.body = std::nullopt;
}
dap.Send(response);
}
} // namespace lldb_dap