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