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