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