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 @@ -29,6 +29,8 @@ #include "lldb/Utility/Args.h" #include "lldb/Utility/State.h" +#include "llvm/ADT/ScopeExit.h" + #include 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 GetDefinitions() override { @@ -542,6 +588,7 @@ } uint32_t m_ignore; + std::unordered_set m_run_to_bkpts; }; bool DoExecute(Args &command, CommandReturnObject &result) override { @@ -579,6 +626,21 @@ } } } + + Target *target = m_exe_ctx.GetTargetPtr(); + std::vector 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 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 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,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 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 { 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,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]) 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; +}