[lldb] Reset breakpoint hit count before new runs

A common debugging pattern is to set a breakpoint that only stops after
a number of hits is recorded. The current implementation never resets
the hit count of breakpoints; as such, if a user re-`run`s their
program, the debugger will never stop on such a breakpoint again.

This behavior is arguably undesirable, as it renders such breakpoints
ineffective on all but the first run. This commit changes the
implementation of the `Will{Launch, Attach}` methods so that they reset
the _target's_ breakpoint hitcounts.

Differential Revision: https://reviews.llvm.org/D133858
This commit is contained in:
Felipe de Azevedo Piovezan 2022-09-13 11:30:07 -04:00
parent 95b3947111
commit 9749587498
20 changed files with 167 additions and 26 deletions

View File

@ -330,6 +330,9 @@ public:
/// The current hit count for all locations. /// The current hit count for all locations.
uint32_t GetHitCount() const; uint32_t GetHitCount() const;
/// Resets the current hit count for all locations.
void ResetHitCount();
/// If \a one_shot is \b true, breakpoint will be deleted on first hit. /// If \a one_shot is \b true, breakpoint will be deleted on first hit.
void SetOneShot(bool one_shot); void SetOneShot(bool one_shot);

View File

@ -138,6 +138,9 @@ public:
void ClearAllBreakpointSites(); void ClearAllBreakpointSites();
/// Resets the hit count of all breakpoints.
void ResetHitCounts();
/// Sets the passed in Locker to hold the Breakpoint List mutex. /// Sets the passed in Locker to hold the Breakpoint List mutex.
/// ///
/// \param[in] lock /// \param[in] lock

View File

@ -87,6 +87,9 @@ public:
/// Return the current Hit Count. /// Return the current Hit Count.
uint32_t GetHitCount() const { return m_hit_counter.GetValue(); } uint32_t GetHitCount() const { return m_hit_counter.GetValue(); }
/// Resets the current Hit Count.
void ResetHitCount() { m_hit_counter.Reset(); }
/// Return the current Ignore Count. /// Return the current Ignore Count.
/// ///
/// \return /// \return

View File

@ -126,6 +126,9 @@ public:
/// Hit count of all locations in this list. /// Hit count of all locations in this list.
uint32_t GetHitCount() const; uint32_t GetHitCount() const;
/// Resets the hit count of all locations in this list.
void ResetHitCount();
/// Enquires of the breakpoint location in this list with ID \a breakID /// Enquires of the breakpoint location in this list with ID \a breakID
/// whether we should stop. /// whether we should stop.
/// ///

View File

@ -884,11 +884,9 @@ public:
/// Called before attaching to a process. /// Called before attaching to a process.
/// ///
/// Allow Process plug-ins to execute some code before attaching a process.
///
/// \return /// \return
/// Returns an error object. /// Returns an error object.
virtual Status WillAttachToProcessWithID(lldb::pid_t pid) { return Status(); } Status WillAttachToProcessWithID(lldb::pid_t pid);
/// Called before attaching to a process. /// Called before attaching to a process.
/// ///
@ -896,8 +894,25 @@ public:
/// ///
/// \return /// \return
/// Returns an error object. /// Returns an error object.
virtual Status WillAttachToProcessWithName(const char *process_name, virtual Status DoWillAttachToProcessWithID(lldb::pid_t pid) {
bool wait_for_launch) { return Status();
}
/// Called before attaching to a process.
///
/// \return
/// Returns an error object.
Status WillAttachToProcessWithName(const char *process_name,
bool wait_for_launch);
/// Called before attaching to a process.
///
/// Allow Process plug-ins to execute some code before attaching a process.
///
/// \return
/// Returns an error object.
virtual Status DoWillAttachToProcessWithName(const char *process_name,
bool wait_for_launch) {
return Status(); return Status();
} }
@ -989,13 +1004,18 @@ public:
/// Called after reported vfork completion. /// Called after reported vfork completion.
virtual void DidVForkDone() {} virtual void DidVForkDone() {}
/// Called before launching to a process.
/// \return
/// Returns an error object.
Status WillLaunch(Module *module);
/// Called before launching to a process. /// Called before launching to a process.
/// ///
/// Allow Process plug-ins to execute some code before launching a process. /// Allow Process plug-ins to execute some code before launching a process.
/// ///
/// \return /// \return
/// Returns an error object. /// Returns an error object.
virtual Status WillLaunch(Module *module) { return Status(); } virtual Status DoWillLaunch(Module *module) { return Status(); }
/// Launch a new process. /// Launch a new process.
/// ///

