| 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-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | |
| 9 | #include "DAP.h" |
| 10 | #include "DAPLog.h" |
| 11 | #include "EventHelper.h" |
| 12 | #include "Handler/RequestHandler.h" |
| 13 | #include "RunInTerminal.h" |
| 14 | #include "Transport.h" |
| 15 | #include "lldb/API/SBDebugger.h" |
| 16 | #include "lldb/API/SBStream.h" |
| 17 | #include "lldb/Host/Config.h" |
| 18 | #include "lldb/Host/File.h" |
| 19 | #include "lldb/Host/MainLoop.h" |
| 20 | #include "lldb/Host/MainLoopBase.h" |
| 21 | #include "lldb/Host/MemoryMonitor.h" |
| 22 | #include "lldb/Host/Socket.h" |
| 23 | #include "lldb/Utility/Status.h" |
| 24 | #include "lldb/Utility/UriParser.h" |
| 25 | #include "lldb/lldb-forward.h" |
| 26 | #include "llvm/ADT/ArrayRef.h" |
| 27 | #include "llvm/ADT/ScopeExit.h" |
| 28 | #include "llvm/ADT/StringExtras.h" |
| 29 | #include "llvm/ADT/StringRef.h" |
| 30 | #include "llvm/Option/Arg.h" |
| 31 | #include "llvm/Option/ArgList.h" |
| 32 | #include "llvm/Option/OptTable.h" |
| 33 | #include "llvm/Option/Option.h" |
| 34 | #include "llvm/Support/CommandLine.h" |
| 35 | #include "llvm/Support/Error.h" |
| 36 | #include "llvm/Support/FileSystem.h" |
| 37 | #include "llvm/Support/InitLLVM.h" |
| 38 | #include "llvm/Support/Path.h" |
| 39 | #include "llvm/Support/PrettyStackTrace.h" |
| 40 | #include "llvm/Support/Signals.h" |
| 41 | #include "llvm/Support/Threading.h" |
| 42 | #include "llvm/Support/raw_ostream.h" |
| 43 | #include <condition_variable> |
| 44 | #include <cstdio> |
| 45 | #include <cstdlib> |
| 46 | #include <fcntl.h> |
| 47 | #include <map> |
| 48 | #include <memory> |
| 49 | #include <mutex> |
| 50 | #include <string> |
| 51 | #include <system_error> |
| 52 | #include <thread> |
| 53 | #include <utility> |
| 54 | #include <vector> |
| 55 | |
| 56 | #if defined(_WIN32) |
| 57 | // We need to #define NOMINMAX in order to skip `min()` and `max()` macro |
| 58 | // definitions that conflict with other system headers. |
| 59 | // We also need to #undef GetObject (which is defined to GetObjectW) because |
| 60 | // the JSON code we use also has methods named `GetObject()` and we conflict |
| 61 | // against these. |
| 62 | #define NOMINMAX |
| 63 | #include <windows.h> |
| 64 | #undef GetObject |
| 65 | #include <io.h> |
| 66 | typedef int socklen_t; |
| 67 | #else |
| 68 | #include <netinet/in.h> |
| 69 | #include <sys/socket.h> |
| 70 | #include <sys/un.h> |
| 71 | #include <unistd.h> |
| 72 | #endif |
| 73 | |
| 74 | #if defined(__linux__) |
| 75 | #include <sys/prctl.h> |
| 76 | #endif |
| 77 | |
| 78 | using namespace lldb_dap; |
| 79 | using lldb_private::File; |
| 80 | using lldb_private::IOObject; |
| 81 | using lldb_private::MainLoop; |
| 82 | using lldb_private::MainLoopBase; |
| 83 | using lldb_private::NativeFile; |
| 84 | using lldb_private::Socket; |
| 85 | using lldb_private::Status; |
| 86 | |
| 87 | namespace { |
| 88 | using namespace llvm::opt; |
| 89 | |
| 90 | enum ID { |
| 91 | OPT_INVALID = 0, // This is not an option ID. |
| 92 | #define OPTION(...) LLVM_MAKE_OPT_ID(__VA_ARGS__), |
| 93 | #include "Options.inc" |
| 94 | #undef OPTION |
| 95 | }; |
| 96 | |
| 97 | #define OPTTABLE_STR_TABLE_CODE |
| 98 | #include "Options.inc" |
| 99 | #undef OPTTABLE_STR_TABLE_CODE |
| 100 | |
| 101 | #define OPTTABLE_PREFIXES_TABLE_CODE |
| 102 | #include "Options.inc" |
| 103 | #undef OPTTABLE_PREFIXES_TABLE_CODE |
| 104 | |
| 105 | static constexpr llvm::opt::OptTable::Info InfoTable[] = { |
| 106 | #define OPTION(...) LLVM_CONSTRUCT_OPT_INFO(__VA_ARGS__), |
| 107 | #include "Options.inc" |
| 108 | #undef OPTION |
| 109 | }; |
| 110 | class LLDBDAPOptTable : public llvm::opt::GenericOptTable { |
| 111 | public: |
| 112 | LLDBDAPOptTable() |
| 113 | : llvm::opt::GenericOptTable(OptionStrTable, OptionPrefixesTable, |
| 114 | InfoTable, true) {} |
| 115 | }; |
| 116 | } // anonymous namespace |
| 117 | |
| 118 | static void PrintHelp(LLDBDAPOptTable &table, llvm::StringRef tool_name) { |
| 119 | std::string usage_str = tool_name.str() + " options" ; |
| 120 | table.printHelp(OS&: llvm::outs(), Usage: usage_str.c_str(), Title: "LLDB DAP" , ShowHidden: false); |
| 121 | |
| 122 | llvm::outs() << R"___( |
| 123 | EXAMPLES: |
| 124 | The debug adapter can be started in two modes. |
| 125 | |
| 126 | Running lldb-dap without any arguments will start communicating with the |
| 127 | parent over stdio. Passing a --connection URI will cause lldb-dap to listen |
| 128 | for a connection in the specified mode. |
| 129 | |
| 130 | lldb-dap --connection connection://localhost:<port> |
| 131 | |
| 132 | Passing --wait-for-debugger will pause the process at startup and wait for a |
| 133 | debugger to attach to the process. |
| 134 | |
| 135 | lldb-dap -g |
| 136 | )___" ; |
| 137 | } |
| 138 | |
| 139 | static void PrintVersion() { |
| 140 | llvm::outs() << "lldb-dap: " ; |
| 141 | llvm::cl::PrintVersionMessage(); |
| 142 | llvm::outs() << "liblldb: " << lldb::SBDebugger::GetVersionString() << '\n'; |
| 143 | } |
| 144 | |
| 145 | // If --launch-target is provided, this instance of lldb-dap becomes a |
| 146 | // runInTerminal launcher. It will ultimately launch the program specified in |
| 147 | // the --launch-target argument, which is the original program the user wanted |
| 148 | // to debug. This is done in such a way that the actual debug adapter can |
| 149 | // place breakpoints at the beginning of the program. |
| 150 | // |
| 151 | // The launcher will communicate with the debug adapter using a fifo file in the |
| 152 | // directory specified in the --comm-file argument. |
| 153 | // |
| 154 | // Regarding the actual flow, this launcher will first notify the debug adapter |
| 155 | // of its pid. Then, the launcher will be in a pending state waiting to be |
| 156 | // attached by the adapter. |
| 157 | // |
| 158 | // Once attached and resumed, the launcher will exec and become the program |
| 159 | // specified by --launch-target, which is the original target the |
| 160 | // user wanted to run. |
| 161 | // |
| 162 | // In case of errors launching the target, a suitable error message will be |
| 163 | // emitted to the debug adapter. |
| 164 | static llvm::Error LaunchRunInTerminalTarget(llvm::opt::Arg &target_arg, |
| 165 | llvm::StringRef comm_file, |
| 166 | lldb::pid_t debugger_pid, |
| 167 | char *argv[]) { |
| 168 | #if defined(_WIN32) |
| 169 | return llvm::createStringError( |
| 170 | "runInTerminal is only supported on POSIX systems" ); |
| 171 | #else |
| 172 | |
| 173 | // On Linux with the Yama security module enabled, a process can only attach |
| 174 | // to its descendants by default. In the runInTerminal case the target |
| 175 | // process is launched by the client so we need to allow tracing explicitly. |
| 176 | #if defined(__linux__) |
| 177 | if (debugger_pid != LLDB_INVALID_PROCESS_ID) |
| 178 | (void)prctl(PR_SET_PTRACER, debugger_pid, 0, 0, 0); |
| 179 | #endif |
| 180 | |
| 181 | RunInTerminalLauncherCommChannel comm_channel(comm_file); |
| 182 | if (llvm::Error err = comm_channel.NotifyPid()) |
| 183 | return err; |
| 184 | |
| 185 | // We will wait to be attached with a timeout. We don't wait indefinitely |
| 186 | // using a signal to prevent being paused forever. |
| 187 | |
| 188 | // This env var should be used only for tests. |
| 189 | const char *timeout_env_var = getenv(name: "LLDB_DAP_RIT_TIMEOUT_IN_MS" ); |
| 190 | int timeout_in_ms = |
| 191 | timeout_env_var != nullptr ? atoi(nptr: timeout_env_var) : 20000; |
| 192 | if (llvm::Error err = comm_channel.WaitUntilDebugAdapterAttaches( |
| 193 | timeout: std::chrono::milliseconds(timeout_in_ms))) { |
| 194 | return err; |
| 195 | } |
| 196 | |
| 197 | const char *target = target_arg.getValue(); |
| 198 | execvp(file: target, argv: argv); |
| 199 | |
| 200 | std::string error = std::strerror(errno); |
| 201 | comm_channel.NotifyError(error); |
| 202 | return llvm::createStringError(EC: llvm::inconvertibleErrorCode(), |
| 203 | S: std::move(error)); |
| 204 | #endif |
| 205 | } |
| 206 | |
| 207 | /// used only by TestVSCode_redirection_to_console.py |
| 208 | static void redirection_test() { |
| 209 | printf(format: "stdout message\n" ); |
| 210 | fprintf(stderr, format: "stderr message\n" ); |
| 211 | fflush(stdout); |
| 212 | fflush(stderr); |
| 213 | } |
| 214 | |
| 215 | /// Duplicates a file descriptor, setting FD_CLOEXEC if applicable. |
| 216 | static int DuplicateFileDescriptor(int fd) { |
| 217 | #if defined(F_DUPFD_CLOEXEC) |
| 218 | // Ensure FD_CLOEXEC is set. |
| 219 | return ::fcntl(fd: fd, F_DUPFD_CLOEXEC, 0); |
| 220 | #else |
| 221 | return ::dup(fd); |
| 222 | #endif |
| 223 | } |
| 224 | |
| 225 | static llvm::Expected<std::pair<Socket::SocketProtocol, std::string>> |
| 226 | validateConnection(llvm::StringRef conn) { |
| 227 | auto uri = lldb_private::URI::Parse(uri: conn); |
| 228 | |
| 229 | if (uri && (uri->scheme == "tcp" || uri->scheme == "connect" || |
| 230 | !uri->hostname.empty() || uri->port)) { |
| 231 | return std::make_pair( |
| 232 | x: Socket::ProtocolTcp, |
| 233 | y: formatv(Fmt: "[{0}]:{1}" , Vals: uri->hostname.empty() ? "0.0.0.0" : uri->hostname, |
| 234 | Vals: uri->port.value_or(u: 0))); |
| 235 | } |
| 236 | |
| 237 | if (uri && (uri->scheme == "unix" || uri->scheme == "unix-connect" || |
| 238 | uri->path != "/" )) { |
| 239 | return std::make_pair(x: Socket::ProtocolUnixDomain, y: uri->path.str()); |
| 240 | } |
| 241 | |
| 242 | return llvm::createStringError( |
| 243 | Fmt: "Unsupported connection specifier, expected 'unix-connect:///path' or " |
| 244 | "'connect://[host]:port', got '%s'." , |
| 245 | Vals: conn.str().c_str()); |
| 246 | } |
| 247 | |
| 248 | static llvm::Error |
| 249 | serveConnection(const Socket::SocketProtocol &protocol, const std::string &name, |
| 250 | Log *log, const ReplMode default_repl_mode, |
| 251 | const std::vector<std::string> &pre_init_commands) { |
| 252 | Status status; |
| 253 | static std::unique_ptr<Socket> listener = Socket::Create(protocol, error&: status); |
| 254 | if (status.Fail()) { |
| 255 | return status.takeError(); |
| 256 | } |
| 257 | |
| 258 | status = listener->Listen(name, /*backlog=*/5); |
| 259 | if (status.Fail()) { |
| 260 | return status.takeError(); |
| 261 | } |
| 262 | |
| 263 | std::string address = llvm::join(R: listener->GetListeningConnectionURI(), Separator: ", " ); |
| 264 | DAP_LOG(log, "started with connection listeners {0}" , address); |
| 265 | |
| 266 | llvm::outs() << "Listening for: " << address << "\n" ; |
| 267 | // Ensure listening address are flushed for calles to retrieve the resolve |
| 268 | // address. |
| 269 | llvm::outs().flush(); |
| 270 | |
| 271 | static MainLoop g_loop; |
| 272 | llvm::sys::SetInterruptFunction([]() { |
| 273 | g_loop.AddPendingCallback( |
| 274 | callback: [](MainLoopBase &loop) { loop.RequestTermination(); }); |
| 275 | }); |
| 276 | std::condition_variable dap_sessions_condition; |
| 277 | std::mutex dap_sessions_mutex; |
| 278 | std::map<IOObject *, DAP *> dap_sessions; |
| 279 | unsigned int clientCount = 0; |
| 280 | auto handle = listener->Accept(loop&: g_loop, sock_cb: [=, &dap_sessions_condition, |
| 281 | &dap_sessions_mutex, &dap_sessions, |
| 282 | &clientCount]( |
| 283 | std::unique_ptr<Socket> sock) { |
| 284 | std::string client_name = llvm::formatv(Fmt: "client_{0}" , Vals: clientCount++).str(); |
| 285 | DAP_LOG(log, "({0}) client connected" , client_name); |
| 286 | |
| 287 | lldb::IOObjectSP io(std::move(sock)); |
| 288 | |
| 289 | // Move the client into a background thread to unblock accepting the next |
| 290 | // client. |
| 291 | std::thread client([=, &dap_sessions_condition, &dap_sessions_mutex, |
| 292 | &dap_sessions]() { |
| 293 | llvm::set_thread_name(client_name + ".runloop" ); |
| 294 | Transport transport(client_name, log, io, io); |
| 295 | DAP dap(log, default_repl_mode, pre_init_commands, transport); |
| 296 | |
| 297 | if (auto Err = dap.ConfigureIO()) { |
| 298 | llvm::logAllUnhandledErrors(E: std::move(Err), OS&: llvm::errs(), |
| 299 | ErrorBanner: "Failed to configure stdout redirect: " ); |
| 300 | return; |
| 301 | } |
| 302 | |
| 303 | { |
| 304 | std::scoped_lock<std::mutex> lock(dap_sessions_mutex); |
| 305 | dap_sessions[io.get()] = &dap; |
| 306 | } |
| 307 | |
| 308 | if (auto Err = dap.Loop()) { |
| 309 | llvm::logAllUnhandledErrors(E: std::move(Err), OS&: llvm::errs(), |
| 310 | ErrorBanner: "DAP session (" + client_name + |
| 311 | ") error: " ); |
| 312 | } |
| 313 | |
| 314 | DAP_LOG(log, "({0}) client disconnected" , client_name); |
| 315 | std::unique_lock<std::mutex> lock(dap_sessions_mutex); |
| 316 | dap_sessions.erase(x: io.get()); |
| 317 | std::notify_all_at_thread_exit(dap_sessions_condition, std::move(lock)); |
| 318 | }); |
| 319 | client.detach(); |
| 320 | }); |
| 321 | |
| 322 | if (auto Err = handle.takeError()) { |
| 323 | return Err; |
| 324 | } |
| 325 | |
| 326 | status = g_loop.Run(); |
| 327 | if (status.Fail()) { |
| 328 | return status.takeError(); |
| 329 | } |
| 330 | |
| 331 | DAP_LOG( |
| 332 | log, |
| 333 | "lldb-dap server shutdown requested, disconnecting remaining clients..." ); |
| 334 | |
| 335 | bool client_failed = false; |
| 336 | { |
| 337 | std::scoped_lock<std::mutex> lock(dap_sessions_mutex); |
| 338 | for (auto [sock, dap] : dap_sessions) { |
| 339 | if (llvm::Error error = dap->Disconnect()) { |
| 340 | client_failed = true; |
| 341 | llvm::errs() << "DAP client " << dap->transport.GetClientName() |
| 342 | << " disconnected failed: " |
| 343 | << llvm::toString(E: std::move(error)) << "\n" ; |
| 344 | } |
| 345 | // Close the socket to ensure the DAP::Loop read finishes. |
| 346 | sock->Close(); |
| 347 | } |
| 348 | } |
| 349 | |
| 350 | // Wait for all clients to finish disconnecting. |
| 351 | std::unique_lock<std::mutex> lock(dap_sessions_mutex); |
| 352 | dap_sessions_condition.wait(lock&: lock, p: [&] { return dap_sessions.empty(); }); |
| 353 | |
| 354 | if (client_failed) |
| 355 | return llvm::make_error<llvm::StringError>( |
| 356 | Args: "disconnecting all clients failed" , Args: llvm::inconvertibleErrorCode()); |
| 357 | |
| 358 | return llvm::Error::success(); |
| 359 | } |
| 360 | |
| 361 | int main(int argc, char *argv[]) { |
| 362 | llvm::InitLLVM IL(argc, argv, /*InstallPipeSignalExitHandler=*/false); |
| 363 | #if !defined(__APPLE__) |
| 364 | llvm::setBugReportMsg("PLEASE submit a bug report to " LLDB_BUG_REPORT_URL |
| 365 | " and include the crash backtrace.\n" ); |
| 366 | #else |
| 367 | llvm::setBugReportMsg("PLEASE submit a bug report to " LLDB_BUG_REPORT_URL |
| 368 | " and include the crash report from " |
| 369 | "~/Library/Logs/DiagnosticReports/.\n" ); |
| 370 | #endif |
| 371 | |
| 372 | llvm::SmallString<256> program_path(argv[0]); |
| 373 | llvm::sys::fs::make_absolute(path&: program_path); |
| 374 | DAP::debug_adapter_path = program_path; |
| 375 | |
| 376 | LLDBDAPOptTable T; |
| 377 | unsigned MAI, MAC; |
| 378 | llvm::ArrayRef<const char *> ArgsArr = llvm::ArrayRef(argv + 1, argc); |
| 379 | llvm::opt::InputArgList input_args = T.ParseArgs(Args: ArgsArr, MissingArgIndex&: MAI, MissingArgCount&: MAC); |
| 380 | |
| 381 | if (input_args.hasArg(OPT_help)) { |
| 382 | PrintHelp(table&: T, tool_name: llvm::sys::path::filename(path: argv[0])); |
| 383 | return EXIT_SUCCESS; |
| 384 | } |
| 385 | |
| 386 | if (input_args.hasArg(OPT_version)) { |
| 387 | PrintVersion(); |
| 388 | return EXIT_SUCCESS; |
| 389 | } |
| 390 | |
| 391 | ReplMode default_repl_mode = ReplMode::Auto; |
| 392 | if (input_args.hasArg(OPT_repl_mode)) { |
| 393 | llvm::opt::Arg *repl_mode = input_args.getLastArg(OPT_repl_mode); |
| 394 | llvm::StringRef repl_mode_value = repl_mode->getValue(); |
| 395 | if (repl_mode_value == "auto" ) { |
| 396 | default_repl_mode = ReplMode::Auto; |
| 397 | } else if (repl_mode_value == "variable" ) { |
| 398 | default_repl_mode = ReplMode::Variable; |
| 399 | } else if (repl_mode_value == "command" ) { |
| 400 | default_repl_mode = ReplMode::Command; |
| 401 | } else { |
| 402 | llvm::errs() << "'" << repl_mode_value |
| 403 | << "' is not a valid option, use 'variable', 'command' or " |
| 404 | "'auto'.\n" ; |
| 405 | return EXIT_FAILURE; |
| 406 | } |
| 407 | } |
| 408 | |
| 409 | if (llvm::opt::Arg *target_arg = input_args.getLastArg(OPT_launch_target)) { |
| 410 | if (llvm::opt::Arg *comm_file = input_args.getLastArg(OPT_comm_file)) { |
| 411 | lldb::pid_t pid = LLDB_INVALID_PROCESS_ID; |
| 412 | llvm::opt::Arg *debugger_pid = input_args.getLastArg(OPT_debugger_pid); |
| 413 | if (debugger_pid) { |
| 414 | llvm::StringRef debugger_pid_value = debugger_pid->getValue(); |
| 415 | if (debugger_pid_value.getAsInteger(Radix: 10, Result&: pid)) { |
| 416 | llvm::errs() << "'" << debugger_pid_value |
| 417 | << "' is not a valid " |
| 418 | "PID\n" ; |
| 419 | return EXIT_FAILURE; |
| 420 | } |
| 421 | } |
| 422 | int target_args_pos = argc; |
| 423 | for (int i = 0; i < argc; i++) { |
| 424 | if (strcmp(s1: argv[i], s2: "--launch-target" ) == 0) { |
| 425 | target_args_pos = i + 1; |
| 426 | break; |
| 427 | } |
| 428 | } |
| 429 | if (llvm::Error err = |
| 430 | LaunchRunInTerminalTarget(target_arg&: *target_arg, comm_file: comm_file->getValue(), debugger_pid: pid, |
| 431 | argv: argv + target_args_pos)) { |
| 432 | llvm::errs() << llvm::toString(E: std::move(err)) << '\n'; |
| 433 | return EXIT_FAILURE; |
| 434 | } |
| 435 | } else { |
| 436 | llvm::errs() << "\"--launch-target\" requires \"--comm-file\" to be " |
| 437 | "specified\n" ; |
| 438 | return EXIT_FAILURE; |
| 439 | } |
| 440 | } |
| 441 | |
| 442 | std::string connection; |
| 443 | if (auto *arg = input_args.getLastArg(Ids: OPT_connection)) { |
| 444 | const auto *path = arg->getValue(); |
| 445 | connection.assign(s: path); |
| 446 | } |
| 447 | |
| 448 | #if !defined(_WIN32) |
| 449 | if (input_args.hasArg(OPT_wait_for_debugger)) { |
| 450 | printf(format: "Paused waiting for debugger to attach (pid = %i)...\n" , getpid()); |
| 451 | pause(); |
| 452 | } |
| 453 | #endif |
| 454 | |
| 455 | std::unique_ptr<Log> log = nullptr; |
| 456 | const char *log_file_path = getenv(name: "LLDBDAP_LOG" ); |
| 457 | if (log_file_path) { |
| 458 | std::error_code EC; |
| 459 | log = std::make_unique<Log>(args&: log_file_path, args&: EC); |
| 460 | if (EC) { |
| 461 | llvm::logAllUnhandledErrors(E: llvm::errorCodeToError(EC), OS&: llvm::errs(), |
| 462 | ErrorBanner: "Failed to create log file: " ); |
| 463 | return EXIT_FAILURE; |
| 464 | } |
| 465 | } |
| 466 | |
| 467 | // Initialize LLDB first before we do anything. |
| 468 | lldb::SBError error = lldb::SBDebugger::InitializeWithErrorHandling(); |
| 469 | if (error.Fail()) { |
| 470 | lldb::SBStream os; |
| 471 | error.GetDescription(description&: os); |
| 472 | llvm::errs() << "lldb initialize failed: " << os.GetData() << "\n" ; |
| 473 | return EXIT_FAILURE; |
| 474 | } |
| 475 | |
| 476 | // Create a memory monitor. This can return nullptr if the host platform is |
| 477 | // not supported. |
| 478 | std::unique_ptr<lldb_private::MemoryMonitor> memory_monitor = |
| 479 | lldb_private::MemoryMonitor::Create(callback: [log = log.get()]() { |
| 480 | DAP_LOG(log, "memory pressure detected" ); |
| 481 | lldb::SBDebugger::MemoryPressureDetected(); |
| 482 | }); |
| 483 | |
| 484 | if (memory_monitor) |
| 485 | memory_monitor->Start(); |
| 486 | |
| 487 | // Terminate the debugger before the C++ destructor chain kicks in. |
| 488 | auto terminate_debugger = llvm::make_scope_exit(F: [&] { |
| 489 | if (memory_monitor) |
| 490 | memory_monitor->Stop(); |
| 491 | lldb::SBDebugger::Terminate(); |
| 492 | }); |
| 493 | |
| 494 | std::vector<std::string> pre_init_commands; |
| 495 | for (const std::string &arg : |
| 496 | input_args.getAllArgValues(OPT_pre_init_command)) { |
| 497 | pre_init_commands.push_back(arg); |
| 498 | } |
| 499 | |
| 500 | if (!connection.empty()) { |
| 501 | auto maybeProtoclAndName = validateConnection(conn: connection); |
| 502 | if (auto Err = maybeProtoclAndName.takeError()) { |
| 503 | llvm::logAllUnhandledErrors(E: std::move(Err), OS&: llvm::errs(), |
| 504 | ErrorBanner: "Invalid connection: " ); |
| 505 | return EXIT_FAILURE; |
| 506 | } |
| 507 | |
| 508 | Socket::SocketProtocol protocol; |
| 509 | std::string name; |
| 510 | std::tie(args&: protocol, args&: name) = *maybeProtoclAndName; |
| 511 | if (auto Err = serveConnection(protocol, name, log: log.get(), default_repl_mode, |
| 512 | pre_init_commands)) { |
| 513 | llvm::logAllUnhandledErrors(E: std::move(Err), OS&: llvm::errs(), |
| 514 | ErrorBanner: "Connection failed: " ); |
| 515 | return EXIT_FAILURE; |
| 516 | } |
| 517 | |
| 518 | return EXIT_SUCCESS; |
| 519 | } |
| 520 | |
| 521 | #if defined(_WIN32) |
| 522 | // Windows opens stdout and stdin in text mode which converts \n to 13,10 |
| 523 | // while the value is just 10 on Darwin/Linux. Setting the file mode to |
| 524 | // binary fixes this. |
| 525 | int result = _setmode(fileno(stdout), _O_BINARY); |
| 526 | assert(result); |
| 527 | result = _setmode(fileno(stdin), _O_BINARY); |
| 528 | UNUSED_IF_ASSERT_DISABLED(result); |
| 529 | assert(result); |
| 530 | #endif |
| 531 | |
| 532 | int stdout_fd = DuplicateFileDescriptor(fd: fileno(stdout)); |
| 533 | if (stdout_fd == -1) { |
| 534 | llvm::logAllUnhandledErrors( |
| 535 | E: llvm::errorCodeToError(EC: llvm::errnoAsErrorCode()), OS&: llvm::errs(), |
| 536 | ErrorBanner: "Failed to configure stdout redirect: " ); |
| 537 | return EXIT_FAILURE; |
| 538 | } |
| 539 | |
| 540 | lldb::IOObjectSP input = std::make_shared<NativeFile>( |
| 541 | args: fileno(stdin), args: File::eOpenOptionReadOnly, args: NativeFile::Unowned); |
| 542 | lldb::IOObjectSP output = std::make_shared<NativeFile>( |
| 543 | args&: stdout_fd, args: File::eOpenOptionWriteOnly, args: NativeFile::Unowned); |
| 544 | |
| 545 | constexpr llvm::StringLiteral client_name = "stdio" ; |
| 546 | Transport transport(client_name, log.get(), input, output); |
| 547 | DAP dap(log.get(), default_repl_mode, pre_init_commands, transport); |
| 548 | |
| 549 | // stdout/stderr redirection to the IDE's console |
| 550 | if (auto Err = dap.ConfigureIO(stdout, stderr)) { |
| 551 | llvm::logAllUnhandledErrors(E: std::move(Err), OS&: llvm::errs(), |
| 552 | ErrorBanner: "Failed to configure stdout redirect: " ); |
| 553 | return EXIT_FAILURE; |
| 554 | } |
| 555 | |
| 556 | // used only by TestVSCode_redirection_to_console.py |
| 557 | if (getenv(name: "LLDB_DAP_TEST_STDOUT_STDERR_REDIRECTION" ) != nullptr) |
| 558 | redirection_test(); |
| 559 | |
| 560 | if (auto Err = dap.Loop()) { |
| 561 | DAP_LOG(log.get(), "({0}) DAP session error: {1}" , client_name, |
| 562 | llvm::toStringWithoutConsuming(Err)); |
| 563 | llvm::logAllUnhandledErrors(E: std::move(Err), OS&: llvm::errs(), |
| 564 | ErrorBanner: "DAP session error: " ); |
| 565 | return EXIT_FAILURE; |
| 566 | } |
| 567 | return EXIT_SUCCESS; |
| 568 | } |
| 569 | |