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