xref: /openbsd-src/gnu/llvm/compiler-rt/lib/xray/xray_x86_64.cpp (revision 810390e339a5425391477d5d41c78d7cab2424ac)
13cab2bb3Spatrick #include "cpuid.h"
23cab2bb3Spatrick #include "sanitizer_common/sanitizer_common.h"
33cab2bb3Spatrick #if !SANITIZER_FUCHSIA
43cab2bb3Spatrick #include "sanitizer_common/sanitizer_posix.h"
53cab2bb3Spatrick #endif
63cab2bb3Spatrick #include "xray_defs.h"
73cab2bb3Spatrick #include "xray_interface_internal.h"
83cab2bb3Spatrick 
9*810390e3Srobert #if SANITIZER_FREEBSD || SANITIZER_NETBSD || SANITIZER_APPLE
103cab2bb3Spatrick #include <sys/types.h>
113cab2bb3Spatrick #include <sys/sysctl.h>
123cab2bb3Spatrick #elif SANITIZER_FUCHSIA
133cab2bb3Spatrick #include <zircon/syscalls.h>
143cab2bb3Spatrick #endif
153cab2bb3Spatrick 
163cab2bb3Spatrick #include <atomic>
173cab2bb3Spatrick #include <cstdint>
183cab2bb3Spatrick #include <errno.h>
193cab2bb3Spatrick #include <fcntl.h>
203cab2bb3Spatrick #include <iterator>
213cab2bb3Spatrick #include <limits>
223cab2bb3Spatrick #include <tuple>
233cab2bb3Spatrick #include <unistd.h>
243cab2bb3Spatrick 
253cab2bb3Spatrick namespace __xray {
263cab2bb3Spatrick 
273cab2bb3Spatrick #if SANITIZER_LINUX
283cab2bb3Spatrick static std::pair<ssize_t, bool>
retryingReadSome(int Fd,char * Begin,char * End)293cab2bb3Spatrick retryingReadSome(int Fd, char *Begin, char *End) XRAY_NEVER_INSTRUMENT {
303cab2bb3Spatrick   auto BytesToRead = std::distance(Begin, End);
313cab2bb3Spatrick   ssize_t BytesRead;
323cab2bb3Spatrick   ssize_t TotalBytesRead = 0;
333cab2bb3Spatrick   while (BytesToRead && (BytesRead = read(Fd, Begin, BytesToRead))) {
343cab2bb3Spatrick     if (BytesRead == -1) {
353cab2bb3Spatrick       if (errno == EINTR)
363cab2bb3Spatrick         continue;
373cab2bb3Spatrick       Report("Read error; errno = %d\n", errno);
383cab2bb3Spatrick       return std::make_pair(TotalBytesRead, false);
393cab2bb3Spatrick     }
403cab2bb3Spatrick 
413cab2bb3Spatrick     TotalBytesRead += BytesRead;
423cab2bb3Spatrick     BytesToRead -= BytesRead;
433cab2bb3Spatrick     Begin += BytesRead;
443cab2bb3Spatrick   }
453cab2bb3Spatrick   return std::make_pair(TotalBytesRead, true);
463cab2bb3Spatrick }
473cab2bb3Spatrick 
readValueFromFile(const char * Filename,long long * Value)483cab2bb3Spatrick static bool readValueFromFile(const char *Filename,
493cab2bb3Spatrick                               long long *Value) XRAY_NEVER_INSTRUMENT {
503cab2bb3Spatrick   int Fd = open(Filename, O_RDONLY | O_CLOEXEC);
513cab2bb3Spatrick   if (Fd == -1)
523cab2bb3Spatrick     return false;
533cab2bb3Spatrick   static constexpr size_t BufSize = 256;
543cab2bb3Spatrick   char Line[BufSize] = {};
553cab2bb3Spatrick   ssize_t BytesRead;
563cab2bb3Spatrick   bool Success;
573cab2bb3Spatrick   std::tie(BytesRead, Success) = retryingReadSome(Fd, Line, Line + BufSize);
583cab2bb3Spatrick   close(Fd);
593cab2bb3Spatrick   if (!Success)
603cab2bb3Spatrick     return false;
613cab2bb3Spatrick   const char *End = nullptr;
623cab2bb3Spatrick   long long Tmp = internal_simple_strtoll(Line, &End, 10);
633cab2bb3Spatrick   bool Result = false;
643cab2bb3Spatrick   if (Line[0] != '\0' && (*End == '\n' || *End == '\0')) {
653cab2bb3Spatrick     *Value = Tmp;
663cab2bb3Spatrick     Result = true;
673cab2bb3Spatrick   }
683cab2bb3Spatrick   return Result;
693cab2bb3Spatrick }
703cab2bb3Spatrick 
getTSCFrequency()713cab2bb3Spatrick uint64_t getTSCFrequency() XRAY_NEVER_INSTRUMENT {
723cab2bb3Spatrick   long long TSCFrequency = -1;
733cab2bb3Spatrick   if (readValueFromFile("/sys/devices/system/cpu/cpu0/tsc_freq_khz",
743cab2bb3Spatrick                         &TSCFrequency)) {
753cab2bb3Spatrick     TSCFrequency *= 1000;
763cab2bb3Spatrick   } else if (readValueFromFile(
773cab2bb3Spatrick                  "/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq",
783cab2bb3Spatrick                  &TSCFrequency)) {
793cab2bb3Spatrick     TSCFrequency *= 1000;
803cab2bb3Spatrick   } else {
813cab2bb3Spatrick     Report("Unable to determine CPU frequency for TSC accounting.\n");
823cab2bb3Spatrick   }
833cab2bb3Spatrick   return TSCFrequency == -1 ? 0 : static_cast<uint64_t>(TSCFrequency);
843cab2bb3Spatrick }
85*810390e3Srobert #elif SANITIZER_FREEBSD || SANITIZER_NETBSD || SANITIZER_APPLE
863cab2bb3Spatrick uint64_t getTSCFrequency() XRAY_NEVER_INSTRUMENT {
873cab2bb3Spatrick     long long TSCFrequency = -1;
883cab2bb3Spatrick     size_t tscfreqsz = sizeof(TSCFrequency);
89*810390e3Srobert #if SANITIZER_APPLE
903cab2bb3Spatrick     if (internal_sysctlbyname("machdep.tsc.frequency", &TSCFrequency,
913cab2bb3Spatrick                               &tscfreqsz, NULL, 0) != -1) {
923cab2bb3Spatrick 
933cab2bb3Spatrick #else
943cab2bb3Spatrick     if (internal_sysctlbyname("machdep.tsc_freq", &TSCFrequency, &tscfreqsz,
953cab2bb3Spatrick                               NULL, 0) != -1) {
963cab2bb3Spatrick #endif
973cab2bb3Spatrick         return static_cast<uint64_t>(TSCFrequency);
983cab2bb3Spatrick     } else {
993cab2bb3Spatrick       Report("Unable to determine CPU frequency for TSC accounting.\n");
1003cab2bb3Spatrick     }
1013cab2bb3Spatrick 
1023cab2bb3Spatrick     return 0;
1033cab2bb3Spatrick }
1043cab2bb3Spatrick #elif !SANITIZER_FUCHSIA
1053cab2bb3Spatrick uint64_t getTSCFrequency() XRAY_NEVER_INSTRUMENT {
1063cab2bb3Spatrick     /* Not supported */
1073cab2bb3Spatrick     return 0;
1083cab2bb3Spatrick }
1093cab2bb3Spatrick #endif
1103cab2bb3Spatrick 
1113cab2bb3Spatrick static constexpr uint8_t CallOpCode = 0xe8;
1123cab2bb3Spatrick static constexpr uint16_t MovR10Seq = 0xba41;
1133cab2bb3Spatrick static constexpr uint16_t Jmp9Seq = 0x09eb;
1143cab2bb3Spatrick static constexpr uint16_t Jmp20Seq = 0x14eb;
1153cab2bb3Spatrick static constexpr uint16_t Jmp15Seq = 0x0feb;
1163cab2bb3Spatrick static constexpr uint8_t JmpOpCode = 0xe9;
1173cab2bb3Spatrick static constexpr uint8_t RetOpCode = 0xc3;
1183cab2bb3Spatrick static constexpr uint16_t NopwSeq = 0x9066;
1193cab2bb3Spatrick 
1203cab2bb3Spatrick static constexpr int64_t MinOffset{std::numeric_limits<int32_t>::min()};
1213cab2bb3Spatrick static constexpr int64_t MaxOffset{std::numeric_limits<int32_t>::max()};
1223cab2bb3Spatrick 
patchFunctionEntry(const bool Enable,const uint32_t FuncId,const XRaySledEntry & Sled,void (* Trampoline)())1233cab2bb3Spatrick bool patchFunctionEntry(const bool Enable, const uint32_t FuncId,
1243cab2bb3Spatrick                         const XRaySledEntry &Sled,
1253cab2bb3Spatrick                         void (*Trampoline)()) XRAY_NEVER_INSTRUMENT {
1263cab2bb3Spatrick   // Here we do the dance of replacing the following sled:
1273cab2bb3Spatrick   //
1283cab2bb3Spatrick   // xray_sled_n:
1293cab2bb3Spatrick   //   jmp +9
1303cab2bb3Spatrick   //   <9 byte nop>
1313cab2bb3Spatrick   //
1323cab2bb3Spatrick   // With the following:
1333cab2bb3Spatrick   //
1343cab2bb3Spatrick   //   mov r10d, <function id>
1353cab2bb3Spatrick   //   call <relative 32bit offset to entry trampoline>
1363cab2bb3Spatrick   //
1373cab2bb3Spatrick   // We need to do this in the following order:
1383cab2bb3Spatrick   //
1393cab2bb3Spatrick   // 1. Put the function id first, 2 bytes from the start of the sled (just
1403cab2bb3Spatrick   // after the 2-byte jmp instruction).
1413cab2bb3Spatrick   // 2. Put the call opcode 6 bytes from the start of the sled.
1423cab2bb3Spatrick   // 3. Put the relative offset 7 bytes from the start of the sled.
1433cab2bb3Spatrick   // 4. Do an atomic write over the jmp instruction for the "mov r10d"
1443cab2bb3Spatrick   // opcode and first operand.
1453cab2bb3Spatrick   //
1463cab2bb3Spatrick   // Prerequisite is to compute the relative offset to the trampoline's address.
1471f9cb04fSpatrick   const uint64_t Address = Sled.address();
1483cab2bb3Spatrick   int64_t TrampolineOffset = reinterpret_cast<int64_t>(Trampoline) -
1491f9cb04fSpatrick                              (static_cast<int64_t>(Address) + 11);
1503cab2bb3Spatrick   if (TrampolineOffset < MinOffset || TrampolineOffset > MaxOffset) {
151*810390e3Srobert     Report("XRay Entry trampoline (%p) too far from sled (%p)\n",
152*810390e3Srobert            reinterpret_cast<void *>(Trampoline),
1531f9cb04fSpatrick            reinterpret_cast<void *>(Address));
1543cab2bb3Spatrick     return false;
1553cab2bb3Spatrick   }
1563cab2bb3Spatrick   if (Enable) {
1571f9cb04fSpatrick     *reinterpret_cast<uint32_t *>(Address + 2) = FuncId;
1581f9cb04fSpatrick     *reinterpret_cast<uint8_t *>(Address + 6) = CallOpCode;
1591f9cb04fSpatrick     *reinterpret_cast<uint32_t *>(Address + 7) = TrampolineOffset;
1603cab2bb3Spatrick     std::atomic_store_explicit(
1611f9cb04fSpatrick         reinterpret_cast<std::atomic<uint16_t> *>(Address), MovR10Seq,
1623cab2bb3Spatrick         std::memory_order_release);
1633cab2bb3Spatrick   } else {
1643cab2bb3Spatrick     std::atomic_store_explicit(
1651f9cb04fSpatrick         reinterpret_cast<std::atomic<uint16_t> *>(Address), Jmp9Seq,
1663cab2bb3Spatrick         std::memory_order_release);
1673cab2bb3Spatrick     // FIXME: Write out the nops still?
1683cab2bb3Spatrick   }
1693cab2bb3Spatrick   return true;
1703cab2bb3Spatrick }
1713cab2bb3Spatrick 
patchFunctionExit(const bool Enable,const uint32_t FuncId,const XRaySledEntry & Sled)1723cab2bb3Spatrick bool patchFunctionExit(const bool Enable, const uint32_t FuncId,
1733cab2bb3Spatrick                        const XRaySledEntry &Sled) XRAY_NEVER_INSTRUMENT {
1743cab2bb3Spatrick   // Here we do the dance of replacing the following sled:
1753cab2bb3Spatrick   //
1763cab2bb3Spatrick   // xray_sled_n:
1773cab2bb3Spatrick   //   ret
1783cab2bb3Spatrick   //   <10 byte nop>
1793cab2bb3Spatrick   //
1803cab2bb3Spatrick   // With the following:
1813cab2bb3Spatrick   //
1823cab2bb3Spatrick   //   mov r10d, <function id>
1833cab2bb3Spatrick   //   jmp <relative 32bit offset to exit trampoline>
1843cab2bb3Spatrick   //
1853cab2bb3Spatrick   // 1. Put the function id first, 2 bytes from the start of the sled (just
1863cab2bb3Spatrick   // after the 1-byte ret instruction).
1873cab2bb3Spatrick   // 2. Put the jmp opcode 6 bytes from the start of the sled.
1883cab2bb3Spatrick   // 3. Put the relative offset 7 bytes from the start of the sled.
1893cab2bb3Spatrick   // 4. Do an atomic write over the jmp instruction for the "mov r10d"
1903cab2bb3Spatrick   // opcode and first operand.
1913cab2bb3Spatrick   //
1923cab2bb3Spatrick   // Prerequisite is to compute the relative offset fo the
1933cab2bb3Spatrick   // __xray_FunctionExit function's address.
1941f9cb04fSpatrick   const uint64_t Address = Sled.address();
1953cab2bb3Spatrick   int64_t TrampolineOffset = reinterpret_cast<int64_t>(__xray_FunctionExit) -
1961f9cb04fSpatrick                              (static_cast<int64_t>(Address) + 11);
1973cab2bb3Spatrick   if (TrampolineOffset < MinOffset || TrampolineOffset > MaxOffset) {
1983cab2bb3Spatrick     Report("XRay Exit trampoline (%p) too far from sled (%p)\n",
199*810390e3Srobert            reinterpret_cast<void *>(__xray_FunctionExit),
200*810390e3Srobert            reinterpret_cast<void *>(Address));
2013cab2bb3Spatrick     return false;
2023cab2bb3Spatrick   }
2033cab2bb3Spatrick   if (Enable) {
2041f9cb04fSpatrick     *reinterpret_cast<uint32_t *>(Address + 2) = FuncId;
2051f9cb04fSpatrick     *reinterpret_cast<uint8_t *>(Address + 6) = JmpOpCode;
2061f9cb04fSpatrick     *reinterpret_cast<uint32_t *>(Address + 7) = TrampolineOffset;
2073cab2bb3Spatrick     std::atomic_store_explicit(
2081f9cb04fSpatrick         reinterpret_cast<std::atomic<uint16_t> *>(Address), MovR10Seq,
2093cab2bb3Spatrick         std::memory_order_release);
2103cab2bb3Spatrick   } else {
2113cab2bb3Spatrick     std::atomic_store_explicit(
2121f9cb04fSpatrick         reinterpret_cast<std::atomic<uint8_t> *>(Address), RetOpCode,
2133cab2bb3Spatrick         std::memory_order_release);
2143cab2bb3Spatrick     // FIXME: Write out the nops still?
2153cab2bb3Spatrick   }
2163cab2bb3Spatrick   return true;
2173cab2bb3Spatrick }
2183cab2bb3Spatrick 
patchFunctionTailExit(const bool Enable,const uint32_t FuncId,const XRaySledEntry & Sled)2193cab2bb3Spatrick bool patchFunctionTailExit(const bool Enable, const uint32_t FuncId,
2203cab2bb3Spatrick                            const XRaySledEntry &Sled) XRAY_NEVER_INSTRUMENT {
2213cab2bb3Spatrick   // Here we do the dance of replacing the tail call sled with a similar
2223cab2bb3Spatrick   // sequence as the entry sled, but calls the tail exit sled instead.
2231f9cb04fSpatrick   const uint64_t Address = Sled.address();
2243cab2bb3Spatrick   int64_t TrampolineOffset =
2253cab2bb3Spatrick       reinterpret_cast<int64_t>(__xray_FunctionTailExit) -
2261f9cb04fSpatrick       (static_cast<int64_t>(Address) + 11);
2273cab2bb3Spatrick   if (TrampolineOffset < MinOffset || TrampolineOffset > MaxOffset) {
2283cab2bb3Spatrick     Report("XRay Tail Exit trampoline (%p) too far from sled (%p)\n",
229*810390e3Srobert            reinterpret_cast<void *>(__xray_FunctionTailExit),
230*810390e3Srobert            reinterpret_cast<void *>(Address));
2313cab2bb3Spatrick     return false;
2323cab2bb3Spatrick   }
2333cab2bb3Spatrick   if (Enable) {
2341f9cb04fSpatrick     *reinterpret_cast<uint32_t *>(Address + 2) = FuncId;
2351f9cb04fSpatrick     *reinterpret_cast<uint8_t *>(Address + 6) = CallOpCode;
2361f9cb04fSpatrick     *reinterpret_cast<uint32_t *>(Address + 7) = TrampolineOffset;
2373cab2bb3Spatrick     std::atomic_store_explicit(
2381f9cb04fSpatrick         reinterpret_cast<std::atomic<uint16_t> *>(Address), MovR10Seq,
2393cab2bb3Spatrick         std::memory_order_release);
2403cab2bb3Spatrick   } else {
2413cab2bb3Spatrick     std::atomic_store_explicit(
2421f9cb04fSpatrick         reinterpret_cast<std::atomic<uint16_t> *>(Address), Jmp9Seq,
2433cab2bb3Spatrick         std::memory_order_release);
2443cab2bb3Spatrick     // FIXME: Write out the nops still?
2453cab2bb3Spatrick   }
2463cab2bb3Spatrick   return true;
2473cab2bb3Spatrick }
2483cab2bb3Spatrick 
patchCustomEvent(const bool Enable,const uint32_t FuncId,const XRaySledEntry & Sled)2493cab2bb3Spatrick bool patchCustomEvent(const bool Enable, const uint32_t FuncId,
2503cab2bb3Spatrick                       const XRaySledEntry &Sled) XRAY_NEVER_INSTRUMENT {
2513cab2bb3Spatrick   // Here we do the dance of replacing the following sled:
2523cab2bb3Spatrick   //
2533cab2bb3Spatrick   // In Version 0:
2543cab2bb3Spatrick   //
2553cab2bb3Spatrick   // xray_sled_n:
2563cab2bb3Spatrick   //   jmp +20          // 2 bytes
2573cab2bb3Spatrick   //   ...
2583cab2bb3Spatrick   //
2593cab2bb3Spatrick   // With the following:
2603cab2bb3Spatrick   //
2613cab2bb3Spatrick   //   nopw             // 2 bytes*
2623cab2bb3Spatrick   //   ...
2633cab2bb3Spatrick   //
2643cab2bb3Spatrick   //
2653cab2bb3Spatrick   // The "unpatch" should just turn the 'nopw' back to a 'jmp +20'.
2663cab2bb3Spatrick   //
2673cab2bb3Spatrick   // ---
2683cab2bb3Spatrick   //
2691f9cb04fSpatrick   // In Version 1 or 2:
2703cab2bb3Spatrick   //
2713cab2bb3Spatrick   //   The jump offset is now 15 bytes (0x0f), so when restoring the nopw back
2723cab2bb3Spatrick   //   to a jmp, use 15 bytes instead.
2733cab2bb3Spatrick   //
2741f9cb04fSpatrick   const uint64_t Address = Sled.address();
2753cab2bb3Spatrick   if (Enable) {
2763cab2bb3Spatrick     std::atomic_store_explicit(
2771f9cb04fSpatrick         reinterpret_cast<std::atomic<uint16_t> *>(Address), NopwSeq,
2783cab2bb3Spatrick         std::memory_order_release);
2793cab2bb3Spatrick   } else {
2803cab2bb3Spatrick     switch (Sled.Version) {
2813cab2bb3Spatrick     case 1:
2821f9cb04fSpatrick     case 2:
2833cab2bb3Spatrick       std::atomic_store_explicit(
2841f9cb04fSpatrick           reinterpret_cast<std::atomic<uint16_t> *>(Address), Jmp15Seq,
2853cab2bb3Spatrick           std::memory_order_release);
2863cab2bb3Spatrick       break;
2873cab2bb3Spatrick     case 0:
2883cab2bb3Spatrick     default:
2893cab2bb3Spatrick       std::atomic_store_explicit(
2901f9cb04fSpatrick           reinterpret_cast<std::atomic<uint16_t> *>(Address), Jmp20Seq,
2913cab2bb3Spatrick           std::memory_order_release);
2923cab2bb3Spatrick       break;
2933cab2bb3Spatrick     }
2943cab2bb3Spatrick     }
2953cab2bb3Spatrick   return false;
2963cab2bb3Spatrick }
2973cab2bb3Spatrick 
patchTypedEvent(const bool Enable,const uint32_t FuncId,const XRaySledEntry & Sled)2983cab2bb3Spatrick bool patchTypedEvent(const bool Enable, const uint32_t FuncId,
2993cab2bb3Spatrick                       const XRaySledEntry &Sled) XRAY_NEVER_INSTRUMENT {
3003cab2bb3Spatrick   // Here we do the dance of replacing the following sled:
3013cab2bb3Spatrick   //
3023cab2bb3Spatrick   // xray_sled_n:
3033cab2bb3Spatrick   //   jmp +20          // 2 byte instruction
3043cab2bb3Spatrick   //   ...
3053cab2bb3Spatrick   //
3063cab2bb3Spatrick   // With the following:
3073cab2bb3Spatrick   //
3083cab2bb3Spatrick   //   nopw             // 2 bytes
3093cab2bb3Spatrick   //   ...
3103cab2bb3Spatrick   //
3113cab2bb3Spatrick   //
3123cab2bb3Spatrick   // The "unpatch" should just turn the 'nopw' back to a 'jmp +20'.
3133cab2bb3Spatrick   // The 20 byte sled stashes three argument registers, calls the trampoline,
3143cab2bb3Spatrick   // unstashes the registers and returns. If the arguments are already in
3153cab2bb3Spatrick   // the correct registers, the stashing and unstashing become equivalently
3163cab2bb3Spatrick   // sized nops.
3171f9cb04fSpatrick   const uint64_t Address = Sled.address();
3183cab2bb3Spatrick   if (Enable) {
3193cab2bb3Spatrick     std::atomic_store_explicit(
3201f9cb04fSpatrick         reinterpret_cast<std::atomic<uint16_t> *>(Address), NopwSeq,
3213cab2bb3Spatrick         std::memory_order_release);
3223cab2bb3Spatrick   } else {
3233cab2bb3Spatrick     std::atomic_store_explicit(
3241f9cb04fSpatrick         reinterpret_cast<std::atomic<uint16_t> *>(Address), Jmp20Seq,
3253cab2bb3Spatrick         std::memory_order_release);
3263cab2bb3Spatrick   }
3273cab2bb3Spatrick   return false;
3283cab2bb3Spatrick }
3293cab2bb3Spatrick 
3303cab2bb3Spatrick #if !SANITIZER_FUCHSIA
3313cab2bb3Spatrick // We determine whether the CPU we're running on has the correct features we
3323cab2bb3Spatrick // need. In x86_64 this will be rdtscp support.
probeRequiredCPUFeatures()3333cab2bb3Spatrick bool probeRequiredCPUFeatures() XRAY_NEVER_INSTRUMENT {
3343cab2bb3Spatrick   unsigned int EAX, EBX, ECX, EDX;
3353cab2bb3Spatrick 
3363cab2bb3Spatrick   // We check whether rdtscp support is enabled. According to the x86_64 manual,
3373cab2bb3Spatrick   // level should be set at 0x80000001, and we should have a look at bit 27 in
3383cab2bb3Spatrick   // EDX. That's 0x8000000 (or 1u << 27).
3393cab2bb3Spatrick   __asm__ __volatile__("cpuid" : "=a"(EAX), "=b"(EBX), "=c"(ECX), "=d"(EDX)
3403cab2bb3Spatrick     : "0"(0x80000001));
3413cab2bb3Spatrick   if (!(EDX & (1u << 27))) {
3423cab2bb3Spatrick     Report("Missing rdtscp support.\n");
3433cab2bb3Spatrick     return false;
3443cab2bb3Spatrick   }
3453cab2bb3Spatrick   // Also check whether we can determine the CPU frequency, since if we cannot,
3463cab2bb3Spatrick   // we should use the emulated TSC instead.
3473cab2bb3Spatrick   if (!getTSCFrequency()) {
3483cab2bb3Spatrick     Report("Unable to determine CPU frequency.\n");
3493cab2bb3Spatrick     return false;
3503cab2bb3Spatrick   }
3513cab2bb3Spatrick   return true;
3523cab2bb3Spatrick }
3533cab2bb3Spatrick #endif
3543cab2bb3Spatrick 
3553cab2bb3Spatrick } // namespace __xray
356