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 <chrono> |
10 | #include <cstdarg> |
11 | #include <fstream> |
12 | #include <mutex> |
13 | #include <sstream> |
14 | |
15 | #include "DAP.h" |
16 | #include "LLDBUtils.h" |
17 | #include "llvm/ADT/StringExtras.h" |
18 | #include "llvm/Support/FormatVariadic.h" |
19 | |
20 | #if defined(_WIN32) |
21 | #define NOMINMAX |
22 | #include <fcntl.h> |
23 | #include <io.h> |
24 | #include <windows.h> |
25 | #endif |
26 | |
27 | using namespace lldb_dap; |
28 | |
29 | namespace lldb_dap { |
30 | |
31 | DAP g_dap; |
32 | |
33 | DAP::DAP() |
34 | : broadcaster("lldb-dap" ), |
35 | exception_breakpoints( |
36 | {{"cpp_catch" , "C++ Catch" , lldb::eLanguageTypeC_plus_plus}, |
37 | {"cpp_throw" , "C++ Throw" , lldb::eLanguageTypeC_plus_plus}, |
38 | {"objc_catch" , "Objective-C Catch" , lldb::eLanguageTypeObjC}, |
39 | {"objc_throw" , "Objective-C Throw" , lldb::eLanguageTypeObjC}, |
40 | {"swift_catch" , "Swift Catch" , lldb::eLanguageTypeSwift}, |
41 | {"swift_throw" , "Swift Throw" , lldb::eLanguageTypeSwift}}), |
42 | focus_tid(LLDB_INVALID_THREAD_ID), sent_terminated_event(false), |
43 | stop_at_entry(false), is_attach(false), |
44 | enable_auto_variable_summaries(false), |
45 | enable_synthetic_child_debugging(false), |
46 | restarting_process_id(LLDB_INVALID_PROCESS_ID), |
47 | configuration_done_sent(false), waiting_for_run_in_terminal(false), |
48 | progress_event_reporter( |
49 | [&](const ProgressEvent &event) { SendJSON(json: event.ToJSON()); }), |
50 | reverse_request_seq(0), repl_mode(ReplMode::Auto) { |
51 | const char *log_file_path = getenv(name: "LLDBDAP_LOG" ); |
52 | #if defined(_WIN32) |
53 | // Windows opens stdout and stdin in text mode which converts \n to 13,10 |
54 | // while the value is just 10 on Darwin/Linux. Setting the file mode to binary |
55 | // fixes this. |
56 | int result = _setmode(fileno(stdout), _O_BINARY); |
57 | assert(result); |
58 | result = _setmode(fileno(stdin), _O_BINARY); |
59 | UNUSED_IF_ASSERT_DISABLED(result); |
60 | assert(result); |
61 | #endif |
62 | if (log_file_path) |
63 | log.reset(p: new std::ofstream(log_file_path)); |
64 | } |
65 | |
66 | DAP::~DAP() = default; |
67 | |
68 | ExceptionBreakpoint *DAP::GetExceptionBreakpoint(const std::string &filter) { |
69 | for (auto &bp : exception_breakpoints) { |
70 | if (bp.filter == filter) |
71 | return &bp; |
72 | } |
73 | return nullptr; |
74 | } |
75 | |
76 | ExceptionBreakpoint *DAP::GetExceptionBreakpoint(const lldb::break_id_t bp_id) { |
77 | for (auto &bp : exception_breakpoints) { |
78 | if (bp.bp.GetID() == bp_id) |
79 | return &bp; |
80 | } |
81 | return nullptr; |
82 | } |
83 | |
84 | // Send the JSON in "json_str" to the "out" stream. Correctly send the |
85 | // "Content-Length:" field followed by the length, followed by the raw |
86 | // JSON bytes. |
87 | void DAP::SendJSON(const std::string &json_str) { |
88 | output.write_full(str: "Content-Length: " ); |
89 | output.write_full(str: llvm::utostr(X: json_str.size())); |
90 | output.write_full(str: "\r\n\r\n" ); |
91 | output.write_full(str: json_str); |
92 | } |
93 | |
94 | // Serialize the JSON value into a string and send the JSON packet to |
95 | // the "out" stream. |
96 | void DAP::SendJSON(const llvm::json::Value &json) { |
97 | std::string s; |
98 | llvm::raw_string_ostream strm(s); |
99 | strm << json; |
100 | static std::mutex mutex; |
101 | std::lock_guard<std::mutex> locker(mutex); |
102 | std::string json_str = strm.str(); |
103 | SendJSON(json_str); |
104 | |
105 | if (log) { |
106 | *log << "<-- " << std::endl |
107 | << "Content-Length: " << json_str.size() << "\r\n\r\n" |
108 | << llvm::formatv(Fmt: "{0:2}" , Vals: json).str() << std::endl; |
109 | } |
110 | } |
111 | |
112 | // Read a JSON packet from the "in" stream. |
113 | std::string DAP::ReadJSON() { |
114 | std::string length_str; |
115 | std::string json_str; |
116 | int length; |
117 | |
118 | if (!input.read_expected(log: log.get(), expected: "Content-Length: " )) |
119 | return json_str; |
120 | |
121 | if (!input.read_line(log: log.get(), line&: length_str)) |
122 | return json_str; |
123 | |
124 | if (!llvm::to_integer(S: length_str, Num&: length)) |
125 | return json_str; |
126 | |
127 | if (!input.read_expected(log: log.get(), expected: "\r\n" )) |
128 | return json_str; |
129 | |
130 | if (!input.read_full(log: log.get(), length, text&: json_str)) |
131 | return json_str; |
132 | |
133 | if (log) |
134 | *log << "--> " << std::endl << "Content-Length: " << length << "\r\n\r\n" ; |
135 | |
136 | return json_str; |
137 | } |
138 | |
139 | // "OutputEvent": { |
140 | // "allOf": [ { "$ref": "#/definitions/Event" }, { |
141 | // "type": "object", |
142 | // "description": "Event message for 'output' event type. The event |
143 | // indicates that the target has produced some output.", |
144 | // "properties": { |
145 | // "event": { |
146 | // "type": "string", |
147 | // "enum": [ "output" ] |
148 | // }, |
149 | // "body": { |
150 | // "type": "object", |
151 | // "properties": { |
152 | // "category": { |
153 | // "type": "string", |
154 | // "description": "The output category. If not specified, |
155 | // 'console' is assumed.", |
156 | // "_enum": [ "console", "stdout", "stderr", "telemetry" ] |
157 | // }, |
158 | // "output": { |
159 | // "type": "string", |
160 | // "description": "The output to report." |
161 | // }, |
162 | // "variablesReference": { |
163 | // "type": "number", |
164 | // "description": "If an attribute 'variablesReference' exists |
165 | // and its value is > 0, the output contains |
166 | // objects which can be retrieved by passing |
167 | // variablesReference to the VariablesRequest." |
168 | // }, |
169 | // "source": { |
170 | // "$ref": "#/definitions/Source", |
171 | // "description": "An optional source location where the output |
172 | // was produced." |
173 | // }, |
174 | // "line": { |
175 | // "type": "integer", |
176 | // "description": "An optional source location line where the |
177 | // output was produced." |
178 | // }, |
179 | // "column": { |
180 | // "type": "integer", |
181 | // "description": "An optional source location column where the |
182 | // output was produced." |
183 | // }, |
184 | // "data": { |
185 | // "type":["array","boolean","integer","null","number","object", |
186 | // "string"], |
187 | // "description": "Optional data to report. For the 'telemetry' |
188 | // category the data will be sent to telemetry, for |
189 | // the other categories the data is shown in JSON |
190 | // format." |
191 | // } |
192 | // }, |
193 | // "required": ["output"] |
194 | // } |
195 | // }, |
196 | // "required": [ "event", "body" ] |
197 | // }] |
198 | // } |
199 | void DAP::SendOutput(OutputType o, const llvm::StringRef output) { |
200 | if (output.empty()) |
201 | return; |
202 | |
203 | llvm::json::Object event(CreateEventObject(event_name: "output" )); |
204 | llvm::json::Object body; |
205 | const char *category = nullptr; |
206 | switch (o) { |
207 | case OutputType::Console: |
208 | category = "console" ; |
209 | break; |
210 | case OutputType::Stdout: |
211 | category = "stdout" ; |
212 | break; |
213 | case OutputType::Stderr: |
214 | category = "stderr" ; |
215 | break; |
216 | case OutputType::Telemetry: |
217 | category = "telemetry" ; |
218 | break; |
219 | } |
220 | body.try_emplace(K: "category" , Args&: category); |
221 | EmplaceSafeString(obj&: body, key: "output" , str: output.str()); |
222 | event.try_emplace(K: "body" , Args: std::move(body)); |
223 | SendJSON(json: llvm::json::Value(std::move(event))); |
224 | } |
225 | |
226 | // interface ProgressStartEvent extends Event { |
227 | // event: 'progressStart'; |
228 | // |
229 | // body: { |
230 | // /** |
231 | // * An ID that must be used in subsequent 'progressUpdate' and |
232 | // 'progressEnd' |
233 | // * events to make them refer to the same progress reporting. |
234 | // * IDs must be unique within a debug session. |
235 | // */ |
236 | // progressId: string; |
237 | // |
238 | // /** |
239 | // * Mandatory (short) title of the progress reporting. Shown in the UI to |
240 | // * describe the long running operation. |
241 | // */ |
242 | // title: string; |
243 | // |
244 | // /** |
245 | // * The request ID that this progress report is related to. If specified a |
246 | // * debug adapter is expected to emit |
247 | // * progress events for the long running request until the request has |
248 | // been |
249 | // * either completed or cancelled. |
250 | // * If the request ID is omitted, the progress report is assumed to be |
251 | // * related to some general activity of the debug adapter. |
252 | // */ |
253 | // requestId?: number; |
254 | // |
255 | // /** |
256 | // * If true, the request that reports progress may be canceled with a |
257 | // * 'cancel' request. |
258 | // * So this property basically controls whether the client should use UX |
259 | // that |
260 | // * supports cancellation. |
261 | // * Clients that don't support cancellation are allowed to ignore the |
262 | // * setting. |
263 | // */ |
264 | // cancellable?: boolean; |
265 | // |
266 | // /** |
267 | // * Optional, more detailed progress message. |
268 | // */ |
269 | // message?: string; |
270 | // |
271 | // /** |
272 | // * Optional progress percentage to display (value range: 0 to 100). If |
273 | // * omitted no percentage will be shown. |
274 | // */ |
275 | // percentage?: number; |
276 | // }; |
277 | // } |
278 | // |
279 | // interface ProgressUpdateEvent extends Event { |
280 | // event: 'progressUpdate'; |
281 | // |
282 | // body: { |
283 | // /** |
284 | // * The ID that was introduced in the initial 'progressStart' event. |
285 | // */ |
286 | // progressId: string; |
287 | // |
288 | // /** |
289 | // * Optional, more detailed progress message. If omitted, the previous |
290 | // * message (if any) is used. |
291 | // */ |
292 | // message?: string; |
293 | // |
294 | // /** |
295 | // * Optional progress percentage to display (value range: 0 to 100). If |
296 | // * omitted no percentage will be shown. |
297 | // */ |
298 | // percentage?: number; |
299 | // }; |
300 | // } |
301 | // |
302 | // interface ProgressEndEvent extends Event { |
303 | // event: 'progressEnd'; |
304 | // |
305 | // body: { |
306 | // /** |
307 | // * The ID that was introduced in the initial 'ProgressStartEvent'. |
308 | // */ |
309 | // progressId: string; |
310 | // |
311 | // /** |
312 | // * Optional, more detailed progress message. If omitted, the previous |
313 | // * message (if any) is used. |
314 | // */ |
315 | // message?: string; |
316 | // }; |
317 | // } |
318 | |
319 | void DAP::SendProgressEvent(uint64_t progress_id, const char *message, |
320 | uint64_t completed, uint64_t total) { |
321 | progress_event_reporter.Push(progress_id, message, completed, total); |
322 | } |
323 | |
324 | void __attribute__((format(printf, 3, 4))) |
325 | DAP::SendFormattedOutput(OutputType o, const char *format, ...) { |
326 | char buffer[1024]; |
327 | va_list args; |
328 | va_start(args, format); |
329 | int actual_length = vsnprintf(s: buffer, maxlen: sizeof(buffer), format: format, arg: args); |
330 | va_end(args); |
331 | SendOutput( |
332 | o, output: llvm::StringRef(buffer, std::min<int>(a: actual_length, b: sizeof(buffer)))); |
333 | } |
334 | |
335 | ExceptionBreakpoint *DAP::GetExceptionBPFromStopReason(lldb::SBThread &thread) { |
336 | const auto num = thread.GetStopReasonDataCount(); |
337 | // Check to see if have hit an exception breakpoint and change the |
338 | // reason to "exception", but only do so if all breakpoints that were |
339 | // hit are exception breakpoints. |
340 | ExceptionBreakpoint *exc_bp = nullptr; |
341 | for (size_t i = 0; i < num; i += 2) { |
342 | // thread.GetStopReasonDataAtIndex(i) will return the bp ID and |
343 | // thread.GetStopReasonDataAtIndex(i+1) will return the location |
344 | // within that breakpoint. We only care about the bp ID so we can |
345 | // see if this is an exception breakpoint that is getting hit. |
346 | lldb::break_id_t bp_id = thread.GetStopReasonDataAtIndex(idx: i); |
347 | exc_bp = GetExceptionBreakpoint(bp_id); |
348 | // If any breakpoint is not an exception breakpoint, then stop and |
349 | // report this as a normal breakpoint |
350 | if (exc_bp == nullptr) |
351 | return nullptr; |
352 | } |
353 | return exc_bp; |
354 | } |
355 | |
356 | lldb::SBThread DAP::GetLLDBThread(const llvm::json::Object &arguments) { |
357 | auto tid = GetSigned(obj: arguments, key: "threadId" , LLDB_INVALID_THREAD_ID); |
358 | return target.GetProcess().GetThreadByID(sb_thread_id: tid); |
359 | } |
360 | |
361 | lldb::SBFrame DAP::GetLLDBFrame(const llvm::json::Object &arguments) { |
362 | const uint64_t frame_id = GetUnsigned(obj: arguments, key: "frameId" , UINT64_MAX); |
363 | lldb::SBProcess process = target.GetProcess(); |
364 | // Upper 32 bits is the thread index ID |
365 | lldb::SBThread thread = |
366 | process.GetThreadByIndexID(index_id: GetLLDBThreadIndexID(dap_frame_id: frame_id)); |
367 | // Lower 32 bits is the frame index |
368 | return thread.GetFrameAtIndex(idx: GetLLDBFrameID(dap_frame_id: frame_id)); |
369 | } |
370 | |
371 | llvm::json::Value DAP::CreateTopLevelScopes() { |
372 | llvm::json::Array scopes; |
373 | scopes.emplace_back(A: CreateScope(name: "Locals" , VARREF_LOCALS, |
374 | namedVariables: g_dap.variables.locals.GetSize(), expensive: false)); |
375 | scopes.emplace_back(A: CreateScope(name: "Globals" , VARREF_GLOBALS, |
376 | namedVariables: g_dap.variables.globals.GetSize(), expensive: false)); |
377 | scopes.emplace_back(A: CreateScope(name: "Registers" , VARREF_REGS, |
378 | namedVariables: g_dap.variables.registers.GetSize(), expensive: false)); |
379 | return llvm::json::Value(std::move(scopes)); |
380 | } |
381 | |
382 | ExpressionContext DAP::DetectExpressionContext(lldb::SBFrame frame, |
383 | std::string &expression) { |
384 | // Include the escape hatch prefix. |
385 | if (!expression.empty() && |
386 | llvm::StringRef(expression).starts_with(Prefix: g_dap.command_escape_prefix)) { |
387 | expression = expression.substr(pos: g_dap.command_escape_prefix.size()); |
388 | return ExpressionContext::Command; |
389 | } |
390 | |
391 | switch (repl_mode) { |
392 | case ReplMode::Variable: |
393 | return ExpressionContext::Variable; |
394 | case ReplMode::Command: |
395 | return ExpressionContext::Command; |
396 | case ReplMode::Auto: |
397 | // To determine if the expression is a command or not, check if the first |
398 | // term is a variable or command. If it's a variable in scope we will prefer |
399 | // that behavior and give a warning to the user if they meant to invoke the |
400 | // operation as a command. |
401 | // |
402 | // Example use case: |
403 | // int p and expression "p + 1" > variable |
404 | // int i and expression "i" > variable |
405 | // int var and expression "va" > command |
406 | std::pair<llvm::StringRef, llvm::StringRef> token = |
407 | llvm::getToken(Source: expression); |
408 | std::string term = token.first.str(); |
409 | lldb::SBCommandReturnObject result; |
410 | debugger.GetCommandInterpreter().ResolveCommand(command_line: term.c_str(), result); |
411 | bool term_is_command = result.Succeeded(); |
412 | bool term_is_variable = frame.FindVariable(var_name: term.c_str()).IsValid(); |
413 | |
414 | // If we have both a variable and command, warn the user about the conflict. |
415 | if (term_is_command && term_is_variable) { |
416 | llvm::errs() |
417 | << "Warning: Expression '" << term |
418 | << "' is both an LLDB command and variable. It will be evaluated as " |
419 | "a variable. To evaluate the expression as an LLDB command, use '" |
420 | << g_dap.command_escape_prefix << "' as a prefix.\n" ; |
421 | } |
422 | |
423 | // Variables take preference to commands in auto, since commands can always |
424 | // be called using the command_escape_prefix |
425 | return term_is_variable ? ExpressionContext::Variable |
426 | : term_is_command ? ExpressionContext::Command |
427 | : ExpressionContext::Variable; |
428 | } |
429 | |
430 | llvm_unreachable("enum cases exhausted." ); |
431 | } |
432 | |
433 | bool DAP::RunLLDBCommands(llvm::StringRef prefix, |
434 | llvm::ArrayRef<std::string> commands) { |
435 | bool required_command_failed = false; |
436 | std::string output = |
437 | ::RunLLDBCommands(prefix, commands, required_command_failed); |
438 | SendOutput(o: OutputType::Console, output); |
439 | return !required_command_failed; |
440 | } |
441 | |
442 | static llvm::Error createRunLLDBCommandsErrorMessage(llvm::StringRef category) { |
443 | return llvm::createStringError( |
444 | EC: llvm::inconvertibleErrorCode(), |
445 | Msg: llvm::formatv( |
446 | Fmt: "Failed to run {0} commands. See the Debug Console for more details." , |
447 | Vals&: category) |
448 | .str() |
449 | .c_str()); |
450 | } |
451 | |
452 | llvm::Error |
453 | DAP::RunAttachCommands(llvm::ArrayRef<std::string> attach_commands) { |
454 | if (!RunLLDBCommands(prefix: "Running attachCommands:" , commands: attach_commands)) |
455 | return createRunLLDBCommandsErrorMessage(category: "attach" ); |
456 | return llvm::Error::success(); |
457 | } |
458 | |
459 | llvm::Error |
460 | DAP::RunLaunchCommands(llvm::ArrayRef<std::string> launch_commands) { |
461 | if (!RunLLDBCommands(prefix: "Running launchCommands:" , commands: launch_commands)) |
462 | return createRunLLDBCommandsErrorMessage(category: "launch" ); |
463 | return llvm::Error::success(); |
464 | } |
465 | |
466 | llvm::Error DAP::RunInitCommands() { |
467 | if (!RunLLDBCommands(prefix: "Running initCommands:" , commands: init_commands)) |
468 | return createRunLLDBCommandsErrorMessage(category: "initCommands" ); |
469 | return llvm::Error::success(); |
470 | } |
471 | |
472 | llvm::Error DAP::RunPreRunCommands() { |
473 | if (!RunLLDBCommands(prefix: "Running preRunCommands:" , commands: pre_run_commands)) |
474 | return createRunLLDBCommandsErrorMessage(category: "preRunCommands" ); |
475 | return llvm::Error::success(); |
476 | } |
477 | |
478 | void DAP::RunPostRunCommands() { |
479 | RunLLDBCommands(prefix: "Running postRunCommands:" , commands: post_run_commands); |
480 | } |
481 | void DAP::RunStopCommands() { |
482 | RunLLDBCommands(prefix: "Running stopCommands:" , commands: stop_commands); |
483 | } |
484 | |
485 | void DAP::RunExitCommands() { |
486 | RunLLDBCommands(prefix: "Running exitCommands:" , commands: exit_commands); |
487 | } |
488 | |
489 | void DAP::RunTerminateCommands() { |
490 | RunLLDBCommands(prefix: "Running terminateCommands:" , commands: terminate_commands); |
491 | } |
492 | |
493 | lldb::SBTarget |
494 | DAP::CreateTargetFromArguments(const llvm::json::Object &arguments, |
495 | lldb::SBError &error) { |
496 | // Grab the name of the program we need to debug and create a target using |
497 | // the given program as an argument. Executable file can be a source of target |
498 | // architecture and platform, if they differ from the host. Setting exe path |
499 | // in launch info is useless because Target.Launch() will not change |
500 | // architecture and platform, therefore they should be known at the target |
501 | // creation. We also use target triple and platform from the launch |
502 | // configuration, if given, since in some cases ELF file doesn't contain |
503 | // enough information to determine correct arch and platform (or ELF can be |
504 | // omitted at all), so it is good to leave the user an apportunity to specify |
505 | // those. Any of those three can be left empty. |
506 | llvm::StringRef target_triple = GetString(obj: arguments, key: "targetTriple" ); |
507 | llvm::StringRef platform_name = GetString(obj: arguments, key: "platformName" ); |
508 | llvm::StringRef program = GetString(obj: arguments, key: "program" ); |
509 | auto target = this->debugger.CreateTarget( |
510 | filename: program.data(), target_triple: target_triple.data(), platform_name: platform_name.data(), |
511 | add_dependent_modules: true, // Add dependent modules. |
512 | error); |
513 | |
514 | if (error.Fail()) { |
515 | // Update message if there was an error. |
516 | error.SetErrorStringWithFormat( |
517 | "Could not create a target for a program '%s': %s." , program.data(), |
518 | error.GetCString()); |
519 | } |
520 | |
521 | return target; |
522 | } |
523 | |
524 | void DAP::SetTarget(const lldb::SBTarget target) { |
525 | this->target = target; |
526 | |
527 | if (target.IsValid()) { |
528 | // Configure breakpoint event listeners for the target. |
529 | lldb::SBListener listener = this->debugger.GetListener(); |
530 | listener.StartListeningForEvents( |
531 | broadcaster: this->target.GetBroadcaster(), |
532 | event_mask: lldb::SBTarget::eBroadcastBitBreakpointChanged); |
533 | listener.StartListeningForEvents(broadcaster: this->broadcaster, |
534 | event_mask: eBroadcastBitStopEventThread); |
535 | } |
536 | } |
537 | |
538 | PacketStatus DAP::GetNextObject(llvm::json::Object &object) { |
539 | std::string json = ReadJSON(); |
540 | if (json.empty()) |
541 | return PacketStatus::EndOfFile; |
542 | |
543 | llvm::StringRef json_sref(json); |
544 | llvm::Expected<llvm::json::Value> json_value = llvm::json::parse(JSON: json_sref); |
545 | if (!json_value) { |
546 | auto error = json_value.takeError(); |
547 | if (log) { |
548 | std::string error_str; |
549 | llvm::raw_string_ostream strm(error_str); |
550 | strm << error; |
551 | strm.flush(); |
552 | *log << "error: failed to parse JSON: " << error_str << std::endl |
553 | << json << std::endl; |
554 | } |
555 | return PacketStatus::JSONMalformed; |
556 | } |
557 | |
558 | if (log) { |
559 | *log << llvm::formatv(Fmt: "{0:2}" , Vals&: *json_value).str() << std::endl; |
560 | } |
561 | |
562 | llvm::json::Object *object_ptr = json_value->getAsObject(); |
563 | if (!object_ptr) { |
564 | if (log) |
565 | *log << "error: json packet isn't a object" << std::endl; |
566 | return PacketStatus::JSONNotObject; |
567 | } |
568 | object = *object_ptr; |
569 | return PacketStatus::Success; |
570 | } |
571 | |
572 | bool DAP::HandleObject(const llvm::json::Object &object) { |
573 | const auto packet_type = GetString(obj: object, key: "type" ); |
574 | if (packet_type == "request" ) { |
575 | const auto command = GetString(obj: object, key: "command" ); |
576 | auto handler_pos = request_handlers.find(x: std::string(command)); |
577 | if (handler_pos != request_handlers.end()) { |
578 | handler_pos->second(object); |
579 | return true; // Success |
580 | } else { |
581 | if (log) |
582 | *log << "error: unhandled command \"" << command.data() << "\"" |
583 | << std::endl; |
584 | return false; // Fail |
585 | } |
586 | } |
587 | |
588 | if (packet_type == "response" ) { |
589 | auto id = GetSigned(obj: object, key: "request_seq" , fail_value: 0); |
590 | ResponseCallback response_handler = [](llvm::Expected<llvm::json::Value>) { |
591 | llvm::errs() << "Unhandled response\n" ; |
592 | }; |
593 | |
594 | { |
595 | std::lock_guard<std::mutex> locker(call_mutex); |
596 | auto inflight = inflight_reverse_requests.find(x: id); |
597 | if (inflight != inflight_reverse_requests.end()) { |
598 | response_handler = std::move(inflight->second); |
599 | inflight_reverse_requests.erase(position: inflight); |
600 | } |
601 | } |
602 | |
603 | // Result should be given, use null if not. |
604 | if (GetBoolean(obj: object, key: "success" , fail_value: false)) { |
605 | llvm::json::Value Result = nullptr; |
606 | if (auto *B = object.get(K: "body" )) { |
607 | Result = std::move(*B); |
608 | } |
609 | response_handler(Result); |
610 | } else { |
611 | llvm::StringRef message = GetString(obj: object, key: "message" ); |
612 | if (message.empty()) { |
613 | message = "Unknown error, response failed" ; |
614 | } |
615 | response_handler(llvm::createStringError( |
616 | EC: std::error_code(-1, std::generic_category()), S: message)); |
617 | } |
618 | |
619 | return true; |
620 | } |
621 | |
622 | return false; |
623 | } |
624 | |
625 | llvm::Error DAP::Loop() { |
626 | while (!sent_terminated_event) { |
627 | llvm::json::Object object; |
628 | lldb_dap::PacketStatus status = GetNextObject(object); |
629 | |
630 | if (status == lldb_dap::PacketStatus::EndOfFile) { |
631 | break; |
632 | } |
633 | |
634 | if (status != lldb_dap::PacketStatus::Success) { |
635 | return llvm::createStringError(EC: llvm::inconvertibleErrorCode(), |
636 | Msg: "failed to send packet" ); |
637 | } |
638 | |
639 | if (!HandleObject(object)) { |
640 | return llvm::createStringError(EC: llvm::inconvertibleErrorCode(), |
641 | Msg: "unhandled packet" ); |
642 | } |
643 | } |
644 | |
645 | return llvm::Error::success(); |
646 | } |
647 | |
648 | void DAP::SendReverseRequest(llvm::StringRef command, |
649 | llvm::json::Value arguments, |
650 | ResponseCallback callback) { |
651 | int64_t id; |
652 | { |
653 | std::lock_guard<std::mutex> locker(call_mutex); |
654 | id = ++reverse_request_seq; |
655 | inflight_reverse_requests.emplace(args&: id, args: std::move(callback)); |
656 | } |
657 | |
658 | SendJSON(json: llvm::json::Object{ |
659 | {.K: "type" , .V: "request" }, |
660 | {.K: "seq" , .V: id}, |
661 | {.K: "command" , .V: command}, |
662 | {.K: "arguments" , .V: std::move(arguments)}, |
663 | }); |
664 | } |
665 | |
666 | void DAP::RegisterRequestCallback(std::string request, |
667 | RequestCallback callback) { |
668 | request_handlers[request] = callback; |
669 | } |
670 | |
671 | lldb::SBError DAP::WaitForProcessToStop(uint32_t seconds) { |
672 | lldb::SBError error; |
673 | lldb::SBProcess process = target.GetProcess(); |
674 | if (!process.IsValid()) { |
675 | error.SetErrorString("invalid process" ); |
676 | return error; |
677 | } |
678 | auto timeout_time = |
679 | std::chrono::steady_clock::now() + std::chrono::seconds(seconds); |
680 | while (std::chrono::steady_clock::now() < timeout_time) { |
681 | const auto state = process.GetState(); |
682 | switch (state) { |
683 | case lldb::eStateAttaching: |
684 | case lldb::eStateConnected: |
685 | case lldb::eStateInvalid: |
686 | case lldb::eStateLaunching: |
687 | case lldb::eStateRunning: |
688 | case lldb::eStateStepping: |
689 | case lldb::eStateSuspended: |
690 | break; |
691 | case lldb::eStateDetached: |
692 | error.SetErrorString("process detached during launch or attach" ); |
693 | return error; |
694 | case lldb::eStateExited: |
695 | error.SetErrorString("process exited during launch or attach" ); |
696 | return error; |
697 | case lldb::eStateUnloaded: |
698 | error.SetErrorString("process unloaded during launch or attach" ); |
699 | return error; |
700 | case lldb::eStateCrashed: |
701 | case lldb::eStateStopped: |
702 | return lldb::SBError(); // Success! |
703 | } |
704 | std::this_thread::sleep_for(rtime: std::chrono::microseconds(250)); |
705 | } |
706 | error.SetErrorStringWithFormat("process failed to stop within %u seconds" , |
707 | seconds); |
708 | return error; |
709 | } |
710 | |
711 | void Variables::Clear() { |
712 | locals.Clear(); |
713 | globals.Clear(); |
714 | registers.Clear(); |
715 | expandable_variables.clear(); |
716 | } |
717 | |
718 | int64_t Variables::GetNewVariableReference(bool is_permanent) { |
719 | if (is_permanent) |
720 | return next_permanent_var_ref++; |
721 | return next_temporary_var_ref++; |
722 | } |
723 | |
724 | bool Variables::IsPermanentVariableReference(int64_t var_ref) { |
725 | return var_ref >= PermanentVariableStartIndex; |
726 | } |
727 | |
728 | lldb::SBValue Variables::GetVariable(int64_t var_ref) const { |
729 | if (IsPermanentVariableReference(var_ref)) { |
730 | auto pos = expandable_permanent_variables.find(Val: var_ref); |
731 | if (pos != expandable_permanent_variables.end()) |
732 | return pos->second; |
733 | } else { |
734 | auto pos = expandable_variables.find(Val: var_ref); |
735 | if (pos != expandable_variables.end()) |
736 | return pos->second; |
737 | } |
738 | return lldb::SBValue(); |
739 | } |
740 | |
741 | int64_t Variables::InsertExpandableVariable(lldb::SBValue variable, |
742 | bool is_permanent) { |
743 | int64_t var_ref = GetNewVariableReference(is_permanent); |
744 | if (is_permanent) |
745 | expandable_permanent_variables.insert(KV: std::make_pair(x&: var_ref, y&: variable)); |
746 | else |
747 | expandable_variables.insert(KV: std::make_pair(x&: var_ref, y&: variable)); |
748 | return var_ref; |
749 | } |
750 | |
751 | bool StartDebuggingRequestHandler::DoExecute( |
752 | lldb::SBDebugger debugger, char **command, |
753 | lldb::SBCommandReturnObject &result) { |
754 | // Command format like: `startDebugging <launch|attach> <configuration>` |
755 | if (!command) { |
756 | result.SetError("Invalid use of startDebugging" ); |
757 | result.SetStatus(lldb::eReturnStatusFailed); |
758 | return false; |
759 | } |
760 | |
761 | if (!command[0] || llvm::StringRef(command[0]).empty()) { |
762 | result.SetError("startDebugging request type missing." ); |
763 | result.SetStatus(lldb::eReturnStatusFailed); |
764 | return false; |
765 | } |
766 | |
767 | if (!command[1] || llvm::StringRef(command[1]).empty()) { |
768 | result.SetError("configuration missing." ); |
769 | result.SetStatus(lldb::eReturnStatusFailed); |
770 | return false; |
771 | } |
772 | |
773 | llvm::StringRef request{command[0]}; |
774 | std::string raw_configuration{command[1]}; |
775 | |
776 | int i = 2; |
777 | while (command[i]) { |
778 | raw_configuration.append(s: " " ).append(s: command[i]); |
779 | } |
780 | |
781 | llvm::Expected<llvm::json::Value> configuration = |
782 | llvm::json::parse(JSON: raw_configuration); |
783 | |
784 | if (!configuration) { |
785 | llvm::Error err = configuration.takeError(); |
786 | std::string msg = |
787 | "Failed to parse json configuration: " + llvm::toString(E: std::move(err)); |
788 | result.SetError(msg.c_str()); |
789 | result.SetStatus(lldb::eReturnStatusFailed); |
790 | return false; |
791 | } |
792 | |
793 | g_dap.SendReverseRequest( |
794 | command: "startDebugging" , |
795 | arguments: llvm::json::Object{{.K: "request" , .V: request}, |
796 | {.K: "configuration" , .V: std::move(*configuration)}}, |
797 | callback: [](llvm::Expected<llvm::json::Value> value) { |
798 | if (!value) { |
799 | llvm::Error err = value.takeError(); |
800 | llvm::errs() << "reverse start debugging request failed: " |
801 | << llvm::toString(E: std::move(err)) << "\n" ; |
802 | } |
803 | }); |
804 | |
805 | result.SetStatus(lldb::eReturnStatusSuccessFinishNoResult); |
806 | |
807 | return true; |
808 | } |
809 | |
810 | bool ReplModeRequestHandler::DoExecute(lldb::SBDebugger debugger, |
811 | char **command, |
812 | lldb::SBCommandReturnObject &result) { |
813 | // Command format like: `repl-mode <variable|command|auto>?` |
814 | // If a new mode is not specified report the current mode. |
815 | if (!command || llvm::StringRef(command[0]).empty()) { |
816 | std::string mode; |
817 | switch (g_dap.repl_mode) { |
818 | case ReplMode::Variable: |
819 | mode = "variable" ; |
820 | break; |
821 | case ReplMode::Command: |
822 | mode = "command" ; |
823 | break; |
824 | case ReplMode::Auto: |
825 | mode = "auto" ; |
826 | break; |
827 | } |
828 | |
829 | result.Printf(format: "lldb-dap repl-mode %s.\n" , mode.c_str()); |
830 | result.SetStatus(lldb::eReturnStatusSuccessFinishResult); |
831 | |
832 | return true; |
833 | } |
834 | |
835 | llvm::StringRef new_mode{command[0]}; |
836 | |
837 | if (new_mode == "variable" ) { |
838 | g_dap.repl_mode = ReplMode::Variable; |
839 | } else if (new_mode == "command" ) { |
840 | g_dap.repl_mode = ReplMode::Command; |
841 | } else if (new_mode == "auto" ) { |
842 | g_dap.repl_mode = ReplMode::Auto; |
843 | } else { |
844 | lldb::SBStream error_message; |
845 | error_message.Printf(format: "Invalid repl-mode '%s'. Expected one of 'variable', " |
846 | "'command' or 'auto'.\n" , |
847 | new_mode.data()); |
848 | result.SetError(error_message.GetData()); |
849 | return false; |
850 | } |
851 | |
852 | result.Printf(format: "lldb-dap repl-mode %s set.\n" , new_mode.data()); |
853 | result.SetStatus(lldb::eReturnStatusSuccessFinishNoResult); |
854 | return true; |
855 | } |
856 | |
857 | void DAP::SetFrameFormat(llvm::StringRef format) { |
858 | if (format.empty()) |
859 | return; |
860 | lldb::SBError error; |
861 | g_dap.frame_format = lldb::SBFormat(format.str().c_str(), error); |
862 | if (error.Fail()) { |
863 | g_dap.SendOutput( |
864 | o: OutputType::Console, |
865 | output: llvm::formatv( |
866 | Fmt: "The provided frame format '{0}' couldn't be parsed: {1}\n" , Vals&: format, |
867 | Vals: error.GetCString()) |
868 | .str()); |
869 | } |
870 | } |
871 | |
872 | void DAP::SetThreadFormat(llvm::StringRef format) { |
873 | if (format.empty()) |
874 | return; |
875 | lldb::SBError error; |
876 | g_dap.thread_format = lldb::SBFormat(format.str().c_str(), error); |
877 | if (error.Fail()) { |
878 | g_dap.SendOutput( |
879 | o: OutputType::Console, |
880 | output: llvm::formatv( |
881 | Fmt: "The provided thread format '{0}' couldn't be parsed: {1}\n" , |
882 | Vals&: format, Vals: error.GetCString()) |
883 | .str()); |
884 | } |
885 | } |
886 | |
887 | } // namespace lldb_dap |
888 | |