31 changes: 21 additions & 10 deletions compiler-rt/lib/xray/xray_buffer_queue.cc
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,21 @@

using namespace __xray;

BufferQueue::BufferQueue(std::size_t B, std::size_t N)
BufferQueue::BufferQueue(std::size_t B, std::size_t N, bool &Success)
: BufferSize(B), Buffers(N), Mutex(), OwnedBuffers(), Finalizing(false) {
for (auto &Buf : Buffers) {
for (auto &T : Buffers) {
void *Tmp = malloc(BufferSize);
if (Tmp == nullptr) {
Success = false;
return;
}

auto &Buf = std::get<0>(T);
Buf.Buffer = Tmp;
Buf.Size = B;
if (Tmp != 0)
OwnedBuffers.insert(Tmp);
OwnedBuffers.emplace(Tmp);
}
Success = true;
}

std::error_code BufferQueue::getBuffer(Buffer &Buf) {
Expand All @@ -35,7 +41,11 @@ std::error_code BufferQueue::getBuffer(Buffer &Buf) {
std::lock_guard<std::mutex> Guard(Mutex);
if (Buffers.empty())
return std::make_error_code(std::errc::not_enough_memory);
Buf = Buffers.front();
auto &T = Buffers.front();
auto &B = std::get<0>(T);
Buf = B;
B.Buffer = nullptr;
B.Size = 0;
Buffers.pop_front();
return {};
}
Expand All @@ -44,9 +54,11 @@ std::error_code BufferQueue::releaseBuffer(Buffer &Buf) {
if (OwnedBuffers.count(Buf.Buffer) == 0)
return std::make_error_code(std::errc::argument_out_of_domain);
std::lock_guard<std::mutex> Guard(Mutex);
Buffers.push_back(Buf);

// Now that the buffer has been released, we mark it as "used".
Buffers.emplace(Buffers.end(), Buf, true /* used */);
Buf.Buffer = nullptr;
Buf.Size = BufferSize;
Buf.Size = 0;
return {};
}

Expand All @@ -57,9 +69,8 @@ std::error_code BufferQueue::finalize() {
}

BufferQueue::~BufferQueue() {
for (auto &Buf : Buffers) {
for (auto &T : Buffers) {
auto &Buf = std::get<0>(T);
free(Buf.Buffer);
Buf.Buffer = nullptr;
Buf.Size = 0;
}
}
36 changes: 26 additions & 10 deletions compiler-rt/lib/xray/xray_buffer_queue.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include <mutex>
#include <system_error>
#include <unordered_set>
#include <utility>

namespace __xray {

Expand All @@ -38,14 +39,18 @@ class BufferQueue {

private:
std::size_t BufferSize;
std::deque<Buffer> Buffers;

// We use a bool to indicate whether the Buffer has been used in this
// freelist implementation.
std::deque<std::tuple<Buffer, bool>> Buffers;
std::mutex Mutex;
std::unordered_set<void *> OwnedBuffers;
std::atomic<bool> Finalizing;

public:
/// Initialise a queue of size |N| with buffers of size |B|.
BufferQueue(std::size_t B, std::size_t N);
/// Initialise a queue of size |N| with buffers of size |B|. We report success
/// through |Success|.
BufferQueue(std::size_t B, std::size_t N, bool &Success);

/// Updates |Buf| to contain the pointer to an appropriate buffer. Returns an
/// error in case there are no available buffers to return when we will run
Expand All @@ -68,15 +73,26 @@ class BufferQueue {

bool finalizing() const { return Finalizing.load(std::memory_order_acquire); }

// Sets the state of the BufferQueue to finalizing, which ensures that:
//
// - All subsequent attempts to retrieve a Buffer will fail.
// - All releaseBuffer operations will not fail.
//
// After a call to finalize succeeds, all subsequent calls to finalize will
// fail with std::errc::state_not_recoverable.
/// Sets the state of the BufferQueue to finalizing, which ensures that:
///
/// - All subsequent attempts to retrieve a Buffer will fail.
/// - All releaseBuffer operations will not fail.
///
/// After a call to finalize succeeds, all subsequent calls to finalize will
/// fail with std::errc::state_not_recoverable.
std::error_code finalize();

/// Applies the provided function F to each Buffer in the queue, only if the
/// Buffer is marked 'used' (i.e. has been the result of getBuffer(...) and a
/// releaseBuffer(...) operation.
template <class F> void apply(F Fn) {
std::lock_guard<std::mutex> G(Mutex);
for (const auto &T : Buffers) {
if (std::get<1>(T))
Fn(std::get<0>(T));
}
}

// Cleans up allocated buffers.
~BufferQueue();
};
Expand Down
542 changes: 542 additions & 0 deletions compiler-rt/lib/xray/xray_fdr_logging.cc

Large diffs are not rendered by default.

95 changes: 95 additions & 0 deletions compiler-rt/lib/xray/xray_fdr_logging.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
//===-- xray_fdr_logging.h ------------------------------------------------===//
//
// 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 function call tracing system.
//
//===----------------------------------------------------------------------===//
#ifndef XRAY_XRAY_FDR_LOGGING_H
#define XRAY_XRAY_FDR_LOGGING_H

#include "xray/xray_log_interface.h"

// FDR (Flight Data Recorder) Mode
// ===============================
//
// The XRay whitepaper describes a mode of operation for function call trace
// logging that involves writing small records into an in-memory circular
// buffer, that then gets logged to disk on demand. To do this efficiently and
// capture as much data as we can, we use smaller records compared to the
// default mode of always writing fixed-size records.

namespace __xray {

enum class RecordType : uint8_t {
Function, Metadata
};

// A MetadataRecord encodes the kind of record in its first byte, and have 15
// additional bytes in the end to hold free-form data.
struct alignas(16) MetadataRecord {
// A MetadataRecord must always have a type of 1.
RecordType Type : 1;

// Each kind of record is represented as a 7-bit value (even though we use an
// unsigned 8-bit enum class to do so).
enum class RecordKinds : uint8_t {
NewBuffer,
EndOfBuffer,
NewCPUId,
TSCWrap,
WalltimeMarker,
};
RecordKinds RecordKind : 7; // Use 7 bits to identify this record type.
char Data[15];
} __attribute__((packed));

static_assert(sizeof(MetadataRecord) == 16, "Wrong size for MetadataRecord.");

struct alignas(8) FunctionRecord {
// A FunctionRecord must always have a type of 0.
RecordType Type : 1;
enum class RecordKinds {
FunctionEnter = 0x00,
FunctionExit = 0x01,
FunctionTailExit = 0x02,
};
RecordKinds RecordKind : 3;

// We only use 28 bits of the function ID, so that we can use as few bytes as
// possible. This means we only support 2^28 (268,435,456) unique function ids
// in a single binary.
int FuncId : 28;

// We use another 4 bytes to hold the delta between the previous entry's TSC.
// In case we've found that the distance is greater than the allowable 32 bits
// (either because we are running in a different CPU and the TSC might be
// different then), we should use a MetadataRecord before this FunctionRecord
// that will contain the full TSC for that CPU, and keep this to 0.
uint32_t TSCDelta;
} __attribute__((packed));

static_assert(sizeof(FunctionRecord) == 8, "Wrong size for FunctionRecord.");

// Options used by the FDR implementation.
struct FDRLoggingOptions {
bool ReportErrors = false;
int Fd = -1;
};

// Flight Data Recorder mode implementation interfaces.
XRayLogInitStatus FDRLogging_init(std::size_t BufferSize, std::size_t BufferMax,
void *Options, size_t OptionsSize);
XRayLogInitStatus FDRLogging_finalize();
void FDRLogging_handleArg0(int32_t FuncId, XRayEntryType Entry);
XRayLogFlushStatus FDRLogging_flush();
XRayLogInitStatus FDRLogging_reset();

} // namespace __xray

#endif // XRAY_XRAY_FDR_LOGGING_H
2 changes: 2 additions & 0 deletions compiler-rt/lib/xray/xray_flags.inc
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,5 @@ XRAY_FLAG(bool, xray_naive_log, true,
"Whether to install the naive log implementation.")
XRAY_FLAG(const char *, xray_logfile_base, "xray-log.",
"Filename base for the xray logfile.")
XRAY_FLAG(bool, xray_fdr_log, false,
"Whether to install the flight data recorder logging implementation.")
66 changes: 8 additions & 58 deletions compiler-rt/lib/xray/xray_inmemory_log.cc
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@
//===----------------------------------------------------------------------===//

#include <cassert>
#include <cstdint>
#include <cstdio>
#include <fcntl.h>
#include <mutex>
#include <sys/stat.h>
Expand All @@ -39,6 +37,7 @@
#include "xray_defs.h"
#include "xray_flags.h"
#include "xray_interface_internal.h"
#include "xray_utils.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
Expand All @@ -53,25 +52,6 @@ namespace __xray {

std::mutex LogMutex;

static void retryingWriteAll(int Fd, char *Begin,
char *End) XRAY_NEVER_INSTRUMENT {
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.
Report("Failed to write; errno = %d\n", errno);
return;
}
TotalBytes -= Written;
if (TotalBytes == 0)
break;
Begin += Written;
}
}

class ThreadExitFlusher {
int Fd;
XRayRecord *Start;
Expand Down Expand Up @@ -102,57 +82,27 @@ class ThreadExitFlusher {

using namespace __xray;

void PrintToStdErr(const char *Buffer) XRAY_NEVER_INSTRUMENT {
fprintf(stderr, "%s", Buffer);
}

static int __xray_OpenLogFile() XRAY_NEVER_INSTRUMENT {
// FIXME: Figure out how to make this less stderr-dependent.
SetPrintfAndReportCallback(PrintToStdErr);
// Open a temporary file once for the log.
static char TmpFilename[256] = {};
static char TmpWildcardPattern[] = "XXXXXX";
auto Argv = GetArgv();
const char *Progname = Argv[0] == nullptr ? "(unknown)" : Argv[0];
const char *LastSlash = internal_strrchr(Progname, '/');

if (LastSlash != nullptr)
Progname = LastSlash + 1;

const int HalfLength = sizeof(TmpFilename) / 2 - sizeof(TmpWildcardPattern);
int NeededLength = internal_snprintf(TmpFilename, sizeof(TmpFilename),
"%.*s%.*s.%s",
HalfLength, flags()->xray_logfile_base,
HalfLength, Progname,
TmpWildcardPattern);
if (NeededLength > int(sizeof(TmpFilename))) {
Report("XRay log file name too long (%d): %s\n", NeededLength, TmpFilename);
return -1;
}
int Fd = mkstemp(TmpFilename);
if (Fd == -1) {
Report("XRay: Failed opening temporary file '%s'; not logging events.\n",
TmpFilename);
int F = getLogFD();
auto CPUFrequency = getCPUFrequency();
if (F == -1)
return -1;
}
if (Verbosity())
fprintf(stderr, "XRay: Log file in '%s'\n", TmpFilename);

// 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 = __xray::cycleFrequency();
Header.CycleFrequency =
CPUFrequency == -1 ? 0 : static_cast<uint64_t>(CPUFrequency);

// FIXME: Actually check whether we have 'constant_tsc' and 'nonstop_tsc'
// before setting the values in the header.
Header.ConstantTSC = 1;
Header.NonstopTSC = 1;
retryingWriteAll(Fd, reinterpret_cast<char *>(&Header),
retryingWriteAll(F, reinterpret_cast<char *>(&Header),
reinterpret_cast<char *>(&Header) + sizeof(Header));
return Fd;
return F;
}

void __xray_InMemoryRawLog(int32_t FuncId,
Expand Down
57 changes: 57 additions & 0 deletions compiler-rt/lib/xray/xray_log_interface.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
//===-- xray_log_interface.cc ---------------------------------------------===//
//
// 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 function call tracing system.
//
//===----------------------------------------------------------------------===//
#include "xray/xray_log_interface.h"

#include "xray/xray_interface.h"
#include "xray_defs.h"

#include <memory>
#include <mutex>

std::mutex XRayImplMutex;
std::unique_ptr<XRayLogImpl> GlobalXRayImpl;

void __xray_set_log_impl(XRayLogImpl Impl) XRAY_NEVER_INSTRUMENT {
if (Impl.log_init == nullptr || Impl.log_finalize == nullptr ||
Impl.handle_arg0 == nullptr || Impl.flush_log == nullptr) {
std::lock_guard<std::mutex> Guard(XRayImplMutex);
GlobalXRayImpl.reset();
return;
}

std::lock_guard<std::mutex> Guard(XRayImplMutex);
GlobalXRayImpl.reset(new XRayLogImpl);
*GlobalXRayImpl = Impl;
}

XRayLogInitStatus __xray_init(size_t BufferSize, size_t MaxBuffers, void *Args,
size_t ArgsSize) XRAY_NEVER_INSTRUMENT {
std::lock_guard<std::mutex> Guard(XRayImplMutex);
if (!GlobalXRayImpl)
return XRayLogInitStatus::XRAY_LOG_UNINITIALIZED;
return GlobalXRayImpl->log_init(BufferSize, MaxBuffers, Args, ArgsSize);
}

XRayLogInitStatus __xray_log_finalize() XRAY_NEVER_INSTRUMENT {
std::lock_guard<std::mutex> Guard(XRayImplMutex);
if (!GlobalXRayImpl)
return XRayLogInitStatus::XRAY_LOG_UNINITIALIZED;
return GlobalXRayImpl->log_finalize();
}

XRayLogFlushStatus __xray_log_flushLog() XRAY_NEVER_INSTRUMENT {
std::lock_guard<std::mutex> Guard(XRayImplMutex);
if (!GlobalXRayImpl)
return XRayLogFlushStatus::XRAY_LOG_NOT_FLUSHING;
return GlobalXRayImpl->flush_log();
}
165 changes: 165 additions & 0 deletions compiler-rt/lib/xray/xray_utils.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
//===-- xray_utils.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.
//
//===----------------------------------------------------------------------===//
#include "xray_utils.h"

#include "sanitizer_common/sanitizer_common.h"
#include "xray_defs.h"
#include "xray_flags.h"
#include <cstdio>
#include <fcntl.h>
#include <iterator>
#include <sys/types.h>
#include <tuple>
#include <unistd.h>
#include <utility>

#if defined(__x86_64__)
#include "xray_x86_64.h"
#elif defined(__arm__) || defined(__aarch64__)
#include "xray_emulate_tsc.h"
#else
#error "Unsupported CPU Architecture"
#endif /* CPU architecture */

namespace __xray {

void PrintToStdErr(const char *Buffer) XRAY_NEVER_INSTRUMENT {
fprintf(stderr, "%s", Buffer);
}

void retryingWriteAll(int Fd, char *Begin, char *End) XRAY_NEVER_INSTRUMENT {
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.
Report("Failed to write; errno = %d\n", errno);
return;
}
TotalBytes -= Written;
if (TotalBytes == 0)
break;
Begin += Written;
}
}

std::pair<ssize_t, bool> retryingReadSome(int Fd, char *Begin,
char *End) XRAY_NEVER_INSTRUMENT {
auto BytesToRead = std::distance(Begin, End);
ssize_t BytesRead;
ssize_t TotalBytesRead = 0;
while (BytesToRead && (BytesRead = read(Fd, Begin, BytesToRead))) {
if (BytesRead == -1) {
if (errno == EINTR)
continue;
Report("Read error; errno = %d\n", errno);
return std::make_pair(TotalBytesRead, false);
}

TotalBytesRead += BytesRead;
BytesToRead -= BytesRead;
Begin += BytesRead;
}
return std::make_pair(TotalBytesRead, true);
}

bool readValueFromFile(const char *Filename,
long long *Value) XRAY_NEVER_INSTRUMENT {
int Fd = open(Filename, O_RDONLY | O_CLOEXEC);
if (Fd == -1)
return false;
static constexpr size_t BufSize = 256;
char Line[BufSize] = {};
ssize_t BytesRead;
bool Success;
std::tie(BytesRead, Success) = retryingReadSome(Fd, Line, Line + BufSize);
if (!Success)
return false;
close(Fd);
char *End = nullptr;
long 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;
}

long long getCPUFrequency() XRAY_NEVER_INSTRUMENT {
// Get the cycle frequency from SysFS on Linux.
long long CPUFrequency = -1;
#if defined(__x86_64__)
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;
} else {
Report("Unable to determine CPU frequency for TSC accounting.\n");
}
#elif defined(__arm__) || defined(__aarch64__)
// There is no instruction like RDTSCP in user mode on ARM. ARM's CP15 does
// not have a constant frequency like TSC on x86(_64), it may go faster
// or slower depending on CPU turbo or power saving mode. Furthermore,
// to read from CP15 on ARM a kernel modification or a driver is needed.
// We can not require this from users of compiler-rt.
// So on ARM we use clock_gettime() which gives the result in nanoseconds.
// To get the measurements per second, we scale this by the number of
// nanoseconds per second, pretending that the TSC frequency is 1GHz and
// one TSC tick is 1 nanosecond.
CPUFrequency = NanosecondsPerSecond;
#else
#error "Unsupported CPU Architecture"
#endif /* CPU architecture */
return CPUFrequency;
}

int getLogFD() XRAY_NEVER_INSTRUMENT {
// FIXME: Figure out how to make this less stderr-dependent.
SetPrintfAndReportCallback(PrintToStdErr);
// Open a temporary file once for the log.
static char TmpFilename[256] = {};
static char TmpWildcardPattern[] = "XXXXXX";
auto Argv = GetArgv();
const char *Progname = Argv[0] == nullptr ? "(unknown)" : Argv[0];
const char *LastSlash = internal_strrchr(Progname, '/');

if (LastSlash != nullptr)
Progname = LastSlash + 1;

const int HalfLength = sizeof(TmpFilename) / 2 - sizeof(TmpWildcardPattern);
int NeededLength = internal_snprintf(
TmpFilename, sizeof(TmpFilename), "%.*s%.*s.%s", HalfLength,
flags()->xray_logfile_base, HalfLength, Progname, TmpWildcardPattern);
if (NeededLength > int(sizeof(TmpFilename))) {
Report("XRay log file name too long (%d): %s\n", NeededLength, TmpFilename);
return -1;
}
int Fd = mkstemp(TmpFilename);
if (Fd == -1) {
Report("XRay: Failed opening temporary file '%s'; not logging events.\n",
TmpFilename);
return -1;
}
if (Verbosity())
fprintf(stderr, "XRay: Log file in '%s'\n", TmpFilename);

return Fd;
}

} // namespace __xray
44 changes: 44 additions & 0 deletions compiler-rt/lib/xray/xray_utils.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//===-- xray_utils.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.
//
// Some shared utilities for the XRay runtime implementation.
//
//===----------------------------------------------------------------------===//
#ifndef XRAY_UTILS_H
#define XRAY_UTILS_H

#include <sys/types.h>
#include <utility>

namespace __xray {

// Default implementation of the reporting interface for sanitizer errors.
void PrintToStdErr(const char *Buffer);

// EINTR-safe write routine, provided a file descriptor and a character range.
void retryingWriteAll(int Fd, char *Begin, char *End);

// Reads a long long value from a provided file.
bool readValueFromFile(const char *Filename, long long *Value);

// EINTR-safe read routine, providing a file descriptor and a character range.
std::pair<ssize_t, bool> retryingReadSome(int Fd, char *Begin, char *End);

// EINTR-safe open routine, uses flag-provided values for initialising a log
// file.
int getLogFD();

// EINTR-safe read of CPU frquency for the current CPU.
long long getCPUFrequency();

} // namespace __xray

#endif // XRAY_UTILS_H
2 changes: 1 addition & 1 deletion compiler-rt/lib/xray/xray_x86_64.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,6 @@ ALWAYS_INLINE uint64_t readTSC(uint8_t &CPU) XRAY_NEVER_INSTRUMENT {
CPU = LongCPU;
return TSC;
}
}
} // namespace __xray

#endif // XRAY_X86_64_H
6 changes: 0 additions & 6 deletions compiler-rt/test/xray/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ set(XRAY_FDR_TEST_DEPS ${SANITIZER_COMMON_LIT_TEST_DEPS})
if(NOT COMPILER_RT_STANDALONE_BUILD AND COMPILER_RT_BUILD_XRAY AND
COMPILER_RT_HAS_XRAY)
list(APPEND XRAY_TEST_DEPS xray)
list(APPEND XRAY_FDR_TEST_DEPS xray-fdr)
endif()

set(XRAY_TEST_ARCH ${XRAY_SUPPORTED_ARCH})
Expand Down Expand Up @@ -41,8 +40,3 @@ 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")

add_lit_testsuite(check-xray-fdr "Running the XRay flight data recorder tests"
${XRAY_FDR_TESTSUITES}
DEPENDS ${XRAY_FDR_TEST_DEPS})
set_target_properties(check-xray-fdr PROPERTIES FOLDER "Compiler-RT Misc")
4 changes: 4 additions & 0 deletions compiler-rt/test/xray/Unit/lit.site.cfg.in
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,7 @@ config.name = 'XRay-Unit'

config.test_exec_root = "@COMPILER_RT_BINARY_DIR@/lib/xray/tests"
config.test_source_root = config.test_exec_root

# Do not patch the XRay unit tests pre-main, and also make the error logging
# verbose to get a more accurate error logging mechanism.
config.environment['XRAY_OPTIONS'] = 'patch_premain=false verbose=1'