Index: include/xray/xray_records.h =================================================================== --- /dev/null +++ include/xray/xray_records.h @@ -0,0 +1,82 @@ +//===-- xray_records.h ------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of XRay, a dynamic runtime instrumentation system. +// +// This header exposes some record types useful for the XRay in-memory logging +// implementation. +// +//===----------------------------------------------------------------------===// + +#ifndef XRAY_XRAY_RECORDS_H +#define XRAY_XRAY_RECORDS_H + +namespace __xray { + +enum FileTypes { + NAIVE_LOG = 0, +}; + +// This data structure is used to describe the contents of the file. We use this +// for versioning the supported XRay file formats. +struct alignas(32) XRayFileHeader { + uint16_t Version = 0; + + // The type of file we're writing out. See the FileTypes enum for more + // information. This allows different implementations of the XRay logging to + // have different files for different information being stored. + uint16_t Type = 0; + + // The frequency by which TSC increases per-second. + uint64_t CycleFrequency = 0; + + // What follows are a set of flags that indicate useful things for when + // reading the data in the file. + bool ConstantTSC = 0; + bool NonstopTSC = 0; + + char Padding[14] = {}; +} __attribute__((packed)); + +static_assert(sizeof(XRayFileHeader) == 32, "XRayFileHeader != 32 bytes"); + +enum RecordTypes { + NORMAL = 0, +}; + +struct alignas(32) XRayRecord { + // This is the type of the record being written. We use 16 bits to allow us to + // treat this as a discriminant, and so that the first 4 bytes get packed + // properly. See RecordTypes for more supported types. + uint16_t RecordType = 0; + + // The CPU where the thread is running. We assume number of CPUs <= 256. + uint8_t CPU = 0; + + // The type of the event. Usually either ENTER = 0 or EXIT = 1. + uint8_t Type = 0; + + // The function ID for the record. + int32_t FuncId = 0; + + // Get the full 8 bytes of the TSC when we get the log record. + uint64_t TSC = 0; + + // The thread ID for the currently running thread. + uint32_t TId = 0; + + // Use some bytes in the end of the record for buffers. + char Buffer[4] = {}; +} __attribute__((packed)); + +static_assert(sizeof(XRayRecord) == 32, "XRayRecord != 32 bytes"); + +} // namespace __xray + +#endif // XRAY_XRAY_RECORDS_H Index: lib/xray/CMakeLists.txt =================================================================== --- lib/xray/CMakeLists.txt +++ lib/xray/CMakeLists.txt @@ -4,10 +4,11 @@ xray_init.cc xray_interface.cc xray_flags.cc + xray_inmemory_log.cc ) set(x86_64_SOURCES - xray_trampoline_x86.S + xray_trampoline_x86_64.S ${XRAY_SOURCES}) include_directories(..) @@ -18,9 +19,9 @@ set(XRAY_COMMON_DEFINITIONS XRAY_HAS_EXCEPTIONS=1) add_compiler_rt_object_libraries(RTXray - ARCHS ${XRAY_SUPPORTED_ARCH} - SOURCES ${XRAY_SOURCES} CFLAGS ${XRAY_CFLAGS} - DEFS ${XRAY_COMMON_DEFINITIONS}) + ARCHS ${XRAY_SUPPORTED_ARCH} + SOURCES ${XRAY_SOURCES} CFLAGS ${XRAY_CFLAGS} + DEFS ${XRAY_COMMON_DEFINITIONS}) add_custom_target(xray) set(XRAY_COMMON_RUNTIME_OBJECT_LIBS Index: lib/xray/xray_flags.inc =================================================================== --- lib/xray/xray_flags.inc +++ lib/xray/xray_flags.inc @@ -16,3 +16,5 @@ XRAY_FLAG(bool, patch_premain, true, "Whether to patch instrumentation points before main.") +XRAY_FLAG(const char *, xray_logfile_base, "xray-log.", + "Filename base for the xray logfile.") Index: lib/xray/xray_inmemory_log.cc =================================================================== --- /dev/null +++ lib/xray/xray_inmemory_log.cc @@ -0,0 +1,195 @@ +//===-- xray_inmemory_log.cc ------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of XRay, a dynamic runtime instrumentation system. +// +// Implementation of a simple in-memory log of XRay events. This defines a +// logging function that's compatible with the XRay handler interface, and +// routines for exporting data to files. +// +//===----------------------------------------------------------------------===// + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sanitizer_common/sanitizer_libc.h" +#include "xray/xray_records.h" +#include "xray_flags.h" +#include "xray_interface_internal.h" + +// __xray_InMemoryRawLog will use a thread-local aligned buffer capped to a +// certain size (32kb by default) and use it as if it were a circular buffer for +// events. We store simple fixed-sized entries in the log for external analysis. + +extern "C" { +void __xray_InMemoryRawLog(int32_t FuncId, XRayEntryType Type); +} + +namespace __xray { + +std::mutex LogMutex; + +static constexpr size_t BuffLen = 1024; + +static void RetryingWrite(int Fd, char *Begin, char *End) { + if (Begin == End) + return; + auto TotalBytes = std::distance(Begin, End); + while (auto Written = write(Fd, Begin, TotalBytes)) { + if (Written <= 0) { + if (errno == EINTR) + continue; // Try again. + fprintf(stderr, "Failed to write; errno = %d", errno); + return; + } + + // FIXME: Figure out whether/how to assert properly. + assert(static_cast(Written) <= TotalBytes); + TotalBytes -= Written; + if (TotalBytes == 0) + break; + Begin += Written; + } +} + +class ThreadExitFlusher { +public: + explicit ThreadExitFlusher(int Fd, XRayRecord *Start, size_t &Offset) + : Fd(Fd), Start(Start), Offset(Offset) {} + + ~ThreadExitFlusher() noexcept { + std::lock_guard L(LogMutex); + RetryingWrite(Fd, reinterpret_cast(Start), + reinterpret_cast(Start + Offset)); + fsync(Fd); + } + +private: + int Fd; + XRayRecord *Start; + size_t &Offset; +}; + +} // namespace __xray + +namespace { + +bool ReadValueFromFile(const char *Filename, long *Value) { + int Fd = open(Filename, O_RDONLY); + if (Fd == -1) + return false; + char Line[256] = {}; + read(Fd, Line, 256); + close(Fd); + char *End = nullptr; + long Tmp = internal_simple_strtoll(Line, &End, 10); + bool Result = false; + if (Line[0] != '\0' && (*End == '\n' || *End == '\0')) { + *Value = Tmp; + Result = true; + } + return Result; +} + +} // namespace + +using namespace __xray; + +void PrintToStdErr(const char *Buffer) { fprintf(stderr, Buffer); } + +void __xray_InMemoryRawLog(int32_t FuncId, XRayEntryType Type) { + using Buffer = + std::aligned_storage::type; + thread_local static Buffer InMemoryBuffer[BuffLen] = {}; + thread_local static size_t Offset = 0; + static int Fd = [] { + // FIXME: Figure out how to make this less stderr-dependent. + SetPrintfAndReportCallback(PrintToStdErr); + // Open a temporary file once for the log. + static char TmpFilename[256] = {}; + auto E = internal_strncat(TmpFilename, flags()->xray_logfile_base, 246); + if ((E + 6) - TmpFilename > 255) { + Report("XRay log file base too long: %s", flags()->xray_logfile_base); + return -1; + } + internal_strncat(TmpFilename, "XXXXXX", 6); + int Fd = mkstemp(TmpFilename); + if (Fd == -1) { + Report("XRay: Failed opening temporary file '%s'; not logging events.", + TmpFilename); + return -1; + } + if (Verbosity()) + fprintf(stderr, "XRay: Log file in '%s'\n", TmpFilename); + + // Get the cycle frequency from SysFS on Linux. + long CPUFrequency = -1; + if (ReadValueFromFile("/sys/devices/system/cpu/cpu0/tsc_freq_khz", + &CPUFrequency)) { + CPUFrequency *= 1000; + } else if (ReadValueFromFile( + "/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq", + &CPUFrequency)) { + CPUFrequency *= 1000; + } + + // Since we're here, we get to write the header. We set it up so that the + // header will only be written once, at the start, and let the threads + // logging do writes which just append. + XRayFileHeader Header; + Header.Version = 1; + Header.Type = FileTypes::NAIVE_LOG; + Header.CycleFrequency = + CPUFrequency == -1 ? uint64_t{0} : static_cast(CPUFrequency); + + // FIXME: Actually check whether we have 'constant_tsc' and 'nonstop_tsc' + // before setting the values in the header. + Header.ConstantTSC = true; + Header.NonstopTSC = true; + RetryingWrite(Fd, reinterpret_cast(&Header), + reinterpret_cast(&Header) + sizeof(Header)); + return Fd; + }(); + if (Fd == -1) + return; + thread_local __xray::ThreadExitFlusher Flusher( + Fd, reinterpret_cast<__xray::XRayRecord *>(InMemoryBuffer), Offset); + thread_local pid_t TId = syscall(SYS_gettid); + + // First we get the useful data, and stuff it into the already aligned buffer + // through a pointer offset. + auto &R = reinterpret_cast<__xray::XRayRecord *>(InMemoryBuffer)[Offset]; + uint32_t CPU; + R.RecordType = RecordTypes::NORMAL; + R.TSC = __rdtscp(&CPU); + R.CPU = CPU; + R.TId = TId; + R.Type = Type; + R.FuncId = FuncId; + ++Offset; + if (Offset == __xray::BuffLen) { + std::lock_guard L(LogMutex); + auto RecordBuffer = reinterpret_cast<__xray::XRayRecord *>(InMemoryBuffer); + RetryingWrite(Fd, reinterpret_cast(RecordBuffer), + reinterpret_cast(RecordBuffer + Offset)); + Offset = 0; + } +} + +static auto Unused = [] { + __xray_set_handler(__xray_InMemoryRawLog); + return true; +}(); Index: lib/xray/xray_trampoline_x86.S =================================================================== --- /dev/null +++ lib/xray/xray_trampoline_x86.S @@ -1,93 +0,0 @@ -//===-- xray_trampoline_x86.s -----------------------------------*- ASM -*-===// -// -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. -// -//===----------------------------------------------------------------------===// -// -// This file is a part of XRay, a dynamic runtime instrumentation system. -// -// This implements the X86-specific assembler for the trampolines. -// -//===----------------------------------------------------------------------===// - - .text - .file "xray_trampoline_x86.S" - .globl __xray_FunctionEntry - .align 16, 0x90 - .type __xray_FunctionEntry,@function - -__xray_FunctionEntry: - .cfi_startproc - // Save caller provided registers before doing any actual work. - pushq %rbp - .cfi_def_cfa_offset 16 - subq $72, %rsp - movq %rdi, 64(%rsp) - movq %rax, 56(%rsp) - movq %rdx, 48(%rsp) - movq %rsi, 40(%rsp) - movq %rcx, 32(%rsp) - movq %r8, 24(%rsp) - movq %r9, 16(%rsp) - - // de-mangled, that's __xray::XRayPatchedFunction, and we're doing an acquire - // load (on x86 is a normal mov instruction). - movq _ZN6__xray19XRayPatchedFunctionE(%rip), %rax - testq %rax, %rax - je .Ltmp0 - - // assume that %r10d has the function id. - movl %r10d, %edi - xor %esi,%esi - callq *%rax -.Ltmp0: - // restore the registers - movq 64(%rsp), %rdi - movq 56(%rsp), %rax - movq 48(%rsp), %rdx - movq 40(%rsp), %rsi - movq 32(%rsp), %rcx - movq 24(%rsp), %r8 - movq 16(%rsp), %r9 - addq $72, %rsp - popq %rbp - retq -.Ltmp1: - .size __xray_FunctionEntry, .Ltmp1-__xray_FunctionEntry - .cfi_endproc - - .globl __xray_FunctionExit - .align 16, 0x90 - .type __xray_FunctionExit,@function -__xray_FunctionExit: - .cfi_startproc - // Save the important registers first. Since we're assuming that this - // function is only jumped into, we only preserve the registers for - // returning. - // FIXME: Figure out whether this is sufficient. - pushq %rbp - .cfi_def_cfa_offset 16 - subq $24, %rsp - .cfi_def_cfa_offset 32 - movq %rax, 16(%rsp) - movq %rdx, 8(%rsp) - movq _ZN6__xray19XRayPatchedFunctionE(%rip), %rax - testq %rax,%rax - je .Ltmp2 - - movl %r10d, %edi - movl $1, %esi - callq *%rax -.Ltmp2: - // Restore the important registers. - movq 16(%rsp), %rax - movq 8(%rsp), %rdx - addq $24, %rsp - popq %rbp - retq -.Ltmp3: - .size __xray_FunctionExit, .Ltmp3-__xray_FunctionExit - .cfi_endproc Index: test/CMakeLists.txt =================================================================== --- test/CMakeLists.txt +++ test/CMakeLists.txt @@ -76,6 +76,9 @@ if(COMPILER_RT_HAS_SCUDO) add_subdirectory(scudo) endif() + if(COMPILER_RT_HAS_XRAY) + add_subdirectory(xray) + endif() endif() if(COMPILER_RT_STANDALONE_BUILD) Index: test/lit.common.cfg =================================================================== --- test/lit.common.cfg +++ test/lit.common.cfg @@ -52,7 +52,8 @@ 'LIBCLANG_LOGGING', 'LIBCLANG_BGPRIO_INDEX', 'LIBCLANG_BGPRIO_EDIT', 'LIBCLANG_NOTHREADS', 'LIBCLANG_RESOURCE_USAGE', - 'LIBCLANG_CODE_COMPLETION_LOGGING'] + 'LIBCLANG_CODE_COMPLETION_LOGGING', + 'XRAY_OPTIONS'] # Clang/Win32 may refer to %INCLUDE%. vsvarsall.bat sets it. if platform.system() != 'Windows': possibly_dangerous_env_vars.append('INCLUDE') Index: test/xray/CMakeLists.txt =================================================================== --- /dev/null +++ test/xray/CMakeLists.txt @@ -0,0 +1,37 @@ +set(XRAY_LIT_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) + +set(XRAY_TESTSUITES) + +set(XRAY_TEST_DEPS ${SANITIZER_COMMON_LIT_TEST_DEPS}) +if(NOT COMPILER_RT_STANDALONE_BUILD) + list(APPEND XRAY_TEST_DEPS xray) +endif() + +set(XRAY_TEST_ARCH ${XRAY_SUPPORTED_ARCH}) +foreach(arch ${XRAY_TEST_ARCH}) + set(XRAY_TEST_TARGET_ARCH ${arch}) + string(TOLOWER "-${arch}-${OS_NAME}" XRAY_TEST_CONFIG_SUFFIX) + + if(ANDROID OR ${arch} MATCHES "arm|aarch64") + # This is only true if we are cross-compiling. + # Build all tests with host compiler and use host tools. + set(XRAY_TEST_TARGET_CC ${COMPILER_RT_TEST_COMPILER}) + set(XRAY_TEST_TARGET_CFLAGS ${COMPILER_RT_TEST_COMPILER_CFLAGS}) + else() + get_target_flags_for_arch(${arch} XRAY_TEST_TARGET_CFLAGS) + string(REPLACE ";" " " XRAY_TEST_TARGET_CFLAGS "${XRAY_TEST_TARGET_CFLAGS}") + endif() + + string(TOUPPER ${arch} ARCH_UPPER_CASE) + set(CONFIG_NAME ${ARCH_UPPER_CASE}${OS_NAME}Config) + + configure_lit_site_cfg( + ${CMAKE_CURRENT_SOURCE_DIR}/lit.site.cfg.in + ${CMAKE_CURRENT_BINARY_DIR}/${CONFIG_NAME}/lit.site.cfg) + list(APPEND XRAY_TESTSUITES ${CMAKE_CURRENT_BINARY_DIR}/${CONFIG_NAME}) +endforeach() + +add_lit_testsuite(check-xray "Running the XRay tests" + ${XRAY_TESTSUITES} + DEPENDS ${XRAY_TEST_DEPS}) +set_target_properties(check-xray PROPERTIES FOLDER "Compiler-RT Misc") Index: test/xray/TestCases/Linux/fixedsize-logging.cc =================================================================== --- /dev/null +++ test/xray/TestCases/Linux/fixedsize-logging.cc @@ -0,0 +1,20 @@ +// Check to make sure that we have a log file with a fixed-size. + +// RUN: %clangxx_xray -std=c++11 %s -o %t +// RUN: XRAY_OPTIONS="verbosity=1 xray_logfile_base=fixedsize-logging-" %run %t 2>&1 | FileCheck %s +// +// After all that, clean up the output xray log. +// +// RUN: rm fixedsize-logging-* + +#include + +[[clang::xray_always_instrument]] void foo() { + printf("foo() is always instrumented!"); +} + +int main() { + // CHECK: XRay: Log file in 'fixedsize-logging-{{.*}}' + foo(); + // CHECK: foo() is always instrumented! +} Index: test/xray/lit.cfg =================================================================== --- /dev/null +++ test/xray/lit.cfg @@ -0,0 +1,34 @@ +# -*- Python -*- + +import os + +# Setup config name. +config.name = 'XRay' + config.name_suffix + +# Setup source root. +config.test_source_root = os.path.dirname(__file__) + +# Setup default compiler flags use with -fxray-instrument option. +clang_xray_cflags = (['-fxray-instrument', config.target_cflags]) +clang_xray_cxxflags = config.cxx_mode_flags + clang_xray_cflags + + +def build_invocation(compile_flags): + return ' ' + ' '.join([config.clang] + compile_flags) + ' ' + +# Setup substitutions. +config.substitutions.append( + ('%clang ', build_invocation([config.target_cflags]))) +config.substitutions.append( + ('%clangxx ', + build_invocation(config.cxx_mode_flags + [config.target_cflags]))) +config.substitutions.append( + ('%clang_xray ', build_invocation(clang_xray_cflags))) +config.substitutions.append( + ('%clangxx_xray', build_invocation(clang_xray_cxxflags))) + +# Default test suffixes. +config.suffixes = ['.c', '.cc', '.cpp'] + +if config.host_os not in ['Linux'] or config.host_arch.find('64') == -1: + config.unsupported = True Index: test/xray/lit.site.cfg.in =================================================================== --- /dev/null +++ test/xray/lit.site.cfg.in @@ -0,0 +1,13 @@ +@LIT_SITE_CFG_IN_HEADER@ + +# Tool-specific config options. +config.name_suffix = "@XRAY_TEST_CONFIG_SUFFIX@" +config.xray_lit_source_dir = "@XRAY_LIT_SOURCE_DIR@" +config.target_cflags = "@XRAY_TEST_TARGET_CFLAGS@" +config.target_arch = "@XRAY_TEST_TARGET_ARCH@" + +# Load common config for all compiler-rt lit tests +lit_config.load_config(config, "@COMPILER_RT_BINARY_DIR@/test/lit.common.configured") + +# Load tool-specific config that would do the real work. +lit_config.load_config(config, "@XRAY_LIT_SOURCE_DIR@/lit.cfg")