From f8d5b8e679523c1fcfd7623411009c97c65dfa4f Mon Sep 17 00:00:00 2001 From: niels-beier Date: Tue, 5 Nov 2024 11:54:01 +0100 Subject: [PATCH 01/26] Add Never freed pointer and double freed pointer detection and report --- CMakeLists.txt | 6 +++++ example_04.cc | 9 ++++++++ example_05.cc | 10 +++++++++ memstats.cc | 60 ++++++++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 example_04.cc create mode 100644 example_05.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index c82c5be..6985ec5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -101,4 +101,10 @@ if(memstats_IS_TOP_LEVEL) target_link_libraries(example_03 PUBLIC MemStats::MemStats) target_compile_features(example_03 PUBLIC cxx_std_11) endif() + + add_executable(example_04 example_04.cc) + target_link_libraries(example_04 PUBLIC MemStats::MemStats) + + add_executable(example_05 example_05.cc) + target_link_libraries(example_05 PUBLIC MemStats::MemStats) endif() diff --git a/example_04.cc b/example_04.cc new file mode 100644 index 0000000..3c123c3 --- /dev/null +++ b/example_04.cc @@ -0,0 +1,9 @@ +volatile void * do_not_optimize; + +int main() +{ + auto val = new int; + do_not_optimize = val; + + return 0; +} diff --git a/example_05.cc b/example_05.cc new file mode 100644 index 0000000..1ae54d3 --- /dev/null +++ b/example_05.cc @@ -0,0 +1,10 @@ +volatile void * do_not_optimize; + +int main() { + int* ptr = new int; + do_not_optimize = ptr; + delete ptr; + delete ptr; + + return 0; +} \ No newline at end of file diff --git a/memstats.cc b/memstats.cc index b4cdd39..e696371 100644 --- a/memstats.cc +++ b/memstats.cc @@ -1,3 +1,4 @@ +#include #include #include #include @@ -301,11 +302,35 @@ void print_legend() << (i + 1 == str_precentage.second ? ']' : ')') << std::endl; } +void report_never_freed() { + std::cout << "\nNever freed pointers:\n"; + + // Report allocations without deallocations + for (int i = 0; i < memstats_events.size(); ++i) { + // Skip deallocations (only allocations can have size > 0) + if (memstats_events[i].size == 0) continue; + bool freed = false; + for (int j = i + 1; j < memstats_events.size(); ++j) { + if (memstats_events[i].ptr == memstats_events[j].ptr && memstats_events[j].size == 0) { + freed = true; + } + } + + if (!freed) { + std::cout << "Pointer " << memstats_events[i].ptr << " was never freed in Thread " << memstats_events[i].thread << "." << std::endl; +#if MEMSTAT_HAVE_STACKTRACE + std::cout << "Current stacktrace:\n" << memstats_events[i].stacktrace << std::endl; +#endif + } + } +} + void memstats_report(const char * report_name) { auto lock = std::unique_lock{memstats_lock}; if (memstats_events.size() == 0) return; + std::cout << "\n------------------- MemStats " << report_name << " -------------------\n"; struct Stats { @@ -314,6 +339,12 @@ void memstats_report(const char * report_name) }; Stats global_stats; unordered_map thread_stats; + unordered_map> ptr_collec; + struct PtrStats { + int times_freed; + const void *ptr; + }; + std::vector ptr_stats; #if MEMSTAT_HAVE_STACKTRACE unordered_map>, Stats> stacktrace_stats; unordered_map stacktrace_entry_stats; @@ -333,14 +364,24 @@ void memstats_report(const char * report_name) register_stats(global_stats); register_stats(thread_stats[info.thread]); + if (ptr_collec[info.ptr].second && info.size > 0) { + PtrStats stats = {ptr_collec[info.ptr].first, info.ptr}; + ptr_stats.push_back(stats); + ptr_collec[info.ptr].first = 0; + ptr_collec[info.ptr].second = false; + } else if (info.size == 0) { + ++ptr_collec[info.ptr].first; + ptr_collec[info.ptr].second = true; + } + + + #if MEMSTAT_HAVE_STACKTRACE register_stats(stacktrace_stats[info.stacktrace]); for (auto entry : info.stacktrace) register_stats(stacktrace_entry_stats[entry]); #endif } - // clean up vector - memstats_events.clear(); static const std::array metric_prefix{' ', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y', 'R', 'Q'}; auto bytes_to_string = [&](std::size_t bytes) @@ -412,6 +453,21 @@ void memstats_report(const char * report_name) } } #endif + + report_never_freed(); + + std::cout << "\nDouble freed pointers:\n"; + + // Report double deallocations + for (const auto& entry : ptr_stats) + { + if (entry.times_freed > 1) + std::cout << "Pointer " << entry.ptr << " was freed " << entry.times_freed << " times." << std::endl; + } + + // clean up vector + memstats_events.clear(); + // avoid printing legend several times, so call once at exit static std::once_flag legend_flag; std::call_once(legend_flag, []() From 2b1f3543468eaee991fc4fe860349cc38bf6165c Mon Sep 17 00:00:00 2001 From: niels-beier Date: Tue, 5 Nov 2024 11:55:22 +0100 Subject: [PATCH 02/26] Ignore .idea folder --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index a08f415..5bf9658 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /build* /.vscode/ +/.idea/ From 15c9621d844e98a4eeb2365455a5faa9e0c7571c Mon Sep 17 00:00:00 2001 From: niels-beier Date: Mon, 2 Dec 2024 14:20:33 +0100 Subject: [PATCH 03/26] Add acquire/release semantic for memstats_events such that order of events should be in execution order --- memstats.cc | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/memstats.cc b/memstats.cc index e696371..84f500f 100644 --- a/memstats.cc +++ b/memstats.cc @@ -130,6 +130,7 @@ MEMSTATS_CONSTINIT #endif static std::vector> memstats_events = {}; #endif +std::atomic memstats_events_ready = true; // Zero- and dynamic-initialization of a thread-local variable does not necessarily happen on any order related to the global ones static thread_local bool memstats_instrumentation_thread = init_memstats_instrumentation_thread(); @@ -262,6 +263,11 @@ unsigned short memstats_bins() void MemStatsInfo::record(void *ptr, std::size_t sz) { + // Wait until all writing steps to memstats_events are done + while (!memstats_events_ready.load(std::memory_order_acquire)) + std::this_thread::yield(); + + memstats_events_ready.store(false, std::memory_order_release); auto time = std::chrono::high_resolution_clock::now(); MemStatsInfo info; info.ptr = ptr; @@ -275,6 +281,8 @@ void MemStatsInfo::record(void *ptr, std::size_t sz) std::unique_lock lk{memstats_lock}; #endif memstats_events.emplace_back(std::move(info)); + + memstats_events_ready.store(true, std::memory_order_release); } template From 4942c116dbb5f343d56ee501eaeac11b639ef2c7 Mon Sep 17 00:00:00 2001 From: niels-beier Date: Tue, 3 Dec 2024 13:34:56 +0100 Subject: [PATCH 04/26] Rename never frees to memory leaks and check if threads are the same --- memstats.cc | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/memstats.cc b/memstats.cc index 84f500f..09310f0 100644 --- a/memstats.cc +++ b/memstats.cc @@ -310,8 +310,8 @@ void print_legend() << (i + 1 == str_precentage.second ? ']' : ')') << std::endl; } -void report_never_freed() { - std::cout << "\nNever freed pointers:\n"; +void report_memory_leaks() { + std::cout << "\nMemory leaks:\n"; // Report allocations without deallocations for (int i = 0; i < memstats_events.size(); ++i) { @@ -319,7 +319,7 @@ void report_never_freed() { if (memstats_events[i].size == 0) continue; bool freed = false; for (int j = i + 1; j < memstats_events.size(); ++j) { - if (memstats_events[i].ptr == memstats_events[j].ptr && memstats_events[j].size == 0) { + if (memstats_events[i].ptr == memstats_events[j].ptr && memstats_events[j].size == 0 && memstats_events[i].thread == memstats_events[j].thread) { freed = true; } } @@ -347,7 +347,7 @@ void memstats_report(const char * report_name) }; Stats global_stats; unordered_map thread_stats; - unordered_map> ptr_collec; + unordered_map>> ptr_collec; struct PtrStats { int times_freed; const void *ptr; @@ -357,6 +357,7 @@ void memstats_report(const char * report_name) unordered_map>, Stats> stacktrace_stats; unordered_map stacktrace_entry_stats; #endif + for (const MemStatsInfo &info : memstats_events) { auto register_stats = [&](Stats &stats) @@ -372,14 +373,14 @@ void memstats_report(const char * report_name) register_stats(global_stats); register_stats(thread_stats[info.thread]); - if (ptr_collec[info.ptr].second && info.size > 0) { - PtrStats stats = {ptr_collec[info.ptr].first, info.ptr}; + if (ptr_collec[info.ptr][info.thread].second && info.size > 0) { + PtrStats stats = {ptr_collec[info.ptr][info.thread].first, info.ptr}; ptr_stats.push_back(stats); - ptr_collec[info.ptr].first = 0; - ptr_collec[info.ptr].second = false; + ptr_collec[info.ptr][info.thread].first = 0; + ptr_collec[info.ptr][info.thread].second = false; } else if (info.size == 0) { - ++ptr_collec[info.ptr].first; - ptr_collec[info.ptr].second = true; + ++ptr_collec[info.ptr][info.thread].first; + ptr_collec[info.ptr][info.thread].second = true; } @@ -462,7 +463,7 @@ void memstats_report(const char * report_name) } #endif - report_never_freed(); + report_memory_leaks(); std::cout << "\nDouble freed pointers:\n"; From ecfcdf111c3187437c51be019322c53e30d44b52 Mon Sep 17 00:00:00 2001 From: niels-beier Date: Sat, 7 Dec 2024 18:19:00 +0100 Subject: [PATCH 05/26] Remove acquire and release semantics and introduce lock for new and delete --- memstats.cc | 258 +++++++++++++++++++++++----------------------------- 1 file changed, 114 insertions(+), 144 deletions(-) diff --git a/memstats.cc b/memstats.cc index 09310f0..86a8ab5 100644 --- a/memstats.cc +++ b/memstats.cc @@ -19,7 +19,9 @@ #include #if __has_include() + #include + #endif #if MEMSTAT_HAVE_STACKTRACE @@ -39,19 +41,19 @@ #include "memstats.hh" // all allocations within this library need to use malloc/free instad of new/delete -template -class MallocAllocator -{ +template +class MallocAllocator { public: using value_type = T; constexpr MallocAllocator() noexcept = default; - template + + template constexpr MallocAllocator(const MallocAllocator &) noexcept {} + ~MallocAllocator() noexcept = default; - T *allocate(std::size_t n) - { + T *allocate(std::size_t n) { if (n > this->max_size()) throw std::bad_alloc(); @@ -61,26 +63,24 @@ class MallocAllocator return ret; } - void deallocate(T *p, std::size_t) - { + void deallocate(T *p, std::size_t) { std::free(p); } - std::size_t max_size() const noexcept - { + std::size_t max_size() const noexcept { return std::size_t(-1) / sizeof(T); } - friend bool operator==(const MallocAllocator&, const MallocAllocator&){ + friend bool operator==(const MallocAllocator &, const MallocAllocator &) { return true; } - friend bool operator!=(const MallocAllocator&, const MallocAllocator&){ + + friend bool operator!=(const MallocAllocator &, const MallocAllocator &) { return false; } }; -struct MemStatsInfo -{ +struct MemStatsInfo { const void *ptr = nullptr; std::size_t size = 0; std::chrono::high_resolution_clock::time_point time = {}; @@ -92,15 +92,14 @@ struct MemStatsInfo static void record(void *ptr, std::size_t sz = 0); }; -bool init_memstats_instrumentation_thread() -{ - if (char *ptr = std::getenv("MEMSTATS_THREAD_INSTRUMENTATION_INIT")) - { +bool init_memstats_instrumentation_thread() { + if (char *ptr = std::getenv("MEMSTATS_THREAD_INSTRUMENTATION_INIT")) { if (std::strcmp(ptr, "true") == 0 or std::strcmp(ptr, "1") == 0) return true; if (std::strcmp(ptr, "false") == 0 or std::strcmp(ptr, "0") == 0) return false; - std::cerr << "Option 'MEMSTATS_THREAD_INSTRUMENTATION_INIT=" << ptr << "' not known. Fallback on default 'false'\n"; + std::cerr << "Option 'MEMSTATS_THREAD_INSTRUMENTATION_INIT=" << ptr + << "' not known. Fallback on default 'false'\n"; } return false; } @@ -130,17 +129,18 @@ MEMSTATS_CONSTINIT #endif static std::vector> memstats_events = {}; #endif -std::atomic memstats_events_ready = true; +std::mutex memstats_events_mutex; + // Zero- and dynamic-initialization of a thread-local variable does not necessarily happen on any order related to the global ones static thread_local bool memstats_instrumentation_thread = init_memstats_instrumentation_thread(); // guard thread-local variable to instrument further delets at exit -bool init_memstats_instrumentation_thread_guard() -{ - std::atexit([]{ memstats_instrumentation_thread = false; }); +bool init_memstats_instrumentation_thread_guard() { + std::atexit([] { memstats_instrumentation_thread = false; }); return true; } + const static thread_local bool memstats_instrumentation_thread_guard = init_memstats_instrumentation_thread_guard(); // We need to make absolutely sure this is constinit so that 'memstats_instrumentation_global' is const-initialized, @@ -151,18 +151,17 @@ MEMSTATS_CONSTINIT static std::atomic memstats_instrumentation_global{fals #error "MemStats needs a conforming C++ standard library where 'std::atomic' can be const-initialized, i.e. its constructor is 'constexpr'!" #endif -bool init_memstats_instrumentation_guard() -{ +bool init_memstats_instrumentation_guard() { bool instrument = false; // Note this variable is const-initialized to false. Here we change it to true and syncronize other threads during dynamic initialization - if (char *ptr = std::getenv("MEMSTATS_ENABLE_INSTRUMENTATION")) - { + if (char *ptr = std::getenv("MEMSTATS_ENABLE_INSTRUMENTATION")) { if (std::strcmp(ptr, "true") == 0 or std::strcmp(ptr, "1") == 0) instrument = true; else if (std::strcmp(ptr, "false") == 0 or std::strcmp(ptr, "0") == 0) instrument = false; else - std::cerr << "Option 'MEMSTATS_ENABLE_INSTRUMENTATION=" << ptr << "' not known. Fallback on default 'false'\n"; + std::cerr << "Option 'MEMSTATS_ENABLE_INSTRUMENTATION=" << ptr + << "' not known. Fallback on default 'false'\n"; } memstats_instrumentation_global.store(instrument, std::memory_order_release); return instrument; @@ -175,26 +174,26 @@ bool init_memstats_instrumentation_guard() // dynamic-initialized in the correct order by delaying its initialization by a non-constexpr function. static bool memstats_instrumentation_guard = init_memstats_instrumentation_guard(); -bool init_memstats_at_exit() -{ +bool init_memstats_at_exit() { static std::once_flag report_flag; std::call_once(report_flag, - []{ std::atexit([]{ - memstats_instrumentation_global.store(false, std::memory_order_release); - bool do_report_at_exit = true; - if (char *ptr = std::getenv("MEMSTATS_REPORT_AT_EXIT")) - { - if (std::strcmp(ptr, "true") == 0 or std::strcmp(ptr, "1") == 0) - do_report_at_exit = true; - else if (std::strcmp(ptr, "false") == 0 or std::strcmp(ptr, "0") == 0) - do_report_at_exit = false; - else - std::cerr << "Option 'MEMSTATS_REPORT_AT_EXIT=" << ptr << "' not known. Fallback on default 'true'\n"; - } - if (do_report_at_exit) - memstats_report("default"); - }); - }); + [] { + std::atexit([] { + memstats_instrumentation_global.store(false, std::memory_order_release); + bool do_report_at_exit = true; + if (char *ptr = std::getenv("MEMSTATS_REPORT_AT_EXIT")) { + if (std::strcmp(ptr, "true") == 0 or std::strcmp(ptr, "1") == 0) + do_report_at_exit = true; + else if (std::strcmp(ptr, "false") == 0 or std::strcmp(ptr, "0") == 0) + do_report_at_exit = false; + else + std::cerr << "Option 'MEMSTATS_REPORT_AT_EXIT=" << ptr + << "' not known. Fallback on default 'true'\n"; + } + if (do_report_at_exit) + memstats_report("default"); + }); + }); return true; } @@ -217,23 +216,23 @@ static const bool memstats_at_exit_guard = init_memstats_at_exit(); */ // bin representation of percentage from 0% to 100% -static const std::array memstats_str_precentage_punctuation{" ", ".", ":", "!"}; -static const std::array memstats_str_precentage_circle{" ", ".", "o", "O"}; -static const std::array memstats_str_precentage_shadow{" ", "░", "▒", "▓", "█"}; -static const std::array memstats_str_precentage_wire{" ", "-", "~", "=", "#"}; -static const std::array memstats_str_precentage_box{" ", "▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"}; -static const std::array memstats_str_precentage_number{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"}; - -std::pair memstats_str_hist_representation() -{ - if (const char *ptr = std::getenv("MEMSTATS_HISTOGRAM_REPRESENTATION")) - { +static const std::array memstats_str_precentage_punctuation{" ", ".", ":", "!"}; +static const std::array memstats_str_precentage_circle{" ", ".", "o", "O"}; +static const std::array memstats_str_precentage_shadow{" ", "░", "▒", "▓", "█"}; +static const std::array memstats_str_precentage_wire{" ", "-", "~", "=", "#"}; +static const std::array memstats_str_precentage_box{" ", "▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"}; +static const std::array memstats_str_precentage_number{"0", "1", "2", "3", "4", "5", "6", "7", "8", + "9"}; + +std::pair memstats_str_hist_representation() { + if (const char *ptr = std::getenv("MEMSTATS_HISTOGRAM_REPRESENTATION")) { if (std::strcmp(ptr, "box") == 0) return std::make_pair(memstats_str_precentage_box.data(), memstats_str_precentage_box.size()); if (std::strcmp(ptr, "number") == 0) return std::make_pair(memstats_str_precentage_number.data(), memstats_str_precentage_number.size()); if (std::strcmp(ptr, "punctuation") == 0) - return std::make_pair(memstats_str_precentage_punctuation.data(), memstats_str_precentage_punctuation.size()); + return std::make_pair(memstats_str_precentage_punctuation.data(), + memstats_str_precentage_punctuation.size()); if (std::strcmp(ptr, "shadow") == 0) return std::make_pair(memstats_str_precentage_shadow.data(), memstats_str_precentage_shadow.size()); if (std::strcmp(ptr, "wire") == 0) @@ -245,29 +244,19 @@ std::pair memstats_str_hist_representation() return std::make_pair(memstats_str_precentage_box.data(), memstats_str_precentage_box.size()); } -unsigned short memstats_bins() -{ - if (const char *ptr = std::getenv("MEMSTATS_BINS")) - { - try - { +unsigned short memstats_bins() { + if (const char *ptr = std::getenv("MEMSTATS_BINS")) { + try { return std::stoi(ptr); } - catch (...) - { + catch (...) { std::cerr << "Option 'MEMSTATS_BINS=" << ptr << "' not known. Fallback on default '15'\n"; } } return 15; } -void MemStatsInfo::record(void *ptr, std::size_t sz) -{ - // Wait until all writing steps to memstats_events are done - while (!memstats_events_ready.load(std::memory_order_acquire)) - std::this_thread::yield(); - - memstats_events_ready.store(false, std::memory_order_release); +void MemStatsInfo::record(void *ptr, std::size_t sz) { auto time = std::chrono::high_resolution_clock::now(); MemStatsInfo info; info.ptr = ptr; @@ -281,17 +270,14 @@ void MemStatsInfo::record(void *ptr, std::size_t sz) std::unique_lock lk{memstats_lock}; #endif memstats_events.emplace_back(std::move(info)); - - memstats_events_ready.store(true, std::memory_order_release); } -template +template using unordered_map = std::unordered_map, std::equal_to, MallocAllocator>>; using string = std::basic_string, MallocAllocator>; using stringstream = std::basic_stringstream, MallocAllocator>; -void print_legend() -{ +void print_legend() { std::cout << "\nMemStats Legend:\n\n"; std::cout << " [{hist}]{max} | {accum}({count}) | {pos}\n\n"; std::cout << "• hist: Distribution of number of 'new' allocations for a given number of bytes\n"; @@ -304,10 +290,10 @@ void print_legend() string buffer; double per_width = 100. / str_precentage.second; for (std::size_t i = 0; i != str_precentage.second; ++i) - std::cout << "• \'" << str_precentage.first[i] << "\' -> [" << std::fixed - << std::setw(4) << std::setprecision(1) << i * per_width - << "%, " << std::setw(5) << (i + 1) * per_width << '%' - << (i + 1 == str_precentage.second ? ']' : ')') << std::endl; + std::cout << "• \'" << str_precentage.first[i] << "\' -> [" << std::fixed + << std::setw(4) << std::setprecision(1) << i * per_width + << "%, " << std::setw(5) << (i + 1) * per_width << '%' + << (i + 1 == str_precentage.second ? ']' : ')') << std::endl; } void report_memory_leaks() { @@ -319,13 +305,15 @@ void report_memory_leaks() { if (memstats_events[i].size == 0) continue; bool freed = false; for (int j = i + 1; j < memstats_events.size(); ++j) { - if (memstats_events[i].ptr == memstats_events[j].ptr && memstats_events[j].size == 0 && memstats_events[i].thread == memstats_events[j].thread) { + if (memstats_events[i].ptr == memstats_events[j].ptr && memstats_events[j].size == 0 && + memstats_events[i].thread == memstats_events[j].thread) { freed = true; } } if (!freed) { - std::cout << "Pointer " << memstats_events[i].ptr << " was never freed in Thread " << memstats_events[i].thread << "." << std::endl; + std::cout << "Pointer " << memstats_events[i].ptr << " was never freed in Thread " + << memstats_events[i].thread << "." << std::endl; #if MEMSTAT_HAVE_STACKTRACE std::cout << "Current stacktrace:\n" << memstats_events[i].stacktrace << std::endl; #endif @@ -333,15 +321,13 @@ void report_memory_leaks() { } } -void memstats_report(const char * report_name) -{ +void memstats_report(const char *report_name) { auto lock = std::unique_lock{memstats_lock}; if (memstats_events.size() == 0) return; std::cout << "\n------------------- MemStats " << report_name << " -------------------\n"; - struct Stats - { + struct Stats { std::size_t count{0}, size{0}, max_size{0}; unordered_map size_freq; }; @@ -358,10 +344,8 @@ void memstats_report(const char * report_name) unordered_map stacktrace_entry_stats; #endif - for (const MemStatsInfo &info : memstats_events) - { - auto register_stats = [&](Stats &stats) - { + for (const MemStatsInfo &info: memstats_events) { + auto register_stats = [&](Stats &stats) { if (info.size) ++stats.count; stats.size += info.size; @@ -384,7 +368,6 @@ void memstats_report(const char * report_name) } - #if MEMSTAT_HAVE_STACKTRACE register_stats(stacktrace_stats[info.stacktrace]); for (auto entry : info.stacktrace) @@ -393,8 +376,7 @@ void memstats_report(const char * report_name) } static const std::array metric_prefix{' ', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y', 'R', 'Q'}; - auto bytes_to_string = [&](std::size_t bytes) - { + auto bytes_to_string = [&](std::size_t bytes) { stringstream stream; short base = std::floor(std::log2(bytes) / 10); if (base > metric_prefix.size()) @@ -403,8 +385,7 @@ void memstats_report(const char * report_name) return stream.str(); }; - auto int_to_string = [&](std::size_t val) - { + auto int_to_string = [&](std::size_t val) { stringstream stream; short base = std::floor(std::log10(val) / 3); if (base > metric_prefix.size()) @@ -414,12 +395,10 @@ void memstats_report(const char * report_name) }; const auto str_precentage = memstats_str_hist_representation(); const auto bins = memstats_bins(); - auto format_histogram = [&](const Stats &stats) - { + auto format_histogram = [&](const Stats &stats) { std::vector> hist(bins, 0); std::size_t max_size = 0; - for (const auto &frec : stats.size_freq) - { + for (const auto &frec: stats.size_freq) { std::size_t size = frec.first, count = frec.second; assert(size <= stats.max_size); auto bin = (bins * (size - 1)) / (stats.max_size); @@ -427,13 +406,13 @@ void memstats_report(const char * report_name) } stringstream stream; stream << "["; - for (auto size : hist) { - const std::size_t bin_entry = - (size * str_precentage.second) / max_size; - // maximum value (size==max_size) will be out of range so we need to guard agains that - stream << str_precentage.first[std::min(bin_entry, str_precentage.second - 1)]; + for (auto size: hist) { + const std::size_t bin_entry = + (size * str_precentage.second) / max_size; + // maximum value (size==max_size) will be out of range so we need to guard agains that + stream << str_precentage.first[std::min(bin_entry, str_precentage.second - 1)]; } - stream << "]" << std::left<< std::setw(6) << bytes_to_string(stats.max_size); + stream << "]" << std::left << std::setw(6) << bytes_to_string(stats.max_size); return stream.str(); }; @@ -442,13 +421,13 @@ void memstats_report(const char * report_name) << std::left << std::setw(5) << int_to_string(global_stats.count) << ") | Total\n"; - for (const auto &pair : thread_stats) - if (pair.second.size) { - std::cout << format_histogram(pair.second) << " | " << std::right - << std::setw(6) << bytes_to_string(pair.second.size) << '(' - << std::left << std::setw(5) << int_to_string(pair.second.count) - << ") | Thread " << pair.first << std::endl; - } + for (const auto &pair: thread_stats) + if (pair.second.size) { + std::cout << format_histogram(pair.second) << " | " << std::right + << std::setw(6) << bytes_to_string(pair.second.size) << '(' + << std::left << std::setw(5) << int_to_string(pair.second.count) + << ") | Thread " << pair.first << std::endl; + } #if MEMSTAT_HAVE_STACKTRACE for (auto [stacktrace_entry, stats] : stacktrace_entry_stats) @@ -468,8 +447,7 @@ void memstats_report(const char * report_name) std::cout << "\nDouble freed pointers:\n"; // Report double deallocations - for (const auto& entry : ptr_stats) - { + for (const auto &entry: ptr_stats) { if (entry.times_freed > 1) std::cout << "Pointer " << entry.ptr << " was freed " << entry.times_freed << " times." << std::endl; } @@ -479,41 +457,37 @@ void memstats_report(const char * report_name) // avoid printing legend several times, so call once at exit static std::once_flag legend_flag; - std::call_once(legend_flag, []() - { std::atexit(print_legend); }); + std::call_once(legend_flag, []() { std::atexit(print_legend); }); } template -T exchange(T& obj, U&& new_value) -{ +T exchange(T &obj, U &&new_value) { T old_value = std::move(obj); obj = std::forward(new_value); return old_value; } -bool memstats_enable_thread_instrumentation() -{ +bool memstats_enable_thread_instrumentation() { return exchange(memstats_instrumentation_thread, true); } -bool memstats_disable_thread_instrumentation() -{ +bool memstats_disable_thread_instrumentation() { return exchange(memstats_instrumentation_thread, false); } -bool memstats_do_instrument() -{ +bool memstats_do_instrument() { return memstats_instrumentation_thread and memstats_instrumentation_global.load(std::memory_order_acquire); } // instrumentation of new -void *operator new(std::size_t sz) -{ +void *operator new(std::size_t sz) { + // prevent multiple thread from allocating memory at the same time s.t. events are in order + std::lock_guard lock(memstats_events_mutex); + if (sz == 0) sz = 1; void *ptr; - while ((ptr = std::malloc(sz)) == nullptr) - { + while ((ptr = std::malloc(sz)) == nullptr) { std::new_handler handler = std::get_new_handler(); if (handler) handler(); @@ -526,34 +500,30 @@ void *operator new(std::size_t sz) } // instrumentation of new -void *operator new(std::size_t sz, std::nothrow_t) noexcept -{ - try - { +void *operator new(std::size_t sz, std::nothrow_t) noexcept { + try { return ::operator new(sz); } - catch (...) - { + catch (...) { } return nullptr; } // instrumentation of delete -void operator delete(void *ptr) noexcept -{ +void operator delete(void *ptr) noexcept { + // prevent multiple thread from deallocating memory at the same time s.t. events are in order + std::lock_guard lock(memstats_events_mutex); + if (memstats_do_instrument()) MemStatsInfo::record(ptr); std::free(ptr); } // instrumentation of delete -void operator delete(void *ptr, std::nothrow_t) noexcept -{ - try - { +void operator delete(void *ptr, std::nothrow_t) noexcept { + try { return ::operator delete(ptr); } - catch (...) - { + catch (...) { } } From 8aa2d8bf930c458d5d340ee476f6773da1f9c838 Mon Sep 17 00:00:00 2001 From: niels-beier Date: Tue, 17 Dec 2024 16:07:07 +0100 Subject: [PATCH 06/26] Remove the lock for testing --- memstats.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/memstats.cc b/memstats.cc index 86a8ab5..4b47d0b 100644 --- a/memstats.cc +++ b/memstats.cc @@ -482,7 +482,7 @@ bool memstats_do_instrument() { // instrumentation of new void *operator new(std::size_t sz) { // prevent multiple thread from allocating memory at the same time s.t. events are in order - std::lock_guard lock(memstats_events_mutex); + //std::lock_guard lock(memstats_events_mutex); if (sz == 0) sz = 1; @@ -512,7 +512,7 @@ void *operator new(std::size_t sz, std::nothrow_t) noexcept { // instrumentation of delete void operator delete(void *ptr) noexcept { // prevent multiple thread from deallocating memory at the same time s.t. events are in order - std::lock_guard lock(memstats_events_mutex); + //std::lock_guard lock(memstats_events_mutex); if (memstats_do_instrument()) MemStatsInfo::record(ptr); From 36045148474ed62ffccd95a3af59189cdbbe7850 Mon Sep 17 00:00:00 2001 From: niels-beier Date: Fri, 24 Jan 2025 13:46:37 +0100 Subject: [PATCH 07/26] Add variable to set pintool path TODO: add flag to enable/disable pintool in memstats --- CMakeLists.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6985ec5..3d3caf4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -88,6 +88,13 @@ export(EXPORT memstats-targets NAMESPACE MemStats:: ) +set(PINTOOL_HEADER_PATH "" CACHE PATH "") + +if (NOT PINTOOL_HEADER_PATH) + message(FATAL_ERROR "For using memstats with the pintool, the path to pin.H is required.") +endif() + +include_directories(${PINTOOL_HEADER_PATH}) if(memstats_IS_TOP_LEVEL) add_executable(example_01 example_01.cc) From 823c0868b37813f7fdfe7caf622feb67dfa68a6f Mon Sep 17 00:00:00 2001 From: niels-beier Date: Fri, 24 Jan 2025 13:47:12 +0100 Subject: [PATCH 08/26] Implement custom memory tracer for reads/writes in a program --- memorytracer.cc | 47 +++++++++++++++++++++++++++++++++++++++++++++++ memorytracer.hh | 31 +++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 memorytracer.cc create mode 100644 memorytracer.hh diff --git a/memorytracer.cc b/memorytracer.cc new file mode 100644 index 0000000..176ffc8 --- /dev/null +++ b/memorytracer.cc @@ -0,0 +1,47 @@ +#include "memorytracer.hh" +#include "pin.H" +#include +#include + +std::vector MemoryTracer::operations; +std::set> MemoryTracer::excludedRanges; +std::mutex opMutex; + +void MemoryTracer::Init() { + PIN_InitSymbols(); + INS_AddInstrumentFunction([](INS ins, VOID* v) { + if (INS_IsMemoryRead(ins)) { + INS_InsertCall(ins, IPOINT_BEFORE, (AFUNPTR)RecordMemoryRead, + IARG_INST_PTR, IARG_MEMORYREAD_EA, IARG_MEMORYREAD_SIZE, IARG_THREAD_ID, IARG_END); + } + if (INS_IsMemoryWrite(ins)) { + INS_InsertCall(ins, IPOINT_BEFORE, (AFUNPTR)RecordMemoryWrite, + IARG_INST_PTR, IARG_MEMORYWRITE_EA, IARG_MEMORYWRITE_SIZE, IARG_THREAD_ID, IARG_END); + } + }, nullptr); + PIN_StartProgram(); +} + +void MemoryTracer::Finalize() { + // Cleanup (optional) +} + +const std::vector& MemoryTracer::GetOperations() { + return operations; +} + +void MemoryTracer::RecordMemoryRead(void* ip, void* addr, uint32_t size, uint32_t tid) { + uintptr_t address = reinterpret_cast(addr); + // TODO: add pintool instrumentation guard + + std::lock_guard lock(opMutex); + operations.push_back({address, size, false, tid}); +} + +void MemoryTracer::RecordMemoryWrite(void* ip, void* addr, uint32_t size, uint32_t tid) { + uintptr_t address = reinterpret_cast(addr); + // TODO: add pintool instrumentation guard + + std::lock_guard lock(opMutex); + operations.push_back({address, size, true, tid}); +} diff --git a/memorytracer.hh b/memorytracer.hh new file mode 100644 index 0000000..2312ad8 --- /dev/null +++ b/memorytracer.hh @@ -0,0 +1,31 @@ +#ifndef MEMSTATS_MEMORYTRACER_HH +#define MEMSTATS_MEMORYTRACER_HH + +#include +#include +#include + +struct MemoryOperation { + uintptr_t address; + size_t size; + bool isWrite; + uint32_t threadId; +}; + +class MemoryTracer { +public: + static void Init(); + static void Finalize(); + static const std::vector& GetOperations(); + +private: + static void RecordMemoryRead(void* ip, void* addr, uint32_t size, uint32_t tid); + static void RecordMemoryWrite(void* ip, void* addr, uint32_t size, uint32_t tid); + + static std::vector operations; + static std::set> excludedRanges; + static std::mutex opMutex; +}; + + +#endif //MEMSTATS_MEMORYTRACER_HH From 41bb76412eb306c7fc58e042eb01870a41c2b88c Mon Sep 17 00:00:00 2001 From: niels-beier Date: Fri, 24 Jan 2025 23:56:59 +0100 Subject: [PATCH 09/26] Introduce thread local variables to dynamically turn on/off the memory tracer and introduce MemoryTracerGuard which disables memory tracing during construction and enables it when going out of scope --- memorytracer.cc | 44 +++++++++++++++++++++++++++++++++++++++++--- memorytracer.hh | 28 ++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 3 deletions(-) diff --git a/memorytracer.cc b/memorytracer.cc index 176ffc8..5359a23 100644 --- a/memorytracer.cc +++ b/memorytracer.cc @@ -2,6 +2,26 @@ #include "pin.H" #include #include +#include +#include + +bool init_memstats_memory_tracing() { + if (char *ptr = std::getenv("MEMSTATS_MEMORY_TRACING")) { + if (std::strcmp(ptr, "true") == 0 or std::strcmp(ptr, "1") == 0) + return true; + if (std::strcmp(ptr, "false") == 0 or std::strcmp(ptr, "0") == 0) + return false; + std::cerr << "Option 'MEMSTATS_MEMORY_TRACING=" << ptr + << "' not known. Fallback on default 'false'\n"; + } + return false; +} + +static thread_local bool memstats_memory_tracing = init_memstats_memory_tracing(); + +bool memstats_do_memory_tracing() { + return memstats_memory_tracing; +} std::vector MemoryTracer::operations; std::set> MemoryTracer::excludedRanges; @@ -23,7 +43,8 @@ void MemoryTracer::Init() { } void MemoryTracer::Finalize() { - // Cleanup (optional) + // TODO: Implement report generation + } const std::vector& MemoryTracer::GetOperations() { @@ -32,7 +53,8 @@ const std::vector& MemoryTracer::GetOperations() { void MemoryTracer::RecordMemoryRead(void* ip, void* addr, uint32_t size, uint32_t tid) { uintptr_t address = reinterpret_cast(addr); - // TODO: add pintool instrumentation guard + if (!memstats_do_memory_tracing()) + return; std::lock_guard lock(opMutex); operations.push_back({address, size, false, tid}); @@ -40,8 +62,24 @@ void MemoryTracer::RecordMemoryRead(void* ip, void* addr, uint32_t size, uint32_ void MemoryTracer::RecordMemoryWrite(void* ip, void* addr, uint32_t size, uint32_t tid) { uintptr_t address = reinterpret_cast(addr); - // TODO: add pintool instrumentation guard + if (!memstats_do_memory_tracing()) + return; std::lock_guard lock(opMutex); operations.push_back({address, size, true, tid}); } + +template +T exchange(T &obj, U &&new_value) { + T old_value = std::move(obj); + obj = std::forward(new_value); + return old_value; +} + +bool memstats_enable_memory_tracer() { + return exchange(memstats_memory_tracing, true); +} + +bool memstats_disable_memory_tracer() { + return exchange(memstats_memory_tracing, false); +} \ No newline at end of file diff --git a/memorytracer.hh b/memorytracer.hh index 2312ad8..58823e9 100644 --- a/memorytracer.hh +++ b/memorytracer.hh @@ -27,5 +27,33 @@ private: static std::mutex opMutex; }; +/** @brief Enable memory tracing for reads and writes of all memory blocks. + * @details Thread-local. Do not call during static- or dynamic-initialization phase. + * @return Whether memory tracing was enabled before to this call + */ +bool memstats_enable_memory_tracer(); + +/** @brief Disable memory tracing for reads and writes of all memory blocks. + * @details Thread-local. Do not call during static- or dynamic-initialization phase. + * @return Whether memory tracing was enabled before to this call + */ +bool memstats_disable_memory_tracer(); + +bool memstats_do_memory_tracing(); + +class MemoryTracerGuard { + const bool was_memory_trace = memstats_disable_memory_tracer(); + + ~MemoryTracerGuard() { + if (was_memory_trace) + memstats_enable_memory_tracer(); + } +}; + +int main(int argc, char *argv[]) { + MemoryTracer::Init(); + MemoryTracer::Finalize(); +} + #endif //MEMSTATS_MEMORYTRACER_HH From 728d200f642adac869be441d7319762304a324e1 Mon Sep 17 00:00:00 2001 From: niels-beier Date: Fri, 24 Jan 2025 23:57:42 +0100 Subject: [PATCH 10/26] Exclude all memstats functionality from being traces by the memory tracer by using the thread local variable --- memstats.cc | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/memstats.cc b/memstats.cc index 4b47d0b..f8917fa 100644 --- a/memstats.cc +++ b/memstats.cc @@ -39,6 +39,7 @@ #endif #include "memstats.hh" +#include "memorytracer.hh" // all allocations within this library need to use malloc/free instad of new/delete template @@ -93,6 +94,8 @@ struct MemStatsInfo { }; bool init_memstats_instrumentation_thread() { + const MemoryTracerGuard guard; + if (char *ptr = std::getenv("MEMSTATS_THREAD_INSTRUMENTATION_INIT")) { if (std::strcmp(ptr, "true") == 0 or std::strcmp(ptr, "1") == 0) return true; @@ -137,6 +140,8 @@ static thread_local bool memstats_instrumentation_thread = init_memstats_instrum // guard thread-local variable to instrument further delets at exit bool init_memstats_instrumentation_thread_guard() { + const MemoryTracerGuard guard; + std::atexit([] { memstats_instrumentation_thread = false; }); return true; } @@ -152,6 +157,8 @@ MEMSTATS_CONSTINIT static std::atomic memstats_instrumentation_global{fals #endif bool init_memstats_instrumentation_guard() { + const MemoryTracerGuard guard; + bool instrument = false; // Note this variable is const-initialized to false. Here we change it to true and syncronize other threads during dynamic initialization if (char *ptr = std::getenv("MEMSTATS_ENABLE_INSTRUMENTATION")) { @@ -175,6 +182,8 @@ bool init_memstats_instrumentation_guard() { static bool memstats_instrumentation_guard = init_memstats_instrumentation_guard(); bool init_memstats_at_exit() { + const MemoryTracerGuard guard; + static std::once_flag report_flag; std::call_once(report_flag, [] { @@ -225,6 +234,8 @@ static const std::array memstats_str_precentage_number{"0", "1 "9"}; std::pair memstats_str_hist_representation() { + const MemoryTracerGuard guard; + if (const char *ptr = std::getenv("MEMSTATS_HISTOGRAM_REPRESENTATION")) { if (std::strcmp(ptr, "box") == 0) return std::make_pair(memstats_str_precentage_box.data(), memstats_str_precentage_box.size()); @@ -245,6 +256,8 @@ std::pair memstats_str_hist_representation() { } unsigned short memstats_bins() { + const MemoryTracerGuard guard; + if (const char *ptr = std::getenv("MEMSTATS_BINS")) { try { return std::stoi(ptr); @@ -257,6 +270,8 @@ unsigned short memstats_bins() { } void MemStatsInfo::record(void *ptr, std::size_t sz) { + const MemoryTracerGuard guard; + auto time = std::chrono::high_resolution_clock::now(); MemStatsInfo info; info.ptr = ptr; @@ -278,6 +293,8 @@ using string = std::basic_string, MallocAllocator, MallocAllocator>; void print_legend() { + const MemoryTracerGuard guard; + std::cout << "\nMemStats Legend:\n\n"; std::cout << " [{hist}]{max} | {accum}({count}) | {pos}\n\n"; std::cout << "• hist: Distribution of number of 'new' allocations for a given number of bytes\n"; @@ -322,6 +339,8 @@ void report_memory_leaks() { } void memstats_report(const char *report_name) { + const MemoryTracerGuard guard; + auto lock = std::unique_lock{memstats_lock}; if (memstats_events.size() == 0) return; @@ -462,25 +481,35 @@ void memstats_report(const char *report_name) { template T exchange(T &obj, U &&new_value) { + const MemoryTracerGuard guard; + T old_value = std::move(obj); obj = std::forward(new_value); return old_value; } bool memstats_enable_thread_instrumentation() { + const MemoryTracerGuard guard; + return exchange(memstats_instrumentation_thread, true); } bool memstats_disable_thread_instrumentation() { + const MemoryTracerGuard guard; + return exchange(memstats_instrumentation_thread, false); } bool memstats_do_instrument() { + const MemoryTracerGuard guard; + return memstats_instrumentation_thread and memstats_instrumentation_global.load(std::memory_order_acquire); } // instrumentation of new void *operator new(std::size_t sz) { + const MemoryTracerGuard guard; + // prevent multiple thread from allocating memory at the same time s.t. events are in order //std::lock_guard lock(memstats_events_mutex); @@ -496,11 +525,14 @@ void *operator new(std::size_t sz) { } if (memstats_do_instrument()) MemStatsInfo::record(ptr, sz); + return ptr; } // instrumentation of new void *operator new(std::size_t sz, std::nothrow_t) noexcept { + const MemoryTracerGuard guard; + try { return ::operator new(sz); } @@ -511,6 +543,8 @@ void *operator new(std::size_t sz, std::nothrow_t) noexcept { // instrumentation of delete void operator delete(void *ptr) noexcept { + const MemoryTracerGuard guard; + // prevent multiple thread from deallocating memory at the same time s.t. events are in order //std::lock_guard lock(memstats_events_mutex); @@ -521,6 +555,8 @@ void operator delete(void *ptr) noexcept { // instrumentation of delete void operator delete(void *ptr, std::nothrow_t) noexcept { + const MemoryTracerGuard guard; + try { return ::operator delete(ptr); } From c3f5d137fe4efb0061cc4c46d73fce0c1fe990d6 Mon Sep 17 00:00:00 2001 From: niels-beier Date: Sun, 26 Jan 2025 12:13:03 +0100 Subject: [PATCH 11/26] Implement final report of memory tracer --- memorytracer.cc | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/memorytracer.cc b/memorytracer.cc index 5359a23..78d3c40 100644 --- a/memorytracer.cc +++ b/memorytracer.cc @@ -43,8 +43,35 @@ void MemoryTracer::Init() { } void MemoryTracer::Finalize() { - // TODO: Implement report generation + // Print histogram of the collected memory read/writes, grouped by the memory address and seperated write/read operations + std::sort(operations.begin(), operations.end(), [](const MemoryOperation& a, const MemoryOperation& b) { + return a.address < b.address; + }); + uintptr_t lastAddress = 0; + size_t lastSize = 0; + size_t readCount = 0; + size_t writeCount = 0; + for (const MemoryOperation& op : operations) { + if (op.address != lastAddress) { + if (lastSize > 0) { + std::cout << "0x" << std::hex << lastAddress << std::dec << ": " << lastSize << " bytes, "; + std::cout << readCount << " reads, " << writeCount << " writes" << std::endl; + } + lastAddress = op.address; + lastSize = op.size; + readCount = 0; + writeCount = 0; + } + if (op.isWrite) + writeCount++; + else + readCount++; + } + if (lastSize > 0) { + std::cout << "0x" << std::hex << lastAddress << std::dec << ": " << lastSize << " bytes, "; + std::cout << readCount << " reads, " << writeCount << " writes" << std::endl; + } } const std::vector& MemoryTracer::GetOperations() { From 596f75abe5e6b3bdc56d64f05d61287be2bef704 Mon Sep 17 00:00:00 2001 From: niels-beier Date: Tue, 4 Feb 2025 11:34:29 +0100 Subject: [PATCH 12/26] Remove unused excludedRanges from memory tracer and move main function to implementation file --- memorytracer.cc | 9 +++++++-- memorytracer.hh | 9 ++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/memorytracer.cc b/memorytracer.cc index 78d3c40..9db3a1c 100644 --- a/memorytracer.cc +++ b/memorytracer.cc @@ -24,7 +24,6 @@ bool memstats_do_memory_tracing() { } std::vector MemoryTracer::operations; -std::set> MemoryTracer::excludedRanges; std::mutex opMutex; void MemoryTracer::Init() { @@ -39,10 +38,11 @@ void MemoryTracer::Init() { IARG_INST_PTR, IARG_MEMORYWRITE_EA, IARG_MEMORYWRITE_SIZE, IARG_THREAD_ID, IARG_END); } }, nullptr); + PIN_AddFiniFunction(MemoryTracer::Finalize, 0); PIN_StartProgram(); } -void MemoryTracer::Finalize() { +void MemoryTracer::Finalize(INT32 code, VOID* v) { // Print histogram of the collected memory read/writes, grouped by the memory address and seperated write/read operations std::sort(operations.begin(), operations.end(), [](const MemoryOperation& a, const MemoryOperation& b) { return a.address < b.address; @@ -109,4 +109,9 @@ bool memstats_enable_memory_tracer() { bool memstats_disable_memory_tracer() { return exchange(memstats_memory_tracing, false); +} + +int main(int argc, char *argv[]) { + MemoryTracer::Init(); + return 0; } \ No newline at end of file diff --git a/memorytracer.hh b/memorytracer.hh index 58823e9..987b43a 100644 --- a/memorytracer.hh +++ b/memorytracer.hh @@ -15,7 +15,7 @@ struct MemoryOperation { class MemoryTracer { public: static void Init(); - static void Finalize(); + static void Finalize(INT32 code, VOID *v); static const std::vector& GetOperations(); private: @@ -23,7 +23,6 @@ private: static void RecordMemoryWrite(void* ip, void* addr, uint32_t size, uint32_t tid); static std::vector operations; - static std::set> excludedRanges; static std::mutex opMutex; }; @@ -44,16 +43,12 @@ bool memstats_do_memory_tracing(); class MemoryTracerGuard { const bool was_memory_trace = memstats_disable_memory_tracer(); +public: ~MemoryTracerGuard() { if (was_memory_trace) memstats_enable_memory_tracer(); } }; -int main(int argc, char *argv[]) { - MemoryTracer::Init(); - MemoryTracer::Finalize(); -} - #endif //MEMSTATS_MEMORYTRACER_HH From ec965f859b8f0709daa143a966da67b5800bb031 Mon Sep 17 00:00:00 2001 From: niels-beier Date: Tue, 4 Feb 2025 11:35:05 +0100 Subject: [PATCH 13/26] Introduce memory tracer and memory tracer guard --- memstats.cc | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/memstats.cc b/memstats.cc index f8917fa..06c22ea 100644 --- a/memstats.cc +++ b/memstats.cc @@ -1,4 +1,3 @@ -#include #include #include #include @@ -39,7 +38,9 @@ #endif #include "memstats.hh" +#if MEMSTATS_USE_MEMORY_TRACER #include "memorytracer.hh" +#endif // all allocations within this library need to use malloc/free instad of new/delete template @@ -94,7 +95,9 @@ struct MemStatsInfo { }; bool init_memstats_instrumentation_thread() { +#if MEMSTATS_USE_MEMORY_TRACER const MemoryTracerGuard guard; +#endif if (char *ptr = std::getenv("MEMSTATS_THREAD_INSTRUMENTATION_INIT")) { if (std::strcmp(ptr, "true") == 0 or std::strcmp(ptr, "1") == 0) @@ -140,7 +143,9 @@ static thread_local bool memstats_instrumentation_thread = init_memstats_instrum // guard thread-local variable to instrument further delets at exit bool init_memstats_instrumentation_thread_guard() { + #if MEMSTATS_USE_MEMORY_TRACER const MemoryTracerGuard guard; +#endif std::atexit([] { memstats_instrumentation_thread = false; }); return true; @@ -157,7 +162,9 @@ MEMSTATS_CONSTINIT static std::atomic memstats_instrumentation_global{fals #endif bool init_memstats_instrumentation_guard() { + #if MEMSTATS_USE_MEMORY_TRACER const MemoryTracerGuard guard; +#endif bool instrument = false; // Note this variable is const-initialized to false. Here we change it to true and syncronize other threads during dynamic initialization @@ -182,7 +189,9 @@ bool init_memstats_instrumentation_guard() { static bool memstats_instrumentation_guard = init_memstats_instrumentation_guard(); bool init_memstats_at_exit() { + #if MEMSTATS_USE_MEMORY_TRACER const MemoryTracerGuard guard; +#endif static std::once_flag report_flag; std::call_once(report_flag, @@ -234,7 +243,9 @@ static const std::array memstats_str_precentage_number{"0", "1 "9"}; std::pair memstats_str_hist_representation() { + #if MEMSTATS_USE_MEMORY_TRACER const MemoryTracerGuard guard; +#endif if (const char *ptr = std::getenv("MEMSTATS_HISTOGRAM_REPRESENTATION")) { if (std::strcmp(ptr, "box") == 0) @@ -256,7 +267,9 @@ std::pair memstats_str_hist_representation() { } unsigned short memstats_bins() { + #if MEMSTATS_USE_MEMORY_TRACER const MemoryTracerGuard guard; +#endif if (const char *ptr = std::getenv("MEMSTATS_BINS")) { try { @@ -270,7 +283,9 @@ unsigned short memstats_bins() { } void MemStatsInfo::record(void *ptr, std::size_t sz) { + #if MEMSTATS_USE_MEMORY_TRACER const MemoryTracerGuard guard; +#endif auto time = std::chrono::high_resolution_clock::now(); MemStatsInfo info; @@ -293,7 +308,9 @@ using string = std::basic_string, MallocAllocator, MallocAllocator>; void print_legend() { + #if MEMSTATS_USE_MEMORY_TRACER const MemoryTracerGuard guard; +#endif std::cout << "\nMemStats Legend:\n\n"; std::cout << " [{hist}]{max} | {accum}({count}) | {pos}\n\n"; @@ -339,7 +356,9 @@ void report_memory_leaks() { } void memstats_report(const char *report_name) { + #if MEMSTATS_USE_MEMORY_TRACER const MemoryTracerGuard guard; +#endif auto lock = std::unique_lock{memstats_lock}; if (memstats_events.size() == 0) @@ -481,7 +500,9 @@ void memstats_report(const char *report_name) { template T exchange(T &obj, U &&new_value) { + #if MEMSTATS_USE_MEMORY_TRACER const MemoryTracerGuard guard; +#endif T old_value = std::move(obj); obj = std::forward(new_value); @@ -489,26 +510,34 @@ T exchange(T &obj, U &&new_value) { } bool memstats_enable_thread_instrumentation() { + #if MEMSTATS_USE_MEMORY_TRACER const MemoryTracerGuard guard; +#endif return exchange(memstats_instrumentation_thread, true); } bool memstats_disable_thread_instrumentation() { + #if MEMSTATS_USE_MEMORY_TRACER const MemoryTracerGuard guard; +#endif return exchange(memstats_instrumentation_thread, false); } bool memstats_do_instrument() { + #if MEMSTATS_USE_MEMORY_TRACER const MemoryTracerGuard guard; +#endif return memstats_instrumentation_thread and memstats_instrumentation_global.load(std::memory_order_acquire); } // instrumentation of new void *operator new(std::size_t sz) { + #if MEMSTATS_USE_MEMORY_TRACER const MemoryTracerGuard guard; +#endif // prevent multiple thread from allocating memory at the same time s.t. events are in order //std::lock_guard lock(memstats_events_mutex); @@ -531,7 +560,9 @@ void *operator new(std::size_t sz) { // instrumentation of new void *operator new(std::size_t sz, std::nothrow_t) noexcept { + #if MEMSTATS_USE_MEMORY_TRACER const MemoryTracerGuard guard; +#endif try { return ::operator new(sz); @@ -543,7 +574,9 @@ void *operator new(std::size_t sz, std::nothrow_t) noexcept { // instrumentation of delete void operator delete(void *ptr) noexcept { + #if MEMSTATS_USE_MEMORY_TRACER const MemoryTracerGuard guard; +#endif // prevent multiple thread from deallocating memory at the same time s.t. events are in order //std::lock_guard lock(memstats_events_mutex); @@ -555,7 +588,9 @@ void operator delete(void *ptr) noexcept { // instrumentation of delete void operator delete(void *ptr, std::nothrow_t) noexcept { + #if MEMSTATS_USE_MEMORY_TRACER const MemoryTracerGuard guard; +#endif try { return ::operator delete(ptr); From c11d3a2386c7dfcb87a2d9304fabec5abb7ceb71 Mon Sep 17 00:00:00 2001 From: niels-beier Date: Tue, 4 Feb 2025 11:35:35 +0100 Subject: [PATCH 14/26] Add option to enable memory tracer --- CMakeLists.txt | 35 ++++++++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3d3caf4..4e6f527 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -88,13 +88,38 @@ export(EXPORT memstats-targets NAMESPACE MemStats:: ) -set(PINTOOL_HEADER_PATH "" CACHE PATH "") +option(USE_MEMORY_TRACER "Enable memory tracing" OFF) -if (NOT PINTOOL_HEADER_PATH) - message(FATAL_ERROR "For using memstats with the pintool, the path to pin.H is required.") -endif() +if(USE_MEMORY_TRACER) + target_compile_definitions(memstats PRIVATE MEMSTATS_USE_MEMORY_TRACER) + + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DPIN_CRT=1 -fno-stack-protector -funwind-tables -fasynchronous-unwind-tables -fno-rtti -DTARGET_IA32E -DHOST_IA32E -fPIC -DTARGET_LINUX -fabi-version=2 -faligned-new -fomit-frame-pointer -fno-strict-aliasing") + + set(PINTOOL_PATH "" CACHE PATH "") + + if (NOT PINTOOL_PATH) + message(FATAL_ERROR "For using memstats with memory tracing, the path to the include directory of the pintool is required.") + endif() -include_directories(${PINTOOL_HEADER_PATH}) + # Include directories + include_directories(${PINTOOL_PATH}/source/include/pin) + include_directories(${PINTOOL_PATH}/source/include/pin/gen) + include_directories(${PINTOOL_PATH}/extras/components/include) + include_directories(${PINTOOL_PATH}/extras/xed-intel64/include/xed) + include_directories(${PINTOOL_PATH}/source/tools/Utils) + include_directories(${PINTOOL_PATH}/source/tools/InstLib) + include_directories(SYSTEM ${PINTOOL_PATH}/extras/cxx/include) + include_directories(SYSTEM ${PINTOOL_PATH}/extras/crt/include) + include_directories(SYSTEM ${PINTOOL_PATH}/extras/crt/include/arch-x86_64) + include_directories(SYSTEM ${PINTOOL_PATH}/extras/crt/include/kernel/uapi) + include_directories(SYSTEM ${PINTOOL_PATH}/extras/crt/include/kernel/uapi/asm-x86) + + set(SOURCES memorytracer.cc) + + add_library(memory_tracer OBJECT ${SOURCES}) + + target_link_libraries(memory_tracer pin xed) +endif() if(memstats_IS_TOP_LEVEL) add_executable(example_01 example_01.cc) From 96bf9955e2d4a831dd4b9c9ca6af669489a0a71e Mon Sep 17 00:00:00 2001 From: niels-beier Date: Mon, 10 Feb 2025 15:58:59 +0100 Subject: [PATCH 15/26] Start to implement final output of memory tracer --- memorytracer.cc | 90 +++++++++++++++++++++++-------------------------- memorytracer.hh | 4 +-- 2 files changed, 44 insertions(+), 50 deletions(-) diff --git a/memorytracer.cc b/memorytracer.cc index 9db3a1c..78a3182 100644 --- a/memorytracer.cc +++ b/memorytracer.cc @@ -2,31 +2,26 @@ #include "pin.H" #include #include -#include +#include #include +#include -bool init_memstats_memory_tracing() { - if (char *ptr = std::getenv("MEMSTATS_MEMORY_TRACING")) { - if (std::strcmp(ptr, "true") == 0 or std::strcmp(ptr, "1") == 0) - return true; - if (std::strcmp(ptr, "false") == 0 or std::strcmp(ptr, "0") == 0) - return false; - std::cerr << "Option 'MEMSTATS_MEMORY_TRACING=" << ptr - << "' not known. Fallback on default 'false'\n"; - } - return false; -} - -static thread_local bool memstats_memory_tracing = init_memstats_memory_tracing(); +static bool memstats_memory_tracing = true; bool memstats_do_memory_tracing() { return memstats_memory_tracing; } std::vector MemoryTracer::operations; -std::mutex opMutex; -void MemoryTracer::Init() { +int MemoryTracer::Init(int argc, char *argv[]) { + + if( PIN_Init(argc,argv) ) + { + std::cout << "Error PIN_Init" << std::endl; + return 1; + } + PIN_InitSymbols(); INS_AddInstrumentFunction([](INS ins, VOID* v) { if (INS_IsMemoryRead(ins)) { @@ -40,37 +35,40 @@ void MemoryTracer::Init() { }, nullptr); PIN_AddFiniFunction(MemoryTracer::Finalize, 0); PIN_StartProgram(); + + return 0; } void MemoryTracer::Finalize(INT32 code, VOID* v) { - // Print histogram of the collected memory read/writes, grouped by the memory address and seperated write/read operations - std::sort(operations.begin(), operations.end(), [](const MemoryOperation& a, const MemoryOperation& b) { - return a.address < b.address; - }); - - uintptr_t lastAddress = 0; - size_t lastSize = 0; - size_t readCount = 0; - size_t writeCount = 0; - for (const MemoryOperation& op : operations) { - if (op.address != lastAddress) { - if (lastSize > 0) { - std::cout << "0x" << std::hex << lastAddress << std::dec << ": " << lastSize << " bytes, "; - std::cout << readCount << " reads, " << writeCount << " writes" << std::endl; - } - lastAddress = op.address; - lastSize = op.size; - readCount = 0; - writeCount = 0; + static const std::array percentage_box{" ", "▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"}; + static const int bins = 15; + + // Count the number of times each memory address is accessed seperately for reads and writes + std::unordered_map> access_count; + for (const auto& op : operations) { + if (op.isWrite) { + access_count[op.address].second++; + } else { + access_count[op.address].first++; } - if (op.isWrite) - writeCount++; - else - readCount++; } - if (lastSize > 0) { - std::cout << "0x" << std::hex << lastAddress << std::dec << ": " << lastSize << " bytes, "; - std::cout << readCount << " reads, " << writeCount << " writes" << std::endl; + + // Print a histogram of the number of times each memory address is accessed using the percentage box characters + for (const auto& [address, count] : access_count) { + std::cout << "0x" << std::hex << address << std::dec << ": "; + for (int i = 0; i < bins; i++) { + int total = count.first + count.second; + int read_percentage = (count.first * bins) / total; + int write_percentage = (count.second * bins) / total; + if (i < read_percentage) { + std::cout << percentage_box[std::min(read_percentage, 8)]; + } else if (i < write_percentage) { + std::cout << percentage_box[std::min(write_percentage, 8)]; + } else { + std::cout << " "; + } + } + std::cout << " | " << count.first << " reads, " << count.second << " writes" << std::endl; } } @@ -79,20 +77,18 @@ const std::vector& MemoryTracer::GetOperations() { } void MemoryTracer::RecordMemoryRead(void* ip, void* addr, uint32_t size, uint32_t tid) { - uintptr_t address = reinterpret_cast(addr); + auto address = reinterpret_cast(addr); if (!memstats_do_memory_tracing()) return; - std::lock_guard lock(opMutex); operations.push_back({address, size, false, tid}); } void MemoryTracer::RecordMemoryWrite(void* ip, void* addr, uint32_t size, uint32_t tid) { - uintptr_t address = reinterpret_cast(addr); + auto address = reinterpret_cast(addr); if (!memstats_do_memory_tracing()) return; - std::lock_guard lock(opMutex); operations.push_back({address, size, true, tid}); } @@ -112,6 +108,6 @@ bool memstats_disable_memory_tracer() { } int main(int argc, char *argv[]) { - MemoryTracer::Init(); + MemoryTracer::Init(argc, argv); return 0; } \ No newline at end of file diff --git a/memorytracer.hh b/memorytracer.hh index 987b43a..b24db95 100644 --- a/memorytracer.hh +++ b/memorytracer.hh @@ -3,7 +3,6 @@ #include #include -#include struct MemoryOperation { uintptr_t address; @@ -14,7 +13,7 @@ struct MemoryOperation { class MemoryTracer { public: - static void Init(); + static int Init(int argc, char *argv[]); static void Finalize(INT32 code, VOID *v); static const std::vector& GetOperations(); @@ -23,7 +22,6 @@ private: static void RecordMemoryWrite(void* ip, void* addr, uint32_t size, uint32_t tid); static std::vector operations; - static std::mutex opMutex; }; /** @brief Enable memory tracing for reads and writes of all memory blocks. From b25a5e8a2559f80e5e73c34eed076976a5a3d96c Mon Sep 17 00:00:00 2001 From: niels-beier Date: Tue, 11 Feb 2025 09:28:50 +0100 Subject: [PATCH 16/26] Remove compilation of memory tracer from cmake and use compile script instead --- CMakeLists.txt | 41 +++++++--------------------------------- memory_tracer/compile.sh | 23 ++++++++++++++++++++++ 2 files changed, 30 insertions(+), 34 deletions(-) create mode 100755 memory_tracer/compile.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index 4e6f527..a0d3cf8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,6 +8,12 @@ add_library(memstats) target_sources(memstats PRIVATE memstats.cc) target_link_libraries(memstats PRIVATE $ $) +option(USE_MEMORY_TRACER "Enable memory tracing" OFF) + +if(USE_MEMORY_TRACER) + target_compile_definitions(memstats PRIVATE MEMSTATS_USE_MEMORY_TRACER) +endif() + include(GNUInstallDirs) install(FILES memstats.hh DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}") @@ -88,39 +94,6 @@ export(EXPORT memstats-targets NAMESPACE MemStats:: ) -option(USE_MEMORY_TRACER "Enable memory tracing" OFF) - -if(USE_MEMORY_TRACER) - target_compile_definitions(memstats PRIVATE MEMSTATS_USE_MEMORY_TRACER) - - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DPIN_CRT=1 -fno-stack-protector -funwind-tables -fasynchronous-unwind-tables -fno-rtti -DTARGET_IA32E -DHOST_IA32E -fPIC -DTARGET_LINUX -fabi-version=2 -faligned-new -fomit-frame-pointer -fno-strict-aliasing") - - set(PINTOOL_PATH "" CACHE PATH "") - - if (NOT PINTOOL_PATH) - message(FATAL_ERROR "For using memstats with memory tracing, the path to the include directory of the pintool is required.") - endif() - - # Include directories - include_directories(${PINTOOL_PATH}/source/include/pin) - include_directories(${PINTOOL_PATH}/source/include/pin/gen) - include_directories(${PINTOOL_PATH}/extras/components/include) - include_directories(${PINTOOL_PATH}/extras/xed-intel64/include/xed) - include_directories(${PINTOOL_PATH}/source/tools/Utils) - include_directories(${PINTOOL_PATH}/source/tools/InstLib) - include_directories(SYSTEM ${PINTOOL_PATH}/extras/cxx/include) - include_directories(SYSTEM ${PINTOOL_PATH}/extras/crt/include) - include_directories(SYSTEM ${PINTOOL_PATH}/extras/crt/include/arch-x86_64) - include_directories(SYSTEM ${PINTOOL_PATH}/extras/crt/include/kernel/uapi) - include_directories(SYSTEM ${PINTOOL_PATH}/extras/crt/include/kernel/uapi/asm-x86) - - set(SOURCES memorytracer.cc) - - add_library(memory_tracer OBJECT ${SOURCES}) - - target_link_libraries(memory_tracer pin xed) -endif() - if(memstats_IS_TOP_LEVEL) add_executable(example_01 example_01.cc) target_link_libraries(example_01 PUBLIC MemStats::MemStats) @@ -139,4 +112,4 @@ if(memstats_IS_TOP_LEVEL) add_executable(example_05 example_05.cc) target_link_libraries(example_05 PUBLIC MemStats::MemStats) -endif() +endif() \ No newline at end of file diff --git a/memory_tracer/compile.sh b/memory_tracer/compile.sh new file mode 100755 index 0000000..83a500b --- /dev/null +++ b/memory_tracer/compile.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +# Current directory +DIR=$PWD +PINTOOL_PATH=$1 + +# Remove memorytracer.so if it exists +rm -f $DIR/memorytracer.so + +# Copy files to the pintool directory +cp -f $PWD/memorytracer.cc $PINTOOL_PATH/source/tools/ManualExamples/memorytracer.cpp +cp -f $PWD/memorytracer.hh $PINTOOL_PATH/source/tools/ManualExamples/memorytracer.hh + +# Compile the memory tracer +cd $PINTOOL_PATH/source/tools/ManualExamples || exit +make obj-intel64/memorytracer.so +cp -f obj-intel64/memorytracer.so $DIR/ + +# Remove copied files +rm -f obj-intel64/memorytracer.so +rm -f obj-intel64/memorytracer.o +rm -f memorytracer.cpp memorytracer.hh +cd $DIR || exit \ No newline at end of file From bb3c372692b30a341e4eeb471968626ee9308e1a Mon Sep 17 00:00:00 2001 From: niels-beier Date: Tue, 11 Feb 2025 09:30:08 +0100 Subject: [PATCH 17/26] Rewrite final output of memory tracer and add capture for function names to enable/disable memory tracer --- .../memorytracer.cc | 93 ++++++++++--------- 1 file changed, 50 insertions(+), 43 deletions(-) rename memorytracer.cc => memory_tracer/memorytracer.cc (58%) diff --git a/memorytracer.cc b/memory_tracer/memorytracer.cc similarity index 58% rename from memorytracer.cc rename to memory_tracer/memorytracer.cc index 78a3182..ef844e7 100644 --- a/memorytracer.cc +++ b/memory_tracer/memorytracer.cc @@ -1,6 +1,5 @@ #include "memorytracer.hh" #include "pin.H" -#include #include #include #include @@ -12,8 +11,39 @@ bool memstats_do_memory_tracing() { return memstats_memory_tracing; } +template +T exchange(T &obj, U &&new_value) { + T old_value = std::move(obj); + obj = std::forward(new_value); + return old_value; +} + +bool memstats_enable_memory_tracer() { + return exchange(memstats_memory_tracing, true); +} + +bool memstats_disable_memory_tracer() { + return exchange(memstats_memory_tracing, false); +} + std::vector MemoryTracer::operations; +VOID Image(IMG img, VOID *v) { + RTN disableRtn = RTN_FindByName(img, "disable_memory_tracer"); + if (RTN_Valid(disableRtn)) { + RTN_Open(disableRtn); + RTN_InsertCall(disableRtn, IPOINT_BEFORE, AFUNPTR(memstats_disable_memory_tracer), IARG_END); + RTN_Close(disableRtn); + } + + RTN enableRtn = RTN_FindByName(img, "enable_memory_tracer"); + if (RTN_Valid(enableRtn)) { + RTN_Open(enableRtn); + RTN_InsertCall(enableRtn, IPOINT_BEFORE, AFUNPTR(memstats_enable_memory_tracer), IARG_END); + RTN_Close(enableRtn); + } +} + int MemoryTracer::Init(int argc, char *argv[]) { if( PIN_Init(argc,argv) ) @@ -23,6 +53,7 @@ int MemoryTracer::Init(int argc, char *argv[]) { } PIN_InitSymbols(); + IMG_AddInstrumentFunction(Image, 0); INS_AddInstrumentFunction([](INS ins, VOID* v) { if (INS_IsMemoryRead(ins)) { INS_InsertCall(ins, IPOINT_BEFORE, (AFUNPTR)RecordMemoryRead, @@ -33,42 +64,33 @@ int MemoryTracer::Init(int argc, char *argv[]) { IARG_INST_PTR, IARG_MEMORYWRITE_EA, IARG_MEMORYWRITE_SIZE, IARG_THREAD_ID, IARG_END); } }, nullptr); - PIN_AddFiniFunction(MemoryTracer::Finalize, 0); + PIN_AddFiniFunction(Finalize, 0); PIN_StartProgram(); return 0; } void MemoryTracer::Finalize(INT32 code, VOID* v) { - static const std::array percentage_box{" ", "▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"}; - static const int bins = 15; - - // Count the number of times each memory address is accessed seperately for reads and writes - std::unordered_map> access_count; + // print number of read/write operations and their total size per address + std::unordered_map> stats; + std::unordered_map> stats_size; for (const auto& op : operations) { - if (op.isWrite) { - access_count[op.address].second++; - } else { - access_count[op.address].first++; - } + stats[op.address][op.isWrite]++; + stats_size[op.address][op.isWrite] += op.size; + } + // sort stats and stats_size by address + std::vector addresses; + for (const auto& [address, _] : stats) { + addresses.push_back(address); } + std::sort(addresses.begin(), addresses.end()); - // Print a histogram of the number of times each memory address is accessed using the percentage box characters - for (const auto& [address, count] : access_count) { - std::cout << "0x" << std::hex << address << std::dec << ": "; - for (int i = 0; i < bins; i++) { - int total = count.first + count.second; - int read_percentage = (count.first * bins) / total; - int write_percentage = (count.second * bins) / total; - if (i < read_percentage) { - std::cout << percentage_box[std::min(read_percentage, 8)]; - } else if (i < write_percentage) { - std::cout << percentage_box[std::min(write_percentage, 8)]; - } else { - std::cout << " "; - } - } - std::cout << " | " << count.first << " reads, " << count.second << " writes" << std::endl; + for (const auto& address : addresses) { + const auto& counts = stats[address]; + + // print address, number of reads, number of writes, total size of reads, total size of writes + std::cout << std::hex << address << std::dec << ": " << counts[0] << " reads, " << counts[1] << " writes, " + << stats_size[address][0] << " bytes read, " << stats_size[address][1] << " bytes written" << std::endl; } } @@ -92,21 +114,6 @@ void MemoryTracer::RecordMemoryWrite(void* ip, void* addr, uint32_t size, uint32 operations.push_back({address, size, true, tid}); } -template -T exchange(T &obj, U &&new_value) { - T old_value = std::move(obj); - obj = std::forward(new_value); - return old_value; -} - -bool memstats_enable_memory_tracer() { - return exchange(memstats_memory_tracing, true); -} - -bool memstats_disable_memory_tracer() { - return exchange(memstats_memory_tracing, false); -} - int main(int argc, char *argv[]) { MemoryTracer::Init(argc, argv); return 0; From 3423490d1773376931905f20f14a6736809a1956 Mon Sep 17 00:00:00 2001 From: niels-beier Date: Tue, 11 Feb 2025 09:31:36 +0100 Subject: [PATCH 18/26] Move memory tracer guard to memstats to enable/disable memory tracer using RAII --- .../memorytracer.hh | 11 -- memstats.cc | 117 ++++++++++-------- 2 files changed, 67 insertions(+), 61 deletions(-) rename memorytracer.hh => memory_tracer/memorytracer.hh (85%) diff --git a/memorytracer.hh b/memory_tracer/memorytracer.hh similarity index 85% rename from memorytracer.hh rename to memory_tracer/memorytracer.hh index b24db95..a85ab1d 100644 --- a/memorytracer.hh +++ b/memory_tracer/memorytracer.hh @@ -38,15 +38,4 @@ bool memstats_disable_memory_tracer(); bool memstats_do_memory_tracing(); -class MemoryTracerGuard { - const bool was_memory_trace = memstats_disable_memory_tracer(); - -public: - ~MemoryTracerGuard() { - if (was_memory_trace) - memstats_enable_memory_tracer(); - } -}; - - #endif //MEMSTATS_MEMORYTRACER_HH diff --git a/memstats.cc b/memstats.cc index 06c22ea..e6dc1e9 100644 --- a/memstats.cc +++ b/memstats.cc @@ -38,9 +38,6 @@ #endif #include "memstats.hh" -#if MEMSTATS_USE_MEMORY_TRACER -#include "memorytracer.hh" -#endif // all allocations within this library need to use malloc/free instad of new/delete template @@ -51,7 +48,8 @@ class MallocAllocator { constexpr MallocAllocator() noexcept = default; template - constexpr MallocAllocator(const MallocAllocator &) noexcept {} + constexpr MallocAllocator(const MallocAllocator &) noexcept { + } ~MallocAllocator() noexcept = default; @@ -82,6 +80,25 @@ class MallocAllocator { } }; +#if MEMSTATS_USE_MEMORY_TRACER +void __attribute__((optimize("O0"))) disable_memory_tracer(void) { +} + +void __attribute__((optimize("O0"))) enable_memory_tracer(void) { +} + +class MemoryTracerGuard { +public: + MemoryTracerGuard() { + disable_memory_tracer(); + } + + ~MemoryTracerGuard() { + enable_memory_tracer(); + } +}; +#endif + struct MemStatsInfo { const void *ptr = nullptr; std::size_t size = 0; @@ -105,7 +122,7 @@ bool init_memstats_instrumentation_thread() { if (std::strcmp(ptr, "false") == 0 or std::strcmp(ptr, "0") == 0) return false; std::cerr << "Option 'MEMSTATS_THREAD_INSTRUMENTATION_INIT=" << ptr - << "' not known. Fallback on default 'false'\n"; + << "' not known. Fallback on default 'false'\n"; } return false; } @@ -133,7 +150,7 @@ static std::recursive_mutex memstats_lock = {}; #if __cpp_lib_constexpr_vector >= 201907L MEMSTATS_CONSTINIT #endif -static std::vector> memstats_events = {}; +static std::vector > memstats_events = {}; #endif std::mutex memstats_events_mutex; @@ -143,7 +160,7 @@ static thread_local bool memstats_instrumentation_thread = init_memstats_instrum // guard thread-local variable to instrument further delets at exit bool init_memstats_instrumentation_thread_guard() { - #if MEMSTATS_USE_MEMORY_TRACER +#if MEMSTATS_USE_MEMORY_TRACER const MemoryTracerGuard guard; #endif @@ -162,7 +179,7 @@ MEMSTATS_CONSTINIT static std::atomic memstats_instrumentation_global{fals #endif bool init_memstats_instrumentation_guard() { - #if MEMSTATS_USE_MEMORY_TRACER +#if MEMSTATS_USE_MEMORY_TRACER const MemoryTracerGuard guard; #endif @@ -175,7 +192,7 @@ bool init_memstats_instrumentation_guard() { instrument = false; else std::cerr << "Option 'MEMSTATS_ENABLE_INSTRUMENTATION=" << ptr - << "' not known. Fallback on default 'false'\n"; + << "' not known. Fallback on default 'false'\n"; } memstats_instrumentation_global.store(instrument, std::memory_order_release); return instrument; @@ -189,7 +206,7 @@ bool init_memstats_instrumentation_guard() { static bool memstats_instrumentation_guard = init_memstats_instrumentation_guard(); bool init_memstats_at_exit() { - #if MEMSTATS_USE_MEMORY_TRACER +#if MEMSTATS_USE_MEMORY_TRACER const MemoryTracerGuard guard; #endif @@ -206,7 +223,7 @@ bool init_memstats_at_exit() { do_report_at_exit = false; else std::cerr << "Option 'MEMSTATS_REPORT_AT_EXIT=" << ptr - << "' not known. Fallback on default 'true'\n"; + << "' not known. Fallback on default 'true'\n"; } if (do_report_at_exit) memstats_report("default"); @@ -239,11 +256,13 @@ static const std::array memstats_str_precentage_circle{" ", "." static const std::array memstats_str_precentage_shadow{" ", "░", "▒", "▓", "█"}; static const std::array memstats_str_precentage_wire{" ", "-", "~", "=", "#"}; static const std::array memstats_str_precentage_box{" ", "▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"}; -static const std::array memstats_str_precentage_number{"0", "1", "2", "3", "4", "5", "6", "7", "8", - "9"}; +static const std::array memstats_str_precentage_number{ + "0", "1", "2", "3", "4", "5", "6", "7", "8", + "9" +}; std::pair memstats_str_hist_representation() { - #if MEMSTATS_USE_MEMORY_TRACER +#if MEMSTATS_USE_MEMORY_TRACER const MemoryTracerGuard guard; #endif @@ -267,15 +286,14 @@ std::pair memstats_str_hist_representation() { } unsigned short memstats_bins() { - #if MEMSTATS_USE_MEMORY_TRACER +#if MEMSTATS_USE_MEMORY_TRACER const MemoryTracerGuard guard; #endif if (const char *ptr = std::getenv("MEMSTATS_BINS")) { try { return std::stoi(ptr); - } - catch (...) { + } catch (...) { std::cerr << "Option 'MEMSTATS_BINS=" << ptr << "' not known. Fallback on default '15'\n"; } } @@ -283,7 +301,7 @@ unsigned short memstats_bins() { } void MemStatsInfo::record(void *ptr, std::size_t sz) { - #if MEMSTATS_USE_MEMORY_TRACER +#if MEMSTATS_USE_MEMORY_TRACER const MemoryTracerGuard guard; #endif @@ -303,12 +321,13 @@ void MemStatsInfo::record(void *ptr, std::size_t sz) { } template -using unordered_map = std::unordered_map, std::equal_to, MallocAllocator>>; -using string = std::basic_string, MallocAllocator>; -using stringstream = std::basic_stringstream, MallocAllocator>; +using unordered_map = std::unordered_map, std::equal_to, MallocAllocator > >; +using string = std::basic_string, MallocAllocator >; +using stringstream = std::basic_stringstream, MallocAllocator >; void print_legend() { - #if MEMSTATS_USE_MEMORY_TRACER +#if MEMSTATS_USE_MEMORY_TRACER const MemoryTracerGuard guard; #endif @@ -325,9 +344,9 @@ void print_legend() { double per_width = 100. / str_precentage.second; for (std::size_t i = 0; i != str_precentage.second; ++i) std::cout << "• \'" << str_precentage.first[i] << "\' -> [" << std::fixed - << std::setw(4) << std::setprecision(1) << i * per_width - << "%, " << std::setw(5) << (i + 1) * per_width << '%' - << (i + 1 == str_precentage.second ? ']' : ')') << std::endl; + << std::setw(4) << std::setprecision(1) << i * per_width + << "%, " << std::setw(5) << (i + 1) * per_width << '%' + << (i + 1 == str_precentage.second ? ']' : ')') << std::endl; } void report_memory_leaks() { @@ -347,7 +366,7 @@ void report_memory_leaks() { if (!freed) { std::cout << "Pointer " << memstats_events[i].ptr << " was never freed in Thread " - << memstats_events[i].thread << "." << std::endl; + << memstats_events[i].thread << "." << std::endl; #if MEMSTAT_HAVE_STACKTRACE std::cout << "Current stacktrace:\n" << memstats_events[i].stacktrace << std::endl; #endif @@ -356,7 +375,7 @@ void report_memory_leaks() { } void memstats_report(const char *report_name) { - #if MEMSTATS_USE_MEMORY_TRACER +#if MEMSTATS_USE_MEMORY_TRACER const MemoryTracerGuard guard; #endif @@ -371,7 +390,7 @@ void memstats_report(const char *report_name) { }; Stats global_stats; unordered_map thread_stats; - unordered_map>> ptr_collec; + unordered_map > > ptr_collec; struct PtrStats { int times_freed; const void *ptr; @@ -434,7 +453,7 @@ void memstats_report(const char *report_name) { const auto str_precentage = memstats_str_hist_representation(); const auto bins = memstats_bins(); auto format_histogram = [&](const Stats &stats) { - std::vector> hist(bins, 0); + std::vector > hist(bins, 0); std::size_t max_size = 0; for (const auto &frec: stats.size_freq) { std::size_t size = frec.first, count = frec.second; @@ -455,16 +474,16 @@ void memstats_report(const char *report_name) { }; std::cout << format_histogram(global_stats) << " | " << std::right - << std::setw(6) << bytes_to_string(global_stats.size) << '(' - << std::left << std::setw(5) << int_to_string(global_stats.count) - << ") | Total\n"; + << std::setw(6) << bytes_to_string(global_stats.size) << '(' + << std::left << std::setw(5) << int_to_string(global_stats.count) + << ") | Total\n"; for (const auto &pair: thread_stats) if (pair.second.size) { std::cout << format_histogram(pair.second) << " | " << std::right - << std::setw(6) << bytes_to_string(pair.second.size) << '(' - << std::left << std::setw(5) << int_to_string(pair.second.count) - << ") | Thread " << pair.first << std::endl; + << std::setw(6) << bytes_to_string(pair.second.size) << '(' + << std::left << std::setw(5) << int_to_string(pair.second.count) + << ") | Thread " << pair.first << std::endl; } #if MEMSTAT_HAVE_STACKTRACE @@ -500,7 +519,7 @@ void memstats_report(const char *report_name) { template T exchange(T &obj, U &&new_value) { - #if MEMSTATS_USE_MEMORY_TRACER +#if MEMSTATS_USE_MEMORY_TRACER const MemoryTracerGuard guard; #endif @@ -510,7 +529,7 @@ T exchange(T &obj, U &&new_value) { } bool memstats_enable_thread_instrumentation() { - #if MEMSTATS_USE_MEMORY_TRACER +#if MEMSTATS_USE_MEMORY_TRACER const MemoryTracerGuard guard; #endif @@ -518,7 +537,7 @@ bool memstats_enable_thread_instrumentation() { } bool memstats_disable_thread_instrumentation() { - #if MEMSTATS_USE_MEMORY_TRACER +#if MEMSTATS_USE_MEMORY_TRACER const MemoryTracerGuard guard; #endif @@ -526,7 +545,7 @@ bool memstats_disable_thread_instrumentation() { } bool memstats_do_instrument() { - #if MEMSTATS_USE_MEMORY_TRACER +#if MEMSTATS_USE_MEMORY_TRACER const MemoryTracerGuard guard; #endif @@ -535,10 +554,10 @@ bool memstats_do_instrument() { // instrumentation of new void *operator new(std::size_t sz) { - #if MEMSTATS_USE_MEMORY_TRACER +#if MEMSTATS_USE_MEMORY_TRACER const MemoryTracerGuard guard; #endif - + // prevent multiple thread from allocating memory at the same time s.t. events are in order //std::lock_guard lock(memstats_events_mutex); @@ -554,30 +573,29 @@ void *operator new(std::size_t sz) { } if (memstats_do_instrument()) MemStatsInfo::record(ptr, sz); - + return ptr; } // instrumentation of new void *operator new(std::size_t sz, std::nothrow_t) noexcept { - #if MEMSTATS_USE_MEMORY_TRACER +#if MEMSTATS_USE_MEMORY_TRACER const MemoryTracerGuard guard; #endif try { return ::operator new(sz); - } - catch (...) { + } catch (...) { } return nullptr; } // instrumentation of delete void operator delete(void *ptr) noexcept { - #if MEMSTATS_USE_MEMORY_TRACER +#if MEMSTATS_USE_MEMORY_TRACER const MemoryTracerGuard guard; #endif - + // prevent multiple thread from deallocating memory at the same time s.t. events are in order //std::lock_guard lock(memstats_events_mutex); @@ -588,13 +606,12 @@ void operator delete(void *ptr) noexcept { // instrumentation of delete void operator delete(void *ptr, std::nothrow_t) noexcept { - #if MEMSTATS_USE_MEMORY_TRACER +#if MEMSTATS_USE_MEMORY_TRACER const MemoryTracerGuard guard; #endif try { return ::operator delete(ptr); - } - catch (...) { + } catch (...) { } } From 0fbf2f1cb64bb058114e2b9aa684450fb39d2ed4 Mon Sep 17 00:00:00 2001 From: niels-beier Date: Tue, 11 Feb 2025 09:33:13 +0100 Subject: [PATCH 19/26] Add .log file and .so file exclusion produced by pintool --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 5bf9658..5f7502e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ /build* /.vscode/ /.idea/ +*.log +*.so From 7395dd33523e290b8bfe893cf958554372cea87d Mon Sep 17 00:00:00 2001 From: niels-beier Date: Tue, 11 Feb 2025 11:00:29 +0100 Subject: [PATCH 20/26] Execute memory_tracer/compile.sh whenever CMake variable USE_MEMORY_TRACER is turned on to compile memory tracer --- CMakeLists.txt | 7 +++++++ memory_tracer/compile.sh | 6 +++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a0d3cf8..9403b9e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,6 +12,13 @@ option(USE_MEMORY_TRACER "Enable memory tracing" OFF) if(USE_MEMORY_TRACER) target_compile_definitions(memstats PRIVATE MEMSTATS_USE_MEMORY_TRACER) + + set(PINTOOL_PATH "" CACHE PATH "") + + if (NOT PINTOOL_PATH) + message(FATAL_ERROR "For using memstats with memory tracing, the path to the directory of pintool is required.") + endif() + execute_process(COMMAND bash ${CMAKE_CURRENT_SOURCE_DIR}/memory_tracer/compile.sh ${PINTOOL_PATH}) endif() include(GNUInstallDirs) diff --git a/memory_tracer/compile.sh b/memory_tracer/compile.sh index 83a500b..a3d0ed4 100755 --- a/memory_tracer/compile.sh +++ b/memory_tracer/compile.sh @@ -1,15 +1,15 @@ #!/bin/bash # Current directory -DIR=$PWD +DIR=$(dirname "$(realpath $0)") PINTOOL_PATH=$1 # Remove memorytracer.so if it exists rm -f $DIR/memorytracer.so # Copy files to the pintool directory -cp -f $PWD/memorytracer.cc $PINTOOL_PATH/source/tools/ManualExamples/memorytracer.cpp -cp -f $PWD/memorytracer.hh $PINTOOL_PATH/source/tools/ManualExamples/memorytracer.hh +cp -f $DIR/memorytracer.cc $PINTOOL_PATH/source/tools/ManualExamples/memorytracer.cpp +cp -f $DIR/memorytracer.hh $PINTOOL_PATH/source/tools/ManualExamples/memorytracer.hh # Compile the memory tracer cd $PINTOOL_PATH/source/tools/ManualExamples || exit From 4c9d7618f03785cbe59c4edffc4f2259e97370a2 Mon Sep 17 00:00:00 2001 From: niels-beier Date: Fri, 7 Mar 2025 18:16:48 +0100 Subject: [PATCH 21/26] Introduce array of array detection by instrumenting malloc calls --- memory_tracer/memorytracer.cc | 99 ++++++++++++++++++++++++++++------- memory_tracer/memorytracer.hh | 8 +++ 2 files changed, 88 insertions(+), 19 deletions(-) diff --git a/memory_tracer/memorytracer.cc b/memory_tracer/memorytracer.cc index ef844e7..f4c52a2 100644 --- a/memory_tracer/memorytracer.cc +++ b/memory_tracer/memorytracer.cc @@ -2,6 +2,7 @@ #include "pin.H" #include #include +#include #include #include @@ -27,6 +28,23 @@ bool memstats_disable_memory_tracer() { } std::vector MemoryTracer::operations; +std::vector MemoryTracer::mallocOperations; + +VOID malloc_before(ADDRINT size) { + if (!memstats_do_memory_tracing()) + return; + + // Record malloc operation with nullptr as address since the adress is not known at this point + MemoryTracer::mallocOperations.push_back({nullptr, size}); +} + +VOID malloc_after(ADDRINT ret) { + if (!memstats_do_memory_tracing()) + return; + + // Update the address of the last malloc operation + MemoryTracer::mallocOperations.back().address = reinterpret_cast(ret); +} VOID Image(IMG img, VOID *v) { RTN disableRtn = RTN_FindByName(img, "disable_memory_tracer"); @@ -42,11 +60,19 @@ VOID Image(IMG img, VOID *v) { RTN_InsertCall(enableRtn, IPOINT_BEFORE, AFUNPTR(memstats_enable_memory_tracer), IARG_END); RTN_Close(enableRtn); } + + RTN mallocRtn = RTN_FindByName(img, "malloc"); + if (RTN_Valid(mallocRtn)) { + RTN_Open(mallocRtn); + RTN_InsertCall(mallocRtn, IPOINT_BEFORE, AFUNPTR(malloc_before), IARG_FUNCARG_ENTRYPOINT_VALUE, 0, IARG_END); + RTN_InsertCall(mallocRtn, IPOINT_AFTER, AFUNPTR(malloc_after), IARG_FUNCRET_EXITPOINT_VALUE, IARG_END); + RTN_Close(mallocRtn); + } } int MemoryTracer::Init(int argc, char *argv[]) { - if( PIN_Init(argc,argv) ) + if(PIN_Init(argc,argv)) { std::cout << "Error PIN_Init" << std::endl; return 1; @@ -70,27 +96,62 @@ int MemoryTracer::Init(int argc, char *argv[]) { return 0; } -void MemoryTracer::Finalize(INT32 code, VOID* v) { - // print number of read/write operations and their total size per address - std::unordered_map> stats; - std::unordered_map> stats_size; - for (const auto& op : operations) { - stats[op.address][op.isWrite]++; - stats_size[op.address][op.isWrite] += op.size; - } - // sort stats and stats_size by address - std::vector addresses; - for (const auto& [address, _] : stats) { - addresses.push_back(address); +void detect_arrays_of_arrays(const std::vector& calls) { + for (size_t i = 0; i < calls.size(); ++i) { + const auto& base_call = calls[i]; + + // Check if it is a potential pointer array allocation (small size, multiple of 8) + if (base_call.size >= 8 && base_call.size % sizeof(void*) == 0) { + size_t n = base_call.size / sizeof(void*); + + std::vector candidates; + size_t consistent_size = 0; + + // Search for n subsequent allocations with similar size + for (size_t j = i + 1; j < calls.size(); ++j) { + const auto& sub_call = calls[j]; + + if (candidates.empty()) { + consistent_size = sub_call.size; // expected size + } + + if (sub_call.size == consistent_size) { + candidates.push_back(sub_call.address); + } else { + break; // if size is not consistent, stop searching + } + + if (candidates.size() >= n) { + break; // found enough candidates! + } + } + + if (candidates.size() >= n / 2) { // Toleranz für unvollständige Allokation + std::cout << "Possibly an array of arrays!\n"; + std::cout << "Pointer-Array at: " << base_call.address << " (" << base_call.size << " Bytes)\n"; + std::cout << "Arrays found (" << candidates.size() << " candidates with size " << consistent_size << "):\n"; + for (auto addr : candidates) { + std::cout << " -> " << addr << "\n"; + } + std::cout << "--------------------------------------\n"; + } + } } - std::sort(addresses.begin(), addresses.end()); +} - for (const auto& address : addresses) { - const auto& counts = stats[address]; +void MemoryTracer::Finalize(INT32 code, VOID* v) { + detect_arrays_of_arrays(MemoryTracer::mallocOperations); - // print address, number of reads, number of writes, total size of reads, total size of writes - std::cout << std::hex << address << std::dec << ": " << counts[0] << " reads, " << counts[1] << " writes, " - << stats_size[address][0] << " bytes read, " << stats_size[address][1] << " bytes written" << std::endl; + // find allocations that were never used + std::map allocations; + for (const auto& op : MemoryTracer::mallocOperations) { + allocations[op.address] = op.size; + } + for (const auto& op : MemoryTracer::operations) { + allocations.erase(reinterpret_cast(op.address)); + } + for (const auto& [address, size] : allocations) { + std::cout << "Allocation at " << address << " (" << size << " bytes) was never used" << std::endl; } } diff --git a/memory_tracer/memorytracer.hh b/memory_tracer/memorytracer.hh index a85ab1d..dc46b10 100644 --- a/memory_tracer/memorytracer.hh +++ b/memory_tracer/memorytracer.hh @@ -11,11 +11,19 @@ struct MemoryOperation { uint32_t threadId; }; +struct MallocOperation { + void* address; + size_t size; +}; + class MemoryTracer { public: static int Init(int argc, char *argv[]); static void Finalize(INT32 code, VOID *v); static const std::vector& GetOperations(); + static const std::vector& GetMallocOperations(); + + static std::vector mallocOperations; private: static void RecordMemoryRead(void* ip, void* addr, uint32_t size, uint32_t tid); From cf4a3d0895460229aeb2ecbe746650856929aaf3 Mon Sep 17 00:00:00 2001 From: niels-beier Date: Fri, 7 Mar 2025 18:17:18 +0100 Subject: [PATCH 22/26] Add example_06.cc as an example for an array of array detection --- CMakeLists.txt | 3 +++ example_06.cc | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 example_06.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index 9403b9e..5e9a230 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -119,4 +119,7 @@ if(memstats_IS_TOP_LEVEL) add_executable(example_05 example_05.cc) target_link_libraries(example_05 PUBLIC MemStats::MemStats) + + add_executable(example_06 example_06.cc) + target_link_libraries(example_05 PUBLIC MemStats::MemStats) endif() \ No newline at end of file diff --git a/example_06.cc b/example_06.cc new file mode 100644 index 0000000..9663504 --- /dev/null +++ b/example_06.cc @@ -0,0 +1,18 @@ +volatile void * do_not_optimize; +volatile int do_not_optimize2; + +int main() { + // Allocate array of arrays + int** ptr = new int*[5]; + do_not_optimize = ptr; + for (int i = 0; i < 5; i++) { + ptr[i] = new int[20]; + do_not_optimize = ptr[i]; + } + + ptr[1][1] = 42; + + do_not_optimize2 = ptr[1][1]; + + return 0; +} \ No newline at end of file From 2a115f3d361cedcd2c44cefd99541e564f815a7c Mon Sep 17 00:00:00 2001 From: niels-beier Date: Mon, 17 Mar 2025 17:16:21 +0100 Subject: [PATCH 23/26] Fix memory leak of example_06.cc --- example_06.cc | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/example_06.cc b/example_06.cc index 9663504..b221280 100644 --- a/example_06.cc +++ b/example_06.cc @@ -1,5 +1,4 @@ volatile void * do_not_optimize; -volatile int do_not_optimize2; int main() { // Allocate array of arrays @@ -10,9 +9,12 @@ int main() { do_not_optimize = ptr[i]; } - ptr[1][1] = 42; + ptr[1][0] = 42; - do_not_optimize2 = ptr[1][1]; + for (int i = 0; i < 5; i++) { + delete[] ptr[i]; + } + delete[] ptr; return 0; } \ No newline at end of file From e79a2873fe58d58a670eba974f249d94076d8c7d Mon Sep 17 00:00:00 2001 From: niels-beier Date: Mon, 17 Mar 2025 17:26:17 +0100 Subject: [PATCH 24/26] Erase all elements that are part of an array region when erasing all used allocations --- memory_tracer/memorytracer.cc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/memory_tracer/memorytracer.cc b/memory_tracer/memorytracer.cc index f4c52a2..8fd4242 100644 --- a/memory_tracer/memorytracer.cc +++ b/memory_tracer/memorytracer.cc @@ -149,6 +149,9 @@ void MemoryTracer::Finalize(INT32 code, VOID* v) { } for (const auto& op : MemoryTracer::operations) { allocations.erase(reinterpret_cast(op.address)); + for (auto it = op.address; it < op.address + op.size; it += 8) { + allocations.erase(reinterpret_cast(it)); + } } for (const auto& [address, size] : allocations) { std::cout << "Allocation at " << address << " (" << size << " bytes) was never used" << std::endl; From 3626993d035c74df0c3473f93a33e7528707eed5 Mon Sep 17 00:00:00 2001 From: niels-beier Date: Mon, 17 Mar 2025 17:37:45 +0100 Subject: [PATCH 25/26] Remove double erase of array beginning --- memory_tracer/memorytracer.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/memory_tracer/memorytracer.cc b/memory_tracer/memorytracer.cc index 8fd4242..733fd51 100644 --- a/memory_tracer/memorytracer.cc +++ b/memory_tracer/memorytracer.cc @@ -148,7 +148,6 @@ void MemoryTracer::Finalize(INT32 code, VOID* v) { allocations[op.address] = op.size; } for (const auto& op : MemoryTracer::operations) { - allocations.erase(reinterpret_cast(op.address)); for (auto it = op.address; it < op.address + op.size; it += 8) { allocations.erase(reinterpret_cast(it)); } From 69144ec9baf7612566554d23936ea8243c17c09e Mon Sep 17 00:00:00 2001 From: niels-beier Date: Mon, 17 Mar 2025 17:38:10 +0100 Subject: [PATCH 26/26] Add details about Intel PIN --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index a6fb804..f4adf06 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ _**Note**: This library only instruments the C++ operators `new` and `delete`, m * Thread Safe * Low overhead when disabled * Portable: Compatible with GCC, Clang, and MVSC with C++11 support +* Memory tracer using Intel PIN for x86 architectures to detect dynamic allocation of arrays containing arrays and memory that was allocated but never used ## Environmental options @@ -27,6 +28,13 @@ _**Note**: This library only instruments the C++ operators `new` and `delete`, m | `memstats_report(name)` | Reports statistics on `new` calls since last report. Not thread-safe. | | `memstats_[enable\|disable]_thread_instrumentation()` | Enables/disables instrumentation on the calling thread. Thread-safe. | +To enable or disable the memory tracer when using it, one just needs to define the following dummy funcions and call them to enable/disable: + +```c++ +void __attribute__((optimize("O0"))) disable_memory_tracer(void) {} + +void __attribute__((optimize("O0"))) enable_memory_tracer(void) {} +``` ## CMake @@ -49,6 +57,12 @@ add_executable(example_01 example_01.cc) target_link_libraries(example_01 PUBLIC MemStats::MemStats) ``` +When calling `cmake ...` using the options `-DUSE_MEMORY_TRACER=ON -DPINTOOL_PATH=/path/to/intelpin`, the memory tracer will be built. + +To execute a program with the memory tracer, one can use the following command: + +/path/to/intelpin/pin -t /path/to/memorytracer/memorytracer.so -- /path/to/program + ## Motivation In a world of increasing abstractions, it's increasibly hard to reason about what calls of our program may have expensive logic. One of such expensive calls are memory allocations because they may end up on system calls or have internal syncronization mechanisims.