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>
66typedef 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
78using namespace lldb_dap;
79using lldb_private::File;
80using lldb_private::IOObject;
81using lldb_private::MainLoop;
82using lldb_private::MainLoopBase;
83using lldb_private::NativeFile;
84using lldb_private::Socket;
85using lldb_private::Status;
86
87namespace {
88using namespace llvm::opt;
89
90enum 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
105static constexpr llvm::opt::OptTable::Info InfoTable[] = {
106#define OPTION(...) LLVM_CONSTRUCT_OPT_INFO(__VA_ARGS__),
107#include "Options.inc"
108#undef OPTION
109};
110class LLDBDAPOptTable : public llvm::opt::GenericOptTable {
111public:
112 LLDBDAPOptTable()
113 : llvm::opt::GenericOptTable(OptionStrTable, OptionPrefixesTable,
114 InfoTable, true) {}
115};
116} // anonymous namespace
117
118static 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"___(
123EXAMPLES:
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 listen://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
139static 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.
164static 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
208static 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.
216static 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
225static llvm::Expected<std::pair<Socket::SocketProtocol, std::string>>
226validateConnection(llvm::StringRef conn) {
227 auto uri = lldb_private::URI::Parse(uri: conn);
228
229 auto make_error = [conn]() -> llvm::Error {
230 return llvm::createStringError(
231 Fmt: "Unsupported connection specifier, expected 'accept:///path' or "
232 "'listen://[host]:port', got '%s'.",
233 Vals: conn.str().c_str());
234 };
235
236 if (!uri)
237 return make_error();
238
239 std::optional<Socket::ProtocolModePair> protocol_and_mode =
240 Socket::GetProtocolAndMode(scheme: uri->scheme);
241 if (!protocol_and_mode || protocol_and_mode->second != Socket::ModeAccept)
242 return make_error();
243
244 if (protocol_and_mode->first == Socket::ProtocolTcp) {
245 return std::make_pair(
246 x: Socket::ProtocolTcp,
247 y: formatv(Fmt: "[{0}]:{1}", Vals: uri->hostname.empty() ? "0.0.0.0" : uri->hostname,
248 Vals: uri->port.value_or(u: 0)));
249 }
250
251 if (protocol_and_mode->first == Socket::ProtocolUnixDomain)
252 return std::make_pair(x: Socket::ProtocolUnixDomain, y: uri->path.str());
253
254 return make_error();
255}
256
257static llvm::Error
258serveConnection(const Socket::SocketProtocol &protocol, const std::string &name,
259 Log *log, const ReplMode default_repl_mode,
260 const std::vector<std::string> &pre_init_commands) {
261 Status status;
262 static std::unique_ptr<Socket> listener = Socket::Create(protocol, error&: status);
263 if (status.Fail()) {
264 return status.takeError();
265 }
266
267 status = listener->Listen(name, /*backlog=*/5);
268 if (status.Fail()) {
269 return status.takeError();
270 }
271
272 std::string address = llvm::join(R: listener->GetListeningConnectionURI(), Separator: ", ");
273 DAP_LOG(log, "started with connection listeners {0}", address);
274
275 llvm::outs() << "Listening for: " << address << "\n";
276 // Ensure listening address are flushed for calles to retrieve the resolve
277 // address.
278 llvm::outs().flush();
279
280 static MainLoop g_loop;
281 llvm::sys::SetInterruptFunction([]() {
282 g_loop.AddPendingCallback(
283 callback: [](MainLoopBase &loop) { loop.RequestTermination(); });
284 });
285 std::condition_variable dap_sessions_condition;
286 std::mutex dap_sessions_mutex;
287 std::map<IOObject *, DAP *> dap_sessions;
288 unsigned int clientCount = 0;
289 auto handle = listener->Accept(loop&: g_loop, sock_cb: [=, &dap_sessions_condition,
290 &dap_sessions_mutex, &dap_sessions,
291 &clientCount](
292 std::unique_ptr<Socket> sock) {
293 std::string client_name = llvm::formatv(Fmt: "client_{0}", Vals: clientCount++).str();
294 DAP_LOG(log, "({0}) client connected", client_name);
295
296 lldb::IOObjectSP io(std::move(sock));
297
298 // Move the client into a background thread to unblock accepting the next
299 // client.
300 std::thread client([=, &dap_sessions_condition, &dap_sessions_mutex,
301 &dap_sessions]() {
302 llvm::set_thread_name(client_name + ".runloop");
303 Transport transport(client_name, log, io, io);
304 DAP dap(log, default_repl_mode, pre_init_commands, transport);
305
306 if (auto Err = dap.ConfigureIO()) {
307 llvm::logAllUnhandledErrors(E: std::move(Err), OS&: llvm::errs(),
308 ErrorBanner: "Failed to configure stdout redirect: ");
309 return;
310 }
311
312 {
313 std::scoped_lock<std::mutex> lock(dap_sessions_mutex);
314 dap_sessions[io.get()] = &dap;
315 }
316
317 if (auto Err = dap.Loop()) {
318 llvm::logAllUnhandledErrors(E: std::move(Err), OS&: llvm::errs(),
319 ErrorBanner: "DAP session (" + client_name +
320 ") error: ");
321 }
322
323 DAP_LOG(log, "({0}) client disconnected", client_name);
324 std::unique_lock<std::mutex> lock(dap_sessions_mutex);
325 dap_sessions.erase(x: io.get());
326 std::notify_all_at_thread_exit(dap_sessions_condition, std::move(lock));
327 });
328 client.detach();
329 });
330
331 if (auto Err = handle.takeError()) {
332 return Err;
333 }
334
335 status = g_loop.Run();
336 if (status.Fail()) {
337 return status.takeError();
338 }
339
340 DAP_LOG(
341 log,
342 "lldb-dap server shutdown requested, disconnecting remaining clients...");
343
344 bool client_failed = false;
345 {
346 std::scoped_lock<std::mutex> lock(dap_sessions_mutex);
347 for (auto [sock, dap] : dap_sessions) {
348 if (llvm::Error error = dap->Disconnect()) {
349 client_failed = true;
350 llvm::errs() << "DAP client " << dap->transport.GetClientName()
351 << " disconnected failed: "
352 << llvm::toString(E: std::move(error)) << "\n";
353 }
354 }
355 }
356
357 // Wait for all clients to finish disconnecting.
358 std::unique_lock<std::mutex> lock(dap_sessions_mutex);
359 dap_sessions_condition.wait(lock&: lock, p: [&] { return dap_sessions.empty(); });
360
361 if (client_failed)
362 return llvm::make_error<llvm::StringError>(
363 Args: "disconnecting all clients failed", Args: llvm::inconvertibleErrorCode());
364
365 return llvm::Error::success();
366}
367
368int main(int argc, char *argv[]) {
369 llvm::InitLLVM IL(argc, argv, /*InstallPipeSignalExitHandler=*/false);
370#if !defined(__APPLE__)
371 llvm::setBugReportMsg("PLEASE submit a bug report to " LLDB_BUG_REPORT_URL
372 " and include the crash backtrace.\n");
373#else
374 llvm::setBugReportMsg("PLEASE submit a bug report to " LLDB_BUG_REPORT_URL
375 " and include the crash report from "
376 "~/Library/Logs/DiagnosticReports/.\n");
377#endif
378
379 llvm::SmallString<256> program_path(argv[0]);
380 llvm::sys::fs::make_absolute(path&: program_path);
381 DAP::debug_adapter_path = program_path;
382
383 LLDBDAPOptTable T;
384 unsigned MAI, MAC;
385 llvm::ArrayRef<const char *> ArgsArr = llvm::ArrayRef(argv + 1, argc);
386 llvm::opt::InputArgList input_args = T.ParseArgs(Args: ArgsArr, MissingArgIndex&: MAI, MissingArgCount&: MAC);
387
388 if (input_args.hasArg(Ids: OPT_help)) {
389 PrintHelp(table&: T, tool_name: llvm::sys::path::filename(path: argv[0]));
390 return EXIT_SUCCESS;
391 }
392
393 if (input_args.hasArg(Ids: OPT_version)) {
394 PrintVersion();
395 return EXIT_SUCCESS;
396 }
397
398 ReplMode default_repl_mode = ReplMode::Auto;
399 if (input_args.hasArg(Ids: OPT_repl_mode)) {
400 llvm::opt::Arg *repl_mode = input_args.getLastArg(Ids: OPT_repl_mode);
401 llvm::StringRef repl_mode_value = repl_mode->getValue();
402 if (repl_mode_value == "auto") {
403 default_repl_mode = ReplMode::Auto;
404 } else if (repl_mode_value == "variable") {
405 default_repl_mode = ReplMode::Variable;
406 } else if (repl_mode_value == "command") {
407 default_repl_mode = ReplMode::Command;
408 } else {
409 llvm::errs() << "'" << repl_mode_value
410 << "' is not a valid option, use 'variable', 'command' or "
411 "'auto'.\n";
412 return EXIT_FAILURE;
413 }
414 }
415
416 if (llvm::opt::Arg *target_arg = input_args.getLastArg(Ids: OPT_launch_target)) {
417 if (llvm::opt::Arg *comm_file = input_args.getLastArg(Ids: OPT_comm_file)) {
418 lldb::pid_t pid = LLDB_INVALID_PROCESS_ID;
419 llvm::opt::Arg *debugger_pid = input_args.getLastArg(Ids: OPT_debugger_pid);
420 if (debugger_pid) {
421 llvm::StringRef debugger_pid_value = debugger_pid->getValue();
422 if (debugger_pid_value.getAsInteger(Radix: 10, Result&: pid)) {
423 llvm::errs() << "'" << debugger_pid_value
424 << "' is not a valid "
425 "PID\n";
426 return EXIT_FAILURE;
427 }
428 }
429 int target_args_pos = argc;
430 for (int i = 0; i < argc; i++) {
431 if (strcmp(s1: argv[i], s2: "--launch-target") == 0) {
432 target_args_pos = i + 1;
433 break;
434 }
435 }
436 if (llvm::Error err =
437 LaunchRunInTerminalTarget(target_arg&: *target_arg, comm_file: comm_file->getValue(), debugger_pid: pid,
438 argv: argv + target_args_pos)) {
439 llvm::errs() << llvm::toString(E: std::move(err)) << '\n';
440 return EXIT_FAILURE;
441 }
442 } else {
443 llvm::errs() << "\"--launch-target\" requires \"--comm-file\" to be "
444 "specified\n";
445 return EXIT_FAILURE;
446 }
447 }
448
449 std::string connection;
450 if (auto *arg = input_args.getLastArg(Ids: OPT_connection)) {
451 const auto *path = arg->getValue();
452 connection.assign(s: path);
453 }
454
455#if !defined(_WIN32)
456 if (input_args.hasArg(Ids: OPT_wait_for_debugger)) {
457 printf(format: "Paused waiting for debugger to attach (pid = %i)...\n", getpid());
458 pause();
459 }
460#endif
461
462 std::unique_ptr<Log> log = nullptr;
463 const char *log_file_path = getenv(name: "LLDBDAP_LOG");
464 if (log_file_path) {
465 std::error_code EC;
466 log = std::make_unique<Log>(args&: log_file_path, args&: EC);
467 if (EC) {
468 llvm::logAllUnhandledErrors(E: llvm::errorCodeToError(EC), OS&: llvm::errs(),
469 ErrorBanner: "Failed to create log file: ");
470 return EXIT_FAILURE;
471 }
472 }
473
474 // Initialize LLDB first before we do anything.
475 lldb::SBError error = lldb::SBDebugger::InitializeWithErrorHandling();
476 if (error.Fail()) {
477 lldb::SBStream os;
478 error.GetDescription(description&: os);
479 llvm::errs() << "lldb initialize failed: " << os.GetData() << "\n";
480 return EXIT_FAILURE;
481 }
482
483 // Create a memory monitor. This can return nullptr if the host platform is
484 // not supported.
485 std::unique_ptr<lldb_private::MemoryMonitor> memory_monitor =
486 lldb_private::MemoryMonitor::Create(callback: [log = log.get()]() {
487 DAP_LOG(log, "memory pressure detected");
488 lldb::SBDebugger::MemoryPressureDetected();
489 });
490
491 if (memory_monitor)
492 memory_monitor->Start();
493
494 // Terminate the debugger before the C++ destructor chain kicks in.
495 auto terminate_debugger = llvm::make_scope_exit(F: [&] {
496 if (memory_monitor)
497 memory_monitor->Stop();
498 lldb::SBDebugger::Terminate();
499 });
500
501 std::vector<std::string> pre_init_commands;
502 for (const std::string &arg :
503 input_args.getAllArgValues(Id: OPT_pre_init_command)) {
504 pre_init_commands.push_back(x: arg);
505 }
506
507 if (!connection.empty()) {
508 auto maybeProtoclAndName = validateConnection(conn: connection);
509 if (auto Err = maybeProtoclAndName.takeError()) {
510 llvm::logAllUnhandledErrors(E: std::move(Err), OS&: llvm::errs(),
511 ErrorBanner: "Invalid connection: ");
512 return EXIT_FAILURE;
513 }
514
515 Socket::SocketProtocol protocol;
516 std::string name;
517 std::tie(args&: protocol, args&: name) = *maybeProtoclAndName;
518 if (auto Err = serveConnection(protocol, name, log: log.get(), default_repl_mode,
519 pre_init_commands)) {
520 llvm::logAllUnhandledErrors(E: std::move(Err), OS&: llvm::errs(),
521 ErrorBanner: "Connection failed: ");
522 return EXIT_FAILURE;
523 }
524
525 return EXIT_SUCCESS;
526 }
527
528#if defined(_WIN32)
529 // Windows opens stdout and stdin in text mode which converts \n to 13,10
530 // while the value is just 10 on Darwin/Linux. Setting the file mode to
531 // binary fixes this.
532 int result = _setmode(fileno(stdout), _O_BINARY);
533 assert(result);
534 result = _setmode(fileno(stdin), _O_BINARY);
535 UNUSED_IF_ASSERT_DISABLED(result);
536 assert(result);
537#endif
538
539 int stdout_fd = DuplicateFileDescriptor(fd: fileno(stdout));
540 if (stdout_fd == -1) {
541 llvm::logAllUnhandledErrors(
542 E: llvm::errorCodeToError(EC: llvm::errnoAsErrorCode()), OS&: llvm::errs(),
543 ErrorBanner: "Failed to configure stdout redirect: ");
544 return EXIT_FAILURE;
545 }
546
547 lldb::IOObjectSP input = std::make_shared<NativeFile>(
548 args: fileno(stdin), args: File::eOpenOptionReadOnly, args: NativeFile::Unowned);
549 lldb::IOObjectSP output = std::make_shared<NativeFile>(
550 args&: stdout_fd, args: File::eOpenOptionWriteOnly, args: NativeFile::Unowned);
551
552 constexpr llvm::StringLiteral client_name = "stdio";
553 Transport transport(client_name, log.get(), input, output);
554 DAP dap(log.get(), default_repl_mode, pre_init_commands, transport);
555
556 // stdout/stderr redirection to the IDE's console
557 if (auto Err = dap.ConfigureIO(stdout, stderr)) {
558 llvm::logAllUnhandledErrors(E: std::move(Err), OS&: llvm::errs(),
559 ErrorBanner: "Failed to configure stdout redirect: ");
560 return EXIT_FAILURE;
561 }
562
563 // used only by TestVSCode_redirection_to_console.py
564 if (getenv(name: "LLDB_DAP_TEST_STDOUT_STDERR_REDIRECTION") != nullptr)
565 redirection_test();
566
567 if (auto Err = dap.Loop()) {
568 DAP_LOG(log.get(), "({0}) DAP session error: {1}", client_name,
569 llvm::toStringWithoutConsuming(Err));
570 llvm::logAllUnhandledErrors(E: std::move(Err), OS&: llvm::errs(),
571 ErrorBanner: "DAP session error: ");
572 return EXIT_FAILURE;
573 }
574 return EXIT_SUCCESS;
575}
576

source code of lldb/tools/lldb-dap/tool/lldb-dap.cpp