162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (C) 2011 Red Hat, Inc., Frederic Weisbecker <fweisbec@redhat.com>
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Selftests for breakpoints (and more generally the do_debug() path) in x86.
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci#include <sys/ptrace.h>
1062306a36Sopenharmony_ci#include <unistd.h>
1162306a36Sopenharmony_ci#include <stddef.h>
1262306a36Sopenharmony_ci#include <sys/user.h>
1362306a36Sopenharmony_ci#include <stdio.h>
1462306a36Sopenharmony_ci#include <stdlib.h>
1562306a36Sopenharmony_ci#include <signal.h>
1662306a36Sopenharmony_ci#include <sys/types.h>
1762306a36Sopenharmony_ci#include <sys/wait.h>
1862306a36Sopenharmony_ci#include <errno.h>
1962306a36Sopenharmony_ci#include <string.h>
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci#include "../kselftest.h"
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_ci#define COUNT_ISN_BPS	4
2462306a36Sopenharmony_ci#define COUNT_WPS	4
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci/* Breakpoint access modes */
2762306a36Sopenharmony_cienum {
2862306a36Sopenharmony_ci	BP_X = 1,
2962306a36Sopenharmony_ci	BP_RW = 2,
3062306a36Sopenharmony_ci	BP_W = 4,
3162306a36Sopenharmony_ci};
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_cistatic pid_t child_pid;
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_ci/*
3662306a36Sopenharmony_ci * Ensures the child and parent are always "talking" about
3762306a36Sopenharmony_ci * the same test sequence. (ie: that we haven't forgotten
3862306a36Sopenharmony_ci * to call check_trapped() somewhere).
3962306a36Sopenharmony_ci */
4062306a36Sopenharmony_cistatic int nr_tests;
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_cistatic void set_breakpoint_addr(void *addr, int n)
4362306a36Sopenharmony_ci{
4462306a36Sopenharmony_ci	int ret;
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_ci	ret = ptrace(PTRACE_POKEUSER, child_pid,
4762306a36Sopenharmony_ci		     offsetof(struct user, u_debugreg[n]), addr);
4862306a36Sopenharmony_ci	if (ret)
4962306a36Sopenharmony_ci		ksft_exit_fail_msg("Can't set breakpoint addr: %s\n",
5062306a36Sopenharmony_ci			strerror(errno));
5162306a36Sopenharmony_ci}
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_cistatic void toggle_breakpoint(int n, int type, int len,
5462306a36Sopenharmony_ci			      int local, int global, int set)
5562306a36Sopenharmony_ci{
5662306a36Sopenharmony_ci	int ret;
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_ci	int xtype, xlen;
5962306a36Sopenharmony_ci	unsigned long vdr7, dr7;
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_ci	switch (type) {
6262306a36Sopenharmony_ci	case BP_X:
6362306a36Sopenharmony_ci		xtype = 0;
6462306a36Sopenharmony_ci		break;
6562306a36Sopenharmony_ci	case BP_W:
6662306a36Sopenharmony_ci		xtype = 1;
6762306a36Sopenharmony_ci		break;
6862306a36Sopenharmony_ci	case BP_RW:
6962306a36Sopenharmony_ci		xtype = 3;
7062306a36Sopenharmony_ci		break;
7162306a36Sopenharmony_ci	}
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci	switch (len) {
7462306a36Sopenharmony_ci	case 1:
7562306a36Sopenharmony_ci		xlen = 0;
7662306a36Sopenharmony_ci		break;
7762306a36Sopenharmony_ci	case 2:
7862306a36Sopenharmony_ci		xlen = 4;
7962306a36Sopenharmony_ci		break;
8062306a36Sopenharmony_ci	case 4:
8162306a36Sopenharmony_ci		xlen = 0xc;
8262306a36Sopenharmony_ci		break;
8362306a36Sopenharmony_ci	case 8:
8462306a36Sopenharmony_ci		xlen = 8;
8562306a36Sopenharmony_ci		break;
8662306a36Sopenharmony_ci	}
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci	dr7 = ptrace(PTRACE_PEEKUSER, child_pid,
8962306a36Sopenharmony_ci		     offsetof(struct user, u_debugreg[7]), 0);
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_ci	vdr7 = (xlen | xtype) << 16;
9262306a36Sopenharmony_ci	vdr7 <<= 4 * n;
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_ci	if (local) {
9562306a36Sopenharmony_ci		vdr7 |= 1 << (2 * n);
9662306a36Sopenharmony_ci		vdr7 |= 1 << 8;
9762306a36Sopenharmony_ci	}
9862306a36Sopenharmony_ci	if (global) {
9962306a36Sopenharmony_ci		vdr7 |= 2 << (2 * n);
10062306a36Sopenharmony_ci		vdr7 |= 1 << 9;
10162306a36Sopenharmony_ci	}
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_ci	if (set)
10462306a36Sopenharmony_ci		dr7 |= vdr7;
10562306a36Sopenharmony_ci	else
10662306a36Sopenharmony_ci		dr7 &= ~vdr7;
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci	ret = ptrace(PTRACE_POKEUSER, child_pid,
10962306a36Sopenharmony_ci		     offsetof(struct user, u_debugreg[7]), dr7);
11062306a36Sopenharmony_ci	if (ret) {
11162306a36Sopenharmony_ci		ksft_print_msg("Can't set dr7: %s\n", strerror(errno));
11262306a36Sopenharmony_ci		exit(-1);
11362306a36Sopenharmony_ci	}
11462306a36Sopenharmony_ci}
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_ci/* Dummy variables to test read/write accesses */
11762306a36Sopenharmony_cistatic unsigned long long dummy_var[4];
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_ci/* Dummy functions to test execution accesses */
12062306a36Sopenharmony_cistatic void dummy_func(void) { }
12162306a36Sopenharmony_cistatic void dummy_func1(void) { }
12262306a36Sopenharmony_cistatic void dummy_func2(void) { }
12362306a36Sopenharmony_cistatic void dummy_func3(void) { }
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_cistatic void (*dummy_funcs[])(void) = {
12662306a36Sopenharmony_ci	dummy_func,
12762306a36Sopenharmony_ci	dummy_func1,
12862306a36Sopenharmony_ci	dummy_func2,
12962306a36Sopenharmony_ci	dummy_func3,
13062306a36Sopenharmony_ci};
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_cistatic int trapped;
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_cistatic void check_trapped(void)
13562306a36Sopenharmony_ci{
13662306a36Sopenharmony_ci	/*
13762306a36Sopenharmony_ci	 * If we haven't trapped, wake up the parent
13862306a36Sopenharmony_ci	 * so that it notices the failure.
13962306a36Sopenharmony_ci	 */
14062306a36Sopenharmony_ci	if (!trapped)
14162306a36Sopenharmony_ci		kill(getpid(), SIGUSR1);
14262306a36Sopenharmony_ci	trapped = 0;
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_ci	nr_tests++;
14562306a36Sopenharmony_ci}
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_cistatic void write_var(int len)
14862306a36Sopenharmony_ci{
14962306a36Sopenharmony_ci	char *pcval; short *psval; int *pival; long long *plval;
15062306a36Sopenharmony_ci	int i;
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_ci	for (i = 0; i < 4; i++) {
15362306a36Sopenharmony_ci		switch (len) {
15462306a36Sopenharmony_ci		case 1:
15562306a36Sopenharmony_ci			pcval = (char *)&dummy_var[i];
15662306a36Sopenharmony_ci			*pcval = 0xff;
15762306a36Sopenharmony_ci			break;
15862306a36Sopenharmony_ci		case 2:
15962306a36Sopenharmony_ci			psval = (short *)&dummy_var[i];
16062306a36Sopenharmony_ci			*psval = 0xffff;
16162306a36Sopenharmony_ci			break;
16262306a36Sopenharmony_ci		case 4:
16362306a36Sopenharmony_ci			pival = (int *)&dummy_var[i];
16462306a36Sopenharmony_ci			*pival = 0xffffffff;
16562306a36Sopenharmony_ci			break;
16662306a36Sopenharmony_ci		case 8:
16762306a36Sopenharmony_ci			plval = (long long *)&dummy_var[i];
16862306a36Sopenharmony_ci			*plval = 0xffffffffffffffffLL;
16962306a36Sopenharmony_ci			break;
17062306a36Sopenharmony_ci		}
17162306a36Sopenharmony_ci		check_trapped();
17262306a36Sopenharmony_ci	}
17362306a36Sopenharmony_ci}
17462306a36Sopenharmony_ci
17562306a36Sopenharmony_cistatic void read_var(int len)
17662306a36Sopenharmony_ci{
17762306a36Sopenharmony_ci	char cval; short sval; int ival; long long lval;
17862306a36Sopenharmony_ci	int i;
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_ci	for (i = 0; i < 4; i++) {
18162306a36Sopenharmony_ci		switch (len) {
18262306a36Sopenharmony_ci		case 1:
18362306a36Sopenharmony_ci			cval = *(char *)&dummy_var[i];
18462306a36Sopenharmony_ci			break;
18562306a36Sopenharmony_ci		case 2:
18662306a36Sopenharmony_ci			sval = *(short *)&dummy_var[i];
18762306a36Sopenharmony_ci			break;
18862306a36Sopenharmony_ci		case 4:
18962306a36Sopenharmony_ci			ival = *(int *)&dummy_var[i];
19062306a36Sopenharmony_ci			break;
19162306a36Sopenharmony_ci		case 8:
19262306a36Sopenharmony_ci			lval = *(long long *)&dummy_var[i];
19362306a36Sopenharmony_ci			break;
19462306a36Sopenharmony_ci		}
19562306a36Sopenharmony_ci		check_trapped();
19662306a36Sopenharmony_ci	}
19762306a36Sopenharmony_ci}
19862306a36Sopenharmony_ci
19962306a36Sopenharmony_ci/*
20062306a36Sopenharmony_ci * Do the r/w/x accesses to trigger the breakpoints. And run
20162306a36Sopenharmony_ci * the usual traps.
20262306a36Sopenharmony_ci */
20362306a36Sopenharmony_cistatic void trigger_tests(void)
20462306a36Sopenharmony_ci{
20562306a36Sopenharmony_ci	int len, local, global, i;
20662306a36Sopenharmony_ci	char val;
20762306a36Sopenharmony_ci	int ret;
20862306a36Sopenharmony_ci
20962306a36Sopenharmony_ci	ret = ptrace(PTRACE_TRACEME, 0, NULL, 0);
21062306a36Sopenharmony_ci	if (ret) {
21162306a36Sopenharmony_ci		ksft_print_msg("Can't be traced? %s\n", strerror(errno));
21262306a36Sopenharmony_ci		return;
21362306a36Sopenharmony_ci	}
21462306a36Sopenharmony_ci
21562306a36Sopenharmony_ci	/* Wake up father so that it sets up the first test */
21662306a36Sopenharmony_ci	kill(getpid(), SIGUSR1);
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_ci	/* Test instruction breakpoints */
21962306a36Sopenharmony_ci	for (local = 0; local < 2; local++) {
22062306a36Sopenharmony_ci		for (global = 0; global < 2; global++) {
22162306a36Sopenharmony_ci			if (!local && !global)
22262306a36Sopenharmony_ci				continue;
22362306a36Sopenharmony_ci
22462306a36Sopenharmony_ci			for (i = 0; i < COUNT_ISN_BPS; i++) {
22562306a36Sopenharmony_ci				dummy_funcs[i]();
22662306a36Sopenharmony_ci				check_trapped();
22762306a36Sopenharmony_ci			}
22862306a36Sopenharmony_ci		}
22962306a36Sopenharmony_ci	}
23062306a36Sopenharmony_ci
23162306a36Sopenharmony_ci	/* Test write watchpoints */
23262306a36Sopenharmony_ci	for (len = 1; len <= sizeof(long); len <<= 1) {
23362306a36Sopenharmony_ci		for (local = 0; local < 2; local++) {
23462306a36Sopenharmony_ci			for (global = 0; global < 2; global++) {
23562306a36Sopenharmony_ci				if (!local && !global)
23662306a36Sopenharmony_ci					continue;
23762306a36Sopenharmony_ci				write_var(len);
23862306a36Sopenharmony_ci			}
23962306a36Sopenharmony_ci		}
24062306a36Sopenharmony_ci	}
24162306a36Sopenharmony_ci
24262306a36Sopenharmony_ci	/* Test read/write watchpoints (on read accesses) */
24362306a36Sopenharmony_ci	for (len = 1; len <= sizeof(long); len <<= 1) {
24462306a36Sopenharmony_ci		for (local = 0; local < 2; local++) {
24562306a36Sopenharmony_ci			for (global = 0; global < 2; global++) {
24662306a36Sopenharmony_ci				if (!local && !global)
24762306a36Sopenharmony_ci					continue;
24862306a36Sopenharmony_ci				read_var(len);
24962306a36Sopenharmony_ci			}
25062306a36Sopenharmony_ci		}
25162306a36Sopenharmony_ci	}
25262306a36Sopenharmony_ci
25362306a36Sopenharmony_ci	/* Icebp trap */
25462306a36Sopenharmony_ci	asm(".byte 0xf1\n");
25562306a36Sopenharmony_ci	check_trapped();
25662306a36Sopenharmony_ci
25762306a36Sopenharmony_ci	/* Int 3 trap */
25862306a36Sopenharmony_ci	asm("int $3\n");
25962306a36Sopenharmony_ci	check_trapped();
26062306a36Sopenharmony_ci
26162306a36Sopenharmony_ci	kill(getpid(), SIGUSR1);
26262306a36Sopenharmony_ci}
26362306a36Sopenharmony_ci
26462306a36Sopenharmony_cistatic void check_success(const char *msg)
26562306a36Sopenharmony_ci{
26662306a36Sopenharmony_ci	int child_nr_tests;
26762306a36Sopenharmony_ci	int status;
26862306a36Sopenharmony_ci	int ret;
26962306a36Sopenharmony_ci
27062306a36Sopenharmony_ci	/* Wait for the child to SIGTRAP */
27162306a36Sopenharmony_ci	wait(&status);
27262306a36Sopenharmony_ci
27362306a36Sopenharmony_ci	ret = 0;
27462306a36Sopenharmony_ci
27562306a36Sopenharmony_ci	if (WSTOPSIG(status) == SIGTRAP) {
27662306a36Sopenharmony_ci		child_nr_tests = ptrace(PTRACE_PEEKDATA, child_pid,
27762306a36Sopenharmony_ci					&nr_tests, 0);
27862306a36Sopenharmony_ci		if (child_nr_tests == nr_tests)
27962306a36Sopenharmony_ci			ret = 1;
28062306a36Sopenharmony_ci		if (ptrace(PTRACE_POKEDATA, child_pid, &trapped, 1))
28162306a36Sopenharmony_ci			ksft_exit_fail_msg("Can't poke: %s\n", strerror(errno));
28262306a36Sopenharmony_ci	}
28362306a36Sopenharmony_ci
28462306a36Sopenharmony_ci	nr_tests++;
28562306a36Sopenharmony_ci
28662306a36Sopenharmony_ci	if (ret)
28762306a36Sopenharmony_ci		ksft_test_result_pass(msg);
28862306a36Sopenharmony_ci	else
28962306a36Sopenharmony_ci		ksft_test_result_fail(msg);
29062306a36Sopenharmony_ci}
29162306a36Sopenharmony_ci
29262306a36Sopenharmony_cistatic void launch_instruction_breakpoints(char *buf, int local, int global)
29362306a36Sopenharmony_ci{
29462306a36Sopenharmony_ci	int i;
29562306a36Sopenharmony_ci
29662306a36Sopenharmony_ci	for (i = 0; i < COUNT_ISN_BPS; i++) {
29762306a36Sopenharmony_ci		set_breakpoint_addr(dummy_funcs[i], i);
29862306a36Sopenharmony_ci		toggle_breakpoint(i, BP_X, 1, local, global, 1);
29962306a36Sopenharmony_ci		ptrace(PTRACE_CONT, child_pid, NULL, 0);
30062306a36Sopenharmony_ci		sprintf(buf, "Test breakpoint %d with local: %d global: %d\n",
30162306a36Sopenharmony_ci			i, local, global);
30262306a36Sopenharmony_ci		check_success(buf);
30362306a36Sopenharmony_ci		toggle_breakpoint(i, BP_X, 1, local, global, 0);
30462306a36Sopenharmony_ci	}
30562306a36Sopenharmony_ci}
30662306a36Sopenharmony_ci
30762306a36Sopenharmony_cistatic void launch_watchpoints(char *buf, int mode, int len,
30862306a36Sopenharmony_ci			       int local, int global)
30962306a36Sopenharmony_ci{
31062306a36Sopenharmony_ci	const char *mode_str;
31162306a36Sopenharmony_ci	int i;
31262306a36Sopenharmony_ci
31362306a36Sopenharmony_ci	if (mode == BP_W)
31462306a36Sopenharmony_ci		mode_str = "write";
31562306a36Sopenharmony_ci	else
31662306a36Sopenharmony_ci		mode_str = "read";
31762306a36Sopenharmony_ci
31862306a36Sopenharmony_ci	for (i = 0; i < COUNT_WPS; i++) {
31962306a36Sopenharmony_ci		set_breakpoint_addr(&dummy_var[i], i);
32062306a36Sopenharmony_ci		toggle_breakpoint(i, mode, len, local, global, 1);
32162306a36Sopenharmony_ci		ptrace(PTRACE_CONT, child_pid, NULL, 0);
32262306a36Sopenharmony_ci		sprintf(buf,
32362306a36Sopenharmony_ci			"Test %s watchpoint %d with len: %d local: %d global: %d\n",
32462306a36Sopenharmony_ci			mode_str, i, len, local, global);
32562306a36Sopenharmony_ci		check_success(buf);
32662306a36Sopenharmony_ci		toggle_breakpoint(i, mode, len, local, global, 0);
32762306a36Sopenharmony_ci	}
32862306a36Sopenharmony_ci}
32962306a36Sopenharmony_ci
33062306a36Sopenharmony_ci/* Set the breakpoints and check the child successfully trigger them */
33162306a36Sopenharmony_cistatic void launch_tests(void)
33262306a36Sopenharmony_ci{
33362306a36Sopenharmony_ci	char buf[1024];
33462306a36Sopenharmony_ci	unsigned int tests = 0;
33562306a36Sopenharmony_ci	int len, local, global, i;
33662306a36Sopenharmony_ci
33762306a36Sopenharmony_ci	tests += 3 * COUNT_ISN_BPS;
33862306a36Sopenharmony_ci	tests += sizeof(long) / 2 * 3 * COUNT_WPS;
33962306a36Sopenharmony_ci	tests += sizeof(long) / 2 * 3 * COUNT_WPS;
34062306a36Sopenharmony_ci	tests += 2;
34162306a36Sopenharmony_ci	ksft_set_plan(tests);
34262306a36Sopenharmony_ci
34362306a36Sopenharmony_ci	/* Instruction breakpoints */
34462306a36Sopenharmony_ci	for (local = 0; local < 2; local++) {
34562306a36Sopenharmony_ci		for (global = 0; global < 2; global++) {
34662306a36Sopenharmony_ci			if (!local && !global)
34762306a36Sopenharmony_ci				continue;
34862306a36Sopenharmony_ci			launch_instruction_breakpoints(buf, local, global);
34962306a36Sopenharmony_ci		}
35062306a36Sopenharmony_ci	}
35162306a36Sopenharmony_ci
35262306a36Sopenharmony_ci	/* Write watchpoint */
35362306a36Sopenharmony_ci	for (len = 1; len <= sizeof(long); len <<= 1) {
35462306a36Sopenharmony_ci		for (local = 0; local < 2; local++) {
35562306a36Sopenharmony_ci			for (global = 0; global < 2; global++) {
35662306a36Sopenharmony_ci				if (!local && !global)
35762306a36Sopenharmony_ci					continue;
35862306a36Sopenharmony_ci				launch_watchpoints(buf, BP_W, len,
35962306a36Sopenharmony_ci						   local, global);
36062306a36Sopenharmony_ci			}
36162306a36Sopenharmony_ci		}
36262306a36Sopenharmony_ci	}
36362306a36Sopenharmony_ci
36462306a36Sopenharmony_ci	/* Read-Write watchpoint */
36562306a36Sopenharmony_ci	for (len = 1; len <= sizeof(long); len <<= 1) {
36662306a36Sopenharmony_ci		for (local = 0; local < 2; local++) {
36762306a36Sopenharmony_ci			for (global = 0; global < 2; global++) {
36862306a36Sopenharmony_ci				if (!local && !global)
36962306a36Sopenharmony_ci					continue;
37062306a36Sopenharmony_ci				launch_watchpoints(buf, BP_RW, len,
37162306a36Sopenharmony_ci						   local, global);
37262306a36Sopenharmony_ci			}
37362306a36Sopenharmony_ci		}
37462306a36Sopenharmony_ci	}
37562306a36Sopenharmony_ci
37662306a36Sopenharmony_ci	/* Icebp traps */
37762306a36Sopenharmony_ci	ptrace(PTRACE_CONT, child_pid, NULL, 0);
37862306a36Sopenharmony_ci	check_success("Test icebp\n");
37962306a36Sopenharmony_ci
38062306a36Sopenharmony_ci	/* Int 3 traps */
38162306a36Sopenharmony_ci	ptrace(PTRACE_CONT, child_pid, NULL, 0);
38262306a36Sopenharmony_ci	check_success("Test int 3 trap\n");
38362306a36Sopenharmony_ci
38462306a36Sopenharmony_ci	ptrace(PTRACE_CONT, child_pid, NULL, 0);
38562306a36Sopenharmony_ci}
38662306a36Sopenharmony_ci
38762306a36Sopenharmony_ciint main(int argc, char **argv)
38862306a36Sopenharmony_ci{
38962306a36Sopenharmony_ci	pid_t pid;
39062306a36Sopenharmony_ci	int ret;
39162306a36Sopenharmony_ci
39262306a36Sopenharmony_ci	ksft_print_header();
39362306a36Sopenharmony_ci
39462306a36Sopenharmony_ci	pid = fork();
39562306a36Sopenharmony_ci	if (!pid) {
39662306a36Sopenharmony_ci		trigger_tests();
39762306a36Sopenharmony_ci		exit(0);
39862306a36Sopenharmony_ci	}
39962306a36Sopenharmony_ci
40062306a36Sopenharmony_ci	child_pid = pid;
40162306a36Sopenharmony_ci
40262306a36Sopenharmony_ci	wait(NULL);
40362306a36Sopenharmony_ci
40462306a36Sopenharmony_ci	launch_tests();
40562306a36Sopenharmony_ci
40662306a36Sopenharmony_ci	wait(NULL);
40762306a36Sopenharmony_ci
40862306a36Sopenharmony_ci	ksft_exit_pass();
40962306a36Sopenharmony_ci}
410