File indexing completed on 2024-05-12 17:15:55

0001 /*
0002     SPDX-FileCopyrightText: 2014-2017 Milian Wolff <mail@milianw.de>
0003 
0004     SPDX-License-Identifier: LGPL-2.1-or-later
0005 */
0006 
0007 /**
0008  * @file libheaptrack.cpp
0009  *
0010  * @brief Collect raw heaptrack data by overloading heap allocation functions.
0011  */
0012 
0013 #include "libheaptrack.h"
0014 
0015 #include <cstdio>
0016 #include <cstdlib>
0017 #include <fcntl.h>
0018 #include <link.h>
0019 #include <pthread.h>
0020 #include <signal.h>
0021 #ifdef __linux__
0022 #include <stdio_ext.h>
0023 #include <syscall.h>
0024 #endif
0025 #ifdef __FreeBSD__
0026 #include <libutil.h>
0027 #include <pthread_np.h>
0028 #include <sys/sysctl.h>
0029 #include <sys/types.h>
0030 #include <sys/user.h>
0031 #endif
0032 #include <sys/file.h>
0033 
0034 #include <atomic>
0035 #include <cinttypes>
0036 #include <memory>
0037 #include <mutex>
0038 #include <sstream>
0039 #include <string>
0040 #include <thread>
0041 
0042 #include "tracetree.h"
0043 #include "util/config.h"
0044 #include "util/libunwind_config.h"
0045 #include "util/linewriter.h"
0046 #include "util/macroutils.h"
0047 
0048 extern "C" {
0049 // see upstream "documentation" at:
0050 // https://github.com/llvm-mirror/compiler-rt/blob/master/include/sanitizer/lsan_interface.h#L76
0051 __attribute__((weak)) const char* __lsan_default_suppressions();
0052 }
0053 namespace __gnu_cxx {
0054 __attribute__((weak)) extern void __freeres();
0055 }
0056 
0057 /**
0058  * uncomment this to get extended debug code for known pointers
0059  * there are still some malloc functions I'm missing apparently,
0060  * related to TLS and such I guess
0061  */
0062 // #define DEBUG_MALLOC_PTRS
0063 
0064 #ifdef DEBUG_MALLOC_PTRS
0065 #include <tsl/robin_set.h>
0066 #endif
0067 
0068 using namespace std;
0069 
0070 namespace {
0071 
0072 using clock = chrono::steady_clock;
0073 chrono::time_point<clock> startTime()
0074 {
0075     static const chrono::time_point<clock> s_start = clock::now();
0076     return s_start;
0077 }
0078 
0079 chrono::milliseconds elapsedTime()
0080 {
0081     return chrono::duration_cast<chrono::milliseconds>(clock::now() - startTime());
0082 }
0083 
0084 pid_t gettid()
0085 {
0086 #ifdef __linux__
0087     return syscall(SYS_gettid);
0088 #elif defined(__FreeBSD__)
0089     return pthread_getthreadid_np();
0090 #endif
0091 }
0092 
0093 /**
0094  * A per-thread handle guard to prevent infinite recursion, which should be
0095  * acquired before doing any special symbol handling.
0096  */
0097 struct RecursionGuard
0098 {
0099     RecursionGuard()
0100         : wasLocked(isActive)
0101     {
0102         isActive = true;
0103     }
0104 
0105     ~RecursionGuard()
0106     {
0107         isActive = wasLocked;
0108     }
0109 
0110     const bool wasLocked;
0111     static thread_local bool isActive;
0112 };
0113 
0114 thread_local bool RecursionGuard::isActive = false;
0115 
0116 enum DebugVerbosity
0117 {
0118     WarningOutput,
0119     NoDebugOutput,
0120     MinimalOutput,
0121     VerboseOutput,
0122     VeryVerboseOutput,
0123 };
0124 
0125 // change this to add more debug output to stderr
0126 constexpr const DebugVerbosity s_debugVerbosity = NoDebugOutput;
0127 
0128 /**
0129  * Call this to optionally show debug information but give the compiler
0130  * a hand in removing it all if debug output is disabled.
0131  */
0132 template <DebugVerbosity debugLevel, typename Callback>
0133 inline void debugLog(Callback callback)
0134 {
0135     if (debugLevel <= s_debugVerbosity) {
0136         RecursionGuard guard;
0137         flockfile(stderr);
0138         if (debugLevel == WarningOutput) {
0139             fprintf(stderr, "heaptrack warning [%d:%d]@%" PRIu64 " ", getpid(), gettid(), elapsedTime().count());
0140         } else {
0141             fprintf(stderr, "heaptrack debug(%d) [%d:%d]@%" PRIu64 " ", debugLevel, getpid(), gettid(),
0142                     elapsedTime().count());
0143         }
0144         callback(stderr);
0145         fputc('\n', stderr);
0146         funlockfile(stderr);
0147     }
0148 }
0149 
0150 /**
0151  * Call this to optionally show debug information but give the compiler
0152  * a hand in removing it all if debug output is disabled.
0153  */
0154 template <DebugVerbosity debugLevel, typename... Args>
0155 inline void debugLog(const char fmt[], Args... args)
0156 {
0157     debugLog<debugLevel>([&](FILE* out) { fprintf(out, fmt, args...); });
0158 }
0159 
0160 void printBacktrace()
0161 {
0162     if (s_debugVerbosity == NoDebugOutput)
0163         return;
0164 
0165     RecursionGuard guard;
0166 
0167     Trace::print();
0168 }
0169 
0170 /**
0171  * Set to true in an atexit handler. In such conditions, the stop callback
0172  * will not be called.
0173  */
0174 atomic<bool> s_atexit {false};
0175 
0176 /**
0177  * Set to true in heaptrack_stop, when s_atexit was not yet set. In such conditions,
0178  * we always fully unload and cleanup behind ourselves
0179  */
0180 atomic<bool> s_forceCleanup {false};
0181 
0182 // based on: https://stackoverflow.com/a/24315631/35250
0183 void replaceAll(string& str, const string& search, const string& replace)
0184 {
0185     size_t start_pos = 0;
0186     while ((start_pos = str.find(search, start_pos)) != string::npos) {
0187         str.replace(start_pos, search.length(), replace);
0188         start_pos += replace.length();
0189     }
0190 }
0191 
0192 // see https://bugs.kde.org/show_bug.cgi?id=408547
0193 // apparently sometimes flock can return EAGAIN, despite that not being a documented return value
0194 static int lockFile(int fd)
0195 {
0196     int ret = -1;
0197     while ((ret = flock(fd, LOCK_EX | LOCK_NB)) == EAGAIN) {
0198         // try again
0199     }
0200     return ret;
0201 }
0202 
0203 int createFile(const char* fileName)
0204 {
0205     string outputFileName;
0206     if (fileName) {
0207         outputFileName.assign(fileName);
0208     }
0209 
0210     if (outputFileName == "-" || outputFileName == "stdout") {
0211         debugLog<VerboseOutput>("%s", "will write to stdout");
0212         return fileno(stdout);
0213     } else if (outputFileName == "stderr") {
0214         debugLog<VerboseOutput>("%s", "will write to stderr");
0215         return fileno(stderr);
0216     }
0217 
0218     if (outputFileName.empty()) {
0219         // env var might not be set when linked directly into an executable
0220         outputFileName = "heaptrack.$$";
0221     }
0222 
0223     replaceAll(outputFileName, "$$", to_string(getpid()));
0224 
0225     auto out = open(outputFileName.c_str(), O_CREAT | O_WRONLY | O_CLOEXEC, 0644);
0226     debugLog<VerboseOutput>("will write to %s/%p\n", outputFileName.c_str(), out);
0227     // we do our own locking, this speeds up the writing significantly
0228     if (out == -1) {
0229         fprintf(stderr, "ERROR: failed to open heaptrack output file %s: %s (%d)\n", outputFileName.c_str(),
0230                 strerror(errno), errno);
0231     } else if (lockFile(out) != 0) {
0232 #ifdef __FreeBSD__
0233         // pipes do not support flock, create a regular file
0234         auto lockpath = outputFileName + ".lock";
0235         auto lockfile = open(lockpath.c_str(), O_CREAT | O_WRONLY | O_CLOEXEC, 0644);
0236         debugLog<VerboseOutput>("will lock %s/%p\n", lockpath.c_str(), lockfile);
0237         if (lockFile(lockfile) == 0) {
0238             // leaking the fd seems fine
0239             return out;
0240         }
0241 #endif
0242         fprintf(stderr, "ERROR: failed to lock heaptrack output file %s: %s (%d)\n", outputFileName.c_str(),
0243                 strerror(errno), errno);
0244         close(out);
0245         return -1;
0246     }
0247 
0248     return out;
0249 }
0250 
0251 /**
0252  * Thread-Safe heaptrack API
0253  *
0254  * The only critical section in libheaptrack is the output of the data,
0255  * dl_iterate_phdr calls, as well as initialization and shutdown.
0256  */
0257 class HeapTrack
0258 {
0259 public:
0260     template <typename Op>
0261     static bool op(const RecursionGuard& /*recursionGuard*/, const Op& op)
0262     {
0263         debugLog<VeryVerboseOutput>("%s", "acquiring lock");
0264 
0265         auto locked = tryLock([]() { return s_forceCleanup.load(); });
0266         if (!locked) {
0267             return false;
0268         }
0269 
0270         debugLog<VeryVerboseOutput>("%s", "lock acquired");
0271 
0272         HeapTrack heaptrack(locked);
0273         op(heaptrack);
0274 
0275         return true;
0276     }
0277 
0278     ~HeapTrack()
0279     {
0280         debugLog<VeryVerboseOutput>("%s", "releasing lock");
0281 
0282         s_lock.unlock();
0283     }
0284 
0285     void initialize(const char* fileName, heaptrack_callback_t initBeforeCallback,
0286                     heaptrack_callback_initialized_t initAfterCallback, heaptrack_callback_t stopCallback)
0287     {
0288         debugLog<MinimalOutput>("initializing: %s", fileName);
0289         if (s_data) {
0290             debugLog<MinimalOutput>("%s", "already initialized");
0291             return;
0292         }
0293 
0294         if (initBeforeCallback) {
0295             debugLog<MinimalOutput>("%s", "calling initBeforeCallback");
0296             initBeforeCallback();
0297             debugLog<MinimalOutput>("%s", "done calling initBeforeCallback");
0298         }
0299 
0300         // do some once-only initializations
0301         static once_flag once;
0302         call_once(once, [] {
0303             debugLog<MinimalOutput>("%s", "doing once-only initialization");
0304 
0305             Trace::setup();
0306 
0307             // do not trace forked child processes
0308             // TODO: make this configurable
0309             pthread_atfork(&prepare_fork, &parent_fork, &child_fork);
0310 
0311             atexit([]() {
0312                 if (s_forceCleanup) {
0313                     return;
0314                 }
0315                 debugLog<MinimalOutput>("%s", "atexit()");
0316 
0317                 // free internal libstdc++ resources
0318                 // see also Valgrind's `--run-cxx-freeres` option
0319                 if (&__gnu_cxx::__freeres) {
0320                     __gnu_cxx::__freeres();
0321                 }
0322 
0323                 s_atexit.store(true);
0324                 heaptrack_stop();
0325             });
0326         });
0327 
0328         const auto out = createFile(fileName);
0329 
0330         if (out == -1) {
0331             if (stopCallback) {
0332                 stopCallback();
0333             }
0334             return;
0335         }
0336 
0337         s_data = new LockedData(out, stopCallback);
0338 
0339         writeVersion();
0340         writeExe();
0341         writeCommandLine();
0342         writeSystemInfo();
0343         writeSuppressions();
0344 
0345         if (initAfterCallback) {
0346             debugLog<MinimalOutput>("%s", "calling initAfterCallback");
0347             initAfterCallback(s_data->out);
0348             debugLog<MinimalOutput>("%s", "calling initAfterCallback done");
0349         }
0350 
0351         debugLog<MinimalOutput>("%s", "initialization done");
0352     }
0353 
0354     void shutdown()
0355     {
0356         if (!s_data) {
0357             return;
0358         }
0359 
0360         debugLog<MinimalOutput>("%s", "shutdown()");
0361 
0362         writeTimestamp();
0363         writeRSS();
0364 
0365         s_data->out.flush();
0366         s_data->out.close();
0367 
0368         // NOTE: we leak heaptrack data on exit, intentionally
0369         // This way, we can be sure to get all static deallocations.
0370         if (!s_atexit || s_forceCleanup) {
0371             delete s_data;
0372             s_data = nullptr;
0373         }
0374 
0375         debugLog<MinimalOutput>("%s", "shutdown() done");
0376     }
0377 
0378     void invalidateModuleCache()
0379     {
0380         if (!s_data) {
0381             return;
0382         }
0383         s_data->moduleCacheDirty = true;
0384     }
0385 
0386     void writeTimestamp()
0387     {
0388         if (!s_data || !s_data->out.canWrite()) {
0389             return;
0390         }
0391 
0392         auto elapsed = elapsedTime();
0393 
0394         debugLog<VeryVerboseOutput>("writeTimestamp(%" PRIx64 ")", elapsed.count());
0395 
0396         s_data->out.writeHexLine('c', static_cast<size_t>(elapsed.count()));
0397     }
0398 
0399     void writeRSS()
0400     {
0401         if (!s_data || !s_data->out.canWrite()) {
0402             return;
0403         }
0404 
0405         size_t rss = 0;
0406 
0407 #ifdef __linux__
0408         if (s_data->procStatm == -1) {
0409             return;
0410         }
0411         // read RSS in pages from statm, then rewind for next read
0412         // NOTE: don't use fscanf here, it could potentially deadlock us
0413         const int BUF_SIZE = 512;
0414         char buf[BUF_SIZE + 1];
0415         if (read(s_data->procStatm, buf, BUF_SIZE) <= 0) {
0416             fprintf(stderr, "WARNING: Failed to read RSS value from /proc/self/statm.\n");
0417             close(s_data->procStatm);
0418             s_data->procStatm = -1;
0419             return;
0420         }
0421         lseek(s_data->procStatm, 0, SEEK_SET);
0422 
0423         if (sscanf(buf, "%*u %zu", &rss) != 1) {
0424             fprintf(stderr, "WARNING: Failed to read RSS value from /proc/self/statm.\n");
0425             close(s_data->procStatm);
0426             s_data->procStatm = -1;
0427             return;
0428         }
0429 #elif defined(__FreeBSD__)
0430         auto proc_info = kinfo_getproc(getpid());
0431         if (proc_info == nullptr) {
0432             return;
0433         }
0434 
0435         rss = proc_info->ki_rssize;
0436 
0437         free(proc_info);
0438 #endif
0439 
0440         // TODO: compare to rusage.ru_maxrss (getrusage) to find "real" peak?
0441         // TODO: use custom allocators with known page sizes to prevent tainting
0442         //       the RSS numbers with heaptrack-internal data
0443 
0444         s_data->out.writeHexLine('R', rss);
0445     }
0446 
0447     void writeVersion()
0448     {
0449         s_data->out.writeHexLine('v', static_cast<size_t>(HEAPTRACK_VERSION),
0450                                  static_cast<size_t>(HEAPTRACK_FILE_FORMAT_VERSION));
0451     }
0452 
0453     void writeExe()
0454     {
0455         const int BUF_SIZE = 1023;
0456         char buf[BUF_SIZE + 1];
0457 
0458 #ifdef __linux__
0459         ssize_t size = readlink("/proc/self/exe", buf, BUF_SIZE);
0460 #elif defined(__FreeBSD__)
0461         int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1};
0462         size_t size = BUF_SIZE;
0463         sysctl(mib, 4, buf, &size, NULL, 0);
0464 #endif
0465 
0466         if (size > 0 && size < BUF_SIZE) {
0467             buf[size] = 0;
0468             s_data->out.write("x %x %s\n", size, buf);
0469         }
0470     }
0471 
0472     void writeCommandLine()
0473     {
0474         s_data->out.write("X");
0475         const int BUF_SIZE = 4096;
0476         char buf[BUF_SIZE + 1] = {0};
0477 
0478 #ifdef __linux__
0479         auto fd = open("/proc/self/cmdline", O_RDONLY);
0480         int bytesRead = read(fd, buf, BUF_SIZE);
0481         close(fd);
0482 #elif defined(__FreeBSD__)
0483         int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_ARGS, getpid()};
0484         size_t bytesRead = BUF_SIZE;
0485         sysctl(mib, 4, buf, &bytesRead, NULL, 0);
0486 #endif
0487 
0488         char* end = buf + bytesRead;
0489         for (char* p = buf; p < end;) {
0490             s_data->out.write(" %s", p);
0491             while (*p++)
0492                 ; // skip until start of next 0-terminated section
0493         }
0494 
0495         s_data->out.write("\n");
0496     }
0497 
0498     void writeSystemInfo()
0499     {
0500         s_data->out.writeHexLine('I', static_cast<size_t>(sysconf(_SC_PAGESIZE)),
0501                                  static_cast<size_t>(sysconf(_SC_PHYS_PAGES)));
0502     }
0503 
0504     void writeSuppressions()
0505     {
0506         if (!__lsan_default_suppressions)
0507             return;
0508 
0509         const char* suppressions = __lsan_default_suppressions();
0510         if (!suppressions)
0511             return;
0512 
0513         std::istringstream stream(suppressions);
0514         std::string line;
0515         while (std::getline(stream, line)) {
0516             s_data->out.write("S ");
0517             s_data->out.write(line);
0518             s_data->out.write("\n");
0519         }
0520     }
0521 
0522     void handleMalloc(void* ptr, size_t size, const Trace& trace)
0523     {
0524         if (!s_data || !s_data->out.canWrite()) {
0525             return;
0526         }
0527         updateModuleCache();
0528 
0529         const auto index = s_data->traceTree.index(trace, [](uintptr_t ip, uint32_t index) {
0530             // decrement addresses by one - otherwise we misattribute the cost to the wrong instruction
0531             // for some reason, it seems like we always get the instruction _after_ the one we are interested in
0532             // see also: https://github.com/libunwind/libunwind/issues/287
0533             // and https://bugs.kde.org/show_bug.cgi?id=439897
0534             --ip;
0535 
0536             return s_data->out.writeHexLine('t', ip, index);
0537         });
0538 
0539 #ifdef DEBUG_MALLOC_PTRS
0540         auto it = s_data->known.find(ptr);
0541         assert(it == s_data->known.end());
0542         s_data->known.insert(ptr);
0543 #endif
0544 
0545         s_data->out.writeHexLine('+', size, index, reinterpret_cast<uintptr_t>(ptr));
0546     }
0547 
0548     void handleFree(void* ptr)
0549     {
0550         if (!s_data || !s_data->out.canWrite()) {
0551             return;
0552         }
0553 
0554 #ifdef DEBUG_MALLOC_PTRS
0555         auto it = s_data->known.find(ptr);
0556         assert(it != s_data->known.end());
0557         s_data->known.erase(it);
0558 #endif
0559 
0560         s_data->out.writeHexLine('-', reinterpret_cast<uintptr_t>(ptr));
0561     }
0562 
0563     static bool isPaused()
0564     {
0565         return s_paused;
0566     }
0567 
0568     static void setPaused(bool state)
0569     {
0570         s_paused = state;
0571     }
0572 
0573 private:
0574     static int dl_iterate_phdr_callback(struct dl_phdr_info* info, size_t /*size*/, void* data)
0575     {
0576         auto heaptrack = reinterpret_cast<HeapTrack*>(data);
0577         const char* fileName = info->dlpi_name;
0578         if (!fileName || !fileName[0]) {
0579             fileName = "x";
0580         }
0581 
0582         debugLog<VerboseOutput>("dlopen_notify_callback: %s %zx", fileName, info->dlpi_addr);
0583 
0584         if (!heaptrack->s_data->out.write("m %x %s %zx", strlen(fileName), fileName, info->dlpi_addr)) {
0585             return 1;
0586         }
0587 
0588         for (int i = 0; i < info->dlpi_phnum; i++) {
0589             const auto& phdr = info->dlpi_phdr[i];
0590             if (phdr.p_type == PT_LOAD) {
0591                 if (!heaptrack->s_data->out.write(" %zx %zx", phdr.p_vaddr, phdr.p_memsz)) {
0592                     return 1;
0593                 }
0594             }
0595         }
0596 
0597         if (!heaptrack->s_data->out.write("\n")) {
0598             return 1;
0599         }
0600 
0601         return 0;
0602     }
0603 
0604     static void prepare_fork()
0605     {
0606         debugLog<MinimalOutput>("%s", "prepare_fork()");
0607         // don't do any custom malloc handling while inside fork
0608         RecursionGuard::isActive = true;
0609     }
0610 
0611     static void parent_fork()
0612     {
0613         debugLog<MinimalOutput>("%s", "parent_fork()");
0614         // the parent process can now continue its custom malloc tracking
0615         RecursionGuard::isActive = false;
0616     }
0617 
0618     static void child_fork()
0619     {
0620         debugLog<MinimalOutput>("%s", "child_fork()");
0621         // but the forked child process cleans up itself
0622         // this is important to prevent two processes writing to the same file
0623         s_data = nullptr;
0624         RecursionGuard::isActive = true;
0625     }
0626 
0627     void updateModuleCache()
0628     {
0629         if (!s_data || !s_data->out.canWrite() || !s_data->moduleCacheDirty) {
0630             return;
0631         }
0632         debugLog<MinimalOutput>("%s", "updateModuleCache()");
0633         if (!s_data->out.write("m 1 -\n")) {
0634             return;
0635         }
0636         dl_iterate_phdr(&dl_iterate_phdr_callback, this);
0637         s_data->moduleCacheDirty = false;
0638     }
0639 
0640     void writeError()
0641     {
0642         debugLog<MinimalOutput>("write error %d/%s", errno, strerror(errno));
0643         printBacktrace();
0644         shutdown();
0645     }
0646 
0647     class LockStatus
0648     {
0649     public:
0650         LockStatus(bool isLocked)
0651             : isLocked(isLocked)
0652         {
0653         }
0654         explicit operator bool() const
0655         {
0656             return isLocked;
0657         }
0658 
0659     private:
0660         bool isLocked = false;
0661     };
0662 
0663     /**
0664      * To prevent deadlocks on shutdown, we try to lock from the timer thread
0665      *
0666      * TODO: c++17 return std::optional<HeapTrack>
0667      */
0668     template <typename StopLockCheck>
0669     static LockStatus tryLock(StopLockCheck stopLockCheck)
0670     {
0671         debugLog<VeryVerboseOutput>("%s", "trying to acquire lock");
0672         while (!s_lock.try_lock()) {
0673             if (stopLockCheck()) {
0674                 return false;
0675             }
0676             this_thread::sleep_for(chrono::microseconds(1));
0677         }
0678         debugLog<VeryVerboseOutput>("%s", "lock acquired");
0679         return true;
0680     }
0681 
0682     /**
0683      * Create locked HeapTrack instance after a successful call to tryLock
0684      */
0685     HeapTrack(POTENTIALLY_UNUSED LockStatus lockStatus)
0686     {
0687         assert(lockStatus);
0688     }
0689 
0690     struct LockedData
0691     {
0692         LockedData(int out, heaptrack_callback_t stopCallback)
0693             : out(out)
0694             , stopCallback(stopCallback)
0695         {
0696 
0697             debugLog<MinimalOutput>("%s", "constructing LockedData");
0698 #ifdef __linux__
0699             procStatm = open("/proc/self/statm", O_RDONLY);
0700             if (procStatm == -1) {
0701                 fprintf(stderr, "WARNING: Failed to open /proc/self/statm for reading: %s.\n", strerror(errno));
0702             }
0703 #endif
0704 
0705             // ensure this utility thread is not handling any signals
0706             // our host application may assume only one specific thread
0707             // will handle the threads, if that's not the case things
0708             // seemingly break in non-obvious ways.
0709             // see also: https://bugs.kde.org/show_bug.cgi?id=378494
0710             sigset_t previousMask;
0711             sigset_t newMask;
0712             sigfillset(&newMask);
0713             if (pthread_sigmask(SIG_SETMASK, &newMask, &previousMask) != 0) {
0714                 fprintf(stderr, "WARNING: Failed to block signals, disabling timer thread.\n");
0715                 return;
0716             }
0717 
0718             // the mask we set above will be inherited by the thread that we spawn below
0719             timerThread = std::thread([&]() {
0720                 RecursionGuard::isActive = true;
0721                 debugLog<MinimalOutput>("%s", "timer thread started");
0722 
0723                 // now loop and repeatedly print the timestamp and RSS usage to the data stream
0724                 while (!stopTimerThread) {
0725                     // TODO: make interval customizable
0726                     this_thread::sleep_for(chrono::milliseconds(10));
0727 
0728                     const auto locked = tryLock([&] { return stopTimerThread.load(); });
0729                     if (!locked) {
0730                         break;
0731                     }
0732 
0733                     HeapTrack heaptrack(locked);
0734                     heaptrack.writeTimestamp();
0735                     heaptrack.writeRSS();
0736                 }
0737             });
0738 
0739             // now restore the previous mask as if nothing ever happened
0740             if (pthread_sigmask(SIG_SETMASK, &previousMask, nullptr) != 0) {
0741                 fprintf(stderr, "WARNING: Failed to restore the signal mask.\n");
0742             }
0743         }
0744 
0745         ~LockedData()
0746         {
0747             debugLog<MinimalOutput>("%s", "destroying LockedData");
0748             stopTimerThread = true;
0749             if (timerThread.joinable()) {
0750                 try {
0751                     timerThread.join();
0752                 } catch (const std::system_error&) {
0753                 }
0754             }
0755 
0756             out.close();
0757 
0758             if (procStatm != -1) {
0759                 close(procStatm);
0760             }
0761 
0762             if (stopCallback && (!s_atexit || s_forceCleanup)) {
0763                 stopCallback();
0764             }
0765             debugLog<MinimalOutput>("%s", "done destroying LockedData");
0766         }
0767 
0768         LineWriter out;
0769 
0770         /// /proc/self/statm file descriptor to read RSS value from
0771         int procStatm = -1;
0772 
0773         /**
0774          * Calls to dlopen/dlclose mark the cache as dirty.
0775          * When this happened, all modules and their section addresses
0776          * must be found again via dl_iterate_phdr before we output the
0777          * next instruction pointer. Otherwise, heaptrack_interpret might
0778          * encounter IPs of an unknown/invalid module.
0779          */
0780         bool moduleCacheDirty = true;
0781 
0782         TraceTree traceTree;
0783 
0784         atomic<bool> stopTimerThread {false};
0785         std::thread timerThread;
0786 
0787         heaptrack_callback_t stopCallback = nullptr;
0788 
0789 #ifdef DEBUG_MALLOC_PTRS
0790         tsl::robin_set<void*> known;
0791 #endif
0792     };
0793 
0794     static std::mutex s_lock;
0795     static LockedData* s_data;
0796 
0797 private:
0798     static std::atomic<bool> s_paused;
0799 };
0800 
0801 std::mutex HeapTrack::s_lock;
0802 HeapTrack::LockedData* HeapTrack::s_data {nullptr};
0803 std::atomic<bool> HeapTrack::s_paused {false};
0804 }
0805 
0806 static void heaptrack_realloc_impl(void* ptr_in, size_t size, void* ptr_out)
0807 {
0808     if (!HeapTrack::isPaused() && ptr_out && !RecursionGuard::isActive) {
0809         RecursionGuard guard;
0810 
0811         debugLog<VeryVerboseOutput>("heaptrack_realloc(%p, %zu, %p)", ptr_in, size, ptr_out);
0812 
0813         Trace trace;
0814         trace.fill(2 + HEAPTRACK_DEBUG_BUILD * 3);
0815 
0816         HeapTrack::op(guard, [&](HeapTrack& heaptrack) {
0817             if (ptr_in) {
0818                 heaptrack.handleFree(ptr_in);
0819             }
0820             heaptrack.handleMalloc(ptr_out, size, trace);
0821         });
0822     }
0823 }
0824 
0825 extern "C" {
0826 
0827 void heaptrack_init(const char* outputFileName, heaptrack_callback_t initBeforeCallback,
0828                     heaptrack_callback_initialized_t initAfterCallback, heaptrack_callback_t stopCallback)
0829 {
0830     RecursionGuard guard;
0831     // initialize
0832     startTime();
0833     s_forceCleanup.store(false);
0834 
0835     debugLog<MinimalOutput>("heaptrack_init(%s)", outputFileName);
0836 
0837     POTENTIALLY_UNUSED auto ret = HeapTrack::op(guard, [&](HeapTrack& heaptrack) {
0838         heaptrack.initialize(outputFileName, initBeforeCallback, initAfterCallback, stopCallback);
0839     });
0840     assert(ret);
0841 }
0842 
0843 void heaptrack_stop()
0844 {
0845     RecursionGuard guard;
0846 
0847     debugLog<MinimalOutput>("%s", "heaptrack_stop()");
0848 
0849     assert(!s_forceCleanup);
0850     POTENTIALLY_UNUSED auto ret = HeapTrack::op(guard, [&](HeapTrack& heaptrack) {
0851         if (!s_atexit) {
0852             s_forceCleanup.store(true);
0853         }
0854 
0855         heaptrack.shutdown();
0856     });
0857     assert(ret);
0858 }
0859 
0860 void heaptrack_pause()
0861 {
0862     HeapTrack::setPaused(true);
0863 }
0864 
0865 void heaptrack_resume()
0866 {
0867     HeapTrack::setPaused(false);
0868 }
0869 
0870 void heaptrack_malloc(void* ptr, size_t size)
0871 {
0872     if (!HeapTrack::isPaused() && ptr && !RecursionGuard::isActive) {
0873         RecursionGuard guard;
0874 
0875         debugLog<VeryVerboseOutput>("heaptrack_malloc(%p, %zu)", ptr, size);
0876 
0877         Trace trace;
0878         trace.fill(2 + HEAPTRACK_DEBUG_BUILD * 2);
0879 
0880         HeapTrack::op(guard, [&](HeapTrack& heaptrack) { heaptrack.handleMalloc(ptr, size, trace); });
0881     }
0882 }
0883 
0884 void heaptrack_free(void* ptr)
0885 {
0886     if (!HeapTrack::isPaused() && ptr && !RecursionGuard::isActive) {
0887         RecursionGuard guard;
0888 
0889         debugLog<VeryVerboseOutput>("heaptrack_free(%p)", ptr);
0890 
0891         HeapTrack::op(guard, [&](HeapTrack& heaptrack) { heaptrack.handleFree(ptr); });
0892     }
0893 }
0894 
0895 void heaptrack_realloc(void* ptr_in, size_t size, void* ptr_out)
0896 {
0897     heaptrack_realloc_impl(ptr_in, size, ptr_out);
0898 }
0899 
0900 void heaptrack_realloc2(uintptr_t ptr_in, size_t size, uintptr_t ptr_out)
0901 {
0902     heaptrack_realloc_impl(reinterpret_cast<void*>(ptr_in), size, reinterpret_cast<void*>(ptr_out));
0903 }
0904 
0905 void heaptrack_invalidate_module_cache()
0906 {
0907     RecursionGuard guard;
0908 
0909     debugLog<VerboseOutput>("%s", "heaptrack_invalidate_module_cache()");
0910 
0911     HeapTrack::op(guard, [&](HeapTrack& heaptrack) { heaptrack.invalidateModuleCache(); });
0912 }
0913 
0914 void heaptrack_warning(heaptrack_warning_callback_t callback)
0915 {
0916     RecursionGuard guard;
0917 
0918     debugLog<WarningOutput>(callback);
0919 }
0920 }