[Lldb-commits] [lldb] 4298b1b - Add a "-b" option to "process continue" to run to a set of breakpoints,

Jim Ingham via lldb-commits lldb-commits at lists.llvm.org
Wed Jun 22 09:55:39 PDT 2022


Author: Jim Ingham
Date: 2022-06-22T09:55:30-07:00
New Revision: 4298b1b8d13715963cc1a917bc122208a29710fb

URL: https://github.com/llvm/llvm-project/commit/4298b1b8d13715963cc1a917bc122208a29710fb
DIFF: https://github.com/llvm/llvm-project/commit/4298b1b8d13715963cc1a917bc122208a29710fb.diff

LOG: Add a "-b" option to "process continue" to run to a set of breakpoints,
temporarily ignoring the others.

Differential Revision: https://reviews.llvm.org/D126513

Added: 
    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

Modified: 
    lldb/source/Commands/CommandObjectProcess.cpp
    lldb/source/Commands/Options.td

Removed: 
    


################################################################################
diff  --git a/lldb/source/Commands/CommandObjectProcess.cpp b/lldb/source/Commands/CommandObjectProcess.cpp
index f5961d9b1c00d..def0e00af0876 100644
--- a/lldb/source/Commands/CommandObjectProcess.cpp
+++ b/lldb/source/Commands/CommandObjectProcess.cpp
@@ -8,9 +8,12 @@
 
 #include "CommandObjectProcess.h"
 #include "CommandObjectTrace.h"
+#include "CommandObjectBreakpoint.h"
 #include "CommandOptionsProcessLaunch.h"
 #include "lldb/Breakpoint/Breakpoint.h"
+#include "lldb/Breakpoint/BreakpointIDList.h"
 #include "lldb/Breakpoint/BreakpointLocation.h"
+#include "lldb/Breakpoint/BreakpointName.h"
 #include "lldb/Breakpoint/BreakpointSite.h"
 #include "lldb/Core/Module.h"
 #include "lldb/Core/PluginManager.h"
@@ -29,6 +32,8 @@
 #include "lldb/Utility/Args.h"
 #include "lldb/Utility/State.h"
 
+#include "llvm/ADT/ScopeExit.h"
+
 #include <bitset>
 
 using namespace lldb;
@@ -516,7 +521,7 @@ class CommandObjectProcessContinue : public CommandObjectParsed {
     ~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 +531,10 @@ class CommandObjectProcessContinue : public CommandObjectParsed {
               "invalid value for ignore option: \"%s\", should be a number.",
               option_arg.str().c_str());
         break;
-
+      case 'b':
+        m_run_to_bkpt_args.AppendArgument(option_arg);
+        m_any_bkpts_specified = true;
+      break;
       default:
         llvm_unreachable("Unimplemented option");
       }
@@ -535,15 +543,20 @@ class CommandObjectProcessContinue : public CommandObjectParsed {
 
     void OptionParsingStarting(ExecutionContext *execution_context) override {
       m_ignore = 0;
+      m_run_to_bkpt_args.Clear();
+      m_any_bkpts_specified = false;
     }
 
     llvm::ArrayRef<OptionDefinition> GetDefinitions() override {
       return llvm::makeArrayRef(g_process_continue_options);
     }
 
-    uint32_t m_ignore;
+    uint32_t m_ignore = 0;
+    Args m_run_to_bkpt_args;
+    bool m_any_bkpts_specified = false;
   };
 
+
   bool DoExecute(Args &command, CommandReturnObject &result) override {
     Process *process = m_exe_ctx.GetProcessPtr();
     bool synchronous_execution = m_interpreter.GetSynchronous();
@@ -579,6 +592,127 @@ class CommandObjectProcessContinue : public CommandObjectParsed {
           }
         }
       }
