1// SPDX-License-Identifier: GPL-2.0
2#include <linux/compiler.h>
3#include <sys/types.h>
4#include <sys/wait.h>
5#include <sys/user.h>
6#include <syscall.h>
7#include <unistd.h>
8#include <stdio.h>
9#include <stdlib.h>
10#include <string.h>
11#include <sys/ptrace.h>
12#include <asm/ptrace.h>
13#include <errno.h>
14#include "debug.h"
15#include "tests/tests.h"
16#include "arch-tests.h"
17
18static noinline int bp_1(void)
19{
20 pr_debug("in %s\n", __func__);
21 return 0;
22}
23
24static noinline int bp_2(void)
25{
26 pr_debug("in %s\n", __func__);
27 return 0;
28}
29
30static int spawn_child(void)
31{
32 int child = fork();
33
34 if (child == 0) {
35 /*
36 * The child sets itself for as tracee and
37 * waits in signal for parent to trace it,
38 * then it calls bp_1 and quits.
39 */
40 int err = ptrace(PTRACE_TRACEME, 0, NULL, NULL);
41
42 if (err) {
43 pr_debug("failed to PTRACE_TRACEME\n");
44 exit(1);
45 }
46
47 raise(SIGCONT);
48 bp_1();
49 exit(0);
50 }
51
52 return child;
53}
54
55/*
56 * This tests creates HW breakpoint, tries to
57 * change it and checks it was properly changed.
58 */
59static int bp_modify1(void)
60{
61 pid_t child;
62 int status;
63 unsigned long rip = 0, dr7 = 1;
64
65 child = spawn_child();
66
67 waitpid(child, &status, 0);
68 if (WIFEXITED(status)) {
69 pr_debug("tracee exited prematurely 1\n");
70 return TEST_FAIL;
71 }
72
73 /*
74 * The parent does following steps:
75 * - creates a new breakpoint (id 0) for bp_2 function
76 * - changes that breakpoint to bp_1 function
77 * - waits for the breakpoint to hit and checks
78 * it has proper rip of bp_1 function
79 * - detaches the child
80 */
81 if (ptrace(PTRACE_POKEUSER, child,
82 offsetof(struct user, u_debugreg[0]), bp_2)) {
83 pr_debug("failed to set breakpoint, 1st time: %s\n",
84 strerror(errno));
85 goto out;
86 }
87
88 if (ptrace(PTRACE_POKEUSER, child,
89 offsetof(struct user, u_debugreg[0]), bp_1)) {
90 pr_debug("failed to set breakpoint, 2nd time: %s\n",
91 strerror(errno));
92 goto out;
93 }
94
95 if (ptrace(PTRACE_POKEUSER, child,
96 offsetof(struct user, u_debugreg[7]), dr7)) {
97 pr_debug("failed to set dr7: %s\n", strerror(errno));
98 goto out;
99 }
100
101 if (ptrace(PTRACE_CONT, child, NULL, NULL)) {
102 pr_debug("failed to PTRACE_CONT: %s\n", strerror(errno));
103 goto out;
104 }
105
106 waitpid(child, &status, 0);
107 if (WIFEXITED(status)) {
108 pr_debug("tracee exited prematurely 2\n");
109 return TEST_FAIL;
110 }
111
112 rip = ptrace(PTRACE_PEEKUSER, child,
113 offsetof(struct user_regs_struct, rip), NULL);
114 if (rip == (unsigned long) -1) {
115 pr_debug("failed to PTRACE_PEEKUSER: %s\n",
116 strerror(errno));
117 goto out;
118 }
119
120 pr_debug("rip %lx, bp_1 %p\n", rip, bp_1);
121
122out:
123 if (ptrace(PTRACE_DETACH, child, NULL, NULL)) {
124 pr_debug("failed to PTRACE_DETACH: %s", strerror(errno));
125 return TEST_FAIL;
126 }
127
128 return rip == (unsigned long) bp_1 ? TEST_OK : TEST_FAIL;
129}
130
131/*
132 * This tests creates HW breakpoint, tries to
133 * change it to bogus value and checks the original
134 * breakpoint is hit.
135 */
136static int bp_modify2(void)
137{
138 pid_t child;
139 int status;
140 unsigned long rip = 0, dr7 = 1;
141
142 child = spawn_child();
143
144 waitpid(child, &status, 0);
145 if (WIFEXITED(status)) {
146 pr_debug("tracee exited prematurely 1\n");
147 return TEST_FAIL;
148 }
149
150 /*
151 * The parent does following steps:
152 * - creates a new breakpoint (id 0) for bp_1 function
153 * - tries to change that breakpoint to (-1) address
154 * - waits for the breakpoint to hit and checks
155 * it has proper rip of bp_1 function
156 * - detaches the child
157 */
158 if (ptrace(PTRACE_POKEUSER, child,
159 offsetof(struct user, u_debugreg[0]), bp_1)) {
160 pr_debug("failed to set breakpoint: %s\n",
161 strerror(errno));
162 goto out;
163 }
164
165 if (ptrace(PTRACE_POKEUSER, child,
166 offsetof(struct user, u_debugreg[7]), dr7)) {
167 pr_debug("failed to set dr7: %s\n", strerror(errno));
168 goto out;
169 }
170
171 if (!ptrace(PTRACE_POKEUSER, child,
172 offsetof(struct user, u_debugreg[0]), (unsigned long) (-1))) {
173 pr_debug("failed, breakpoint set to bogus address\n");
174 goto out;
175 }
176
177 if (ptrace(PTRACE_CONT, child, NULL, NULL)) {
178 pr_debug("failed to PTRACE_CONT: %s\n", strerror(errno));
179 goto out;
180 }
181
182 waitpid(child, &status, 0);
183 if (WIFEXITED(status)) {
184 pr_debug("tracee exited prematurely 2\n");
185 return TEST_FAIL;
186 }
187
188 rip = ptrace(PTRACE_PEEKUSER, child,
189 offsetof(struct user_regs_struct, rip), NULL);
190 if (rip == (unsigned long) -1) {
191 pr_debug("failed to PTRACE_PEEKUSER: %s\n",
192 strerror(errno));
193 goto out;
194 }
195
196 pr_debug("rip %lx, bp_1 %p\n", rip, bp_1);
197
198out:
199 if (ptrace(PTRACE_DETACH, child, NULL, NULL)) {
200 pr_debug("failed to PTRACE_DETACH: %s", strerror(errno));
201 return TEST_FAIL;
202 }
203
204 return rip == (unsigned long) bp_1 ? TEST_OK : TEST_FAIL;
205}
206
207int test__bp_modify(struct test_suite *test __maybe_unused,
208 int subtest __maybe_unused)
209{
210 TEST_ASSERT_VAL("modify test 1 failed\n", !bp_modify1());
211 TEST_ASSERT_VAL("modify test 2 failed\n", !bp_modify2());
212
213 return 0;
214}
215

source code of linux/tools/perf/arch/x86/tests/bp-modify.c