feat: Get a trace of all of the allocations, using a doubly linked list and wrappers for malloc/calloc and free. (#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 <halfpacho@gmail.com>

* 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 <halfpacho@gmail.com>
This commit is contained in:
tinouduart33 2021-02-17 23:38:41 +01:00 committed by GitHub
parent e81bc16832
commit 3682694f76
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 392 additions and 0 deletions

View File

@ -48,6 +48,7 @@ endif()
## Add subdirectories containing CMakeLists.txt ## Add subdirectories containing CMakeLists.txt
# to configure and compile files in the respective folders # to configure and compile files in the respective folders
add_subdirectory(developer_tools)
add_subdirectory(hash) add_subdirectory(hash)
add_subdirectory(misc) add_subdirectory(misc)
add_subdirectory(games) add_subdirectory(games)

View File

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

View File

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

View File

@ -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 */

View File

@ -0,0 +1,31 @@
/**
* @file
* @brief File used to test the malloc_dbg, calloc_dbg and free_dbg functions.
* @details
* This file only have a main function that calls malloc, calloc and free.
* When the program exits, memory leaks must be printed.
* @author [tinouduart33](https://github.com/tinouduart33)
* @see malloc_dbg.c, malloc_dbg.h
*/
#include <stdio.h> /// For IO operations if needed.
#include <stdlib.h> /// For the EXIT_SUCCESS macro and the "real" malloc, calloc and free functions.
#include "malloc_dbg.h" /// For the macros malloc, calloc and free and the malloc_dbg, calloc_dbg and free_dbg functions.
/**
* @brief Main function
* @param argc number of arguments (not used)
* @param argv list of arguments (not used)
* @returns 0 on exit
*/
int main(int argc, char* argv[])
{
int* iptr = malloc(10 * sizeof(int));
char* cptr = calloc(256, sizeof(char));
free(iptr);
// free(cptr);
return 0;
}