jingham created this revision.
jingham added reviewers: JDevlieghere, kastiglione.
Herald added a project: All.
jingham requested review of this revision.
Herald added a project: LLDB.
Herald added a subscriber: lldb-commits.

Sometimes you have a complex set of breakpoints, some enabled and some not, but 
for the next continue you just want to run till you hit some subset of the 
breakpoints you have set without disturbing the state of other breakpoints.  
This patch adds an option for that.  Just say:

(lldb) continue -b 10

That will temporarily disable all the other breakpoints but 10, run till you 
hit 10, and then stop.

Note, this is just a continue option, not a thread plan, so if you stop for any 
other reason like a signal, or you interrupt the process, then the state will 
be restored and you will stop.  We don't force continuing to one of the 
breakpoints specified.  continue is not stateful, so I think this is what 
people would expect.

You can also specify a breakpoint name, and the option can be repeated, so:

(lldb) continue -b 10 -b MyBKPTS -b 20

will continue till you hit either breakpoints 10, 20, or any that have the name 
MyBKTPS.

Continuing to a breakpoint that's disabled is not going to be very satisfying.  
I could either temporarily enable the breakpoint, continue to it, then disable 
it again, or return an error.  In this patch I return an error, since I think 
you would want to know you are trying to continue to a breakpoint you've 
disabled.  But I could be persuaded the other behavior is more convenient.

BTW, this is a built-in version of the Python command in:

https://reviews.llvm.org/D125148

I still think that's worthwhile as an example, but the behavior seemed useful 
enough to be built-in.


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D126513

Files:
  lldb/source/Commands/CommandObjectProcess.cpp
  lldb/source/Commands/Options.td
  lldb/test/API/commands/process/continue_to_bkpt/Makefile
  lldb/test/API/commands/process/continue_to_bkpt/TestContinueToBkpts.py
  lldb/test/API/commands/process/continue_to_bkpt/main.c

