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