162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci#include <linux/compiler.h>
362306a36Sopenharmony_ci#include <sys/types.h>
462306a36Sopenharmony_ci#include <sys/wait.h>
562306a36Sopenharmony_ci#include <sys/user.h>
662306a36Sopenharmony_ci#include <syscall.h>
762306a36Sopenharmony_ci#include <unistd.h>
862306a36Sopenharmony_ci#include <stdio.h>
962306a36Sopenharmony_ci#include <stdlib.h>
1062306a36Sopenharmony_ci#include <string.h>
1162306a36Sopenharmony_ci#include <sys/ptrace.h>
1262306a36Sopenharmony_ci#include <asm/ptrace.h>
1362306a36Sopenharmony_ci#include <errno.h>
1462306a36Sopenharmony_ci#include "debug.h"
1562306a36Sopenharmony_ci#include "tests/tests.h"
1662306a36Sopenharmony_ci#include "arch-tests.h"
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_cistatic noinline int bp_1(void)
1962306a36Sopenharmony_ci{
2062306a36Sopenharmony_ci	pr_debug("in %s\n", __func__);
2162306a36Sopenharmony_ci	return 0;
2262306a36Sopenharmony_ci}
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_cistatic noinline int bp_2(void)
2562306a36Sopenharmony_ci{
2662306a36Sopenharmony_ci	pr_debug("in %s\n", __func__);
2762306a36Sopenharmony_ci	return 0;
2862306a36Sopenharmony_ci}
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_cistatic int spawn_child(void)
3162306a36Sopenharmony_ci{
3262306a36Sopenharmony_ci	int child = fork();
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_ci	if (child == 0) {
3562306a36Sopenharmony_ci		/*
3662306a36Sopenharmony_ci		 * The child sets itself for as tracee and
3762306a36Sopenharmony_ci		 * waits in signal for parent to trace it,
3862306a36Sopenharmony_ci		 * then it calls bp_1 and quits.
3962306a36Sopenharmony_ci		 */
4062306a36Sopenharmony_ci		int err = ptrace(PTRACE_TRACEME, 0, NULL, NULL);
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_ci		if (err) {
4362306a36Sopenharmony_ci			pr_debug("failed to PTRACE_TRACEME\n");
4462306a36Sopenharmony_ci			exit(1);
4562306a36Sopenharmony_ci		}
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_ci		raise(SIGCONT);
4862306a36Sopenharmony_ci		bp_1();
4962306a36Sopenharmony_ci		exit(0);
5062306a36Sopenharmony_ci	}
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci	return child;
5362306a36Sopenharmony_ci}
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci/*
5662306a36Sopenharmony_ci * This tests creates HW breakpoint, tries to
5762306a36Sopenharmony_ci * change it and checks it was properly changed.
5862306a36Sopenharmony_ci */
5962306a36Sopenharmony_cistatic int bp_modify1(void)
6062306a36Sopenharmony_ci{
6162306a36Sopenharmony_ci	pid_t child;
6262306a36Sopenharmony_ci	int status;
6362306a36Sopenharmony_ci	unsigned long rip = 0, dr7 = 1;
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_ci	child = spawn_child();
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci	waitpid(child, &status, 0);
6862306a36Sopenharmony_ci	if (WIFEXITED(status)) {
6962306a36Sopenharmony_ci		pr_debug("tracee exited prematurely 1\n");
7062306a36Sopenharmony_ci		return TEST_FAIL;
7162306a36Sopenharmony_ci	}
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci	/*
7462306a36Sopenharmony_ci	 * The parent does following steps:
7562306a36Sopenharmony_ci	 *  - creates a new breakpoint (id 0) for bp_2 function
7662306a36Sopenharmony_ci	 *  - changes that breakpoint to bp_1 function
7762306a36Sopenharmony_ci	 *  - waits for the breakpoint to hit and checks
7862306a36Sopenharmony_ci	 *    it has proper rip of bp_1 function
7962306a36Sopenharmony_ci	 *  - detaches the child
8062306a36Sopenharmony_ci	 */
8162306a36Sopenharmony_ci	if (ptrace(PTRACE_POKEUSER, child,
8262306a36Sopenharmony_ci		   offsetof(struct user, u_debugreg[0]), bp_2)) {
8362306a36Sopenharmony_ci		pr_debug("failed to set breakpoint, 1st time: %s\n",
8462306a36Sopenharmony_ci			 strerror(errno));
8562306a36Sopenharmony_ci		goto out;
8662306a36Sopenharmony_ci	}
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci	if (ptrace(PTRACE_POKEUSER, child,
8962306a36Sopenharmony_ci		   offsetof(struct user, u_debugreg[0]), bp_1)) {
9062306a36Sopenharmony_ci		pr_debug("failed to set breakpoint, 2nd time: %s\n",
9162306a36Sopenharmony_ci			 strerror(errno));
9262306a36Sopenharmony_ci		goto out;
9362306a36Sopenharmony_ci	}
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ci	if (ptrace(PTRACE_POKEUSER, child,
9662306a36Sopenharmony_ci		   offsetof(struct user, u_debugreg[7]), dr7)) {
9762306a36Sopenharmony_ci		pr_debug("failed to set dr7: %s\n", strerror(errno));
9862306a36Sopenharmony_ci		goto out;
9962306a36Sopenharmony_ci	}
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci	if (ptrace(PTRACE_CONT, child, NULL, NULL)) {
10262306a36Sopenharmony_ci		pr_debug("failed to PTRACE_CONT: %s\n", strerror(errno));
10362306a36Sopenharmony_ci		goto out;
10462306a36Sopenharmony_ci	}
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_ci	waitpid(child, &status, 0);
10762306a36Sopenharmony_ci	if (WIFEXITED(status)) {
10862306a36Sopenharmony_ci		pr_debug("tracee exited prematurely 2\n");
10962306a36Sopenharmony_ci		return TEST_FAIL;
11062306a36Sopenharmony_ci	}
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ci	rip = ptrace(PTRACE_PEEKUSER, child,
11362306a36Sopenharmony_ci		     offsetof(struct user_regs_struct, rip), NULL);
11462306a36Sopenharmony_ci	if (rip == (unsigned long) -1) {
11562306a36Sopenharmony_ci		pr_debug("failed to PTRACE_PEEKUSER: %s\n",
11662306a36Sopenharmony_ci			 strerror(errno));
11762306a36Sopenharmony_ci		goto out;
11862306a36Sopenharmony_ci	}
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci	pr_debug("rip %lx, bp_1 %p\n", rip, bp_1);
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_ciout:
12362306a36Sopenharmony_ci	if (ptrace(PTRACE_DETACH, child, NULL, NULL)) {
12462306a36Sopenharmony_ci		pr_debug("failed to PTRACE_DETACH: %s", strerror(errno));
12562306a36Sopenharmony_ci		return TEST_FAIL;
12662306a36Sopenharmony_ci	}
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci	return rip == (unsigned long) bp_1 ? TEST_OK : TEST_FAIL;
12962306a36Sopenharmony_ci}
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_ci/*
13262306a36Sopenharmony_ci * This tests creates HW breakpoint, tries to
13362306a36Sopenharmony_ci * change it to bogus value and checks the original
13462306a36Sopenharmony_ci * breakpoint is hit.
13562306a36Sopenharmony_ci */
13662306a36Sopenharmony_cistatic int bp_modify2(void)
13762306a36Sopenharmony_ci{
13862306a36Sopenharmony_ci	pid_t child;
13962306a36Sopenharmony_ci	int status;
14062306a36Sopenharmony_ci	unsigned long rip = 0, dr7 = 1;
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_ci	child = spawn_child();
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_ci	waitpid(child, &status, 0);
14562306a36Sopenharmony_ci	if (WIFEXITED(status)) {
14662306a36Sopenharmony_ci		pr_debug("tracee exited prematurely 1\n");
14762306a36Sopenharmony_ci		return TEST_FAIL;
14862306a36Sopenharmony_ci	}
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_ci	/*
15162306a36Sopenharmony_ci	 * The parent does following steps:
15262306a36Sopenharmony_ci	 *  - creates a new breakpoint (id 0) for bp_1 function
15362306a36Sopenharmony_ci	 *  - tries to change that breakpoint to (-1) address
15462306a36Sopenharmony_ci	 *  - waits for the breakpoint to hit and checks
15562306a36Sopenharmony_ci	 *    it has proper rip of bp_1 function
15662306a36Sopenharmony_ci	 *  - detaches the child
15762306a36Sopenharmony_ci	 */
15862306a36Sopenharmony_ci	if (ptrace(PTRACE_POKEUSER, child,
15962306a36Sopenharmony_ci		   offsetof(struct user, u_debugreg[0]), bp_1)) {
16062306a36Sopenharmony_ci		pr_debug("failed to set breakpoint: %s\n",
16162306a36Sopenharmony_ci			 strerror(errno));
16262306a36Sopenharmony_ci		goto out;
16362306a36Sopenharmony_ci	}
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_ci	if (ptrace(PTRACE_POKEUSER, child,
16662306a36Sopenharmony_ci		   offsetof(struct user, u_debugreg[7]), dr7)) {
16762306a36Sopenharmony_ci		pr_debug("failed to set dr7: %s\n", strerror(errno));
16862306a36Sopenharmony_ci		goto out;
16962306a36Sopenharmony_ci	}
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_ci	if (!ptrace(PTRACE_POKEUSER, child,
17262306a36Sopenharmony_ci		   offsetof(struct user, u_debugreg[0]), (unsigned long) (-1))) {
17362306a36Sopenharmony_ci		pr_debug("failed, breakpoint set to bogus address\n");
17462306a36Sopenharmony_ci		goto out;
17562306a36Sopenharmony_ci	}
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_ci	if (ptrace(PTRACE_CONT, child, NULL, NULL)) {
17862306a36Sopenharmony_ci		pr_debug("failed to PTRACE_CONT: %s\n", strerror(errno));
17962306a36Sopenharmony_ci		goto out;
18062306a36Sopenharmony_ci	}
18162306a36Sopenharmony_ci
18262306a36Sopenharmony_ci	waitpid(child, &status, 0);
18362306a36Sopenharmony_ci	if (WIFEXITED(status)) {
18462306a36Sopenharmony_ci		pr_debug("tracee exited prematurely 2\n");
18562306a36Sopenharmony_ci		return TEST_FAIL;
18662306a36Sopenharmony_ci	}
18762306a36Sopenharmony_ci
18862306a36Sopenharmony_ci	rip = ptrace(PTRACE_PEEKUSER, child,
18962306a36Sopenharmony_ci		     offsetof(struct user_regs_struct, rip), NULL);
19062306a36Sopenharmony_ci	if (rip == (unsigned long) -1) {
19162306a36Sopenharmony_ci		pr_debug("failed to PTRACE_PEEKUSER: %s\n",
19262306a36Sopenharmony_ci			 strerror(errno));
19362306a36Sopenharmony_ci		goto out;
19462306a36Sopenharmony_ci	}
19562306a36Sopenharmony_ci
19662306a36Sopenharmony_ci	pr_debug("rip %lx, bp_1 %p\n", rip, bp_1);
19762306a36Sopenharmony_ci
19862306a36Sopenharmony_ciout:
19962306a36Sopenharmony_ci	if (ptrace(PTRACE_DETACH, child, NULL, NULL)) {
20062306a36Sopenharmony_ci		pr_debug("failed to PTRACE_DETACH: %s", strerror(errno));
20162306a36Sopenharmony_ci		return TEST_FAIL;
20262306a36Sopenharmony_ci	}
20362306a36Sopenharmony_ci
20462306a36Sopenharmony_ci	return rip == (unsigned long) bp_1 ? TEST_OK : TEST_FAIL;
20562306a36Sopenharmony_ci}
20662306a36Sopenharmony_ci
20762306a36Sopenharmony_ciint test__bp_modify(struct test_suite *test __maybe_unused,
20862306a36Sopenharmony_ci		    int subtest __maybe_unused)
20962306a36Sopenharmony_ci{
21062306a36Sopenharmony_ci	TEST_ASSERT_VAL("modify test 1 failed\n", !bp_modify1());
21162306a36Sopenharmony_ci	TEST_ASSERT_VAL("modify test 2 failed\n", !bp_modify2());
21262306a36Sopenharmony_ci
21362306a36Sopenharmony_ci	return 0;
21462306a36Sopenharmony_ci}
215