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