Skip to content

Commit

Permalink
sys: add malloc_monitor, deprecate malloc_tracing
Browse files Browse the repository at this point in the history
  • Loading branch information
mguetschow committed Mar 11, 2024
1 parent 4d2b2f7 commit 916a4fa
Show file tree
Hide file tree
Showing 16 changed files with 619 additions and 2 deletions.
4 changes: 4 additions & 0 deletions makefiles/pseudomodules.inc.mk
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,11 @@ PSEUDOMODULES += libc_gettimeofday
## @defgroup pseudomodule_malloc_tracing malloc_tracing
## @brief Debug dynamic memory management by hooking in a print into each call
## of malloc(), calloc(), realloc() and free
## @{
## @deprecated Use module `malloc_monitor` with verbous configuration instead;
## will be removed after 2024.07 release.
PSEUDOMODULES += malloc_tracing
## @}

## @defgroup pseudomodule_mpu_stack_guard mpu_stack_guard
## @brief MPU based stack guard
Expand Down
1 change: 1 addition & 0 deletions sys/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ rsource "evtimer/Kconfig"
rsource "log_color/Kconfig"
rsource "log_printfnoformat/Kconfig"
rsource "luid/Kconfig"
rsource "malloc_monitor/Kconfig"
rsource "malloc_thread_safe/Kconfig"
rsource "matstat/Kconfig"
rsource "memarray/Kconfig"
Expand Down
64 changes: 64 additions & 0 deletions sys/include/malloc_monitor.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Copyright (C) 2024 TU Dresden
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @defgroup sys_malloc_monitor Heap Memory Usage Monitor
* @ingroup sys_memory_management
*

Check failure on line 11 in sys/include/malloc_monitor.h

View workflow job for this annotation

GitHub Actions / static-tests

trailing whitespace.

Check warning on line 11 in sys/include/malloc_monitor.h

View workflow job for this annotation

GitHub Actions / static-tests

