1 //===-- CommandCompletions.cpp --------------------------------------------===// 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 "llvm/ADT/SmallString.h" 10 #include "llvm/ADT/StringSet.h" 11 12 #include "lldb/Core/FileSpecList.h" 13 #include "lldb/Core/Module.h" 14 #include "lldb/Core/PluginManager.h" 15 #include "lldb/Host/FileSystem.h" 16 #include "lldb/Interpreter/CommandCompletions.h" 17 #include "lldb/Interpreter/CommandInterpreter.h" 18 #include "lldb/Interpreter/OptionValueProperties.h" 19 #include "lldb/Symbol/CompileUnit.h" 20 #include "lldb/Symbol/Variable.h" 21 #include "lldb/Target/Language.h" 22 #include "lldb/Target/RegisterContext.h" 23 #include "lldb/Target/Thread.h" 24 #include "lldb/Utility/FileSpec.h" 25 #include "lldb/Utility/StreamString.h" 26 #include "lldb/Utility/TildeExpressionResolver.h" 27 28 #include "llvm/Support/FileSystem.h" 29 #include "llvm/Support/Path.h" 30 31 using namespace lldb_private; 32 33 // This is the command completion callback that is used to complete the 34 // argument of the option it is bound to (in the OptionDefinition table 35 // below). 36 typedef void (*CompletionCallback)(CommandInterpreter &interpreter, 37 CompletionRequest &request, 38 // A search filter to limit the search... 39 lldb_private::SearchFilter *searcher); 40 41 struct CommonCompletionElement { 42 uint32_t type; 43 CompletionCallback callback; 44 }; 45 46 bool CommandCompletions::InvokeCommonCompletionCallbacks( 47 CommandInterpreter &interpreter, uint32_t completion_mask, 48 CompletionRequest &request, SearchFilter *searcher) { 49 bool handled = false; 50 51 const CommonCompletionElement common_completions[] = { 52 {eSourceFileCompletion, CommandCompletions::SourceFiles}, 53 {eDiskFileCompletion, CommandCompletions::DiskFiles}, 54 {eDiskDirectoryCompletion, CommandCompletions::DiskDirectories}, 55 {eSymbolCompletion, CommandCompletions::Symbols}, 56 {eModuleCompletion, CommandCompletions::Modules}, 57 {eSettingsNameCompletion, CommandCompletions::SettingsNames}, 58 {ePlatformPluginCompletion, CommandCompletions::PlatformPluginNames}, 59 {eArchitectureCompletion, CommandCompletions::ArchitectureNames}, 60 {eVariablePathCompletion, CommandCompletions::VariablePath}, 61 {eRegisterCompletion, CommandCompletions::Registers}, 62 {eBreakpointCompletion, CommandCompletions::Breakpoints}, 63 {eProcessPluginCompletion, CommandCompletions::ProcessPluginNames}, 64 {eDisassemblyFlavorCompletion, CommandCompletions::DisassemblyFlavors}, 65 {eTypeLanguageCompletion, CommandCompletions::TypeLanguages}, 66 {eFrameIndexCompletion, CommandCompletions::FrameIndexes}, 67 {eNoCompletion, nullptr} // This one has to be last in the list. 68 }; 69 70 for (int i = 0;; i++) { 71 if (common_completions[i].type == eNoCompletion) 72 break; 73 else if ((common_completions[i].type & completion_mask) == 74 common_completions[i].type && 75 common_completions[i].callback != nullptr) { 76 handled = true; 77 common_completions[i].callback(interpreter, request, searcher); 78 } 79 } 80 return handled; 81 } 82 83 namespace { 84 // The Completer class is a convenient base class for building searchers that 85 // go along with the SearchFilter passed to the standard Completer functions. 86 class Completer : public Searcher { 87 public: 88 Completer(CommandInterpreter &interpreter, CompletionRequest &request) 89 : m_interpreter(interpreter), m_request(request) {} 90 91 ~Completer() override = default; 92 93 CallbackReturn SearchCallback(SearchFilter &filter, SymbolContext &context, 94 Address *addr) override = 0; 95 96 lldb::SearchDepth GetDepth() override = 0; 97 98 virtual void DoCompletion(SearchFilter *filter) = 0; 99 100 protected: 101 CommandInterpreter &m_interpreter; 102 CompletionRequest &m_request; 103 104 private: 105 Completer(const Completer &) = delete; 106 const Completer &operator=(const Completer &) = delete; 107 }; 108 } // namespace 109 110 // SourceFileCompleter implements the source file completer 111 namespace { 112 class SourceFileCompleter : public Completer { 113 public: 114 SourceFileCompleter(CommandInterpreter &interpreter, 115 CompletionRequest &request) 116 : Completer(interpreter, request), m_matching_files() { 117 FileSpec partial_spec(m_request.GetCursorArgumentPrefix()); 118 m_file_name = partial_spec.GetFilename().GetCString(); 119 m_dir_name = partial_spec.GetDirectory().GetCString(); 120 } 121 122 lldb::SearchDepth GetDepth() override { return lldb::eSearchDepthCompUnit; } 123 124 Searcher::CallbackReturn SearchCallback(SearchFilter &filter, 125 SymbolContext &context, 126 Address *addr) override { 127 if (context.comp_unit != nullptr) { 128 const char *cur_file_name = 129 context.comp_unit->GetPrimaryFile().GetFilename().GetCString(); 130 const char *cur_dir_name = 131 context.comp_unit->GetPrimaryFile().GetDirectory().GetCString(); 132 133 bool match = false; 134 if (m_file_name && cur_file_name && 135 strstr(cur_file_name, m_file_name) == cur_file_name) 136 match = true; 137 138 if (match && m_dir_name && cur_dir_name && 139 strstr(cur_dir_name, m_dir_name) != cur_dir_name) 140 match = false; 141 142 if (match) { 143 m_matching_files.AppendIfUnique(context.comp_unit->GetPrimaryFile()); 144 } 145 } 146 return Searcher::eCallbackReturnContinue; 147 } 148 149 void DoCompletion(SearchFilter *filter) override { 150 filter->Search(*this); 151 // Now convert the filelist to completions: 152 for (size_t i = 0; i < m_matching_files.GetSize(); i++) { 153 m_request.AddCompletion( 154 m_matching_files.GetFileSpecAtIndex(i).GetFilename().GetCString()); 155 } 156 } 157 158 private: 159 FileSpecList m_matching_files; 160 const char *m_file_name; 161 const char *m_dir_name; 162 163 SourceFileCompleter(const SourceFileCompleter &) = delete; 164 const SourceFileCompleter &operator=(const SourceFileCompleter &) = delete; 165 }; 166 } // namespace 167 168 static bool regex_chars(const char comp) { 169 return llvm::StringRef("[](){}+.*|^$\\?").contains(comp); 170 } 171 172 namespace { 173 class SymbolCompleter : public Completer { 174 175 public: 176 SymbolCompleter(CommandInterpreter &interpreter, CompletionRequest &request) 177 : Completer(interpreter, request) { 178 std::string regex_str; 179 if (!m_request.GetCursorArgumentPrefix().empty()) { 180 regex_str.append("^"); 181 regex_str.append(std::string(m_request.GetCursorArgumentPrefix())); 182 } else { 183 // Match anything since the completion string is empty 184 regex_str.append("."); 185 } 186 std::string::iterator pos = 187 find_if(regex_str.begin() + 1, regex_str.end(), regex_chars); 188 while (pos < regex_str.end()) { 189 pos = regex_str.insert(pos, '\\'); 190 pos = find_if(pos + 2, regex_str.end(), regex_chars); 191 } 192 m_regex = RegularExpression(regex_str); 193 } 194 195 lldb::SearchDepth GetDepth() override { return lldb::eSearchDepthModule; } 196 197 Searcher::CallbackReturn SearchCallback(SearchFilter &filter, 198 SymbolContext &context, 199 Address *addr) override { 200 if (context.module_sp) { 201 SymbolContextList sc_list; 202 const bool include_symbols = true; 203 const bool include_inlines = true; 204 context.module_sp->FindFunctions(m_regex, include_symbols, 205 include_inlines, sc_list); 206 207 SymbolContext sc; 208 // Now add the functions & symbols to the list - only add if unique: 209 for (uint32_t i = 0; i < sc_list.GetSize(); i++) { 210 if (sc_list.GetContextAtIndex(i, sc)) { 211 ConstString func_name = sc.GetFunctionName(Mangled::ePreferDemangled); 212 // Ensure that the function name matches the regex. This is more than 213 // a sanity check. It is possible that the demangled function name 214 // does not start with the prefix, for example when it's in an 215 // anonymous namespace. 216 if (!func_name.IsEmpty() && m_regex.Execute(func_name.GetStringRef())) 217 m_match_set.insert(func_name); 218 } 219 } 220 } 221 return Searcher::eCallbackReturnContinue; 222 } 223 224 void DoCompletion(SearchFilter *filter) override { 225 filter->Search(*this); 226 collection::iterator pos = m_match_set.begin(), end = m_match_set.end(); 227 for (pos = m_match_set.begin(); pos != end; pos++) 228 m_request.AddCompletion((*pos).GetCString()); 229 } 230 231 private: 232 RegularExpression m_regex; 233 typedef std::set<ConstString> collection; 234 collection m_match_set; 235 236 SymbolCompleter(const SymbolCompleter &) = delete; 237 const SymbolCompleter &operator=(const SymbolCompleter &) = delete; 238 }; 239 } // namespace 240 241 namespace { 242 class ModuleCompleter : public Completer { 243 public: 244 ModuleCompleter(CommandInterpreter &interpreter, CompletionRequest &request) 245 : Completer(interpreter, request) { 246 FileSpec partial_spec(m_request.GetCursorArgumentPrefix()); 247 m_file_name = partial_spec.GetFilename().GetCString(); 248 m_dir_name = partial_spec.GetDirectory().GetCString(); 249 } 250 251 lldb::SearchDepth GetDepth() override { return lldb::eSearchDepthModule; } 252 253 Searcher::CallbackReturn SearchCallback(SearchFilter &filter, 254 SymbolContext &context, 255 Address *addr) override { 256 if (context.module_sp) { 257 const char *cur_file_name = 258 context.module_sp->GetFileSpec().GetFilename().GetCString(); 259 const char *cur_dir_name = 260 context.module_sp->GetFileSpec().GetDirectory().GetCString(); 261 262 bool match = false; 263 if (m_file_name && cur_file_name && 264 strstr(cur_file_name, m_file_name) == cur_file_name) 265 match = true; 266 267 if (match && m_dir_name && cur_dir_name && 268 strstr(cur_dir_name, m_dir_name) != cur_dir_name) 269 match = false; 270 271 if (match) { 272 m_request.AddCompletion(cur_file_name); 273 } 274 } 275 return Searcher::eCallbackReturnContinue; 276 } 277 278 void DoCompletion(SearchFilter *filter) override { filter->Search(*this); } 279 280 private: 281 const char *m_file_name; 282 const char *m_dir_name; 283 284 ModuleCompleter(const ModuleCompleter &) = delete; 285 const ModuleCompleter &operator=(const ModuleCompleter &) = delete; 286 }; 287 } // namespace 288 289 void CommandCompletions::SourceFiles(CommandInterpreter &interpreter, 290 CompletionRequest &request, 291 SearchFilter *searcher) { 292 SourceFileCompleter completer(interpreter, request); 293 294 if (searcher == nullptr) { 295 lldb::TargetSP target_sp = interpreter.GetDebugger().GetSelectedTarget(); 296 SearchFilterForUnconstrainedSearches null_searcher(target_sp); 297 completer.DoCompletion(&null_searcher); 298 } else { 299 completer.DoCompletion(searcher); 300 } 301 } 302 303 static void DiskFilesOrDirectories(const llvm::Twine &partial_name, 304 bool only_directories, 305 CompletionRequest &request, 306 TildeExpressionResolver &Resolver) { 307 llvm::SmallString<256> CompletionBuffer; 308 llvm::SmallString<256> Storage; 309 partial_name.toVector(CompletionBuffer); 310 311 if (CompletionBuffer.size() >= PATH_MAX) 312 return; 313 314 namespace path = llvm::sys::path; 315 316 llvm::StringRef SearchDir; 317 llvm::StringRef PartialItem; 318 319 if (CompletionBuffer.startswith("~")) { 320 llvm::StringRef Buffer(CompletionBuffer); 321 size_t FirstSep = 322 Buffer.find_if([](char c) { return path::is_separator(c); }); 323 324 llvm::StringRef Username = Buffer.take_front(FirstSep); 325 llvm::StringRef Remainder; 326 if (FirstSep != llvm::StringRef::npos) 327 Remainder = Buffer.drop_front(FirstSep + 1); 328 329 llvm::SmallString<256> Resolved; 330 if (!Resolver.ResolveExact(Username, Resolved)) { 331 // We couldn't resolve it as a full username. If there were no slashes 332 // then this might be a partial username. We try to resolve it as such 333 // but after that, we're done regardless of any matches. 334 if (FirstSep == llvm::StringRef::npos) { 335 llvm::StringSet<> MatchSet; 336 Resolver.ResolvePartial(Username, MatchSet); 337 for (const auto &S : MatchSet) { 338 Resolved = S.getKey(); 339 path::append(Resolved, path::get_separator()); 340 request.AddCompletion(Resolved, "", CompletionMode::Partial); 341 } 342 } 343 return; 344 } 345 346 // If there was no trailing slash, then we're done as soon as we resolve 347 // the expression to the correct directory. Otherwise we need to continue 348 // looking for matches within that directory. 349 if (FirstSep == llvm::StringRef::npos) { 350 // Make sure it ends with a separator. 351 path::append(CompletionBuffer, path::get_separator()); 352 request.AddCompletion(CompletionBuffer, "", CompletionMode::Partial); 353 return; 354 } 355 356 // We want to keep the form the user typed, so we special case this to 357 // search in the fully resolved directory, but CompletionBuffer keeps the 358 // unmodified form that the user typed. 359 Storage = Resolved; 360 llvm::StringRef RemainderDir = path::parent_path(Remainder); 361 if (!RemainderDir.empty()) { 362 // Append the remaining path to the resolved directory. 363 Storage.append(path::get_separator()); 364 Storage.append(RemainderDir); 365 } 366 SearchDir = Storage; 367 } else { 368 SearchDir = path::parent_path(CompletionBuffer); 369 } 370 371 size_t FullPrefixLen = CompletionBuffer.size(); 372 373 PartialItem = path::filename(CompletionBuffer); 374 375 // path::filename() will return "." when the passed path ends with a 376 // directory separator. We have to filter those out, but only when the 377 // "." doesn't come from the completion request itself. 378 if (PartialItem == "." && path::is_separator(CompletionBuffer.back())) 379 PartialItem = llvm::StringRef(); 380 381 if (SearchDir.empty()) { 382 llvm::sys::fs::current_path(Storage); 383 SearchDir = Storage; 384 } 385 assert(!PartialItem.contains(path::get_separator())); 386 387 // SearchDir now contains the directory to search in, and Prefix contains the 388 // text we want to match against items in that directory. 389 390 FileSystem &fs = FileSystem::Instance(); 391 std::error_code EC; 392 llvm::vfs::directory_iterator Iter = fs.DirBegin(SearchDir, EC); 393 llvm::vfs::directory_iterator End; 394 for (; Iter != End && !EC; Iter.increment(EC)) { 395 auto &Entry = *Iter; 396 llvm::ErrorOr<llvm::vfs::Status> Status = fs.GetStatus(Entry.path()); 397 398 if (!Status) 399 continue; 400 401 auto Name = path::filename(Entry.path()); 402 403 // Omit ".", ".." 404 if (Name == "." || Name == ".." || !Name.startswith(PartialItem)) 405 continue; 406 407 bool is_dir = Status->isDirectory(); 408 409 // If it's a symlink, then we treat it as a directory as long as the target 410 // is a directory. 411 if (Status->isSymlink()) { 412 FileSpec symlink_filespec(Entry.path()); 413 FileSpec resolved_filespec; 414 auto error = fs.ResolveSymbolicLink(symlink_filespec, resolved_filespec); 415 if (error.Success()) 416 is_dir = fs.IsDirectory(symlink_filespec); 417 } 418 419 if (only_directories && !is_dir) 420 continue; 421 422 // Shrink it back down so that it just has the original prefix the user 423 // typed and remove the part of the name which is common to the located 424 // item and what the user typed. 425 CompletionBuffer.resize(FullPrefixLen); 426 Name = Name.drop_front(PartialItem.size()); 427 CompletionBuffer.append(Name); 428 429 if (is_dir) { 430 path::append(CompletionBuffer, path::get_separator()); 431 } 432 433 CompletionMode mode = 434 is_dir ? CompletionMode::Partial : CompletionMode::Normal; 435 request.AddCompletion(CompletionBuffer, "", mode); 436 } 437 } 438 439 static void DiskFilesOrDirectories(const llvm::Twine &partial_name, 440 bool only_directories, StringList &matches, 441 TildeExpressionResolver &Resolver) { 442 CompletionResult result; 443 std::string partial_name_str = partial_name.str(); 444 CompletionRequest request(partial_name_str, partial_name_str.size(), result); 445 DiskFilesOrDirectories(partial_name, only_directories, request, Resolver); 446 result.GetMatches(matches); 447 } 448 449 static void DiskFilesOrDirectories(CompletionRequest &request, 450 bool only_directories) { 451 StandardTildeExpressionResolver resolver; 452 DiskFilesOrDirectories(request.GetCursorArgumentPrefix(), only_directories, 453 request, resolver); 454 } 455 456 void CommandCompletions::DiskFiles(CommandInterpreter &interpreter, 457 CompletionRequest &request, 458 SearchFilter *searcher) { 459 DiskFilesOrDirectories(request, /*only_dirs*/ false); 460 } 461 462 void CommandCompletions::DiskFiles(const llvm::Twine &partial_file_name, 463 StringList &matches, 464 TildeExpressionResolver &Resolver) { 465 DiskFilesOrDirectories(partial_file_name, false, matches, Resolver); 466 } 467 468 void CommandCompletions::DiskDirectories(CommandInterpreter &interpreter, 469 CompletionRequest &request, 470 SearchFilter *searcher) { 471 DiskFilesOrDirectories(request, /*only_dirs*/ true); 472 } 473 474 void CommandCompletions::DiskDirectories(const llvm::Twine &partial_file_name, 475 StringList &matches, 476 TildeExpressionResolver &Resolver) { 477 DiskFilesOrDirectories(partial_file_name, true, matches, Resolver); 478 } 479 480 void CommandCompletions::Modules(CommandInterpreter &interpreter, 481 CompletionRequest &request, 482 SearchFilter *searcher) { 483 ModuleCompleter completer(interpreter, request); 484 485 if (searcher == nullptr) { 486 lldb::TargetSP target_sp = interpreter.GetDebugger().GetSelectedTarget(); 487 SearchFilterForUnconstrainedSearches null_searcher(target_sp); 488 completer.DoCompletion(&null_searcher); 489 } else { 490 completer.DoCompletion(searcher); 491 } 492 } 493 494 void CommandCompletions::Symbols(CommandInterpreter &interpreter, 495 CompletionRequest &request, 496 SearchFilter *searcher) { 497 SymbolCompleter completer(interpreter, request); 498 499 if (searcher == nullptr) { 500 lldb::TargetSP target_sp = interpreter.GetDebugger().GetSelectedTarget(); 501 SearchFilterForUnconstrainedSearches null_searcher(target_sp); 502 completer.DoCompletion(&null_searcher); 503 } else { 504 completer.DoCompletion(searcher); 505 } 506 } 507 508 void CommandCompletions::SettingsNames(CommandInterpreter &interpreter, 509 CompletionRequest &request, 510 SearchFilter *searcher) { 511 // Cache the full setting name list 512 static StringList g_property_names; 513 if (g_property_names.GetSize() == 0) { 514 // Generate the full setting name list on demand 515 lldb::OptionValuePropertiesSP properties_sp( 516 interpreter.GetDebugger().GetValueProperties()); 517 if (properties_sp) { 518 StreamString strm; 519 properties_sp->DumpValue(nullptr, strm, OptionValue::eDumpOptionName); 520 const std::string &str = std::string(strm.GetString()); 521 g_property_names.SplitIntoLines(str.c_str(), str.size()); 522 } 523 } 524 525 for (const std::string &s : g_property_names) 526 request.TryCompleteCurrentArg(s); 527 } 528 529 void CommandCompletions::PlatformPluginNames(CommandInterpreter &interpreter, 530 CompletionRequest &request, 531 SearchFilter *searcher) { 532 PluginManager::AutoCompletePlatformName(request.GetCursorArgumentPrefix(), 533 request); 534 } 535 536 void CommandCompletions::ArchitectureNames(CommandInterpreter &interpreter, 537 CompletionRequest &request, 538 SearchFilter *searcher) { 539 ArchSpec::AutoComplete(request); 540 } 541 542 void CommandCompletions::VariablePath(CommandInterpreter &interpreter, 543 CompletionRequest &request, 544 SearchFilter *searcher) { 545 Variable::AutoComplete(interpreter.GetExecutionContext(), request); 546 } 547 548 void CommandCompletions::Registers(CommandInterpreter &interpreter, 549 CompletionRequest &request, 550 SearchFilter *searcher) { 551 std::string reg_prefix = ""; 552 if (request.GetCursorArgumentPrefix().startswith("$")) 553 reg_prefix = "$"; 554 555 RegisterContext *reg_ctx = 556 interpreter.GetExecutionContext().GetRegisterContext(); 557 const size_t reg_num = reg_ctx->GetRegisterCount(); 558 for (size_t reg_idx = 0; reg_idx < reg_num; ++reg_idx) { 559 const RegisterInfo *reg_info = reg_ctx->GetRegisterInfoAtIndex(reg_idx); 560 request.TryCompleteCurrentArg(reg_prefix + reg_info->name, 561 reg_info->alt_name); 562 } 563 } 564 565 void CommandCompletions::Breakpoints(CommandInterpreter &interpreter, 566 CompletionRequest &request, 567 SearchFilter *searcher) { 568 lldb::TargetSP target = interpreter.GetDebugger().GetSelectedTarget(); 569 if (!target) 570 return; 571 572 const BreakpointList &breakpoints = target->GetBreakpointList(); 573 574 std::unique_lock<std::recursive_mutex> lock; 575 target->GetBreakpointList().GetListMutex(lock); 576 577 size_t num_breakpoints = breakpoints.GetSize(); 578 if (num_breakpoints == 0) 579 return; 580 581 for (size_t i = 0; i < num_breakpoints; ++i) { 582 lldb::BreakpointSP bp = breakpoints.GetBreakpointAtIndex(i); 583 584 StreamString s; 585 bp->GetDescription(&s, lldb::eDescriptionLevelBrief); 586 llvm::StringRef bp_info = s.GetString(); 587 588 const size_t colon_pos = bp_info.find_first_of(':'); 589 if (colon_pos != llvm::StringRef::npos) 590 bp_info = bp_info.drop_front(colon_pos + 2); 591 592 request.TryCompleteCurrentArg(std::to_string(bp->GetID()), bp_info); 593 } 594 } 595 596 void CommandCompletions::ProcessPluginNames(CommandInterpreter &interpreter, 597 CompletionRequest &request, 598 SearchFilter *searcher) { 599 PluginManager::AutoCompleteProcessName(request.GetCursorArgumentPrefix(), 600 request); 601 } 602 603 void CommandCompletions::DisassemblyFlavors(CommandInterpreter &interpreter, 604 CompletionRequest &request, 605 SearchFilter *searcher) { 606 // Currently the only valid options for disassemble -F are default, and for 607 // Intel architectures, att and intel. 608 static const char *flavors[] = {"default", "att", "intel"}; 609 for (const char *flavor : flavors) { 610 request.TryCompleteCurrentArg(flavor); 611 } 612 } 613 614 void CommandCompletions::TypeLanguages(CommandInterpreter &interpreter, 615 CompletionRequest &request, 616 SearchFilter *searcher) { 617 for (int bit : 618 Language::GetLanguagesSupportingTypeSystems().bitvector.set_bits()) { 619 request.TryCompleteCurrentArg( 620 Language::GetNameForLanguageType(static_cast<lldb::LanguageType>(bit))); 621 } 622 } 623 624 void CommandCompletions::FrameIndexes(CommandInterpreter &interpreter, 625 CompletionRequest &request, 626 SearchFilter *searcher) { 627 const ExecutionContext &exe_ctx = interpreter.GetExecutionContext(); 628 if (!exe_ctx.HasProcessScope()) 629 return; 630 631 lldb::ThreadSP thread_sp = exe_ctx.GetThreadSP(); 632 const uint32_t frame_num = thread_sp->GetStackFrameCount(); 633 for (uint32_t i = 0; i < frame_num; ++i) { 634 lldb::StackFrameSP frame_sp = thread_sp->GetStackFrameAtIndex(i); 635 StreamString strm; 636 frame_sp->Dump(&strm, false, true); 637 request.TryCompleteCurrentArg(std::to_string(i), strm.GetString()); 638 } 639 } 640