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