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 <stdarg.h>
10 #include <fstream>
11 #include <mutex>
12 
13 #include "VSCode.h"
14 #include "LLDBUtils.h"
15 
16 #if defined(_WIN32)
17 #include <io.h>
18 #include <fcntl.h>
19 #endif
20 
21 using namespace lldb_vscode;
22 
23 namespace {
24   inline bool IsEmptyLine(llvm::StringRef S) {
25     return S.ltrim().empty();
26   }
27 } // namespace
28 
29 namespace lldb_vscode {
30 
31 VSCode g_vsc;
32 
33 VSCode::VSCode()
34     : in(stdin), out(stdout), launch_info(nullptr), variables(),
35       broadcaster("lldb-vscode"), num_regs(0), num_locals(0), num_globals(0),
36       log(), exception_breakpoints(
37                  {{"cpp_catch", "C++ Catch", lldb::eLanguageTypeC_plus_plus},
38                   {"cpp_throw", "C++ Throw", lldb::eLanguageTypeC_plus_plus},
39                   {"objc_catch", "Objective C Catch", lldb::eLanguageTypeObjC},
40                   {"objc_throw", "Objective C Throw", lldb::eLanguageTypeObjC},
41                   {"swift_catch", "Swift Catch", lldb::eLanguageTypeSwift},
42                   {"swift_throw", "Swift Throw", lldb::eLanguageTypeSwift}}),
43       focus_tid(LLDB_INVALID_THREAD_ID), sent_terminated_event(false),
44       stop_at_entry(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   assert(_setmode(fileno(stdout), _O_BINARY));
51   assert(_setmode(fileno(stdin), _O_BINARY));
52 #endif
53   if (log_file_path)
54     log.reset(new std::ofstream(log_file_path));
55 }
56 
57 VSCode::~VSCode() {
58   CloseInputStream();
59   CloseOutputStream();
60 }
61 
62 void VSCode::CloseInputStream() {
63   if (in != stdin) {
64     fclose(in);
65     in = nullptr;
66   }
67 }
68 
69 void VSCode::CloseOutputStream() {
70   if (out != stdout) {
71     fclose(out);
72     out = nullptr;
73   }
74 }
75 
76 int64_t VSCode::GetLineForPC(int64_t sourceReference, lldb::addr_t pc) const {
77   auto pos = source_map.find(sourceReference);
78   if (pos != source_map.end())
79     return pos->second.GetLineForPC(pc);
80   return 0;
81 }
82 
83 ExceptionBreakpoint *VSCode::GetExceptionBreakpoint(const std::string &filter) {
84   for (auto &bp : exception_breakpoints) {
85     if (bp.filter == filter)
86       return &bp;
87   }
88   return nullptr;
89 }
90 
91 ExceptionBreakpoint *
92 VSCode::GetExceptionBreakpoint(const lldb::break_id_t bp_id) {
93   for (auto &bp : exception_breakpoints) {
94     if (bp.bp.GetID() == bp_id)
95       return &bp;
96   }
97   return nullptr;
98 }
99 
100 //----------------------------------------------------------------------
101 // Send the JSON in "json_str" to the "out" stream. Correctly send the
102 // "Content-Length:" field followed by the length, followed by the raw
103 // JSON bytes.
104 //----------------------------------------------------------------------
105 void VSCode::SendJSON(const std::string &json_str) {
106   fprintf(out, "Content-Length: %u\r\n\r\n%s", (uint32_t)json_str.size(),
107           json_str.c_str());
108   fflush(out);
109   if (log) {
110     *log << "<-- " << std::endl
111          << "Content-Length: " << json_str.size() << "\r\n\r\n"
112          << json_str << std::endl;
113   }
114 }
115 
116 //----------------------------------------------------------------------
117 // Serialize the JSON value into a string and send the JSON packet to
118 // the "out" stream.
119 //----------------------------------------------------------------------
120 void VSCode::SendJSON(const llvm::json::Value &json) {
121   std::string s;
122   llvm::raw_string_ostream strm(s);
123   strm << json;
124   static std::mutex mutex;
125   std::lock_guard<std::mutex> locker(mutex);
126   SendJSON(strm.str());
127 }
128 
129 //----------------------------------------------------------------------
130 // Read a JSON packet from the "in" stream.
131 //----------------------------------------------------------------------
132 std::string VSCode::ReadJSON() {
133   static std::string header("Content-Length: ");
134 
135   uint32_t packet_len = 0;
136   std::string json_str;
137   char line[1024];
138 
139   while (fgets(line, sizeof(line), in)) {
140     if (strncmp(line, header.data(), header.size()) == 0) {
141       packet_len = atoi(line + header.size());
142       if (fgets(line, sizeof(line), in)) {
143         if (!IsEmptyLine(line))
144           if (log)
145             *log << "warning: expected empty line but got: \"" << line << "\""
146                  << std::endl;
147         break;
148       }
149     } else {
150       if (log)
151         *log << "warning: expected \"" << header << "\" but got: \"" << line
152              << "\"" << std::endl;
153     }
154   }
155   // This is followed by two windows newline sequences ("\r\n\r\n") so eat
156   // two the newline sequences
157   if (packet_len > 0) {
158     json_str.resize(packet_len);
159     auto bytes_read = fread(&json_str[0], 1, packet_len, in);
160     if (bytes_read < packet_len) {
161       if (log)
162         *log << "error: read fewer bytes (" << bytes_read
163              << ") than requested (" << packet_len << ")" << std::endl;
164       json_str.erase(bytes_read);
165     }
166     if (log) {
167       *log << "--> " << std::endl;
168       *log << header << packet_len << "\r\n\r\n" << json_str << std::endl;
169     }
170   }
171   return json_str;
172 }
173 
174 //----------------------------------------------------------------------
175 // "OutputEvent": {
176 //   "allOf": [ { "$ref": "#/definitions/Event" }, {
177 //     "type": "object",
178 //     "description": "Event message for 'output' event type. The event
179 //                     indicates that the target has produced some output.",
180 //     "properties": {
181 //       "event": {
182 //         "type": "string",
183 //         "enum": [ "output" ]
184 //       },
185 //       "body": {
186 //         "type": "object",
187 //         "properties": {
188 //           "category": {
189 //             "type": "string",
190 //             "description": "The output category. If not specified,
191 //                             'console' is assumed.",
192 //             "_enum": [ "console", "stdout", "stderr", "telemetry" ]
193 //           },
194 //           "output": {
195 //             "type": "string",
196 //             "description": "The output to report."
197 //           },
198 //           "variablesReference": {
199 //             "type": "number",
200 //             "description": "If an attribute 'variablesReference' exists
201 //                             and its value is > 0, the output contains
202 //                             objects which can be retrieved by passing
203 //                             variablesReference to the VariablesRequest."
204 //           },
205 //           "source": {
206 //             "$ref": "#/definitions/Source",
207 //             "description": "An optional source location where the output
208 //                             was produced."
209 //           },
210 //           "line": {
211 //             "type": "integer",
212 //             "description": "An optional source location line where the
213 //                             output was produced."
214 //           },
215 //           "column": {
216 //             "type": "integer",
217 //             "description": "An optional source location column where the
218 //                             output was produced."
219 //           },
220 //           "data": {
221 //             "type":["array","boolean","integer","null","number","object",
222 //                     "string"],
223 //             "description": "Optional data to report. For the 'telemetry'
224 //                             category the data will be sent to telemetry, for
225 //                             the other categories the data is shown in JSON
226 //                             format."
227 //           }
228 //         },
229 //         "required": ["output"]
230 //       }
231 //     },
232 //     "required": [ "event", "body" ]
233 //   }]
234 // }
235 //----------------------------------------------------------------------
236 void VSCode::SendOutput(OutputType o, const llvm::StringRef output) {
237   if (output.empty())
238     return;
239 
240   llvm::json::Object event(CreateEventObject("output"));
241   llvm::json::Object body;
242   const char *category = nullptr;
243   switch (o) {
244   case OutputType::Console:
245     category = "console";
246     break;
247   case OutputType::Stdout:
248     category = "stdout";
249     break;
250   case OutputType::Stderr:
251     category = "stderr";
252     break;
253   case OutputType::Telemetry:
254     category = "telemetry";
255     break;
256   }
257   body.try_emplace("category", category);
258   EmplaceSafeString(body, "output", output.str());
259   event.try_emplace("body", std::move(body));
260   SendJSON(llvm::json::Value(std::move(event)));
261 }
262 
263 void __attribute__((format(printf, 3, 4)))
264 VSCode::SendFormattedOutput(OutputType o, const char *format, ...) {
265   char buffer[1024];
266   va_list args;
267   va_start(args, format);
268   int actual_length = vsnprintf(buffer, sizeof(buffer), format, args);
269   va_end(args);
270   SendOutput(o, llvm::StringRef(buffer,
271                                 std::min<int>(actual_length, sizeof(buffer))));
272 }
273 
274 int64_t VSCode::GetNextSourceReference() {
275   static int64_t ref = 0;
276   return ++ref;
277 }
278 
279 ExceptionBreakpoint *
280 VSCode::GetExceptionBPFromStopReason(lldb::SBThread &thread) {
281   const auto num = thread.GetStopReasonDataCount();
282   // Check to see if have hit an exception breakpoint and change the
283   // reason to "exception", but only do so if all breakpoints that were
284   // hit are exception breakpoints.
285   ExceptionBreakpoint *exc_bp = nullptr;
286   for (size_t i = 0; i < num; i += 2) {
287     // thread.GetStopReasonDataAtIndex(i) will return the bp ID and
288     // thread.GetStopReasonDataAtIndex(i+1) will return the location
289     // within that breakpoint. We only care about the bp ID so we can
290     // see if this is an exception breakpoint that is getting hit.
291     lldb::break_id_t bp_id = thread.GetStopReasonDataAtIndex(i);
292     exc_bp = GetExceptionBreakpoint(bp_id);
293     // If any breakpoint is not an exception breakpoint, then stop and
294     // report this as a normal breakpoint
295     if (exc_bp == nullptr)
296       return nullptr;
297   }
298   return exc_bp;
299 }
300 
301 lldb::SBThread VSCode::GetLLDBThread(const llvm::json::Object &arguments) {
302   auto tid = GetSigned(arguments, "threadId", LLDB_INVALID_THREAD_ID);
303   return target.GetProcess().GetThreadByID(tid);
304 }
305 
306 lldb::SBFrame VSCode::GetLLDBFrame(const llvm::json::Object &arguments) {
307   const uint64_t frame_id = GetUnsigned(arguments, "frameId", UINT64_MAX);
308   lldb::SBProcess process = target.GetProcess();
309   // Upper 32 bits is the thread index ID
310   lldb::SBThread thread =
311       process.GetThreadByIndexID(GetLLDBThreadIndexID(frame_id));
312   // Lower 32 bits is the frame index
313   return thread.GetFrameAtIndex(GetLLDBFrameID(frame_id));
314 }
315 
316 llvm::json::Value VSCode::CreateTopLevelScopes() {
317   llvm::json::Array scopes;
318   scopes.emplace_back(CreateScope("Locals", VARREF_LOCALS, num_locals, false));
319   scopes.emplace_back(
320       CreateScope("Globals", VARREF_GLOBALS, num_globals, false));
321   scopes.emplace_back(CreateScope("Registers", VARREF_REGS, num_regs, false));
322   return llvm::json::Value(std::move(scopes));
323 }
324 
325 void VSCode::RunLLDBCommands(llvm::StringRef prefix,
326                              const std::vector<std::string> &commands) {
327   SendOutput(OutputType::Console,
328              llvm::StringRef(::RunLLDBCommands(prefix, commands)));
329 }
330 
331 void VSCode::RunInitCommands() {
332   RunLLDBCommands("Running initCommands:", init_commands);
333 }
334 
335 void VSCode::RunPreRunCommands() {
336   RunLLDBCommands("Running preRunCommands:", pre_run_commands);
337 }
338 
339 void VSCode::RunStopCommands() {
340   RunLLDBCommands("Running stopCommands:", stop_commands);
341 }
342 
343 void VSCode::RunExitCommands() {
344   RunLLDBCommands("Running exitCommands:", exit_commands);
345 }
346 
347 } // namespace lldb_vscode
348 
349