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