Index: lldb/test/API/commands/process/continue_to_bkpt/main.c
===================================================================
--- /dev/null
+++ lldb/test/API/commands/process/continue_to_bkpt/main.c
@@ -0,0 +1,18 @@
+#include <stdio.h>
+
+int main (int argc, char const *argv[])
+{
+  int pass_me = argc + 10; // Stop here to get started.
+  printf("This is the zeroth stop\n");
+  printf("This is the first stop\n");
+  printf("This is the second stop\n");
+  printf("This is the third stop\n");
+  printf("This is the fourth stop\n");
+  printf("This is the fifth stop\n");
+  printf("This is the sixth stop\n");
+  printf("This is the seventh stop\n");
+  printf("This is the eighth stop\n");
+  printf("This is the nineth stop\n");
+
+  return 0;
+}
Index: lldb/test/API/commands/process/continue_to_bkpt/TestContinueToBkpts.py
===================================================================
--- /dev/null
+++ lldb/test/API/commands/process/continue_to_bkpt/TestContinueToBkpts.py
@@ -0,0 +1,78 @@
+"""
+Test the "process continue -t" option.
+"""
+
+
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+
+class TestContinueToBkpts(TestBase):
+
+    NO_DEBUG_INFO_TESTCASE = True
+
+    mydir = TestBase.compute_mydir(__file__)
+
+    @add_test_categories(['pyapi'])
+    def test_continue_to_breakpoints(self):
+        """Test that the continue to breakpoints feature works correctly."""
+        self.build()
+        self.do_test_continue_to_breakpoint()
+
+    def setUp(self):
+        # Call super's setUp().
+        TestBase.setUp(self)
+        self.main_source_spec = lldb.SBFileSpec("main.c")
+
+    def continue_and_check(self, stop_list, bkpt_to_hit):
+        command = "process continue"
+        for elem in stop_list:
+            command += " -b {0}".format(elem)
+        self.expect(command)
+        self.assertEqual(self.thread.stop_reason, lldb.eStopReasonBreakpoint, "Hit a breakpoint")
+        self.assertEqual(self.thread.GetStopReasonDataAtIndex(0), bkpt_to_hit, "Hit the right breakpoint")
+        for bkpt_id in self.bkpt_list:
+            bkpt = self.target.FindBreakpointByID(bkpt_id)
+            self.assertTrue(bkpt.IsValid(), "Breakpoint id's round trip")
+            if bkpt.MatchesName("disabled"):
+                self.assertFalse(bkpt.IsEnabled(), "Disabled breakpoints stay disabled: {0}".format(bkpt.GetID()))
+            else:
+                self.assertTrue(bkpt.IsEnabled(), "Enabled breakpoints stay enabled: {0}".format(bkpt.GetID()))
+        
+    def do_test_continue_to_breakpoint(self):
+        """Test the continue to breakpoint feature."""
+        (self.target, process, self.thread, bkpt) = lldbutil.run_to_source_breakpoint(self,
+                                   "Stop here to get started", self.main_source_spec)
+
+        # Now set up all our breakpoints:
+        bkpt_pattern = "This is the {0} stop"
+        bkpt_elements = ["zeroth", "first", "second", "third", "fourth", "fifth", "sixth", "seventh", "eighth", "nineth"]
+        disabled_bkpts = ["first", "eigth"]
+        bkpts_for_MyBKPT = ["first", "sixth", "nineth"]
+        self.bkpt_list = []
+        for elem in bkpt_elements:
+            bkpt = self.target.BreakpointCreateBySourceRegex(bkpt_pattern.format(elem), self.main_source_spec)
+            self.assertGreater(bkpt.GetNumLocations(), 0, "Found a bkpt match")
+            self.bkpt_list.append(bkpt.GetID())
+            bkpt.AddName(elem)
+            if elem in disabled_bkpts:
+                bkpt.AddName("disabled")
+                bkpt.SetEnabled(False)
+            if elem in bkpts_for_MyBKPT:
+                bkpt.AddName("MyBKPT")
+
+        # First try to continue to a disabled breakpoint and make sure we get an error:
+        self.expect("process continue -b {0}".format(self.bkpt_list[1]), error=True, msg="Running to a disabled breakpoint by number")
+        self.expect("process continue -b disabled", error=True, msg="Running to a disabled set of breakpoints")
+        
+        # Now move forward, this time with breakpoint numbers.  First time we don't skip other bkpts.
+        bkpt = self.bkpt_list[0]
+        self.continue_and_check([str(bkpt)], bkpt)
+
+        # Now skip to the third stop, do it by name and supply one of the later breakpoints as well:
+        self.continue_and_check([bkpt_elements[2], bkpt_elements[7]], self.bkpt_list[2])
+
+        # Now try a name that has several breakpoints.
+        self.continue_and_check(["MyBKPT"], self.bkpt_list[6])
Index: lldb/test/API/commands/process/continue_to_bkpt/Makefile
===================================================================
--- /dev/null
+++ lldb/test/API/commands/process/continue_to_bkpt/Makefile
@@ -0,0 +1,3 @@
+C_SOURCES := main.c
+
+include Makefile.rules
Index: lldb/source/Commands/Options.td
===================================================================
--- lldb/source/Commands/Options.td
+++ lldb/source/Commands/Options.td
@@ -718,9 +718,14 @@
 }
 
 let Command = "process continue" in {
-  def process_continue_ignore_count : Option<"ignore-count", "i">,
+  def process_continue_ignore_count : Option<"ignore-count", "i">, Group<1>,
     Arg<"UnsignedInteger">, Desc<"Ignore <N> crossings of the breakpoint (if it"
     " exists) for the currently selected thread.">;
+  def process_continue_run_to_bkpt : Option<"continue-to-bkpt", "b">, Group<2>,
+    Arg<"BreakpointID">, Desc<"Specify a breakpoint to continue to, temporarily "
+    "ignoring other breakpoints.  Can also be a breakpoint name, and can be "
+    "specified more than once to continue to one of a set of breakpoints.  "
+    "The continue action will be done synchronously if this option is specified.">;
 }
 
 let Command = "process detach" in {
Index: lldb/source/Commands/CommandObjectProcess.cpp
===================================================================
--- lldb/source/Commands/CommandObjectProcess.cpp
+++ lldb/source/Commands/CommandObjectProcess.cpp
@@ -29,6 +29,8 @@
 #include "lldb/Utility/Args.h"
 #include "lldb/Utility/State.h"
 
+#include "llvm/ADT/ScopeExit.h"
+
 #include <bitset>
 
 using namespace lldb;
@@ -516,7 +518,7 @@
     ~CommandOptions() override = default;
 
     Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg,
-                          ExecutionContext *execution_context) override {
+                          ExecutionContext *exe_ctx) override {
       Status error;
       const int short_option = m_getopt_table[option_idx].val;
       switch (short_option) {
@@ -526,7 +528,50 @@
               "invalid value for ignore option: \"%s\", should be a number.",
               option_arg.str().c_str());
         break;
-
+      case 'b':
+      {
+        break_id_t bkpt_id;
+        Target *target = exe_ctx->GetTargetPtr();
+        if (!option_arg.getAsInteger(0, bkpt_id)) {
+          BreakpointSP bkpt_sp = target->GetBreakpointByID(bkpt_id);
+          if (!bkpt_sp) {
+            error.SetErrorStringWithFormatv(
+                "continue-to breakpoint {0} not found.", option_arg);
+            break;
+          }
+          if (bkpt_id < 0) {
+            error.SetErrorStringWithFormatv(
+                "continue-to breakpoints cannot be internal breakpoints.");
+            break;
+          }
+          if (!bkpt_sp->IsEnabled()) {
+            error.SetErrorStringWithFormatv("continue-to breakpoints can't be"
+                "disable: {0}", bkpt_id);
+          }
+          m_run_to_bkpts.insert(bkpt_id);
+          break;
+        }
+        BreakpointList &breakpoints = target->GetBreakpointList();
+        bool any_set = false;
+        bool any_enabled = false;  
+        for (BreakpointSP bp_sp : breakpoints.Breakpoints()) {
+          if (bp_sp->MatchesName(option_arg.str().c_str())) {
+            if (bp_sp->IsEnabled())
+              any_enabled = true;
+            any_set = true;
+            m_run_to_bkpts.insert(bp_sp->GetID());
+          }
+        }
+        if (!any_set)
+          error.SetErrorStringWithFormatv(
+              "continue-to breakpoint name '{0}' has no breakpoints.", 
+              option_arg);
+        if (!any_enabled)
+          error.SetErrorStringWithFormatv(
+              "continue-to breakpoint name '{0}' has no enabled breakpoints.", 
+              option_arg);
+        break;
+      }  
       default:
         llvm_unreachable("Unimplemented option");
       }
