1 | //===-- CommandObjectFrame.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 | #include "CommandObjectFrame.h" |
9 | #include "lldb/Core/Debugger.h" |
10 | #include "lldb/Core/ValueObject.h" |
11 | #include "lldb/DataFormatters/DataVisualization.h" |
12 | #include "lldb/DataFormatters/ValueObjectPrinter.h" |
13 | #include "lldb/Host/Config.h" |
14 | #include "lldb/Host/OptionParser.h" |
15 | #include "lldb/Interpreter/CommandInterpreter.h" |
16 | #include "lldb/Interpreter/CommandOptionArgumentTable.h" |
17 | #include "lldb/Interpreter/CommandReturnObject.h" |
18 | #include "lldb/Interpreter/OptionArgParser.h" |
19 | #include "lldb/Interpreter/OptionGroupFormat.h" |
20 | #include "lldb/Interpreter/OptionGroupValueObjectDisplay.h" |
21 | #include "lldb/Interpreter/OptionGroupVariable.h" |
22 | #include "lldb/Interpreter/Options.h" |
23 | #include "lldb/Symbol/Function.h" |
24 | #include "lldb/Symbol/SymbolContext.h" |
25 | #include "lldb/Symbol/Variable.h" |
26 | #include "lldb/Symbol/VariableList.h" |
27 | #include "lldb/Target/StackFrame.h" |
28 | #include "lldb/Target/StackFrameRecognizer.h" |
29 | #include "lldb/Target/StopInfo.h" |
30 | #include "lldb/Target/Target.h" |
31 | #include "lldb/Target/Thread.h" |
32 | #include "lldb/Utility/Args.h" |
33 | |
34 | #include <memory> |
35 | #include <optional> |
36 | #include <string> |
37 | |
38 | using namespace lldb; |
39 | using namespace lldb_private; |
40 | |
41 | #pragma mark CommandObjectFrameDiagnose |
42 | |
43 | // CommandObjectFrameInfo |
44 | |
45 | // CommandObjectFrameDiagnose |
46 | |
47 | #define LLDB_OPTIONS_frame_diag |
48 | #include "CommandOptions.inc" |
49 | |
50 | class CommandObjectFrameDiagnose : public CommandObjectParsed { |
51 | public: |
52 | class CommandOptions : public Options { |
53 | public: |
54 | CommandOptions() { OptionParsingStarting(execution_context: nullptr); } |
55 | |
56 | ~CommandOptions() override = default; |
57 | |
58 | Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, |
59 | ExecutionContext *execution_context) override { |
60 | Status error; |
61 | const int short_option = m_getopt_table[option_idx].val; |
62 | switch (short_option) { |
63 | case 'r': |
64 | reg = ConstString(option_arg); |
65 | break; |
66 | |
67 | case 'a': { |
68 | address.emplace(); |
69 | if (option_arg.getAsInteger(0, *address)) { |
70 | address.reset(); |
71 | error.SetErrorStringWithFormat("invalid address argument '%s'" , |
72 | option_arg.str().c_str()); |
73 | } |
74 | } break; |
75 | |
76 | case 'o': { |
77 | offset.emplace(); |
78 | if (option_arg.getAsInteger(0, *offset)) { |
79 | offset.reset(); |
80 | error.SetErrorStringWithFormat("invalid offset argument '%s'" , |
81 | option_arg.str().c_str()); |
82 | } |
83 | } break; |
84 | |
85 | default: |
86 | llvm_unreachable("Unimplemented option" ); |
87 | } |
88 | |
89 | return error; |
90 | } |
91 | |
92 | void OptionParsingStarting(ExecutionContext *execution_context) override { |
93 | address.reset(); |
94 | reg.reset(); |
95 | offset.reset(); |
96 | } |
97 | |
98 | llvm::ArrayRef<OptionDefinition> GetDefinitions() override { |
99 | return llvm::ArrayRef(g_frame_diag_options); |
100 | } |
101 | |
102 | // Options. |
103 | std::optional<lldb::addr_t> address; |
104 | std::optional<ConstString> reg; |
105 | std::optional<int64_t> offset; |
106 | }; |
107 | |
108 | CommandObjectFrameDiagnose(CommandInterpreter &interpreter) |
109 | : CommandObjectParsed(interpreter, "frame diagnose" , |
110 | "Try to determine what path the current stop " |
111 | "location used to get to a register or address" , |
112 | nullptr, |
113 | eCommandRequiresThread | eCommandTryTargetAPILock | |
114 | eCommandProcessMustBeLaunched | |
115 | eCommandProcessMustBePaused) { |
116 | AddSimpleArgumentList(eArgTypeFrameIndex, eArgRepeatOptional); |
117 | } |
118 | |
119 | ~CommandObjectFrameDiagnose() override = default; |
120 | |
121 | Options *GetOptions() override { return &m_options; } |
122 | |
123 | protected: |
124 | void DoExecute(Args &command, CommandReturnObject &result) override { |
125 | Thread *thread = m_exe_ctx.GetThreadPtr(); |
126 | StackFrameSP frame_sp = thread->GetSelectedFrame(select_most_relevant: SelectMostRelevantFrame); |
127 | |
128 | ValueObjectSP valobj_sp; |
129 | |
130 | if (m_options.address) { |
131 | if (m_options.reg || m_options.offset) { |
132 | result.AppendError( |
133 | in_string: "`frame diagnose --address` is incompatible with other arguments." ); |
134 | return; |
135 | } |
136 | valobj_sp = frame_sp->GuessValueForAddress(*m_options.address); |
137 | } else if (m_options.reg) { |
138 | valobj_sp = frame_sp->GuessValueForRegisterAndOffset( |
139 | *m_options.reg, m_options.offset.value_or(0)); |
140 | } else { |
141 | StopInfoSP stop_info_sp = thread->GetStopInfo(); |
142 | if (!stop_info_sp) { |
143 | result.AppendError(in_string: "No arguments provided, and no stop info." ); |
144 | return; |
145 | } |
146 | |
147 | valobj_sp = StopInfo::GetCrashingDereference(stop_info_sp); |
148 | } |
149 | |
150 | if (!valobj_sp) { |
151 | result.AppendError(in_string: "No diagnosis available." ); |
152 | return; |
153 | } |
154 | |
155 | DumpValueObjectOptions::DeclPrintingHelper helper = |
156 | [&valobj_sp](ConstString type, ConstString var, |
157 | const DumpValueObjectOptions &opts, |
158 | Stream &stream) -> bool { |
159 | const ValueObject::GetExpressionPathFormat format = ValueObject:: |
160 | GetExpressionPathFormat::eGetExpressionPathFormatHonorPointers; |
161 | valobj_sp->GetExpressionPath(s&: stream, format); |
162 | stream.PutCString(cstr: " =" ); |
163 | return true; |
164 | }; |
165 | |
166 | DumpValueObjectOptions options; |
167 | options.SetDeclPrintingHelper(helper); |
168 | // We've already handled the case where the value object sp is null, so |
169 | // this is just to make sure future changes don't skip that: |
170 | assert(valobj_sp.get() && "Must have a valid ValueObject to print" ); |
171 | ValueObjectPrinter printer(*valobj_sp, &result.GetOutputStream(), |
172 | options); |
173 | printer.PrintValueObject(); |
174 | } |
175 | |
176 | CommandOptions m_options; |
177 | }; |
178 | |
179 | #pragma mark CommandObjectFrameInfo |
180 | |
181 | // CommandObjectFrameInfo |
182 | |
183 | class CommandObjectFrameInfo : public CommandObjectParsed { |
184 | public: |
185 | CommandObjectFrameInfo(CommandInterpreter &interpreter) |
186 | : CommandObjectParsed(interpreter, "frame info" , |
187 | "List information about the current " |
188 | "stack frame in the current thread." , |
189 | "frame info" , |
190 | eCommandRequiresFrame | eCommandTryTargetAPILock | |
191 | eCommandProcessMustBeLaunched | |
192 | eCommandProcessMustBePaused) {} |
193 | |
194 | ~CommandObjectFrameInfo() override = default; |
195 | |
196 | protected: |
197 | void DoExecute(Args &command, CommandReturnObject &result) override { |
198 | m_exe_ctx.GetFrameRef().DumpUsingSettingsFormat(strm: &result.GetOutputStream()); |
199 | result.SetStatus(eReturnStatusSuccessFinishResult); |
200 | } |
201 | }; |
202 | |
203 | #pragma mark CommandObjectFrameSelect |
204 | |
205 | // CommandObjectFrameSelect |
206 | |
207 | #define LLDB_OPTIONS_frame_select |
208 | #include "CommandOptions.inc" |
209 | |
210 | class CommandObjectFrameSelect : public CommandObjectParsed { |
211 | public: |
212 | class CommandOptions : public Options { |
213 | public: |
214 | CommandOptions() { OptionParsingStarting(execution_context: nullptr); } |
215 | |
216 | ~CommandOptions() override = default; |
217 | |
218 | Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, |
219 | ExecutionContext *execution_context) override { |
220 | Status error; |
221 | const int short_option = m_getopt_table[option_idx].val; |
222 | switch (short_option) { |
223 | case 'r': { |
224 | int32_t offset = 0; |
225 | if (option_arg.getAsInteger(0, offset) || offset == INT32_MIN) { |
226 | error.SetErrorStringWithFormat("invalid frame offset argument '%s'" , |
227 | option_arg.str().c_str()); |
228 | } else |
229 | relative_frame_offset = offset; |
230 | break; |
231 | } |
232 | |
233 | default: |
234 | llvm_unreachable("Unimplemented option" ); |
235 | } |
236 | |
237 | return error; |
238 | } |
239 | |
240 | void OptionParsingStarting(ExecutionContext *execution_context) override { |
241 | relative_frame_offset.reset(); |
242 | } |
243 | |
244 | llvm::ArrayRef<OptionDefinition> GetDefinitions() override { |
245 | return llvm::ArrayRef(g_frame_select_options); |
246 | } |
247 | |
248 | std::optional<int32_t> relative_frame_offset; |
249 | }; |
250 | |
251 | CommandObjectFrameSelect(CommandInterpreter &interpreter) |
252 | : CommandObjectParsed(interpreter, "frame select" , |
253 | "Select the current stack frame by " |
254 | "index from within the current thread " |
255 | "(see 'thread backtrace'.)" , |
256 | nullptr, |
257 | eCommandRequiresThread | eCommandTryTargetAPILock | |
258 | eCommandProcessMustBeLaunched | |
259 | eCommandProcessMustBePaused) { |
260 | AddSimpleArgumentList(arg_type: eArgTypeFrameIndex, repetition_type: eArgRepeatOptional); |
261 | } |
262 | |
263 | ~CommandObjectFrameSelect() override = default; |
264 | |
265 | Options *GetOptions() override { return &m_options; } |
266 | |
267 | protected: |
268 | void DoExecute(Args &command, CommandReturnObject &result) override { |
269 | // No need to check "thread" for validity as eCommandRequiresThread ensures |
270 | // it is valid |
271 | Thread *thread = m_exe_ctx.GetThreadPtr(); |
272 | |
273 | uint32_t frame_idx = UINT32_MAX; |
274 | if (m_options.relative_frame_offset) { |
275 | // The one and only argument is a signed relative frame index |
276 | frame_idx = thread->GetSelectedFrameIndex(select_most_relevant: SelectMostRelevantFrame); |
277 | if (frame_idx == UINT32_MAX) |
278 | frame_idx = 0; |
279 | |
280 | if (*m_options.relative_frame_offset < 0) { |
281 | if (static_cast<int32_t>(frame_idx) >= |
282 | -*m_options.relative_frame_offset) |
283 | frame_idx += *m_options.relative_frame_offset; |
284 | else { |
285 | if (frame_idx == 0) { |
286 | // If you are already at the bottom of the stack, then just warn |
287 | // and don't reset the frame. |
288 | result.AppendError(in_string: "Already at the bottom of the stack." ); |
289 | return; |
290 | } else |
291 | frame_idx = 0; |
292 | } |
293 | } else if (*m_options.relative_frame_offset > 0) { |
294 | // I don't want "up 20" where "20" takes you past the top of the stack |
295 | // to produce an error, but rather to just go to the top. OTOH, start |
296 | // by seeing if the requested frame exists, in which case we can avoid |
297 | // counting the stack here... |
298 | const uint32_t frame_requested = frame_idx |
299 | + *m_options.relative_frame_offset; |
300 | StackFrameSP frame_sp = thread->GetStackFrameAtIndex(idx: frame_requested); |
301 | if (frame_sp) |
302 | frame_idx = frame_requested; |
303 | else { |
304 | // The request went past the stack, so handle that case: |
305 | const uint32_t num_frames = thread->GetStackFrameCount(); |
306 | if (static_cast<int32_t>(num_frames - frame_idx) > |
307 | *m_options.relative_frame_offset) |
308 | frame_idx += *m_options.relative_frame_offset; |
309 | else { |
310 | if (frame_idx == num_frames - 1) { |
311 | // If we are already at the top of the stack, just warn and don't |
312 | // reset the frame. |
313 | result.AppendError(in_string: "Already at the top of the stack." ); |
314 | return; |
315 | } else |
316 | frame_idx = num_frames - 1; |
317 | } |
318 | } |
319 | } |
320 | } else { |
321 | if (command.GetArgumentCount() > 1) { |
322 | result.AppendErrorWithFormat( |
323 | format: "too many arguments; expected frame-index, saw '%s'.\n" , |
324 | command[0].c_str()); |
325 | m_options.GenerateOptionUsage( |
326 | strm&: result.GetErrorStream(), cmd&: *this, |
327 | screen_width: GetCommandInterpreter().GetDebugger().GetTerminalWidth()); |
328 | return; |
329 | } |
330 | |
331 | if (command.GetArgumentCount() == 1) { |
332 | if (command[0].ref().getAsInteger(0, frame_idx)) { |
333 | result.AppendErrorWithFormat(format: "invalid frame index argument '%s'." , |
334 | command[0].c_str()); |
335 | return; |
336 | } |
337 | } else if (command.GetArgumentCount() == 0) { |
338 | frame_idx = thread->GetSelectedFrameIndex(select_most_relevant: SelectMostRelevantFrame); |
339 | if (frame_idx == UINT32_MAX) { |
340 | frame_idx = 0; |
341 | } |
342 | } |
343 | } |
344 | |
345 | bool success = thread->SetSelectedFrameByIndexNoisily( |
346 | frame_idx, output_stream&: result.GetOutputStream()); |
347 | if (success) { |
348 | m_exe_ctx.SetFrameSP(thread->GetSelectedFrame(select_most_relevant: SelectMostRelevantFrame)); |
349 | result.SetStatus(eReturnStatusSuccessFinishResult); |
350 | } else { |
351 | result.AppendErrorWithFormat(format: "Frame index (%u) out of range.\n" , |
352 | frame_idx); |
353 | } |
354 | } |
355 | |
356 | CommandOptions m_options; |
357 | }; |
358 | |
359 | #pragma mark CommandObjectFrameVariable |
360 | // List images with associated information |
361 | class CommandObjectFrameVariable : public CommandObjectParsed { |
362 | public: |
363 | CommandObjectFrameVariable(CommandInterpreter &interpreter) |
364 | : CommandObjectParsed( |
365 | interpreter, "frame variable" , |
366 | "Show variables for the current stack frame. Defaults to all " |
367 | "arguments and local variables in scope. Names of argument, " |
368 | "local, file static and file global variables can be specified." , |
369 | nullptr, |
370 | eCommandRequiresFrame | eCommandTryTargetAPILock | |
371 | eCommandProcessMustBeLaunched | eCommandProcessMustBePaused | |
372 | eCommandRequiresProcess), |
373 | m_option_variable( |
374 | true), // Include the frame specific options by passing "true" |
375 | m_option_format(eFormatDefault) { |
376 | SetHelpLong(R"( |
377 | Children of aggregate variables can be specified such as 'var->child.x'. In |
378 | 'frame variable', the operators -> and [] do not invoke operator overloads if |
379 | they exist, but directly access the specified element. If you want to trigger |
380 | operator overloads use the expression command to print the variable instead. |
381 | |
382 | It is worth noting that except for overloaded operators, when printing local |
383 | variables 'expr local_var' and 'frame var local_var' produce the same results. |
384 | However, 'frame variable' is more efficient, since it uses debug information and |
385 | memory reads directly, rather than parsing and evaluating an expression, which |
386 | may even involve JITing and running code in the target program.)" ); |
387 | |
388 | AddSimpleArgumentList(arg_type: eArgTypeVarName, repetition_type: eArgRepeatStar); |
389 | |
390 | m_option_group.Append(group: &m_option_variable, LLDB_OPT_SET_ALL, LLDB_OPT_SET_1); |
391 | m_option_group.Append(group: &m_option_format, |
392 | src_mask: OptionGroupFormat::OPTION_GROUP_FORMAT | |
393 | OptionGroupFormat::OPTION_GROUP_GDB_FMT, |
394 | LLDB_OPT_SET_1); |
395 | m_option_group.Append(group: &m_varobj_options, LLDB_OPT_SET_ALL, LLDB_OPT_SET_1); |
396 | m_option_group.Finalize(); |
397 | } |
398 | |
399 | ~CommandObjectFrameVariable() override = default; |
400 | |
401 | Options *GetOptions() override { return &m_option_group; } |
402 | |
403 | protected: |
404 | llvm::StringRef GetScopeString(VariableSP var_sp) { |
405 | if (!var_sp) |
406 | return llvm::StringRef(); |
407 | |
408 | switch (var_sp->GetScope()) { |
409 | case eValueTypeVariableGlobal: |
410 | return "GLOBAL: " ; |
411 | case eValueTypeVariableStatic: |
412 | return "STATIC: " ; |
413 | case eValueTypeVariableArgument: |
414 | return "ARG: " ; |
415 | case eValueTypeVariableLocal: |
416 | return "LOCAL: " ; |
417 | case eValueTypeVariableThreadLocal: |
418 | return "THREAD: " ; |
419 | default: |
420 | break; |
421 | } |
422 | |
423 | return llvm::StringRef(); |
424 | } |
425 | |
426 | /// Returns true if `scope` matches any of the options in `m_option_variable`. |
427 | bool ScopeRequested(lldb::ValueType scope) { |
428 | switch (scope) { |
429 | case eValueTypeVariableGlobal: |
430 | case eValueTypeVariableStatic: |
431 | return m_option_variable.show_globals; |
432 | case eValueTypeVariableArgument: |
433 | return m_option_variable.show_args; |
434 | case eValueTypeVariableLocal: |
435 | return m_option_variable.show_locals; |
436 | case eValueTypeInvalid: |
437 | case eValueTypeRegister: |
438 | case eValueTypeRegisterSet: |
439 | case eValueTypeConstResult: |
440 | case eValueTypeVariableThreadLocal: |
441 | case eValueTypeVTable: |
442 | case eValueTypeVTableEntry: |
443 | return false; |
444 | } |
445 | llvm_unreachable("Unexpected scope value" ); |
446 | } |
447 | |
448 | /// Finds all the variables in `all_variables` whose name matches `regex`, |
449 | /// inserting them into `matches`. Variables already contained in `matches` |
450 | /// are not inserted again. |
451 | /// Nullopt is returned in case of no matches. |
452 | /// A sub-range of `matches` with all newly inserted variables is returned. |
453 | /// This may be empty if all matches were already contained in `matches`. |
454 | std::optional<llvm::ArrayRef<VariableSP>> |
455 | findUniqueRegexMatches(RegularExpression ®ex, |
456 | VariableList &matches, |
457 | const VariableList &all_variables) { |
458 | bool any_matches = false; |
459 | const size_t previous_num_vars = matches.GetSize(); |
460 | |
461 | for (const VariableSP &var : all_variables) { |
462 | if (!var->NameMatches(regex) || !ScopeRequested(scope: var->GetScope())) |
463 | continue; |
464 | any_matches = true; |
465 | matches.AddVariableIfUnique(var_sp: var); |
466 | } |
467 | |
468 | if (any_matches) |
469 | return matches.toArrayRef().drop_front(N: previous_num_vars); |
470 | return std::nullopt; |
471 | } |
472 | |
473 | void DoExecute(Args &command, CommandReturnObject &result) override { |
474 | // No need to check "frame" for validity as eCommandRequiresFrame ensures |
475 | // it is valid |
476 | StackFrame *frame = m_exe_ctx.GetFramePtr(); |
477 | |
478 | Stream &s = result.GetOutputStream(); |
479 | |
480 | // Using a regex should behave like looking for an exact name match: it |
481 | // also finds globals. |
482 | m_option_variable.show_globals |= m_option_variable.use_regex; |
483 | |
484 | // Be careful about the stack frame, if any summary formatter runs code, it |
485 | // might clear the StackFrameList for the thread. So hold onto a shared |
486 | // pointer to the frame so it stays alive. |
487 | |
488 | Status error; |
489 | VariableList *variable_list = |
490 | frame->GetVariableList(get_file_globals: m_option_variable.show_globals, error_ptr: &error); |
491 | |
492 | if (error.Fail() && (!variable_list || variable_list->GetSize() == 0)) { |
493 | result.AppendError(in_string: error.AsCString()); |
494 | |
495 | } |
496 | ValueObjectSP valobj_sp; |
497 | |
498 | TypeSummaryImplSP summary_format_sp; |
499 | if (!m_option_variable.summary.IsCurrentValueEmpty()) |
500 | DataVisualization::NamedSummaryFormats::GetSummaryFormat( |
501 | type: ConstString(m_option_variable.summary.GetCurrentValue()), |
502 | entry&: summary_format_sp); |
503 | else if (!m_option_variable.summary_string.IsCurrentValueEmpty()) |
504 | summary_format_sp = std::make_shared<StringSummaryFormat>( |
505 | args: TypeSummaryImpl::Flags(), |
506 | args: m_option_variable.summary_string.GetCurrentValue()); |
507 | |
508 | DumpValueObjectOptions options(m_varobj_options.GetAsDumpOptions( |
509 | lang_descr_verbosity: eLanguageRuntimeDescriptionDisplayVerbosityFull, format: eFormatDefault, |
510 | summary_sp: summary_format_sp)); |
511 | |
512 | const SymbolContext &sym_ctx = |
513 | frame->GetSymbolContext(resolve_scope: eSymbolContextFunction); |
514 | if (sym_ctx.function && sym_ctx.function->IsTopLevelFunction()) |
515 | m_option_variable.show_globals = true; |
516 | |
517 | if (variable_list) { |
518 | const Format format = m_option_format.GetFormat(); |
519 | options.SetFormat(format); |
520 | |
521 | if (!command.empty()) { |
522 | VariableList regex_var_list; |
523 | |
524 | // If we have any args to the variable command, we will make variable |
525 | // objects from them... |
526 | for (auto &entry : command) { |
527 | if (m_option_variable.use_regex) { |
528 | llvm::StringRef name_str = entry.ref(); |
529 | RegularExpression regex(name_str); |
530 | if (regex.IsValid()) { |
531 | std::optional<llvm::ArrayRef<VariableSP>> results = |
532 | findUniqueRegexMatches(regex, matches&: regex_var_list, all_variables: *variable_list); |
533 | if (!results) { |
534 | result.AppendErrorWithFormat( |
535 | format: "no variables matched the regular expression '%s'." , |
536 | entry.c_str()); |
537 | continue; |
538 | } |
539 | for (const VariableSP &var_sp : *results) { |
540 | valobj_sp = frame->GetValueObjectForFrameVariable( |
541 | variable_sp: var_sp, use_dynamic: m_varobj_options.use_dynamic); |
542 | if (valobj_sp) { |
543 | std::string scope_string; |
544 | if (m_option_variable.show_scope) |
545 | scope_string = GetScopeString(var_sp).str(); |
546 | |
547 | if (!scope_string.empty()) |
548 | s.PutCString(cstr: scope_string); |
549 | |
550 | if (m_option_variable.show_decl && |
551 | var_sp->GetDeclaration().GetFile()) { |
552 | bool show_fullpaths = false; |
553 | bool show_module = true; |
554 | if (var_sp->DumpDeclaration(s: &s, show_fullpaths, |
555 | show_module)) |
556 | s.PutCString(cstr: ": " ); |
557 | } |
558 | valobj_sp->Dump(s&: result.GetOutputStream(), options); |
559 | } |
560 | } |
561 | } else { |
562 | if (llvm::Error err = regex.GetError()) |
563 | result.AppendError(in_string: llvm::toString(E: std::move(err))); |
564 | else |
565 | result.AppendErrorWithFormat( |
566 | format: "unknown regex error when compiling '%s'" , entry.c_str()); |
567 | } |
568 | } else // No regex, either exact variable names or variable |
569 | // expressions. |
570 | { |
571 | Status error; |
572 | uint32_t expr_path_options = |
573 | StackFrame::eExpressionPathOptionCheckPtrVsMember | |
574 | StackFrame::eExpressionPathOptionsAllowDirectIVarAccess | |
575 | StackFrame::eExpressionPathOptionsInspectAnonymousUnions; |
576 | lldb::VariableSP var_sp; |
577 | valobj_sp = frame->GetValueForVariableExpressionPath( |
578 | var_expr: entry.ref(), use_dynamic: m_varobj_options.use_dynamic, options: expr_path_options, |
579 | var_sp, error); |
580 | if (valobj_sp) { |
581 | std::string scope_string; |
582 | if (m_option_variable.show_scope) |
583 | scope_string = GetScopeString(var_sp).str(); |
584 | |
585 | if (!scope_string.empty()) |
586 | s.PutCString(cstr: scope_string); |
587 | if (m_option_variable.show_decl && var_sp && |
588 | var_sp->GetDeclaration().GetFile()) { |
589 | var_sp->GetDeclaration().DumpStopContext(s: &s, show_fullpaths: false); |
590 | s.PutCString(cstr: ": " ); |
591 | } |
592 | |
593 | options.SetFormat(format); |
594 | options.SetVariableFormatDisplayLanguage( |
595 | valobj_sp->GetPreferredDisplayLanguage()); |
596 | |
597 | Stream &output_stream = result.GetOutputStream(); |
598 | options.SetRootValueObjectName( |
599 | valobj_sp->GetParent() ? entry.c_str() : nullptr); |
600 | valobj_sp->Dump(s&: output_stream, options); |
601 | } else { |
602 | if (auto error_cstr = error.AsCString(default_error_str: nullptr)) |
603 | result.AppendError(in_string: error_cstr); |
604 | else |
605 | result.AppendErrorWithFormat( |
606 | format: "unable to find any variable expression path that matches " |
607 | "'%s'." , |
608 | entry.c_str()); |
609 | } |
610 | } |
611 | } |
612 | } else // No command arg specified. Use variable_list, instead. |
613 | { |
614 | const size_t num_variables = variable_list->GetSize(); |
615 | if (num_variables > 0) { |
616 | for (size_t i = 0; i < num_variables; i++) { |
617 | VariableSP var_sp = variable_list->GetVariableAtIndex(idx: i); |
618 | if (!ScopeRequested(scope: var_sp->GetScope())) |
619 | continue; |
620 | std::string scope_string; |
621 | if (m_option_variable.show_scope) |
622 | scope_string = GetScopeString(var_sp).str(); |
623 | |
624 | // Use the variable object code to make sure we are using the same |
625 | // APIs as the public API will be using... |
626 | valobj_sp = frame->GetValueObjectForFrameVariable( |
627 | variable_sp: var_sp, use_dynamic: m_varobj_options.use_dynamic); |
628 | if (valobj_sp) { |
629 | // When dumping all variables, don't print any variables that are |
630 | // not in scope to avoid extra unneeded output |
631 | if (valobj_sp->IsInScope()) { |
632 | if (!valobj_sp->GetTargetSP() |
633 | ->GetDisplayRuntimeSupportValues() && |
634 | valobj_sp->IsRuntimeSupportValue()) |
635 | continue; |
636 | |
637 | if (!scope_string.empty()) |
638 | s.PutCString(cstr: scope_string); |
639 | |
640 | if (m_option_variable.show_decl && |
641 | var_sp->GetDeclaration().GetFile()) { |
642 | var_sp->GetDeclaration().DumpStopContext(s: &s, show_fullpaths: false); |
643 | s.PutCString(cstr: ": " ); |
644 | } |
645 | |
646 | options.SetFormat(format); |
647 | options.SetVariableFormatDisplayLanguage( |
648 | valobj_sp->GetPreferredDisplayLanguage()); |
649 | options.SetRootValueObjectName( |
650 | var_sp ? var_sp->GetName().AsCString() : nullptr); |
651 | valobj_sp->Dump(s&: result.GetOutputStream(), options); |
652 | } |
653 | } |
654 | } |
655 | } |
656 | } |
657 | if (result.GetStatus() != eReturnStatusFailed) |
658 | result.SetStatus(eReturnStatusSuccessFinishResult); |
659 | } |
660 | |
661 | if (m_option_variable.show_recognized_args) { |
662 | auto recognized_frame = frame->GetRecognizedFrame(); |
663 | if (recognized_frame) { |
664 | ValueObjectListSP recognized_arg_list = |
665 | recognized_frame->GetRecognizedArguments(); |
666 | if (recognized_arg_list) { |
667 | for (auto &rec_value_sp : recognized_arg_list->GetObjects()) { |
668 | options.SetFormat(m_option_format.GetFormat()); |
669 | options.SetVariableFormatDisplayLanguage( |
670 | rec_value_sp->GetPreferredDisplayLanguage()); |
671 | options.SetRootValueObjectName(rec_value_sp->GetName().AsCString()); |
672 | rec_value_sp->Dump(s&: result.GetOutputStream(), options); |
673 | } |
674 | } |
675 | } |
676 | } |
677 | |
678 | m_interpreter.PrintWarningsIfNecessary(s&: result.GetOutputStream(), |
679 | cmd_name: m_cmd_name); |
680 | |
681 | // Increment statistics. |
682 | TargetStats &target_stats = GetSelectedOrDummyTarget().GetStatistics(); |
683 | if (result.Succeeded()) |
684 | target_stats.GetFrameVariableStats().NotifySuccess(); |
685 | else |
686 | target_stats.GetFrameVariableStats().NotifyFailure(); |
687 | } |
688 | |
689 | OptionGroupOptions m_option_group; |
690 | OptionGroupVariable m_option_variable; |
691 | OptionGroupFormat m_option_format; |
692 | OptionGroupValueObjectDisplay m_varobj_options; |
693 | }; |
694 | |
695 | #pragma mark CommandObjectFrameRecognizer |
696 | |
697 | #define LLDB_OPTIONS_frame_recognizer_add |
698 | #include "CommandOptions.inc" |
699 | |
700 | class CommandObjectFrameRecognizerAdd : public CommandObjectParsed { |
701 | private: |
702 | class CommandOptions : public Options { |
703 | public: |
704 | CommandOptions() = default; |
705 | ~CommandOptions() override = default; |
706 | |
707 | Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, |
708 | ExecutionContext *execution_context) override { |
709 | Status error; |
710 | const int short_option = m_getopt_table[option_idx].val; |
711 | |
712 | switch (short_option) { |
713 | case 'f': { |
714 | bool value, success; |
715 | value = OptionArgParser::ToBoolean(s: option_arg, fail_value: true, success_ptr: &success); |
716 | if (success) { |
717 | m_first_instruction_only = value; |
718 | } else { |
719 | error.SetErrorStringWithFormat( |
720 | "invalid boolean value '%s' passed for -f option" , |
721 | option_arg.str().c_str()); |
722 | } |
723 | } break; |
724 | case 'l': |
725 | m_class_name = std::string(option_arg); |
726 | break; |
727 | case 's': |
728 | m_module = std::string(option_arg); |
729 | break; |
730 | case 'n': |
731 | m_symbols.push_back(std::string(option_arg)); |
732 | break; |
733 | case 'x': |
734 | m_regex = true; |
735 | break; |
736 | default: |
737 | llvm_unreachable("Unimplemented option" ); |
738 | } |
739 | |
740 | return error; |
741 | } |
742 | |
743 | void OptionParsingStarting(ExecutionContext *execution_context) override { |
744 | m_module = "" ; |
745 | m_symbols.clear(); |
746 | m_class_name = "" ; |
747 | m_regex = false; |
748 | m_first_instruction_only = true; |
749 | } |
750 | |
751 | llvm::ArrayRef<OptionDefinition> GetDefinitions() override { |
752 | return llvm::ArrayRef(g_frame_recognizer_add_options); |
753 | } |
754 | |
755 | // Instance variables to hold the values for command options. |
756 | std::string m_class_name; |
757 | std::string m_module; |
758 | std::vector<std::string> m_symbols; |
759 | bool m_regex; |
760 | bool m_first_instruction_only; |
761 | }; |
762 | |
763 | CommandOptions m_options; |
764 | |
765 | Options *GetOptions() override { return &m_options; } |
766 | |
767 | protected: |
768 | void DoExecute(Args &command, CommandReturnObject &result) override; |
769 | |
770 | public: |
771 | CommandObjectFrameRecognizerAdd(CommandInterpreter &interpreter) |
772 | : CommandObjectParsed(interpreter, "frame recognizer add" , |
773 | "Add a new frame recognizer." , nullptr) { |
774 | SetHelpLong(R"( |
775 | Frame recognizers allow for retrieving information about special frames based on |
776 | ABI, arguments or other special properties of that frame, even without source |
777 | code or debug info. Currently, one use case is to extract function arguments |
778 | that would otherwise be unaccesible, or augment existing arguments. |
779 | |
780 | Adding a custom frame recognizer is possible by implementing a Python class |
781 | and using the 'frame recognizer add' command. The Python class should have a |
782 | 'get_recognized_arguments' method and it will receive an argument of type |
783 | lldb.SBFrame representing the current frame that we are trying to recognize. |
784 | The method should return a (possibly empty) list of lldb.SBValue objects that |
785 | represent the recognized arguments. |
786 | |
787 | An example of a recognizer that retrieves the file descriptor values from libc |
788 | functions 'read', 'write' and 'close' follows: |
789 | |
790 | class LibcFdRecognizer(object): |
791 | def get_recognized_arguments(self, frame): |
792 | if frame.name in ["read", "write", "close"]: |
793 | fd = frame.EvaluateExpression("$arg1").unsigned |
794 | target = frame.thread.process.target |
795 | value = target.CreateValueFromExpression("fd", "(int)%d" % fd) |
796 | return [value] |
797 | return [] |
798 | |
799 | The file containing this implementation can be imported via 'command script |
800 | import' and then we can register this recognizer with 'frame recognizer add'. |
801 | It's important to restrict the recognizer to the libc library (which is |
802 | libsystem_kernel.dylib on macOS) to avoid matching functions with the same name |
803 | in other modules: |
804 | |
805 | (lldb) command script import .../fd_recognizer.py |
806 | (lldb) frame recognizer add -l fd_recognizer.LibcFdRecognizer -n read -s libsystem_kernel.dylib |
807 | |
808 | When the program is stopped at the beginning of the 'read' function in libc, we |
809 | can view the recognizer arguments in 'frame variable': |
810 | |
811 | (lldb) b read |
812 | (lldb) r |
813 | Process 1234 stopped |
814 | * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.3 |
815 | frame #0: 0x00007fff06013ca0 libsystem_kernel.dylib`read |
816 | (lldb) frame variable |
817 | (int) fd = 3 |
818 | |
819 | )" ); |
820 | } |
821 | ~CommandObjectFrameRecognizerAdd() override = default; |
822 | }; |
823 | |
824 | void CommandObjectFrameRecognizerAdd::DoExecute(Args &command, |
825 | CommandReturnObject &result) { |
826 | #if LLDB_ENABLE_PYTHON |
827 | if (m_options.m_class_name.empty()) { |
828 | result.AppendErrorWithFormat( |
829 | format: "%s needs a Python class name (-l argument).\n" , m_cmd_name.c_str()); |
830 | return; |
831 | } |
832 | |
833 | if (m_options.m_module.empty()) { |
834 | result.AppendErrorWithFormat(format: "%s needs a module name (-s argument).\n" , |
835 | m_cmd_name.c_str()); |
836 | return; |
837 | } |
838 | |
839 | if (m_options.m_symbols.empty()) { |
840 | result.AppendErrorWithFormat( |
841 | format: "%s needs at least one symbol name (-n argument).\n" , |
842 | m_cmd_name.c_str()); |
843 | return; |
844 | } |
845 | |
846 | if (m_options.m_regex && m_options.m_symbols.size() > 1) { |
847 | result.AppendErrorWithFormat( |
848 | format: "%s needs only one symbol regular expression (-n argument).\n" , |
849 | m_cmd_name.c_str()); |
850 | return; |
851 | } |
852 | |
853 | ScriptInterpreter *interpreter = GetDebugger().GetScriptInterpreter(); |
854 | |
855 | if (interpreter && |
856 | !interpreter->CheckObjectExists(m_options.m_class_name.c_str())) { |
857 | result.AppendWarning(in_string: "The provided class does not exist - please define it " |
858 | "before attempting to use this frame recognizer" ); |
859 | } |
860 | |
861 | StackFrameRecognizerSP recognizer_sp = |
862 | StackFrameRecognizerSP(new ScriptedStackFrameRecognizer( |
863 | interpreter, m_options.m_class_name.c_str())); |
864 | if (m_options.m_regex) { |
865 | auto module = |
866 | RegularExpressionSP(new RegularExpression(m_options.m_module)); |
867 | auto func = |
868 | RegularExpressionSP(new RegularExpression(m_options.m_symbols.front())); |
869 | GetSelectedOrDummyTarget().GetFrameRecognizerManager().AddRecognizer( |
870 | recognizer_sp, module, func, m_options.m_first_instruction_only); |
871 | } else { |
872 | auto module = ConstString(m_options.m_module); |
873 | std::vector<ConstString> symbols(m_options.m_symbols.begin(), |
874 | m_options.m_symbols.end()); |
875 | GetSelectedOrDummyTarget().GetFrameRecognizerManager().AddRecognizer( |
876 | recognizer_sp, module, symbols, m_options.m_first_instruction_only); |
877 | } |
878 | #endif |
879 | |
880 | result.SetStatus(eReturnStatusSuccessFinishNoResult); |
881 | } |
882 | |
883 | class CommandObjectFrameRecognizerClear : public CommandObjectParsed { |
884 | public: |
885 | CommandObjectFrameRecognizerClear(CommandInterpreter &interpreter) |
886 | : CommandObjectParsed(interpreter, "frame recognizer clear" , |
887 | "Delete all frame recognizers." , nullptr) {} |
888 | |
889 | ~CommandObjectFrameRecognizerClear() override = default; |
890 | |
891 | protected: |
892 | void DoExecute(Args &command, CommandReturnObject &result) override { |
893 | GetSelectedOrDummyTarget() |
894 | .GetFrameRecognizerManager() |
895 | .RemoveAllRecognizers(); |
896 | result.SetStatus(eReturnStatusSuccessFinishResult); |
897 | } |
898 | }; |
899 | |
900 | class CommandObjectFrameRecognizerDelete : public CommandObjectParsed { |
901 | public: |
902 | CommandObjectFrameRecognizerDelete(CommandInterpreter &interpreter) |
903 | : CommandObjectParsed(interpreter, "frame recognizer delete" , |
904 | "Delete an existing frame recognizer by id." , |
905 | nullptr) { |
906 | AddSimpleArgumentList(arg_type: eArgTypeRecognizerID); |
907 | } |
908 | |
909 | ~CommandObjectFrameRecognizerDelete() override = default; |
910 | |
911 | void |
912 | HandleArgumentCompletion(CompletionRequest &request, |
913 | OptionElementVector &opt_element_vector) override { |
914 | if (request.GetCursorIndex() != 0) |
915 | return; |
916 | |
917 | GetSelectedOrDummyTarget().GetFrameRecognizerManager().ForEach( |
918 | callback: [&request](uint32_t rid, std::string rname, std::string module, |
919 | llvm::ArrayRef<lldb_private::ConstString> symbols, |
920 | bool regexp) { |
921 | StreamString strm; |
922 | if (rname.empty()) |
923 | rname = "(internal)" ; |
924 | |
925 | strm << rname; |
926 | if (!module.empty()) |
927 | strm << ", module " << module; |
928 | if (!symbols.empty()) |
929 | for (auto &symbol : symbols) |
930 | strm << ", symbol " << symbol; |
931 | if (regexp) |
932 | strm << " (regexp)" ; |
933 | |
934 | request.TryCompleteCurrentArg(completion: std::to_string(val: rid), description: strm.GetString()); |
935 | }); |
936 | } |
937 | |
938 | protected: |
939 | void DoExecute(Args &command, CommandReturnObject &result) override { |
940 | if (command.GetArgumentCount() == 0) { |
941 | if (!m_interpreter.Confirm( |
942 | message: "About to delete all frame recognizers, do you want to do that?" , |
943 | default_answer: true)) { |
944 | result.AppendMessage(in_string: "Operation cancelled..." ); |
945 | return; |
946 | } |
947 | |
948 | GetSelectedOrDummyTarget() |
949 | .GetFrameRecognizerManager() |
950 | .RemoveAllRecognizers(); |
951 | result.SetStatus(eReturnStatusSuccessFinishResult); |
952 | return; |
953 | } |
954 | |
955 | if (command.GetArgumentCount() != 1) { |
956 | result.AppendErrorWithFormat(format: "'%s' takes zero or one arguments.\n" , |
957 | m_cmd_name.c_str()); |
958 | return; |
959 | } |
960 | |
961 | uint32_t recognizer_id; |
962 | if (!llvm::to_integer(S: command.GetArgumentAtIndex(idx: 0), Num&: recognizer_id)) { |
963 | result.AppendErrorWithFormat(format: "'%s' is not a valid recognizer id.\n" , |
964 | command.GetArgumentAtIndex(idx: 0)); |
965 | return; |
966 | } |
967 | |
968 | if (!GetSelectedOrDummyTarget() |
969 | .GetFrameRecognizerManager() |
970 | .RemoveRecognizerWithID(recognizer_id)) { |
971 | result.AppendErrorWithFormat(format: "'%s' is not a valid recognizer id.\n" , |
972 | command.GetArgumentAtIndex(idx: 0)); |
973 | return; |
974 | } |
975 | result.SetStatus(eReturnStatusSuccessFinishResult); |
976 | } |
977 | }; |
978 | |
979 | class CommandObjectFrameRecognizerList : public CommandObjectParsed { |
980 | public: |
981 | CommandObjectFrameRecognizerList(CommandInterpreter &interpreter) |
982 | : CommandObjectParsed(interpreter, "frame recognizer list" , |
983 | "Show a list of active frame recognizers." , |
984 | nullptr) {} |
985 | |
986 | ~CommandObjectFrameRecognizerList() override = default; |
987 | |
988 | protected: |
989 | void DoExecute(Args &command, CommandReturnObject &result) override { |
990 | bool any_printed = false; |
991 | GetSelectedOrDummyTarget().GetFrameRecognizerManager().ForEach( |
992 | callback: [&result, &any_printed]( |
993 | uint32_t recognizer_id, std::string name, std::string module, |
994 | llvm::ArrayRef<ConstString> symbols, bool regexp) { |
995 | Stream &stream = result.GetOutputStream(); |
996 | |
997 | if (name.empty()) |
998 | name = "(internal)" ; |
999 | |
1000 | stream << std::to_string(val: recognizer_id) << ": " << name; |
1001 | if (!module.empty()) |
1002 | stream << ", module " << module; |
1003 | if (!symbols.empty()) |
1004 | for (auto &symbol : symbols) |
1005 | stream << ", symbol " << symbol; |
1006 | if (regexp) |
1007 | stream << " (regexp)" ; |
1008 | |
1009 | stream.EOL(); |
1010 | stream.Flush(); |
1011 | |
1012 | any_printed = true; |
1013 | }); |
1014 | |
1015 | if (any_printed) |
1016 | result.SetStatus(eReturnStatusSuccessFinishResult); |
1017 | else { |
1018 | result.GetOutputStream().PutCString(cstr: "no matching results found.\n" ); |
1019 | result.SetStatus(eReturnStatusSuccessFinishNoResult); |
1020 | } |
1021 | } |
1022 | }; |
1023 | |
1024 | class CommandObjectFrameRecognizerInfo : public CommandObjectParsed { |
1025 | public: |
1026 | CommandObjectFrameRecognizerInfo(CommandInterpreter &interpreter) |
1027 | : CommandObjectParsed( |
1028 | interpreter, "frame recognizer info" , |
1029 | "Show which frame recognizer is applied a stack frame (if any)." , |
1030 | nullptr) { |
1031 | AddSimpleArgumentList(arg_type: eArgTypeFrameIndex); |
1032 | } |
1033 | |
1034 | ~CommandObjectFrameRecognizerInfo() override = default; |
1035 | |
1036 | protected: |
1037 | void DoExecute(Args &command, CommandReturnObject &result) override { |
1038 | const char *frame_index_str = command.GetArgumentAtIndex(idx: 0); |
1039 | uint32_t frame_index; |
1040 | if (!llvm::to_integer(S: frame_index_str, Num&: frame_index)) { |
1041 | result.AppendErrorWithFormat(format: "'%s' is not a valid frame index." , |
1042 | frame_index_str); |
1043 | return; |
1044 | } |
1045 | |
1046 | Process *process = m_exe_ctx.GetProcessPtr(); |
1047 | if (process == nullptr) { |
1048 | result.AppendError(in_string: "no process" ); |
1049 | return; |
1050 | } |
1051 | Thread *thread = m_exe_ctx.GetThreadPtr(); |
1052 | if (thread == nullptr) { |
1053 | result.AppendError(in_string: "no thread" ); |
1054 | return; |
1055 | } |
1056 | if (command.GetArgumentCount() != 1) { |
1057 | result.AppendErrorWithFormat( |
1058 | format: "'%s' takes exactly one frame index argument.\n" , m_cmd_name.c_str()); |
1059 | return; |
1060 | } |
1061 | |
1062 | StackFrameSP frame_sp = thread->GetStackFrameAtIndex(idx: frame_index); |
1063 | if (!frame_sp) { |
1064 | result.AppendErrorWithFormat(format: "no frame with index %u" , frame_index); |
1065 | return; |
1066 | } |
1067 | |
1068 | auto recognizer = GetSelectedOrDummyTarget() |
1069 | .GetFrameRecognizerManager() |
1070 | .GetRecognizerForFrame(frame: frame_sp); |
1071 | |
1072 | Stream &output_stream = result.GetOutputStream(); |
1073 | output_stream.Printf(format: "frame %d " , frame_index); |
1074 | if (recognizer) { |
1075 | output_stream << "is recognized by " ; |
1076 | output_stream << recognizer->GetName(); |
1077 | } else { |
1078 | output_stream << "not recognized by any recognizer" ; |
1079 | } |
1080 | output_stream.EOL(); |
1081 | result.SetStatus(eReturnStatusSuccessFinishResult); |
1082 | } |
1083 | }; |
1084 | |
1085 | class CommandObjectFrameRecognizer : public CommandObjectMultiword { |
1086 | public: |
1087 | CommandObjectFrameRecognizer(CommandInterpreter &interpreter) |
1088 | : CommandObjectMultiword( |
1089 | interpreter, "frame recognizer" , |
1090 | "Commands for editing and viewing frame recognizers." , |
1091 | "frame recognizer [<sub-command-options>] " ) { |
1092 | LoadSubCommand(cmd_name: "add" , command_obj: CommandObjectSP(new CommandObjectFrameRecognizerAdd( |
1093 | interpreter))); |
1094 | LoadSubCommand( |
1095 | cmd_name: "clear" , |
1096 | command_obj: CommandObjectSP(new CommandObjectFrameRecognizerClear(interpreter))); |
1097 | LoadSubCommand( |
1098 | cmd_name: "delete" , |
1099 | command_obj: CommandObjectSP(new CommandObjectFrameRecognizerDelete(interpreter))); |
1100 | LoadSubCommand(cmd_name: "list" , command_obj: CommandObjectSP(new CommandObjectFrameRecognizerList( |
1101 | interpreter))); |
1102 | LoadSubCommand(cmd_name: "info" , command_obj: CommandObjectSP(new CommandObjectFrameRecognizerInfo( |
1103 | interpreter))); |
1104 | } |
1105 | |
1106 | ~CommandObjectFrameRecognizer() override = default; |
1107 | }; |
1108 | |
1109 | #pragma mark CommandObjectMultiwordFrame |
1110 | |
1111 | // CommandObjectMultiwordFrame |
1112 | |
1113 | CommandObjectMultiwordFrame::CommandObjectMultiwordFrame( |
1114 | CommandInterpreter &interpreter) |
1115 | : CommandObjectMultiword(interpreter, "frame" , |
1116 | "Commands for selecting and " |
1117 | "examing the current " |
1118 | "thread's stack frames." , |
1119 | "frame <subcommand> [<subcommand-options>]" ) { |
1120 | LoadSubCommand(cmd_name: "diagnose" , |
1121 | command_obj: CommandObjectSP(new CommandObjectFrameDiagnose(interpreter))); |
1122 | LoadSubCommand(cmd_name: "info" , |
1123 | command_obj: CommandObjectSP(new CommandObjectFrameInfo(interpreter))); |
1124 | LoadSubCommand(cmd_name: "select" , |
1125 | command_obj: CommandObjectSP(new CommandObjectFrameSelect(interpreter))); |
1126 | LoadSubCommand(cmd_name: "variable" , |
1127 | command_obj: CommandObjectSP(new CommandObjectFrameVariable(interpreter))); |
1128 | #if LLDB_ENABLE_PYTHON |
1129 | LoadSubCommand(cmd_name: "recognizer" , command_obj: CommandObjectSP(new CommandObjectFrameRecognizer( |
1130 | interpreter))); |
1131 | #endif |
1132 | } |
1133 | |
1134 | CommandObjectMultiwordFrame::~CommandObjectMultiwordFrame() = default; |
1135 | |