| 1 | //===-- AttachRequestHandler.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 "EventHelper.h" |
| 11 | #include "JSONUtils.h" |
| 12 | #include "LLDBUtils.h" |
| 13 | #include "Protocol/ProtocolRequests.h" |
| 14 | #include "RequestHandler.h" |
| 15 | #include "lldb/API/SBAttachInfo.h" |
| 16 | #include "lldb/API/SBListener.h" |
| 17 | #include "lldb/lldb-defines.h" |
| 18 | #include "llvm/Support/Error.h" |
| 19 | #include "llvm/Support/FileSystem.h" |
| 20 | |
| 21 | using namespace llvm; |
| 22 | using namespace lldb_dap::protocol; |
| 23 | |
| 24 | namespace lldb_dap { |
| 25 | |
| 26 | /// The `attach` request is sent from the client to the debug adapter to attach |
| 27 | /// to a debuggee that is already running. |
| 28 | /// |
| 29 | /// Since attaching is debugger/runtime specific, the arguments for this request |
| 30 | /// are not part of this specification. |
| 31 | Error AttachRequestHandler::Run(const AttachRequestArguments &args) const { |
| 32 | // Validate that we have a well formed attach request. |
| 33 | if (args.attachCommands.empty() && args.coreFile.empty() && |
| 34 | args.configuration.program.empty() && |
| 35 | args.pid == LLDB_INVALID_PROCESS_ID && |
| 36 | args.gdbRemotePort == LLDB_DAP_INVALID_PORT) |
| 37 | return make_error<DAPError>( |
| 38 | Args: "expected one of 'pid', 'program', 'attachCommands', " |
| 39 | "'coreFile' or 'gdb-remote-port' to be specified" ); |
| 40 | |
| 41 | // Check if we have mutually exclusive arguments. |
| 42 | if ((args.pid != LLDB_INVALID_PROCESS_ID) && |
| 43 | (args.gdbRemotePort != LLDB_DAP_INVALID_PORT)) |
| 44 | return make_error<DAPError>( |
| 45 | Args: "'pid' and 'gdb-remote-port' are mutually exclusive" ); |
| 46 | |
| 47 | dap.SetConfiguration(confing: args.configuration, /*is_attach=*/true); |
| 48 | if (!args.coreFile.empty()) |
| 49 | dap.stop_at_entry = true; |
| 50 | |
| 51 | PrintWelcomeMessage(); |
| 52 | |
| 53 | // This is a hack for loading DWARF in .o files on Mac where the .o files |
| 54 | // in the debug map of the main executable have relative paths which |
| 55 | // require the lldb-dap binary to have its working directory set to that |
| 56 | // relative root for the .o files in order to be able to load debug info. |
| 57 | if (!dap.configuration.debuggerRoot.empty()) |
| 58 | sys::fs::set_current_path(dap.configuration.debuggerRoot); |
| 59 | |
| 60 | // Run any initialize LLDB commands the user specified in the launch.json |
| 61 | if (llvm::Error err = dap.RunInitCommands()) |
| 62 | return err; |
| 63 | |
| 64 | dap.ConfigureSourceMaps(); |
| 65 | |
| 66 | lldb::SBError error; |
| 67 | lldb::SBTarget target = dap.CreateTarget(error); |
| 68 | if (error.Fail()) |
| 69 | return ToError(error); |
| 70 | |
| 71 | dap.SetTarget(target); |
| 72 | |
| 73 | // Run any pre run LLDB commands the user specified in the launch.json |
| 74 | if (Error err = dap.RunPreRunCommands()) |
| 75 | return err; |
| 76 | |
| 77 | if ((args.pid == LLDB_INVALID_PROCESS_ID || |
| 78 | args.gdbRemotePort == LLDB_DAP_INVALID_PORT) && |
| 79 | args.waitFor) { |
| 80 | dap.SendOutput(o: OutputType::Console, |
| 81 | output: llvm::formatv(Fmt: "Waiting to attach to \"{0}\"..." , |
| 82 | Vals: dap.target.GetExecutable().GetFilename()) |
| 83 | .str()); |
| 84 | } |
| 85 | |
| 86 | { |
| 87 | // Perform the launch in synchronous mode so that we don't have to worry |
| 88 | // about process state changes during the launch. |
| 89 | ScopeSyncMode scope_sync_mode(dap.debugger); |
| 90 | |
| 91 | if (!args.attachCommands.empty()) { |
| 92 | // Run the attach commands, after which we expect the debugger's selected |
| 93 | // target to contain a valid and stopped process. Otherwise inform the |
| 94 | // user that their command failed or the debugger is in an unexpected |
| 95 | // state. |
| 96 | if (llvm::Error err = dap.RunAttachCommands(attach_commands: args.attachCommands)) |
| 97 | return err; |
| 98 | |
| 99 | dap.target = dap.debugger.GetSelectedTarget(); |
| 100 | |
| 101 | // Validate the attachCommand results. |
| 102 | if (!dap.target.GetProcess().IsValid()) |
| 103 | return make_error<DAPError>( |
| 104 | Args: "attachCommands failed to attach to a process" ); |
| 105 | } else if (!args.coreFile.empty()) { |
| 106 | dap.target.LoadCore(core_file: args.coreFile.data(), error); |
| 107 | } else if (args.gdbRemotePort != LLDB_DAP_INVALID_PORT) { |
| 108 | lldb::SBListener listener = dap.debugger.GetListener(); |
| 109 | |
| 110 | // If the user hasn't provided the hostname property, default |
| 111 | // localhost being used. |
| 112 | std::string connect_url = |
| 113 | llvm::formatv(Fmt: "connect://{0}:" , Vals: args.gdbRemoteHostname); |
| 114 | connect_url += std::to_string(val: args.gdbRemotePort); |
| 115 | dap.target.ConnectRemote(listener, url: connect_url.c_str(), plugin_name: "gdb-remote" , |
| 116 | error); |
| 117 | } else { |
| 118 | // Attach by pid or process name. |
| 119 | lldb::SBAttachInfo attach_info; |
| 120 | if (args.pid != LLDB_INVALID_PROCESS_ID) |
| 121 | attach_info.SetProcessID(args.pid); |
| 122 | else if (!dap.configuration.program.empty()) |
| 123 | attach_info.SetExecutable(dap.configuration.program.data()); |
| 124 | attach_info.SetWaitForLaunch(b: args.waitFor, /*async=*/false); |
| 125 | dap.target.Attach(attach_info, error); |
| 126 | } |
| 127 | } |
| 128 | |
| 129 | // Make sure the process is attached and stopped. |
| 130 | error = dap.WaitForProcessToStop(seconds: args.configuration.timeout); |
| 131 | if (error.Fail()) |
| 132 | return ToError(error); |
| 133 | |
| 134 | if (args.coreFile.empty() && !dap.target.GetProcess().IsValid()) |
| 135 | return make_error<DAPError>(Args: "failed to attach to process" ); |
| 136 | |
| 137 | dap.RunPostRunCommands(); |
| 138 | |
| 139 | return Error::success(); |
| 140 | } |
| 141 | |
| 142 | void AttachRequestHandler::PostRun() const { |
| 143 | dap.SendJSON(json: CreateEventObject(event_name: "initialized" )); |
| 144 | } |
| 145 | |
| 146 | } // namespace lldb_dap |
| 147 | |