1 | //===-- EventHelper.h -----------------------------------------------------===// |
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 "EventHelper.h" |
10 | #include "DAP.h" |
11 | #include "DAPError.h" |
12 | #include "JSONUtils.h" |
13 | #include "LLDBUtils.h" |
14 | #include "Protocol/ProtocolEvents.h" |
15 | #include "Protocol/ProtocolTypes.h" |
16 | #include "lldb/API/SBFileSpec.h" |
17 | #include "llvm/Support/Error.h" |
18 | |
19 | #if defined(_WIN32) |
20 | #define NOMINMAX |
21 | #include <windows.h> |
22 | |
23 | #ifndef PATH_MAX |
24 | #define PATH_MAX MAX_PATH |
25 | #endif |
26 | #endif |
27 | |
28 | using namespace llvm; |
29 | |
30 | namespace lldb_dap { |
31 | |
32 | static void SendThreadExitedEvent(DAP &dap, lldb::tid_t tid) { |
33 | llvm::json::Object event(CreateEventObject(event_name: "thread" )); |
34 | llvm::json::Object body; |
35 | body.try_emplace(K: "reason" , Args: "exited" ); |
36 | body.try_emplace(K: "threadId" , Args: (int64_t)tid); |
37 | event.try_emplace(K: "body" , Args: std::move(body)); |
38 | dap.SendJSON(json: llvm::json::Value(std::move(event))); |
39 | } |
40 | |
41 | void SendTargetBasedCapabilities(DAP &dap) { |
42 | if (!dap.target.IsValid()) |
43 | return; |
44 | |
45 | protocol::CapabilitiesEventBody body; |
46 | |
47 | // We only support restarting launch requests not attach requests. |
48 | if (dap.last_launch_request) |
49 | body.capabilities.supportedFeatures.insert( |
50 | V: protocol::eAdapterFeatureRestartRequest); |
51 | |
52 | // Only notify the client if supportedFeatures changed. |
53 | if (!body.capabilities.supportedFeatures.empty()) |
54 | dap.Send(message: protocol::Event{.event: "capabilities" , .body: body}); |
55 | } |
56 | |
57 | // "ProcessEvent": { |
58 | // "allOf": [ |
59 | // { "$ref": "#/definitions/Event" }, |
60 | // { |
61 | // "type": "object", |
62 | // "description": "Event message for 'process' event type. The event |
63 | // indicates that the debugger has begun debugging a |
64 | // new process. Either one that it has launched, or one |
65 | // that it has attached to.", |
66 | // "properties": { |
67 | // "event": { |
68 | // "type": "string", |
69 | // "enum": [ "process" ] |
70 | // }, |
71 | // "body": { |
72 | // "type": "object", |
73 | // "properties": { |
74 | // "name": { |
75 | // "type": "string", |
76 | // "description": "The logical name of the process. This is |
77 | // usually the full path to process's executable |
78 | // file. Example: /home/myproj/program.js." |
79 | // }, |
80 | // "systemProcessId": { |
81 | // "type": "integer", |
82 | // "description": "The system process id of the debugged process. |
83 | // This property will be missing for non-system |
84 | // processes." |
85 | // }, |
86 | // "isLocalProcess": { |
87 | // "type": "boolean", |
88 | // "description": "If true, the process is running on the same |
89 | // computer as the debug adapter." |
90 | // }, |
91 | // "startMethod": { |
92 | // "type": "string", |
93 | // "enum": [ "launch", "attach", "attachForSuspendedLaunch" ], |
94 | // "description": "Describes how the debug engine started |
95 | // debugging this process.", |
96 | // "enumDescriptions": [ |
97 | // "Process was launched under the debugger.", |
98 | // "Debugger attached to an existing process.", |
99 | // "A project launcher component has launched a new process in |
100 | // a suspended state and then asked the debugger to attach." |
101 | // ] |
102 | // } |
103 | // }, |
104 | // "required": [ "name" ] |
105 | // } |
106 | // }, |
107 | // "required": [ "event", "body" ] |
108 | // } |
109 | // ] |
110 | // } |
111 | void SendProcessEvent(DAP &dap, LaunchMethod launch_method) { |
112 | lldb::SBFileSpec exe_fspec = dap.target.GetExecutable(); |
113 | char exe_path[PATH_MAX]; |
114 | exe_fspec.GetPath(dst_path: exe_path, dst_len: sizeof(exe_path)); |
115 | llvm::json::Object event(CreateEventObject(event_name: "process" )); |
116 | llvm::json::Object body; |
117 | EmplaceSafeString(obj&: body, key: "name" , str: exe_path); |
118 | const auto pid = dap.target.GetProcess().GetProcessID(); |
119 | body.try_emplace(K: "systemProcessId" , Args: (int64_t)pid); |
120 | body.try_emplace(K: "isLocalProcess" , Args: true); |
121 | const char *startMethod = nullptr; |
122 | switch (launch_method) { |
123 | case Launch: |
124 | startMethod = "launch" ; |
125 | break; |
126 | case Attach: |
127 | startMethod = "attach" ; |
128 | break; |
129 | case AttachForSuspendedLaunch: |
130 | startMethod = "attachForSuspendedLaunch" ; |
131 | break; |
132 | } |
133 | body.try_emplace(K: "startMethod" , Args&: startMethod); |
134 | event.try_emplace(K: "body" , Args: std::move(body)); |
135 | dap.SendJSON(json: llvm::json::Value(std::move(event))); |
136 | } |
137 | |
138 | // Send a thread stopped event for all threads as long as the process |
139 | // is stopped. |
140 | llvm::Error SendThreadStoppedEvent(DAP &dap, bool on_entry) { |
141 | lldb::SBMutex lock = dap.GetAPIMutex(); |
142 | std::lock_guard<lldb::SBMutex> guard(lock); |
143 | |
144 | lldb::SBProcess process = dap.target.GetProcess(); |
145 | if (!process.IsValid()) |
146 | return make_error<DAPError>(Args: "invalid process" ); |
147 | |
148 | lldb::StateType state = process.GetState(); |
149 | if (!lldb::SBDebugger::StateIsStoppedState(state)) |
150 | return make_error<NotStoppedError>(); |
151 | |
152 | llvm::DenseSet<lldb::tid_t> old_thread_ids; |
153 | old_thread_ids.swap(RHS&: dap.thread_ids); |
154 | uint32_t stop_id = process.GetStopID(); |
155 | const uint32_t num_threads = process.GetNumThreads(); |
156 | |
157 | // First make a pass through the threads to see if the focused thread |
158 | // has a stop reason. In case the focus thread doesn't have a stop |
159 | // reason, remember the first thread that has a stop reason so we can |
160 | // set it as the focus thread if below if needed. |
161 | lldb::tid_t first_tid_with_reason = LLDB_INVALID_THREAD_ID; |
162 | uint32_t num_threads_with_reason = 0; |
163 | bool focus_thread_exists = false; |
164 | for (uint32_t thread_idx = 0; thread_idx < num_threads; ++thread_idx) { |
165 | lldb::SBThread thread = process.GetThreadAtIndex(index: thread_idx); |
166 | const lldb::tid_t tid = thread.GetThreadID(); |
167 | const bool has_reason = ThreadHasStopReason(thread); |
168 | // If the focus thread doesn't have a stop reason, clear the thread ID |
169 | if (tid == dap.focus_tid) { |
170 | focus_thread_exists = true; |
171 | if (!has_reason) |
172 | dap.focus_tid = LLDB_INVALID_THREAD_ID; |
173 | } |
174 | if (has_reason) { |
175 | ++num_threads_with_reason; |
176 | if (first_tid_with_reason == LLDB_INVALID_THREAD_ID) |
177 | first_tid_with_reason = tid; |
178 | } |
179 | } |
180 | |
181 | // We will have cleared dap.focus_tid if the focus thread doesn't have |
182 | // a stop reason, so if it was cleared, or wasn't set, or doesn't exist, |
183 | // then set the focus thread to the first thread with a stop reason. |
184 | if (!focus_thread_exists || dap.focus_tid == LLDB_INVALID_THREAD_ID) |
185 | dap.focus_tid = first_tid_with_reason; |
186 | |
187 | // If no threads stopped with a reason, then report the first one so |
188 | // we at least let the UI know we stopped. |
189 | if (num_threads_with_reason == 0) { |
190 | lldb::SBThread thread = process.GetThreadAtIndex(index: 0); |
191 | dap.focus_tid = thread.GetThreadID(); |
192 | dap.SendJSON(json: CreateThreadStopped(dap, thread, stop_id)); |
193 | } else { |
194 | for (uint32_t thread_idx = 0; thread_idx < num_threads; ++thread_idx) { |
195 | lldb::SBThread thread = process.GetThreadAtIndex(index: thread_idx); |
196 | dap.thread_ids.insert(V: thread.GetThreadID()); |
197 | if (ThreadHasStopReason(thread)) { |
198 | dap.SendJSON(json: CreateThreadStopped(dap, thread, stop_id)); |
199 | } |
200 | } |
201 | } |
202 | |
203 | for (const auto &tid : old_thread_ids) { |
204 | auto end = dap.thread_ids.end(); |
205 | auto pos = dap.thread_ids.find(V: tid); |
206 | if (pos == end) |
207 | SendThreadExitedEvent(dap, tid); |
208 | } |
209 | |
210 | dap.RunStopCommands(); |
211 | return Error::success(); |
212 | } |
213 | |
214 | // Send a "terminated" event to indicate the process is done being |
215 | // debugged. |
216 | void SendTerminatedEvent(DAP &dap) { |
217 | // Prevent races if the process exits while we're being asked to disconnect. |
218 | llvm::call_once(flag&: dap.terminated_event_flag, F: [&] { |
219 | dap.RunTerminateCommands(); |
220 | // Send a "terminated" event |
221 | llvm::json::Object event(CreateTerminatedEventObject(target&: dap.target)); |
222 | dap.SendJSON(json: llvm::json::Value(std::move(event))); |
223 | }); |
224 | } |
225 | |
226 | // Grab any STDOUT and STDERR from the process and send it up to VS Code |
227 | // via an "output" event to the "stdout" and "stderr" categories. |
228 | void SendStdOutStdErr(DAP &dap, lldb::SBProcess &process) { |
229 | char buffer[OutputBufferSize]; |
230 | size_t count; |
231 | while ((count = process.GetSTDOUT(dst: buffer, dst_len: sizeof(buffer))) > 0) |
232 | dap.SendOutput(o: OutputType::Stdout, output: llvm::StringRef(buffer, count)); |
233 | while ((count = process.GetSTDERR(dst: buffer, dst_len: sizeof(buffer))) > 0) |
234 | dap.SendOutput(o: OutputType::Stderr, output: llvm::StringRef(buffer, count)); |
235 | } |
236 | |
237 | // Send a "continued" event to indicate the process is in the running state. |
238 | void SendContinuedEvent(DAP &dap) { |
239 | lldb::SBProcess process = dap.target.GetProcess(); |
240 | if (!process.IsValid()) { |
241 | return; |
242 | } |
243 | |
244 | // If the focus thread is not set then we haven't reported any thread status |
245 | // to the client, so nothing to report. |
246 | if (!dap.configuration_done || dap.focus_tid == LLDB_INVALID_THREAD_ID) { |
247 | return; |
248 | } |
249 | |
250 | llvm::json::Object event(CreateEventObject(event_name: "continued" )); |
251 | llvm::json::Object body; |
252 | body.try_emplace(K: "threadId" , Args: (int64_t)dap.focus_tid); |
253 | body.try_emplace(K: "allThreadsContinued" , Args: true); |
254 | event.try_emplace(K: "body" , Args: std::move(body)); |
255 | dap.SendJSON(json: llvm::json::Value(std::move(event))); |
256 | } |
257 | |
258 | // Send a "exited" event to indicate the process has exited. |
259 | void SendProcessExitedEvent(DAP &dap, lldb::SBProcess &process) { |
260 | llvm::json::Object event(CreateEventObject(event_name: "exited" )); |
261 | llvm::json::Object body; |
262 | body.try_emplace(K: "exitCode" , Args: (int64_t)process.GetExitStatus()); |
263 | event.try_emplace(K: "body" , Args: std::move(body)); |
264 | dap.SendJSON(json: llvm::json::Value(std::move(event))); |
265 | } |
266 | |
267 | } // namespace lldb_dap |
268 | |