Created
May 24, 2018 06:09
-
-
Save rocallahan/32561514df847ec3b54370fe7553500d to your computer and use it in GitHub Desktop.
Test for hardware watchpoint quirk leading to RCB overcount
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* -*- Mode: C; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ | |
#define _GNU_SOURCE | |
#include <assert.h> | |
#include <errno.h> | |
#include <linux/perf_event.h> | |
#include <sched.h> | |
#include <signal.h> | |
#include <stddef.h> | |
#include <stdint.h> | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <sys/mman.h> | |
#include <sys/ptrace.h> | |
#include <sys/syscall.h> | |
#include <sys/user.h> | |
#include <sys/wait.h> | |
#include <unistd.h> | |
static int pipe_fds[2]; | |
static void serialize(void) { | |
asm volatile ("lfence" : : "a"(0)); | |
} | |
static void do_child(char* start, size_t size) { | |
serialize(); | |
asm volatile ("rep stosb\n\t" | |
: : "a"(1), "c"(size), "D"(start) : "memory"); | |
serialize(); | |
} | |
static void init_perf_event_attr(struct perf_event_attr* attr) { | |
memset(attr, 0, sizeof(*attr)); | |
attr->type = PERF_TYPE_RAW; | |
attr->size = sizeof(*attr); | |
attr->config = 0x5101c4; | |
attr->exclude_kernel = 1; | |
attr->exclude_guest = 1; | |
} | |
static int open_perf(pid_t child) { | |
struct perf_event_attr attr; | |
init_perf_event_attr(&attr); | |
int fd = syscall(__NR_perf_event_open, &attr, child, -1, -1, 0); | |
if (fd < 0 && (errno == EPERM || errno == EACCES)) { | |
fprintf(stderr, "Can't open Retired Conditional Branches Counter, check perf_event_paranoid"); | |
exit(2); | |
} | |
assert(fd >= 0); | |
return fd; | |
} | |
static void breakpoint(void) { | |
asm volatile ("int $0x3"); | |
} | |
static int64_t read_counter(int fd) { | |
int64_t val; | |
ssize_t nread = read(fd, &val, sizeof(val)); | |
assert(nread == sizeof(val)); | |
return val; | |
} | |
static int64_t count_ticks(int memset_start, int memset_size, int watch_offset, int use_watchpoint) { | |
int watch_size = 8; | |
size_t size = memset_start + watch_offset + watch_size; | |
char* p = (char*)mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE| MAP_ANONYMOUS, -1, 0); | |
char* watchpoint = p + memset_start + watch_offset; | |
pid_t child; | |
int status; | |
int perf_fd; | |
int64_t ticks; | |
assert(p != MAP_FAILED); | |
if (0 == (child = fork())) { | |
char ch; | |
read(pipe_fds[0], &ch, 1); | |
breakpoint(); | |
do_child(p + memset_start, memset_size); | |
breakpoint(); | |
exit(77); | |
} | |
assert(0 == ptrace(PTRACE_SEIZE, child, NULL, NULL)); | |
assert(1 == write(pipe_fds[1], "x", 1)); | |
assert(child == waitpid(child, &status, 0)); | |
assert(status == ((SIGTRAP << 8) | 0x7f)); | |
perf_fd = open_perf(child); | |
/* Use DR0 to break-on-write of 'watchpoint' */ | |
assert(0 == ptrace(PTRACE_POKEUSER, child, | |
(void*)offsetof(struct user, u_debugreg[0]), | |
(void*)watchpoint)); | |
if (use_watchpoint) { | |
/* Enable DR0 break-on-write */ | |
assert(0 == ptrace(PTRACE_POKEUSER, child, | |
(void*)offsetof(struct user, u_debugreg[7]), | |
(void*)0x10001)); | |
} | |
assert(0 == ptrace(PTRACE_CONT, child, NULL, NULL)); | |
assert(child == waitpid(child, &status, 0)); | |
assert(status == ((SIGTRAP << 8) | 0x7f)); | |
/* Watchpoint should not have triggered */ | |
assert(0 == ptrace(PTRACE_PEEKUSER, child, | |
(void*)offsetof(struct user, u_debugreg[6]))); | |
ticks = read_counter(perf_fd); | |
assert(0 == ptrace(PTRACE_DETACH, child, NULL, NULL)); | |
assert(child == waitpid(child, &status, 0)); | |
assert(WIFEXITED(status)); | |
assert(WEXITSTATUS(status) == 77); | |
assert(0 == close(perf_fd)); | |
assert(0 == munmap(p, size)); | |
return ticks; | |
} | |
/** | |
* memset_start: offset of start of stos operation | |
* memset_size: number of bytes | |
* watch_offset: offset of watchpoint from memset_start | |
*/ | |
static int check_delta(int memset_start, int memset_size, int watch_offset) { | |
long long base_ticks = count_ticks(memset_start, memset_size, watch_offset, 0); | |
long long watch_ticks = count_ticks(memset_start, memset_size, watch_offset, 1); | |
printf("memset_start %d memset_size %d watch_offset %d ERROR %lld (%lld-%lld)\n", | |
memset_start, memset_size, watch_offset, watch_ticks - base_ticks, | |
watch_ticks, base_ticks); | |
return watch_ticks != base_ticks; | |
} | |
int main(void) { | |
int ret = 0; | |
int i; | |
assert(0 == pipe(pipe_fds)); | |
puts("Memset size 0:"); | |
ret |= check_delta(0, 0, 63); | |
ret |= check_delta(0, 0, 64); | |
ret |= check_delta(0, 0, 127); | |
ret |= check_delta(0, 0, 128); | |
puts("Memset size 1:"); | |
ret |= check_delta(0, 1, 63); | |
ret |= check_delta(0, 1, 64); | |
ret |= check_delta(0, 1, 127); | |
ret |= check_delta(0, 1, 128); | |
puts("Memset size 64:"); | |
/* Can't test offset 63 because it would be triggered */ | |
ret |= check_delta(0, 64, 64); | |
ret |= check_delta(0, 64, 127); | |
ret |= check_delta(0, 64, 128); | |
puts("Memset size 65:"); | |
/* Can't test offset 64 because it would be triggered */ | |
ret |= check_delta(0, 65, 65); | |
ret |= check_delta(0, 65, 127); | |
ret |= check_delta(0, 65, 128); | |
ret |= check_delta(0, 65, 192); | |
puts("Memset size 1, unaligned:"); | |
ret |= check_delta(3, 1, 63); | |
ret |= check_delta(3, 1, 64); | |
ret |= check_delta(3, 1, 127); | |
ret |= check_delta(3, 1, 128); | |
puts(ret ? "FAIL" : "SUCCESS"); | |
return ret; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment