1 | //===-- RequestHandler.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 "Handler/RequestHandler.h" |
10 | #include "DAP.h" |
11 | #include "EventHelper.h" |
12 | #include "Handler/ResponseHandler.h" |
13 | #include "JSONUtils.h" |
14 | #include "LLDBUtils.h" |
15 | #include "Protocol/ProtocolBase.h" |
16 | #include "Protocol/ProtocolRequests.h" |
17 | #include "RunInTerminal.h" |
18 | #include "lldb/API/SBDefines.h" |
19 | #include "lldb/API/SBEnvironment.h" |
20 | #include "llvm/Support/Error.h" |
21 | #include <mutex> |
22 | |
23 | #if !defined(_WIN32) |
24 | #include <unistd.h> |
25 | #endif |
26 | |
27 | using namespace lldb_dap::protocol; |
28 | |
29 | namespace lldb_dap { |
30 | |
31 | static std::vector<const char *> |
32 | MakeArgv(const llvm::ArrayRef<std::string> &strs) { |
33 | // Create and return an array of "const char *", one for each C string in |
34 | // "strs" and terminate the list with a NULL. This can be used for argument |
35 | // vectors (argv) or environment vectors (envp) like those passed to the |
36 | // "main" function in C programs. |
37 | std::vector<const char *> argv; |
38 | for (const auto &s : strs) |
39 | argv.push_back(x: s.c_str()); |
40 | argv.push_back(x: nullptr); |
41 | return argv; |
42 | } |
43 | |
44 | static uint32_t SetLaunchFlag(uint32_t flags, bool flag, |
45 | lldb::LaunchFlags mask) { |
46 | if (flag) |
47 | flags |= mask; |
48 | else |
49 | flags &= ~mask; |
50 | |
51 | return flags; |
52 | } |
53 | |
54 | static llvm::Error |
55 | RunInTerminal(DAP &dap, const protocol::LaunchRequestArguments &arguments) { |
56 | if (!dap.clientFeatures.contains( |
57 | V: protocol::eClientFeatureRunInTerminalRequest)) |
58 | return llvm::make_error<DAPError>(Args: "Cannot use runInTerminal, feature is " |
59 | "not supported by the connected client" ); |
60 | |
61 | if (arguments.configuration.program.empty()) |
62 | return llvm::make_error<DAPError>( |
63 | Args: "program must be set to when using runInTerminal" ); |
64 | |
65 | dap.is_attach = true; |
66 | lldb::SBAttachInfo attach_info; |
67 | |
68 | llvm::Expected<std::shared_ptr<FifoFile>> comm_file_or_err = |
69 | CreateRunInTerminalCommFile(); |
70 | if (!comm_file_or_err) |
71 | return comm_file_or_err.takeError(); |
72 | FifoFile &comm_file = *comm_file_or_err.get(); |
73 | |
74 | RunInTerminalDebugAdapterCommChannel comm_channel(comm_file.m_path); |
75 | |
76 | lldb::pid_t debugger_pid = LLDB_INVALID_PROCESS_ID; |
77 | #if !defined(_WIN32) |
78 | debugger_pid = getpid(); |
79 | #endif |
80 | |
81 | llvm::json::Object reverse_request = CreateRunInTerminalReverseRequest( |
82 | program: arguments.configuration.program, args: arguments.args, env: arguments.env, |
83 | cwd: arguments.cwd, comm_file: comm_file.m_path, debugger_pid); |
84 | dap.SendReverseRequest<LogFailureResponseHandler>(command: "runInTerminal" , |
85 | arguments: std::move(reverse_request)); |
86 | |
87 | if (llvm::Expected<lldb::pid_t> pid = comm_channel.GetLauncherPid()) |
88 | attach_info.SetProcessID(*pid); |
89 | else |
90 | return pid.takeError(); |
91 | |
92 | std::optional<ScopeSyncMode> scope_sync_mode(dap.debugger); |
93 | lldb::SBError error; |
94 | dap.target.Attach(attach_info, error); |
95 | |
96 | if (error.Fail()) |
97 | return llvm::createStringError(EC: llvm::inconvertibleErrorCode(), |
98 | Fmt: "Failed to attach to the target process. %s" , |
99 | Vals: comm_channel.GetLauncherError().c_str()); |
100 | // This will notify the runInTerminal launcher that we attached. |
101 | // We have to make this async, as the function won't return until the launcher |
102 | // resumes and reads the data. |
103 | std::future<lldb::SBError> did_attach_message_success = |
104 | comm_channel.NotifyDidAttach(); |
105 | |
106 | // We just attached to the runInTerminal launcher, which was waiting to be |
107 | // attached. We now resume it, so it can receive the didAttach notification |
108 | // and then perform the exec. Upon continuing, the debugger will stop the |
109 | // process right in the middle of the exec. To the user, what we are doing is |
110 | // transparent, as they will only be able to see the process since the exec, |
111 | // completely unaware of the preparatory work. |
112 | dap.target.GetProcess().Continue(); |
113 | |
114 | // Now that the actual target is just starting (i.e. exec was just invoked), |
115 | // we return the debugger to its sync state. |
116 | scope_sync_mode.reset(); |
117 | |
118 | // If sending the notification failed, the launcher should be dead by now and |
119 | // the async didAttach notification should have an error message, so we |
120 | // return it. Otherwise, everything was a success. |
121 | did_attach_message_success.wait(); |
122 | error = did_attach_message_success.get(); |
123 | if (error.Success()) |
124 | return llvm::Error::success(); |
125 | return llvm::createStringError(EC: llvm::inconvertibleErrorCode(), |
126 | S: error.GetCString()); |
127 | } |
128 | |
129 | void BaseRequestHandler::Run(const Request &request) { |
130 | // If this request was cancelled, send a cancelled response. |
131 | if (dap.IsCancelled(request)) { |
132 | Response cancelled{/*request_seq=*/request.seq, |
133 | /*command=*/request.command, |
134 | /*success=*/false, |
135 | /*message=*/eResponseMessageCancelled, |
136 | /*body=*/std::nullopt}; |
137 | dap.Send(message: cancelled); |
138 | return; |
139 | } |
140 | |
141 | lldb::SBMutex lock = dap.GetAPIMutex(); |
142 | std::lock_guard<lldb::SBMutex> guard(lock); |
143 | |
144 | // FIXME: After all the requests have migrated from LegacyRequestHandler > |
145 | // RequestHandler<> we should be able to move this into |
146 | // RequestHandler<>::operator(). |
147 | operator()(request); |
148 | |
149 | // FIXME: After all the requests have migrated from LegacyRequestHandler > |
150 | // RequestHandler<> we should be able to check `debugger.InterruptRequest` and |
151 | // mark the response as cancelled. |
152 | } |
153 | |
154 | llvm::Error BaseRequestHandler::LaunchProcess( |
155 | const protocol::LaunchRequestArguments &arguments) const { |
156 | auto launchCommands = arguments.launchCommands; |
157 | |
158 | // Instantiate a launch info instance for the target. |
159 | auto launch_info = dap.target.GetLaunchInfo(); |
160 | |
161 | // Grab the current working directory if there is one and set it in the |
162 | // launch info. |
163 | if (!arguments.cwd.empty()) |
164 | launch_info.SetWorkingDirectory(arguments.cwd.data()); |
165 | |
166 | // Extract any extra arguments and append them to our program arguments for |
167 | // when we launch |
168 | if (!arguments.args.empty()) |
169 | launch_info.SetArguments(argv: MakeArgv(strs: arguments.args).data(), append: true); |
170 | |
171 | // Pass any environment variables along that the user specified. |
172 | if (!arguments.env.empty()) { |
173 | lldb::SBEnvironment env; |
174 | for (const auto &kv : arguments.env) |
175 | env.Set(name: kv.first().data(), value: kv.second.c_str(), overwrite: true); |
176 | launch_info.SetEnvironment(env, append: true); |
177 | } |
178 | |
179 | launch_info.SetDetachOnError(arguments.detachOnError); |
180 | launch_info.SetShellExpandArguments(arguments.shellExpandArguments); |
181 | |
182 | auto flags = launch_info.GetLaunchFlags(); |
183 | flags = |
184 | SetLaunchFlag(flags, flag: arguments.disableASLR, mask: lldb::eLaunchFlagDisableASLR); |
185 | flags = SetLaunchFlag(flags, flag: arguments.disableSTDIO, |
186 | mask: lldb::eLaunchFlagDisableSTDIO); |
187 | launch_info.SetLaunchFlags(flags | lldb::eLaunchFlagDebug | |
188 | lldb::eLaunchFlagStopAtEntry); |
189 | |
190 | { |
191 | // Perform the launch in synchronous mode so that we don't have to worry |
192 | // about process state changes during the launch. |
193 | ScopeSyncMode scope_sync_mode(dap.debugger); |
194 | |
195 | if (arguments.runInTerminal) { |
196 | if (llvm::Error err = RunInTerminal(dap, arguments)) |
197 | return err; |
198 | } else if (launchCommands.empty()) { |
199 | lldb::SBError error; |
200 | dap.target.Launch(launch_info, error); |
201 | if (error.Fail()) |
202 | return ToError(error); |
203 | } else { |
204 | // Set the launch info so that run commands can access the configured |
205 | // launch details. |
206 | dap.target.SetLaunchInfo(launch_info); |
207 | if (llvm::Error err = dap.RunLaunchCommands(launch_commands: launchCommands)) |
208 | return err; |
209 | |
210 | // The custom commands might have created a new target so we should use |
211 | // the selected target after these commands are run. |
212 | dap.target = dap.debugger.GetSelectedTarget(); |
213 | } |
214 | } |
215 | |
216 | // Make sure the process is launched and stopped at the entry point before |
217 | // proceeding. |
218 | lldb::SBError error = |
219 | dap.WaitForProcessToStop(seconds: arguments.configuration.timeout); |
220 | if (error.Fail()) |
221 | return ToError(error); |
222 | |
223 | return llvm::Error::success(); |
224 | } |
225 | |
226 | void BaseRequestHandler::PrintWelcomeMessage() const { |
227 | #ifdef LLDB_DAP_WELCOME_MESSAGE |
228 | dap.SendOutput(OutputType::Console, LLDB_DAP_WELCOME_MESSAGE); |
229 | #endif |
230 | } |
231 | |
232 | bool BaseRequestHandler::HasInstructionGranularity( |
233 | const llvm::json::Object &arguments) const { |
234 | if (std::optional<llvm::StringRef> value = arguments.getString(K: "granularity" )) |
235 | return value == "instruction" ; |
236 | return false; |
237 | } |
238 | |
239 | } // namespace lldb_dap |
240 | |