Cope with the case where the user-supplied callbacks want the watchpoint itself to be disabled!

Previously we put a WatchpointSentry object within StopInfo.cpp to disable-and-then-enable the watchpoint itself
while we are performing the actions associated with the triggered watchpoint, which can cause the user-initiated
watchpoint disabling action to be negated.

Add a test case to verify that a watchpoint can be disabled during the callbacks.

llvm-svn: 162483
This commit is contained in:
Johnny Chen 2012-08-23 22:28:26 +00:00
parent 0845a1a077
commit 892943f9dd
5 changed files with 93 additions and 1 deletions

View File

@ -156,6 +156,9 @@ public:
void
TurnOffEphemeralMode();
bool
IsDisabledDuringEphemeralMode();
private:
friend class Target;
friend class WatchpointList;
@ -170,6 +173,10 @@ private:
bool m_is_ephemeral; // True if the watchpoint is in the ephemeral mode, meaning that it is
// undergoing a pair of temporary disable/enable actions to avoid recursively
// triggering further watchpoint events.
uint32_t m_disabled_count; // Keep track of the count that the watchpoint is disabled while in ephemeral mode.
// At the end of the ephemeral mode when the watchpoint is to be enabled agian,
// we check the count, if it is more than 1, it means the user-supplied actions
// actually want the watchpoint to be disabled!
uint32_t m_watch_read:1, // 1 if we stop when the watched data is read from
m_watch_write:1, // 1 if we stop when the watched data is written to
m_watch_was_read:1, // Set to 1 when watchpoint is hit for a read access

View File

@ -30,6 +30,7 @@ Watchpoint::Watchpoint (lldb::addr_t addr, size_t size, bool hardware) :
m_is_hardware(hardware),
m_is_watch_variable(false),
m_is_ephemeral(false),
m_disabled_count(0),
m_watch_read(0),
m_watch_write(0),
m_watch_was_read(0),
@ -322,6 +323,14 @@ void
Watchpoint::TurnOffEphemeralMode()
{
m_is_ephemeral = false;
// Leaving ephemeral mode, reset the m_disabled_count!
m_disabled_count = 0;
}
bool
Watchpoint::IsDisabledDuringEphemeralMode()
{
return m_disabled_count > 1;
}
void
@ -331,6 +340,9 @@ Watchpoint::SetEnabled(bool enabled)
{
if (!m_is_ephemeral)
SetHardwareIndex(LLDB_INVALID_INDEX32);
else
++m_disabled_count;
// Don't clear the snapshots for now.
// Within StopInfo.cpp, we purposely do disable/enable watchpoint while performing watchpoint actions.
//ClearSnapshots();

View File

@ -2281,6 +2281,10 @@ ProcessGDBRemote::DisableWatchpoint (Watchpoint *wp)
{
if (log)
log->Printf ("ProcessGDBRemote::DisableWatchpoint (watchID = %llu) addr = 0x%8.8llx -- SUCCESS (already disabled)", watchID, (uint64_t)addr);
// See also 'class WatchpointSentry' within StopInfo.cpp.
// This disabling attempt might come from the user-supplied actions, we'll route it in order for
// the watchpoint object to intelligently process this action.
wp->SetEnabled(false);
return error;
}

View File

@ -463,7 +463,8 @@ public:
{
if (process && watchpoint)
{
process->EnableWatchpoint(watchpoint);
if (!watchpoint->IsDisabledDuringEphemeralMode())
process->EnableWatchpoint(watchpoint);
watchpoint->TurnOffEphemeralMode();
}
}

View File

@ -39,6 +39,21 @@ class WatchpointLLDBCommandTestCase(TestBase):
self.setTearDownCleanup(dictionary=self.d)
self.watchpoint_command()
@unittest2.skipUnless(sys.platform.startswith("darwin"), "requires Darwin")
@dsym_test
def test_watchpoint_command_can_disable_a_watchpoint_with_dsym(self):
"""Test that 'watchpoint command' action can disable a watchpoint after it is triggered."""
self.buildDsym(dictionary=self.d)
self.setTearDownCleanup(dictionary=self.d)
self.watchpoint_command_can_disable_a_watchpoint()
@dwarf_test
def test_watchpoint_command_can_disable_a_watchpoint_with_dwarf(self):
"""Test that 'watchpoint command' action can disable a watchpoint after it is triggered."""
self.buildDwarf(dictionary=self.d)
self.setTearDownCleanup(dictionary=self.d)
self.watchpoint_command_can_disable_a_watchpoint()
def watchpoint_command(self):
"""Do 'watchpoint command add'."""
exe = os.path.join(os.getcwd(), self.exe_name)
@ -90,6 +105,59 @@ class WatchpointLLDBCommandTestCase(TestBase):
self.expect("frame variable -g cookie",
substrs = ['(int32_t)', 'cookie = 777'])
def watchpoint_command_can_disable_a_watchpoint(self):
"""Test that 'watchpoint command' action can disable a watchpoint after it is triggered."""
exe = os.path.join(os.getcwd(), self.exe_name)
self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET)
# Add a breakpoint to set a watchpoint when stopped on the breakpoint.
self.expect("breakpoint set -l %d" % self.line, BREAKPOINT_CREATED,
startstr = "Breakpoint created: 1: file ='%s', line = %d, locations = 1" %
(self.source, self.line))
# Run the program.
self.runCmd("run", RUN_SUCCEEDED)
# We should be stopped again due to the breakpoint.
# The stop reason of the thread should be breakpoint.
self.expect("thread list", STOPPED_DUE_TO_BREAKPOINT,
substrs = ['stopped',
'stop reason = breakpoint'])
# Now let's set a write-type watchpoint for 'global'.
self.expect("watchpoint set variable -w write global", WATCHPOINT_CREATED,
substrs = ['Watchpoint created', 'size = 4', 'type = w',
'%s:%d' % (self.source, self.decl)])
self.runCmd('watchpoint command add 1 -o "watchpoint disable 1"')
# List the watchpoint command we just added.
self.expect("watchpoint command list 1",
substrs = ['watchpoint disable 1'])
# Use the '-v' option to do verbose listing of the watchpoint.
# The hit count should be 0 initially.
self.expect("watchpoint list -v",
substrs = ['hit_count = 0'])
self.runCmd("process continue")
# We should be stopped again due to the watchpoint (write type).
# The stop reason of the thread should be watchpoint.
self.expect("thread backtrace", STOPPED_DUE_TO_WATCHPOINT,
substrs = ['stop reason = watchpoint'])
# Check that the watchpoint has been disabled.
self.expect("watchpoint list -v",
substrs = ['disabled'])
self.runCmd("process continue")
# There should be no more watchpoint hit and the process status should
# be 'exited'.
self.expect("process status",
substrs = ['exited'])
if __name__ == '__main__':
import atexit