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