Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
f8d5b8e
Add Never freed pointer and double freed pointer detection and report
0xshuzo Nov 5, 2024
2b1f354
Ignore .idea folder
0xshuzo Nov 5, 2024
15c9621
Add acquire/release semantic for memstats_events such that order of e…
0xshuzo Dec 2, 2024
4942c11
Rename never frees to memory leaks and check if threads are the same
0xshuzo Dec 3, 2024
ecfcdf1
Remove acquire and release semantics and introduce lock for new and d…
0xshuzo Dec 7, 2024
8aa2d8b
Remove the lock for testing
0xshuzo Dec 17, 2024
3604514
Add variable to set pintool path
0xshuzo Jan 24, 2025
823c086
Implement custom memory tracer for reads/writes in a program
0xshuzo Jan 24, 2025
41bb764
Introduce thread local variables to dynamically turn on/off the memor…
0xshuzo Jan 24, 2025
728d200
Exclude all memstats functionality from being traces by the memory tr…
0xshuzo Jan 24, 2025
c3f5d13
Implement final report of memory tracer
0xshuzo Jan 26, 2025
596f75a
Remove unused excludedRanges from memory tracer and move main functio…
0xshuzo Feb 4, 2025
ec965f8
Introduce memory tracer and memory tracer guard
0xshuzo Feb 4, 2025
c11d3a2
Add option to enable memory tracer
0xshuzo Feb 4, 2025
96bf995
Start to implement final output of memory tracer
0xshuzo Feb 10, 2025
b25a5e8
Remove compilation of memory tracer from cmake and use compile script…
0xshuzo Feb 11, 2025
bb3c372
Rewrite final output of memory tracer and add capture for function na…
0xshuzo Feb 11, 2025
3423490
Move memory tracer guard to memstats to enable/disable memory tracer …
0xshuzo Feb 11, 2025
0fbf2f1
Add .log file and .so file exclusion produced by pintool
0xshuzo Feb 11, 2025
7395dd3
Execute memory_tracer/compile.sh whenever CMake variable USE_MEMORY_T…
0xshuzo Feb 11, 2025
4c9d761
Introduce array of array detection by instrumenting malloc calls
0xshuzo Mar 7, 2025
cf4a3d0
Add example_06.cc as an example for an array of array detection
0xshuzo Mar 7, 2025
2a115f3
Fix memory leak of example_06.cc
0xshuzo Mar 17, 2025
e79a287
Erase all elements that are part of an array region when erasing all …
0xshuzo Mar 17, 2025
3626993
Remove double erase of array beginning
0xshuzo Mar 17, 2025
69144ec
Add details about Intel PIN
0xshuzo Mar 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
/build*
/.vscode/
/.idea/
*.log
*.so
25 changes: 23 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,19 @@ add_library(memstats)
target_sources(memstats PRIVATE memstats.cc)
target_link_libraries(memstats PRIVATE $<TARGET_NAME_IF_EXISTS:TBB::tbb> $<TARGET_NAME_IF_EXISTS:Threads::Threads>)

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)
install(FILES memstats.hh
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}")
Expand Down Expand Up @@ -88,7 +101,6 @@ export(EXPORT memstats-targets
NAMESPACE MemStats::
)


if(memstats_IS_TOP_LEVEL)
add_executable(example_01 example_01.cc)
target_link_libraries(example_01 PUBLIC MemStats::MemStats)
Expand All @@ -101,4 +113,13 @@ if(memstats_IS_TOP_LEVEL)
target_link_libraries(example_03 PUBLIC MemStats::MemStats)
target_compile_features(example_03 PUBLIC cxx_std_11)
endif()
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)

add_executable(example_06 example_06.cc)
target_link_libraries(example_05 PUBLIC MemStats::MemStats)
endif()
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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

Expand All @@ -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.
Expand Down
9 changes: 9 additions & 0 deletions example_04.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
volatile void * do_not_optimize;

int main()
{
auto val = new int;
do_not_optimize = val;

return 0;
}
10 changes: 10 additions & 0 deletions example_05.cc
Original file line number Diff line number Diff line change
@@ -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;
}
20 changes: 20 additions & 0 deletions example_06.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
volatile void * do_not_optimize;

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][0] = 42;

for (int i = 0; i < 5; i++) {
delete[] ptr[i];
}
delete[] ptr;

return 0;
}
23 changes: 23 additions & 0 deletions memory_tracer/compile.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#!/bin/bash

# Current directory
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 $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
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
183 changes: 183 additions & 0 deletions memory_tracer/memorytracer.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
#include "memorytracer.hh"
#include "pin.H"
#include <algorithm>
#include <array>
#include <map>
#include <iostream>
#include <unordered_map>

static bool memstats_memory_tracing = true;

bool memstats_do_memory_tracing() {
return memstats_memory_tracing;
}

template<class T, class U = T>
T exchange(T &obj, U &&new_value) {
T old_value = std::move(obj);
obj = std::forward<U>(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<MemoryOperation> MemoryTracer::operations;
std::vector<MallocOperation> 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<void*>(ret);
}

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);
}

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))
{
std::cout << "Error PIN_Init" << std::endl;
return 1;
}

PIN_InitSymbols();
IMG_AddInstrumentFunction(Image, 0);
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_AddFiniFunction(Finalize, 0);
PIN_StartProgram();

return 0;
}

void detect_arrays_of_arrays(const std::vector<MallocOperation>& 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<void*> 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";
}
}
}
}

void MemoryTracer::Finalize(INT32 code, VOID* v) {
detect_arrays_of_arrays(MemoryTracer::mallocOperations);

// find allocations that were never used
std::map<void*, size_t> allocations;
for (const auto& op : MemoryTracer::mallocOperations) {
allocations[op.address] = op.size;
}
for (const auto& op : MemoryTracer::operations) {
for (auto it = op.address; it < op.address + op.size; it += 8) {
allocations.erase(reinterpret_cast<void*>(it));
}
}
for (const auto& [address, size] : allocations) {
std::cout << "Allocation at " << address << " (" << size << " bytes) was never used" << std::endl;
}
}

const std::vector<MemoryOperation>& MemoryTracer::GetOperations() {
return operations;
}

void MemoryTracer::RecordMemoryRead(void* ip, void* addr, uint32_t size, uint32_t tid) {
auto address = reinterpret_cast<uintptr_t>(addr);
if (!memstats_do_memory_tracing())
return;

operations.push_back({address, size, false, tid});
}

void MemoryTracer::RecordMemoryWrite(void* ip, void* addr, uint32_t size, uint32_t tid) {
auto address = reinterpret_cast<uintptr_t>(addr);
if (!memstats_do_memory_tracing())
return;

operations.push_back({address, size, true, tid});
}

int main(int argc, char *argv[]) {
MemoryTracer::Init(argc, argv);
return 0;
}
49 changes: 49 additions & 0 deletions memory_tracer/memorytracer.hh
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#ifndef MEMSTATS_MEMORYTRACER_HH
#define MEMSTATS_MEMORYTRACER_HH

#include <vector>
#include <mutex>

struct MemoryOperation {
uintptr_t address;
size_t size;
bool isWrite;
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<MemoryOperation>& GetOperations();
static const std::vector<MallocOperation>& GetMallocOperations();

static std::vector<MallocOperation> mallocOperations;

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<MemoryOperation> operations;
};

/** @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();

#endif //MEMSTATS_MEMORYTRACER_HH
Loading