View File

@ -780,6 +780,9 @@ public:
bool RemoveBreakpointByID(lldb::break_id_t break_id); bool RemoveBreakpointByID(lldb::break_id_t break_id);
/// Resets the hit count of all breakpoints.
void ResetBreakpointHitCounts();
// The flag 'end_to_end', default to true, signifies that the operation is // The flag 'end_to_end', default to true, signifies that the operation is
// performed end to end, for both the debugger and the debuggee. // performed end to end, for both the debugger and the debuggee.

View File

@ -328,6 +328,11 @@ uint32_t Breakpoint::GetIgnoreCount() const {
uint32_t Breakpoint::GetHitCount() const { return m_hit_counter.GetValue(); } uint32_t Breakpoint::GetHitCount() const { return m_hit_counter.GetValue(); }
void Breakpoint::ResetHitCount() {
m_hit_counter.Reset();
m_locations.ResetHitCount();
}
bool Breakpoint::IsOneShot() const { return m_options.IsOneShot(); } bool Breakpoint::IsOneShot() const { return m_options.IsOneShot(); }
void Breakpoint::SetOneShot(bool one_shot) { m_options.SetOneShot(one_shot); } void Breakpoint::SetOneShot(bool one_shot) { m_options.SetOneShot(one_shot); }

View File

@ -186,6 +186,12 @@ void BreakpointList::ClearAllBreakpointSites() {
bp_sp->ClearAllBreakpointSites(); bp_sp->ClearAllBreakpointSites();
} }
void BreakpointList::ResetHitCounts() {
std::lock_guard<std::recursive_mutex> guard(m_mutex);
for (const auto &bp_sp : m_breakpoints)
bp_sp->ResetHitCount();
}
void BreakpointList::GetListMutex( void BreakpointList::GetListMutex(
std::unique_lock<std::recursive_mutex> &lock) { std::unique_lock<std::recursive_mutex> &lock) {
lock = std::unique_lock<std::recursive_mutex>(m_mutex); lock = std::unique_lock<std::recursive_mutex>(m_mutex);

View File

@ -176,6 +176,12 @@ uint32_t BreakpointLocationList::GetHitCount() const {
return hit_count; return hit_count;
} }
void BreakpointLocationList::ResetHitCount() {
std::lock_guard<std::recursive_mutex> guard(m_mutex);
for (auto &loc : m_locations)
loc->ResetHitCount();
}
size_t BreakpointLocationList::GetNumResolvedLocations() const { size_t BreakpointLocationList::GetNumResolvedLocations() const {
std::lock_guard<std::recursive_mutex> guard(m_mutex); std::lock_guard<std::recursive_mutex> guard(m_mutex);
size_t resolve_count = 0; size_t resolve_count = 0;

View File

@ -168,21 +168,21 @@ ProcessKDP::~ProcessKDP() {
Finalize(); Finalize();
} }
Status ProcessKDP::WillLaunch(Module *module) { Status ProcessKDP::DoWillLaunch(Module *module) {
Status error; Status error;
error.SetErrorString("launching not supported in kdp-remote plug-in"); error.SetErrorString("launching not supported in kdp-remote plug-in");
return error; return error;
} }
Status ProcessKDP::WillAttachToProcessWithID(lldb::pid_t pid) { Status ProcessKDP::DoWillAttachToProcessWithID(lldb::pid_t pid) {
Status error; Status error;
error.SetErrorString( error.SetErrorString(
"attaching to a by process ID not supported in kdp-remote plug-in"); "attaching to a by process ID not supported in kdp-remote plug-in");
return error; return error;
} }
Status ProcessKDP::WillAttachToProcessWithName(const char *process_name, Status ProcessKDP::DoWillAttachToProcessWithName(const char *process_name,
bool wait_for_launch) { bool wait_for_launch) {
Status error; Status error;
error.SetErrorString( error.SetErrorString(
"attaching to a by process name not supported in kdp-remote plug-in"); "attaching to a by process name not supported in kdp-remote plug-in");

View File

@ -56,17 +56,17 @@ public:
lldb_private::CommandObject *GetPluginCommandObject() override; lldb_private::CommandObject *GetPluginCommandObject() override;
// Creating a new process, or attaching to an existing one // Creating a new process, or attaching to an existing one
lldb_private::Status WillLaunch(lldb_private::Module *module) override; lldb_private::Status DoWillLaunch(lldb_private::Module *module) override;
lldb_private::Status lldb_private::Status
DoLaunch(lldb_private::Module *exe_module, DoLaunch(lldb_private::Module *exe_module,
lldb_private::ProcessLaunchInfo &launch_info) override; lldb_private::ProcessLaunchInfo &launch_info) override;
lldb_private::Status WillAttachToProcessWithID(lldb::pid_t pid) override; lldb_private::Status DoWillAttachToProcessWithID(lldb::pid_t pid) override;
lldb_private::Status lldb_private::Status
WillAttachToProcessWithName(const char *process_name, DoWillAttachToProcessWithName(const char *process_name,
bool wait_for_launch) override; bool wait_for_launch) override;
lldb_private::Status DoConnectRemote(llvm::StringRef remote_url) override; lldb_private::Status DoConnectRemote(llvm::StringRef remote_url) override;

View File

@ -510,16 +510,16 @@ void ProcessGDBRemote::BuildDynamicRegisterInfo(bool force) {
AddRemoteRegisters(registers, arch_to_use); AddRemoteRegisters(registers, arch_to_use);
} }
Status ProcessGDBRemote::WillLaunch(lldb_private::Module *module) { Status ProcessGDBRemote::DoWillLaunch(lldb_private::Module *module) {
return WillLaunchOrAttach(); return WillLaunchOrAttach();
} }
Status ProcessGDBRemote::WillAttachToProcessWithID(lldb::pid_t pid) { Status ProcessGDBRemote::DoWillAttachToProcessWithID(lldb::pid_t pid) {
return WillLaunchOrAttach(); return WillLaunchOrAttach();
} }
Status ProcessGDBRemote::WillAttachToProcessWithName(const char *process_name, Status ProcessGDBRemote::DoWillAttachToProcessWithName(const char *process_name,
bool wait_for_launch) { bool wait_for_launch) {
return WillLaunchOrAttach(); return WillLaunchOrAttach();
} }

View File

@ -77,16 +77,16 @@ public:
CommandObject *GetPluginCommandObject() override; CommandObject *GetPluginCommandObject() override;
// Creating a new process, or attaching to an existing one // Creating a new process, or attaching to an existing one
Status WillLaunch(Module *module) override; Status DoWillLaunch(Module *module) override;
Status DoLaunch(Module *exe_module, ProcessLaunchInfo &launch_info) override; Status DoLaunch(Module *exe_module, ProcessLaunchInfo &launch_info) override;
void DidLaunch() override; void DidLaunch() override;
Status WillAttachToProcessWithID(lldb::pid_t pid) override; Status DoWillAttachToProcessWithID(lldb::pid_t pid) override;
Status WillAttachToProcessWithName(const char *process_name, Status DoWillAttachToProcessWithName(const char *process_name,
bool wait_for_launch) override; bool wait_for_launch) override;
Status DoConnectRemote(llvm::StringRef remote_url) override; Status DoConnectRemote(llvm::StringRef remote_url) override;

View File

@ -2760,6 +2760,22 @@ ListenerSP ProcessAttachInfo::GetListenerForProcess(Debugger &debugger) {
return debugger.GetListener(); return debugger.GetListener();
} }
Status Process::WillLaunch(Module *module) {
GetTarget().ResetBreakpointHitCounts();
return DoWillLaunch(module);
}
Status Process::WillAttachToProcessWithID(lldb::pid_t pid) {
GetTarget().ResetBreakpointHitCounts();
return DoWillAttachToProcessWithID(pid);
}
Status Process::WillAttachToProcessWithName(const char *process_name,
bool wait_for_launch) {
GetTarget().ResetBreakpointHitCounts();
return DoWillAttachToProcessWithName(process_name, wait_for_launch);
}
Status Process::Attach(ProcessAttachInfo &attach_info) { Status Process::Attach(ProcessAttachInfo &attach_info) {
m_abi_sp.reset(); m_abi_sp.reset();
m_process_input_reader.reset(); m_process_input_reader.reset();

View File

@ -1003,6 +1003,10 @@ bool Target::EnableBreakpointByID(break_id_t break_id) {
return false; return false;
} }
void Target::ResetBreakpointHitCounts() {
GetBreakpointList().ResetHitCounts();
}
Status Target::SerializeBreakpointsToFile(const FileSpec &file, Status Target::SerializeBreakpointsToFile(const FileSpec &file,
const BreakpointIDList &bp_ids, const BreakpointIDList &bp_ids,
bool append) { bool append) {

View File

@ -82,5 +82,6 @@ class AddressBreakpointTestCase(TestBase):
len(threads), 1, len(threads), 1,
"There should be a thread stopped at our breakpoint") "There should be a thread stopped at our breakpoint")
# The hit count for the breakpoint should now be 2. # The hit count for the breakpoint should be 1, since we reset counts
self.assertEquals(breakpoint.GetHitCount(), 2) # for each run.
self.assertEquals(breakpoint.GetHitCount(), 1)

View File

@ -313,8 +313,9 @@ class BreakpointCommandTestCase(TestBase):
substrs=['stopped', substrs=['stopped',
'stop reason = breakpoint']) 'stop reason = breakpoint'])
# The breakpoint should have a hit count of 2. # The breakpoint should have a hit count of 1, since we reset counts
lldbutil.check_breakpoint(self, bpno = 1, expected_hit_count = 2) # for each run.
lldbutil.check_breakpoint(self, bpno = 1, expected_hit_count = 1)
def breakpoint_command_script_parameters(self): def breakpoint_command_script_parameters(self):
"""Test that the frame and breakpoint location are being properly passed to the script breakpoint command function.""" """Test that the frame and breakpoint location are being properly passed to the script breakpoint command function."""

View File

@ -0,0 +1,3 @@
CXX_SOURCES := main.cpp
include Makefile.rules

View File

@ -0,0 +1,58 @@
"""
Test breakpoint hit count is reset when target runs.
"""
import lldb
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
class HitcountResetUponRun(TestBase):
BREAKPOINT_TEXT = 'Set a breakpoint here'
def check_stopped_at_breakpoint_and_hit_once(self, thread, breakpoint):
frame0 = thread.GetFrameAtIndex(0)
location1 = breakpoint.FindLocationByAddress(frame0.GetPC())
self.assertTrue(location1)
self.assertEqual(location1.GetHitCount(), 1)
self.assertEqual(breakpoint.GetHitCount(), 1)
def test_hitcount_reset_upon_run(self):
self.build()
exe = self.getBuildArtifact("a.out")
target = self.dbg.CreateTarget(exe)
self.assertTrue(target, VALID_TARGET)
breakpoint = target.BreakpointCreateBySourceRegex(
self.BREAKPOINT_TEXT, lldb.SBFileSpec('main.cpp'))
self.assertTrue(
breakpoint.IsValid() and breakpoint.GetNumLocations() == 1,
VALID_BREAKPOINT)
process = target.LaunchSimple(
None, None, self.get_process_working_directory())
self.assertTrue(process, PROCESS_IS_VALID)
from lldbsuite.test.lldbutil import get_stopped_thread
# Verify 1st breakpoint location is hit.
thread = get_stopped_thread(process, lldb.eStopReasonBreakpoint)
self.assertTrue(
thread.IsValid(),
"There should be a thread stopped due to breakpoint")
self.check_stopped_at_breakpoint_and_hit_once(thread, breakpoint)
# Relaunch
process.Kill()
process = target.LaunchSimple(
None, None, self.get_process_working_directory())
self.assertTrue(process, PROCESS_IS_VALID)
# Verify the hit counts are still one.
thread = get_stopped_thread(process, lldb.eStopReasonBreakpoint)
self.assertTrue(
thread.IsValid(),
"There should be a thread stopped due to breakpoint")
self.check_stopped_at_breakpoint_and_hit_once(thread, breakpoint)

View File

@ -0,0 +1,6 @@
#include <stdio.h>
int main(int argc, char const *argv[]) {
printf("Set a breakpoint here.\n");
return 0;
}