aboutsummaryrefslogtreecommitdiff
path: root/lldb/source/Breakpoint/WatchpointAlgorithms.cpp
blob: d65de13db1dad52eaa2b79c9a2897eb50ecd3bb6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
//===-- 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 "lldb/Utility/LLDBLog.h"
#include "lldb/Utility/Log.h"

#include <algorithm>
#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 & 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());
  }

  Log *log = GetLog(LLDBLog::Watchpoints);
  LLDB_LOGV(log, "AtomizeWatchpointRequest user request addr {0:x} size {1}",
            addr, size);
  std::vector<WatchpointResourceSP> resources;
  for (Region &ent : entries) {
    LLDB_LOGV(log, "AtomizeWatchpointRequest creating resource {0:x} size {1}",
              ent.addr, ent.size);
    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) {

  Log *log = GetLog(LLDBLog::Watchpoints);
  LLDB_LOGV(log,
            "AtomizeWatchpointRequest user request addr {0:x} size {1} "
            "min_byte_size {2}, max_byte_size {3}, address_byte_size {4}",
            user_addr, user_size, min_byte_size, max_byte_size,
            address_byte_size);

  // Can't watch zero bytes.
  if (user_size == 0)
    return {};

  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
  aligned_size = llvm::bit_ceil(aligned_size);

  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;
}