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