Path: blob/main/contrib/llvm-project/compiler-rt/lib/xray/xray_basic_logging.cpp
35265 views
//===-- xray_basic_logging.cpp ----------------------------------*- C++ -*-===//1//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//7//8// This file is a part of XRay, a dynamic runtime instrumentation system.9//10// Implementation of a simple in-memory log of XRay events. This defines a11// logging function that's compatible with the XRay handler interface, and12// routines for exporting data to files.13//14//===----------------------------------------------------------------------===//1516#include <errno.h>17#include <fcntl.h>18#include <pthread.h>19#include <sys/stat.h>20#if SANITIZER_FREEBSD || SANITIZER_NETBSD || SANITIZER_APPLE21#include <sys/syscall.h>22#endif23#include <sys/types.h>24#include <time.h>25#include <unistd.h>2627#include "sanitizer_common/sanitizer_allocator_internal.h"28#include "sanitizer_common/sanitizer_libc.h"29#include "xray/xray_records.h"30#include "xray_recursion_guard.h"31#include "xray_basic_flags.h"32#include "xray_basic_logging.h"33#include "xray_defs.h"34#include "xray_flags.h"35#include "xray_interface_internal.h"36#include "xray_tsc.h"37#include "xray_utils.h"3839namespace __xray {4041static SpinMutex LogMutex;4243namespace {44// We use elements of this type to record the entry TSC of every function ID we45// see as we're tracing a particular thread's execution.46struct alignas(16) StackEntry {47int32_t FuncId;48uint16_t Type;49uint8_t CPU;50uint8_t Padding;51uint64_t TSC;52};5354static_assert(sizeof(StackEntry) == 16, "Wrong size for StackEntry");5556struct XRAY_TLS_ALIGNAS(64) ThreadLocalData {57void *InMemoryBuffer = nullptr;58size_t BufferSize = 0;59size_t BufferOffset = 0;60void *ShadowStack = nullptr;61size_t StackSize = 0;62size_t StackEntries = 0;63__xray::LogWriter *LogWriter = nullptr;64};6566struct BasicLoggingOptions {67int DurationFilterMicros = 0;68size_t MaxStackDepth = 0;69size_t ThreadBufferSize = 0;70};71} // namespace7273static pthread_key_t PThreadKey;7475static atomic_uint8_t BasicInitialized{0};7677struct BasicLoggingOptions GlobalOptions;7879thread_local atomic_uint8_t Guard{0};8081static atomic_uint8_t UseRealTSC{0};82static atomic_uint64_t ThresholdTicks{0};83static atomic_uint64_t TicksPerSec{0};84static atomic_uint64_t CycleFrequency{NanosecondsPerSecond};8586static LogWriter *getLog() XRAY_NEVER_INSTRUMENT {87LogWriter* LW = LogWriter::Open();88if (LW == nullptr)89return LW;9091static pthread_once_t DetectOnce = PTHREAD_ONCE_INIT;92pthread_once(&DetectOnce, +[] {93if (atomic_load(&UseRealTSC, memory_order_acquire))94atomic_store(&CycleFrequency, getTSCFrequency(), memory_order_release);95});9697// Since we're here, we get to write the header. We set it up so that the98// header will only be written once, at the start, and let the threads99// logging do writes which just append.100XRayFileHeader Header;101// Version 2 includes tail exit records.102// Version 3 includes pid inside records.103Header.Version = 3;104Header.Type = FileTypes::NAIVE_LOG;105Header.CycleFrequency = atomic_load(&CycleFrequency, memory_order_acquire);106107// FIXME: Actually check whether we have 'constant_tsc' and 'nonstop_tsc'108// before setting the values in the header.109Header.ConstantTSC = 1;110Header.NonstopTSC = 1;111LW->WriteAll(reinterpret_cast<char *>(&Header),112reinterpret_cast<char *>(&Header) + sizeof(Header));113return LW;114}115116static LogWriter *getGlobalLog() XRAY_NEVER_INSTRUMENT {117static pthread_once_t OnceInit = PTHREAD_ONCE_INIT;118static LogWriter *LW = nullptr;119pthread_once(&OnceInit, +[] { LW = getLog(); });120return LW;121}122123static ThreadLocalData &getThreadLocalData() XRAY_NEVER_INSTRUMENT {124thread_local ThreadLocalData TLD;125thread_local bool UNUSED TOnce = [] {126if (GlobalOptions.ThreadBufferSize == 0) {127if (Verbosity())128Report("Not initializing TLD since ThreadBufferSize == 0.\n");129return false;130}131pthread_setspecific(PThreadKey, &TLD);132TLD.LogWriter = getGlobalLog();133TLD.InMemoryBuffer = reinterpret_cast<XRayRecord *>(134InternalAlloc(sizeof(XRayRecord) * GlobalOptions.ThreadBufferSize,135nullptr, alignof(XRayRecord)));136TLD.BufferSize = GlobalOptions.ThreadBufferSize;137TLD.BufferOffset = 0;138if (GlobalOptions.MaxStackDepth == 0) {139if (Verbosity())140Report("Not initializing the ShadowStack since MaxStackDepth == 0.\n");141TLD.StackSize = 0;142TLD.StackEntries = 0;143TLD.ShadowStack = nullptr;144return false;145}146TLD.ShadowStack = reinterpret_cast<StackEntry *>(147InternalAlloc(sizeof(StackEntry) * GlobalOptions.MaxStackDepth, nullptr,148alignof(StackEntry)));149TLD.StackSize = GlobalOptions.MaxStackDepth;150TLD.StackEntries = 0;151return false;152}();153return TLD;154}155156template <class RDTSC>157void InMemoryRawLog(int32_t FuncId, XRayEntryType Type,158RDTSC ReadTSC) XRAY_NEVER_INSTRUMENT {159auto &TLD = getThreadLocalData();160LogWriter *LW = getGlobalLog();161if (LW == nullptr)162return;163164// Use a simple recursion guard, to handle cases where we're already logging165// and for one reason or another, this function gets called again in the same166// thread.167RecursionGuard G(Guard);168if (!G)169return;170171uint8_t CPU = 0;172uint64_t TSC = ReadTSC(CPU);173174switch (Type) {175case XRayEntryType::ENTRY:176case XRayEntryType::LOG_ARGS_ENTRY: {177// Short circuit if we've reached the maximum depth of the stack.178if (TLD.StackEntries++ >= TLD.StackSize)179return;180181// When we encounter an entry event, we keep track of the TSC and the CPU,182// and put it in the stack.183StackEntry E;184E.FuncId = FuncId;185E.CPU = CPU;186E.Type = Type;187E.TSC = TSC;188auto StackEntryPtr = static_cast<char *>(TLD.ShadowStack) +189(sizeof(StackEntry) * (TLD.StackEntries - 1));190internal_memcpy(StackEntryPtr, &E, sizeof(StackEntry));191break;192}193case XRayEntryType::EXIT:194case XRayEntryType::TAIL: {195if (TLD.StackEntries == 0)196break;197198if (--TLD.StackEntries >= TLD.StackSize)199return;200201// When we encounter an exit event, we check whether all the following are202// true:203//204// - The Function ID is the same as the most recent entry in the stack.205// - The CPU is the same as the most recent entry in the stack.206// - The Delta of the TSCs is less than the threshold amount of time we're207// looking to record.208//209// If all of these conditions are true, we pop the stack and don't write a210// record and move the record offset back.211StackEntry StackTop;212auto StackEntryPtr = static_cast<char *>(TLD.ShadowStack) +213(sizeof(StackEntry) * TLD.StackEntries);214internal_memcpy(&StackTop, StackEntryPtr, sizeof(StackEntry));215if (StackTop.FuncId == FuncId && StackTop.CPU == CPU &&216StackTop.TSC < TSC) {217auto Delta = TSC - StackTop.TSC;218if (Delta < atomic_load(&ThresholdTicks, memory_order_relaxed)) {219DCHECK(TLD.BufferOffset > 0);220TLD.BufferOffset -= StackTop.Type == XRayEntryType::ENTRY ? 1 : 2;221return;222}223}224break;225}226default:227// Should be unreachable.228DCHECK(false && "Unsupported XRayEntryType encountered.");229break;230}231232// First determine whether the delta between the function's enter record and233// the exit record is higher than the threshold.234XRayRecord R;235R.RecordType = RecordTypes::NORMAL;236R.CPU = CPU;237R.TSC = TSC;238R.TId = GetTid();239R.PId = internal_getpid();240R.Type = Type;241R.FuncId = FuncId;242auto FirstEntry = reinterpret_cast<XRayRecord *>(TLD.InMemoryBuffer);243internal_memcpy(FirstEntry + TLD.BufferOffset, &R, sizeof(R));244if (++TLD.BufferOffset == TLD.BufferSize) {245SpinMutexLock Lock(&LogMutex);246LW->WriteAll(reinterpret_cast<char *>(FirstEntry),247reinterpret_cast<char *>(FirstEntry + TLD.BufferOffset));248TLD.BufferOffset = 0;249TLD.StackEntries = 0;250}251}252253template <class RDTSC>254void InMemoryRawLogWithArg(int32_t FuncId, XRayEntryType Type, uint64_t Arg1,255RDTSC ReadTSC) XRAY_NEVER_INSTRUMENT {256auto &TLD = getThreadLocalData();257auto FirstEntry =258reinterpret_cast<XRayArgPayload *>(TLD.InMemoryBuffer);259const auto &BuffLen = TLD.BufferSize;260LogWriter *LW = getGlobalLog();261if (LW == nullptr)262return;263264// First we check whether there's enough space to write the data consecutively265// in the thread-local buffer. If not, we first flush the buffer before266// attempting to write the two records that must be consecutive.267if (TLD.BufferOffset + 2 > BuffLen) {268SpinMutexLock Lock(&LogMutex);269LW->WriteAll(reinterpret_cast<char *>(FirstEntry),270reinterpret_cast<char *>(FirstEntry + TLD.BufferOffset));271TLD.BufferOffset = 0;272TLD.StackEntries = 0;273}274275// Then we write the "we have an argument" record.276InMemoryRawLog(FuncId, Type, ReadTSC);277278RecursionGuard G(Guard);279if (!G)280return;281282// And, from here on write the arg payload.283XRayArgPayload R;284R.RecordType = RecordTypes::ARG_PAYLOAD;285R.FuncId = FuncId;286R.TId = GetTid();287R.PId = internal_getpid();288R.Arg = Arg1;289internal_memcpy(FirstEntry + TLD.BufferOffset, &R, sizeof(R));290if (++TLD.BufferOffset == BuffLen) {291SpinMutexLock Lock(&LogMutex);292LW->WriteAll(reinterpret_cast<char *>(FirstEntry),293reinterpret_cast<char *>(FirstEntry + TLD.BufferOffset));294TLD.BufferOffset = 0;295TLD.StackEntries = 0;296}297}298299void basicLoggingHandleArg0RealTSC(int32_t FuncId,300XRayEntryType Type) XRAY_NEVER_INSTRUMENT {301InMemoryRawLog(FuncId, Type, readTSC);302}303304void basicLoggingHandleArg0EmulateTSC(int32_t FuncId, XRayEntryType Type)305XRAY_NEVER_INSTRUMENT {306InMemoryRawLog(FuncId, Type, [](uint8_t &CPU) XRAY_NEVER_INSTRUMENT {307timespec TS;308int result = clock_gettime(CLOCK_REALTIME, &TS);309if (result != 0) {310Report("clock_gettimg(2) return %d, errno=%d.", result, int(errno));311TS = {0, 0};312}313CPU = 0;314return TS.tv_sec * NanosecondsPerSecond + TS.tv_nsec;315});316}317318void basicLoggingHandleArg1RealTSC(int32_t FuncId, XRayEntryType Type,319uint64_t Arg1) XRAY_NEVER_INSTRUMENT {320InMemoryRawLogWithArg(FuncId, Type, Arg1, readTSC);321}322323void basicLoggingHandleArg1EmulateTSC(int32_t FuncId, XRayEntryType Type,324uint64_t Arg1) XRAY_NEVER_INSTRUMENT {325InMemoryRawLogWithArg(326FuncId, Type, Arg1, [](uint8_t &CPU) XRAY_NEVER_INSTRUMENT {327timespec TS;328int result = clock_gettime(CLOCK_REALTIME, &TS);329if (result != 0) {330Report("clock_gettimg(2) return %d, errno=%d.", result, int(errno));331TS = {0, 0};332}333CPU = 0;334return TS.tv_sec * NanosecondsPerSecond + TS.tv_nsec;335});336}337338static void TLDDestructor(void *P) XRAY_NEVER_INSTRUMENT {339ThreadLocalData &TLD = *reinterpret_cast<ThreadLocalData *>(P);340auto ExitGuard = at_scope_exit([&TLD] {341// Clean up dynamic resources.342if (TLD.InMemoryBuffer)343InternalFree(TLD.InMemoryBuffer);344if (TLD.ShadowStack)345InternalFree(TLD.ShadowStack);346if (Verbosity())347Report("Cleaned up log for TID: %llu\n", GetTid());348});349350if (TLD.LogWriter == nullptr || TLD.BufferOffset == 0) {351if (Verbosity())352Report("Skipping buffer for TID: %llu; Offset = %zu\n", GetTid(),353TLD.BufferOffset);354return;355}356357{358SpinMutexLock L(&LogMutex);359TLD.LogWriter->WriteAll(reinterpret_cast<char *>(TLD.InMemoryBuffer),360reinterpret_cast<char *>(TLD.InMemoryBuffer) +361(sizeof(XRayRecord) * TLD.BufferOffset));362}363364// Because this thread's exit could be the last one trying to write to365// the file and that we're not able to close out the file properly, we366// sync instead and hope that the pending writes are flushed as the367// thread exits.368TLD.LogWriter->Flush();369}370371XRayLogInitStatus basicLoggingInit(UNUSED size_t BufferSize,372UNUSED size_t BufferMax, void *Options,373size_t OptionsSize) XRAY_NEVER_INSTRUMENT {374uint8_t Expected = 0;375if (!atomic_compare_exchange_strong(&BasicInitialized, &Expected, 1,376memory_order_acq_rel)) {377if (Verbosity())378Report("Basic logging already initialized.\n");379return XRayLogInitStatus::XRAY_LOG_INITIALIZED;380}381382static pthread_once_t OnceInit = PTHREAD_ONCE_INIT;383pthread_once(&OnceInit, +[] {384pthread_key_create(&PThreadKey, TLDDestructor);385atomic_store(&UseRealTSC, probeRequiredCPUFeatures(), memory_order_release);386// Initialize the global TicksPerSec value.387atomic_store(&TicksPerSec,388probeRequiredCPUFeatures() ? getTSCFrequency()389: NanosecondsPerSecond,390memory_order_release);391if (!atomic_load(&UseRealTSC, memory_order_relaxed) && Verbosity())392Report("WARNING: Required CPU features missing for XRay instrumentation, "393"using emulation instead.\n");394});395396FlagParser P;397BasicFlags F;398F.setDefaults();399registerXRayBasicFlags(&P, &F);400P.ParseString(useCompilerDefinedBasicFlags());401auto *EnvOpts = GetEnv("XRAY_BASIC_OPTIONS");402if (EnvOpts == nullptr)403EnvOpts = "";404405P.ParseString(EnvOpts);406407// If XRAY_BASIC_OPTIONS was not defined, then we use the deprecated options408// set through XRAY_OPTIONS instead.409if (internal_strlen(EnvOpts) == 0) {410F.func_duration_threshold_us =411flags()->xray_naive_log_func_duration_threshold_us;412F.max_stack_depth = flags()->xray_naive_log_max_stack_depth;413F.thread_buffer_size = flags()->xray_naive_log_thread_buffer_size;414}415416P.ParseString(static_cast<const char *>(Options));417GlobalOptions.ThreadBufferSize = F.thread_buffer_size;418GlobalOptions.DurationFilterMicros = F.func_duration_threshold_us;419GlobalOptions.MaxStackDepth = F.max_stack_depth;420*basicFlags() = F;421422atomic_store(&ThresholdTicks,423atomic_load(&TicksPerSec, memory_order_acquire) *424GlobalOptions.DurationFilterMicros / 1000000,425memory_order_release);426__xray_set_handler_arg1(atomic_load(&UseRealTSC, memory_order_acquire)427? basicLoggingHandleArg1RealTSC428: basicLoggingHandleArg1EmulateTSC);429__xray_set_handler(atomic_load(&UseRealTSC, memory_order_acquire)430? basicLoggingHandleArg0RealTSC431: basicLoggingHandleArg0EmulateTSC);432433// TODO: Implement custom event and typed event handling support in Basic434// Mode.435__xray_remove_customevent_handler();436__xray_remove_typedevent_handler();437438return XRayLogInitStatus::XRAY_LOG_INITIALIZED;439}440441XRayLogInitStatus basicLoggingFinalize() XRAY_NEVER_INSTRUMENT {442uint8_t Expected = 0;443if (!atomic_compare_exchange_strong(&BasicInitialized, &Expected, 0,444memory_order_acq_rel) &&445Verbosity())446Report("Basic logging already finalized.\n");447448// Nothing really to do aside from marking state of the global to be449// uninitialized.450451return XRayLogInitStatus::XRAY_LOG_FINALIZED;452}453454XRayLogFlushStatus basicLoggingFlush() XRAY_NEVER_INSTRUMENT {455// This really does nothing, since flushing the logs happen at the end of a456// thread's lifetime, or when the buffers are full.457return XRayLogFlushStatus::XRAY_LOG_FLUSHED;458}459460// This is a handler that, effectively, does nothing.461void basicLoggingHandleArg0Empty(int32_t, XRayEntryType) XRAY_NEVER_INSTRUMENT {462}463464bool basicLogDynamicInitializer() XRAY_NEVER_INSTRUMENT {465XRayLogImpl Impl{466basicLoggingInit,467basicLoggingFinalize,468basicLoggingHandleArg0Empty,469basicLoggingFlush,470};471auto RegistrationResult = __xray_log_register_mode("xray-basic", Impl);472if (RegistrationResult != XRayLogRegisterStatus::XRAY_REGISTRATION_OK &&473Verbosity())474Report("Cannot register XRay Basic Mode to 'xray-basic'; error = %d\n",475RegistrationResult);476if (flags()->xray_naive_log ||477!internal_strcmp(flags()->xray_mode, "xray-basic")) {478auto SelectResult = __xray_log_select_mode("xray-basic");479if (SelectResult != XRayLogRegisterStatus::XRAY_REGISTRATION_OK) {480if (Verbosity())481Report("Failed selecting XRay Basic Mode; error = %d\n", SelectResult);482return false;483}484485// We initialize the implementation using the data we get from the486// XRAY_BASIC_OPTIONS environment variable, at this point of the487// implementation.488auto *Env = GetEnv("XRAY_BASIC_OPTIONS");489auto InitResult =490__xray_log_init_mode("xray-basic", Env == nullptr ? "" : Env);491if (InitResult != XRayLogInitStatus::XRAY_LOG_INITIALIZED) {492if (Verbosity())493Report("Failed initializing XRay Basic Mode; error = %d\n", InitResult);494return false;495}496497// At this point we know that we've successfully initialized Basic mode498// tracing, and the only chance we're going to get for the current thread to499// clean-up may be at thread/program exit. To ensure that we're going to get500// the cleanup even without calling the finalization routines, we're501// registering a program exit function that will do the cleanup.502static pthread_once_t DynamicOnce = PTHREAD_ONCE_INIT;503pthread_once(&DynamicOnce, +[] {504static void *FakeTLD = nullptr;505FakeTLD = &getThreadLocalData();506Atexit(+[] { TLDDestructor(FakeTLD); });507});508}509return true;510}511512} // namespace __xray513514static auto UNUSED Unused = __xray::basicLogDynamicInitializer();515516517