1 //===-- CommandObjectWatchpointCommand.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 <vector>
10 
11 #include "CommandObjectWatchpoint.h"
12 #include "CommandObjectWatchpointCommand.h"
13 #include "lldb/Breakpoint/StoppointCallbackContext.h"
14 #include "lldb/Breakpoint/Watchpoint.h"
15 #include "lldb/Core/IOHandler.h"
16 #include "lldb/Host/OptionParser.h"
17 #include "lldb/Interpreter/CommandInterpreter.h"
18 #include "lldb/Interpreter/CommandReturnObject.h"
19 #include "lldb/Interpreter/OptionArgParser.h"
20 #include "lldb/Target/Target.h"
21 
22 using namespace lldb;
23 using namespace lldb_private;
24 
25 // FIXME: "script-type" needs to have its contents determined dynamically, so
26 // somebody can add a new scripting language to lldb and have it pickable here
27 // without having to change this enumeration by hand and rebuild lldb proper.
28 static constexpr OptionEnumValueElement g_script_option_enumeration[] = {
29     {
30         eScriptLanguageNone,
31         "command",
32         "Commands are in the lldb command interpreter language",
33     },
34     {
35         eScriptLanguagePython,
36         "python",
37         "Commands are in the Python language.",
38     },
39     {
40         eScriptLanguageLua,
41         "lua",
42         "Commands are in the Lua language.",
43     },
44     {
45         eSortOrderByName,
46         "default-script",
47         "Commands are in the default scripting language.",
48     },
49 };
50 
51 static constexpr OptionEnumValues ScriptOptionEnum() {
52   return OptionEnumValues(g_script_option_enumeration);
53 }
54 
55 #define LLDB_OPTIONS_watchpoint_command_add
56 #include "CommandOptions.inc"
57 
58 class CommandObjectWatchpointCommandAdd : public CommandObjectParsed,
59                                           public IOHandlerDelegateMultiline {
60 public:
61   CommandObjectWatchpointCommandAdd(CommandInterpreter &interpreter)
62       : CommandObjectParsed(interpreter, "add",
63                             "Add a set of LLDB commands to a watchpoint, to be "
64                             "executed whenever the watchpoint is hit.  "
65                             "The commands added to the watchpoint replace any "
66                             "commands previously added to it.",
67                             nullptr, eCommandRequiresTarget),
68         IOHandlerDelegateMultiline("DONE",
69                                    IOHandlerDelegate::Completion::LLDBCommand),
70         m_options() {
71     SetHelpLong(
72         R"(
73 General information about entering watchpoint commands
74 ------------------------------------------------------
75 
76 )"
77         "This command will prompt for commands to be executed when the specified \
78 watchpoint is hit.  Each command is typed on its own line following the '> ' \
79 prompt until 'DONE' is entered."
80         R"(
81 
82 )"
83         "Syntactic errors may not be detected when initially entered, and many \
84 malformed commands can silently fail when executed.  If your watchpoint commands \
85 do not appear to be executing, double-check the command syntax."
86         R"(
87 
88 )"
89         "Note: You may enter any debugger command exactly as you would at the debugger \
90 prompt.  There is no limit to the number of commands supplied, but do NOT enter \
91 more than one command per line."
92         R"(
93 
94 Special information about PYTHON watchpoint commands
95 ----------------------------------------------------
96 
97 )"
98         "You may enter either one or more lines of Python, including function \
99 definitions or calls to functions that will have been imported by the time \
100 the code executes.  Single line watchpoint commands will be interpreted 'as is' \
101 when the watchpoint is hit.  Multiple lines of Python will be wrapped in a \
102 generated function, and a call to the function will be attached to the watchpoint."
103         R"(
104 
105 This auto-generated function is passed in three arguments:
106 
107     frame:  an lldb.SBFrame object for the frame which hit the watchpoint.
108 
109     wp:     the watchpoint that was hit.
110 
111 )"
112         "When specifying a python function with the --python-function option, you need \
113 to supply the function name prepended by the module name:"
114         R"(
115 
116     --python-function myutils.watchpoint_callback
117 
118 The function itself must have the following prototype:
119 
120 def watchpoint_callback(frame, wp):
121   # Your code goes here
122 
123 )"
124         "The arguments are the same as the arguments passed to generated functions as \
125 described above.  Note that the global variable 'lldb.frame' will NOT be updated when \
126 this function is called, so be sure to use the 'frame' argument. The 'frame' argument \
127 can get you to the thread via frame.GetThread(), the thread can get you to the \
128 process via thread.GetProcess(), and the process can get you back to the target \
129 via process.GetTarget()."
130         R"(
131 
132 )"
133         "Important Note: As Python code gets collected into functions, access to global \
134 variables requires explicit scoping using the 'global' keyword.  Be sure to use correct \
135 Python syntax, including indentation, when entering Python watchpoint commands."
136         R"(
137 
138 Example Python one-line watchpoint command:
139 
140 (lldb) watchpoint command add -s python 1
141 Enter your Python command(s). Type 'DONE' to end.
142 > print "Hit this watchpoint!"
143 > DONE
144 
145 As a convenience, this also works for a short Python one-liner:
146 
147 (lldb) watchpoint command add -s python 1 -o 'import time; print time.asctime()'
148 (lldb) run
149 Launching '.../a.out'  (x86_64)
150 (lldb) Fri Sep 10 12:17:45 2010
151 Process 21778 Stopped
152 * thread #1: tid = 0x2e03, 0x0000000100000de8 a.out`c + 7 at main.c:39, stop reason = watchpoint 1.1, queue = com.apple.main-thread
153   36
154   37   	int c(int val)
155   38   	{
156   39 ->	    return val + 3;
157   40   	}
158   41
159   42   	int main (int argc, char const *argv[])
160 
161 Example multiple line Python watchpoint command, using function definition:
162 
163 (lldb) watchpoint command add -s python 1
164 Enter your Python command(s). Type 'DONE' to end.
165 > def watchpoint_output (wp_no):
166 >     out_string = "Hit watchpoint number " + repr (wp_no)
167 >     print out_string
168 >     return True
169 > watchpoint_output (1)
170 > DONE
171 
172 Example multiple line Python watchpoint command, using 'loose' Python:
173 
174 (lldb) watchpoint command add -s p 1
175 Enter your Python command(s). Type 'DONE' to end.
176 > global wp_count
177 > wp_count = wp_count + 1
178 > print "Hit this watchpoint " + repr(wp_count) + " times!"
179 > DONE
180 
181 )"
182         "In this case, since there is a reference to a global variable, \
183 'wp_count', you will also need to make sure 'wp_count' exists and is \
184 initialized:"
185         R"(
186 
187 (lldb) script
188 >>> wp_count = 0
189 >>> quit()
190 
191 )"
192         "Final Note: A warning that no watchpoint command was generated when there \
193 are no syntax errors may indicate that a function was declared but never called.");
194 
195     CommandArgumentEntry arg;
196     CommandArgumentData wp_id_arg;
197 
198     // Define the first (and only) variant of this arg.
199     wp_id_arg.arg_type = eArgTypeWatchpointID;
200     wp_id_arg.arg_repetition = eArgRepeatPlain;
201 
202     // There is only one variant this argument could be; put it into the
203     // argument entry.
204     arg.push_back(wp_id_arg);
205 
206     // Push the data for the first argument into the m_arguments vector.
207     m_arguments.push_back(arg);
208   }
209 
210   ~CommandObjectWatchpointCommandAdd() override = default;
211 
212   Options *GetOptions() override { return &m_options; }
213 
214   void IOHandlerActivated(IOHandler &io_handler, bool interactive) override {
215     StreamFileSP output_sp(io_handler.GetOutputStreamFileSP());
216     if (output_sp && interactive) {
217       output_sp->PutCString(
218           "Enter your debugger command(s).  Type 'DONE' to end.\n");
219       output_sp->Flush();
220     }
221   }
222 
223   void IOHandlerInputComplete(IOHandler &io_handler,
224                               std::string &line) override {
225     io_handler.SetIsDone(true);
226 
227     // The WatchpointOptions object is owned by the watchpoint or watchpoint
228     // location
229     WatchpointOptions *wp_options =
230         (WatchpointOptions *)io_handler.GetUserData();
231     if (wp_options) {
232       std::unique_ptr<WatchpointOptions::CommandData> data_up(
233           new WatchpointOptions::CommandData());
234       if (data_up) {
235         data_up->user_source.SplitIntoLines(line);
236         auto baton_sp = std::make_shared<WatchpointOptions::CommandBaton>(
237             std::move(data_up));
238         wp_options->SetCallback(WatchpointOptionsCallbackFunction, baton_sp);
239       }
240     }
241   }
242 
243   void CollectDataForWatchpointCommandCallback(WatchpointOptions *wp_options,
244                                                CommandReturnObject &result) {
245     m_interpreter.GetLLDBCommandsFromIOHandler(
246         "> ",        // Prompt
247         *this,       // IOHandlerDelegate
248         wp_options); // Baton for the "io_handler" that will be passed back into
249                      // our IOHandlerDelegate functions
250   }
251 
252   /// Set a one-liner as the callback for the watchpoint.
253   void SetWatchpointCommandCallback(WatchpointOptions *wp_options,
254                                     const char *oneliner) {
255     std::unique_ptr<WatchpointOptions::CommandData> data_up(
256         new WatchpointOptions::CommandData());
257 
258     // It's necessary to set both user_source and script_source to the
259     // oneliner. The former is used to generate callback description (as in
260     // watchpoint command list) while the latter is used for Python to
261     // interpret during the actual callback.
262     data_up->user_source.AppendString(oneliner);
263     data_up->script_source.assign(oneliner);
264     data_up->stop_on_error = m_options.m_stop_on_error;
265 
266     auto baton_sp =
267         std::make_shared<WatchpointOptions::CommandBaton>(std::move(data_up));
268     wp_options->SetCallback(WatchpointOptionsCallbackFunction, baton_sp);
269   }
270 
271   static bool
272   WatchpointOptionsCallbackFunction(void *baton,
273                                     StoppointCallbackContext *context,
274                                     lldb::user_id_t watch_id) {
275     bool ret_value = true;
276     if (baton == nullptr)
277       return true;
278 
279     WatchpointOptions::CommandData *data =
280         (WatchpointOptions::CommandData *)baton;
281     StringList &commands = data->user_source;
282 
283     if (commands.GetSize() > 0) {
284       ExecutionContext exe_ctx(context->exe_ctx_ref);
285       Target *target = exe_ctx.GetTargetPtr();
286       if (target) {
287         Debugger &debugger = target->GetDebugger();
288         CommandReturnObject result(debugger.GetUseColor());
289 
290         // Rig up the results secondary output stream to the debugger's, so the
291         // output will come out synchronously if the debugger is set up that
292         // way.
293         StreamSP output_stream(debugger.GetAsyncOutputStream());
294         StreamSP error_stream(debugger.GetAsyncErrorStream());
295         result.SetImmediateOutputStream(output_stream);
296         result.SetImmediateErrorStream(error_stream);
297 
298         CommandInterpreterRunOptions options;
299         options.SetStopOnContinue(true);
300         options.SetStopOnError(data->stop_on_error);
301         options.SetEchoCommands(false);
302         options.SetPrintResults(true);
303         options.SetPrintErrors(true);
304         options.SetAddToHistory(false);
305 
306         debugger.GetCommandInterpreter().HandleCommands(commands, exe_ctx,
307                                                         options, result);
308         result.GetImmediateOutputStream()->Flush();
309         result.GetImmediateErrorStream()->Flush();
310       }
311     }
312     return ret_value;
313   }
314 
315   class CommandOptions : public Options {
316   public:
317     CommandOptions()
318         : Options(), m_use_commands(false), m_use_script_language(false),
319           m_script_language(eScriptLanguageNone), m_use_one_liner(false),
320           m_one_liner(), m_function_name() {}
321 
322     ~CommandOptions() override = default;
323 
324     Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg,
325                           ExecutionContext *execution_context) override {
326       Status error;
327       const int short_option = m_getopt_table[option_idx].val;
328 
329       switch (short_option) {
330       case 'o':
331         m_use_one_liner = true;
332         m_one_liner = std::string(option_arg);
333         break;
334 
335       case 's':
336         m_script_language = (lldb::ScriptLanguage)OptionArgParser::ToOptionEnum(
337             option_arg, GetDefinitions()[option_idx].enum_values,
338             eScriptLanguageNone, error);
339 
340         switch (m_script_language) {
341         case eScriptLanguagePython:
342         case eScriptLanguageLua:
343           m_use_script_language = true;
344           break;
345         case eScriptLanguageNone:
346         case eScriptLanguageUnknown:
347           m_use_script_language = false;
348           break;
349         }
350         break;
351 
352       case 'e': {
353         bool success = false;
354         m_stop_on_error =
355             OptionArgParser::ToBoolean(option_arg, false, &success);
356         if (!success)
357           error.SetErrorStringWithFormat(
358               "invalid value for stop-on-error: \"%s\"",
359               option_arg.str().c_str());
360       } break;
361 
362       case 'F':
363         m_use_one_liner = false;
364         m_function_name.assign(std::string(option_arg));
365         break;
366 
367       default:
368         llvm_unreachable("Unimplemented option");
369       }
370       return error;
371     }
372 
373     void OptionParsingStarting(ExecutionContext *execution_context) override {
374       m_use_commands = true;
375       m_use_script_language = false;
376       m_script_language = eScriptLanguageNone;
377 
378       m_use_one_liner = false;
379       m_stop_on_error = true;
380       m_one_liner.clear();
381       m_function_name.clear();
382     }
383 
384     llvm::ArrayRef<OptionDefinition> GetDefinitions() override {
385       return llvm::makeArrayRef(g_watchpoint_command_add_options);
386     }
387 
388     // Instance variables to hold the values for command options.
389 
390     bool m_use_commands;
391     bool m_use_script_language;
392     lldb::ScriptLanguage m_script_language;
393 
394     // Instance variables to hold the values for one_liner options.
395     bool m_use_one_liner;
396     std::string m_one_liner;
397     bool m_stop_on_error;
398     std::string m_function_name;
399   };
400 
401 protected:
402   bool DoExecute(Args &command, CommandReturnObject &result) override {
403     Target *target = &GetSelectedTarget();
404 
405     const WatchpointList &watchpoints = target->GetWatchpointList();
406     size_t num_watchpoints = watchpoints.GetSize();
407 
408     if (num_watchpoints == 0) {
409       result.AppendError("No watchpoints exist to have commands added");
410       result.SetStatus(eReturnStatusFailed);
411       return false;
412     }
413 
414     if (!m_options.m_function_name.empty()) {
415       if (!m_options.m_use_script_language) {
416         m_options.m_script_language = GetDebugger().GetScriptLanguage();
417         m_options.m_use_script_language = true;
418       }
419     }
420 
421     std::vector<uint32_t> valid_wp_ids;
422     if (!CommandObjectMultiwordWatchpoint::VerifyWatchpointIDs(target, command,
423                                                                valid_wp_ids)) {
424       result.AppendError("Invalid watchpoints specification.");
425       result.SetStatus(eReturnStatusFailed);
426       return false;
427     }
428 
429     result.SetStatus(eReturnStatusSuccessFinishNoResult);
430     const size_t count = valid_wp_ids.size();
431     for (size_t i = 0; i < count; ++i) {
432       uint32_t cur_wp_id = valid_wp_ids.at(i);
433       if (cur_wp_id != LLDB_INVALID_WATCH_ID) {
434         Watchpoint *wp = target->GetWatchpointList().FindByID(cur_wp_id).get();
435         // Sanity check wp first.
436         if (wp == nullptr)
437           continue;
438 
439         WatchpointOptions *wp_options = wp->GetOptions();
440         // Skip this watchpoint if wp_options is not good.
441         if (wp_options == nullptr)
442           continue;
443 
444         // If we are using script language, get the script interpreter in order
445         // to set or collect command callback.  Otherwise, call the methods
446         // associated with this object.
447         if (m_options.m_use_script_language) {
448           ScriptInterpreter *script_interp = GetDebugger().GetScriptInterpreter(
449               /*can_create=*/true, m_options.m_script_language);
450           // Special handling for one-liner specified inline.
451           if (m_options.m_use_one_liner) {
452             script_interp->SetWatchpointCommandCallback(
453                 wp_options, m_options.m_one_liner.c_str());
454           }
455           // Special handling for using a Python function by name instead of
456           // extending the watchpoint callback data structures, we just
457           // automatize what the user would do manually: make their watchpoint
458           // command be a function call
459           else if (!m_options.m_function_name.empty()) {
460             std::string oneliner(m_options.m_function_name);
461             oneliner += "(frame, wp, internal_dict)";
462             script_interp->SetWatchpointCommandCallback(
463                 wp_options, oneliner.c_str());
464           } else {
465             script_interp->CollectDataForWatchpointCommandCallback(wp_options,
466                                                                    result);
467           }
468         } else {
469           // Special handling for one-liner specified inline.
470           if (m_options.m_use_one_liner)
471             SetWatchpointCommandCallback(wp_options,
472                                          m_options.m_one_liner.c_str());
473           else
474             CollectDataForWatchpointCommandCallback(wp_options, result);
475         }
476       }
477     }
478 
479     return result.Succeeded();
480   }
481 
482 private:
483   CommandOptions m_options;
484 };
485 
486 // CommandObjectWatchpointCommandDelete
487 
488 class CommandObjectWatchpointCommandDelete : public CommandObjectParsed {
489 public:
490   CommandObjectWatchpointCommandDelete(CommandInterpreter &interpreter)
491       : CommandObjectParsed(interpreter, "delete",
492                             "Delete the set of commands from a watchpoint.",
493                             nullptr, eCommandRequiresTarget) {
494     CommandArgumentEntry arg;
495     CommandArgumentData wp_id_arg;
496 
497     // Define the first (and only) variant of this arg.
498     wp_id_arg.arg_type = eArgTypeWatchpointID;
499     wp_id_arg.arg_repetition = eArgRepeatPlain;
500 
501     // There is only one variant this argument could be; put it into the
502     // argument entry.
503     arg.push_back(wp_id_arg);
504 
505     // Push the data for the first argument into the m_arguments vector.
506     m_arguments.push_back(arg);
507   }
508 
509   ~CommandObjectWatchpointCommandDelete() override = default;
510 
511 protected:
512   bool DoExecute(Args &command, CommandReturnObject &result) override {
513     Target *target = &GetSelectedTarget();
514 
515     const WatchpointList &watchpoints = target->GetWatchpointList();
516     size_t num_watchpoints = watchpoints.GetSize();
517 
518     if (num_watchpoints == 0) {
519       result.AppendError("No watchpoints exist to have commands deleted");
520       result.SetStatus(eReturnStatusFailed);
521       return false;
522     }
523 
524     if (command.GetArgumentCount() == 0) {
525       result.AppendError(
526           "No watchpoint specified from which to delete the commands");
527       result.SetStatus(eReturnStatusFailed);
528       return false;
529     }
530 
531     std::vector<uint32_t> valid_wp_ids;
532     if (!CommandObjectMultiwordWatchpoint::VerifyWatchpointIDs(target, command,
533                                                                valid_wp_ids)) {
534       result.AppendError("Invalid watchpoints specification.");
535       result.SetStatus(eReturnStatusFailed);
536       return false;
537     }
538 
539     result.SetStatus(eReturnStatusSuccessFinishNoResult);
540     const size_t count = valid_wp_ids.size();
541     for (size_t i = 0; i < count; ++i) {
542       uint32_t cur_wp_id = valid_wp_ids.at(i);
543       if (cur_wp_id != LLDB_INVALID_WATCH_ID) {
544         Watchpoint *wp = target->GetWatchpointList().FindByID(cur_wp_id).get();
545         if (wp)
546           wp->ClearCallback();
547       } else {
548         result.AppendErrorWithFormat("Invalid watchpoint ID: %u.\n", cur_wp_id);
549         result.SetStatus(eReturnStatusFailed);
550         return false;
551       }
552     }
553     return result.Succeeded();
554   }
555 };
556 
557 // CommandObjectWatchpointCommandList
558 
559 class CommandObjectWatchpointCommandList : public CommandObjectParsed {
560 public:
561   CommandObjectWatchpointCommandList(CommandInterpreter &interpreter)
562       : CommandObjectParsed(interpreter, "list",
563                             "List the script or set of commands to be executed "
564                             "when the watchpoint is hit.",
565                             nullptr, eCommandRequiresTarget) {
566     CommandArgumentEntry arg;
567     CommandArgumentData wp_id_arg;
568 
569     // Define the first (and only) variant of this arg.
570     wp_id_arg.arg_type = eArgTypeWatchpointID;
571     wp_id_arg.arg_repetition = eArgRepeatPlain;
572 
573     // There is only one variant this argument could be; put it into the
574     // argument entry.
575     arg.push_back(wp_id_arg);
576 
577     // Push the data for the first argument into the m_arguments vector.
578     m_arguments.push_back(arg);
579   }
580 
581   ~CommandObjectWatchpointCommandList() override = default;
582 
583 protected:
584   bool DoExecute(Args &command, CommandReturnObject &result) override {
585     Target *target = &GetSelectedTarget();
586 
587     const WatchpointList &watchpoints = target->GetWatchpointList();
588     size_t num_watchpoints = watchpoints.GetSize();
589 
590     if (num_watchpoints == 0) {
591       result.AppendError("No watchpoints exist for which to list commands");
592       result.SetStatus(eReturnStatusFailed);
593       return false;
594     }
595 
596     if (command.GetArgumentCount() == 0) {
597       result.AppendError(
598           "No watchpoint specified for which to list the commands");
599       result.SetStatus(eReturnStatusFailed);
600       return false;
601     }
602 
603     std::vector<uint32_t> valid_wp_ids;
604     if (!CommandObjectMultiwordWatchpoint::VerifyWatchpointIDs(target, command,
605                                                                valid_wp_ids)) {
606       result.AppendError("Invalid watchpoints specification.");
607       result.SetStatus(eReturnStatusFailed);
608       return false;
609     }
610 
611     result.SetStatus(eReturnStatusSuccessFinishNoResult);
612     const size_t count = valid_wp_ids.size();
613     for (size_t i = 0; i < count; ++i) {
614       uint32_t cur_wp_id = valid_wp_ids.at(i);
615       if (cur_wp_id != LLDB_INVALID_WATCH_ID) {
616         Watchpoint *wp = target->GetWatchpointList().FindByID(cur_wp_id).get();
617 
618         if (wp) {
619           const WatchpointOptions *wp_options = wp->GetOptions();
620           if (wp_options) {
621             // Get the callback baton associated with the current watchpoint.
622             const Baton *baton = wp_options->GetBaton();
623             if (baton) {
624               result.GetOutputStream().Printf("Watchpoint %u:\n", cur_wp_id);
625               baton->GetDescription(result.GetOutputStream().AsRawOstream(),
626                                     eDescriptionLevelFull,
627                                     result.GetOutputStream().GetIndentLevel() +
628                                         2);
629             } else {
630               result.AppendMessageWithFormat(
631                   "Watchpoint %u does not have an associated command.\n",
632                   cur_wp_id);
633             }
634           }
635           result.SetStatus(eReturnStatusSuccessFinishResult);
636         } else {
637           result.AppendErrorWithFormat("Invalid watchpoint ID: %u.\n",
638                                        cur_wp_id);
639           result.SetStatus(eReturnStatusFailed);
640         }
641       }
642     }
643 
644     return result.Succeeded();
645   }
646 };
647 
648 // CommandObjectWatchpointCommand
649 
650 CommandObjectWatchpointCommand::CommandObjectWatchpointCommand(
651     CommandInterpreter &interpreter)
652     : CommandObjectMultiword(
653           interpreter, "command",
654           "Commands for adding, removing and examining LLDB commands "
655           "executed when the watchpoint is hit (watchpoint 'commands').",
656           "command <sub-command> [<sub-command-options>] <watchpoint-id>") {
657   CommandObjectSP add_command_object(
658       new CommandObjectWatchpointCommandAdd(interpreter));
659   CommandObjectSP delete_command_object(
660       new CommandObjectWatchpointCommandDelete(interpreter));
661   CommandObjectSP list_command_object(
662       new CommandObjectWatchpointCommandList(interpreter));
663 
664   add_command_object->SetCommandName("watchpoint command add");
665   delete_command_object->SetCommandName("watchpoint command delete");
666   list_command_object->SetCommandName("watchpoint command list");
667 
668   LoadSubCommand("add", add_command_object);
669   LoadSubCommand("delete", delete_command_object);
670   LoadSubCommand("list", list_command_object);
671 }
672 
673 CommandObjectWatchpointCommand::~CommandObjectWatchpointCommand() = default;
674