xref: /openbmc/linux/tools/perf/arch/x86/tests/bp-modify.c (revision 33f44bfd)
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 
bp_1(void)18 static noinline int bp_1(void)
19 {
20 	pr_debug("in %s\n", __func__);
21 	return 0;
22 }
23 
bp_2(void)24 static noinline int bp_2(void)
25 {
26 	pr_debug("in %s\n", __func__);
27 	return 0;
28 }
29 
spawn_child(void)30 static 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  */
bp_modify1(void)59 static 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 
122 out:
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  */
bp_modify2(void)136 static 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 
198 out:
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 
test__bp_modify(struct test_suite * test __maybe_unused,int subtest __maybe_unused)207 int 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