Warning, file /sdk/heaptrack/src/track/heaptrack_preload.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002     SPDX-FileCopyrightText: 2014-2017 Milian Wolff <mail@milianw.de>
0003 
0004     SPDX-License-Identifier: LGPL-2.1-or-later
0005 */
0006 
0007 #include "libheaptrack.h"
0008 #include "util/config.h"
0009 
0010 #include <cstdio>
0011 #include <cstdlib>
0012 #include <cstring>
0013 #include <dlfcn.h>
0014 #include <unistd.h>
0015 
0016 #include <atomic>
0017 #include <type_traits>
0018 
0019 using namespace std;
0020 
0021 #if defined(_ISOC11_SOURCE)
0022 #define HAVE_ALIGNED_ALLOC 1
0023 #else
0024 #define HAVE_ALIGNED_ALLOC 0
0025 #endif
0026 
0027 // NOTE: adding noexcept to C functions is a hard error in clang++
0028 //       (but not even a warning in GCC, even with -Wall)
0029 #if defined(__GNUC__) && !defined(__clang__)
0030 #define LIBC_FUN_ATTRS noexcept
0031 #else
0032 #define LIBC_FUN_ATTRS
0033 #endif
0034 
0035 extern "C" {
0036 
0037 // Foward declare mimalloc (https://github.com/microsoft/mimalloc) functions so we don't need to include its .h.
0038 void* mi_malloc(size_t size) LIBC_FUN_ATTRS;
0039 void* mi_calloc(size_t count, size_t size) LIBC_FUN_ATTRS;
0040 void* mi_realloc(void* p, size_t newsize) LIBC_FUN_ATTRS;
0041 void mi_free(void* p) LIBC_FUN_ATTRS;
0042 }
0043 
0044 namespace {
0045 
0046 namespace hooks {
0047 
0048 enum class HookType
0049 {
0050     Required,
0051     Optional
0052 };
0053 
0054 template <typename Signature, typename Base, HookType Type>
0055 struct hook
0056 {
0057     Signature original = nullptr;
0058 
0059     void init() noexcept
0060     {
0061         auto ret = dlsym(RTLD_NEXT, Base::identifier);
0062         if (!ret && Type == HookType::Optional) {
0063             return;
0064         }
0065         if (!ret) {
0066             fprintf(stderr, "Could not find original function %s\n", Base::identifier);
0067             abort();
0068         }
0069         original = reinterpret_cast<Signature>(ret);
0070     }
0071 
0072     template <typename... Args>
0073     auto operator()(Args... args) const noexcept -> decltype(original(args...))
0074     {
0075         return original(args...);
0076     }
0077 
0078     explicit operator bool() const noexcept
0079     {
0080         return original;
0081     }
0082 };
0083 
0084 #define HOOK(name, type)                                                                                               \
0085     struct name##_t : public hook<decltype(&::name), name##_t, type>                                                   \
0086     {                                                                                                                  \
0087         static constexpr const char* identifier = #name;                                                               \
0088     } name
0089 
0090 #pragma GCC diagnostic push
0091 #pragma GCC diagnostic ignored "-Wignored-attributes"
0092 
0093 HOOK(malloc, HookType::Required);
0094 HOOK(free, HookType::Required);
0095 HOOK(calloc, HookType::Required);
0096 #if HAVE_CFREE
0097 HOOK(cfree, HookType::Optional);
0098 #endif
0099 HOOK(realloc, HookType::Required);
0100 HOOK(posix_memalign, HookType::Optional);
0101 #if HAVE_VALLOC
0102 HOOK(valloc, HookType::Optional);
0103 #endif
0104 #if HAVE_ALIGNED_ALLOC
0105 HOOK(aligned_alloc, HookType::Optional);
0106 #endif
0107 HOOK(dlopen, HookType::Required);
0108 HOOK(dlclose, HookType::Required);
0109 
0110 // mimalloc functions
0111 HOOK(mi_malloc, HookType::Optional);
0112 HOOK(mi_calloc, HookType::Optional);
0113 HOOK(mi_realloc, HookType::Optional);
0114 HOOK(mi_free, HookType::Optional);
0115 
0116 #pragma GCC diagnostic pop
0117 #undef HOOK
0118 
0119 /**
0120  * Dummy implementation, since the call to dlsym from findReal triggers a call
0121  * to calloc.
0122  *
0123  * This is only called at startup and will eventually be replaced by the
0124  * "proper" calloc implementation.
0125  */
0126 struct DummyPool
0127 {
0128     static const constexpr size_t MAX_SIZE = 1024;
0129     char buf[MAX_SIZE] = {0};
0130     size_t offset = 0;
0131 
0132     bool isDummyAllocation(void* ptr) noexcept
0133     {
0134         return ptr >= buf && ptr < buf + MAX_SIZE;
0135     }
0136 
0137     void* alloc(size_t num, size_t size) noexcept
0138     {
0139         size_t oldOffset = offset;
0140         offset += num * size;
0141         if (offset >= MAX_SIZE) {
0142             fprintf(stderr,
0143                     "failed to initialize, dummy calloc buf size exhausted: "
0144                     "%zu requested, %zu available\n",
0145                     offset, MAX_SIZE);
0146             abort();
0147         }
0148         return buf + oldOffset;
0149     }
0150 };
0151 
0152 DummyPool& dummyPool()
0153 {
0154     static DummyPool pool;
0155     return pool;
0156 }
0157 
0158 void* dummy_calloc(size_t num, size_t size) noexcept
0159 {
0160     return dummyPool().alloc(num, size);
0161 }
0162 
0163 void init()
0164 {
0165     // heaptrack_init itself calls calloc via std::mutex/_libpthread_init on FreeBSD
0166     hooks::calloc.original = &dummy_calloc;
0167     hooks::calloc.init();
0168     heaptrack_init(
0169         getenv("DUMP_HEAPTRACK_OUTPUT"),
0170         [] {
0171             hooks::dlopen.init();
0172             hooks::dlclose.init();
0173             hooks::malloc.init();
0174             hooks::free.init();
0175             hooks::calloc.init();
0176 #if HAVE_CFREE
0177             hooks::cfree.init();
0178 #endif
0179             hooks::realloc.init();
0180             hooks::posix_memalign.init();
0181 #if HAVE_VALLOC
0182             hooks::valloc.init();
0183 #endif
0184 #if HAVE_ALIGNED_ALLOC
0185             hooks::aligned_alloc.init();
0186 #endif
0187 
0188             // mimalloc functions
0189             hooks::mi_malloc.init();
0190             hooks::mi_calloc.init();
0191             hooks::mi_realloc.init();
0192             hooks::mi_free.init();
0193 
0194             // cleanup environment to prevent tracing of child apps
0195             unsetenv("LD_PRELOAD");
0196             unsetenv("DUMP_HEAPTRACK_OUTPUT");
0197         },
0198         nullptr, nullptr);
0199 }
0200 }
0201 }
0202 
0203 extern "C" {
0204 
0205 /// TODO: memalign, pvalloc, ...?
0206 
0207 void* malloc(size_t size) LIBC_FUN_ATTRS
0208 {
0209     if (!hooks::malloc) {
0210         hooks::init();
0211     }
0212 
0213     void* ptr = hooks::malloc(size);
0214     heaptrack_malloc(ptr, size);
0215     return ptr;
0216 }
0217 
0218 void free(void* ptr) LIBC_FUN_ATTRS
0219 {
0220     if (!hooks::free) {
0221         hooks::init();
0222     }
0223 
0224     if (hooks::dummyPool().isDummyAllocation(ptr)) {
0225         return;
0226     }
0227 
0228     // call handler before handing over the real free implementation
0229     // to ensure the ptr is not reused in-between and thus the output
0230     // stays consistent
0231     heaptrack_free(ptr);
0232 
0233     hooks::free(ptr);
0234 }
0235 
0236 void* realloc(void* ptr, size_t size) LIBC_FUN_ATTRS
0237 {
0238     if (!hooks::realloc) {
0239         hooks::init();
0240     }
0241 
0242     void* ret = hooks::realloc(ptr, size);
0243 
0244     if (ret) {
0245         heaptrack_realloc(ptr, size, ret);
0246     }
0247 
0248     return ret;
0249 }
0250 
0251 void* calloc(size_t num, size_t size) LIBC_FUN_ATTRS
0252 {
0253     if (!hooks::calloc) {
0254         hooks::init();
0255     }
0256 
0257     void* ret = hooks::calloc(num, size);
0258 
0259     if (ret) {
0260         heaptrack_malloc(ret, num * size);
0261     }
0262 
0263     return ret;
0264 }
0265 
0266 #if HAVE_CFREE
0267 void cfree(void* ptr) LIBC_FUN_ATTRS
0268 {
0269     if (!hooks::cfree) {
0270         hooks::init();
0271     }
0272 
0273     // call handler before handing over the real free implementation
0274     // to ensure the ptr is not reused in-between and thus the output
0275     // stays consistent
0276     if (ptr) {
0277         heaptrack_free(ptr);
0278     }
0279 
0280     hooks::cfree(ptr);
0281 }
0282 #endif
0283 
0284 int posix_memalign(void** memptr, size_t alignment, size_t size) LIBC_FUN_ATTRS
0285 {
0286     if (!hooks::posix_memalign) {
0287         hooks::init();
0288     }
0289 
0290     int ret = hooks::posix_memalign(memptr, alignment, size);
0291 
0292     if (!ret) {
0293         heaptrack_malloc(*memptr, size);
0294     }
0295 
0296     return ret;
0297 }
0298 
0299 #if HAVE_ALIGNED_ALLOC
0300 void* aligned_alloc(size_t alignment, size_t size) LIBC_FUN_ATTRS
0301 {
0302     if (!hooks::aligned_alloc) {
0303         hooks::init();
0304     }
0305 
0306     void* ret = hooks::aligned_alloc(alignment, size);
0307 
0308     if (ret) {
0309         heaptrack_malloc(ret, size);
0310     }
0311 
0312     return ret;
0313 }
0314 #endif
0315 
0316 #if HAVE_VALLOC
0317 void* valloc(size_t size) LIBC_FUN_ATTRS
0318 {
0319     if (!hooks::valloc) {
0320         hooks::init();
0321     }
0322 
0323     void* ret = hooks::valloc(size);
0324 
0325     if (ret) {
0326         heaptrack_malloc(ret, size);
0327     }
0328 
0329     return ret;
0330 }
0331 #endif
0332 
0333 void* dlopen(const char* filename, int flag) LIBC_FUN_ATTRS
0334 {
0335     if (!hooks::dlopen) {
0336         hooks::init();
0337     }
0338 
0339 #ifdef RTLD_DEEPBIND
0340     if (filename && flag & RTLD_DEEPBIND) {
0341         heaptrack_warning([](FILE* out) {
0342             fprintf(out,
0343                     "Detected dlopen call with RTLD_DEEPBIND which breaks function call interception. "
0344                     "Heaptrack will drop this flag. If your application relies on it, try to run `heaptrack "
0345                     "--use-inject` instead.");
0346         });
0347         flag &= ~RTLD_DEEPBIND;
0348     }
0349 #endif
0350 
0351     void* ret = hooks::dlopen(filename, flag);
0352 
0353     if (ret) {
0354         heaptrack_invalidate_module_cache();
0355     }
0356 
0357     return ret;
0358 }
0359 
0360 int dlclose(void* handle) LIBC_FUN_ATTRS
0361 {
0362     if (!hooks::dlclose) {
0363         hooks::init();
0364     }
0365 
0366     int ret = hooks::dlclose(handle);
0367 
0368     if (!ret) {
0369         heaptrack_invalidate_module_cache();
0370     }
0371 
0372     return ret;
0373 }
0374 
0375 // mimalloc functions, implementations just copied from above and names changed
0376 void* mi_malloc(size_t size) LIBC_FUN_ATTRS
0377 {
0378     if (!hooks::mi_malloc) {
0379         hooks::init();
0380     }
0381 
0382     void* ptr = hooks::mi_malloc(size);
0383     heaptrack_malloc(ptr, size);
0384     return ptr;
0385 }
0386 
0387 void* mi_realloc(void* ptr, size_t size) LIBC_FUN_ATTRS
0388 {
0389     if (!hooks::mi_realloc) {
0390         hooks::init();
0391     }
0392 
0393     void* ret = hooks::mi_realloc(ptr, size);
0394 
0395     if (ret) {
0396         heaptrack_realloc(ptr, size, ret);
0397     }
0398 
0399     return ret;
0400 }
0401 
0402 void* mi_calloc(size_t num, size_t size) LIBC_FUN_ATTRS
0403 {
0404     if (!hooks::mi_calloc) {
0405         hooks::init();
0406     }
0407 
0408     void* ret = hooks::mi_calloc(num, size);
0409 
0410     if (ret) {
0411         heaptrack_malloc(ret, num * size);
0412     }
0413 
0414     return ret;
0415 }
0416 
0417 void mi_free(void* ptr) LIBC_FUN_ATTRS
0418 {
0419     if (!hooks::mi_free) {
0420         hooks::init();
0421     }
0422 
0423     if (hooks::dummyPool().isDummyAllocation(ptr)) {
0424         return;
0425     }
0426 
0427     // call handler before handing over the real free implementation
0428     // to ensure the ptr is not reused in-between and thus the output
0429     // stays consistent
0430     heaptrack_free(ptr);
0431 
0432     hooks::mi_free(ptr);
0433 }
0434 }