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

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