diff --git a/lldb/source/Commands/CommandObjectProcess.cpp b/lldb/source/Commands/CommandObjectProcess.cpp --- 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 using namespace lldb; @@ -516,7 +521,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 +531,10 @@ "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 @@ void OptionParsingStarting(ExecutionContext *execution_context) override { m_ignore = 0; + m_run_to_bkpt_args.Clear(); + m_any_bkpts_specified = false; } llvm::ArrayRef 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 @@ } } } + + 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 bkpts_disabled; + std::vector 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 bkpts_seen; + std::unordered_set 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 guard( @@ -597,10 +731,39 @@ 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 --- a/lldb/source/Commands/Options.td +++ b/lldb/source/Commands/Options.td @@ -718,9 +718,13 @@ } 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 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 --- /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 --- /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 --- /dev/null +++ b/lldb/test/API/commands/process/continue_to_bkpt/main.c @@ -0,0 +1,18 @@ +#include + +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; +}