• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 <fstream>
10 #include <mutex>
11 #include <stdarg.h>
12 
13 #include "LLDBUtils.h"
14 #include "VSCode.h"
15 #include "llvm/Support/FormatVariadic.h"
16 
17 #if defined(_WIN32)
18 #define NOMINMAX
19 #include <fcntl.h>
20 #include <io.h>
21 #include <windows.h>
22 #endif
23 
24 using namespace lldb_vscode;
25 
26 namespace lldb_vscode {
27 
28 VSCode g_vsc;
29 
VSCode()30 VSCode::VSCode()
31     : variables(), broadcaster("lldb-vscode"), num_regs(0), num_locals(0),
32       num_globals(0), log(),
33       exception_breakpoints(
34           {{"cpp_catch", "C++ Catch", lldb::eLanguageTypeC_plus_plus},
35            {"cpp_throw", "C++ Throw", lldb::eLanguageTypeC_plus_plus},
36            {"objc_catch", "Objective C Catch", lldb::eLanguageTypeObjC},
37            {"objc_throw", "Objective C Throw", lldb::eLanguageTypeObjC},
38            {"swift_catch", "Swift Catch", lldb::eLanguageTypeSwift},
39            {"swift_throw", "Swift Throw", lldb::eLanguageTypeSwift}}),
40       focus_tid(LLDB_INVALID_THREAD_ID), sent_terminated_event(false),
41       stop_at_entry(false), is_attach(false),
42       reverse_request_seq(0), waiting_for_run_in_terminal(false) {
43   const char *log_file_path = getenv("LLDBVSCODE_LOG");
44 #if defined(_WIN32)
45   // Windows opens stdout and stdin in text mode which converts \n to 13,10
46   // while the value is just 10 on Darwin/Linux. Setting the file mode to binary
47   // fixes this.
48   int result = _setmode(fileno(stdout), _O_BINARY);
49   assert(result);
50   result = _setmode(fileno(stdin), _O_BINARY);
51   (void)result;
52   assert(result);
53 #endif
54   if (log_file_path)
55     log.reset(new std::ofstream(log_file_path));
56 }
57 
~VSCode()58 VSCode::~VSCode() {}
59 
GetLineForPC(int64_t sourceReference,lldb::addr_t pc) const60 int64_t VSCode::GetLineForPC(int64_t sourceReference, lldb::addr_t pc) const {
61   auto pos = source_map.find(sourceReference);
62   if (pos != source_map.end())
63     return pos->second.GetLineForPC(pc);
64   return 0;
65 }
66 
GetExceptionBreakpoint(const std::string & filter)67 ExceptionBreakpoint *VSCode::GetExceptionBreakpoint(const std::string &filter) {
68   for (auto &bp : exception_breakpoints) {
69     if (bp.filter == filter)
70       return &bp;
71   }
72   return nullptr;
73 }
74 
75 ExceptionBreakpoint *
GetExceptionBreakpoint(const lldb::break_id_t bp_id)76 VSCode::GetExceptionBreakpoint(const lldb::break_id_t bp_id) {
77   for (auto &bp : exception_breakpoints) {
78     if (bp.bp.GetID() == bp_id)
79       return &bp;
80   }
81   return nullptr;
82 }
83 
84 // Send the JSON in "json_str" to the "out" stream. Correctly send the
85 // "Content-Length:" field followed by the length, followed by the raw
86 // JSON bytes.
SendJSON(const std::string & json_str)87 void VSCode::SendJSON(const std::string &json_str) {
88   output.write_full("Content-Length: ");
89   output.write_full(llvm::utostr(json_str.size()));
90   output.write_full("\r\n\r\n");
91   output.write_full(json_str);
92 
93   if (log) {
94     *log << "<-- " << std::endl
95          << "Content-Length: " << json_str.size() << "\r\n\r\n"
96          << json_str << std::endl;
97   }
98 }
99 
100 // Serialize the JSON value into a string and send the JSON packet to
101 // the "out" stream.
SendJSON(const llvm::json::Value & json)102 void VSCode::SendJSON(const llvm::json::Value &json) {
103   std::string s;
104   llvm::raw_string_ostream strm(s);
105   strm << json;
106   static std::mutex mutex;
107   std::lock_guard<std::mutex> locker(mutex);
108   SendJSON(strm.str());
109 }
110 
111 // Read a JSON packet from the "in" stream.
ReadJSON()112 std::string VSCode::ReadJSON() {
113   std::string length_str;
114   std::string json_str;
115   int length;
116 
117   if (!input.read_expected(log.get(), "Content-Length: "))
118     return json_str;
119 
120   if (!input.read_line(log.get(), length_str))
121     return json_str;
122 
123   if (!llvm::to_integer(length_str, length))
124     return json_str;
125 
126   if (!input.read_expected(log.get(), "\r\n"))
127     return json_str;
128 
129   if (!input.read_full(log.get(), length, json_str))
130     return json_str;
131 
132   if (log) {
133     *log << "--> " << std::endl
134          << "Content-Length: " << length << "\r\n\r\n"
135          << json_str << std::endl;
136   }
137 
138   return json_str;
139 }
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 // }
SendOutput(OutputType o,const llvm::StringRef output)201 void VSCode::SendOutput(OutputType o, const llvm::StringRef output) {
202   if (output.empty())
203     return;
204 
205   llvm::json::Object event(CreateEventObject("output"));
206   llvm::json::Object body;
207   const char *category = nullptr;
208   switch (o) {
209   case OutputType::Console:
210     category = "console";
211     break;
212   case OutputType::Stdout:
213     category = "stdout";
214     break;
215   case OutputType::Stderr:
216     category = "stderr";
217     break;
218   case OutputType::Telemetry:
219     category = "telemetry";
220     break;
221   }
222   body.try_emplace("category", category);
223   EmplaceSafeString(body, "output", output.str());
224   event.try_emplace("body", std::move(body));
225   SendJSON(llvm::json::Value(std::move(event)));
226 }
227 
228 void __attribute__((format(printf, 3, 4)))
SendFormattedOutput(OutputType o,const char * format,...)229 VSCode::SendFormattedOutput(OutputType o, const char *format, ...) {
230   char buffer[1024];
231   va_list args;
232   va_start(args, format);
233   int actual_length = vsnprintf(buffer, sizeof(buffer), format, args);
234   va_end(args);
235   SendOutput(
236       o, llvm::StringRef(buffer, std::min<int>(actual_length, sizeof(buffer))));
237 }
238 
GetNextSourceReference()239 int64_t VSCode::GetNextSourceReference() {
240   static int64_t ref = 0;
241   return ++ref;
242 }
243 
244 ExceptionBreakpoint *
GetExceptionBPFromStopReason(lldb::SBThread & thread)245 VSCode::GetExceptionBPFromStopReason(lldb::SBThread &thread) {
246   const auto num = thread.GetStopReasonDataCount();
247   // Check to see if have hit an exception breakpoint and change the
248   // reason to "exception", but only do so if all breakpoints that were
249   // hit are exception breakpoints.
250   ExceptionBreakpoint *exc_bp = nullptr;
251   for (size_t i = 0; i < num; i += 2) {
252     // thread.GetStopReasonDataAtIndex(i) will return the bp ID and
253     // thread.GetStopReasonDataAtIndex(i+1) will return the location
254     // within that breakpoint. We only care about the bp ID so we can
255     // see if this is an exception breakpoint that is getting hit.
256     lldb::break_id_t bp_id = thread.GetStopReasonDataAtIndex(i);
257     exc_bp = GetExceptionBreakpoint(bp_id);
258     // If any breakpoint is not an exception breakpoint, then stop and
259     // report this as a normal breakpoint
260     if (exc_bp == nullptr)
261       return nullptr;
262   }
263   return exc_bp;
264 }
265 
GetLLDBThread(const llvm::json::Object & arguments)266 lldb::SBThread VSCode::GetLLDBThread(const llvm::json::Object &arguments) {
267   auto tid = GetSigned(arguments, "threadId", LLDB_INVALID_THREAD_ID);
268   return target.GetProcess().GetThreadByID(tid);
269 }
270 
GetLLDBFrame(const llvm::json::Object & arguments)271 lldb::SBFrame VSCode::GetLLDBFrame(const llvm::json::Object &arguments) {
272   const uint64_t frame_id = GetUnsigned(arguments, "frameId", UINT64_MAX);
273   lldb::SBProcess process = target.GetProcess();
274   // Upper 32 bits is the thread index ID
275   lldb::SBThread thread =
276       process.GetThreadByIndexID(GetLLDBThreadIndexID(frame_id));
277   // Lower 32 bits is the frame index
278   return thread.GetFrameAtIndex(GetLLDBFrameID(frame_id));
279 }
280 
CreateTopLevelScopes()281 llvm::json::Value VSCode::CreateTopLevelScopes() {
282   llvm::json::Array scopes;
283   scopes.emplace_back(CreateScope("Locals", VARREF_LOCALS, num_locals, false));
284   scopes.emplace_back(
285       CreateScope("Globals", VARREF_GLOBALS, num_globals, false));
286   scopes.emplace_back(CreateScope("Registers", VARREF_REGS, num_regs, false));
287   return llvm::json::Value(std::move(scopes));
288 }
289 
RunLLDBCommands(llvm::StringRef prefix,const std::vector<std::string> & commands)290 void VSCode::RunLLDBCommands(llvm::StringRef prefix,
291                              const std::vector<std::string> &commands) {
292   SendOutput(OutputType::Console,
293              llvm::StringRef(::RunLLDBCommands(prefix, commands)));
294 }
295 
RunInitCommands()296 void VSCode::RunInitCommands() {
297   RunLLDBCommands("Running initCommands:", init_commands);
298 }
299 
RunPreRunCommands()300 void VSCode::RunPreRunCommands() {
301   RunLLDBCommands("Running preRunCommands:", pre_run_commands);
302 }
303 
RunStopCommands()304 void VSCode::RunStopCommands() {
305   RunLLDBCommands("Running stopCommands:", stop_commands);
306 }
307 
RunExitCommands()308 void VSCode::RunExitCommands() {
309   RunLLDBCommands("Running exitCommands:", exit_commands);
310 }
311 
RunTerminateCommands()312 void VSCode::RunTerminateCommands() {
313   RunLLDBCommands("Running terminateCommands:", terminate_commands);
314 }
315 
316 lldb::SBTarget
CreateTargetFromArguments(const llvm::json::Object & arguments,lldb::SBError & error)317 VSCode::CreateTargetFromArguments(const llvm::json::Object &arguments,
318                                   lldb::SBError &error) {
319   // Grab the name of the program we need to debug and create a target using
320   // the given program as an argument. Executable file can be a source of target
321   // architecture and platform, if they differ from the host. Setting exe path
322   // in launch info is useless because Target.Launch() will not change
323   // architecture and platform, therefore they should be known at the target
324   // creation. We also use target triple and platform from the launch
325   // configuration, if given, since in some cases ELF file doesn't contain
326   // enough information to determine correct arch and platform (or ELF can be
327   // omitted at all), so it is good to leave the user an apportunity to specify
328   // those. Any of those three can be left empty.
329   llvm::StringRef target_triple = GetString(arguments, "targetTriple");
330   llvm::StringRef platform_name = GetString(arguments, "platformName");
331   llvm::StringRef program = GetString(arguments, "program");
332   auto target = this->debugger.CreateTarget(
333       program.data(), target_triple.data(), platform_name.data(),
334       true, // Add dependent modules.
335       error);
336 
337   if (error.Fail()) {
338     // Update message if there was an error.
339     error.SetErrorStringWithFormat(
340         "Could not create a target for a program '%s': %s.", program.data(),
341         error.GetCString());
342   }
343 
344   return target;
345 }
346 
SetTarget(const lldb::SBTarget target)347 void VSCode::SetTarget(const lldb::SBTarget target) {
348   this->target = target;
349 
350   if (target.IsValid()) {
351     // Configure breakpoint event listeners for the target.
352     lldb::SBListener listener = this->debugger.GetListener();
353     listener.StartListeningForEvents(
354         this->target.GetBroadcaster(),
355         lldb::SBTarget::eBroadcastBitBreakpointChanged);
356     listener.StartListeningForEvents(this->broadcaster,
357                                      eBroadcastBitStopEventThread);
358     listener.StartListeningForEvents(
359         this->target.GetBroadcaster(),
360         lldb::SBTarget::eBroadcastBitModulesLoaded |
361             lldb::SBTarget::eBroadcastBitModulesUnloaded |
362             lldb::SBTarget::eBroadcastBitSymbolsLoaded);
363   }
364 }
365 
GetNextObject(llvm::json::Object & object)366 PacketStatus VSCode::GetNextObject(llvm::json::Object &object) {
367   std::string json = ReadJSON();
368   if (json.empty())
369     return PacketStatus::EndOfFile;
370 
371   llvm::StringRef json_sref(json);
372   llvm::Expected<llvm::json::Value> json_value = llvm::json::parse(json_sref);
373   if (!json_value) {
374     auto error = json_value.takeError();
375     if (log) {
376       std::string error_str;
377       llvm::raw_string_ostream strm(error_str);
378       strm << error;
379       strm.flush();
380       *log << "error: failed to parse JSON: " << error_str << std::endl
381            << json << std::endl;
382     }
383     return PacketStatus::JSONMalformed;
384   }
385   object = *json_value->getAsObject();
386   if (!json_value->getAsObject()) {
387     if (log)
388       *log << "error: json packet isn't a object" << std::endl;
389     return PacketStatus::JSONNotObject;
390   }
391   return PacketStatus::Success;
392 }
393 
HandleObject(const llvm::json::Object & object)394 bool VSCode::HandleObject(const llvm::json::Object &object) {
395   const auto packet_type = GetString(object, "type");
396   if (packet_type == "request") {
397     const auto command = GetString(object, "command");
398     auto handler_pos = request_handlers.find(std::string(command));
399     if (handler_pos != request_handlers.end()) {
400       handler_pos->second(object);
401       return true; // Success
402     } else {
403       if (log)
404         *log << "error: unhandled command \"" << command.data() << std::endl;
405       return false; // Fail
406     }
407   }
408   return false;
409 }
410 
SendReverseRequest(llvm::json::Object request,llvm::json::Object & response)411 PacketStatus VSCode::SendReverseRequest(llvm::json::Object request,
412                                         llvm::json::Object &response) {
413   request.try_emplace("seq", ++reverse_request_seq);
414   SendJSON(llvm::json::Value(std::move(request)));
415   while (true) {
416     PacketStatus status = GetNextObject(response);
417     const auto packet_type = GetString(response, "type");
418     if (packet_type == "response")
419       return status;
420     else {
421       // Not our response, we got another packet
422       HandleObject(response);
423     }
424   }
425   return PacketStatus::EndOfFile;
426 }
427 
RegisterRequestCallback(std::string request,RequestCallback callback)428 void VSCode::RegisterRequestCallback(std::string request,
429                                      RequestCallback callback) {
430   request_handlers[request] = callback;
431 }
432 
433 } // namespace lldb_vscode
434