1 //===-- JSONUtils.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 <algorithm> 10 11 #include "llvm/ADT/Optional.h" 12 #include "llvm/Support/FormatAdapters.h" 13 #include "llvm/Support/Path.h" 14 #include "llvm/Support/ScopedPrinter.h" 15 16 #include "lldb/API/SBBreakpoint.h" 17 #include "lldb/API/SBBreakpointLocation.h" 18 #include "lldb/API/SBValue.h" 19 #include "lldb/Host/PosixApi.h" 20 21 #include "ExceptionBreakpoint.h" 22 #include "JSONUtils.h" 23 #include "LLDBUtils.h" 24 #include "VSCode.h" 25 26 namespace lldb_vscode { 27 28 void EmplaceSafeString(llvm::json::Object &obj, llvm::StringRef key, 29 llvm::StringRef str) { 30 if (LLVM_LIKELY(llvm::json::isUTF8(str))) 31 obj.try_emplace(key, str.str()); 32 else 33 obj.try_emplace(key, llvm::json::fixUTF8(str)); 34 } 35 36 llvm::StringRef GetAsString(const llvm::json::Value &value) { 37 if (auto s = value.getAsString()) 38 return *s; 39 return llvm::StringRef(); 40 } 41 42 // Gets a string from a JSON object using the key, or returns an empty string. 43 llvm::StringRef GetString(const llvm::json::Object &obj, llvm::StringRef key) { 44 if (llvm::Optional<llvm::StringRef> value = obj.getString(key)) 45 return *value; 46 return llvm::StringRef(); 47 } 48 49 llvm::StringRef GetString(const llvm::json::Object *obj, llvm::StringRef key) { 50 if (obj == nullptr) 51 return llvm::StringRef(); 52 return GetString(*obj, key); 53 } 54 55 // Gets an unsigned integer from a JSON object using the key, or returns the 56 // specified fail value. 57 uint64_t GetUnsigned(const llvm::json::Object &obj, llvm::StringRef key, 58 uint64_t fail_value) { 59 if (auto value = obj.getInteger(key)) 60 return (uint64_t)*value; 61 return fail_value; 62 } 63 64 uint64_t GetUnsigned(const llvm::json::Object *obj, llvm::StringRef key, 65 uint64_t fail_value) { 66 if (obj == nullptr) 67 return fail_value; 68 return GetUnsigned(*obj, key, fail_value); 69 } 70 71 bool GetBoolean(const llvm::json::Object &obj, llvm::StringRef key, 72 bool fail_value) { 73 if (auto value = obj.getBoolean(key)) 74 return *value; 75 if (auto value = obj.getInteger(key)) 76 return *value != 0; 77 return fail_value; 78 } 79 80 bool GetBoolean(const llvm::json::Object *obj, llvm::StringRef key, 81 bool fail_value) { 82 if (obj == nullptr) 83 return fail_value; 84 return GetBoolean(*obj, key, fail_value); 85 } 86 87 int64_t GetSigned(const llvm::json::Object &obj, llvm::StringRef key, 88 int64_t fail_value) { 89 if (auto value = obj.getInteger(key)) 90 return *value; 91 return fail_value; 92 } 93 94 int64_t GetSigned(const llvm::json::Object *obj, llvm::StringRef key, 95 int64_t fail_value) { 96 if (obj == nullptr) 97 return fail_value; 98 return GetSigned(*obj, key, fail_value); 99 } 100 101 bool ObjectContainsKey(const llvm::json::Object &obj, llvm::StringRef key) { 102 return obj.find(key) != obj.end(); 103 } 104 105 std::vector<std::string> GetStrings(const llvm::json::Object *obj, 106 llvm::StringRef key) { 107 std::vector<std::string> strs; 108 auto json_array = obj->getArray(key); 109 if (!json_array) 110 return strs; 111 for (const auto &value : *json_array) { 112 switch (value.kind()) { 113 case llvm::json::Value::String: 114 strs.push_back(value.getAsString()->str()); 115 break; 116 case llvm::json::Value::Number: 117 case llvm::json::Value::Boolean: 118 strs.push_back(llvm::to_string(value)); 119 break; 120 case llvm::json::Value::Null: 121 case llvm::json::Value::Object: 122 case llvm::json::Value::Array: 123 break; 124 } 125 } 126 return strs; 127 } 128 129 void SetValueForKey(lldb::SBValue &v, llvm::json::Object &object, 130 llvm::StringRef key) { 131 132 llvm::StringRef value = v.GetValue(); 133 llvm::StringRef summary = v.GetSummary(); 134 llvm::StringRef type_name = v.GetType().GetDisplayTypeName(); 135 136 std::string result; 137 llvm::raw_string_ostream strm(result); 138 if (!value.empty()) { 139 strm << value; 140 if (!summary.empty()) 141 strm << ' ' << summary; 142 } else if (!summary.empty()) { 143 strm << ' ' << summary; 144 } else if (!type_name.empty()) { 145 strm << type_name; 146 lldb::addr_t address = v.GetLoadAddress(); 147 if (address != LLDB_INVALID_ADDRESS) 148 strm << " @ " << llvm::format_hex(address, 0); 149 } 150 strm.flush(); 151 EmplaceSafeString(object, key, result); 152 } 153 154 void FillResponse(const llvm::json::Object &request, 155 llvm::json::Object &response) { 156 // Fill in all of the needed response fields to a "request" and set "success" 157 // to true by default. 158 response.try_emplace("type", "response"); 159 response.try_emplace("seq", (int64_t)0); 160 EmplaceSafeString(response, "command", GetString(request, "command")); 161 const int64_t seq = GetSigned(request, "seq", 0); 162 response.try_emplace("request_seq", seq); 163 response.try_emplace("success", true); 164 } 165 166 // "Scope": { 167 // "type": "object", 168 // "description": "A Scope is a named container for variables. Optionally 169 // a scope can map to a source or a range within a source.", 170 // "properties": { 171 // "name": { 172 // "type": "string", 173 // "description": "Name of the scope such as 'Arguments', 'Locals'." 174 // }, 175 // "variablesReference": { 176 // "type": "integer", 177 // "description": "The variables of this scope can be retrieved by 178 // passing the value of variablesReference to the 179 // VariablesRequest." 180 // }, 181 // "namedVariables": { 182 // "type": "integer", 183 // "description": "The number of named variables in this scope. The 184 // client can use this optional information to present 185 // the variables in a paged UI and fetch them in chunks." 186 // }, 187 // "indexedVariables": { 188 // "type": "integer", 189 // "description": "The number of indexed variables in this scope. The 190 // client can use this optional information to present 191 // the variables in a paged UI and fetch them in chunks." 192 // }, 193 // "expensive": { 194 // "type": "boolean", 195 // "description": "If true, the number of variables in this scope is 196 // large or expensive to retrieve." 197 // }, 198 // "source": { 199 // "$ref": "#/definitions/Source", 200 // "description": "Optional source for this scope." 201 // }, 202 // "line": { 203 // "type": "integer", 204 // "description": "Optional start line of the range covered by this 205 // scope." 206 // }, 207 // "column": { 208 // "type": "integer", 209 // "description": "Optional start column of the range covered by this 210 // scope." 211 // }, 212 // "endLine": { 213 // "type": "integer", 214 // "description": "Optional end line of the range covered by this scope." 215 // }, 216 // "endColumn": { 217 // "type": "integer", 218 // "description": "Optional end column of the range covered by this 219 // scope." 220 // } 221 // }, 222 // "required": [ "name", "variablesReference", "expensive" ] 223 // } 224 llvm::json::Value CreateScope(const llvm::StringRef name, 225 int64_t variablesReference, 226 int64_t namedVariables, bool expensive) { 227 llvm::json::Object object; 228 EmplaceSafeString(object, "name", name.str()); 229 object.try_emplace("variablesReference", variablesReference); 230 object.try_emplace("expensive", expensive); 231 object.try_emplace("namedVariables", namedVariables); 232 return llvm::json::Value(std::move(object)); 233 } 234 235 // "Breakpoint": { 236 // "type": "object", 237 // "description": "Information about a Breakpoint created in setBreakpoints 238 // or setFunctionBreakpoints.", 239 // "properties": { 240 // "id": { 241 // "type": "integer", 242 // "description": "An optional unique identifier for the breakpoint." 243 // }, 244 // "verified": { 245 // "type": "boolean", 246 // "description": "If true breakpoint could be set (but not necessarily 247 // at the desired location)." 248 // }, 249 // "message": { 250 // "type": "string", 251 // "description": "An optional message about the state of the breakpoint. 252 // This is shown to the user and can be used to explain 253 // why a breakpoint could not be verified." 254 // }, 255 // "source": { 256 // "$ref": "#/definitions/Source", 257 // "description": "The source where the breakpoint is located." 258 // }, 259 // "line": { 260 // "type": "integer", 261 // "description": "The start line of the actual range covered by the 262 // breakpoint." 263 // }, 264 // "column": { 265 // "type": "integer", 266 // "description": "An optional start column of the actual range covered 267 // by the breakpoint." 268 // }, 269 // "endLine": { 270 // "type": "integer", 271 // "description": "An optional end line of the actual range covered by 272 // the breakpoint." 273 // }, 274 // "endColumn": { 275 // "type": "integer", 276 // "description": "An optional end column of the actual range covered by 277 // the breakpoint. If no end line is given, then the end 278 // column is assumed to be in the start line." 279 // } 280 // }, 281 // "required": [ "verified" ] 282 // } 283 llvm::json::Value CreateBreakpoint(lldb::SBBreakpoint &bp, 284 llvm::Optional<llvm::StringRef> request_path, 285 llvm::Optional<uint32_t> request_line) { 286 // Each breakpoint location is treated as a separate breakpoint for VS code. 287 // They don't have the notion of a single breakpoint with multiple locations. 288 llvm::json::Object object; 289 if (!bp.IsValid()) 290 return llvm::json::Value(std::move(object)); 291 292 object.try_emplace("verified", bp.GetNumResolvedLocations() > 0); 293 object.try_emplace("id", bp.GetID()); 294 // VS Code DAP doesn't currently allow one breakpoint to have multiple 295 // locations so we just report the first one. If we report all locations 296 // then the IDE starts showing the wrong line numbers and locations for 297 // other source file and line breakpoints in the same file. 298 299 // Below we search for the first resolved location in a breakpoint and report 300 // this as the breakpoint location since it will have a complete location 301 // that is at least loaded in the current process. 302 lldb::SBBreakpointLocation bp_loc; 303 const auto num_locs = bp.GetNumLocations(); 304 for (size_t i = 0; i < num_locs; ++i) { 305 bp_loc = bp.GetLocationAtIndex(i); 306 if (bp_loc.IsResolved()) 307 break; 308 } 309 // If not locations are resolved, use the first location. 310 if (!bp_loc.IsResolved()) 311 bp_loc = bp.GetLocationAtIndex(0); 312 auto bp_addr = bp_loc.GetAddress(); 313 314 if (request_path) 315 object.try_emplace("source", CreateSource(*request_path)); 316 317 if (bp_addr.IsValid()) { 318 auto line_entry = bp_addr.GetLineEntry(); 319 const auto line = line_entry.GetLine(); 320 if (line != UINT32_MAX) 321 object.try_emplace("line", line); 322 object.try_emplace("source", CreateSource(line_entry)); 323 } 324 // We try to add request_line as a fallback 325 if (request_line) 326 object.try_emplace("line", *request_line); 327 return llvm::json::Value(std::move(object)); 328 } 329 330 llvm::json::Value CreateModule(lldb::SBModule &module) { 331 llvm::json::Object object; 332 if (!module.IsValid()) 333 return llvm::json::Value(std::move(object)); 334 const char *uuid = module.GetUUIDString(); 335 object.try_emplace("id", uuid ? std::string(uuid) : std::string("")); 336 object.try_emplace("name", std::string(module.GetFileSpec().GetFilename())); 337 char module_path_arr[PATH_MAX]; 338 module.GetFileSpec().GetPath(module_path_arr, sizeof(module_path_arr)); 339 std::string module_path(module_path_arr); 340 object.try_emplace("path", module_path); 341 if (module.GetNumCompileUnits() > 0) { 342 object.try_emplace("symbolStatus", "Symbols loaded."); 343 char symbol_path_arr[PATH_MAX]; 344 module.GetSymbolFileSpec().GetPath(symbol_path_arr, sizeof(symbol_path_arr)); 345 std::string symbol_path(symbol_path_arr); 346 object.try_emplace("symbolFilePath", symbol_path); 347 } else { 348 object.try_emplace("symbolStatus", "Symbols not found."); 349 } 350 std::string loaded_addr = std::to_string( 351 module.GetObjectFileHeaderAddress().GetLoadAddress(g_vsc.target)); 352 object.try_emplace("addressRange", loaded_addr); 353 std::string version_str; 354 uint32_t version_nums[3]; 355 uint32_t num_versions = module.GetVersion(version_nums, sizeof(version_nums)/sizeof(uint32_t)); 356 for (uint32_t i=0; i<num_versions; ++i) { 357 if (!version_str.empty()) 358 version_str += "."; 359 version_str += std::to_string(version_nums[i]); 360 } 361 if (!version_str.empty()) 362 object.try_emplace("version", version_str); 363 return llvm::json::Value(std::move(object)); 364 } 365 366 void AppendBreakpoint(lldb::SBBreakpoint &bp, llvm::json::Array &breakpoints, 367 llvm::Optional<llvm::StringRef> request_path, 368 llvm::Optional<uint32_t> request_line) { 369 breakpoints.emplace_back(CreateBreakpoint(bp, request_path, request_line)); 370 } 371 372 // "Event": { 373 // "allOf": [ { "$ref": "#/definitions/ProtocolMessage" }, { 374 // "type": "object", 375 // "description": "Server-initiated event.", 376 // "properties": { 377 // "type": { 378 // "type": "string", 379 // "enum": [ "event" ] 380 // }, 381 // "event": { 382 // "type": "string", 383 // "description": "Type of event." 384 // }, 385 // "body": { 386 // "type": [ "array", "boolean", "integer", "null", "number" , 387 // "object", "string" ], 388 // "description": "Event-specific information." 389 // } 390 // }, 391 // "required": [ "type", "event" ] 392 // }] 393 // }, 394 // "ProtocolMessage": { 395 // "type": "object", 396 // "description": "Base class of requests, responses, and events.", 397 // "properties": { 398 // "seq": { 399 // "type": "integer", 400 // "description": "Sequence number." 401 // }, 402 // "type": { 403 // "type": "string", 404 // "description": "Message type.", 405 // "_enum": [ "request", "response", "event" ] 406 // } 407 // }, 408 // "required": [ "seq", "type" ] 409 // } 410 llvm::json::Object CreateEventObject(const llvm::StringRef event_name) { 411 llvm::json::Object event; 412 event.try_emplace("seq", 0); 413 event.try_emplace("type", "event"); 414 EmplaceSafeString(event, "event", event_name); 415 return event; 416 } 417 418 // "ExceptionBreakpointsFilter": { 419 // "type": "object", 420 // "description": "An ExceptionBreakpointsFilter is shown in the UI as an 421 // option for configuring how exceptions are dealt with.", 422 // "properties": { 423 // "filter": { 424 // "type": "string", 425 // "description": "The internal ID of the filter. This value is passed 426 // to the setExceptionBreakpoints request." 427 // }, 428 // "label": { 429 // "type": "string", 430 // "description": "The name of the filter. This will be shown in the UI." 431 // }, 432 // "default": { 433 // "type": "boolean", 434 // "description": "Initial value of the filter. If not specified a value 435 // 'false' is assumed." 436 // } 437 // }, 438 // "required": [ "filter", "label" ] 439 // } 440 llvm::json::Value 441 CreateExceptionBreakpointFilter(const ExceptionBreakpoint &bp) { 442 llvm::json::Object object; 443 EmplaceSafeString(object, "filter", bp.filter); 444 EmplaceSafeString(object, "label", bp.label); 445 object.try_emplace("default", bp.default_value); 446 return llvm::json::Value(std::move(object)); 447 } 448 449 // "Source": { 450 // "type": "object", 451 // "description": "A Source is a descriptor for source code. It is returned 452 // from the debug adapter as part of a StackFrame and it is 453 // used by clients when specifying breakpoints.", 454 // "properties": { 455 // "name": { 456 // "type": "string", 457 // "description": "The short name of the source. Every source returned 458 // from the debug adapter has a name. When sending a 459 // source to the debug adapter this name is optional." 460 // }, 461 // "path": { 462 // "type": "string", 463 // "description": "The path of the source to be shown in the UI. It is 464 // only used to locate and load the content of the 465 // source if no sourceReference is specified (or its 466 // value is 0)." 467 // }, 468 // "sourceReference": { 469 // "type": "number", 470 // "description": "If sourceReference > 0 the contents of the source must 471 // be retrieved through the SourceRequest (even if a path 472 // is specified). A sourceReference is only valid for a 473 // session, so it must not be used to persist a source." 474 // }, 475 // "presentationHint": { 476 // "type": "string", 477 // "description": "An optional hint for how to present the source in the 478 // UI. A value of 'deemphasize' can be used to indicate 479 // that the source is not available or that it is 480 // skipped on stepping.", 481 // "enum": [ "normal", "emphasize", "deemphasize" ] 482 // }, 483 // "origin": { 484 // "type": "string", 485 // "description": "The (optional) origin of this source: possible values 486 // 'internal module', 'inlined content from source map', 487 // etc." 488 // }, 489 // "sources": { 490 // "type": "array", 491 // "items": { 492 // "$ref": "#/definitions/Source" 493 // }, 494 // "description": "An optional list of sources that are related to this 495 // source. These may be the source that generated this 496 // source." 497 // }, 498 // "adapterData": { 499 // "type":["array","boolean","integer","null","number","object","string"], 500 // "description": "Optional data that a debug adapter might want to loop 501 // through the client. The client should leave the data 502 // intact and persist it across sessions. The client 503 // should not interpret the data." 504 // }, 505 // "checksums": { 506 // "type": "array", 507 // "items": { 508 // "$ref": "#/definitions/Checksum" 509 // }, 510 // "description": "The checksums associated with this file." 511 // } 512 // } 513 // } 514 llvm::json::Value CreateSource(lldb::SBLineEntry &line_entry) { 515 llvm::json::Object object; 516 lldb::SBFileSpec file = line_entry.GetFileSpec(); 517 if (file.IsValid()) { 518 const char *name = file.GetFilename(); 519 if (name) 520 EmplaceSafeString(object, "name", name); 521 char path[PATH_MAX] = ""; 522 file.GetPath(path, sizeof(path)); 523 if (path[0]) { 524 EmplaceSafeString(object, "path", std::string(path)); 525 } 526 } 527 return llvm::json::Value(std::move(object)); 528 } 529 530 llvm::json::Value CreateSource(llvm::StringRef source_path) { 531 llvm::json::Object source; 532 llvm::StringRef name = llvm::sys::path::filename(source_path); 533 EmplaceSafeString(source, "name", name); 534 EmplaceSafeString(source, "path", source_path); 535 return llvm::json::Value(std::move(source)); 536 } 537 538 llvm::json::Value CreateSource(lldb::SBFrame &frame, int64_t &disasm_line) { 539 disasm_line = 0; 540 auto line_entry = frame.GetLineEntry(); 541 if (line_entry.GetFileSpec().IsValid()) 542 return CreateSource(line_entry); 543 544 llvm::json::Object object; 545 const auto pc = frame.GetPC(); 546 547 lldb::SBInstructionList insts; 548 lldb::SBFunction function = frame.GetFunction(); 549 lldb::addr_t low_pc = LLDB_INVALID_ADDRESS; 550 if (function.IsValid()) { 551 low_pc = function.GetStartAddress().GetLoadAddress(g_vsc.target); 552 auto addr_srcref = g_vsc.addr_to_source_ref.find(low_pc); 553 if (addr_srcref != g_vsc.addr_to_source_ref.end()) { 554 // We have this disassembly cached already, return the existing 555 // sourceReference 556 object.try_emplace("sourceReference", addr_srcref->second); 557 disasm_line = g_vsc.GetLineForPC(addr_srcref->second, pc); 558 } else { 559 insts = function.GetInstructions(g_vsc.target); 560 } 561 } else { 562 lldb::SBSymbol symbol = frame.GetSymbol(); 563 if (symbol.IsValid()) { 564 low_pc = symbol.GetStartAddress().GetLoadAddress(g_vsc.target); 565 auto addr_srcref = g_vsc.addr_to_source_ref.find(low_pc); 566 if (addr_srcref != g_vsc.addr_to_source_ref.end()) { 567 // We have this disassembly cached already, return the existing 568 // sourceReference 569 object.try_emplace("sourceReference", addr_srcref->second); 570 disasm_line = g_vsc.GetLineForPC(addr_srcref->second, pc); 571 } else { 572 insts = symbol.GetInstructions(g_vsc.target); 573 } 574 } 575 } 576 const auto num_insts = insts.GetSize(); 577 if (low_pc != LLDB_INVALID_ADDRESS && num_insts > 0) { 578 EmplaceSafeString(object, "name", frame.GetFunctionName()); 579 SourceReference source; 580 llvm::raw_string_ostream src_strm(source.content); 581 std::string line; 582 for (size_t i = 0; i < num_insts; ++i) { 583 lldb::SBInstruction inst = insts.GetInstructionAtIndex(i); 584 const auto inst_addr = inst.GetAddress().GetLoadAddress(g_vsc.target); 585 const char *m = inst.GetMnemonic(g_vsc.target); 586 const char *o = inst.GetOperands(g_vsc.target); 587 const char *c = inst.GetComment(g_vsc.target); 588 if (pc == inst_addr) 589 disasm_line = i + 1; 590 const auto inst_offset = inst_addr - low_pc; 591 int spaces = 0; 592 if (inst_offset < 10) 593 spaces = 3; 594 else if (inst_offset < 100) 595 spaces = 2; 596 else if (inst_offset < 1000) 597 spaces = 1; 598 line.clear(); 599 llvm::raw_string_ostream line_strm(line); 600 line_strm << llvm::formatv("{0:X+}: <{1}> {2} {3,12} {4}", inst_addr, 601 inst_offset, llvm::fmt_repeat(' ', spaces), m, 602 o); 603 604 // If there is a comment append it starting at column 60 or after one 605 // space past the last char 606 const uint32_t comment_row = std::max(line_strm.str().size(), (size_t)60); 607 if (c && c[0]) { 608 if (line.size() < comment_row) 609 line_strm.indent(comment_row - line_strm.str().size()); 610 line_strm << " # " << c; 611 } 612 src_strm << line_strm.str() << "\n"; 613 source.addr_to_line[inst_addr] = i + 1; 614 } 615 // Flush the source stream 616 src_strm.str(); 617 auto sourceReference = VSCode::GetNextSourceReference(); 618 g_vsc.source_map[sourceReference] = std::move(source); 619 g_vsc.addr_to_source_ref[low_pc] = sourceReference; 620 object.try_emplace("sourceReference", sourceReference); 621 } 622 return llvm::json::Value(std::move(object)); 623 } 624 625 // "StackFrame": { 626 // "type": "object", 627 // "description": "A Stackframe contains the source location.", 628 // "properties": { 629 // "id": { 630 // "type": "integer", 631 // "description": "An identifier for the stack frame. It must be unique 632 // across all threads. This id can be used to retrieve 633 // the scopes of the frame with the 'scopesRequest' or 634 // to restart the execution of a stackframe." 635 // }, 636 // "name": { 637 // "type": "string", 638 // "description": "The name of the stack frame, typically a method name." 639 // }, 640 // "source": { 641 // "$ref": "#/definitions/Source", 642 // "description": "The optional source of the frame." 643 // }, 644 // "line": { 645 // "type": "integer", 646 // "description": "The line within the file of the frame. If source is 647 // null or doesn't exist, line is 0 and must be ignored." 648 // }, 649 // "column": { 650 // "type": "integer", 651 // "description": "The column within the line. If source is null or 652 // doesn't exist, column is 0 and must be ignored." 653 // }, 654 // "endLine": { 655 // "type": "integer", 656 // "description": "An optional end line of the range covered by the 657 // stack frame." 658 // }, 659 // "endColumn": { 660 // "type": "integer", 661 // "description": "An optional end column of the range covered by the 662 // stack frame." 663 // }, 664 // "moduleId": { 665 // "type": ["integer", "string"], 666 // "description": "The module associated with this frame, if any." 667 // }, 668 // "presentationHint": { 669 // "type": "string", 670 // "enum": [ "normal", "label", "subtle" ], 671 // "description": "An optional hint for how to present this frame in 672 // the UI. A value of 'label' can be used to indicate 673 // that the frame is an artificial frame that is used 674 // as a visual label or separator. A value of 'subtle' 675 // can be used to change the appearance of a frame in 676 // a 'subtle' way." 677 // } 678 // }, 679 // "required": [ "id", "name", "line", "column" ] 680 // } 681 llvm::json::Value CreateStackFrame(lldb::SBFrame &frame) { 682 llvm::json::Object object; 683 int64_t frame_id = MakeVSCodeFrameID(frame); 684 object.try_emplace("id", frame_id); 685 EmplaceSafeString(object, "name", frame.GetFunctionName()); 686 int64_t disasm_line = 0; 687 object.try_emplace("source", CreateSource(frame, disasm_line)); 688 689 auto line_entry = frame.GetLineEntry(); 690 if (disasm_line > 0) { 691 object.try_emplace("line", disasm_line); 692 } else { 693 auto line = line_entry.GetLine(); 694 if (line == UINT32_MAX) 695 line = 0; 696 object.try_emplace("line", line); 697 } 698 object.try_emplace("column", line_entry.GetColumn()); 699 return llvm::json::Value(std::move(object)); 700 } 701 702 // "Thread": { 703 // "type": "object", 704 // "description": "A Thread", 705 // "properties": { 706 // "id": { 707 // "type": "integer", 708 // "description": "Unique identifier for the thread." 709 // }, 710 // "name": { 711 // "type": "string", 712 // "description": "A name of the thread." 713 // } 714 // }, 715 // "required": [ "id", "name" ] 716 // } 717 llvm::json::Value CreateThread(lldb::SBThread &thread) { 718 llvm::json::Object object; 719 object.try_emplace("id", (int64_t)thread.GetThreadID()); 720 char thread_str[64]; 721 snprintf(thread_str, sizeof(thread_str), "Thread #%u", thread.GetIndexID()); 722 const char *name = thread.GetName(); 723 if (name) { 724 std::string thread_with_name(thread_str); 725 thread_with_name += ' '; 726 thread_with_name += name; 727 EmplaceSafeString(object, "name", thread_with_name); 728 } else { 729 EmplaceSafeString(object, "name", std::string(thread_str)); 730 } 731 return llvm::json::Value(std::move(object)); 732 } 733 734 // "StoppedEvent": { 735 // "allOf": [ { "$ref": "#/definitions/Event" }, { 736 // "type": "object", 737 // "description": "Event message for 'stopped' event type. The event 738 // indicates that the execution of the debuggee has stopped 739 // due to some condition. This can be caused by a break 740 // point previously set, a stepping action has completed, 741 // by executing a debugger statement etc.", 742 // "properties": { 743 // "event": { 744 // "type": "string", 745 // "enum": [ "stopped" ] 746 // }, 747 // "body": { 748 // "type": "object", 749 // "properties": { 750 // "reason": { 751 // "type": "string", 752 // "description": "The reason for the event. For backward 753 // compatibility this string is shown in the UI if 754 // the 'description' attribute is missing (but it 755 // must not be translated).", 756 // "_enum": [ "step", "breakpoint", "exception", "pause", "entry" ] 757 // }, 758 // "description": { 759 // "type": "string", 760 // "description": "The full reason for the event, e.g. 'Paused 761 // on exception'. This string is shown in the UI 762 // as is." 763 // }, 764 // "threadId": { 765 // "type": "integer", 766 // "description": "The thread which was stopped." 767 // }, 768 // "text": { 769 // "type": "string", 770 // "description": "Additional information. E.g. if reason is 771 // 'exception', text contains the exception name. 772 // This string is shown in the UI." 773 // }, 774 // "allThreadsStopped": { 775 // "type": "boolean", 776 // "description": "If allThreadsStopped is true, a debug adapter 777 // can announce that all threads have stopped. 778 // The client should use this information to 779 // enable that all threads can be expanded to 780 // access their stacktraces. If the attribute 781 // is missing or false, only the thread with the 782 // given threadId can be expanded." 783 // } 784 // }, 785 // "required": [ "reason" ] 786 // } 787 // }, 788 // "required": [ "event", "body" ] 789 // }] 790 // } 791 llvm::json::Value CreateThreadStopped(lldb::SBThread &thread, 792 uint32_t stop_id) { 793 llvm::json::Object event(CreateEventObject("stopped")); 794 llvm::json::Object body; 795 switch (thread.GetStopReason()) { 796 case lldb::eStopReasonTrace: 797 case lldb::eStopReasonPlanComplete: 798 body.try_emplace("reason", "step"); 799 break; 800 case lldb::eStopReasonBreakpoint: { 801 ExceptionBreakpoint *exc_bp = g_vsc.GetExceptionBPFromStopReason(thread); 802 if (exc_bp) { 803 body.try_emplace("reason", "exception"); 804 EmplaceSafeString(body, "description", exc_bp->label); 805 } else { 806 body.try_emplace("reason", "breakpoint"); 807 char desc_str[64]; 808 uint64_t bp_id = thread.GetStopReasonDataAtIndex(0); 809 uint64_t bp_loc_id = thread.GetStopReasonDataAtIndex(1); 810 snprintf(desc_str, sizeof(desc_str), "breakpoint %" PRIu64 ".%" PRIu64, 811 bp_id, bp_loc_id); 812 EmplaceSafeString(body, "description", desc_str); 813 } 814 } break; 815 case lldb::eStopReasonWatchpoint: 816 case lldb::eStopReasonInstrumentation: 817 body.try_emplace("reason", "breakpoint"); 818 break; 819 case lldb::eStopReasonSignal: 820 body.try_emplace("reason", "exception"); 821 break; 822 case lldb::eStopReasonException: 823 body.try_emplace("reason", "exception"); 824 break; 825 case lldb::eStopReasonExec: 826 body.try_emplace("reason", "entry"); 827 break; 828 case lldb::eStopReasonThreadExiting: 829 case lldb::eStopReasonInvalid: 830 case lldb::eStopReasonNone: 831 break; 832 } 833 if (stop_id == 0) 834 body.try_emplace("reason", "entry"); 835 const lldb::tid_t tid = thread.GetThreadID(); 836 body.try_emplace("threadId", (int64_t)tid); 837 // If no description has been set, then set it to the default thread stopped 838 // description. If we have breakpoints that get hit and shouldn't be reported 839 // as breakpoints, then they will set the description above. 840 if (ObjectContainsKey(body, "description")) { 841 char description[1024]; 842 if (thread.GetStopDescription(description, sizeof(description))) { 843 EmplaceSafeString(body, "description", std::string(description)); 844 } 845 } 846 if (tid == g_vsc.focus_tid) { 847 body.try_emplace("threadCausedFocus", true); 848 } 849 body.try_emplace("preserveFocusHint", tid != g_vsc.focus_tid); 850 body.try_emplace("allThreadsStopped", true); 851 event.try_emplace("body", std::move(body)); 852 return llvm::json::Value(std::move(event)); 853 } 854 855 // "Variable": { 856 // "type": "object", 857 // "description": "A Variable is a name/value pair. Optionally a variable 858 // can have a 'type' that is shown if space permits or when 859 // hovering over the variable's name. An optional 'kind' is 860 // used to render additional properties of the variable, 861 // e.g. different icons can be used to indicate that a 862 // variable is public or private. If the value is 863 // structured (has children), a handle is provided to 864 // retrieve the children with the VariablesRequest. If 865 // the number of named or indexed children is large, the 866 // numbers should be returned via the optional 867 // 'namedVariables' and 'indexedVariables' attributes. The 868 // client can use this optional information to present the 869 // children in a paged UI and fetch them in chunks.", 870 // "properties": { 871 // "name": { 872 // "type": "string", 873 // "description": "The variable's name." 874 // }, 875 // "value": { 876 // "type": "string", 877 // "description": "The variable's value. This can be a multi-line text, 878 // e.g. for a function the body of a function." 879 // }, 880 // "type": { 881 // "type": "string", 882 // "description": "The type of the variable's value. Typically shown in 883 // the UI when hovering over the value." 884 // }, 885 // "presentationHint": { 886 // "$ref": "#/definitions/VariablePresentationHint", 887 // "description": "Properties of a variable that can be used to determine 888 // how to render the variable in the UI." 889 // }, 890 // "evaluateName": { 891 // "type": "string", 892 // "description": "Optional evaluatable name of this variable which can 893 // be passed to the 'EvaluateRequest' to fetch the 894 // variable's value." 895 // }, 896 // "variablesReference": { 897 // "type": "integer", 898 // "description": "If variablesReference is > 0, the variable is 899 // structured and its children can be retrieved by 900 // passing variablesReference to the VariablesRequest." 901 // }, 902 // "namedVariables": { 903 // "type": "integer", 904 // "description": "The number of named child variables. The client can 905 // use this optional information to present the children 906 // in a paged UI and fetch them in chunks." 907 // }, 908 // "indexedVariables": { 909 // "type": "integer", 910 // "description": "The number of indexed child variables. The client 911 // can use this optional information to present the 912 // children in a paged UI and fetch them in chunks." 913 // } 914 // }, 915 // "required": [ "name", "value", "variablesReference" ] 916 // } 917 llvm::json::Value CreateVariable(lldb::SBValue v, int64_t variablesReference, 918 int64_t varID, bool format_hex) { 919 llvm::json::Object object; 920 auto name = v.GetName(); 921 EmplaceSafeString(object, "name", name ? name : "<null>"); 922 if (format_hex) 923 v.SetFormat(lldb::eFormatHex); 924 SetValueForKey(v, object, "value"); 925 auto type_cstr = v.GetType().GetDisplayTypeName(); 926 EmplaceSafeString(object, "type", type_cstr ? type_cstr : NO_TYPENAME); 927 if (varID != INT64_MAX) 928 object.try_emplace("id", varID); 929 if (v.MightHaveChildren()) 930 object.try_emplace("variablesReference", variablesReference); 931 else 932 object.try_emplace("variablesReference", (int64_t)0); 933 lldb::SBStream evaluateStream; 934 v.GetExpressionPath(evaluateStream); 935 const char *evaluateName = evaluateStream.GetData(); 936 if (evaluateName && evaluateName[0]) 937 EmplaceSafeString(object, "evaluateName", std::string(evaluateName)); 938 return llvm::json::Value(std::move(object)); 939 } 940 941 llvm::json::Value CreateCompileUnit(lldb::SBCompileUnit unit) { 942 llvm::json::Object object; 943 char unit_path_arr[PATH_MAX]; 944 unit.GetFileSpec().GetPath(unit_path_arr, sizeof(unit_path_arr)); 945 std::string unit_path(unit_path_arr); 946 object.try_emplace("compileUnitPath", unit_path); 947 return llvm::json::Value(std::move(object)); 948 } 949 950 } // namespace lldb_vscode 951