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 }