770 lines · cpp
1//===-- lldb-dap.cpp ------------------------------------------------------===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8 9#include "ClientLauncher.h"10#include "DAP.h"11#include "DAPLog.h"12#include "EventHelper.h"13#include "Handler/RequestHandler.h"14#include "RunInTerminal.h"15#include "Transport.h"16#include "lldb/API/SBDebugger.h"17#include "lldb/API/SBStream.h"18#include "lldb/Host/Config.h"19#include "lldb/Host/File.h"20#include "lldb/Host/FileSystem.h"21#include "lldb/Host/MainLoop.h"22#include "lldb/Host/MainLoopBase.h"23#include "lldb/Host/MemoryMonitor.h"24#include "lldb/Host/Socket.h"25#include "lldb/Utility/AnsiTerminal.h"26#include "lldb/Utility/Status.h"27#include "lldb/Utility/UriParser.h"28#include "lldb/lldb-forward.h"29#include "llvm/ADT/ArrayRef.h"30#include "llvm/ADT/DenseMap.h"31#include "llvm/ADT/ScopeExit.h"32#include "llvm/ADT/SmallVector.h"33#include "llvm/ADT/StringExtras.h"34#include "llvm/ADT/StringRef.h"35#include "llvm/Option/Arg.h"36#include "llvm/Option/ArgList.h"37#include "llvm/Option/OptTable.h"38#include "llvm/Option/Option.h"39#include "llvm/Support/CommandLine.h"40#include "llvm/Support/Error.h"41#include "llvm/Support/FileSystem.h"42#include "llvm/Support/InitLLVM.h"43#include "llvm/Support/Path.h"44#include "llvm/Support/PrettyStackTrace.h"45#include "llvm/Support/Signals.h"46#include "llvm/Support/Threading.h"47#include "llvm/Support/WithColor.h"48#include "llvm/Support/raw_ostream.h"49#include <condition_variable>50#include <cstddef>51#include <cstdio>52#include <cstdlib>53#include <exception>54#include <fcntl.h>55#include <map>56#include <memory>57#include <mutex>58#include <string>59#include <system_error>60#include <thread>61#include <utility>62#include <vector>63 64#if defined(_WIN32)65// We need to #define NOMINMAX in order to skip `min()` and `max()` macro66// definitions that conflict with other system headers.67// We also need to #undef GetObject (which is defined to GetObjectW) because68// the JSON code we use also has methods named `GetObject()` and we conflict69// against these.70#define NOMINMAX71#include <windows.h>72#undef GetObject73#include <io.h>74typedef int socklen_t;75#else76#include <netinet/in.h>77#include <sys/socket.h>78#include <sys/un.h>79#include <termios.h>80#include <unistd.h>81#endif82 83#if defined(__linux__)84#include <sys/prctl.h>85#endif86 87using namespace lldb_dap;88using lldb_private::File;89using lldb_private::IOObject;90using lldb_private::MainLoop;91using lldb_private::MainLoopBase;92using lldb_private::NativeFile;93using lldb_private::Socket;94using lldb_private::Status;95 96namespace {97using namespace llvm::opt;98 99enum ID {100 OPT_INVALID = 0, // This is not an option ID.101#define OPTION(...) LLVM_MAKE_OPT_ID(__VA_ARGS__),102#include "Options.inc"103#undef OPTION104};105 106#define OPTTABLE_STR_TABLE_CODE107#include "Options.inc"108#undef OPTTABLE_STR_TABLE_CODE109 110#define OPTTABLE_PREFIXES_TABLE_CODE111#include "Options.inc"112#undef OPTTABLE_PREFIXES_TABLE_CODE113 114static constexpr llvm::opt::OptTable::Info InfoTable[] = {115#define OPTION(...) LLVM_CONSTRUCT_OPT_INFO(__VA_ARGS__),116#include "Options.inc"117#undef OPTION118};119class LLDBDAPOptTable : public llvm::opt::GenericOptTable {120public:121 LLDBDAPOptTable()122 : llvm::opt::GenericOptTable(OptionStrTable, OptionPrefixesTable,123 InfoTable, true) {}124};125} // anonymous namespace126 127static void PrintHelp(LLDBDAPOptTable &table, llvm::StringRef tool_name) {128 std::string usage_str = tool_name.str() + " options";129 table.printHelp(llvm::outs(), usage_str.c_str(), "LLDB DAP", false);130 131 llvm::outs() << R"___(132EXAMPLES:133 The debug adapter can be started in two modes.134 135 Running lldb-dap without any arguments will start communicating with the136 parent over stdio. Passing a --connection URI will cause lldb-dap to listen137 for a connection in the specified mode.138 139 lldb-dap --connection listen://localhost:<port>140 141 Passing --wait-for-debugger will pause the process at startup and wait for a142 debugger to attach to the process.143 144 lldb-dap -g145 146 You can also use lldb-dap to launch a supported client, for example the147 LLDB-DAP Visual Studio Code extension.148 149 lldb-dap --client vscode -- /path/to/binary <args>150 151)___";152}153 154static void PrintVersion() {155 llvm::outs() << "lldb-dap: ";156 llvm::cl::PrintVersionMessage();157 llvm::outs() << "liblldb: " << lldb::SBDebugger::GetVersionString() << '\n';158}159 160static llvm::Error LaunchClient(const llvm::opt::InputArgList &args) {161 auto *client_arg = args.getLastArg(OPT_client);162 assert(client_arg && "must have client arg");163 164 std::optional<ClientLauncher::Client> client =165 ClientLauncher::GetClientFrom(client_arg->getValue());166 if (!client)167 return llvm::createStringError(168 llvm::formatv("unsupported client: {0}", client_arg->getValue()));169 170 std::vector<llvm::StringRef> launch_args;171 if (auto *arg = args.getLastArgNoClaim(OPT_REM)) {172 for (auto *value : arg->getValues()) {173 launch_args.push_back(value);174 }175 }176 177 if (launch_args.empty())178 return llvm::createStringError("no launch arguments provided");179 180 return ClientLauncher::GetLauncher(*client)->Launch(launch_args);181}182 183#if not defined(_WIN32)184struct FDGroup {185 int GetFlags() const {186 if (read && write)187 return O_NOCTTY | O_CREAT | O_RDWR;188 if (read)189 return O_NOCTTY | O_RDONLY;190 return O_NOCTTY | O_CREAT | O_WRONLY | O_TRUNC;191 }192 193 std::vector<int> fds;194 bool read = false;195 bool write = false;196};197 198static llvm::Error RedirectToFile(const FDGroup &fdg, llvm::StringRef file) {199 if (!fdg.read && !fdg.write)200 return llvm::Error::success();201 int target_fd = lldb_private::FileSystem::Instance().Open(202 file.str().c_str(), fdg.GetFlags(), 0666);203 if (target_fd == -1)204 return llvm::errorCodeToError(205 std::error_code(errno, std::generic_category()));206 for (int fd : fdg.fds) {207 if (target_fd == fd)208 continue;209 if (::dup2(target_fd, fd) == -1)210 return llvm::errorCodeToError(211 std::error_code(errno, std::generic_category()));212 }213 ::close(target_fd);214 return llvm::Error::success();215}216 217static llvm::Error218SetupIORedirection(const llvm::SmallVectorImpl<llvm::StringRef> &files) {219 llvm::SmallDenseMap<llvm::StringRef, FDGroup> groups;220 for (size_t i = 0; i < files.size(); i++) {221 if (files[i].empty())222 continue;223 auto group = groups.find(files[i]);224 if (group == groups.end())225 group = groups.insert({files[i], {{static_cast<int>(i)}}}).first;226 else227 group->second.fds.push_back(i);228 switch (i) {229 case 0:230 group->second.read = true;231 break;232 case 1:233 case 2:234 group->second.write = true;235 break;236 default:237 group->second.read = true;238 group->second.write = true;239 break;240 }241 }242 for (const auto &[file, group] : groups) {243 if (llvm::Error err = RedirectToFile(group, file))244 return llvm::createStringError(245 llvm::formatv("{0}: {1}", file, llvm::toString(std::move(err))));246 }247 return llvm::Error::success();248}249#endif250 251// If --launch-target is provided, this instance of lldb-dap becomes a252// runInTerminal launcher. It will ultimately launch the program specified in253// the --launch-target argument, which is the original program the user wanted254// to debug. This is done in such a way that the actual debug adapter can255// place breakpoints at the beginning of the program.256//257// The launcher will communicate with the debug adapter using a fifo file in the258// directory specified in the --comm-file argument.259//260// Regarding the actual flow, this launcher will first notify the debug adapter261// of its pid. Then, the launcher will be in a pending state waiting to be262// attached by the adapter.263//264// Once attached and resumed, the launcher will exec and become the program265// specified by --launch-target, which is the original target the266// user wanted to run.267//268// In case of errors launching the target, a suitable error message will be269// emitted to the debug adapter.270static llvm::Error LaunchRunInTerminalTarget(llvm::opt::Arg &target_arg,271 llvm::StringRef comm_file,272 lldb::pid_t debugger_pid,273 llvm::StringRef stdio,274 char *argv[]) {275#if defined(_WIN32)276 return llvm::createStringError(277 "runInTerminal is only supported on POSIX systems");278#else279 280 // On Linux with the Yama security module enabled, a process can only attach281 // to its descendants by default. In the runInTerminal case the target282 // process is launched by the client so we need to allow tracing explicitly.283#if defined(__linux__)284 if (debugger_pid != LLDB_INVALID_PROCESS_ID)285 (void)prctl(PR_SET_PTRACER, debugger_pid, 0, 0, 0);286#endif287 288 lldb_private::FileSystem::Initialize();289 if (!stdio.empty()) {290 llvm::SmallVector<llvm::StringRef, 3> files;291 stdio.split(files, ':');292 while (files.size() < 3)293 files.push_back(files.back());294 if (llvm::Error err = SetupIORedirection(files))295 return err;296 } else if ((isatty(STDIN_FILENO) != 0) &&297 llvm::StringRef(getenv("TERM")).starts_with_insensitive("xterm")) {298 // Clear the screen.299 llvm::outs() << ANSI_CSI_RESET_CURSOR ANSI_CSI_ERASE_VIEWPORT300 ANSI_CSI_ERASE_SCROLLBACK;301 // VS Code will reuse the same terminal for the same debug configuration302 // between runs. Clear the input buffer prior to starting the new process so303 // prior input is not carried forward to the new debug session.304 tcflush(STDIN_FILENO, TCIFLUSH);305 }306 307 RunInTerminalLauncherCommChannel comm_channel(comm_file);308 if (llvm::Error err = comm_channel.NotifyPid())309 return err;310 311 // We will wait to be attached with a timeout. We don't wait indefinitely312 // using a signal to prevent being paused forever.313 314 // This env var should be used only for tests.315 const char *timeout_env_var = getenv("LLDB_DAP_RIT_TIMEOUT_IN_MS");316 int timeout_in_ms =317 timeout_env_var != nullptr ? atoi(timeout_env_var) : 20000;318 if (llvm::Error err = comm_channel.WaitUntilDebugAdapterAttaches(319 std::chrono::milliseconds(timeout_in_ms))) {320 return err;321 }322 323 const char *target = target_arg.getValue();324 execvp(target, argv);325 326 std::string error = std::strerror(errno);327 comm_channel.NotifyError(error);328 return llvm::createStringError(llvm::inconvertibleErrorCode(),329 std::move(error));330#endif331}332 333/// used only by TestVSCode_redirection_to_console.py334static void redirection_test() {335 printf("stdout message\n");336 fprintf(stderr, "stderr message\n");337 fflush(stdout);338 fflush(stderr);339}340 341/// Duplicates a file descriptor, setting FD_CLOEXEC if applicable.342static int DuplicateFileDescriptor(int fd) {343#if defined(F_DUPFD_CLOEXEC)344 // Ensure FD_CLOEXEC is set.345 return ::fcntl(fd, F_DUPFD_CLOEXEC, 0);346#else347 return ::dup(fd);348#endif349}350 351static void352ResetConnectionTimeout(std::mutex &connection_timeout_mutex,353 MainLoopBase::TimePoint &conncetion_timeout_time_point) {354 std::scoped_lock<std::mutex> lock(connection_timeout_mutex);355 conncetion_timeout_time_point = MainLoopBase::TimePoint();356}357 358static void359TrackConnectionTimeout(MainLoop &loop, std::mutex &connection_timeout_mutex,360 MainLoopBase::TimePoint &conncetion_timeout_time_point,361 std::chrono::seconds ttl_seconds) {362 MainLoopBase::TimePoint next_checkpoint =363 std::chrono::steady_clock::now() + std::chrono::seconds(ttl_seconds);364 {365 std::scoped_lock<std::mutex> lock(connection_timeout_mutex);366 // We don't need to take the max of `ttl_time_point` and `next_checkpoint`,367 // because `next_checkpoint` must be the latest.368 conncetion_timeout_time_point = next_checkpoint;369 }370 loop.AddCallback(371 [&connection_timeout_mutex, &conncetion_timeout_time_point,372 next_checkpoint](MainLoopBase &loop) {373 std::scoped_lock<std::mutex> lock(connection_timeout_mutex);374 if (conncetion_timeout_time_point == next_checkpoint)375 loop.RequestTermination();376 },377 next_checkpoint);378}379 380static llvm::Expected<std::pair<Socket::SocketProtocol, std::string>>381validateConnection(llvm::StringRef conn) {382 auto uri = lldb_private::URI::Parse(conn);383 384 auto make_error = [conn]() -> llvm::Error {385 return llvm::createStringError(386 "Unsupported connection specifier, expected 'accept:///path' or "387 "'listen://[host]:port', got '%s'.",388 conn.str().c_str());389 };390 391 if (!uri)392 return make_error();393 394 std::optional<Socket::ProtocolModePair> protocol_and_mode =395 Socket::GetProtocolAndMode(uri->scheme);396 if (!protocol_and_mode || protocol_and_mode->second != Socket::ModeAccept)397 return make_error();398 399 if (protocol_and_mode->first == Socket::ProtocolTcp) {400 return std::make_pair(401 Socket::ProtocolTcp,402 formatv("[{0}]:{1}", uri->hostname.empty() ? "0.0.0.0" : uri->hostname,403 uri->port.value_or(0)));404 }405 406 if (protocol_and_mode->first == Socket::ProtocolUnixDomain)407 return std::make_pair(Socket::ProtocolUnixDomain, uri->path.str());408 409 return make_error();410}411 412static llvm::Error serveConnection(413 const Socket::SocketProtocol &protocol, const std::string &name, Log *log,414 const ReplMode default_repl_mode,415 const std::vector<std::string> &pre_init_commands, bool no_lldbinit,416 std::optional<std::chrono::seconds> connection_timeout_seconds) {417 Status status;418 static std::unique_ptr<Socket> listener = Socket::Create(protocol, status);419 if (status.Fail()) {420 return status.takeError();421 }422 423 status = listener->Listen(name, /*backlog=*/5);424 if (status.Fail()) {425 return status.takeError();426 }427 428 std::string address = llvm::join(listener->GetListeningConnectionURI(), ", ");429 DAP_LOG(log, "started with connection listeners {0}", address);430 431 llvm::outs() << "Listening for: " << address << "\n";432 // Ensure listening address are flushed for calles to retrieve the resolve433 // address.434 llvm::outs().flush();435 436 static MainLoop g_loop;437 llvm::sys::SetInterruptFunction([]() {438 g_loop.AddPendingCallback(439 [](MainLoopBase &loop) { loop.RequestTermination(); });440 });441 static MainLoopBase::TimePoint g_connection_timeout_time_point;442 static std::mutex g_connection_timeout_mutex;443 if (connection_timeout_seconds)444 TrackConnectionTimeout(g_loop, g_connection_timeout_mutex,445 g_connection_timeout_time_point,446 connection_timeout_seconds.value());447 std::condition_variable dap_sessions_condition;448 unsigned int clientCount = 0;449 auto handle = listener->Accept(g_loop, [=, &clientCount](450 std::unique_ptr<Socket> sock) {451 // Reset the keep alive timer, because we won't be killing the server452 // while this connection is being served.453 if (connection_timeout_seconds)454 ResetConnectionTimeout(g_connection_timeout_mutex,455 g_connection_timeout_time_point);456 std::string client_name = llvm::formatv("client_{0}", clientCount++).str();457 DAP_LOG(log, "({0}) client connected", client_name);458 459 lldb::IOObjectSP io(std::move(sock));460 461 // Move the client into a background thread to unblock accepting the next462 // client.463 std::thread client([=]() {464 llvm::set_thread_name(client_name + ".runloop");465 MainLoop loop;466 Transport transport(client_name, log, io, io);467 DAP dap(log, default_repl_mode, pre_init_commands, no_lldbinit,468 client_name, transport, loop);469 470 if (auto Err = dap.ConfigureIO()) {471 llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(),472 "Failed to configure stdout redirect: ");473 return;474 }475 476 // Register the DAP session with the global manager.477 DAPSessionManager::GetInstance().RegisterSession(&loop, &dap);478 479 if (auto Err = dap.Loop()) {480 llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(),481 "DAP session (" + client_name +482 ") error: ");483 }484 485 DAP_LOG(log, "({0}) client disconnected", client_name);486 // Unregister the DAP session from the global manager.487 DAPSessionManager::GetInstance().UnregisterSession(&loop);488 // Start the countdown to kill the server at the end of each connection.489 if (connection_timeout_seconds)490 TrackConnectionTimeout(g_loop, g_connection_timeout_mutex,491 g_connection_timeout_time_point,492 connection_timeout_seconds.value());493 });494 client.detach();495 });496 497 if (auto Err = handle.takeError()) {498 return Err;499 }500 501 status = g_loop.Run();502 if (status.Fail()) {503 return status.takeError();504 }505 506 DAP_LOG(507 log,508 "lldb-dap server shutdown requested, disconnecting remaining clients...");509 510 // Disconnect all active sessions using the global manager.511 DAPSessionManager::GetInstance().DisconnectAllSessions();512 513 // Wait for all clients to finish disconnecting and return any errors.514 return DAPSessionManager::GetInstance().WaitForAllSessionsToDisconnect();515}516 517int main(int argc, char *argv[]) {518 llvm::InitLLVM IL(argc, argv, /*InstallPipeSignalExitHandler=*/false);519#if !defined(__APPLE__)520 llvm::setBugReportMsg("PLEASE submit a bug report to " LLDB_BUG_REPORT_URL521 " and include the crash backtrace.\n");522#else523 llvm::setBugReportMsg("PLEASE submit a bug report to " LLDB_BUG_REPORT_URL524 " and include the crash report from "525 "~/Library/Logs/DiagnosticReports/.\n");526#endif527 528 llvm::SmallString<256> program_path(argv[0]);529 llvm::sys::fs::make_absolute(program_path);530 DAP::debug_adapter_path = program_path;531 532 LLDBDAPOptTable T;533 unsigned MAI, MAC;534 llvm::ArrayRef<const char *> ArgsArr = llvm::ArrayRef(argv + 1, argc);535 llvm::opt::InputArgList input_args = T.ParseArgs(ArgsArr, MAI, MAC);536 537 if (input_args.hasArg(OPT_help)) {538 PrintHelp(T, llvm::sys::path::filename(argv[0]));539 return EXIT_SUCCESS;540 }541 542 if (input_args.hasArg(OPT_version)) {543 PrintVersion();544 return EXIT_SUCCESS;545 }546 547 if (input_args.hasArg(OPT_client)) {548 if (llvm::Error error = LaunchClient(input_args)) {549 llvm::WithColor::error() << llvm::toString(std::move(error)) << '\n';550 return EXIT_FAILURE;551 }552 return EXIT_SUCCESS;553 }554 555 ReplMode default_repl_mode = ReplMode::Auto;556 if (input_args.hasArg(OPT_repl_mode)) {557 llvm::opt::Arg *repl_mode = input_args.getLastArg(OPT_repl_mode);558 llvm::StringRef repl_mode_value = repl_mode->getValue();559 if (repl_mode_value == "auto") {560 default_repl_mode = ReplMode::Auto;561 } else if (repl_mode_value == "variable") {562 default_repl_mode = ReplMode::Variable;563 } else if (repl_mode_value == "command") {564 default_repl_mode = ReplMode::Command;565 } else {566 llvm::errs() << "'" << repl_mode_value567 << "' is not a valid option, use 'variable', 'command' or "568 "'auto'.\n";569 return EXIT_FAILURE;570 }571 }572 573 if (llvm::opt::Arg *target_arg = input_args.getLastArg(OPT_launch_target)) {574 if (llvm::opt::Arg *comm_file = input_args.getLastArg(OPT_comm_file)) {575 lldb::pid_t pid = LLDB_INVALID_PROCESS_ID;576 llvm::opt::Arg *debugger_pid = input_args.getLastArg(OPT_debugger_pid);577 if (debugger_pid) {578 llvm::StringRef debugger_pid_value = debugger_pid->getValue();579 if (debugger_pid_value.getAsInteger(10, pid)) {580 llvm::errs() << "'" << debugger_pid_value581 << "' is not a valid "582 "PID\n";583 return EXIT_FAILURE;584 }585 }586 int target_args_pos = argc;587 for (int i = 0; i < argc; i++) {588 if (strcmp(argv[i], "--launch-target") == 0) {589 target_args_pos = i + 1;590 break;591 }592 }593 llvm::StringRef stdio = input_args.getLastArgValue(OPT_stdio);594 if (llvm::Error err =595 LaunchRunInTerminalTarget(*target_arg, comm_file->getValue(), pid,596 stdio, argv + target_args_pos)) {597 llvm::errs() << llvm::toString(std::move(err)) << '\n';598 return EXIT_FAILURE;599 }600 } else {601 llvm::errs() << "\"--launch-target\" requires \"--comm-file\" to be "602 "specified\n";603 return EXIT_FAILURE;604 }605 }606 607 std::string connection;608 if (auto *arg = input_args.getLastArg(OPT_connection)) {609 const auto *path = arg->getValue();610 connection.assign(path);611 }612 613 std::optional<std::chrono::seconds> connection_timeout_seconds;614 if (llvm::opt::Arg *connection_timeout_arg =615 input_args.getLastArg(OPT_connection_timeout)) {616 if (!connection.empty()) {617 llvm::StringRef connection_timeout_string_value =618 connection_timeout_arg->getValue();619 int connection_timeout_int_value;620 if (connection_timeout_string_value.getAsInteger(621 10, connection_timeout_int_value)) {622 llvm::errs() << "'" << connection_timeout_string_value623 << "' is not a valid connection timeout value\n";624 return EXIT_FAILURE;625 }626 // Ignore non-positive values.627 if (connection_timeout_int_value > 0)628 connection_timeout_seconds =629 std::chrono::seconds(connection_timeout_int_value);630 } else {631 llvm::errs()632 << "\"--connection-timeout\" requires \"--connection\" to be "633 "specified\n";634 return EXIT_FAILURE;635 }636 }637 638#if !defined(_WIN32)639 if (input_args.hasArg(OPT_wait_for_debugger)) {640 printf("Paused waiting for debugger to attach (pid = %i)...\n", getpid());641 pause();642 }643#endif644 645 std::unique_ptr<Log> log = nullptr;646 const char *log_file_path = getenv("LLDBDAP_LOG");647 if (log_file_path) {648 std::error_code EC;649 log = std::make_unique<Log>(log_file_path, EC);650 if (EC) {651 llvm::logAllUnhandledErrors(llvm::errorCodeToError(EC), llvm::errs(),652 "Failed to create log file: ");653 return EXIT_FAILURE;654 }655 }656 657 // Initialize LLDB first before we do anything.658 lldb::SBError error = lldb::SBDebugger::InitializeWithErrorHandling();659 if (error.Fail()) {660 lldb::SBStream os;661 error.GetDescription(os);662 llvm::errs() << "lldb initialize failed: " << os.GetData() << "\n";663 return EXIT_FAILURE;664 }665 666 // Create a memory monitor. This can return nullptr if the host platform is667 // not supported.668 std::unique_ptr<lldb_private::MemoryMonitor> memory_monitor =669 lldb_private::MemoryMonitor::Create([log = log.get()]() {670 DAP_LOG(log, "memory pressure detected");671 lldb::SBDebugger::MemoryPressureDetected();672 });673 674 if (memory_monitor)675 memory_monitor->Start();676 677 // Terminate the debugger before the C++ destructor chain kicks in.678 auto terminate_debugger = llvm::make_scope_exit([&] {679 if (memory_monitor)680 memory_monitor->Stop();681 lldb::SBDebugger::Terminate();682 });683 684 std::vector<std::string> pre_init_commands;685 for (const std::string &arg :686 input_args.getAllArgValues(OPT_pre_init_command)) {687 pre_init_commands.push_back(arg);688 }689 690 bool no_lldbinit = input_args.hasArg(OPT_no_lldbinit);691 692 if (!connection.empty()) {693 auto maybeProtoclAndName = validateConnection(connection);694 if (auto Err = maybeProtoclAndName.takeError()) {695 llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(),696 "Invalid connection: ");697 return EXIT_FAILURE;698 }699 700 Socket::SocketProtocol protocol;701 std::string name;702 std::tie(protocol, name) = *maybeProtoclAndName;703 if (auto Err = serveConnection(protocol, name, log.get(), default_repl_mode,704 pre_init_commands, no_lldbinit,705 connection_timeout_seconds)) {706 llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(),707 "Connection failed: ");708 return EXIT_FAILURE;709 }710 711 return EXIT_SUCCESS;712 }713 714#if defined(_WIN32)715 // Windows opens stdout and stdin in text mode which converts \n to 13,10716 // while the value is just 10 on Darwin/Linux. Setting the file mode to717 // binary fixes this.718 int result = _setmode(fileno(stdout), _O_BINARY);719 assert(result);720 result = _setmode(fileno(stdin), _O_BINARY);721 UNUSED_IF_ASSERT_DISABLED(result);722 assert(result);723#endif724 725 int stdout_fd = DuplicateFileDescriptor(fileno(stdout));726 if (stdout_fd == -1) {727 llvm::logAllUnhandledErrors(728 llvm::errorCodeToError(llvm::errnoAsErrorCode()), llvm::errs(),729 "Failed to configure stdout redirect: ");730 return EXIT_FAILURE;731 }732 733 lldb::IOObjectSP input = std::make_shared<NativeFile>(734 fileno(stdin), File::eOpenOptionReadOnly, NativeFile::Unowned);735 lldb::IOObjectSP output = std::make_shared<NativeFile>(736 stdout_fd, File::eOpenOptionWriteOnly, NativeFile::Unowned);737 738 constexpr llvm::StringLiteral client_name = "stdio";739 MainLoop loop;740 Transport transport(client_name, log.get(), input, output);741 DAP dap(log.get(), default_repl_mode, pre_init_commands, no_lldbinit,742 client_name, transport, loop);743 744 // stdout/stderr redirection to the IDE's console745 if (auto Err = dap.ConfigureIO(stdout, stderr)) {746 llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(),747 "Failed to configure stdout redirect: ");748 return EXIT_FAILURE;749 }750 751 // Register the DAP session with the global manager for stdio mode.752 // This is needed for the event handling to find the correct DAP instance.753 DAPSessionManager::GetInstance().RegisterSession(&loop, &dap);754 755 // used only by TestVSCode_redirection_to_console.py756 if (getenv("LLDB_DAP_TEST_STDOUT_STDERR_REDIRECTION") != nullptr)757 redirection_test();758 759 if (auto Err = dap.Loop()) {760 DAP_LOG(log.get(), "({0}) DAP session error: {1}", client_name,761 llvm::toStringWithoutConsuming(Err));762 llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(),763 "DAP session error: ");764 DAPSessionManager::GetInstance().UnregisterSession(&loop);765 return EXIT_FAILURE;766 }767 DAPSessionManager::GetInstance().UnregisterSession(&loop);768 return EXIT_SUCCESS;769}770