xref: /openbmc/linux/tools/testing/selftests/breakpoints/breakpoint_test.c (revision 75bf465f0bc33e9b776a46d6a1b9b990f5fb7c37)
1  // SPDX-License-Identifier: GPL-2.0-only
2  /*
3   * Copyright (C) 2011 Red Hat, Inc., Frederic Weisbecker <fweisbec@redhat.com>
4   *
5   * Selftests for breakpoints (and more generally the do_debug() path) in x86.
6   */
7  
8  
9  #include <sys/ptrace.h>
10  #include <unistd.h>
11  #include <stddef.h>
12  #include <sys/user.h>
13  #include <stdio.h>
14  #include <stdlib.h>
15  #include <signal.h>
16  #include <sys/types.h>
17  #include <sys/wait.h>
18  #include <errno.h>
19  #include <string.h>
20  
21  #include "../kselftest.h"
22  
23  #define COUNT_ISN_BPS	4
24  #define COUNT_WPS	4
25  
26  /* Breakpoint access modes */
27  enum {
28  	BP_X = 1,
29  	BP_RW = 2,
30  	BP_W = 4,
31  };
32  
33  static pid_t child_pid;
34  
35  /*
36   * Ensures the child and parent are always "talking" about
37   * the same test sequence. (ie: that we haven't forgotten
38   * to call check_trapped() somewhere).
39   */
40  static int nr_tests;
41  
set_breakpoint_addr(void * addr,int n)42  static void set_breakpoint_addr(void *addr, int n)
43  {
44  	int ret;
45  
46  	ret = ptrace(PTRACE_POKEUSER, child_pid,
47  		     offsetof(struct user, u_debugreg[n]), addr);
48  	if (ret)
49  		ksft_exit_fail_msg("Can't set breakpoint addr: %s\n",
50  			strerror(errno));
51  }
52  
toggle_breakpoint(int n,int type,int len,int local,int global,int set)53  static void toggle_breakpoint(int n, int type, int len,
54  			      int local, int global, int set)
55  {
56  	int ret;
57  
58  	int xtype, xlen;
59  	unsigned long vdr7, dr7;
60  
61  	switch (type) {
62  	case BP_X:
63  		xtype = 0;
64  		break;
65  	case BP_W:
66  		xtype = 1;
67  		break;
68  	case BP_RW:
69  		xtype = 3;
70  		break;
71  	}
72  
73  	switch (len) {
74  	case 1:
75  		xlen = 0;
76  		break;
77  	case 2:
78  		xlen = 4;
79  		break;
80  	case 4:
81  		xlen = 0xc;
82  		break;
83  	case 8:
84  		xlen = 8;
85  		break;
86  	}
87  
88  	dr7 = ptrace(PTRACE_PEEKUSER, child_pid,
89  		     offsetof(struct user, u_debugreg[7]), 0);
90  
91  	vdr7 = (xlen | xtype) << 16;
92  	vdr7 <<= 4 * n;
93  
94  	if (local) {
95  		vdr7 |= 1 << (2 * n);
96  		vdr7 |= 1 << 8;
97  	}
98  	if (global) {
99  		vdr7 |= 2 << (2 * n);
100  		vdr7 |= 1 << 9;
101  	}
102  
103  	if (set)
104  		dr7 |= vdr7;
105  	else
106  		dr7 &= ~vdr7;
107  
108  	ret = ptrace(PTRACE_POKEUSER, child_pid,
109  		     offsetof(struct user, u_debugreg[7]), dr7);
110  	if (ret) {
111  		ksft_print_msg("Can't set dr7: %s\n", strerror(errno));
112  		exit(-1);
113  	}
114  }
115  
116  /* Dummy variables to test read/write accesses */
117  static unsigned long long dummy_var[4];
118  
119  /* Dummy functions to test execution accesses */
dummy_func(void)120  static void dummy_func(void) { }
dummy_func1(void)121  static void dummy_func1(void) { }
dummy_func2(void)122  static void dummy_func2(void) { }
dummy_func3(void)123  static void dummy_func3(void) { }
124  
125  static void (*dummy_funcs[])(void) = {
126  	dummy_func,
127  	dummy_func1,
128  	dummy_func2,
129  	dummy_func3,
130  };
131  
132  static int trapped;
133  
check_trapped(void)134  static void check_trapped(void)
135  {
136  	/*
137  	 * If we haven't trapped, wake up the parent
138  	 * so that it notices the failure.
139  	 */
140  	if (!trapped)
141  		kill(getpid(), SIGUSR1);
142  	trapped = 0;
143  
144  	nr_tests++;
145  }
146  
write_var(int len)147  static void write_var(int len)
148  {
149  	char *pcval; short *psval; int *pival; long long *plval;
150  	int i;
151  
152  	for (i = 0; i < 4; i++) {
153  		switch (len) {
154  		case 1:
155  			pcval = (char *)&dummy_var[i];
156  			*pcval = 0xff;
157  			break;
158  		case 2:
159  			psval = (short *)&dummy_var[i];
160  			*psval = 0xffff;
161  			break;
162  		case 4:
163  			pival = (int *)&dummy_var[i];
164  			*pival = 0xffffffff;
165  			break;
166  		case 8:
167  			plval = (long long *)&dummy_var[i];
168  			*plval = 0xffffffffffffffffLL;
169  			break;
170  		}
171  		check_trapped();
172  	}
173  }
174  
read_var(int len)175  static void read_var(int len)
176  {
177  	char cval; short sval; int ival; long long lval;
178  	int i;
179  
180  	for (i = 0; i < 4; i++) {
181  		switch (len) {
182  		case 1:
183  			cval = *(char *)&dummy_var[i];
184  			break;
185  		case 2:
186  			sval = *(short *)&dummy_var[i];
187  			break;
188  		case 4:
189  			ival = *(int *)&dummy_var[i];
190  			break;
191  		case 8:
192  			lval = *(long long *)&dummy_var[i];
193  			break;
194  		}
195  		check_trapped();
196  	}
197  }
198  
199  /*
200   * Do the r/w/x accesses to trigger the breakpoints. And run
201   * the usual traps.
202   */
trigger_tests(void)203  static void trigger_tests(void)
204  {
205  	int len, local, global, i;
206  	char val;
207  	int ret;
208  
209  	ret = ptrace(PTRACE_TRACEME, 0, NULL, 0);
210  	if (ret) {
211  		ksft_print_msg("Can't be traced? %s\n", strerror(errno));
212  		return;
213  	}
214  
215  	/* Wake up father so that it sets up the first test */
216  	kill(getpid(), SIGUSR1);
217  
218  	/* Test instruction breakpoints */
219  	for (local = 0; local < 2; local++) {
220  		for (global = 0; global < 2; global++) {
221  			if (!local && !global)
222  				continue;
223  
224  			for (i = 0; i < COUNT_ISN_BPS; i++) {
225  				dummy_funcs[i]();
226  				check_trapped();
227  			}
228  		}
229  	}
230  
231  	/* Test write watchpoints */
232  	for (len = 1; len <= sizeof(long); len <<= 1) {
233  		for (local = 0; local < 2; local++) {
234  			for (global = 0; global < 2; global++) {
235  				if (!local && !global)
236  					continue;
237  				write_var(len);
238  			}
239  		}
240  	}
241  
242  	/* Test read/write watchpoints (on read accesses) */
243  	for (len = 1; len <= sizeof(long); len <<= 1) {
244  		for (local = 0; local < 2; local++) {
245  			for (global = 0; global < 2; global++) {
246  				if (!local && !global)
247  					continue;
248  				read_var(len);
249  			}
250  		}
251  	}
252  
253  	/* Icebp trap */
254  	asm(".byte 0xf1\n");
255  	check_trapped();
256  
257  	/* Int 3 trap */
258  	asm("int $3\n");
259  	check_trapped();
260  
261  	kill(getpid(), SIGUSR1);
262  }
263  
check_success(const char * msg)264  static void check_success(const char *msg)
265  {
266  	int child_nr_tests;
267  	int status;
268  	int ret;
269  
270  	/* Wait for the child to SIGTRAP */
271  	wait(&status);
272  
273  	ret = 0;
274  
275  	if (WSTOPSIG(status) == SIGTRAP) {
276  		child_nr_tests = ptrace(PTRACE_PEEKDATA, child_pid,
277  					&nr_tests, 0);
278  		if (child_nr_tests == nr_tests)
279  			ret = 1;
280  		if (ptrace(PTRACE_POKEDATA, child_pid, &trapped, 1))
281  			ksft_exit_fail_msg("Can't poke: %s\n", strerror(errno));
282  	}
283  
284  	nr_tests++;
285  
286  	if (ret)
287  		ksft_test_result_pass(msg);
288  	else
289  		ksft_test_result_fail(msg);
290  }
291  
launch_instruction_breakpoints(char * buf,int local,int global)292  static void launch_instruction_breakpoints(char *buf, int local, int global)
293  {
294  	int i;
295  
296  	for (i = 0; i < COUNT_ISN_BPS; i++) {
297  		set_breakpoint_addr(dummy_funcs[i], i);
298  		toggle_breakpoint(i, BP_X, 1, local, global, 1);
299  		ptrace(PTRACE_CONT, child_pid, NULL, 0);
300  		sprintf(buf, "Test breakpoint %d with local: %d global: %d\n",
301  			i, local, global);
302  		check_success(buf);
303  		toggle_breakpoint(i, BP_X, 1, local, global, 0);
304  	}
305  }
306  
launch_watchpoints(char * buf,int mode,int len,int local,int global)307  static void launch_watchpoints(char *buf, int mode, int len,
308  			       int local, int global)
309  {
310  	const char *mode_str;
311  	int i;
312  
313  	if (mode == BP_W)
314  		mode_str = "write";
315  	else
316  		mode_str = "read";
317  
318  	for (i = 0; i < COUNT_WPS; i++) {
319  		set_breakpoint_addr(&dummy_var[i], i);
320  		toggle_breakpoint(i, mode, len, local, global, 1);
321  		ptrace(PTRACE_CONT, child_pid, NULL, 0);
322  		sprintf(buf,
323  			"Test %s watchpoint %d with len: %d local: %d global: %d\n",
324  			mode_str, i, len, local, global);
325  		check_success(buf);
326  		toggle_breakpoint(i, mode, len, local, global, 0);
327  	}
328  }
329  
330  /* Set the breakpoints and check the child successfully trigger them */
launch_tests(void)331  static void launch_tests(void)
332  {
333  	char buf[1024];
334  	unsigned int tests = 0;
335  	int len, local, global, i;
336  
337  	tests += 3 * COUNT_ISN_BPS;
338  	tests += sizeof(long) / 2 * 3 * COUNT_WPS;
339  	tests += sizeof(long) / 2 * 3 * COUNT_WPS;
340  	tests += 2;
341  	ksft_set_plan(tests);
342  
343  	/* Instruction breakpoints */
344  	for (local = 0; local < 2; local++) {
345  		for (global = 0; global < 2; global++) {
346  			if (!local && !global)
347  				continue;
348  			launch_instruction_breakpoints(buf, local, global);
349  		}
350  	}
351  
352  	/* Write watchpoint */
353  	for (len = 1; len <= sizeof(long); len <<= 1) {
354  		for (local = 0; local < 2; local++) {
355  			for (global = 0; global < 2; global++) {
356  				if (!local && !global)
357  					continue;
358  				launch_watchpoints(buf, BP_W, len,
359  						   local, global);
360  			}
361  		}
362  	}
363  
364  	/* Read-Write watchpoint */
365  	for (len = 1; len <= sizeof(long); len <<= 1) {
366  		for (local = 0; local < 2; local++) {
367  			for (global = 0; global < 2; global++) {
368  				if (!local && !global)
369  					continue;
370  				launch_watchpoints(buf, BP_RW, len,
371  						   local, global);
372  			}
373  		}
374  	}
375  
376  	/* Icebp traps */
377  	ptrace(PTRACE_CONT, child_pid, NULL, 0);
378  	check_success("Test icebp\n");
379  
380  	/* Int 3 traps */
381  	ptrace(PTRACE_CONT, child_pid, NULL, 0);
382  	check_success("Test int 3 trap\n");
383  
384  	ptrace(PTRACE_CONT, child_pid, NULL, 0);
385  }
386  
main(int argc,char ** argv)387  int main(int argc, char **argv)
388  {
389  	pid_t pid;
390  	int ret;
391  
392  	ksft_print_header();
393  
394  	pid = fork();
395  	if (!pid) {
396  		trigger_tests();
397  		exit(0);
398  	}
399  
400  	child_pid = pid;
401  
402  	wait(NULL);
403  
404  	launch_tests();
405  
406  	wait(NULL);
407  
408  	ksft_exit_pass();
409  }
410