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 }