| 1 | //===-- lldb-gdbserver.cpp --------------------------------------*- C++ -*-===// |
| 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 <cerrno> |
| 10 | #include <cstdint> |
| 11 | #include <cstdio> |
| 12 | #include <cstdlib> |
| 13 | #include <cstring> |
| 14 | |
| 15 | #ifndef _WIN32 |
| 16 | #include <csignal> |
| 17 | #include <unistd.h> |
| 18 | #endif |
| 19 | |
| 20 | #include "LLDBServerUtilities.h" |
| 21 | #include "Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h" |
| 22 | #include "Plugins/Process/gdb-remote/ProcessGDBRemoteLog.h" |
| 23 | #include "lldb/Host/Config.h" |
| 24 | #include "lldb/Host/ConnectionFileDescriptor.h" |
| 25 | #include "lldb/Host/FileSystem.h" |
| 26 | #include "lldb/Host/Pipe.h" |
| 27 | #include "lldb/Host/common/NativeProcessProtocol.h" |
| 28 | #include "lldb/Host/common/TCPSocket.h" |
| 29 | #include "lldb/Target/Process.h" |
| 30 | #include "lldb/Utility/LLDBLog.h" |
| 31 | #include "lldb/Utility/Status.h" |
| 32 | #include "llvm/ADT/StringRef.h" |
| 33 | #include "llvm/Option/ArgList.h" |
| 34 | #include "llvm/Option/OptTable.h" |
| 35 | #include "llvm/Option/Option.h" |
| 36 | #include "llvm/Support/Errno.h" |
| 37 | #include "llvm/Support/Error.h" |
| 38 | #include "llvm/Support/WithColor.h" |
| 39 | |
| 40 | #if defined(__linux__) |
| 41 | #include "Plugins/Process/Linux/NativeProcessLinux.h" |
| 42 | #elif defined(__FreeBSD__) |
| 43 | #include "Plugins/Process/FreeBSD/NativeProcessFreeBSD.h" |
| 44 | #elif defined(__NetBSD__) |
| 45 | #include "Plugins/Process/NetBSD/NativeProcessNetBSD.h" |
| 46 | #elif defined(_WIN32) |
| 47 | #include "Plugins/Process/Windows/Common/NativeProcessWindows.h" |
| 48 | #endif |
| 49 | |
| 50 | #ifndef LLGS_PROGRAM_NAME |
| 51 | #define LLGS_PROGRAM_NAME "lldb-server" |
| 52 | #endif |
| 53 | |
| 54 | #ifndef LLGS_VERSION_STR |
| 55 | #define LLGS_VERSION_STR "local_build" |
| 56 | #endif |
| 57 | |
| 58 | using namespace llvm; |
| 59 | using namespace lldb; |
| 60 | using namespace lldb_private; |
| 61 | using namespace lldb_private::lldb_server; |
| 62 | using namespace lldb_private::process_gdb_remote; |
| 63 | |
| 64 | namespace { |
| 65 | #if defined(__linux__) |
| 66 | typedef process_linux::NativeProcessLinux::Manager NativeProcessManager; |
| 67 | #elif defined(__FreeBSD__) |
| 68 | typedef process_freebsd::NativeProcessFreeBSD::Manager NativeProcessManager; |
| 69 | #elif defined(__NetBSD__) |
| 70 | typedef process_netbsd::NativeProcessNetBSD::Manager NativeProcessManager; |
| 71 | #elif defined(_WIN32) |
| 72 | typedef NativeProcessWindows::Manager NativeProcessManager; |
| 73 | #else |
| 74 | // Dummy implementation to make sure the code compiles |
| 75 | class NativeProcessManager : public NativeProcessProtocol::Manager { |
| 76 | public: |
| 77 | NativeProcessManager(MainLoop &mainloop) |
| 78 | : NativeProcessProtocol::Manager(mainloop) {} |
| 79 | |
| 80 | llvm::Expected<std::unique_ptr<NativeProcessProtocol>> |
| 81 | Launch(ProcessLaunchInfo &launch_info, |
| 82 | NativeProcessProtocol::NativeDelegate &native_delegate) override { |
| 83 | llvm_unreachable("Not implemented" ); |
| 84 | } |
| 85 | llvm::Expected<std::unique_ptr<NativeProcessProtocol>> |
| 86 | Attach(lldb::pid_t pid, |
| 87 | NativeProcessProtocol::NativeDelegate &native_delegate) override { |
| 88 | llvm_unreachable("Not implemented" ); |
| 89 | } |
| 90 | }; |
| 91 | #endif |
| 92 | } |
| 93 | |
| 94 | #ifndef _WIN32 |
| 95 | // Watch for signals |
| 96 | static int g_sighup_received_count = 0; |
| 97 | |
| 98 | static void sighup_handler(MainLoopBase &mainloop) { |
| 99 | ++g_sighup_received_count; |
| 100 | |
| 101 | Log *log = GetLog(mask: LLDBLog::Process); |
| 102 | LLDB_LOGF(log, "lldb-server:%s swallowing SIGHUP (receive count=%d)" , |
| 103 | __FUNCTION__, g_sighup_received_count); |
| 104 | |
| 105 | if (g_sighup_received_count >= 2) |
| 106 | mainloop.RequestTermination(); |
| 107 | } |
| 108 | #endif // #ifndef _WIN32 |
| 109 | |
| 110 | void handle_attach_to_pid(GDBRemoteCommunicationServerLLGS &gdb_server, |
| 111 | lldb::pid_t pid) { |
| 112 | Status error = gdb_server.AttachToProcess(pid); |
| 113 | if (error.Fail()) { |
| 114 | fprintf(stderr, format: "error: failed to attach to pid %" PRIu64 ": %s\n" , pid, |
| 115 | error.AsCString()); |
| 116 | exit(status: 1); |
| 117 | } |
| 118 | } |
| 119 | |
| 120 | void handle_attach_to_process_name(GDBRemoteCommunicationServerLLGS &gdb_server, |
| 121 | const std::string &process_name) { |
| 122 | // FIXME implement. |
| 123 | } |
| 124 | |
| 125 | void handle_attach(GDBRemoteCommunicationServerLLGS &gdb_server, |
| 126 | const std::string &attach_target) { |
| 127 | assert(!attach_target.empty() && "attach_target cannot be empty" ); |
| 128 | |
| 129 | // First check if the attach_target is convertible to a long. If so, we'll use |
| 130 | // it as a pid. |
| 131 | char *end_p = nullptr; |
| 132 | const long int pid = strtol(nptr: attach_target.c_str(), endptr: &end_p, base: 10); |
| 133 | |
| 134 | // We'll call it a match if the entire argument is consumed. |
| 135 | if (end_p && |
| 136 | static_cast<size_t>(end_p - attach_target.c_str()) == |
| 137 | attach_target.size()) |
| 138 | handle_attach_to_pid(gdb_server, pid: static_cast<lldb::pid_t>(pid)); |
| 139 | else |
| 140 | handle_attach_to_process_name(gdb_server, process_name: attach_target); |
| 141 | } |
| 142 | |
| 143 | void handle_launch(GDBRemoteCommunicationServerLLGS &gdb_server, |
| 144 | llvm::ArrayRef<llvm::StringRef> Arguments) { |
| 145 | ProcessLaunchInfo info; |
| 146 | info.GetFlags().Set(eLaunchFlagStopAtEntry | eLaunchFlagDebug | |
| 147 | eLaunchFlagDisableASLR); |
| 148 | info.SetArguments(args: Args(Arguments), first_arg_is_executable: true); |
| 149 | |
| 150 | llvm::SmallString<64> cwd; |
| 151 | if (std::error_code ec = llvm::sys::fs::current_path(result&: cwd)) { |
| 152 | llvm::errs() << "Error getting current directory: " << ec.message() << "\n" ; |
| 153 | exit(status: 1); |
| 154 | } |
| 155 | FileSpec cwd_spec(cwd); |
| 156 | FileSystem::Instance().Resolve(file_spec&: cwd_spec); |
| 157 | info.SetWorkingDirectory(cwd_spec); |
| 158 | info.GetEnvironment() = Host::GetEnvironment(); |
| 159 | |
| 160 | gdb_server.SetLaunchInfo(info); |
| 161 | |
| 162 | Status error = gdb_server.LaunchProcess(); |
| 163 | if (error.Fail()) { |
| 164 | llvm::errs() << llvm::formatv(Fmt: "error: failed to launch '{0}': {1}\n" , |
| 165 | Vals: Arguments[0], Vals&: error); |
| 166 | exit(status: 1); |
| 167 | } |
| 168 | } |
| 169 | |
| 170 | static Status writeSocketIdToPipe(Pipe &port_pipe, |
| 171 | const std::string &socket_id) { |
| 172 | // NB: Include the nul character at the end. |
| 173 | llvm::StringRef buf(socket_id.data(), socket_id.size() + 1); |
| 174 | while (!buf.empty()) { |
| 175 | if (llvm::Expected<size_t> written = |
| 176 | port_pipe.Write(buf: buf.data(), size: buf.size())) |
| 177 | buf = buf.drop_front(N: *written); |
| 178 | else |
| 179 | return Status::FromError(error: written.takeError()); |
| 180 | } |
| 181 | return Status(); |
| 182 | } |
| 183 | |
| 184 | Status writeSocketIdToPipe(const char *const named_pipe_path, |
| 185 | llvm::StringRef socket_id) { |
| 186 | Pipe port_name_pipe; |
| 187 | // Wait for 10 seconds for pipe to be opened. |
| 188 | if (llvm::Error err = port_name_pipe.OpenAsWriter(name: named_pipe_path, child_process_inherit: false, |
| 189 | timeout: std::chrono::seconds{10})) |
| 190 | return Status::FromError(error: std::move(err)); |
| 191 | |
| 192 | return writeSocketIdToPipe(port_pipe&: port_name_pipe, socket_id: socket_id.str()); |
| 193 | } |
| 194 | |
| 195 | Status writeSocketIdToPipe(lldb::pipe_t unnamed_pipe, |
| 196 | llvm::StringRef socket_id) { |
| 197 | Pipe port_pipe{LLDB_INVALID_PIPE, unnamed_pipe}; |
| 198 | return writeSocketIdToPipe(port_pipe, socket_id: socket_id.str()); |
| 199 | } |
| 200 | |
| 201 | void ConnectToRemote(MainLoop &mainloop, |
| 202 | GDBRemoteCommunicationServerLLGS &gdb_server, |
| 203 | bool reverse_connect, llvm::StringRef host_and_port, |
| 204 | const char *const progname, const char *const subcommand, |
| 205 | const char *const named_pipe_path, pipe_t unnamed_pipe, |
| 206 | shared_fd_t connection_fd) { |
| 207 | Status error; |
| 208 | |
| 209 | std::unique_ptr<Connection> connection_up; |
| 210 | std::string url; |
| 211 | |
| 212 | if (connection_fd != SharedSocket::kInvalidFD) { |
| 213 | #ifdef _WIN32 |
| 214 | NativeSocket sockfd; |
| 215 | error = SharedSocket::GetNativeSocket(connection_fd, sockfd); |
| 216 | if (error.Fail()) { |
| 217 | llvm::errs() << llvm::formatv("error: GetNativeSocket failed: {0}\n" , |
| 218 | error.AsCString()); |
| 219 | exit(-1); |
| 220 | } |
| 221 | connection_up = std::unique_ptr<Connection>(new ConnectionFileDescriptor( |
| 222 | new TCPSocket(sockfd, /*should_close=*/true))); |
| 223 | #else |
| 224 | url = llvm::formatv(Fmt: "fd://{0}" , Vals&: connection_fd).str(); |
| 225 | |
| 226 | // Create the connection. |
| 227 | ::fcntl(fd: connection_fd, F_SETFD, FD_CLOEXEC); |
| 228 | #endif |
| 229 | } else if (!host_and_port.empty()) { |
| 230 | llvm::Expected<std::string> url_exp = |
| 231 | LLGSArgToURL(url_arg: host_and_port, reverse_connect); |
| 232 | if (!url_exp) { |
| 233 | llvm::errs() << llvm::formatv(Fmt: "error: invalid host:port or URL '{0}': " |
| 234 | "{1}\n" , |
| 235 | Vals&: host_and_port, |
| 236 | Vals: llvm::toString(E: url_exp.takeError())); |
| 237 | exit(status: -1); |
| 238 | } |
| 239 | |
| 240 | url = std::move(url_exp.get()); |
| 241 | } |
| 242 | |
| 243 | if (!url.empty()) { |
| 244 | // Create the connection or server. |
| 245 | std::unique_ptr<ConnectionFileDescriptor> conn_fd_up{ |
| 246 | new ConnectionFileDescriptor}; |
| 247 | auto connection_result = conn_fd_up->Connect( |
| 248 | url, |
| 249 | socket_id_callback: [named_pipe_path, unnamed_pipe](llvm::StringRef socket_id) { |
| 250 | // If we have a named pipe to write the socket id back to, do that |
| 251 | // now. |
| 252 | if (named_pipe_path && named_pipe_path[0]) { |
| 253 | Status error = writeSocketIdToPipe(named_pipe_path, socket_id); |
| 254 | if (error.Fail()) |
| 255 | llvm::errs() << llvm::formatv( |
| 256 | Fmt: "failed to write to the named pipe '{0}': {1}\n" , |
| 257 | Vals: named_pipe_path, Vals: error.AsCString()); |
| 258 | } |
| 259 | // If we have an unnamed pipe to write the socket id back to, do |
| 260 | // that now. |
| 261 | else if (unnamed_pipe != LLDB_INVALID_PIPE) { |
| 262 | Status error = writeSocketIdToPipe(unnamed_pipe, socket_id); |
| 263 | if (error.Fail()) |
| 264 | llvm::errs() << llvm::formatv( |
| 265 | Fmt: "failed to write to the unnamed pipe: {0}\n" , Vals&: error); |
| 266 | } |
| 267 | }, |
| 268 | error_ptr: &error); |
| 269 | |
| 270 | if (error.Fail()) { |
| 271 | llvm::errs() << llvm::formatv( |
| 272 | Fmt: "error: failed to connect to client at '{0}': {1}\n" , Vals&: url, Vals&: error); |
| 273 | exit(status: -1); |
| 274 | } |
| 275 | if (connection_result != eConnectionStatusSuccess) { |
| 276 | llvm::errs() << llvm::formatv( |
| 277 | Fmt: "error: failed to connect to client at '{0}' " |
| 278 | "(connection status: {1})\n" , |
| 279 | Vals&: url, Vals: static_cast<int>(connection_result)); |
| 280 | exit(status: -1); |
| 281 | } |
| 282 | connection_up = std::move(conn_fd_up); |
| 283 | } |
| 284 | error = gdb_server.InitializeConnection(connection: std::move(connection_up)); |
| 285 | if (error.Fail()) { |
| 286 | llvm::errs() << llvm::formatv(Fmt: "failed to initialize connection\n" , Vals&: error); |
| 287 | exit(status: -1); |
| 288 | } |
| 289 | llvm::outs() << "Connection established.\n" ; |
| 290 | } |
| 291 | |
| 292 | namespace { |
| 293 | using namespace llvm::opt; |
| 294 | |
| 295 | enum ID { |
| 296 | OPT_INVALID = 0, // This is not an option ID. |
| 297 | #define OPTION(...) LLVM_MAKE_OPT_ID(__VA_ARGS__), |
| 298 | #include "LLGSOptions.inc" |
| 299 | #undef OPTION |
| 300 | }; |
| 301 | |
| 302 | #define OPTTABLE_STR_TABLE_CODE |
| 303 | #include "LLGSOptions.inc" |
| 304 | #undef OPTTABLE_STR_TABLE_CODE |
| 305 | |
| 306 | #define OPTTABLE_PREFIXES_TABLE_CODE |
| 307 | #include "LLGSOptions.inc" |
| 308 | #undef OPTTABLE_PREFIXES_TABLE_CODE |
| 309 | |
| 310 | static constexpr opt::OptTable::Info InfoTable[] = { |
| 311 | #define OPTION(...) LLVM_CONSTRUCT_OPT_INFO(__VA_ARGS__), |
| 312 | #include "LLGSOptions.inc" |
| 313 | #undef OPTION |
| 314 | }; |
| 315 | |
| 316 | class LLGSOptTable : public opt::GenericOptTable { |
| 317 | public: |
| 318 | LLGSOptTable() |
| 319 | : opt::GenericOptTable(OptionStrTable, OptionPrefixesTable, InfoTable) {} |
| 320 | |
| 321 | void PrintHelp(llvm::StringRef Name) { |
| 322 | std::string Usage = |
| 323 | (Name + " [options] [[host]:port] [[--] program args...]" ).str(); |
| 324 | OptTable::printHelp(OS&: llvm::outs(), Usage: Usage.c_str(), Title: "lldb-server" ); |
| 325 | llvm::outs() << R"( |
| 326 | DESCRIPTION |
| 327 | lldb-server connects to the LLDB client, which drives the debugging session. |
| 328 | If no connection options are given, the [host]:port argument must be present |
| 329 | and will denote the address that lldb-server will listen on. [host] defaults |
| 330 | to "localhost" if empty. Port can be zero, in which case the port number will |
| 331 | be chosen dynamically and written to destinations given by --named-pipe and |
| 332 | --pipe arguments. |
| 333 | |
| 334 | If no target is selected at startup, lldb-server can be directed by the LLDB |
| 335 | client to launch or attach to a process. |
| 336 | |
| 337 | )" ; |
| 338 | } |
| 339 | }; |
| 340 | } // namespace |
| 341 | |
| 342 | int main_gdbserver(int argc, char *argv[]) { |
| 343 | Status error; |
| 344 | MainLoop mainloop; |
| 345 | #ifndef _WIN32 |
| 346 | // Setup signal handlers first thing. |
| 347 | signal(SIGPIPE, SIG_IGN); |
| 348 | MainLoop::SignalHandleUP sighup_handle = |
| 349 | mainloop.RegisterSignal(SIGHUP, callback: sighup_handler, error); |
| 350 | #endif |
| 351 | |
| 352 | const char *progname = argv[0]; |
| 353 | const char *subcommand = argv[1]; |
| 354 | std::string attach_target; |
| 355 | std::string named_pipe_path; |
| 356 | std::string log_file; |
| 357 | StringRef |
| 358 | log_channels; // e.g. "lldb process threads:gdb-remote default:linux all" |
| 359 | lldb::pipe_t unnamed_pipe = LLDB_INVALID_PIPE; |
| 360 | bool reverse_connect = false; |
| 361 | shared_fd_t connection_fd = SharedSocket::kInvalidFD; |
| 362 | |
| 363 | // ProcessLaunchInfo launch_info; |
| 364 | ProcessAttachInfo attach_info; |
| 365 | |
| 366 | LLGSOptTable Opts; |
| 367 | llvm::BumpPtrAllocator Alloc; |
| 368 | llvm::StringSaver Saver(Alloc); |
| 369 | bool HasError = false; |
| 370 | opt::InputArgList Args = Opts.parseArgs(Argc: argc - 1, Argv: argv + 1, Unknown: OPT_UNKNOWN, |
| 371 | Saver, ErrorFn: [&](llvm::StringRef Msg) { |
| 372 | WithColor::error() << Msg << "\n" ; |
| 373 | HasError = true; |
| 374 | }); |
| 375 | std::string Name = |
| 376 | (llvm::sys::path::filename(path: argv[0]) + " g[dbserver]" ).str(); |
| 377 | std::string HelpText = |
| 378 | "Use '" + Name + " --help' for a complete list of options.\n" ; |
| 379 | if (HasError) { |
| 380 | llvm::errs() << HelpText; |
| 381 | return 1; |
| 382 | } |
| 383 | |
| 384 | if (Args.hasArg(OPT_help)) { |
| 385 | Opts.PrintHelp(Name); |
| 386 | return 0; |
| 387 | } |
| 388 | |
| 389 | #ifndef _WIN32 |
| 390 | if (Args.hasArg(OPT_setsid)) { |
| 391 | // Put llgs into a new session. Terminals group processes |
| 392 | // into sessions and when a special terminal key sequences |
| 393 | // (like control+c) are typed they can cause signals to go out to |
| 394 | // all processes in a session. Using this --setsid (-S) option |
| 395 | // will cause debugserver to run in its own sessions and be free |
| 396 | // from such issues. |
| 397 | // |
| 398 | // This is useful when llgs is spawned from a command |
| 399 | // line application that uses llgs to do the debugging, |
| 400 | // yet that application doesn't want llgs receiving the |
| 401 | // signals sent to the session (i.e. dying when anyone hits ^C). |
| 402 | { |
| 403 | const ::pid_t new_sid = setsid(); |
| 404 | if (new_sid == -1) { |
| 405 | WithColor::warning() |
| 406 | << llvm::formatv(Fmt: "failed to set new session id for {0} ({1})\n" , |
| 407 | LLGS_PROGRAM_NAME, Vals: llvm::sys::StrError()); |
| 408 | } |
| 409 | } |
| 410 | } |
| 411 | #endif |
| 412 | |
| 413 | log_file = Args.getLastArgValue(Id: OPT_log_file).str(); |
| 414 | log_channels = Args.getLastArgValue(Id: OPT_log_channels); |
| 415 | named_pipe_path = Args.getLastArgValue(Id: OPT_named_pipe).str(); |
| 416 | reverse_connect = Args.hasArg(Ids: OPT_reverse_connect); |
| 417 | attach_target = Args.getLastArgValue(Id: OPT_attach).str(); |
| 418 | if (Args.hasArg(OPT_pipe)) { |
| 419 | uint64_t Arg; |
| 420 | if (!llvm::to_integer(Args.getLastArgValue(Id: OPT_pipe), Arg)) { |
| 421 | WithColor::error() << "invalid '--pipe' argument\n" << HelpText; |
| 422 | return 1; |
| 423 | } |
| 424 | unnamed_pipe = (pipe_t)Arg; |
| 425 | } |
| 426 | if (Args.hasArg(OPT_fd)) { |
| 427 | int64_t fd; |
| 428 | if (!llvm::to_integer(Args.getLastArgValue(Id: OPT_fd), fd)) { |
| 429 | WithColor::error() << "invalid '--fd' argument\n" << HelpText; |
| 430 | return 1; |
| 431 | } |
| 432 | connection_fd = (shared_fd_t)fd; |
| 433 | } |
| 434 | |
| 435 | if (!LLDBServerUtilities::SetupLogging( |
| 436 | log_file, log_channels, |
| 437 | LLDB_LOG_OPTION_PREPEND_TIMESTAMP | |
| 438 | LLDB_LOG_OPTION_PREPEND_FILE_FUNCTION)) |
| 439 | return -1; |
| 440 | |
| 441 | std::vector<llvm::StringRef> Inputs; |
| 442 | for (opt::Arg *Arg : Args.filtered(OPT_INPUT)) |
| 443 | Inputs.push_back(Arg->getValue()); |
| 444 | if (opt::Arg *Arg = Args.getLastArg(OPT_REM)) { |
| 445 | for (const char *Val : Arg->getValues()) |
| 446 | Inputs.push_back(Val); |
| 447 | } |
| 448 | if (Inputs.empty() && connection_fd == SharedSocket::kInvalidFD) { |
| 449 | WithColor::error() << "no connection arguments\n" << HelpText; |
| 450 | return 1; |
| 451 | } |
| 452 | |
| 453 | NativeProcessManager manager(mainloop); |
| 454 | GDBRemoteCommunicationServerLLGS gdb_server(mainloop, manager); |
| 455 | |
| 456 | llvm::StringRef host_and_port; |
| 457 | if (!Inputs.empty() && connection_fd == SharedSocket::kInvalidFD) { |
| 458 | host_and_port = Inputs.front(); |
| 459 | Inputs.erase(position: Inputs.begin()); |
| 460 | } |
| 461 | |
| 462 | // Any arguments left over are for the program that we need to launch. If |
| 463 | // there |
| 464 | // are no arguments, then the GDB server will start up and wait for an 'A' |
| 465 | // packet |
| 466 | // to launch a program, or a vAttach packet to attach to an existing process, |
| 467 | // unless |
| 468 | // explicitly asked to attach with the --attach={pid|program_name} form. |
| 469 | if (!attach_target.empty()) |
| 470 | handle_attach(gdb_server, attach_target); |
| 471 | else if (!Inputs.empty()) |
| 472 | handle_launch(gdb_server, Arguments: Inputs); |
| 473 | |
| 474 | // Print version info. |
| 475 | printf(format: "%s-%s\n" , LLGS_PROGRAM_NAME, LLGS_VERSION_STR); |
| 476 | |
| 477 | ConnectToRemote(mainloop, gdb_server, reverse_connect, host_and_port, |
| 478 | progname, subcommand, named_pipe_path: named_pipe_path.c_str(), |
| 479 | unnamed_pipe, connection_fd); |
| 480 | |
| 481 | if (!gdb_server.IsConnected()) { |
| 482 | fprintf(stderr, format: "no connection information provided, unable to run\n" ); |
| 483 | return 1; |
| 484 | } |
| 485 | |
| 486 | Status ret = mainloop.Run(); |
| 487 | if (ret.Fail()) { |
| 488 | fprintf(stderr, format: "lldb-server terminating due to error: %s\n" , |
| 489 | ret.AsCString()); |
| 490 | return 1; |
| 491 | } |
| 492 | fprintf(stderr, format: "lldb-server exiting...\n" ); |
| 493 | |
| 494 | return 0; |
| 495 | } |
| 496 | |