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