diff options
Diffstat (limited to 'lldb/tools')
-rw-r--r-- | lldb/tools/debugserver/source/RNBRemote.cpp | 121 | ||||
-rw-r--r-- | lldb/tools/debugserver/source/RNBRemote.h | 2 | ||||
-rw-r--r-- | lldb/tools/lldb-dap/Handler/RequestHandler.cpp | 4 | ||||
-rw-r--r-- | lldb/tools/lldb-dap/JSONUtils.cpp | 15 | ||||
-rw-r--r-- | lldb/tools/lldb-dap/JSONUtils.h | 7 | ||||
-rw-r--r-- | lldb/tools/lldb-dap/Options.td | 6 | ||||
-rw-r--r-- | lldb/tools/lldb-dap/Protocol/ProtocolRequests.h | 1 | ||||
-rw-r--r-- | lldb/tools/lldb-dap/ProtocolUtils.cpp | 8 | ||||
-rw-r--r-- | lldb/tools/lldb-dap/package.json | 5 | ||||
-rw-r--r-- | lldb/tools/lldb-dap/tool/lldb-dap.cpp | 87 | ||||
-rw-r--r-- | lldb/tools/lldb-mcp/lldb-mcp.cpp | 2 |
11 files changed, 252 insertions, 6 deletions
diff --git a/lldb/tools/debugserver/source/RNBRemote.cpp b/lldb/tools/debugserver/source/RNBRemote.cpp index b06c6bf..b2d7937 100644 --- a/lldb/tools/debugserver/source/RNBRemote.cpp +++ b/lldb/tools/debugserver/source/RNBRemote.cpp @@ -170,6 +170,20 @@ static uint64_t decode_uint64(const char *p, int base, char **end = nullptr, return addr; } +/// Attempts to parse a prefix of `number_str` as a uint64_t. If +/// successful, the number is returned and the prefix is dropped from +/// `number_str`. +static std::optional<uint64_t> extract_u64(std::string_view &number_str) { + char *str_end = nullptr; + errno = 0; + uint64_t number = strtoull(number_str.data(), &str_end, 16); + if (errno != 0) + return std::nullopt; + assert(str_end); + number_str.remove_prefix(str_end - number_str.data()); + return number; +} + static void append_hex_value(std::ostream &ostrm, const void *buf, size_t buf_size, bool swap) { int i; @@ -204,6 +218,25 @@ static void append_hexified_string(std::ostream &ostrm, } } +/// Returns true if `str` starts with `prefix`. +static bool starts_with(std::string_view str, std::string_view prefix) { + return str.substr(0, prefix.size()) == prefix; +} + +/// Splits `list_str` into multiple string_views separated by `,`. +static std::vector<std::string_view> +parse_comma_separated_list(std::string_view list_str) { + std::vector<std::string_view> list; + while (!list_str.empty()) { + auto pos = list_str.find(','); + list.push_back(list_str.substr(0, pos)); + if (pos == list_str.npos) + break; + list_str.remove_prefix(pos + 1); + } + return list; +} + // from System.framework/Versions/B/PrivateHeaders/sys/codesign.h extern "C" { #define CS_OPS_STATUS 0 /* return status */ @@ -270,6 +303,11 @@ void RNBRemote::CreatePacketTable() { "Read memory")); t.push_back(Packet(read_register, &RNBRemote::HandlePacket_p, NULL, "p", "Read one register")); + // Careful: this *must* come before the `M` packet, as debugserver matches + // packet prefixes against known packet names. Inverting the order would match + // `MultiMemRead` as an `M` packet. + t.push_back(Packet(multi_mem_read, &RNBRemote::HandlePacket_MultiMemRead, + NULL, "MultiMemRead", "Read multiple memory addresses")); t.push_back(Packet(write_memory, &RNBRemote::HandlePacket_M, NULL, "M", "Write memory")); t.push_back(Packet(write_register, &RNBRemote::HandlePacket_P, NULL, "P", @@ -3150,6 +3188,88 @@ rnb_err_t RNBRemote::HandlePacket_m(const char *p) { return SendPacket(ostrm.str()); } +rnb_err_t RNBRemote::HandlePacket_MultiMemRead(const char *p) { + const std::string_view packet_name("MultiMemRead:"); + std::string_view packet(p); + + if (!starts_with(packet, packet_name)) + return HandlePacket_ILLFORMED(__FILE__, __LINE__, p, + "Invalid MultiMemRead packet prefix"); + + packet.remove_prefix(packet_name.size()); + + const std::string_view ranges_prefix("ranges:"); + if (!starts_with(packet, ranges_prefix)) + return HandlePacket_ILLFORMED(__FILE__, __LINE__, packet.data(), + "Missing 'ranges' in MultiMemRead packet"); + packet.remove_prefix(ranges_prefix.size()); + + std::vector<std::pair<nub_addr_t, std::size_t>> ranges; + std::size_t total_length = 0; + + // Ranges should have the form: <addr>,<size>[,<addr>,<size>]*; + auto end_of_ranges_pos = packet.find(';'); + if (end_of_ranges_pos == packet.npos) + return HandlePacket_ILLFORMED(__FILE__, __LINE__, packet.data(), + "MultiMemRead missing end of ranges marker"); + + std::vector<std::string_view> numbers_list = + parse_comma_separated_list(packet.substr(0, end_of_ranges_pos)); + packet.remove_prefix(end_of_ranges_pos + 1); + + // Ranges are pairs, so the number of elements must be even. + if (numbers_list.size() % 2 == 1) + return HandlePacket_ILLFORMED( + __FILE__, __LINE__, p, + "MultiMemRead has an odd number of numbers for the ranges"); + + for (unsigned idx = 0; idx < numbers_list.size(); idx += 2) { + std::optional<uint64_t> maybe_addr = extract_u64(numbers_list[idx]); + std::optional<uint64_t> maybe_length = extract_u64(numbers_list[idx + 1]); + if (!maybe_addr || !maybe_length) + return HandlePacket_ILLFORMED(__FILE__, __LINE__, packet.data(), + "Invalid MultiMemRead range"); + // A sanity check that the packet requested is not too large or a negative + // number. + if (*maybe_length > 4 * 1024 * 1024) + return HandlePacket_ILLFORMED(__FILE__, __LINE__, packet.data(), + "MultiMemRead length is too large"); + + ranges.emplace_back(*maybe_addr, *maybe_length); + total_length += *maybe_length; + } + + if (ranges.empty()) + return HandlePacket_ILLFORMED(__FILE__, __LINE__, p, + "MultiMemRead has an empty range list"); + + if (!packet.empty()) + return HandlePacket_ILLFORMED( + __FILE__, __LINE__, p, "MultiMemRead packet has unrecognized fields"); + + std::vector<std::vector<uint8_t>> buffers; + buffers.reserve(ranges.size()); + for (auto [base_addr, length] : ranges) { + buffers.emplace_back(length, 0); + nub_size_t bytes_read = DNBProcessMemoryRead(m_ctx.ProcessID(), base_addr, + length, buffers.back().data()); + buffers.back().resize(bytes_read); + } + + std::ostringstream reply_stream; + bool first = true; + for (const std::vector<uint8_t> &buffer : buffers) { + reply_stream << (first ? "" : ",") << std::hex << buffer.size(); + first = false; + } + reply_stream << ';'; + + for (const std::vector<uint8_t> &buffer : buffers) + binary_encode_data_vector(reply_stream, buffer); + + return SendPacket(reply_stream.str()); +} + // Read memory, sent it up as binary data. // Usage: xADDR,LEN // ADDR and LEN are both base 16. @@ -3503,6 +3623,7 @@ rnb_err_t RNBRemote::HandlePacket_qSupported(const char *p) { if (supports_memory_tagging()) reply << "memory-tagging+;"; + reply << "MultiMemRead+;"; return SendPacket(reply.str().c_str()); } diff --git a/lldb/tools/debugserver/source/RNBRemote.h b/lldb/tools/debugserver/source/RNBRemote.h index cf1c978..b32c00a 100644 --- a/lldb/tools/debugserver/source/RNBRemote.h +++ b/lldb/tools/debugserver/source/RNBRemote.h @@ -136,6 +136,7 @@ public: query_transfer, // 'qXfer:' json_query_dyld_process_state, // 'jGetDyldProcessState' enable_error_strings, // 'QEnableErrorStrings' + multi_mem_read, // 'MultiMemRead' unknown_type }; // clang-format on @@ -216,6 +217,7 @@ public: rnb_err_t HandlePacket_last_signal(const char *p); rnb_err_t HandlePacket_m(const char *p); rnb_err_t HandlePacket_M(const char *p); + rnb_err_t HandlePacket_MultiMemRead(const char *p); rnb_err_t HandlePacket_x(const char *p); rnb_err_t HandlePacket_X(const char *p); rnb_err_t HandlePacket_z(const char *p); diff --git a/lldb/tools/lldb-dap/Handler/RequestHandler.cpp b/lldb/tools/lldb-dap/Handler/RequestHandler.cpp index 7738913..e7d9b89 100644 --- a/lldb/tools/lldb-dap/Handler/RequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/RequestHandler.cpp @@ -57,7 +57,7 @@ SetupIORedirection(const std::vector<std::optional<std::string>> &stdio, 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) + if (stdio.size() <= i) path = stdio.back(); else path = stdio[i]; @@ -107,7 +107,7 @@ RunInTerminal(DAP &dap, const protocol::LaunchRequestArguments &arguments) { llvm::json::Object reverse_request = CreateRunInTerminalReverseRequest( arguments.configuration.program, arguments.args, arguments.env, - arguments.cwd, comm_file.m_path, debugger_pid, + arguments.cwd, comm_file.m_path, debugger_pid, arguments.stdio, arguments.console == protocol::eConsoleExternalTerminal); dap.SendReverseRequest<LogFailureResponseHandler>("runInTerminal", std::move(reverse_request)); diff --git a/lldb/tools/lldb-dap/JSONUtils.cpp b/lldb/tools/lldb-dap/JSONUtils.cpp index 4f26599..71e91f8 100644 --- a/lldb/tools/lldb-dap/JSONUtils.cpp +++ b/lldb/tools/lldb-dap/JSONUtils.cpp @@ -866,7 +866,8 @@ llvm::json::Value CreateCompileUnit(lldb::SBCompileUnit &unit) { llvm::json::Object CreateRunInTerminalReverseRequest( llvm::StringRef program, const std::vector<std::string> &args, const llvm::StringMap<std::string> &env, llvm::StringRef cwd, - llvm::StringRef comm_file, lldb::pid_t debugger_pid, bool external) { + llvm::StringRef comm_file, lldb::pid_t debugger_pid, + const std::vector<std::optional<std::string>> &stdio, bool external) { llvm::json::Object run_in_terminal_args; if (external) { // This indicates the IDE to open an external terminal window. @@ -885,6 +886,18 @@ llvm::json::Object CreateRunInTerminalReverseRequest( } req_args.push_back("--launch-target"); req_args.push_back(program.str()); + if (!stdio.empty()) { + req_args.push_back("--stdio"); + std::stringstream ss; + for (const std::optional<std::string> &file : stdio) { + if (file) + ss << *file; + ss << ":"; + } + std::string files = ss.str(); + files.pop_back(); + req_args.push_back(std::move(files)); + } req_args.insert(req_args.end(), args.begin(), args.end()); run_in_terminal_args.try_emplace("args", req_args); diff --git a/lldb/tools/lldb-dap/JSONUtils.h b/lldb/tools/lldb-dap/JSONUtils.h index e9094f6..0c865a3 100644 --- a/lldb/tools/lldb-dap/JSONUtils.h +++ b/lldb/tools/lldb-dap/JSONUtils.h @@ -388,6 +388,10 @@ llvm::json::Value CreateCompileUnit(lldb::SBCompileUnit &unit); /// launcher uses it on Linux tell the kernel that it should allow the /// debugger process to attach. /// +/// \param[in] stdio +/// An array of file paths for redirecting the program's standard IO +/// streams. +/// /// \param[in] external /// If set to true, the program will run in an external terminal window /// instead of IDE's integrated terminal. @@ -398,7 +402,8 @@ llvm::json::Value CreateCompileUnit(lldb::SBCompileUnit &unit); llvm::json::Object CreateRunInTerminalReverseRequest( llvm::StringRef program, const std::vector<std::string> &args, const llvm::StringMap<std::string> &env, llvm::StringRef cwd, - llvm::StringRef comm_file, lldb::pid_t debugger_pid, bool external); + llvm::StringRef comm_file, lldb::pid_t debugger_pid, + const std::vector<std::optional<std::string>> &stdio, bool external); /// Create a "Terminated" JSON object that contains statistics /// diff --git a/lldb/tools/lldb-dap/Options.td b/lldb/tools/lldb-dap/Options.td index c8492c6..5e9dd7a 100644 --- a/lldb/tools/lldb-dap/Options.td +++ b/lldb/tools/lldb-dap/Options.td @@ -47,6 +47,12 @@ def debugger_pid: S<"debugger-pid">, HelpText<"The PID of the lldb-dap instance that sent the launchInTerminal " "request when using --launch-target.">; +def stdio: S<"stdio">, + MetaVarName<"<stdin:stdout:stderr:...>">, + HelpText<"An array of file paths for redirecting the program's standard IO " + "streams. A colon-separated list of entries. Empty value means no " + "redirection.">; + def repl_mode : S<"repl-mode">, MetaVarName<"<mode>">, diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h index 92dada2..a85a68b 100644 --- a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h +++ b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h @@ -300,6 +300,7 @@ struct LaunchRequestArguments { /// terminal or external terminal. Console console = eConsoleInternal; + /// An array of file paths for redirecting the program's standard IO streams. std::vector<std::optional<std::string>> stdio; /// @} diff --git a/lldb/tools/lldb-dap/ProtocolUtils.cpp b/lldb/tools/lldb-dap/ProtocolUtils.cpp index 775c82f..868c67c 100644 --- a/lldb/tools/lldb-dap/ProtocolUtils.cpp +++ b/lldb/tools/lldb-dap/ProtocolUtils.cpp @@ -301,6 +301,14 @@ Variable CreateVariable(lldb::SBValue v, int64_t var_ref, bool format_hex, if (lldb::addr_t addr = v.GetLoadAddress(); addr != LLDB_INVALID_ADDRESS) var.memoryReference = addr; + bool is_readonly = v.GetType().IsAggregateType() || + v.GetValueType() == lldb::eValueTypeRegisterSet; + if (is_readonly) { + if (!var.presentationHint) + var.presentationHint = {VariablePresentationHint()}; + var.presentationHint->attributes.push_back("readOnly"); + } + return var; } diff --git a/lldb/tools/lldb-dap/package.json b/lldb/tools/lldb-dap/package.json index e961c2e..3f0f150 100644 --- a/lldb/tools/lldb-dap/package.json +++ b/lldb/tools/lldb-dap/package.json @@ -626,7 +626,10 @@ "stdio": { "type": "array", "items": { - "type": "string" + "type": [ + "string", + "null" + ] }, "description": "The stdio property specifies the redirection targets for the debuggee's stdio streams. A null value redirects a stream to the default debug terminal. String can be a path to file, named pipe or TTY device. If less than three values are provided, the list will be padded with the last value. Specifying more than three values will create additional file descriptors (4, 5, etc.).", "default": [] diff --git a/lldb/tools/lldb-dap/tool/lldb-dap.cpp b/lldb/tools/lldb-dap/tool/lldb-dap.cpp index 93446c0..e59cef9 100644 --- a/lldb/tools/lldb-dap/tool/lldb-dap.cpp +++ b/lldb/tools/lldb-dap/tool/lldb-dap.cpp @@ -16,6 +16,7 @@ #include "lldb/API/SBStream.h" #include "lldb/Host/Config.h" #include "lldb/Host/File.h" +#include "lldb/Host/FileSystem.h" #include "lldb/Host/MainLoop.h" #include "lldb/Host/MainLoopBase.h" #include "lldb/Host/MemoryMonitor.h" @@ -24,7 +25,9 @@ #include "lldb/Utility/UriParser.h" #include "lldb/lldb-forward.h" #include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/DenseMap.h" #include "llvm/ADT/ScopeExit.h" +#include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringExtras.h" #include "llvm/ADT/StringRef.h" #include "llvm/Option/Arg.h" @@ -42,8 +45,10 @@ #include "llvm/Support/WithColor.h" #include "llvm/Support/raw_ostream.h" #include <condition_variable> +#include <cstddef> #include <cstdio> #include <cstdlib> +#include <exception> #include <fcntl.h> #include <map> #include <memory> @@ -143,6 +148,74 @@ static void PrintVersion() { llvm::outs() << "liblldb: " << lldb::SBDebugger::GetVersionString() << '\n'; } +#if not defined(_WIN32) +struct FDGroup { + int GetFlags() const { + if (read && write) + return O_NOCTTY | O_CREAT | O_RDWR; + if (read) + return O_NOCTTY | O_RDONLY; + return O_NOCTTY | O_CREAT | O_WRONLY | O_TRUNC; + } + + std::vector<int> fds; + bool read = false; + bool write = false; +}; + +static llvm::Error RedirectToFile(const FDGroup &fdg, llvm::StringRef file) { + if (!fdg.read && !fdg.write) + return llvm::Error::success(); + int target_fd = lldb_private::FileSystem::Instance().Open( + file.str().c_str(), fdg.GetFlags(), 0666); + if (target_fd == -1) + return llvm::errorCodeToError( + std::error_code(errno, std::generic_category())); + for (int fd : fdg.fds) { + if (target_fd == fd) + continue; + if (::dup2(target_fd, fd) == -1) + return llvm::errorCodeToError( + std::error_code(errno, std::generic_category())); + } + ::close(target_fd); + return llvm::Error::success(); +} + +static llvm::Error +SetupIORedirection(const llvm::SmallVectorImpl<llvm::StringRef> &files) { + llvm::SmallDenseMap<llvm::StringRef, FDGroup> groups; + for (size_t i = 0; i < files.size(); i++) { + if (files[i].empty()) + continue; + auto group = groups.find(files[i]); + if (group == groups.end()) + group = groups.insert({files[i], {{static_cast<int>(i)}}}).first; + else + group->second.fds.push_back(i); + switch (i) { + case 0: + group->second.read = true; + break; + case 1: + case 2: + group->second.write = true; + break; + default: + group->second.read = true; + group->second.write = true; + break; + } + } + for (const auto &[file, group] : groups) { + if (llvm::Error err = RedirectToFile(group, file)) + return llvm::createStringError( + llvm::formatv("{0}: {1}", file, llvm::toString(std::move(err)))); + } + return llvm::Error::success(); +} +#endif + // If --launch-target is provided, this instance of lldb-dap becomes a // runInTerminal launcher. It will ultimately launch the program specified in // the --launch-target argument, which is the original program the user wanted @@ -165,6 +238,7 @@ static void PrintVersion() { static llvm::Error LaunchRunInTerminalTarget(llvm::opt::Arg &target_arg, llvm::StringRef comm_file, lldb::pid_t debugger_pid, + llvm::StringRef stdio, char *argv[]) { #if defined(_WIN32) return llvm::createStringError( @@ -179,6 +253,16 @@ static llvm::Error LaunchRunInTerminalTarget(llvm::opt::Arg &target_arg, (void)prctl(PR_SET_PTRACER, debugger_pid, 0, 0, 0); #endif + lldb_private::FileSystem::Initialize(); + if (!stdio.empty()) { + llvm::SmallVector<llvm::StringRef, 3> files; + stdio.split(files, ':'); + while (files.size() < 3) + files.push_back(files.back()); + if (llvm::Error err = SetupIORedirection(files)) + return err; + } + RunInTerminalLauncherCommChannel comm_channel(comm_file); if (llvm::Error err = comm_channel.NotifyPid()) return err; @@ -484,9 +568,10 @@ int main(int argc, char *argv[]) { break; } } + llvm::StringRef stdio = input_args.getLastArgValue(OPT_stdio); if (llvm::Error err = LaunchRunInTerminalTarget(*target_arg, comm_file->getValue(), pid, - argv + target_args_pos)) { + stdio, argv + target_args_pos)) { llvm::errs() << llvm::toString(std::move(err)) << '\n'; return EXIT_FAILURE; } diff --git a/lldb/tools/lldb-mcp/lldb-mcp.cpp b/lldb/tools/lldb-mcp/lldb-mcp.cpp index 68e9872..3fc3622 100644 --- a/lldb/tools/lldb-mcp/lldb-mcp.cpp +++ b/lldb/tools/lldb-mcp/lldb-mcp.cpp @@ -85,6 +85,8 @@ FileSpec driverPath() { llvm::Error launch() { FileSpec lldb_exec = driverPath(); lldb_private::ProcessLaunchInfo info; + info.SetMonitorProcessCallback( + &lldb_private::ProcessLaunchInfo::NoOpMonitorCallback); info.SetExecutableFile(lldb_exec, /*add_exe_file_as_first_arg=*/true); info.GetArguments().AppendArgument("-O"); |