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