Find Bugs Faster: Using Tracealyzer, We Found Memory Leaks in Hours Rather Than Days

Mar 12, 2020 |

VNT electronicsJan Ropek of VNT electronics, a Czech maker of electronic dog training equipment, tells his story of how Tracealyzer was used to find elusive memory leaks. It could take days to locate and fix a single leak, but when they deployed Tracealyzer the same bug hunt was completed in a matter of hours.


In our device we use FreeRTOS (originally version 9, now upgraded to 10.0.1) running on a STM32L162VCT. We have long been looking for a way to exchange data between different tasks in the application. Of course, if we only need to send a few bytes, we can use a FreeRTOS queue by copying the necessary information directly to the queue structure.

Tracealyzer's Trace view

In Tracealyzer’s Trace view, we can see when new data was received in Task RF and then displayed by TaskDisplay.

The data structure of our queue in this case would like so:

typedef struct {
  uint32_t Address;
  uint32_t Data1;
  uint32_t Data2;
} tQueueTasks;

When we wanted to show battery voltage on the LCD screen, we could just do something like this:

tQueueTasks myQueue;
myQueue.Address = LCD_SHOW_BATT_VOLTAGE;
myQueue.Data1 = ADC_GetValue (BatteryPin);
xQueueSend (QueueDisplayHandle, &myQueue, portMAX_DELAY);

The situation got more complicated when we wanted to send larger amounts of data (tens of bytes). How should we deal with that? Have statically allocated memory in the queue structure, like so?

typedef struct {
  uint32_t Address;
  uint32_t Data1;
  uint32_t Data2;
  uint32_t Buffer[100];
} tQueueTasks;

Then we could fill the Buffer[100] with whatever data we wanted to send. But we would probably not utilize all 100 bytes most of the time, and sometimes we would need even more. This is why we came up with a dynamic memory allocation solution, where we every time allocate just the amount of memory we need to send.

The steps needed to send data from Task1 to Task2 then becomes:

  • Task1: allocate the memory
  • Task1: send pointer to this allocated area to Task2, through the queue
  • Task2: receive pointer and process data

For dynamic packets, the queue data structure was updated to look like this:

typedef struct {
  uint32_t Address;
  uint32_t Data1;
  void *pointer;
} tQueueTasks;

The dynamic allocation method can be applied to arbitrarily large data packets. Usually we create fixed buffers for specific cases such as sending data to the LCD, sending data over WiFi, et cetera. However, we know that the heap is of limited size, so with this style of sending data is necessary to watch allocation and freeing of memory. Otherwise, we will run out of heap space – and many times, we did forget to release memory somewhere in the code and the whole system collapsed.

Memory Heap Utilization

The Memory Heap Utilization view helped the author of today’s blog post discover several memory leaks.

Reading the code didn’t help

The more code you have to look through, the harder it will be to find the error. When we didn’t know where to look we could spend days searching, until we tried Percepio Tracealyzer. In particular, we used streaming mode in combination with user events, and watched heap allocation and deallocation in a live update view. Thanks to the heap utilization feature (above), we found several mistakes in our code, usually within a few hours, where we forgot to deallocate memory. For us, this is one of the most important functions in Tracealyzer.

We know that the newest version of FreeRTOS has something called Streaming buffer that fulfills a similar purpose of sending data beetween tasks. However, we originally came up with our solution for FreeRTOS version 9 and we like it so we keep using it in version 10.


This user story was submitted as a response to our Everybody Has A Story challenge in December. Every published story is awarded with a free 1-year license for Tracealyzer. You are welcome to submit your own story; please apply to support@percepio.com no later than 31st March to qualify.