|
| 1 | +//===-- WatchpointAlgorithms.cpp ------------------------------------------===// |
| 2 | +// |
| 3 | +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | +// See https://llvm.org/LICENSE.txt for license information. |
| 5 | +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | +// |
| 7 | +//===----------------------------------------------------------------------===// |
| 8 | + |
| 9 | +#include "lldb/Breakpoint/WatchpointAlgorithms.h" |
| 10 | +#include "lldb/Breakpoint/WatchpointResource.h" |
| 11 | +#include "lldb/Target/Process.h" |
| 12 | +#include "lldb/Utility/ArchSpec.h" |
| 13 | + |
| 14 | +#include <utility> |
| 15 | +#include <vector> |
| 16 | + |
| 17 | +using namespace lldb; |
| 18 | +using namespace lldb_private; |
| 19 | + |
| 20 | +std::vector<WatchpointResourceSP> |
| 21 | +WatchpointAlgorithms::AtomizeWatchpointRequest( |
| 22 | + addr_t addr, size_t size, bool read, bool write, |
| 23 | + WatchpointHardwareFeature supported_features, ArchSpec &arch) { |
| 24 | + |
| 25 | + std::vector<Region> entries; |
| 26 | + |
| 27 | + if (supported_features & |
| 28 | + WatchpointHardwareFeature::eWatchpointHardwareArmMASK) { |
| 29 | + entries = |
| 30 | + PowerOf2Watchpoints(addr, size, |
| 31 | + /*min_byte_size*/ 1, |
| 32 | + /*max_byte_size*/ INT32_MAX, |
| 33 | + /*address_byte_size*/ arch.GetAddressByteSize()); |
| 34 | + } else { |
| 35 | + // As a fallback, assume we can watch any power-of-2 |
| 36 | + // number of bytes up through the size of an address in the target. |
| 37 | + entries = |
| 38 | + PowerOf2Watchpoints(addr, size, |
| 39 | + /*min_byte_size*/ 1, |
| 40 | + /*max_byte_size*/ arch.GetAddressByteSize(), |
| 41 | + /*address_byte_size*/ arch.GetAddressByteSize()); |
| 42 | + } |
| 43 | + |
| 44 | + std::vector<WatchpointResourceSP> resources; |
| 45 | + for (Region &ent : entries) { |
| 46 | + WatchpointResourceSP wp_res_sp = |
| 47 | + std::make_shared<WatchpointResource>(ent.addr, ent.size, read, write); |
| 48 | + resources.push_back(wp_res_sp); |
| 49 | + } |
| 50 | + |
| 51 | + return resources; |
| 52 | +} |
| 53 | + |
| 54 | +/// Convert a user's watchpoint request (\a user_addr and \a user_size) |
| 55 | +/// into hardware watchpoints, for a target that can watch a power-of-2 |
| 56 | +/// region of memory (1, 2, 4, 8, etc), aligned to that same power-of-2 |
| 57 | +/// memory address. |
| 58 | +/// |
| 59 | +/// If a user asks to watch 4 bytes at address 0x1002 (0x1002-0x1005 |
| 60 | +/// inclusive) we can implement this with two 2-byte watchpoints |
| 61 | +/// (0x1002 and 0x1004) or with an 8-byte watchpoint at 0x1000. |
| 62 | +/// A 4-byte watchpoint at 0x1002 would not be properly 4 byte aligned. |
| 63 | +/// |
| 64 | +/// If a user asks to watch 16 bytes at 0x1000, and this target supports |
| 65 | +/// 8-byte watchpoints, we can implement this with two 8-byte watchpoints |
| 66 | +/// at 0x1000 and 0x1008. |
| 67 | +std::vector<WatchpointAlgorithms::Region> |
| 68 | +WatchpointAlgorithms::PowerOf2Watchpoints(addr_t user_addr, size_t user_size, |
| 69 | + size_t min_byte_size, |
| 70 | + size_t max_byte_size, |
| 71 | + uint32_t address_byte_size) { |
| 72 | + |
| 73 | + // Can't watch zero bytes. |
| 74 | + if (user_size == 0) |
| 75 | + return {}; |
| 76 | + |
| 77 | + // The aligned watch region will be less than/equal to the size of |
| 78 | + // an address in this target. |
| 79 | + const int address_bit_size = address_byte_size * 8; |
| 80 | + |
| 81 | + size_t aligned_size = std::max(user_size, min_byte_size); |
| 82 | + /// Round up \a user_size to the next power-of-2 size |
| 83 | + /// user_size == 8 -> aligned_size == 8 |
| 84 | + /// user_size == 9 -> aligned_size == 16 |
| 85 | + /// user_size == 15 -> aligned_size == 16 |
| 86 | + /// user_size == 192 -> aligned_size == 256 |
| 87 | + /// Could be `std::bit_ceil(aligned_size)` when we build with C++20? |
| 88 | + |
| 89 | + aligned_size = 1ULL << (address_bit_size - __builtin_clzll(aligned_size - 1)); |
| 90 | + |
| 91 | + addr_t aligned_start = user_addr & ~(aligned_size - 1); |
| 92 | + |
| 93 | + // Does this power-of-2 memory range, aligned to power-of-2 that the |
| 94 | + // hardware can watch, completely cover the requested region. |
| 95 | + if (aligned_size <= max_byte_size && |
| 96 | + aligned_start + aligned_size >= user_addr + user_size) |
| 97 | + return {{aligned_start, aligned_size}}; |
| 98 | + |
| 99 | + // If the maximum region we can watch is larger than the aligned |
| 100 | + // size, try increasing the region size by one power of 2 and see |
| 101 | + // if aligning to that amount can cover the requested region. |
| 102 | + // |
| 103 | + // Increasing the aligned_size repeatedly instead of splitting the |
| 104 | + // watchpoint can result in us watching large regions of memory |
| 105 | + // unintentionally when we could use small two watchpoints. e.g. |
| 106 | + // user_addr 0x3ff8 user_size 32 |
| 107 | + // can be watched with four 8-byte watchpoints or if it's done with one |
| 108 | + // MASK watchpoint, it would need to be a 32KB watchpoint (a 16KB |
| 109 | + // watchpoint at 0x0 only covers 0x0000-0x4000). A user request |
| 110 | + // at the end of a power-of-2 region can lead to these undesirably |
| 111 | + // large watchpoints and many false positive hits to ignore. |
| 112 | + if (max_byte_size >= (aligned_size << 1)) { |
| 113 | + aligned_size <<= 1; |
| 114 | + aligned_start = user_addr & ~(aligned_size - 1); |
| 115 | + if (aligned_size <= max_byte_size && |
| 116 | + aligned_start + aligned_size >= user_addr + user_size) |
| 117 | + return {{aligned_start, aligned_size}}; |
| 118 | + |
| 119 | + // Go back to our original aligned size, to try the multiple |
| 120 | + // watchpoint approach. |
| 121 | + aligned_size >>= 1; |
| 122 | + } |
| 123 | + |
| 124 | + // We need to split the user's watchpoint into two or more watchpoints |
| 125 | + // that can be monitored by hardware, because of alignment and/or size |
| 126 | + // reasons. |
| 127 | + aligned_size = std::min(aligned_size, max_byte_size); |
| 128 | + aligned_start = user_addr & ~(aligned_size - 1); |
| 129 | + |
| 130 | + std::vector<Region> result; |
| 131 | + addr_t current_address = aligned_start; |
| 132 | + const addr_t user_end_address = user_addr + user_size; |
| 133 | + while (current_address + aligned_size < user_end_address) { |
| 134 | + result.push_back({current_address, aligned_size}); |
| 135 | + current_address += aligned_size; |
| 136 | + } |
| 137 | + |
| 138 | + if (current_address < user_end_address) |
| 139 | + result.push_back({current_address, aligned_size}); |
| 140 | + |
| 141 | + return result; |
| 142 | +} |
0 commit comments