blob: c89f32d6f36d84a37b9c5f1406758e223ee23e9c [file] [log] [blame]
/*
*
* honggfuzz - architecture dependent code (LINUX/PERF)
* -----------------------------------------
*
* Author: Robert Swiecki <swiecki@google.com>
*
* Copyright 2010-2015 by Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License.
*
*/
#include "common.h"
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <linux/hw_breakpoint.h>
#include <linux/perf_event.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/poll.h>
#include <sys/ptrace.h>
#include <sys/syscall.h>
#include <unistd.h>
#include "linux/perf.h"
#include "log.h"
/*
* 1 + 16 pages
*/
#define _HF_PERF_MMAP_DATA_SZ (1024 * 1024)
#define _HF_PERF_MMAP_TOT_SZ (getpagesize() + _HF_PERF_MMAP_DATA_SZ)
static __thread uint8_t *perfMmap = NULL;
static __thread int perfSig = 0;
#define _HF_PERF_BRANCHES_SZ (1024 * 256)
/* *INDENT-OFF* */
static __thread uint64_t perfPC[_HF_PERF_BRANCHES_SZ];
/* *INDENT-ON* */
static size_t arch_perfCountBranches(void)
{
size_t i = 0;
for (i = 0; i < _HF_PERF_BRANCHES_SZ; i++) {
if (perfPC[i] == 0ULL) {
return i;
}
LOGMSG(l_DEBUG, "Branch entry: PC: %" PRIx64, perfPC[i]);
}
return i;
}
static inline void arch_perfAddPC(uint64_t pc)
{
for (size_t i = 0; i < _HF_PERF_BRANCHES_SZ; i++) {
if (perfPC[i] == pc) {
break;
}
if (perfPC[i] == 0ULL) {
perfPC[i] = pc;
break;
}
}
}
static inline void arch_perfMmapParse(int fd)
{
if (fd == 999) {
return;
}
struct perf_event_mmap_page *pem = (struct perf_event_mmap_page *)perfMmap;
/* Memory Barriers */
#if defined(__x86_64__)
#define rmb() __asm__ __volatile__ ("lfence" ::: "memory")
#elif defined(__i386__)
#define rmb() __asm__ __volatile__ ("lock; addl $0,0(%%esp)" ::: "memory")
#else
#define rmb() __asm__ __volatile__ ("" ::: "memory")
#endif
uint64_t dataHeadOff = pem->data_head % _HF_PERF_MMAP_DATA_SZ;
uint64_t dataTailOff = pem->data_tail % _HF_PERF_MMAP_DATA_SZ;
/* Required as per 'man perf_event_open' */
rmb();
uint8_t *dataTailPtr = perfMmap + getpagesize() + dataTailOff;
uint8_t *localData = malloc(_HF_PERF_MMAP_DATA_SZ);
if (localData == NULL) {
LOGMSG_P(l_FATAL, "malloc(%zu) failed", _HF_PERF_MMAP_DATA_SZ);
}
size_t localDataLen = 0;
if (dataHeadOff > dataTailOff) {
localDataLen = dataHeadOff - dataTailOff;
memcpy(localData, dataTailPtr, localDataLen);
}
if (dataHeadOff < dataTailOff) {
localDataLen = _HF_PERF_MMAP_DATA_SZ - dataTailOff + dataHeadOff;
memcpy(&localData[0], dataTailPtr, _HF_PERF_MMAP_DATA_SZ - dataTailOff);
memcpy(&localData[_HF_PERF_MMAP_DATA_SZ - dataTailOff], perfMmap + getpagesize(),
dataHeadOff);
}
/* Ok, let it go */
pem->data_tail = pem->data_head;
for (struct perf_event_header * peh = (struct perf_event_header *)localData;
(uintptr_t) peh < (uintptr_t) (localData + localDataLen);
peh = (struct perf_event_header *)((uint8_t *) peh + peh->size)) {
/* Cannot recover from this condition */
if (peh->size == 0) {
LOGMSG(l_FATAL, "(struct perf_event_header)->size == 0 (%" PRIu16 ")", peh->size);
break;
}
if (peh->type != PERF_RECORD_SAMPLE) {
LOGMSG(l_DEBUG, "(struct perf_event_header)->type != PERF_RECORD_SAMPLE (%" PRIu16 ")",
peh->type);
continue;
}
if (peh->misc != PERF_RECORD_MISC_USER && peh->misc != PERF_RECORD_MISC_KERNEL) {
LOGMSG(l_FATAL,
"(struct perf_event_header)->type != PERF_RECORD_MISC_USER (%" PRIu16 ")",
peh->misc);
continue;
}
uint64_t ip = *(uint64_t *) ((uint8_t *) peh + sizeof(peh));
arch_perfAddPC(ip);
}
free(localData);
}
static long 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 arch_perfSigHandler(int signum, siginfo_t * si, void *unused)
{
perfSig = signum;
return;
if (signum == SIGCHLD) {
return;
}
if (si == NULL) {
return;
}
if (unused == NULL) {
return;
}
}
bool arch_perfEnable(pid_t pid, honggfuzz_t * hfuzz, int *perfFd)
{
if (hfuzz->dynFileMethod == _HF_DYNFILE_NONE) {
return true;
}
bzero(perfPC, sizeof(perfPC));
LOGMSG(l_DEBUG, "Enabling PERF for PID=%d", pid);
struct perf_event_attr pe;
memset(&pe, 0, sizeof(struct perf_event_attr));
pe.size = sizeof(struct perf_event_attr);
pe.disabled = 0;
pe.exclude_kernel = 1;
pe.exclude_hv = 1;
pe.exclude_callchain_kernel = 1;
pe.type = PERF_TYPE_HARDWARE;
switch (hfuzz->dynFileMethod) {
case _HF_DYNFILE_INSTR_COUNT:
LOGMSG(l_DEBUG, "Using: PERF_COUNT_HW_INSTRUCTIONS for PID: %d", pid);
pe.config = PERF_COUNT_HW_INSTRUCTIONS;
pe.inherit = 1;
break;
case _HF_DYNFILE_BRANCH_COUNT:
LOGMSG(l_DEBUG, "Using: PERF_COUNT_HW_BRANCH_INSTRUCTIONS for PID: %d", pid);
pe.config = PERF_COUNT_HW_BRANCH_INSTRUCTIONS;
pe.inherit = 1;
break;
case _HF_DYNFILE_UNIQUE_PC_COUNT:
LOGMSG(l_DEBUG, "Using: PERF_SAMPLE_BRANCH_STACK/PERF_SAMPLE_IP for PID: %d", pid);
pe.config = PERF_COUNT_HW_BRANCH_INSTRUCTIONS;
pe.sample_type = PERF_SAMPLE_IP;
pe.sample_period = 1; /* It's BTS based, so must be equal to 1 */
pe.watermark = 1;
pe.wakeup_watermark = (4096 * 16);
break;
default:
LOGMSG(l_ERROR, "Unknown perf mode: '%c' for PID: %d", hfuzz->dynFileMethod, pid);
return false;
break;
}
*perfFd = perf_event_open(&pe, pid, -1, -1, 0);
if (*perfFd == -1) {
LOGMSG_P(l_ERROR, "perf_event_open() failed");
if (hfuzz->dynFileMethod == _HF_DYNFILE_UNIQUE_PC_COUNT) {
LOGMSG(l_ERROR,
"-Dp mode (sample IP/PC) requires LBR/BTS present in Intel Haswell and newer (i.e. not in AMD CPUs)");
}
return false;
}
if (hfuzz->dynFileMethod != _HF_DYNFILE_UNIQUE_PC_COUNT) {
return true;
}
perfMmap = mmap(NULL, _HF_PERF_MMAP_TOT_SZ, PROT_READ | PROT_WRITE, MAP_SHARED, *perfFd, 0);
if (perfMmap == MAP_FAILED) {
LOGMSG_P(l_ERROR, "mmap() failed");
close(*perfFd);
return false;
}
sigset_t smask;
sigemptyset(&smask);
struct sigaction sa = {
.sa_handler = NULL,
.sa_sigaction = arch_perfSigHandler,
.sa_mask = smask,
.sa_flags = SA_SIGINFO | SA_RESTART,
.sa_restorer = NULL
};
if (sigaction(SIGCHLD, &sa, NULL) == -1) {
LOGMSG_P(l_ERROR, "sigaction() failed");
return false;
}
if (sigaction(SIGTRAP, &sa, NULL) == -1) {
LOGMSG_P(l_ERROR, "sigaction() failed");
return false;
}
return true;
}
void arch_perfPoll(int perfFd)
{
for (;;) {
struct pollfd pollfds = {.fd = perfFd,.events = POLLIN,.revents = 0 };
int ret = poll(&pollfds, 1, 100);
if (ret <= 0) {
return;
}
arch_perfMmapParse(perfFd);
}
}
void arch_perfAnalyze(honggfuzz_t * hfuzz, fuzzer_t * fuzzer, int perfFd)
{
uint64_t count = 0LL;
if (hfuzz->dynFileMethod == _HF_DYNFILE_NONE) {
return;
}
if (hfuzz->dynFileMethod == _HF_DYNFILE_UNIQUE_PC_COUNT) {
arch_perfMmapParse(perfFd);
count = fuzzer->branchCnt = arch_perfCountBranches();
goto out;
}
if (read(perfFd, &count, sizeof(count)) != sizeof(count)) {
LOGMSG_P(l_ERROR, "read(perfFd='%d') failed", perfFd);
goto out;
}
fuzzer->branchCnt = count;
out:
LOGMSG(l_INFO,
"File size (New/Best): %zu/%zu, Perf events seen: Best: %" PRIu64 " / New: %" PRIu64,
fuzzer->dynamicFileSz, hfuzz->dynamicFileBestSz, hfuzz->branchBestCnt, count);
if (perfMmap != NULL) {
munmap(perfMmap, _HF_PERF_MMAP_TOT_SZ);
}
close(perfFd);
return;
}