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