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 
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   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 int64_t VSCode::GetLineForPC(int64_t sourceReference, lldb::addr_t pc) const {
60   auto pos = source_map.find(sourceReference);
61   if (pos != source_map.end())
62     return pos->second.GetLineForPC(pc);
63   return 0;
64 }
65 
66 ExceptionBreakpoint *VSCode::GetExceptionBreakpoint(const std::string &filter) {
67   for (auto &bp : exception_breakpoints) {
68     if (bp.filter == filter)
69       return &bp;
70   }
71   return nullptr;
72 }
73 
74 ExceptionBreakpoint *
75 VSCode::GetExceptionBreakpoint(const lldb::break_id_t bp_id) {
76   for (auto &bp : exception_breakpoints) {
77     if (bp.bp.GetID() == bp_id)
78       return &bp;
79   }
80   return nullptr;
81 }
82 
83 // Send the JSON in "json_str" to the "out" stream. Correctly send the
84 // "Content-Length:" field followed by the length, followed by the raw
85 // JSON bytes.
86 void VSCode::SendJSON(const std::string &json_str) {
87   output.write_full("Content-Length: ");
88   output.write_full(llvm::utostr(json_str.size()));
89   output.write_full("\r\n\r\n");
90   output.write_full(json_str);
91 
92   if (log) {
93     *log << "<-- " << std::endl
94          << "Content-Length: " << json_str.size() << "\r\n\r\n"
95          << json_str << std::endl;
96   }
97 }
98 
99 // Serialize the JSON value into a string and send the JSON packet to
100 // the "out" stream.
101 void VSCode::SendJSON(const llvm::json::Value &json) {
102   std::string s;
103   llvm::raw_string_ostream strm(s);
104   strm << json;
105   static std::mutex mutex;
106   std::lock_guard<std::mutex> locker(mutex);
107   SendJSON(strm.str());
108 }
109 
110 // Read a JSON packet from the "in" stream.
111 std::string VSCode::ReadJSON() {
112   std::string length_str;
113   std::string json_str;
114   int length;
115 
116   if (!input.read_expected(log.get(), "Content-Length: "))
117     return json_str;
118 
119   if (!input.read_line(log.get(), length_str))
120     return json_str;
121 
122   if (!llvm::to_integer(length_str, length))
123     return json_str;
124 
125   if (!input.read_expected(log.get(), "\r\n"))
126     return json_str;
127 
128   if (!input.read_full(log.get(), length, json_str))
129     return json_str;
130 
131   if (log) {
132     *log << "--> " << std::endl
133          << "Content-Length: " << length << "\r\n\r\n"
134          << json_str << std::endl;
135   }
136 
137   return json_str;
138 }
139 
140 // "OutputEvent": {
141 //   "allOf": [ { "$ref": "#/definitions/Event" }, {
142 //     "type": "object",
143 //     "description": "Event message for 'output' event type. The event
144 //                     indicates that the target has produced some output.",
145 //     "properties": {
146 //       "event": {
147 //         "type": "string",
148 //         "enum": [ "output" ]
149 //       },
150 //       "body": {
151 //         "type": "object",
152 //         "properties": {
153 //           "category": {
154 //             "type": "string",
155 //             "description": "The output category. If not specified,
156 //                             'console' is assumed.",
157 //             "_enum": [ "console", "stdout", "stderr", "telemetry" ]
158 //           },
159 //           "output": {
160 //             "type": "string",
161 //             "description": "The output to report."
162 //           },
163 //           "variablesReference": {
164 //             "type": "number",
165 //             "description": "If an attribute 'variablesReference' exists
166 //                             and its value is > 0, the output contains
167 //                             objects which can be retrieved by passing
168 //                             variablesReference to the VariablesRequest."
169 //           },
170 //           "source": {
171 //             "$ref": "#/definitions/Source",
172 //             "description": "An optional source location where the output
173 //                             was produced."
174 //           },
175 //           "line": {
176 //             "type": "integer",
177 //             "description": "An optional source location line where the
178 //                             output was produced."
179 //           },
180 //           "column": {
181 //             "type": "integer",
182 //             "description": "An optional source location column where the
183 //                             output was produced."
184 //           },
185 //           "data": {
186 //             "type":["array","boolean","integer","null","number","object",
187 //                     "string"],
188 //             "description": "Optional data to report. For the 'telemetry'
189 //                             category the data will be sent to telemetry, for
190 //                             the other categories the data is shown in JSON
191 //                             format."
192 //           }
193 //         },
194 //         "required": ["output"]
195 //       }
196 //     },
197 //     "required": [ "event", "body" ]
198 //   }]
199 // }
200 void VSCode::SendOutput(OutputType o, const llvm::StringRef output) {
201   if (output.empty())
202     return;
203 
204   llvm::json::Object event(CreateEventObject("output"));
205   llvm::json::Object body;
206   const char *category = nullptr;
207   switch (o) {
208   case OutputType::Console:
209     category = "console";
210     break;
211   case OutputType::Stdout:
212     category = "stdout";
213     break;
214   case OutputType::Stderr:
215     category = "stderr";
216     break;
217   case OutputType::Telemetry:
218     category = "telemetry";
219     break;
220   }
221   body.try_emplace("category", category);
222   EmplaceSafeString(body, "output", output.str());
223   event.try_emplace("body", std::move(body));
224   SendJSON(llvm::json::Value(std::move(event)));
225 }
226 
227 void __attribute__((format(printf, 3, 4)))
228 VSCode::SendFormattedOutput(OutputType o, const char *format, ...) {
229   char buffer[1024];
230   va_list args;
231   va_start(args, format);
232   int actual_length = vsnprintf(buffer, sizeof(buffer), format, args);
233   va_end(args);
234   SendOutput(
235       o, llvm::StringRef(buffer, std::min<int>(actual_length, sizeof(buffer))));
236 }
237 
238 int64_t VSCode::GetNextSourceReference() {
239   static int64_t ref = 0;
240   return ++ref;
241 }
242 
243 ExceptionBreakpoint *
244 VSCode::GetExceptionBPFromStopReason(lldb::SBThread &thread) {
245   const auto num = thread.GetStopReasonDataCount();
246   // Check to see if have hit an exception breakpoint and change the
247   // reason to "exception", but only do so if all breakpoints that were
248   // hit are exception breakpoints.
249   ExceptionBreakpoint *exc_bp = nullptr;
250   for (size_t i = 0; i < num; i += 2) {
251     // thread.GetStopReasonDataAtIndex(i) will return the bp ID and
252     // thread.GetStopReasonDataAtIndex(i+1) will return the location
253     // within that breakpoint. We only care about the bp ID so we can
254     // see if this is an exception breakpoint that is getting hit.
255     lldb::break_id_t bp_id = thread.GetStopReasonDataAtIndex(i);
256     exc_bp = GetExceptionBreakpoint(bp_id);
257     // If any breakpoint is not an exception breakpoint, then stop and
258     // report this as a normal breakpoint
259     if (exc_bp == nullptr)
260       return nullptr;
261   }
262   return exc_bp;
263 }
264 
265 lldb::SBThread VSCode::GetLLDBThread(const llvm::json::Object &arguments) {
266   auto tid = GetSigned(arguments, "threadId", LLDB_INVALID_THREAD_ID);
267   return target.GetProcess().GetThreadByID(tid);
268 }
269 
270 lldb::SBFrame VSCode::GetLLDBFrame(const llvm::json::Object &arguments) {
271   const uint64_t frame_id = GetUnsigned(arguments, "frameId", UINT64_MAX);
272   lldb::SBProcess process = target.GetProcess();
273   // Upper 32 bits is the thread index ID
274   lldb::SBThread thread =
275       process.GetThreadByIndexID(GetLLDBThreadIndexID(frame_id));
276   // Lower 32 bits is the frame index
277   return thread.GetFrameAtIndex(GetLLDBFrameID(frame_id));
278 }
279 
280 llvm::json::Value VSCode::CreateTopLevelScopes() {
281   llvm::json::Array scopes;
282   scopes.emplace_back(CreateScope("Locals", VARREF_LOCALS, num_locals, false));
283   scopes.emplace_back(
284       CreateScope("Globals", VARREF_GLOBALS, num_globals, false));
285   scopes.emplace_back(CreateScope("Registers", VARREF_REGS, num_regs, false));
286   return llvm::json::Value(std::move(scopes));
287 }
288 
289 void VSCode::RunLLDBCommands(llvm::StringRef prefix,
290                              const std::vector<std::string> &commands) {
291   SendOutput(OutputType::Console,
292              llvm::StringRef(::RunLLDBCommands(prefix, commands)));
293 }
294 
295 void VSCode::RunInitCommands() {
296   RunLLDBCommands("Running initCommands:", init_commands);
297 }
298 
299 void VSCode::RunPreRunCommands() {
300   RunLLDBCommands("Running preRunCommands:", pre_run_commands);
301 }
302 
303 void VSCode::RunStopCommands() {
304   RunLLDBCommands("Running stopCommands:", stop_commands);
305 }
306 
307 void VSCode::RunExitCommands() {
308   RunLLDBCommands("Running exitCommands:", exit_commands);
309 }
310 
311 void VSCode::RunTerminateCommands() {
312   RunLLDBCommands("Running terminateCommands:", terminate_commands);
313 }
314 
315 lldb::SBTarget
316 VSCode::CreateTargetFromArguments(const llvm::json::Object &arguments,
317                                   lldb::SBError &error) {
318   // Grab the name of the program we need to debug and create a target using
319   // the given program as an argument. Executable file can be a source of target
320   // architecture and platform, if they differ from the host. Setting exe path
321   // in launch info is useless because Target.Launch() will not change
322   // architecture and platform, therefore they should be known at the target
323   // creation. We also use target triple and platform from the launch
324   // configuration, if given, since in some cases ELF file doesn't contain
325   // enough information to determine correct arch and platform (or ELF can be
326   // omitted at all), so it is good to leave the user an apportunity to specify
327   // those. Any of those three can be left empty.
328   llvm::StringRef target_triple = GetString(arguments, "targetTriple");
329   llvm::StringRef platform_name = GetString(arguments, "platformName");
330   llvm::StringRef program = GetString(arguments, "program");
331   auto target = this->debugger.CreateTarget(
332       program.data(), target_triple.data(), platform_name.data(),
333       true, // Add dependent modules.
334       error);
335 
336   if (error.Fail()) {
337     // Update message if there was an error.
338     error.SetErrorStringWithFormat(
339         "Could not create a target for a program '%s': %s.", program.data(),
340         error.GetCString());
341   }
342 
343   return target;
344 }
345 
346 void VSCode::SetTarget(const lldb::SBTarget target) {
347   this->target = target;
348 
349   if (target.IsValid()) {
350     // Configure breakpoint event listeners for the target.
351     lldb::SBListener listener = this->debugger.GetListener();
352     listener.StartListeningForEvents(
353         this->target.GetBroadcaster(),
354         lldb::SBTarget::eBroadcastBitBreakpointChanged);
355     listener.StartListeningForEvents(this->broadcaster,
356                                      eBroadcastBitStopEventThread);
357     listener.StartListeningForEvents(
358         this->target.GetBroadcaster(),
359         lldb::SBTarget::eBroadcastBitModulesLoaded |
360             lldb::SBTarget::eBroadcastBitModulesUnloaded |
361             lldb::SBTarget::eBroadcastBitSymbolsLoaded);
362   }
363 }
364 
365 } // namespace lldb_vscode
366