Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

powerpc/watchpoints: Remove ptrace/perf exclusion tracking

ptrace and perf watchpoints were considered incompatible in
commit 29da4f91c0c1 ("powerpc/watchpoint: Don't allow concurrent perf
and ptrace events"), but the logic in that commit doesn't really apply.

Ptrace doesn't automatically single step; the ptracer must request this
explicitly. And the ptracer can do so regardless of whether a
ptrace/perf watchpoint triggered or not: it could single step every
instruction if it wanted to. Whatever stopped the ptracee before
executing the instruction that would trigger the perf watchpoint is no
longer relevant by this point.

To get correct behaviour when perf and ptrace are watching the same
data we must ignore the perf watchpoint. After all, ptrace has
before-execute semantics, and perf is after-execute, so perf doesn't
actually care about the watchpoint trigger at this point in time.
Pausing before execution does not mean we will actually end up executing
the instruction.

Importantly though, we don't remove the perf watchpoint yet. This is
key.

The ptracer is free to do whatever it likes right now. E.g., it can
continue the process, single step. or even set the child PC somewhere
completely different.

If it does try to execute the instruction though, without reinserting
the watchpoint (in which case we go back to the start of this example),
the perf watchpoint would immediately trigger. This time there is no
ptrace watchpoint, so we can safely perform a single step and increment
the perf counter. Upon receiving the single step exception, the existing
code already handles propagating or consuming it based on whether
another subsystem (e.g. ptrace) requested a single step. Again, this is
needed with or without perf/ptrace exclusion, because ptrace could be
single stepping this instruction regardless of if a watchpoint is
involved.

Signed-off-by: Benjamin Gray <bgray@linux.ibm.com>
Signed-off-by: Michael Ellerman <mpe@ellerman.id.au>
Link: https://msgid.link/20230801011744.153973-6-bgray@linux.ibm.com

authored by

Benjamin Gray and committed by
Michael Ellerman
bd29813a 5a2d8b9c

+1 -248
+1 -248
arch/powerpc/kernel/hw_breakpoint.c
··· 114 114 return bp->overflow_handler == ptrace_triggered; 115 115 } 116 116 117 - struct breakpoint { 118 - struct list_head list; 119 - struct perf_event *bp; 120 - bool ptrace_bp; 121 - }; 122 - 123 - /* 124 - * While kernel/events/hw_breakpoint.c does its own synchronization, we cannot 125 - * rely on it safely synchronizing internals here; however, we can rely on it 126 - * not requesting more breakpoints than available. 127 - */ 128 - static DEFINE_SPINLOCK(cpu_bps_lock); 129 - static DEFINE_PER_CPU(struct breakpoint *, cpu_bps[HBP_NUM_MAX]); 130 - static DEFINE_SPINLOCK(task_bps_lock); 131 - static LIST_HEAD(task_bps); 132 - 133 - static struct breakpoint *alloc_breakpoint(struct perf_event *bp) 134 - { 135 - struct breakpoint *tmp; 136 - 137 - tmp = kzalloc(sizeof(*tmp), GFP_KERNEL); 138 - if (!tmp) 139 - return ERR_PTR(-ENOMEM); 140 - tmp->bp = bp; 141 - tmp->ptrace_bp = is_ptrace_bp(bp); 142 - return tmp; 143 - } 144 - 145 - static bool bp_addr_range_overlap(struct perf_event *bp1, struct perf_event *bp2) 146 - { 147 - __u64 bp1_saddr, bp1_eaddr, bp2_saddr, bp2_eaddr; 148 - 149 - bp1_saddr = ALIGN_DOWN(bp1->attr.bp_addr, HW_BREAKPOINT_SIZE); 150 - bp1_eaddr = ALIGN(bp1->attr.bp_addr + bp1->attr.bp_len, HW_BREAKPOINT_SIZE); 151 - bp2_saddr = ALIGN_DOWN(bp2->attr.bp_addr, HW_BREAKPOINT_SIZE); 152 - bp2_eaddr = ALIGN(bp2->attr.bp_addr + bp2->attr.bp_len, HW_BREAKPOINT_SIZE); 153 - 154 - return (bp1_saddr < bp2_eaddr && bp1_eaddr > bp2_saddr); 155 - } 156 - 157 - static bool alternate_infra_bp(struct breakpoint *b, struct perf_event *bp) 158 - { 159 - return is_ptrace_bp(bp) ? !b->ptrace_bp : b->ptrace_bp; 160 - } 161 - 162 - static bool can_co_exist(struct breakpoint *b, struct perf_event *bp) 163 - { 164 - return !(alternate_infra_bp(b, bp) && bp_addr_range_overlap(b->bp, bp)); 165 - } 166 - 167 - static int task_bps_add(struct perf_event *bp) 168 - { 169 - struct breakpoint *tmp; 170 - 171 - tmp = alloc_breakpoint(bp); 172 - if (IS_ERR(tmp)) 173 - return PTR_ERR(tmp); 174 - 175 - spin_lock(&task_bps_lock); 176 - list_add(&tmp->list, &task_bps); 177 - spin_unlock(&task_bps_lock); 178 - return 0; 179 - } 180 - 181 - static void task_bps_remove(struct perf_event *bp) 182 - { 183 - struct list_head *pos, *q; 184 - 185 - spin_lock(&task_bps_lock); 186 - list_for_each_safe(pos, q, &task_bps) { 187 - struct breakpoint *tmp = list_entry(pos, struct breakpoint, list); 188 - 189 - if (tmp->bp == bp) { 190 - list_del(&tmp->list); 191 - kfree(tmp); 192 - break; 193 - } 194 - } 195 - spin_unlock(&task_bps_lock); 196 - } 197 - 198 - /* 199 - * If any task has breakpoint from alternate infrastructure, 200 - * return true. Otherwise return false. 201 - */ 202 - static bool all_task_bps_check(struct perf_event *bp) 203 - { 204 - struct breakpoint *tmp; 205 - bool ret = false; 206 - 207 - spin_lock(&task_bps_lock); 208 - list_for_each_entry(tmp, &task_bps, list) { 209 - if (!can_co_exist(tmp, bp)) { 210 - ret = true; 211 - break; 212 - } 213 - } 214 - spin_unlock(&task_bps_lock); 215 - return ret; 216 - } 217 - 218 - /* 219 - * If same task has breakpoint from alternate infrastructure, 220 - * return true. Otherwise return false. 221 - */ 222 - static bool same_task_bps_check(struct perf_event *bp) 223 - { 224 - struct breakpoint *tmp; 225 - bool ret = false; 226 - 227 - spin_lock(&task_bps_lock); 228 - list_for_each_entry(tmp, &task_bps, list) { 229 - if (tmp->bp->hw.target == bp->hw.target && 230 - !can_co_exist(tmp, bp)) { 231 - ret = true; 232 - break; 233 - } 234 - } 235 - spin_unlock(&task_bps_lock); 236 - return ret; 237 - } 238 - 239 - static int cpu_bps_add(struct perf_event *bp) 240 - { 241 - struct breakpoint **cpu_bp; 242 - struct breakpoint *tmp; 243 - int i = 0; 244 - 245 - tmp = alloc_breakpoint(bp); 246 - if (IS_ERR(tmp)) 247 - return PTR_ERR(tmp); 248 - 249 - spin_lock(&cpu_bps_lock); 250 - cpu_bp = per_cpu_ptr(cpu_bps, bp->cpu); 251 - for (i = 0; i < nr_wp_slots(); i++) { 252 - if (!cpu_bp[i]) { 253 - cpu_bp[i] = tmp; 254 - break; 255 - } 256 - } 257 - spin_unlock(&cpu_bps_lock); 258 - return 0; 259 - } 260 - 261 - static void cpu_bps_remove(struct perf_event *bp) 262 - { 263 - struct breakpoint **cpu_bp; 264 - int i = 0; 265 - 266 - spin_lock(&cpu_bps_lock); 267 - cpu_bp = per_cpu_ptr(cpu_bps, bp->cpu); 268 - for (i = 0; i < nr_wp_slots(); i++) { 269 - if (!cpu_bp[i]) 270 - continue; 271 - 272 - if (cpu_bp[i]->bp == bp) { 273 - kfree(cpu_bp[i]); 274 - cpu_bp[i] = NULL; 275 - break; 276 - } 277 - } 278 - spin_unlock(&cpu_bps_lock); 279 - } 280 - 281 - static bool cpu_bps_check(int cpu, struct perf_event *bp) 282 - { 283 - struct breakpoint **cpu_bp; 284 - bool ret = false; 285 - int i; 286 - 287 - spin_lock(&cpu_bps_lock); 288 - cpu_bp = per_cpu_ptr(cpu_bps, cpu); 289 - for (i = 0; i < nr_wp_slots(); i++) { 290 - if (cpu_bp[i] && !can_co_exist(cpu_bp[i], bp)) { 291 - ret = true; 292 - break; 293 - } 294 - } 295 - spin_unlock(&cpu_bps_lock); 296 - return ret; 297 - } 298 - 299 - static bool all_cpu_bps_check(struct perf_event *bp) 300 - { 301 - int cpu; 302 - 303 - for_each_online_cpu(cpu) { 304 - if (cpu_bps_check(cpu, bp)) 305 - return true; 306 - } 307 - return false; 308 - } 309 - 310 - int arch_reserve_bp_slot(struct perf_event *bp) 311 - { 312 - int ret; 313 - 314 - /* ptrace breakpoint */ 315 - if (is_ptrace_bp(bp)) { 316 - if (all_cpu_bps_check(bp)) 317 - return -ENOSPC; 318 - 319 - if (same_task_bps_check(bp)) 320 - return -ENOSPC; 321 - 322 - return task_bps_add(bp); 323 - } 324 - 325 - /* perf breakpoint */ 326 - if (is_kernel_addr(bp->attr.bp_addr)) 327 - return 0; 328 - 329 - if (bp->hw.target && bp->cpu == -1) { 330 - if (same_task_bps_check(bp)) 331 - return -ENOSPC; 332 - 333 - return task_bps_add(bp); 334 - } else if (!bp->hw.target && bp->cpu != -1) { 335 - if (all_task_bps_check(bp)) 336 - return -ENOSPC; 337 - 338 - return cpu_bps_add(bp); 339 - } 340 - 341 - if (same_task_bps_check(bp)) 342 - return -ENOSPC; 343 - 344 - ret = cpu_bps_add(bp); 345 - if (ret) 346 - return ret; 347 - ret = task_bps_add(bp); 348 - if (ret) 349 - cpu_bps_remove(bp); 350 - 351 - return ret; 352 - } 353 - 354 - void arch_release_bp_slot(struct perf_event *bp) 355 - { 356 - if (!is_kernel_addr(bp->attr.bp_addr)) { 357 - if (bp->hw.target) 358 - task_bps_remove(bp); 359 - if (bp->cpu != -1) 360 - cpu_bps_remove(bp); 361 - } 362 - } 363 - 364 117 /* 365 118 * Check for virtual address in kernel space. 366 119 */ ··· 440 687 */ 441 688 if (ptrace_bp) { 442 689 for (i = 0; i < nr_wp_slots(); i++) { 443 - if (!hit[i]) 690 + if (!hit[i] || !is_ptrace_bp(bp[i])) 444 691 continue; 445 692 perf_bp_event(bp[i], regs); 446 693 bp[i] = NULL;