trailing whitespace
* @{
*
* @brief monitor heap memory usage (calls to malloc/calloc/realloc/free)
* @author Mikolai Gütschow <mikolai.guetschow@tu-dresden.de>
*/

#ifndef MALLOC_MONITOR_H
#define MALLOC_MONITOR_H

#include <assert.h>
#include <stdint.h>
#include <stddef.h>
#include <string.h>

#include "architecture.h"

#ifdef __cplusplus
extern "C" {
#endif

/**
* @brief Obtain current heap memory usage.
*
* @return current heap memory usage in bytes
*/
size_t malloc_monitor_get_usage_current(void);

/**
* @brief Obtain maximum heap memory usage since last call to
* @ref malloc_monitor_reset_high_watermark().
*
* @return maximum heap memory usage in bytes
*/
size_t malloc_monitor_get_usage_high_watermark(void);

/**
* @brief Reset maximum heap memory usage.
*

Check failure on line 49 in sys/include/malloc_monitor.h

View workflow job for this annotation

GitHub Actions / static-tests

trailing whitespace.

Check warning on line 49 in sys/include/malloc_monitor.h

View workflow job for this annotation

GitHub Actions / static-tests

trailing whitespace
* After calling this function, @ref malloc_monitor_get_usage_high_watermark()
* will return @ref malloc_monitor_get_usage_current() until further changes
* to heap memory usage.
*/
void malloc_monitor_reset_high_watermark(void);

#ifdef __cplusplus
}
#endif

#endif /* MALLOC_MONITOR_H */

/**
* @}
*/
73 changes: 73 additions & 0 deletions sys/include/malloc_monitor_internal.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* Copyright (C) 2024 TU Dresden
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @defgroup sys_malloc_monitor_internals Heap Memory Usage Monitor internals
* @ingroup sys_malloc_monitor
* @{
*
* @brief internals for monitoring heap memory usage (calls to malloc/calloc/realloc/free)
* @author Mikolai Gütschow <mikolai.guetschow@tu-dresden.de>
*/

#ifndef MALLOC_MONITOR_H
#define MALLOC_MONITOR_H

#include <assert.h>
#include <stdint.h>

Check failure on line 21 in sys/include/malloc_monitor_internal.h

View workflow job for this annotation

GitHub Actions / static-tests

Wrong header guard format: --- sys/include/malloc_monitor_internal.h +++ sys/include/malloc_monitor_internal.h @@ -14,8 +14,8 @@ * @author Mikolai Gütschow <mikolai.guetschow@tu-dresden.de> */ -#ifndef MALLOC_MONITOR_H -#define MALLOC_MONITOR_H +#ifndef MALLOC_MONITOR_INTERNAL_H +#define MALLOC_MONITOR_INTERNAL_H #include <assert.h> #include <stdint.h>
#include <stddef.h>
#include <string.h>

#include "architecture.h"

#ifdef __cplusplus
extern "C" {
#endif

/**
* @brief Record malloc/calloc/realloc call increasing heap usage.
*
* @param[in] ptr pointer to newly allocated memory
* @param[in] size size of newly allocated memory
* @param[in] pc PC of calling function
* @param[in] func_prefix prefix identifying memory function, one of "m","c","re"
*
* @internal
*/
void malloc_monitor_add(void *ptr, size_t size, uinttxtptr_t pc, char *func_prefix);

/**
* @brief Record free/realloc call decreasing heap usage.
*
* @param[in] ptr pointer to memory that is being freed
* @param[in] pc PC of calling function
*
* @internal
*/
void malloc_monitor_rm(void *ptr, uinttxtptr_t pc);

/**
* @brief Record realloc call either increasing or decreasing heap usage.
*
* @param[in] ptr_old pointer to previously allocated memory
* @param[in] ptr_new pointer to newly allocated memory
* @param[in] size_new size of newly allocated memory
* @param[in] pc PC of calling function
*
* @internal
*/
void malloc_monitor_mv(void *ptr_old, void *ptr_new, size_t size_new, uinttxtptr_t pc);

#ifdef __cplusplus
}
#endif

#endif /* MALLOC_MONITOR_H */

/**
* @}

Check failure on line 72 in sys/include/malloc_monitor_internal.h

View workflow job for this annotation

GitHub Actions / static-tests

Wrong header guard format: --- sys/include/malloc_monitor_internal.h +++ sys/include/malloc_monitor_internal.h @@ -66,7 +66,7 @@ } #endif -#endif /* MALLOC_MONITOR_H */ +#endif /* MALLOC_MONITOR_INTERNAL_H */ /** * @}
*/
23 changes: 23 additions & 0 deletions sys/malloc_monitor/Kconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Copyright (C) 2024 TU Dresden
#
# This file is subject to the terms and conditions of the GNU Lesser
# General Public License v2.1. See the file LICENSE in the top level
# directory for more details.
#

menuconfig MODULE_SYS_MALLOC_MONITOR
bool "Heap Memory Usage Monitor"

config MODULE_SYS_MALLOC_MONITOR_SIZE
int "Monitor Size"
default 100
depends on MODULE_SYS_MALLOC_MONITOR
help
Specifies maximum number of pointers that can be monitored at once.

config MODULE_SYS_MALLOC_MONITOR_VERBOSE
bool "Verbose"
default false
depends on MODULE_SYS_MALLOC_MONITOR
help
Print detailed log of calls to malloc/realloc/free
1 change: 1 addition & 0 deletions sys/malloc_monitor/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include $(RIOTBASE)/Makefile.base
1 change: 1 addition & 0 deletions sys/malloc_monitor/Makefile.dep
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
USEMODULE += malloc_thread_safe
71 changes: 71 additions & 0 deletions sys/malloc_monitor/doc.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/**
@defgroup sys_malloc_monitor Heap Memory Usage Monitor
@ingroup sys_memory_management
@brief This module allows to monitor the dynamic memory usage of a certain piece of code.
@warning This module automatically selects @ref sys_malloc_ts and naturally
incurs a certain runtime overhead. It is not meant for production usage.

# Description

This module allows to monitor the dynamic memory usage of a certain piece of code.
It works by hooking into (wrappers to) @ref malloc(), @ref calloc(), @ref realloc(),
and @ref free() calls to internally record the current and all-time maximum heap memory usage.

Note that in general dynamic memory management is a bad idea on the constrained devices RIOT
is targeting. So maybe it is better to just adapt your code to use static memory management instead.

# Usage

Enable the module with `USEMODULE += malloc_monitor`.

Add `#include "malloc_monitor.h"` to the file in which you want to monitor dynamic memory usage.
Use @ref malloc_monitor_get_usage_current() to retrieve the size of the currently allocated
heap memory in bytes. @ref malloc_monitor_get_usage_high_watermark() returns the all-time maximum
since startup or the last call to @ref malloc_monitor_reset_high_watermark().

Note that `malloc_monitor` currently has no notion of threads and will at any point in time report
the global dynamic memory usage, not the one used by the currently running thread.
Thread-safety is achieved through usage of @ref sys_malloc_ts, though.

## Example

Imagine you want to investigate the dynamic memory consumption of a certain function `func()`.
The following snippet could get you started:

```c
#include <stddef.h>
#include <stdio.h>

#include "malloc_monitor.h"

int main(void)
{
size_t before = malloc_monitor_get_usage_current();
size_t before_max = malloc_monitor_get_usage_high_watermark();
func();
size_t after = malloc_monitor_get_usage_current();
size_t after_max = malloc_monitor_get_usage_high_watermark();

if (after != before) {
puts("func() " (after < before ? "decreased" : "increased") " global dynamic memory usage.");
}
printf("The maximal dynamic memory usage of func() was %d bytes.", after_max - before_max);
}
```

For further usage examples, refer to the corresponding tests in `tests/sys/malloc_monitor`.

# Configuration

The maximum number of pointers that can be monitored at once can be set with Kconfig
in System > Heap Memory Usage Monitor > Monitor Size or by setting the corresponding
CFlag in your application's Makefile as `CFLAGS += CONFIG_MODULE_SYS_MALLOC_MONITOR_SIZE=42`.
It defaults to 100.

For more fine-grained debugging of invalid calls to @ref free(), duplicated calls to @ref free(),
or memory leaks, the module can be configured to print information on every call to @ref malloc(),
@ref calloc(), @ref realloc(), or @ref free() by setting System > Heap Memory Usage Monitor > Verbose
or adding `CFLAGS += CONFIG_MODULE_SYS_MALLOC_MONITOR_VERBOSE=1` to your Makefile.
`malloc_monitor` defaults to be non-verbose.

*/
127 changes: 127 additions & 0 deletions sys/malloc_monitor/malloc_monitor.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/*
* Copyright (C) 2024 TU Dresden
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/

/**
* @{
*
* @file
* @brief monitor heap memory usage (calls to malloc/realloc/free)
* @author Mikolai Gütschow <mikolai.guetschow@tu-dresden.de>
*/

#include <stdio.h>
#include <string.h>

#include "architecture.h"
#include "cpu.h"
#include "malloc_monitor.h"
#include "malloc_monitor_internal.h"

#ifndef CONFIG_MODULE_SYS_MALLOC_MONITOR_SIZE
#define CONFIG_MODULE_SYS_MALLOC_MONITOR_SIZE 100
#endif

#ifndef CONFIG_MODULE_SYS_MALLOC_MONITOR_VERBOSE
#define CONFIG_MODULE_SYS_MALLOC_MONITOR_VERBOSE 0
#endif

static struct {
void *addr[CONFIG_MODULE_SYS_MALLOC_MONITOR_SIZE];
size_t size[CONFIG_MODULE_SYS_MALLOC_MONITOR_SIZE];
size_t current;
size_t high_watermark;
} malloc_monitor = {
.addr = {NULL},
.current = 0,
.high_watermark = 0,
};

void malloc_monitor_add(void *ptr, size_t size, uinttxtptr_t pc, char *func_prefix)
{
if (ptr == NULL) return;

Check warning on line 46 in sys/malloc_monitor/malloc_monitor.c

View workflow job for this annotation

GitHub Actions / static-tests

full block {} expected in the control structure
for (uint8_t i=0; i<CONFIG_MODULE_SYS_MALLOC_MONITOR_SIZE; i++) {
if (malloc_monitor.addr[i] == NULL) {
malloc_monitor.addr[i] = ptr;
malloc_monitor.size[i] = size;
malloc_monitor.current += size;
if (malloc_monitor.current > malloc_monitor.high_watermark)
malloc_monitor.high_watermark = malloc_monitor.current;

Check warning on line 53 in sys/malloc_monitor/malloc_monitor.c

View workflow job for this annotation

GitHub Actions / static-tests

full block {} expected in the control structure
#if CONFIG_MODULE_SYS_MALLOC_MONITOR_VERBOSE
printf("%salloc(%" PRIuSIZE ") @ 0x%" PRIxTXTPTR " returned %p\n",
func_prefix, size, pc, ptr);
#endif
return;
}
}
(void)func_prefix;
(void)pc;
printf("malloc_monitor: maximum number of pointers to be monitored "
"(as set by CONFIG_MODULE_SYS_MALLOC_MONITOR_SIZE) exceeded.\n");
}

void malloc_monitor_rm(void *ptr, uinttxtptr_t pc)
{
if (ptr == NULL) return;

Check warning on line 69 in sys/malloc_monitor/malloc_monitor.c

View workflow job for this annotation

GitHub Actions / static-tests

full block {} expected in the control structure
for (uint8_t i=0; i<CONFIG_MODULE_SYS_MALLOC_MONITOR_SIZE; i++) {
if (malloc_monitor.addr[i] == ptr) {
malloc_monitor.addr[i] = NULL;
malloc_monitor.current -= malloc_monitor.size[i];
#if CONFIG_MODULE_SYS_MALLOC_MONITOR_VERBOSE
printf("malloc_monitor: free(%p) @ 0x%" PRIxTXTPTR " \n", ptr, pc);
#endif
return;
}
}
printf("malloc_monitor: free(%p) @ 0x%" PRIxTXTPTR " invalid\n", ptr, pc);
}

void malloc_monitor_mv(void *ptr_old, void *ptr_new, size_t size_new, uinttxtptr_t pc)
{
if (ptr_old == NULL) {
malloc_monitor_add(ptr_new, size_new, pc, "re");
return;
}
if (size_new == 0) {
malloc_monitor_rm(ptr_old, pc);
return;
}
if (ptr_new == NULL) return;

Check warning on line 93 in sys/malloc_monitor/malloc_monitor.c

View workflow job for this annotation

GitHub Actions / static-tests

full block {} expected in the control structure
for (uint8_t i=0; i<CONFIG_MODULE_SYS_MALLOC_MONITOR_SIZE; i++) {
if (malloc_monitor.addr[i] == ptr_old) {
malloc_monitor.addr[i] = ptr_new;
size_t size_old = malloc_monitor.size[i];
malloc_monitor.size[i] = size_new;
if (size_new > size_old) {
malloc_monitor.current += size_new - size_old;
if (malloc_monitor.current > malloc_monitor.high_watermark)
malloc_monitor.high_watermark = malloc_monitor.current;

Check warning on line 102 in sys/malloc_monitor/malloc_monitor.c

View workflow job for this annotation

GitHub Actions / static-tests

full block {} expected in the control structure
}
else {
malloc_monitor.current -= size_old - size_new;
}
#if CONFIG_MODULE_SYS_MALLOC_MONITOR_VERBOSE
printf("malloc_monitor: realloc(%p, %" PRIuSIZE ") @0x%" PRIxTXTPTR " returned %p\n",
ptr_old, size_new, pc, ptr_new);
#endif
return;
}
}
printf("malloc_monitor: realloc(%p) @ 0x%" PRIxTXTPTR " invalid\n", ptr_old, pc);
}

size_t malloc_monitor_get_usage_current(void) {
return malloc_monitor.current;
}
size_t malloc_monitor_get_usage_high_watermark(void) {
return malloc_monitor.high_watermark;
}
void malloc_monitor_reset_high_watermark(void) {
malloc_monitor.high_watermark = malloc_monitor.current;
}

/** @} */
2 changes: 1 addition & 1 deletion sys/malloc_thread_safe/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,4 @@ config MODULE_MALLOC_TRACING

Note that generally dynamic memory management is a bad idea on the
constrained devices RIOT is targeting. So maybe it is better to just
adapt your code to use static memory management instead.
adapt your code to use static memory management instead.
Loading

0 comments on commit 916a4fa

Please sign in to comment.