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