409 lines · 7.6 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-only/* * Copyright (C) 2011 Red Hat, Inc., Frederic Weisbecker <fweisbec@redhat.com> * * Selftests for breakpoints (and more generally the do_debug() path) in x86. */  #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 <errno.h>#include <string.h> #include "kselftest.h" #define COUNT_ISN_BPS	4#define COUNT_WPS	4 /* Breakpoint access modes */enum {	BP_X = 1,	BP_RW = 2,	BP_W = 4,}; static pid_t child_pid; /* * Ensures the child and parent are always "talking" about * the same test sequence. (ie: that we haven't forgotten * to call check_trapped() somewhere). */static int nr_tests; static void set_breakpoint_addr(void *addr, int n){	int ret; 	ret = ptrace(PTRACE_POKEUSER, child_pid,		     offsetof(struct user, u_debugreg[n]), addr);	if (ret)		ksft_exit_fail_msg("Can't set breakpoint addr: %s\n",			strerror(errno));} static void toggle_breakpoint(int n, int type, int len,			      int local, int global, int set){	int ret; 	int xtype, xlen;	unsigned long vdr7, dr7; 	switch (type) {	case BP_X:		xtype = 0;		break;	case BP_W:		xtype = 1;		break;	case BP_RW:		xtype = 3;		break;	} 	switch (len) {	case 1:		xlen = 0;		break;	case 2:		xlen = 4;		break;	case 4:		xlen = 0xc;		break;	case 8:		xlen = 8;		break;	} 	dr7 = ptrace(PTRACE_PEEKUSER, child_pid,		     offsetof(struct user, u_debugreg[7]), 0); 	vdr7 = (xlen | xtype) << 16;	vdr7 <<= 4 * n; 	if (local) {		vdr7 |= 1 << (2 * n);		vdr7 |= 1 << 8;	}	if (global) {		vdr7 |= 2 << (2 * n);		vdr7 |= 1 << 9;	} 	if (set)		dr7 |= vdr7;	else		dr7 &= ~vdr7; 	ret = ptrace(PTRACE_POKEUSER, child_pid,		     offsetof(struct user, u_debugreg[7]), dr7);	if (ret) {		ksft_print_msg("Can't set dr7: %s\n", strerror(errno));		exit(-1);	}} /* Dummy variables to test read/write accesses */static unsigned long long dummy_var[4]; /* Dummy functions to test execution accesses */static void dummy_func(void) { }static void dummy_func1(void) { }static void dummy_func2(void) { }static void dummy_func3(void) { } static void (*dummy_funcs[])(void) = {	dummy_func,	dummy_func1,	dummy_func2,	dummy_func3,}; static int trapped; static void check_trapped(void){	/*	 * If we haven't trapped, wake up the parent	 * so that it notices the failure.	 */	if (!trapped)		kill(getpid(), SIGUSR1);	trapped = 0; 	nr_tests++;} static void write_var(int len){	char *pcval; short *psval; int *pival; long long *plval;	int i; 	for (i = 0; i < 4; i++) {		switch (len) {		case 1:			pcval = (char *)&dummy_var[i];			*pcval = 0xff;			break;		case 2:			psval = (short *)&dummy_var[i];			*psval = 0xffff;			break;		case 4:			pival = (int *)&dummy_var[i];			*pival = 0xffffffff;			break;		case 8:			plval = (long long *)&dummy_var[i];			*plval = 0xffffffffffffffffLL;			break;		}		check_trapped();	}} static void read_var(int len){	char cval; short sval; int ival; long long lval;	int i; 	for (i = 0; i < 4; i++) {		switch (len) {		case 1:			cval = *(char *)&dummy_var[i];			break;		case 2:			sval = *(short *)&dummy_var[i];			break;		case 4:			ival = *(int *)&dummy_var[i];			break;		case 8:			lval = *(long long *)&dummy_var[i];			break;		}		check_trapped();	}} /* * Do the r/w/x accesses to trigger the breakpoints. And run * the usual traps. */static void trigger_tests(void){	int len, local, global, i;	char val;	int ret; 	ret = ptrace(PTRACE_TRACEME, 0, NULL, 0);	if (ret) {		ksft_print_msg("Can't be traced? %s\n", strerror(errno));		return;	} 	/* Wake up father so that it sets up the first test */	kill(getpid(), SIGUSR1); 	/* Test instruction breakpoints */	for (local = 0; local < 2; local++) {		for (global = 0; global < 2; global++) {			if (!local && !global)				continue; 			for (i = 0; i < COUNT_ISN_BPS; i++) {				dummy_funcs[i]();				check_trapped();			}		}	} 	/* Test write watchpoints */	for (len = 1; len <= sizeof(long); len <<= 1) {		for (local = 0; local < 2; local++) {			for (global = 0; global < 2; global++) {				if (!local && !global)					continue;				write_var(len);			}		}	} 	/* Test read/write watchpoints (on read accesses) */	for (len = 1; len <= sizeof(long); len <<= 1) {		for (local = 0; local < 2; local++) {			for (global = 0; global < 2; global++) {				if (!local && !global)					continue;				read_var(len);			}		}	} 	/* Icebp trap */	asm(".byte 0xf1\n");	check_trapped(); 	/* Int 3 trap */	asm("int $3\n");	check_trapped(); 	kill(getpid(), SIGUSR1);} static void check_success(const char *msg){	int child_nr_tests;	int status;	int ret; 	/* Wait for the child to SIGTRAP */	wait(&status); 	ret = 0; 	if (WSTOPSIG(status) == SIGTRAP) {		child_nr_tests = ptrace(PTRACE_PEEKDATA, child_pid,					&nr_tests, 0);		if (child_nr_tests == nr_tests)			ret = 1;		if (ptrace(PTRACE_POKEDATA, child_pid, &trapped, 1))			ksft_exit_fail_msg("Can't poke: %s\n", strerror(errno));	} 	nr_tests++; 	if (ret)		ksft_test_result_pass("%s", msg);	else		ksft_test_result_fail("%s", msg);} static void launch_instruction_breakpoints(char *buf, int local, int global){	int i; 	for (i = 0; i < COUNT_ISN_BPS; i++) {		set_breakpoint_addr(dummy_funcs[i], i);		toggle_breakpoint(i, BP_X, 1, local, global, 1);		ptrace(PTRACE_CONT, child_pid, NULL, 0);		sprintf(buf, "Test breakpoint %d with local: %d global: %d\n",			i, local, global);		check_success(buf);		toggle_breakpoint(i, BP_X, 1, local, global, 0);	}} static void launch_watchpoints(char *buf, int mode, int len,			       int local, int global){	const char *mode_str;	int i; 	if (mode == BP_W)		mode_str = "write";	else		mode_str = "read"; 	for (i = 0; i < COUNT_WPS; i++) {		set_breakpoint_addr(&dummy_var[i], i);		toggle_breakpoint(i, mode, len, local, global, 1);		ptrace(PTRACE_CONT, child_pid, NULL, 0);		sprintf(buf,			"Test %s watchpoint %d with len: %d local: %d global: %d\n",			mode_str, i, len, local, global);		check_success(buf);		toggle_breakpoint(i, mode, len, local, global, 0);	}} /* Set the breakpoints and check the child successfully trigger them */static void launch_tests(void){	char buf[1024];	unsigned int tests = 0;	int len, local, global, i; 	tests += 3 * COUNT_ISN_BPS;	tests += sizeof(long) / 2 * 3 * COUNT_WPS;	tests += sizeof(long) / 2 * 3 * COUNT_WPS;	tests += 2;	ksft_set_plan(tests); 	/* Instruction breakpoints */	for (local = 0; local < 2; local++) {		for (global = 0; global < 2; global++) {			if (!local && !global)				continue;			launch_instruction_breakpoints(buf, local, global);		}	} 	/* Write watchpoint */	for (len = 1; len <= sizeof(long); len <<= 1) {		for (local = 0; local < 2; local++) {			for (global = 0; global < 2; global++) {				if (!local && !global)					continue;				launch_watchpoints(buf, BP_W, len,						   local, global);			}		}	} 	/* Read-Write watchpoint */	for (len = 1; len <= sizeof(long); len <<= 1) {		for (local = 0; local < 2; local++) {			for (global = 0; global < 2; global++) {				if (!local && !global)					continue;				launch_watchpoints(buf, BP_RW, len,						   local, global);			}		}	} 	/* Icebp traps */	ptrace(PTRACE_CONT, child_pid, NULL, 0);	check_success("Test icebp\n"); 	/* Int 3 traps */	ptrace(PTRACE_CONT, child_pid, NULL, 0);	check_success("Test int 3 trap\n"); 	ptrace(PTRACE_CONT, child_pid, NULL, 0);} int main(int argc, char **argv){	pid_t pid;	int ret; 	ksft_print_header(); 	pid = fork();	if (!pid) {		trigger_tests();		exit(0);	} 	child_pid = pid; 	wait(NULL); 	launch_tests(); 	wait(NULL); 	ksft_exit_pass();}