xref: /linux/tools/perf/arch/x86/tests/bp-modify.c (revision bf4afc53b77aeaa48b5409da5c8da6bb4eff7f43)
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 
18 static noinline int bp_1(void)
19 {
20 	pr_debug("in %s\n", __func__);
21 	return 0;
22 }
23 
24 static noinline int bp_2(void)
25 {
26 	pr_debug("in %s\n", __func__);
27 	return 0;
28 }
29 
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  */
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: %m\n");
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: %m\n");
90 		goto out;
91 	}
92 
93 	if (ptrace(PTRACE_POKEUSER, child,
94 		   offsetof(struct user, u_debugreg[7]), dr7)) {
95 		pr_debug("failed to set dr7: %m\n");
96 		goto out;
97 	}
98 
99 	if (ptrace(PTRACE_CONT, child, NULL, NULL)) {
100 		pr_debug("failed to PTRACE_CONT: %m\n");
101 		goto out;
102 	}
103 
104 	waitpid(child, &status, 0);
105 	if (WIFEXITED(status)) {
106 		pr_debug("tracee exited prematurely 2\n");
107 		return TEST_FAIL;
108 	}
109 
110 	rip = ptrace(PTRACE_PEEKUSER, child,
111 		     offsetof(struct user_regs_struct, rip), NULL);
112 	if (rip == (unsigned long) -1) {
113 		pr_debug("failed to PTRACE_PEEKUSER: %m\n");
114 		goto out;
115 	}
116 
117 	pr_debug("rip %lx, bp_1 %p\n", rip, bp_1);
118 out:
119 	if (ptrace(PTRACE_DETACH, child, NULL, NULL)) {
120 		pr_debug("failed to PTRACE_DETACH: %m\n");
121 		return TEST_FAIL;
122 
123 	}
124 	return rip == (unsigned long) bp_1 ? TEST_OK : TEST_FAIL;
125 }
126 
127 /*
128  * This tests creates HW breakpoint, tries to
129  * change it to bogus value and checks the original
130  * breakpoint is hit.
131  */
132 static int bp_modify2(void)
133 {
134 	pid_t child;
135 	int status;
136 	unsigned long rip = 0, dr7 = 1;
137 
138 	child = spawn_child();
139 
140 	waitpid(child, &status, 0);
141 	if (WIFEXITED(status)) {
142 		pr_debug("tracee exited prematurely 1\n");
143 		return TEST_FAIL;
144 	}
145 
146 	/*
147 	 * The parent does following steps:
148 	 *  - creates a new breakpoint (id 0) for bp_1 function
149 	 *  - tries to change that breakpoint to (-1) address
150 	 *  - waits for the breakpoint to hit and checks
151 	 *    it has proper rip of bp_1 function
152 	 *  - detaches the child
153 	 */
154 	if (ptrace(PTRACE_POKEUSER, child,
155 		   offsetof(struct user, u_debugreg[0]), bp_1)) {
156 		pr_debug("failed to set breakpoint: %m\n");
157 		goto out;
158 	}
159 
160 	if (ptrace(PTRACE_POKEUSER, child,
161 		   offsetof(struct user, u_debugreg[7]), dr7)) {
162 		pr_debug("failed to set dr7: %m\n");
163 		goto out;
164 	}
165 
166 	if (!ptrace(PTRACE_POKEUSER, child,
167 		   offsetof(struct user, u_debugreg[0]), (unsigned long) (-1))) {
168 		pr_debug("failed, breakpoint set to bogus address\n");
169 		goto out;
170 	}
171 
172 	if (ptrace(PTRACE_CONT, child, NULL, NULL)) {
173 		pr_debug("failed to PTRACE_CONT: %m\n");
174 		goto out;
175 	}
176 
177 	waitpid(child, &status, 0);
178 	if (WIFEXITED(status)) {
179 		pr_debug("tracee exited prematurely 2\n");
180 		return TEST_FAIL;
181 	}
182 
183 	rip = ptrace(PTRACE_PEEKUSER, child,
184 		     offsetof(struct user_regs_struct, rip), NULL);
185 	if (rip == (unsigned long) -1) {
186 		pr_debug("failed to PTRACE_PEEKUSER: %m\n");
187 		goto out;
188 	}
189 
190 	pr_debug("rip %lx, bp_1 %p\n", rip, bp_1);
191 
192 out:
193 	if (ptrace(PTRACE_DETACH, child, NULL, NULL)) {
194 		pr_debug("failed to PTRACE_DETACH: %m\n");
195 		return TEST_FAIL;
196 	}
197 
198 	return rip == (unsigned long) bp_1 ? TEST_OK : TEST_FAIL;
199 }
200 
201 int test__bp_modify(struct test_suite *test __maybe_unused,
202 		    int subtest __maybe_unused)
203 {
204 	TEST_ASSERT_VAL("modify test 1 failed\n", !bp_modify1());
205 	TEST_ASSERT_VAL("modify test 2 failed\n", !bp_modify2());
206 
207 	return 0;
208 }
209