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 "LLDBUtils.h"
14 #include "VSCode.h"
15 #include "llvm/Support/FormatVariadic.h"
16 
17 #if defined(_WIN32)
18 #define NOMINMAX
19 #include <windows.h>
20 #include <fcntl.h>
21 #include <io.h>
22 #endif
23 
24 using namespace lldb_vscode;
25 
26 namespace lldb_vscode {
27 
28 VSCode g_vsc;
29 
30 VSCode::VSCode()
31     : launch_info(nullptr), variables(), broadcaster("lldb-vscode"),
32       num_regs(0), num_locals(0), 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) {
42   const char *log_file_path = getenv("LLDBVSCODE_LOG");
43 #if defined(_WIN32)
44 // Windows opens stdout and stdin in text mode which converts \n to 13,10
45 // while the value is just 10 on Darwin/Linux. Setting the file mode to binary
46 // fixes this.
47   int result = _setmode(fileno(stdout), _O_BINARY);
48   assert(result);
49   result = _setmode(fileno(stdin), _O_BINARY);
50   (void)result;
51   assert(result);
52 #endif
53   if (log_file_path)
54     log.reset(new std::ofstream(log_file_path));
55 }
56 
57 VSCode::~VSCode() {
58 }
59 
60 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 
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 *
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.
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.
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.
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   return json_str;
133 }
134 
135 // "OutputEvent": {
136 //   "allOf": [ { "$ref": "#/definitions/Event" }, {
137 //     "type": "object",
138 //     "description": "Event message for 'output' event type. The event
139 //                     indicates that the target has produced some output.",
140 //     "properties": {
141 //       "event": {
142 //         "type": "string",
143 //         "enum": [ "output" ]
144 //       },
145 //       "body": {
146 //         "type": "object",
147 //         "properties": {
148 //           "category": {
149 //             "type": "string",
150 //             "description": "The output category. If not specified,
151 //                             'console' is assumed.",
152 //             "_enum": [ "console", "stdout", "stderr", "telemetry" ]
153 //           },
154 //           "output": {
155 //             "type": "string",
156 //             "description": "The output to report."
157 //           },
158 //           "variablesReference": {
159 //             "type": "number",
160 //             "description": "If an attribute 'variablesReference' exists
161 //                             and its value is > 0, the output contains
162 //                             objects which can be retrieved by passing
163 //                             variablesReference to the VariablesRequest."
164 //           },
165 //           "source": {
166 //             "$ref": "#/definitions/Source",
167 //             "description": "An optional source location where the output
168 //                             was produced."
169 //           },
170 //           "line": {
171 //             "type": "integer",
172 //             "description": "An optional source location line where the
173 //                             output was produced."
174 //           },
175 //           "column": {
176 //             "type": "integer",
177 //             "description": "An optional source location column where the
178 //                             output was produced."
179 //           },
180 //           "data": {
181 //             "type":["array","boolean","integer","null","number","object",
182 //                     "string"],
183 //             "description": "Optional data to report. For the 'telemetry'
184 //                             category the data will be sent to telemetry, for
185 //                             the other categories the data is shown in JSON
186 //                             format."
187 //           }
188 //         },
189 //         "required": ["output"]
190 //       }
191 //     },
192 //     "required": [ "event", "body" ]
193 //   }]
194 // }
195 void VSCode::SendOutput(OutputType o, const llvm::StringRef output) {
196   if (output.empty())
197     return;
198 
199   llvm::json::Object event(CreateEventObject("output"));
200   llvm::json::Object body;
201   const char *category = nullptr;
202   switch (o) {
203   case OutputType::Console:
204     category = "console";
205     break;
206   case OutputType::Stdout:
207     category = "stdout";
208     break;
209   case OutputType::Stderr:
210     category = "stderr";
211     break;
212   case OutputType::Telemetry:
213     category = "telemetry";
214     break;
215   }
216   body.try_emplace("category", category);
217   EmplaceSafeString(body, "output", output.str());
218   event.try_emplace("body", std::move(body));
219   SendJSON(llvm::json::Value(std::move(event)));
220 }
221 
222 void __attribute__((format(printf, 3, 4)))
223 VSCode::SendFormattedOutput(OutputType o, const char *format, ...) {
224   char buffer[1024];
225   va_list args;
226   va_start(args, format);
227   int actual_length = vsnprintf(buffer, sizeof(buffer), format, args);
228   va_end(args);
229   SendOutput(o, llvm::StringRef(buffer,
230                                 std::min<int>(actual_length, sizeof(buffer))));
231 }
232 
233 int64_t VSCode::GetNextSourceReference() {
234   static int64_t ref = 0;
235   return ++ref;
236 }
237 
238 ExceptionBreakpoint *
239 VSCode::GetExceptionBPFromStopReason(lldb::SBThread &thread) {
240   const auto num = thread.GetStopReasonDataCount();
241   // Check to see if have hit an exception breakpoint and change the
242   // reason to "exception", but only do so if all breakpoints that were
243   // hit are exception breakpoints.
244   ExceptionBreakpoint *exc_bp = nullptr;
245   for (size_t i = 0; i < num; i += 2) {
246     // thread.GetStopReasonDataAtIndex(i) will return the bp ID and
247     // thread.GetStopReasonDataAtIndex(i+1) will return the location
248     // within that breakpoint. We only care about the bp ID so we can
249     // see if this is an exception breakpoint that is getting hit.
250     lldb::break_id_t bp_id = thread.GetStopReasonDataAtIndex(i);
251     exc_bp = GetExceptionBreakpoint(bp_id);
252     // If any breakpoint is not an exception breakpoint, then stop and
253     // report this as a normal breakpoint
254     if (exc_bp == nullptr)
255       return nullptr;
256   }
257   return exc_bp;
258 }
259 
260 lldb::SBThread VSCode::GetLLDBThread(const llvm::json::Object &arguments) {
261   auto tid = GetSigned(arguments, "threadId", LLDB_INVALID_THREAD_ID);
262   return target.GetProcess().GetThreadByID(tid);
263 }
264 
265 lldb::SBFrame VSCode::GetLLDBFrame(const llvm::json::Object &arguments) {
266   const uint64_t frame_id = GetUnsigned(arguments, "frameId", UINT64_MAX);
267   lldb::SBProcess process = target.GetProcess();
268   // Upper 32 bits is the thread index ID
269   lldb::SBThread thread =
270       process.GetThreadByIndexID(GetLLDBThreadIndexID(frame_id));
271   // Lower 32 bits is the frame index
272   return thread.GetFrameAtIndex(GetLLDBFrameID(frame_id));
273 }
274 
275 llvm::json::Value VSCode::CreateTopLevelScopes() {
276   llvm::json::Array scopes;
277   scopes.emplace_back(CreateScope("Locals", VARREF_LOCALS, num_locals, false));
278   scopes.emplace_back(
279       CreateScope("Globals", VARREF_GLOBALS, num_globals, false));
280   scopes.emplace_back(CreateScope("Registers", VARREF_REGS, num_regs, false));
281   return llvm::json::Value(std::move(scopes));
282 }
283 
284 void VSCode::RunLLDBCommands(llvm::StringRef prefix,
285                              const std::vector<std::string> &commands) {
286   SendOutput(OutputType::Console,
287              llvm::StringRef(::RunLLDBCommands(prefix, commands)));
288 }
289 
290 void VSCode::RunInitCommands() {
291   RunLLDBCommands("Running initCommands:", init_commands);
292 }
293 
294 void VSCode::RunPreRunCommands() {
295   RunLLDBCommands("Running preRunCommands:", pre_run_commands);
296 }
297 
298 void VSCode::RunStopCommands() {
299   RunLLDBCommands("Running stopCommands:", stop_commands);
300 }
301 
302 void VSCode::RunExitCommands() {
303   RunLLDBCommands("Running exitCommands:", exit_commands);
304 }
305 
306 } // namespace lldb_vscode
307