1/* Test case for setting a memory-write unaligned watchpoint on aarch64.
2
3  This software is provided 'as-is', without any express or implied
4  warranty.  In no event will the authors be held liable for any damages
5  arising from the use of this software.
6
7  Permission is granted to anyone to use this software for any purpose,
8  including commercial applications, and to alter it and redistribute it
9  freely.  */
10
11#define _GNU_SOURCE 1
12#include <stdlib.h>
13#include <unistd.h>
14#include <sys/ptrace.h>
15#include <asm/ptrace.h>
16#include <assert.h>
17#include <sys/wait.h>
18#include <stddef.h>
19#include <errno.h>
20#include <sys/uio.h>
21#include <elf.h>
22#include <error.h>
23
24static pid_t child;
25
26static void
27cleanup (void)
28{
29  if (child > 0)
30    kill (child, SIGKILL);
31  child = 0;
32}
33
34/* Macros to extract fields from the hardware debug information word.  */
35#define AARCH64_DEBUG_NUM_SLOTS(x) ((x) & 0xff)
36#define AARCH64_DEBUG_ARCH(x) (((x) >> 8) & 0xff)
37/* Macro for the expected version of the ARMv8-A debug architecture.  */
38#define AARCH64_DEBUG_ARCH_V8 0x6
39#define DR_CONTROL_ENABLED(ctrl)        (((ctrl) & 0x1) == 1)
40#define DR_CONTROL_LENGTH(ctrl)         (((ctrl) >> 5) & 0xff)
41
42static void
43set_watchpoint (pid_t pid, volatile void *addr, unsigned len_mask)
44{
45  struct user_hwdebug_state dreg_state;
46  struct iovec iov;
47  long l;
48
49  assert (len_mask >= 0x01);
50  assert (len_mask <= 0xff);
51
52  iov.iov_base = &dreg_state;
53  iov.iov_len = sizeof (dreg_state);
54  errno = 0;
55  l = ptrace (PTRACE_GETREGSET, pid, NT_ARM_HW_WATCH, &iov);
56  assert (l == 0);
57  assert (AARCH64_DEBUG_ARCH (dreg_state.dbg_info) == AARCH64_DEBUG_ARCH_V8);
58  assert (AARCH64_DEBUG_NUM_SLOTS (dreg_state.dbg_info) >= 1);
59
60  assert (!DR_CONTROL_ENABLED (dreg_state.dbg_regs[0].ctrl));
61  dreg_state.dbg_regs[0].ctrl |= 1;
62  assert ( DR_CONTROL_ENABLED (dreg_state.dbg_regs[0].ctrl));
63
64  assert (DR_CONTROL_LENGTH (dreg_state.dbg_regs[0].ctrl) == 0);
65  dreg_state.dbg_regs[0].ctrl |= len_mask << 5;
66  assert (DR_CONTROL_LENGTH (dreg_state.dbg_regs[0].ctrl) == len_mask);
67
68  dreg_state.dbg_regs[0].ctrl |= 2 << 3; // write
69  dreg_state.dbg_regs[0].ctrl |= 2 << 1; // enabled at el0
70  dreg_state.dbg_regs[0].addr = (uintptr_t) addr;
71
72  iov.iov_base = &dreg_state;
73  iov.iov_len = (offsetof (struct user_hwdebug_state, dbg_regs)
74                 + sizeof (dreg_state.dbg_regs[0]));
75  errno = 0;
76  l = ptrace (PTRACE_SETREGSET, pid, NT_ARM_HW_WATCH, &iov);
77  if (errno != 0)
78    error (1, errno, "PTRACE_SETREGSET: NT_ARM_HW_WATCH");
79  assert (l == 0);
80}
81
82static volatile long long check;
83
84int
85main (void)
86{
87  pid_t got_pid;
88  int i, status;
89  long l;
90
91  atexit (cleanup);
92
93  child = fork ();
94  assert (child >= 0);
95  if (child == 0)
96    {
97      l = ptrace (PTRACE_TRACEME, 0, NULL, NULL);
98      assert (l == 0);
99      i = raise (SIGUSR1);
100      assert (i == 0);
101      check = -1;
102      i = raise (SIGUSR2);
103      /* NOTREACHED */
104      assert (0);
105    }
106
107  got_pid = waitpid (child, &status, 0);
108  assert (got_pid == child);
109  assert (WIFSTOPPED (status));
110  assert (WSTOPSIG (status) == SIGUSR1);
111
112  /* Add a watchpoint to check.
113     Restart the child. It will write to check.
114     Check child has stopped on the watchpoint.  */
115  set_watchpoint (child, &check, 0x02);
116
117  errno = 0;
118  l = ptrace (PTRACE_CONT, child, 0l, 0l);
119  assert_perror (errno);
120  assert (l == 0);
121
122  got_pid = waitpid (child, &status, 0);
123  assert (got_pid == child);
124  assert (WIFSTOPPED (status));
125  if (WSTOPSIG (status) == SIGUSR2)
126    {
127      /* We missed the watchpoint - unsupported by hardware?  */
128      cleanup ();
129      return 2;
130    }
131  assert (WSTOPSIG (status) == SIGTRAP);
132
133  return 0;
134}
135