343 lines
		
	
	
		
			7.0 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			343 lines
		
	
	
		
			7.0 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // SPDX-License-Identifier: GPL-2.0+
 | |
| 
 | |
| /*
 | |
|  * Ptrace test for hw breakpoints
 | |
|  *
 | |
|  * Based on tools/testing/selftests/breakpoints/breakpoint_test.c
 | |
|  *
 | |
|  * This test forks and the parent then traces the child doing various
 | |
|  * types of ptrace enabled breakpoints
 | |
|  *
 | |
|  * Copyright (C) 2018 Michael Neuling, IBM Corporation.
 | |
|  */
 | |
| 
 | |
| #include <sys/ptrace.h>
 | |
| #include <unistd.h>
 | |
| #include <stddef.h>
 | |
| #include <sys/user.h>
 | |
| #include <stdio.h>
 | |
| #include <stdlib.h>
 | |
| #include <signal.h>
 | |
| #include <sys/types.h>
 | |
| #include <sys/wait.h>
 | |
| #include "ptrace.h"
 | |
| 
 | |
| /* Breakpoint access modes */
 | |
| enum {
 | |
| 	BP_X = 1,
 | |
| 	BP_RW = 2,
 | |
| 	BP_W = 4,
 | |
| };
 | |
| 
 | |
| static pid_t child_pid;
 | |
| static struct ppc_debug_info dbginfo;
 | |
| 
 | |
| static void get_dbginfo(void)
 | |
