
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 synchronous, including when we have attach or launch commands. This removes the need to synchronize between the request and event thread. Background: https://discourse.llvm.org/t/reliability-of-the-lldb-dap-tests/86125
300 lines
11 KiB
C++
300 lines
11 KiB
C++
//===-- 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
|