xref: /openbmc/linux/tools/perf/arch/x86/tests/bp-modify.c (revision 03ab8e6297acd1bc0eedaa050e2a1635c576fd11)
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