aboutsummaryrefslogtreecommitdiff
path: root/lldb
diff options
context:
space:
mode:
Diffstat (limited to 'lldb')
-rw-r--r--lldb/include/lldb/Breakpoint/WatchpointAlgorithms.h109
-rw-r--r--lldb/include/lldb/lldb-enumerations.h26
-rw-r--r--lldb/packages/Python/lldbsuite/test/concurrent_base.py7
-rw-r--r--lldb/source/Breakpoint/CMakeLists.txt1
-rw-r--r--lldb/source/Breakpoint/Watchpoint.cpp28
-rw-r--r--lldb/source/Breakpoint/WatchpointAlgorithms.cpp142
-rw-r--r--lldb/source/Breakpoint/WatchpointResource.cpp4
-rw-r--r--lldb/source/Commands/CommandObjectWatchpoint.cpp15
-rw-r--r--lldb/source/Plugins/Process/Utility/StopInfoMachException.cpp13
-rw-r--r--lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp34
-rw-r--r--lldb/source/Target/StopInfo.cpp4
-rw-r--r--lldb/test/API/functionalities/watchpoint/large-watchpoint/TestLargeWatchpoint.py5
-rw-r--r--lldb/test/API/functionalities/watchpoint/unaligned-large-watchpoint/Makefile3
-rw-r--r--lldb/test/API/functionalities/watchpoint/unaligned-large-watchpoint/TestUnalignedLargeWatchpoint.py101
-rw-r--r--lldb/test/API/functionalities/watchpoint/unaligned-large-watchpoint/main.c35
-rw-r--r--lldb/unittests/Breakpoint/CMakeLists.txt1
-rw-r--r--lldb/unittests/Breakpoint/WatchpointAlgorithmsTests.cpp156
17 files changed, 652 insertions, 32 deletions
diff --git a/lldb/include/lldb/Breakpoint/WatchpointAlgorithms.h b/lldb/include/lldb/Breakpoint/WatchpointAlgorithms.h
new file mode 100644
index 0000000..9b5bb4e
--- /dev/null
+++ b/lldb/include/lldb/Breakpoint/WatchpointAlgorithms.h
@@ -0,0 +1,109 @@
+//===-- WatchpointAlgorithms.h ----------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_BREAKPOINT_WATCHPOINTALGORITHMS_H
+#define LLDB_BREAKPOINT_WATCHPOINTALGORITHMS_H
+
+#include "lldb/Breakpoint/WatchpointResource.h"
+#include "lldb/Utility/ArchSpec.h"
+#include "lldb/lldb-public.h"
+
+#include <vector>
+
+namespace lldb_private {
+
+class WatchpointAlgorithms {
+
+public:
+ /// Convert a user's watchpoint request into an array of memory
+ /// regions that can be watched by one hardware watchpoint register
+ /// on the current target.
+ ///
+ /// \param[in] addr
+ /// The start address specified by the user.
+ ///
+ /// \param[in] size
+ /// The number of bytes the user wants to watch.
+ ///
+ /// \param[in] read
+ /// True if we are watching for read accesses.
+ ///
+ /// \param[in] write
+ /// True if we are watching for write accesses.
+ /// \a read and \a write may both be true.
+ /// There is no "modify" style for WatchpointResources -
+ /// WatchpointResources are akin to the hardware watchpoint
+ /// registers which are either in terms of read or write.
+ /// "modify" distinction is done at the Watchpoint layer, where
+ /// we check the actual range of bytes the user requested.
+ ///
+ /// \param[in] supported_features
+ /// The bit flags in this parameter are set depending on which
+ /// WatchpointHardwareFeature enum values the current target supports.
+ /// The eWatchpointHardwareFeatureUnknown bit may be set if we
+ /// don't have specific information about what the remote stub
+ /// can support, and a reasonablec default will be used.
+ ///
+ /// \param[in] arch
+ /// The ArchSpec of the current Target.
+ ///
+ /// \return
+ /// A vector of WatchpointResourceSP's, one per hardware watchpoint
+ /// register needed. We may return more WatchpointResources than the
+ /// target can watch at once; if all resources cannot be set, the
+ /// watchpoint cannot be set.
+ static std::vector<lldb::WatchpointResourceSP> AtomizeWatchpointRequest(
+ lldb::addr_t addr, size_t size, bool read, bool write,
+ lldb::WatchpointHardwareFeature supported_features, ArchSpec &arch);
+
+ struct Region {
+ lldb::addr_t addr;
+ size_t size;
+ };
+
+protected:
+ /// Convert a user's watchpoint request into an array of addr+size that
+ /// can be watched with power-of-2 style hardware watchpoints.
+ ///
+ /// This is the default algorithm if we have no further information;
+ /// most watchpoint implementations can be assumed to be able to watch up
+ /// to pointer-size regions of memory in power-of-2 sizes and alingments.
+ ///
+ /// \param[in] user_addr
+ /// The user's start address.
+ ///
+ /// \param[in] user_size
+ /// The user's specified byte length.
+ ///
+ /// \param[in] min_byte_size
+ /// The minimum byte size supported on this target.
+ /// In most cases, this will be 1. AArch64 MASK watchpoints can
+ /// watch a minimum of 8 bytes (although Byte Address Select watchpoints
+ /// can watch 1 to pointer-size bytes in a pointer-size aligned granule).
+ ///
+ /// \param[in] max_byte_size
+ /// The maximum byte size supported for one watchpoint on this target.
+ ///
+ /// \param[in] address_byte_size
+ /// The address byte size on this target.
+ static std::vector<Region> PowerOf2Watchpoints(lldb::addr_t user_addr,
+ size_t user_size,
+ size_t min_byte_size,
+ size_t max_byte_size,
+ uint32_t address_byte_size);
+};
+
+// For the unittests to have access to the individual algorithms
+class WatchpointAlgorithmsTest : public WatchpointAlgorithms {
+public:
+ using WatchpointAlgorithms::PowerOf2Watchpoints;
+};
+
+} // namespace lldb_private
+
+#endif // LLDB_BREAKPOINT_WATCHPOINTALGORITHMS_H
diff --git a/lldb/include/lldb/lldb-enumerations.h b/lldb/include/lldb/lldb-enumerations.h
index 392d333..50cbccb 100644
--- a/lldb/include/lldb/lldb-enumerations.h
+++ b/lldb/include/lldb/lldb-enumerations.h
@@ -448,6 +448,32 @@ enum WatchpointWriteType {
eWatchpointWriteTypeOnModify
};
+/// The hardware and native stub capabilities for a given target,
+/// for translating a user's watchpoint request into hardware
+/// capable watchpoint resources.
+FLAGS_ENUM(WatchpointHardwareFeature){
+ /// lldb will fall back to a default that assumes the target
+ /// can watch up to pointer-size power-of-2 regions, aligned to
+ /// power-of-2.
+ eWatchpointHardwareFeatureUnknown = (1u << 0),
+
+ /// Intel systems can watch 1, 2, 4, or 8 bytes (in 64-bit targets),
+ /// aligned naturally.
+ eWatchpointHardwareX86 = (1u << 1),
+
+ /// ARM systems with Byte Address Select watchpoints
+ /// can watch any consecutive series of bytes up to the
+ /// size of a pointer (4 or 8 bytes), at a pointer-size
+ /// alignment.
+ eWatchpointHardwareArmBAS = (1u << 2),
+
+ /// ARM systems with MASK watchpoints can watch any power-of-2
+ /// sized region from 8 bytes to 2 gigabytes, aligned to that
+ /// same power-of-2 alignment.
+ eWatchpointHardwareArmMASK = (1u << 3),
+};
+LLDB_MARK_AS_BITMASK_ENUM(WatchpointHardwareFeature)
+
/// Programming language type.
///
/// These enumerations use the same language enumerations as the DWARF
diff --git a/lldb/packages/Python/lldbsuite/test/concurrent_base.py b/lldb/packages/Python/lldbsuite/test/concurrent_base.py
index 72e04bb..39eb27f 100644
--- a/lldb/packages/Python/lldbsuite/test/concurrent_base.py
+++ b/lldb/packages/Python/lldbsuite/test/concurrent_base.py
@@ -166,7 +166,12 @@ class ConcurrentEventsBase(TestBase):
# Initialize the (single) watchpoint on the global variable (g_watchme)
if num_watchpoint_threads + num_delay_watchpoint_threads > 0:
- self.runCmd("watchpoint set variable g_watchme")
+ # The concurrent tests have multiple threads modifying a variable
+ # with the same value. The default "modify" style watchpoint will
+ # only report this as 1 hit for all threads, because they all wrote
+ # the same value. The testsuite needs "write" style watchpoints to
+ # get the correct number of hits reported.
+ self.runCmd("watchpoint set variable -w write g_watchme")
for w in self.inferior_target.watchpoint_iter():
self.thread_watchpoint = w
self.assertTrue(
diff --git a/lldb/source/Breakpoint/CMakeLists.txt b/lldb/source/Breakpoint/CMakeLists.txt
index 3b39189..2fa659f 100644
--- a/lldb/source/Breakpoint/CMakeLists.txt
+++ b/lldb/source/Breakpoint/CMakeLists.txt
@@ -21,6 +21,7 @@ add_lldb_library(lldbBreakpoint NO_PLUGIN_DEPENDENCIES
StoppointSite.cpp
StopPointSiteList.cpp
Watchpoint.cpp
+ WatchpointAlgorithms.cpp
WatchpointList.cpp
WatchpointOptions.cpp
WatchpointResource.cpp
diff --git a/lldb/source/Breakpoint/Watchpoint.cpp b/lldb/source/Breakpoint/Watchpoint.cpp
index 1a8ef87..7728fd09 100644
--- a/lldb/source/Breakpoint/Watchpoint.cpp
+++ b/lldb/source/Breakpoint/Watchpoint.cpp
@@ -45,10 +45,16 @@ Watchpoint::Watchpoint(Target &target, lldb::addr_t addr, uint32_t size,
LLDB_LOG_ERROR(GetLog(LLDBLog::Watchpoints), std::move(err),
"Failed to set type: {0}");
} else {
- if (auto ts = *type_system_or_err)
- m_type =
- ts->GetBuiltinTypeForEncodingAndBitSize(eEncodingUint, 8 * size);
- else
+ if (auto ts = *type_system_or_err) {
+ if (size <= target.GetArchitecture().GetAddressByteSize()) {
+ m_type =
+ ts->GetBuiltinTypeForEncodingAndBitSize(eEncodingUint, 8 * size);
+ } else {
+ CompilerType clang_uint8_type =
+ ts->GetBuiltinTypeForEncodingAndBitSize(eEncodingUint, 8);
+ m_type = clang_uint8_type.GetArrayType(size);
+ }
+ } else
LLDB_LOG_ERROR(GetLog(LLDBLog::Watchpoints), std::move(err),
"Failed to set type: Typesystem is no longer live: {0}");
}
@@ -352,6 +358,20 @@ void Watchpoint::DumpWithLevel(Stream *s,
s->Printf("\n declare @ '%s'", m_decl_str.c_str());
if (!m_watch_spec_str.empty())
s->Printf("\n watchpoint spec = '%s'", m_watch_spec_str.c_str());
+ if (IsEnabled()) {
+ if (ProcessSP process_sp = m_target.GetProcessSP()) {
+ auto &resourcelist = process_sp->GetWatchpointResourceList();
+ size_t idx = 0;
+ s->Printf("\n watchpoint resources:");
+ for (WatchpointResourceSP &wpres : resourcelist.Sites()) {
+ if (wpres->ConstituentsContains(this)) {
+ s->Printf("\n #%zu: ", idx);
+ wpres->Dump(s);
+ }
+ idx++;
+ }
+ }
+ }
// Dump the snapshots we have taken.
DumpSnapshots(s, " ");
diff --git a/lldb/source/Breakpoint/WatchpointAlgorithms.cpp b/lldb/source/Breakpoint/WatchpointAlgorithms.cpp
new file mode 100644
index 0000000..95a978a
--- /dev/null
+++ b/lldb/source/Breakpoint/WatchpointAlgorithms.cpp
@@ -0,0 +1,142 @@
+//===-- WatchpointAlgorithms.cpp ------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "lldb/Breakpoint/WatchpointAlgorithms.h"
+#include "lldb/Breakpoint/WatchpointResource.h"
+#include "lldb/Target/Process.h"
+#include "lldb/Utility/ArchSpec.h"
+
+#include <utility>
+#include <vector>
+
+using namespace lldb;
+using namespace lldb_private;
+
+std::vector<WatchpointResourceSP>
+WatchpointAlgorithms::AtomizeWatchpointRequest(
+ addr_t addr, size_t size, bool read, bool write,
+ WatchpointHardwareFeature supported_features, ArchSpec &arch) {
+
+ std::vector<Region> entries;
+
+ if (supported_features &
+ WatchpointHardwareFeature::eWatchpointHardwareArmMASK) {
+ entries =
+ PowerOf2Watchpoints(addr, size,
+ /*min_byte_size*/ 1,
+ /*max_byte_size*/ INT32_MAX,
+ /*address_byte_size*/ arch.GetAddressByteSize());
+ } else {
+ // As a fallback, assume we can watch any power-of-2
+ // number of bytes up through the size of an address in the target.
+ entries =
+ PowerOf2Watchpoints(addr, size,
+ /*min_byte_size*/ 1,
+ /*max_byte_size*/ arch.GetAddressByteSize(),
+ /*address_byte_size*/ arch.GetAddressByteSize());
+ }
+
+ std::vector<WatchpointResourceSP> resources;
+ for (Region &ent : entries) {
+ WatchpointResourceSP wp_res_sp =
+ std::make_shared<WatchpointResource>(ent.addr, ent.size, read, write);
+ resources.push_back(wp_res_sp);
+ }
+
+ return resources;
+}
+
+/// Convert a user's watchpoint request (\a user_addr and \a user_size)
+/// into hardware watchpoints, for a target that can watch a power-of-2
+/// region of memory (1, 2, 4, 8, etc), aligned to that same power-of-2
+/// memory address.
+///
+/// If a user asks to watch 4 bytes at address 0x1002 (0x1002-0x1005
+/// inclusive) we can implement this with two 2-byte watchpoints
+/// (0x1002 and 0x1004) or with an 8-byte watchpoint at 0x1000.
+/// A 4-byte watchpoint at 0x1002 would not be properly 4 byte aligned.
+///
+/// If a user asks to watch 16 bytes at 0x1000, and this target supports
+/// 8-byte watchpoints, we can implement this with two 8-byte watchpoints
+/// at 0x1000 and 0x1008.
+std::vector<WatchpointAlgorithms::Region>
+WatchpointAlgorithms::PowerOf2Watchpoints(addr_t user_addr, size_t user_size,
+ size_t min_byte_size,
+ size_t max_byte_size,
+ uint32_t address_byte_size) {
+
+ // Can't watch zero bytes.
+ if (user_size == 0)
+ return {};
+
+ // The aligned watch region will be less than/equal to the size of
+ // an address in this target.
+ const int address_bit_size = address_byte_size * 8;
+
+ size_t aligned_size = std::max(user_size, min_byte_size);
+ /// Round up \a user_size to the next power-of-2 size
+ /// user_size == 8 -> aligned_size == 8
+ /// user_size == 9 -> aligned_size == 16
+ /// user_size == 15 -> aligned_size == 16
+ /// user_size == 192 -> aligned_size == 256
+ /// Could be `std::bit_ceil(aligned_size)` when we build with C++20?
+
+ aligned_size = 1ULL << (address_bit_size - __builtin_clzll(aligned_size - 1));
+
+ addr_t aligned_start = user_addr & ~(aligned_size - 1);
+
+ // Does this power-of-2 memory range, aligned to power-of-2 that the
+ // hardware can watch, completely cover the requested region.
+ if (aligned_size <= max_byte_size &&
+ aligned_start + aligned_size >= user_addr + user_size)
+ return {{aligned_start, aligned_size}};
+
+ // If the maximum region we can watch is larger than the aligned
+ // size, try increasing the region size by one power of 2 and see
+ // if aligning to that amount can cover the requested region.
+ //
+ // Increasing the aligned_size repeatedly instead of splitting the
+ // watchpoint can result in us watching large regions of memory
+ // unintentionally when we could use small two watchpoints. e.g.
+ // user_addr 0x3ff8 user_size 32
+ // can be watched with four 8-byte watchpoints or if it's done with one
+ // MASK watchpoint, it would need to be a 32KB watchpoint (a 16KB
+ // watchpoint at 0x0 only covers 0x0000-0x4000). A user request
+ // at the end of a power-of-2 region can lead to these undesirably
+ // large watchpoints and many false positive hits to ignore.
+ if (max_byte_size >= (aligned_size << 1)) {
+ aligned_size <<= 1;
+ aligned_start = user_addr & ~(aligned_size - 1);
+ if (aligned_size <= max_byte_size &&
+ aligned_start + aligned_size >= user_addr + user_size)
+ return {{aligned_start, aligned_size}};
+
+ // Go back to our original aligned size, to try the multiple
+ // watchpoint approach.
+ aligned_size >>= 1;
+ }
+
+ // We need to split the user's watchpoint into two or more watchpoints
+ // that can be monitored by hardware, because of alignment and/or size
+ // reasons.
+ aligned_size = std::min(aligned_size, max_byte_size);
+ aligned_start = user_addr & ~(aligned_size - 1);
+
+ std::vector<Region> result;
+ addr_t current_address = aligned_start;
+ const addr_t user_end_address = user_addr + user_size;
+ while (current_address + aligned_size < user_end_address) {
+ result.push_back({current_address, aligned_size});
+ current_address += aligned_size;
+ }
+
+ if (current_address < user_end_address)
+ result.push_back({current_address, aligned_size});
+
+ return result;
+}
diff --git a/lldb/source/Breakpoint/WatchpointResource.cpp b/lldb/source/Breakpoint/WatchpointResource.cpp
index c1eb50c..8f15fc7 100644
--- a/lldb/source/Breakpoint/WatchpointResource.cpp
+++ b/lldb/source/Breakpoint/WatchpointResource.cpp
@@ -9,6 +9,7 @@
#include <assert.h>
#include "lldb/Breakpoint/WatchpointResource.h"
+#include "lldb/Utility/Stream.h"
#include <algorithm>
@@ -113,7 +114,8 @@ bool WatchpointResource::ShouldStop(StoppointCallbackContext *context) {
}
void WatchpointResource::Dump(Stream *s) const {
- return; // LWP_TODO
+ s->Printf("addr = 0x%8.8" PRIx64 " size = %zu", m_addr, m_size);
+ return;
}
wp_resource_id_t WatchpointResource::GetNextID() {
diff --git a/lldb/source/Commands/CommandObjectWatchpoint.cpp b/lldb/source/Commands/CommandObjectWatchpoint.cpp
index c80868d..438a16c 100644
--- a/lldb/source/Commands/CommandObjectWatchpoint.cpp
+++ b/lldb/source/Commands/CommandObjectWatchpoint.cpp
@@ -1139,9 +1139,22 @@ protected:
// Fetch the type from the value object, the type of the watched object is
// the pointee type
- /// of the expression, so convert to that if we found a valid type.
+ /// of the expression, so convert to that if we found a valid type.
CompilerType compiler_type(valobj_sp->GetCompilerType());
+ std::optional<uint64_t> valobj_size = valobj_sp->GetByteSize();
+ // Set the type as a uint8_t array if the size being watched is
+ // larger than the ValueObject's size (which is probably the size
+ // of a pointer).
+ if (valobj_size && size > *valobj_size) {
+ auto type_system = compiler_type.GetTypeSystem();
+ if (type_system) {
+ CompilerType clang_uint8_type =
+ type_system->GetBuiltinTypeForEncodingAndBitSize(eEncodingUint, 8);
+ compiler_type = clang_uint8_type.GetArrayType(size);
+ }
+ }
+
Status error;
WatchpointSP watch_sp =
target->CreateWatchpoint(addr, size, &compiler_type, watch_type, error);
diff --git a/lldb/source/Plugins/Process/Utility/StopInfoMachException.cpp b/lldb/source/Plugins/Process/Utility/StopInfoMachException.cpp
index 565941d..d756354 100644
--- a/lldb/source/Plugins/Process/Utility/StopInfoMachException.cpp
+++ b/lldb/source/Plugins/Process/Utility/StopInfoMachException.cpp
@@ -491,14 +491,13 @@ static StopInfoSP GetStopInfoForHardwareBP(Thread &thread, Target *target,
uint64_t exc_sub_sub_code) {
// Try hardware watchpoint.
if (target) {
- // LWP_TODO: We need to find the WatchpointResource that matches
- // the address, and evaluate its Watchpoints.
-
// The exc_sub_code indicates the data break address.
- lldb::WatchpointSP wp_sp =
- target->GetWatchpointList().FindByAddress((lldb::addr_t)exc_sub_code);
- if (wp_sp && wp_sp->IsEnabled()) {
- return StopInfo::CreateStopReasonWithWatchpointID(thread, wp_sp->GetID());
+ WatchpointResourceSP wp_rsrc_sp =
+ target->GetProcessSP()->GetWatchpointResourceList().FindByAddress(
+ (addr_t)exc_sub_code);
+ if (wp_rsrc_sp && wp_rsrc_sp->GetNumberOfConstituents() > 0) {
+ return StopInfo::CreateStopReasonWithWatchpointID(
+ thread, wp_rsrc_sp->GetConstituentAtIndex(0)->GetID());
}
}
diff --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
index fd724350b..4e3447e 100644
--- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
+++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
@@ -24,6 +24,7 @@
#include <sys/types.h>
#include "lldb/Breakpoint/Watchpoint.h"
+#include "lldb/Breakpoint/WatchpointAlgorithms.h"
#include "lldb/Breakpoint/WatchpointResource.h"
#include "lldb/Core/Debugger.h"
#include "lldb/Core/Module.h"
@@ -3153,23 +3154,22 @@ Status ProcessGDBRemote::EnableWatchpoint(WatchpointSP wp_sp, bool notify) {
bool write = wp_sp->WatchpointWrite() || wp_sp->WatchpointModify();
size_t size = wp_sp->GetByteSize();
- // New WatchpointResources needed to implement this Watchpoint.
- std::vector<WatchpointResourceSP> resources;
-
- // LWP_TODO: Break up the user's request into pieces that can be watched
- // given the capabilities of the target cpu / stub software.
- // As a default, breaking the watched region up into target-pointer-sized,
- // aligned, groups.
- //
- // Beyond the default, a stub can / should inform us of its capabilities,
- // e.g. a stub that can do AArch64 power-of-2 MASK watchpoints.
- //
- // And the cpu may have unique capabilities. AArch64 BAS watchpoints
- // can watch any sequential bytes in a doubleword, but Intel watchpoints
- // can only watch 1, 2, 4, 8 bytes within a doubleword.
- WatchpointResourceSP wp_res_sp =
- std::make_shared<WatchpointResource>(addr, size, read, write);
- resources.push_back(wp_res_sp);
+ ArchSpec target_arch = GetTarget().GetArchitecture();
+ WatchpointHardwareFeature supported_features =
+ eWatchpointHardwareFeatureUnknown;
+
+ // LWP_TODO: enable MASK watchpoint for arm64 debugserver
+ // when it reports that it supports them.
+ if (target_arch.GetTriple().getOS() == llvm::Triple::MacOSX &&
+ target_arch.GetTriple().getArch() == llvm::Triple::aarch64) {
+#if 0
+ supported_features |= eWatchpointHardwareArmMASK;
+#endif
+ }
+
+ std::vector<WatchpointResourceSP> resources =
+ WatchpointAlgorithms::AtomizeWatchpointRequest(
+ addr, size, read, write, supported_features, target_arch);
// LWP_TODO: Now that we know the WP Resources needed to implement this
// Watchpoint, we need to look at currently allocated Resources in the
diff --git a/lldb/source/Target/StopInfo.cpp b/lldb/source/Target/StopInfo.cpp
index 3b65d66..95f7805 100644
--- a/lldb/source/Target/StopInfo.cpp
+++ b/lldb/source/Target/StopInfo.cpp
@@ -987,8 +987,10 @@ protected:
// Don't stop if the watched region value is unmodified, and
// this is a Modify-type watchpoint.
- if (m_should_stop && !wp_sp->WatchedValueReportable(exe_ctx))
+ if (m_should_stop && !wp_sp->WatchedValueReportable(exe_ctx)) {
+ wp_sp->UndoHitCount();
m_should_stop = false;
+ }
// Finally, if we are going to stop, print out the new & old values:
if (m_should_stop) {
diff --git a/lldb/test/API/functionalities/watchpoint/large-watchpoint/TestLargeWatchpoint.py b/lldb/test/API/functionalities/watchpoint/large-watchpoint/TestLargeWatchpoint.py
index c5e1614..f7ceb47 100644
--- a/lldb/test/API/functionalities/watchpoint/large-watchpoint/TestLargeWatchpoint.py
+++ b/lldb/test/API/functionalities/watchpoint/large-watchpoint/TestLargeWatchpoint.py
@@ -25,6 +25,11 @@ class UnalignedWatchpointTestCase(TestBase):
@skipIf(archs=no_match(["arm64", "arm64e", "aarch64"]))
@skipUnlessDarwin
+ # LWP_TODO: until debugserver advertises that it supports
+ # MASK watchpoints, this test can't be enabled, lldb won't
+ # try to send watchpoints larger than 8 bytes.
+ @skipIfDarwin
+
# debugserver only gained the ability to watch larger regions
# with this patch.
@skipIfOutOfTreeDebugserver
diff --git a/lldb/test/API/functionalities/watchpoint/unaligned-large-watchpoint/Makefile b/lldb/test/API/functionalities/watchpoint/unaligned-large-watchpoint/Makefile
new file mode 100644
index 0000000..1049594
--- /dev/null
+++ b/lldb/test/API/functionalities/watchpoint/unaligned-large-watchpoint/Makefile
@@ -0,0 +1,3 @@
+C_SOURCES := main.c
+
+include Makefile.rules
diff --git a/lldb/test/API/functionalities/watchpoint/unaligned-large-watchpoint/TestUnalignedLargeWatchpoint.py b/lldb/test/API/functionalities/watchpoint/unaligned-large-watchpoint/TestUnalignedLargeWatchpoint.py
new file mode 100644
index 0000000..fea8f62
--- /dev/null
+++ b/lldb/test/API/functionalities/watchpoint/unaligned-large-watchpoint/TestUnalignedLargeWatchpoint.py
@@ -0,0 +1,101 @@
+"""
+Watch a large unaligned memory region that
+lldb will need multiple hardware watchpoints
+to cover.
+"""
+
+
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+
+class UnalignedLargeWatchpointTestCase(TestBase):
+ def continue_and_report_stop_reason(self, process, iter_str):
+ if self.TraceOn():
+ self.runCmd("script print('continue')")
+ process.Continue()
+ self.assertIn(
+ process.GetState(), [lldb.eStateStopped, lldb.eStateExited], iter_str
+ )
+ thread = process.GetSelectedThread()
+ return thread.GetStopReason()
+
+ NO_DEBUG_INFO_TESTCASE = True
+
+ # Test on 64-bit targets where we probably have
+ # four watchpoint registers that can watch doublewords (8-byte).
+ @skipIf(archs=no_match(["arm64", "arm64e", "aarch64", "x86_64"]))
+ def test_unaligned_large_watchpoint(self):
+ """Test watching an unaligned region of memory that requires multiple watchpoints."""
+ self.build()
+ self.main_source_file = lldb.SBFileSpec("main.c")
+ (target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(
+ self, "break here", self.main_source_file
+ )
+ self.runCmd("break set -p done")
+ self.runCmd("break set -p exiting")
+
+ frame = thread.GetFrameAtIndex(0)
+
+ array_addr = frame.GetValueForVariablePath("array").GetValueAsUnsigned()
+
+ # Don't assume that the heap allocated array is aligned
+ # to a 1024 byte boundary to begin with, force alignment.
+ # wa_addr = (array_addr + 1024) & ~(1024 - 1)
+ wa_addr = array_addr
+
+ # Now make the start address unaligned.
+ wa_addr = wa_addr + 7
+
+ err = lldb.SBError()
+ wp_opts = lldb.SBWatchpointOptions()
+ wp_opts.SetWatchpointTypeWrite(lldb.eWatchpointWriteTypeOnModify)
+ wp = target.WatchpointCreateByAddress(wa_addr, 22, wp_opts, err)
+ self.assertTrue(wp.IsValid())
+ self.assertSuccess(err)
+ if self.TraceOn():
+ self.runCmd("watch list -v")
+
+ c_count = 0
+ reason = self.continue_and_report_stop_reason(process, "continue #%d" % c_count)
+ while reason == lldb.eStopReasonWatchpoint:
+ c_count = c_count + 1
+ reason = self.continue_and_report_stop_reason(
+ process, "continue #%d" % c_count
+ )
+ self.assertLessEqual(c_count, 22)
+
+ self.assertEqual(c_count, 22)
+ self.expect("watchpoint list -v", substrs=["hit_count = 22"])
+ self.assertEqual(wp.GetHitCount(), 22)
+
+ target.DeleteWatchpoint(wp.GetID())
+
+ # Now try watching a 16 byte variable
+ # (not unaligned, but a good check to do anyway)
+ #
+ frame = thread.GetFrameAtIndex(0)
+ err = lldb.SBError()
+ wp = frame.locals["variable"][0].Watch(True, False, True, err)
+ self.assertSuccess(err)
+ if self.TraceOn():
+ self.runCmd("frame select 0")
+ self.runCmd("watchpoint list")
+
+ c_count = 0
+ reason = self.continue_and_report_stop_reason(process, "continue #%d" % c_count)
+ while reason == lldb.eStopReasonWatchpoint:
+ c_count = c_count + 1
+ reason = self.continue_and_report_stop_reason(
+ process, "continue #%d" % c_count
+ )
+ self.assertLessEqual(c_count, 4)
+
+ if self.TraceOn():
+ self.runCmd("frame select 0")
+
+ self.assertEqual(c_count, 4)
+ self.expect("watchpoint list -v", substrs=["hit_count = 4"])
+ self.assertEqual(wp.GetHitCount(), 4)
diff --git a/lldb/test/API/functionalities/watchpoint/unaligned-large-watchpoint/main.c b/lldb/test/API/functionalities/watchpoint/unaligned-large-watchpoint/main.c
new file mode 100644
index 0000000..4c8c66b
--- /dev/null
+++ b/lldb/test/API/functionalities/watchpoint/unaligned-large-watchpoint/main.c
@@ -0,0 +1,35 @@
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+struct obj {
+ uint32_t one;
+ uint32_t two;
+ uint32_t three;
+ uint32_t four;
+};
+
+int main() {
+ const int count = 16776960;
+ uint8_t *array = (uint8_t *)malloc(count);
+ memset(array, 0, count);
+ struct obj variable;
+ variable.one = variable.two = variable.three = variable.four = 0;
+
+ puts("break here");
+
+ for (int i = 0; i < count; i++)
+ array[i]++;
+
+ puts("done iterating");
+
+ variable.one = 1;
+ variable.two = 2;
+ variable.three = 3;
+ variable.four = 4;
+
+ printf("variable value is %d\n",
+ variable.one + variable.two + variable.three + variable.four);
+ puts("exiting.");
+}
diff --git a/lldb/unittests/Breakpoint/CMakeLists.txt b/lldb/unittests/Breakpoint/CMakeLists.txt
index 3164af2..757c2da 100644
--- a/lldb/unittests/Breakpoint/CMakeLists.txt
+++ b/lldb/unittests/Breakpoint/CMakeLists.txt
@@ -1,5 +1,6 @@
add_lldb_unittest(LLDBBreakpointTests
BreakpointIDTest.cpp
+ WatchpointAlgorithmsTests.cpp
LINK_LIBS
lldbBreakpoint
diff --git a/lldb/unittests/Breakpoint/WatchpointAlgorithmsTests.cpp b/lldb/unittests/Breakpoint/WatchpointAlgorithmsTests.cpp
new file mode 100644
index 0000000..6617d36
--- /dev/null
+++ b/lldb/unittests/Breakpoint/WatchpointAlgorithmsTests.cpp
@@ -0,0 +1,156 @@
+//===-- WatchpointAlgorithmsTests.cpp -------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "gtest/gtest.h"
+
+#include "lldb/Breakpoint/WatchpointAlgorithms.h"
+
+#include <utility>
+#include <vector>
+
+using namespace lldb;
+using namespace lldb_private;
+
+struct testcase {
+ WatchpointAlgorithms::Region user; // What the user requested
+ std::vector<WatchpointAlgorithms::Region>
+ hw; // The hardware watchpoints we'll use
+};
+
+void check_testcase(testcase test,
+ std::vector<WatchpointAlgorithms::Region> result,
+ size_t min_byte_size, size_t max_byte_size,
+ uint32_t address_byte_size) {
+
+ EXPECT_EQ(result.size(), test.hw.size());
+ for (size_t i = 0; i < result.size(); i++) {
+ EXPECT_EQ(result[i].addr, test.hw[i].addr);
+ EXPECT_EQ(result[i].size, test.hw[i].size);
+ }
+}
+
+TEST(WatchpointAlgorithmsTests, PowerOf2Watchpoints) {
+
+ // clang-format off
+ std::vector<testcase> doubleword_max = {
+ {
+ {0x1012, 8},
+ {{0x1010, 8}, {0x1018, 8}}
+ },
+ {
+ {0x1002, 4},
+ {{0x1000, 8}}
+ },
+ {
+ {0x1006, 4},
+ {{0x1004, 4}, {0x1008, 4}}
+ },
+ {
+ {0x1006, 8},
+ {{0x1000, 8}, {0x1008, 8}}
+ },
+ {
+ {0x1000, 24},
+ {{0x1000, 8}, {0x1008, 8}, {0x1010, 8}}
+ },
+ {
+ {0x1014, 26},
+ {{0x1010, 8}, {0x1018, 8}, {0x1020, 8}, {0x1028, 8}}
+ },
+ };
+ // clang-format on
+ for (testcase test : doubleword_max) {
+ addr_t user_addr = test.user.addr;
+ size_t user_size = test.user.size;
+ size_t min_byte_size = 1;
+ size_t max_byte_size = 8;
+ size_t address_byte_size = 8;
+ auto result = WatchpointAlgorithmsTest::PowerOf2Watchpoints(
+ user_addr, user_size, min_byte_size, max_byte_size, address_byte_size);
+
+ check_testcase(test, result, min_byte_size, max_byte_size,
+ address_byte_size);
+ }
+
+ // clang-format off
+ std::vector<testcase> word_max = {
+ {
+ {0x1002, 4},
+ {{0x1000, 4}, {0x1004, 4}}
+ },
+ };
+ // clang-format on
+ for (testcase test : word_max) {
+ addr_t user_addr = test.user.addr;
+ size_t user_size = test.user.size;
+ size_t min_byte_size = 1;
+ size_t max_byte_size = 4;
+ size_t address_byte_size = 4;
+ auto result = WatchpointAlgorithmsTest::PowerOf2Watchpoints(
+ user_addr, user_size, min_byte_size, max_byte_size, address_byte_size);
+
+ check_testcase(test, result, min_byte_size, max_byte_size,
+ address_byte_size);
+ }
+
+ // clang-format off
+ std::vector<testcase> twogig_max = {
+ {
+ {0x1010, 16},
+ {{0x1010, 16}}
+ },
+ {
+ {0x1010, 24},
+ {{0x1000, 64}}
+ },
+
+ // We increase 36 to the aligned 64 byte size, but
+ // 0x1000-0x1040 doesn't cover the requested region. Then
+ // we expand to 128 bytes starting at 0x1000 that does
+ // cover it. Is this a good tradeoff for a 36 byte region?
+ {
+ {0x1024, 36},
+ {{0x1000, 128}}
+ },
+ {
+ {0x1000, 192},
+ {{0x1000, 256}}
+ },
+ {
+ {0x1080, 192},
+ {{0x1000, 512}}
+ },
+
+ // In this case, our aligned size is 128, and increasing it to 256
+ // still can't watch the requested region. The algorithm
+ // falls back to using two 128 byte watchpoints.
+ // The alternative would be to use a 1024B watchpoint
+ // starting at 0x1000, to watch this 120 byte user request.
+ //
+ // This still isn't ideal. The user is asking to watch 0x12e0-1358
+ // and could be optimally handled by a
+ // 16-byte watchpoint at 0x12e0 and a 128-byte watchpoint at 0x1300
+ {
+ {0x12e0, 120},
+ {{0x1280, 128}, {0x1300, 128}}
+ },
+ };
+ // clang-format on
+ for (testcase test : twogig_max) {
+ addr_t user_addr = test.user.addr;
+ size_t user_size = test.user.size;
+ size_t min_byte_size = 1;
+ size_t max_byte_size = INT32_MAX;
+ size_t address_byte_size = 8;
+ auto result = WatchpointAlgorithmsTest::PowerOf2Watchpoints(
+ user_addr, user_size, min_byte_size, max_byte_size, address_byte_size);
+
+ check_testcase(test, result, min_byte_size, max_byte_size,
+ address_byte_size);
+ }
+}