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