xref: /openbsd-src/gnu/llvm/lldb/source/Commands/CommandObjectRegister.cpp (revision f6aab3d83b51b91c24247ad2c2573574de475a82)
1dda28197Spatrick //===-- CommandObjectRegister.cpp -----------------------------------------===//
2061da546Spatrick //
3061da546Spatrick // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4061da546Spatrick // See https://llvm.org/LICENSE.txt for license information.
5061da546Spatrick // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6061da546Spatrick //
7061da546Spatrick //===----------------------------------------------------------------------===//
8061da546Spatrick 
9061da546Spatrick #include "CommandObjectRegister.h"
10061da546Spatrick #include "lldb/Core/Debugger.h"
11061da546Spatrick #include "lldb/Core/DumpRegisterValue.h"
12061da546Spatrick #include "lldb/Host/OptionParser.h"
13*f6aab3d8Srobert #include "lldb/Interpreter/CommandOptionArgumentTable.h"
14061da546Spatrick #include "lldb/Interpreter/CommandReturnObject.h"
15061da546Spatrick #include "lldb/Interpreter/OptionGroupFormat.h"
16061da546Spatrick #include "lldb/Interpreter/OptionValueArray.h"
17061da546Spatrick #include "lldb/Interpreter/OptionValueBoolean.h"
18061da546Spatrick #include "lldb/Interpreter/OptionValueUInt64.h"
19061da546Spatrick #include "lldb/Interpreter/Options.h"
20061da546Spatrick #include "lldb/Target/ExecutionContext.h"
21061da546Spatrick #include "lldb/Target/Process.h"
22061da546Spatrick #include "lldb/Target/RegisterContext.h"
23061da546Spatrick #include "lldb/Target/SectionLoadList.h"
24061da546Spatrick #include "lldb/Target/Thread.h"
25061da546Spatrick #include "lldb/Utility/Args.h"
26061da546Spatrick #include "lldb/Utility/DataExtractor.h"
27061da546Spatrick #include "lldb/Utility/RegisterValue.h"
28061da546Spatrick #include "llvm/Support/Errno.h"
29061da546Spatrick 
30061da546Spatrick using namespace lldb;
31061da546Spatrick using namespace lldb_private;
32061da546Spatrick 
33061da546Spatrick // "register read"
34061da546Spatrick #define LLDB_OPTIONS_register_read
35061da546Spatrick #include "CommandOptions.inc"
36061da546Spatrick 
37061da546Spatrick class CommandObjectRegisterRead : public CommandObjectParsed {
38061da546Spatrick public:
CommandObjectRegisterRead(CommandInterpreter & interpreter)39061da546Spatrick   CommandObjectRegisterRead(CommandInterpreter &interpreter)
40061da546Spatrick       : CommandObjectParsed(
41061da546Spatrick             interpreter, "register read",
42061da546Spatrick             "Dump the contents of one or more register values from the current "
43061da546Spatrick             "frame.  If no register is specified, dumps them all.",
44061da546Spatrick             nullptr,
45061da546Spatrick             eCommandRequiresFrame | eCommandRequiresRegContext |
46061da546Spatrick                 eCommandProcessMustBeLaunched | eCommandProcessMustBePaused),
47*f6aab3d8Srobert         m_format_options(eFormatDefault) {
48061da546Spatrick     CommandArgumentEntry arg;
49061da546Spatrick     CommandArgumentData register_arg;
50061da546Spatrick 
51061da546Spatrick     // Define the first (and only) variant of this arg.
52061da546Spatrick     register_arg.arg_type = eArgTypeRegisterName;
53061da546Spatrick     register_arg.arg_repetition = eArgRepeatStar;
54061da546Spatrick 
55061da546Spatrick     // There is only one variant this argument could be; put it into the
56061da546Spatrick     // argument entry.
57061da546Spatrick     arg.push_back(register_arg);
58061da546Spatrick 
59061da546Spatrick     // Push the data for the first argument into the m_arguments vector.
60061da546Spatrick     m_arguments.push_back(arg);
61061da546Spatrick 
62061da546Spatrick     // Add the "--format"
63061da546Spatrick     m_option_group.Append(&m_format_options,
64061da546Spatrick                           OptionGroupFormat::OPTION_GROUP_FORMAT |
65061da546Spatrick                               OptionGroupFormat::OPTION_GROUP_GDB_FMT,
66061da546Spatrick                           LLDB_OPT_SET_ALL);
67061da546Spatrick     m_option_group.Append(&m_command_options);
68061da546Spatrick     m_option_group.Finalize();
69061da546Spatrick   }
70061da546Spatrick 
71061da546Spatrick   ~CommandObjectRegisterRead() override = default;
72061da546Spatrick 
73dda28197Spatrick   void
HandleArgumentCompletion(CompletionRequest & request,OptionElementVector & opt_element_vector)74dda28197Spatrick   HandleArgumentCompletion(CompletionRequest &request,
75dda28197Spatrick                            OptionElementVector &opt_element_vector) override {
76dda28197Spatrick     if (!m_exe_ctx.HasProcessScope())
77dda28197Spatrick       return;
78dda28197Spatrick 
79dda28197Spatrick     CommandCompletions::InvokeCommonCompletionCallbacks(
80dda28197Spatrick         GetCommandInterpreter(), CommandCompletions::eRegisterCompletion,
81dda28197Spatrick         request, nullptr);
82dda28197Spatrick   }
83dda28197Spatrick 
GetOptions()84061da546Spatrick   Options *GetOptions() override { return &m_option_group; }
85061da546Spatrick 
DumpRegister(const ExecutionContext & exe_ctx,Stream & strm,RegisterContext * reg_ctx,const RegisterInfo * reg_info)86061da546Spatrick   bool DumpRegister(const ExecutionContext &exe_ctx, Stream &strm,
87061da546Spatrick                     RegisterContext *reg_ctx, const RegisterInfo *reg_info) {
88061da546Spatrick     if (reg_info) {
89061da546Spatrick       RegisterValue reg_value;
90061da546Spatrick 
91061da546Spatrick       if (reg_ctx->ReadRegister(reg_info, reg_value)) {
92061da546Spatrick         strm.Indent();
93061da546Spatrick 
94061da546Spatrick         bool prefix_with_altname = (bool)m_command_options.alternate_name;
95061da546Spatrick         bool prefix_with_name = !prefix_with_altname;
96061da546Spatrick         DumpRegisterValue(reg_value, &strm, reg_info, prefix_with_name,
97*f6aab3d8Srobert                           prefix_with_altname, m_format_options.GetFormat(), 8,
98*f6aab3d8Srobert                           exe_ctx.GetBestExecutionContextScope());
99061da546Spatrick         if ((reg_info->encoding == eEncodingUint) ||
100061da546Spatrick             (reg_info->encoding == eEncodingSint)) {
101061da546Spatrick           Process *process = exe_ctx.GetProcessPtr();
102061da546Spatrick           if (process && reg_info->byte_size == process->GetAddressByteSize()) {
103061da546Spatrick             addr_t reg_addr = reg_value.GetAsUInt64(LLDB_INVALID_ADDRESS);
104061da546Spatrick             if (reg_addr != LLDB_INVALID_ADDRESS) {
105061da546Spatrick               Address so_reg_addr;
106061da546Spatrick               if (exe_ctx.GetTargetRef()
107061da546Spatrick                       .GetSectionLoadList()
108061da546Spatrick                       .ResolveLoadAddress(reg_addr, so_reg_addr)) {
109061da546Spatrick                 strm.PutCString("  ");
110061da546Spatrick                 so_reg_addr.Dump(&strm, exe_ctx.GetBestExecutionContextScope(),
111061da546Spatrick                                  Address::DumpStyleResolvedDescription);
112061da546Spatrick               }
113061da546Spatrick             }
114061da546Spatrick           }
115061da546Spatrick         }
116061da546Spatrick         strm.EOL();
117061da546Spatrick         return true;
118061da546Spatrick       }
119061da546Spatrick     }
120061da546Spatrick     return false;
121061da546Spatrick   }
122061da546Spatrick 
DumpRegisterSet(const ExecutionContext & exe_ctx,Stream & strm,RegisterContext * reg_ctx,size_t set_idx,bool primitive_only=false)123061da546Spatrick   bool DumpRegisterSet(const ExecutionContext &exe_ctx, Stream &strm,
124061da546Spatrick                        RegisterContext *reg_ctx, size_t set_idx,
125061da546Spatrick                        bool primitive_only = false) {
126061da546Spatrick     uint32_t unavailable_count = 0;
127061da546Spatrick     uint32_t available_count = 0;
128061da546Spatrick 
129061da546Spatrick     if (!reg_ctx)
130061da546Spatrick       return false; // thread has no registers (i.e. core files are corrupt,
131061da546Spatrick                     // incomplete crash logs...)
132061da546Spatrick 
133061da546Spatrick     const RegisterSet *const reg_set = reg_ctx->GetRegisterSet(set_idx);
134061da546Spatrick     if (reg_set) {
135061da546Spatrick       strm.Printf("%s:\n", (reg_set->name ? reg_set->name : "unknown"));
136061da546Spatrick       strm.IndentMore();
137061da546Spatrick       const size_t num_registers = reg_set->num_registers;
138061da546Spatrick       for (size_t reg_idx = 0; reg_idx < num_registers; ++reg_idx) {
139061da546Spatrick         const uint32_t reg = reg_set->registers[reg_idx];
140061da546Spatrick         const RegisterInfo *reg_info = reg_ctx->GetRegisterInfoAtIndex(reg);
141061da546Spatrick         // Skip the dumping of derived register if primitive_only is true.
142061da546Spatrick         if (primitive_only && reg_info && reg_info->value_regs)
143061da546Spatrick           continue;
144061da546Spatrick 
145061da546Spatrick         if (DumpRegister(exe_ctx, strm, reg_ctx, reg_info))
146061da546Spatrick           ++available_count;
147061da546Spatrick         else
148061da546Spatrick           ++unavailable_count;
149061da546Spatrick       }
150061da546Spatrick       strm.IndentLess();
151061da546Spatrick       if (unavailable_count) {
152061da546Spatrick         strm.Indent();
153061da546Spatrick         strm.Printf("%u registers were unavailable.\n", unavailable_count);
154061da546Spatrick       }
155061da546Spatrick       strm.EOL();
156061da546Spatrick     }
157061da546Spatrick     return available_count > 0;
158061da546Spatrick   }
159061da546Spatrick 
160061da546Spatrick protected:
DoExecute(Args & command,CommandReturnObject & result)161061da546Spatrick   bool DoExecute(Args &command, CommandReturnObject &result) override {
162061da546Spatrick     Stream &strm = result.GetOutputStream();
163061da546Spatrick     RegisterContext *reg_ctx = m_exe_ctx.GetRegisterContext();
164061da546Spatrick 
165061da546Spatrick     const RegisterInfo *reg_info = nullptr;
166061da546Spatrick     if (command.GetArgumentCount() == 0) {
167061da546Spatrick       size_t set_idx;
168061da546Spatrick 
169061da546Spatrick       size_t num_register_sets = 1;
170061da546Spatrick       const size_t set_array_size = m_command_options.set_indexes.GetSize();
171061da546Spatrick       if (set_array_size > 0) {
172061da546Spatrick         for (size_t i = 0; i < set_array_size; ++i) {
173061da546Spatrick           set_idx = m_command_options.set_indexes[i]->GetUInt64Value(UINT32_MAX,
174061da546Spatrick                                                                      nullptr);
175061da546Spatrick           if (set_idx < reg_ctx->GetRegisterSetCount()) {
176061da546Spatrick             if (!DumpRegisterSet(m_exe_ctx, strm, reg_ctx, set_idx)) {
177061da546Spatrick               if (errno)
178061da546Spatrick                 result.AppendErrorWithFormatv("register read failed: {0}\n",
179061da546Spatrick                                               llvm::sys::StrError());
180061da546Spatrick               else
181061da546Spatrick                 result.AppendError("unknown error while reading registers.\n");
182061da546Spatrick               break;
183061da546Spatrick             }
184061da546Spatrick           } else {
185061da546Spatrick             result.AppendErrorWithFormat(
186061da546Spatrick                 "invalid register set index: %" PRIu64 "\n", (uint64_t)set_idx);
187061da546Spatrick             break;
188061da546Spatrick           }
189061da546Spatrick         }
190061da546Spatrick       } else {
191061da546Spatrick         if (m_command_options.dump_all_sets)
192061da546Spatrick           num_register_sets = reg_ctx->GetRegisterSetCount();
193061da546Spatrick 
194061da546Spatrick         for (set_idx = 0; set_idx < num_register_sets; ++set_idx) {
195061da546Spatrick           // When dump_all_sets option is set, dump primitive as well as
196061da546Spatrick           // derived registers.
197061da546Spatrick           DumpRegisterSet(m_exe_ctx, strm, reg_ctx, set_idx,
198061da546Spatrick                           !m_command_options.dump_all_sets.GetCurrentValue());
199061da546Spatrick         }
200061da546Spatrick       }
201061da546Spatrick     } else {
202061da546Spatrick       if (m_command_options.dump_all_sets) {
203061da546Spatrick         result.AppendError("the --all option can't be used when registers "
204061da546Spatrick                            "names are supplied as arguments\n");
205061da546Spatrick       } else if (m_command_options.set_indexes.GetSize() > 0) {
206061da546Spatrick         result.AppendError("the --set <set> option can't be used when "
207061da546Spatrick                            "registers names are supplied as arguments\n");
208061da546Spatrick       } else {
209061da546Spatrick         for (auto &entry : command) {
210061da546Spatrick           // in most LLDB commands we accept $rbx as the name for register RBX
211061da546Spatrick           // - and here we would reject it and non-existant. we should be more
212061da546Spatrick           // consistent towards the user and allow them to say reg read $rbx -
213061da546Spatrick           // internally, however, we should be strict and not allow ourselves
214061da546Spatrick           // to call our registers $rbx in our own API
215061da546Spatrick           auto arg_str = entry.ref();
216061da546Spatrick           arg_str.consume_front("$");
217061da546Spatrick 
218061da546Spatrick           reg_info = reg_ctx->GetRegisterInfoByName(arg_str);
219061da546Spatrick 
220061da546Spatrick           if (reg_info) {
221061da546Spatrick             if (!DumpRegister(m_exe_ctx, strm, reg_ctx, reg_info))
222061da546Spatrick               strm.Printf("%-12s = error: unavailable\n", reg_info->name);
223061da546Spatrick           } else {
224061da546Spatrick             result.AppendErrorWithFormat("Invalid register name '%s'.\n",
225061da546Spatrick                                          arg_str.str().c_str());
226061da546Spatrick           }
227061da546Spatrick         }
228061da546Spatrick       }
229061da546Spatrick     }
230061da546Spatrick     return result.Succeeded();
231061da546Spatrick   }
232061da546Spatrick 
233061da546Spatrick   class CommandOptions : public OptionGroup {
234061da546Spatrick   public:
CommandOptions()235061da546Spatrick     CommandOptions()
236*f6aab3d8Srobert         : set_indexes(OptionValue::ConvertTypeToMask(OptionValue::eTypeUInt64)),
237061da546Spatrick           dump_all_sets(false, false), // Initial and default values are false
238061da546Spatrick           alternate_name(false, false) {}
239061da546Spatrick 
240061da546Spatrick     ~CommandOptions() override = default;
241061da546Spatrick 
GetDefinitions()242061da546Spatrick     llvm::ArrayRef<OptionDefinition> GetDefinitions() override {
243*f6aab3d8Srobert       return llvm::ArrayRef(g_register_read_options);
244061da546Spatrick     }
245061da546Spatrick 
OptionParsingStarting(ExecutionContext * execution_context)246061da546Spatrick     void OptionParsingStarting(ExecutionContext *execution_context) override {
247061da546Spatrick       set_indexes.Clear();
248061da546Spatrick       dump_all_sets.Clear();
249061da546Spatrick       alternate_name.Clear();
250061da546Spatrick     }
251061da546Spatrick 
SetOptionValue(uint32_t option_idx,llvm::StringRef option_value,ExecutionContext * execution_context)252061da546Spatrick     Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_value,
253061da546Spatrick                           ExecutionContext *execution_context) override {
254061da546Spatrick       Status error;
255061da546Spatrick       const int short_option = GetDefinitions()[option_idx].short_option;
256061da546Spatrick       switch (short_option) {
257061da546Spatrick       case 's': {
258061da546Spatrick         OptionValueSP value_sp(OptionValueUInt64::Create(option_value, error));
259061da546Spatrick         if (value_sp)
260061da546Spatrick           set_indexes.AppendValue(value_sp);
261061da546Spatrick       } break;
262061da546Spatrick 
263061da546Spatrick       case 'a':
264061da546Spatrick         // When we don't use OptionValue::SetValueFromCString(const char *) to
265061da546Spatrick         // set an option value, it won't be marked as being set in the options
266061da546Spatrick         // so we make a call to let users know the value was set via option
267061da546Spatrick         dump_all_sets.SetCurrentValue(true);
268061da546Spatrick         dump_all_sets.SetOptionWasSet();
269061da546Spatrick         break;
270061da546Spatrick 
271061da546Spatrick       case 'A':
272061da546Spatrick         // When we don't use OptionValue::SetValueFromCString(const char *) to
273061da546Spatrick         // set an option value, it won't be marked as being set in the options
274061da546Spatrick         // so we make a call to let users know the value was set via option
275061da546Spatrick         alternate_name.SetCurrentValue(true);
276061da546Spatrick         dump_all_sets.SetOptionWasSet();
277061da546Spatrick         break;
278061da546Spatrick 
279061da546Spatrick       default:
280061da546Spatrick         llvm_unreachable("Unimplemented option");
281061da546Spatrick       }
282061da546Spatrick       return error;
283061da546Spatrick     }
284061da546Spatrick 
285061da546Spatrick     // Instance variables to hold the values for command options.
286061da546Spatrick     OptionValueArray set_indexes;
287061da546Spatrick     OptionValueBoolean dump_all_sets;
288061da546Spatrick     OptionValueBoolean alternate_name;
289061da546Spatrick   };
290061da546Spatrick 
291061da546Spatrick   OptionGroupOptions m_option_group;
292061da546Spatrick   OptionGroupFormat m_format_options;
293061da546Spatrick   CommandOptions m_command_options;
294061da546Spatrick };
295061da546Spatrick 
296061da546Spatrick // "register write"
297061da546Spatrick class CommandObjectRegisterWrite : public CommandObjectParsed {
298061da546Spatrick public:
CommandObjectRegisterWrite(CommandInterpreter & interpreter)299061da546Spatrick   CommandObjectRegisterWrite(CommandInterpreter &interpreter)
300061da546Spatrick       : CommandObjectParsed(interpreter, "register write",
301061da546Spatrick                             "Modify a single register value.", nullptr,
302061da546Spatrick                             eCommandRequiresFrame | eCommandRequiresRegContext |
303061da546Spatrick                                 eCommandProcessMustBeLaunched |
304061da546Spatrick                                 eCommandProcessMustBePaused) {
305061da546Spatrick     CommandArgumentEntry arg1;
306061da546Spatrick     CommandArgumentEntry arg2;
307061da546Spatrick     CommandArgumentData register_arg;
308061da546Spatrick     CommandArgumentData value_arg;
309061da546Spatrick 
310061da546Spatrick     // Define the first (and only) variant of this arg.
311061da546Spatrick     register_arg.arg_type = eArgTypeRegisterName;
312061da546Spatrick     register_arg.arg_repetition = eArgRepeatPlain;
313061da546Spatrick 
314061da546Spatrick     // There is only one variant this argument could be; put it into the
315061da546Spatrick     // argument entry.
316061da546Spatrick     arg1.push_back(register_arg);
317061da546Spatrick 
318061da546Spatrick     // Define the first (and only) variant of this arg.
319061da546Spatrick     value_arg.arg_type = eArgTypeValue;
320061da546Spatrick     value_arg.arg_repetition = eArgRepeatPlain;
321061da546Spatrick 
322061da546Spatrick     // There is only one variant this argument could be; put it into the
323061da546Spatrick     // argument entry.
324061da546Spatrick     arg2.push_back(value_arg);
325061da546Spatrick 
326061da546Spatrick     // Push the data for the first argument into the m_arguments vector.
327061da546Spatrick     m_arguments.push_back(arg1);
328061da546Spatrick     m_arguments.push_back(arg2);
329061da546Spatrick   }
330061da546Spatrick 
331061da546Spatrick   ~CommandObjectRegisterWrite() override = default;
332061da546Spatrick 
333dda28197Spatrick   void
HandleArgumentCompletion(CompletionRequest & request,OptionElementVector & opt_element_vector)334dda28197Spatrick   HandleArgumentCompletion(CompletionRequest &request,
335dda28197Spatrick                            OptionElementVector &opt_element_vector) override {
336dda28197Spatrick     if (!m_exe_ctx.HasProcessScope() || request.GetCursorIndex() != 0)
337dda28197Spatrick       return;
338dda28197Spatrick 
339dda28197Spatrick     CommandCompletions::InvokeCommonCompletionCallbacks(
340dda28197Spatrick         GetCommandInterpreter(), CommandCompletions::eRegisterCompletion,
341dda28197Spatrick         request, nullptr);
342dda28197Spatrick   }
343dda28197Spatrick 
344061da546Spatrick protected:
DoExecute(Args & command,CommandReturnObject & result)345061da546Spatrick   bool DoExecute(Args &command, CommandReturnObject &result) override {
346061da546Spatrick     DataExtractor reg_data;
347061da546Spatrick     RegisterContext *reg_ctx = m_exe_ctx.GetRegisterContext();
348061da546Spatrick 
349061da546Spatrick     if (command.GetArgumentCount() != 2) {
350061da546Spatrick       result.AppendError(
351061da546Spatrick           "register write takes exactly 2 arguments: <reg-name> <value>");
352061da546Spatrick     } else {
353061da546Spatrick       auto reg_name = command[0].ref();
354061da546Spatrick       auto value_str = command[1].ref();
355061da546Spatrick 
356061da546Spatrick       // in most LLDB commands we accept $rbx as the name for register RBX -
357061da546Spatrick       // and here we would reject it and non-existant. we should be more
358061da546Spatrick       // consistent towards the user and allow them to say reg write $rbx -
359061da546Spatrick       // internally, however, we should be strict and not allow ourselves to
360061da546Spatrick       // call our registers $rbx in our own API
361061da546Spatrick       reg_name.consume_front("$");
362061da546Spatrick 
363061da546Spatrick       const RegisterInfo *reg_info = reg_ctx->GetRegisterInfoByName(reg_name);
364061da546Spatrick 
365061da546Spatrick       if (reg_info) {
366061da546Spatrick         RegisterValue reg_value;
367061da546Spatrick 
368061da546Spatrick         Status error(reg_value.SetValueFromString(reg_info, value_str));
369061da546Spatrick         if (error.Success()) {
370061da546Spatrick           if (reg_ctx->WriteRegister(reg_info, reg_value)) {
371061da546Spatrick             // Toss all frames and anything else in the thread after a register
372061da546Spatrick             // has been written.
373061da546Spatrick             m_exe_ctx.GetThreadRef().Flush();
374061da546Spatrick             result.SetStatus(eReturnStatusSuccessFinishNoResult);
375061da546Spatrick             return true;
376061da546Spatrick           }
377061da546Spatrick         }
378061da546Spatrick         if (error.AsCString()) {
379061da546Spatrick           result.AppendErrorWithFormat(
380061da546Spatrick               "Failed to write register '%s' with value '%s': %s\n",
381061da546Spatrick               reg_name.str().c_str(), value_str.str().c_str(),
382061da546Spatrick               error.AsCString());
383061da546Spatrick         } else {
384061da546Spatrick           result.AppendErrorWithFormat(
385061da546Spatrick               "Failed to write register '%s' with value '%s'",
386061da546Spatrick               reg_name.str().c_str(), value_str.str().c_str());
387061da546Spatrick         }
388061da546Spatrick       } else {
389061da546Spatrick         result.AppendErrorWithFormat("Register not found for '%s'.\n",
390061da546Spatrick                                      reg_name.str().c_str());
391061da546Spatrick       }
392061da546Spatrick     }
393061da546Spatrick     return result.Succeeded();
394061da546Spatrick   }
395061da546Spatrick };
396061da546Spatrick 
397061da546Spatrick // CommandObjectRegister constructor
CommandObjectRegister(CommandInterpreter & interpreter)398061da546Spatrick CommandObjectRegister::CommandObjectRegister(CommandInterpreter &interpreter)
399061da546Spatrick     : CommandObjectMultiword(interpreter, "register",
400061da546Spatrick                              "Commands to access registers for the current "
401061da546Spatrick                              "thread and stack frame.",
402061da546Spatrick                              "register [read|write] ...") {
403061da546Spatrick   LoadSubCommand("read",
404061da546Spatrick                  CommandObjectSP(new CommandObjectRegisterRead(interpreter)));
405061da546Spatrick   LoadSubCommand("write",
406061da546Spatrick                  CommandObjectSP(new CommandObjectRegisterWrite(interpreter)));
407061da546Spatrick }
408061da546Spatrick 
409061da546Spatrick CommandObjectRegister::~CommandObjectRegister() = default;
410