@@ -535,6 +580,7 @@
 
     void OptionParsingStarting(ExecutionContext *execution_context) override {
       m_ignore = 0;
+      m_run_to_bkpts.clear();
     }
 
     llvm::ArrayRef<OptionDefinition> GetDefinitions() override {
@@ -542,6 +588,7 @@
     }
 
     uint32_t m_ignore;
+    std::unordered_set<break_id_t> m_run_to_bkpts; 
   };
 
   bool DoExecute(Args &command, CommandReturnObject &result) override {
@@ -579,6 +626,21 @@
           }
         }
       }
+      
+      Target *target = m_exe_ctx.GetTargetPtr();
+      std::vector<break_id_t> disabled_bkpts;
+      auto run_to_bkpts_end = m_options.m_run_to_bkpts.end();
+      if (!m_options.m_run_to_bkpts.empty()) {
+        BreakpointList &bkpt_list = target->GetBreakpointList();
+        for (BreakpointSP bp_sp : bkpt_list.Breakpoints()) {
+          break_id_t bp_id = bp_sp->GetID();
+          if (bp_sp->IsEnabled() 
+              && m_options.m_run_to_bkpts.find(bp_id) == run_to_bkpts_end) {
+              bp_sp->SetEnabled(false);
+              disabled_bkpts.push_back(bp_id);
+          }
+        }
+      }
 
       { // Scope for thread list mutex:
         std::lock_guard<std::recursive_mutex> guard(
@@ -597,10 +659,33 @@
 
       StreamString stream;
       Status error;
+      // For now we can only do -b with synchronous:
+      bool old_sync = synchronous_execution;
+      
+      // Be sure to reset the sync value if we have to change it.
+      auto restore_sync = llvm::make_scope_exit(
+          [this, old_sync]() {
+            this->GetDebugger().SetAsyncExecution(!old_sync);
+          });
+
+      if (!m_options.m_run_to_bkpts.empty()) {
+        GetDebugger().SetAsyncExecution(false);
+        synchronous_execution = true;
+      } 
       if (synchronous_execution)
         error = process->ResumeSynchronous(&stream);
       else
         error = process->Resume();
+        
+      // Now re-enable the breakpoints we disabled:
+      for (auto bkpt : disabled_bkpts) {
+        BreakpointSP break_sp = target->GetBreakpointByID(bkpt);
+        // I have no way of telling whether the stop action disabled this 
+        // breakpoint, which is a bit of a shame as you might end up re-enabling
+        // a breakpoint that the stop action disabled.  
+        if (break_sp)
+          break_sp->SetEnabled(true);
+      }
 
       if (error.Success()) {
         // There is a race condition where this thread will return up the call
_______________________________________________
lldb-commits mailing list
lldb-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to