/* * * honggfuzz - architecture dependent code (LINUX/PERF) * ----------------------------------------- * * Author: Robert Swiecki * * Copyright 2010-2018 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 "perf.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "libhfcommon/common.h" #include "libhfcommon/files.h" #include "libhfcommon/log.h" #include "libhfcommon/util.h" #include "pt.h" #define _HF_PERF_MAP_SZ (1024 * 512) #define _HF_PERF_AUX_SZ (1024 * 1024) /* PERF_TYPE for Intel_PT/BTS -1 if none */ static int32_t perfIntelPtPerfType = -1; static int32_t perfIntelBtsPerfType = -1; #if defined(PERF_ATTR_SIZE_VER5) __attribute__((hot)) static inline void arch_perfBtsCount(run_t* run) { struct perf_event_mmap_page* pem = (struct perf_event_mmap_page*)run->arch_linux.perfMmapBuf; struct bts_branch { uint64_t from; uint64_t to; uint64_t misc; }; uint64_t aux_head = ATOMIC_GET(pem->aux_head); struct bts_branch* br = (struct bts_branch*)run->arch_linux.perfMmapAux; for (; br < ((struct bts_branch*)(run->arch_linux.perfMmapAux + aux_head)); br++) { /* * Kernel sometimes reports branches from the kernel (iret), we are not interested in that * as it makes the whole concept of unique branch counting less predictable */ if (!run->global->arch_linux.kernelOnly && (__builtin_expect(br->from > 0xFFFFFFFF00000000, false) || __builtin_expect(br->to > 0xFFFFFFFF00000000, false))) { LOG_D("Adding branch %#018" PRIx64 " - %#018" PRIx64, br->from, br->to); continue; } if (br->from >= run->global->arch_linux.dynamicCutOffAddr || br->to >= run->global->arch_linux.dynamicCutOffAddr) { continue; } register size_t pos = ((br->from << 12) ^ (br->to & 0xFFF)); pos &= _HF_PERF_BITMAP_BITSZ_MASK; register bool prev = ATOMIC_BITMAP_SET(run->global->feedback.covFeedbackMap->bbMapPc, pos); if (!prev) { run->hwCnts.newBBCnt++; } } } #endif /* defined(PERF_ATTR_SIZE_VER5) */ static inline void arch_perfMmapParse(run_t* run HF_ATTR_UNUSED) { #if defined(PERF_ATTR_SIZE_VER5) struct perf_event_mmap_page* pem = (struct perf_event_mmap_page*)run->arch_linux.perfMmapBuf; if (pem->aux_head == pem->aux_tail) { return; } if (pem->aux_head < pem->aux_tail) { LOG_F("The PERF AUX data has been overwritten. The AUX buffer is too small"); } if (run->global->feedback.dynFileMethod & _HF_DYNFILE_BTS_EDGE) { arch_perfBtsCount(run); } if (run->global->feedback.dynFileMethod & _HF_DYNFILE_IPT_BLOCK) { arch_ptAnalyze(run); } #endif /* defined(PERF_ATTR_SIZE_VER5) */ } 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, (uintptr_t)pid, (uintptr_t)cpu, (uintptr_t)group_fd, (uintptr_t)flags); } static bool arch_perfCreate(run_t* run, pid_t pid, dynFileMethod_t method, int* perfFd) { LOG_D("Enabling PERF for pid=%d method=%x", pid, method); if (*perfFd != -1) { LOG_F("The PERF FD is already initialized, possibly conflicting perf types enabled"); } if ((method & _HF_DYNFILE_BTS_EDGE) && perfIntelBtsPerfType == -1) { LOG_F("Intel BTS events (new type) are not supported on this platform"); } if ((method & _HF_DYNFILE_IPT_BLOCK) && perfIntelPtPerfType == -1) { LOG_F("Intel PT events are not supported on this platform"); } struct perf_event_attr pe; memset(&pe, 0, sizeof(struct perf_event_attr)); pe.size = sizeof(struct perf_event_attr); if (run->global->arch_linux.kernelOnly) { pe.exclude_user = 1; } else { pe.exclude_kernel = 1; } pe.disabled = 1; if (!run->global->exe.persistent) { pe.enable_on_exec = 1; } pe.exclude_hv = 1; pe.type = PERF_TYPE_HARDWARE; switch (method) { case _HF_DYNFILE_INSTR_COUNT: LOG_D("Using: PERF_COUNT_HW_INSTRUCTIONS for pid=%d", (int)pid); pe.config = PERF_COUNT_HW_INSTRUCTIONS; pe.inherit = 1; break; case _HF_DYNFILE_BRANCH_COUNT: LOG_D("Using: PERF_COUNT_HW_BRANCH_INSTRUCTIONS for pid=%d", (int)pid); pe.config = PERF_COUNT_HW_BRANCH_INSTRUCTIONS; pe.inherit = 1; break; case _HF_DYNFILE_BTS_EDGE: LOG_D("Using: (Intel BTS) type=%" PRIu32 " for pid=%d", perfIntelBtsPerfType, (int)pid); pe.type = perfIntelBtsPerfType; break; case _HF_DYNFILE_IPT_BLOCK: LOG_D("Using: (Intel PT) type=%" PRIu32 " for pid=%d", perfIntelPtPerfType, (int)pid); pe.type = perfIntelPtPerfType; pe.config = RTIT_CTL_DISRETC; break; default: LOG_E("Unknown perf mode: '%d' for pid=%d", method, (int)pid); return false; break; } #if !defined(PERF_FLAG_FD_CLOEXEC) #define PERF_FLAG_FD_CLOEXEC 0 #endif *perfFd = perf_event_open(&pe, pid, -1, -1, PERF_FLAG_FD_CLOEXEC); if (*perfFd == -1) { PLOG_E("perf_event_open() failed"); return false; } if (method != _HF_DYNFILE_BTS_EDGE && method != _HF_DYNFILE_IPT_BLOCK) { return true; } #if defined(PERF_ATTR_SIZE_VER5) if ((run->arch_linux.perfMmapBuf = mmap(NULL, _HF_PERF_MAP_SZ + getpagesize(), PROT_READ | PROT_WRITE, MAP_SHARED, *perfFd, 0)) == MAP_FAILED) { run->arch_linux.perfMmapBuf = NULL; PLOG_W("mmap(mmapBuf) failed, sz=%zu, try increasing the kernel.perf_event_mlock_kb sysctl " "(up to even 300000000)", (size_t)_HF_PERF_MAP_SZ + getpagesize()); close(*perfFd); *perfFd = -1; return false; } struct perf_event_mmap_page* pem = (struct perf_event_mmap_page*)run->arch_linux.perfMmapBuf; pem->aux_offset = pem->data_offset + pem->data_size; pem->aux_size = _HF_PERF_AUX_SZ; if ((run->arch_linux.perfMmapAux = mmap( NULL, pem->aux_size, PROT_READ, MAP_SHARED, *perfFd, pem->aux_offset)) == MAP_FAILED) { munmap(run->arch_linux.perfMmapBuf, _HF_PERF_MAP_SZ + getpagesize()); run->arch_linux.perfMmapBuf = NULL; run->arch_linux.perfMmapAux = NULL; PLOG_W( "mmap(mmapAuxBuf) failed, try increasing the kernel.perf_event_mlock_kb sysctl (up to " "even 300000000)"); close(*perfFd); *perfFd = -1; return false; } #else /* defined(PERF_ATTR_SIZE_VER5) */ LOG_F("Your includes are too old to support Intel PT/BTS"); #endif /* defined(PERF_ATTR_SIZE_VER5) */ return true; } bool arch_perfOpen(run_t* run) { if (run->global->feedback.dynFileMethod == _HF_DYNFILE_NONE) { return true; } if (run->global->feedback.dynFileMethod & _HF_DYNFILE_INSTR_COUNT) { if (!arch_perfCreate(run, run->pid, _HF_DYNFILE_INSTR_COUNT, &run->arch_linux.cpuInstrFd)) { LOG_E("Cannot set up perf for pid=%d (_HF_DYNFILE_INSTR_COUNT)", (int)run->pid); goto out; } } if (run->global->feedback.dynFileMethod & _HF_DYNFILE_BRANCH_COUNT) { if (!arch_perfCreate( run, run->pid, _HF_DYNFILE_BRANCH_COUNT, &run->arch_linux.cpuBranchFd)) { LOG_E("Cannot set up perf for pid=%d (_HF_DYNFILE_BRANCH_COUNT)", (int)run->pid); goto out; } } if (run->global->feedback.dynFileMethod & _HF_DYNFILE_BTS_EDGE) { if (!arch_perfCreate(run, run->pid, _HF_DYNFILE_BTS_EDGE, &run->arch_linux.cpuIptBtsFd)) { LOG_E("Cannot set up perf for pid=%d (_HF_DYNFILE_BTS_EDGE)", (int)run->pid); goto out; } } if (run->global->feedback.dynFileMethod & _HF_DYNFILE_IPT_BLOCK) { if (!arch_perfCreate(run, run->pid, _HF_DYNFILE_IPT_BLOCK, &run->arch_linux.cpuIptBtsFd)) { LOG_E("Cannot set up perf for pid=%d (_HF_DYNFILE_IPT_BLOCK)", (int)run->pid); goto out; } } return true; out: close(run->arch_linux.cpuInstrFd); run->arch_linux.cpuInstrFd = -1; close(run->arch_linux.cpuBranchFd); run->arch_linux.cpuBranchFd = -1; close(run->arch_linux.cpuIptBtsFd); run->arch_linux.cpuIptBtsFd = -1; return false; } void arch_perfClose(run_t* run) { if (run->global->feedback.dynFileMethod == _HF_DYNFILE_NONE) { return; } if (run->arch_linux.perfMmapAux != NULL) { munmap(run->arch_linux.perfMmapAux, _HF_PERF_AUX_SZ); run->arch_linux.perfMmapAux = NULL; } if (run->arch_linux.perfMmapBuf != NULL) { munmap(run->arch_linux.perfMmapBuf, _HF_PERF_MAP_SZ + getpagesize()); run->arch_linux.perfMmapBuf = NULL; } if (run->global->feedback.dynFileMethod & _HF_DYNFILE_INSTR_COUNT) { close(run->arch_linux.cpuInstrFd); run->arch_linux.cpuInstrFd = -1; } if (run->global->feedback.dynFileMethod & _HF_DYNFILE_BRANCH_COUNT) { close(run->arch_linux.cpuBranchFd); run->arch_linux.cpuBranchFd = -1; } if (run->global->feedback.dynFileMethod & _HF_DYNFILE_BTS_EDGE) { close(run->arch_linux.cpuIptBtsFd); run->arch_linux.cpuIptBtsFd = -1; } if (run->global->feedback.dynFileMethod & _HF_DYNFILE_IPT_BLOCK) { close(run->arch_linux.cpuIptBtsFd); run->arch_linux.cpuIptBtsFd = -1; } } bool arch_perfEnable(run_t* run) { if (run->global->feedback.dynFileMethod == _HF_DYNFILE_NONE) { return true; } /* It's enabled on exec in such scenario */ if (!run->global->exe.persistent) { return true; } if (run->global->feedback.dynFileMethod & _HF_DYNFILE_INSTR_COUNT) { ioctl(run->arch_linux.cpuInstrFd, PERF_EVENT_IOC_ENABLE, 0); } if (run->global->feedback.dynFileMethod & _HF_DYNFILE_BRANCH_COUNT) { ioctl(run->arch_linux.cpuBranchFd, PERF_EVENT_IOC_ENABLE, 0); } if (run->global->feedback.dynFileMethod & _HF_DYNFILE_BTS_EDGE) { ioctl(run->arch_linux.cpuIptBtsFd, PERF_EVENT_IOC_ENABLE, 0); } if (run->global->feedback.dynFileMethod & _HF_DYNFILE_IPT_BLOCK) { ioctl(run->arch_linux.cpuIptBtsFd, PERF_EVENT_IOC_ENABLE, 0); } return true; } static void arch_perfMmapReset(run_t* run) { /* smp_mb() required as per /usr/include/linux/perf_event.h */ wmb(); struct perf_event_mmap_page* pem = (struct perf_event_mmap_page*)run->arch_linux.perfMmapBuf; ATOMIC_SET(pem->data_head, 0); ATOMIC_SET(pem->data_tail, 0); #if defined(PERF_ATTR_SIZE_VER5) ATOMIC_SET(pem->aux_head, 0); ATOMIC_SET(pem->aux_tail, 0); #endif /* defined(PERF_ATTR_SIZE_VER5) */ } void arch_perfAnalyze(run_t* run) { if (run->global->feedback.dynFileMethod == _HF_DYNFILE_NONE) { return; } uint64_t instrCount = 0; if ((run->global->feedback.dynFileMethod & _HF_DYNFILE_INSTR_COUNT) && run->arch_linux.cpuInstrFd != -1) { ioctl(run->arch_linux.cpuInstrFd, PERF_EVENT_IOC_DISABLE, 0); if (files_readFromFd(run->arch_linux.cpuInstrFd, (uint8_t*)&instrCount, sizeof(instrCount)) != sizeof(instrCount)) { PLOG_E("read(perfFd='%d') failed", run->arch_linux.cpuInstrFd); } ioctl(run->arch_linux.cpuInstrFd, PERF_EVENT_IOC_RESET, 0); } uint64_t branchCount = 0; if ((run->global->feedback.dynFileMethod & _HF_DYNFILE_BRANCH_COUNT) && run->arch_linux.cpuBranchFd != -1) { ioctl(run->arch_linux.cpuBranchFd, PERF_EVENT_IOC_DISABLE, 0); if (files_readFromFd(run->arch_linux.cpuBranchFd, (uint8_t*)&branchCount, sizeof(branchCount)) != sizeof(branchCount)) { PLOG_E("read(perfFd='%d') failed", run->arch_linux.cpuBranchFd); } ioctl(run->arch_linux.cpuBranchFd, PERF_EVENT_IOC_RESET, 0); } if ((run->global->feedback.dynFileMethod & _HF_DYNFILE_BTS_EDGE) && run->arch_linux.cpuIptBtsFd != -1) { ioctl(run->arch_linux.cpuIptBtsFd, PERF_EVENT_IOC_DISABLE, 0); arch_perfMmapParse(run); arch_perfMmapReset(run); ioctl(run->arch_linux.cpuIptBtsFd, PERF_EVENT_IOC_RESET, 0); } if ((run->global->feedback.dynFileMethod & _HF_DYNFILE_IPT_BLOCK) && run->arch_linux.cpuIptBtsFd != -1) { ioctl(run->arch_linux.cpuIptBtsFd, PERF_EVENT_IOC_DISABLE, 0); arch_perfMmapParse(run); arch_perfMmapReset(run); ioctl(run->arch_linux.cpuIptBtsFd, PERF_EVENT_IOC_RESET, 0); } run->hwCnts.cpuInstrCnt = instrCount; run->hwCnts.cpuBranchCnt = branchCount; } bool arch_perfInit(honggfuzz_t* hfuzz HF_ATTR_UNUSED) { static char const intel_pt_path[] = "/sys/bus/event_source/devices/intel_pt/type"; static char const intel_bts_path[] = "/sys/bus/event_source/devices/intel_bts/type"; if (files_exists(intel_pt_path)) { uint8_t buf[256]; ssize_t sz = files_readFileToBufMax(intel_pt_path, buf, sizeof(buf) - 1); if (sz > 0) { buf[sz] = '\0'; perfIntelPtPerfType = (int32_t)strtoul((char*)buf, NULL, 10); LOG_D("perfIntelPtPerfType = %" PRIu32, perfIntelPtPerfType); } } if (files_exists(intel_bts_path)) { uint8_t buf[256]; ssize_t sz = files_readFileToBufMax(intel_bts_path, buf, sizeof(buf) - 1); if (sz > 0) { buf[sz] = '\0'; perfIntelBtsPerfType = (int32_t)strtoul((char*)buf, NULL, 10); LOG_D("perfIntelBtsPerfType = %" PRIu32, perfIntelBtsPerfType); } } perf_ptInit(); return true; }