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