Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions lldb/tools/lldb-dap/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ add_lldb_tool(lldb-dap
SourceBreakpoint.cpp
Watchpoint.cpp

Request/Request.cpp
Request/AttachRequest.cpp

LINK_LIBS
liblldb
lldbHost
Expand All @@ -46,6 +49,8 @@ add_lldb_tool(lldb-dap
Support
)

target_include_directories(lldb-dap PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})

if(LLDB_DAP_WELCOME_MESSAGE)
target_compile_definitions(lldb-dap
PRIVATE
Expand Down
23 changes: 16 additions & 7 deletions lldb/tools/lldb-dap/DAP.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -744,16 +744,25 @@ bool DAP::HandleObject(const llvm::json::Object &object) {
const auto packet_type = GetString(object, "type");
if (packet_type == "request") {
const auto command = GetString(object, "command");

// Try the new request handler first.
auto new_handler_pos = new_request_handlers.find(command);
if (new_handler_pos != new_request_handlers.end()) {
(*new_handler_pos->second)(object);
return true; // Success
}

// FIXME: Remove request_handlers once everything has been migrated.
auto handler_pos = request_handlers.find(command);
if (handler_pos == request_handlers.end()) {
if (log)
*log << "error: unhandled command \"" << command.data() << "\""
<< std::endl;
return false; // Fail
if (handler_pos != request_handlers.end()) {
handler_pos->second(*this, object);
return true; // Success
}

handler_pos->second(*this, object);
return true; // Success
if (log)
*log << "error: unhandled command \"" << command.data() << "\""
<< std::endl;
return false; // Fail
}

if (packet_type == "response") {
Expand Down
8 changes: 8 additions & 0 deletions lldb/tools/lldb-dap/DAP.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include "InstructionBreakpoint.h"
#include "OutputRedirector.h"
#include "ProgressEvent.h"
#include "Request/Request.h"
#include "SourceBreakpoint.h"
#include "lldb/API/SBBroadcaster.h"
#include "lldb/API/SBCommandInterpreter.h"
Expand All @@ -37,6 +38,7 @@
#include "llvm/Support/JSON.h"
#include "llvm/Support/Threading.h"
#include <map>
#include <memory>
#include <mutex>
#include <optional>
#include <thread>
Expand Down Expand Up @@ -184,6 +186,7 @@ struct DAP {
lldb::pid_t restarting_process_id;
bool configuration_done_sent;
std::map<std::string, RequestCallback, std::less<>> request_handlers;
llvm::StringMap<std::unique_ptr<Request>> new_request_handlers;
bool waiting_for_run_in_terminal;
ProgressEventReporter progress_event_reporter;
// Keep track of the last stop thread index IDs as threads won't go away
Expand Down Expand Up @@ -330,6 +333,11 @@ struct DAP {
/// IDE.
void RegisterRequestCallback(std::string request, RequestCallback callback);

/// Registers a request handler.
template <typename Request> void RegisterRequest() {
new_request_handlers[Request::getName()] = std::make_unique<Request>(*this);
}

/// Debuggee will continue from stopped state.
void WillContinue() { variables.Clear(); }

Expand Down
211 changes: 211 additions & 0 deletions lldb/tools/lldb-dap/Request/AttachRequest.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
//===-- AttachRequest.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 "JSONUtils.h"
#include "Request.h"
#include "lldb/API/SBListener.h"
#include "llvm/Support/FileSystem.h"

namespace lldb_dap {
/// Prints a welcome message on the editor if the preprocessor variable
/// LLDB_DAP_WELCOME_MESSAGE is defined.
static void PrintWelcomeMessage(DAP &dap) {
#ifdef LLDB_DAP_WELCOME_MESSAGE
dap.SendOutput(OutputType::Console, LLDB_DAP_WELCOME_MESSAGE);
#endif
}

// "AttachRequest": {
// "allOf": [ { "$ref": "#/definitions/Request" }, {
// "type": "object",
// "description": "Attach request; value of command field is 'attach'.",
// "properties": {
// "command": {
// "type": "string",
// "enum": [ "attach" ]
// },
// "arguments": {
// "$ref": "#/definitions/AttachRequestArguments"
// }
// },
// "required": [ "command", "arguments" ]
// }]
// },
// "AttachRequestArguments": {
// "type": "object",
// "description": "Arguments for 'attach' request.\nThe attach request has no
// standardized attributes."
// },
// "AttachResponse": {
// "allOf": [ { "$ref": "#/definitions/Response" }, {
// "type": "object",
// "description": "Response to 'attach' request. This is just an
// acknowledgement, so no body field is required."
// }]
// }

void AttachRequest::operator()(const llvm::json::Object &request) {
dap.is_attach = true;
dap.last_launch_or_attach_request = request;
llvm::json::Object response;
lldb::SBError error;
FillResponse(request, response);
lldb::SBAttachInfo attach_info;
const int invalid_port = 0;
const auto *arguments = request.getObject("arguments");
const lldb::pid_t pid =
GetUnsigned(arguments, "pid", LLDB_INVALID_PROCESS_ID);
const auto gdb_remote_port =
GetUnsigned(arguments, "gdb-remote-port", invalid_port);
const auto gdb_remote_hostname =
GetString(arguments, "gdb-remote-hostname", "localhost");
if (pid != LLDB_INVALID_PROCESS_ID)
attach_info.SetProcessID(pid);
const auto wait_for = GetBoolean(arguments, "waitFor", false);
attach_info.SetWaitForLaunch(wait_for, false /*async*/);
dap.init_commands = GetStrings(arguments, "initCommands");
dap.pre_run_commands = GetStrings(arguments, "preRunCommands");
dap.stop_commands = GetStrings(arguments, "stopCommands");
dap.exit_commands = GetStrings(arguments, "exitCommands");
dap.terminate_commands = GetStrings(arguments, "terminateCommands");
auto attachCommands = GetStrings(arguments, "attachCommands");
llvm::StringRef core_file = GetString(arguments, "coreFile");
const uint64_t timeout_seconds = GetUnsigned(arguments, "timeout", 30);
dap.stop_at_entry =
core_file.empty() ? GetBoolean(arguments, "stopOnEntry", false) : true;
dap.post_run_commands = GetStrings(arguments, "postRunCommands");
const llvm::StringRef debuggerRoot = GetString(arguments, "debuggerRoot");
dap.enable_auto_variable_summaries =
GetBoolean(arguments, "enableAutoVariableSummaries", false);
dap.enable_synthetic_child_debugging =
GetBoolean(arguments, "enableSyntheticChildDebugging", false);
dap.display_extended_backtrace =
GetBoolean(arguments, "displayExtendedBacktrace", false);
dap.command_escape_prefix = GetString(arguments, "commandEscapePrefix", "`");
dap.SetFrameFormat(GetString(arguments, "customFrameFormat"));
dap.SetThreadFormat(GetString(arguments, "customThreadFormat"));

PrintWelcomeMessage(dap);

// This is a hack for loading DWARF in .o files on Mac where the .o files
// in the debug map of the main executable have relative paths which require
// the lldb-dap binary to have its working directory set to that relative
// root for the .o files in order to be able to load debug info.
if (!debuggerRoot.empty())
llvm::sys::fs::set_current_path(debuggerRoot);

// Run any initialize LLDB commands the user specified in the launch.json
if (llvm::Error err = dap.RunInitCommands()) {
response["success"] = false;
EmplaceSafeString(response, "message", llvm::toString(std::move(err)));
dap.SendJSON(llvm::json::Value(std::move(response)));
return;
}

SetSourceMapFromArguments(*arguments);

lldb::SBError status;
dap.SetTarget(dap.CreateTargetFromArguments(*arguments, status));
if (status.Fail()) {
response["success"] = llvm::json::Value(false);
EmplaceSafeString(response, "message", status.GetCString());
dap.SendJSON(llvm::json::Value(std::move(response)));
return;
}

// Run any pre run LLDB commands the user specified in the launch.json
if (llvm::Error err = dap.RunPreRunCommands()) {
response["success"] = false;
EmplaceSafeString(response, "message", llvm::toString(std::move(err)));
dap.SendJSON(llvm::json::Value(std::move(response)));
return;
}

if ((pid == LLDB_INVALID_PROCESS_ID || gdb_remote_port == invalid_port) &&
wait_for) {
char attach_msg[256];
auto attach_msg_len = snprintf(attach_msg, sizeof(attach_msg),
"Waiting to attach to \"%s\"...",
dap.target.GetExecutable().GetFilename());
dap.SendOutput(OutputType::Console,
llvm::StringRef(attach_msg, attach_msg_len));
}
if (attachCommands.empty()) {
// No "attachCommands", just attach normally.
// Disable async events so the attach will be successful when we return from
// the launch call and the launch will happen synchronously
dap.debugger.SetAsync(false);
if (core_file.empty()) {
if ((pid != LLDB_INVALID_PROCESS_ID) &&
(gdb_remote_port != invalid_port)) {
// If both pid and port numbers are specified.
error.SetErrorString("The user can't specify both pid and port");
} else if (gdb_remote_port != invalid_port) {
// If port is specified and pid is not.
lldb::SBListener listener = dap.debugger.GetListener();

// If the user hasn't provided the hostname property, default localhost
// being used.
std::string connect_url =
llvm::formatv("connect://{0}:", gdb_remote_hostname);
connect_url += std::to_string(gdb_remote_port);
dap.target.ConnectRemote(listener, connect_url.c_str(), "gdb-remote",
error);
} else {
// Attach by process name or id.
dap.target.Attach(attach_info, error);
}
} else
dap.target.LoadCore(core_file.data(), error);
// Reenable async events
dap.debugger.SetAsync(true);
} else {
// We have "attachCommands" that are a set of commands that are expected
// to execute the commands after which a process should be created. If there
// is no valid process after running these commands, we have failed.
if (llvm::Error err = dap.RunAttachCommands(attachCommands)) {
response["success"] = false;
EmplaceSafeString(response, "message", llvm::toString(std::move(err)));
dap.SendJSON(llvm::json::Value(std::move(response)));
return;
}
// 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 attached and stopped before proceeding as the
// the launch commands are not run using the synchronous mode.
error = dap.WaitForProcessToStop(timeout_seconds);
}

if (error.Success() && core_file.empty()) {
auto attached_pid = dap.target.GetProcess().GetProcessID();
if (attached_pid == LLDB_INVALID_PROCESS_ID) {
if (attachCommands.empty())
error.SetErrorString("failed to attach to a process");
else
error.SetErrorString("attachCommands failed to attach to a process");
}
}

if (error.Fail()) {
response["success"] = llvm::json::Value(false);
EmplaceSafeString(response, "message", std::string(error.GetCString()));
} else {
dap.RunPostRunCommands();
}

dap.SendJSON(llvm::json::Value(std::move(response)));
if (error.Success()) {
SendProcessEvent(Attach);
dap.SendJSON(CreateEventObject("initialized"));
}
}

} // namespace lldb_dap
92 changes: 92 additions & 0 deletions lldb/tools/lldb-dap/Request/Request.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
//===-- Request.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 "Request.h"
#include "DAP.h"
#include "JSONUtils.h"
#include "lldb/API/SBFileSpec.h"

namespace lldb_dap {

void Request::SendProcessEvent(Request::LaunchMethod launch_method) {
lldb::SBFileSpec exe_fspec = dap.target.GetExecutable();
char exe_path[PATH_MAX];
exe_fspec.GetPath(exe_path, sizeof(exe_path));
llvm::json::Object event(CreateEventObject("process"));
llvm::json::Object body;
EmplaceSafeString(body, "name", std::string(exe_path));
const auto pid = dap.target.GetProcess().GetProcessID();
body.try_emplace("systemProcessId", (int64_t)pid);
body.try_emplace("isLocalProcess", true);
const char *startMethod = nullptr;
switch (launch_method) {
case Launch:
startMethod = "launch";
break;
case Attach:
startMethod = "attach";
break;
case AttachForSuspendedLaunch:
startMethod = "attachForSuspendedLaunch";
break;
}
body.try_emplace("startMethod", startMethod);
event.try_emplace("body", std::move(body));
dap.SendJSON(llvm::json::Value(std::move(event)));
}

// Both attach and launch take a either a sourcePath or sourceMap
// argument (or neither), from which we need to set the target.source-map.
void Request::SetSourceMapFromArguments(const llvm::json::Object &arguments) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the fact this is present in both the launch and attach events means we may want to move this kind of logic to the DAP object or to some other helper.

I think we'd end up with a lot of various helpers in the base class, which may not be applicable to the various subclasses.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. Are you okay with doing that in a separate PR? If I move stuff into the base class, it becomes obvious what needs to be moved (I'll add a FIXME).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, that is fine by me

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");

// 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});
}
}

} // namespace lldb_dap
Loading