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 <chrono> 10 #include <fstream> 11 #include <mutex> 12 #include <sstream> 13 #include <stdarg.h> 14 15 #include "LLDBUtils.h" 16 #include "VSCode.h" 17 #include "llvm/Support/FormatVariadic.h" 18 19 #if defined(_WIN32) 20 #define NOMINMAX 21 #include <fcntl.h> 22 #include <io.h> 23 #include <windows.h> 24 #endif 25 26 using namespace lldb_vscode; 27 28 namespace lldb_vscode { 29 30 VSCode g_vsc; 31 32 VSCode::VSCode() 33 : variables(), broadcaster("lldb-vscode"), num_regs(0), num_locals(0), 34 num_globals(0), log(), 35 exception_breakpoints( 36 {{"cpp_catch", "C++ Catch", lldb::eLanguageTypeC_plus_plus}, 37 {"cpp_throw", "C++ Throw", lldb::eLanguageTypeC_plus_plus}, 38 {"objc_catch", "Objective C Catch", lldb::eLanguageTypeObjC}, 39 {"objc_throw", "Objective C Throw", lldb::eLanguageTypeObjC}, 40 {"swift_catch", "Swift Catch", lldb::eLanguageTypeSwift}, 41 {"swift_throw", "Swift Throw", lldb::eLanguageTypeSwift}}), 42 focus_tid(LLDB_INVALID_THREAD_ID), sent_terminated_event(false), 43 stop_at_entry(false), is_attach(false), 44 reverse_request_seq(0), waiting_for_run_in_terminal(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 int result = _setmode(fileno(stdout), _O_BINARY); 51 assert(result); 52 result = _setmode(fileno(stdin), _O_BINARY); 53 (void)result; 54 assert(result); 55 #endif 56 if (log_file_path) 57 log.reset(new std::ofstream(log_file_path)); 58 } 59 60 VSCode::~VSCode() {} 61 62 int64_t VSCode::GetLineForPC(int64_t sourceReference, lldb::addr_t pc) const { 63 auto pos = source_map.find(sourceReference); 64 if (pos != source_map.end()) 65 return pos->second.GetLineForPC(pc); 66 return 0; 67 } 68 69 ExceptionBreakpoint *VSCode::GetExceptionBreakpoint(const std::string &filter) { 70 for (auto &bp : exception_breakpoints) { 71 if (bp.filter == filter) 72 return &bp; 73 } 74 return nullptr; 75 } 76 77 ExceptionBreakpoint * 78 VSCode::GetExceptionBreakpoint(const lldb::break_id_t bp_id) { 79 for (auto &bp : exception_breakpoints) { 80 if (bp.bp.GetID() == bp_id) 81 return &bp; 82 } 83 return nullptr; 84 } 85 86 // Send the JSON in "json_str" to the "out" stream. Correctly send the 87 // "Content-Length:" field followed by the length, followed by the raw 88 // JSON bytes. 89 void VSCode::SendJSON(const std::string &json_str) { 90 output.write_full("Content-Length: "); 91 output.write_full(llvm::utostr(json_str.size())); 92 output.write_full("\r\n\r\n"); 93 output.write_full(json_str); 94 95 if (log) { 96 *log << "<-- " << std::endl 97 << "Content-Length: " << json_str.size() << "\r\n\r\n" 98 << json_str << std::endl; 99 } 100 } 101 102 // Serialize the JSON value into a string and send the JSON packet to 103 // the "out" stream. 104 void VSCode::SendJSON(const llvm::json::Value &json) { 105 std::string s; 106 llvm::raw_string_ostream strm(s); 107 strm << json; 108 static std::mutex mutex; 109 std::lock_guard<std::mutex> locker(mutex); 110 SendJSON(strm.str()); 111 } 112 113 // Read a JSON packet from the "in" stream. 114 std::string VSCode::ReadJSON() { 115 std::string length_str; 116 std::string json_str; 117 int length; 118 119 if (!input.read_expected(log.get(), "Content-Length: ")) 120 return json_str; 121 122 if (!input.read_line(log.get(), length_str)) 123 return json_str; 124 125 if (!llvm::to_integer(length_str, length)) 126 return json_str; 127 128 if (!input.read_expected(log.get(), "\r\n")) 129 return json_str; 130 131 if (!input.read_full(log.get(), length, json_str)) 132 return json_str; 133 134 if (log) { 135 *log << "--> " << std::endl 136 << "Content-Length: " << length << "\r\n\r\n" 137 << json_str << std::endl; 138 } 139 140 return json_str; 141 } 142 143 // "OutputEvent": { 144 // "allOf": [ { "$ref": "#/definitions/Event" }, { 145 // "type": "object", 146 // "description": "Event message for 'output' event type. The event 147 // indicates that the target has produced some output.", 148 // "properties": { 149 // "event": { 150 // "type": "string", 151 // "enum": [ "output" ] 152 // }, 153 // "body": { 154 // "type": "object", 155 // "properties": { 156 // "category": { 157 // "type": "string", 158 // "description": "The output category. If not specified, 159 // 'console' is assumed.", 160 // "_enum": [ "console", "stdout", "stderr", "telemetry" ] 161 // }, 162 // "output": { 163 // "type": "string", 164 // "description": "The output to report." 165 // }, 166 // "variablesReference": { 167 // "type": "number", 168 // "description": "If an attribute 'variablesReference' exists 169 // and its value is > 0, the output contains 170 // objects which can be retrieved by passing 171 // variablesReference to the VariablesRequest." 172 // }, 173 // "source": { 174 // "$ref": "#/definitions/Source", 175 // "description": "An optional source location where the output 176 // was produced." 177 // }, 178 // "line": { 179 // "type": "integer", 180 // "description": "An optional source location line where the 181 // output was produced." 182 // }, 183 // "column": { 184 // "type": "integer", 185 // "description": "An optional source location column where the 186 // output was produced." 187 // }, 188 // "data": { 189 // "type":["array","boolean","integer","null","number","object", 190 // "string"], 191 // "description": "Optional data to report. For the 'telemetry' 192 // category the data will be sent to telemetry, for 193 // the other categories the data is shown in JSON 194 // format." 195 // } 196 // }, 197 // "required": ["output"] 198 // } 199 // }, 200 // "required": [ "event", "body" ] 201 // }] 202 // } 203 void VSCode::SendOutput(OutputType o, const llvm::StringRef output) { 204 if (output.empty()) 205 return; 206 207 llvm::json::Object event(CreateEventObject("output")); 208 llvm::json::Object body; 209 const char *category = nullptr; 210 switch (o) { 211 case OutputType::Console: 212 category = "console"; 213 break; 214 case OutputType::Stdout: 215 category = "stdout"; 216 break; 217 case OutputType::Stderr: 218 category = "stderr"; 219 break; 220 case OutputType::Telemetry: 221 category = "telemetry"; 222 break; 223 } 224 body.try_emplace("category", category); 225 EmplaceSafeString(body, "output", output.str()); 226 event.try_emplace("body", std::move(body)); 227 SendJSON(llvm::json::Value(std::move(event))); 228 } 229 230 // interface ProgressStartEvent extends Event { 231 // event: 'progressStart'; 232 // 233 // body: { 234 // /** 235 // * An ID that must be used in subsequent 'progressUpdate' and 236 // 'progressEnd' 237 // * events to make them refer to the same progress reporting. 238 // * IDs must be unique within a debug session. 239 // */ 240 // progressId: string; 241 // 242 // /** 243 // * Mandatory (short) title of the progress reporting. Shown in the UI to 244 // * describe the long running operation. 245 // */ 246 // title: string; 247 // 248 // /** 249 // * The request ID that this progress report is related to. If specified a 250 // * debug adapter is expected to emit 251 // * progress events for the long running request until the request has 252 // been 253 // * either completed or cancelled. 254 // * If the request ID is omitted, the progress report is assumed to be 255 // * related to some general activity of the debug adapter. 256 // */ 257 // requestId?: number; 258 // 259 // /** 260 // * If true, the request that reports progress may be canceled with a 261 // * 'cancel' request. 262 // * So this property basically controls whether the client should use UX 263 // that 264 // * supports cancellation. 265 // * Clients that don't support cancellation are allowed to ignore the 266 // * setting. 267 // */ 268 // cancellable?: boolean; 269 // 270 // /** 271 // * Optional, more detailed progress message. 272 // */ 273 // message?: string; 274 // 275 // /** 276 // * Optional progress percentage to display (value range: 0 to 100). If 277 // * omitted no percentage will be shown. 278 // */ 279 // percentage?: number; 280 // }; 281 // } 282 // 283 // interface ProgressUpdateEvent extends Event { 284 // event: 'progressUpdate'; 285 // 286 // body: { 287 // /** 288 // * The ID that was introduced in the initial 'progressStart' event. 289 // */ 290 // progressId: string; 291 // 292 // /** 293 // * Optional, more detailed progress message. If omitted, the previous 294 // * message (if any) is used. 295 // */ 296 // message?: string; 297 // 298 // /** 299 // * Optional progress percentage to display (value range: 0 to 100). If 300 // * omitted no percentage will be shown. 301 // */ 302 // percentage?: number; 303 // }; 304 // } 305 // 306 // interface ProgressEndEvent extends Event { 307 // event: 'progressEnd'; 308 // 309 // body: { 310 // /** 311 // * The ID that was introduced in the initial 'ProgressStartEvent'. 312 // */ 313 // progressId: string; 314 // 315 // /** 316 // * Optional, more detailed progress message. If omitted, the previous 317 // * message (if any) is used. 318 // */ 319 // message?: string; 320 // }; 321 // } 322 323 void VSCode::SendProgressEvent(uint64_t progress_id, const char *message, 324 uint64_t completed, uint64_t total) { 325 enum ProgressEventType { 326 progressInvalid, 327 progressStart, 328 progressUpdate, 329 progressEnd 330 }; 331 const char *event_name = nullptr; 332 ProgressEventType event_type = progressInvalid; 333 if (completed == 0) { 334 event_type = progressStart; 335 event_name = "progressStart"; 336 } else if (completed == total) { 337 event_type = progressEnd; 338 event_name = "progressEnd"; 339 } else if (completed < total) { 340 event_type = progressUpdate; 341 event_name = "progressUpdate"; 342 } 343 if (event_type == progressInvalid) 344 return; 345 346 llvm::json::Object event(CreateEventObject(event_name)); 347 llvm::json::Object body; 348 std::string progress_id_str; 349 llvm::raw_string_ostream progress_id_strm(progress_id_str); 350 progress_id_strm << progress_id; 351 progress_id_strm.flush(); 352 body.try_emplace("progressId", progress_id_str); 353 if (event_type == progressStart) { 354 EmplaceSafeString(body, "title", message); 355 body.try_emplace("cancellable", false); 356 } 357 auto now = std::chrono::duration<double>( 358 std::chrono::system_clock::now().time_since_epoch()); 359 std::string timestamp(llvm::formatv("{0:f9}", now.count())); 360 EmplaceSafeString(body, "timestamp", timestamp); 361 362 if (0 < total && total < UINT64_MAX) { 363 uint32_t percentage = (uint32_t)(((float)completed / (float)total) * 100.0); 364 body.try_emplace("percentage", percentage); 365 } 366 event.try_emplace("body", std::move(body)); 367 SendJSON(llvm::json::Value(std::move(event))); 368 } 369 370 void __attribute__((format(printf, 3, 4))) 371 VSCode::SendFormattedOutput(OutputType o, const char *format, ...) { 372 char buffer[1024]; 373 va_list args; 374 va_start(args, format); 375 int actual_length = vsnprintf(buffer, sizeof(buffer), format, args); 376 va_end(args); 377 SendOutput( 378 o, llvm::StringRef(buffer, std::min<int>(actual_length, sizeof(buffer)))); 379 } 380 381 int64_t VSCode::GetNextSourceReference() { 382 static int64_t ref = 0; 383 return ++ref; 384 } 385 386 ExceptionBreakpoint * 387 VSCode::GetExceptionBPFromStopReason(lldb::SBThread &thread) { 388 const auto num = thread.GetStopReasonDataCount(); 389 // Check to see if have hit an exception breakpoint and change the 390 // reason to "exception", but only do so if all breakpoints that were 391 // hit are exception breakpoints. 392 ExceptionBreakpoint *exc_bp = nullptr; 393 for (size_t i = 0; i < num; i += 2) { 394 // thread.GetStopReasonDataAtIndex(i) will return the bp ID and 395 // thread.GetStopReasonDataAtIndex(i+1) will return the location 396 // within that breakpoint. We only care about the bp ID so we can 397 // see if this is an exception breakpoint that is getting hit. 398 lldb::break_id_t bp_id = thread.GetStopReasonDataAtIndex(i); 399 exc_bp = GetExceptionBreakpoint(bp_id); 400 // If any breakpoint is not an exception breakpoint, then stop and 401 // report this as a normal breakpoint 402 if (exc_bp == nullptr) 403 return nullptr; 404 } 405 return exc_bp; 406 } 407 408 lldb::SBThread VSCode::GetLLDBThread(const llvm::json::Object &arguments) { 409 auto tid = GetSigned(arguments, "threadId", LLDB_INVALID_THREAD_ID); 410 return target.GetProcess().GetThreadByID(tid); 411 } 412 413 lldb::SBFrame VSCode::GetLLDBFrame(const llvm::json::Object &arguments) { 414 const uint64_t frame_id = GetUnsigned(arguments, "frameId", UINT64_MAX); 415 lldb::SBProcess process = target.GetProcess(); 416 // Upper 32 bits is the thread index ID 417 lldb::SBThread thread = 418 process.GetThreadByIndexID(GetLLDBThreadIndexID(frame_id)); 419 // Lower 32 bits is the frame index 420 return thread.GetFrameAtIndex(GetLLDBFrameID(frame_id)); 421 } 422 423 llvm::json::Value VSCode::CreateTopLevelScopes() { 424 llvm::json::Array scopes; 425 scopes.emplace_back(CreateScope("Locals", VARREF_LOCALS, num_locals, false)); 426 scopes.emplace_back( 427 CreateScope("Globals", VARREF_GLOBALS, num_globals, false)); 428 scopes.emplace_back(CreateScope("Registers", VARREF_REGS, num_regs, false)); 429 return llvm::json::Value(std::move(scopes)); 430 } 431 432 void VSCode::RunLLDBCommands(llvm::StringRef prefix, 433 const std::vector<std::string> &commands) { 434 SendOutput(OutputType::Console, 435 llvm::StringRef(::RunLLDBCommands(prefix, commands))); 436 } 437 438 void VSCode::RunInitCommands() { 439 RunLLDBCommands("Running initCommands:", init_commands); 440 } 441 442 void VSCode::RunPreRunCommands() { 443 RunLLDBCommands("Running preRunCommands:", pre_run_commands); 444 } 445 446 void VSCode::RunStopCommands() { 447 RunLLDBCommands("Running stopCommands:", stop_commands); 448 } 449 450 void VSCode::RunExitCommands() { 451 RunLLDBCommands("Running exitCommands:", exit_commands); 452 } 453 454 void VSCode::RunTerminateCommands() { 455 RunLLDBCommands("Running terminateCommands:", terminate_commands); 456 } 457 458 lldb::SBTarget 459 VSCode::CreateTargetFromArguments(const llvm::json::Object &arguments, 460 lldb::SBError &error) { 461 // Grab the name of the program we need to debug and create a target using 462 // the given program as an argument. Executable file can be a source of target 463 // architecture and platform, if they differ from the host. Setting exe path 464 // in launch info is useless because Target.Launch() will not change 465 // architecture and platform, therefore they should be known at the target 466 // creation. We also use target triple and platform from the launch 467 // configuration, if given, since in some cases ELF file doesn't contain 468 // enough information to determine correct arch and platform (or ELF can be 469 // omitted at all), so it is good to leave the user an apportunity to specify 470 // those. Any of those three can be left empty. 471 llvm::StringRef target_triple = GetString(arguments, "targetTriple"); 472 llvm::StringRef platform_name = GetString(arguments, "platformName"); 473 llvm::StringRef program = GetString(arguments, "program"); 474 auto target = this->debugger.CreateTarget( 475 program.data(), target_triple.data(), platform_name.data(), 476 true, // Add dependent modules. 477 error); 478 479 if (error.Fail()) { 480 // Update message if there was an error. 481 error.SetErrorStringWithFormat( 482 "Could not create a target for a program '%s': %s.", program.data(), 483 error.GetCString()); 484 } 485 486 return target; 487 } 488 489 void VSCode::SetTarget(const lldb::SBTarget target) { 490 this->target = target; 491 492 if (target.IsValid()) { 493 // Configure breakpoint event listeners for the target. 494 lldb::SBListener listener = this->debugger.GetListener(); 495 listener.StartListeningForEvents( 496 this->target.GetBroadcaster(), 497 lldb::SBTarget::eBroadcastBitBreakpointChanged); 498 listener.StartListeningForEvents(this->broadcaster, 499 eBroadcastBitStopEventThread); 500 } 501 } 502 503 PacketStatus VSCode::GetNextObject(llvm::json::Object &object) { 504 std::string json = ReadJSON(); 505 if (json.empty()) 506 return PacketStatus::EndOfFile; 507 508 llvm::StringRef json_sref(json); 509 llvm::Expected<llvm::json::Value> json_value = llvm::json::parse(json_sref); 510 if (!json_value) { 511 auto error = json_value.takeError(); 512 if (log) { 513 std::string error_str; 514 llvm::raw_string_ostream strm(error_str); 515 strm << error; 516 strm.flush(); 517 *log << "error: failed to parse JSON: " << error_str << std::endl 518 << json << std::endl; 519 } 520 return PacketStatus::JSONMalformed; 521 } 522 object = *json_value->getAsObject(); 523 if (!json_value->getAsObject()) { 524 if (log) 525 *log << "error: json packet isn't a object" << std::endl; 526 return PacketStatus::JSONNotObject; 527 } 528 return PacketStatus::Success; 529 } 530 531 bool VSCode::HandleObject(const llvm::json::Object &object) { 532 const auto packet_type = GetString(object, "type"); 533 if (packet_type == "request") { 534 const auto command = GetString(object, "command"); 535 auto handler_pos = request_handlers.find(std::string(command)); 536 if (handler_pos != request_handlers.end()) { 537 handler_pos->second(object); 538 return true; // Success 539 } else { 540 if (log) 541 *log << "error: unhandled command \"" << command.data() << std::endl; 542 return false; // Fail 543 } 544 } 545 return false; 546 } 547 548 PacketStatus VSCode::SendReverseRequest(llvm::json::Object request, 549 llvm::json::Object &response) { 550 request.try_emplace("seq", ++reverse_request_seq); 551 SendJSON(llvm::json::Value(std::move(request))); 552 while (true) { 553 PacketStatus status = GetNextObject(response); 554 const auto packet_type = GetString(response, "type"); 555 if (packet_type == "response") 556 return status; 557 else { 558 // Not our response, we got another packet 559 HandleObject(response); 560 } 561 } 562 return PacketStatus::EndOfFile; 563 } 564 565 void VSCode::RegisterRequestCallback(std::string request, 566 RequestCallback callback) { 567 request_handlers[request] = callback; 568 } 569 570 } // namespace lldb_vscode 571