+      
+      Target *target = m_exe_ctx.GetTargetPtr();
+      BreakpointIDList run_to_bkpt_ids;
+      CommandObjectMultiwordBreakpoint::VerifyBreakpointOrLocationIDs(
+          m_options.m_run_to_bkpt_args, target, result, &run_to_bkpt_ids, 
+          BreakpointName::Permissions::disablePerm);
+      if (!result.Succeeded()) {
+        return false;
+      }
+      result.Clear();
+      if (m_options.m_any_bkpts_specified && run_to_bkpt_ids.GetSize() == 0) {
+        result.AppendError("continue-to breakpoints did not specify any actual "
+                           "breakpoints or locations");
+        return false;
+      }
+
+      // First figure out which breakpoints & locations were specified by the
+      // user:
+      size_t num_run_to_bkpt_ids = run_to_bkpt_ids.GetSize();
+      std::vector<break_id_t> bkpts_disabled;
+      std::vector<BreakpointID> locs_disabled;
+      if (num_run_to_bkpt_ids != 0) {
+        // Go through the ID's specified, and separate the breakpoints from are 
+        // the breakpoint.location specifications since the latter require
+        // special handling.  We also figure out whether there's at least one
+        // specifier in the set that is enabled.
+        BreakpointList &bkpt_list = target->GetBreakpointList();
+        std::unordered_set<break_id_t> bkpts_seen;
+        std::unordered_set<break_id_t> bkpts_with_locs_seen;
+        BreakpointIDList with_locs;
+        bool any_enabled = false;
+  
+        for (size_t idx = 0; idx < num_run_to_bkpt_ids; idx++) {
+          BreakpointID bkpt_id = run_to_bkpt_ids.GetBreakpointIDAtIndex(idx);
+          break_id_t bp_id = bkpt_id.GetBreakpointID();
+          break_id_t loc_id = bkpt_id.GetLocationID();
+          BreakpointSP bp_sp 
+              = bkpt_list.FindBreakpointByID(bp_id);
+          // Note, VerifyBreakpointOrLocationIDs checks for existence, so we 
+          // don't need to do it again here.
+          if (bp_sp->IsEnabled()) {
+            if (loc_id == LLDB_INVALID_BREAK_ID) {
+              // A breakpoint (without location) was specified.  Make sure that 
+              // at least one of the locations is enabled.
+              size_t num_locations = bp_sp->GetNumLocations();
+              for (size_t loc_idx = 0; loc_idx < num_locations; loc_idx++) {
+                BreakpointLocationSP loc_sp 
+                    = bp_sp->GetLocationAtIndex(loc_idx);
+                if (loc_sp->IsEnabled()) {
+                  any_enabled = true;
+                  break;
+                }
+              }
+            } else {
+              // A location was specified, check if it was enabled:
+              BreakpointLocationSP loc_sp = bp_sp->FindLocationByID(loc_id);
+              if (loc_sp->IsEnabled())
+                any_enabled = true;
+            }
+          
+            // Then sort the bp & bp.loc entries for later use:
+            if (bkpt_id.GetLocationID() == LLDB_INVALID_BREAK_ID)
+              bkpts_seen.insert(bkpt_id.GetBreakpointID());
+            else {
+              bkpts_with_locs_seen.insert(bkpt_id.GetBreakpointID());
+              with_locs.AddBreakpointID(bkpt_id);
+            }
+          }
+        }
+        // Do all the error checking here so once we start disabling we don't
+        // have to back out half-way through.
+        
+        // Make sure at least one of the specified breakpoints is enabled.
+        if (!any_enabled) {
+          result.AppendError("at least one of the continue-to breakpoints must "
+                             "be enabled.");
+          return false;
+        }
+        
+        // Also, if you specify BOTH a breakpoint and one of it's locations,
+        // we flag that as an error, since it won't do what you expect, the
+        // breakpoint directive will mean "run to all locations", which is not
+        // what the location directive means...
+        for (break_id_t bp_id : bkpts_with_locs_seen) {
+          if (bkpts_seen.count(bp_id)) {
+            result.AppendErrorWithFormatv("can't specify both a breakpoint and "
+                               "one of its locations: {0}", bp_id);
+          }
+        }
+        
+        // Now go through the breakpoints in the target, disabling all the ones
+        // that the user didn't mention:
+        for (BreakpointSP bp_sp : bkpt_list.Breakpoints()) {
+          break_id_t bp_id = bp_sp->GetID();
+          // Handle the case where no locations were specified.  Note we don't
+          // have to worry about the case where a breakpoint and one of its
+          // locations are both in the lists, we've already disallowed that.
+          if (!bkpts_with_locs_seen.count(bp_id)) {
+            if (!bkpts_seen.count(bp_id) && bp_sp->IsEnabled()) {
+              bkpts_disabled.push_back(bp_id);
+              bp_sp->SetEnabled(false);
+            }
+            continue;
+          }
+          // Next, handle the case where a location was specified:
+          // Run through all the locations of this breakpoint and disable
+          // the ones that aren't on our "with locations" BreakpointID list:
+          size_t num_locations = bp_sp->GetNumLocations();
+          BreakpointID tmp_id(bp_id, LLDB_INVALID_BREAK_ID);
+          for (size_t loc_idx = 0; loc_idx < num_locations; loc_idx++) {
+            BreakpointLocationSP loc_sp = bp_sp->GetLocationAtIndex(loc_idx);
+            tmp_id.SetBreakpointLocationID(loc_idx);
+            size_t position = 0;
+            if (!with_locs.FindBreakpointID(tmp_id, &position) 
+                && loc_sp->IsEnabled()) {
+              locs_disabled.push_back(tmp_id);
+              loc_sp->SetEnabled(false);
+            }
+          }
+        }
+      }
 
       { // Scope for thread list mutex:
         std::lock_guard<std::recursive_mutex> guard(
@@ -597,10 +731,39 @@ class CommandObjectProcessContinue : public CommandObjectParsed {
 
       StreamString stream;
       Status error;
+      // For now we can only do -b with synchronous:
+      bool old_sync = GetDebugger().GetAsyncExecution();
+      
+      if (run_to_bkpt_ids.GetSize() != 0) {
+        GetDebugger().SetAsyncExecution(false);
+        synchronous_execution = true;
+      } 
       if (synchronous_execution)
         error = process->ResumeSynchronous(&stream);
       else
         error = process->Resume();
+      
+      if (run_to_bkpt_ids.GetSize() != 0) {
+        GetDebugger().SetAsyncExecution(old_sync);
+      } 
+       
+      // Now re-enable the breakpoints we disabled:
+      BreakpointList &bkpt_list = target->GetBreakpointList();
+      for (break_id_t bp_id : bkpts_disabled) {
+        BreakpointSP bp_sp = bkpt_list.FindBreakpointByID(bp_id);
+        if (bp_sp)
+          bp_sp->SetEnabled(true);
+      }
+      for (const BreakpointID &bkpt_id : locs_disabled) {
+        BreakpointSP bp_sp 
+            = bkpt_list.FindBreakpointByID(bkpt_id.GetBreakpointID());
+        if (bp_sp) {
+          BreakpointLocationSP loc_sp 
+              = bp_sp->FindLocationByID(bkpt_id.GetLocationID());
+          if (loc_sp)
+            loc_sp->SetEnabled(true);
+        }
+      }
 
       if (error.Success()) {
         // There is a race condition where this thread will return up the call

diff  --git a/lldb/source/Commands/Options.td b/lldb/source/Commands/Options.td
index 5a1c4bcd2f3a5..4ab1f375eccdc 100644
--- a/lldb/source/Commands/Options.td
+++ b/lldb/source/Commands/Options.td
@@ -718,9 +718,13 @@ let Command = "process attach" in {
 }
 
 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<"BreakpointIDRange">, Desc<"Specify a breakpoint to continue to, temporarily "
+    "ignoring other breakpoints.  Can be specified more than once.  "
+    "The continue action will be done synchronously if this option is specified.">;
 }
 
 let Command = "process detach" in {

diff  --git a/lldb/test/API/commands/process/continue_to_bkpt/Makefile b/lldb/test/API/commands/process/continue_to_bkpt/Makefile
new file mode 100644
index 0000000000000..10495940055b6
--- /dev/null
+++ b/lldb/test/API/commands/process/continue_to_bkpt/Makefile
@@ -0,0 +1,3 @@
+C_SOURCES := main.c
+
+include Makefile.rules

diff  --git a/lldb/test/API/commands/process/continue_to_bkpt/TestContinueToBkpts.py b/lldb/test/API/commands/process/continue_to_bkpt/TestContinueToBkpts.py
new file mode 100644
index 0000000000000..82f4404241283
--- /dev/null
+++ b/lldb/test/API/commands/process/continue_to_bkpt/TestContinueToBkpts.py
@@ -0,0 +1,132 @@
+"""
+Test the "process continue -b" 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, loc_to_hit = 0):
+        """Build up a command that will run a continue -b commands using the breakpoints on stop_list, and
+           ensure that we hit bkpt_to_hit.
+           If loc_to_hit is not 0, also verify that we hit that location."""
+        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")
+        if loc_to_hit != 0:
+            self.assertEqual(self.thread.GetStopReasonDataAtIndex(1), loc_to_hit, "Hit the right location")
+        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()))
+        # Also do our multiple location one:
+        bkpt = self.target.FindBreakpointByID(self.multiple_loc_id)
+        self.assertTrue(bkpt.IsValid(), "Breakpoint with locations round trip")
+        for i in range(1,3):
+            loc = bkpt.FindLocationByID(i)
+            self.assertTrue(loc.IsValid(), "Locations round trip")
+            if i == 2:
+                self.assertTrue(loc.IsEnabled(), "Locations that were enabled stay enabled")
+            else:
+                self.assertFalse(loc.IsEnabled(), "Locations that were disabled stay disabled")
+        
+    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")
+        # Also make one that has several locations, so we can test locations:
+        mult_bkpt = self.target.BreakpointCreateBySourceRegex(bkpt_pattern.format("(seventh|eighth|nineth)"), self.main_source_spec)
+        self.assertEqual(mult_bkpt.GetNumLocations(), 3, "Got three matches")
+        mult_bkpt.AddName("Locations")
+        # Disable all of these:
+        for i in range(1,4):
+            loc = mult_bkpt.FindLocationByID(i)
+            self.assertTrue(loc.IsValid(), "Location {0} is valid".format(i))
+            loc.SetEnabled(False)
+            self.assertFalse(loc.IsEnabled(), "Loc {0} wasn't disabled".format(i))
+        self.multiple_loc_id = mult_bkpt.GetID()
+
+        # First test out various error conditions
+
+        # All locations of the multiple_loc_id are disabled, so running to this should be an error:
+        self.expect("process continue -b {0}".format(self.multiple_loc_id), error=True, msg="Running to a disabled breakpoint by number")
+
+        # Now re-enable the middle one so we can run to it:
+        loc = mult_bkpt.FindLocationByID(2)
+        loc.SetEnabled(True)
+
+        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 {0}.1".format(self.bkpt_list[1]), error=True, msg="Running to a location of a disabled breakpoint")
+        self.expect("process continue -b disabled", error=True, msg="Running to a disabled set of breakpoints")
+        self.expect("process continue -b {0}.{1}".format(self.multiple_loc_id, 1), error=True, msg="Running to a disabled breakpoint location")
+        self.expect("process continue -b {0}".format("THERE_ARE_NO_BREAKPOINTS_BY_THIS_NAME"), error=True, msg="Running to no such name")
+        self.expect("process continue -b {0}".format(1000), error=True, msg="Running to no such breakpoint")
+        self.expect("process continue -b {0}.{1}".format(self.multiple_loc_id, 1000), error=True, msg="Running to no such location")
+        
+        # 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:
+        # This continue has to muck with the sync mode of the debugger, so let's make sure we
+        # put it back.  First try if it was in sync mode:
+        orig_async = self.dbg.GetAsync()
+        self.dbg.SetAsync(True)
+        self.continue_and_check([bkpt_elements[2], bkpt_elements[7]], self.bkpt_list[2])
+        after_value = self.dbg.GetAsync()
+        self.dbg.SetAsync(orig_async)
+        self.assertTrue(after_value, "Preserve async as True if it started that way")
+        
+        # Now try a name that has several breakpoints.
+        # This time I'm also going to check that we put the debugger async mode back if
+        # if was False to begin with:
+        self.dbg.SetAsync(False)
+        self.continue_and_check(["MyBKPT"], self.bkpt_list[6])
+        after_value = self.dbg.GetAsync()
+        self.dbg.SetAsync(orig_async)
+        self.assertFalse(after_value, "Preserve async as False if it started that way")
+
+        # Now let's run to a particular location.  Also specify a breakpoint we've already hit:
+        self.continue_and_check([self.bkpt_list[0], self.multiple_loc_id], self.multiple_loc_id, 2)

diff  --git a/lldb/test/API/commands/process/continue_to_bkpt/main.c b/lldb/test/API/commands/process/continue_to_bkpt/main.c
new file mode 100644
index 0000000000000..f179ebc48af96
--- /dev/null
+++ b/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;
+}


        


More information about the lldb-commits mailing list