[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