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