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 <fstream> 10 #include <mutex> 11 #include <stdarg.h> 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 <fcntl.h> 20 #include <io.h> 21 #include <windows.h> 22 #endif 23 24 using namespace lldb_vscode; 25 26 namespace lldb_vscode { 27 28 VSCode g_vsc; 29 30 VSCode::VSCode() 31 : variables(), broadcaster("lldb-vscode"), num_regs(0), num_locals(0), 32 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), is_attach(false), 42 reverse_request_seq(0), waiting_for_run_in_terminal(false) { 43 const char *log_file_path = getenv("LLDBVSCODE_LOG"); 44 #if defined(_WIN32) 45 // Windows opens stdout and stdin in text mode which converts \n to 13,10 46 // while the value is just 10 on Darwin/Linux. Setting the file mode to binary 47 // fixes this. 48 int result = _setmode(fileno(stdout), _O_BINARY); 49 assert(result); 50 result = _setmode(fileno(stdin), _O_BINARY); 51 (void)result; 52 assert(result); 53 #endif 54 if (log_file_path) 55 log.reset(new std::ofstream(log_file_path)); 56 } 57 58 VSCode::~VSCode() {} 59 60 int64_t VSCode::GetLineForPC(int64_t sourceReference, lldb::addr_t pc) const { 61 auto pos = source_map.find(sourceReference); 62 if (pos != source_map.end()) 63 return pos->second.GetLineForPC(pc); 64 return 0; 65 } 66 67 ExceptionBreakpoint *VSCode::GetExceptionBreakpoint(const std::string &filter) { 68 for (auto &bp : exception_breakpoints) { 69 if (bp.filter == filter) 70 return &bp; 71 } 72 return nullptr; 73 } 74 75 ExceptionBreakpoint * 76 VSCode::GetExceptionBreakpoint(const lldb::break_id_t bp_id) { 77 for (auto &bp : exception_breakpoints) { 78 if (bp.bp.GetID() == bp_id) 79 return &bp; 80 } 81 return nullptr; 82 } 83 84 // Send the JSON in "json_str" to the "out" stream. Correctly send the 85 // "Content-Length:" field followed by the length, followed by the raw 86 // JSON bytes. 87 void VSCode::SendJSON(const std::string &json_str) { 88 output.write_full("Content-Length: "); 89 output.write_full(llvm::utostr(json_str.size())); 90 output.write_full("\r\n\r\n"); 91 output.write_full(json_str); 92 93 if (log) { 94 *log << "<-- " << std::endl 95 << "Content-Length: " << json_str.size() << "\r\n\r\n" 96 << json_str << std::endl; 97 } 98 } 99 100 // Serialize the JSON value into a string and send the JSON packet to 101 // the "out" stream. 102 void VSCode::SendJSON(const llvm::json::Value &json) { 103 std::string s; 104 llvm::raw_string_ostream strm(s); 105 strm << json; 106 static std::mutex mutex; 107 std::lock_guard<std::mutex> locker(mutex); 108 SendJSON(strm.str()); 109 } 110 111 // Read a JSON packet from the "in" stream. 112 std::string VSCode::ReadJSON() { 113 std::string length_str; 114 std::string json_str; 115 int length; 116 117 if (!input.read_expected(log.get(), "Content-Length: ")) 118 return json_str; 119 120 if (!input.read_line(log.get(), length_str)) 121 return json_str; 122 123 if (!llvm::to_integer(length_str, length)) 124 return json_str; 125 126 if (!input.read_expected(log.get(), "\r\n")) 127 return json_str; 128 129 if (!input.read_full(log.get(), length, json_str)) 130 return json_str; 131 132 if (log) { 133 *log << "--> " << std::endl 134 << "Content-Length: " << length << "\r\n\r\n" 135 << json_str << std::endl; 136 } 137 138 return json_str; 139 } 140 141 // "OutputEvent": { 142 // "allOf": [ { "$ref": "#/definitions/Event" }, { 143 // "type": "object", 144 // "description": "Event message for 'output' event type. The event 145 // indicates that the target has produced some output.", 146 // "properties": { 147 // "event": { 148 // "type": "string", 149 // "enum": [ "output" ] 150 // }, 151 // "body": { 152 // "type": "object", 153 // "properties": { 154 // "category": { 155 // "type": "string", 156 // "description": "The output category. If not specified, 157 // 'console' is assumed.", 158 // "_enum": [ "console", "stdout", "stderr", "telemetry" ] 159 // }, 160 // "output": { 161 // "type": "string", 162 // "description": "The output to report." 163 // }, 164 // "variablesReference": { 165 // "type": "number", 166 // "description": "If an attribute 'variablesReference' exists 167 // and its value is > 0, the output contains 168 // objects which can be retrieved by passing 169 // variablesReference to the VariablesRequest." 170 // }, 171 // "source": { 172 // "$ref": "#/definitions/Source", 173 // "description": "An optional source location where the output 174 // was produced." 175 // }, 176 // "line": { 177 // "type": "integer", 178 // "description": "An optional source location line where the 179 // output was produced." 180 // }, 181 // "column": { 182 // "type": "integer", 183 // "description": "An optional source location column where the 184 // output was produced." 185 // }, 186 // "data": { 187 // "type":["array","boolean","integer","null","number","object", 188 // "string"], 189 // "description": "Optional data to report. For the 'telemetry' 190 // category the data will be sent to telemetry, for 191 // the other categories the data is shown in JSON 192 // format." 193 // } 194 // }, 195 // "required": ["output"] 196 // } 197 // }, 198 // "required": [ "event", "body" ] 199 // }] 200 // } 201 void VSCode::SendOutput(OutputType o, const llvm::StringRef output) { 202 if (output.empty()) 203 return; 204 205 llvm::json::Object event(CreateEventObject("output")); 206 llvm::json::Object body; 207 const char *category = nullptr; 208 switch (o) { 209 case OutputType::Console: 210 category = "console"; 211 break; 212 case OutputType::Stdout: 213 category = "stdout"; 214 break; 215 case OutputType::Stderr: 216 category = "stderr"; 217 break; 218 case OutputType::Telemetry: 219 category = "telemetry"; 220 break; 221 } 222 body.try_emplace("category", category); 223 EmplaceSafeString(body, "output", output.str()); 224 event.try_emplace("body", std::move(body)); 225 SendJSON(llvm::json::Value(std::move(event))); 226 } 227 228 void __attribute__((format(printf, 3, 4))) 229 VSCode::SendFormattedOutput(OutputType o, const char *format, ...) { 230 char buffer[1024]; 231 va_list args; 232 va_start(args, format); 233 int actual_length = vsnprintf(buffer, sizeof(buffer), format, args); 234 va_end(args); 235 SendOutput( 236 o, llvm::StringRef(buffer, std::min<int>(actual_length, sizeof(buffer)))); 237 } 238 239 int64_t VSCode::GetNextSourceReference() { 240 static int64_t ref = 0; 241 return ++ref; 242 } 243 244 ExceptionBreakpoint * 245 VSCode::GetExceptionBPFromStopReason(lldb::SBThread &thread) { 246 const auto num = thread.GetStopReasonDataCount(); 247 // Check to see if have hit an exception breakpoint and change the 248 // reason to "exception", but only do so if all breakpoints that were 249 // hit are exception breakpoints. 250 ExceptionBreakpoint *exc_bp = nullptr; 251 for (size_t i = 0; i < num; i += 2) { 252 // thread.GetStopReasonDataAtIndex(i) will return the bp ID and 253 // thread.GetStopReasonDataAtIndex(i+1) will return the location 254 // within that breakpoint. We only care about the bp ID so we can 255 // see if this is an exception breakpoint that is getting hit. 256 lldb::break_id_t bp_id = thread.GetStopReasonDataAtIndex(i); 257 exc_bp = GetExceptionBreakpoint(bp_id); 258 // If any breakpoint is not an exception breakpoint, then stop and 259 // report this as a normal breakpoint 260 if (exc_bp == nullptr) 261 return nullptr; 262 } 263 return exc_bp; 264 } 265 266 lldb::SBThread VSCode::GetLLDBThread(const llvm::json::Object &arguments) { 267 auto tid = GetSigned(arguments, "threadId", LLDB_INVALID_THREAD_ID); 268 return target.GetProcess().GetThreadByID(tid); 269 } 270 271 lldb::SBFrame VSCode::GetLLDBFrame(const llvm::json::Object &arguments) { 272 const uint64_t frame_id = GetUnsigned(arguments, "frameId", UINT64_MAX); 273 lldb::SBProcess process = target.GetProcess(); 274 // Upper 32 bits is the thread index ID 275 lldb::SBThread thread = 276 process.GetThreadByIndexID(GetLLDBThreadIndexID(frame_id)); 277 // Lower 32 bits is the frame index 278 return thread.GetFrameAtIndex(GetLLDBFrameID(frame_id)); 279 } 280 281 llvm::json::Value VSCode::CreateTopLevelScopes() { 282 llvm::json::Array scopes; 283 scopes.emplace_back(CreateScope("Locals", VARREF_LOCALS, num_locals, false)); 284 scopes.emplace_back( 285 CreateScope("Globals", VARREF_GLOBALS, num_globals, false)); 286 scopes.emplace_back(CreateScope("Registers", VARREF_REGS, num_regs, false)); 287 return llvm::json::Value(std::move(scopes)); 288 } 289 290 void VSCode::RunLLDBCommands(llvm::StringRef prefix, 291 const std::vector<std::string> &commands) { 292 SendOutput(OutputType::Console, 293 llvm::StringRef(::RunLLDBCommands(prefix, commands))); 294 } 295 296 void VSCode::RunInitCommands() { 297 RunLLDBCommands("Running initCommands:", init_commands); 298 } 299 300 void VSCode::RunPreRunCommands() { 301 RunLLDBCommands("Running preRunCommands:", pre_run_commands); 302 } 303 304 void VSCode::RunStopCommands() { 305 RunLLDBCommands("Running stopCommands:", stop_commands); 306 } 307 308 void VSCode::RunExitCommands() { 309 RunLLDBCommands("Running exitCommands:", exit_commands); 310 } 311 312 void VSCode::RunTerminateCommands() { 313 RunLLDBCommands("Running terminateCommands:", terminate_commands); 314 } 315 316 lldb::SBTarget 317 VSCode::CreateTargetFromArguments(const llvm::json::Object &arguments, 318 lldb::SBError &error) { 319 // Grab the name of the program we need to debug and create a target using 320 // the given program as an argument. Executable file can be a source of target 321 // architecture and platform, if they differ from the host. Setting exe path 322 // in launch info is useless because Target.Launch() will not change 323 // architecture and platform, therefore they should be known at the target 324 // creation. We also use target triple and platform from the launch 325 // configuration, if given, since in some cases ELF file doesn't contain 326 // enough information to determine correct arch and platform (or ELF can be 327 // omitted at all), so it is good to leave the user an apportunity to specify 328 // those. Any of those three can be left empty. 329 llvm::StringRef target_triple = GetString(arguments, "targetTriple"); 330 llvm::StringRef platform_name = GetString(arguments, "platformName"); 331 llvm::StringRef program = GetString(arguments, "program"); 332 auto target = this->debugger.CreateTarget( 333 program.data(), target_triple.data(), platform_name.data(), 334 true, // Add dependent modules. 335 error); 336 337 if (error.Fail()) { 338 // Update message if there was an error. 339 error.SetErrorStringWithFormat( 340 "Could not create a target for a program '%s': %s.", program.data(), 341 error.GetCString()); 342 } 343 344 return target; 345 } 346 347 void VSCode::SetTarget(const lldb::SBTarget target) { 348 this->target = target; 349 350 if (target.IsValid()) { 351 // Configure breakpoint event listeners for the target. 352 lldb::SBListener listener = this->debugger.GetListener(); 353 listener.StartListeningForEvents( 354 this->target.GetBroadcaster(), 355 lldb::SBTarget::eBroadcastBitBreakpointChanged); 356 listener.StartListeningForEvents(this->broadcaster, 357 eBroadcastBitStopEventThread); 358 listener.StartListeningForEvents( 359 this->target.GetBroadcaster(), 360 lldb::SBTarget::eBroadcastBitModulesLoaded | 361 lldb::SBTarget::eBroadcastBitModulesUnloaded | 362 lldb::SBTarget::eBroadcastBitSymbolsLoaded); 363 } 364 } 365 366 PacketStatus VSCode::GetNextObject(llvm::json::Object &object) { 367 std::string json = ReadJSON(); 368 if (json.empty()) 369 return PacketStatus::EndOfFile; 370 371 llvm::StringRef json_sref(json); 372 llvm::Expected<llvm::json::Value> json_value = llvm::json::parse(json_sref); 373 if (!json_value) { 374 auto error = json_value.takeError(); 375 if (log) { 376 std::string error_str; 377 llvm::raw_string_ostream strm(error_str); 378 strm << error; 379 strm.flush(); 380 *log << "error: failed to parse JSON: " << error_str << std::endl 381 << json << std::endl; 382 } 383 return PacketStatus::JSONMalformed; 384 } 385 object = *json_value->getAsObject(); 386 if (!json_value->getAsObject()) { 387 if (log) 388 *log << "error: json packet isn't a object" << std::endl; 389 return PacketStatus::JSONNotObject; 390 } 391 return PacketStatus::Success; 392 } 393 394 bool VSCode::HandleObject(const llvm::json::Object &object) { 395 const auto packet_type = GetString(object, "type"); 396 if (packet_type == "request") { 397 const auto command = GetString(object, "command"); 398 auto handler_pos = request_handlers.find(std::string(command)); 399 if (handler_pos != request_handlers.end()) { 400 handler_pos->second(object); 401 return true; // Success 402 } else { 403 if (log) 404 *log << "error: unhandled command \"" << command.data() << std::endl; 405 return false; // Fail 406 } 407 } 408 return false; 409 } 410 411 PacketStatus VSCode::SendReverseRequest(llvm::json::Object request, 412 llvm::json::Object &response) { 413 request.try_emplace("seq", ++reverse_request_seq); 414 SendJSON(llvm::json::Value(std::move(request))); 415 while (true) { 416 PacketStatus status = GetNextObject(response); 417 const auto packet_type = GetString(response, "type"); 418 if (packet_type == "response") 419 return status; 420 else { 421 // Not our response, we got another packet 422 HandleObject(response); 423 } 424 } 425 return PacketStatus::EndOfFile; 426 } 427 428 void VSCode::RegisterRequestCallback(std::string request, 429 RequestCallback callback) { 430 request_handlers[request] = callback; 431 } 432 433 } // namespace lldb_vscode 434