1 | //===-- DAP.cpp -------------------------------------------------*- C++ -*-===// |
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 "DAP.h" |
10 | #include "DAPLog.h" |
11 | #include "EventHelper.h" |
12 | #include "Handler/RequestHandler.h" |
13 | #include "Handler/ResponseHandler.h" |
14 | #include "JSONUtils.h" |
15 | #include "LLDBUtils.h" |
16 | #include "OutputRedirector.h" |
17 | #include "Protocol/ProtocolBase.h" |
18 | #include "Protocol/ProtocolRequests.h" |
19 | #include "Protocol/ProtocolTypes.h" |
20 | #include "Transport.h" |
21 | #include "lldb/API/SBBreakpoint.h" |
22 | #include "lldb/API/SBCommandInterpreter.h" |
23 | #include "lldb/API/SBCommandReturnObject.h" |
24 | #include "lldb/API/SBEvent.h" |
25 | #include "lldb/API/SBLanguageRuntime.h" |
26 | #include "lldb/API/SBListener.h" |
27 | #include "lldb/API/SBProcess.h" |
28 | #include "lldb/API/SBStream.h" |
29 | #include "lldb/Utility/IOObject.h" |
30 | #include "lldb/Utility/Status.h" |
31 | #include "lldb/lldb-defines.h" |
32 | #include "lldb/lldb-enumerations.h" |
33 | #include "lldb/lldb-types.h" |
34 | #include "llvm/ADT/ArrayRef.h" |
35 | #include "llvm/ADT/STLExtras.h" |
36 | #include "llvm/ADT/ScopeExit.h" |
37 | #include "llvm/ADT/StringExtras.h" |
38 | #include "llvm/ADT/StringRef.h" |
39 | #include "llvm/ADT/Twine.h" |
40 | #include "llvm/Support/Chrono.h" |
41 | #include "llvm/Support/Error.h" |
42 | #include "llvm/Support/ErrorHandling.h" |
43 | #include "llvm/Support/FormatVariadic.h" |
44 | #include "llvm/Support/raw_ostream.h" |
45 | #include <algorithm> |
46 | #include <cassert> |
47 | #include <chrono> |
48 | #include <condition_variable> |
49 | #include <cstdarg> |
50 | #include <cstdint> |
51 | #include <cstdio> |
52 | #include <fstream> |
53 | #include <future> |
54 | #include <memory> |
55 | #include <mutex> |
56 | #include <optional> |
57 | #include <string> |
58 | #include <thread> |
59 | #include <utility> |
60 | #include <variant> |
61 | |
62 | #if defined(_WIN32) |
63 | #define NOMINMAX |
64 | #include <fcntl.h> |
65 | #include <io.h> |
66 | #include <windows.h> |
67 | #else |
68 | #include <unistd.h> |
69 | #endif |
70 | |
71 | using namespace lldb_dap; |
72 | using namespace lldb_dap::protocol; |
73 | |
74 | namespace { |
75 | #ifdef _WIN32 |
76 | const char DEV_NULL[] = "nul" ; |
77 | #else |
78 | const char DEV_NULL[] = "/dev/null" ; |
79 | #endif |
80 | } // namespace |
81 | |
82 | namespace lldb_dap { |
83 | |
84 | static std::string GetStringFromStructuredData(lldb::SBStructuredData &data, |
85 | const char *key) { |
86 | lldb::SBStructuredData keyValue = data.GetValueForKey(key); |
87 | if (!keyValue) |
88 | return std::string(); |
89 | |
90 | const size_t length = keyValue.GetStringValue(dst: nullptr, dst_len: 0); |
91 | |
92 | if (length == 0) |
93 | return std::string(); |
94 | |
95 | std::string str(length + 1, 0); |
96 | keyValue.GetStringValue(dst: &str[0], dst_len: length + 1); |
97 | return str; |
98 | } |
99 | |
100 | static uint64_t GetUintFromStructuredData(lldb::SBStructuredData &data, |
101 | const char *key) { |
102 | lldb::SBStructuredData keyValue = data.GetValueForKey(key); |
103 | |
104 | if (!keyValue.IsValid()) |
105 | return 0; |
106 | return keyValue.GetUnsignedIntegerValue(); |
107 | } |
108 | |
109 | /// Return string with first character capitalized. |
110 | static std::string capitalize(llvm::StringRef str) { |
111 | if (str.empty()) |
112 | return "" ; |
113 | return ((llvm::Twine)llvm::toUpper(x: str[0]) + str.drop_front()).str(); |
114 | } |
115 | |
116 | llvm::StringRef DAP::debug_adapter_path = "" ; |
117 | |
118 | DAP::DAP(Log *log, const ReplMode default_repl_mode, |
119 | std::vector<std::string> pre_init_commands, Transport &transport) |
120 | : log(log), transport(transport), broadcaster("lldb-dap" ), |
121 | progress_event_reporter( |
122 | [&](const ProgressEvent &event) { SendJSON(json: event.ToJSON()); }), |
123 | repl_mode(default_repl_mode) { |
124 | configuration.preInitCommands = std::move(pre_init_commands); |
125 | RegisterRequests(); |
126 | } |
127 | |
128 | DAP::~DAP() = default; |
129 | |
130 | void DAP::PopulateExceptionBreakpoints() { |
131 | llvm::call_once(flag&: init_exception_breakpoints_flag, F: [this]() { |
132 | exception_breakpoints = std::vector<ExceptionBreakpoint>{}; |
133 | |
134 | if (lldb::SBDebugger::SupportsLanguage(language: lldb::eLanguageTypeC_plus_plus)) { |
135 | exception_breakpoints->emplace_back(args&: *this, args: "cpp_catch" , args: "C++ Catch" , |
136 | args: lldb::eLanguageTypeC_plus_plus); |
137 | exception_breakpoints->emplace_back(args&: *this, args: "cpp_throw" , args: "C++ Throw" , |
138 | args: lldb::eLanguageTypeC_plus_plus); |
139 | } |
140 | if (lldb::SBDebugger::SupportsLanguage(language: lldb::eLanguageTypeObjC)) { |
141 | exception_breakpoints->emplace_back( |
142 | args&: *this, args: "objc_catch" , args: "Objective-C Catch" , args: lldb::eLanguageTypeObjC); |
143 | exception_breakpoints->emplace_back( |
144 | args&: *this, args: "objc_throw" , args: "Objective-C Throw" , args: lldb::eLanguageTypeObjC); |
145 | } |
146 | if (lldb::SBDebugger::SupportsLanguage(language: lldb::eLanguageTypeSwift)) { |
147 | exception_breakpoints->emplace_back(args&: *this, args: "swift_catch" , args: "Swift Catch" , |
148 | args: lldb::eLanguageTypeSwift); |
149 | exception_breakpoints->emplace_back(args&: *this, args: "swift_throw" , args: "Swift Throw" , |
150 | args: lldb::eLanguageTypeSwift); |
151 | } |
152 | // Besides handling the hardcoded list of languages from above, we try to |
153 | // find any other languages that support exception breakpoints using the |
154 | // SB API. |
155 | for (int raw_lang = lldb::eLanguageTypeUnknown; |
156 | raw_lang < lldb::eNumLanguageTypes; ++raw_lang) { |
157 | lldb::LanguageType lang = static_cast<lldb::LanguageType>(raw_lang); |
158 | |
159 | // We first discard any languages already handled above. |
160 | if (lldb::SBLanguageRuntime::LanguageIsCFamily(language: lang) || |
161 | lang == lldb::eLanguageTypeSwift) |
162 | continue; |
163 | |
164 | if (!lldb::SBDebugger::SupportsLanguage(language: lang)) |
165 | continue; |
166 | |
167 | const char *name = lldb::SBLanguageRuntime::GetNameForLanguageType(language: lang); |
168 | if (!name) |
169 | continue; |
170 | std::string raw_lang_name = name; |
171 | std::string capitalized_lang_name = capitalize(str: name); |
172 | |
173 | if (lldb::SBLanguageRuntime::SupportsExceptionBreakpointsOnThrow(language: lang)) { |
174 | const char *raw_throw_keyword = |
175 | lldb::SBLanguageRuntime::GetThrowKeywordForLanguage(language: lang); |
176 | std::string throw_keyword = |
177 | raw_throw_keyword ? raw_throw_keyword : "throw" ; |
178 | |
179 | exception_breakpoints->emplace_back( |
180 | args&: *this, args: raw_lang_name + "_" + throw_keyword, |
181 | args: capitalized_lang_name + " " + capitalize(str: throw_keyword), args&: lang); |
182 | } |
183 | |
184 | if (lldb::SBLanguageRuntime::SupportsExceptionBreakpointsOnCatch(language: lang)) { |
185 | const char *raw_catch_keyword = |
186 | lldb::SBLanguageRuntime::GetCatchKeywordForLanguage(language: lang); |
187 | std::string catch_keyword = |
188 | raw_catch_keyword ? raw_catch_keyword : "catch" ; |
189 | |
190 | exception_breakpoints->emplace_back( |
191 | args&: *this, args: raw_lang_name + "_" + catch_keyword, |
192 | args: capitalized_lang_name + " " + capitalize(str: catch_keyword), args&: lang); |
193 | } |
194 | } |
195 | assert(!exception_breakpoints->empty() && "should not be empty" ); |
196 | }); |
197 | } |
198 | |
199 | ExceptionBreakpoint *DAP::GetExceptionBreakpoint(llvm::StringRef filter) { |
200 | // PopulateExceptionBreakpoints() is called after g_dap.debugger is created |
201 | // in a request-initialize. |
202 | // |
203 | // But this GetExceptionBreakpoint() method may be called before attaching, in |
204 | // which case, we may not have populated the filter yet. |
205 | // |
206 | // We also cannot call PopulateExceptionBreakpoints() in DAP::DAP() because |
207 | // we need SBDebugger::Initialize() to have been called before this. |
208 | // |
209 | // So just calling PopulateExceptionBreakoints(),which does lazy-populating |
210 | // seems easiest. Two other options include: |
211 | // + call g_dap.PopulateExceptionBreakpoints() in lldb-dap.cpp::main() |
212 | // right after the call to SBDebugger::Initialize() |
213 | // + Just call PopulateExceptionBreakpoints() to get a fresh list everytime |
214 | // we query (a bit overkill since it's not likely to change?) |
215 | PopulateExceptionBreakpoints(); |
216 | |
217 | for (auto &bp : *exception_breakpoints) { |
218 | if (bp.GetFilter() == filter) |
219 | return &bp; |
220 | } |
221 | return nullptr; |
222 | } |
223 | |
224 | ExceptionBreakpoint *DAP::GetExceptionBreakpoint(const lldb::break_id_t bp_id) { |
225 | // See comment in the other GetExceptionBreakpoint(). |
226 | PopulateExceptionBreakpoints(); |
227 | |
228 | for (auto &bp : *exception_breakpoints) { |
229 | if (bp.GetID() == bp_id) |
230 | return &bp; |
231 | } |
232 | return nullptr; |
233 | } |
234 | |
235 | llvm::Error DAP::ConfigureIO(std::FILE *overrideOut, std::FILE *overrideErr) { |
236 | in = lldb::SBFile(std::fopen(filename: DEV_NULL, modes: "r" ), /*transfer_ownership=*/true); |
237 | |
238 | if (auto Error = out.RedirectTo(file_override: overrideOut, callback: [this](llvm::StringRef output) { |
239 | SendOutput(o: OutputType::Console, output); |
240 | })) |
241 | return Error; |
242 | |
243 | if (auto Error = err.RedirectTo(file_override: overrideErr, callback: [this](llvm::StringRef output) { |
244 | SendOutput(o: OutputType::Console, output); |
245 | })) |
246 | return Error; |
247 | |
248 | return llvm::Error::success(); |
249 | } |
250 | |
251 | void DAP::StopEventHandlers() { |
252 | if (event_thread.joinable()) { |
253 | broadcaster.BroadcastEventByType(event_type: eBroadcastBitStopEventThread); |
254 | event_thread.join(); |
255 | } |
256 | if (progress_event_thread.joinable()) { |
257 | broadcaster.BroadcastEventByType(event_type: eBroadcastBitStopProgressThread); |
258 | progress_event_thread.join(); |
259 | } |
260 | } |
261 | |
262 | // Serialize the JSON value into a string and send the JSON packet to |
263 | // the "out" stream. |
264 | void DAP::SendJSON(const llvm::json::Value &json) { |
265 | // FIXME: Instead of parsing the output message from JSON, pass the `Message` |
266 | // as parameter to `SendJSON`. |
267 | Message message; |
268 | llvm::json::Path::Root root; |
269 | if (!fromJSON(json, message, root)) { |
270 | DAP_LOG_ERROR(log, root.getError(), "({1}) encoding failed: {0}" , |
271 | transport.GetClientName()); |
272 | return; |
273 | } |
274 | Send(message); |
275 | } |
276 | |
277 | void DAP::Send(const Message &message) { |
278 | // FIXME: After all the requests have migrated from LegacyRequestHandler > |
279 | // RequestHandler<> this should be handled in RequestHandler<>::operator(). |
280 | if (auto *resp = std::get_if<Response>(ptr: &message); |
281 | resp && debugger.InterruptRequested()) { |
282 | // Clear the interrupt request. |
283 | debugger.CancelInterruptRequest(); |
284 | |
285 | // If the debugger was interrupted, convert this response into a 'cancelled' |
286 | // response because we might have a partial result. |
287 | Response cancelled{/*request_seq=*/resp->request_seq, |
288 | /*command=*/resp->command, |
289 | /*success=*/false, |
290 | /*message=*/eResponseMessageCancelled, |
291 | /*body=*/std::nullopt}; |
292 | if (llvm::Error err = transport.Write(M: cancelled)) |
293 | DAP_LOG_ERROR(log, std::move(err), "({1}) write failed: {0}" , |
294 | transport.GetClientName()); |
295 | return; |
296 | } |
297 | |
298 | if (llvm::Error err = transport.Write(M: message)) |
299 | DAP_LOG_ERROR(log, std::move(err), "({1}) write failed: {0}" , |
300 | transport.GetClientName()); |
301 | } |
302 | |
303 | // "OutputEvent": { |
304 | // "allOf": [ { "$ref": "#/definitions/Event" }, { |
305 | // "type": "object", |
306 | // "description": "Event message for 'output' event type. The event |
307 | // indicates that the target has produced some output.", |
308 | // "properties": { |
309 | // "event": { |
310 | // "type": "string", |
311 | // "enum": [ "output" ] |
312 | // }, |
313 | // "body": { |
314 | // "type": "object", |
315 | // "properties": { |
316 | // "category": { |
317 | // "type": "string", |
318 | // "description": "The output category. If not specified, |
319 | // 'console' is assumed.", |
320 | // "_enum": [ "console", "stdout", "stderr", "telemetry" ] |
321 | // }, |
322 | // "output": { |
323 | // "type": "string", |
324 | // "description": "The output to report." |
325 | // }, |
326 | // "variablesReference": { |
327 | // "type": "number", |
328 | // "description": "If an attribute 'variablesReference' exists |
329 | // and its value is > 0, the output contains |
330 | // objects which can be retrieved by passing |
331 | // variablesReference to the VariablesRequest." |
332 | // }, |
333 | // "source": { |
334 | // "$ref": "#/definitions/Source", |
335 | // "description": "An optional source location where the output |
336 | // was produced." |
337 | // }, |
338 | // "line": { |
339 | // "type": "integer", |
340 | // "description": "An optional source location line where the |
341 | // output was produced." |
342 | // }, |
343 | // "column": { |
344 | // "type": "integer", |
345 | // "description": "An optional source location column where the |
346 | // output was produced." |
347 | // }, |
348 | // "data": { |
349 | // "type":["array","boolean","integer","null","number","object", |
350 | // "string"], |
351 | // "description": "Optional data to report. For the 'telemetry' |
352 | // category the data will be sent to telemetry, for |
353 | // the other categories the data is shown in JSON |
354 | // format." |
355 | // } |
356 | // }, |
357 | // "required": ["output"] |
358 | // } |
359 | // }, |
360 | // "required": [ "event", "body" ] |
361 | // }] |
362 | // } |
363 | void DAP::SendOutput(OutputType o, const llvm::StringRef output) { |
364 | if (output.empty()) |
365 | return; |
366 | |
367 | const char *category = nullptr; |
368 | switch (o) { |
369 | case OutputType::Console: |
370 | category = "console" ; |
371 | break; |
372 | case OutputType::Important: |
373 | category = "important" ; |
374 | break; |
375 | case OutputType::Stdout: |
376 | category = "stdout" ; |
377 | break; |
378 | case OutputType::Stderr: |
379 | category = "stderr" ; |
380 | break; |
381 | case OutputType::Telemetry: |
382 | category = "telemetry" ; |
383 | break; |
384 | } |
385 | |
386 | // Send each line of output as an individual event, including the newline if |
387 | // present. |
388 | ::size_t idx = 0; |
389 | do { |
390 | ::size_t end = output.find(C: '\n', From: idx); |
391 | if (end == llvm::StringRef::npos) |
392 | end = output.size() - 1; |
393 | llvm::json::Object event(CreateEventObject(event_name: "output" )); |
394 | llvm::json::Object body; |
395 | body.try_emplace(K: "category" , Args&: category); |
396 | EmplaceSafeString(obj&: body, key: "output" , str: output.slice(Start: idx, End: end + 1).str()); |
397 | event.try_emplace(K: "body" , Args: std::move(body)); |
398 | SendJSON(json: llvm::json::Value(std::move(event))); |
399 | idx = end + 1; |
400 | } while (idx < output.size()); |
401 | } |
402 | |
403 | // interface ProgressStartEvent extends Event { |
404 | // event: 'progressStart'; |
405 | // |
406 | // body: { |
407 | // /** |
408 | // * An ID that must be used in subsequent 'progressUpdate' and |
409 | // 'progressEnd' |
410 | // * events to make them refer to the same progress reporting. |
411 | // * IDs must be unique within a debug session. |
412 | // */ |
413 | // progressId: string; |
414 | // |
415 | // /** |
416 | // * Mandatory (short) title of the progress reporting. Shown in the UI to |
417 | // * describe the long running operation. |
418 | // */ |
419 | // title: string; |
420 | // |
421 | // /** |
422 | // * The request ID that this progress report is related to. If specified a |
423 | // * debug adapter is expected to emit |
424 | // * progress events for the long running request until the request has |
425 | // been |
426 | // * either completed or cancelled. |
427 | // * If the request ID is omitted, the progress report is assumed to be |
428 | // * related to some general activity of the debug adapter. |
429 | // */ |
430 | // requestId?: number; |
431 | // |
432 | // /** |
433 | // * If true, the request that reports progress may be canceled with a |
434 | // * 'cancel' request. |
435 | // * So this property basically controls whether the client should use UX |
436 | // that |
437 | // * supports cancellation. |
438 | // * Clients that don't support cancellation are allowed to ignore the |
439 | // * setting. |
440 | // */ |
441 | // cancellable?: boolean; |
442 | // |
443 | // /** |
444 | // * Optional, more detailed progress message. |
445 | // */ |
446 | // message?: string; |
447 | // |
448 | // /** |
449 | // * Optional progress percentage to display (value range: 0 to 100). If |
450 | // * omitted no percentage will be shown. |
451 | // */ |
452 | // percentage?: number; |
453 | // }; |
454 | // } |
455 | // |
456 | // interface ProgressUpdateEvent extends Event { |
457 | // event: 'progressUpdate'; |
458 | // |
459 | // body: { |
460 | // /** |
461 | // * The ID that was introduced in the initial 'progressStart' event. |
462 | // */ |
463 | // progressId: string; |
464 | // |
465 | // /** |
466 | // * Optional, more detailed progress message. If omitted, the previous |
467 | // * message (if any) is used. |
468 | // */ |
469 | // message?: string; |
470 | // |
471 | // /** |
472 | // * Optional progress percentage to display (value range: 0 to 100). If |
473 | // * omitted no percentage will be shown. |
474 | // */ |
475 | // percentage?: number; |
476 | // }; |
477 | // } |
478 | // |
479 | // interface ProgressEndEvent extends Event { |
480 | // event: 'progressEnd'; |
481 | // |
482 | // body: { |
483 | // /** |
484 | // * The ID that was introduced in the initial 'ProgressStartEvent'. |
485 | // */ |
486 | // progressId: string; |
487 | // |
488 | // /** |
489 | // * Optional, more detailed progress message. If omitted, the previous |
490 | // * message (if any) is used. |
491 | // */ |
492 | // message?: string; |
493 | // }; |
494 | // } |
495 | |
496 | void DAP::SendProgressEvent(uint64_t progress_id, const char *message, |
497 | uint64_t completed, uint64_t total) { |
498 | progress_event_reporter.Push(progress_id, message, completed, total); |
499 | } |
500 | |
501 | void __attribute__((format(printf, 3, 4))) |
502 | DAP::SendFormattedOutput(OutputType o, const char *format, ...) { |
503 | char buffer[1024]; |
504 | va_list args; |
505 | va_start(args, format); |
506 | int actual_length = vsnprintf(s: buffer, maxlen: sizeof(buffer), format: format, arg: args); |
507 | va_end(args); |
508 | SendOutput( |
509 | o, output: llvm::StringRef(buffer, std::min<int>(a: actual_length, b: sizeof(buffer)))); |
510 | } |
511 | |
512 | ExceptionBreakpoint *DAP::GetExceptionBPFromStopReason(lldb::SBThread &thread) { |
513 | const auto num = thread.GetStopReasonDataCount(); |
514 | // Check to see if have hit an exception breakpoint and change the |
515 | // reason to "exception", but only do so if all breakpoints that were |
516 | // hit are exception breakpoints. |
517 | ExceptionBreakpoint *exc_bp = nullptr; |
518 | for (size_t i = 0; i < num; i += 2) { |
519 | // thread.GetStopReasonDataAtIndex(i) will return the bp ID and |
520 | // thread.GetStopReasonDataAtIndex(i+1) will return the location |
521 | // within that breakpoint. We only care about the bp ID so we can |
522 | // see if this is an exception breakpoint that is getting hit. |
523 | lldb::break_id_t bp_id = thread.GetStopReasonDataAtIndex(idx: i); |
524 | exc_bp = GetExceptionBreakpoint(bp_id); |
525 | // If any breakpoint is not an exception breakpoint, then stop and |
526 | // report this as a normal breakpoint |
527 | if (exc_bp == nullptr) |
528 | return nullptr; |
529 | } |
530 | return exc_bp; |
531 | } |
532 | |
533 | lldb::SBThread DAP::GetLLDBThread(lldb::tid_t tid) { |
534 | return target.GetProcess().GetThreadByID(sb_thread_id: tid); |
535 | } |
536 | |
537 | lldb::SBThread DAP::GetLLDBThread(const llvm::json::Object &arguments) { |
538 | auto tid = GetInteger<int64_t>(obj: arguments, key: "threadId" ) |
539 | .value_or(LLDB_INVALID_THREAD_ID); |
540 | return target.GetProcess().GetThreadByID(sb_thread_id: tid); |
541 | } |
542 | |
543 | lldb::SBFrame DAP::GetLLDBFrame(uint64_t frame_id) { |
544 | lldb::SBProcess process = target.GetProcess(); |
545 | // Upper 32 bits is the thread index ID |
546 | lldb::SBThread thread = |
547 | process.GetThreadByIndexID(index_id: GetLLDBThreadIndexID(dap_frame_id: frame_id)); |
548 | // Lower 32 bits is the frame index |
549 | return thread.GetFrameAtIndex(idx: GetLLDBFrameID(dap_frame_id: frame_id)); |
550 | } |
551 | |
552 | lldb::SBFrame DAP::GetLLDBFrame(const llvm::json::Object &arguments) { |
553 | const auto frame_id = |
554 | GetInteger<uint64_t>(obj: arguments, key: "frameId" ).value_or(UINT64_MAX); |
555 | return GetLLDBFrame(frame_id); |
556 | } |
557 | |
558 | ReplMode DAP::DetectReplMode(lldb::SBFrame frame, std::string &expression, |
559 | bool partial_expression) { |
560 | // Check for the escape hatch prefix. |
561 | if (!expression.empty() && |
562 | llvm::StringRef(expression) |
563 | .starts_with(Prefix: configuration.commandEscapePrefix)) { |
564 | expression = expression.substr(pos: configuration.commandEscapePrefix.size()); |
565 | return ReplMode::Command; |
566 | } |
567 | |
568 | switch (repl_mode) { |
569 | case ReplMode::Variable: |
570 | return ReplMode::Variable; |
571 | case ReplMode::Command: |
572 | return ReplMode::Command; |
573 | case ReplMode::Auto: |
574 | // To determine if the expression is a command or not, check if the first |
575 | // term is a variable or command. If it's a variable in scope we will prefer |
576 | // that behavior and give a warning to the user if they meant to invoke the |
577 | // operation as a command. |
578 | // |
579 | // Example use case: |
580 | // int p and expression "p + 1" > variable |
581 | // int i and expression "i" > variable |
582 | // int var and expression "va" > command |
583 | std::pair<llvm::StringRef, llvm::StringRef> token = |
584 | llvm::getToken(Source: expression); |
585 | |
586 | // If the first token is not fully finished yet, we can't |
587 | // determine whether this will be a variable or a lldb command. |
588 | if (partial_expression && token.second.empty()) |
589 | return ReplMode::Auto; |
590 | |
591 | std::string term = token.first.str(); |
592 | lldb::SBCommandInterpreter interpreter = debugger.GetCommandInterpreter(); |
593 | bool term_is_command = interpreter.CommandExists(cmd: term.c_str()) || |
594 | interpreter.UserCommandExists(cmd: term.c_str()) || |
595 | interpreter.AliasExists(cmd: term.c_str()); |
596 | bool term_is_variable = frame.FindVariable(var_name: term.c_str()).IsValid(); |
597 | |
598 | // If we have both a variable and command, warn the user about the conflict. |
599 | if (term_is_command && term_is_variable) { |
600 | llvm::errs() |
601 | << "Warning: Expression '" << term |
602 | << "' is both an LLDB command and variable. It will be evaluated as " |
603 | "a variable. To evaluate the expression as an LLDB command, use '" |
604 | << configuration.commandEscapePrefix << "' as a prefix.\n" ; |
605 | } |
606 | |
607 | // Variables take preference to commands in auto, since commands can always |
608 | // be called using the command_escape_prefix |
609 | return term_is_variable ? ReplMode::Variable |
610 | : term_is_command ? ReplMode::Command |
611 | : ReplMode::Variable; |
612 | } |
613 | |
614 | llvm_unreachable("enum cases exhausted." ); |
615 | } |
616 | |
617 | bool DAP::RunLLDBCommands(llvm::StringRef prefix, |
618 | llvm::ArrayRef<std::string> commands) { |
619 | bool required_command_failed = false; |
620 | std::string output = ::RunLLDBCommands( |
621 | debugger, prefix, commands, required_command_failed, |
622 | /*parse_command_directives*/ true, /*echo_commands*/ true); |
623 | SendOutput(o: OutputType::Console, output); |
624 | return !required_command_failed; |
625 | } |
626 | |
627 | static llvm::Error createRunLLDBCommandsErrorMessage(llvm::StringRef category) { |
628 | return llvm::createStringError( |
629 | EC: llvm::inconvertibleErrorCode(), |
630 | S: llvm::formatv( |
631 | Fmt: "Failed to run {0} commands. See the Debug Console for more details." , |
632 | Vals&: category) |
633 | .str() |
634 | .c_str()); |
635 | } |
636 | |
637 | llvm::Error |
638 | DAP::RunAttachCommands(llvm::ArrayRef<std::string> attach_commands) { |
639 | if (!RunLLDBCommands(prefix: "Running attachCommands:" , commands: attach_commands)) |
640 | return createRunLLDBCommandsErrorMessage(category: "attach" ); |
641 | return llvm::Error::success(); |
642 | } |
643 | |
644 | llvm::Error |
645 | DAP::RunLaunchCommands(llvm::ArrayRef<std::string> launch_commands) { |
646 | if (!RunLLDBCommands(prefix: "Running launchCommands:" , commands: launch_commands)) |
647 | return createRunLLDBCommandsErrorMessage(category: "launch" ); |
648 | return llvm::Error::success(); |
649 | } |
650 | |
651 | llvm::Error DAP::RunInitCommands() { |
652 | if (!RunLLDBCommands(prefix: "Running initCommands:" , commands: configuration.initCommands)) |
653 | return createRunLLDBCommandsErrorMessage(category: "initCommands" ); |
654 | return llvm::Error::success(); |
655 | } |
656 | |
657 | llvm::Error DAP::RunPreInitCommands() { |
658 | if (!RunLLDBCommands(prefix: "Running preInitCommands:" , |
659 | commands: configuration.preInitCommands)) |
660 | return createRunLLDBCommandsErrorMessage(category: "preInitCommands" ); |
661 | return llvm::Error::success(); |
662 | } |
663 | |
664 | llvm::Error DAP::RunPreRunCommands() { |
665 | if (!RunLLDBCommands(prefix: "Running preRunCommands:" , commands: configuration.preRunCommands)) |
666 | return createRunLLDBCommandsErrorMessage(category: "preRunCommands" ); |
667 | return llvm::Error::success(); |
668 | } |
669 | |
670 | void DAP::RunPostRunCommands() { |
671 | RunLLDBCommands(prefix: "Running postRunCommands:" , commands: configuration.postRunCommands); |
672 | } |
673 | void DAP::RunStopCommands() { |
674 | RunLLDBCommands(prefix: "Running stopCommands:" , commands: configuration.stopCommands); |
675 | } |
676 | |
677 | void DAP::RunExitCommands() { |
678 | RunLLDBCommands(prefix: "Running exitCommands:" , commands: configuration.exitCommands); |
679 | } |
680 | |
681 | void DAP::RunTerminateCommands() { |
682 | RunLLDBCommands(prefix: "Running terminateCommands:" , |
683 | commands: configuration.terminateCommands); |
684 | } |
685 | |
686 | lldb::SBTarget DAP::CreateTarget(lldb::SBError &error) { |
687 | // Grab the name of the program we need to debug and create a target using |
688 | // the given program as an argument. Executable file can be a source of target |
689 | // architecture and platform, if they differ from the host. Setting exe path |
690 | // in launch info is useless because Target.Launch() will not change |
691 | // architecture and platform, therefore they should be known at the target |
692 | // creation. We also use target triple and platform from the launch |
693 | // configuration, if given, since in some cases ELF file doesn't contain |
694 | // enough information to determine correct arch and platform (or ELF can be |
695 | // omitted at all), so it is good to leave the user an opportunity to specify |
696 | // those. Any of those three can be left empty. |
697 | auto target = this->debugger.CreateTarget( |
698 | /*filename=*/configuration.program.data(), |
699 | /*target_triple=*/configuration.targetTriple.data(), |
700 | /*platform_name=*/configuration.platformName.data(), |
701 | /*add_dependent_modules=*/true, // Add dependent modules. |
702 | error); |
703 | |
704 | return target; |
705 | } |
706 | |
707 | void DAP::SetTarget(const lldb::SBTarget target) { |
708 | this->target = target; |
709 | |
710 | if (target.IsValid()) { |
711 | // Configure breakpoint event listeners for the target. |
712 | lldb::SBListener listener = this->debugger.GetListener(); |
713 | listener.StartListeningForEvents( |
714 | broadcaster: this->target.GetBroadcaster(), |
715 | event_mask: lldb::SBTarget::eBroadcastBitBreakpointChanged | |
716 | lldb::SBTarget::eBroadcastBitModulesLoaded | |
717 | lldb::SBTarget::eBroadcastBitModulesUnloaded | |
718 | lldb::SBTarget::eBroadcastBitSymbolsLoaded | |
719 | lldb::SBTarget::eBroadcastBitSymbolsChanged); |
720 | listener.StartListeningForEvents(broadcaster: this->broadcaster, |
721 | event_mask: eBroadcastBitStopEventThread); |
722 | } |
723 | } |
724 | |
725 | bool DAP::HandleObject(const Message &M) { |
726 | TelemetryDispatcher dispatcher(&debugger); |
727 | dispatcher.Set(key: "client_name" , value: transport.GetClientName().str()); |
728 | if (const auto *req = std::get_if<Request>(ptr: &M)) { |
729 | { |
730 | std::lock_guard<std::mutex> guard(m_active_request_mutex); |
731 | m_active_request = req; |
732 | |
733 | // Clear the interrupt request prior to invoking a handler. |
734 | if (debugger.InterruptRequested()) |
735 | debugger.CancelInterruptRequest(); |
736 | } |
737 | |
738 | auto cleanup = llvm::make_scope_exit(F: [&]() { |
739 | std::scoped_lock<std::mutex> active_request_lock(m_active_request_mutex); |
740 | m_active_request = nullptr; |
741 | }); |
742 | |
743 | auto handler_pos = request_handlers.find(Key: req->command); |
744 | dispatcher.Set(key: "client_data" , |
745 | value: llvm::Twine("request_command:" , req->command).str()); |
746 | if (handler_pos != request_handlers.end()) { |
747 | handler_pos->second->Run(*req); |
748 | return true; // Success |
749 | } |
750 | |
751 | dispatcher.Set(key: "error" , |
752 | value: llvm::Twine("unhandled-command:" + req->command).str()); |
753 | DAP_LOG(log, "({0}) error: unhandled command '{1}'" , |
754 | transport.GetClientName(), req->command); |
755 | return false; // Fail |
756 | } |
757 | |
758 | if (const auto *resp = std::get_if<Response>(ptr: &M)) { |
759 | std::unique_ptr<ResponseHandler> response_handler; |
760 | { |
761 | std::lock_guard<std::mutex> guard(call_mutex); |
762 | auto inflight = inflight_reverse_requests.find(Val: resp->request_seq); |
763 | if (inflight != inflight_reverse_requests.end()) { |
764 | response_handler = std::move(inflight->second); |
765 | inflight_reverse_requests.erase(I: inflight); |
766 | } |
767 | } |
768 | |
769 | if (!response_handler) |
770 | response_handler = |
771 | std::make_unique<UnknownResponseHandler>(args: "" , args: resp->request_seq); |
772 | |
773 | // Result should be given, use null if not. |
774 | if (resp->success) { |
775 | (*response_handler)(resp->body); |
776 | dispatcher.Set(key: "client_data" , |
777 | value: llvm::Twine("response_command:" , resp->command).str()); |
778 | } else { |
779 | llvm::StringRef message = "Unknown error, response failed" ; |
780 | if (resp->message) { |
781 | message = |
782 | std::visit(visitor: llvm::makeVisitor( |
783 | Callables: [](const std::string &message) -> llvm::StringRef { |
784 | return message; |
785 | }, |
786 | Callables: [](const protocol::ResponseMessage &message) |
787 | -> llvm::StringRef { |
788 | switch (message) { |
789 | case protocol::eResponseMessageCancelled: |
790 | return "cancelled" ; |
791 | case protocol::eResponseMessageNotStopped: |
792 | return "notStopped" ; |
793 | } |
794 | llvm_unreachable("unknown response message kind." ); |
795 | }), |
796 | variants: *resp->message); |
797 | } |
798 | dispatcher.Set(key: "error" , value: message.str()); |
799 | |
800 | (*response_handler)(llvm::createStringError( |
801 | EC: std::error_code(-1, std::generic_category()), S: message)); |
802 | } |
803 | |
804 | return true; |
805 | } |
806 | |
807 | dispatcher.Set(key: "error" , value: "Unsupported protocol message" ); |
808 | DAP_LOG(log, "Unsupported protocol message" ); |
809 | |
810 | return false; |
811 | } |
812 | |
813 | void DAP::SendTerminatedEvent() { |
814 | // Prevent races if the process exits while we're being asked to disconnect. |
815 | llvm::call_once(flag&: terminated_event_flag, F: [&] { |
816 | RunTerminateCommands(); |
817 | // Send a "terminated" event |
818 | llvm::json::Object event(CreateTerminatedEventObject(target)); |
819 | SendJSON(json: llvm::json::Value(std::move(event))); |
820 | }); |
821 | } |
822 | |
823 | llvm::Error DAP::Disconnect() { return Disconnect(terminateDebuggee: !is_attach); } |
824 | |
825 | llvm::Error DAP::Disconnect(bool terminateDebuggee) { |
826 | lldb::SBError error; |
827 | lldb::SBProcess process = target.GetProcess(); |
828 | auto state = process.GetState(); |
829 | switch (state) { |
830 | case lldb::eStateInvalid: |
831 | case lldb::eStateUnloaded: |
832 | case lldb::eStateDetached: |
833 | case lldb::eStateExited: |
834 | break; |
835 | case lldb::eStateConnected: |
836 | case lldb::eStateAttaching: |
837 | case lldb::eStateLaunching: |
838 | case lldb::eStateStepping: |
839 | case lldb::eStateCrashed: |
840 | case lldb::eStateSuspended: |
841 | case lldb::eStateStopped: |
842 | case lldb::eStateRunning: { |
843 | ScopeSyncMode scope_sync_mode(debugger); |
844 | error = terminateDebuggee ? process.Kill() : process.Detach(); |
845 | break; |
846 | } |
847 | } |
848 | |
849 | SendTerminatedEvent(); |
850 | |
851 | disconnecting = true; |
852 | |
853 | return ToError(error); |
854 | } |
855 | |
856 | bool DAP::IsCancelled(const protocol::Request &req) { |
857 | std::lock_guard<std::mutex> guard(m_cancelled_requests_mutex); |
858 | return m_cancelled_requests.contains(V: req.seq); |
859 | } |
860 | |
861 | void DAP::ClearCancelRequest(const CancelArguments &args) { |
862 | std::lock_guard<std::mutex> guard(m_cancelled_requests_mutex); |
863 | if (args.requestId) |
864 | m_cancelled_requests.erase(V: *args.requestId); |
865 | } |
866 | |
867 | template <typename T> |
868 | static std::optional<T> getArgumentsIfRequest(const Message &pm, |
869 | llvm::StringLiteral command) { |
870 | auto *const req = std::get_if<Request>(ptr: &pm); |
871 | if (!req || req->command != command) |
872 | return std::nullopt; |
873 | |
874 | T args; |
875 | llvm::json::Path::Root root; |
876 | if (!fromJSON(req->arguments, args, root)) |
877 | return std::nullopt; |
878 | |
879 | return args; |
880 | } |
881 | |
882 | llvm::Error DAP::Loop() { |
883 | // Can't use \a std::future<llvm::Error> because it doesn't compile on |
884 | // Windows. |
885 | std::future<lldb::SBError> queue_reader = |
886 | std::async(policy: std::launch::async, fn: [&]() -> lldb::SBError { |
887 | llvm::set_thread_name(transport.GetClientName() + ".transport_handler" ); |
888 | auto cleanup = llvm::make_scope_exit(F: [&]() { |
889 | // Ensure we're marked as disconnecting when the reader exits. |
890 | disconnecting = true; |
891 | m_queue_cv.notify_all(); |
892 | }); |
893 | |
894 | while (!disconnecting) { |
895 | llvm::Expected<Message> next = |
896 | transport.Read(timeout: std::chrono::seconds(1)); |
897 | if (next.errorIsA<EndOfFileError>()) { |
898 | consumeError(Err: next.takeError()); |
899 | break; |
900 | } |
901 | |
902 | // If the read timed out, continue to check if we should disconnect. |
903 | if (next.errorIsA<TimeoutError>()) { |
904 | consumeError(Err: next.takeError()); |
905 | continue; |
906 | } |
907 | |
908 | if (llvm::Error err = next.takeError()) { |
909 | lldb::SBError errWrapper; |
910 | errWrapper.SetErrorString(llvm::toString(E: std::move(err)).c_str()); |
911 | return errWrapper; |
912 | } |
913 | |
914 | if (const protocol::Request *req = |
915 | std::get_if<protocol::Request>(ptr: &*next); |
916 | req && req->arguments == "disconnect" ) |
917 | disconnecting = true; |
918 | |
919 | const std::optional<CancelArguments> cancel_args = |
920 | getArgumentsIfRequest<CancelArguments>(pm: *next, command: "cancel" ); |
921 | if (cancel_args) { |
922 | { |
923 | std::lock_guard<std::mutex> guard(m_cancelled_requests_mutex); |
924 | if (cancel_args->requestId) |
925 | m_cancelled_requests.insert(V: *cancel_args->requestId); |
926 | } |
927 | |
928 | // If a cancel is requested for the active request, make a best |
929 | // effort attempt to interrupt. |
930 | std::lock_guard<std::mutex> guard(m_active_request_mutex); |
931 | if (m_active_request && |
932 | cancel_args->requestId == m_active_request->seq) { |
933 | DAP_LOG( |
934 | log, |
935 | "({0}) interrupting inflight request (command={1} seq={2})" , |
936 | transport.GetClientName(), m_active_request->command, |
937 | m_active_request->seq); |
938 | debugger.RequestInterrupt(); |
939 | } |
940 | } |
941 | |
942 | { |
943 | std::lock_guard<std::mutex> guard(m_queue_mutex); |
944 | m_queue.push_back(x: std::move(*next)); |
945 | } |
946 | m_queue_cv.notify_one(); |
947 | } |
948 | |
949 | return lldb::SBError(); |
950 | }); |
951 | |
952 | auto cleanup = llvm::make_scope_exit(F: [&]() { |
953 | out.Stop(); |
954 | err.Stop(); |
955 | StopEventHandlers(); |
956 | }); |
957 | |
958 | while (true) { |
959 | std::unique_lock<std::mutex> lock(m_queue_mutex); |
960 | m_queue_cv.wait(lock&: lock, p: [&] { return disconnecting || !m_queue.empty(); }); |
961 | |
962 | if (disconnecting && m_queue.empty()) |
963 | break; |
964 | |
965 | Message next = m_queue.front(); |
966 | m_queue.pop_front(); |
967 | |
968 | // Unlock while we're processing the event. |
969 | lock.unlock(); |
970 | |
971 | if (!HandleObject(M: next)) |
972 | return llvm::createStringError(EC: llvm::inconvertibleErrorCode(), |
973 | S: "unhandled packet" ); |
974 | } |
975 | |
976 | return ToError(error: queue_reader.get()); |
977 | } |
978 | |
979 | lldb::SBError DAP::WaitForProcessToStop(std::chrono::seconds seconds) { |
980 | lldb::SBError error; |
981 | lldb::SBProcess process = target.GetProcess(); |
982 | if (!process.IsValid()) { |
983 | error.SetErrorString("invalid process" ); |
984 | return error; |
985 | } |
986 | auto timeout_time = |
987 | std::chrono::steady_clock::now() + std::chrono::seconds(seconds); |
988 | while (std::chrono::steady_clock::now() < timeout_time) { |
989 | const auto state = process.GetState(); |
990 | switch (state) { |
991 | case lldb::eStateUnloaded: |
992 | case lldb::eStateAttaching: |
993 | case lldb::eStateConnected: |
994 | case lldb::eStateInvalid: |
995 | case lldb::eStateLaunching: |
996 | case lldb::eStateRunning: |
997 | case lldb::eStateStepping: |
998 | case lldb::eStateSuspended: |
999 | break; |
1000 | case lldb::eStateDetached: |
1001 | error.SetErrorString("process detached during launch or attach" ); |
1002 | return error; |
1003 | case lldb::eStateExited: |
1004 | error.SetErrorString("process exited during launch or attach" ); |
1005 | return error; |
1006 | case lldb::eStateCrashed: |
1007 | case lldb::eStateStopped: |
1008 | return lldb::SBError(); // Success! |
1009 | } |
1010 | std::this_thread::sleep_for(rtime: std::chrono::microseconds(250)); |
1011 | } |
1012 | error.SetErrorString( |
1013 | llvm::formatv(Fmt: "process failed to stop within {0}" , Vals&: seconds) |
1014 | .str() |
1015 | .c_str()); |
1016 | return error; |
1017 | } |
1018 | |
1019 | void DAP::ConfigureSourceMaps() { |
1020 | if (configuration.sourceMap.empty() && configuration.sourcePath.empty()) |
1021 | return; |
1022 | |
1023 | std::string sourceMapCommand; |
1024 | llvm::raw_string_ostream strm(sourceMapCommand); |
1025 | strm << "settings set target.source-map " ; |
1026 | |
1027 | if (!configuration.sourceMap.empty()) { |
1028 | for (const auto &kv : configuration.sourceMap) { |
1029 | strm << "\"" << kv.first << "\" \"" << kv.second << "\" " ; |
1030 | } |
1031 | } else if (!configuration.sourcePath.empty()) { |
1032 | strm << "\".\" \"" << configuration.sourcePath << "\"" ; |
1033 | } |
1034 | |
1035 | RunLLDBCommands(prefix: "Setting source map:" , commands: {sourceMapCommand}); |
1036 | } |
1037 | |
1038 | void DAP::SetConfiguration(const protocol::Configuration &config, |
1039 | bool is_attach) { |
1040 | configuration = config; |
1041 | stop_at_entry = config.stopOnEntry; |
1042 | this->is_attach = is_attach; |
1043 | |
1044 | if (configuration.customFrameFormat) |
1045 | SetFrameFormat(*configuration.customFrameFormat); |
1046 | if (configuration.customThreadFormat) |
1047 | SetThreadFormat(*configuration.customThreadFormat); |
1048 | } |
1049 | |
1050 | void DAP::SetFrameFormat(llvm::StringRef format) { |
1051 | lldb::SBError error; |
1052 | frame_format = lldb::SBFormat(format.str().c_str(), error); |
1053 | if (error.Fail()) { |
1054 | SendOutput(o: OutputType::Console, |
1055 | output: llvm::formatv( |
1056 | Fmt: "The provided frame format '{0}' couldn't be parsed: {1}\n" , |
1057 | Vals&: format, Vals: error.GetCString()) |
1058 | .str()); |
1059 | } |
1060 | } |
1061 | |
1062 | void DAP::SetThreadFormat(llvm::StringRef format) { |
1063 | lldb::SBError error; |
1064 | thread_format = lldb::SBFormat(format.str().c_str(), error); |
1065 | if (error.Fail()) { |
1066 | SendOutput(o: OutputType::Console, |
1067 | output: llvm::formatv( |
1068 | Fmt: "The provided thread format '{0}' couldn't be parsed: {1}\n" , |
1069 | Vals&: format, Vals: error.GetCString()) |
1070 | .str()); |
1071 | } |
1072 | } |
1073 | |
1074 | InstructionBreakpoint * |
1075 | DAP::GetInstructionBreakpoint(const lldb::break_id_t bp_id) { |
1076 | for (auto &bp : instruction_breakpoints) { |
1077 | if (bp.second.GetID() == bp_id) |
1078 | return &bp.second; |
1079 | } |
1080 | return nullptr; |
1081 | } |
1082 | |
1083 | InstructionBreakpoint * |
1084 | DAP::GetInstructionBPFromStopReason(lldb::SBThread &thread) { |
1085 | const auto num = thread.GetStopReasonDataCount(); |
1086 | InstructionBreakpoint *inst_bp = nullptr; |
1087 | for (size_t i = 0; i < num; i += 2) { |
1088 | // thread.GetStopReasonDataAtIndex(i) will return the bp ID and |
1089 | // thread.GetStopReasonDataAtIndex(i+1) will return the location |
1090 | // within that breakpoint. We only care about the bp ID so we can |
1091 | // see if this is an instruction breakpoint that is getting hit. |
1092 | lldb::break_id_t bp_id = thread.GetStopReasonDataAtIndex(idx: i); |
1093 | inst_bp = GetInstructionBreakpoint(bp_id); |
1094 | // If any breakpoint is not an instruction breakpoint, then stop and |
1095 | // report this as a normal breakpoint |
1096 | if (inst_bp == nullptr) |
1097 | return nullptr; |
1098 | } |
1099 | return inst_bp; |
1100 | } |
1101 | |
1102 | protocol::Capabilities DAP::GetCapabilities() { |
1103 | protocol::Capabilities capabilities; |
1104 | |
1105 | // Supported capabilities that are not specific to a single request. |
1106 | capabilities.supportedFeatures = { |
1107 | protocol::eAdapterFeatureLogPoints, |
1108 | protocol::eAdapterFeatureSteppingGranularity, |
1109 | protocol::eAdapterFeatureValueFormattingOptions, |
1110 | }; |
1111 | |
1112 | // Capabilities associated with specific requests. |
1113 | for (auto &kv : request_handlers) { |
1114 | llvm::SmallDenseSet<AdapterFeature, 1> features = |
1115 | kv.second->GetSupportedFeatures(); |
1116 | capabilities.supportedFeatures.insert(I: features.begin(), E: features.end()); |
1117 | } |
1118 | |
1119 | // Available filters or options for the setExceptionBreakpoints request. |
1120 | std::vector<protocol::ExceptionBreakpointsFilter> filters; |
1121 | for (const auto &exc_bp : *exception_breakpoints) |
1122 | filters.emplace_back(args: CreateExceptionBreakpointFilter(bp: exc_bp)); |
1123 | capabilities.exceptionBreakpointFilters = std::move(filters); |
1124 | |
1125 | // FIXME: This should be registered based on the supported languages? |
1126 | std::vector<std::string> completion_characters; |
1127 | completion_characters.emplace_back(args: "." ); |
1128 | // FIXME: I wonder if we should remove this key... its very aggressive |
1129 | // triggering and accepting completions. |
1130 | completion_characters.emplace_back(args: " " ); |
1131 | completion_characters.emplace_back(args: "\t" ); |
1132 | capabilities.completionTriggerCharacters = std::move(completion_characters); |
1133 | |
1134 | // Put in non-DAP specification lldb specific information. |
1135 | capabilities.lldbExtVersion = debugger.GetVersionString(); |
1136 | |
1137 | return capabilities; |
1138 | } |
1139 | |
1140 | void DAP::StartEventThread() { |
1141 | event_thread = std::thread(&DAP::EventThread, this); |
1142 | } |
1143 | |
1144 | void DAP::StartProgressEventThread() { |
1145 | progress_event_thread = std::thread(&DAP::ProgressEventThread, this); |
1146 | } |
1147 | |
1148 | void DAP::ProgressEventThread() { |
1149 | lldb::SBListener listener("lldb-dap.progress.listener" ); |
1150 | debugger.GetBroadcaster().AddListener( |
1151 | listener, event_mask: lldb::SBDebugger::eBroadcastBitProgress | |
1152 | lldb::SBDebugger::eBroadcastBitExternalProgress); |
1153 | broadcaster.AddListener(listener, event_mask: eBroadcastBitStopProgressThread); |
1154 | lldb::SBEvent event; |
1155 | bool done = false; |
1156 | while (!done) { |
1157 | if (listener.WaitForEvent(num_seconds: 1, event)) { |
1158 | const auto event_mask = event.GetType(); |
1159 | if (event.BroadcasterMatchesRef(broadcaster)) { |
1160 | if (event_mask & eBroadcastBitStopProgressThread) { |
1161 | done = true; |
1162 | } |
1163 | } else { |
1164 | lldb::SBStructuredData data = |
1165 | lldb::SBDebugger::GetProgressDataFromEvent(event); |
1166 | |
1167 | const uint64_t progress_id = |
1168 | GetUintFromStructuredData(data, key: "progress_id" ); |
1169 | const uint64_t completed = GetUintFromStructuredData(data, key: "completed" ); |
1170 | const uint64_t total = GetUintFromStructuredData(data, key: "total" ); |
1171 | const std::string details = |
1172 | GetStringFromStructuredData(data, key: "details" ); |
1173 | |
1174 | if (completed == 0) { |
1175 | if (total == UINT64_MAX) { |
1176 | // This progress is non deterministic and won't get updated until it |
1177 | // is completed. Send the "message" which will be the combined title |
1178 | // and detail. The only other progress event for thus |
1179 | // non-deterministic progress will be the completed event So there |
1180 | // will be no need to update the detail. |
1181 | const std::string message = |
1182 | GetStringFromStructuredData(data, key: "message" ); |
1183 | SendProgressEvent(progress_id, message: message.c_str(), completed, total); |
1184 | } else { |
1185 | // This progress is deterministic and will receive updates, |
1186 | // on the progress creation event VSCode will save the message in |
1187 | // the create packet and use that as the title, so we send just the |
1188 | // title in the progressCreate packet followed immediately by a |
1189 | // detail packet, if there is any detail. |
1190 | const std::string title = |
1191 | GetStringFromStructuredData(data, key: "title" ); |
1192 | SendProgressEvent(progress_id, message: title.c_str(), completed, total); |
1193 | if (!details.empty()) |
1194 | SendProgressEvent(progress_id, message: details.c_str(), completed, total); |
1195 | } |
1196 | } else { |
1197 | // This progress event is either the end of the progress dialog, or an |
1198 | // update with possible detail. The "detail" string we send to VS Code |
1199 | // will be appended to the progress dialog's initial text from when it |
1200 | // was created. |
1201 | SendProgressEvent(progress_id, message: details.c_str(), completed, total); |
1202 | } |
1203 | } |
1204 | } |
1205 | } |
1206 | } |
1207 | |
1208 | // All events from the debugger, target, process, thread and frames are |
1209 | // received in this function that runs in its own thread. We are using a |
1210 | // "FILE *" to output packets back to VS Code and they have mutexes in them |
1211 | // them prevent multiple threads from writing simultaneously so no locking |
1212 | // is required. |
1213 | void DAP::EventThread() { |
1214 | llvm::set_thread_name(transport.GetClientName() + ".event_handler" ); |
1215 | lldb::SBEvent event; |
1216 | lldb::SBListener listener = debugger.GetListener(); |
1217 | broadcaster.AddListener(listener, event_mask: eBroadcastBitStopEventThread); |
1218 | debugger.GetBroadcaster().AddListener( |
1219 | listener, event_mask: lldb::eBroadcastBitError | lldb::eBroadcastBitWarning); |
1220 | bool done = false; |
1221 | while (!done) { |
1222 | if (listener.WaitForEvent(num_seconds: 1, event)) { |
1223 | const auto event_mask = event.GetType(); |
1224 | if (lldb::SBProcess::EventIsProcessEvent(event)) { |
1225 | lldb::SBProcess process = lldb::SBProcess::GetProcessFromEvent(event); |
1226 | if (event_mask & lldb::SBProcess::eBroadcastBitStateChanged) { |
1227 | auto state = lldb::SBProcess::GetStateFromEvent(event); |
1228 | switch (state) { |
1229 | case lldb::eStateConnected: |
1230 | case lldb::eStateDetached: |
1231 | case lldb::eStateInvalid: |
1232 | case lldb::eStateUnloaded: |
1233 | break; |
1234 | case lldb::eStateAttaching: |
1235 | case lldb::eStateCrashed: |
1236 | case lldb::eStateLaunching: |
1237 | case lldb::eStateStopped: |
1238 | case lldb::eStateSuspended: |
1239 | // Only report a stopped event if the process was not |
1240 | // automatically restarted. |
1241 | if (!lldb::SBProcess::GetRestartedFromEvent(event)) { |
1242 | SendStdOutStdErr(dap&: *this, process); |
1243 | if (llvm::Error err = SendThreadStoppedEvent(dap&: *this)) |
1244 | DAP_LOG_ERROR(log, std::move(err), |
1245 | "({1}) reporting thread stopped: {0}" , |
1246 | transport.GetClientName()); |
1247 | } |
1248 | break; |
1249 | case lldb::eStateRunning: |
1250 | case lldb::eStateStepping: |
1251 | WillContinue(); |
1252 | SendContinuedEvent(dap&: *this); |
1253 | break; |
1254 | case lldb::eStateExited: |
1255 | lldb::SBStream stream; |
1256 | process.GetStatus(status&: stream); |
1257 | SendOutput(o: OutputType::Console, output: stream.GetData()); |
1258 | |
1259 | // When restarting, we can get an "exited" event for the process we |
1260 | // just killed with the old PID, or even with no PID. In that case |
1261 | // we don't have to terminate the session. |
1262 | if (process.GetProcessID() == LLDB_INVALID_PROCESS_ID || |
1263 | process.GetProcessID() == restarting_process_id) { |
1264 | restarting_process_id = LLDB_INVALID_PROCESS_ID; |
1265 | } else { |
1266 | // Run any exit LLDB commands the user specified in the |
1267 | // launch.json |
1268 | RunExitCommands(); |
1269 | SendProcessExitedEvent(dap&: *this, process); |
1270 | SendTerminatedEvent(); |
1271 | done = true; |
1272 | } |
1273 | break; |
1274 | } |
1275 | } else if ((event_mask & lldb::SBProcess::eBroadcastBitSTDOUT) || |
1276 | (event_mask & lldb::SBProcess::eBroadcastBitSTDERR)) { |
1277 | SendStdOutStdErr(dap&: *this, process); |
1278 | } |
1279 | } else if (lldb::SBTarget::EventIsTargetEvent(event)) { |
1280 | if (event_mask & lldb::SBTarget::eBroadcastBitModulesLoaded || |
1281 | event_mask & lldb::SBTarget::eBroadcastBitModulesUnloaded || |
1282 | event_mask & lldb::SBTarget::eBroadcastBitSymbolsLoaded || |
1283 | event_mask & lldb::SBTarget::eBroadcastBitSymbolsChanged) { |
1284 | const uint32_t num_modules = |
1285 | lldb::SBTarget::GetNumModulesFromEvent(event); |
1286 | std::lock_guard<std::mutex> guard(modules_mutex); |
1287 | for (uint32_t i = 0; i < num_modules; ++i) { |
1288 | lldb::SBModule module = |
1289 | lldb::SBTarget::GetModuleAtIndexFromEvent(idx: i, event); |
1290 | if (!module.IsValid()) |
1291 | continue; |
1292 | llvm::StringRef module_id = module.GetUUIDString(); |
1293 | if (module_id.empty()) |
1294 | continue; |
1295 | |
1296 | llvm::StringRef reason; |
1297 | bool id_only = false; |
1298 | if (modules.contains(key: module_id)) { |
1299 | if (event_mask & lldb::SBTarget::eBroadcastBitModulesUnloaded) { |
1300 | modules.erase(Key: module_id); |
1301 | reason = "removed" ; |
1302 | id_only = true; |
1303 | } else { |
1304 | reason = "changed" ; |
1305 | } |
1306 | } else { |
1307 | modules.insert(key: module_id); |
1308 | reason = "new" ; |
1309 | } |
1310 | |
1311 | llvm::json::Object body; |
1312 | body.try_emplace(K: "reason" , Args&: reason); |
1313 | body.try_emplace(K: "module" , Args: CreateModule(target, module, id_only)); |
1314 | llvm::json::Object module_event = CreateEventObject(event_name: "module" ); |
1315 | module_event.try_emplace(K: "body" , Args: std::move(body)); |
1316 | SendJSON(json: llvm::json::Value(std::move(module_event))); |
1317 | } |
1318 | } |
1319 | } else if (lldb::SBBreakpoint::EventIsBreakpointEvent(event)) { |
1320 | if (event_mask & lldb::SBTarget::eBroadcastBitBreakpointChanged) { |
1321 | auto event_type = |
1322 | lldb::SBBreakpoint::GetBreakpointEventTypeFromEvent(event); |
1323 | auto bp = Breakpoint( |
1324 | *this, lldb::SBBreakpoint::GetBreakpointFromEvent(event)); |
1325 | // If the breakpoint was set through DAP, it will have the |
1326 | // BreakpointBase::kDAPBreakpointLabel. Regardless of whether |
1327 | // locations were added, removed, or resolved, the breakpoint isn't |
1328 | // going away and the reason is always "changed". |
1329 | if ((event_type & lldb::eBreakpointEventTypeLocationsAdded || |
1330 | event_type & lldb::eBreakpointEventTypeLocationsRemoved || |
1331 | event_type & lldb::eBreakpointEventTypeLocationsResolved) && |
1332 | bp.MatchesName(name: BreakpointBase::kDAPBreakpointLabel)) { |
1333 | // As the DAP client already knows the path of this breakpoint, we |
1334 | // don't need to send it back as part of the "changed" event. This |
1335 | // avoids sending paths that should be source mapped. Note that |
1336 | // CreateBreakpoint doesn't apply source mapping and certain |
1337 | // implementation ignore the source part of this event anyway. |
1338 | llvm::json::Value source_bp = bp.ToProtocolBreakpoint(); |
1339 | source_bp.getAsObject()->erase(K: "source" ); |
1340 | |
1341 | llvm::json::Object body; |
1342 | body.try_emplace(K: "breakpoint" , Args&: source_bp); |
1343 | body.try_emplace(K: "reason" , Args: "changed" ); |
1344 | |
1345 | llvm::json::Object bp_event = CreateEventObject(event_name: "breakpoint" ); |
1346 | bp_event.try_emplace(K: "body" , Args: std::move(body)); |
1347 | |
1348 | SendJSON(json: llvm::json::Value(std::move(bp_event))); |
1349 | } |
1350 | } |
1351 | } else if (event_mask & lldb::eBroadcastBitError || |
1352 | event_mask & lldb::eBroadcastBitWarning) { |
1353 | lldb::SBStructuredData data = |
1354 | lldb::SBDebugger::GetDiagnosticFromEvent(event); |
1355 | if (!data.IsValid()) |
1356 | continue; |
1357 | std::string type = GetStringValue(data: data.GetValueForKey(key: "type" )); |
1358 | std::string message = GetStringValue(data: data.GetValueForKey(key: "message" )); |
1359 | SendOutput(o: OutputType::Important, |
1360 | output: llvm::formatv(Fmt: "{0}: {1}" , Vals&: type, Vals&: message).str()); |
1361 | } else if (event.BroadcasterMatchesRef(broadcaster)) { |
1362 | if (event_mask & eBroadcastBitStopEventThread) { |
1363 | done = true; |
1364 | } |
1365 | } |
1366 | } |
1367 | } |
1368 | } |
1369 | |
1370 | std::vector<protocol::Breakpoint> DAP::SetSourceBreakpoints( |
1371 | const protocol::Source &source, |
1372 | const std::optional<std::vector<protocol::SourceBreakpoint>> &breakpoints) { |
1373 | std::vector<protocol::Breakpoint> response_breakpoints; |
1374 | if (source.sourceReference) { |
1375 | // Breakpoint set by assembly source. |
1376 | auto &existing_breakpoints = |
1377 | m_source_assembly_breakpoints[*source.sourceReference]; |
1378 | response_breakpoints = |
1379 | SetSourceBreakpoints(source, breakpoints, existing_breakpoints); |
1380 | } else { |
1381 | // Breakpoint set by a regular source file. |
1382 | const auto path = source.path.value_or(u: "" ); |
1383 | auto &existing_breakpoints = m_source_breakpoints[path]; |
1384 | response_breakpoints = |
1385 | SetSourceBreakpoints(source, breakpoints, existing_breakpoints); |
1386 | } |
1387 | |
1388 | return response_breakpoints; |
1389 | } |
1390 | |
1391 | std::vector<protocol::Breakpoint> DAP::SetSourceBreakpoints( |
1392 | const protocol::Source &source, |
1393 | const std::optional<std::vector<protocol::SourceBreakpoint>> &breakpoints, |
1394 | SourceBreakpointMap &existing_breakpoints) { |
1395 | std::vector<protocol::Breakpoint> response_breakpoints; |
1396 | |
1397 | SourceBreakpointMap request_breakpoints; |
1398 | if (breakpoints) { |
1399 | for (const auto &bp : *breakpoints) { |
1400 | SourceBreakpoint src_bp(*this, bp); |
1401 | std::pair<uint32_t, uint32_t> bp_pos(src_bp.GetLine(), |
1402 | src_bp.GetColumn()); |
1403 | request_breakpoints.try_emplace(k: bp_pos, args&: src_bp); |
1404 | |
1405 | const auto [iv, inserted] = |
1406 | existing_breakpoints.try_emplace(k: bp_pos, args&: src_bp); |
1407 | // We check if this breakpoint already exists to update it. |
1408 | if (inserted) { |
1409 | if (llvm::Error error = iv->second.SetBreakpoint(source)) { |
1410 | protocol::Breakpoint invalid_breakpoint; |
1411 | invalid_breakpoint.message = llvm::toString(E: std::move(error)); |
1412 | invalid_breakpoint.verified = false; |
1413 | response_breakpoints.push_back(x: std::move(invalid_breakpoint)); |
1414 | existing_breakpoints.erase(position: iv); |
1415 | continue; |
1416 | } |
1417 | } else { |
1418 | iv->second.UpdateBreakpoint(request_bp: src_bp); |
1419 | } |
1420 | |
1421 | protocol::Breakpoint response_breakpoint = |
1422 | iv->second.ToProtocolBreakpoint(); |
1423 | response_breakpoint.source = source; |
1424 | |
1425 | if (!response_breakpoint.line && |
1426 | src_bp.GetLine() != LLDB_INVALID_LINE_NUMBER) |
1427 | response_breakpoint.line = src_bp.GetLine(); |
1428 | if (!response_breakpoint.column && |
1429 | src_bp.GetColumn() != LLDB_INVALID_COLUMN_NUMBER) |
1430 | response_breakpoint.column = src_bp.GetColumn(); |
1431 | response_breakpoints.push_back(x: std::move(response_breakpoint)); |
1432 | } |
1433 | } |
1434 | |
1435 | // Delete any breakpoints in this source file that aren't in the |
1436 | // request_bps set. There is no call to remove breakpoints other than |
1437 | // calling this function with a smaller or empty "breakpoints" list. |
1438 | for (auto it = existing_breakpoints.begin(); |
1439 | it != existing_breakpoints.end();) { |
1440 | auto request_pos = request_breakpoints.find(x: it->first); |
1441 | if (request_pos == request_breakpoints.end()) { |
1442 | // This breakpoint no longer exists in this source file, delete it |
1443 | target.BreakpointDelete(break_id: it->second.GetID()); |
1444 | it = existing_breakpoints.erase(position: it); |
1445 | } else { |
1446 | ++it; |
1447 | } |
1448 | } |
1449 | |
1450 | return response_breakpoints; |
1451 | } |
1452 | |
1453 | void DAP::RegisterRequests() { |
1454 | RegisterRequest<AttachRequestHandler>(); |
1455 | RegisterRequest<BreakpointLocationsRequestHandler>(); |
1456 | RegisterRequest<CancelRequestHandler>(); |
1457 | RegisterRequest<CompletionsRequestHandler>(); |
1458 | RegisterRequest<ConfigurationDoneRequestHandler>(); |
1459 | RegisterRequest<ContinueRequestHandler>(); |
1460 | RegisterRequest<DataBreakpointInfoRequestHandler>(); |
1461 | RegisterRequest<DisassembleRequestHandler>(); |
1462 | RegisterRequest<DisconnectRequestHandler>(); |
1463 | RegisterRequest<EvaluateRequestHandler>(); |
1464 | RegisterRequest<ExceptionInfoRequestHandler>(); |
1465 | RegisterRequest<InitializeRequestHandler>(); |
1466 | RegisterRequest<LaunchRequestHandler>(); |
1467 | RegisterRequest<LocationsRequestHandler>(); |
1468 | RegisterRequest<NextRequestHandler>(); |
1469 | RegisterRequest<PauseRequestHandler>(); |
1470 | RegisterRequest<ReadMemoryRequestHandler>(); |
1471 | RegisterRequest<RestartRequestHandler>(); |
1472 | RegisterRequest<ScopesRequestHandler>(); |
1473 | RegisterRequest<SetBreakpointsRequestHandler>(); |
1474 | RegisterRequest<SetDataBreakpointsRequestHandler>(); |
1475 | RegisterRequest<SetExceptionBreakpointsRequestHandler>(); |
1476 | RegisterRequest<SetFunctionBreakpointsRequestHandler>(); |
1477 | RegisterRequest<SetInstructionBreakpointsRequestHandler>(); |
1478 | RegisterRequest<SetVariableRequestHandler>(); |
1479 | RegisterRequest<SourceRequestHandler>(); |
1480 | RegisterRequest<StackTraceRequestHandler>(); |
1481 | RegisterRequest<StepInRequestHandler>(); |
1482 | RegisterRequest<StepInTargetsRequestHandler>(); |
1483 | RegisterRequest<StepOutRequestHandler>(); |
1484 | RegisterRequest<ThreadsRequestHandler>(); |
1485 | RegisterRequest<VariablesRequestHandler>(); |
1486 | |
1487 | // Custom requests |
1488 | RegisterRequest<CompileUnitsRequestHandler>(); |
1489 | RegisterRequest<ModulesRequestHandler>(); |
1490 | |
1491 | // Testing requests |
1492 | RegisterRequest<TestGetTargetBreakpointsRequestHandler>(); |
1493 | } |
1494 | |
1495 | } // namespace lldb_dap |
1496 | |