Skip to content

Commit

Permalink
feat: Get a trace of all of the allocations, using a doubly linked li…
Browse files Browse the repository at this point in the history
…st and wrappers for malloc/calloc and free. (TheAlgorithms#782)

* Add malloc, calloc and free wrappers to trace all of the allocations

* Improve memory leak reporting when multiple allocations occurs in the same file at the same line.

* Change directory name from 'debugging' to 'developer_tools' and added CMakeLists.txt, also change an include name to match a file name

* Edit root CMakeLists.Txt

* Update developer_tools/malloc_dbg.h

Co-authored-by: David Leal <[email protected]>

* Edit CMakeLists.txt to create a static library from malloc_dbg.c

* Add comments for the includes

* Change test.c name to test_malloc_dbg.c, also edit CMakeLists.txt to link malloc_dbg.a only to test_malloc_dbg.

* Change comment style and change EXIT_SUCCESS to 0

* Fix typo in doxygen comments

* Enhance comments

* Apply suggestions from code review

Co-authored-by: David Leal <[email protected]>
  • Loading branch information
tinouduart33 and Panquesito7 authored Feb 17, 2021
1 parent e81bc16 commit 3682694
Show file tree
Hide file tree
Showing 5 changed files with 392 additions and 0 deletions.
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ endif()

## Add subdirectories containing CMakeLists.txt
# to configure and compile files in the respective folders
add_subdirectory(developer_tools)
add_subdirectory(hash)
add_subdirectory(misc)
add_subdirectory(games)
Expand Down
35 changes: 35 additions & 0 deletions developer_tools/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# If necessary, use the RELATIVE flag, otherwise each source file may be listed
# with full pathname. RELATIVE may makes it easier to extract an executable name
# automatically.
file( GLOB APP_SOURCES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.c )
# file( GLOB APP_SOURCES ${CMAKE_SOURCE_DIR}/*.c )
# AUX_SOURCE_DIRECTORY(${CMAKE_CURRENT_SOURCE_DIR} APP_SOURCES)

add_library(malloc_dbg malloc_dbg.c) # Create a static library from malloc_dbg.c file

foreach( testsourcefile ${APP_SOURCES} )
string( REPLACE ".c" "" testname ${testsourcefile} )
string( REPLACE ".C" "" testname ${testname} )
string( REPLACE " " "_" testname ${testname} )

if ("${testsourcefile}" STREQUAL "malloc_dbg.c")
continue()
endif()

add_executable( ${testname} ${testsourcefile} )

if ("${testname}" STREQUAL "test_malloc_dbg")
target_link_libraries(${testname} malloc_dbg)
endif()

if(OpenMP_C_FOUND)
target_link_libraries(${testname} OpenMP::OpenMP_C)
endif()

if(MATH_LIBRARY)
target_link_libraries(${testname} ${MATH_LIBRARY})
endif()
# target_link_libraries(${testname} malloc_dbg) # Link the malloc_dbg library
install(TARGETS ${testname} DESTINATION "bin/developer_tools")

endforeach( testsourcefile ${APP_SOURCES} )
289 changes: 289 additions & 0 deletions developer_tools/malloc_dbg.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,289 @@
/**
* @file
* @brief This file contains malloc_dbg, calloc_dbg, free_dbg and printLeaks implementations.
* @author [tinouduart33](https://github.com/tinouduart33)
*/

#include <stdlib.h> /// For the malloc, calloc and free functions.
#include <stdio.h> /// For IO operations (printf).
#include <string.h> /// For the memcmp function.
#include "malloc_dbg.h" /// Header file which contains the prototypes of malloc_dbg, calloc_dbg and fre_dbg.

/* We must undef these macros in order to use the real malloc / calloc and free functions */
#undef malloc
#undef calloc
#undef free

/**
* @brief Structure used to save an allocated pointer
*/
typedef struct MEMORY_INFORMATION
{
void* ptr; ///< Pointer returned by malloc / calloc
const char* fileName; ///< File in which malloc or calloc has been called
const char* functionName; ///< Function in which malloc or calloc has been called
size_t bytes; ///< Number of bytes allocated
int line; ///< Line number (in file) corresponding to the malloc / calloc call
struct MEMORY_INFORMATION* next; ///< Next element in the list
struct MEMORY_INFORMATION* previous; ///< Previous element in the list
} mem_info;

/** We use a global variable for the list so we can use it at any time.
* */
mem_info* memoryInformation = NULL;

/** Another global variable. This one is used to know if we already call the atexit function.
* */
int atexitCalled = 0;


/**
* @brief addMemInfo function add a memory allocation in the memoryInfo list.
* @details This function creates a new element and add it on top of the list
* @param memoryInfo Pointer to the doubly linked list used to store all of the allocations
* @param ptrToreturn Pointer returned by malloc or calloc
* @param bytes Size in bytes of the allocation
* @param line Line where the allocation has been called
* @param filename File where the allocation has been called
* @param functionName Name of the function where the allocation has been called
* @returns mem_info pointer if it succeeds, NULL otherwise
*/
mem_info* addMemInfo(mem_info* memoryInfo, void* ptrToReturn, size_t bytes, int line, const char* filename, const char* functionName)
{
mem_info* newMemInfo = (mem_info*)malloc(sizeof(mem_info));
if (!newMemInfo)
{
return NULL;
}

newMemInfo->ptr = ptrToReturn;
newMemInfo->bytes = bytes;
newMemInfo->line = line;
newMemInfo->fileName = filename;
newMemInfo->functionName = functionName;
newMemInfo->next = memoryInfo;
newMemInfo->previous = NULL;
if (memoryInformation)
memoryInformation->previous = newMemInfo;

return newMemInfo;
}

/**
* @brief inList function is used to know if an element is already in the memoryInfo list.
* @details This function is used to know if an allocation in a specific file at a specific line already exists in the list.
* @param filename File in which malloc or calloc has been called
* @param line Line number in the file in which malloc or calloc has been called
* @returns Position of the element in the list if the element is found, -1 otherwise.
*/
int inList(const char* filename, int line)
{
mem_info* tmp = memoryInformation;
int counter = 0;
int len = strlen(filename);

while (tmp)
{
if (len == strlen(tmp->fileName))
{
if (!memcmp(filename, tmp->fileName, len) && tmp->line == line)
{
return counter;
}
}
tmp = tmp->next;
counter++;
}
return -1;
}

/**
* @brief editInfo function is used to edit an element in the memoryInfo list.
* @details This function is used to edit the number of bytes allocated at a specific line.
* @param elemPos Position of an element in the doubly linked list memoryInfo
* @param bytes Size of the allocation in bytes
* @returns Nothing.
*/
void editInfo(int elemPos, size_t bytes)
{
int counter = 0;
mem_info* tmp = memoryInformation;

while (counter != elemPos)
{
tmp = tmp->next;
counter++;
}
tmp->bytes += bytes;
}

/**
* @brief malloc_dbg function is a wrapper around the malloc function.
* @details This function calls malloc and allocates the number of bytes passed in the parameters.
* If the allocation succeeds then it add the pointer returned by malloc in the mem_info list.
* @param bytes Size of the allocation in bytes
* @param filename Caller file
* @param functionName Caller function
* @returns Pointer returned by malloc if it's valid, NULL otherwhise.
*/
void* malloc_dbg(size_t bytes, int line, const char* filename, const char* functionName)
{
void* ptrToReturn = malloc(bytes);
int pos = 0;
if (!ptrToReturn)
{
return NULL;
}

// We must check atexitCalled value to know if we already called the function
if (!atexitCalled)
{
atexit(printLeaks); // Used to call printLeaks when the program exit
atexitCalled = 1;
}

pos = inList(filename, line);
if (pos == -1)
{
// Add a new element in the mem_info list
memoryInformation = addMemInfo(memoryInformation, ptrToReturn, bytes, line, filename, functionName);
if (!memoryInformation)
{
free(ptrToReturn);
return NULL;
}
}
else
{
editInfo(pos, bytes);
}
return ptrToReturn;
}

/**
* @brief calloc_dbg function is a wrapper around the calloc function.
* @details This function calls calloc and allocates the number of bytes passed in the parameters.
* If the allocation succeeds then it add the pointer returned by malloc in the mem_info list.
* @param elementCount number of element to allocate
* @param elementSize Size of each element
* @param line Line number in the caller file
* @param filename Caller file
* @param functionName Caller function
* @returns Pointer returned by calloc if it's valid, NULL otherwhise.
*/
void* calloc_dbg(size_t elementCount, size_t elementSize, int line, const char* filename, const char* functionName)
{
void* ptrToReturn = calloc(elementCount, elementSize);
if (!ptrToReturn)
{
return NULL;
}

// We must check atexitCalled value to know if we already called the function
if (!atexitCalled)
{
atexit(printLeaks); // Used to call printLeaks when the program exit
atexitCalled = 1;
}

// Add a new element in the mem_info list
memoryInformation = addMemInfo(memoryInformation, ptrToReturn, elementCount * elementSize, line, filename, functionName);
if (!memoryInformation)
{
free(ptrToReturn);
return NULL;
}

return ptrToReturn;
}

/**
* @brief free_dbg function is used to free the memory allocated to a pointer.
* @details This function free the memory pointed by the pointer passed in parameter.
* To free this pointer, we loop through the mem_info list and check if we find the pointer.
* Once it's found, the pointer is freed and the element is deleted from the list.
* @param ptrToFree Pointer that must be freed
* @returns Nothing.
*/
void free_dbg(void* ptrToFree)
{
mem_info* tmp = memoryInformation;
mem_info* toFree = NULL;
mem_info* previous = NULL;

// Check if the head contains the pointer to free
if (tmp->ptr == ptrToFree)
{
toFree = tmp;
memoryInformation = tmp->next;
free(toFree->ptr);
free(toFree);
if (memoryInformation)
{
memoryInformation->previous = NULL;
}
return;
}

// We can loop through the list without any problems, the head is not the pointer
while (tmp)
{
if (tmp->ptr == ptrToFree) // If we found the pointer that must be freed
{
toFree = tmp;
tmp = tmp->next;
previous = toFree->previous;

if (previous)
{
previous->next = tmp;
}
if (tmp)
{
tmp->previous = previous;
}

free(toFree->ptr);
if (toFree == memoryInformation)
{
memoryInformation = NULL;
}
free(toFree);
return;
}
tmp = tmp->next;
}
}

/**
* @brief printLeaks function is used to print all the memory leaks.
* @details This function is called when the program exits. It loop through the mem_info list and if it's not empty,
* it prints the memory leaks.
* @returns Nothing.
*/
void printLeaks()
{
mem_info* tmp = memoryInformation;
mem_info* previous = NULL;
size_t sum = 0;
int nbBlocks = 0;

if (tmp)
{
printf("Memory Leaks detected.\n");
}

while (tmp)
{
previous = tmp;
printf("\n%ld bytes lost\n", tmp->bytes);
printf("address : 0x%p in %s\t%s:%d\n", tmp->ptr, tmp->functionName, tmp->fileName, tmp->line);
printf("\n====================================\n");
sum += tmp->bytes;
tmp = tmp->next;
free(previous);
nbBlocks++;
}

printf("SUMMARY :\n%ld bytes lost in %d blocks\n", sum, nbBlocks);
}
36 changes: 36 additions & 0 deletions developer_tools/malloc_dbg.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/**
* @file
* @brief Header file that contains macros used to replace malloc/calloc and free.
* @details
* Macros malloc, calloc and free respectively calls malloc_dbg, calloc_dbg and free_dbg.
* malloc_dbg and calloc_dbg allocates memory using the "real" malloc and calloc and store
* the pointer returned (with additional informations) in a linked list.
* Thanks to this linked list, it is possible to check memory leaks.
* @author [tinouduart33](https://github.com/tinouduart33)
* @see malloc_dbg.c
*/

#ifndef MALLOC_DBG_H
#define MALLOC_DBG_H

/** This macro replace the standard malloc function with malloc_dbg.
* */
#define malloc(bytes) malloc_dbg(bytes, __LINE__, __FILE__, __FUNCTION__)

/** This macro replace the standard calloc function with calloc_dbg.
* */
#define calloc(elemCount, elemSize) calloc_dbg(elemCount, elemSize, __LINE__, __FILE__, __FUNCTION__)

/** This macro replace the standard free function with free_dbg.
* */
#define free(ptr) free_dbg(ptr)

void* malloc_dbg(size_t bytes, int line, const char* filename, const char* functionName);

void* calloc_dbg(size_t elementCount, size_t elementSize, int line, const char* filename, const char* functionName);

void free_dbg(void* ptrToFree);

void printLeaks(void);

#endif /* MALLOC_DBG_H */
Loading

0 comments on commit 3682694

Please sign in to comment.