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 <fstream>
11 #include <mutex>
12 #include <sstream>
13 #include <stdarg.h>
14 
15 #include "LLDBUtils.h"
16 #include "VSCode.h"
17 #include "llvm/Support/FormatVariadic.h"
18 
19 #if defined(_WIN32)
20 #define NOMINMAX
21 #include <fcntl.h>
22 #include <io.h>
23 #include <windows.h>
24 #endif
25 
26 using namespace lldb_vscode;
27 
28 namespace lldb_vscode {
29 
30 VSCode g_vsc;
31 
32 VSCode::VSCode()
33     : variables(), broadcaster("lldb-vscode"), num_regs(0), num_locals(0),
34       num_globals(0), log(),
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       reverse_request_seq(0), waiting_for_run_in_terminal(false) {
45   const char *log_file_path = getenv("LLDBVSCODE_LOG");
46 #if defined(_WIN32)
47   // Windows opens stdout and stdin in text mode which converts \n to 13,10
48   // while the value is just 10 on Darwin/Linux. Setting the file mode to binary
49   // fixes this.
50   int result = _setmode(fileno(stdout), _O_BINARY);
51   assert(result);
52   result = _setmode(fileno(stdin), _O_BINARY);
53   (void)result;
54   assert(result);
55 #endif
56   if (log_file_path)
57     log.reset(new std::ofstream(log_file_path));
58 }
59 
60 VSCode::~VSCode() {}
61 
62 int64_t VSCode::GetLineForPC(int64_t sourceReference, lldb::addr_t pc) const {
63   auto pos = source_map.find(sourceReference);
64   if (pos != source_map.end())
65     return pos->second.GetLineForPC(pc);
66   return 0;
67 }
68 
69 ExceptionBreakpoint *VSCode::GetExceptionBreakpoint(const std::string &filter) {
70   for (auto &bp : exception_breakpoints) {
71     if (bp.filter == filter)
72       return &bp;
73   }
74   return nullptr;
75 }
76 
77 ExceptionBreakpoint *
78 VSCode::GetExceptionBreakpoint(const lldb::break_id_t bp_id) {
79   for (auto &bp : exception_breakpoints) {
80     if (bp.bp.GetID() == bp_id)
81       return &bp;
82   }
83   return nullptr;
84 }
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 void VSCode::SendJSON(const std::string &json_str) {
90   output.write_full("Content-Length: ");
91   output.write_full(llvm::utostr(json_str.size()));
92   output.write_full("\r\n\r\n");
93   output.write_full(json_str);
94 
95   if (log) {
96     *log << "<-- " << std::endl
97          << "Content-Length: " << json_str.size() << "\r\n\r\n"
98          << json_str << std::endl;
99   }
100 }
101 
102 // Serialize the JSON value into a string and send the JSON packet to
103 // the "out" stream.
104 void VSCode::SendJSON(const llvm::json::Value &json) {
105   std::string s;
106   llvm::raw_string_ostream strm(s);
107   strm << json;
108   static std::mutex mutex;
109   std::lock_guard<std::mutex> locker(mutex);
110   SendJSON(strm.str());
111 }
112 
113 // Read a JSON packet from the "in" stream.
114 std::string VSCode::ReadJSON() {
115   std::string length_str;
116   std::string json_str;
117   int length;
118 
119   if (!input.read_expected(log.get(), "Content-Length: "))
120     return json_str;
121 
122   if (!input.read_line(log.get(), length_str))
123     return json_str;
124 
125   if (!llvm::to_integer(length_str, length))
126     return json_str;
127 
128   if (!input.read_expected(log.get(), "\r\n"))
129     return json_str;
130 
131   if (!input.read_full(log.get(), length, json_str))
132     return json_str;
133 
134   if (log) {
135     *log << "--> " << std::endl
136          << "Content-Length: " << length << "\r\n\r\n"
137          << json_str << std::endl;
138   }
139 
140   return json_str;
141 }
142 
143 // "OutputEvent": {
144 //   "allOf": [ { "$ref": "#/definitions/Event" }, {
145 //     "type": "object",
146 //     "description": "Event message for 'output' event type. The event
147 //                     indicates that the target has produced some output.",
148 //     "properties": {
149 //       "event": {
150 //         "type": "string",
151 //         "enum": [ "output" ]
152 //       },
153 //       "body": {
154 //         "type": "object",
155 //         "properties": {
156 //           "category": {
157 //             "type": "string",
158 //             "description": "The output category. If not specified,
159 //                             'console' is assumed.",
160 //             "_enum": [ "console", "stdout", "stderr", "telemetry" ]
161 //           },
162 //           "output": {
163 //             "type": "string",
164 //             "description": "The output to report."
165 //           },
166 //           "variablesReference": {
167 //             "type": "number",
168 //             "description": "If an attribute 'variablesReference' exists
169 //                             and its value is > 0, the output contains
170 //                             objects which can be retrieved by passing
171 //                             variablesReference to the VariablesRequest."
172 //           },
173 //           "source": {
174 //             "$ref": "#/definitions/Source",
175 //             "description": "An optional source location where the output
176 //                             was produced."
177 //           },
178 //           "line": {
179 //             "type": "integer",
180 //             "description": "An optional source location line where the
181 //                             output was produced."
182 //           },
183 //           "column": {
184 //             "type": "integer",
185 //             "description": "An optional source location column where the
186 //                             output was produced."
187 //           },
188 //           "data": {
189 //             "type":["array","boolean","integer","null","number","object",
190 //                     "string"],
191 //             "description": "Optional data to report. For the 'telemetry'
192 //                             category the data will be sent to telemetry, for
193 //                             the other categories the data is shown in JSON
194 //                             format."
195 //           }
196 //         },
197 //         "required": ["output"]
198 //       }
199 //     },
200 //     "required": [ "event", "body" ]
201 //   }]
202 // }
203 void VSCode::SendOutput(OutputType o, const llvm::StringRef output) {
204   if (output.empty())
205     return;
206 
207   llvm::json::Object event(CreateEventObject("output"));
208   llvm::json::Object body;
209   const char *category = nullptr;
210   switch (o) {
211   case OutputType::Console:
212     category = "console";
213     break;
214   case OutputType::Stdout:
215     category = "stdout";
216     break;
217   case OutputType::Stderr:
218     category = "stderr";
219     break;
220   case OutputType::Telemetry:
221     category = "telemetry";
222     break;
223   }
224   body.try_emplace("category", category);
225   EmplaceSafeString(body, "output", output.str());
226   event.try_emplace("body", std::move(body));
227   SendJSON(llvm::json::Value(std::move(event)));
228 }
229 
230 // interface ProgressStartEvent extends Event {
231 //   event: 'progressStart';
232 //
233 //   body: {
234 //     /**
235 //      * An ID that must be used in subsequent 'progressUpdate' and
236 //      'progressEnd'
237 //      * events to make them refer to the same progress reporting.
238 //      * IDs must be unique within a debug session.
239 //      */
240 //     progressId: string;
241 //
242 //     /**
243 //      * Mandatory (short) title of the progress reporting. Shown in the UI to
244 //      * describe the long running operation.
245 //      */
246 //     title: string;
247 //
248 //     /**
249 //      * The request ID that this progress report is related to. If specified a
250 //      * debug adapter is expected to emit
251 //      * progress events for the long running request until the request has
252 //      been
253 //      * either completed or cancelled.
254 //      * If the request ID is omitted, the progress report is assumed to be
255 //      * related to some general activity of the debug adapter.
256 //      */
257 //     requestId?: number;
258 //
259 //     /**
260 //      * If true, the request that reports progress may be canceled with a
261 //      * 'cancel' request.
262 //      * So this property basically controls whether the client should use UX
263 //      that
264 //      * supports cancellation.
265 //      * Clients that don't support cancellation are allowed to ignore the
266 //      * setting.
267 //      */
268 //     cancellable?: boolean;
269 //
270 //     /**
271 //      * Optional, more detailed progress message.
272 //      */
273 //     message?: string;
274 //
275 //     /**
276 //      * Optional progress percentage to display (value range: 0 to 100). If
277 //      * omitted no percentage will be shown.
278 //      */
279 //     percentage?: number;
280 //   };
281 // }
282 //
283 // interface ProgressUpdateEvent extends Event {
284 //   event: 'progressUpdate';
285 //
286 //   body: {
287 //     /**
288 //      * The ID that was introduced in the initial 'progressStart' event.
289 //      */
290 //     progressId: string;
291 //
292 //     /**
293 //      * Optional, more detailed progress message. If omitted, the previous
294 //      * message (if any) is used.
295 //      */
296 //     message?: string;
297 //
298 //     /**
299 //      * Optional progress percentage to display (value range: 0 to 100). If
300 //      * omitted no percentage will be shown.
301 //      */
302 //     percentage?: number;
303 //   };
304 // }
305 //
306 // interface ProgressEndEvent extends Event {
307 //   event: 'progressEnd';
308 //
309 //   body: {
310 //     /**
311 //      * The ID that was introduced in the initial 'ProgressStartEvent'.
312 //      */
313 //     progressId: string;
314 //
315 //     /**
316 //      * Optional, more detailed progress message. If omitted, the previous
317 //      * message (if any) is used.
318 //      */
319 //     message?: string;
320 //   };
321 // }
322 
323 void VSCode::SendProgressEvent(uint64_t progress_id, const char *message,
324                                uint64_t completed, uint64_t total) {
325   enum ProgressEventType {
326     progressInvalid,
327     progressStart,
328     progressUpdate,
329     progressEnd
330   };
331   const char *event_name = nullptr;
332   ProgressEventType event_type = progressInvalid;
333   if (completed == 0) {
334     event_type = progressStart;
335     event_name = "progressStart";
336   } else if (completed == total) {
337     event_type = progressEnd;
338     event_name = "progressEnd";
339   } else if (completed < total) {
340     event_type = progressUpdate;
341     event_name = "progressUpdate";
342   }
343   if (event_type == progressInvalid)
344     return;
345 
346   llvm::json::Object event(CreateEventObject(event_name));
347   llvm::json::Object body;
348   std::string progress_id_str;
349   llvm::raw_string_ostream progress_id_strm(progress_id_str);
350   progress_id_strm << progress_id;
351   progress_id_strm.flush();
352   body.try_emplace("progressId", progress_id_str);
353   if (event_type == progressStart) {
354     EmplaceSafeString(body, "title", message);
355     body.try_emplace("cancellable", false);
356   }
357   auto now = std::chrono::duration<double>(
358       std::chrono::system_clock::now().time_since_epoch());
359   std::string timestamp(llvm::formatv("{0:f9}", now.count()));
360   EmplaceSafeString(body, "timestamp", timestamp);
361 
362   if (0 < total && total < UINT64_MAX) {
363     uint32_t percentage = (uint32_t)(((float)completed / (float)total) * 100.0);
364     body.try_emplace("percentage", percentage);
365   }
366   event.try_emplace("body", std::move(body));
367   SendJSON(llvm::json::Value(std::move(event)));
368 }
369 
370 void __attribute__((format(printf, 3, 4)))
371 VSCode::SendFormattedOutput(OutputType o, const char *format, ...) {
372   char buffer[1024];
373   va_list args;
374   va_start(args, format);
375   int actual_length = vsnprintf(buffer, sizeof(buffer), format, args);
376   va_end(args);
377   SendOutput(
378       o, llvm::StringRef(buffer, std::min<int>(actual_length, sizeof(buffer))));
379 }
380 
381 int64_t VSCode::GetNextSourceReference() {
382   static int64_t ref = 0;
383   return ++ref;
384 }
385 
386 ExceptionBreakpoint *
387 VSCode::GetExceptionBPFromStopReason(lldb::SBThread &thread) {
388   const auto num = thread.GetStopReasonDataCount();
389   // Check to see if have hit an exception breakpoint and change the
390   // reason to "exception", but only do so if all breakpoints that were
391   // hit are exception breakpoints.
392   ExceptionBreakpoint *exc_bp = nullptr;
393   for (size_t i = 0; i < num; i += 2) {
394     // thread.GetStopReasonDataAtIndex(i) will return the bp ID and
395     // thread.GetStopReasonDataAtIndex(i+1) will return the location
396     // within that breakpoint. We only care about the bp ID so we can
397     // see if this is an exception breakpoint that is getting hit.
398     lldb::break_id_t bp_id = thread.GetStopReasonDataAtIndex(i);
399     exc_bp = GetExceptionBreakpoint(bp_id);
400     // If any breakpoint is not an exception breakpoint, then stop and
401     // report this as a normal breakpoint
402     if (exc_bp == nullptr)
403       return nullptr;
404   }
405   return exc_bp;
406 }
407 
408 lldb::SBThread VSCode::GetLLDBThread(const llvm::json::Object &arguments) {
409   auto tid = GetSigned(arguments, "threadId", LLDB_INVALID_THREAD_ID);
410   return target.GetProcess().GetThreadByID(tid);
411 }
412 
413 lldb::SBFrame VSCode::GetLLDBFrame(const llvm::json::Object &arguments) {
414   const uint64_t frame_id = GetUnsigned(arguments, "frameId", UINT64_MAX);
415   lldb::SBProcess process = target.GetProcess();
416   // Upper 32 bits is the thread index ID
417   lldb::SBThread thread =
418       process.GetThreadByIndexID(GetLLDBThreadIndexID(frame_id));
419   // Lower 32 bits is the frame index
420   return thread.GetFrameAtIndex(GetLLDBFrameID(frame_id));
421 }
422 
423 llvm::json::Value VSCode::CreateTopLevelScopes() {
424   llvm::json::Array scopes;
425   scopes.emplace_back(CreateScope("Locals", VARREF_LOCALS, num_locals, false));
426   scopes.emplace_back(
427       CreateScope("Globals", VARREF_GLOBALS, num_globals, false));
428   scopes.emplace_back(CreateScope("Registers", VARREF_REGS, num_regs, false));
429   return llvm::json::Value(std::move(scopes));
430 }
431 
432 void VSCode::RunLLDBCommands(llvm::StringRef prefix,
433                              const std::vector<std::string> &commands) {
434   SendOutput(OutputType::Console,
435              llvm::StringRef(::RunLLDBCommands(prefix, commands)));
436 }
437 
438 void VSCode::RunInitCommands() {
439   RunLLDBCommands("Running initCommands:", init_commands);
440 }
441 
442 void VSCode::RunPreRunCommands() {
443   RunLLDBCommands("Running preRunCommands:", pre_run_commands);
444 }
445 
446 void VSCode::RunStopCommands() {
447   RunLLDBCommands("Running stopCommands:", stop_commands);
448 }
449 
450 void VSCode::RunExitCommands() {
451   RunLLDBCommands("Running exitCommands:", exit_commands);
452 }
453 
454 void VSCode::RunTerminateCommands() {
455   RunLLDBCommands("Running terminateCommands:", terminate_commands);
456 }
457 
458 lldb::SBTarget
459 VSCode::CreateTargetFromArguments(const llvm::json::Object &arguments,
460                                   lldb::SBError &error) {
461   // Grab the name of the program we need to debug and create a target using
462   // the given program as an argument. Executable file can be a source of target
463   // architecture and platform, if they differ from the host. Setting exe path
464   // in launch info is useless because Target.Launch() will not change
465   // architecture and platform, therefore they should be known at the target
466   // creation. We also use target triple and platform from the launch
467   // configuration, if given, since in some cases ELF file doesn't contain
468   // enough information to determine correct arch and platform (or ELF can be
469   // omitted at all), so it is good to leave the user an apportunity to specify
470   // those. Any of those three can be left empty.
471   llvm::StringRef target_triple = GetString(arguments, "targetTriple");
472   llvm::StringRef platform_name = GetString(arguments, "platformName");
473   llvm::StringRef program = GetString(arguments, "program");
474   auto target = this->debugger.CreateTarget(
475       program.data(), target_triple.data(), platform_name.data(),
476       true, // Add dependent modules.
477       error);
478 
479   if (error.Fail()) {
480     // Update message if there was an error.
481     error.SetErrorStringWithFormat(
482         "Could not create a target for a program '%s': %s.", program.data(),
483         error.GetCString());
484   }
485 
486   return target;
487 }
488 
489 void VSCode::SetTarget(const lldb::SBTarget target) {
490   this->target = target;
491 
492   if (target.IsValid()) {
493     // Configure breakpoint event listeners for the target.
494     lldb::SBListener listener = this->debugger.GetListener();
495     listener.StartListeningForEvents(
496         this->target.GetBroadcaster(),
497         lldb::SBTarget::eBroadcastBitBreakpointChanged);
498     listener.StartListeningForEvents(this->broadcaster,
499                                      eBroadcastBitStopEventThread);
500   }
501 }
502 
503 PacketStatus VSCode::GetNextObject(llvm::json::Object &object) {
504   std::string json = ReadJSON();
505   if (json.empty())
506     return PacketStatus::EndOfFile;
507 
508   llvm::StringRef json_sref(json);
509   llvm::Expected<llvm::json::Value> json_value = llvm::json::parse(json_sref);
510   if (!json_value) {
511     auto error = json_value.takeError();
512     if (log) {
513       std::string error_str;
514       llvm::raw_string_ostream strm(error_str);
515       strm << error;
516       strm.flush();
517       *log << "error: failed to parse JSON: " << error_str << std::endl
518            << json << std::endl;
519     }
520     return PacketStatus::JSONMalformed;
521   }
522   object = *json_value->getAsObject();
523   if (!json_value->getAsObject()) {
524     if (log)
525       *log << "error: json packet isn't a object" << std::endl;
526     return PacketStatus::JSONNotObject;
527   }
528   return PacketStatus::Success;
529 }
530 
531 bool VSCode::HandleObject(const llvm::json::Object &object) {
532   const auto packet_type = GetString(object, "type");
533   if (packet_type == "request") {
534     const auto command = GetString(object, "command");
535     auto handler_pos = request_handlers.find(std::string(command));
536     if (handler_pos != request_handlers.end()) {
537       handler_pos->second(object);
538       return true; // Success
539     } else {
540       if (log)
541         *log << "error: unhandled command \"" << command.data() << std::endl;
542       return false; // Fail
543     }
544   }
545   return false;
546 }
547 
548 PacketStatus VSCode::SendReverseRequest(llvm::json::Object request,
549                                         llvm::json::Object &response) {
550   request.try_emplace("seq", ++reverse_request_seq);
551   SendJSON(llvm::json::Value(std::move(request)));
552   while (true) {
553     PacketStatus status = GetNextObject(response);
554     const auto packet_type = GetString(response, "type");
555     if (packet_type == "response")
556       return status;
557     else {
558       // Not our response, we got another packet
559       HandleObject(response);
560     }
561   }
562   return PacketStatus::EndOfFile;
563 }
564 
565 void VSCode::RegisterRequestCallback(std::string request,
566                                      RequestCallback callback) {
567   request_handlers[request] = callback;
568 }
569 
570 } // namespace lldb_vscode
571