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
28using namespace llvm;
29
30namespace lldb_dap {
31
32static 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
41void 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// }
111void 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.
140llvm::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.
216void 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.
228void 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.
238void 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.
259void 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

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