Skip to content

Instantly share code, notes, and snippets.

@rocallahan
Created May 24, 2018 06:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rocallahan/32561514df847ec3b54370fe7553500d to your computer and use it in GitHub Desktop.
Save rocallahan/32561514df847ec3b54370fe7553500d to your computer and use it in GitHub Desktop.
Test for hardware watchpoint quirk leading to RCB overcount
/* -*- 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