| #define _GNU_SOURCE |
| |
| #include <stdio.h> |
| #include <sys/prctl.h> |
| #include <asm/prctl.h> |
| #include <sys/ucontext.h> |
| #include <unistd.h> |
| #include <errno.h> |
| #include <signal.h> |
| #include <string.h> |
| #include <err.h> |
| #include <linux/perf_event.h> |
| #include <sys/mman.h> |
| #include <sys/syscall.h> |
| |
| static volatile sig_atomic_t got_sigbus = 0; |
| |
| static void sethandler(int sig, void (*handler)(int, siginfo_t *, void *), |
| int flags) |
| { |
| struct sigaction sa; |
| memset(&sa, 0, sizeof(sa)); |
| sa.sa_sigaction = handler; |
| sa.sa_flags = SA_SIGINFO | flags; |
| sigemptyset(&sa.sa_mask); |
| if (sigaction(sig, &sa, 0)) |
| err(1, "sigaction"); |
| } |
| |
| #ifdef __x86_64__ |
| # define REG_IP REG_RIP |
| #else |
| # define REG_IP REG_EIP |
| #endif |
| |
| extern const char rdpmc_insn[]; |
| |
| static void sigsegv(int sig, siginfo_t *si, void *ctx_void) |
| { |
| ucontext_t *ctx = (ucontext_t*)ctx_void; |
| |
| if (ctx->uc_mcontext.gregs[REG_IP] == (unsigned long)rdpmc_insn) { |
| got_sigbus = 1; |
| ctx->uc_mcontext.gregs[REG_IP] += 2; |
| } else { |
| raise(SIGABRT); |
| } |
| } |
| |
| __attribute__((noinline, noclone)) static void do_rdpmc(unsigned int ecx) |
| { |
| unsigned int eax, edx; |
| |
| got_sigbus = 0; |
| |
| asm volatile ("rdpmc_insn: rdpmc" : "=a" (eax), "=d" (edx) : "c" (ecx)); |
| |
| if (got_sigbus) |
| return; |
| |
| printf("PMC 0x%08X: 0x%08X%08X\n", ecx, edx, eax); |
| } |
| |
| static int perf_event_open(struct perf_event_attr *hw_event, pid_t pid, |
| int cpu, int group_fd, unsigned long flags) |
| { |
| return syscall(__NR_perf_event_open, hw_event, pid, cpu, |
| group_fd, flags); |
| } |
| |
| static void* map_one(void) |
| { |
| struct perf_event_attr pe; |
| int fd; |
| |
| memset(&pe, 0, sizeof(struct perf_event_attr)); |
| pe.type = PERF_TYPE_HARDWARE; |
| pe.size = sizeof(struct perf_event_attr); |
| pe.config = PERF_COUNT_HW_INSTRUCTIONS; |
| pe.disabled = 0; |
| pe.exclude_kernel = 1; |
| pe.exclude_hv = 1; |
| |
| fd = perf_event_open(&pe, getpid(), -1, -1, 0); |
| if (fd == -1) |
| err(1, "perf_event_open"); |
| |
| return mmap(NULL, 4096, PROT_READ, MAP_SHARED, fd, 0); |
| } |
| |
| int main(int argc, char **argv) |
| { |
| if (argc == 2 && !strcmp(argv[1], "try_harder")) { |
| map_one(); |
| munmap(map_one(), 4096); |
| } |
| |
| sethandler(SIGSEGV, sigsegv, 0); |
| |
| for (unsigned int i = 0; i < 4096; i++) |
| do_rdpmc(i); |
| |
| for (unsigned int i = 0; i < 4096; i++) |
| do_rdpmc(i | (1<<30)); |
| |
| return 0; |
| } |