View Issue Details

IDProjectCategoryView StatusLast Update
0007587CentOS-7kernelpublic2014-09-11 14:56
Reportertperry Assigned To 
PrioritynormalSeveritymajorReproducibilityrandom
Status newResolutionopen 
PlatformAMD FX-8350OSCentOS LinuxOS Version7.0.1406
Product Version7.0-1406 
Summary0007587: Debug status register reported by ptrace is incorrect when running KVM virtual machines on an AMD system
DescriptionThe value in DR6 (the debug status register) in a process being traced using ptrace is intermittently reported to be an inconsistent value.

We see three manifestations of this problem, let's call them A, B and C. In A, DR6 in a tracee in a VM reliably reports that no watchpoints have fired, despite a watchpoint having been set and a SIGTRAP having been observed by the debugger. In B, DR6 in a tracee in a VM reliably reports that the watchpoint 0 has fired, regardless of which watchpoint was requested to be set in DR7 (the debug control register). (In this case, the single-step flag is also set, meaning that the contents of DR6 are 0x4001.) In C, DR6 in the *host* system *intermittently* behaves according to manifestation B.

We can reproduce this problem reliably on our AMD system. We don't see the problem on Intel, and we don't see the problem before the VMs have been started.

In an attempt to fix the problem, we've applied some patches to KVM and rebuilt/reloaded the kvm.ko and kvm-amd.ko modules.

The following patch fixes manifestation A:

http://markmail.org/message/7kt2gufxre3wgonz

And the following patch fixes manifestation B:

http://markmail.org/message/wdgqjpabvqekqspl

However, having applied these, the problem is fixed in the VMs, but not in the host system, which exhibits the bug in the same way as before (manifestation C).
Steps To Reproduce1. Set up one or more virtual machines using KVM.
2. Run the attached version of breakpoint_test.c on the host and the VMs, which has been modified to print the values that ptrace has read from the DR6 register and check that they are correct.

The command I use is:
$ gcc breakpoint_test.c && while true; do while ./a.out; do :; done | grep FAIL; done

It may take some time for failures to appear, but once they do, they'll appear frequently and reliably.
TagsNo tags attached.
abrt_hash
URL

Activities

tperry

tperry

2014-09-11 14:56

reporter  

breakpoint_test.c (7,984 bytes)   
/*
 * Copyright (C) 2011 Red Hat, Inc., Frederic Weisbecker <fweisbec@redhat.com>
 *
 * Licensed under the terms of the GNU GPL License version 2
 *
 * Selftests for breakpoints (and more generally the do_debug() path) in x86.
 *
 * *** Modified to expose DR6 errors. ***
 */


#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>


/* 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) {
		perror("Can't set breakpoint addr\n");
		exit(-1);
	}
}

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;
		printf("Setting watchpoint %d, type: %s, len: %d, local: %d, global: %d",
				n, type == BP_X ? " BP_X" : type == BP_W ? " BP_W" : "BP_RW",
				len, local, global);
	}
	else {
		dr7 &= ~vdr7;
		printf(", clearing watchpoint %d", n);
	}

	printf(", DR7: 0x%08lx", dr7);

	ret = ptrace(PTRACE_POKEUSER, child_pid,
		     offsetof(struct user, u_debugreg[7]), dr7);
	if (ret) {
		perror("Can't set dr7");
		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) {
		perror("Can't be traced?\n");
		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 < 4; 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 i)
{
	const char *msg2;
	int child_nr_tests;
	int status;

	/* Wait for the child to SIGTRAP */
	wait(&status);

	msg2 = "Failed";

	if (WSTOPSIG(status) == SIGTRAP) {
		child_nr_tests = ptrace(PTRACE_PEEKDATA, child_pid,
					&nr_tests, 0);
		if (child_nr_tests == nr_tests)
			msg2 = "Ok";
		if (ptrace(PTRACE_POKEDATA, child_pid, &trapped, 1)) {
			perror("Can't poke\n");
			exit(-1);
		}

		unsigned long dr6;
		dr6 = ptrace(PTRACE_PEEKUSER, child_pid,
			     offsetof(struct user, u_debugreg[6]), 0);
		printf(", DR6: 0x%08lx", dr6);
		dr6 &= ~0xffff4ff0;
		if (dr6 == 1 << i)
			printf(" (PASS)");
		else {
			printf(" (FAIL)");
			printf("\n");
			exit(-1);
		}
	}

	nr_tests++;

	//printf("%s [%s]\n", msg, msg2);
}

static void launch_instruction_breakpoints(char *buf, int local, int global)
{
	int i;

	for (i = 0; i < 4; 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",
			//i, local, global);
		check_success(buf, i);
		toggle_breakpoint(i, BP_X, 1, local, global, 0);
		printf("\n");
	}
}

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 < 4; 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", mode_str, i, len, local, global);
		check_success(buf, i);
		toggle_breakpoint(i, mode, len, local, global, 0);
		printf("\n");
	}
}

/* Set the breakpoints and check the child successfully trigger them */
static void launch_tests(void)
{
	char buf[1024];
	int len, local, global, i;

	/* 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");

	/* Int 3 traps */
	//ptrace(PTRACE_CONT, child_pid, NULL, 0);
	//check_success("Test int 3 trap");

	ptrace(PTRACE_CONT, child_pid, NULL, 0);
}

int main(int argc, char **argv)
{
	pid_t pid;
	int ret;

	pid = fork();
	if (!pid) {
		trigger_tests();
		return 0;
	}

	child_pid = pid;

	wait(NULL);

	launch_tests();

	wait(NULL);

	return 0;
}
breakpoint_test.c (7,984 bytes)   

Issue History

Date Modified Username Field Change
2014-09-11 14:56 tperry New Issue
2014-09-11 14:56 tperry File Added: breakpoint_test.c