| {
 | |
| 	int ret;
 | |
| 
 | |
| 	ret = ptrace(PPC_PTRACE_GETHWDBGINFO, child_pid, NULL, &dbginfo);
 | |
| 	if (ret) {
 | |
| 		perror("Can't get breakpoint info\n");
 | |
| 		exit(-1);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static bool hwbreak_present(void)
 | |
| {
 | |
| 	return (dbginfo.num_data_bps != 0);
 | |
| }
 | |
| 
 | |
| static bool dawr_present(void)
 | |
| {
 | |
| 	return !!(dbginfo.features & PPC_DEBUG_FEATURE_DATA_BP_DAWR);
 | |
| }
 | |
| 
 | |
| static void set_breakpoint_addr(void *addr)
 | |
| {
 | |
| 	int ret;
 | |
| 
 | |
| 	ret = ptrace(PTRACE_SET_DEBUGREG, child_pid, 0, addr);
 | |
| 	if (ret) {
 | |
| 		perror("Can't set breakpoint addr\n");
 | |
| 		exit(-1);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static int set_hwbreakpoint_addr(void *addr, int range)
 | |
| {
 | |
| 	int ret;
 | |
| 
 | |
| 	struct ppc_hw_breakpoint info;
 | |
| 
 | |
| 	info.version = 1;
 | |
| 	info.trigger_type = PPC_BREAKPOINT_TRIGGER_RW;
 | |
| 	info.addr_mode = PPC_BREAKPOINT_MODE_EXACT;
 | |
| 	if (range > 0)
 | |
| 		info.addr_mode = PPC_BREAKPOINT_MODE_RANGE_INCLUSIVE;
 | |
| 	info.condition_mode = PPC_BREAKPOINT_CONDITION_NONE;
 | |
| 	info.addr = (__u64)addr;
 | |
| 	info.addr2 = (__u64)addr + range;
 | |
| 	info.condition_value = 0;
 | |
| 
 | |
| 	ret = ptrace(PPC_PTRACE_SETHWDEBUG, child_pid, 0, &info);
 | |
| 	if (ret < 0) {
 | |
| 		perror("Can't set breakpoint\n");
 | |
| 		exit(-1);
 | |
| 	}
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static int del_hwbreakpoint_addr(int watchpoint_handle)
 | |
| {
 | |
| 	int ret;
 | |
| 
 | |
| 	ret = ptrace(PPC_PTRACE_DELHWDEBUG, child_pid, 0, watchpoint_handle);
 | |
| 	if (ret < 0) {
 | |
| 		perror("Can't delete hw breakpoint\n");
 | |
| 		exit(-1);
 | |
| 	}
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| #define DAWR_LENGTH_MAX 512
 | |
| 
 | |
| /* Dummy variables to test read/write accesses */
 | |
| static unsigned long long
 | |
| 	dummy_array[DAWR_LENGTH_MAX / sizeof(unsigned long long)]
 | |
| 	__attribute__((aligned(512)));
 | |
| static unsigned long long *dummy_var = dummy_array;
 | |
| 
 | |
| static void write_var(int len)
 | |
| {
 | |
| 	long long *plval;
 | |
| 	char *pcval;
 | |
| 	short *psval;
 | |
| 	int *pival;
 | |
| 
 | |
| 	switch (len) {
 | |
| 	case 1:
 | |
| 		pcval = (char *)dummy_var;
 | |
| 		*pcval = 0xff;
 | |
| 		break;
 | |
| 	case 2:
 | |
| 		psval = (short *)dummy_var;
 | |
| 		*psval = 0xffff;
 | |
| 		break;
 | |
| 	case 4:
 | |
| 		pival = (int *)dummy_var;
 | |
| 		*pival = 0xffffffff;
 | |
| 		break;
 | |
| 	case 8:
 | |
| 		plval = (long long *)dummy_var;
 | |
| 		*plval = 0xffffffffffffffffLL;
 | |
| 		break;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static void read_var(int len)
 | |
| {
 | |
| 	char cval __attribute__((unused));
 | |
| 	short sval __attribute__((unused));
 | |
| 	int ival __attribute__((unused));
 | |
| 	long long lval __attribute__((unused));
 | |
| 
 | |
| 	switch (len) {
 | |
| 	case 1:
 | |
| 		cval = *(char *)dummy_var;
 | |
| 		break;
 | |
| 	case 2:
 | |
| 		sval = *(short *)dummy_var;
 | |
| 		break;
 | |
| 	case 4:
 | |
| 		ival = *(int *)dummy_var;
 | |
| 		break;
 | |
| 	case 8:
 | |
| 		lval = *(long long *)dummy_var;
 | |
| 		break;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Do the r/w accesses to trigger the breakpoints. And run
 | |
|  * the usual traps.
 | |
|  */
 | |
| static void trigger_tests(void)
 | |
| {
 | |
| 	int len, ret;
 | |
| 
 | |
| 	ret = ptrace(PTRACE_TRACEME, 0, NULL, 0);
 | |
| 	if (ret) {
 | |
| 		perror("Can't be traced?\n");
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	/* Wake up father so that it sets up the first test */
 | |
| 	kill(getpid(), SIGUSR1);
 | |
| 
 | |
| 	/* Test write watchpoints */
 | |
| 	for (len = 1; len <= sizeof(long); len <<= 1)
 | |
| 		write_var(len);
 | |
| 
 | |
| 	/* Test read/write watchpoints (on read accesses) */
 | |
| 	for (len = 1; len <= sizeof(long); len <<= 1)
 | |
| 		read_var(len);
 | |
| 
 | |
| 	/* Test when breakpoint is unset */
 | |
| 
 | |
| 	/* Test write watchpoints */
 | |
| 	for (len = 1; len <= sizeof(long); len <<= 1)
 | |
| 		write_var(len);
 | |
| 
 | |
| 	/* Test read/write watchpoints (on read accesses) */
 | |
| 	for (len = 1; len <= sizeof(long); len <<= 1)
 | |
| 		read_var(len);
 | |
| }
 | |
| 
 | |
| static void check_success(const char *msg)
 | |
| {
 | |
| 	const char *msg2;
 | |
| 	int status;
 | |
| 
 | |
| 	/* Wait for the child to SIGTRAP */
 | |
| 	wait(&status);
 | |
| 
 | |
| 	msg2 = "Failed";
 | |
| 
 | |
| 	if (WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP) {
 | |
| 		msg2 = "Child process hit the breakpoint";
 | |
| 	}
 | |
| 
 | |
| 	printf("%s Result: [%s]\n", msg, msg2);
 | |
| }
 | |
| 
 | |
| static void launch_watchpoints(char *buf, int mode, int len,
 | |
| 			       struct ppc_debug_info *dbginfo, bool dawr)
 | |
| {
 | |
| 	const char *mode_str;
 | |
| 	unsigned long data = (unsigned long)(dummy_var);
 | |
| 	int wh, range;
 | |
| 
 | |
| 	data &= ~0x7UL;
 | |
| 
 | |
| 	if (mode == BP_W) {
 | |
| 		data |= (1UL << 1);
 | |
| 		mode_str = "write";
 | |
| 	} else {
 | |
| 		data |= (1UL << 0);
 | |
| 		data |= (1UL << 1);
 | |
| 		mode_str = "read";
 | |
| 	}
 | |
| 
 | |
| 	/* Set DABR_TRANSLATION bit */
 | |
| 	data |= (1UL << 2);
 | |
| 
 | |
| 	/* use PTRACE_SET_DEBUGREG breakpoints */
 | |
| 	set_breakpoint_addr((void *)data);
 | |
| 	ptrace(PTRACE_CONT, child_pid, NULL, 0);
 | |
| 	sprintf(buf, "Test %s watchpoint with len: %d ", mode_str, len);
 | |
| 	check_success(buf);
 | |
| 	/* Unregister hw brkpoint */
 | |
| 	set_breakpoint_addr(NULL);
 | |
| 
 | |
| 	data = (data & ~7); /* remove dabr control bits */
 | |
| 
 | |
| 	/* use PPC_PTRACE_SETHWDEBUG breakpoint */
 | |
| 	if (!(dbginfo->features & PPC_DEBUG_FEATURE_DATA_BP_RANGE))
 | |
| 		return; /* not supported */
 | |
| 	wh = set_hwbreakpoint_addr((void *)data, 0);
 | |
| 	ptrace(PTRACE_CONT, child_pid, NULL, 0);
 | |
| 	sprintf(buf, "Test %s watchpoint with len: %d ", mode_str, len);
 | |
| 	check_success(buf);
 | |
| 	/* Unregister hw brkpoint */
 | |
| 	del_hwbreakpoint_addr(wh);
 | |
| 
 | |
| 	/* try a wider range */
 | |
| 	range = 8;
 | |
| 	if (dawr)
 | |
| 		range = 512 - ((int)data & (DAWR_LENGTH_MAX - 1));
 | |
| 	wh = set_hwbreakpoint_addr((void *)data, range);
 | |
| 	ptrace(PTRACE_CONT, child_pid, NULL, 0);
 | |
| 	sprintf(buf, "Test %s watchpoint with len: %d ", mode_str, len);
 | |
| 	check_success(buf);
 | |
| 	/* Unregister hw brkpoint */
 | |
| 	del_hwbreakpoint_addr(wh);
 | |
| }
 | |
| 
 | |
| /* Set the breakpoints and check the child successfully trigger them */
 | |
| static int launch_tests(bool dawr)
 | |
| {
 | |
| 	char buf[1024];
 | |
| 	int len, i, status;
 | |
| 
 | |
| 	struct ppc_debug_info dbginfo;
 | |
| 
 | |
| 	i = ptrace(PPC_PTRACE_GETHWDBGINFO, child_pid, NULL, &dbginfo);
 | |
| 	if (i) {
 | |
| 		perror("Can't set breakpoint info\n");
 | |
| 		exit(-1);
 | |
| 	}
 | |
| 	if (!(dbginfo.features & PPC_DEBUG_FEATURE_DATA_BP_RANGE))
 | |
| 		printf("WARNING: Kernel doesn't support PPC_PTRACE_SETHWDEBUG\n");
 | |
| 
 | |
| 	/* Write watchpoint */
 | |
| 	for (len = 1; len <= sizeof(long); len <<= 1)
 | |
| 		launch_watchpoints(buf, BP_W, len, &dbginfo, dawr);
 | |
| 
 | |
| 	/* Read-Write watchpoint */
 | |
| 	for (len = 1; len <= sizeof(long); len <<= 1)
 | |
| 		launch_watchpoints(buf, BP_RW, len, &dbginfo, dawr);
 | |
| 
 | |
| 	ptrace(PTRACE_CONT, child_pid, NULL, 0);
 | |
| 
 | |
| 	/*
 | |
| 	 * Now we have unregistered the breakpoint, access by child
 | |
| 	 * should not cause SIGTRAP.
 | |
| 	 */
 | |
| 
 | |
| 	wait(&status);
 | |
| 
 | |
| 	if (WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP) {
 | |
| 		printf("FAIL: Child process hit the breakpoint, which is not expected\n");
 | |
| 		ptrace(PTRACE_CONT, child_pid, NULL, 0);
 | |
| 		return TEST_FAIL;
 | |
| 	}
 | |
| 
 | |
| 	if (WIFEXITED(status))
 | |
| 		printf("Child exited normally\n");
 | |
| 
 | |
| 	return TEST_PASS;
 | |
| }
 | |
| 
 | |
| static int ptrace_hwbreak(void)
 | |
| {
 | |
| 	pid_t pid;
 | |
| 	int ret;
 | |
| 	bool dawr;
 | |
| 
 | |
| 	pid = fork();
 | |
| 	if (!pid) {
 | |
| 		trigger_tests();
 | |
| 		return 0;
 | |
| 	}
 | |
| 
 | |
| 	wait(NULL);
 | |
| 
 | |
| 	child_pid = pid;
 | |
| 
 | |
| 	get_dbginfo();
 | |
| 	SKIP_IF(!hwbreak_present());
 | |
| 	dawr = dawr_present();
 | |
| 
 | |
| 	ret = launch_tests(dawr);
 | |
| 
 | |
| 	wait(NULL);
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| int main(int argc, char **argv, char **envp)
 | |
| {
 | |
| 	return test_harness(ptrace_hwbreak, "ptrace-hwbreak");
 | |
| }
 | 
